using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// /// The Collection of references in a /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class ReferenceCollection : IEnumerable { internal readonly Repository repo; /// /// Needed for mocking purposes. /// protected ReferenceCollection() { } /// /// Initializes a new instance of the class. /// /// The repo. internal ReferenceCollection(Repository repo) { this.repo = repo; } /// /// Gets the with the specified name. /// /// The canonical name of the reference to resolve. /// The resolved if it has been found, null otherwise. public virtual Reference this[string name] { get { return Resolve(name); } } #region IEnumerable Members /// /// Returns an enumerator that iterates through the collection. /// /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return Proxy.git_reference_list(repo.Handle) .Select(n => this[n]) .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 /// /// Creates a direct reference with the specified name and target /// /// The canonical name of the reference to create. /// Id of the target object. /// The optional message to log in the when adding the /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . public virtual DirectReference Add(string name, ObjectId targetId, string logMessage, bool allowOverwrite = false) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetId, "targetId"); using (ReferenceSafeHandle handle = Proxy.git_reference_create(repo.Handle, name, targetId, allowOverwrite, logMessage)) { return (DirectReference)Reference.BuildFromPtr(handle, repo); } } /// /// Creates a direct reference with the specified name and target /// /// The canonical name of the reference to create. /// Id of the target object. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . public virtual DirectReference Add(string name, ObjectId targetId, bool allowOverwrite = false) { return Add(name, targetId, null, allowOverwrite); } /// /// Creates a symbolic reference with the specified name and target /// /// The canonical name of the reference to create. /// The target reference. /// The optional message to log in the when adding the /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . public virtual SymbolicReference Add(string name, Reference targetRef, string logMessage, bool allowOverwrite = false) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetRef, "targetRef"); using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, name, targetRef.CanonicalName, allowOverwrite, logMessage)) { return (SymbolicReference)Reference.BuildFromPtr(handle, repo); } } /// /// Creates a symbolic reference with the specified name and target /// /// The canonical name of the reference to create. /// The target reference. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . public virtual SymbolicReference Add(string name, Reference targetRef, bool allowOverwrite = false) { return Add(name, targetRef, null, allowOverwrite); } /// /// Remove a reference from the repository /// /// The reference to delete. public virtual void Remove(Reference reference) { Ensure.ArgumentNotNull(reference, "reference"); Proxy.git_reference_remove(repo.Handle, reference.CanonicalName); } /// /// Rename an existing reference with a new name, and update the reflog /// /// The reference to rename. /// The new canonical name. /// Message added to the reflog. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . public virtual Reference Rename(Reference reference, string newName, string logMessage = null, bool allowOverwrite = false) { Ensure.ArgumentNotNull(reference, "reference"); Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); if (logMessage == null) { logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: renamed {1} to {2}", reference.IsLocalBranch() ? "branch" : "reference", reference.CanonicalName, newName); } using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(reference.CanonicalName)) using (ReferenceSafeHandle handle = Proxy.git_reference_rename(referencePtr, newName, allowOverwrite, logMessage)) { return Reference.BuildFromPtr(handle, repo); } } /// /// Rename an existing reference with a new name /// /// The reference to rename. /// The new canonical name. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . public virtual Reference Rename(Reference reference, string newName, bool allowOverwrite = false) { return Rename(reference, newName, null, allowOverwrite); } internal T Resolve(string name) where T : Reference { Ensure.ArgumentNotNullOrEmptyString(name, "name"); using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(name, false)) { return referencePtr == null ? null : Reference.BuildFromPtr(referencePtr, repo); } } /// /// Updates the target of a direct reference. /// /// The direct reference which target should be updated. /// The new target. /// The optional message to log in the of the reference /// A new . public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId, string logMessage) { Ensure.ArgumentNotNull(directRef, "directRef"); Ensure.ArgumentNotNull(targetId, "targetId"); if (directRef.CanonicalName == "HEAD") { return UpdateHeadTarget(targetId, logMessage); } return UpdateDirectReferenceTarget(directRef, targetId, logMessage); } private Reference UpdateDirectReferenceTarget(Reference directRef, ObjectId targetId, string logMessage) { using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(directRef.CanonicalName)) using (ReferenceSafeHandle handle = Proxy.git_reference_set_target(referencePtr, targetId, logMessage)) { return Reference.BuildFromPtr(handle, repo); } } /// /// Updates the target of a direct reference /// /// The direct reference which target should be updated. /// The new target. /// A new . public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId) { return UpdateTarget(directRef, targetId, null); } /// /// Updates the target of a symbolic reference /// /// The symbolic reference which target should be updated. /// The new target. /// The optional message to log in the of the reference. /// A new . public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) { Ensure.ArgumentNotNull(symbolicRef, "symbolicRef"); Ensure.ArgumentNotNull(targetRef, "targetRef"); if (symbolicRef.CanonicalName == "HEAD") { return UpdateHeadTarget(targetRef, logMessage); } return UpdateSymbolicRefenceTarget(symbolicRef, targetRef, logMessage); } private Reference UpdateSymbolicRefenceTarget(Reference symbolicRef, Reference targetRef, string logMessage) { using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(symbolicRef.CanonicalName)) using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_set_target(referencePtr, targetRef.CanonicalName, logMessage)) { return Reference.BuildFromPtr(handle, repo); } } /// /// Updates the target of a symbolic reference /// /// The symbolic reference which target should be updated. /// The new target. /// A new . public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef) { return UpdateTarget(symbolicRef, targetRef, null); } internal Reference MoveHeadTarget(T target) { if (target is ObjectId) { Proxy.git_repository_set_head_detached(repo.Handle, target as ObjectId); } else if (target is DirectReference || target is SymbolicReference) { Proxy.git_repository_set_head(repo.Handle, (target as Reference).CanonicalName); } else if (target is string) { var targetIdentifier = target as string; if (Reference.IsValidName(targetIdentifier) && targetIdentifier.LooksLikeLocalBranch()) { Proxy.git_repository_set_head(repo.Handle, targetIdentifier); } else { using (var annotatedCommit = Proxy.git_annotated_commit_from_revspec(repo.Handle, targetIdentifier)) { Proxy.git_repository_set_head_detached_from_annotated(repo.Handle, annotatedCommit); } } } else { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid target type.", typeof(T))); } return repo.Refs.Head; } internal Reference UpdateHeadTarget(ObjectId target, string logMessage) { Add("HEAD", target, logMessage, true); return repo.Refs.Head; } internal Reference UpdateHeadTarget(Reference target, string logMessage) { Ensure.ArgumentConformsTo(target, r => (r is DirectReference || r is SymbolicReference), "target"); Add("HEAD", target, logMessage, true); return repo.Refs.Head; } internal Reference UpdateHeadTarget(string target, string logMessage) { this.Add("HEAD", target, logMessage, true); return repo.Refs.Head; } internal ReferenceSafeHandle RetrieveReferencePtr(string referenceName, bool shouldThrowIfNotFound = true) { ReferenceSafeHandle reference = Proxy.git_reference_lookup(repo.Handle, referenceName, shouldThrowIfNotFound); return reference; } /// /// Returns the list of references of the repository matching the specified . /// /// The glob pattern the reference name should match. /// A list of references, ready to be enumerated. public virtual IEnumerable FromGlob(string pattern) { Ensure.ArgumentNotNullOrEmptyString(pattern, "pattern"); return Proxy.git_reference_foreach_glob(repo.Handle, pattern, LaxUtf8Marshaler.FromNative) .Select(n => this[n]); } /// /// Shortcut to return the HEAD reference. /// /// /// A if the HEAD is detached; /// otherwise a . /// public virtual Reference Head { get { return this["HEAD"]; } } private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } /// /// Returns as a the reflog of the named /// /// The canonical name of the reference /// a , enumerable of public virtual ReflogCollection Log(string canonicalName) { Ensure.ArgumentNotNullOrEmptyString(canonicalName, "canonicalName"); return new ReflogCollection(repo, canonicalName); } /// /// Returns as a the reflog of the /// /// The reference /// a , enumerable of public virtual ReflogCollection Log(Reference reference) { Ensure.ArgumentNotNull(reference, "reference"); return new ReflogCollection(repo, reference.CanonicalName); } /// /// Rewrite some of the commits in the repository and all the references that can reach them. /// /// Specifies behavior for this rewrite. /// The objects to rewrite. public virtual void RewriteHistory(RewriteHistoryOptions options, params Commit[] commitsToRewrite) { Ensure.ArgumentNotNull(commitsToRewrite, "commitsToRewrite"); RewriteHistory(options, commitsToRewrite.AsEnumerable()); } /// /// Rewrite some of the commits in the repository and all the references that can reach them. /// /// Specifies behavior for this rewrite. /// The objects to rewrite. public virtual void RewriteHistory(RewriteHistoryOptions options, IEnumerable commitsToRewrite) { Ensure.ArgumentNotNull(commitsToRewrite, "commitsToRewrite"); Ensure.ArgumentNotNull(options, "options"); Ensure.ArgumentNotNullOrEmptyString(options.BackupRefsNamespace, "options.BackupRefsNamespace"); IList originalRefs = this.ToList(); if (originalRefs.Count == 0) { // Nothing to do return; } var historyRewriter = new HistoryRewriter(repo, commitsToRewrite, options); historyRewriter.Execute(); } /// /// Ensure that a reflog exists for the given canonical name /// /// Canonical name of the reference internal void EnsureHasLog(string canonicalName) { Proxy.git_reference_ensure_log(repo.Handle, canonicalName); } } }