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