Rationale

As I’ve previously noted, I have taught quite a few1 people Git from scratch, and as their first foray into distributed2 source code management.3 Some immediately graduate to using graphical Git, because they have bigger fish to fry, while others are curious how I can manage without it. This post is for the latter. If you’re part of the unfortunate camp that has to learn Git from me, well you’re in luck! That post is not this one

Exhibit 1: how to "git gud"


GUI

Sure they’re pretty but that’s all they are. I feel some GUI’s abstract away way too much detail and set some bad defaults.

Examples:

  1. GitHub Desktop’s concept of “sync”. If git pull wrapping git fetch and git merge wasn’t enough, now we have a git sync button that wraps git pull and git push
  2. It’s not obvious from IntelliJ IDEA’s SCM4 integration that you can edit commit messages.
  3. I’m fairly certain no GUI exposes the all-powerful `git reflog
  4. SourceTree clones appear to not prepopulate common directories like .git/hooks/ and .git/info/ if nothing exists

SourceTree does have a very nice “stage hunk” and “discard hunk” feature, that’s pretty much the only feature I’ve found valuable.


CLI

.gitconfig

Having used Git as my primary VCS since learning5 it at my first co-op placement (thx pymetrics!) I really enjoyed just needing to run type git in any terminal located anywhere to assess my current capabilities. Not being tied to an IDE’s implementation, and also not having to install any other tooling, helps me immediately hit the ground running wherever & whenever.

If the word .gitconfig doesn’t ring a bell I would suggest you try running git config -l --edit. What this is is a dot file specifying your user-specific configuration! It tends to live in your home directory, usually at cd ~. If you publicly share this file somewhere on the web you can rapidly set up a new computers/remote servers with your own familiar flavour of Git`. [Insert shameless selfplug]: mine’s hosted @ GitHub.

Skip to the next big heading if you don’t care to read about the core aliases I’ve defined for myself. You may lose some context when I talk about custom git aliases, though.

    # basic laziness
    st = status # show index and local/remote info
    br = branch # list branches
    co = checkout # checkout something!
    cm = commit # create a commit!
    df = diff # show differences between index and HEAD
    fe = fetch # update your locals from remotes
    mb = merge-base # <branch a> <branch b> find shared ancestor commit
    sta = stash # yes, saving two characters is worth an alias to me
    sl = stash list # show your stash
    sd = stash drop # baleet your stash
    sp = stash pop # apply the top patch from the stash, then throw it away if nothing went wrong
    sw = stash show -p # preview what a popping a stash would do
    lg = log --oneline --graph --color --decorate # prettier `git log`, also try adding the `--all` flag

    # commits/checkpoints
    aa = add -A # stage all the things
    save = ! git add -A && git commit -m 'SAVEPOINT' # save all the things
    wip = commit -am "WIP" # save all the things, kinda
    wipe = ! git add -A && git commit -qm 'WIPE SAVEPOINT' && git reset HEAD~1 --hard # ragequit all the code
    undo = reset HEAD~1 --soft # undo your last commit and return index to how it was

    # yo dawg
    ec = config --global -e # edit this file!

    # Hiding Pt. I
    fu = update-index --assume-unchanged # tell Git to stop caring about changes to a file
    fud = ! git ls-files -v `git rev-parse --show-toplevel` | grep "^[a-z]" # see which files you've ran `git fu` on
    sorry = update-index --no-assume-unchanged # revert `git fu`

    # Hiding Pt. II
    hid = ! git ls-files -v | grep '^S' # see all files you've hidden from git
    ax = ! sh -c 'echo "$1" >> .git/info/exclude' - # like a local .gitignore
    axed = ! cat .git/info/exclude # surprise, it's actually stored locally in a file

    # Funstuff
    sh = ! git log -1 | cowsay -f dragon-and-cow | lolcat # pretty print HEAD, requires some set like `gem install cowsay` and `gem install lolcat`
    gg = ! git commit -am "various performance improvments + bugfixes" # typical Google Play commit

You may notice there are no terribly dangerous ops being wrapped behind an alias in there; this is by design.

Workflows

“yo, review this PR for me”

Here’s an example of a typical workflow. A colleague throws you on a particularly hairy PR and you decide you can’t review it solely from the coziness of BitBucket/GitHub/GitLab/. You decide to `checkout` their branch but you're also very likely in the middle of some of your own work. You try a `git checkout PR#### origin/` and Git complains. You try to `git stash` and you get more weirdness. *sigh*

For me, I’ve become used to skipping the git-stash and instead utilize two of my aliases: git save and git undo. This will usually work as intended unless you’ve hidden way too many things using my two levels of hiding. Now you can just git save, git checkout, do your PR due diligence, and then git checkout and git undo on your original working branch and be exactly6 where you left off!

“argh, I lost the coin flip when two PR’s got merged during the same window, and I’ve already deleted my local and remote branch :( “

You may fear that you have lost your work, or have to try to recover your changes through your remote VCS but you probably don’t. Unless it’s been awhile, or you’ve been garbage collecting and deleting stuff you shouldn’t have, you can usually find what you’ve “lost” in the git reflog.

My understanding is git reflog tracks all changes to the HEAD “pointer” that Git keeps for branches within your local repository. Just read a few lines of the reflog, or do some simple git ops and you’ll understand what I mean.

Once you identify the commit hash of the place in time you’d like to return to you can run a git reset --hard <commit hash>. Now you can welcome back your lost commits. This is why running a regular git save, and git wip is nice because they show up as obvious checkpoints in the reflog.

“everything is not okay. lemme just copy my work elsewhere, nuke my local repo, and re-clone it”

It’s very rare and unusual that your local git can be that screwed up unless you’ve been prodding around within the .git/ folder itself. Your git status is probably reflecting some “detached head” nonsense or an interactive rebase conflict madness that you can’t resolve. To fix this, try parsing out the command git is telling you in its feedback: e.g. pass the --abort to what git itself is suggesting when you run a git status, that usually gets you right back on track.

Obligatory Git XKCD

Personally, I try to make it a point to keep long-lived repo’s for the sake of my reflog. The reason being that if you’re ever that far lost in the sauce, you can probably revert to a safe point in past history. Similar to the previous workflow, you can also utilize git reset --hard <commit hash that is known to be okay>.

Lastly, you may just want to git reset --hard HEAD to wipe out your current git index and return to what the latest commit’s repo looked like. I’d suggest using my wrapped git wipe so that you can return to those changes you so desperately tried to get rid of after you’ve cooled your head but remembered you actually did something useful with.

Explicitness

Much like how molly-guard forces you to type the currently connected machine’s hostname before shutting down (this can save your butt when you power down your prod server like a boss). I really like the idea of confirmation through explicitness and hence I don’t wrap commands like git push -f and git ops that accept --hard. You better be willingly conducting those destructive and dangerous git ops, not absent-mindedly.

To change published history is probably the biggest taboo Git has. Don’t do this, unless you’re absolutely certain no one is relying on your work, AKA you’re pushing to your own private remote branch. Don’t ever change commits that someone else may have already acquired. If you do, you’ll end up with pretty big headaches later when you try to consolidate work and git’s like wtf m8.

NubTip: you can totally change your local history, AKA stuff you haven’t git pushed. Otherwise, you’re usually better off running git revert on your last commit and then pushing that result.

ProTip: Don’t worry about losing your committed7 work through running git rebases and the like because there’s always the git reflog that tracks changes to git’s HEAD pointer. You’ll only really lose your committed work if you delete your .git/ folder in the local repo or run a very aggressive git gc (although Git v2.# seems to be smarter about it even with aggressive arg flags being passed).

Responsiveness

Sure the CLI may not show as much information all the time, but it definitely hides everything you need just a few words away. I find myself using git status very very frequently.

Actually, that was a lie: I actually have aliased g=git and I utilize my git-aliases as well so it becomes g st.

I also enjoy being vague with Git v2.# because it uses more conservative and sane defaults out of the box. A prime example would be the new behaviour of git push. By being more vague, e.g. passing less flags, I usually accomplish what I need without needing to delve in git-help. Git will also help out when additional flags are required, e.g. pass -u to also set as upstream when pushing to a remote for the first time.

Besides helping me not have to recall many random flags and switches, Git-cli is also a staunch conversational partner :D


LFS

You may have noticed slowdowns when Git works with larger sized files. Usually these larger files are of a binary nature, e.g. they don’t diff well and are usually multi-media. Luckily, if you don’t expect these files to change frequently, Git Large File Storage has a solution for you!

It essentially replaces these Binary Large Objects (BLOB) with file pointers that add another layer of indirection that allows caching performance. Git-lfs is an add on that also needs some tweaking via file scrubs, and may cost you out of pocket if you use too much bandwidth.


Addendum

git shit done

In the end, if GUI’s work for you, then by all means continue. If you want to become a Git guru on your own time, good on ya! Someone will probably thank you for being one later down the line.

$ git push -f8

<!-- $ rm rf .git/ -->9


  1. still in the double digits but it’s still ticking upwards :D 

  2. VCS isn’t just for distributed work! I found myself using Git a ton during my undergrad. Aside from tracking progress, you also get regular checkpoints that you can revert back to inevitably. It also makes sharing work a breeze 

  3. Ctrl + c, Ctrl + v, Dropbox, and e-mail are still technically “source code management” 

  4. SCM usually means Software Configuration Management but some, and I guess me, use it to mean source code management. I’ll try to use the term VCS from now on 

  5. Funnily enough, I actually learnt Git v1.# and have only recently became used to the more conservative behaviour, and sane defaults, of Git v2.# 

  6. this wasn’t exactly true up until recently because I was utilizing --mixed in my git undo alias. I discovered this through my dad’s git index getting clobbered when he was learning Git with the mindset of very rarely committing your work but staging files a lot (a remnant of using other VCS’ that were not as lightweight as Git). A colleague happened to utilize some op in SourceTree and I saw the GUI passed the --soft flag that is in use by me now :D 

  7. emphasis because git won’t have a record of untracked files, for obvious reasons. This is why it’s good to pick up a frequent commit habit [and also branching habit as well]! 

  8. don’t actually do this unless you really know what you’re doing. tbh, avoid --force but do try out --force-with-lease when you must 

  9. srsly don’t do this, you’re only hurting yourself, and the person who you end up asking to resolve this