using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;
namespace LibGit2Sharp
{
///
/// Holds the result of the determination of the state of the working directory.
/// Only files that differ from the current index and/or commit will be considered.
///
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class RepositoryStatus : IEnumerable
{
private readonly ICollection statusEntries;
private readonly List added = new List();
private readonly List staged = new List();
private readonly List removed = new List();
private readonly List missing = new List();
private readonly List modified = new List();
private readonly List untracked = new List();
private readonly List ignored = new List();
private readonly List renamedInIndex = new List();
private readonly List renamedInWorkDir = new List();
private readonly List unaltered = new List();
private readonly bool isDirty;
private readonly IDictionary> dispatcher = Build();
private static IDictionary> Build()
{
return new Dictionary>
{
{ FileStatus.NewInWorkdir, (rs, s) => rs.untracked.Add(s) },
{ FileStatus.ModifiedInWorkdir, (rs, s) => rs.modified.Add(s) },
{ FileStatus.DeletedFromWorkdir, (rs, s) => rs.missing.Add(s) },
{ FileStatus.NewInIndex, (rs, s) => rs.added.Add(s) },
{ FileStatus.ModifiedInIndex, (rs, s) => rs.staged.Add(s) },
{ FileStatus.DeletedFromIndex, (rs, s) => rs.removed.Add(s) },
{ FileStatus.RenamedInIndex, (rs, s) => rs.renamedInIndex.Add(s) },
{ FileStatus.Ignored, (rs, s) => rs.ignored.Add(s) },
{ FileStatus.RenamedInWorkdir, (rs, s) => rs.renamedInWorkDir.Add(s) },
};
}
///
/// Needed for mocking purposes.
///
protected RepositoryStatus()
{ }
internal RepositoryStatus(Repository repo, StatusOptions options)
{
statusEntries = new List();
using (GitStatusOptions coreOptions = CreateStatusOptions(options ?? new StatusOptions()))
using (StatusListSafeHandle list = Proxy.git_status_list_new(repo.Handle, coreOptions))
{
int count = Proxy.git_status_list_entrycount(list);
for (int i = 0; i < count; i++)
{
StatusEntrySafeHandle e = Proxy.git_status_byindex(list, i);
GitStatusEntry entry = e.MarshalAsGitStatusEntry();
GitDiffDelta deltaHeadToIndex = null;
GitDiffDelta deltaIndexToWorkDir = null;
if (entry.HeadToIndexPtr != IntPtr.Zero)
{
deltaHeadToIndex = entry.HeadToIndexPtr.MarshalAs();
}
if (entry.IndexToWorkDirPtr != IntPtr.Zero)
{
deltaIndexToWorkDir = entry.IndexToWorkDirPtr.MarshalAs();
}
AddStatusEntryForDelta(entry.Status, deltaHeadToIndex, deltaIndexToWorkDir);
}
isDirty = statusEntries.Any(entry => entry.State != FileStatus.Ignored && entry.State != FileStatus.Unaltered);
}
}
private static GitStatusOptions CreateStatusOptions(StatusOptions options)
{
var coreOptions = new GitStatusOptions
{
Version = 1,
Show = (GitStatusShow)options.Show,
Flags =
GitStatusOptionFlags.IncludeIgnored |
GitStatusOptionFlags.IncludeUntracked |
GitStatusOptionFlags.RecurseUntrackedDirs,
};
if (options.DetectRenamesInIndex)
{
coreOptions.Flags |=
GitStatusOptionFlags.RenamesHeadToIndex |
GitStatusOptionFlags.RenamesFromRewrites;
}
if (options.DetectRenamesInWorkDir)
{
coreOptions.Flags |=
GitStatusOptionFlags.RenamesIndexToWorkDir |
GitStatusOptionFlags.RenamesFromRewrites;
}
if (options.ExcludeSubmodules)
{
coreOptions.Flags |=
GitStatusOptionFlags.ExcludeSubmodules;
}
if (options.RecurseIgnoredDirs)
{
coreOptions.Flags |=
GitStatusOptionFlags.RecurseIgnoredDirs;
}
if (options.PathSpec != null)
{
coreOptions.PathSpec = GitStrArrayManaged.BuildFrom(options.PathSpec);
}
if (options.DisablePathSpecMatch)
{
coreOptions.Flags |=
GitStatusOptionFlags.DisablePathspecMatch;
}
if (options.IncludeUnaltered)
{
coreOptions.Flags |=
GitStatusOptionFlags.IncludeUnmodified;
}
return coreOptions;
}
private void AddStatusEntryForDelta(FileStatus gitStatus, GitDiffDelta deltaHeadToIndex, GitDiffDelta deltaIndexToWorkDir)
{
RenameDetails headToIndexRenameDetails = null;
RenameDetails indexToWorkDirRenameDetails = null;
if ((gitStatus & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex)
{
headToIndexRenameDetails = new RenameDetails(
LaxFilePathMarshaler.FromNative(deltaHeadToIndex.OldFile.Path).Native,
LaxFilePathMarshaler.FromNative(deltaHeadToIndex.NewFile.Path).Native,
(int)deltaHeadToIndex.Similarity);
}
if ((gitStatus & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir)
{
indexToWorkDirRenameDetails = new RenameDetails(
LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.OldFile.Path).Native,
LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.NewFile.Path).Native,
(int)deltaIndexToWorkDir.Similarity);
}
var filePath = (deltaIndexToWorkDir != null) ?
LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.NewFile.Path).Native :
LaxFilePathMarshaler.FromNative(deltaHeadToIndex.NewFile.Path).Native;
StatusEntry statusEntry = new StatusEntry(filePath, gitStatus, headToIndexRenameDetails, indexToWorkDirRenameDetails);
if (gitStatus == FileStatus.Unaltered)
{
unaltered.Add(statusEntry);
}
else
{
foreach (KeyValuePair> kvp in dispatcher)
{
if (!gitStatus.HasFlag(kvp.Key))
{
continue;
}
kvp.Value(this, statusEntry);
}
}
statusEntries.Add(statusEntry);
}
///
/// Gets the for the specified relative path.
///
public virtual StatusEntry this[string path]
{
get
{
Ensure.ArgumentNotNullOrEmptyString(path, "path");
var entries = statusEntries.Where(e => string.Equals(e.FilePath, path, StringComparison.Ordinal)).ToList();
Debug.Assert(!(entries.Count > 1));
if (entries.Count == 0)
{
return new StatusEntry(path, FileStatus.Nonexistent);
}
return entries.Single();
}
}
///
/// Returns an enumerator that iterates through the collection.
///
/// An object that can be used to iterate through the collection.
public virtual IEnumerator GetEnumerator()
{
return statusEntries.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();
}
///
/// List of files added to the index, which are not in the current commit
///
public virtual IEnumerable Added
{
get { return added; }
}
///
/// List of files added to the index, which are already in the current commit with different content
///
public virtual IEnumerable Staged
{
get { return staged; }
}
///
/// List of files removed from the index but are existent in the current commit
///
public virtual IEnumerable Removed
{
get { return removed; }
}
///
/// List of files existent in the index but are missing in the working directory
///
public virtual IEnumerable Missing
{
get { return missing; }
}
///
/// List of files with unstaged modifications. A file may be modified and staged at the same time if it has been modified after adding.
///
public virtual IEnumerable Modified
{
get { return modified; }
}
///
/// List of files existing in the working directory but are neither tracked in the index nor in the current commit.
///
public virtual IEnumerable Untracked
{
get { return untracked; }
}
///
/// List of files existing in the working directory that are ignored.
///
public virtual IEnumerable Ignored
{
get { return ignored; }
}
///
/// List of files that were renamed and staged.
///
public virtual IEnumerable RenamedInIndex
{
get { return renamedInIndex; }
}
///
/// List of files that were renamed in the working directory but have not been staged.
///
public virtual IEnumerable RenamedInWorkDir
{
get { return renamedInWorkDir; }
}
///
/// List of files that were unmodified in the working directory.
///
public virtual IEnumerable Unaltered
{
get { return unaltered; }
}
///
/// True if the index or the working directory has been altered since the last commit. False otherwise.
///
public virtual bool IsDirty
{
get { return isDirty; }
}
private string DebuggerDisplay
{
get
{
return string.Format(
CultureInfo.InvariantCulture,
"+{0} ~{1} -{2} | +{3} ~{4} -{5} | i{6}",
Added.Count(), Staged.Count(), Removed.Count(),
Untracked.Count(), Modified.Count(), Missing.Count(),
Ignored.Count());
}
}
}
}