Finding Uncommon Changesets

posted by Steve Losh on December 9, 2009

This post was inspired by an email on the mercurial-devel mailing list.

The question in a nutshell is: how can you determine which changesets are included in revision X but not included in revision Y? This could be useful if you’re updating a piece of software you use and want to know everything that changed between the old revision and the new one.

It sounds like a simple task, but there are a few tricky things that you need to watch out for.

Note: In this tip I’ll be using the shortlog alias instead of the normal hg log command to make the output more readable. Everything will work exactly the same with hg log (because hg slog is just an alias to hg log with an extra template), so feel free to use that instead if you prefer more detail.

The Simple (and Incorrect) Way

Let’s start with the simplest way to list revisions: the --rev option. Say we have a simple repository whose graph looks like this:

$ hg glog
o  4 Fix another bug. (6 seconds ago by Steve Losh) tip 
|
o  3 Fix a bug. (16 seconds ago by Steve Losh)  
|
@  2 Add another simple feature. (34 seconds ago by Steve Losh)  
|
o  1 Add a simple feature. (40 seconds ago by Steve Losh)  
|
o  0 Initial revision. (7 minutes ago by Steve Losh)

If you’re currently at revision 2 and want to know what changes would be included if you updated to tip, you might use something like this:

 $ hg slog --rev 2:tip
 2 Add another simple feature. (5 minutes ago by Steve Losh) 
 3 Fix a bug. (5 minutes ago by Steve Losh) 
 4 Fix another bug. (5 minutes ago by Steve Losh) tip

You can see that the output includes revision 2, which really shouldn’t be listed because it’s the one you’re already at. This probably isn’t a big deal because you can just ignore it.

However, this approach doesn’t work if you have multiple branches in the repository that are being worked on simultaneously. For example:

$ hg glog
@  6 Fix a critical bug. (1 second ago by Steve Losh) tip 
|
| o  5 Start the rewrite of the UI. (24 seconds ago by Steve Losh)  ui-rewrite
| |
o |  4 Fix another bug. (8 minutes ago by Steve Losh)  
| |
o |  3 Fix a bug. (8 minutes ago by Steve Losh)  
| |
o |  2 Add another simple feature. (9 minutes ago by Steve Losh)  
|/
o  1 Add a simple feature. (9 minutes ago by Steve Losh)  
|
o  0 Initial revision. (16 minutes ago by Steve Losh)

$ hg slog --rev 2:tip
2 Add another simple feature. (9 minutes ago by Steve Losh) 
3 Fix a bug. (9 minutes ago by Steve Losh) 
4 Fix another bug. (9 minutes ago by Steve Losh) 
5 Start the rewrite of the UI. (54 seconds ago by Steve Losh) 
6 Fix a critical bug. (31 seconds ago by Steve Losh) tip

Notice that the output of the hg slog command included changeset 5. It’s on a separate branch, so updating from 2 to tip (6) won’t actually include changes from 5.

This happens because when you specify a range of revisions Mercurial will step through them in the revision-number order. That means saying 1:4 really means “1, 2, 3, and 4 no matter which branches they happen to be on.”

We can do better than this by using some extra hg log options.

The Correct Way

If we step back and think about the problem we’re trying to solve, we can reduce it to a simple definition. We want to see all changesets that:

To solve the first problem, we can use a combination of two options, --rev and --follow, like so:

$ hg slog --rev tip:0 --follow
6 Fix a critical bug. (11 minutes ago by Steve Losh) tip
4 Fix another bug. (20 minutes ago by Steve Losh) 
3 Fix a bug. (20 minutes ago by Steve Losh) 
2 Add another simple feature. (20 minutes ago by Steve Losh) 
1 Add a simple feature. (21 minutes ago by Steve Losh) 
0 Initial revision. (28 minutes ago by Steve Losh)

Notice how the extra changeset on the other branch isn’t shown.

Using --rev DESTINATION:0 with --follow means “show me every changeset that is an ancestor of DESTINATION“. Check out hg help log for the details of what each option does.

We’ve still got one more thing to do — we want to remove changesets that are already included in our current revision from the list:

$ hg slog --rev tip:0 --follow --prune 2
6 Fix a critical bug. (16 minutes ago by Steve Losh) tip
4 Fix another bug. (25 minutes ago by Steve Losh) 
3 Fix a bug. (25 minutes ago by Steve Losh)

That’s it! Changesets 6, 4 and 3 are the changesets that will take effect if we update from 2 to 6 in our sample repository.

The general form for this command looks like this:

hg slog --rev DESTINATION:0 --follow --prune SOURCE

Of course you can use all the normal shortcuts for specifying revisions. If you want to see would be included in an update to tip from the current revision you’re working on:

hg slog -r tip:0 -fP .

Note: this command won’t work if you’re updating “backwards”. It can only show you additional changesets that would be included — it will not mention changesets that are included in SOURCE but not in DESTINATION.

How can you tell which changesets are included in revision X but not in Y?