3 ms·
For Mercurial, you need to totally abandon the idea that a branch is stored by reffing a commit, and the branch is implicitly defined as the ancestor commits of
by quietbritishjim 8y ago
For Mercurial, you need to totally abandon the idea that a branch is stored by reffing a commit, and the branch is implicitly defined as the ancestor commits of that head. Instead, every single commit has a branch name baked into it as a simple string; a branch is defined as all commits whose branch name matches that name. To open a new branch, simply make a commit with a branch name that hasn't been used before. By default, the branch name of a commit is the same as its immediate ancestor.
When you switch branch in Mercurial, in principle it simply looks through all commits to determine the latest one with that branch name. In practice I'm sure there is a cache of this, which is the closest there is to git refs, but that is totally transparent. All commits must have a branch name, and the first commit’s branch name is usually "default" (equivalent to git's "master"). That close branch feature you mentioned is a special type of commit that stops that branch from being included in the list of branches, but the branch still exists because all previous commits to it still do.
This has a few consequences compared to Git's branches, which may be good or bad depending on the situation and your personal opinion. One is that a commit cannot be a member of more than one branch. Another is that branches are far less mutable than they are in Git; to change the branch that a commit is in you must actually destroy and re-create that commit (e.g. with rebase).
Another consequence is that it is fine for a branch to have multiple heads; just go back to a slightly older revision and commit again. Or, more commonly, pull after making your own commits and find someone else has committed to the same branch. (If you prefer: find that someone else has made some commits with the same "branch name" metadata.) Usually this situation is only transient because you would merge or rebase. Indeed, you cannot create multiple heads for a branch on a remote repo unless you force push, so you'd normally merge/rebase before pushing.
Another bit of commit metadata is state: public, draft or secret. Commits are draft when you create them, essentially meaning "not pushed". Public commits are ones that have been pushed to or pulled from a remote repo. In git, to see what things look like on the remote, you’d use the remote ref; in Mercurial, you’d look at the public commits. Admittedly you lose the ability to understand which remote repo a commit was seen on, whereas git supports multiple remotes. (During a push, Mercurial will still check whether “public” commits are on a remote repo and push them if necessary.) You can mark a commit as “secret” and then it will not be pushed unless you change its state back. This is what I use when I commit something for my own reference later on, which I was talking about in my previous comment. Often when I rebase, I leave the original commits behind and mark them as secret in case something goes wrong.
I can see you think branches are "something special" in Mercurial. Maybe in Git you could use refs to implement something totally different to branches, whereas in Mercurial you're stuck with that exact implementation. But refs seem like an unnecessary implementation detail to me. I never want to implement something similar but not quite the same as branches; I just want branches to work! And by not having any refs whatsoever the system seems quite a bit simpler.
- chousuke 8y agoThanks for the explanation. I don't think it's a good idea, but it does clarify some things. In git, a branch is a branch by virtue of being an actual physical branch in the data structure. To elaborate, I think a branch having multiple heads is not really a good thing, since that leads to the actual structure being one or more branches (each point of divergence results in a branch in the commit lineage), but only one name to refer to the collection of commits making up those branches. That abstraction doesn't agree with me. Since a branch (a lineage of commits) is unambiguously defined by its head commit, having a named ref as the branch abstraction makes more sense to me. I don't know how it could be any simpler.