diff --git a/sources/tech/20231123 git branches- intuition - reality.md b/sources/tech/20231123 git branches- intuition - reality.md new file mode 100644 index 0000000000..a8713ca604 --- /dev/null +++ b/sources/tech/20231123 git branches- intuition - reality.md @@ -0,0 +1,302 @@ +[#]: subject: "git branches: intuition & reality" +[#]: via: "https://jvns.ca/blog/2023/11/23/branches-intuition-reality/" +[#]: author: "Julia Evans https://jvns.ca/" +[#]: collector: "lujun9972/lctt-scripts-1700446145" +[#]: translator: " " +[#]: reviewer: " " +[#]: publisher: " " +[#]: url: " " + +git branches: intuition & reality +====== + +Hello! I’ve been working on writing a zine about git so I’ve been thinking about git branches a lot. I keep hearing from people that they find the way git branches work to be counterintuitive. It got me thinking: what might an “intuitive” notion of a branch be, and how is it different from how git actually works? + +So in this post I want to briefly talk about + + * an intuitive mental model I think many people have + * how git actually represents branches internally (“branches are a pointer to a commit” etc) + * how the “intuitive model” and the real way it works are actually pretty closely related + * some limits of the intuitive model and why it might cause problems + + + +Nothing in this post is remotely groundbreaking so I’m going to try to keep it pretty short. + +### an intuitive model of a branch + +Of course, people have many different intuitions about branches. Here’s the one that I think corresponds most closely to the physical “a branch of an apple tree” metaphor. + +My guess is that a lot of people think about a git branch like this: the 2 commits in pink in this picture are on a “branch”. + +![][1] + +I think there are two important things about this diagram: + + 1. the branch has 2 commits on it + 2. the branch has a “parent” (`main`) which it’s an offshoot of + + + +That seems pretty reasonable, but that’s not how git defines a branch – most importantly, git doesn’t have any concept of a branch’s “parent”. So how does git define a branch? + +### in git, a branch is the full history + +In git, a branch is the full history of every previous commit, not just the “offshoot” commits. So in our picture above both branches (`main` and `branch`) have 4 commits on them. + +I made an example repository at which has its branches set up the same way as in the picture above. Let’s look at the 2 branches: + +`main` has 4 commits on it: + +``` + + $ git log --oneline main + 70f727a d + f654888 c + 3997a46 b + a74606f a + +``` + +and `mybranch` has 4 commits on it too. The bottom two commits are shared between both branches. + +``` + + $ git log --oneline mybranch + 13cb960 y + 9554dab x + 3997a46 b + a74606f a + +``` + +So `mybranch` has 4 commits on it, not just the 2 commits `13cb960` and `9554dab` that are “offshoot” commits. + +You can get git to draw all the commits on both branches like this: + +``` + + $ git log --all --oneline --graph + * 70f727a (HEAD -> main, origin/main) d + * f654888 c + | * 13cb960 (origin/mybranch, mybranch) y + | * 9554dab x + |/ + * 3997a46 b + * a74606f a + +``` + +### a branch is stored as a commit ID + +Internally in git, branches are stored as tiny text files which have a commit ID in them. That commit is the latest commit on the branch. This is the “technically correct” definition I was talking about at the beginning. + +Let’s look at the text files for `main` and `mybranch` in our example repo: + +``` + + $ cat .git/refs/heads/main + 70f727acbe9ea3e3ed3092605721d2eda8ebb3f4 + $ cat .git/refs/heads/mybranch + 13cb960ad86c78bfa2a85de21cd54818105692bc + +``` + +This makes sense: `70f727` is the latest commit on `main` and `13cb96` is the latest commit on `mybranch`. + +The reason this works is that every commit contains a pointer to its parent(s), so git can follow the chain of pointers to get every commit on the branch. + +Like I mentioned before, the thing that’s missing here is any relationship at all between these two branches. There’s no indication that `mybranch` is an offshoot of `main`. + +Now that we’ve talked about how the intuitive notion of a branch is “wrong”, I want to talk about how it’s also right in some very important ways. + +### people’s intuition is usually not that wrong + +I think it’s pretty popular to tell people that their intuition about git is “wrong”. I find that kind of silly – in general, even if people’s intuition about a topic is technically incorrect in some ways, people usually have the intuition they do for very legitimate reasons! “Wrong” models can be super useful. + +So let’s talk about 3 ways the intuitive “offshoot” notion of a branch matches up very closely with how we actually use git in practice. + +### rebases use the “intuitive” notion of a branch + +Now let’s go back to our original picture. + +![][1] + +When you rebase `mybranch` on `main`, it takes the commits on the “intuitive” branch (just the 2 pink commits) and replays them onto `main`. + +The result is that just the 2 (`x` and `y`) get copied. Here’s what that looks like: + +``` + + $ git switch mybranch + $ git rebase main + $ git log --oneline mybranch + 952fa64 (HEAD -> mybranch) y + 7d50681 x + 70f727a (origin/main, main) d + f654888 c + 3997a46 b + a74606f a + +``` + +Here `git rebase` has created two new commits (`952fa64` and `7d50681`) whose information comes from the previous two `x` and `y` commits. + +So the intuitive model isn’t THAT wrong! It tells you exactly what happens in a rebase. + +But because git doesn’t know that `mybranch` is an offshoot of `main`, you need to tell it explicitly where to rebase the branch. + +### merges use the “intuitive” notion of a branch too + +Merges don’t copy commits, but they do need a “base” commit: the way merges work is that it looks at two sets of changes (starting from the shared base) and then merges them. + +Let’s undo the rebase we just did and then see what the merge base is. + +``` + + $ git switch mybranch + $ git reset --hard 13cb960 # undo the rebase + $ git merge-base main mybranch + 3997a466c50d2618f10d435d36ef12d5c6f62f57 + +``` + +This gives us the “base” commit where our branch branched off, `3997a4`. That’s exactly the commit you would think it might be based on our intuitive picture. + +### github pull requests also use the intuitive idea + +If we create a pull request on GitHub to merge `mybranch` into `main`, it’ll also show us 2 commits: the commits `x` and `y`. That makes sense and also matches our intuitive notion of a branch. + +![][2] + +I assume if you make a merge request on GitLab it shows you something similar. + +### intuition is pretty good, but it has some limits + +This leaves our intuitive definition of a branch looking pretty good actually! The “intuitive” idea of what a branch is matches exactly with how merges and rebases and GitHub pull requests work. + +You do need to explicitly specify the other branch when merging or rebasing or making a pull request (like `git rebase main`), because git doesn’t know what branch you think your offshoot is based on. + +But the intuitive notion of a branch has one fairly serious problem: the way you intuitively think about `main` and an offshoot branch are very different, and git doesn’t know that. + +So let’s talk about the different kinds of git branches. + +### trunk and offshoot branches + +To a human, `main` and `mybranch` are pretty different, and you probably have pretty different intentions around how you want to use them. + +I think it’s pretty normal to think of some branches as being “trunk” branches, and some branches as being “offshoots”. Also you can have an offshoot of an offshoot. + +Of course, git itself doesn’t make any such distinctions (the term “offshoot” is one I just made up!), but what kind of a branch it is definitely affects how you treat it. + +For example: + + * you might rebase `mybranch` onto `main` but you probably wouldn’t rebase `main` onto `mybranch` – that would be weird! + * in general people are much more careful around rewriting the history on “trunk” branches than short-lived offshoot branches + + + +### git lets you do rebases “backwards” + +One thing I think throws people off about git is – because git doesn’t have any notion of whether a branch is an “offshoot” of another branch, it won’t give you any guidance about if/when it’s appropriate to rebase branch X on branch Y. You just have to know. + +for example, you can do either: + +``` + + $ git checkout main + $ git rebase mybranch + +``` + +or + +``` + + $ git checkout mybranch + $ git rebase main + +``` + +Git will happily let you do either one, even though in this case `git rebase main` is extremely normal and `git rebase mybranch` is pretty weird. A lot of people said they found this confusing so here’s a picture of the two kinds of rebases: + +![][3] + +Similarly, you can do merges “backwards”, though that’s much more normal than doing a backwards rebase – merging `mybranch` into `main` and `main` into `mybranch` are both useful things to do for different reasons. + +Here’s a diagram of the two ways you can merge: + +![][4] + +### git’s lack of hierarchy between branches is a little weird + +I hear the statement “the `main` branch is not special” a lot and I’ve been puzzled about it – in most of the repositories I work in, `main` **is** pretty special! Why are people saying it’s not? + +I think the point is that even though branches **do** have relationships between them (`main` is often special!), git doesn’t know anything about those relationships. + +You have to tell git explicitly about the relationship between branches every single time you run a git command like `git rebase` or `git merge`, and if you make a mistake things can get really weird. + +I don’t know whether git’s design here is “right” or “wrong” (it definitely has some pros and cons, and I’m very tired of reading endless arguments about it), but I do think it’s surprising to a lot of people for good reason. + +### git’s UI around branches is weird too + +Let’s say you want to look at just the “offshoot” commits on a branch, which as we’ve discussed is a completely normal thing to want. + +Here’s how to see just the 2 offshoot commits on our branch with `git log`: + +``` + + $ git switch mybranch + $ git log main..mybranch --oneline + 13cb960 (HEAD -> mybranch, origin/mybranch) y + 9554dab x + +``` + +You can look at the combined diff for those same 2 commits with `git diff` like this: + +``` + + $ git diff main...mybranch + +``` + +So to see the 2 commits `x` and `y` with `git log`, you need to use 2 dots (`..`), but to look at the same commits with `git diff`, you need to use 3 dots (`...`). + +Personally I can never remember what `..` and `...` mean so I just avoid them completely even though in principle they seem useful. + +### in GitHub, the default branch is special + +Also, it’s worth mentioning that GitHub does have a “special branch”: every github repo has a “default branch” (in git terms, it’s what `HEAD` points at), which is special in the following ways: + + * it’s what you check out when you `git clone` the repository + * it’s the default destination for pull requests + * github will suggest that you protect the default branch from force pushes + + + +and probably even more that I’m not thinking of. + +### that’s all! + +This all seems extremely obvious in retrospect, but it took me a long time to figure out what a more “intuitive” idea of a branch even might be because I was so used to the technical “a branch is a reference to a commit” definition. + +I also hadn’t really thought about how git makes you tell it about the hierarchy between your branches every time you run a `git rebase` or `git merge` command – for me it’s second nature to do that and it’s not a big deal, but now that I’m thinking about it, it’s pretty easy to see how somebody could get mixed up. + +-------------------------------------------------------------------------------- + +via: https://jvns.ca/blog/2023/11/23/branches-intuition-reality/ + +作者:[Julia Evans][a] +选题:[lujun9972][b] +译者:[译者ID](https://github.com/译者ID) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://jvns.ca/ +[b]: https://github.com/lujun9972 +[1]: https://jvns.ca/images/git-branch.png +[2]: https://jvns.ca/images/gh-pr.png +[3]: https://jvns.ca/images/backwards-rebase.png +[4]: https://jvns.ca/images/merge-two-ways.png