Section 52.7 Forming Logical Commits
There is an art to making a pull request that is easier to review, and which will be useful to others later (such as when using
git bisect
to isolate the introduction of a bug). Here are some notes:- Always
rebase
your sequence of commits ontomaster
before creating a pull request. Any (rare) conflicts should be your responsibility. If we delay in getting to a review, then maybe conflicts are our responsibility. - Make logical commits. Changes to common templates, HTML-specific templates, LaTeX-specific templates, etc. should all be on separate commits. Contributions to the Guide, and examples for testing in the sample article, should follow (even if testing examples may have been an early commit on your branch during development). Make schema changes last since it will be easier for us to manufacture derived files as an add-on to your work.
- Do not put partial work on two disjoint files into one commit, come back later, add a second commit with more work in each file, and call it good. Likely there should be two commits—relevant code in one file, related code in the other file. This is a general suggestion: a stream-of-consciousness commit history is of no benefit to anybody, even you.
- Do not make a mistake (typo, whitespace, logical error) in one commit, and then fix it several commits later as part of the same pull request. If you made a mistake, learn how to make the change/fix so it looks like it never happened. You do not want other developers to think you make mistakes, do you?
- Done right, a pull request with no changes will be merged as-is with no changes to the commit hashes. Consider that a goal, and we will congratulate you when it happens the first time (and expect it from then on!)
Alright, those are high expectations. How do you make a well-formed sequece of commits? This is not a git tutorial, but we will make some suggestions. git has what is called a staging area where you can gradually place a collection of changes before making them part of a single commit. The command
git commit -a
is a bad habit and breaking yourself of it will help you learn to be more flexible about how you package changes into a commit via the staging area. Finally -m
is a useful option for making (or changing) a commit message without being thrown into an editor.- If your most recent commit (or only commit!) is lacking you can add new changes into it by adding them to the staging area and using
git commit --amend
to introduce them into the commit. git reset HEAD~n
will return your files to a state as if you have made no commits (presuming you had \(n\) of them in the first place). Your edits will all be available (this is not a “hard” reset, and forget that we even mentioned such a possibility). Then you can selectively stage portions of your work with tools likegit add <file>
orgit add -p
and build up individual logical commits in the staging area. Of course, you do not always need to reset all of your commits on a branch, perhaps only a few will need reworking. Caution: do not reset so many commits that you blow past branch pointers and lose them, such asmaster
.-
You can do an interactive rebase with
git rebase -i HEAD~n
. This unwinds \(n\) commits on the current branch, makes a little script for replaying them in the proper order, and throws you into an editor with the script. Exit the editor and the script runs. With that description/process nothing interesting happens. What is interesting is that you can edit the script to affect the replay.You can rearrange the order of the commits. But this only works if you know that interchanged commits do not build on one another. For example, I often start developing a new feature by designing the PreTeXt markup and making an example in the sample article. But once I am all done, I move it to be later than the code, as an example of how the new code will behave (ansd I do not leave the sample article in a broken state).Suppose you have ten commits on a branch, and you discover that the third-newest has a small mistake. Correct the mistake and make a throw-away commit with just that correction on it. Now, do an interactive rebase with the newest four commits (you just added one),git rebase -i HEAD~4
. Edit the script to place the throwaway commit just after the commit with the mistake, and do not leave it aspick
, but edit that action tofix
(anf
is all you really need). Be sure to remove the line that has the original version of the throwaway commit. Exit your editor. Poof! Three commits and the mistake is corrected. (Do not use thesquash
action, that accumulates commit messages, which we do not want.)This is analgous togit --amend
except it is needed for the times when you see a change that needs to be made several commits ago. -
The
git cherry-pick <commit>
command allows you to recycle existing commits onto new branches. Suppose your current branch ends up having two very different projects on it. Make a new branch and cherry-pick the commits for one project onto it. Now do an interactive rebase on the original branch and remove those commits from the script. Of course, this assumes you know the two sets of commits are independent of each other.Here is a different solution to the same problem. Do agit reset
of the entire branch, as described above. Build up the first project into a set of commits. Now dogit stash save
to put all the remaining edits for the second project into the stash temporarily). Switch tomaster
, make a new branch,git stash pop
to get all the edits back, and start building up the second branch. Note that this assumes the second project does not build on the first project. - Be bold! But maybe make backups first? You can often abandon things that are not going well. If
git rebase master
has conflicts you can look around at the files affected, and then you can give up withgit rebase --abort
. There are other bail-outs for other commands. - The commands
git log
,git diff
, andgit status
are your friends. It is never a mistake to use them more than necessary.