using System; using System.Collections.Generic; using System.IO; using System.Reflection; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// /// A Repository is the primary interface into a git repository /// public class Repository : IDisposable { private readonly BranchCollection branches; private readonly CommitCollection commits; private readonly Lazy config; private readonly RepositorySafeHandle handle; private readonly Index index; private readonly ReferenceCollection refs; private readonly Lazy remotes; private readonly TagCollection tags; private readonly Lazy info; private readonly bool isBare; private readonly List handlesToCleanup = new List(); private static readonly Lazy versionRetriever = new Lazy(RetrieveVersion); /// /// Initializes a new instance of the class. /// For a standard repository, should either point to the ".git" folder or to the working directory. For a bare repository, should directly point to the repository folder. /// /// /// The path to the git repository to open, can be either the path to the git directory (for non-bare repositories this /// would be the ".git" folder inside the working directory) or the path to the working directory. /// public Repository(string path) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); int res = NativeMethods.git_repository_open(out handle, path); Ensure.Success(res); RegisterForCleanup(handle); isBare = NativeMethods.RepositoryStateChecker(handle, NativeMethods.git_repository_is_bare); if (!isBare) { index = new Index(this); } commits = new CommitCollection(this); refs = new ReferenceCollection(this); branches = new BranchCollection(this); tags = new TagCollection(this); info = new Lazy(() => new RepositoryInformation(this, isBare)); config = new Lazy(() => new Configuration(this)); remotes = new Lazy(() => new RemoteCollection(this)); } ~Repository() { Dispose(false); } internal RepositorySafeHandle Handle { get { return handle; } } /// /// Shortcut to return the branch pointed to by HEAD /// /// public Branch Head { get { Reference reference = Refs["HEAD"]; if (reference is SymbolicReference) { return new Branch(this, reference); } return new DetachedHead(this, reference); } } /// /// Provides access to the configuration settings for this repository. /// public Configuration Config { get { return config.Value; } } /// /// Gets the index. /// public Index Index { get { return index; } } /// /// Lookup and enumerate references in the repository. /// public ReferenceCollection Refs { get { return refs; } } /// /// Lookup and manage remotes in the repository. /// public RemoteCollection Remotes { get { return remotes.Value; } } /// /// Lookup and enumerate commits in the repository. /// Iterating this collection directly starts walking from the HEAD. /// public IQueryableCommitCollection Commits { get { return commits; } } /// /// Lookup and enumerate branches in the repository. /// public BranchCollection Branches { get { return branches; } } /// /// Lookup and enumerate tags in the repository. /// public TagCollection Tags { get { return tags; } } /// /// Provides high level information about this repository. /// public RepositoryInformation Info { get { return info.Value; } } #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// protected virtual void Dispose(bool disposing) { handlesToCleanup.ForEach(handleToCleanup => handleToCleanup.SafeDispose()); } #endregion /// /// Initialize a repository at the specified . /// /// The path to the working folder when initializing a standard ".git" repository. Otherwise, when initializing a bare repository, the path to the expected location of this later. /// true to initialize a bare repository. False otherwise, to initialize a standard ".git" repository. /// a new instance of the class. The client code is responsible for calling on this instance. public static Repository Init(string path, bool isBare = false) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); RepositorySafeHandle repo; int res = NativeMethods.git_repository_init(out repo, path, isBare); Ensure.Success(res); FilePath repoPath = NativeMethods.git_repository_path(repo); repo.SafeDispose(); return new Repository(repoPath.Native); } /// /// Try to lookup an object by its and . If no matching object is found, null will be returned. /// /// The id to lookup. /// The kind of GitObject being looked up /// The or null if it was not found. public GitObject Lookup(ObjectId id, GitObjectType type = GitObjectType.Any) { return LookupInternal(id, type, null); } internal GitObject LookupTreeEntryTarget(ObjectId id, FilePath path) { return LookupInternal(id, GitObjectType.Any, path); } internal GitObject LookupInternal(ObjectId id, GitObjectType type, FilePath knownPath) { Ensure.ArgumentNotNull(id, "id"); GitOid oid = id.Oid; GitObjectSafeHandle obj = null; try { int res; if (id is AbbreviatedObjectId) { res = NativeMethods.git_object_lookup_prefix(out obj, handle, ref oid, (uint)((AbbreviatedObjectId)id).Length, type); } else { res = NativeMethods.git_object_lookup(out obj, handle, ref oid, type); } if (res == (int)GitErrorCode.GIT_ENOTFOUND || res == (int)GitErrorCode.GIT_EINVALIDTYPE) { return null; } Ensure.Success(res); if (id is AbbreviatedObjectId) { id = GitObject.ObjectIdOf(obj); } return GitObject.CreateFromPtr(obj, id, this, knownPath); } finally { obj.SafeDispose(); } } /// /// Try to lookup an object by its sha or a reference canonical name and . If no matching object is found, null will be returned. /// /// The sha or reference canonical name to lookup. /// The kind of being looked up /// The or null if it was not found. public GitObject Lookup(string shaOrReferenceName, GitObjectType type = GitObjectType.Any) { return Lookup(shaOrReferenceName, type, LookUpOptions.None); } internal GitObject Lookup(string shaOrReferenceName, GitObjectType type, LookUpOptions lookUpOptions) { ObjectId id; Reference reference = Refs[shaOrReferenceName]; if (reference != null) { id = reference.PeelToTargetObjectId(); } else { ObjectId.TryParse(shaOrReferenceName, out id); } if (id == null) { if (lookUpOptions.Has(LookUpOptions.ThrowWhenNoGitObjectHasBeenFound)) { Ensure.GitObjectIsNotNull(null, shaOrReferenceName); } return null; } GitObject gitObj = Lookup(id, type); if (lookUpOptions.Has(LookUpOptions.ThrowWhenNoGitObjectHasBeenFound)) { Ensure.GitObjectIsNotNull(gitObj, shaOrReferenceName); } if (!lookUpOptions.Has(LookUpOptions.DereferenceResultToCommit)) { return gitObj; } return gitObj.DereferenceToCommit(shaOrReferenceName, lookUpOptions.Has(LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit)); } /// /// Probe for a git repository. /// The lookup start from and walk upward parent directories if nothing has been found. /// /// The base path where the lookup starts. /// The path to the git repository. public static string Discover(string startingPath) { var buffer = new byte[NativeMethods.GIT_PATH_MAX]; int result = NativeMethods.git_repository_discover(buffer, buffer.Length, startingPath, false, null); if ((GitErrorCode)result == GitErrorCode.GIT_ENOTAREPO) { return null; } Ensure.Success(result); FilePath discoveredPath = Utf8Marshaler.Utf8FromBuffer(buffer); return discoveredPath.Native; } /// /// Sets the current to the specified commit and optionally resets the and /// the content of the working tree to match. /// /// Flavor of reset operation to perform. /// The sha or reference canonical name of the target commit object. public void Reset(ResetOptions resetOptions, string shaOrReferenceName) { Ensure.ArgumentNotNullOrEmptyString(shaOrReferenceName, "shaOrReferenceName"); if (resetOptions.Has(ResetOptions.Mixed) && Info.IsBare) { throw new LibGit2Exception("Mixed reset is not allowed in a bare repository"); } GitObject commit = Lookup(shaOrReferenceName, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | LookUpOptions.DereferenceResultToCommit | LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); //TODO: Check for unmerged entries string refToUpdate = Info.IsHeadDetached ? "HEAD" : Head.CanonicalName; Refs.UpdateTarget(refToUpdate, commit.Sha); if (resetOptions == ResetOptions.Soft) { return; } Index.ReplaceContentWithTree(((Commit)commit).Tree); if (resetOptions == ResetOptions.Mixed) { return; } throw new NotImplementedException(); } internal void RegisterForCleanup(SafeHandleBase handleToCleanup) { handlesToCleanup.Add(handleToCleanup); } /// /// Gets the current LibGit2Sharp version. /// /// The format of the version number is as follows: /// Major.Minor.Patch-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64) /// /// public static string Version { get { return versionRetriever.Value; } } private static string RetrieveVersion() { Assembly assembly = typeof(Repository).Assembly; Version version = assembly.GetName().Version; string libgit2Hash = ReadContentFromResource(assembly, "libgit2_hash.txt"); string libgit2sharpHash = ReadContentFromResource(assembly, "libgit2sharp_hash.txt"); return string.Format("{0}-{1}-{2} ({3})", version.ToString(3), libgit2sharpHash.Substring(0, 7), libgit2Hash.Substring(0,7), NativeMethods.ProcessorArchitecture ); } private static string ReadContentFromResource(Assembly assembly, string partialResourceName) { using (var sr = new StreamReader(assembly.GetManifestResourceStream(string.Format("LibGit2Sharp.{0}", partialResourceName)))) { return sr.ReadLine(); } } } }