I have a 4 versions of a project. Versions 1-3 were "prototypes" that were originally not under source control (they exist as separate directories in the filesystem). Version 4 has been under source control since inception and now has a long history of commits.
I want to combine these so that versions 1-3 become individual changesets where each one is the descendant of the previous. The root of version 4 should become the descendant of version 3 (and no collapsing of version 4's history of course).
All changes are private, not public (there is no problem with rewriting history, as it were).
What I've done so far and tried:
1. I setup new hg repositories in the directory of the prototype versions
2. I cloned the repository of version 4
3. I pulled (with hg pull --force) the unrelated repositories for versions 1-3 into the cloned repository.
This gives me 4 unrelated "roots" (changesets with no ancestors) in a single repository. When I combine them I don't want to remember these 4 roots. hg rebase should let me move changesets and destroy the originals, unlike hg merge.
Here I'll use 101 as the revision for "version 1" (which is a single changeset with no parent) and 102 as the revision for "version 2".
Attempt 1: I try hg rebase -b 102 -d 101 but get the respone nothing to rebase. Presumably this is because they have no common ancestor (I feel like this is inconsistent... -b 102 would include all ancestors except the common ancestor, which would be nothing in this case.)
Attempt 2: I try hg rebase -s 102 -d 101. This results in merge conflicts. I do hg revert --all --rev 102 and hg resolve -m to indicate that I prefer "version 2" in all conflicts (although I wonder if that is really the right way to prefer one parent over another in the presence of adds/removes?). But when I commit, I don't have a linear history --- revision 102 is still there!
If I do hg rebase --continue as the final command (instead of hg commit) then it works perfectly.
I didn't read the last part of the documentation:
http://www.selenic.com/mercurial/hg.1.html#rebase
As I know, it's better to use --tool "internal:other" on rebasing instead of reverting to a revision.
An alternative to rebase which has worked for me is to use the hg convert extension and --splicemap option.
Although convert is intended to actually migrate from other source control systems (such as SVN) it can also "convert" from hg to hg.
The `--splicemap`` option allows you to explicitly set the child / parent relationship between changesets. I have used this to eliminate branches which originated from unrelated repositories. Mercurial appears to handle this well, e.g., files with the same name appear to have continuous history following the convert/splicemap operation.
I'm not sure if this is necessarily a "better" solution than rebase. It may be more of a niche approach for odd cases (?). One clear downside is that for large repositories the convert procedure can take quite a while to run.
Splicemap documentation.
Related
We have a very unfortunate situation where a new feature branch was made from a second, unrelated feature branch that had been put on hold rather than from default. There's multiple changesets within both feature branches and while most changed files are unrelated several high level project files that have edits on both branches. Default has also had several updates and merges during this time too. Thankfully the intermediate feature hasn't been updated concurrently with the new feature. I've looked into various commands and options available and lost how to best fix this situation. The tree currently looks roughly like:
Default -- Various edits and merges -- tip
\
\-- Named Branch 1 (15 changes) -- Named Branch 2 (30 edits)
I want to get to a point where default has the changes from Named Branch 2 but none from Named Branch 1. I also want a branch that has the changes from Named Branch 1 available for when we return to this feature.
I suspect there's no nice easy way to do this, and there's going to be some messy parts in the history, however I am at a loss at how to start going about this task.
hg graft can cherry-pick changesets from one branch to another. Update to the destination branch, then graft the revisions you want to copy by running, for example:
hg graft 8 9 10
Conflicts will be handled using the normal merge process.
If you use TortoiseHg, select the changesets to graft to the current selected changeset, then right-click and select Graft Selected to local...:
Result:
Since you want to move the entire branch 2, you could consider using rebase. Make a clone of the repository and try this:
hg rebase --source <first branch2 rev> --dest <new parent in default> --keepbranches
This will in principle transform the history to what it should have been. However:
You may have to resolve conflicts arising when <first branch2 ver> gets moved to a new parent.
Since rebase rewrites history, you'll have to get everyone to cooperate in synchronizing their repositories. Whether that's feasible or worth the trouble in your case I can't say, but it's not that difficult: Assuming everyone has pushed any changes in branch 2, they can pull the new history and then get rid of the obsolete version of branch 2 with hg strip:
hg strip <first branch2 rev>
I guess I must have misunderstood the author of this post when he said:
hg pull -r X -f repo # ... will give me all changesets up to X
Because when I did that (in a freshly-created repository), I only got those changesets that were ancestors of the branch that revision X is on.
What I wanted was all the changesets that had been committed to the remote repo up to and including X, chronologically. In other words, in addition to the branch that X is on (and its ancestors), I also wanted all other branches (including closed branches) committed before X that hadn't been merged to X's branch.
How would I express the command to do that?
BTW, in this particular repo, there are closed branches that have names that are identical to currently open/active branches, so if the solution involves enumerating all the branch names (which would be tedious, but do-able), it would still need to get the closed occurrences of such branches as well as the open ones.
(For completeness I suppose I should also say that I ran the command from the command-line of TortoiseHG 2.7 on Windows, in case the behavior of hg pull that I've described above isn't what I should have expected.)
You can't do that on pull in a single command. "Chronologically" means a lot less than you think it might. Anyone can do a commit with any timestamp they want, so the dates aren't good selectors. If you mean "with an earlier revision number" those too can change from repo to repo, so pulling all revisions with a revision number lower than N could give different results for different invocations.
If you want to try the revision-number-based version anyway, you'd probably have your best luck pulling everything to a trash repo locally and then pushing only what you want to a new local repository:
hg clone http://remotehost/path local-clone # clones everything
hg init another-local-clone
hg push --repository local-clone --rev '0:X' another-local-clone
after that another-local-clone will have all the changesets whose revision numbers is X or lower in local-clone, which is (but isn't guaranteed to be) the same as the remote clone
If that seems awkward it's because "committed before" isn't a terribly useful concept in DVCS land -- it assumes a linearity that neither git not Mercurial consider important.
If I have a bunch of uncommitted changes and want to set it aside while working on something else instead, and then later (f.i. after several days) come back to it and proceed working. What would be the easiest workflow to accomplish this? (So far I have only experience with Mercurial's basic functionality). My usual method was to create a new branch using clone, but there might be better ways.
You have a handful options:
Shelve the items. This saves the changes and removes them from the working directory so the branch can continue. It doesn't create a change-set.
hg shelve --all --name "UnfinishedChanges"
hg unshelve --name "UnfinishedChanges"
Update/Edit: Newer versions of mercurial may need to use
hg shelve -n "UnfinishedChanges"
hg unshelve "UnfinishedChanges"
You can still use --name as an alternative to -n, but mercurial doesn't seem to like --name anymore. Additionally, the --all is no longer required and mercurial will in fact freak out over it.
Patch queue the items using mq. This isn't too dissimilar to shelve in some respects, but behaves differently. The end result is the same, changes are removed and can be optionally re-applied later. When pushed, the patches are logical change-sets, when popped they are saved elsewhere and are not part of change-set history.
hg qnew "UnfinishedWork"
hg qrefresh
hg qpop
hg qpush "UnfinishedWork"
Commit them locally, update to the previous change-set and continue working and make use of anonymous branches (or multiple heads). If you then want the changes, you can merge heads. If you don't want the changes, you can strip the change-set.
hg commit -m"Commiting unfinished work in-line."
hg update -r<previous revision>
hg strip -r<revision of temporary commit>
Commit them to a named branch. The workflow then becomes the same as option 3 - merge or strip when you are ready.
hg branch "NewBranch"
hg commit -m"Commiting unfinished work to temporary named branch."
hg update <previous branch name>
Personally I use option 3 or 4 as I don't mind stripping change-sets or checking-in partial code (so long as that doesn't eventually get pushed). This can be used in conjunction with the new Phase stuff to hide your local change-sets from other users if need-be.
I also use the rebase command to move change-sets around to avoid merges where a merge wouldn't add anything to the history of the code. Merges I tend to save for activity between important branches (such as release branches), or activity from a longer-lived feature branch. There is also the histedit command I use for compressing change-sets where the "chattiness" of them reduces the value.
Patch queues are also a common mechanism for doing this, but they have stack semantics. You push and pop patches, but a patch that is "underneath" another patch in the stack requires that the one on top of it be pushed also.
Warning, as with all these options, if the files have more changes since the temporary changes that you've shelved / queued / branched, there will be merge resolution required when un-shelving / pushing / merging.
Personally, I don't like any of the answers posted so far:
I don't like clone branching because I like each project to have only one directory. Working on different directories at the same time completly messes the history of recent files of my editors. I always end up changing the wrong file. So I don't do that anymore.
I use shelve for quick fixes (just to move my uncommited changes to another branch, if I realize I'm at the wrong one). You are talking about days, no way I'd shelve something for days.
I think mq is too complicated for such an ordinary sittuation
I think the best way is to simply commit your changes, than you go back to the changeset before you start these changes and work from there. There are some minor issues, let me illustrate:
Let's say you have the changeset A. Than you start your changes. At this point you want set it aside for a while. First of all, commit your work:
hg ci -m "Working on new stuff"
If you want, you can add a bookmark to make it easier to come back later. I always create bookmarks to my anonymous branches.
hg bookmark new-stuff
Go back to the changeset before these modifications
hg update A
From here, you work and generate the changeset C. Now you have 2 heads (B and C), you'll be warned when you try to push. You can push only one branch by specifying the head of that branch:
hg push -r C
Or you can change the phase of the new-stuff branch to secret. Secret changesets won't be pushed.
hg phase -r new-stuff --secret --force
To keep local uncommited changes, easiest way for me is just to save them as a patch file.
hg diff > /tmp/`hg id -i`.patch
and when you need to return to previous state:
hg up <REV_WHERE_SAVED>
hg patch --no-commit /tmp/<REV_WHERE_SAVED>.patch
You can just clone your repo multiple times. I tend to have a root clone, then multiple childs from there. Example:
MyProject.Root
MyProject.BugFix1
MyProject.BugFix2
MyProject.FeatureChange1
MyProject.FeatureChange2
The 4 childs are all cloned from the root and push/pull to/from the root. The root then push/pulls from the master repo on the network/internet somewhere. The root acts as your sort of personal staging area.
So in your case, you'd just clone up a new repo and start working. Leave your 'shelved' work alone in the other repo. It's that simple.
The only downside is disk space usage, but if that were a concern you'd not be using DVCS at all anyway ;) Oh and it does kind of pollute your Visual Studio "recent projects" list, but what the hey.
[Edit following comments] :-
To conclude then... what you're doing is completely fine and normal. I would argue it is the best possible way to work when the following are true: 1) it is short-lived 2) you don't need to collaborate with other developers 3) the changes don't need to leave your PC until commit/push time.
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.
svnmerge helps to block some changesets from a specific branch. How can this be achieved with Mercurial?
What subversion calls a merge is pretty different to what a merge is in Mercurial. The operation that svnmerge or svn merge does is usually referred to as a "cherry pick" in other version control systems. Basically it creates a new commit that is a copy of the original changeset in the target branch. In Mercurial that can be done using the transplant extension.
This method is less popular in DVCS compared to regular merging as it creates multiple heads, which are harder to maintain. Here's how you would use it and what the resulting output would look like:
$ hg checkout -r 8
$ hg branch release
$ hg transplant 10
applying 8ba2867cf974
8ba2867cf974 transplanted to 1f5920f61c94
7---8---9---A [default]
\
B [release]
Here you have your commit A that fixes the bug on trunk and you hg transplant it to release, creating a new commit B that contains the same changes as A, but with different parents.
The alternative approach is to not use transplant and check in only fixes to the release branch. You would create a new release branch and make the fix there, then you would merge the release branch into default. That would look like this:
$ hg checkout -r 8
$ hg branch release
... fix fix fix ...
$ hg commit -m"Make fix A"
$ hg checkout -r 9
$ hg merge release
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg commit -m"Merged release branch"
7---8---9---B- [default]
\ /
A---- [release]
Here B is a merge commit. It has no "extra" changes associated with it (unless there were conflicts that were resolved) and has the special property of marking the release branch as merged into default at that point. There's only one actual changeset that fixes the bug - the one marked "A" - although of course the changes themselves are in the default branch too.
The advantage of this approach are:
only have one head. This is less confusing for developers and gives a nice sense of closure
you can see at a glance that default contains all the bug fixes from release
you can see release has that important bug fix
Normally you'd tag the release branch "v1.1" or whatever at the appropriate point so you know where that release came from.
The disadvantages with this approach are when:
release is so old that merging to default causes conflicts
changes on release that you didn't want to merge into default mixed in with changes that you did want
There is not much you can do with the first issue - if you have to maintain a branch that is very old, then you will end up with diverging branches anyway. The patches are probably very different anyway.
The second problem occurs if for example you have an "emergency" patch to the release version that papers over or works around a bug, but requires a larger fix on default to address the underlying problem. In that case, you would have to merge release and then create a new explicit commit to "undo" the commit you didn't want.
This practice actually has similar advantages with svn merge in a way. If you merge changes selectively from trunk to release, you have to remember the revisions that you merged (assuming you aren't merging all of trunk). svn merge sets the svn:mergeinfo properties, but tracking these can cause problems, plus you have to check the logs carefully to say "oh, yeah, I did merge that fix into release".
If you svn-merge the whole release branch to trunk when you have a fix, there is no need to remember what revisions have been merged.