Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add tree to tree Diff feature

  • Loading branch information...
commit 81b1186c4541130f4636b5af9340109a75378986 1 parent c7da543
@yorah yorah authored nulltoken committed
View
333 LibGit2Sharp.Tests/DiffFixture.cs
@@ -0,0 +1,333 @@
+using System.Linq;
+using System.Text;
+using LibGit2Sharp.Tests.TestHelpers;
+using Xunit;
+
+namespace LibGit2Sharp.Tests
+{
+ public class DiffFixture : BaseFixture
+ {
+ //TODO Test binary files (do we have hunks/line callbacks)
+ //TODO What does content contain when dealing with a Binary file?
+ //TODO When does it make sense to expose the Binary property?
+ //TODO The PrintCallBack lacks some context (GitDiffDelta)
+
+ [Fact]
+ public void ComparingATreeAgainstItselfReturnsNoDifference()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree tree = repo.Head.Tip.Tree;
+
+ TreeChanges changes = repo.Diff.Compare(tree, tree);
+
+ Assert.Empty(changes);
+ }
+ }
+
+ [Fact]
+ public void RetrievingANonExistentFileChangeReturnsNull()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree tree = repo.Head.Tip.Tree;
+
+ TreeChanges changes = repo.Diff.Compare(tree, tree);
+
+ Assert.Null(changes["batman"]);
+ }
+ }
+
+ /*
+ * $ git diff --stat HEAD^..HEAD
+ * 1.txt | 1 +
+ * 1 file changed, 1 insertion(+)
+ */
+ [Fact]
+ public void CanCompareACommitTreeAgainstItsParent()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree commitTree = repo.Head.Tip.Tree;
+ Tree parentCommitTree = repo.Head.Tip.Parents.Single().Tree;
+
+ TreeChanges changes = repo.Diff.Compare(parentCommitTree, commitTree);
+
+ Assert.Equal(1, changes.Count());
+ Assert.Equal(1, changes.Added.Count());
+
+ TreeEntryChanges treeEntryChanges = changes["1.txt"];
+
+ Assert.Equal("1.txt", treeEntryChanges.Path);
+ Assert.Equal(ChangeKind.Added, treeEntryChanges.Status);
+ // Also in Added collection
+ Assert.Equal(treeEntryChanges, changes.Added.Single());
+ Assert.Equal(1, treeEntryChanges.LinesAdded);
+
+ Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode);
+ }
+ }
+
+ /*
+ * $ git diff --stat origin/test..HEAD
+ * 1.txt | 1 +
+ * 1/branch_file.txt | 1 +
+ * README | 1 +
+ * branch_file.txt | 1 +
+ * deleted_staged_file.txt | 1 +
+ * deleted_unstaged_file.txt | 1 +
+ * modified_staged_file.txt | 1 +
+ * modified_unstaged_file.txt | 1 +
+ * new.txt | 1 +
+ * readme.txt | 2 --
+ * 10 files changed, 9 insertions(+), 2 deletions(-)
+ */
+ [Fact]
+ public void CanCompareACommitTreeAgainstATreeWithNoCommonAncestor()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree commitTree = repo.Head.Tip.Tree;
+ Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree;
+
+ TreeChanges changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree);
+
+ Assert.Equal(10, changes.Count());
+ Assert.Equal(9, changes.Added.Count());
+ Assert.Equal(1, changes.Deleted.Count());
+
+ Assert.Equal("readme.txt", changes.Deleted.Single().Path);
+ Assert.Equal(new[] { "1.txt", "1/branch_file.txt", "README", "branch_file.txt", "deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new.txt" },
+ changes.Added.Select(x => x.Path));
+
+ Assert.Equal(9, changes.LinesAdded);
+ Assert.Equal(2, changes.LinesDeleted);
+ Assert.Equal(2, changes["readme.txt"].LinesDeleted);
+ }
+ }
+
+ /*
+ * $ git diff -M f8d44d7..4be51d6
+ * diff --git a/my-name-does-not-feel-right.txt b/super-file.txt
+ * similarity index 82%
+ * rename from my-name-does-not-feel-right.txt
+ * rename to super-file.txt
+ * index e8953ab..16bdf1d 100644
+ * --- a/my-name-does-not-feel-right.txt
+ * +++ b/super-file.txt
+ * @@ -2,3 +2,4 @@ That's a terrible name!
+ * I don't like it.
+ * People look down at me and laugh. :-(
+ * Really!!!!
+ * +Yeah! Better!
+ *
+ * $ git diff -M --shortstat f8d44d7..4be51d6
+ * 1 file changed, 1 insertion(+)
+ */
+ [Fact(Skip = "Not implemented in libgit2 yet.")]
+ public void CanDetectTheRenamingOfAModifiedFile()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree rootCommitTree = repo.Lookup<Commit>("f8d44d7").Tree;
+ Tree commitTreeWithRenamedFile = repo.Lookup<Commit>("4be51d6").Tree;
+
+ TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile);
+
+ Assert.Equal(1, changes.Count());
+ Assert.Equal("super-file.txt", changes["super-file.txt"].Path);
+ Assert.Equal("my-name-does-not-feel-right.txt", changes["super-file.txt"].OldPath);
+ //Assert.Equal(1, changes.FilesRenamed.Count());
+ }
+ }
+
+ /*
+ * $ git diff f8d44d7..ec9e401
+ * diff --git a/numbers.txt b/numbers.txt
+ * index 7909961..4625a36 100644
+ * --- a/numbers.txt
+ * +++ b/numbers.txt
+ * @@ -8,8 +8,9 @@
+ * 8
+ * 9
+ * 10
+ * -12
+ * +11
+ * 12
+ * 13
+ * 14
+ * 15
+ * +16
+ * \ No newline at end of file
+ *
+ * $ git diff --shortstat f8d44d7..ec9e401
+ * 1 file changed, 2 insertions(+), 1 deletion(-)
+ */
+ [Fact]
+ public void CanCompareTwoVersionsOfAFileWithATrailingNewlineDeletion()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree rootCommitTree = repo.Lookup<Commit>("f8d44d7").Tree;
+ Tree commitTreeWithUpdatedFile = repo.Lookup<Commit>("ec9e401").Tree;
+
+ TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile);
+
+ Assert.Equal(1, changes.Count());
+ Assert.Equal(1, changes.Modified.Count());
+
+ TreeEntryChanges treeEntryChanges = changes.Modified.Single();
+
+ Assert.Equal(2, treeEntryChanges.LinesAdded);
+ Assert.Equal(1, treeEntryChanges.LinesDeleted);
+ }
+ }
+
+ /*
+ * $ git diff --inter-hunk-context=2 f8d44d7..7252fe2
+ * diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt
+ * deleted file mode 100644
+ * index e8953ab..0000000
+ * --- a/my-name-does-not-feel-right.txt
+ * +++ /dev/null
+ * @@ -1,4 +0,0 @@
+ * -That's a terrible name!
+ * -I don't like it.
+ * -People look down at me and laugh. :-(
+ * -Really!!!!
+ * diff --git a/numbers.txt b/numbers.txt
+ * index 7909961..4e935b7 100644
+ * --- a/numbers.txt
+ * +++ b/numbers.txt
+ * @@ -1,4 +1,5 @@
+ * 1
+ * +2
+ * 3
+ * 4
+ * 5
+ * @@ -8,8 +9,9 @@
+ * 8
+ * 9
+ * 10
+ * -12
+ * +11
+ * 12
+ * 13
+ * 14
+ * 15
+ * +16
+ * diff --git a/super-file.txt b/super-file.txt
+ * new file mode 100644
+ * index 0000000..16bdf1d
+ * --- /dev/null
+ * +++ b/super-file.txt
+ * @@ -0,0 +1,5 @@
+ * +That's a terrible name!
+ * +I don't like it.
+ * +People look down at me and laugh. :-(
+ * +Really!!!!
+ * +Yeah! Better!
+ *
+ * $ git diff --stat f8d44d7..7252fe2
+ * my-name-does-not-feel-right.txt | 4 ----
+ * numbers.txt | 4 +++-
+ * super-file.txt | 5 +++++
+ * 3 files changed, 8 insertions(+), 5 deletions(-)
+ */
+ [Fact]
+ public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks()
+ {
+ using (var repo = new Repository(StandardTestRepoPath))
+ {
+ Tree rootCommitTree = repo.Lookup<Commit>("f8d44d7").Tree;
+ Tree mergedCommitTree = repo.Lookup<Commit>("7252fe2").Tree;
+
+ TreeChanges changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree);
+
+ Assert.Equal(3, changes.Count());
+ Assert.Equal(1, changes.Modified.Count());
+ Assert.Equal(1, changes.Deleted.Count());
+ Assert.Equal(1, changes.Added.Count());
+
+ TreeEntryChanges treeEntryChanges = changes["numbers.txt"];
+
+ Assert.Equal(3, treeEntryChanges.LinesAdded);
+ Assert.Equal(1, treeEntryChanges.LinesDeleted);
+
+ Assert.Equal(Mode.Nonexistent, changes["my-name-does-not-feel-right.txt"].Mode);
+
+ var expected = new StringBuilder()
+ .Append("diff --git a/numbers.txt b/numbers.txt\n")
+ .Append("index 7909961..4e935b7 100644\n")
+ .Append("--- a/numbers.txt\n")
+ .Append("+++ b/numbers.txt\n")
+ .Append("@@ -1,4 +1,5 @@\n")
+ .Append(" 1\n")
+ .Append("+2\n")
+ .Append(" 3\n")
+ .Append(" 4\n")
+ .Append(" 5\n")
+ .Append("@@ -8,8 +9,9 @@\n")
+ .Append(" 8\n")
+ .Append(" 9\n")
+ .Append(" 10\n")
+ .Append("-12\n")
+ .Append("+11\n")
+ .Append(" 12\n")
+ .Append(" 13\n")
+ .Append(" 14\n")
+ .Append(" 15\n")
+ .Append("+16\n");
+
+ Assert.Equal(expected.ToString(), treeEntryChanges.Patch);
+
+ expected = new StringBuilder()
+ .Append("diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt\n")
+ .Append("deleted file mode 100644\n")
+ .Append("index e8953ab..0000000\n")
+ .Append("--- a/my-name-does-not-feel-right.txt\n")
+ .Append("+++ /dev/null\n")
+ .Append("@@ -1,4 +0,0 @@\n")
+ .Append("-That's a terrible name!\n")
+ .Append("-I don't like it.\n")
+ .Append("-People look down at me and laugh. :-(\n")
+ .Append("-Really!!!!\n")
+ .Append("diff --git a/numbers.txt b/numbers.txt\n")
+ .Append("index 7909961..4e935b7 100644\n")
+ .Append("--- a/numbers.txt\n")
+ .Append("+++ b/numbers.txt\n")
+ .Append("@@ -1,4 +1,5 @@\n")
+ .Append(" 1\n")
+ .Append("+2\n")
+ .Append(" 3\n")
+ .Append(" 4\n")
+ .Append(" 5\n")
+ .Append("@@ -8,8 +9,9 @@\n")
+ .Append(" 8\n")
+ .Append(" 9\n")
+ .Append(" 10\n")
+ .Append("-12\n")
+ .Append("+11\n")
+ .Append(" 12\n")
+ .Append(" 13\n")
+ .Append(" 14\n")
+ .Append(" 15\n")
+ .Append("+16\n")
+ .Append("diff --git a/super-file.txt b/super-file.txt\n")
+ .Append("new file mode 100644\n")
+ .Append("index 0000000..16bdf1d\n")
+ .Append("--- /dev/null\n")
+ .Append("+++ b/super-file.txt\n")
+ .Append("@@ -0,0 +1,5 @@\n")
+ .Append("+That's a terrible name!\n")
+ .Append("+I don't like it.\n")
+ .Append("+People look down at me and laugh. :-(\n")
+ .Append("+Really!!!!\n")
+ .Append("+Yeah! Better!\n");
+
+ // TODO: uncomment the line below when https://github.com/libgit2/libgit2/pull/643 is merged into development branch.
+ //Assert.Equal(expected.ToString(), changes.Patch);
+ }
+ }
+ }
+}
View
1  LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
@@ -47,6 +47,7 @@
<ItemGroup>
<Compile Include="ConfigurationFixture.cs" />
<Compile Include="ObjectDatabaseFixture.cs" />
+ <Compile Include="DiffFixture.cs" />
<Compile Include="ResetFixture.cs" />
<Compile Include="LazyFixture.cs" />
<Compile Include="RemoteFixture.cs" />
View
48 LibGit2Sharp/ChangeKind.cs
@@ -0,0 +1,48 @@
+namespace LibGit2Sharp
+{
+ /// <summary>
+ /// The kind of changes that a Diff can report.
+ /// </summary>
+ public enum ChangeKind
+ {
+ /// <summary>
+ /// No changes detected.
+ /// </summary>
+ Unmodified = 0,
+
+ /// <summary>
+ /// The file was added.
+ /// </summary>
+ Added = 1,
+
+ /// <summary>
+ /// The file was deleted.
+ /// </summary>
+ Deleted = 2,
+
+ /// <summary>
+ /// The file content was modified.
+ /// </summary>
+ Modified = 3,
+
+ /// <summary>
+ /// The file was renamed.
+ /// </summary>
+ Renamed = 4,
+
+ /// <summary>
+ /// The file was copied.
+ /// </summary>
+ Copied = 5,
+
+ /// <summary>
+ /// The file is ignored in the workdir.
+ /// </summary>
+ Ignored = 6,
+
+ /// <summary>
+ /// The file is untracked in the workdir.
+ /// </summary>
+ Untracked = 7,
+ }
+}
View
17 LibGit2Sharp/Core/GitDiff.cs
@@ -1,8 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Runtime.InteropServices;
-using System.Text;
namespace LibGit2Sharp.Core
{
@@ -46,18 +43,6 @@ internal enum GitDiffFileFlags
GIT_DIFF_FILE_UNMAP_DATA = (1 << 5),
}
- public enum GitDeltaType
- {
- GIT_DELTA_UNMODIFIED = 0,
- GIT_DELTA_ADDED = 1,
- GIT_DELTA_DELETED = 2,
- GIT_DELTA_MODIFIED = 3,
- GIT_DELTA_RENAMED = 4,
- GIT_DELTA_COPIED = 5,
- GIT_DELTA_IGNORED = 6,
- GIT_DELTA_UNTRACKED = 7,
- }
-
[StructLayout(LayoutKind.Sequential)]
internal class GitDiffFile
{
@@ -73,7 +58,7 @@ internal class GitDiffDelta
{
public GitDiffFile OldFile;
public GitDiffFile NewFile;
- public GitDeltaType Status;
+ public ChangeKind Status;
public UIntPtr Similarity;
public IntPtr Binary;
}
View
52 LibGit2Sharp/Diff.cs
@@ -0,0 +1,52 @@
+using LibGit2Sharp.Core;
+using LibGit2Sharp.Core.Handles;
+
+namespace LibGit2Sharp
+{
+ /// <summary>
+ /// Show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or changes between two files on disk.
+ /// <para>Copied and renamed files currently cannot be detected, as the feature is not supported by libgit2 yet.
+ /// These files will be shown as a pair of Deleted/Added files.</para>
+ /// </summary>
+ public class Diff
+ {
+ private readonly Repository repo;
+
+ internal Diff(Repository repo)
+ {
+ this.repo = repo;
+ }
+
+ /// <summary>
+ /// Show changes between two trees.
+ /// </summary>
+ /// <param name = "oldTree">The <see cref = "Tree"/> you want to compare from.</param>
+ /// <param name = "newTree">The <see cref = "Tree"/> you want to compare to.</param>
+ /// <returns>A <see cref = "TreeChanges"/> containing the changes between the <paramref name = "oldTree"/> and the <paramref name = "newTree"/>.</returns>
+ public TreeChanges Compare(Tree oldTree, Tree newTree)
+ {
+ using (DiffListSafeHandle diff = BuildDiffListFromTrees(oldTree.Id, newTree.Id))
+ {
+ return new TreeChanges(diff);
+ }
+ }
+
+ private DiffListSafeHandle BuildDiffListFromTrees(ObjectId oldTree, ObjectId newTree)
+ {
+ using (var osw1 = new ObjectSafeWrapper(oldTree, repo))
+ using (var osw2 = new ObjectSafeWrapper(newTree, repo))
+ {
+ DiffListSafeHandle diff;
+ GitDiffOptions options = BuildDefaultOptions();
+ Ensure.Success(NativeMethods.git_diff_tree_to_tree(repo.Handle, options, osw1.ObjectPtr, osw2.ObjectPtr, out diff));
+
+ return diff;
+ }
+ }
+
+ private GitDiffOptions BuildDefaultOptions()
+ {
+ return new GitDiffOptions { InterhunkLines = 2 };
+ }
+ }
+}
View
5 LibGit2Sharp/LibGit2Sharp.csproj
@@ -59,6 +59,7 @@
<Compile Include="Core\Compat\Tuple.cs" />
<Compile Include="Core\DisposableEnumerable.cs" />
<Compile Include="Core\EnumExtensions.cs" />
+ <Compile Include="ChangeKind.cs" />
<Compile Include="Core\GitDiff.cs" />
<Compile Include="Core\GitObjectExtensions.cs" />
<Compile Include="Core\Handles\ObjectDatabaseSafeHandle.cs" />
@@ -73,6 +74,10 @@
<Compile Include="Core\Handles\SignatureSafeHandle.cs" />
<Compile Include="Core\Handles\TreeEntrySafeHandle.cs" />
<Compile Include="DetachedHead.cs" />
+ <Compile Include="Diff.cs" />
+ <Compile Include="PatchPrinter.cs" />
+ <Compile Include="TreeChanges.cs" />
+ <Compile Include="TreeEntryChanges.cs" />
<Compile Include="LibGit2Exception.cs" />
<Compile Include="Core\Handles\ConfigurationSafeHandle.cs" />
<Compile Include="Core\Ensure.cs" />
View
5 LibGit2Sharp/Mode.cs
@@ -8,6 +8,11 @@ public enum Mode
// Inspired from http://stackoverflow.com/a/8347325/335418
/// <summary>
+ /// 000000 file mode (the entry doesn't exist)
+ /// </summary>
+ Nonexistent = 0,
+
+ /// <summary>
/// 040000 file mode
/// </summary>
Directory = 0x4000,
View
55 LibGit2Sharp/PatchPrinter.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+using LibGit2Sharp.Core;
+
+namespace LibGit2Sharp
+{
+ internal class PatchPrinter
+ {
+ private readonly IDictionary<string, TreeEntryChanges> filesChanges;
+ private readonly StringBuilder fullPatchBuilder;
+ private string currentFilePath;
+
+ private static readonly Regex oldFilePathRegex = new Regex(@"\-\-\-\s*a/(.*)\n");
+ private static readonly Regex newFilePathRegex = new Regex(@"\+\+\+\s*b/(.*)\n");
+
+ internal PatchPrinter(IDictionary<string, TreeEntryChanges> filesChanges, StringBuilder fullPatchBuilder)
+ {
+ this.filesChanges = filesChanges;
+ this.fullPatchBuilder = fullPatchBuilder;
+ }
+
+ internal int PrintCallBack(IntPtr data, GitDiffLineOrigin lineorigin, string formattedoutput)
+ {
+ switch (lineorigin)
+ {
+ case GitDiffLineOrigin.GIT_DIFF_LINE_FILE_HDR:
+ ExtractAndUpdateFilePath(formattedoutput);
+ break;
+ }
+
+ filesChanges[currentFilePath].PatchBuilder.Append(formattedoutput);
+ fullPatchBuilder.Append(formattedoutput);
+
+ return 0;
+ }
+
+ // We are walking around a bug in libgit2: when a file is deleted, the oldFilePath and the newFilePath are inverted (this has been recently fixed in one of the latest commit, see https://github.com/libgit2/libgit2/pull/643)
+ private void ExtractAndUpdateFilePath(string formattedoutput)
+ {
+ var match = oldFilePathRegex.Match(formattedoutput);
+ if (match.Success)
+ {
+ currentFilePath = match.Groups[1].Value;
+ }
+
+ match = newFilePathRegex.Match(formattedoutput);
+ if (match.Success)
+ {
+ currentFilePath = match.Groups[1].Value;
+ }
+ }
+ }
+}
View
10 LibGit2Sharp/Repository.cs
@@ -22,6 +22,7 @@ public class Repository : IDisposable
private readonly Lazy<RemoteCollection> remotes;
private readonly TagCollection tags;
private readonly Lazy<RepositoryInformation> info;
+ private readonly Diff diff;
private readonly bool isBare;
private readonly Lazy<ObjectDatabase> odb;
private readonly Stack<SafeHandleBase> handlesToCleanup = new Stack<SafeHandleBase>();
@@ -59,6 +60,7 @@ public Repository(string path)
config = new Lazy<Configuration>(() => new Configuration(this));
remotes = new Lazy<RemoteCollection>(() => new RemoteCollection(this));
odb = new Lazy<ObjectDatabase>(() => new ObjectDatabase(this));
+ diff = new Diff(this);
}
/// <summary>
@@ -177,6 +179,14 @@ public RepositoryInformation Info
get { return info.Value; }
}
+ /// <summary>
+ /// Provides access to diffing functionalities to show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or changes between two files on disk.
+ /// </summary>
+ public Diff Diff
+ {
+ get { return diff; }
+ }
+
#region IDisposable Members
/// <summary>
View
181 LibGit2Sharp/TreeChanges.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using LibGit2Sharp.Core;
+using LibGit2Sharp.Core.Handles;
+
+namespace LibGit2Sharp
+{
+ /// <summary>
+ /// Holds the result of a diff between two trees.
+ /// <para>Changes at the granularity of the file can be obtained through the different sub-collections <see cref="Added"/>, <see cref="Deleted"/> and <see cref="Modified"/>.</para>
+ /// </summary>
+ public class TreeChanges : IEnumerable<TreeEntryChanges>
+ {
+ private static readonly Utf8Marshaler marshaler = (Utf8Marshaler)Utf8Marshaler.GetInstance(string.Empty);
+
+ private readonly IDictionary<string, TreeEntryChanges> changes = new Dictionary<string, TreeEntryChanges>();
+ private readonly List<TreeEntryChanges> added = new List<TreeEntryChanges>();
+ private readonly List<TreeEntryChanges> deleted = new List<TreeEntryChanges>();
+ private readonly List<TreeEntryChanges> modified = new List<TreeEntryChanges>();
+ private int linesAdded;
+ private int linesDeleted;
+
+ private readonly IDictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>> fileDispatcher = Build();
+ private readonly string patch;
+
+ private static IDictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>> Build()
+ {
+ return new Dictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>>
+ {
+ { ChangeKind.Modified, (de, d) => de.modified.Add(d) },
+ { ChangeKind.Deleted, (de, d) => de.deleted.Add(d) },
+ { ChangeKind.Added, (de, d) => de.added.Add(d) },
+ };
+ }
+
+ internal TreeChanges(DiffListSafeHandle diff)
+ {
+ var fullPatchBuilder = new StringBuilder();
+
+ Ensure.Success(NativeMethods.git_diff_foreach(diff, IntPtr.Zero, FileCallback, null, LineCallback));
+ Ensure.Success(NativeMethods.git_diff_print_patch(diff, IntPtr.Zero, new PatchPrinter(changes, fullPatchBuilder).PrintCallBack));
+
+ patch = fullPatchBuilder.ToString();
+ }
+
+ private int LineCallback(IntPtr data, GitDiffDelta delta, GitDiffLineOrigin lineorigin, IntPtr content, IntPtr contentlen)
+ {
+ var newFilePath = (string)marshaler.MarshalNativeToManaged(delta.NewFile.Path);
+
+ switch (lineorigin)
+ {
+ case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION:
+ IncrementLinesAdded(newFilePath);
+ break;
+
+ case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION:
+ IncrementLinesDeleted(newFilePath);
+ break;
+ }
+
+ return 0;
+ }
+
+ private void IncrementLinesDeleted(string filePath)
+ {
+ linesDeleted++;
+ this[filePath].LinesDeleted++;
+ }
+
+ private void IncrementLinesAdded(string filePath)
+ {
+ linesAdded++;
+ this[filePath].LinesAdded++;
+ }
+
+ private int FileCallback(IntPtr data, GitDiffDelta delta, float progress)
+ {
+ AddFileChange(delta);
+
+ return 0;
+ }
+
+ private void AddFileChange(GitDiffDelta delta)
+ {
+ var newFilePath = (string)marshaler.MarshalNativeToManaged(delta.NewFile.Path);
+ var oldFilePath = (string)marshaler.MarshalNativeToManaged(delta.OldFile.Path);
+ var newMode = (Mode)delta.NewFile.Mode;
+ var oldMode = (Mode)delta.OldFile.Mode;
+
+ var diffFile = new TreeEntryChanges(newFilePath, newMode, delta.Status, oldFilePath, oldMode);
+
+ fileDispatcher[delta.Status](this, diffFile);
+ changes.Add(diffFile.Path, diffFile);
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns>An <see cref = "IEnumerator{T}" /> object that can be used to iterate through the collection.</returns>
+ public IEnumerator<TreeEntryChanges> GetEnumerator()
+ {
+ return changes.Values.GetEnumerator();
+ }
+
+ /// <summary>
+ /// Returns an enumerator that iterates through the collection.
+ /// </summary>
+ /// <returns>An <see cref = "IEnumerator" /> object that can be used to iterate through the collection.</returns>
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ /// <summary>
+ /// Gets the <see cref = "TreeEntryChanges"/> corresponding to the specified <paramref name = "path"/>.
+ /// </summary>
+ public TreeEntryChanges this[string path]
+ {
+ get
+ {
+ TreeEntryChanges treeEntryChanges;
+ if (changes.TryGetValue(path, out treeEntryChanges))
+ {
+ return treeEntryChanges;
+ }
+
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// List of <see cref = "TreeEntryChanges"/> that have been been added.
+ /// </summary>
+ public IEnumerable<TreeEntryChanges> Added
+ {
+ get { return added; }
+ }
+
+ /// <summary>
+ /// List of <see cref = "TreeEntryChanges"/> that have been deleted.
+ /// </summary>
+ public IEnumerable<TreeEntryChanges> Deleted
+ {
+ get { return deleted; }
+ }
+
+ /// <summary>
+ /// List of <see cref = "TreeEntryChanges"/> that have been modified.
+ /// </summary>
+ public IEnumerable<TreeEntryChanges> Modified
+ {
+ get { return modified; }
+ }
+
+ /// <summary>
+ /// The total number of lines added in this diff.
+ /// </summary>
+ public int LinesAdded
+ {
+ get { return linesAdded; }
+ }
+
+ /// <summary>
+ /// The total number of lines added in this diff.
+ /// </summary>
+ public int LinesDeleted
+ {
+ get { return linesDeleted; }
+ }
+
+ /// <summary>
+ /// The full patch file of this diff.
+ /// </summary>
+ public string Patch
+ {
+ get { return patch; }
+ }
+ }
+}
View
70 LibGit2Sharp/TreeEntryChanges.cs
@@ -0,0 +1,70 @@
+using System.Text;
+using LibGit2Sharp.Core;
+
+namespace LibGit2Sharp
+{
+ /// <summary>
+ /// Holds the changes between two versions of a tree entry.
+ /// </summary>
+ public class TreeEntryChanges
+ {
+ private readonly StringBuilder patchBuilder = new StringBuilder();
+
+ internal TreeEntryChanges(string path, Mode mode, ChangeKind status, string oldPath, Mode oldMode)
+ {
+ Path = path;
+ Mode = mode;
+ Status = status;
+ OldPath = oldPath;
+ OldMode = oldMode;
+ }
+
+ /// <summary>
+ /// The new path.
+ /// </summary>
+ public string Path { get; private set; }
+
+ /// <summary>
+ /// The new <see cref="Mode"/>.
+ /// </summary>
+ public Mode Mode { get; private set; }
+
+ /// <summary>
+ /// The kind of change that has been done (added, deleted, modified ...).
+ /// </summary>
+ public ChangeKind Status { get; private set; }
+
+ /// <summary>
+ /// The old path.
+ /// </summary>
+ public string OldPath { get; private set; }
+
+ /// <summary>
+ /// The old <see cref="Mode"/>.
+ /// </summary>
+ public Mode OldMode { get; private set; }
+
+ /// <summary>
+ /// The number of lines added.
+ /// </summary>
+ public int LinesAdded { get; internal set; }
+
+ /// <summary>
+ /// The number of lines deleted.
+ /// </summary>
+ public int LinesDeleted { get; internal set; }
+
+ /// <summary>
+ /// The patch corresponding to these changes.
+ /// </summary>
+ public string Patch
+ {
+ get { return patchBuilder.ToString(); }
+ }
+
+ internal StringBuilder PatchBuilder
+ {
+ get { return patchBuilder; }
+ }
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.