I have an issue while using Bazaar and I would like to get the best practices to fix it.
Here is the context:
In the trunk of our project, one wrongly merged a branch (BranchA) in the trunk but he used revert on most files while keeping the merge info (so from bazaar BranchA was effectively merged in trunk, preventing to REALLY merge it later).
The merged revision was committed as r4.
After this (undesired) merge, many devs committed right after (r5 & r6).
So my question is : how to undo this wrong merge ? (while keeping the commits after).
I tried to uncommit back to r3 and merge each rev from r4 to R6 (omitting r4)
I tried 'reverse cherry pick' r4 ... but the BranchA merge information may still memorized.
trunk
|
r6
|
r5
| branchA
| |
r4 ---+
| |
| r2.2
| |
r3 r2.1
| |
r2 ---+
|
r1
If you have a solution or some clue to fix it, please share it !
(for example can REBASE command help here?)
Since information was lost due to the reverts in the merge that has gone wrong, there won't be a 100% fix. Your best bet is to create a new branch off of r3 in the trunk and cherry pick r5 and r6, like this:
cd /path/to/repo
bzr branch trunk -r3 branchX
cd branchX
bzr merge -c5 ../trunk
bzr commit -m "cherry picked r5: $(bzr log --line -r5 ../trunk)"
bzr merge -c6 ../trunk
bzr commit -m "cherry picked r6: $(bzr log --line -r6 ../trunk)"
Or, before cherry picking r5, you might want to merge from branchA the "right way".
The rebase command won't help you here. The purpose of rebasing is basically to reorder revisions: whether you merge or rebase, you end up with the same content in the files, only the revision history graph will have a different shape. The purpose of rebase is usually to make a history graph look flat, which can be neat, but it's only cosmetics, in terms of content the end result is the same.
Related
Suppose you have a master branch:
A--B--C
Feature 1 Branch:
A--B--C--D
Feature 2 Branch:
A--B--C--E
When we do a git merge Feature1 into master, it merges fine, however when trying to merge Feature2, we are presented with vi asking us to enter a commit message for the merge. Is there a way to merge these branches without having extra merge commits? They share the same history from master aside from the feature commit.
The final history on master should look like:
A--B--C--D--E
depending on which commit date (D or E) is first
Background
In git, the history is built up by recording the parents of each commit - generally, a "normal" commit has one parent, and a "merge commit" has two, but there can actually be any number of parents, including zero.
Every commit is identified by a hash of both its content and its metadata - that includes who committed it and when, and its list of parents. You can't change any part of that data without getting a new commit hash, so all commits are effectively immutable.
A "branch" in git actually just points to a single commit, and git follows history backwards from there.
The scenario, as git sees it
Each commit points at its parent or parents, and each branch points at a commit.
Note that the angles on this graph don't mean anything, they're just to lay it out in 2D.
+--D <--(feature1)
v
A <--B <--C <--(master)
^
+--E <--(feature2)
The fast-forward merge
By default, git will "fast-forward" history whenever it can. What this means is that it just moves the branch pointer without touching any commits at all.
This is what you see when you merge your first feature branch: git fast-forwards the "master" pointer to point at commit D, and leaves everything else alone:
+--(master)
V
+--D <--(feature1)
v
A <--B <--C
^
+--E <--(feature2)
Which (remembering that angles don't mean anything) we can also draw like this:
A <--B <--C <--D <--(master, feature1)
^
+--E <--(feature2)
The merge commit
When we come to merge the second feature branch, we can't just fast-forward any more - pointing "master" at commit E would lose commit D. So git's other option is to create a "merge commit" - a commit with more than one parent. The pointer for "master" can then point to this new commit.
This is why you're prompted for a message on your second merge, because git is creating a new commit (let's call it "M2") so both D and E will be in its history:
A <--B <--C <--D <--(feature1)
^ ^
| |
| M2 <--(master)
| |
| v
+----E <--(feature2)
Which we might also draw like this:
+--(feature1)
v
A <--B <--C <--D <--M2 <--(master)
^ |
| v
+---------E <--(feature2)
Note that we could have forced git to do this with the previous merge as well, using git merge --no-ff, which would have given us something more like this:
+----D <--(feature1)
| ^
v |
A <--B <--C <--M1 <--M2 <--(master)
^ |
| v
+----------E <--(feature2)
Rebase
So, how do we make a history that looks like this?
A <--B <--C <--D <--E <--(master)
On the face of it, we can't: E's parent is recorded as C, not D, and commits are immutable. But what we can do is create a new commit which looks like E but has D as its parent. This is what git rebase does.
After fast-forwarding feature 1, we had this:
A <--B <--C <--D <--(master, feature1)
^
+--E <--(feature2)
If we now git rebase master feature2, git will create a new version of all commits reachable from feature2 which aren't already reachable from master. It will try to create commits which apply the same changes, and by default copy commit messages and even the original author and timestamp, but they'll have new parents.
It will then point feature2 at these new commits; in our case, the result will look something like this:
A <--B <--C <--D <--(master, feature1)
^ ^
| +--E2 <--(feature2)
|
+--E
The original commit E is now not reachable from any branch, and will be cleaned up. But now we can avoid the merge commit: the new commit E2 is in a position where we can fast-forward master again:
A <--B <--C <--D <--(feature1)
^
+--E2 <--(feature2)
|
+ <--(master)
To redraw:
+--(feature1)
v
A <--B <--C <--D <--E2 <--(master, feature2)
I have committed some code, below I have given my problem statement.
commits
abcdef5 - empty
qwerty5 - my code
Both of them are on my branch abc
How to merge both of them into 1 commit?
I just need qwerty5 in my branch.
If abcdef5 is really empty (and it the most recent commit on your local branch abc), you could simply drop it:
git reset --hard #~
No squash required there.
Then you can push your branch.
You can use the following steps to squash your empty commit:
git rebase -i HEAD~2
1. Review the commits
pick abcdef5 a
pick qwerty5 b
2. Squash commit a into b
s abcdef5 a
pick qwerty5 b
3. Edit the final commit message
# This is a combination of 2 commits.
# The first commit's message is:
message from commit a
# This is the 2nd commit message:
message from commit b
4. Push your code
git push --force
Hope this helps. :)
I have the following situation :
branch A| branch master
| |
| |
| | branch B
| | |<------B2
| |___|<------B1
| |<--------some commits (*) in B and in master, but not in A
\___|
|
I created a branch B from master but that was a mistake, I should have created B from A, and not from master.
So my initial thought was to Rebase B onto A.
The problem with this approach is that the commits (*) are also rebased in B, which I don't want.
So I wanted to use "cherry-picking" to duplicate the commits from B (after the divergent point with master) into A.
But since Egit uses Interactive Rebase to do that, the problem is the same. The commits (*) are present in A.
Basically, I want to apply the functional changes introduced by B1 and B2 in the branch A, without the changes introduced by (*).
The only solution that I found is to manually 'create Patch' for B1 and apply it in A, and then 'create Patch' for B2, and apply it in A.
Is there another way to easily do what I want to achieve using Egit ?
In the command line:
git rebase --onto a last-ignored-commit b
or
git rebase --onto a first-wanted-commit~ b
If you are already on branch-b you do not need to specify that as the last argument. Both commands evaluate to the same thing. The tilde (~) means find the first parent. Therefore last-ignored-commit and first-wanted-commit~ both refer to the same commit.
As I understand one of the main advantages of distributed revision control system like Mercurial is that you should not worry about breaking something in your super-important main repo (which is used by quite a lot other developers), and do all you work and researches in your personal remote clone until you understand that everything stable and you can push your work back.
And hence I got my question: if it possible to push back not all your changes history (with several revisions which you made for yourself), but only one which is actually diff between your repo and current state of master.
One example:
hg init master-repo; cd master-repo
echo -e 'Important file\nWith Bug!' > file
hg commit -A -m "initial commit"
cd ..; hg clone master-repo hotfix-repo; cd hotfix-repo
echo "fix1" >> file
hg commit -m "first attempt to fix bug"
echo "fix2" >> file
hg commit -m 'Fixed it!'
Now (possibly after pull and merge with newest master-repo' changes) I want to push back only one changeset containing all changes that I've done without my local commits history.
One possible solution is to create one more clone then use diff/patch between two clones to extract/apply changes from first one and commit them all at once in second repo. Then do push as in normal case. But is it possible to so using only mercurial commands?
Thanks in forward!
Opinions differ on whether or not it's good to collapse trial-and-error changesets into a single changeset before pushing:
Pros: you avoid having changesets in your history where your test suite fails — those changesets are bad for hg bisect and add noise.
Con: you cannot collapse changesets that you have published to other repositories — doing so would only rewrite your local changesets and you would then have to clean up the other repositories manually.
Technically, it's perfectly safe to collapse a set of changesets into a single changeset before pushing. You start with
... a --- b --- c --- x --- y --- z
and you rewrite this into
... a --- b --- c --- w
where w has exactly the same repository state as z had (but a different parent changeset, obviously). There are no merges here (and hence no merge conflicts) so it cannot fail.
After rewriting, you can pull and merge with the upstream (d and e):
... a --- b --- c --- w --- v
\ /
d ----- e
You need an extension to do any kind of history rewriting. Here I would suggest one of:
Collapse extension: as the name implies, this extension is dedicated to collapsing changesets.
Histedit extension: full-fledged history editing, but the fold command let's you collapse changesets.
Rebase extension: this standard extension can move changesets around and collapse them at the same time. In the example above, it would move x --- y --- z after e:
... a --- b --- c --- d --- e --- x' --- y' --- z'
You can then optionally collapse x' to z':
... a --- b --- c --- d --- e --- w'
Compared to just collapsing x to z, rebasing does involve merges so it can fail.
Intro
I think, rewrite history and send/get "polished" changesets (in Git-boys style) is, in common, bad idea - history is history, it have own value.
After all, for "mainline" branch (you use branches, isn't it), all your changes will be presented as one mergeset, regardless of changesets count in branch
Short answer
No. In Mercurial you pull/push and get&accept full set of changesets, which created difference in repo history
Long answer
Somehow you can, by rewting your history of changesets before exchange to "other side". Just remember - each changeset represent not new state of object, but diff between old and current (if describe it shortly), thus - by ordinary removing changests you can get wrong final result.
Anyway, you have a lot of ways to rewrite own history (as extensions):
Collapse
History Edit
MQ (with mq-patches per se, folding changesets in mq-patch and splitting the same way)
Maybe some others, unknown for me
I have some old commit messages in a Mercurial repository that should be changed (to adjust for some new tools). I already understand that this hacking has to be done on the master repository and all local repositories would have to be re-cloned, because checksums of all subsequent changesets will also change.
I've tried following the recipes in "How to edit incorrect commit messages in Mercurial?", but with MQ extension I got stuck on error message
X:\project>hg qimport -r 2:tip
abort: revision 2 is the root of more than one branch
and with Histedit quite similarly
X:\project>hg histedit 2
abort: cannot edit history that would orphan nodes
The problem seems to be that there have been branches created after the changeset.
I can see how it would become messy if I'd want to change the contents of patch, but perhaps there's a workaround that I've missed for editing the commit message?
I would use a hacked version of the convert extension to do this. The extension can do hg → hg conversions which lets you alter author and branch names. There is not support for changing commit messages yet, but you can hack it.
Specifically, you should change the getcommit method from:
def getcommit(self, rev):
ctx = self.changectx(rev)
parents = [p.hex() for p in self.parents(ctx)]
if self.saverev:
crev = rev
else:
crev = None
return commit(author=ctx.user(), date=util.datestr(ctx.date()),
desc=ctx.description(), rev=crev, parents=parents,
branch=ctx.branch(), extra=ctx.extra(),
sortkey=ctx.rev())
which is responsible for reading the old commits. Change the
desc=ctx.description()
to
desc=adjust(ctx.description())
and then implement the adjust function at the top of the file:
def adjust(text):
return text.upper()
If these are accidental/duplicate branches due to using --amend and push --force then strip them first and try 'histedit' again then wipe the central repo on bitbucket; try the following which worked for me:
Inspect the repository log and look for branches, you can use the GraphlogExtension which you will have to enable first:
# hg log -G | more
...
o changeset: 43:c2fcca731aa5
| parent: 41:59669b9dfa4a
| user: Daniel Sokolowski (https://webdesign.danols.com)
| date: Tue Aug 27 20:14:38 2013 -0400
| summary: Progress snapshot: major content text and model instance ..
...
| o changeset: 42:c50724a6f1c6
|/ user: Daniel Sokolowski (https://webdesign.danols.com)
| date: Tue Aug 27 20:14:38 2013 -0400
| summary: Progress snapshot: major content text and model instance ...
|
o changeset: 41:59669b9dfa4a
| user: Daniel Sokolowski (https://webdesign.danols.com)
...
Enable the MqExtension and strip all branches.
# hg strip --no-backup 42:c50724a6f1c6
# hg strip --no-backup 45:3420dja12jsa
...
If needed change the commit to 'draft' (see Phases) and re-run 'histedit' and all should be good now.
# hg histedit 14:599dfa4a669b
abort: cannot edit immutable changeset: b7cfa2f28bde
# hg phase -f -d 14:599dfa4a669b
# hg hsitedit 14:599dfa4a669ba
I needed something similar so I filed a feature request.