Mercurial: Move a modified file between commits - mercurial

Let's say I have the following commit history:
A - newest
B
C - oldest
I have changed a file foo in B, but I want to move the change from B to A.
How can I do that with hg?

There are several different ways that I can think of. Both have some moderate user requirements (please make sure you have backed up or pushed to a non-publishing repository first).
1 ) Use histedit and amend (requires that each extensions be enabled).
- copy file you wish to move to a safe location
- run hg histedit and edit changeset b and restore file to its original state
- run hg histedit --continue to commit changes.
- copy file from safe location to repository
- run hg amend to append file to changeset A
2 ) Use split and histedit.
- run hg split -r b
- split out all but the file you wish to move into a new changeset
- create a new changeset onto of that containing the fie (give it a temporary description)
- run hg histedit
- move the temp change above A
- roll the temp change into A
3 ) Use hg-evolve uncommit / amend. While this is a somewhat advanced method, I much prefer this one myself.
- run hg update B
- run hg uncommit and select the file you wish to move.
- run hg evolve to correct the stack
- run hg update A
Note: if you get a warning about needing to use --clean use hg shelve before
running the update followed by hg unshelve afterwords.
- run hg amend to add file to change A
4 ) Use hg uncommit all contents of changesets A and B and then recommit using hg commit -i to reassemble the changesets with the desired content.
There are likely a number of other ways, but this is what came to me.

Here's a simplistic but functional approach, maybe for people not very experienced with some of the more advanced HG commands:
Make sure your working directory is clean (no changes).
hg update -r C
hg revert -r B
hg commit ... as needed, but leave out the change destined for A. This creates B'.
hg shelve ... store away the uncommitted changes
hg revert -r A
hg unshelve ... restore uncommitted changes, merging them into the workding directory
hg commit ... creates C'.
hg strip -r B ... (optional!)
(where it says -r A that means use the revision # corresponding to A)
Explanation: we just create two (or more) entirely new changesets and then delete the originals (which is optional).
We're using revert basically to mean "copy from some changeset to the working directory" which I find is very handy in many circumstances.
After these operations -- but prior to using strip -- the history would look like:
*
|
A A'
| |
B B'
|/
C
and strip will just delete A, B.

Related

How to Switch the branch an commit with Tortoise Hg?

I have two branches named X and Y. X is previously created and y is the latest one. Now I am at branch 'Y' and I have modified some files. I need to commit the files which I have modified and I need to commit the changes to 'X' branch instead of 'Y' branch. How can I do this with Mercury Hg or Tortoise Hg? (How to Switch to 'x' branch and commit the changes?)
You could
Commit it to Y branch
Then transplant/grave it to X branch
Then strip changeset from Y branch
As result it's look like you commited it to X instead of Y
If you have not committed the changes then you should be able to do the following:
hg update X
hg commit
Unless you've changed some of the same files in the same places within the file, this should be sufficient. If you have, then your hg update X command should fail. You have a few options at this point.
You can use Mercurial Queues (mq).
You can put your current changes into a patch queue:
hg qnew stufftomove -e
You'll be presented with whatever editor you use to create a commit message. Go ahead and fill in your commit message with whatever you want it to be when it is ultimately committed. If you don't use -e, it will just create the patch queue with no commit message. Later you can make a commit message with hg qrefresh -e if you want.
After the patch queue is created, pop it off with:
hg qpop
Now move to the X branch:
hg update X
Now push the patch queue on. This will apply the patch file:
hg qpush
This can either go on cleanly, or if you did have merge conflicts, you'll get a message that .rej files have been created. Those show you the parts of the changes that could not be applied automatically.
If you have .rej files, manually apply the pieces that didn't work and then run
hg qrefresh
This will update the patch file with the new changes.
When everything looks like what you want it to, you can convert the patch queue to a real commit with
hg qfinish stufftomove
Your file changes should now be committed on the branch you want them to be on.
Alternatively, if you already committed the changes on the wrong branch, and have not pushed your changes (or had them pulled) to a remote repository, you can do this:
hg qimport -r <revision to import>
At this point, continue the above instructions starting with hg qpop.
If you can be more specific about the state of the files and branches, I can help more specifically. In any case, I hope this helps.
Side note: If you've got merge conflicts in the steps above and don't like dealing with .rej files, you can use hg rebase which will allow you to resolve the conflicts in your favorite merge tool (or whatever tool you've got configured). Hope this helps.
I use Shelving for such operation: https://tortoisehg.bitbucket.io/manual/2.9/shelve.html
While you're on 'Y' branch, put all your changes to shelf. Then switch to 'X' branch and restore changes from your shelf.

Add a parent to the original changeset in Mercurial

I have a project with 24 months of source control history in a Mercurial repository.
I've recently found some old tarballs of the project that predate source control, and i think they would be useful to import into the repository as "pre-historic" changesets.
Can i somehow add a parent to my initial commit?
Alternatively, is it possible to re-play my entire repository history on top of the tarballs, preserving all metadata (timestamps etc)?
Is it possible to have the new parent commits use the timestamps of these old tarballs?
You can use the convert extension to build a new repository where the tarballs are imported as revisions before your current root revision.
First, you import the tarballs based on the null revision:
$ hg update null
$ tar -xvzf backup-2010.tar.gz
$ hg addremove
$ hg commit -m 'Version from 2010'
$ rm -r *
$ tar -xvzf backup-2011.tar.gz
$ hg addremove
$ hg commit -m 'Version from 2011'
I'm using addremove above to give Mercurial a chance to detect renames between each tarball (look at the --similarity flag to fine-tune this and use hg rename --after by hand to help Mercurial further). Also, I remove all the files in the working copy before importing a new tarball: that way the next commit will contain exactly the snapshot present in the tarball you unpack.
After you've imported all the tarballs like above, you have a parallel history in your repository:
[c1] --- [c2] --- [c3] ... [cN]
[t1] --- [t2] --- [tM]
Your old commits are c1 to cN and the commits from the tarballs are t1 to tM. At the moment they share no history — it's as if you used hg pull -f to pull an unrelated repository into the current one.
The convert extension can now be used to do a Mercurial to Mercurial conversion where you rewrite the parent revision of c1 to be tM. Use the --splicemap flag for this. It needs a file with
<full changeset hash for c1> <full changeset hash for tM>
Use hg log --template '{node} ' -r c1 -r tM > splicemap to generate such a file. Then run
$ hg convert --splicemap splicemap . spliced
to generate a new repository spliced with the combined history. The repository is new, so you need to get everybody to re-clone it.
This technique is similar to using hg rebase as suggested by Kindread. The difference is that convert wont try to merge anything: it simply rewrites the parent pointer in c1 to be tM. Since there is no merging involved, this cannot fails with weird merge conflicts.
You should look at using rebase. This can allow you to make the changes the 2nd changeset on your repo ( you have to rebase from the 1st ).
https://www.mercurial-scm.org/wiki/RebaseExtension
However, note that if there are other clones of this repo existing ( such as for fellow developers, or on a repo server ), you will have issues with them pulling the revised repo. You will probably have to co-ordinate with the owners of those clone's to get all work into a single clone, rebase that clone, and then have everyone re-clone from the revised clone. You will also have to change the phase the of the changesets.
https://www.mercurial-scm.org/wiki/Phases
Honestly though, I would just add them to your 'modern-day' repo, I don't think making them pre-historic would give you any notable advantage over adding them to the top.

Mercurial: "undoing" two or more commits

In How do I do a pristine checkout with mercurial? Martin Geisler discuss how to remove already Mercurial commit'ed files using:
hg strip "outgoing()"
But what if I I want to keep my added files which went into "outgoing()" - example:
Two users a and b — starting on the same changeset
User a:
echo "A" > A.txt; hg ci -M -m ""; hg push
User b (forgets to run hg pull -u):
echo "B" > B.txt; hg ci -M -m "" B.txt;
echo "C" > C.txt; hg ci -M -m "" C.txt;
If user b run hg strip "outgoing()" then B.txt and C.txt are lost. hg rollback is not an option since there are two commits.
Can user b revert his files as "locally added - nontracked", then do hg pull -u, which gets A.txt, then handle the add/commit/push for B.txt and C.txt later?
Martin Geisler answered this earlier in the mentioned thread (a comment which I deleted and moved here:
hg update "p1(min(outgoing()))"
hg revert --all --rev tip
hg strip "outgoing()"
hg pull -u
Now user c can finalize his work in the new files B.txt and C.txt and commit+push those.
Other ways to do this?
You could but, by doing so, you are working against one of the biggest features of a DVCS like mercurial, that is, to easily and reliably handle the merging of multiple lines of development as in your case. If user b's goal is to have a line of development with all three changes applied, then the standard way to do that in hg would be to just go ahead and do an hg pull -u which will create a new head containing the change(s) from user a (and any other changes pushed to repo used for pulling) and then use hg merge to merge the two heads, the head containing user b's two change sets and the other containing user a's change set (as pulled). In a simple case like this one with no overlapping changes, hg should do all the right things by default.
$ hg pull -u
[...]
added 1 changesets with 1 changes to 1 files (+1 heads)
not updating: crosses branches (merge branches or update --check to force update)
$ hg merge
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg ci -m "merge"
If there were conflicts between the two heads (i.e. both users committed changes to the same files), there might need to be conflict resolution editing as part of the merge; hg will tell you if that is the case.
Another option is the rebase extension. With your scenario:
A and B start with the same history.
A commits and pushs a change.
B commits two changes, but can't push because of A's commit.
B pulls A's change.
B runs hg rebase and pushes.
Before rebase:
Common ---------------------------- A (tip)
\
B1 - B2 (working parent)
After:
Common - A - B1 - B2 (tip, working parent)

Mercurial: How to compact/combine specified old revisions into one?

I've got a repository. In the middle of its life-cycle I deleted a lot of unnecessary files from it (I decided to keep them unversioned).
hg remove
hg commit
The repo grows bigger and bigger.
And I decided to get rid of old revisions the from initial one to the revision where lot of files were removed (let's name it X).
Other words I want combine these revisions (from the initial to the X) into one initial revision.
But same time to keep the history of the following revisions (X+1, etc..) as they are.
I googled for the solution, but failed.
And found nothing clever than do this:
hg init newrepo
cd oldrepo
hg archive -r X newrepo
hg export -r X+1: -o "~/patches/%R-%h.diff"
cd newrepo
hg commit -A -m 'initial release (after archiving)'
hg import ~/patches/*.diff
And damn it, after few successfully applied patches
I receive:
Hunk #1 FAILED at xxx
Hunk #2 FAILED at xxx
2 out of 2 hunks FAILED -- saving rejects to file xxx.rej
abort: patch failed to apply
What I do wrong?
I've got 1 repo without branches (to be more exact to the revision X all branches were merged).
The second solution was
* hg convert to svn
* hg convert to mercurial from revisiob X+1
Failed with python backtrace (probably it was caused by our repo has about 3K files).
To filter out files from repository, you want to use hg convert (Mercurial to Mercurial) with --filemap argument (see documentation for more details). Keep in mind the affected changeset IDs (and those of all their descendants) will change.
Take a look at the Collapse extension which seems to do what you want.
You can fold changesets with MQ, or use histedit extension

ignore non-existant files when merging in Mercurial

I am working with a repository with stable and experimental branches. Sometimes I add a file on the experimental branch that is not yet ready for the stable branch. When I merge, I want to merge the changes in the files that are common to both branches, but ignore the files that don't exist on one of the branches.
Here's a simple example:
hg init
hg branch stable
(create file A)
hg add A
hg commit -m "Added A"
hg branch exp
(create file B)
hg add B
hg commit -m "Added B, which is really experimental"
(modify file A)
hg commit -m "Some changes to A"
hg update stable
hg merge exp
However I change the merge tool configuration, Mercurial always seems to take B along with the merge. Since it doesn't exist on the stable branch, it's never a conflict.
I could do the following:
hg update stable
hg merge exp
hg commit -m "Merged"
hg revert -r 0 B
but that requires me to know which files need reverting.
Any thoughts on the simplest way to make the merge ignore files that don't exist on one branch, and preferably do it automatically?
You cannot ask Mercurial to do what you want.
You do not merge individual files, you merge the entire branch, which means that all the files that are part of the branch becomes part of the merge. This is how Mercurial is designed and how it operates.
Now, you could revert/forget/delete the files you don't want before you commit, but then you're just setting yourself up for disaster later.
I recommend you separate things you want to keep from things you don't know if you want to keep so that you can merge one branch and let the other be separate for now.