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