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 IList includedIdentifier = new List { "HEAD" }; private IList excludedIdentifier = new List(); 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) && includedIdentifier.Any(o => PointsAtTheHead(o.ToString()))) // TODO: ToString() == fragile { 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 = ToList(filter.Since), excludedIdentifier = ToList(filter.Until) }; } private static IList ToList(object obj) { var list = new List(); if (obj == null) { return list; } var types = new[] { typeof(string), typeof(ObjectId), typeof(Commit), typeof(TagAnnotation), typeof(Tag), typeof(Branch), typeof(DetachedHead), typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) }; if (types.Contains(obj.GetType())) { list.Add(obj); return list; } list.AddRange(((IEnumerable)obj).Cast()); return list; } 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 description of why a change was made to the repository. /// The of who made the change. /// The of who added the change to the repository. /// True to amend the current pointed at by , false otherwise. /// The generated . public Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit) { Ensure.ArgumentNotNull(message, "message"); Ensure.ArgumentNotNull(author, "author"); Ensure.ArgumentNotNull(committer, "committer"); if (amendPreviousCommit && repo.Info.IsEmpty) { throw new LibGit2Exception("Can not amend anything. The Head doesn't point at any commit."); } GitOid treeOid; int res = NativeMethods.git_tree_create_fromindex(out treeOid, repo.Index.Handle); Ensure.Success(res); var parentIds = RetrieveParentIdsOfTheCommitBeingCreated(repo, amendPreviousCommit); GitOid commitOid; using (var treePtr = new ObjectSafeWrapper(new ObjectId(treeOid), repo)) using (var parentObjectPtrs = new DisposableEnumerable(parentIds.Select(id => new ObjectSafeWrapper(id, repo)))) using (SignatureSafeHandle authorHandle = author.BuildHandle()) using (SignatureSafeHandle committerHandle = committer.BuildHandle()) { string encoding = null; //TODO: Handle the encoding of the commit to be created IntPtr[] parentsPtrs = parentObjectPtrs.Select(o => o.ObjectPtr ).ToArray(); res = NativeMethods.git_commit_create(out commitOid, repo.Handle, repo.Refs["HEAD"].CanonicalName, authorHandle, committerHandle, encoding, message, treePtr.ObjectPtr, parentObjectPtrs.Count(), parentsPtrs); Ensure.Success(res); } return repo.Lookup(new ObjectId(commitOid)); } private static IEnumerable RetrieveParentIdsOfTheCommitBeingCreated(Repository repo, bool amendPreviousCommit) { if (amendPreviousCommit) { return repo.Head.Tip.Parents.Select(c => c.Id); } if (repo.Info.IsEmpty) { return Enumerable.Empty(); } return new[] { repo.Head.Tip.Id }; } private class CommitEnumerator : IEnumerator { private readonly Repository repo; private readonly RevWalkerSafeHandle handle; private ObjectId currentOid; public CommitEnumerator(Repository repo, IList includedIdentifier, IList excludedIdentifier, GitSortOptions sortingStrategy) { this.repo = repo; int res = NativeMethods.git_revwalk_new(out handle, repo.Handle); repo.RegisterForCleanup(handle); Ensure.Success(res); Sort(sortingStrategy); Push(includedIdentifier); Hide(excludedIdentifier); } #region IEnumerator Members public Commit Current { get { return repo.Lookup(currentOid); } } object IEnumerator.Current { get { return Current; } } public bool MoveNext() { GitOid oid; int 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) { handle.SafeDispose(); } private delegate int HidePushSignature(RevWalkerSafeHandle handle, ref GitOid oid); private void InternalHidePush(IList identifier, HidePushSignature hidePush) { IEnumerable oids = RetrieveCommitOids(identifier).TakeWhile(o => o != null); foreach (ObjectId actedOn in oids) { GitOid oid = actedOn.Oid; int res = hidePush(handle, ref oid); Ensure.Success(res); } } private void Push(IList identifier) { InternalHidePush(identifier, NativeMethods.git_revwalk_push); } private void Hide(IList identifier) { if (identifier == null) { return; } InternalHidePush(identifier, NativeMethods.git_revwalk_hide); } private void Sort(GitSortOptions options) { NativeMethods.git_revwalk_sorting(handle, options); } private ObjectId DereferenceToCommit(string identifier) { // TODO: Should we check the type? Git-log allows TagAnnotation oid as parameter. But what about Blobs and Trees? GitObject commit = repo.Lookup(identifier, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | LookUpOptions.DereferenceResultToCommit); return commit != null ? commit.Id : null; } private IEnumerable RetrieveCommitOids(object identifier) { if (identifier is string) { yield return DereferenceToCommit(identifier as string); yield break; } if (identifier is ObjectId) { yield return DereferenceToCommit(((ObjectId)identifier).Sha); yield break; } if (identifier is Commit) { yield return ((Commit)identifier).Id; yield break; } if (identifier is TagAnnotation) { yield return DereferenceToCommit(((TagAnnotation)identifier).Target.Id.Sha); yield break; } if (identifier is Tag) { yield return DereferenceToCommit(((Tag)identifier).Target.Id.Sha); yield break; } if (identifier is Branch) { var branch = (Branch)identifier; Ensure.GitObjectIsNotNull(branch.Tip, branch.CanonicalName); yield return branch.Tip.Id; yield break; } if (identifier is Reference) { yield return DereferenceToCommit(((Reference)identifier).CanonicalName); yield break; } if (identifier is IEnumerable) { var enumerable = (IEnumerable)identifier; foreach (object entry in enumerable) { foreach (ObjectId oid in RetrieveCommitOids(entry)) { yield return oid; } } yield break; } throw new LibGit2Exception(string.Format(CultureInfo.InvariantCulture, "Unexpected kind of identifier '{0}'.", identifier)); } } } }