In Mercurial , revision numbers are local-clone specific, and they are provided as a facility to point to a specific changeset in a more user-friendly way than a changeset id.
However when displaying the timeline graph, TortoiseHG will order by revision number. If some guy pushes commits not that often, you'll get all his commits at once with adjacent revision numbers, and it will mess up the whole point of the graph.
This graph would be more insightful: there are two lines of development, with two authors, one merging the work of the other one:
Therefore, is it possible to manually reorder the revision numbers (as long as the topological order of changesets is still compatible with the new numbers?
Thanks.
I have a similar use case: I came to this question because I pushed a bunch of changesets from different branches at once, and they did not arrive sorted by date. I now have some merges with low-numbered parents that are not in fact that old; scrolling up and down the log to see them is a pain. I followed the approach suggested in @Ry4an's answer-- it's really quite simple, if you can predict (i.e., compute) the changeset order you want.
If your workflow only merges branch heads, you can get the desired revision order by sorting revsets by date. This you can do with the following command:
hg log -r 'sort(0:tip, date)' --template '{rev}\n'
You can then clone the repository and use a loop in your favorite scripting language to pull the revisions one by one in chronological order. Rather than init a repository from scratch, I ended up doing it like this:
hg clone -r 0 messy-repo sorted-repo
cd sorted-repo
for REV in `hg log -R ../messy-repo -r 'sort(1:tip, date)' --template '{rev}\n'`
do
hg pull ../messy-repo -r $REV
done
I should note (before somebody else does :-)) that this will increase the storage size of the repository, because of the way deltas are computed. I don't mind.
As others are saying, it's possible, but probably not worth the work, since it would be local to your clone (and any clones of your clone). It's not something you could push to a remote repository w/o deleting everything there first, and even if you did that the people who had clones from that remote repository locally would see "no changes" when they pulled.
That said, if you want to try you just need to use a series of hg pull -r REV
commands into a new clone. Something like this:
hg init my_reordered_clone
cd my_reordered_clone
hg pull -r d84b1 ../un_reordered_clone
hg pull -r 6d269 ../un_reordered_clone
hg pull -r bb9e4 ../un_reordered_clone
Clearly that's too much work to be worth it for aesthetic purposes, but the concept there is that when you pull with -r
you get that changeset and all of its ancestors, so if you do your pulls at the points where anonymous branches merged one at a time, you'll be pulling only the additional changesets from that line of development.
It is possible to reorder your repository (that's what contrib/shrink-revlog.py
does). But in this case it seems overkill and complicated.
Since this is mostly a display issue, you should instead ask THG to implement the reordering you would like to have. I admittedly have no idea what you find messed up in the graph above.
Below is an implementation to narrow the graph width using a csscript
The command to copy your repo with the history reordered is
cs-script\cscs.exe HgSortMergeChangesets.cs fromRepo toNewRepo
The file "HgSortMergeChangesets.cs" has the following contents:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
class Program
{
static int Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("usage: SortHgRepo <fromRepo> <toRepo>");
return -1;
}
var fromRepo = new DirectoryInfo(args[0]);
var toRepo = new DirectoryInfo(args[1]);
int errorCode = VerifyParameters(toRepo, fromRepo);
if (errorCode != 0)
{
return errorCode;
}
var revsOutput = ExecCmdReturnStdOut("hg.exe", "log -r \"sort(merge(),date)\" -T \"{rev} {date|date}\\n\"", fromRepo.FullName, Console.WriteLine);
var mergeChangesets = ParseChangesetLog(revsOutput)
.ToList();
ExecCmdReturnStdOut("hg.exe", string.Format("clone -U -r 0 . \"{0}\"", toRepo.FullName), fromRepo.FullName, Console.WriteLine);
foreach (var changeset in mergeChangesets)
{
ExecCmdReturnStdOut("hg.exe", string.Format("pull \"{1}\" -r {0}", changeset.ChangesetId, fromRepo.FullName), toRepo.FullName, Console.WriteLine);
}
ExecCmdReturnStdOut("hg.exe", string.Format("pull \"{0}\"", fromRepo.FullName), toRepo.FullName, Console.WriteLine);
return 0;
}
private static int VerifyParameters(DirectoryInfo toRepo, DirectoryInfo fromRepo)
{
if (toRepo.Exists)
{
Console.WriteLine("The destination repo already exists: {0}", toRepo);
{
return -2;
}
}
if (!fromRepo.Exists)
{
Console.WriteLine("The source repo does not exists: {0}", fromRepo);
{
return -3;
}
}
// make sure the source dir is a repo
try
{
var identity = ExecCmdReturnStdOut("hg.exe", "identify", fromRepo.FullName, Console.WriteLine);
Console.WriteLine(identity);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("The source directory, {0}, does not look like an Hg repo.", fromRepo);
return -4;
}
return 0;
}
private static IEnumerable<Changeset> ParseChangesetLog(string revsOutput)
{
using (var r = new StringReader(revsOutput))
{
string line;
while ((line = r.ReadLine()) != null)
{
var spacePos = line.IndexOf(' ');
yield return new Changeset
{
ChangesetId = int.Parse(line.Substring(0, spacePos)),
DateStr = line.Substring(spacePos + 1)
};
}
}
}
class Changeset
{
public int ChangesetId;
public string DateStr;
public DateTime Date { get { return DateTime.ParseExact(DateStr, "ddd MMM dd H:mm:ss yyyy zzz", null); } }
}
public static string ExecCmdReturnStdOut(string program, string args, string workingDir, Action<string> writeline)
{
writeline(String.Format("Executing: \"{0}\" {1} in {2}", program, args, workingDir));
using (var proc = new Process())
{
proc.StartInfo.Arguments = args;
proc.StartInfo.CreateNoWindow = false;
proc.StartInfo.FileName = program;
proc.StartInfo.WorkingDirectory = workingDir;
proc.StartInfo.RedirectStandardError = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.Start();
var output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit();
if (proc.ExitCode != 0)
{
throw new Exception(string.Format("error code {0} returned when running command {1} in dir {2}", proc.ExitCode, "\"" + program + "\" " + args, workingDir));
}
return output;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With