using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using LibGit2Sharp.Core;
namespace LibGit2Sharp
{
///
/// A collection of commits in a
///
public class CommitCollection : IQueryableCommitCollection
{
private readonly Repository repo;
private object includedIdentifier = "HEAD";
private object excludedIdentifier;
private readonly GitSortOptions sortOptions;
///
/// Initializes a new instance of the class.
/// The commits will be enumerated according in reverse chronological order.
///
/// The repository.
internal CommitCollection(Repository repo)
: this(repo, GitSortOptions.Time)
{
}
///
/// Initializes a new instance of the class.
///
/// The repository.
/// The sorting strategy which should be applied when enumerating the commits.
internal CommitCollection(Repository repo, GitSortOptions sortingStrategy)
{
this.repo = repo;
sortOptions = sortingStrategy;
}
///
/// Gets the current sorting strategy applied when enumerating the collection
///
public GitSortOptions SortedBy
{
get { return sortOptions; }
}
#region IEnumerable Members
///
/// Returns an enumerator that iterates through the collection.
///
/// An object that can be used to iterate through the collection.
public IEnumerator GetEnumerator()
{
if ((repo.Info.IsEmpty) && PointsAtTheHead(includedIdentifier.ToString()))
{
return Enumerable.Empty().GetEnumerator();
}
return new CommitEnumerator(repo, includedIdentifier, excludedIdentifier, sortOptions);
}
///
/// 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
///
/// Returns the list of commits of the repository matching the specified .
///
/// The options used to control which commits will be returned.
/// A collection of commits, ready to be enumerated.
public ICommitCollection QueryBy(Filter filter)
{
Ensure.ArgumentNotNull(filter, "filter");
Ensure.ArgumentNotNull(filter.Since, "filter.Since");
Ensure.ArgumentNotNullOrEmptyString(filter.Since.ToString(), "filter.Since");
return new CommitCollection(repo, filter.SortBy)
{
includedIdentifier = filter.Since,
excludedIdentifier = filter.Until
};
}
private static bool PointsAtTheHead(string shaOrRefName)
{
return ("HEAD".Equals(shaOrRefName, StringComparison.Ordinal) || "refs/heads/master".Equals(shaOrRefName, StringComparison.Ordinal));
}
///
/// Stores the content of the as a new into the repository.
///
/// 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.
/// The generated .
public Commit Create(Signature author, Signature committer, string message)
{
GitOid treeOid;
int res = NativeMethods.git_tree_create_fromindex(out treeOid, repo.Index.Handle);
Ensure.Success(res);
Reference head = repo.Refs["HEAD"];
GitOid commitOid;
using (var treePtr = new ObjectSafeWrapper(new ObjectId(treeOid), repo))
using (var headPtr = RetrieveHeadCommitPtr(head))
{
var parentPtrs = BuildArrayFrom(headPtr);
res = NativeMethods.git_commit_create(out commitOid, repo.Handle, head.CanonicalName, author.Handle,
committer.Handle, message, treePtr.ObjectPtr, parentPtrs.Count(), parentPtrs);
}
Ensure.Success(res);
return repo.Lookup(new ObjectId(commitOid));
}
private static IntPtr[] BuildArrayFrom(ObjectSafeWrapper headPtr)
{
if (headPtr.ObjectPtr == IntPtr.Zero)
{
return new IntPtr[]{};
}
return new IntPtr[]{headPtr.ObjectPtr};
}
private ObjectSafeWrapper RetrieveHeadCommitPtr(Reference head)
{
DirectReference oidRef = head.ResolveToDirectReference();
if (oidRef == null)
{
return new ObjectSafeWrapper(null, repo);
}
return new ObjectSafeWrapper(new ObjectId(oidRef.TargetIdentifier), repo);
}
private class CommitEnumerator : IEnumerator
{
private readonly Repository repo;
private readonly RevWalkerSafeHandle handle;
private ObjectId currentOid;
public CommitEnumerator(Repository repo, object includedIdentifier, object excludedIdentifier, GitSortOptions sortingStrategy)
{
this.repo = repo;
int res = NativeMethods.git_revwalk_new(out handle, repo.Handle);
Ensure.Success(res);
Sort(sortingStrategy);
Push(includedIdentifier);
Hide(excludedIdentifier);
}
#region IEnumerator Members
public Commit Current
{
get
{
if (currentOid == null)
{
throw new InvalidOperationException();
}
return repo.Lookup(currentOid);
}
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
GitOid oid;
var res = NativeMethods.git_revwalk_next(out oid, handle);
if (res == (int)GitErrorCode.GIT_EREVWALKOVER)
{
return false;
}
Ensure.Success(res);
currentOid = new ObjectId(oid);
return true;
}
public void Reset()
{
NativeMethods.git_revwalk_reset(handle);
}
#endregion
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (handle == null || handle.IsInvalid)
{
return;
}
handle.Dispose();
}
private void Push(object identifier)
{
var oid = RetrieveCommitId(identifier).Oid;
int res = NativeMethods.git_revwalk_push(handle, ref oid);
Ensure.Success(res);
}
private void Hide(object identifier)
{
if (identifier == null)
{
return;
}
var oid = RetrieveCommitId(identifier).Oid;
int res = NativeMethods.git_revwalk_hide(handle, ref oid);
Ensure.Success(res);
}
private void Sort(GitSortOptions options)
{
NativeMethods.git_revwalk_sorting(handle, options);
}
private ObjectId RetrieveCommitId(object identifier)
{
string shaOrReferenceName = RetrieveShaOrReferenceName(identifier);
GitObject gitObj = repo.Lookup(shaOrReferenceName);
// TODO: Should we check the type? Git-log allows TagAnnotation oid as parameter. But what about Blobs and Trees?
if (gitObj == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "No valid git object pointed at by '{0}' exists in the repository.", identifier));
}
return gitObj.Id;
}
private static string RetrieveShaOrReferenceName(object identifier)
{
if (identifier is string)
{
return identifier as string;
}
if (identifier is ObjectId || identifier is Reference || identifier is Branch)
return identifier.ToString();
if (identifier is Commit)
return ((Commit)identifier).Id.Sha;
throw new InvalidOperationException(string.Format("Unexpected kind of identifier '{0}'.", identifier));
}
}
}
}