Archive for the 'git' Category

Useful Git Commands: a Shortcut for GitHub Pull Requests

3 Nov 2015

Normally, when someone asks me to merge something in git I need to add his or her repository using git remote add, fetch the branch I need, and then merge it. When someone submits a pull request to a project hosted on GitHub, however, GitHub additionally publishes it as something I can fetch from my own repository:

% git ls-remote origin | grep pull/6
03d7fb7af91a74bb7658a0742fa68bfeb5d50a3f	refs/pull/6/head
f8ebbc62555143019947f1255b064cda38fd239f	refs/pull/6/merge
8a42021dbabcfc222b4d47c5ada47e344154d934	refs/pull/60/head
f4d8052000601e59e4e7d4dec4aa4094df4e39a0	refs/pull/61/head
8a519986f5b59721692ec75608edf0f404f88e87	refs/pull/62/head
9c03e723e8b50ca56a1257659d686a68a69e6e40	refs/pull/62/merge
f3a983c6fc3ff236c2bc678cfec3885da609f79a	refs/pull/63/head
e7953c21a77fb37fb7158dd87fc0c156dd8f97ae	refs/pull/63/merge

With this I can use one line of configuration to create a convenient shortcut that lets me immediately check out any pull request:

% git config --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/origin/pull/*"
% git checkout pull/63
Branch pull/63 set up to track remote branch pull/63 from upstream.
Switched to a new branch 'pull/63'

Useful Git Commands: URL Rewriting

29 Aug 2014

People have to use SSH or HTTPS to push to GitHub, but when fetching one can use git’s own network protocol because it is generally faster. You can make a specific repository on your machine use SSH only for pushing by cloning it with the faster git:// URL and running something like this:

% git config remote.upstream.pushurl git@github.com:gholms/boto

That works nicely, but you have to do it once for every single repository you want to interact with. That quickly becomes annoying. Thankfully, you can leverage git’s URL-rewriting mechanism to make this easier:

% git config --global url.git://github.com/.insteadOf github:
% git config --global url.git@github.com:.pushInsteadOf github:

This adds two new rules to your git configuration:

  1. If a URL starts with github: then replace that with git://github.com/.
  2. If a URL starts with github: and you are pushing then replace that with git@github.com:.

After you do that you can simply use URLs like github:gholms/boto when cloning. They will get rewritten to git@github.com:gholms/boto when pushing, and git://github.com/gholms/boto the rest of the time, speeding things up without creating additional work in the future.

This should work if you prefer HTTPS for pushing to GitHub, or if you use other servers, too. Just tweak the commands.

Useful Git Commands: Summarizing Lots of Log Entries

2 Apr 2014

When looking for a summary of a git repository’s history, the output of git log isn’t always as informative as one might like. It displays every commit in chronological order, which effectively hides the changes that merges bring in. It is also quite verbose, showing complete log messages, author info, commit hashes, and so on, drowning us with so much info that only a few commits will fit on the screen at once. After supplying the command with the right cocktail of options, though, its output becomes a significantly better summary:

Output of ``git graph''

The output above came from a command that is long enough that I made an alias, for it, git graph, in my ~/.gitconfig file:

[alias]
    graph = log --graph --abbrev-commit --date=relative --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(blue)<%an>%Creset'

Don’t forget that git log accepts a list of things to show logs for as well, so if you want to look at the logs for branch-1 and branch-2 you can simply use git graph branch-1 branch-2 to make them both show up in the graph.

Reverting a Range of Git Commits Separately

3 Jul 2012

All kinds of articles on the Internet tell you how to revert a range of git commits in one massive, squashed-together revert commit. But to split them up into separate revert commits you have to pass a list of commits to git revert, and that list has to go in reverse order to avoid conflicts.

git rev-list --reverse ${last_good_commit}.. | xargs git revert

Speed Up Merges with Git’s Index

14 Jun 2012

We’ve all been there, running git merge only to have it spew CONFLICT all over the place and quit partway through. But if we already know that every time there is a conflict then all of “our” code that we are merging changes into should take precedence over “their” changes that we are pulling in, git 1.7.3 provides a handy shortcut for that:

% git merge -X ours theirbranch

If “their” changes that we are pulling in should always take precedence instead then things are equally easy:

% git merge -X theirs theirbranch

If “ours” or “theirs” style merges will work, but only for certain files, things get a bit more complicated. But with a bit of background knowledge of how git’s index works we can save ourselves a bunch of work.

The index stores what stage a file is in. Normally, a file’s stage is 0.

% git ls-files -s myfile
100644 4b48deed3a433909bfd6b6ab3d4b91348b6af464 0       myfile

A file with a merge conflict is different because the index actually has three different versions of it: the version that the two branches we are merging most recently had in common, the version on “our” side of the merge, and the version on “their” side of the merge. In git these correspond to stages 1, 2, and 3. This is one reason one has to run git add on every conflicting file before completing a merge.

% git ls-files -s myfile
100644 4b48deed3a433909bfd6b6ab3d4b91348b6af464 1       myfile
100644 5be4a414b32cf4204f889469942986d3d783da84 2       myfile
100644 39c5733494077ae8bc45c45c15a708ffe9871966 3       myfile

Rather than opening up the conflicting file and clearing out the parts that clash by hand, we can instead simply copy the version we want from the index into our working tree:

% git checkout-index -f --stage 3 myfile
% git add myfile
% git commit

Git Pull via Sneakernet

8 Jun 2012

Today I found myself needing to move some commits between two repositories. In general the best way to do this is by pulling changes from one into the other, but in this case the repositories did not have direct access to each other. Rather than copying an entire repository from one machine to another or mucking about with a pile of patches, we can save time by performing the sending and receiving sides of the network-enabled git fetch command by hand.

In the source repository, add the changes we want to move to a bundle that we can copy to a USB stick:

% git bundle create changes.bundle master..mybranch
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 313 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
% cp changes.bundle /media/usbstick

In the destination repository, ensure that we have the commits necessary to use the bundle and then tell git fetch to grab the changes from it:

% git bundle verify /media/usbstick/changes.bundle
The bundle contains 1 ref
7a1d2087f10e6db33e6b4a28e2c427b65238a62c refs/heads/mybranch
The bundle requires these 1 ref
6f5fced94ef76f1b46e259db72ad6fc39c49ba72 
/media/usbstick/changes.bundle is okay
% git fetch /media/usbstick/changes.bundle mybranch
Receiving objects: 100% (3/3), done.
Resolving deltas: 100% (2/2), completed with 2 local objects.
From /media/usbstick/changes.bundle
 * branch            mybranch   -> FETCH_HEAD
% git merge FETCH_HEAD
Updating 6f5fced..7a1d208
Fast-forward
 README |    2 ++
 1 file changed, 2 insertions(+)

Fixing a Git Branch that Started in the Wrong Place

21 May 2012

Today I did some work on a branch in git only to discover that I based it on some unstable code rather than the stable code that I usually want to use as a baseline.

You can fix this by using a bit of git’s rebase magic:

git rebase --onto master testing

This moves the entire current branch onto another.