Condensing a mercurial repository - recommanded way? - mercurial

Let's say I have a repository 'Main', and Max and co work on a clone each. Max has some local commits ('f'&'g') that are not yet pushed to 'Main'. This is how it looks now (pipes being pushs/pulls):
A--B1--B2--C--D1--D2--D3--E (Main)
| | | |
A--B1--B2--C--D1--D2--D3--E--f--g (Max)
'B1' and 'B2' as well as 'D1', 'D2' and 'D3' are changes that only make sense together. We would like to combine 'B1' and 'B2' to a single changeset 'B' and combine 'D1', 'D2' and 'D3' to a single changeset 'D'. The new structure should look like this:
A--B--C--D--E (Main)
| | |
A--B--C--D--E--f--g (Max)
My (main) question is: What is the reccommended way of doing this?
Now let's make things worse:
We have a branch that was merged within the change-sets that we want to collapse. It would look like this:
A--B1--B2--C--D1--D2------D4--E (Main)
| | \-------D3-/ |
| | |
A--B1--B2--C--D1--D2------D4--E--f--g (Max)
\-------D3-/
The new history should look like this:
A--B--C--D--E (Main)
| | |
A--B--C--D--E--f--g (Max)
How would you do that?
Thanks in advance.

It depends on how much effort you want to put into this. While I don't know a solution within Mercurial itself (I only know history editing functions which can't cope with merges), Git does have the functionality you need:
If I would really have to do such an operation, I would
Try to convince the management that this is not worth it
Try harder to convince the management that this is not worth it
Make a backup! The following steps involve destructive operations, so consider this as not optional. You have been warned.
exort the repo with hg-git into a git repository
export the complete (git) history into a fast-import-stream with git fastexport --no-data --all > history.fi
Create a Pseudohistory by editing history.fi, dropping your unwanted revisions
import the adjusted history into the git repo with ``git fast-import -f < history.fi`
check extensively if the newly created history is in fact the way you want it to have
clone Max into a local work repository
Remove successors of commmit A in the local work repository
pull your updated history back from git (again with hg-git) into the local work repository
check, if the Mercurial history matches your expectation (diffs of commits between the new and old repos, metadata (time stamps, committer names, ...)
Remove successors of commmit A in every repo (Main, Max and every developer clone)
hg push -r E Main the partial history back to Main out of the work repository
hg push -r g Max the complete history back to Max out of the work repository

Related

Retroactively use Mercurial rename

In my project, I haven't been using hg remove, hg mv or hg addremove due to ignorance. Consequently, every time I've renamed or moved a file, the history of that file has been messed up and now when I look at an individual file's history, I will only see a portion of the history.
What I'm looking for is a way to go back and retroactively fix all of those renaming mistakes so that the file history will stay together. What I imagine would be most likely is a way to edit the data in ".hg\store\data" to make this work. I've been experimenting, and I see the lines copy: and copyrev: in the data for the files I've renamed, so I suspect that has something to do with it.
Assume that I have control of the central repository and that there are no clones of it currently.
Summary:
Since you have full control of the repo this can be 100% fixed using normal hg commands.
The principle idea is to insert new changesets in the right places which effectively correct the original ones.
Let's say your history looks like this:
A-B-C-*
(* is your working folder)
and it was in B that you renamed a file in the filesystem without renaming it in hg.
Do this:
hg up A
hg revert -r B --all
hg mv oldfilename newfilename
hg commit -m <message>
The key here is using revert which is used to copy changes from a changeset into your working folder. This only works this way because you have updated to the predecessor of the changeset you are reverting.
at this point your history looks like:
A-B-C
\
B'-*
where B' is the "corrected" variant of B. Continue with:
hg rebase -s C -d B'
and you have:
A-B
\
B'-C-*
You can now clean up by doing:
hg strip B
leaving just:
A-B'-C-*
Of course where I used revisions like B you need to type the actual revision # or hash.
You could also use TortoiseHG or some other GUI to do a lot of these steps.
This answer covers the situation where you do NOT have full control of the repo. Its a little trickier and you can't get quite as clean of a result, but it still can be dealt with using normal hg commands.
The principle idea is to insert new changesets in the right places which effectively correct the original ones, and merge them after the fact.
Let's say your history looks like this:
A-B-C-*
(* is your working folder)
and it was in B that you renamed a file in the filesystem without renaming it in hg.
Do this:
hg up A
hg mv oldfilename newfilename
hg commit -m <message>
at this point your history looks like:
A-B-C
\
B'-*
where B' is the "corrected" variant of B. Continue with:
hg up C
hg merge B'
hg commit
and you have:
A-B-C-D-*
\ /
B'
If you look at the file history of the file in question, it will look something like this:
o D merge
|\
| o B' rename file
| |
o | B change where the file should have been renamed
|
o A some earlier change
/
o ...
|
o ...
So the history is all linked together for the file. Its just a little weird that B looks like it started from nowhere (because it actually did).

Can I retrospectively move Mercurial revisions to a branch?

I have a project with a natural long-term branch cum fork; I won't want to re-merge this with the default branch. I initially managed this fork with a bookmark, but on reflection it seemed to make more sense to have this as a named branch, so (without thinking about it too much) I created a branch at the bookmarked head (4).
o 7:default
|
| o 6:fork
| |
| o 5:fork
| |
| o 4:default
| |
o | 3:default
| |
| o 2:default
|/
o 1:default
|
o 0:default
But this means that I now have a branch plus a lonely head (4) on the default branch which isn't going to go anywhere (because it's really part of the branch/fork, and I don't plan to commit to (4) on the default branch). But this means that the repository is now somewhat untidy: I have a fork which exists partly on the default branch and partly, later, on the named branch and which, because it's on default looks as if it should be merged later. And oooh, that untidiness is a terrible thing (perhaps I should get out more).
What I'd like is either (a) to magically put selected revisions (ie, the ones leading to the now lonely head) onto the named branch, or (b) go back to plan A (bookmarks) and retrospectively put revisions on the named branch (5 & 6) back on the default branch and add a bookmark.
Possibilities:
There is a question Move branch start in Mercurial which overlaps with this, but is framed in terms of an expectation that the branch will be re-merged in future. Also, I'd quite like to retain the intermediate (post-fork pre-branch) revisions as being on the branch.
Use hg convert to create a copy of the repository minus the fork, create the branch, and start hand-applying changesets. Messy and rather error-prone, but doable (there aren't too many revisions in the branch).
Acknowledge that life is messy and full of disappointments, that history is history, and that I should stop worrying about inessentials.
or...
I'm holding out for magic.
Ghost merge. Merge revision 4 into revision 7. Discard all
changes of rev. 4 when doing the merge.
Close the head. Update to rev. 4, close the branch there.
Rebase. If revisions 2, 4, 5, 6 are in the draft phase. You can rebase starting from rev. 2, moving all descendants to another branch.
Options 1 and 2 will only hide the unwanted head (history unchanged). To rewrite history, go with option 3.
Rebase to default. You can also rebase from revision 5, moving that branch back to default. Using bookmarks again, as the branch never existed.

'Forgetting' a dead-end branch

I've got a mercurial repository. It was on rev A. I made some changes, committed (to rev B), and pushed. However, later, I realised I didn't want to make those changes. I updated back to rev A, and made some alternative changes, to rev C.
C
| -
| B
|/
A
However, now I can't push rev C, because it complains that it would create a new remote head (which it would). How do I make the remote mercurial simply forget about rev B and all the changes therein, so I can push rev C and carry on from there?
Editing History is hard. Once you push a changeset to a public repository, it can no longer be easily pruned from the history.
The most direct solution to your problem is:
hg update <tip of branch you want to forget>
hg commit --close-branch -m "close unwanted branch"
hg update <tip of branch you want to keep>
Push all your changes. As you noted, you will need to use --force since multiple heads on the branch now exist.
If you really need to prune the branch, then read EditingHistory again. If it still seems feasible, you can use one of the methods described in PruningDeadBranches.
Personally I'd close the branch and force the push (as Tim Henigan describes), as it leaves the DAG in a state which is truthful. There is another option though. Doing a dummy merge. This is a merge, but one where you ignore the incoming changes.
hg update C
hg -y merge --tool=internal:fail B
hg revert --all --rev .
hg resolve -a -m
hg ci
The end result is
M
|\
C |
| |
| B
|/
A
... but M doesn't contain any of B's changes.
Use the backout command to reverse the B change.
hg up B
hg backout B
Now, you're working directory is in the same state it was before B, which I'll call A'.
After you backout, make your real change and commit. This is what your history will look like:
C
|
A'
|
B
|
A
Mercurial branches are permanent and registered in commit objects themselves. There are some (not too easy) methods for closing and/or removing branches, mainly listed here. I have even already used some of them before. Note that those are somewhat desperate solutions for people who did use branches thinking they can be temporary.
However, if you really want temporary branches, use bookmarks.
Other people have some good answers for getting rid of 'B', but just to put it out there you can always do:
hg push --rev C
which will push C but not B and not complain about new heads.

How does the 3 way merge in Mercurial/Meld work?

I'm working on a project where I have a commit that introduced a feature with major problems that weren't discovered immediately. Now I want to completely remove that revision while keeping the work following it but i'm having a hard time wrapping my head around this 3 way merge. Here is a simplified graph of my project.
o changeset: 134:7f81764aa03a
| tag: tip
| parent: 128:451d8a19edea
| summary: Backed out changeset 451d8a19edea
|
| # changeset: 133:5eefa40e2a29
| | summary: (Change I need to keep keep)
| |
*snip 3 commits*
| o changeset: 129:5f6182a97d40
|/ summary: (Change I need to keep keep)
|
o changeset: 128:451d8a19edea
| summary: (Change that introduced a major problem)
|
o changeset: 127:4f26dc55455d
| summary: (summary doesn't matter for this question)
If I understand this correctly, r127 and r134 are exactly the same. When I hg up -C -r 133 and then run hg merge, Meld pops up with three forms of one of my files: local, base, and other. local seems to be r133 but i'm having a hard time wrapping my head around what "base" and "other" mean.
Local is r133
Other is r134
Base is r128 (the common ancestor to both r133 and r 134)
When you perform a 3 way merge it compares all three of those together to help you decide what to take and from where. By seeing what change is in the other revision and what the common ancestor looked like you are able to make a much more informed decision about what to keep and what to change.
Your question is really confusing, but here are some information that may help you.
What is base?
Base is the unmodified version of the revision that you have currently checked out and worked on. where possibly other changes have forked off (you can have revisions in between your current local and base!). its just where the nearest revision where no other fork has diverted from afterwards (same parent) (in your case r128)
What is head?
Head is the latest revision in version control. if you work alone on only one copy it will probably be base. but a co worker might have modified the same file and checked it into version control, then head is later than your base.
What is local?
Local is your modified version (in your case r133)
What is other?
Other is the some fork/branch that has also your Base as parent (in your case r134)
How does 3-way merge work?
3 way merge works (at least in meld) hierarchically. usually from left to right like this:
local > base > other / head
local/base is mostly trivial because its just what you modified
then you can merge your changes into the head revision or the one of your co worker or whatever.
There can be multiple other/head revisions, but then it's not your job to merge in and therefore more than 3 way compare doesn't make sense.

Mercurial: how can I see only the changes introduced by a merge?

I'm trying to get in the habit of doing code reviews, but merges have been making the process difficult because I don't know how to ask Mercurial to "show only changes introduced by the merge which were not present in either of its parents."
Or, slightly more formally (thanks to Steve Losh):
Show me every hunk in the merge that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
For example, assume I have a repository with two files, a and b. If "a" is changed in revision 1, "b" is changed in revision 2 (which is on a separate branch) and these two changes are merged in revision 3, I'll get a history which looks like this:
# changeset: 3
|\ summary: Merged.
| |
| o changeset: 2
| | summary: Changing b
| |
o | changeset: 1
|/ summary: Changing a
|
o changeset: 0
summary: Adding a and b
But if I ask to see the changes introduced by revision 3, hg di -c 3, Mercurial will show me the same thing as if I asked to see the changes introduced in revision 1, hg di -c 1:
$ hg di -c 3
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
$ hg di -c 1
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
But, obviously, this isn't very helpful - instead, I would like to be told that no new changes were introduced by revision 3 (or, if there was a conflict during the merge, I would like to see only the resolution to that conflict). Something like:
$ hg di -c 3
$
So, how can I do this?
ps: I know that I can reduce the number of merges in my repository using rebaseā€¦ But that's not my problem - my problem is figuring out what was changed with a merge.
The short answer: you can't do this with any stock Mercurial command.
Running hg diff -c 3 will show you the changes between 3 and its first parent -- i.e. the changeset you were at when you ran hg merge.
This makes sense when you think of branches as more than just simple changesets. When you run hg up 1 && hg merge 2 you're telling Mercurial: "Merge changeset 2 into changeset 1".
It's more obvious if you're using named branches. Say changeset 2 in your example was on a named branch called rewrite-ui. When you run hg update 1 && hg merge rewrite-ui you're effectively saying: "Merge all the changes in the rewrite-ui branch into the current branch." When you later run hg diff -c on this changeset it's showing you everything that was introduced to the default branch (or whatever branch 1 happens to be on) by the merge, which makes sense.
From your question, though, it looks like you're looking for a way to say:
Show me every hunk in this changeset that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
This isn't a simple thing to calculate (I'm not even sure I got the description right just now). I can definitely see how it would be useful, though, so if you can define it unambiguously you might convince one of us Mercurial contributors that read SO to implement it.
In order to do code reviews you really want to see just the changes in the project that you are reviewing. To that end we use a new branch for each story and use pull requests to spotlight the changes, having merged all changes into the story branch before creating the pull request. We host our code on bitbucket and the review / pull request tools are really very good, offering a side by side diff.
Pull requests with side-by-side diffs