Hardscrabble 🍫

By Max Jacobson

Commit your skeletons right away

26 Nov 2018

I was just writing a post about habits around starting new git repositories and there was one additional thought that isn’t quite related but which I also want to say and so now I’m really blogging tonight and coming back to you with a second post.

Please commit your skeletons right away.

Imagine you’re making a new rails app, and you use the rails command to generate a skeleton for your new application:

rails new the_facebook
cd the_facebook
$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)


nothing added to commit but untracked files present (use "git add" to track)

This command will generate a whole bunch of files. It will also initialize a new git repository. But it doesn’t commit those new files for you.

I urge you: please commit them right away (after your sunrise commit).

Why? Because these files were all generated for you by a computer, and the computer deserves credit. Kind of. Really, it’s because you’re about to make a bunch of changes to those files and you’re also about to forget which of those lines you wrote and which of those lines the computer wrote. You’re going to be maintaining this code base for the rest of your life. I mean, maybe. It’s really helpful to look at git blame and figure out who wrote which lines and why and in my opinion it can actually be helpful to have that context all the way down to the very beginning.

The same is true of cargo new for rust people and jekyll new for blogger people. The ember new command is a welcome exception: it commits its skeleton and throws in some cute ASCII art for free.

If you already didn’t do this with your repository, rest assured that it doesn’t really matter, it’s just kind of nice.

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

01 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

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


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

# 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

# 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.