How do I get the git diff compared to main?

I want the same git diff blob I get from adding .diff to a PR in Github.
Tried this:

      - main

    runs-on: ubuntu-latest

      - uses: actions/checkout@v2

      - name: Debug github.sha
        run: |
          git diff --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }}

This has worked in a different repo but now I’m getting:

Run git diff --diff-filter=ACMRT 056dd907dec16280916ee9779a1416c8b618f53e 0605ea0f43db356598fbea3469b8ce381f084900
  git diff --diff-filter=ACMRT 056dd907dec16280916ee9779a1416c8b618f53e 0605ea0f43db356598fbea3469b8ce381f084900
  shell: /usr/bin/bash -e {0}
fatal: bad object 056dd907dec16280916ee9779a1416c8b618f53e
Error: Process completed with exit code 128.

I don’t think I understand how actions/checkout but I think 0605ea0f43db356598fbea3469b8ce381f084900 isn’t a commit I’d find anywhere else. It’s a merge commit made by actions/checkout or something. I honestly don’t understand that stuff but it seems to be what’s going on from skimming various issues and discourse and stackoverflow posts.

All I need is: what’s the diff?

For example, here’s a PR and here’s the diff which is simply add .diff to the URL. How do I get that into my bash inside the GitHub Action?

By default actions/checkout fetches only the commit github.ref points at. If you want to compare other points in history, you’ll need to retrieve more of the history, possibly all. See:

1 Like

What’s so confusing is that it’s not necessary to do that on this workflow:

That one uses just:

      - uses: actions/checkout@v2

And then further down, it does

git diff --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} > build/DIFF

and that works.

I see that workflow calls some actions and scripts that are specifically for dealing with diffs, as far as their names and comments in the workflow say. Maybe one of those fetches additional history?

1 Like

Yeah, that’s very likely the explanation. Under the hood it does a git pull or something that unbreaks things further down.

I think this trick might actually solve it. But I’m now not entirely certain what that means. Will it potentially be a different code that gets CI run? I.e. different from what would be merged.

That changes what gets checked out, not whether history is available. So comparing it with other commits will still fail.

Your code above looks like you want to compare two specific commits. That is possible regardless of what you check out, as long as you have the history available. To make the full history available with actions/checkout you need to use fetch-depth: 0.

Won’t fetch-depth include EVERYTHING? That could be huge. All I need is the commit from which the PR branch was started and nothing older than that.

Yes. The options actions/checkout offers for shallow clones are somewhat limited. For the comparison between two commits you naturally need both commits, and the full clone is the only reliable way to get both.

Alternatively you could look at rolling your own repository setup using Git commands, maybe using partial clone (which can get missing objects when needed). I can’t judge if it’d be worth the effort in your use case. If it sounds interesting, here’s an introduction:

1 Like

True that fetch-depth=0 will fetch everything, and it will therefore enable you to do any diff that you want. However if all you want is the diff between the Pull Request and the base from which it was originally cloned or forked, the you can fetch only those to items. The following worked for me, and this only works for Pull Requests, not push, nor merge, etc.

- name: Check out repository code
  uses: actions/checkout@v2
  # no need to specify `fetch-depth`, let it default to most recent commit only.
- name: Fetch and Diff PR with base from which it was cloned
  # git fetch will fail if ${{ github.event.pull_request.base.sha }} is not set.
  # ${{ github.event.pull_request.base.sha }} WILL be set for event `pull_request`
  if: ${{ github.event.pull_request.base.sha }}  
  run: |
    git fetch origin main ${{ github.event.pull_request.base.sha }}
    git diff ${{ github.event.pull_request.base.sha }} ${{ github.sha }}


  • ${{ github.event.pull_request.base.sha }} is the base commit from which the PR was originally cloned or forked.

  • ${{ github.event.pull_request.head.sha }} is the head (latest) commit of the PR.

  • ${{ github.sha }} is the latest commit in the “github actions workspace”; it is a merge commit of the PR HEAD, ${{ github.event.pull_request.head.sha }}, into commit from which it was originally cloned, ${{ github.event.pull_request.base.sha }}. For diff purposes, the following two diffs should be identical:

    git diff ${{ github.event.pull_request.base.sha }} ${{ github.sha }}
    git diff ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }}

    I don’t know when they would ever be different, but if you want to play it safe and do the second one (i.e. diff base with head) then you will have to also first

    git fetch ${{ github.event.pull_request.head.sha }}`

By the way, a very big thank you to @peterbe and to @airtower-luna for the above questions and comments. I would not have been able to figure this out if not for your conversation above. Based on your conversation I ran a bunch of experiments and was able to determine what I have posted here.

1 Like