Reading List
The most recent articles from a list of feeds I subscribe to.
Making crochet cacti
I noticed some tech bloggers I follow have been making April Cools Day posts about topics they don’t normally write about (like decaf or microscopes). The goal isn’t to trick anyone, just to write about something different for a day.
I thought those posts were fun so here is a post with some notes on learning to crochet tiny cacti.
first, the cacti
I’ve been trying to do some non-computer hobbies, without putting a lot of pressure on myself to be “good” at them. Here are some cacti I crocheted:
They are a little wonky and I like them.
a couple of other critters
Here are a couple of other things I made: an elephant, an orange guy, a much earlier attempt at a cactus, and an in-progress cactus
Some of these are also pretty wonky, but sometimes it adds to the charm: for example the elephant’s head is attached at an angle which was not on purpose but I think adds to the effect. (orange guy pattern, elephant pattern)
I haven’t really been making clothing: I like working in a pretty chaotic way and I think you need to be a lot more careful when you make clothing so that it will actually fit.
the first project: a mouse
The first project I made was this little mouse. It took me a few hours (maybe 3 hours?) and I made a lot of mistakes and it definitely was not as cute as it was in the pictures in the pattern, but it was still good! I can’t find a picture right now though.
buying patterns is great
Originally I started out using free patterns, but I found some cacti patterns I really liked in an ebook called Knotmonsters: Cactus Gardens Edition, so I bought it.
I like the patterns in that book and also buying patterns seems like a nice way to support people who are making fun patterns. I found this guide to designing your own patterns through searching on Ravelry and it seems like a lot of work! Maybe I will do it one day but for now I appreciate the work of other people who make the patterns.
modifying patterns chaotically is great too
I’ve been modifying all of the patterns I make in a somewhat chaotic way, often just because I made a mistake somewhere along the way and then decide to move forward and change the pattern to adjust for the mistake instead of undoing my work. Some of of the changes I’ve made are:
- remove rows
- put fewer stitches in a row
- use a different stitch
This doesn’t always work but often it works well enough, and I think all of the mistakes help me learn.
no safety eyes
A lot of the patterns I’ve been seeing for animals suggest using “safety eyes” (plastic eyes). I didn’t really feel like buying those , so I’ve been embroidering eyes on instead. “Embroidering” might not be accurate, really I just sew some black yarn on in a haphazard way and hope it doesn’t come out looking too weird.
My crochet kit came with a big plastic yarn needle that I’ve been using to embroider and also
no stitch markers
My crochet kit came with some plastic “stitch markers” which you can use to figure out where the beginning of your row is, so you know when you’re done. I’ve been finding it easier to just use a short piece of scrap yarn instead.
on dealing with all the counting
In crochet there is a LOT of counting. Like “single crochet 3 times, then double crochet 1 time, then repeat that 6 times”. I find it hard to do that accurately without making mistakes, and all of the counting is not that fun! A few things that have helped:
- go back and look at my stitches to see what I did (“have I done 1 single crochet, or 2?”). I’m not actually very good at doing this, but I find it easier to see my stitches with wool/cotton yarn than with acrylic yarn for some reason.
- count how many stitches in total I’ve done since the last row, and make sure it seems approximately right (“well, I’m supposed to have 20 stitches and I have 19, that’s pretty close!”). Then I’ll maybe just add an extra stitch in the wrong place to adjust, or maybe just leave it the way it is.
notes on yarn
So far I’ve tried three kinds of yarn: merino (for the elephant), cotton (for the cacti), and acrylic (for the orange dude). I still don’t know which one I like best, but since I’m doing small projects it feels like the right move is still to just buy small amounts of yarn and experiment. I think I like the cotton and merino more than the acrylic.
For the cacti I used Ricorumi cotton yarn, which comes in tiny balls (which is good for me because if I don’t end up liking it, I don’t have a lot of extra!) and in a lot of different colours.
There are a lot of yarn weights (lace! sock! sport! DK! worsted! bulky! and more!). I don’t really underestand them yet but I think so far I’ve been mostly using DK and worsted yarn.
hook size? who knows!
I’ve mostly been using a 3.5mm hook, probably because I read a tutorial that said to use a 3.5mm hook. It seems to work fine! I used a larger hook size when making a hat, and that also worked.
I still don’t really know how to choose hook sizes but that doesn’t seem to have a lot of consequences when making cacti.
every stitch I’ve learned
I think I’ve probably only learned how to do 5 things in crochet so far:
- magic ring (mr)
- single crochet (sc)
- half double crochet (hdc)
- front post half double crochet (fphdc)
- double crochet (dc)
- back loops only/front loops only (flo/blo)
- increase/decrease
The way I’ve been approaching learning new crochet stitches is:
- find a pattern I want to make
- start it without reviewing it very much at all
- when I get to a stitch I don’t know, watch youtube videos
- don’t watch it very carefully and get it wrong
- eventually realize that it doesn’t look right at all, rewatch the video, and continue
I’ve been using Sarah Maker’s pages a lot, except for the magic ring where I used this 3-minute youtube video.
The magic ring took me a very long time to learn to do correctly, I didn’t pay attention very closely to the 3-minute youtube video so I did it wrong in maybe 4 projects before I figured out how to do it right.
every single thing I’ve bought
So far I’ve only needed:
- a crochet kit (which I got as a gift). it came with yarn, a bunch of crochet needles in different sizes, big sewing needles, and some other things I haven’t needed yet.
- some Ricorumi cotton (for the cacti)
- 1 ball of gray yarn (for the elephant)
I’ve been trying to not buy too much stuff, because I never know if I’ll get bored with a new hobby, and if I get bored it’s annoying to have a bunch of stuff lying around. Some examples of things I’ve avoided buying so far:
- Instead of buying polyester fiberfill, to fill all of the critters I’ve just been cutting up an old sweater I have that was falling apart.
- I’ve been embroidering the eyes instead of buying safety eyes
Everything I have right now fits in a the box the crochet kit came in (which is about the size of a large shoebox), and my plan is to keep it that way for a while.
that’s all!
Mainly what I like about crochet so far is that:
- it’s a way to not be on the computer, and you can chat with people while doing it
- you can do it without buying too much stuff, it’s pretty compact
- I end up with cacti in our living room which is great (I also have a bunch of real succulents, so they go with those)
- it seems extremely forgiving of mistakes and experimentation
There are definitely still a lot of things I’m doing “wrong” but it’s fun to learn through trial and error.
Some Git poll results
A new thing I’ve been trying while writing this Git zine is doing a bunch of polls on Mastodon to learn about:
- which git commands/workflows people use (like “do you use merge or rebase more?” or “do you put your current git branch in your shell prompt?”)
- what kinds of problems people run into with git (like “have you lost work because of a git problem in the last year or two?”)
- which terminology people find confusing (like “how confident do you feel that you know what HEAD means in git?”)
- how people think about various git concepts (“how do you think about git branches?”)
- in what ways my usage of git is “normal” and in what ways it’s “weird”. Where am I pretty similar to the majority of people, and where am I different?
It’s been a lot of fun and some of the results have been surprising to me, so here are some of the results. I’m partly just posting these so that I can have them all in one place for myself to refer to, but maybe some of you will find them interesting too.
these polls are highly unscientific
Polls on social media that I thought about for approximately 45 seconds before posting are not the most rigorous way of doing user research, so I’m pretty cautious about drawing conclusions from them. Potential problems include: I phrased the poll badly, the set of possible responses aren’t chosen very carefully, some of the poll responses I just picked because I thought they were funny, and the set of people who follow me on Mastodon is not representative of all git users.
But here are a couple of examples of why I still find these poll results useful:
- The first poll is “what’s your approach to merge commits and rebase in git”? 600 people (30% of responders) replied “I usually use merge, rarely/never rebase”. It’s helpful for me to know that there are a lot of people out there who rarely/never use rebase, because I use rebase all the time – it’s a good reminder that my experiences isn’t necessarily representative.
- For the poll “how confident do you feel that you know what HEAD means in
git?”, 14% of people replied “literally no idea”. That tells me to be careful
about assuming that people know what
HEADmeans in my writing.
where to read more
If you want to read more about any given poll, you can click at the date at the bottom – there’s usually a bunch of interesting follow-up discussion.
Also this post has a lot of CSS so it might not work well in a feed reader.
Now! Here are the polls! I’m mostly just going to post the results without commenting on them.
merge and rebase
poll: what's your approach to merge commits and rebase in git?
merge conflicts
poll: if you use git, how often do you deal with nontrivial merge conflicts? (like where 2 people were really editing the same code at the same time and you need to take time to think about how to reconcile the edits)
another merge conflict poll:
have you ever seen a bug in production caused by an incorrect merge conflict resolution? I've heard about this as a reason to prefer merges over rebase (because it makes the merge conflict resolution easier to audit) and I'm curious about how common it is
I thought it was interesting in the next one that “edit the weird text file by hand” was most people’s preference:
poll: when you have a merge conflict, how do you prefer to handle it?
merge conflict follow up: if you prefer to edit the weird text file by hand instead of using a dedicated merge conflict tool, why is that?
poll: did you know that in a git merge conflict, the order of the code is different when you do a merge/rebase?
merge:
<<<<<<< HEAD
YOUR CODE
=======
OTHER BRANCH'S CODE
>>>>>>> c694cf8aabe
rebase:
<<<<<<< HEAD
OTHER BRANCH'S CODE
=======
YOUR CODE
>>>>>>> d945752 (your commit message)
(where "YOUR CODE" is the code from the branch you were on when you ran `git merge` or `git rebase`)
git pull
poll: do you prefer `git fetch` or `git pull`?
(no lectures about why you think `git pull` is bad please but if you use both I'd be curious to hear in what cases you use fetch!)
commits
[poll] how do you think of a git commit?
(sorry, you can't pick “it’s all 3”, I'm curious about which one feels most true to you)
branches
poll: how do you think about git branches? (I'll put an image in a reply with pictures for the 3 options)
as with all of these polls obviously all 3 are valid, I'm curious which one feels the most true to you
git environment
poll: do you put your current git branch in your shell prompt?
poll: do you use git on the command line or in a GUI?
(you can pick more than one option if it’s a mix of both, sorry magit users I didn't have space for you in this poll)
losing work
poll: have you lost work because of a git problem in the last year or two? (it counts even if it was "your fault" :))
meaning of various git terms
These polls gave me the impression that for a lot of git terms (fast-forward, reference, HEAD), there are a lot of git users who have “literally no idea” what they mean. That makes me want to be careful about using and defining those terms.
poll: how confident do you feel that you know what HEAD means in git?
another poll: how do you think of HEAD in git?
poll: when you see this message in `git status`:
”Your branch is up to date with 'origin/main’.”
do you know that your branch may not actually be up to date with the `main` branch on the remote?
poll: how confident do you feel that you know what the term "fast-forward" means in git, for example in this error message:
`! [rejected] main -> main (non-fast-forward)`
or this one:
fatal: Not possible to fast-forward, aborting.
(I promise this is not a trick question, I'm just writing a blog post about git terminology and I'm trying to gauge how people feel about various core git terms)
poll: how confident do you feel that you know what a "ref" or "reference" is in git? (“ref” and “reference” are the same thing)
for example in this error message (from `git push`)
error: failed to push some refs to 'github.com:jvns/int-exposed'
or this one: (from `git switch mybranch`)
fatal: invalid reference: mybranch
another git terminology poll: how confident do you feel that you know what a git commit is?
(not a trick question, I'm mostly curious how this one relates to people's reported confidence about more "advanced" terms like reference/fast-forward/HEAD)
poll: in git, do you think of "detached HEAD state" and "not having any branch checked out" as being the same thing?
poll: how confident do you feel that you know what the term "current branch" means in git?
(deleted & reposted to clarify that I'm asking about the meaning of the term)
other version control systems
I occasionally hear “SVN was better than git!” but this “svn vs git” poll makes me think that’s a minority opinion. I’m much more cautious about concluding anything from the hg-vs-git poll but it does seem like some people prefer git and some people prefer Mercurial.
poll 2: if you've used both svn and git, which do you prefer?
(no replies please, i have already read 300 comments about git vs other version control systems today and they were great but i can't read more)
gonna do a short thread of git vs other version control systems polls just to get an overall vibe
poll 1: if you've used both hg and git, which do you prefer?
(no replies please though, i have already read 300 comments about git vs other version control systems today and i can't read more)
that’s all!
It’s been very fun to run all of these polls and I’ve learned a lot about how people use and think about git.
The "current branch" in git
Hello! I know I just wrote a blog post about HEAD in git, but I’ve been thinking more about what the term “current branch” means in git and it’s a little weirder than I thought.
four possible definitions for “current branch”
- It’s what’s in the file
.git/HEAD. This is how the git glossary defines it. - It’s what
git statussays on the first line - It’s what you most recently checked out with
git checkoutorgit switch - It’s what’s in your shell’s git prompt. I use fish_git_prompt so that’s what I’ll be talking about.
I originally thought that these 4 definitions were all more or less the same, but after chatting with some people on Mastodon, I realized that they’re more different from each other than I thought.
So let’s talk about a few git scenarios and how each of these definitions plays
out in each of them. I used git version 2.39.2 (Apple Git-143) for all of these experiments.
scenario 1: right after git checkout main
Here’s the most normal situation: you check out a branch.
.git/HEADcontainsref: refs/heads/maingit statussaysOn branch main- The thing I most recently checked out was:
main - My shell’s git prompt says:
(main)
In this case the 4 definitions all match up: they’re all main. Simple enough.
scenario 2: right after git checkout 775b2b399
Now let’s imagine I check out a specific commit ID (so that we’re in “detached HEAD state”).
.git/HEADcontains775b2b399fb8b13ee3341e819f2aaa024a37fa92git statussaysHEAD detached at 775b2b39- The thing I most recently checked out was
775b2b399 - My shell’s git prompt says
((775b2b39))
Again, these all basically match up – some of them have truncated the commit ID and some haven’t, but that’s it. Let’s move on.
scenario 3: right after git checkout v1.0.13
What if we’ve checked out a tag, instead of a branch or commit ID?
.git/HEADcontainsca182053c7710a286d72102f4576cf32e0dafcfbgit statussaysHEAD detached at v1.0.13- The thing I most recently checked out was
v1.0.13 - My shell’s git prompt says
((v1.0.13))
Now things start to get a bit weirder! .git/HEAD disagrees with the other 3
indicators: git status, the git prompt, and what I checked out are all the
same (v1.0.13), but .git/HEAD contains a commit ID.
The reason for this is that git is trying to help us out: commit IDs are kind
of opaque, so if there’s a tag that corresponds to the current commit, git status will show us that instead.
Some notes about this:
- If we check out the commit by its ID (
git checkout ca182053c7710a286d72) instead of by its tag, what shows up ingit statusand in my shell prompt are exactly the same – git doesn’t actually “know” that we checked out a tag. - it looks like you can find the tags matching
HEADby runninggit describe HEAD --tags --exact-match(here’s the fish git prompt code) - You can see where
git-prompt.shadded support for describing a commit by a tag in this way in commit 27c578885 in 2008. - I don’t know if it makes a difference whether the tag is annotated or not.
- If there are 2 tags with the same commit ID, it gets a little weird. For
example, if I add the tag
v1.0.12to this commit so that it’s with bothv1.0.12andv1.0.13, you can see here that my git prompt changes, and then the prompt andgit statusdisagree about which tag to display:
bork@grapefruit ~/w/int-exposed ((v1.0.12))> git status
HEAD detached at v1.0.13
(my prompt shows v1.0.12 and git status shows v1.0.13)
scenario 4: in the middle of a rebase
Now: what if I check out the main branch, do a rebase, but then there was a
merge conflict in the middle of the rebase? Here’s the situation:
.git/HEADcontainsc694cf8aabe2148b2299a988406f3395c0461742(the commit ID of the commit that I’m rebasing onto,origin/mainin this case)git statussaysinteractive rebase in progress; onto c694cf8- The thing I most recently checked out was
main - My shell’s git prompt says
(main|REBASE-i 1/1)
Some notes about this:
- I think that in some sense the “current branch” is
mainhere – it’s what I most recently checked out, it’s what we’ll go back to after the rebase is done, and it’s where we’d go back to if I rungit rebase --abort - in another sense, we’re in a detached HEAD state at
c694cf8aabe2. But it doesn’t have the usual implications of being in “detached HEAD state” – if you make a commit, it won’t get orphaned! Instead, assuming you finish the rebase, it’ll get absorbed into the rebase and put somewhere in the middle of your branch. - it looks like during the rebase, the old “current branch” (
main) is stored in.git/rebase-merge/head-name. Not totally sure about this though.
scenario 5: right after git init
What about when we create an empty repository with git init?
.git/HEADcontainsref: refs/heads/maingit statussaysOn branch main(and “No commits yet”)- The thing I most recently checked out was, well, nothing
- My shell’s git prompt says:
(main)
So here everything mostly lines up, except that we’ve never run git checkout or git switch. Basically Git automatically switches to whatever
branch was configured in init.defaultBranch.
scenario 6: a bare git repository
What if we clone a bare repository with git clone --bare https://github.com/rbspy/rbspy?
HEADcontainsref: refs/heads/maingit statussaysfatal: this operation must be run in a work tree- The thing I most recently checked out was, well, nothing,
git checkoutdoesn’t even work in bare repositories - My shell’s git prompt says:
(BARE:main)
So #1 and #4 match (they both agree that the current branch is “main”), but git status and git checkout don’t even work.
Some notes about this one:
- I think
HEADin a bare repository mainly only really affects 1 thing: it’s the branch that gets checked out when you clone the repository. It’s also used when you rungit log. - if you really want to, you can update
HEADin a bare repository to a different branch withgit symbolic-ref HEAD refs/heads/whatever. I’ve never needed to do that though and it seems weird becausegit symbolic refdoesn’t check if the thing you’re pointingHEADat is actually a branch that exists. Not sure if there’s a better way.
all the results
Here’s a table with all of the results:
.git/HEAD |
git status | checked out | prompt | |
|---|---|---|---|---|
1. checkout main |
ref: refs/heads/main |
On branch main |
main | (main) |
2. checkout 775b2b |
775b2b399... |
HEAD detached at 775b2b39 |
775b2b399 | ((775b2b39)) |
3. checkout v1.0.13 |
ca182053c... |
HEAD detached at v1.0.13 |
v1.0.13 | ((v1.0.13)) |
| 4. inside rebase | c694cf8aa... |
interactive rebase in progress; onto c694cf8 |
main | (main|REBASE-i 1/1) |
5. after git init |
ref: refs/heads/main |
On branch main |
n/a | (main) |
| 6. bare repository | ref: refs/heads/main |
fatal: this operation must be run in a work tree |
n/a | (BARE:main) |
“current branch” doesn’t seem completely well defined
My original instinct when talking about git was to agree with the git glossary
and say that HEAD and the “current branch” mean the exact same thing.
But this doesn’t seem as ironclad as I used to think anymore! Some thoughts:
.git/HEADis definitely the one with the most consistent format – it’s always either a branch or a commit ID. The others are all much messier- I have a lot more sympathy than I used to for the definition “the current branch is whatever you last checked out”. Git does a lot of work to remember which branch you last checked out (even if you’re currently doing a bisect or a merge or something else that temporarily moves HEAD off of that branch) and it feels weird to ignore that.
git statusgives a lot of helpful context – these 5 status messages say a lot more than just whatHEADis set to currentlyon branch mainHEAD detached at 775b2b39HEAD detached at v1.0.13interactive rebase in progress; onto c694cf8on branch main, no commits yet
some more “current branch” definitions
I’m going to try to collect some other definitions of the term current branch that I heard from people on Mastodon here and write some notes on them.
- “the branch that would be updated if i made a commit”
- Most of the time this is the same as
.git/HEAD - Arguably if you’re in the middle of a rebase, it’s different from
HEAD, because ultimately that new commit will end up on the branch in.git/rebase-merge/head-name
- “the branch most git operations work against”
- This is sort of the same as what’s in
.git/HEAD, except that some operations (likegit status) will behave differently in some situations, like howgit statuswon’t tell you the current branch if you’re in a bare repository
on orphaned commits
One thing I noticed that wasn’t captured in any of this is whether the
current commit is orphaned or not – the git status message (HEAD detached from c694cf8) is the same whether or not your current commit is
orphaned.
I imagine this is because figuring out whether or not a given commit is
orphaned might take a long time in a large repository: you can find out if
the current commit is orphaned with git branch --contains HEAD, and that
command takes about 500ms in a repository with 70,000 commits.
Git will warn you if the commit is orphaned (“Warning: you are leaving 1 commit behind, not connected to any of your branches…”) when you switch to a different branch though.
that’s all!
I don’t have anything particularly smart to say about any of this. The more I think about git the more I can understand why people get confused.
How HEAD works in git
Hello! The other day I ran a Mastodon poll asking people how confident they were that they understood how HEAD works in Git. The results (out of 1700 votes) were a little surprising to me:
- 10% “100%”
- 36% “pretty confident”
- 39% “somewhat confident?”
- 15% “literally no idea”
I was surprised that people were so unconfident about their understanding –
I’d been thinking of HEAD as a pretty straightforward topic.
Usually when people say that a topic is confusing when I think it’s not, the
reason is that there’s actually some hidden complexity that I wasn’t
considering. And after some follow up conversations, it turned out that HEAD
actually was a bit more complicated than I’d appreciated!
Here’s a quick table of contents:
- HEAD is actually a few different things
- the file .git/HEAD
- HEAD as in git show HEAD
- next: all the output formats
HEAD is actually a few different things
After talking to a bunch of different people about HEAD, I realized that
HEAD actually has a few different closely related meanings:
- The file
.git/HEAD HEADas ingit show HEAD(git calls this a “revision parameter”)- All of the ways git uses
HEADin the output of various commands (<<<<<<<<<<HEAD,(HEAD -> main),detached HEAD state,On branch main, etc)
These are extremely closely related to each other, but I don’t think the relationship is totally obvious to folks who are starting out with git.
the file .git/HEAD
Git has a very important file called .git/HEAD. The way this file works is that it contains either:
- The name of a branch (like
ref: refs/heads/main) - A commit ID (like
96fa6899ea34697257e84865fefc56beb42d6390)
This file is what determines what your “current branch” is in Git. For example, when you run git status and see this:
$ git status
On branch main
it means that the file .git/HEAD contains ref: refs/heads/main.
If .git/HEAD contains a commit ID instead of a branch, git calls that
“detached HEAD state”. We’ll get to that later.
(People will sometimes say that HEAD contains a name of a reference or a
commit ID, but I’m pretty sure that that the reference has to be a branch.
You can technically make .git/HEAD contain the name of a reference that
isn’t a branch by manually editing .git/HEAD, but I don’t think you can do it
with a regular git command. I’d be interested to know if there is a
regular-git-command way to make .git/HEAD a non-branch reference though, and if
so why you might want to do that!)
HEAD as in git show HEAD
It’s very common to use HEAD in git commands to refer to a commit ID, like:
git diff HEADgit rebase -i HEAD^^^^git diff main..HEADgit reset --hard HEAD@{2}
All of these things (HEAD, HEAD^^^, HEAD@{2}) are called “revision parameters”. They’re documented in man
gitrevisions, and Git will try to
resolve them to a commit ID.
(I’ve honestly never actually heard the term “revision parameter” before, but that’s the term that’ll get you to the documentation for this concept)
HEAD in git show HEAD has a pretty simple meaning: it resolves to the
current commit you have checked out! Git resolves HEAD in one of two ways:
- if
.git/HEADcontains a branch name, it’ll be the latest commit on that branch (for example by reading it from.git/refs/heads/main) - if
.git/HEADcontains a commit ID, it’ll be that commit ID
next: all the output formats
Now we’ve talked about the file .git/HEAD, and the “revision parameter”
HEAD, like in git show HEAD. We’re left with all of the various ways git
uses HEAD in its output.
git status: “on branch main” or “HEAD detached”
When you run git status, the first line will always look like one of these two:
on branch main. This means that.git/HEADcontains a branch.HEAD detached at 90c81c72. This means that.git/HEADcontains a commit ID.
I promised earlier I’d explain what “HEAD detached” means, so let’s do that now.
detached HEAD state
“HEAD is detached” or “detached HEAD state” mean that you have no current branch.
Having no current branch is a little dangerous because if you make new commits, those commits won’t be attached to any branch – they’ll be orphaned! Orphaned commits are a problem for 2 reasons:
- the commits are more difficult to find (you can’t run
git log somebranchto find them) - orphaned commits will eventually be deleted by git’s garbage collection
Personally I’m very careful about avoiding creating commits in detached HEAD state, though some people prefer to work that way. Getting out of detached HEAD state is pretty easy though, you can either:
- Go back to a branch (
git checkout main) - Create a new branch at that commit (
git checkout -b newbranch) - If you’re in detached HEAD state because you’re in the middle of a rebase, finish or abort the rebase (
git rebase --abort)
Okay, back to other git commands which have HEAD in their output!
git log: (HEAD -> main)
When you run git log and look at the first line, you might see one of the following 3 things:
commit 96fa6899ea (HEAD -> main)commit 96fa6899ea (HEAD, main)commit 96fa6899ea (HEAD)
It’s not totally obvious how to interpret these, so here’s the deal:
- inside the
(...), git lists every reference that points at that commit, for example(HEAD -> main, origin/main, origin/HEAD)meansHEAD,main,origin/main, andorigin/HEADall point at that commit (either directly or indirectly) HEAD -> mainmeans that your current branch ismain- If that line says
HEAD,instead ofHEAD ->, it means you’re in detached HEAD state (you have no current branch)
if we use these rules to explain the 3 examples above: the result is:
commit 96fa6899ea (HEAD -> main)means:.git/HEADcontainsref: refs/heads/main.git/refs/heads/maincontains96fa6899ea
commit 96fa6899ea (HEAD, main)means:.git/HEADcontains96fa6899ea(HEAD is “detached”).git/refs/heads/mainalso contains96fa6899ea
commit 96fa6899ea (HEAD)means:.git/HEADcontains96fa6899ea(HEAD is “detached”).git/refs/heads/maineither contains a different commit ID or doesn’t exist
merge conflicts: <<<<<<< HEAD is just confusing
When you’re resolving a merge conflict, you might see something like this:
<<<<<<< HEAD
def parse(input):
return input.split("\n")
=======
def parse(text):
return text.split("\n\n")
>>>>>>> somebranch
I find HEAD in this context extremely confusing and I basically just ignore it. Here’s why.
- When you do a merge,
HEADin the merge conflict is the same as whatHEADwas when you rangit merge. Simple. - When you do a rebase,
HEADin the merge conflict is something totally different: it’s the other commit that you’re rebasing on top of. So it’s totally different from whatHEADwas when you rangit rebase. It’s like this because rebase works by first checking out the other commit and then repeatedly cherry-picking commits on top of it.
Similarly, the meaning of “ours” and “theirs” are flipped in a merge and rebase.
The fact that the meaning of HEAD changes depending on whether I’m doing a
rebase or merge is really just too confusing for me and I find it much simpler
to just ignore HEAD entirely and use another method to figure out which part
of the code is which.
some thoughts on consistent terminology
I think HEAD would be more intuitive if git’s terminology around HEAD were a little more internally consistent.
For example, git talks about “detached HEAD state”, but never about “attached
HEAD state” – git’s documentation never uses the term “attached” at all to
refer to HEAD. And git talks about being “on” a branch, but never “not on” a
branch.
So it’s very hard to guess that on branch main is actually the opposite of
HEAD detached. How is the user supposed to guess that HEAD detached has
anything to do with branches at all, or that “on branch main” has anything to
do with HEAD?
that’s all!
If I think of other ways HEAD is used in Git (especially ways HEAD appears in
Git’s output), I might add them to this post later.
If you find HEAD confusing, I hope this helps a bit!
Popular git config options
Hello! I always wish that command line tools came with data about how popular their various options are, like:
- “basically nobody uses this one”
- “80% of people use this, probably take a look”
- “this one has 6 possible values but people only really use these 2 in practice”
So I asked about people’s favourite git config options on Mastodon:
what are your favourite git config options to set? Right now I only really have
git config push.autosetupremote trueandgit config init.defaultBranch mainset in my~/.gitconfig, curious about what other people set
As usual I got a TON of great answers and learned about a bunch of very popular git config options that I’d never heard of.
I’m going to list the options, starting with (very roughly) the most popular ones. Here’s a table of contents:
- pull.ff only or pull.rebase true
- merge.conflictstyle zdiff3
- rebase.autosquash true
- rebase.autostash true
- push.default simple, push.default current
- init.defaultBranch main
- commit.verbose true
- rerere.enabled true
- help.autocorrect 10
- core.pager delta
- diff.algorithm histogram
- core.excludesfile ~/.gitignore
- includeIf: separate git configs for personal and work
- fsckobjects: avoid data corruption
- submodule stuff
- and more
- how to set these
- config changes I’ve made after writing this post
All of the options are documented in man git-config, or this page.
pull.ff only or pull.rebase true
These two were the most popular. These both have similar goals: to avoid accidentally creating a merge commit
when you run git pull on a branch where the upstream branch has diverged.
pull.rebase trueis the equivalent of runninggit pull --rebaseevery time yougit pullpull.ff onlyis the equivalent of runninggit pull --ff-onlyevery time yougit pull
I’m pretty sure it doesn’t make sense to set both of them at once, since --ff-only
overrides --rebase.
Personally I don’t use either of these since I prefer to decide how to handle
that situation every time, and now git’s default behaviour when your branch has
diverged from the upstream is to just throw an error and ask you what to do
(very similar to what git pull --ff-only does).
merge.conflictstyle zdiff3
Next: making merge conflicts more readable! merge.conflictstyle zdiff3 and merge.conflictstyle diff3 were both super popular (“totally indispensable”).
The main idea is The consensus seemed to be “diff3 is great, and zdiff3 (which is newer) is even better!”.
So what’s the deal with diff3. Well, by default in git, merge conflicts look like this:
<<<<<<< HEAD
def parse(input):
return input.split("\n")
=======
def parse(text):
return text.split("\n\n")
>>>>>>> somebranch
I’m supposed to decide whether input.split("\n") or text.split("\n\n") is
better. But how? What if I don’t remember whether \n or \n\n is right? Enter diff3!
Here’s what the same merge conflict look like with merge.conflictstyle diff3 set:
<<<<<<< HEAD
def parse(input):
return input.split("\n")
||||||| b9447fc
def parse(input):
return input.split("\n\n")
=======
def parse(text):
return text.split("\n\n")
>>>>>>> somebranch
This has extra information: now the original version of the code is in the middle! So we can see that:
- one side changed
\n\nto\n - the other side renamed
inputtotext
So presumably the correct merge conflict resolution is return text.split("\n"), since that combines the changes from both sides.
I haven’t used zdiff3, but a lot of people seem to think it’s better. The blog post Better Git Conflicts with zdiff3 talks more about it.
rebase.autosquash true
Autosquash was also a new feature to me. The goal is to make it easier to modify old commits.
Here’s how it works:
- You have a commit that you would like to be combined with some commit that’s 3 commits ago, say
add parsing code - You commit it with
git commit --fixup OLD_COMMIT_ID, which gives the new commit the commit messagefixup! add parsing code - Now, when you run
git rebase --autosquash main, it will automatically combine all thefixup!commits with their targets
rebase.autosquash true means that --autosquash always gets passed automatically to git rebase.
rebase.autostash true
This automatically runs git stash before a git rebase and git stash pop after. It basically passes --autostash to git rebase.
Personally I’m a little scared of this since it potentially can result in merge conflicts after the rebase, but I guess that doesn’t come up very often for people since it seems like a really popular configuration option.
push.default simple, push.default current, push.autoSetupRemote true
These push options tell git push to automatically push the current branch to a remote branch with the same name.
push.default simpleis the default in Git. It only works if your branch is already tracking a remote branchpush.default currentis similar, but it’ll always push the local branch to a remote branch with the same name.push.autoSetupRemote trueis a little different – this one makes it so when you first push a branch, it’ll automatically set up tracking for it
I think I prefer push.autoSetupRemote true to push.default current because
push.autoSetupRemote true also lets you pull from the matching remote
branch (though you do need to push first to set up tracking). push.default current only lets you push.
I believe the only thing to be careful of with push.autoSetupRemote true and
push.default current is that you need to be confident that you’re never going
to accidentally make a local branch with the same name as an unrelated remote
branch. Lots of people have branch naming conventions (like julia/my-change)
that make this kind of conflict very unlikely, or just have few enough
collaborators that branch name conflicts probably won’t happen.
init.defaultBranch main
Create a main branch instead of a master branch when creating a new repo.
commit.verbose true
This adds the whole commit diff in the text editor where you’re writing your commit message, to help you remember what you were doing.
rerere.enabled true
This enables rerere ("reuse recovered resolution"), which remembers how you resolved merge conflicts
during a git rebase and automatically resolves conflicts for you when it can.
help.autocorrect 10
By default git’s autocorrect try to check for typos (like git ocmmit), but won’t actually run the corrected command.
If you want it to run the suggestion automatically, you can set
help.autocorrect
to 1 (run after 0.1 seconds), 10 (run after 1 second), immediate (run
immediately), or prompt (run after prompting)
core.pager delta
The “pager” is what git uses to display the output of git diff, git log, git show, etc. People set it to:
delta(a fancy diff viewing tool with syntax highlighting)less -x5,9(sets tabstops, which I guess helps if you have a lot of files with tabs in them?)less -F -X(not sure about this one,-Fseems to disable the pager if everything fits on one screen if but my git seems to do that already anyway)cat(to disable paging altogether)
I used to use delta but turned it off because somehow I messed up the colour
scheme in my terminal and couldn’t figure out how to fix it. I think it’s a
great tool though.
I believe delta also suggests that you set up interactive.diffFilter delta --color-only to syntax highlight code when you run git add -p.
diff.algorithm histogram
Git’s default diff algorithm often handles functions being reordered badly. For example look at this diff:
-.header {
+.footer {
margin: 0;
}
-.footer {
+.header {
margin: 0;
+ color: green;
}
I find it pretty confusing. But with diff.algorithm histogram, the diff looks like this instead, which I find much clearer:
-.header {
- margin: 0;
-}
-
.footer {
margin: 0;
}
+.header {
+ margin: 0;
+ color: green;
+}
Some folks also use patience, but histogram seems to be more popular. When to Use Each of the Git Diff Algorithms has more on this.
core.excludesfile: a global .gitignore
core.excludeFiles = ~/.gitignore lets you set a global gitignore file that
applies to all repositories, for things like .idea or .DS_Store that you
never want to commit to any repo. It defaults to ~/.config/git/ignore.
includeIf: separate git configs for personal and work
Lots of people said they use this to configure different email addresses for personal and work repositories. You can set it up something like this:
[includeIf "gitdir:~/code/<work>/"]
path = "~/code/<work>/.gitconfig"
url."git@github.com:".insteadOf 'https://github.com/'
I often accidentally clone the HTTP version of a repository instead of the
SSH version and then have to manually go into ~/.git/config and edit the
remote URL. This seems like a nice workaround: it’ll replace
https://github.com in remotes with git@github.com:.
Here’s what it looks like in ~/.gitconfig since it’s kind of a mouthful:
[url "git@github.com:"]
insteadOf = "https://github.com/"
One person said they use pushInsteadOf instead to only do the replacement for
git push because they don’t want to have to unlock their SSH key when
pulling a public repo.
A couple of other people mentioned setting insteadOf = "gh:" so they can git remote add gh:jvns/mysite to add a remote with less typing.
fsckobjects: avoid data corruption
A couple of people mentioned this one. Someone explained it as “detect data corruption eagerly. Rarely matters but has saved my entire team a couple times”.
transfer.fsckobjects = true
fetch.fsckobjects = true
receive.fsckObjects = true
submodule stuff
I’ve never understood anything about submodules but a couple of person said they like to set:
status.submoduleSummary truediff.submodule logsubmodule.recurse true
I won’t attempt to explain those but there’s an explanation on Mastodon by @unlambda here.
and more
Here’s everything else that was suggested by at least 2 people:
blame.ignoreRevsFile .git-blame-ignore-revslets you specify a file with commits to ignore duringgit blame, so that giant renames don’t mess up your blamesbranch.sort -committerdate, makesgit branchsort by most recently used branches instead of alphabetical, to make it easier to find branches.tag.sort taggerdateis similar for tags.color.ui false: to turn off colourcommit.cleanup scissors: so that you can write#includein a commit message without the#being treated as a comment and removedcore.autocrlf false: on Windows, to work well with folks using Unixcore.editor emacs: to use emacs (or another editor) to edit commit messagescredential.helper osxkeychain: use the Mac keychain for managingdiff.tool difftastic: use difftastic (ormeldornvimdiffs) to display diffsdiff.colorMoved default: uses different colours to highlight lines in diffs that have been “moved”diff.colorMovedWS allow-indentation-change: withdiff.colorMovedset, also ignores indentation changesdiff.context 10: include more context in diffsfetch.prune trueandfetch.prunetags- automatically delete remote tracking branches that have been deletedgpg.format ssh: allow you to sign commits with SSH keyslog.date iso: display dates as2023-05-25 13:54:51instead ofThu May 25 13:54:51 2023merge.keepbackup false, to get rid of the.origfiles git creates during a merge conflictmerge.tool meld(ornvim, ornvimdiff) so that you can usegit mergetoolto help resolve merge conflictspush.followtags true: push new tags along with commits being pushedrebase.missingCommitsCheck error: don’t allow deleting commits during a rebaserebase.updateRefs true: makes it much easier to rebase multiple stacked branches at a time. Here’s a blog post about it.
how to set these
I generally set git config options with git config --global NAME VALUE, for
example git config --global diff.algorithm histogram. I usually set all of my
options globally because it stresses me out to have different git behaviour in
different repositories.
If I want to delete an option I’ll edit ~/.gitconfig manually, where they look like this:
[diff]
algorithm = histogram
config changes I’ve made after writing this post
My git config is pretty minimal, I already had:
init.defaultBranch mainpush.autoSetupRemote truemerge.tool melddiff.colorMoved default(which actually doesn’t even work for me for some reason but I haven’t found the time to debug)
and I added these 3 after writing this blog post:
diff.algorithm histogrambranch.sort -committerdatemerge.conflictstyle zdiff3
I’d probably also set rebase.autosquash if making carefully crafted pull
requests with multiple commits were a bigger part of my life right now.
I’ve learned to be cautious about setting new config options – it takes me a
long time to get used to the new behaviour and if I change too many things at
once I just get confused. branch.sort -committerdate is something I was
already using anyway (through an alias), and I’m pretty sold that diff.algorithm histogram will make my diffs easier to read when I reorder functions.
that’s all!
I’m always amazed by how useful to just ask a lot of people what stuff they like and then list the most commonly mentioned ones, like with this list of new-ish command line tools I put together a couple of years ago. Having a list of 20 or 30 options to consider feels so much more efficient than combing through a list of all 600 or so git config options
It was a little confusing to summarize these because git’s default options have actually changed a lot of the years, so people occasionally have options set that were important 8 years ago but today are the default. Also a couple of the experimental options people were using have been removed and replaced with a different version.
I did my best to explain things accurately as of how git works right now in 2024 but I’ve definitely made mistakes in here somewhere, especially because I don’t use most of these options myself. Let me know on Mastodon if you see a mistake and I’ll try to fix it.
I might also ask people about aliases later, there were a bunch of great ones that I left out because this was already getting long.