View mercurial history with straightline development smashed - mercurial

I'm interested in viewing the topology of my branches, ideally in a pretty way (a la graphlog). For example I want to see how many (open) branches there are, when they split, the last time they merged to and from each other, etc. I am not interested in all the merges between them, nor straight line development on each branch.
This is useful when looking at forks on bitbucket for example. Github's network graph helps, but often the branch structure is drowned out by straightline development and/or frequent merges.
I thought that perhaps I could use revsets like
hg glog --rev "head() or merge() or branch_points()"
but then glog shows all revisions in between, not to mention the fact that I couldn't figure out how to specify branch_points() i.e. revisions which have more than one child.
Is there an extentsion for mercurial (or another DVCS) which can approximate my desires? If not is there a better way to get this information?

The following patch adds the branchpoint revset to Mercurial. It currently only applies to Mercurial 2.2, but there it works nicely.
I don’t know if I‘ll have time to push for inclusion quickly. You could go to the mailing list and offer to get it ready for inclusion (that would save me that work).
The revision is also in https://bitbucket.org/ArneBab/hg-stable
# HG changeset patch
# User bab#draketo.de
# Date 1343931127 -7200
# Branch stable
# Node ID f5e211663739e31f2e476c43992ee5335f9d8146
# Parent 00182b3d087909e3c3ae44761efecdde8f319ef3
revsets: added branchpoint() for revisions with more than one child.
Reason: Get very terse information via
hg glog --rev "head() or merge() or branchpoint()"
diff -r 00182b3d0879 -r f5e211663739 mercurial/revset.py
--- a/mercurial/revset.py Tue May 01 19:09:15 2012 +0900
+++ b/mercurial/revset.py Thu Aug 02 20:12:07 2012 +0200
## -710,6 +710,15 ##
cl = repo.changelog
return [r for r in subset if cl.parentrevs(r)[1] != -1]
+def branchpoint(repo, subset, x):
+ """``branchpoint()``
+ Changeset has more than one child.
+ """
+ # i18n: "merge" is a keyword
+ getargs(x, 0, 0, _("branchpoint takes no arguments"))
+ cl = repo.changelog
+ return [r for r in subset if cl.children(repo[r].node())[1:]]
+
def minrev(repo, subset, x):
"""``min(set)``
Changeset with lowest revision number in set.
## -1137,6 +1146,7 ##
"bisected": bisected,
"bookmark": bookmark,
"branch": branch,
+ "branchpoint": branchpoint,
"children": children,
"closed": closed,
"contains": contains,

$ hg log -Gr "merge() + head()"

Related

How to get all named branches part of a specific changeset

Given
---a----b----c----d----e----f (default)
| | |
---g (1.0) ----h---i---(2.0)
I want to know, for a specific changeset, which named branches (releases in my case) that this changeset is part of (a parent of the head of the named branch).
input -> output
a -> default, 1.0, 2.0
g -> default, 1.0, 2.0
d -> default, 2.0
e -> default
h -> 2.0
How can I do this the easiest way (something with hg log ancestors I guess?)? I would prefer not taking the branch names as input but dynamically using all (open) named branches.
For others having the same question as myself...
I found the extension contains, https://bitbucket.org/resi/hg-contains, which does exactly what I want.
hg headscontaining -b --revno [changesetId] --template {branches}
[revsetalias]
bd($1) = descendants($1) - branch(default)
In use: hg bd(CSET-ID)
Edit: Well, my initial version was better than needed (and not usable) - it returned list of branches immediately, which may be hardly used as revset in final Mercurial commands.
With single operation result is revset for using in templated log
hg log -r "bd(CSET)" --template "{branch}\n"

Mercurial: Picking commits to be included in a release

I have a local mercurial repository (for now) within which I have already made several commits, each commit is a self contained bug fix. Is it possible to pick which of the bug fixes (commits) I want to be included when it is time to build a release version of my application.
To elaborate, assuming A, B, C, D, and E are commits I have already done to my repository and each of them relates to a bug fix like so:
A <- B <- C <- D <- E <- working dir
I need to be able to for example pick which of the bug fixes will go into the release version (this depends on the time allocated for deployment as well as testing outcomes). So for example I might get a report saying the release should only contain bug fixes A, C and D.
Is it possible to construct a release version containing only the A, C and D commits (Keeping in mind that each commit is self contained and does not depend on the other commits to actually be there)?
Probably having a branch for each bug fix and then merging into a release branch is the easiest way to accomplish this (or is it not?), but the current situation at hand is as described above with no branches.
This isn't the normal work mode of Mercurial (or git). A repository can only contain a changeset if it also contains all of that changeset's ancestors. So you can't get D into a repo without also having A, B, and C in there.
So here's:
What you Should have Done
Control the parentage of your changesets. Don't make C the parent of D just because you happen to have fixed D after C. Before you fix a bug hg update to the previous release.
Imagine A was a release and B, C, and D, were all bug fixes. If you do a loop like this:
foreach bug you have:
hg update A
... fix bug ...
hg commit
hg merge # merges with the "other" head
then you'll end up with a graph like this:
---[A]----[B2]--[C2]--[D2]----
| / / /
+-[B] / /
| / /
+-----[C] /
| /
+---------[D]
and now if you want to create a release with only, say, B and D in it you can do:
hg update B
hg merge D
and that creates a new head that has A + B + D but no C.
Tl;Dr: make a change's parent be as early in history as you can, not whatever happens to be tip at the time.
What you can do Now
That's the ideal, but fortunately it's no big thing. You can never bring exactly D across without bringing C (because C's hash is part of the calculation of D's hash), but you can bring the work that's in D into a new head easily enough. Here are some ways, any of which will work:
hg export / hg import
hg transplant
hg graft (new in 2.0)
hg rebase (only possible if you haven't yet pushed)
Any of those will let you bring that patch/delta that's in D over -- it will have a different hash ID and when some day you merge D in for real (using merge) you'll have duplicate work in two different changesets, but merge will figure it all out.
If this was my tree and it hasn't been pushed anywhere, I'd (assuming an empty patch queue and MQ enabled):
hg qimport -g -r B: # import revisions B and later into mq as "git" style patches
hg qpop -a # unapply them all
hg qpush --move C # Apply changes in C (--move rearranges the order)
hg qpush --move D # Apply changes in D
hg qfin -a # Convert C & D back to changesets
hg push <release server> # Push them out to the release branch
Then you can hg qpush -a; hg qfin -a to get B & E back into changesets.
Final Result:
---A---C---D---B---E
Advantages:
Nobody needs know you didn't do things in this order to start with (evil grin)
You could modify any of the change-sets whilst doing this
Alternatively, with graft in 2.0:
hg update -r A # Goto rev A (no need to do anything special for A)
hg graft C # Graft C on to a new anonymous branch
hg graft D # Graft D
This will give you
---A---B---C---D---E
\
--C'--D' <-You are here
An hg push -r D' should just push the new, cherry-picked, head.
You can then hg merge to get one head again with B and E included.
Advantages:
Non destructive, so true history is kept, and no chance of loss if you muck up
hg tags the new changesets with the hash of the original version, so totally trackable
Probably a little simpler.
While it's somehow strange way and release-policy, you can do it in different form. You have to manipulate with two main objects: changesets and branches
Version 1
You use two branches (default + f.e "release 1.0"). Default branch is mainline of your work - all changesets commited to this branch. At release-time, you branch first needed-for-release changeset into (new) branch, transplant or graft rest of needed in release changesets from default to this branch, head of release 1.0 will be prepared for release this way.
Next release will differ only in new branch name
Version 2
One branch used, MQ extension added. Variations:
all changesets are MQ-pathes and only needed for release are applied to repo
changesets are changesets, only unwanted for release converted to mq-pathes, later qfinish'ed and returned to repo

How to show list of unapplied changesets in Mercurial

After pushing changesets to a repository called 'A' how can I see the list of changesets waiting to be applied when I am in 'A'?
Expanding on that,
In repo B I push changesets to repo B
I change to repo B
How can I list the changesets pushed in step 1?
Not sure what you mean by "unapplied" changesets, however here's a couple thoughts.
You can easily see what changesets will be pushed to a repository by doing hg outgoing prior to doing the hg push. This will list all of the changesets that will be pushed using default options.
Similarly you can use hg incoming in the destination repository to show what changesets would be pulled from another repo.
As for "unapplied" changesets, if I assume you mean changesets that are newer than the working directory, you could use hg log -r .:tip, which should (I've not had a chance to test it) show all newer revisions, but not actually all recently-pushed ones.
Edit: I've updated the revision set in the -r option to something that should work. Have a look at revsets on the Mercurial manpage for more possibilities.
$ hg summary
parent: 0:9f47fcf4811f
.
branch: default
commit: (clean)
update: 2 new changesets (update) <<<<<
The update bit tells you what (I think) you want.
I had written a different answer, but I ended up with a better way of doing what is needed here (an even better and definitive –for me– solution is at the end of this post, in the [EDIT] section).
Use hg log.
Specifically, issue an hg sum command first. This will give me:
parent: 189:77e9fd7e4554
<some commit message>
branch: default
commit: (clean)
update: 2 new changesets (update)
To see what those 2 new changesets are made of, I use
hg log -r tip -r 2 -v
Obviously, 2 is to be replaced with the number of changesets that hg sum reports.
This works because tip will refer to the most recent (or "unapplied") changeset. By limiting the output to the 2 latest changes (-l 2), the information is shown only for those changesets that I'm interested in. With -v, the list of files affected by the changeset is also shown.
To make things simpler, I have defined a user command in my .bashrc file:
alias hglog="hg log -r tip -l $1"
This allows me to type hg sum (to get the number of pending/unapplied changesets) and then to type hglog x where x is the number of changesets revealed by hg sum.
There is probably a more complete way of doing this, for instance using custom templates, but I guess it's pushing things too far in terms of sophistication.
[EDIT] (Third iteration)
I have reached the most satisfying answer to this question by expanding on the alias idea so that I no longer have to type hg sum. My .bashrc file now contains this:
show_pending_changesets() {
nb=$(hg sum | grep "update:" | sed 's/update: \([0-9]*\) .*/\1/');
if [ `expr $nb + 1 2> /dev/null` ] ; then
hg log -r tip -v -l $nb
else
echo "Nothing new to report"
fi ;
}
...
alias hgwhatsnew=show_pending_changesets
Explanation: I'm using sed to extract the number of changesets from the last line (which is the one that starts with update:) of the output of hg sum. That number is then fed to hg log. All I have to do then is to type hgw and tab-complete it. HTH

Mercurial: allow merge from a release branch to the default one, but not vice versa

I'm using default branch for ongoing development, and now going to create a new named branch to mark a release. All further development will be on the default branch, all production bugfixes will be done on the new one (with subsequent merge to default), like this:
#>hg branches
aristotle 42:dbd...
default 41:da5...
#>hg branch
default
#>echo "Feature #1 for the next release" >> feature1.txt
#>hg add
#>hg commit -m "Implement feature #1 for the next release"
...... eek, need to make an urgent fix on Production .....
#>hg update aristotle
#>echo "Fixed urgent bug #123 on Production" >> fix123.txt
#>hg add
#>hg commit -m "Fixed bug #123 on Production"
created new head
#>hg update default
#>hg merge aristotle
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, dont forget to commit)
#>hg commit -m "Merge in the fix for bug #123"
#>hg push
The above seems the way to go, however it seems easy to mess things up and merge the other way around (from default to aristotle which means all the new features will appear in the production branch).
Maybe my fears are groundless because one will notice the mess before pushing the commit to the central repo, but I'd like to see if it's possible to make the approach more foolproof.
So I started looking into hooks:
[hooks]
pretxnchangegroup.branch = hg heads --template "{branches} " | find "aristotle" && exit 1 || exit 0
..but then realized it's not what I need, because this will not allow me to push aristotle changes at all.
So I'm not sure what to do. Ideally, I want developers to see the "wrong way merge" message when they attempt to commit a merge from default to aristotle locally (obviously, there should be a double-check on the central repo), while merging from production branch to the default one should be possible.
Thanks!
This should do it. It uses a revset query to find any merges into aristotle from default.
hg log -r 'children(p2(::aristotle and ::default and merge()) and branch(default)) and branch(aristotle)'
::aristotle and ::default and merge() finds all merges that are ancestors of both aristotle and default branches
p2(...) and branch(default) grabs the second parent (incoming changeset) that are on the default branch.
children(...) and branch(aristotle) then grabs the actual merge changeset that is on the aristotle branch.
I recently needed to figure this out myself but also needed to ensure that default wasn't indirectly merged into my release branch, i.e. default -> feature -> release.
This is pretty much an exact duplicate of a question from a few days ago Ensuring a merge between branches happens in one direction
but I didn't like my answer to that one, so how about this one:
Keep separate clones for past releases. When, in your example above, you decide you need to do an emergency fix on aristotle do this instead:
cd ..
hg clone -r aristotle myrepo myrepo-aristotle
then you have a clone with only aristotle in it and you can't accidentally merge it to default in that clone.
That said, it's still not a great answer. The only real consolation is that if you merge in a direction you don't like you can always re-clone the two heads and merge in the other direction.
I found this question whilst looking for a solution to a related problem, I know its an old question, but thought I would share our solution.
We have release branches called release-x.y.z and ongoing development on default. We use a hook based on the example precommit_badbranch_badmerge found at https://www.mercurial-scm.org/wiki/HookExamples.
We basically extract the version number for each branch involved in any merge and make sure it is going the right way (default is treated as 9999.9999.9999 anything that isn't a release or default branch gets -1,-1,-1 (can be merged into anything).
Note that this only does a simple 'immediate' check, it doesn't catch issues where someone merges from default to the blah branch and then onto a release branch.
As a side note, we also have a policy that the merge to default must be pushed at the same time as the original change on the release branch, this avoids someone else having to do the merge in order to merge their own changes - it is enforcing that that I was looking for a solution for.
Note, you invoke this using the hooks section of the server/central repo
[hooks]
pretxnchangegroup.prevent_bad_merges = python:<path_to_hook>\prevent_bad_merges.py:prevent_bad_merges
python hook below:
# based on precommit_badbranch_badmerge
# at https://www.mercurial-scm.org/wiki/HookExamples
import re
# this isnt a proper compare, it will just return numbers > 0 if source is after target
def compare_versions(source_version, target_version):
# if either side is -1, ignore it
if (source_version[0] == -1) or (target_version[0] == -1):
return 0;
if source_version[0] > target_version[0]:
return 1
elif source_version[0] == target_version[0]:
if source_version[1] > target_version[1]:
return 2
elif source_version[1] == target_version[1]:
if source_version[2] > target_version[2]:
return 3
return 0
def get_version(branch):
if branch == 'default':
major=9999
minor=9999
revision=9999
else:
# note python uses ?P for named matches
match = re.match('(release-(?P<major>\d)\.(?P<minor>\d)\.(?P<revision>\d))', branch)
if match:
major = int(match.group('major'))
minor = int(match.group('minor'))
revision = int(match.group('revision'))
else:
major = -1
minor = -1
revision = -1
return [major,minor,revision]
def prevent_bad_merges(ui, repo, node, **kwargs):
ui.debug("in hook\n")
for rev in xrange(repo[node].rev(), len(repo)):
ui.debug("in loop\n")
# get context (change)
ctx = repo[rev]
ui.debug("got ctx\n")
if len(ctx.parents()) > 1:
ui.debug("got a merge\n")
branch = ctx.branch()
ui.debug(branch +"\n")
parent1 = ctx.parents()[0]
ui.debug("got parent1\n")
parent2 = ctx.parents()[1]
ui.debug("got parent2\n")
target_branch = repo[parent1.node()].branch()
ui.debug("got parent1 branch\n")
target_version = get_version(target_branch)
ui.debug("got parent1 version\n")
source_branch = repo[parent2.node()].branch()
ui.debug("got parent2 branch\n")
source_version = get_version(source_branch)
ui.debug("got parent2 version\n")
# This could happen if someone does
# hg update 1.1-branch
# hg branch 1.2-branch
# hg merge 1.0-branch
# which is a strange thing to do. So disallow it.
if target_branch != branch:
ui.warn('PREVENT BAD MERGE HOOK FAILED : \n'
'merging to a different branch from first parent '
'is just weird: please don\'t do that\n')
return True
ui.debug(source_branch, "\n")
ui.debug(str(source_version[0]), "\n")
#ui.debug("major:", source_version[0], "\n")
#ui.debug("minor:", source_version[1], "\n")
#ui.debug("revn :", source_version[2], "\n")
ui.debug(target_branch, "\n")
ui.debug(str(target_version[0]), "\n")
#ui.debug("major:", target_version[0], "\n")
#ui.debug("minor:", target_version[1], "\n")
#ui.debug("revn :", target_version[2], "\n")
# Check for backwards merge.
if compare_versions(source_version, target_version) > 0:
ui.warn('PREVENT BAD MERGE HOOK FAILED : \n'
'invalid backwards merge from %r to %r\n'
% (source_branch, target_branch))
return True
else:
ui.debug("Not a merge\n")
# Not merging: nothing more to check.
return False

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