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 IEnumerable<HgChangesetDetails> GetChangesetsDetails(HgRevset hgRevset)
        {
            //
            // We need to preload all changesets and manifests
            var changesets =
                GetChangesets(
                    hgRevset.
                        Select(e => Changelog.Revlog[e.Revision]).
                        SelectMany(e => new[] { e.Revision, e.FirstParentRevision, e.SecondParentRevision }).
                        Where(e => e != uint.MaxValue).
                        Distinct()).
                        ToDictionary(c => c.Metadata.NodeID);

            var manifests =
                    GetManifestEntries(
                        new HgRevset(
                            changesets.Values.
                                Select(c => c.ManifestNodeID).
                                Select(e => Manifest.Revlog.GetEntry(e)))).
                        ToDictionary(m => m.Metadata.NodeID);

            foreach(var entry in hgRevset.OldestToNewest)
            {
                var c = changesets[entry.NodeID];
                
                //
                // This is somehow possible for imported repos
                if(c.ManifestNodeID == HgNodeID.Null) continue;

                var m = manifests[c.ManifestNodeID];

                IList<HgChangesetFileDetails> files;

                //
                // For a very first changeset we treat all files as Added
                if(c.Metadata.FirstParentRevisionNodeID == HgNodeID.Null)
                {
                    files =
                        m.Files.
                            Select(mfe => new HgChangesetFileDetails(mfe.Path, HgChangesetFileOperation.Added, null)).
                            ToList();
                } //  if
                else
                {
                    Func<HgManifestFileEntry, string> fileName = (mfe) => mfe.Path.FullPath.TrimStart('/');

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

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

                    //
                    // This is possible for imported repos. See above
                    var parentManifestFiles = mp1 == null ?
                        new List<string>() : 
                        mp1.Files.Select(fileName).ToList();

                    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).ToList();
                    var removed = removedFiles.Select(Removed);
                    var added = addedFiles.Select(Added);
                    var modified = modifiedFiles.Select(Modified);

                    //
                    // 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);
                yield return changesetDetails;
            } // foreach
        }