(How) can I collapse commits branching out - mercurial

My revision graph looks like:
F
|
E
|\
| |
C |
| D
| |
B |
| |
|/
|
A
I want to collapse all commits from B to F into a single one. A is not mine, and I don't want to touch it. I'd like to have in the end:
X
|
A
Is this possible ? I've tried various collapse and rebase commands but could not achieve this.

You can accomplish this with the mq extension for the patch queue. You will need to remove the merge changeset (since I don't think you can qimport a merge) and reorder the patches (which may require some hand merging of the patch file).
hg qimport --rev F --name F
hg qpop
hg strip -r tip -- this removes the merge changeset
hg qimport -r C -n C
hg qpop
hg qimport -r B -n B
hg qpop
hg qimport -r D -n D -- now you have all 4 changes (D, B, C, F) in the patch queue
At this point you can hg qpush --all to apply all the patches and you will need to resolve all the conflicts that result in rejected patches. This is to manually redo the work that the merge changeset E had previously accomplished. Once that is completed via editing and hg qref commands, qpop all the patches except for D.
hg qfold B C F -- this will merge the B, C, and F patches into patch D
hg qfin D -- this will convert patch D into a finalized changeset
A few more notes:
As always, back up your work before you start (a copy of the folder is fine). You can also use your diff tools after the steps to compare the original and the result to ensure that you didn't miss anything.
If you have pushed any of those changesets to another repo, you will need to delete them there (either with hg strip or changing their phase to secret).
If the merge changeset E did a lot of work (i.e. there were conflicts between B+C and D) the extra step between 9. and 10. will be messy since your typical merge tools are not available.
The follow up question I have for you though: Why? What do you hope to accomplish? Branching and merging with smaller changesets is standard operating procedure with DVCS. After a few more changes, all those changes will scroll into history and never come up again. Worrying about a perfect history graph is really unnecessary.

Related

Mercurial: Move a modified file between commits

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.

Hg: How to fold local commits in the middle of the stack

Simple question:
Let's say I have local commits like following:
master -> a -> b -> c
I want to merge a and b. What is the hg command for this? I tried
hg up b
hg fold -r a
got an error saying: abort: cannot fold chain not ending with a head or with branching
I tried
hg up b
hg amend
This created a stack like
master -> a.preamend -> b -> c
\-> a
which doesn't allow me to continue on c. Finally, I tried
hg up b
hg histedit a
abort: can only histedit a changeset together with all its descendants
No luck. What I want is something like:
hg up b
hg *merge* -r a
returns
master -> b -> c
Thanks in advance!
About error message
This error message saying abort: cannot fold chain not ending with a head or with branching occurs when new unstable changes are not allowed in your repository.
Unstable changes are those obsolete changes whose children(s) are not obsolete. You should look for experimental.evolution in hg config and add "unstable" to it.
About folding changesets
There are two ways to fold commits in the middle of stack using fold.
Use --exact option : You can do hg fold -r a -r b --exact. In this it does not matter what is the parent of your working directory is. You have to mention the revisions to be folded.
Update to the last child which you want to prune, in this case it is b, using hg update -r b. Then run hg fold -r a. The revision passed to fold must be the initial revision of the chain which you want to fold.
You had
master -> a -> b -> c
You folded a and b, they became obsolete, since b has a child c which is not obsolete, it is an unstable changeset.
So what you are doing is not wrong, it's just not allowed according to your configuration.
if you want to squash the changes from two commits in to one commit then, you can use hg rebase command to squash the changes.
$ hg rebase -r a::b -d master --collapse
this will squash the changes of both a and b commits in to one commit
for more info refer to: RebaseExtension

Mercurial undo a series of commits

I have a mercurial repo with the following history (most recent commit at the top) on a feature branch:
mergeDefaultA
|
mergeDefaultB
|
C
|
mergeDefaultD
mergeDefaultXXXX are merge commits that came as the result of merging the default branch into the feature branch.
What has happened is commit C is screwed, but this was not noticed until after I had pushed mergeDefaultA to Bitbucket. What I want is the following picture:
exactlyWhatIsInMergeDefaultD
|
mergeDefaultA
|
mergeDefaultB
|
C
|
mergeDefaultD
Where exactlyWhatIsInMergeDefaultD is literally exactly what was the state of the code in mergeDefaultD. However, everything I'm reading seems to indicate either you can't undo a series of commits like this (only a single commit back) and even then many of the options aren't available once you've pushed "into the wild".
How do I achieve this?
If this was git, I'd do:
git revert mergeDefaultD
How do I do the same in Mercurial?
Here's what I think you want:
hg revert -r GOOD_REVISION_NUMBER --all
hg commit -A -m "reverting back to revision GOOD_REVISION_NUMBER"
Once that is committed, as soon as someone pulls from you (or you push to them) they will get the most recent revision containing only the good stuff. If they ever do want to go back to the bad stuff, you could always update to that revision:
hg update -r BAD_REVISION_NUMBER
To expand a bit on Harvtronix' answer (which is fine, by the way):
One simple way is to revert to the old revision number ('GOOD') and commit. Note: reverting means that you set the files to the same content as in revision 'GOOD', you don't go back down the tree to that commit. If you did, you would indeed branch off and have two heads.
hg revert -r GOOD --all
hg commit -m "all is good now"
Another way can be to only throw out revision C (if I read your explanation correctly, it's actually just C that is causing the issue). 'hg backout'will introduce the reverse of C in your working directory, so you can then commit it.
hg backout -r C
hg commit -m "Backed out C"
Finally, one last option is to close the bad branch, update to the last commit that was fine and commit further there:
hg up -r BAD
hg commit --close-branch -m "This head is bad"
hg up -r GOOD
... continue here ...

Mercurial, change branch of changeset

I have 2 branches in my Mercurial repository. 'default' and 'other'
default branch A - B - C - D
other branch E - F
I need to move B changeset to other branch.
So it will look like this:
default branch A - C - D
other branch E - F - B
Is it possible?
Thanks in advance!
Graft-based solution
Graft (hg help graft) B to target branch
Remove (histed extension) B from source branch (graft only make copy of changeset, without removing original)
Rebase-based solution
Rebase B to other branch
Because rebase move also descendants of rebased changeset - rebase C back to default
The following solution doesn't require enabling any extensions. It does assume the existence of the patch utility, though.
On default:
$ hg diff -c B > diff.out
$ hg backout --merge -r B
$ hg merge
$ hg ci
On other branch:
$ patch -p1 < diff.out
$ hg ci

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)