using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// /// Provides helper overloads to a . /// public static class RepositoryExtensions { /// /// Try to lookup an object by its sha or a reference name. /// /// The kind of to lookup. /// The being looked up. /// The revparse spec for the object to lookup. /// The retrieved , or null if none was found. public static T Lookup(this IRepository repository, string objectish) where T : GitObject { EnsureNoGitLink(); if (typeof (T) == typeof (GitObject)) { return (T)repository.Lookup(objectish); } return (T)repository.Lookup(objectish, GitObject.TypeToKindMap[typeof(T)]); } /// /// Try to lookup an object by its . /// /// The kind of to lookup. /// The being looked up. /// The id. /// The retrieved , or null if none was found. public static T Lookup(this IRepository repository, ObjectId id) where T : GitObject { EnsureNoGitLink(); if (typeof(T) == typeof(GitObject)) { return (T)repository.Lookup(id); } return (T)repository.Lookup(id, GitObject.TypeToKindMap[typeof(T)]); } private static void EnsureNoGitLink() where T : GitObject { if (typeof(T) != typeof(GitLink)) { return; } throw new ArgumentException("A GitObject of type 'GitLink' cannot be looked up."); } /// /// Creates a lightweight tag with the specified name. This tag will point at the commit pointed at by the . /// /// The being worked with. /// The name of the tag to create. public static Tag ApplyTag(this IRepository repository, string tagName) { return repository.Tags.Add(tagName, RetrieveHeadCommit(repository)); } /// /// Creates a lightweight tag with the specified name. This tag will point at the . /// /// The being worked with. /// The name of the tag to create. /// The revparse spec for the target object. public static Tag ApplyTag(this IRepository repository, string tagName, string objectish) { return repository.Tags.Add(tagName, objectish); } /// /// Creates an annotated tag with the specified name. This tag will point at the commit pointed at by the . /// /// The being worked with. /// The name of the tag to create. /// The identity of the creator of this tag. /// The annotation message. public static Tag ApplyTag(this IRepository repository, string tagName, Signature tagger, string message) { return repository.Tags.Add(tagName, RetrieveHeadCommit(repository), tagger, message); } private static Commit RetrieveHeadCommit(IRepository repository) { Commit commit = repository.Head.Tip; Ensure.GitObjectIsNotNull(commit, "HEAD", m => new UnbornBranchException(m)); return commit; } /// /// Creates an annotated tag with the specified name. This tag will point at the . /// /// The being worked with. /// The name of the tag to create. /// The revparse spec for the target object. /// The identity of the creator of this tag. /// The annotation message. public static Tag ApplyTag(this IRepository repository, string tagName, string objectish, Signature tagger, string message) { return repository.Tags.Add(tagName, objectish, tagger, message); } /// /// Creates a branch with the specified name. This branch will point at the commit pointed at by the . /// /// The being worked with. /// The name of the branch to create. public static Branch CreateBranch(this IRepository repository, string branchName) { var head = repository.Head; var reflogName = head is DetachedHead ? head.Tip.Sha : head.FriendlyName; return CreateBranch(repository, branchName, reflogName); } /// /// Creates a branch with the specified name. This branch will point at . /// /// The being worked with. /// The name of the branch to create. /// The commit which should be pointed at by the Branch. public static Branch CreateBranch(this IRepository repository, string branchName, Commit target) { return repository.Branches.Add(branchName, target); } /// /// Creates a branch with the specified name. This branch will point at the commit pointed at by the . /// /// The being worked with. /// The name of the branch to create. /// The revparse spec for the target commit. public static Branch CreateBranch(this IRepository repository, string branchName, string committish) { return repository.Branches.Add(branchName, committish); } /// /// Sets the current and resets the and /// the content of the working tree to match. /// /// The being worked with. /// Flavor of reset operation to perform. public static void Reset(this IRepository repository, ResetMode resetMode) { repository.Reset(resetMode, "HEAD"); } /// /// Sets the current to the specified commitish and optionally resets the and /// the content of the working tree to match. /// /// The being worked with. /// Flavor of reset operation to perform. /// A revparse spec for the target commit object. public static void Reset(this IRepository repository, ResetMode resetMode, string committish) { Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); Commit commit = LookUpCommit(repository, committish); repository.Reset(resetMode, commit); } /// /// Replaces entries in the with entries from the specified commit. /// /// The being worked with. /// A revparse spec for the target commit object. /// The list of paths (either files or directories) that should be considered. /// /// If set, the passed will be treated as explicit paths. /// Use these options to determine how unmatched explicit paths should be handled. /// [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] public static void Reset(this IRepository repository, string committish = "HEAD", IEnumerable paths = null, ExplicitPathsOptions explicitPathsOptions = null) { if (repository.Info.IsBare) { throw new BareRepositoryException("Reset is not allowed in a bare repository"); } Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); Commit commit = LookUpCommit(repository, committish); repository.Index.Replace(commit, paths, explicitPathsOptions); } private static Commit LookUpCommit(IRepository repository, string committish) { GitObject obj = repository.Lookup(committish); Ensure.GitObjectIsNotNull(obj, committish); return obj.DereferenceToCommit(true); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. /// /// The being worked with. /// The description of why a change was made to the repository. /// The generated . public static Commit Commit(this IRepository repository, string message) { return repository.Commit(message, (CommitOptions)null); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. /// /// The being worked with. /// The description of why a change was made to the repository. /// The that specify the commit behavior. /// The generated . public static Commit Commit(this IRepository repository, string message, CommitOptions options) { Signature author = repository.Config.BuildSignature(DateTimeOffset.Now, true); return repository.Commit(message, author, options); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. /// The Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. /// /// The being worked with. /// The of who made the change. /// The description of why a change was made to the repository. /// The generated . public static Commit Commit(this IRepository repository, string message, Signature author) { return repository.Commit(message, author, (CommitOptions)null); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. /// The Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. /// /// The being worked with. /// The of who made the change. /// The description of why a change was made to the repository. /// The that specify the commit behavior. /// The generated . public static Commit Commit(this IRepository repository, string message, Signature author, CommitOptions options) { Signature committer = repository.Config.BuildSignature(DateTimeOffset.Now, true); return repository.Commit(message, author, committer, options); } /// /// Fetch from the specified remote. /// /// The being worked with. /// The name of the to fetch from. public static void Fetch(this IRepository repository, string remoteName) { repository.Fetch(remoteName, null); } /// /// Fetch from the specified remote. /// /// The being worked with. /// The name of the to fetch from. /// controlling fetch behavior public static void Fetch(this IRepository repository, string remoteName, FetchOptions options) { Ensure.ArgumentNotNull(repository, "repository"); Ensure.ArgumentNotNullOrEmptyString(remoteName, "remoteName"); Remote remote = repository.Network.Remotes.RemoteForName(remoteName, true); repository.Network.Fetch(remote, options); } /// /// Checkout the specified , reference or SHA. /// /// The being worked with. /// A revparse spec for the commit or branch to checkout. /// The that was checked out. public static Branch Checkout(this IRepository repository, string commitOrBranchSpec) { CheckoutOptions options = new CheckoutOptions(); return repository.Checkout(commitOrBranchSpec, options); } /// /// Checkout the commit pointed at by the tip of the specified . /// /// If this commit is the current tip of the branch as it exists in the repository, the HEAD /// will point to this branch. Otherwise, the HEAD will be detached, pointing at the commit sha. /// /// /// The being worked with. /// The to check out. /// The that was checked out. public static Branch Checkout(this IRepository repository, Branch branch) { CheckoutOptions options = new CheckoutOptions(); return repository.Checkout(branch, options); } /// /// Checkout the specified . /// /// Will detach the HEAD and make it point to this commit sha. /// /// /// The being worked with. /// The to check out. /// The that was checked out. public static Branch Checkout(this IRepository repository, Commit commit) { CheckoutOptions options = new CheckoutOptions(); return repository.Checkout(commit, options); } internal static string BuildRelativePathFrom(this Repository repo, string path) { //TODO: To be removed when libgit2 natively implements this if (!Path.IsPathRooted(path)) { return path; } string normalizedPath = Path.GetFullPath(path); if (!repo.PathStartsWith(normalizedPath, repo.Info.WorkingDirectory)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to process file '{0}'. This file is not located under the working directory of the repository ('{1}').", normalizedPath, repo.Info.WorkingDirectory)); } return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); } private static ObjectId DereferenceToCommit(Repository repo, string identifier) { var options = LookUpOptions.DereferenceResultToCommit; if (!AllowOrphanReference(repo, identifier)) { options |= LookUpOptions.ThrowWhenNoGitObjectHasBeenFound; } // 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, options); return commit != null ? commit.Id : null; } private static bool AllowOrphanReference(IRepository repo, string identifier) { return string.Equals(identifier, "HEAD", StringComparison.Ordinal) || string.Equals(identifier, repo.Head.CanonicalName, StringComparison.Ordinal); } private static ObjectId SingleCommittish(this Repository repo, object identifier) { if (ReferenceEquals(identifier, null)) { return null; } if (identifier is string) { return DereferenceToCommit(repo, (string)identifier); } if (identifier is ObjectId) { return DereferenceToCommit(repo, ((ObjectId)identifier).Sha); } if (identifier is Commit) { return ((Commit)identifier).Id; } if (identifier is TagAnnotation) { return DereferenceToCommit(repo, ((TagAnnotation)identifier).Target.Id.Sha); } if (identifier is Tag) { return DereferenceToCommit(repo, ((Tag)identifier).Target.Id.Sha); } var branch = identifier as Branch; if (branch != null) { if (branch.Tip != null || !branch.IsCurrentRepositoryHead) { Ensure.GitObjectIsNotNull(branch.Tip, branch.CanonicalName); return branch.Tip.Id; } } if (identifier is Reference) { return DereferenceToCommit(repo, ((Reference)identifier).CanonicalName); } return null; } /// /// Dereferences the passed identifier to a commit. If the identifier is enumerable, all items are dereferenced. /// /// Repository to search /// Committish to dereference /// If true, allow thrown exceptions to propagate. If false, exceptions will be swallowed and null returned. /// A series of commit s which identify commit objects. internal static IEnumerable Committishes(this Repository repo, object identifier, bool throwIfNotFound = false) { var singleReturnValue = repo.SingleCommittish(identifier); if (singleReturnValue != null) { yield return singleReturnValue; yield break; } if (identifier is IEnumerable) { foreach (object entry in (IEnumerable)identifier) { foreach (ObjectId oid in Committishes(repo, entry)) { yield return oid; } } yield break; } if (throwIfNotFound) { throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unexpected kind of identifier '{0}'.", identifier)); } yield return null; } /// /// Dereference the identifier to a commit. If the identifier is enumerable, dereference the first element. /// /// The to search /// Committish to dereference /// An for a commit object. internal static ObjectId Committish(this Repository repo, object identifier) { return repo.Committishes(identifier, true).First(); } /// /// Merges changes from branch into the branch pointed at by HEAD. /// /// The being worked with. /// The branch to merge into the branch pointed at by HEAD. /// The of who is performing the merge. /// The of the merge. public static MergeResult Merge(this IRepository repository, Branch branch, Signature merger) { return repository.Merge(branch, merger, null); } /// /// Merges changes from the commit into the branch pointed at by HEAD. /// /// The being worked with. /// The commit to merge into the branch pointed at by HEAD. /// The of who is performing the merge. /// The of the merge. public static MergeResult Merge(this IRepository repository, string committish, Signature merger) { return repository.Merge(committish, merger, null); } /// /// Checkout the tip commit of the specified object. If this commit is the /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit /// as a detached HEAD. /// /// The being worked with. /// The to check out. /// controlling checkout behavior. /// The that was checked out. public static Branch Checkout(this IRepository repository, Branch branch, CheckoutOptions options) { return repository.Checkout(branch, options); } /// /// Checkout the specified . /// /// Will detach the HEAD and make it point to this commit sha. /// /// /// The being worked with. /// The to check out. /// controlling checkout behavior. /// The that was checked out. public static Branch Checkout(this IRepository repository, Commit commit, CheckoutOptions options) { return repository.Checkout(commit, options); } /// /// Checkout the specified , reference or SHA. /// /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. /// /// /// The being worked with. /// A revparse spec for the commit or branch to checkout. /// controlling checkout behavior. /// The that was checked out. public static Branch Checkout(this IRepository repository, string committishOrBranchSpec, CheckoutOptions options) { return repository.Checkout(committishOrBranchSpec, options); } /// /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. /// /// This method does not switch branches or update the current repository HEAD. /// /// /// The being worked with. /// A revparse spec for the commit or branch to checkout paths from. /// The paths to checkout. Will throw if null is passed in. Passing an empty enumeration results in nothing being checked out. public static void CheckoutPaths(this IRepository repository, string committishOrBranchSpec, IEnumerable paths) { repository.CheckoutPaths(committishOrBranchSpec, paths, null); } /// /// Sets the current to the specified commit and optionally resets the and /// the content of the working tree to match. /// /// The being worked with. /// Flavor of reset operation to perform. /// The target commit object. public static void Reset(this IRepository repository, ResetMode resetMode, Commit commit) { repository.Reset(resetMode, commit); } /// /// Replaces entries in the with entries from the specified commit. /// /// The being worked with. /// The target commit object. /// The list of paths (either files or directories) that should be considered. [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] public static void Reset(this IRepository repository, Commit commit, IEnumerable paths) { repository.Index.Replace(commit, paths, null); } /// /// Replaces entries in the with entries from the specified commit. /// /// The being worked with. /// The target commit object. [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] public static void Reset(this IRepository repository, Commit commit) { repository.Index.Replace(commit, null, null); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. /// /// The being worked with. /// 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. /// The generated . public static Commit Commit(this IRepository repository, string message, Signature author, Signature committer) { return repository.Commit(message, author, committer, null); } /// /// Find where each line of a file originated. /// /// The being worked with. /// Path of the file to blame. /// The blame for the file. public static BlameHunkCollection Blame(this IRepository repository, string path) { return repository.Blame(path, null); } /// /// Cherry-picks the specified commit. /// /// The being worked with. /// The to cherry-pick. /// The of who is performing the cherry pick. /// The result of the cherry pick. public static CherryPickResult CherryPick(this IRepository repository, Commit commit, Signature committer) { return repository.CherryPick(commit, committer, null); } /// /// Merges changes from commit into the branch pointed at by HEAD. /// /// The being worked with. /// The commit to merge into the branch pointed at by HEAD. /// The of who is performing the merge. /// The of the merge. public static MergeResult Merge(this IRepository repository, Commit commit, Signature merger) { return repository.Merge(commit, merger, null); } /// /// Revert the specified commit. /// /// The being worked with. /// The to revert. /// The of who is performing the revert. /// The result of the revert. public static RevertResult Revert(this IRepository repository, Commit commit, Signature reverter) { return repository.Revert(commit, reverter, null); } /// /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). /// /// The being worked with. /// The path of the file within the working directory. public static void Stage(this IRepository repository, string path) { repository.Stage(path, null); } /// /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). /// /// The being worked with. /// The collection of paths of the files within the working directory. public static void Stage(this IRepository repository, IEnumerable paths) { repository.Stage(paths, null); } /// /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). /// /// The being worked with. /// The path of the file within the working directory. public static void Unstage(this IRepository repository, string path) { repository.Unstage(path, null); } /// /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). /// /// The being worked with. /// The collection of paths of the files within the working directory. public static void Unstage(this IRepository repository, IEnumerable paths) { repository.Unstage(paths, null); } /// /// Removes a file from the staging area, and optionally removes it from the working directory as well. /// /// If the file has already been deleted from the working directory, this method will only deal /// with promoting the removal to the staging area. /// /// /// The default behavior is to remove the file from the working directory as well. /// /// /// The being worked with. /// The path of the file within the working directory. public static void Remove(this IRepository repository, string path) { repository.Remove(path, true, null); } /// /// Removes a file from the staging area, and optionally removes it from the working directory as well. /// /// If the file has already been deleted from the working directory, this method will only deal /// with promoting the removal to the staging area. /// /// /// The default behavior is to remove the file from the working directory as well. /// /// /// The being worked with. /// The path of the file within the working directory. /// True to remove the file from the working directory, False otherwise. public static void Remove(this IRepository repository, string path, bool removeFromWorkingDirectory) { repository.Remove(path, removeFromWorkingDirectory, null); } /// /// Removes a collection of fileS from the staging, and optionally removes them from the working directory as well. /// /// If a file has already been deleted from the working directory, this method will only deal /// with promoting the removal to the staging area. /// /// /// The default behavior is to remove the files from the working directory as well. /// /// /// The being worked with. /// The collection of paths of the files within the working directory. public static void Remove(this IRepository repository, IEnumerable paths) { repository.Remove(paths, true, null); } /// /// Removes a collection of fileS from the staging, and optionally removes them from the working directory as well. /// /// If a file has already been deleted from the working directory, this method will only deal /// with promoting the removal to the staging area. /// /// /// The default behavior is to remove the files from the working directory as well. /// /// /// The being worked with. /// The collection of paths of the files within the working directory. /// True to remove the files from the working directory, False otherwise. public static void Remove(this IRepository repository, IEnumerable paths, bool removeFromWorkingDirectory) { repository.Remove(paths, removeFromWorkingDirectory, null); } /// /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commit. /// /// A holding the state of all the files. /// The being worked with. public static RepositoryStatus RetrieveStatus(this IRepository repository) { Proxy.git_index_read(repository.Index.Handle); return new RepositoryStatus((Repository)repository, null); } /// /// Finds the most recent annotated tag that is reachable from a commit. /// /// If the tag points to the commit, then only the tag is shown. Otherwise, /// it suffixes the tag name with the number of additional commits on top /// of the tagged object and the abbreviated object name of the most recent commit. /// /// /// The being worked with. /// The commit to be described. /// A descriptive identifier for the commit based on the nearest annotated tag. public static string Describe(this IRepository repository, Commit commit) { return repository.Describe(commit, new DescribeOptions()); } } }