Mercurial/Git flow - When are new heads created? - mercurial

We have a course where we study Mercurial. I have a question regarding Mercurial but I believe Git acts the exact same way here.
To answer this question I used the Mercurial Workbench and followed the graph after each command.
This is the question:
For the following sequence of mercurial operations, explain which line(s)
cause the number of heads to change in any of the referenced repositories (say which
repository is affected, and why). Assume the main repository initially has a single
head, with some existing data:
(I have bolded what I think are lines where new heads are headed)
/home/user> hg clone http://remoteserver/mainrepository clone1
/home/user> hg clone http://remoteserver/mainrepository clone2
/home/user> cd clone1
/home/user/clone1> hg tag initial
/home/user/clone1> echo one > a.txt # Create a new file “a.txt” containing “one”
/home/user/clone1> echo two > b.txt # Create a new file “b.txt” containing “two”
/home/user/clone1> hg add a.txt
/home/user/clone1> hg commit -m ”Added a file”
/home/user/clone1> hg tag file-one
/home/user/clone1> hg push -f ../clone2
/home/user/clone1> cd ../clone2
/home/user/clone2> echo three > c.txt # Create a new file “c.txt” containing
“three”
/home/user/clone2> hg add c.txt
/home/user/clone2> hg commit -m ”Added another file”
/home/user/clone2> hg push -f ../clone1
/home/user/clone2> cd ../clone1
/home/user/clone1> hg up initial
/home/user/clone1> hg add b.txt
/home/user/clone1> hg commit -m ”Added yet another file”
/home/user/clone1> hg up file-one
/home/user/clone1> hg push -f ../clone2
I came to this conclusion:
on row 14, a new head is created on clone2
on row 15, a new head is created on clone1
on row 19, a new head is created on clone1
on row 21, a new head is created on clone2
We end up with 3 heads in clone1 and 3 heads in clone2.
My question is:
Is this correct?
Can you explain to me what happens at each step a new row is added?
I tried to come up with my own reasons but I need an expert opinion on this.
Thank you

For (1): yes, that's correct. For (2), I'm not sure what you're asking ("at each step a new row is added"—what does the word row mean here?). [Edit per comments] let's look at steps 14, 15, 19, and 21 with respect to previous steps.
At step 14, you run hg commit -m "Added another file". The current commit is not an existing tip commit, because at step 12 you moved into the "clone2" directory / clone, whose current commit is unchanged from the time you made the clone (at step 2). That is, if the current commit was numeric-ID 0 and hash-ID a1234567.... at step 2, that is still the current commit, even though step 10 added more commits to that repository. (The current branch is probably default, although this is less important since both clone1 and clone2 are on the same branch and the commits added to clone1 are now present in clone2, on that same branch. The key item is that the current commit in clone2 is not the head of the current branch in clone2, thanks to the pushed-in commits.)
In a more typical situation, such as that at step 7, you'd be on a head commit (a commit with no children) when you make the new commit. Making the new commit creates that new commit with the current commit as its parent, so that the commit that was a head (had no children) is no longer a head (has a child); and the new commit itself, being new, has no children and is therefore a head. The count of heads is therefore adjusted both -1 (loss of previous head) and +1 (gain of new head via new commit).
But the new commit you make at step 14 has a not-a-head-anymore commit as its parent. Its "headness" was erased earlier by the git push at step 10, when it acquired a child commit. Now it has two children, which leaves it not-a-head; and the new child commit, being a new commit, has no children, making it a head. The head count is thus adjusted +1 with no offsetting -1.
At step 15, you run hg push -f ../clone1. This puts the new commit you just made at step 14 into clone1. Since the new commit you made is a head, clone1 gains a new head.
Step 19 is similar, but we're back in clone1 (due to step 16). The current commit is not an existing head, thanks to step 17 (hg up initial which uses the tag to find the commit to switch-to). You make a new commit, which is automatically a new head; but the commit to which you add this new commit as a new child is not itself a head, so the head count goes up one. Step 21 simply pushes this new commit into clone2: it's new to clone2, so it is a new head, but it does not transform any existing head into a non-head.
You haven't gotten here yet, but...
Note that when you use hg branch <newbranch> and then hg commit, you create a new child for whatever commit was current at the time, but that new child is in a different branch. The new commit, which becomes the current commit, is the new head of the new (now existing) branch, and the fact that it's a child of the commit that used to be current is irrelevant, since "head-ness" is determined by whether there are child commits in the same branch.
More generally (pre-edit text continues below)
... I believe Git acts the exact same way here
It does in terms of commits, but not in terms of heads.
Mercurial defines head as any commit with no outgoing arcs into (child commits in) the same branch. Mercurial's branches are very different from Git's, and Git more or less defines a head as "any commit that has a branch name pointing directly to it" (though Git calls these tip commits, and refers to the branch name itself as a "head").
Git's branches have no permanence: they exist only because the branch name itself exists, and the branch name exists only as long as you don't tell Git to delete it. As soon as you delete the name, the branch ceases to exist. The commits stick around until there are no more references to them, at which point the garbage collector (once it runs) discards them. Some references are squirreled away in reflogs and there's a separate reflog for each branch (deleted when the branch is deleted) plus one for the special name HEAD (never deleted), so even though the branch's reflog references vanish, the HEAD reflog references tend to keep deleted-branch commits alive for a while.
If those (in-Git) commits are reachable from some other branch, they continue to stick around because of that. Meanwhile, every commit in Git is, in general, reachable from N branches simultaneously (N being any nonnegative integer, and usually N ≥ 1). We say that those commits are contained within those branches. By contrast, every commit in Mercurial is on exactly one branch, with its branch having been chosen at commit-time, and it never changes.
In other words, we can say that in Mercurial, a branch's existence depends on the commits that are on the branch; but in Git, a commit's existence depends on the (branch and other) names that contain that commit. Any name will do, including tag names—Git has a bigger namespace here than Mercurial, since its tags are stored externally (which makes them unversioned, with all that this implies).
To make this work in Mercurial, "head-ness" is an automatic property of a commit, and to make this work in Git, "head-ness" (or branch-tip-ness) is managed manually instead. To make it mostly-automatic, git commit will automatically update the current branch (whose name is stored in HEAD) to point to the new commit just made.
Both VCSes add new commits the same way: they take whatever is in the proposed commit—stored in Git's index, or in Mercurial's work-state as shown by hg summary—and package that into a real commit, whose parent is the current commit. In Mercurial, having made that commit, we're done: it's automatically a head as it has no children. In Git, having made the new commit, our final task is to write the commit's ID into the current branch name, so that the branch name now points to the new tip commit.

Related

Duplicate a branch in Mercurial?

How can I duplicate a branch in Mercurial? I need the new branch to be against head (as the first one is).
The GIT equivalent (if I was in branch-a) would be:
git checkout -b branch-b
A Mercurial branch is a named entity that consists of all the commits contained within the branch. So in order to duplicate some existing branch, you must also duplicate all of its commits to new commits that are in the new branch. We then get into metaphysics arguments about commit identity. It's probably not a good idea to go here at all, but if you do want to go here, use hg graft to copy all the desired commits into the new branch.
A Git branch consists of a name containing a raw commit hash ID. So duplicating a Git branch under a new name is trivial. Note that the set of branches that contain any given commit changes dynamically over time: a branch that was only on feature/tall may now only be on master, even though that commit is still that commit, even via most of these metaphysical arguments. (Only the "no identity over time" argument lets us claim that this is not the same commit.)
Another way to put it is that Mercurial's branches actually mean something, but Git's don't. If you need true branches, you can't use Git in the first place. Don't try to import Git's bizzareness into Mercurial: you'll just make your own life miserable.
Meanwhile, though, Mercurial contains a DAG just like Git. If you use Mercurial bookmarks, those work like Git branches. It's probably wiser, then, to just use bookmarks and be done with it.
If all else fails, see hg graft.
the new branch to be against head
What is this (in usual business-term, not Git-lingua)? While in common (and in details) #torek is totally right, he forgot to write exact command-set, something like
hg up <rev-id>
hg branch <new-branch-name>
hg graft -r "branch(old-branch-name)" --log

Sort hg branches by modification time

When cleaning up branches in git, I have been using something like this to differentiate between old/inactive and new/active branches.
How can I sort all branches by their modification time in Mercurial?
The concept of a branch is just different enough in Mercurial vs Git to make this a bit tricky. Depending on what you really want, the "trickiness" may be that it becomes completely trivial!
Start with hg heads, which shows all heads (see hg help heads for the definition of a head). Each head is a commit, and therefore:
has an ID;
is in one particular branch;1 and
has a date stamp.
It also has a local revision number within your repository, which won't necessarily match the revision number in some clone that has the same commit. That is:
$ hg heads
changeset: 5:5f5df3fc4f1c
Changeset 5f5df3fc4f1c in some clone might be 4:5f5df3fc4f1c or 100:5f5df3fc4f1c or some such. The number in front of the colon is the local revision number.
Now, the dates on commits are up to the machine that creates the commit, so they can be quite wildly wrong. The local revision numbers are assigned sequentially as you add commits to your Mercurial repository, though—so if a local revision number is higher, that means that you introduced the commit later. (Caveat: this could be "a lot later than actually made" if you just now brought new commits in from an old, inactive, but never-before-incorporated clone.)
In that sense, then, the output of hg heads, which is shown by default in reverse local-number order, is already in the order you probably want (or the reverse of it). Things printed later are "less active". So there's probably no work to do here other than to read through the output of hg heads (and check whether there are multiple heads within some branch).
1This differs from Git in that in Git, a tip commit is the commit to which some refs/heads/ name points. That tip defines the branch, but that commit can be in multiple branches! The tip is necessarily a head in the Mercurial sense,2 but there can only be one such head, because it's the branch's name that locates and thereby defines the head / tip commit, and each name identifies only one commit.
2Unless, that is, you mean hg heads --topo, in which case a Git commit identified by refs/heads/name might not be a head after all:
...--o--* <-- refs/heads/midpoint
\
o--● <-- refs/heads/tip
The commit identified by refs/heads/tip is a topological head while that identified by refs/heads/midpoint is not. In Git the midpoint commit is on both branches.
If you do want different sortings, it's harder: instead of hg heads, you must use hg log and use a revision specifier. Note that -r "head() and not closed()" produces the same output as hg heads, so you can start with that. The output from hg log is sorted in whatever order you choose, but defaults to the same local-numeric-revision sorting. See hg help revisions for more.

mercurial hg up -C created a branch with the same name as current, how to remove it or merge it

I had a change in my branch and after unseccessfull merge, I tried to revert that unsuccessefull merge with hg up -c but it created a new branch instead. I can merge it into current or discard it or what to do with it?
EDIT:
actually I did the following, I had a branch and committed changes there there. then I wanted to push my changes to server, so I pulled changes, and tried to merge with them, but there were a conflict I couldn't resolve myself and I thought: I'll revert all changes back and merge again - so used hg up -C which I thought, will revert everything I changed during my unfinished merge. But what actually happened, another branch with the same name was created, containing only that changes I committed previously and with the same name as a branch I was working in and I was switched to the branch where I was working, which didn't have my changes. So two questions here: what actually happened and why another branch with the same name was created?
Having multiple heads on the same branch, which I think is what you're saying with "a branch with the same name as current" is a normal situation and, yes, you can use hg merge to consolidate them into one head. Use the hg heads command to find the hashes of the two heads of branch X. Then:
hg update REVISION_ID_OF_ONE_HEAD # changes your working directory to match one of the heads
hg merge REVISION_ID_OF_THE_OTHER_HEAD # merges that head's changes in
hg commit # create a new changeset that is the child of both those heads thus reducing the head count by one
Also #ringding is correct that hg update never creates branches. You either already had them and didn't know or received another head when you pulled.

How can I create a branch for a non-tip revision in Mercurial?

In my repo, I have the revisions 1 to 10. I've pushed up to 5 (so the next hg push would publish revisions 6-10).
But I have to interrupt my work now and the result isn't 100% complete. So I'd like to move the revisions 6-10 into a new "experimental" branch to allow someone else to complete the work without disrupting the sources for everyone.
How can I add a branch to a non-tip revision (in my case: Starting with revision 6)? Or should I use a completely different approach?
You cannot apply a branch name after the fact without modifying your history.
The most simple approach is to ask the other users to use revision 5 as the parent for any changes they create. For example, the other users would:
hg clone <your repo> or even hg clone --rev 5
hg update -r 5
work, work, work
hg commit
When they commit a change, it will create a second head on the default branch, but that should not create any problems. You will simply need to merge the two heads together once your experimental changes are complete.
That being said, moving your changesets onto a branch can be accomplished using Mercurial Queues (MQ). The following sequence shows how it be done:
hg qinit (Create a new patch queue)
hg qimport --rev 6:10 (import r6-10 into a new patch queue)
hg qpop -a (remove all patches from your working copy)
hg branch <branch name> (create your new experimental branch)
hg qpush -a (apply all the patches to your branch)
hg qfinish -a (convert all patches to permanent changesets)
Tim already has good suggestions. Additionally you could push your experimental changes into a distinct experimental clone on your central server (I guess you use one). This clone could also be used by other developers to push their not-yet-finished work in order to let others review or continue it. It is also clear that this clone's code is not ready to be used. Once some task is finished, the corresponding changesets can be pushed to the stable repository.
Actually named branches are a good idea for your case, but the fact that their names are burned into history mostly is more a problem than a feature. IMHO Git's branch names are more practically. However, to some extend you could also handle your case with bookmarks, which are pushable since Mercurial 1.7 (not sure here). That is you bookmark revision 5 with something like stable (or whatever you agree on in your team) and revision 10 gets bookmarked with something like Aarons-not-finished-work. The other developers would then just pull stable, except your colleague who is supposed to continue your work, who would pull the other bookmark. However, personally I did not use a such workflow yet, so I cannot say if it performs well in practice.

What does a mercurial revision with no parent mean?

I have a Mercurial repository that is in a strange state now. This is what it looks like in TortoiseHG:
I didn't think this would be possible. Revision 54 has a parent of "-1 (000000000000)" (i.e. nothing). There's clearly something I don't understand yet about Mercurial, can anyone let me know what this means - and what must have happened for it to get into this state. As far as I know, it's only had stuff pushed and pulled from it - and nobody has been using any wacky extensions.
Revisions 54 and 55 were just adding tags, but if I 'update -C' to revision 54 I end up with ONLY the .hgtags file.
I've made a clone from revision 53 to fix this. But I'd rather understand what happened here, so I can avoid it happening again.
When you look at the definition of a changeset, you see:
Each changeset has zero, one or two parent changesets:
It has two parent changesets, if the commit was a merge.
It has no parent, if the changeset is a root in the repository.
There may be multiple roots in a repository (normally, there is only one), each representing the start of a branch.
"Updating" back to a changeset which already has a child, changing files and then committing creates a new child changeset, thus starting a new branch. Branches can be named.
So maybe this is what you did:
updating back to 53 (which had already a child '54' of its own back then)
changing files
committing, thus starting a new branch from 54, with no parent
(that would make a second commit with the same parent)
or:
comitting 53 with a --close-branch option,
potentially a new commit (without switching back to another branch) might begin a new one
Ry4an (an actual Mercurial specialist ;) ) chimes in and comments:
--close-branch doesn't do anything except hide a branch from a list, and it's undone next time you commit on that branch. It won't create multiple roots.
VonC is right in his diagnosis, multiple heads.
But no combination of 'update' and 'commit' will get you into that state.
To end up with multiple roots one usually has done a 'hg pull' from repo and used --force to override an "unrelated repositories" warning.
("no parent", meaning the parent ids are set to 00000, see "behind the scene":
(source: red-bean.com)
)
Another way to see this is if you did hg update null after committing rev. 53. For example, consider this sequence:
hg init foo
# create some files
hg addremove
hg commit -m "Revision 0"
# edit, edit, edit
hg commit -m "Revision 1"
hg update null
hg tag -m "Create tag v1.0.0.0" "v1.0.0.0"
At this point, hg log will show revision 2's parent as -1:0000000000. Since hg update null clears out the working directory, the only file in it would be .hgtags (just like you were seeing).
Did you have other tags prior to rev. 53? If my suspicion is correct, they would not be present in your rev. 54 .hgtags.