Recovering a commit from Github’s Reflog

This morning when I arrived to work I was presented with a very interesting Git/Github problem by a co-worker. He had pushed a commit from his home computer last night and this morning he had force pushed over the top of it without realizing it. Normally, this isn’t such a big problem since the commit would still exist in Git’s reflog and you could recover it from there. The problem was that he never did a fetch from Github before doing the force push. This effectively erased the commit and made it unrecoverable without his home computer. Or so we initially thought…

First, use Github’s Events API to retrieve the commit SHA.


$ curl https://api.github.com/repos/<user>/<repo>/events

This will return a JSON response which you can read through to find the commit you lost. Use the commit message and approximate times to narrow your search. Here is an example portion of the response (I’ve deleted some info, but you should get the point):


{
    "id": "1970551769",
    "type": "PushEvent",
    "actor": {
      "id": 563541,
      "login": "<user>",
      "gravatar_id": "<id>",
      "url": "https://api.github.com/users/<user>",
      "avatar_url": "<url>"
    },
    "repo": {
      "id": 9652839,
      "name": "<user>/<repo>",
      "url": "https://api.github.com/repos/<user>/<repo>"
    },
    "payload": {
      "push_id": 303837533,
      "size": 1,
      "distinct_size": 1,
      "ref": "refs/heads/<branch>",
      "head": "a973ddd28d599c9ba128de56182f8769d2b9843b",
      "before": "4ef3d74316c04c892d17250f0ba251b328274e5f",
      "commits": [
        {
          "sha": "384f275933d5b762cdb27175aeff1263a8a7b7f7",
          "author": {
            "email": "<email>",
            "name": "<author>"
          },
          "message": "<commit message>",
          "distinct": true,
          "url": "https://api.github.com/repos/<user>/<repo>/commits/384f275933d5b762cdb27175aeff1263a8a7b7f7"
        }
      ]
    },
    "public": false,
    "created_at": "2014-02-06T14:05:17Z"
}

Next, use Github’s Refs API to create a new branch pointing to that commit:


$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"ref":"refs/heads/D-commit", "sha":"384f275933d5b762cdb27175aeff1263a8a7b7f7"}' https://api.github.com/repos/<user>/<repo>/git/refs

Breaking down the JSON in this request we have this:


{
  "ref": "refs/heads/D-commit",
  "sha": "384f275933d5b762cdb27175aeff1263a8a7b7f7"
}

The “ref” in the JSON is Git ref we want to create, it MUST be of the form: refs/heads/<branch>, where <branch> is the name of the branch we want to create in Git.
The “sha” is the commit that we want this new branch to point to.

If successful Github will respond back with something like this:


HTTP/1.1 201 Created
Server: GitHub.com
Date: Thu, 06 Feb 2014 14:47:02 GMT
Content-Type: application/json; charset=utf-8
Status: 201 Created
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4991
X-RateLimit-Reset: 1391700034
Cache-Control: private, max-age=60, s-maxage=60
ETag: "43e54fee5cf29edd01fa8bfce094ed1b"
Location: https://api.github.com/repos/<user>/<repo>/git/refs/heads/D-commit
Vary: Accept, Authorization, Cookie, X-GitHub-OTP
X-GitHub-Media-Type: github.beta
X-Content-Type-Options: nosniff
Content-Length: 339
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin: *
X-GitHub-Request-Id: 4389B15A:3E60:1D2506F:52F3A066

{
  "ref": "refs/heads/D-commit",
  "url": "https://api.github.com/repos/<user>/<repo>/git/refs/heads/D-commit",
  "object": {
    "sha": "384f275933d5b762cdb27175aeff1263a8a7b7f7",
    "type": "commit",
    "url": "https://api.github.com/repos/<user>/<repo>/git/commits/384f275933d5b762cdb27175aeff1263a8a7b7f7"
  }
}

Now if you pull up your repository in Github, you will see the new branch and doing a ‘git fetch’ will retrieve that new branch to your local repository. From there you can cherry-pick or merge the commits back into your work.

One thought on “Recovering a commit from Github’s Reflog

  1. David Norton says:

    Excellent. You can almost hear the people saying “This is exactly why Git is so [great, terrible]”

  2. Dan Woods says:

    John, you’re a true pro. This will be a life-saver for me later on in life.

  3. WORMSS says:

    I dont know if its just a Windows thing or not, but I had to use double quotes around the outside and escape all nested quotes on the inside. -d “{“ref”:”refs/heads/D-commit”, “sha”:”384f275933d5b762cdb27175aeff1263a8a7b7f7″}”

  4. Jessie Younh says:

    THANK YOU! Just had the exact same thing happen (force pushed from my work comp after making progress on my home comp) and this fixed it for me. 🙂

  5. Vittorio says:

    This is truly a life saver! The new API version require an authorization token to create branches but it’s as simple as adding ‘-H “Authorization: token “‘ to the command line.

    Thanks again!

  6. Tom Klancer says:

    This is great! I just recovered a year-old lost commit.

  7. Hadas says:

    You just saved my butt! thanks.

    By the way, it took me a while to figure out that the events api results are paginated. the default result is just the first page, if you need something older you can check the response header and follow the links to the next page. or you can also set the “page” query parameter to the desired number of page.

  8. Chris says:

    I was accessing a private repo; to do that, I just had to add `-u ` to the commands you provided. Thanks!

  9. Sharang says:

    Thanks for this!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*