class: center, middle # Intermediate Git 2013-03-12 Zack Newman, ADI adicu.com --- # Expectations for this talk I assume some familiarity with basic Git operations. But PLEASE stop me if I say something that doesn't make sense. > He who asks is a fool for five minutes, but he who does not ask remains a fool forever. > .right[*Chinese Proverb*] --- # How Git Works Files are *commited*, *modified*, or *staged* Stored in the Git directory, working directory, or staging area respectively .center[![Local Operations](local-operations.png)] --- # How Git Works What is a commit? * Snapshot of the state of your repo * **Not** a diff (unlike in Subversion) .center[![File Lifecycle](file-lifecycle.png)] --- # How Git Works Tiny bit of internals (each of these has a SHA1): * *blob*: a version of a file * *tree*: a collection of pointers to blobs * *commit*: pointer to a tree, plus commit metadata .center[![Commits, Trees, and Blobs](commit-tree-blob.png)] --- # How Git Works Commits include pointers to parent commits. .center[![Commit Parents](commit-parents.png)] --- # Branches What's a *branch*? Simply a pointer to a commit! .center[![Single Branch](single-branch.png)] Don't believe me? Check! .no-highlight > cat .git/refs/heads/master d12a3c549eb486c5ad5f95c145df4685097755ad > git show d12a3c54 commit d12a3c549eb486c5ad5f95c145df4685097755ad Author: Zack Newman
... --- # Branches .no-highlight git branch testing # create a new branch .center[![New Branch](new-branch.png)] .no-highlight > cat .git/refs/heads/testing 8e19ecbd2b962a7eb4a2aa36b5bac51442a1b2a8 > git show 8e19ecb commit 8e19ecbd2b962a7eb4a2aa36b5bac51442a1b2a8 Author: Zack Newman
... --- # Branches How does Git know what branch I'm on? .no-highlight > cat .git/HEAD ref: refs/heads/master .center[![HEAD](branch-head.png)] --- # Branches What happens when I commit? .bash git checkout testing # edit edit git commit .center[![Advancing HEAD](commit-head.png)] If we were to merge `testing` into `master` right now, it'd be simple (fast-forward). --- # Branches Let's make things more interesting: .bash git checkout master # edit edit git commit .center[![Divergent History](divergent-history.png)] --- # Merging .bash git merge testing Git will: 1. Find common ancestor 2. Identify commit to merge into and commit to merge in 3. Create a new commit based on three-way merging. This commit has two parents. If this is successful, it is safe to delete the branch. .bash git branch -d testing --- # Merge Conflicts A lot of times, merging *won't* be successful. .center[![Merge Conflict](meme-merge.jpg)] Good news: This isn't actually *that* painful of a process. Git provides some tools to make it easier. Bad news: You're still going to have to do it *a lot*. --- # Merge Conflicts Merge conflicts occur when Git can't figure out how to apply two diffs to the same blob. `app.py -- HEAD` import math `app.py -- master` from math import sqrt `app.py -- new_branch` import math print(math.sqrt(2)) --- # Merge Conflicts .no-highlight Auto-merging app.py CONFLICT (content): Merge conflict in app.py Automatic merge failed; fix conflicts and then commit the result. See what's up using `git status` (not shown). `app.py` from math import sqrt ======= import math print(math.sqrt(2)) >>>>>>> testing We **want** there to be a merge conflict in this case so that we can resolve it properly: from math import sqrt print(sqrt(2)) Then, .bash git add app.py && git commit --- # Merge Conflicts That's really all there is to it. Some tips/tricks though: * `git mergetool` * `git checkout --theirs|--ours [file]` --- # Rewriting History Make sure **never** to do any of the following things after you've pushed your changes to a remote repo. .center[![Danger Zone](danger-zone.jpg)] --- # Rewriting History Forgot a file? Messed up the commit message? .bash git commit --amend --- # Rewriting History Alternative to merging: the rebase ## ![Pre-Rebase](pre-rebase.png) → ![Pre-Rebase-Merge](pre-rebase-merge.png) --- # Rewriting History git checkout experiment git rebase master This will: 1. Find the common ancestor of `experiment` and `master`. 2. Getting all of the diffs of the commits from `experiment` and saving them to temporary files. 3. Setting `experiment` to point to the same commit as `master`. 4. Apply each of the diffs in sequence. .center[![Rebase](rebase.png)] Merge conflicts can occur here. --- # Rewriting History At this point, you can simply .bash git checkout master git merge experiment and `master` gets fast-forwarded. .center[![Rebase-Merge](rebase-merge.png)] This is sweet because you don't get any ugly merge-fix commits in `master`. --- # Rewriting History You can also interactive rebase. Say you've been hacking on a feature branch and it's really ugly, but you want to clean it up before you merge into `master`. .bash git checkout -b feature # hack hack git commit -a # hack hack git commit -a # etc. git rebase -i master --- # Rewriting History This will put you into interactive rebase mode: .no-highlight pick f7f3f6d changed my name a bit pick 310154e updated README formatting and added blame pick a5f4a0d added cat-file # Rebase 710f0f8..a5f4a0d onto 710f0f8 # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # If you remove a line here THAT COMMIT WILL BE LOST. # However, if you remove everything, the rebase will be aborted. # Note that this runs old-to-new. You can do any of the commands, then exit. Git will give you pretty clear instructions on what to do from there. --- # Tagging If you're maintianing a library or something that gets actually released, tags are really helpful. At its simplest, a tag is essentially a branch that doesn't change: it points to the same commit forever. Tags can also be annotated with messages, GPG-signed, and have metadata. .bash git tag v1.0 # create plain tag git tag -a v1.0 # create annotated tag git tag -s v1.0 # create signed tag git tag -v v1.0 # verify signed tag git push origin v1.0 # push tag to remote, which doesn't happen by default Please please please use [semantic versioning](http://semver.org/). --- # Configuration `git config` takes: * `--system`: `/etc/gitconfig` * `--global`: `~/.gitconfig` * default: `.git/config` Some useful `git config` commands: .bash git config --help # you should definitely do these two git config --global user.name "Zack Newman" git config --global user.email znewman01@gmail.com # do this one! git config --global color.ui true # aliases are fun, but I won't dive too deep here git config --global alias.co checkout git config --global alias.hi !echo hi # whitespace/formatting, diff/merge tools, editor, GPG key --- # Configuration `.gitignore` files are really useful! You should have a global gitignore. Mine lives at `~/.gitignore`. Set that location: git config --global core.excludesfile ~/.gitignore Example contents: .no-highlight *~ # editor backups! .DS_Store # if you use a Mac and commit one of these files I will be very upset venv # anything specific to YOUR system goes in YOUR gitignore --- # Best Practices * Don't commit clutter! * Each commit should stand on its own. * Don't combine two different changes into one commit (whitespace commits should be separated). * Don't leave empty space at the end of lines (`git diff --check`). * Don't commit lines that are longer than your project's convention (I like 80 chars). * Don't commit commented out code. --- # Best Practices According to Linus Torvalds himself: > A good commit message looks like this: > > .no-highlight > Header line: explaining the commit in one line > > Body of commit message is a few lines of text, explaining things > in more detail, possibly giving some background about the issue > being fixed, etc etc. > > The body of the commit message can be several paragraphs, and > please do proper word-wrap and keep columns shorter than about > 74 characters or so. That way "git log" will show things > nicely even when it's indented. > > Reported-by: whoever-reported-it > Signed-off-by: Your Name
> > where that header line really should be meaningful, and really should be > just one line. That header line is what is shown by tools like gitk and > shortlog, and should summarize the change in one readable line of text, > independently of the longer explanation. --- # Best Practices I like Tim Pope's example even better: .no-highight Capitalized, short (50 chars or less) summary More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of an email and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); tools like rebase can get confused if you run the two together. Write your commit message in the imperative: "Fix bug" and not "Fixed bug" or "Fixes bug." This convention matches up with commit messages generated by commands like git merge and git revert. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here - Use a hanging indent --- # Misc. Tips and Tricks `git commit` options: * `-a`: add all files currently in the repo to the staging area before committing. * '-m': specify commit message on the command line. Don't do this in a real project; write a good commit message. * '-v': *verbose*: include the diff for this commit in the editor for the commit message so you can remember what you did. * '-p': commit interactively by chunks. This is crazy: you can commit some parts of files but not others. `git mv`: git detects moved files automagically Bash autocompletions: Google and install, it'll make your life easier. Or use zsh. --- # Misc. Tips and Tricks `git stash`: store files from your working directory without committing them. # example usage git checkout -b feature_branch # hack hack hack # your boss says there's a critical bug in production git stash git checkout master # fix fix fix git commit git checkout feature_branch git stash pop --- # The Future Further resources: * *Pro Git* by Scott Chacon * *zack@adicu.com* * `man` pages Other topics: * Recovery * `git bisect`