Feb 11, 2014

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/&lt;user&gt;/&lt;repo&gt;/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/&lt;user&gt;/&lt;repo&gt;/git/refs/heads/D-commit",
  "object": {
    "sha": "384f275933d5b762cdb27175aeff1263a8a7b7f7",
    "type": "commit",
    "url": "https://api.github.com/repos/&lt;user&gt;/&lt;repo&gt;/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.

About the Author

Object Partners profile.

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!

  10. cben says:

    This post has saved me & coworkers about 5 times already 🙂

    Instead of obtaining auth token for creating the branch via API, I was able to open in the browser https://api.github.com/repos///commits/384f275933d5b762cdb27175aeff1263a8a7b7f7 at the desired commit,
    click on the “Tree: 384f275933” dropdown, type the desired name and click “Create branch: “.

  11. xhuman says:

    If you only need to recover one or two files then there is no need to create extra branches and use curl: you can get the file contents directly in your web browser by opening the your JSON API endpoint, i.e. https://api.github.com/repos///events , locating and clicking the URL for the commit you need, and then in the next json that opens click the `raw_url` for your file(s).

Leave a Reply to WORMSS Cancel reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]