public IList<HgChangeset> GetChangesets(HgRevset hgRevset)
 {
     return GetChangesets(hgRevset.Select(hre => hre.Revision)).ToList();
 }
        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
        }
        private IEnumerable<HgChunk> BuildBundleGroup(HgRepository hgRepository, HgRevlog hgRevlog, HgRevset hgRevset, Action<HgRevlogEntryData> callback = null)
        {
            var hgRevlogReader = new HgRevlogReader(hgRevlog, fileSystem);
            
            //
            // See http://stackoverflow.com/a/10359273/60188. Pure magic
            var revisionChunks =
                hgRevset.
                    Select(hre => hre.Revision).
                    OrderBy(r => r).
                    Select((r, i) => new { r, i }).
                    GroupBy(x => x.r - x.i). 
                    Select(x => x.Select(xx => xx.r)).
                    Select(c => c.ToArray()).
                    ToArray();

            if(revisionChunks.Length == 0) yield break;

            byte[] prev = null;
            uint prevRev = uint.MaxValue;
            var prediff = false;
            var hgRevlogEntry = hgRevlog[revisionChunks[0][0]];
            if(hgRevlogEntry.FirstParentRevisionNodeID != HgNodeID.Null)
            {
                prev = hgRevlogReader.ReadRevlogEntry(hgRevlogEntry.FirstParentRevision).Data;
                prediff = true;
            }
            
            foreach(var revisionChunk in revisionChunks)
            {
                foreach(var revision in revisionChunk)
                {
                    hgRevlogEntry = hgRevlog[revision];
                    var hgChangeset = hgRepository.Changelog.Revlog[hgRevlogEntry.LinkRevision];
                
                    byte[] data = null;

                    if(prev == null || hgRevlogEntry.BaseRevision == hgRevlogEntry.Revision || prediff || (prevRev != UInt32.MaxValue && prevRev + 1 != revision))
                    {
                        var hgRevlogEntryData = hgRevlogReader.ReadRevlogEntry(revision);

                        if(prev == null)
                        {   
                            //
                            // Trivial case
                            var buffer = new byte[hgRevlogEntryData.Data.Length + 12];
                            using(var stream = new MemoryStream(buffer))
                            using(var binaryWriter = new BigEndianBinaryWriter(stream))
                            {
                                binaryWriter.Write((uint)0);
                                binaryWriter.Write((uint)0);
                                binaryWriter.Write((uint)hgRevlogEntryData.Data.Length);
                                binaryWriter.Write(hgRevlogEntryData.Data);
                            } // using

                            data = buffer;
                        } // if
                        else
                        {
                            data = BDiff.Diff(prev, hgRevlogEntryData.Data);
                            if(prediff)
                                prediff = false;
                        } // else

                        prev = hgRevlogEntryData.Data;
                    } // if
                    else
                    {
                        data = hgRevlogReader.ReadRevlogEntryDataRaw(revision);
                        prev = MPatch.Patch(prev, new List<byte[]> { data });
                    } // else

                    if(callback != null) callback(new HgRevlogEntryData(hgRevlogEntry, prev));

                    if(performIntegrityChecks)
                    {
                        var expectedNodeID = GetRevlogEntryDataNodeID(hgRevlogEntry.FirstParentRevisionNodeID, hgRevlogEntry.SecondParentRevisionNodeID, prev);
                        if(expectedNodeID != hgRevlogEntry.NodeID)
                        {
                            // TODO: Exception class
                            throw new ApplicationException("integrity violation for " + hgRevlogEntry.NodeID.Short);
                        } // if
                    } // if

                    var hgChunk = new HgChunk(hgRevlogEntry.NodeID, hgRevlogEntry.FirstParentRevisionNodeID, hgRevlogEntry.SecondParentRevisionNodeID,
                        hgChangeset.NodeID, data);

                    yield return hgChunk;

                    prevRev = revision;
                } // foreach
            } // foreach
        }