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);
}
}
}