For example, when I was first learning about git, I decided for some silly reason to make a git repo to store all of my little experiments—20 lines of python or matlab type stuff—just to have them all in one place. The main branch basically just has a README to remind me why the directory is empty, and to look at the other branches.
This is a very silly way to use git, I think, because they aren’t really branches of one coherent project. Diffing and merging them is useless. If they produce temporary output files, they all do it in totally non-standard ways that have no relation to one another. There really is no value to collecting them together. But it is possible, of course, because git can point to anything! Thankfully it is hiding on my hard-drive, hopefully nobody will ever see it other than me.
All of these phrases reinforce the common misconception that branches are “containers” for commits.
It would seem that you could query, “oh, what branch is this commit on?” But that’s not how git works. A commit has no idea what branch it is on, and that’s unintuitive. The only definition of being “on a branch” is, starting at the commit that the branch is currently pointing to, all ancestors of that commit are on the branch.
Having to walk the commit ancestry to see if a commit is on a branch doesn't make it not true.
Even despite knowing that's not how they work in git, it's usually easier for me to treat them that way.
A branch is a DAG of commits terminating at one commit.
They should have just called it a pointer if it's a pointer. But here we are anyway.
The branch, like the tag, is a single reference to a commit.
Do this:
$ cat .git/refs/heads/main(Also, note the path you ask us to cat does not contain the word "branch". You refer to how things are stored to support your definition, but the way things are stored doesn't use the term "branch".)
Plus that definition describes both tags and branches, how do you tell them apart? It’s clearly lacking. Actually that definition fits commits too.
I’d rather use these:
Branch: a pointer that advances
Tag: a permanent pointer
Commit/hash: an even more permanent pointer (because even a force can’t change it) that also describes the changes.
That said, it only makes sense to make such implicit mental shortcuts once you're already comfortable with git's data model. If you're not and you're still learning it, it's only going to confuse you.
The “dots on the line” visualization is pretty clear: you can have any number of branches pointing to a specific dot/commit. How do you add “branches” without fundamentally breaking that concept? When you create a branch, you’re not copying the line, you’re just adding a pointer.
Because that is true; that is accurate. Thus, I don't understand your original claim that branches work in an unintuitive way.
To put this in programming terms, a git branch is a pointer. I think of a branch as the value is the most natural way to think about it.
This mismatch is what leads to endless tutorials on what git actually means when it uses its vocabulary.
One problem I'm trying to express with that question is: You say it's unintuitive, but when asked why you refer to a very low level technical detail.
My suspicion is that people find git branches unintuitive, and thus they think something more intuitive must exist. They have vague ideas about what "more intuitive" might mean, usually it means "just do what I want in this one situation I'm currently facing". My theory is that, if pressed, nobody can fully define a behavior for branches that is more intuitive than the current git implementation.
It shouldn't be controversial to say the porcelain can and should be much more straightforward to use.
DVCS concepts like the DAG and where you want to apply diffs are complicated the first time around, but after you get the concepts it should be easy to use, not difficult.
Mercurial's branches are a better match for how I've seen branches use in practice. Tags are essentially the same between both. Evolve's hidden commits make it easier to recover lost data than Git's reflog, though the underlying implementation not permitting garbage collection kind of sucks.
The branch name is a pointer. All pointers (commits) are also branches of the DAG, just anonymous. Orphaned commits (i.e., not reachable from any named branch or tag) can be garbage collected (deleted).
The point is that raw pointer values do not make for user-friendly interfaces. Humans deal well with names and poorly with numbers. Thus we need names. Every VCS in the world supports branch names. The issue here is that Git's default rules for branches are shockingly simple for users coming from Mercurial and other VCSes, but those default rules are also the simplest possible default rules and you can add rules on top.
Like you, I have no problem working with the raw pointer values some of the time, so I often work in detached-HEAD mode, though in the end I have to always name commits I don't want to lose to GC and that I want to be able to find in the future -- in the end I still need names for commits, I still need named branches and tags.
[Pedantically] this includes orphaned branches.
> They should have just called it a pointer if it's a pointer.
A branch in every VCS is first and foremost a name. That name has to evaluate to a) content, b) history. In Git and Git-like immutable-commit systems this can trivially be implemented by a table mapping branch (and tag and...) names to commits. I.e., in any immutable-commit VCS branches are naturally pointers.
The only real question is whether there should be additional semantics layered upon the pointer nature of branches. Indeed, there should be. In Git these are the basic rules:
- branch names are updated when adding commits in a workspace that has that branch checked out
- tag names are NOT updated when adding commits when adding commits in a workspace that has that tag checked out
- local configuration can enforce a variety of rules via commit hooks
- upstreams can enforce a variety of rules, such as fast-forward-only (no history rewriting)
This gives users a great deal of flexibility, including the ability to ban history rewriting on published branches and to allow it on unpublished branches.