diff --git a/LibGit2Sharp.Tests/IndexerFixture.cs b/LibGit2Sharp.Tests/IndexerFixture.cs new file mode 100644 index 000000000..73623faae --- /dev/null +++ b/LibGit2Sharp.Tests/IndexerFixture.cs @@ -0,0 +1,51 @@ +using System; +using System.IO; +using LibGit2Sharp.Advanced; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class IndexerFixture : BaseFixture + { + [Fact] + public void CanIndexFromAFilePath() + { + TransferProgress copiedProgress = default(TransferProgress); + using (var repo = new Repository(SandboxStandardTestRepoGitDir())) + { + var expectedId = new ObjectId("a81e489679b7d3418f9ab594bda8ceb37dd4c695"); + var path = Path.Combine(repo.Info.Path, "objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack"); + TransferProgress progress; + var packId = Indexer.Index(out progress, path, repo.Info.WorkingDirectory, 438 /* 0666 */, + onProgress: (p) => { + copiedProgress = p; + return true; + }); + + Assert.Equal(expectedId, packId); + Assert.Equal(1628, progress.TotalObjects); + Assert.Equal(1628, copiedProgress.TotalObjects); + } + } + + [Fact] + public void CanIndexFromAStream() + { + using (var repo = new Repository(SandboxStandardTestRepoGitDir())) + { + var expectedId = new ObjectId("a81e489679b7d3418f9ab594bda8ceb37dd4c695"); + var path = Path.Combine(repo.Info.Path, "objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack"); + TransferProgress progress; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + var packId = Indexer.Index(out progress, stream, repo.Info.WorkingDirectory, 438 /* 0666 */); + Assert.Equal(expectedId, packId); + Assert.Equal(1628, progress.TotalObjects); + } + } + } + + } +} + diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index f93a24f30..6aafebda9 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -122,6 +122,7 @@ + diff --git a/LibGit2Sharp/Advanced/Indexer.cs b/LibGit2Sharp/Advanced/Indexer.cs new file mode 100644 index 000000000..efdfd32c0 --- /dev/null +++ b/LibGit2Sharp/Advanced/Indexer.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp.Advanced +{ + /// + /// The Indexer is our implementation of the git-index-pack command. It is used to process the packfile + /// which comes from the remote side on a fetch in order to create the corresponding .idx file. + /// + public class Indexer : IDisposable + { + + readonly IndexerSafeHandle handle; + readonly TransferProgressHandler callback; + + Indexer(string prefix, uint mode, ObjectDatabase odb = null, TransferProgressHandler onProgress = null) + { + /* The runtime won't let us pass null as a SafeHandle, wo create a "dummy" one to represent NULL */ + ObjectDatabaseSafeHandle odbHandle = odb != null ? odb.Handle : new ObjectDatabaseSafeHandle(); + callback = onProgress; + handle = Proxy.git_indexer_new(prefix, odbHandle, mode, GitDownloadTransferProgressHandler); + } + + /// + /// Index the specified stream. This function runs synchronously; you may want to run it + /// in a background thread. + /// + /// The amount of objects processed etc will be written to this structure on exit + /// Stream to run the indexing process on + /// Path in which to store the pack and index files + /// Filemode to use for creating the pack and index files + /// Optional object db to use if the pack contains thin deltas + /// Function to call to report progress. It returns a boolean indicating whether + /// to continue working on the stream + public static ObjectId Index(out TransferProgress progress, Stream stream, string prefix, uint mode, ObjectDatabase odb = null, TransferProgressHandler onProgress = null) + { + var buffer = new byte[65536]; + int read; + var indexProgress = default(GitTransferProgress); + + using (var idx = new Indexer(prefix, mode, odb, onProgress)) + { + var handle = idx.handle; + + do + { + read = stream.Read(buffer, 0, buffer.Length); + Proxy.git_indexer_append(handle, buffer, (UIntPtr)read, ref indexProgress); + } while (read > 0); + + Proxy.git_indexer_commit(handle, ref indexProgress); + + progress = new TransferProgress(indexProgress); + return Proxy.git_indexer_hash(handle); + } + } + + /// + /// Index the packfile at the specified path. This function runs synchronously; you may want to run it + /// in a background thread. + /// The amount of objects processed etc will be written to this structure on exit + /// Path to the file to index + /// Path in which to store the pack and index files + /// Filemode to use for creating the pack and index files + /// Optional object db to use if the pack contains thin deltas + /// Function to call to report progress. It returns a boolean indicating whether + /// to continue working on the stream + + /// + public static ObjectId Index(out TransferProgress progress, string path, string prefix, uint mode, ObjectDatabase odb = null, TransferProgressHandler onProgress = null) + { + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + return Index(out progress, fs, prefix, mode, odb, onProgress); + } + } + + // This comes from RemoteCallbacks + /// + /// The delegate with the signature that matches the native git_transfer_progress_callback function's signature. + /// + /// structure containing progress information. + /// Payload data. + /// the result of the wrapped + int GitDownloadTransferProgressHandler(ref GitTransferProgress progress, IntPtr payload) + { + bool shouldContinue = true; + + if (callback != null) + { + shouldContinue = callback(new TransferProgress(progress)); + } + + return Proxy.ConvertResultToCancelFlag(shouldContinue); + } + + #region IDisposable implementation + + public void Dispose() + { + handle.SafeDispose(); + } + + #endregion + } +} diff --git a/LibGit2Sharp/Core/Handles/IndexerSafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexerSafeHandle.cs new file mode 100644 index 000000000..1e60f6491 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/IndexerSafeHandle.cs @@ -0,0 +1,13 @@ +using System; +namespace LibGit2Sharp.Core.Handles +{ + internal class IndexerSafeHandle : SafeHandleBase + { + protected override bool ReleaseHandleImpl() + { + Proxy.git_indexer_free(handle); + return true; + } + } +} + diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index a8fb1eacf..0e84d56f7 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1578,6 +1578,27 @@ internal static extern int git_treebuilder_insert( [DllImport(libgit2)] internal static extern int git_cherrypick(RepositorySafeHandle repo, GitObjectSafeHandle commit, GitCherryPickOptions options); + + [DllImport(libgit2)] + public static extern int git_indexer_new( + out IndexerSafeHandle handle, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath prefix, + uint mode, + ObjectDatabaseSafeHandle odbHandle, + git_transfer_progress_callback progress_callback, + IntPtr payload); + + [DllImport(libgit2)] + public static extern int git_indexer_append(IndexerSafeHandle idx, byte[] data, UIntPtr size, ref GitTransferProgress progress); + + [DllImport(libgit2)] + public static extern int git_indexer_commit(IndexerSafeHandle idx, ref GitTransferProgress progress); + + [DllImport(libgit2)] + public static extern OidSafeHandle git_indexer_hash(IndexerSafeHandle idx); + + [DllImport(libgit2)] + public static extern void git_indexer_free(IntPtr idx); } } // ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index ab2f7eadc..04bc86fd4 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1086,6 +1086,52 @@ public static void git_index_clear(Index index) #endregion + #region git_indexer_ + + public static IndexerSafeHandle git_indexer_new(FilePath prefix, ObjectDatabaseSafeHandle odbHandle, uint mode, + NativeMethods.git_transfer_progress_callback progressCallback) + { + using (ThreadAffinity()) + { + IndexerSafeHandle handle; + + int res = NativeMethods.git_indexer_new(out handle, prefix, mode, odbHandle, progressCallback, IntPtr.Zero); + Ensure.ZeroResult(res); + + return handle; + } + } + + public static void git_indexer_append(IndexerSafeHandle handle, byte[] data, UIntPtr length, ref GitTransferProgress progress) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_indexer_append(handle, data, length, ref progress); + Ensure.ZeroResult(res); + } + } + + public static void git_indexer_commit(IndexerSafeHandle handle, ref GitTransferProgress progress) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_indexer_commit(handle, ref progress); + Ensure.ZeroResult(res); + } + } + + public static ObjectId git_indexer_hash(IndexerSafeHandle handle) + { + return NativeMethods.git_indexer_hash(handle).MarshalAsObjectId(); + } + + public static void git_indexer_free(IntPtr handle) + { + NativeMethods.git_indexer_free(handle); + } + + #endregion + #region git_merge_ public static IndexSafeHandle git_merge_trees(RepositorySafeHandle repo, GitObjectSafeHandle ancestorTree, GitObjectSafeHandle ourTree, GitObjectSafeHandle theirTree) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 3ca4a3892..2aa011cf6 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -340,6 +340,8 @@ + + diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index b4b1da006..a0b77cd77 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -60,6 +60,11 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + internal ObjectDatabaseSafeHandle Handle + { + get { return handle; } + } + /// /// Determines if the given object can be found in the object database. /// diff --git a/libgit2 b/libgit2 index 9bbc8f350..8acf058ff 160000 --- a/libgit2 +++ b/libgit2 @@ -1 +1 @@ -Subproject commit 9bbc8f350b80a5a6e94651ec667cf9e5d545b317 +Subproject commit 8acf058ff708582e9bbfe04e297d3b17a4d5c2e8