Skip to content

Ensure that conflicts are staged and never ignored #1062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
autocrlf = false
38 changes: 38 additions & 0 deletions LibGit2Sharp.Tests/StageFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
}
5 changes: 5 additions & 0 deletions LibGit2Sharp/ChangeKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,10 @@ public enum ChangeKind
/// Entry is unreadable.
/// </summary>
Unreadable = 9,

/// <summary>
/// Entry is currently in conflict.
/// </summary>
Conflicted = 10,
}
}
1 change: 1 addition & 0 deletions LibGit2Sharp/Core/GitDiff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
5 changes: 5 additions & 0 deletions LibGit2Sharp/FileStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,10 @@ public enum FileStatus
/// The file is <see cref="NewInWorkdir"/> but its name and/or path matches an exclude pattern in a <c>gitignore</c> file.
/// </summary>
Ignored = (1 << 14), /* GIT_STATUS_IGNORED */

/// <summary>
/// The file is <see cref="Conflicted"/> due to a merge.
/// </summary>
Conflicted = (1 << 15), /* GIT_STATUS_CONFLICTED */
}
}
29 changes: 26 additions & 3 deletions LibGit2Sharp/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,7 @@ public void Stage(IEnumerable<string> 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();

Expand All @@ -1667,10 +1668,25 @@ public void Stage(IEnumerable<string> 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)
Expand All @@ -1682,6 +1698,13 @@ public void Stage(IEnumerable<string> paths, StageOptions stageOptions)
AddToIndex(treeEntryChanges.Path);
break;

case ChangeKind.Conflicted:
if (treeEntryChanges.Exists)
{
AddToIndex(treeEntryChanges.Path);
}
break;

default:
continue;
}
Expand Down
10 changes: 10 additions & 0 deletions LibGit2Sharp/TreeChanges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class TreeChanges : IEnumerable<TreeEntryChanges>
private readonly List<TreeEntryChanges> unmodified = new List<TreeEntryChanges>();
private readonly List<TreeEntryChanges> renamed = new List<TreeEntryChanges>();
private readonly List<TreeEntryChanges> copied = new List<TreeEntryChanges>();
private readonly List<TreeEntryChanges> conflicted = new List<TreeEntryChanges>();

private readonly IDictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>> fileDispatcher = Build();

Expand All @@ -39,6 +40,7 @@ private static IDictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>> 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) },
};
}

Expand Down Expand Up @@ -146,6 +148,14 @@ public virtual IEnumerable<TreeEntryChanges> Unmodified
get { return unmodified; }
}

/// <summary>
/// List of <see cref="TreeEntryChanges"/> which are conflicted
/// </summary>
public virtual IEnumerable<TreeEntryChanges> Conflicted
{
get { return conflicted; }
}

private string DebuggerDisplay
{
get
Expand Down
23 changes: 23 additions & 0 deletions LibGit2Sharp/TreeEntryChanges.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -46,6 +48,17 @@ internal TreeEntryChanges(GitDiffDelta delta)
/// </summary>
public virtual ObjectId Oid { get; private set; }

/// <summary>
/// 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
/// <see cref="ChangeType.Deleted"/>.
/// </summary>
public virtual bool Exists { get; private set; }

/// <summary>
/// The kind of change that has been done (added, deleted, modified ...).
/// </summary>
Expand All @@ -66,6 +79,16 @@ internal TreeEntryChanges(GitDiffDelta delta)
/// </summary>
public virtual ObjectId OldOid { get; private set; }

/// <summary>
/// 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 <see cref="ChangeType.Added"/>.
/// </summary>
public virtual bool OldExists { get; private set; }

private string DebuggerDisplay
{
get
Expand Down