Terrible Ideas in Git
This article was derived from a talk that GitHub Universe faithfully rejects every year. I can't understand why....
For better or worse, git has become one of the Open Source community's more ubiquitous tools. It lets you manage code effectively. It helps engineers who are far apart collaborate with each other. At its heart, it's very simple, which is why the diagram in so many blog posts inevitably looks something like the one shown in Figure 1.
Figure 1. Git Model (Source: https://nvie.com)
The unfortunate truth that's rarely discussed in detail is that git has a dark side: it makes us feel dumb. I don't care who you are—we all hit a point wherein we shrug, give up and go scrambling for Stack Overflow (motto: "This thread has been closed as Off Topic") to figure out how best to get out of the terrible situations we've caused for ourselves. The only question is how far down the rabbit hole you can get before the madness overtakes you, and you begin raising goats for a living instead.
At its core, all git does is track changes to files and folders.
git commit effectively takes a snapshot of the filesystem (as represented by the items added to the staging area) at a given point in time:
[email protected] ~/demo1 % git init Initialized empty Git repository in /home/cquinn/demo1/.git/ [email protected](master|...) ~/demo1 % git add ubuntu.iso [email protected](master|·1) ~/demo1 % git commit ↪-m "Initial commit" [master (root-commit) b0d3bfb] Initial commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ubuntu.iso [email protected](master|✓) ~/demo1 % git rm --cached ↪ubuntu.iso rm 'ubuntu.iso' [email protected](master|·1✓) ~/demo1 % git ↪commit -m "There I fixed it" [master 2d86934] There I fixed it 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ubuntu.iso [email protected](master|...) ~/demo1 % du -hs .git 174M .git
So if you do something foolish, such as committing large binaries, you can't just revert the commit—it's still going to live in your git repository. If you've pushed that thing elsewhere, you get to rewrite history forcibly, either with
git-filter-branch or the
bfg. Either way, it's extra work that's unpleasant to others who share your repository.
Fundamentally, all that git does is create a .git folder in the top level of the repository. This subdirectory contains files and folders that change over time. Wait, isn't there a tool for that?
[email protected] ~/demo2 % git init Initialized empty Git repository in /home/cquinn/demo2/.git/ [email protected](master|✓) ~/demo2 % cd .git [email protected] ~/demo2/.git % ls HEAD branches config description hooks info objects refs [email protected] ~/demo2/.git % git init Initialized empty Git repository in /home/cquinn/demo2/ ↪.git/.git/
I'm not sure why you would do such a thing, but the point is that you definitely could.
[email protected] ~/demo2 % git
Have you ever started typing a git command and gotten lost when googling for it? Then you find the command and paste it in:
[email protected] ~/demo2 % git git status git: 'git' is not a git command. See 'git --help'. Did you mean this? ign
And then you feel dumb. Let's fix that:
# echo git @? \> /usr/local/bin/git-git [email protected](master|✓) ~/demo2 % sudo bash [email protected]:~/demo2# echo 'git [email protected]' > ↪/usr/local/bin/git-git [email protected]:~/demo2# chmod +x /usr/local/bin/git-git
And, there you go:
[email protected](master|&#check;) ~/demo2 % git git git git ↪git git git status On branch master Initial commit nothing to commit (create/copy files and use "git add" to track)
I'm not saying this is a good idea—only that it can be done.
Actually Useful Git Tricks
Ever screw up syntactically?
[email protected](master|·1) ~/demo4 % git stts git: 'stts' is not a git command. See 'git --help'. Did you mean this? status
Then you sit around feeling sorry for yourself. Rejoice: git features an autocorrect setting:
[email protected](master|·1) ~/demo4 % git config ↪--global help.autocorrect 8 [email protected](master|·1) ~/demo4 % git stts WARNING: You called a Git command named 'stts', ↪which does not exist. Continuing under the assumption that you meant 'status' in 0.8 seconds automatically... On branch master Your branch is up-to-date with 'origin/master'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: file2.txt
--global tag applies this to the user's ~/.gitconfig, but the more relevant part is the final two arguments.
help.autocorrect enables automatic typo detection, while
8 is how many tenths of seconds you have to dive for Ctrl-C before git does something terrible to your environment.
The last tool I want to point out is
myrepos. This tool allows you to operate effectively on the many, many, many repositories that comprise your company's terrible microservices architecture (fun fact: microservices was proposed as a joke at a conference talk that people took seriously):
[email protected] ~ % mr list 31384 10:48:52 ↪Fri 06- 8-2018 mr list: /Users/cquinn/.config/vcsh/repo.d/gitconfig.git mr list: /Users/cquinn/.config/vcsh/repo.d/mr.git mr list: /Users/cquinn/.config/vcsh/repo.d/ssh.git mr list: /Users/cquinn/.config/vcsh/repo.d/tmux.git mr list: /Users/cquinn/.config/vcsh/repo.d/vim.git mr list: /Users/cquinn/.config/vcsh/repo.d/zsh.git mr list: /Users/cquinn/Dropbox/src/docPR/complete/ ↪amazon-cloud-directory-developer-guide mr list: finished (7 ok) [email protected] ~ % mr status 31385 10:48:53 ↪Fri 06- 8-2018 mr status: /Users/cquinn/.config/vcsh/repo.d/gitconfig.git On branch master Your branch is up to date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in ↪working directory) modified: ../../../../.gitconfig no changes added to commit (use "git add" and/or ↪"git commit -a") mr status: /Users/cquinn/.config/vcsh/repo.d/mr.git On branch master Your branch is up to date with 'origin/master'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in ↪working directory) modified: ../../../../.mrconfig no changes added to commit (use "git add" and/or ↪"git commit -a") mr status: /Users/cquinn/.config/vcsh/repo.d/ssh.git mr status: /Users/cquinn/.config/vcsh/repo.d/tmux.git On branch master Your branch is up to date with 'origin/master'. nothing to commit, working tree clean mr status: /Users/cquinn/.config/vcsh/repo.d/vim.git mr status: /Users/cquinn/.config/vcsh/repo.d/zsh.git Behind origin/master by 1 commits M ../../../../.zsh/functions/gitstatus.py M ../../../../.zshrc mr status: /Users/cquinn/Dropbox/src/docPR/complete/ ↪amazon-cloud-directory-developer-guide mr status: finished (7 ok)
It also takes concurrency arguments to parallelize workloads;
mr -j8 status results in eight working threads, for example.
Two other parting tips for you. First, if you're using GitHub, install Hub as a wrapper around git; it extends the command to embrace concepts such as forks, pull requests and pages. Second, set your prompt (there are many projects that work in various shells; poke around a bit or ask me on Twitter for up-to-the-minute suggestions) to reflect your current git status—what branch you're on, whether there's uncommitted work and so on. The subtle visual cues will help you avoid making terrible mistakes.