diff --git a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs index fea0bbb74..046fe5214 100644 --- a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs +++ b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs @@ -202,6 +202,40 @@ public void ComparingBlobsWithNoSpacesIndentHeuristicOptionMakesNoDifference() } } + [Fact] + public void DiffSetsTheAddedAndDeletedLinesCorrectly() + { + var path = SandboxStandardTestRepoGitDir(); + + using (var repo = new Repository(path)) + { + var oldContent = + @"1 +2 +3 +4"; + + var newContent = + @"1 +2 +3 +5"; + var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); + var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); + + ContentChanges changes = repo.Diff.Compare(oldBlob, newBlob); + + Assert.Single(changes.AddedLines); + Assert.Single(changes.DeletedLines); + + Assert.Equal("4", changes.DeletedLines.First().Content); + Assert.Equal("5", changes.AddedLines.First().Content); + + Assert.Equal(4, changes.DeletedLines.First().LineNumber); + Assert.Equal(4, changes.AddedLines.First().LineNumber); + } + } + static string CanonicalChangedLines(ContentChanges changes) { // Create an ordered representation of lines that have been added or removed diff --git a/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs index 2fb359f24..b712a214b 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs @@ -17,7 +17,7 @@ private static void SetUpSimpleDiffContext(IRepository repo) File.AppendAllText(fullpath, "world\n"); - Commands.Stage(repo,fullpath); + Commands.Stage(repo, fullpath); File.AppendAllText(fullpath, "!!!\n"); } @@ -509,5 +509,25 @@ public void CanCompareANullTreeAgainstTheWorkdirAndTheIndex() } } } + + [Fact] + public void CompareSetsCorrectAddedAndDeletedLines() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + SetUpSimpleDiffContext(repo); + + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory | DiffTargets.Index)) + { + foreach (var entry in changes) + { + Assert.Equal(2, entry.AddedLines.Count()); + } + } + } + } } } diff --git a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs index dba762bfe..d03b26e4e 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs @@ -4,7 +4,6 @@ using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -20,7 +19,7 @@ public void ComparingATreeAgainstItselfReturnsNoDifference() { Tree tree = repo.Head.Tip.Tree; - using(var changes = repo.Diff.Compare(tree, tree)) + using (var changes = repo.Diff.Compare(tree, tree)) { Assert.Empty(changes); } @@ -112,13 +111,13 @@ public void CanDetectABinaryChange() File.AppendAllText(filepath, "abcdef"); - using(var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) + using (var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) Assert.True(patch[filename].IsBinaryComparison); Commands.Stage(repo, filename); var commit2 = repo.Commit("Update binary file", Constants.Signature, Constants.Signature); - using(var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + using (var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) Assert.True(patch2[filename].IsBinaryComparison); } } @@ -138,13 +137,13 @@ public void CanDetectABinaryDeletion() File.Delete(filepath); - using(var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new [] {filename})) + using (var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) Assert.True(patch[filename].IsBinaryComparison); Commands.Remove(repo, filename); var commit2 = repo.Commit("Delete binary file", Constants.Signature, Constants.Signature); - using(var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + using (var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) Assert.True(patch2[filename].IsBinaryComparison); } } @@ -704,7 +703,7 @@ public void CanIncludeUnmodifiedEntriesWhenEnabled() Touch(repo.Info.WorkingDirectory, "a.txt", "abc\ndef\n"); Touch(repo.Info.WorkingDirectory, "b.txt", "abc\ndef\n"); - Commands.Stage(repo, new[] {"a.txt", "b.txt"}); + Commands.Stage(repo, new[] { "a.txt", "b.txt" }); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "b.txt"), "ghi\njkl\n"); @@ -728,12 +727,12 @@ public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWh var path = Repository.Init(scd.DirectoryPath); using (var repo = new Repository(path)) { - const string originalPath = "original.txt"; - const string renamedPath = "renamed.txt"; + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; const string originalPath2 = "original2.txt"; - const string copiedPath1 = "copied.txt"; + const string copiedPath1 = "copied.txt"; const string originalPath3 = "original3.txt"; - const string copiedPath2 = "copied2.txt"; + const string copiedPath2 = "copied2.txt"; Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); Touch(repo.Info.WorkingDirectory, originalPath2, "1\n2\n3\n4\n"); @@ -986,7 +985,7 @@ public void CanHandleTwoTreeEntryChangesWithTheSamePathUsingSimilarityNone() Assert.Single(changes.Deleted); Assert.Single(changes.TypeChanged); - TreeEntryChanges change = changes.Single(c => c.Path== path); + TreeEntryChanges change = changes.Single(c => c.Path == path); Assert.Equal(Mode.SymbolicLink, change.OldMode); Assert.Equal(Mode.NonExecutableFile, change.Mode); Assert.Equal(ChangeKind.TypeChanged, change.Status); @@ -1087,7 +1086,7 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() using (var repo = new Repository(path)) { SetFilemode(repo, true); - using(var changes = repo.Diff.Compare(new[] { file })) + using (var changes = repo.Diff.Compare(new[] { file })) { Assert.Single(changes); @@ -1147,6 +1146,44 @@ public void RetrievingDiffChangesMustAlwaysBeCaseSensitive() } } + [Fact] + public void RetrievingDiffContainsRightAmountOfAddedAndDeletedLines() + { + ObjectId treeOldOid, treeNewOid; + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + Blob oldContent = OdbHelper.CreateBlob(repo, "awesome content\n"); + Blob newContent = OdbHelper.CreateBlob(repo, "more awesome content\n"); + + var td = new TreeDefinition() + .Add("A.TXT", oldContent, Mode.NonExecutableFile) + .Add("a.txt", oldContent, Mode.NonExecutableFile); + + treeOldOid = repo.ObjectDatabase.CreateTree(td).Id; + + td = new TreeDefinition() + .Add("A.TXT", newContent, Mode.NonExecutableFile) + .Add("a.txt", newContent, Mode.NonExecutableFile); + + treeNewOid = repo.ObjectDatabase.CreateTree(td).Id; + } + + using (var repo = new Repository(repoPath)) + { + using (var changes = repo.Diff.Compare(repo.Lookup(treeOldOid), repo.Lookup(treeNewOid))) + { + foreach (var entry in changes) + { + Assert.Single(entry.AddedLines); + Assert.Single(entry.DeletedLines); + } + } + } + } + [Fact] public void UsingPatienceAlgorithmCompareOptionProducesPatienceDiff() { diff --git a/LibGit2Sharp/ContentChanges.cs b/LibGit2Sharp/ContentChanges.cs index 221c99efa..c4628f919 100644 --- a/LibGit2Sharp/ContentChanges.cs +++ b/LibGit2Sharp/ContentChanges.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; @@ -51,6 +52,16 @@ internal void AppendToPatch(string patch) /// public virtual int LinesDeleted { get; internal set; } + /// + /// The list of added lines. + /// + public virtual List AddedLines { get; } = new List(); + + /// + /// The list of deleted lines. + /// + public virtual List DeletedLines { get; } = new List(); + /// /// The patch corresponding to these changes. /// @@ -95,11 +106,13 @@ private unsafe int LineCallback(git_diff_delta* delta, GitDiffHunk hunk, GitDiff switch (line.lineOrigin) { case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION: + AddedLines.Add(new Line(line.NewLineNo, decodedContent)); LinesAdded++; prefix = Encoding.ASCII.GetString(new[] { (byte)line.lineOrigin }); break; case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION: + DeletedLines.Add(new Line(line.OldLineNo, decodedContent)); LinesDeleted++; prefix = Encoding.ASCII.GetString(new[] { (byte)line.lineOrigin }); break; diff --git a/LibGit2Sharp/Line.cs b/LibGit2Sharp/Line.cs new file mode 100644 index 000000000..830247fc3 --- /dev/null +++ b/LibGit2Sharp/Line.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Represents a line with line number and content. + /// + public struct Line + { + /// + /// The line number of the original line in the blob. + /// + public int LineNumber { get; } + + /// + /// The content of the line in the original blob. + /// + public string Content { get; } + + internal Line(int lineNumber, string content) + { + LineNumber = lineNumber; + Content = content; + } + } +} diff --git a/LibGit2Sharp/Patch.cs b/LibGit2Sharp/Patch.cs index 2cd4d1605..50157eb32 100644 --- a/LibGit2Sharp/Patch.cs +++ b/LibGit2Sharp/Patch.cs @@ -77,12 +77,14 @@ private unsafe int PrintCallBack(git_diff_delta* delta, GitDiffHunk hunk, GitDif case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION: linesAdded++; currentChange.LinesAdded++; + currentChange.AddedLines.Add(new Line(line.NewLineNo, patchPart)); prefix = "+"; break; case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION: linesDeleted++; currentChange.LinesDeleted++; + currentChange.DeletedLines.Add(new Line(line.OldLineNo, patchPart)); prefix = "-"; break; } @@ -168,7 +170,7 @@ public virtual string Content /// /// . /// The patch content as string. - public static implicit operator string (Patch patch) + public static implicit operator string(Patch patch) { return patch.fullPatchBuilder.ToString(); }