using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;
namespace LibGit2Sharp
{
///
/// The Index is a staging area between the Working directory and the Repository.
/// It's used to prepare and aggregate the changes that will be part of the next commit.
///
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class Index : IEnumerable
{
private readonly IndexHandle handle;
private readonly Repository repo;
private readonly ConflictCollection conflicts;
///
/// Needed for mocking purposes.
///
protected Index()
{ }
internal Index(Repository repo)
{
this.repo = repo;
handle = Proxy.git_repository_index(repo.Handle);
conflicts = new ConflictCollection(this);
repo.RegisterForCleanup(handle);
}
internal Index(Repository repo, string indexPath)
{
this.repo = repo;
handle = Proxy.git_index_open(indexPath);
Proxy.git_repository_set_index(repo.Handle, handle);
conflicts = new ConflictCollection(this);
repo.RegisterForCleanup(handle);
}
internal IndexHandle Handle
{
get { return handle; }
}
///
/// Gets the number of in the .
///
public virtual int Count
{
get { return Proxy.git_index_entrycount(handle); }
}
///
/// Determines if the is free from conflicts.
///
public virtual bool IsFullyMerged
{
get { return !Proxy.git_index_has_conflicts(handle); }
}
///
/// Gets the with the specified relative path.
///
public virtual unsafe IndexEntry this[string path]
{
get
{
Ensure.ArgumentNotNullOrEmptyString(path, "path");
git_index_entry* entry = Proxy.git_index_get_bypath(handle, path, 0);
return IndexEntry.BuildFromPtr(entry);
}
}
private unsafe IndexEntry this[int index]
{
get
{
git_index_entry* entryHandle = Proxy.git_index_get_byindex(handle, (UIntPtr)index);
return IndexEntry.BuildFromPtr(entryHandle);
}
}
#region IEnumerable Members
private List AllIndexEntries()
{
var entryCount = Count;
var list = new List(entryCount);
for (int i = 0; i < entryCount; i++)
{
list.Add(this[i]);
}
return list;
}
///
/// Returns an enumerator that iterates through the collection.
///
/// An object that can be used to iterate through the collection.
public virtual IEnumerator GetEnumerator()
{
return AllIndexEntries().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
///
/// Replaces entries in the with entries from the specified .
///
/// This overwrites all existing state in the .
///
///
/// The to read the entries from.
public virtual void Replace(Tree source)
{
using (var obj = new ObjectSafeWrapper(source.Id, repo.Handle))
{
Proxy.git_index_read_fromtree(this, obj.ObjectPtr);
}
UpdatePhysicalIndex();
}
///
/// Clears all entries the . This is semantically equivalent to
/// creating an empty object and resetting the to that .
///
/// This overwrites all existing state in the .
///
///
public virtual void Clear()
{
Proxy.git_index_clear(this);
UpdatePhysicalIndex();
}
private void RemoveFromIndex(string relativePath)
{
Proxy.git_index_remove_bypath(handle, relativePath);
}
///
/// Removes a specified entry from the .
///
/// The path of the entry to be removed.
public virtual void Remove(string indexEntryPath)
{
if (indexEntryPath == null)
{
throw new ArgumentNullException("indexEntryPath");
}
RemoveFromIndex(indexEntryPath);
UpdatePhysicalIndex();
}
///
/// Adds a file from the working directory in the .
///
/// If an entry with the same path already exists in the ,
/// the newly added one will overwrite it.
///
///
/// The path, in the working directory, of the file to be added.
public virtual void Add(string pathInTheWorkdir)
{
if (pathInTheWorkdir == null)
{
throw new ArgumentNullException("pathInTheWorkdir");
}
Proxy.git_index_add_bypath(handle, pathInTheWorkdir);
UpdatePhysicalIndex();
}
///
/// Adds an entry in the from a .
///
/// If an entry with the same path already exists in the ,
/// the newly added one will overwrite it.
///
///
/// The which content should be added to the .
/// The path to be used in the .
/// Either ,
/// or .
public virtual void Add(Blob blob, string indexEntryPath, Mode indexEntryMode)
{
Ensure.ArgumentConformsTo(indexEntryMode, m => m.HasAny(TreeEntryDefinition.BlobModes), "indexEntryMode");
if (blob == null)
{
throw new ArgumentNullException("blob");
}
if (indexEntryPath == null)
{
throw new ArgumentNullException("indexEntryPath");
}
AddEntryToTheIndex(indexEntryPath, blob.Id, indexEntryMode);
UpdatePhysicalIndex();
}
private void UpdatePhysicalIndex()
{
Proxy.git_index_write(handle);
}
internal void Replace(TreeChanges changes)
{
foreach (TreeEntryChanges treeEntryChanges in changes)
{
switch (treeEntryChanges.Status)
{
case ChangeKind.Unmodified:
continue;
case ChangeKind.Added:
RemoveFromIndex(treeEntryChanges.Path);
continue;
case ChangeKind.Deleted:
case ChangeKind.Modified:
AddEntryToTheIndex(treeEntryChanges.OldPath,
treeEntryChanges.OldOid,
treeEntryChanges.OldMode);
continue;
default:
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Entry '{0}' bears an unexpected ChangeKind '{1}'",
treeEntryChanges.Path,
treeEntryChanges.Status));
}
}
UpdatePhysicalIndex();
}
///
/// Gets the conflicts that exist.
///
public virtual ConflictCollection Conflicts
{
get { return conflicts; }
}
private unsafe void AddEntryToTheIndex(string path, ObjectId id, Mode mode)
{
IntPtr pathPtr = StrictFilePathMarshaler.FromManaged(path);
var indexEntry = new git_index_entry
{
mode = (uint)mode,
path = (char*) pathPtr,
};
Marshal.Copy(id.RawId, 0, new IntPtr(indexEntry.id.Id), GitOid.Size);
Proxy.git_index_add(handle, &indexEntry);
EncodingMarshaler.Cleanup(pathPtr);
}
private string DebuggerDisplay
{
get
{
return string.Format(CultureInfo.InvariantCulture, "Count = {0}", Count);
}
}
///
/// Replaces entries in the with entries from the specified .
///
/// The target object.
public virtual void Replace(Commit commit)
{
Replace(commit, null, null);
}
///
/// Replaces entries in the with entries from the specified .
///
/// The target object.
/// The list of paths (either files or directories) that should be considered.
public virtual void Replace(Commit commit, IEnumerable paths)
{
Replace(commit, paths, null);
}
///
/// Replaces entries in the with entries from the specified .
///
/// The target 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.
///
public virtual void Replace(Commit commit, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions)
{
Ensure.ArgumentNotNull(commit, "commit");
var changes = repo.Diff.Compare(commit.Tree, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None });
Replace(changes);
}
}
}