using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// /// Provides methods to directly work against the Git object database /// without involving the index nor the working directory. /// public class ObjectDatabase : IEnumerable { private readonly Repository repo; private readonly ObjectDatabaseSafeHandle handle; /// /// Needed for mocking purposes. /// protected ObjectDatabase() { } internal ObjectDatabase(Repository repo) { this.repo = repo; handle = Proxy.git_repository_odb(repo.Handle); repo.RegisterForCleanup(handle); } #region Implementation of IEnumerable /// /// Returns an enumerator that iterates through the collection. /// /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { ICollection oids = Proxy.git_odb_foreach(handle, ptr => ptr.MarshalAs()); return oids .Select(gitOid => repo.Lookup(new ObjectId(gitOid))) .GetEnumerator(); } /// /// Returns an enumerator that iterates through the collection. /// /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion /// /// Determines if the given object can be found in the object database. /// /// Identifier of the object being searched for. /// True if the object has been found; false otherwise. public virtual bool Contains(ObjectId objectId) { Ensure.ArgumentNotNull(objectId, "objectId"); return Proxy.git_odb_exists(handle, objectId); } /// /// Inserts a into the object database, created from the content of a file. /// /// Path to the file to create the blob from. A relative path is allowed to /// be passed if the is a standard, non-bare, repository. The path /// will then be considered as a path relative to the root of the working directory. /// The created . public virtual Blob CreateBlob(string path) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); if (repo.Info.IsBare && !Path.IsPathRooted(path)) { throw new InvalidOperationException( string.Format(CultureInfo.InvariantCulture, "Cannot create a blob in a bare repository from a relative path ('{0}').", path)); } ObjectId id = Path.IsPathRooted(path) ? Proxy.git_blob_create_fromdisk(repo.Handle, path) : Proxy.git_blob_create_fromfile(repo.Handle, path); return repo.Lookup(id); } /// /// Adds the provided backend to the object database with the specified priority. /// /// The backend to add /// The priority at which libgit2 should consult this backend (higher values are consulted first) public virtual void AddBackend(OdbBackend backend, int priority) { Ensure.ArgumentNotNull(backend, "backend"); Ensure.ArgumentConformsTo(priority, s => s > 0, "priority"); Proxy.git_odb_add_backend(handle, backend.GitOdbBackendPointer, priority); } private class Processor { private readonly Stream stream; private readonly int? numberOfBytesToConsume; private int totalNumberOfReadBytes; public Processor(Stream stream, int? numberOfBytesToConsume) { this.stream = stream; this.numberOfBytesToConsume = numberOfBytesToConsume; } public int Provider(IntPtr content, int max_length, IntPtr data) { var local = new byte[max_length]; int bytesToRead = max_length; if (numberOfBytesToConsume.HasValue) { int totalRemainingBytesToRead = numberOfBytesToConsume.Value - totalNumberOfReadBytes; if (totalRemainingBytesToRead < max_length) { bytesToRead = totalRemainingBytesToRead; } } int numberOfReadBytes = stream.Read(local, 0, bytesToRead); totalNumberOfReadBytes += numberOfReadBytes; Marshal.Copy(local, 0, content, numberOfReadBytes); return numberOfReadBytes; } } /// /// Inserts a into the object database, created from the content of a data provider. /// /// The stream from which will be read the content of the blob to be created. /// The hintpath is used to determine what git filters should be applied to the object before it can be placed to the object database. /// The number of bytes to consume from the stream. /// The created . public virtual Blob CreateBlob(Stream stream, string hintpath = null, int? numberOfBytesToConsume = null) { Ensure.ArgumentNotNull(stream, "stream"); if (!stream.CanRead) { throw new ArgumentException("The stream cannot be read from.", "stream"); } var proc = new Processor(stream, numberOfBytesToConsume); ObjectId id = Proxy.git_blob_create_fromchunks(repo.Handle, hintpath, proc.Provider); return repo.Lookup(id); } /// /// Inserts a into the object database, created from a . /// /// The . /// The created . public virtual Tree CreateTree(TreeDefinition treeDefinition) { Ensure.ArgumentNotNull(treeDefinition, "treeDefinition"); return treeDefinition.Build(repo); } /// /// Inserts a into the object database, referencing an existing . /// /// Prettifing the message includes: /// * Removing empty lines from the beginning and end. /// * Removing trailing spaces from every line. /// * Turning multiple consecutive empty lines between paragraphs into just one empty line. /// * Ensuring the commit message ends with a newline. /// * Removing every line starting with "#". /// /// /// The of who made the change. /// The of who added the change to the repository. /// The description of why a change was made to the repository. /// True to prettify the message, or false to leave it as is /// The of the to be created. /// The parents of the to be created. /// The created . public virtual Commit CreateCommit(Signature author, Signature committer, string message, bool prettifyMessage, Tree tree, IEnumerable parents) { Ensure.ArgumentNotNull(message, "message"); Ensure.ArgumentDoesNotContainZeroByte(message, "message"); Ensure.ArgumentNotNull(author, "author"); Ensure.ArgumentNotNull(committer, "committer"); Ensure.ArgumentNotNull(tree, "tree"); Ensure.ArgumentNotNull(parents, "parents"); if (prettifyMessage) { message = Proxy.git_message_prettify(message); } GitOid[] parentIds = parents.Select(p => p.Id.Oid).ToArray(); ObjectId commitId = Proxy.git_commit_create(repo.Handle, null, author, committer, message, tree, parentIds); return repo.Lookup(commitId); } /// /// Inserts a into the object database, pointing to a specific . /// /// The name. /// The being pointed at. /// The tagger. /// The message. /// The created . public virtual TagAnnotation CreateTagAnnotation(string name, GitObject target, Signature tagger, string message) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(message, "message"); Ensure.ArgumentNotNull(target, "target"); Ensure.ArgumentNotNull(tagger, "tagger"); Ensure.ArgumentDoesNotContainZeroByte(name, "name"); Ensure.ArgumentDoesNotContainZeroByte(message, "message"); string prettifiedMessage = Proxy.git_message_prettify(message); ObjectId tagId = Proxy.git_tag_annotation_create(repo.Handle, name, target, tagger, prettifiedMessage); return repo.Lookup(tagId); } /// /// Archive the given commit. /// /// The commit. /// The archiver to use. public virtual void Archive(Commit commit, ArchiverBase archiver) { Ensure.ArgumentNotNull(commit, "commit"); Ensure.ArgumentNotNull(archiver, "archiver"); archiver.OrchestrateArchiving(commit.Tree, commit.Id, commit.Committer.When); } /// /// Archive the given tree. /// /// The tree. /// The archiver to use. public virtual void Archive(Tree tree, ArchiverBase archiver) { Ensure.ArgumentNotNull(tree, "tree"); Ensure.ArgumentNotNull(archiver, "archiver"); archiver.OrchestrateArchiving(tree, null, DateTimeOffset.UtcNow); } /// /// Returns the merge base (best common ancestor) of the given commits /// and the distance between each of these commits and this base. /// /// The being used as a reference. /// The being compared against . /// A instance of . public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit another) { Ensure.ArgumentNotNull(one, "one"); Ensure.ArgumentNotNull(another, "another"); return new HistoryDivergence(repo, one, another); } } }