git basics

Posted on 2020-11-15

I’ve been streaming. Sorry I didn’t tell you more than an hour in advance,1 it was kind of rushed. Anyway.

I usually try and keep some reminder of my submit history in source control. Except it doesn’t blend so well with the twitch flow, so I commonly omit2 to do so while on air.

So after the dust’s settled and the video’s all nice and trimmed, all I have left to do is reconstruct history.

Here’s what I did:3

$ git init
$ git add .
$ git tag bronze $(git write-tree)
$ cat > fall.hs                     # wood-2 from history pane
$ $EDITOR fall.cabal       # edit with common sense
$ git commit -am'Wood-2' --date=16:21:30
$ cat > fall.hs                     # wood-1 from history pane
$ git checkout -p bronze fall.cabal # common sense
$ git checkout -p bronze   # common sense
$ git commit -am'Wood-1' --date=17:39:54
$ git checkout -p bronze
$ git commit -am'Bronze' --date=18:11:11
$ git tag -d bronze

I didn’t rush into this though. You know me. I started by asking the support channel’s advice.

<22:05:27> JBM: rhetorical git question of the day
<22:05:38> JBM: am I going to be able to tag a tree
                without committing to a branch?

That didn’t pan out. No hostility or antipathy, though.

Let’s walk through this. What do I want exactly, and why?

I simply want a git repository of my contest code, with appropriate commits at the right time. The only catch is I’m only taking care of it now, post-hoc. A picture being worth a thousand words: I want this:

As you can tell from the idealized “current” state, I started the contest at some random time this afternoon, pushed some Wood-2 code at 16:21, some Wood-1 code at 17:40, and some Bronze code at 18:11.

I haven’t even created a git repository there yet. That part’s easy enough:

$ git init

But what next? Any commit I can create from this state will “naturally” become the initial commit of the current default branch.

While I do want a commit for my current (bronze) state, I don’t want it to be an initial commit. I want it to have a lineage: the wood-1 and wood-2 commits that naturally preceded. So I can’t really git commit -am now.

Ok, I can’t git commit, but I can git add, right? Yup, that ought to be safe enough. git add updates the index from the working directory; the working directory is a state I do want to keep, there can’t be any harm in reifying it. So let’s do it.

$ git add .

The index is now synchronized to the working directory. To my current bronze code. Good, but not very persistent yet. How do we fix that?

We’re off the standard path; the porcelain won’t help us much here. It’s going to take two steps.

$ git write-tree
$ git tag bronze 83fa495d1611e91fe23ffac73354885266cef9fb

The first command instructs git to store our index to the repository. It returns the resulting hash address on standard output. But as that object is not referred to by any head4, it’s not persistent at all. So we create such a head, in the form of a tag. I happened to just copy-paste the SHA-1, but the procedure could be abbreviated with bash process substitution:

$ git tag bronze $(git write-tree)

$() syntax, a synonym for the more antiquated backtick syntax, simply substitutes a command’s standard output as a command-line parameter.

Right. The current state is now preserved. In a peculiar git repository: a non-empty yet branchless one. This won’t last.

We now need to start worrying about creating the actual branch. We’ll proceed “naturally”, recreating it as we would have done at the time.

To recreate the initial5 state, we’ll update the working directory to set the files the way they were at the time. The main source file by copy-paste from the contest submission history, the other files by making heavy use of common sense and subtler use of editing technology:

$ cat > fall.hs
    [large copy-paste here]
$ $EDITOR fall.cabal
    [short editing session here — such subtle]

Now we’re ready to use a git command almost the normal way:

$ git commit -am'Wood-2' --date=16:21:30

You normally don’t use another date/time as the current one, but the rest of the command is common enough.

Getting the working directory to the second interesting state is performed almost the same way:

$ cat > fall.hs
$ git checkout -p bronze fall.cabal

The first file by copy-paste, the others using git’s interactive patching ability.6

$ git commit -am'Wood-1' --date=17:39:54

That was easy.

And now what I’d been asking the rhetorical question for:

$ git checkout -p bronze

I used checkout -p with liberal approving of the diffs because that’s the first thing that came to mind. With hindsight, and if this needed reproduction and/or automation, this would be a proper case for git read-tree. But hey, I don’t use this one every day, I’ll cut myself some slack for not thinking of it spontaneously.

Anyway, our working directory is now in the state we’d expect. So all that’s left to do is make it permanent and clean up the temporaries.

$ git commit -am'Bronze' --date=18:11:11
$ git tag -d bronze

All done!

I really like that git won the SCM wars. I used it from week one, and while I’ll admit its internals are not necessarily a good thing to have available to anybody, I’m very glad they’re accessible when I need them.

It feels like a historical accident. As far as I’m concerned, it’s a happy accident.

How would you have done it? git being open means there are a lot of possibilities to achieve the same end result, and I’d really like to hear about yours!7

Thanks to @alze for being the one to try and answer my rhetorical question of the day.

  1. But, you know, all you have to do is watch that topic and Discourse will tell you automatically!↩︎

  2. “forget”↩︎

  3. In case you didn’t know or notice, I use git.↩︎

  4. We don’t even have any head at this point!↩︎

  5. More specifically, the first one we’d want to store↩︎

  6. And the usual dose of common sense.↩︎

  7. Especially if it’s better.↩︎