hardscrabble 🍫

By Max Jacobson

Psst. Check out my RubyConf 2017 talk, There are no rules in Ruby.

blog posts

Sunrise commits

26 Nov 2018

I’ve picked up the habit from some people I’ve worked with that whenever I create a new repository, I make an initial empty commit that has the commit message :sunrise: and no changes in it. I thought it might be helpful to jot down some context on why I do that, or at least why I think I do that.

Starting new repositories

When you initialize a new git repository, it doesn’t yet have any commits in it. Let’s say you create a new repository:

mkdir my-great-repository
cd my-great-repository
git init

And then ask git to tell you about it:

git status

It will print out:

On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

And if you ask git to tell you about the commits:

git log

It won’t be able to:

fatal: your current branch 'master' does not have any commits yet

Let’s try to make a commit and see what happens:

git commit --message "some commit"

It didn’t let us:

On branch master

Initial commit

nothing to commit

We tried to make a commit, but we hadn’t staged any changes to be included in the commit, and so git was like … no. Which is kind of fair, since ordinarily the point of making a commit is to introduce a change to the code base. But the first commit is kind of special: what does it even mean to make a change to nothing?

I encourage you not to spend too long pondering that question.

There are basically two ways out of this:

  1. Actually add some files and commit them
  2. Tell git that you don’t mind having an empty commit, and make an empty commit

The first way: having the first commit include some files

The first way is probably what most people do, since it’s pretty straight-forward:

echo "Hello" > README.md
git add README.md
git commit --message "some commit"

The last command will output:

[master (root-commit) e52641c] some commit

Notice the part that says (root-commit), which is how you know that it’s the first commit.

This is basically fine.

Running git log works how you might suspect: it shows that there’s one commit.

Running git show works just fine: it prints out the details of the latest commit, including the changes.

It gets a little more complicated if you want to use git diff. Let’s say you want to construct a git diff command which will display the changes you introduced in your first commit. What would that look like?

git diff ??? e52641c

Bizarrely, there is a way to do this, and it looks like this:

git diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 e52641c

What is that first sha? Basically don’t worry about it. It’s a constant in libgit2, although apparently it might change if git changes the algorithm it uses to generate hashes.

The second way: making a sunrise commit

The other thing that you could’ve done was make a sunrise commit:

git commit --allow-empty --message ":sunrise:"

What’s going on here?

This time, git lets us make a commit even though we haven’t staged any change, because we specifically passed the --allow-empty flag to the commit command.

The commit message is short and sweet and paints a picture that fills your heart with hope.

That’s it.

Some advantages to making a sunrise commit:

  1. You can make a new repository on GitHub and push your sunrise commit to your default branch, and then immediately check out a feature branch and start working on sketching out the initial project structure, and open a PR to introduce that.
  2. If you want to make a new branch that has a totally empty tree, you can checkout your sunrise commit and then branch off from there. There are other ways to do that but they melt my brain a little more.
  3. All of the meaningful commits in your repository will have a parent, making them easily diffed.
  4. You feel the simple pleasure of following the recommendation from a blog post.
  5. Probably some other reason that I’m forgetting (feel free to tell me).

A handy alias

If you find yourself following this pattern, you may want to add this handy alias:

git config --global alias.sunrise "commit --allow-empty --message ':sunrise:'"

That way, you can run simply git sunrise after you initialize a new repository.

Note: this will render as the sunrise emoji on GitHub. You can feel free to use the actual emoji. I don’t because emoji don’t render properly in terminal emulators on Linux, at least in my experience.

Shout out

I’m pretty sure I picked up this habit from Devon Blandin.

git cleanup-branches

02 Mar 2018

Do you clean up your git branches as you go or are you, like I am, a lazy hoarder?

$ git branch | wc -l
150

Look at all those things I did that I don’t care about anymore.

Yesterday I googled a little to try and find some magic incantation that would just clean up my branches for me. There are some, but I find that they’re either too conservative or too liberal for me. By “too conservative” I mean that they try to only delete branches that have been merged, except that they’re not actually very accurate, because they aren’t aware of GitHub’s “Squash and Merge” or “Rebase and Merge”, which I use pretty much exclusively. By “too liberal” I mean that some people recommend just literally deleting all of your branches.

I want to have control over the situation.

I can just run git branch -D branch-name-goes-here over and over, one-by-one, for all of my branches, but that would take several minutes, which I definitely technically have, but don’t want to spend that way, even while curled up with a podcast.

What I really kind of want is some kind of interactive process that gives me total control but doesn’t take that long to do. So I made a little shell script, which looks like this to use:

gif demonstrating git cleanup-branches which lets you interactively delete branches

As you may notice, it takes some loose inspiration from git’s interactive rebase.

It does something like this:

  1. get your list of branches
  2. open your default editor (whatever you have the $EDITOR global variable set to) (vim for me)
  3. wait for you to mark which branches should be deleted
  4. delete the ones you marked

git lets you plug in little scripts by just naming them git-whatever-you-want and putting that script on your $PATH and I think it’s fun to take advantage of that.

Here’s the latest version of the script as of this writing:

#!/usr/bin/env bash

set -euo pipefail

file="/tmp/git-cleanup-branches-$(uuidgen)"

function removeCurrentBranch {
  sed -E '/\*/d'
}

function leftTrim {
  sed -E 's/\*?[[:space:]]+//'
}


all_branches=$(git branch | removeCurrentBranch | leftTrim)

# write branches to file
for branch in $all_branches; do
  echo "keep $branch" >> $file
done

# write instructions to file
echo "

# All of your branches are listed above
# (except for the current branch, which you can't delete)
# change keep to d to delete the branch
# all other lines are ignored" >> $file

# prompt user to edit file
$EDITOR "$file"

# check each line of the file
cat $file | while read -r line; do

  # if the line starts with "d "
  if echo $line | grep --extended-regexp "^d " > /dev/null; then
    # delete the branch
    branch=$(echo $line | sed -E 's/^d //')

    git branch -D $branch
  fi
done

# clean up
rm $file

It follows the “chainable shell function” pattern I’ve written about before.

It uses set -o pipefail, my favorite recent discovery in shell scripting, which makes sure that each command succeeds, not just each expression. I should probably do a separate blog post about that with more detail.

Yeah, I guess that’s pretty much it. Have fun shell scripting out there.