diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config index 78387c50b..277bd5530 100644 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config +++ b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config @@ -6,3 +6,4 @@ symlinks = false ignorecase = true hideDotFiles = dotGitOnly + autocrlf = false diff --git a/LibGit2Sharp.Tests/StageFixture.cs b/LibGit2Sharp.Tests/StageFixture.cs index 534383953..3118a6237 100644 --- a/LibGit2Sharp.Tests/StageFixture.cs +++ b/LibGit2Sharp.Tests/StageFixture.cs @@ -334,5 +334,43 @@ public void CanStageIgnoredPaths(string path) Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(path)); } } + + [Theory] + [InlineData("new_untracked_file.txt", FileStatus.Ignored)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInIndex)] + public void IgnoredFilesAreOnlyStagedIfTheyreInTheRepo(string filename, FileStatus expected) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, ".gitignore"), + String.Format("{0}\n", filename)); + + repo.Stage(filename); + Assert.Equal(expected, repo.RetrieveStatus(filename)); + } + } + + [Theory] + [InlineData("ancestor-and-ours.txt", FileStatus.Unaltered)] + [InlineData("ancestor-and-theirs.txt", FileStatus.NewInIndex)] + [InlineData("ancestor-only.txt", FileStatus.Nonexistent)] + [InlineData("conflicts-one.txt", FileStatus.ModifiedInIndex)] + [InlineData("conflicts-two.txt", FileStatus.ModifiedInIndex)] + [InlineData("ours-only.txt", FileStatus.Unaltered)] + [InlineData("ours-and-theirs.txt", FileStatus.ModifiedInIndex)] + [InlineData("theirs-only.txt", FileStatus.NewInIndex)] + public void CanStageConflictedIgnoredFiles(string filename, FileStatus expected) + { + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) + { + File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, ".gitignore"), + String.Format("{0}\n", filename)); + + repo.Stage(filename); + Assert.Equal(expected, repo.RetrieveStatus(filename)); + } + } } } diff --git a/LibGit2Sharp/ChangeKind.cs b/LibGit2Sharp/ChangeKind.cs index c95095a37..304438be8 100644 --- a/LibGit2Sharp/ChangeKind.cs +++ b/LibGit2Sharp/ChangeKind.cs @@ -55,5 +55,10 @@ public enum ChangeKind /// Entry is unreadable. /// Unreadable = 9, + + /// + /// Entry is currently in conflict. + /// + Conflicted = 10, } } diff --git a/LibGit2Sharp/Core/GitDiff.cs b/LibGit2Sharp/Core/GitDiff.cs index 1c71fbb5c..ab9f691db 100644 --- a/LibGit2Sharp/Core/GitDiff.cs +++ b/LibGit2Sharp/Core/GitDiff.cs @@ -231,6 +231,7 @@ internal enum GitDiffFlags GIT_DIFF_FLAG_BINARY = (1 << 0), GIT_DIFF_FLAG_NOT_BINARY = (1 << 1), GIT_DIFF_FLAG_VALID_ID = (1 << 2), + GIT_DIFF_FLAG_EXISTS = (1 << 3), } [StructLayout(LayoutKind.Sequential)] diff --git a/LibGit2Sharp/FileStatus.cs b/LibGit2Sharp/FileStatus.cs index 191972f35..b07323449 100644 --- a/LibGit2Sharp/FileStatus.cs +++ b/LibGit2Sharp/FileStatus.cs @@ -125,5 +125,10 @@ public enum FileStatus /// The file is but its name and/or path matches an exclude pattern in a gitignore file. /// Ignored = (1 << 14), /* GIT_STATUS_IGNORED */ + + /// + /// The file is due to a merge. + /// + Conflicted = (1 << 15), /* GIT_STATUS_CONFLICTED */ } } diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 79c9077bc..4ab882eb8 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -1656,6 +1656,7 @@ public void Stage(IEnumerable paths, StageOptions stageOptions) .Where( tec => tec.Status != ChangeKind.Added && tec.Status != ChangeKind.Modified && + tec.Status != ChangeKind.Conflicted && tec.Status != ChangeKind.Unmodified && tec.Status != ChangeKind.Deleted).ToList(); @@ -1667,10 +1668,25 @@ public void Stage(IEnumerable paths, StageOptions stageOptions) unexpectedTypesOfChanges[0].Path, unexpectedTypesOfChanges[0].Status)); } - foreach (TreeEntryChanges treeEntryChanges in changes - .Where(tec => tec.Status == ChangeKind.Deleted)) + /* Remove files from the index that don't exist on disk */ + foreach (TreeEntryChanges treeEntryChanges in changes) { - RemoveFromIndex(treeEntryChanges.Path); + switch (treeEntryChanges.Status) + { + case ChangeKind.Conflicted: + if (!treeEntryChanges.Exists) + { + RemoveFromIndex(treeEntryChanges.Path); + } + break; + + case ChangeKind.Deleted: + RemoveFromIndex(treeEntryChanges.Path); + break; + + default: + continue; + } } foreach (TreeEntryChanges treeEntryChanges in changes) @@ -1682,6 +1698,13 @@ public void Stage(IEnumerable paths, StageOptions stageOptions) AddToIndex(treeEntryChanges.Path); break; + case ChangeKind.Conflicted: + if (treeEntryChanges.Exists) + { + AddToIndex(treeEntryChanges.Path); + } + break; + default: continue; } diff --git a/LibGit2Sharp/TreeChanges.cs b/LibGit2Sharp/TreeChanges.cs index 385770875..ae3fd38cc 100644 --- a/LibGit2Sharp/TreeChanges.cs +++ b/LibGit2Sharp/TreeChanges.cs @@ -25,6 +25,7 @@ public class TreeChanges : IEnumerable private readonly List unmodified = new List(); private readonly List renamed = new List(); private readonly List copied = new List(); + private readonly List conflicted = new List(); private readonly IDictionary> fileDispatcher = Build(); @@ -39,6 +40,7 @@ private static IDictionary> Bu { ChangeKind.Unmodified, (de, d) => de.unmodified.Add(d) }, { ChangeKind.Renamed, (de, d) => de.renamed.Add(d) }, { ChangeKind.Copied, (de, d) => de.copied.Add(d) }, + { ChangeKind.Conflicted, (de, d) => de.conflicted.Add(d) }, }; } @@ -146,6 +148,14 @@ public virtual IEnumerable Unmodified get { return unmodified; } } + /// + /// List of which are conflicted + /// + public virtual IEnumerable Conflicted + { + get { return conflicted; } + } + private string DebuggerDisplay { get diff --git a/LibGit2Sharp/TreeEntryChanges.cs b/LibGit2Sharp/TreeEntryChanges.cs index 205ff42c3..bb288d265 100644 --- a/LibGit2Sharp/TreeEntryChanges.cs +++ b/LibGit2Sharp/TreeEntryChanges.cs @@ -25,6 +25,8 @@ internal TreeEntryChanges(GitDiffDelta delta) OldMode = (Mode)delta.OldFile.Mode; Oid = delta.NewFile.Id; OldOid = delta.OldFile.Id; + Exists = (delta.NewFile.Flags & GitDiffFlags.GIT_DIFF_FLAG_EXISTS) != 0; + OldExists = (delta.OldFile.Flags & GitDiffFlags.GIT_DIFF_FLAG_EXISTS) != 0; Status = (delta.Status == ChangeKind.Untracked || delta.Status == ChangeKind.Ignored) ? ChangeKind.Added @@ -46,6 +48,17 @@ internal TreeEntryChanges(GitDiffDelta delta) /// public virtual ObjectId Oid { get; private set; } + /// + /// The file exists in the new side of the diff. + /// This is useful in determining if you have content in + /// the ours or theirs side of a conflict. This will + /// be false during a conflict that deletes both the + /// "ours" and "theirs" sides, or when the diff is a + /// delete and the status is + /// . + /// + public virtual bool Exists { get; private set; } + /// /// The kind of change that has been done (added, deleted, modified ...). /// @@ -66,6 +79,16 @@ internal TreeEntryChanges(GitDiffDelta delta) /// public virtual ObjectId OldOid { get; private set; } + /// + /// The file exists in the old side of the diff. + /// This is useful in determining if you have an ancestor + /// side to a conflict. This will be false during a + /// conflict that involves both the "ours" and "theirs" + /// side being added, or when the diff is an add and the + /// status is . + /// + public virtual bool OldExists { get; private set; } + private string DebuggerDisplay { get