git gud
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
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:
- GitHub Desktop’s concept of “sync”. If
git pull
wrappinggit fetch
andgit merge
wasn’t enough, now we have agit sync
button that wrapsgit pull
andgit push
- It’s not obvious from IntelliJ IDEA’s SCM4 integration that you can edit commit messages.
- I’m fairly certain no GUI exposes the all-powerful `git reflog
- 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/
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.
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 push
ed. Otherwise, you’re usually better off runninggit revert
on your last commit and thenpush
ing that result.ProTip: Don’t worry about losing your committed7 work through running
git rebase
s and the like because there’s always thegit reflog
that tracks changes togit
’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 aggressivegit 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 -f
8
<!-- $ rm rf .git/ -->
9
-
still in the double digits but it’s still ticking upwards :D ↩
-
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 ↩
-
Ctrl + c
,Ctrl + v
, Dropbox, and e-mail are still technically “source code management” ↩ -
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 ↩
-
Funnily enough, I actually learnt Git v1.# and have only recently became used to the more conservative behaviour, and sane defaults, of Git v2.# ↩
-
this wasn’t exactly true up until recently because I was utilizing
--mixed
in mygit undo
alias. I discovered this through my dad’sgit
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 ↩ -
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]! ↩ -
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 ↩ -
srsly don’t do this, you’re only hurting yourself, and the person who you end up asking to resolve this ↩