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.
Related
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
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.
I'm looking for a counter-part of git commit --amend in Mercurial, i.e. a way to modify the commit which my working copy is linked to. I'm only interested in the last commit, not an arbitrary earlier commit.
The requirements for this amend-procedure are:
if possible, it should not require any extensions. It must not require non-default extensions, i.e. extensions which do not come with an official Mercurial installation.
if the commit to amend is one head of my current branch, no new head should be created. If the commit is not head, a new head may be created.
the procedure should be safe in a way that if for whatever reasons the amending fails, I want to have the same working copy and repository state restored as before the amending. With other words, if the amending itself can fail, there should be a fail-safe procedure to restore the working copy and repository state. I'm referring to "failures" which lie in the nature of the amend-procedure (like e.g. conflicts), not to file-system-related problems (like access restrictions, not being able to lock a file for writing, ...)
Update (1):
the procedure must be automatable, so it can be performed by a GUI client without any user interaction required.
Update (2):
files in the working directory must not be touched (there may be file system locks on certain modified files). This especially means, that a possible approach may at no point require a clean working directory.
With the release of Mercurial 2.2, you can use the --amend option with hg commit to update the last commit with the current working directory
From the command line reference:
The --amend flag can be used to amend the parent of the working directory with a new commit that contains the changes in the parent in addition to those currently reported by hg status, if there are any. The old commit is stored in a backup bundle in .hg/strip-backup (see hg help bundle and hg help unbundle on how to restore it).
Message, user and date are taken from the amended commit unless specified. When a message isn't specified on the command line, the editor will open with the message of the amended commit.
The great thing is that this mechanism is "safe", because it relies on the relatively new "Phases" feature to prevent updates that would change history that's already been made available outside of the local repository.
You have 3 options to edit commits in Mercurial:
hg strip --keep --rev -1 undo the last (1) commit(s), so you can do it again (see this answer for more information).
Using the MQ extension, which is shipped with Mercurial
Even if it isn't shipped with Mercurial, the Histedit extension is worth mentioning
You can also have a look on the Editing History page of the Mercurial wiki.
In short, editing history is really hard and discouraged. And if you've already pushed your changes, there's barely nothing you can do, except if you have total control of all the other clones.
I'm not really familiar with the git commit --amend command, but AFAIK, Histedit is what seems to be the closest approach, but sadly it isn't shipped with Mercurial. MQ is really complicated to use, but you can do nearly anything with it.
GUI equivalent for hg commit --amend:
This also works from TortoiseHG's GUI (I'm using v2.5):
Swich to the 'Commit' view or, in the workbench view, select the 'working directory' entry.
The 'Commit' button has an option named 'Amend current revision' (click the button's drop-down arrow to find it).
||
||
\/
Caveat emptor:
This extra option will only be enabled if the mercurial version is at least
2.2.0, and if the current revision is not public, is not a patch and has no
children. [...]
Clicking the button will call
'commit --amend' to 'amend' the revision.
More info about this on the THG dev channel
I'm tuning into what krtek has written. More specifically solution 1:
Assumptions:
you've committed one (!) changeset but have not pushed it yet
you want to modify this changeset (e.g. add, remove or change files and/or the commit message)
Solution:
use hg rollback to undo the last commit
commit again with the new changes in place
The rollback really undoes the last operation. Its way of working is quite simple: normal operations in HG will only append to files; this includes a commit. Mercurial keeps track of the file lengths of the last transaction and can therefore completely undo one step by truncating the files back to their old lengths.
Assuming that you have not yet propagated your changes, here is what you can do.
Add to your .hgrc:
[extensions]
mq =
In your repository:
hg qimport -r0:tip
hg qpop -a
Of course you need not start with revision zero or pop all patches, for the last just one pop (hg qpop) suffices (see below).
remove the last entry in the .hg/patches/series file, or the patches you do not like. Reordering is possible too.
hg qpush -a; hg qfinish -a
remove the .diff files (unapplied patches) still in .hg/patches (should be one in your case).
If you don't want to take back all of your patch, you can edit it by using hg qimport -r0:tip (or similar), then edit stuff and use hg qrefresh to merge the changes into the topmost patch on your stack. Read hg help qrefresh.
By editing .hg/patches/series, you can even remove several patches, or reorder some. If your last revision is 99, you may just use hg qimport -r98:tip; hg qpop; [edit series file]; hg qpush -a; hg qfinish -a.
Of course, this procedure is highly discouraged and risky. Make a backup of everything before you do this!
As a sidenote, I've done it zillions of times on private-only repositories.
Recent versions of Mercurial include the evolve extension which provides the hg amend command. This allows amending a commit without losing the pre-amend history in your version control.
hg amend [OPTION]... [FILE]...
aliases: refresh
combine a changeset with updates and replace it with a new one
Commits a new changeset incorporating both the changes to the given files
and all the changes from the current parent changeset into the repository.
See 'hg commit' for details about committing changes.
If you don't specify -m, the parent's message will be reused.
Behind the scenes, Mercurial first commits the update as a regular child
of the current parent. Then it creates a new commit on the parent's
parents with the updated contents. Then it changes the working copy parent
to this new combined changeset. Finally, the old changeset and its update
are hidden from 'hg log' (unless you use --hidden with log).
See https://www.mercurial-scm.org/doc/evolution/user-guide.html#example-3-amend-a-changeset-with-evolve for a complete description of the evolve extension.
Might not solve all the problems in the original question, but since this seems to be the de facto post on how mercurial can amend to previous commit, I'll add my 2 cents worth of information.
If you are like me, and only wish to modify the previous commit message (fix a typo etc) without adding any files, this will work
hg commit -X 'glob:**' --amend
Without any include or exclude patterns hg commit will by default include all files in working directory. Applying pattern -X 'glob:**' will exclude all possible files, allowing only to modify the commit message.
Functionally it is same as git commit --amend when there are no files in index/stage.
Another solution could be use the uncommit command to exclude specific file from current commit.
hg uncommit [file/directory]
This is very helpful when you want to keep current commit and deselect some files from commit (especially helpful for files/directories have been deleted).
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.
We converted everything to Mercurial coming from CVS and so far so good. An issue we encountered today though is this situation.
Before the move to Mercurial I had a few pending changes from a while back, features that were started and later postponed for various reason. Odds are that someone else will finish those features months from now picking up from where I left off.
After cloning the new Mercurial repository I created separate branches to isolate those features.
It left me with something like this (made up rev. number)
hg update default
hg branch feature1
hg commit -m "Description of what I was doing in feature1"
hg update default
hg branch feature2
hg commit -m "Description of what I was doing feature2" (my tip is now here)
hg update default
hg push -f (to force the creation of my new branches, w/o affecting default, I haven't merged them)
During this the team have been working and pushing to our central repository so the default branch is say rev 40 (tip)
Now my push -f worked fine but positioned (tip) to my latest change -> 50:feature3 (tip). I was expecting the tip to stay at default on the central repository and just have my branches in there for someone to pick them up whenever. The graph also looks pretty funny when I use hgwebdir so I am pretty sure that's the wrong approach.
How would I do this? Should I close the branch first? Is tip actually important or just meta-data.
tip is always the most recent changeset added to the repository. From hg help revs:
The reserved name "tip" is a special tag that always identifies the most recent revision.
So long as the head of the default branch is what you expect, you'll be OK. No need to close the branch (but it's better to use hg push --new-branch if your version of Mercurial is new enough to support it).
tip is just an automatically-applied label referring to (I think) the most recent commit. Not a big deal; it's just there for convenience.
The tip label is pure meta-data and always points to the changeset with the highest revision number -- there is no more logic than that.
However, the fact that tip now points to a changeset on the feature branch wont cause you any trouble. When people make a clone, they will automatically be updated to the tip-most changeset on the default branch. So they can begin working right away after a clone. Furthermore, people who already have a clone will stay on their named branch when they run hg update. Here hg update takes you to the tip-most changeset on that named branch, e.g., on default if that is where you started.
People may think that hg update tip is the same as hg update, but that is only when there are no named branches at play. With named branches, giving an explicit revision name such as tip can changeset your named branch -- a plain hg update cannot.