Git is complicated. It’s an extremely powerful tool, but it’s also easy to get burned — I’ve received the dreaded
detached HEAD warning on multiple occasions and been at a complete loss as to why that had happened. However, as with any tool, I’ve continued to learn to use it, and now I’m fairly comfortable performing tasks that a year ago would have stumped me.
Git is a wonderful tool, but it becomes much easier when you have other tools layered on top of it.
I spend nearly my entire day in Atom, so when they released GitHub for Atom, I was thrilled. Now, I can stage and unstage files as well as individual lines, right from the editor. It’s majorly boosted my productivity.
This is an Atom package that enables a wide array of Git functionality. While GitHub for Atom mostly “lives” in the right-hand dock,
git-plus adds context menu options throughout the UI, as well as commands accessible from the palette. They each have their strengths, and I’m always glad I have both installed.
Sourcetree is an amazing app that I’ve been using for years. I almost never use it for common tasks such as committing, merging, and pushing. Instead, what I find useful is that it gives a clear, configurable visual display of the commit graph, and I can right-click on any commit to get its hash. Generally I’ll use these hashes for more advanced operations, such as starting an interactive rebase from a specific commit.
You can also cherry-pick or revert any single commit directly from the UI.
To install on Mac with Homebrew Cask:
brew cask install sourcetree
bitbucket-git-helpers is a handy set of aliases useful for quickly opening your browser to Bitbucket pages related to the repo (pull requests, create pull request).
Add these under the
[alias] section of your
~/.gitconfig to take advantage of them.
This one’s super handy: it prints the name of the current branch. Pretty simple, but it comes in handy when working with branches with long names, or chaining together commands.
this = rev-parse --abbrev-ref HEAD
Push the current branch to an upstream with the matching name. I usually use this for feature branches.
puot = !git push -u origin $(git this)
I can’t tell you how many times I’ve meant to create a feature branch from
develop, but instead accidentally created a feature branch off of whatever feature I was working on at the time. This command prevents me from doing that.
git cod feat/foobar creates a new
feat/foobar branch from upstream develop. It’s usually a good idea to run
git fetch before doing this as well, to make sure the new branch is up-to-date.
cod = checkout origin/develop -b
Here are a handful of common tasks.
If you’re like me, you may switch branches multiple times over the course of a day, but have trouble remembering what branch you were on. To list the 10 branches with most recent commits, run this command:
git branch --sort=-committerdate | head
If you’ve got aliases set up, you can write a slightly shorter version:
git recent | head
This process has immeasurably improved my ability to write clean PRs. As I go, I try to make very small commits. Then, when I’m ready, I perform an interactive rebase, to merge together commits that are too tiny to stand on their own, reword some awkward commit messages, and remove commits that I only made as a quick test.
If I were preparing a PR for the
develop branch, I’d run:
git rebase -i --autosquash origin/develop
This would open Vim with a text file resembling the following:
pick 6841ac5 Do not check in this change! pick 9bd806c Implement feature X pick 102bda9 Implement feature Y pick 3011ca5 Fix bug in feature X pick 9002550 Write tests for feature X pick e781dae Write tests for faeture Y
As you can see, it’s a little rough, and I might not want to check it in this way. I might edit the file to look like this:
drop 6841ac5 Do not check in this change! pick 9bd806c Implement feature X fixup 3011ca5 Fix bug in feature X pick 9002550 Write tests for feature X pick 102bda9 Implement feature Y reword e781dae Write tests for faeture Y
drop command ignores a commit, the
fixup command squashes a commit with the previous one, the
reword command allows you to rewrite a commit message, and the
pick command leaves that commit alone. There are also several other things you can do here; full instructions are available when you run the command.
Save and quit, and Git will roll through the commit history, pausing if there is a merge conflict or to allow you to reword a commit message.
First, check out the branch that has the merge you wish to reverse:
git checkout develop
Next, get the ID of the merge commit. I like to do this from Sourcetree by right-clicking a commit and choosing “Copy SHA-1 to Clipboard”, but you can also do this with
Finally, run the following command, making sure to paste in the correct hash for
git revert -m 1 $HASH
I like to have my projects set up to run tests before I push. Sometimes, though, I have to push a branch in an incomplete state, or delete a remote branch while I have active changes. I used to remove the git pre-push hook, perform the push, then re-enable it. Turns out there’s a much easier way: the
--no-verify flag. For instance, if I want to push some incomplete work on the
git push --no-verify -u origin feat/foo-bar
Sometimes, you just need to do more than one thing at a time.
Generally I’ll either use
git stash or a
WIP (“work in progress”) commit to put things on hold while I work in another branch. However, there’s another command that’s useful in some instances where you really do want another copy of the project on your hard drive. The neat thing about this command is that you can work on another copy of a local branch, without having to first push it to your remote.
To create a new working copy of the
develop branch, run this command:
git worktree add ../new-working-copy develop
This will check out the
develop branch into the
../new-working-copy directory. You can change these arguments as you need to.
One caveat to watch out for: you can’t check out the same branch in both your original and new working copy at the same time. This is mildly annoying, but far better than getting your repo into a state where you can’t reason about what’s going on. If you get an
already checked out error after doing this, either change to a different branch in your other working copy, or else delete the working copy and run
git worktree prune if you no longer need it.
This one is useful when you’re making structural changes to a repo, and want a complete list of all the files that are tracked on a given branch or commit.
To display all files at the tip of the
master branch, run:
git ls-tree -r master --name-only
More generally, if you have the
git this alias enabled, you can run:
git ls-tree -r $(git this) --name-only
to see all files on the active branch.
Run your build — let’s say it builds into a folder called
/public/. Then, make sure this folder isn’t ignored via
git add and commit. Then, run the command:
git subtree push --prefix public origin gh-pages
If you’re running this in a CI environment, you can keep the built assets out of your working branch if you don’t push after committing the built assets. It’s a good idea to have the build step modify the
.gitignore as well, to continue to prevent people from accidentally committing their built assets.
You can see my