diff --git a/LibGit2Sharp.Tests/PackBuilderFixture.cs b/LibGit2Sharp.Tests/PackBuilderFixture.cs index 3d0071df0..27852db91 100644 --- a/LibGit2Sharp.Tests/PackBuilderFixture.cs +++ b/LibGit2Sharp.Tests/PackBuilderFixture.cs @@ -3,190 +3,280 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using System.Collections.Generic; +using LibGit2Sharp.Advanced; namespace LibGit2Sharp.Tests { public class PackBuilderFixture : BaseFixture { [Fact] - public void TestDefaultPackDelegate() + public void TestDefaultPackAllWithDirPath() { - TestIfSameRepoAfterPacking(null); + TestBody((repo, packDirPath) => + { + PackBuilderResults results = repo.ObjectDatabase.PackAll(packDirPath); + }); } [Fact] - public void TestCommitsPerBranchPackDelegate() + public void TestCommitsPerBranch() { - TestIfSameRepoAfterPacking(AddingObjectIdsTestDelegate); + TestBody((repo, packDirPath) => + { + using (PackBuilder builder = new PackBuilder(repo)) + { + foreach (Branch branch in repo.Branches) + { + foreach (Commit commit in branch.Commits) + { + builder.AddRecursively(commit); + } + } + + foreach (Tag tag in repo.Tags) + { + builder.Add(tag.Target); + } + + builder.WritePackTo(packDirPath); + } + }); } [Fact] - public void TestCommitsPerBranchIdsPackDelegate() + public void TestCommitsPerBranchIds() { - TestIfSameRepoAfterPacking(AddingObjectsTestDelegate); + TestBody((repo, packDirPath) => + { + using (PackBuilder builder = new PackBuilder(repo)) + { + foreach (Branch branch in repo.Branches) + { + foreach (Commit commit in branch.Commits) + { + builder.AddRecursively(commit.Id); + } + } + + foreach (Tag tag in repo.Tags) + { + builder.Add(tag.Target.Id); + } + + builder.WritePackTo(packDirPath); + } + }); } - internal void TestIfSameRepoAfterPacking(Action packDelegate) + [Fact] + public void TestCreatingMultiplePackFilesByType() { - // read a repo - // pack with the provided action - // write the pack file in a mirror repo - // read new repo - // compare + TestBody((repo, packDirPath) => + { + long totalNumberOfWrittenObjects = 0; - string orgRepoPath = SandboxPackBuilderTestRepo(); - string mrrRepoPath = SandboxPackBuilderTestRepo(); - string mrrRepoPackDirPath = Path.Combine(mrrRepoPath + "/.git/objects"); + using (PackBuilder builder = new PackBuilder(repo)) + { + for (int i = 0; i < 3; i++) + { + foreach (GitObject obj in repo.ObjectDatabase) + { + if (i == 0 && obj is Commit) + builder.Add(obj.Id); - DirectoryHelper.DeleteDirectory(mrrRepoPackDirPath); - Directory.CreateDirectory(mrrRepoPackDirPath + "/pack"); + if (i == 1 && obj is Tree) + builder.Add(obj.Id); - PackBuilderOptions packBuilderOptions = new PackBuilderOptions(mrrRepoPackDirPath + "/pack"); + if (i == 2 && obj is Blob) + builder.Add(obj.Id); + } - using (Repository orgRepo = new Repository(orgRepoPath)) - { - PackBuilderResults results; - if (packDelegate != null) - results = orgRepo.ObjectDatabase.Pack(packBuilderOptions, b => packDelegate(orgRepo, b)); - else - results = orgRepo.ObjectDatabase.Pack(packBuilderOptions); + PackBuilderResults results = builder.WritePackTo(packDirPath); - // written objects count is the same as in objects database - Assert.Equal(orgRepo.ObjectDatabase.Count(), results.WrittenObjectsCount); + // for reuse to build the next pack file. + builder.Reset(); - // loading a repo from the written pack file. - using (Repository mrrRepo = new Repository(mrrRepoPath)) - { - // make sure the objects of the original repo are the same as the ones in the mirror repo - // doing that by making sure the count is the same, and the set difference is empty - Assert.True(mrrRepo.ObjectDatabase.Count() == orgRepo.ObjectDatabase.Count() && !mrrRepo.ObjectDatabase.Except(orgRepo.ObjectDatabase).Any()); + // assert the pack file is written + Assert.True(File.Exists(Path.Combine(packDirPath, "pack-" + results.PackHash + ".pack"))); + Assert.True(File.Exists(Path.Combine(packDirPath, "pack-" + results.PackHash + ".idx"))); - Assert.Equal(orgRepo.Commits.Count(), mrrRepo.Commits.Count()); - Assert.Equal(orgRepo.Branches.Count(), mrrRepo.Branches.Count()); - Assert.Equal(orgRepo.Refs.Count(), mrrRepo.Refs.Count()); + totalNumberOfWrittenObjects += results.WrittenObjectsCount; + } } - } + + // assert total number of written objects count is the same as in objects database + Assert.Equal(repo.ObjectDatabase.Count(), totalNumberOfWrittenObjects); + }); } - internal void AddingObjectIdsTestDelegate(IRepository repo, PackBuilder builder) + [Fact] + public void TestCreatingMultiplePackFilesByCount() { - foreach (Branch branch in repo.Branches) + TestBody((repo, packDirPath) => { - foreach (Commit commit in branch.Commits) + long totalNumberOfWrittenObjects = 0; + PackBuilderResults results; + + using (PackBuilder packBuilder = new PackBuilder(repo)) { - builder.AddRecursively(commit.Id); + int currentObject = 0; + + foreach (GitObject gitObject in repo.ObjectDatabase) + { + packBuilder.Add(gitObject.Id); + + if (currentObject++ % 100 == 0) + { + results = packBuilder.WritePackTo(packDirPath); + packBuilder.Reset(); + + // assert the pack file is written + Assert.True(File.Exists(Path.Combine(packDirPath, "pack-" + results.PackHash + ".pack"))); + Assert.True(File.Exists(Path.Combine(packDirPath, "pack-" + results.PackHash + ".idx"))); + + totalNumberOfWrittenObjects += results.WrittenObjectsCount; + } + } + + if (currentObject % 100 != 1) + { + results = packBuilder.WritePackTo(packDirPath); + packBuilder.Reset(); + + // assert the pack file is written + Assert.True(File.Exists(Path.Combine(packDirPath, "pack-" + results.PackHash + ".pack"))); + Assert.True(File.Exists(Path.Combine(packDirPath, "pack-" + results.PackHash + ".idx"))); + + totalNumberOfWrittenObjects += results.WrittenObjectsCount; + } } - } - foreach (Tag tag in repo.Tags) + // assert total number of written objects count is the same as in objects database + Assert.Equal(repo.ObjectDatabase.Count(), totalNumberOfWrittenObjects); + }); + } + + [Fact] + public void CanWritePackAndIndexFiles() + { + using (Repository repo = new Repository(SandboxPackBuilderTestRepo())) { - builder.Add(tag.Target.Id); + string path = Path.GetTempPath(); + + PackBuilderResults results = repo.ObjectDatabase.PackAll(path); + + Assert.Equal(repo.ObjectDatabase.Count(), results.WrittenObjectsCount); + Assert.True(File.Exists(Path.Combine(path, "pack-" + results.PackHash + ".pack"))); + Assert.True(File.Exists(Path.Combine(path, "pack-" + results.PackHash + ".idx"))); } } - internal void AddingObjectsTestDelegate(IRepository repo, PackBuilder builder) + [Fact] + public void TestEmptyPackFile() { - foreach (Branch branch in repo.Branches) + using (Repository repo = new Repository(SandboxPackBuilderTestRepo())) { - foreach (Commit commit in branch.Commits) + string path = Path.GetTempPath(); + + using (PackBuilder builder = new PackBuilder(repo)) { - builder.AddRecursively(commit); - } - } + PackBuilderResults results = builder.WritePackTo(path); - foreach (Tag tag in repo.Tags) - { - builder.Add(tag.Target); + Assert.True(File.Exists(Path.Combine(path, "pack-" + results.PackHash + ".pack"))); + Assert.True(File.Exists(Path.Combine(path, "pack-" + results.PackHash + ".idx"))); + } } } [Fact] - public void ExceptionIfPathDoesNotExist() + public void TestPackFileForEmptyRepository() { - Assert.Throws(() => new PackBuilderOptions("aaa")); + using (Repository repo = new Repository(InitNewRepository())) + { + string path = Path.GetTempPath(); + + PackBuilderResults results = repo.ObjectDatabase.PackAll(path); + + Assert.Equal(repo.ObjectDatabase.Count(), results.WrittenObjectsCount); + Assert.True(File.Exists(Path.Combine(path, "pack-" + results.PackHash + ".pack"))); + Assert.True(File.Exists(Path.Combine(path, "pack-" + results.PackHash + ".idx"))); + } } [Fact] - public void ExceptionIfPathEqualsNull() + public void ExceptionIfPathDoesNotExistAtPackAll() { - Assert.Throws(() => new PackBuilderOptions(null)); + using (Repository repo = new Repository(SandboxPackBuilderTestRepo())) + { + Assert.Throws(() => repo.ObjectDatabase.PackAll("aaaaa")); + } } [Fact] - public void ExceptionIfOptionsEqualsNull() + public void ExceptionIfPathDoesNotExistAtWriteToPack() { - string orgRepoPath = SandboxPackBuilderTestRepo(); - - using (Repository orgRepo = new Repository(orgRepoPath)) + using (Repository repo = new Repository(SandboxPackBuilderTestRepo())) + using (PackBuilder builder = new PackBuilder(repo)) { - Assert.Throws(() => - { - orgRepo.ObjectDatabase.Pack(null); - }); + Assert.Throws(() => builder.WritePackTo("aaaaa")); } } [Fact] - public void ExceptionIfBuildDelegateEqualsNull() + public void ExceptionIfAddNullObjectID() { - string orgRepoPath = SandboxPackBuilderTestRepo(); - PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); - - using (Repository orgRepo = new Repository(orgRepoPath)) + using (Repository repo = new Repository(SandboxPackBuilderTestRepo())) + using (PackBuilder builder = new PackBuilder(repo)) { - Assert.Throws(() => - { - orgRepo.ObjectDatabase.Pack(packBuilderOptions, null); - }); + Assert.Throws(() => builder.Add(null)); } } [Fact] - public void ExceptionIfNegativeNumberOfThreads() + public void ExceptionIfAddRecursivelyNullObjectID() { - string orgRepoPath = SandboxPackBuilderTestRepo(); - PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); - - Assert.Throws(() => + using (Repository repo = new Repository(SandboxPackBuilderTestRepo())) + using (PackBuilder builder = new PackBuilder(repo)) { - packBuilderOptions.MaximumNumberOfThreads = -1; - }); + Assert.Throws(() => builder.AddRecursively(null)); + } } - [Fact] - public void ExceptionIfAddNullObjectID() + internal void TestBody(Action fullPackingAction) { + // read a repo, pack with the provided action, write the pack file in a mirror repo, read new repo, compare + string orgRepoPath = SandboxPackBuilderTestRepo(); - PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); + string mrrRepoPath = SandboxPackBuilderTestRepo(); + string mrrRepoPackDirPath = Path.Combine(mrrRepoPath + "/.git/objects"); + + DirectoryHelper.DeleteDirectory(mrrRepoPackDirPath); + Directory.CreateDirectory(mrrRepoPackDirPath + "/pack"); using (Repository orgRepo = new Repository(orgRepoPath)) { - Assert.Throws(() => + fullPackingAction(orgRepo, mrrRepoPackDirPath + "/pack"); + + // loading the mirror repo from the written pack file and make sure it's identical to the original. + using (Repository mrrRepo = new Repository(mrrRepoPath)) { - orgRepo.ObjectDatabase.Pack(packBuilderOptions, builder => - { - builder.Add(null); - }); - }); + AssertIfNotIdenticalRepositories(orgRepo, mrrRepo); + } } } - [Fact] - public void ExceptionIfAddRecursivelyNullObjectID() + internal void AssertIfNotIdenticalRepositories(IRepository repo1, IRepository repo2) { - string orgRepoPath = SandboxPackBuilderTestRepo(); - PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); + // make sure the objects of the original repo are the same as the ones in the mirror repo + // doing that by making sure the count is the same, and the set difference is empty + Assert.True(repo1.ObjectDatabase.Count() == repo2.ObjectDatabase.Count() + && !repo2.ObjectDatabase.Except(repo1.ObjectDatabase).Any()); - using (Repository orgRepo = new Repository(orgRepoPath)) - { - Assert.Throws(() => - { - orgRepo.ObjectDatabase.Pack(packBuilderOptions, builder => - { - builder.AddRecursively(null); - }); - }); - } + Assert.Equal(repo1.Commits.Count(), repo2.Commits.Count()); + Assert.Equal(repo1.Branches.Count(), repo2.Branches.Count()); + Assert.Equal(repo1.Refs.Count(), repo2.Refs.Count()); + Assert.Equal(repo1.Tags.Count(), repo2.Tags.Count()); } } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 0e3d6b3fc..859f68dc7 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -983,6 +983,9 @@ internal static extern int git_packbuilder_write( [DllImport(libgit2)] internal static extern UInt32 git_packbuilder_written(PackBuilderSafeHandle packbuilder); + [DllImport(libgit2)] + internal static extern OidSafeHandle git_packbuilder_hash(PackBuilderSafeHandle packbuilder); + [DllImport(libgit2)] internal static extern int git_reference_create( out ReferenceSafeHandle reference, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index dec711d24..399c10e09 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1616,6 +1616,12 @@ public static uint git_packbuilder_written(PackBuilderSafeHandle packbuilder) { return NativeMethods.git_packbuilder_written(packbuilder); } + + public static ObjectId git_packbuilder_hash(PackBuilderSafeHandle packbuilder) + { + return NativeMethods.git_packbuilder_hash(packbuilder).MarshalAsObjectId(); + } + #endregion #region git_rebase diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 0adb09afd..cbd16792d 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using LibGit2Sharp.Advanced; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -650,15 +651,9 @@ public virtual MergeTreeResult MergeCommits(Commit ours, Commit theirs, MergeTre /// Packing options /// This method will invoke the default action of packing all objects in an arbitrary order. /// Packing results - public virtual PackBuilderResults Pack(PackBuilderOptions options) + public virtual PackBuilderResults PackAll(string directoryPath) { - return InternalPack(options, builder => - { - foreach (GitObject obj in repo.ObjectDatabase) - { - builder.Add(obj.Id); - } - }); + return InternalPack(directoryPath, null); } /// @@ -668,9 +663,9 @@ public virtual PackBuilderResults Pack(PackBuilderOptions options) /// Packing options /// Packing action /// Packing results - public virtual PackBuilderResults Pack(PackBuilderOptions options, Action packDelegate) + public virtual PackBuilderResults PackAll(Stream outStream) { - return InternalPack(options, packDelegate); + return InternalPack(null, outStream); } /// @@ -680,26 +675,26 @@ public virtual PackBuilderResults Pack(PackBuilderOptions options, ActionPacking options /// Packing action /// Packing results - private PackBuilderResults InternalPack(PackBuilderOptions options, Action packDelegate) + private PackBuilderResults InternalPack(string directoryPath, Stream outStream) { - Ensure.ArgumentNotNull(options, "options"); - Ensure.ArgumentNotNull(packDelegate, "packDelegate"); - - PackBuilderResults results = new PackBuilderResults(); + PackBuilderResults results; using (PackBuilder builder = new PackBuilder(repo)) { - // set pre-build options - builder.SetMaximumNumberOfThreads(options.MaximumNumberOfThreads); - - // call the provided action - packDelegate(builder); + foreach (GitObject obj in repo.ObjectDatabase) + { + builder.Add(obj.Id); + } // writing the pack and index files - builder.Write(options.PackDirectoryPath); - - // adding the results to the PackBuilderResults object - results.WrittenObjectsCount = builder.WrittenObjectsCount; + if (directoryPath != null) + { + results = builder.WritePackTo(directoryPath); + } + else + { + results = builder.WritePackTo(outStream); + } } return results; diff --git a/LibGit2Sharp/PackBuilder.cs b/LibGit2Sharp/PackBuilder.cs index 1f797c9bf..406a8613c 100644 --- a/LibGit2Sharp/PackBuilder.cs +++ b/LibGit2Sharp/PackBuilder.cs @@ -3,23 +3,25 @@ using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; -namespace LibGit2Sharp +namespace LibGit2Sharp.Advanced { /// /// Representation of a git PackBuilder. /// public sealed class PackBuilder : IDisposable { - private readonly PackBuilderSafeHandle packBuilderHandle; + private PackBuilderSafeHandle packBuilderHandle; + private readonly RepositorySafeHandle repositoryHandle; /// /// Constructs a PackBuilder for a . /// - internal PackBuilder(Repository repository) + public PackBuilder(Repository repository) { Ensure.ArgumentNotNull(repository, "repository"); - packBuilderHandle = Proxy.git_packbuilder_new(repository.Handle); + repositoryHandle = repository.Handle; + packBuilderHandle = Proxy.git_packbuilder_new(repositoryHandle); } /// @@ -85,10 +87,25 @@ void IDisposable.Dispose() /// /// Writes the pack file and corresponding index file to path. /// - /// The path that pack and index files will be written to it. - internal void Write(string path) + /// The directory path that pack and index files will be written to it. + public PackBuilderResults WritePackTo(string packDirectoryPath) { - Proxy.git_packbuilder_write(packBuilderHandle, path); + Ensure.ArgumentNotNullOrEmptyString(packDirectoryPath, "packDirectoryPath"); + + if (!Directory.Exists(packDirectoryPath)) + { + throw new DirectoryNotFoundException("The Directory " + packDirectoryPath + " does not exist."); + } + + Proxy.git_packbuilder_write(packBuilderHandle, packDirectoryPath); + + return new PackBuilderResults(WrittenObjectsCount, PackHash); + } + + public PackBuilderResults WritePackTo(Stream outStream) + { + // Leaving it for Kevin David. + return new PackBuilderResults(); } /// @@ -96,7 +113,7 @@ internal void Write(string path) /// /// Returns the number of actual threads to be used. /// The Number of threads to spawn. An argument of 0 ensures using all available CPUs - internal int SetMaximumNumberOfThreads(int nThread) + public int SetMaximumNumberOfThreads(int nThread) { // Libgit2 set the number of threads to 1 by default, 0 ensures git_online_cpus return (int)Proxy.git_packbuilder_set_threads(packBuilderHandle, (uint)nThread); @@ -105,11 +122,21 @@ internal int SetMaximumNumberOfThreads(int nThread) /// /// Number of objects the PackBuilder will write out. /// - internal long ObjectsCount + public long ObjectsCount { get { return Proxy.git_packbuilder_object_count(packBuilderHandle); } } + /// + /// Gets the pack file's hash + /// A pack file's name is derived from the sorted hashing of all object names. + /// This is only correct after the pack file has been written. + /// + internal string PackHash + { + get { return Proxy.git_packbuilder_hash(packBuilderHandle).Sha; } + } + /// /// Number of objects the PackBuilder has already written out. /// This is only correct after the pack file has been written. @@ -119,6 +146,15 @@ internal long WrittenObjectsCount get { return Proxy.git_packbuilder_written(packBuilderHandle); } } + public void Reset() + { + // Dispose the old handle + packBuilderHandle.SafeDispose(); + + // Create a new handle + packBuilderHandle = Proxy.git_packbuilder_new(repositoryHandle); + } + internal PackBuilderSafeHandle Handle { get { return packBuilderHandle; } @@ -130,74 +166,20 @@ internal PackBuilderSafeHandle Handle /// public struct PackBuilderResults { - /// - /// Number of objects the PackBuilder has already written out. - /// - public long WrittenObjectsCount { get; internal set; } - } - - /// - /// Packing options of the . - /// - public sealed class PackBuilderOptions - { - private string path; - private int nThreads; - - /// - /// Constructor - /// - /// Directory path to write the pack and index files to it - /// The default value for maximum number of threads to spawn is 0 which ensures using all available CPUs. - /// if packDirectory is null or empty - /// if packDirectory doesn't exist - public PackBuilderOptions(string packDirectory) + internal PackBuilderResults(long writtenObjectsCount, string packHash) : this() { - PackDirectoryPath = packDirectory; - MaximumNumberOfThreads = 0; + WrittenObjectsCount = writtenObjectsCount; + PackHash = packHash; } /// - /// Directory path to write the pack and index files to it. + /// Number of objects that the PackBuilder has already written out. /// - public string PackDirectoryPath - { - set - { - Ensure.ArgumentNotNullOrEmptyString(value, "packDirectory"); - - if (!Directory.Exists(value)) - { - throw new DirectoryNotFoundException("The Directory " + value + " does not exist."); - } - - path = value; - } - get - { - return path; - } - } + public long WrittenObjectsCount { get; internal set; } /// - /// Maximum number of threads to spawn. - /// The default value is 0 which ensures using all available CPUs. + /// Hash of the pack file that the PackBuilder has already written out. /// - public int MaximumNumberOfThreads - { - set - { - if (value < 0) - { - throw new ArgumentException("Argument can not be negative", "value"); - } - - nThreads = value; - } - get - { - return nThreads; - } - } + public string PackHash { get; internal set; } } }