public HgChangesetDetails GetChangesetDetails(HgNodeID hgNodeID, int context = 3)
        {
            var c = Changelog[hgNodeID];
            if(c == null) return null;

            var m = Manifest[c.ManifestNodeID];
            if(m == null) return new HgChangesetDetails(c, new List<HgChangesetFileDetails>());

            IList<HgChangesetFileDetails> files;

            var diffGenerator = new HgDiffGenerator();
            
            //
            // For a very first changeset we treat all files as Added
            if(c.Metadata.FirstParentRevisionNodeID == HgNodeID.Null)
            {
                files =
                    m.Files.
                        Select(f => new {
                            f = f.Path,
                            file = GetFile(m[f.Path.FullPath]),
                        }).
                        Select(f => new HgChangesetFileDetails(f.f, HgChangesetFileOperation.Added, 
                            f.file.IsBinary ? 
                                null :
                                GetHgRollupFileDiffInfo(f.f, f.file.IsBinary, diffGenerator.UnifiedDiff("", Encoder.DecodeAsUtf8(f.file.Data), context, false, true, true)))).
                        ToList();
            } //  if
            else
            {
                Func<HgManifestFileEntry, string> fileName = mfe => mfe.Path.FullPath.TrimStart('/');

                HgManifestEntry mp1 = null;
                ISet<string> cf = null, f_ = null, fp1 = null;

                cf = new HashSet<string>(c.Files.Select(f => f.TrimStart('/')).ToList());
                f_ = new HashSet<string>(m.Files.Select(fileName));

                var p1 = Changelog[c.Metadata.FirstParentRevisionNodeID];
                mp1 = Manifest[p1.ManifestNodeID];
                fp1 = new HashSet<string>(mp1.Files.Select(fileName));

                var addedFiles = f_.Except(fp1).Intersect(cf).ToList();
                var removedFiles = fp1.Except(f_).Intersect(cf).ToList();
                var modifiedFiles = f_.Intersect(fp1).Intersect(cf).Where(f => m[f].FilelogNodeID != mp1[f].FilelogNodeID || 
                    !GetFile(m[f]).Data.SequenceEqual(GetFile(mp1[f]).Data)).ToList();

                Func<HgFile, HgFile, HgUnifiedDiff> diff = (f /* from */, t /* to */) => {
                    if(f != null && f.IsBinary || t != null && t.IsBinary) return null;

                    var a = f == null ? "" : Encoder.DecodeAsUtf8(f.Data);
                    var b = t == null ? "" : Encoder.DecodeAsUtf8(t.Data);

                    return diffGenerator.UnifiedDiff(a, b, context, false, true, true);
                };

                var removed = 
                    removedFiles.
                        Select(f => new {
                            f,
                            file = GetFile(mp1[f]),
                        }).
                        Where(f => f.file != null).
                        Select(f => Removed(f.f, 
                            GetHgRollupFileDiffInfo(
                                new HgPath(f.f),
                                f.file.IsBinary,
                                diff(f.file, null)))).
                        Where(f => f.Diff != null && (f.Diff.Additions > 0 || f.Diff.Removals > 0));

                var added = 
                    addedFiles.
                        Select(f => new {
                            f,
                            file = GetFile(m[f]),
                        }).
                        Where(f => f.file != null).
                        Select(f => Added(f.f, 
                            GetHgRollupFileDiffInfo(
                                new HgPath(f.f),
                                f.file.IsBinary,
                                diff(null, f.file)))).
                        Where(f => f.Diff != null && (f.Diff.Additions > 0 || f.Diff.Removals > 0));

                var modified = 
                    modifiedFiles.
                        Select(f => new {
                            f,
                            file = GetFile(m[f]),
                        }).
                        Where(f => f.file != null).
                        Select(f => Modified(f.f, 
                            GetHgRollupFileDiffInfo(
                                new HgPath(f.f),
                                f.file.IsBinary,
                                diff(GetFile(mp1[f.f]), f.file)))).
                        Where(f => f.Diff == null || f.Diff.Additions > 0 || f.Diff.Removals > 0);

                //
                // Prepare enough room to avoid reallocations later on
                files = new List<HgChangesetFileDetails>(f_.Count * 2);

                files.AddRange(added);
                files.AddRange(modified);
                files.AddRange(removed);
            } // else

            var changesetDetails = new HgChangesetDetails(c, files);
            return changesetDetails;
        }
        public HgCompareInfo PerformComparison(HgNodeID baseNodeID, HgNodeID headNodeID, bool includeFileDiffs = false)
        {
            var hgRevsetManager = new HgRevsetManager();
            var @base = Changelog[baseNodeID];
            var head = Changelog[headNodeID];

            var baseAncestors = hgRevsetManager.GetAncestors(this, new HgRevset(@base.Metadata));
            var headAncestors = hgRevsetManager.GetAncestors(this, new HgRevset(head.Metadata));

            var changesets = GetChangesets(headAncestors - baseAncestors);

            if(changesets.Count == 0)
                return new HgCompareInfo(@base, head, new List<HgChangeset>(), new List<HgRollupFileDiffInfo>());

            var files = changesets.SelectMany(c => c.Files).Distinct().OrderBy(f => f).ToList();

            var startManifest = Manifest[@base.ManifestNodeID];
            var endManifest = Manifest[head.ManifestNodeID];

            //
            // Only pick files that were indeed changed between two bounding changesets
            
            var diffs = new List<HgRollupFileDiffInfo>();
            if(includeFileDiffs)
            {
                var changedFiles = files.Where(f => startManifest[f] == null && endManifest[f] != null || startManifest[f] != null && endManifest[f] == null || (startManifest[f] != null && endManifest[f] != null && startManifest[f].FilelogNodeID != endManifest[f].FilelogNodeID)).ToList();
                var diffGenerator = new HgDiffGenerator();
                diffs = changedFiles.
                    Select(f => new {
                        file = f,
                        start = startManifest[f],
                        end = endManifest[f]
                    }).
                    Select(f => new {
                        f.file,
                        start = f.start == null ? null : GetFile(f.start),
                        end = f.end == null ? null : GetFile(f.end)
                    }).
                    Select(f => new {
                        f.file,
                        isBinary = f.start != null && f.start.IsBinary || f.end != null && f.end.IsBinary,
                        f.start,
                        f.end
                    }).
                    Select(f => new {
                        f.file,
                        f.isBinary,
                        start = f.start == null || f.isBinary ? "" : Encoder.DecodeAsUtf8(f.start.Data),
                        end = f.end == null || f.isBinary ? "" : Encoder.DecodeAsUtf8(f.end.Data)
                    }).
                    Select(f => new { 
                        f.file,
                        f.isBinary,
                        diff = f.isBinary ? null : diffGenerator.UnifiedDiff(f.start, f.end, 3)
                    }).
                    //
                    // Do not include files that were not in fact changed between revision
                    Where(f => f.diff == null || f.diff.Hunks.Sum(h => h.Lines.Count(l => l.Added || l.Removed )) > 0).
                    Select(f => GetHgRollupFileDiffInfo(new HgPath(f.file), f.isBinary, f.diff)).
                    ToList();
            }

            return new HgCompareInfo(@base, head, changesets, diffs);
        }
        public HgAnnotation GetAnnotation(HgPath path, HgNodeID? startFilelogNodeID = null)
        {
            var fileHistory = repository.GetFileHistory(path, startFilelogNodeID).OrderBy(c => c.Metadata.Revision).ToList();

            var prev = "";
            var diffGenerator = new HgDiffGenerator();

            var annotationLines = new LinkedList<HgAnnotationLine>();

            var manifestEntries = 
                repository.
                    GetManifestEntries(new HgRevset(fileHistory.Select(f => repository.Manifest.Revlog.GetEntry(f.ManifestNodeID)))).
                    ToDictionary(me => me.Metadata.NodeID, me => me);

            foreach(var changeset in fileHistory)
            {
                var manifest = manifestEntries[changeset.ManifestNodeID];
                var current =
                    Encoding.UTF8.GetString(
                        repository.GetFile(manifest.GetFile(path)).Data);

                var diff = diffGenerator.Diff(prev, current);

                LinkedListNode<HgAnnotationLine> annotationLine = null; // before first
                for(var i = 0; i < diff.Lines.Count; ++i)
                {
                    if(diff.Lines[i].Unchanged)
                    {
                        annotationLine = annotationLine == null ? 
                            annotationLines.First : 
                            annotationLine.Next;
                        
                        continue;
                    } // if

                    if(diff.Lines[i].Removed)
                    {
                        if(annotationLine == null)
                        {
                            annotationLines.RemoveFirst();
                        } // if
                        else
                        {
                            if(annotationLine.Next != null)
                                annotationLines.Remove(annotationLine.Next);
                        } // else
                    }
                    else
                    {
                        if(annotationLine == null)
                        {
                            annotationLines.AddFirst(new HgAnnotationLine(changeset, diff.Lines[i].Content));
                            annotationLine = annotationLines.First;
                        }
                        else
                        {
                            annotationLines.AddAfter(annotationLine, new HgAnnotationLine(changeset, diff.Lines[i].Content));
                            annotationLine = annotationLine.Next;
                        }
                    }
                } // for


                prev = current;
            } // foreach


           /* var lines = prev.Split('\n');
            Debug.Assert(
                lines.Length == annotationLines.Count,
                string.Format("Annotation line count mismatch: {0} in annotation vs {1} in file", annotationLines.Count, lines.Length));*/

            return new HgAnnotation(path, annotationLines);
        }