N.B. Some of this revolves around puppet, but if you know nothing about puppet, you won’t miss anything.
I’ve been doing a bunch of stuff in puppet lately. Since my actual puppet runs are inside VMs which I nuke fairly frequently, doing the actual bits of development in the VM is impractical (and somewhat dangerous even).
I use git to do my normal version control anyway, so that’s nothing
earth shattering, but I also use git to update
the VM from what’s on my laptop because it works really well.
Since I do that, the number of commits I generate can get large, as some commits are adding a close quote or colon, or what have you. Not only that, but I often have a few commits that I never want to actually get merged at the end of the day because they’re just there to set the node configration, or tweak things that only apply because I’m fiddling in a dev vm, and not a real environment.
How to manage all of this?
I have a script that I copy to my VMs after I bring them up the first
time which I call
plunk. The script is really just a one-liner of:
Basically, it syncs
/etc/puppet to the contents of my laptop’s puppet
So after the VM comes up, I
scp that to the VM, log in and do:
1 2 3
sudo bash which is a bad practice, but it’s in a disposeable
test VM that never lives for more than a day and is not accessible
outside my laptop. I never do this on a real box though).
Puppet will do it’s thing, and I’ll find something that’s not right or just need tweaking. So I’ll edit the relevant bits on my laptop, and do what I call a ‘derp commit’ like this:
When I’m iterating quickly, the commit messages are useless and of no lasting value, and as you’ll see later, they’re just removed anyway, so while I’m in a tight loop, I’ve got two tabs in a shell window, one logged into the VM and the other at the top of my local puppet git repo.
Then after the commit is done, I run this in the tab that’s logged into the VM:
Then lather, rinse, repeat for a while. Now I may have to make some changes that I don’t ever actually want merged into master at the end of all of this. It’s things like the “cluster” settings so that it connects to a zookeeper instance running on the VM instead of the normal dev instance, or what have you. When I need those kinds of changes, I just commit them with a message of “DO NOT MERGE” or something similar (I’ll call it DNM for short for the rest of this).
After a while of development, I wind up with a git log of:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
After some period of time, I’ll decide to clean up the commits,
so I’ll start the rebase. In particular, I’ll get the commit id
of the commit just past the last one I want to join into. In this case it
which will bring up an editor with this:
From there I’ll basically sort them with “derps” first, then the “DO NOT MERGE”, maintaining their original order within those two categories:
Then I’ll change
picks on the derp commits to be
f to squash them
into the “main” commit. Then I’ll change all of
pick on the DO NOT
MERGEs, except for the first one.
From there, save and exit, and you have the following git log:
1 2 3 4 5 6 7 8 9 10 11
Nice and clean.
Multiple Logical Commits
Now if there should be multiple logical commits, you can do it one of two ways (or even both) depending on how you work. If I want to track the commits as I go, I’ll commit with a message like ‘derp kafka thing’ or ‘derp graphite’ and then do a similar sorting process like I did above, just with two ‘derp’ categories instead of just the one.
Alternatively, I’ll split them out at the end, like so:
Then I’ll change
edit for the commit I want to split
Then I back up the state of the tree to the previous commit.
Now I create three commits, one with the kafka bits, one with graphite, and one with everything else.
1 2 3 4 5
And lastly, I’ll finish up by letting the rebase continue until it finishes.
If all went well you should see:
I mess up editing commits all the time. Fortunately, there’s
git rebase --abort which will restore the state of the world back to
what it was before you entered interactive rebase.
But now the commit list is a tad out of order. The base bits should be first and not kafka, right?
So we rebase again
And we see this:
Let’s put the base one first like this:
And now our commit log looks like it ought to:
Time For The Pull Request
It’s time for code review, and changes will have to be made and I’d like to preserve the DNM commits until it’s all done and the branch merged, but don’t want the DNM merged (duh).
This turns out to not be at all difficult. When you go to push your branch to be reviewed, instead of doing something like:
You do this instead:
refs/heads/ part only needs to be done the first time when
creating the remote branch, after that you can just use:
And your reviewers won’t see the DNM commits in your pull request.
If you have more than one DNM commit you want to maintain, then
HEAD^ above as appropriate.
Now you have to be careful that the what’s in the DNM commits doesn’t affect what should be merged, but that’s outside of the scope of this post.
Developing On Top
Sometimes you want to build on top of a commit like this, but without the DNM commit. It’s pretty simple from there. Just do a:
Then continue on with life. But if say one of your reviewers requests
some changes that you want to make in the first pull request, a little
rebasing is necessary. Assuming you’ve got the original branch checked
out, lets say you make a single commit. Then you interactively rebase
to reorder the commits like we did earlier. Push again, this time with
-f flag or it won’t work.
But now we have this branch we made before the rebase. What do we do
now? With the second branch checked out, we rebase it onto the old
one in the proper place. Let’s say that the commit before our first
commit on the new branch is
a8db21 we then rebase that onto example,
skipping the DNM commit like so. If there is more than one DNM commit
on example, you may need to use more than one
Being able to do things like this definitely is really nice. It allows me to commit whenever I want and make the commit log at the end look like I want it to. Factoring in all of that and a doc that GitHub put out called How to undo (almost) anything with Git, I learned to stop worrying and love git rebase.