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