Github Actions: Git diff master...HEAD fatal: no merge base
Asked Answered
S

3

17

Some context

Long story short: I am trying to run jest --changedSince=master when I open up a new Pull Request. The jest changedSince flag runs git diff master...HEAD in the background. This results in an error which I can't seem to get my head around.

Current situation

To debug this I have a Github Action which has a step which closely resembles the following:

runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - run: |
        git fetch --no-tags --depth=1 origin master
        git checkout -t origin/master
        git checkout pull/1/merge
        git diff master...HEAD

This results in the following error (Which is the same error returned by Jest):

fatal: refs/remotes/origin/master...HEAD: no merge base

What I have tried

The problem does not occur when I run this locally (imagine that). I do realise that the biggest issue here is most likely the fact that the actions/checkout@2 action does not fetch the entire repository. That's why I added the git fetch & git checkout -t origin/master and pull/1/merge command.

I did attempt to run git branch -a to debug if this worked as expected, these were the results:

* (HEAD detached at pull/1/merge)
  master
  remotes/origin/master
  remotes/pull/1/merge

All the refs which are required to run git diff master...HEAD seem to be available in the action.

I have also tried to check if the actual ref is returned correctly: I ran git show-ref master inside the action which returns:

<commit-sha> refs/heads/master
<commit-sha> refs/remotes/origin/master

I am out of ideas on what could cause this issue, any ideas are appreciated greatly!

Salenasalene answered 22/11, 2020 at 18:29 Comment(2)
You have a detached head. That's the reason. Just git checkout -b mytestbranch and proceed as before.Boxberry
Sounds like you are trying to compare two unrelated branches? As in, they have no common ancestor? Perhaps one was started as an orphan branch from the other? In that case git will have no problem comparing with .., but ... won't work because there is no common ancestor.Kellikellia
S
20

If you want to explore the history of master and HEAD (as in : find the merge base for these two commits), you can't limit your clone/fetch actions to a shallow clone -- not with depth=1 at least.

Try setting fetch-depth: 0 (or perhaps fetch-depth: 1000 # a high enough number) in the config of your action/checkout@v2 (as described in the project's Readme), and drop the --depth=1 (or set it to a higher value) when you run git fetch origin master.


Another way could be to get this kind of info through the GitHub API, instead of querying the local clone.

Spiffing answered 22/11, 2020 at 20:9 Comment(3)
I do admit that in the future I would like to find a way that does not require fetching every single ref in the repo. Your answer does work and does solve the problem for now. Thank youMulholland
This answer helped me solve a similar problem in gitlab. Thanks. I was getting error no merge base for git diff comamnd. There it is solved through the Git shallow clone setting in the CI/CD repository settings.Chelseachelsey
I ran into same problem, when using git merge-base. To me it was more confusing, because I was not getting any details of an error, just that command exit status was 1. Using fetch-depth: 0 solved it.Snowshed
L
1

I'm using the following in a Github action to use a single GH API call to determine the commits needed to have a merge base:

          comparison=$(gh api\
            repos/Howdju/howdju/compare/${{ github.event.pull_request.base.sha }}...${{github.event.pull_request.head.sha }})
          behind_by=$(echo -E $comparison | jq -r '.behind_by')
          ahead_by=$(echo -E $comparison | jq -r '.ahead_by')
          echo "ahead_by: $ahead_by; behind by: $behind_by"
          # +1 because fetch depth=1 is the commit itself.
          if [[ $behind_by -gt 0 ]]; then
            base_depth=$((behind_by+1))
            echo Fetching base to depth $base_depth
            git -c protocol.version=2 fetch --no-tags --no-recurse-submodules\
              --depth=$base_depth origin ${{github.event.pull_request.base.sha }}
          fi
          if [[ $ahead_by -gt 0 ]]; then
            head_depth=$((ahead_by+1))
            echo Fetching head to depth $head_depth
            git -c protocol.version=2 fetch --no-tags --no-recurse-submodules\
              --depth=$head_depth origin ${{github.event.pull_request.head.sha }}
          fi
Leland answered 30/12, 2022 at 1:27 Comment(0)
V
1

Expanding on LeGEC's answer, you can calculate the depth of the clone before cloning.

steps:
  - name: Get base depth
    id: base-depth
    run: echo "base-depth=$(expr ${{ github.event.pull_request.commits }} + 1)" >> $GITHUB_OUTPUT

  - name: Checkout repository
    uses: actions/checkout@v4
    with:
      ref: ${{ github.event.pull_request.head.sha }}
      fetch-depth: ${{ steps.base-depth.outputs.base-depth }}

This ensures the clone includes all commits from the branch, as well as the merge base commit.

Vosges answered 15/7 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.