Skip to content
This repository
Browse code

Add tree to tree Diff feature

  • Loading branch information...
commit 81b1186c4541130f4636b5af9340109a75378986 1 parent c7da543
yorah authored April 25, 2012 nulltoken committed April 28, 2012
333  LibGit2Sharp.Tests/DiffFixture.cs
... ...
@@ -0,0 +1,333 @@
  1
+using System.Linq;
  2
+using System.Text;
  3
+using LibGit2Sharp.Tests.TestHelpers;
  4
+using Xunit;
  5
+
  6
+namespace LibGit2Sharp.Tests
  7
+{
  8
+    public class DiffFixture : BaseFixture
  9
+    {
  10
+        //TODO Test binary files (do we have hunks/line callbacks)
  11
+        //TODO What does content contain when dealing with a Binary file?
  12
+        //TODO When does it make sense to expose the Binary property?
  13
+        //TODO The PrintCallBack lacks some context (GitDiffDelta)
  14
+
  15
+        [Fact]
  16
+        public void ComparingATreeAgainstItselfReturnsNoDifference()
  17
+        {
  18
+            using (var repo = new Repository(StandardTestRepoPath))
  19
+            {
  20
+                Tree tree = repo.Head.Tip.Tree;
  21
+
  22
+                TreeChanges changes = repo.Diff.Compare(tree, tree);
  23
+
  24
+                Assert.Empty(changes);
  25
+            }
  26
+        }
  27
+
  28
+        [Fact]
  29
+        public void RetrievingANonExistentFileChangeReturnsNull()
  30
+        {
  31
+            using (var repo = new Repository(StandardTestRepoPath))
  32
+            {
  33
+                Tree tree = repo.Head.Tip.Tree;
  34
+
  35
+                TreeChanges changes = repo.Diff.Compare(tree, tree);
  36
+
  37
+                Assert.Null(changes["batman"]);
  38
+            }
  39
+        }
  40
+
  41
+        /*
  42
+         * $ git diff --stat HEAD^..HEAD
  43
+         *  1.txt |    1 +
  44
+         *  1 file changed, 1 insertion(+)
  45
+         */
  46
+        [Fact]
  47
+        public void CanCompareACommitTreeAgainstItsParent()
  48
+        {
  49
+            using (var repo = new Repository(StandardTestRepoPath))
  50
+            {
  51
+                Tree commitTree = repo.Head.Tip.Tree;
  52
+                Tree parentCommitTree = repo.Head.Tip.Parents.Single().Tree;
  53
+
  54
+                TreeChanges changes = repo.Diff.Compare(parentCommitTree, commitTree);
  55
+
  56
+                Assert.Equal(1, changes.Count());
  57
+                Assert.Equal(1, changes.Added.Count());
  58
+
  59
+                TreeEntryChanges treeEntryChanges = changes["1.txt"];
  60
+
  61
+                Assert.Equal("1.txt", treeEntryChanges.Path);
  62
+                Assert.Equal(ChangeKind.Added, treeEntryChanges.Status);
  63
+                // Also in Added collection
  64
+                Assert.Equal(treeEntryChanges, changes.Added.Single());
  65
+                Assert.Equal(1, treeEntryChanges.LinesAdded);
  66
+
  67
+                Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode);
  68
+            }
  69
+        }
  70
+
  71
+        /*
  72
+         * $ git diff --stat origin/test..HEAD
  73
+         *  1.txt                      |    1 +
  74
+         *  1/branch_file.txt          |    1 +
  75
+         *  README                     |    1 +
  76
+         *  branch_file.txt            |    1 +
  77
+         *  deleted_staged_file.txt    |    1 +
  78
+         *  deleted_unstaged_file.txt  |    1 +
  79
+         *  modified_staged_file.txt   |    1 +
  80
+         *  modified_unstaged_file.txt |    1 +
  81
+         *  new.txt                    |    1 +
  82
+         *  readme.txt                 |    2 --
  83
+         *  10 files changed, 9 insertions(+), 2 deletions(-)
  84
+         */
  85
+        [Fact]
  86
+        public void CanCompareACommitTreeAgainstATreeWithNoCommonAncestor()
  87
+        {
  88
+            using (var repo = new Repository(StandardTestRepoPath))
  89
+            {
  90
+                Tree commitTree = repo.Head.Tip.Tree;
  91
+                Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree;
  92
+
  93
+                TreeChanges changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree);
  94
+
  95
+                Assert.Equal(10, changes.Count());
  96
+                Assert.Equal(9, changes.Added.Count());
  97
+                Assert.Equal(1, changes.Deleted.Count());
  98
+
  99
+                Assert.Equal("readme.txt", changes.Deleted.Single().Path);
  100
+                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" },
  101
+                             changes.Added.Select(x => x.Path));
  102
+
  103
+                Assert.Equal(9, changes.LinesAdded);
  104
+                Assert.Equal(2, changes.LinesDeleted);
  105
+                Assert.Equal(2, changes["readme.txt"].LinesDeleted);
  106
+            }
  107
+        }
  108
+
  109
+        /*
  110
+         * $ git diff -M f8d44d7..4be51d6
  111
+         * diff --git a/my-name-does-not-feel-right.txt b/super-file.txt
  112
+         * similarity index 82%
  113
+         * rename from my-name-does-not-feel-right.txt
  114
+         * rename to super-file.txt
  115
+         * index e8953ab..16bdf1d 100644
  116
+         * --- a/my-name-does-not-feel-right.txt
  117
+         * +++ b/super-file.txt
  118
+         * @@ -2,3 +2,4 @@ That's a terrible name!
  119
+         *  I don't like it.
  120
+         *  People look down at me and laugh. :-(
  121
+         *  Really!!!!
  122
+         * +Yeah! Better!
  123
+         *
  124
+         * $ git diff -M --shortstat f8d44d7..4be51d6
  125
+         *  1 file changed, 1 insertion(+)
  126
+         */
  127
+        [Fact(Skip = "Not implemented in libgit2 yet.")]
  128
+        public void CanDetectTheRenamingOfAModifiedFile()
  129
+        {
  130
+            using (var repo = new Repository(StandardTestRepoPath))
  131
+            {
  132
+                Tree rootCommitTree = repo.Lookup<Commit>("f8d44d7").Tree;
  133
+                Tree commitTreeWithRenamedFile = repo.Lookup<Commit>("4be51d6").Tree;
  134
+
  135
+                TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile);
  136
+
  137
+                Assert.Equal(1, changes.Count());
  138
+                Assert.Equal("super-file.txt", changes["super-file.txt"].Path);
  139
+                Assert.Equal("my-name-does-not-feel-right.txt", changes["super-file.txt"].OldPath);
  140
+                //Assert.Equal(1, changes.FilesRenamed.Count());
  141
+            }
  142
+        }
  143
+
  144
+        /*
  145
+         * $ git diff f8d44d7..ec9e401
  146
+         * diff --git a/numbers.txt b/numbers.txt
  147
+         * index 7909961..4625a36 100644
  148
+         * --- a/numbers.txt
  149
+         * +++ b/numbers.txt
  150
+         * @@ -8,8 +8,9 @@
  151
+         *  8
  152
+         *  9
  153
+         *  10
  154
+         * -12
  155
+         * +11
  156
+         *  12
  157
+         *  13
  158
+         *  14
  159
+         *  15
  160
+         * +16
  161
+         * \ No newline at end of file
  162
+         *
  163
+         * $ git diff --shortstat f8d44d7..ec9e401
  164
+         *  1 file changed, 2 insertions(+), 1 deletion(-)
  165
+         */
  166
+        [Fact]
  167
+        public void CanCompareTwoVersionsOfAFileWithATrailingNewlineDeletion()
  168
+        {
  169
+            using (var repo = new Repository(StandardTestRepoPath))
  170
+            {
  171
+                Tree rootCommitTree = repo.Lookup<Commit>("f8d44d7").Tree;
  172
+                Tree commitTreeWithUpdatedFile = repo.Lookup<Commit>("ec9e401").Tree;
  173
+
  174
+                TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile);
  175
+
  176
+                Assert.Equal(1, changes.Count());
  177
+                Assert.Equal(1, changes.Modified.Count());
  178
+
  179
+                TreeEntryChanges treeEntryChanges = changes.Modified.Single();
  180
+
  181
+                Assert.Equal(2, treeEntryChanges.LinesAdded);
  182
+                Assert.Equal(1, treeEntryChanges.LinesDeleted);
  183
+            }
  184
+        }
  185
+
  186
+        /*
  187
+         * $ git diff --inter-hunk-context=2 f8d44d7..7252fe2
  188
+         * diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt
  189
+         * deleted file mode 100644
  190
+         * index e8953ab..0000000
  191
+         * --- a/my-name-does-not-feel-right.txt
  192
+         * +++ /dev/null
  193
+         * @@ -1,4 +0,0 @@
  194
+         * -That's a terrible name!
  195
+         * -I don't like it.
  196
+         * -People look down at me and laugh. :-(
  197
+         * -Really!!!!
  198
+         * diff --git a/numbers.txt b/numbers.txt
  199
+         * index 7909961..4e935b7 100644
  200
+         * --- a/numbers.txt
  201
+         * +++ b/numbers.txt
  202
+         * @@ -1,4 +1,5 @@
  203
+         *  1
  204
+         * +2
  205
+         *  3
  206
+         *  4
  207
+         *  5
  208
+         * @@ -8,8 +9,9 @@
  209
+         *  8
  210
+         *  9
  211
+         *  10
  212
+         * -12
  213
+         * +11
  214
+         *  12
  215
+         *  13
  216
+         *  14
  217
+         *  15
  218
+         * +16
  219
+         * diff --git a/super-file.txt b/super-file.txt
  220
+         * new file mode 100644
  221
+         * index 0000000..16bdf1d
  222
+         * --- /dev/null
  223
+         * +++ b/super-file.txt
  224
+         * @@ -0,0 +1,5 @@
  225
+         * +That's a terrible name!
  226
+         * +I don't like it.
  227
+         * +People look down at me and laugh. :-(
  228
+         * +Really!!!!
  229
+         * +Yeah! Better!
  230
+         *
  231
+         * $ git diff --stat f8d44d7..7252fe2
  232
+         *  my-name-does-not-feel-right.txt |    4 ----
  233
+         *  numbers.txt                     |    4 +++-
  234
+         *  super-file.txt                  |    5 +++++
  235
+         *  3 files changed, 8 insertions(+), 5 deletions(-)
  236
+         */
  237
+        [Fact]
  238
+        public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks()
  239
+        {
  240
+            using (var repo = new Repository(StandardTestRepoPath))
  241
+            {
  242
+                Tree rootCommitTree = repo.Lookup<Commit>("f8d44d7").Tree;
  243
+                Tree mergedCommitTree = repo.Lookup<Commit>("7252fe2").Tree;
  244
+
  245
+                TreeChanges changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree);
  246
+
  247
+                Assert.Equal(3, changes.Count());
  248
+                Assert.Equal(1, changes.Modified.Count());
  249
+                Assert.Equal(1, changes.Deleted.Count());
  250
+                Assert.Equal(1, changes.Added.Count());
  251
+
  252
+                TreeEntryChanges treeEntryChanges = changes["numbers.txt"];
  253
+
  254
+                Assert.Equal(3, treeEntryChanges.LinesAdded);
  255
+                Assert.Equal(1, treeEntryChanges.LinesDeleted);
  256
+
  257
+                Assert.Equal(Mode.Nonexistent, changes["my-name-does-not-feel-right.txt"].Mode);
  258
+
  259
+                var expected = new StringBuilder()
  260
+                    .Append("diff --git a/numbers.txt b/numbers.txt\n")
  261
+                    .Append("index 7909961..4e935b7 100644\n")
  262
+                    .Append("--- a/numbers.txt\n")
  263
+                    .Append("+++ b/numbers.txt\n")
  264
+                    .Append("@@ -1,4 +1,5 @@\n")
  265
+                    .Append(" 1\n")
  266
+                    .Append("+2\n")
  267
+                    .Append(" 3\n")
  268
+                    .Append(" 4\n")
  269
+                    .Append(" 5\n")
  270
+                    .Append("@@ -8,8 +9,9 @@\n")
  271
+                    .Append(" 8\n")
  272
+                    .Append(" 9\n")
  273
+                    .Append(" 10\n")
  274
+                    .Append("-12\n")
  275
+                    .Append("+11\n")
  276
+                    .Append(" 12\n")
  277
+                    .Append(" 13\n")
  278
+                    .Append(" 14\n")
  279
+                    .Append(" 15\n")
  280
+                    .Append("+16\n");
  281
+
  282
+                Assert.Equal(expected.ToString(), treeEntryChanges.Patch);
  283
+
  284
+                expected = new StringBuilder()
  285
+                    .Append("diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt\n")
  286
+                    .Append("deleted file mode 100644\n")
  287
+                    .Append("index e8953ab..0000000\n")
  288
+                    .Append("--- a/my-name-does-not-feel-right.txt\n")
  289
+                    .Append("+++ /dev/null\n")
  290
+                    .Append("@@ -1,4 +0,0 @@\n")
  291
+                    .Append("-That's a terrible name!\n")
  292
+                    .Append("-I don't like it.\n")
  293
+                    .Append("-People look down at me and laugh. :-(\n")
  294
+                    .Append("-Really!!!!\n")
  295
+                    .Append("diff --git a/numbers.txt b/numbers.txt\n")
  296
+                    .Append("index 7909961..4e935b7 100644\n")
  297
+                    .Append("--- a/numbers.txt\n")
  298
+                    .Append("+++ b/numbers.txt\n")
  299
+                    .Append("@@ -1,4 +1,5 @@\n")
  300
+                    .Append(" 1\n")
  301
+                    .Append("+2\n")
  302
+                    .Append(" 3\n")
  303
+                    .Append(" 4\n")
  304
+                    .Append(" 5\n")
  305
+                    .Append("@@ -8,8 +9,9 @@\n")
  306
+                    .Append(" 8\n")
  307
+                    .Append(" 9\n")
  308
+                    .Append(" 10\n")
  309
+                    .Append("-12\n")
  310
+                    .Append("+11\n")
  311
+                    .Append(" 12\n")
  312
+                    .Append(" 13\n")
  313
+                    .Append(" 14\n")
  314
+                    .Append(" 15\n")
  315
+                    .Append("+16\n")
  316
+                    .Append("diff --git a/super-file.txt b/super-file.txt\n")
  317
+                    .Append("new file mode 100644\n")
  318
+                    .Append("index 0000000..16bdf1d\n")
  319
+                    .Append("--- /dev/null\n")
  320
+                    .Append("+++ b/super-file.txt\n")
  321
+                    .Append("@@ -0,0 +1,5 @@\n")
  322
+                    .Append("+That's a terrible name!\n")
  323
+                    .Append("+I don't like it.\n")
  324
+                    .Append("+People look down at me and laugh. :-(\n")
  325
+                    .Append("+Really!!!!\n")
  326
+                    .Append("+Yeah! Better!\n");
  327
+
  328
+                // TODO: uncomment the line below when https://github.com/libgit2/libgit2/pull/643 is merged into development branch.
  329
+                //Assert.Equal(expected.ToString(), changes.Patch);
  330
+            }
  331
+        }
  332
+    }
  333
+}
1  LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
@@ -47,6 +47,7 @@
47 47
   <ItemGroup>
48 48
     <Compile Include="ConfigurationFixture.cs" />
49 49
     <Compile Include="ObjectDatabaseFixture.cs" />
  50
+    <Compile Include="DiffFixture.cs" />
50 51
     <Compile Include="ResetFixture.cs" />
51 52
     <Compile Include="LazyFixture.cs" />
52 53
     <Compile Include="RemoteFixture.cs" />
48  LibGit2Sharp/ChangeKind.cs
... ...
@@ -0,0 +1,48 @@
  1
+namespace LibGit2Sharp
  2
+{
  3
+    /// <summary>
  4
+    ///   The kind of changes that a Diff can report.
  5
+    /// </summary>
  6
+    public enum ChangeKind
  7
+    {
  8
+        /// <summary>
  9
+        ///   No changes detected.
  10
+        /// </summary>
  11
+        Unmodified = 0,
  12
+
  13
+        /// <summary>
  14
+        ///   The file was added.
  15
+        /// </summary>
  16
+        Added = 1,
  17
+
  18
+        /// <summary>
  19
+        ///   The file was deleted.
  20
+        /// </summary>
  21
+        Deleted = 2,
  22
+
  23
+        /// <summary>
  24
+        ///   The file content was modified.
  25
+        /// </summary>
  26
+        Modified = 3,
  27
+
  28
+        /// <summary>
  29
+        ///   The file was renamed.
  30
+        /// </summary>
  31
+        Renamed = 4,
  32
+
  33
+        /// <summary>
  34
+        ///   The file was copied.
  35
+        /// </summary>
  36
+        Copied = 5,
  37
+
  38
+        /// <summary>
  39
+        ///   The file is ignored in the workdir.
  40
+        /// </summary>
  41
+        Ignored = 6,
  42
+
  43
+        /// <summary>
  44
+        ///   The file is untracked in the workdir.
  45
+        /// </summary>
  46
+        Untracked = 7,
  47
+    }
  48
+}
17  LibGit2Sharp/Core/GitDiff.cs
... ...
@@ -1,8 +1,5 @@
1 1
 using System;
2  
-using System.Collections.Generic;
3  
-using System.Linq;
4 2
 using System.Runtime.InteropServices;
5  
-using System.Text;
6 3
 
7 4
 namespace LibGit2Sharp.Core
8 5
 {
@@ -46,18 +43,6 @@ internal enum GitDiffFileFlags
46 43
         GIT_DIFF_FILE_UNMAP_DATA = (1 << 5),
47 44
     }
48 45
 
49  
-    public enum GitDeltaType
50  
-    {
51  
-        GIT_DELTA_UNMODIFIED = 0,
52  
-        GIT_DELTA_ADDED = 1,
53  
-        GIT_DELTA_DELETED = 2,
54  
-        GIT_DELTA_MODIFIED = 3,
55  
-        GIT_DELTA_RENAMED = 4,
56  
-        GIT_DELTA_COPIED = 5,
57  
-        GIT_DELTA_IGNORED = 6,
58  
-        GIT_DELTA_UNTRACKED = 7,
59  
-    }
60  
-
61 46
     [StructLayout(LayoutKind.Sequential)]
62 47
     internal class GitDiffFile
63 48
     {
@@ -73,7 +58,7 @@ internal class GitDiffDelta
73 58
     {
74 59
         public GitDiffFile OldFile;
75 60
         public GitDiffFile NewFile;
76  
-        public GitDeltaType Status;
  61
+        public ChangeKind Status;
77 62
         public UIntPtr Similarity;
78 63
         public IntPtr Binary;
79 64
     }
52  LibGit2Sharp/Diff.cs
... ...
@@ -0,0 +1,52 @@
  1
+using LibGit2Sharp.Core;
  2
+using LibGit2Sharp.Core.Handles;
  3
+
  4
+namespace LibGit2Sharp
  5
+{
  6
+    /// <summary>
  7
+    ///   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.
  8
+    ///   <para>Copied and renamed files currently cannot be detected, as the feature is not supported by libgit2 yet.
  9
+    ///   These files will be shown as a pair of Deleted/Added files.</para>
  10
+    /// </summary>
  11
+    public class Diff
  12
+    {
  13
+        private readonly Repository repo;
  14
+
  15
+        internal Diff(Repository repo)
  16
+        {
  17
+            this.repo = repo;
  18
+        }
  19
+
  20
+        /// <summary>
  21
+        ///   Show changes between two trees.
  22
+        /// </summary>
  23
+        /// <param name = "oldTree">The <see cref = "Tree"/> you want to compare from.</param>
  24
+        /// <param name = "newTree">The <see cref = "Tree"/> you want to compare to.</param>
  25
+        /// <returns>A <see cref = "TreeChanges"/> containing the changes between the <paramref name = "oldTree"/> and the <paramref name = "newTree"/>.</returns>
  26
+        public TreeChanges Compare(Tree oldTree, Tree newTree)
  27
+        {
  28
+            using (DiffListSafeHandle diff = BuildDiffListFromTrees(oldTree.Id, newTree.Id))
  29
+            {
  30
+                return new TreeChanges(diff);
  31
+            }
  32
+        }
  33
+
  34
+        private DiffListSafeHandle BuildDiffListFromTrees(ObjectId oldTree, ObjectId newTree)
  35
+        {
  36
+            using (var osw1 = new ObjectSafeWrapper(oldTree, repo))
  37
+            using (var osw2 = new ObjectSafeWrapper(newTree, repo))
  38
+            {
  39
+                DiffListSafeHandle diff;
  40
+                GitDiffOptions options = BuildDefaultOptions();
  41
+                Ensure.Success(NativeMethods.git_diff_tree_to_tree(repo.Handle, options, osw1.ObjectPtr, osw2.ObjectPtr, out diff));
  42
+
  43
+                return diff;
  44
+            }
  45
+        }
  46
+
  47
+        private GitDiffOptions BuildDefaultOptions()
  48
+        {
  49
+            return new GitDiffOptions { InterhunkLines = 2 };
  50
+        }
  51
+    }
  52
+}
5  LibGit2Sharp/LibGit2Sharp.csproj
@@ -59,6 +59,7 @@
59 59
     <Compile Include="Core\Compat\Tuple.cs" />
60 60
     <Compile Include="Core\DisposableEnumerable.cs" />
61 61
     <Compile Include="Core\EnumExtensions.cs" />
  62
+    <Compile Include="ChangeKind.cs" />
62 63
     <Compile Include="Core\GitDiff.cs" />
63 64
     <Compile Include="Core\GitObjectExtensions.cs" />
64 65
     <Compile Include="Core\Handles\ObjectDatabaseSafeHandle.cs" />
@@ -73,6 +74,10 @@
73 74
     <Compile Include="Core\Handles\SignatureSafeHandle.cs" />
74 75
     <Compile Include="Core\Handles\TreeEntrySafeHandle.cs" />
75 76
     <Compile Include="DetachedHead.cs" />
  77
+    <Compile Include="Diff.cs" />
  78
+    <Compile Include="PatchPrinter.cs" />
  79
+    <Compile Include="TreeChanges.cs" />
  80
+    <Compile Include="TreeEntryChanges.cs" />
76 81
     <Compile Include="LibGit2Exception.cs" />
77 82
     <Compile Include="Core\Handles\ConfigurationSafeHandle.cs" />
78 83
     <Compile Include="Core\Ensure.cs" />
5  LibGit2Sharp/Mode.cs
@@ -8,6 +8,11 @@ public enum Mode
8 8
         // Inspired from http://stackoverflow.com/a/8347325/335418
9 9
 
10 10
         /// <summary>
  11
+        ///   000000 file mode (the entry doesn't exist)
  12
+        /// </summary>
  13
+        Nonexistent = 0,
  14
+
  15
+        /// <summary>
11 16
         ///   040000 file mode
12 17
         /// </summary>
13 18
         Directory = 0x4000,
55  LibGit2Sharp/PatchPrinter.cs
... ...
@@ -0,0 +1,55 @@
  1
+using System;
  2
+using System.Collections.Generic;
  3
+using System.Text;
  4
+using System.Text.RegularExpressions;
  5
+using LibGit2Sharp.Core;
  6
+
  7
+namespace LibGit2Sharp
  8
+{
  9
+    internal class PatchPrinter
  10
+    {
  11
+        private readonly IDictionary<string, TreeEntryChanges> filesChanges;
  12
+        private readonly StringBuilder fullPatchBuilder;
  13
+        private string currentFilePath;
  14
+
  15
+        private static readonly Regex oldFilePathRegex = new Regex(@"\-\-\-\s*a/(.*)\n");
  16
+        private static readonly Regex newFilePathRegex = new Regex(@"\+\+\+\s*b/(.*)\n");
  17
+
  18
+        internal PatchPrinter(IDictionary<string, TreeEntryChanges> filesChanges, StringBuilder fullPatchBuilder)
  19
+        {
  20
+            this.filesChanges = filesChanges;
  21
+            this.fullPatchBuilder = fullPatchBuilder;
  22
+        }
  23
+
  24
+        internal int PrintCallBack(IntPtr data, GitDiffLineOrigin lineorigin, string formattedoutput)
  25
+        {
  26
+            switch (lineorigin)
  27
+            {
  28
+                case GitDiffLineOrigin.GIT_DIFF_LINE_FILE_HDR:
  29
+                    ExtractAndUpdateFilePath(formattedoutput);
  30
+                    break;
  31
+            }
  32
+
  33
+            filesChanges[currentFilePath].PatchBuilder.Append(formattedoutput);
  34
+            fullPatchBuilder.Append(formattedoutput);
  35
+
  36
+            return 0;
  37
+        }
  38
+
  39
+        // 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)
  40
+        private void ExtractAndUpdateFilePath(string formattedoutput)
  41
+        {
  42
+            var match = oldFilePathRegex.Match(formattedoutput);
  43
+            if (match.Success)
  44
+            {
  45
+                currentFilePath = match.Groups[1].Value;
  46
+            }
  47
+
  48
+            match = newFilePathRegex.Match(formattedoutput);
  49
+            if (match.Success)
  50
+            {
  51
+                currentFilePath = match.Groups[1].Value;
  52
+            }
  53
+        }
  54
+    }
  55
+}
10  LibGit2Sharp/Repository.cs
@@ -22,6 +22,7 @@ public class Repository : IDisposable
22 22
         private readonly Lazy<RemoteCollection> remotes;
23 23
         private readonly TagCollection tags;
24 24
         private readonly Lazy<RepositoryInformation> info;
  25
+        private readonly Diff diff;
25 26
         private readonly bool isBare;
26 27
         private readonly Lazy<ObjectDatabase> odb;
27 28
         private readonly Stack<SafeHandleBase> handlesToCleanup = new Stack<SafeHandleBase>();
@@ -59,6 +60,7 @@ public Repository(string path)
59 60
             config = new Lazy<Configuration>(() => new Configuration(this));
60 61
             remotes = new Lazy<RemoteCollection>(() => new RemoteCollection(this));
61 62
             odb = new Lazy<ObjectDatabase>(() => new ObjectDatabase(this));
  63
+            diff = new Diff(this);
62 64
         }
63 65
 
64 66
         /// <summary>
@@ -177,6 +179,14 @@ public RepositoryInformation Info
177 179
             get { return info.Value; }
178 180
         }
179 181
 
  182
+        /// <summary>
  183
+        ///   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.
  184
+        /// </summary>
  185
+        public Diff Diff
  186
+        {
  187
+            get { return diff; }
  188
+        }
  189
+
180 190
         #region IDisposable Members
181 191
 
182 192
         /// <summary>
181  LibGit2Sharp/TreeChanges.cs
... ...
@@ -0,0 +1,181 @@
  1
+using System;
  2
+using System.Collections;
  3
+using System.Collections.Generic;
  4
+using System.Text;
  5
+using LibGit2Sharp.Core;
  6
+using LibGit2Sharp.Core.Handles;
  7
+
  8
+namespace LibGit2Sharp
  9
+{
  10
+    /// <summary>
  11
+    ///   Holds the result of a diff between two trees.
  12
+    ///   <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>
  13
+    /// </summary>
  14
+    public class TreeChanges : IEnumerable<TreeEntryChanges>
  15
+    {
  16
+        private static readonly Utf8Marshaler marshaler = (Utf8Marshaler)Utf8Marshaler.GetInstance(string.Empty);
  17
+
  18
+        private readonly IDictionary<string, TreeEntryChanges> changes = new Dictionary<string, TreeEntryChanges>();
  19
+        private readonly List<TreeEntryChanges> added = new List<TreeEntryChanges>();
  20
+        private readonly List<TreeEntryChanges> deleted = new List<TreeEntryChanges>();
  21
+        private readonly List<TreeEntryChanges> modified = new List<TreeEntryChanges>();
  22
+        private int linesAdded;
  23
+        private int linesDeleted;
  24
+
  25
+        private readonly IDictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>> fileDispatcher = Build();
  26
+        private readonly string patch;
  27
+
  28
+        private static IDictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>> Build()
  29
+        {
  30
+            return new Dictionary<ChangeKind, Action<TreeChanges, TreeEntryChanges>>
  31
+                       {
  32
+                           { ChangeKind.Modified, (de, d) => de.modified.Add(d) },
  33
+                           { ChangeKind.Deleted, (de, d) => de.deleted.Add(d) },
  34
+                           { ChangeKind.Added, (de, d) => de.added.Add(d) },
  35
+                       };
  36
+        }
  37
+
  38
+        internal TreeChanges(DiffListSafeHandle diff)
  39
+        {
  40
+            var fullPatchBuilder = new StringBuilder();
  41
+
  42
+            Ensure.Success(NativeMethods.git_diff_foreach(diff, IntPtr.Zero, FileCallback, null, LineCallback));
  43
+            Ensure.Success(NativeMethods.git_diff_print_patch(diff, IntPtr.Zero, new PatchPrinter(changes, fullPatchBuilder).PrintCallBack));
  44
+
  45
+            patch = fullPatchBuilder.ToString();
  46
+        }
  47
+
  48
+        private int LineCallback(IntPtr data, GitDiffDelta delta, GitDiffLineOrigin lineorigin, IntPtr content, IntPtr contentlen)
  49
+        {
  50
+            var newFilePath = (string)marshaler.MarshalNativeToManaged(delta.NewFile.Path);
  51
+
  52
+            switch (lineorigin)
  53
+            {
  54
+                case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION:
  55
+                    IncrementLinesAdded(newFilePath);
  56
+                    break;
  57
+
  58
+                case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION:
  59
+                    IncrementLinesDeleted(newFilePath);
  60
+                    break;
  61
+            }
  62
+
  63
+            return 0;
  64
+        }
  65
+
  66
+        private void IncrementLinesDeleted(string filePath)
  67
+        {
  68
+            linesDeleted++;
  69
+            this[filePath].LinesDeleted++;
  70
+        }
  71
+
  72
+        private void IncrementLinesAdded(string filePath)
  73
+        {
  74
+            linesAdded++;
  75
+            this[filePath].LinesAdded++;
  76
+        }
  77
+
  78
+        private int FileCallback(IntPtr data, GitDiffDelta delta, float progress)
  79
+        {
  80
+            AddFileChange(delta);
  81
+
  82
+            return 0;
  83
+        }
  84
+
  85
+        private void AddFileChange(GitDiffDelta delta)
  86
+        {
  87
+            var newFilePath = (string)marshaler.MarshalNativeToManaged(delta.NewFile.Path);
  88
+            var oldFilePath = (string)marshaler.MarshalNativeToManaged(delta.OldFile.Path);
  89
+            var newMode = (Mode)delta.NewFile.Mode;
  90
+            var oldMode = (Mode)delta.OldFile.Mode;
  91
+
  92
+            var diffFile = new TreeEntryChanges(newFilePath, newMode, delta.Status, oldFilePath, oldMode);
  93
+
  94
+            fileDispatcher[delta.Status](this, diffFile);
  95
+            changes.Add(diffFile.Path, diffFile);
  96
+        }
  97
+
  98
+        /// <summary>
  99
+        ///   Returns an enumerator that iterates through the collection.
  100
+        /// </summary>
  101
+        /// <returns>An <see cref = "IEnumerator{T}" /> object that can be used to iterate through the collection.</returns>
  102
+        public IEnumerator<TreeEntryChanges> GetEnumerator()
  103
+        {
  104
+            return changes.Values.GetEnumerator();
  105
+        }
  106
+
  107
+        /// <summary>
  108
+        ///   Returns an enumerator that iterates through the collection.
  109
+        /// </summary>
  110
+        /// <returns>An <see cref = "IEnumerator" /> object that can be used to iterate through the collection.</returns>
  111
+        IEnumerator IEnumerable.GetEnumerator()
  112
+        {
  113
+            return GetEnumerator();
  114
+        }
  115
+
  116
+        /// <summary>
  117
+        ///   Gets the <see cref = "TreeEntryChanges"/> corresponding to the specified <paramref name = "path"/>.
  118
+        /// </summary>
  119
+        public TreeEntryChanges this[string path]
  120
+        {
  121
+            get
  122
+            {
  123
+                TreeEntryChanges treeEntryChanges;
  124
+                if (changes.TryGetValue(path, out treeEntryChanges))
  125
+                {
  126
+                    return treeEntryChanges;
  127
+                }
  128
+
  129
+                return null;
  130
+            }
  131
+        }
  132
+
  133
+        /// <summary>
  134
+        ///   List of <see cref = "TreeEntryChanges"/> that have been been added.
  135
+        /// </summary>
  136
+        public IEnumerable<TreeEntryChanges> Added
  137
+        {
  138
+            get { return added; }
  139
+        }
  140
+
  141
+        /// <summary>
  142
+        ///   List of <see cref = "TreeEntryChanges"/> that have been deleted.
  143
+        /// </summary>
  144
+        public IEnumerable<TreeEntryChanges> Deleted
  145
+        {
  146
+            get { return deleted; }
  147
+        }
  148
+
  149
+        /// <summary>
  150
+        ///   List of <see cref = "TreeEntryChanges"/> that have been modified.
  151
+        /// </summary>
  152
+        public IEnumerable<TreeEntryChanges> Modified
  153
+        {
  154
+            get { return modified; }
  155
+        }
  156
+
  157
+        /// <summary>
  158
+        ///   The total number of lines added in this diff.
  159
+        /// </summary>
  160
+        public int LinesAdded
  161
+        {
  162
+            get { return linesAdded; }
  163
+        }
  164
+
  165
+        /// <summary>
  166
+        ///   The total number of lines added in this diff.
  167
+        /// </summary>
  168
+        public int LinesDeleted
  169
+        {
  170
+            get { return linesDeleted; }
  171
+        }
  172
+
  173
+        /// <summary>
  174
+        ///   The full patch file of this diff.
  175
+        /// </summary>
  176
+        public string Patch
  177
+        {
  178
+            get { return patch; }
  179
+        }
  180
+    }
  181
+}
70  LibGit2Sharp/TreeEntryChanges.cs
... ...
@@ -0,0 +1,70 @@
  1
+using System.Text;
  2
+using LibGit2Sharp.Core;
  3
+
  4
+namespace LibGit2Sharp
  5
+{
  6
+    /// <summary>
  7
+    ///   Holds the changes between two versions of a tree entry.
  8
+    /// </summary>
  9
+    public class TreeEntryChanges
  10
+    {
  11
+        private readonly StringBuilder patchBuilder = new StringBuilder();
  12
+
  13
+        internal TreeEntryChanges(string path, Mode mode, ChangeKind status, string oldPath, Mode oldMode)
  14
+        {
  15
+            Path = path;
  16
+            Mode = mode;
  17
+            Status = status;
  18
+            OldPath = oldPath;
  19
+            OldMode = oldMode;
  20
+        }
  21
+
  22
+        /// <summary>
  23
+        ///   The new path.
  24
+        /// </summary>
  25
+        public string Path { get; private set; }
  26
+
  27
+        /// <summary>
  28
+        ///   The new <see cref="Mode"/>.
  29
+        /// </summary>
  30
+        public Mode Mode { get; private set; }
  31
+
  32
+        /// <summary>
  33
+        ///   The kind of change that has been done (added, deleted, modified ...).
  34
+        /// </summary>
  35
+        public ChangeKind Status { get; private set; }
  36
+
  37
+        /// <summary>
  38
+        ///   The old path.
  39
+        /// </summary>
  40
+        public string OldPath { get; private set; }
  41
+
  42
+        /// <summary>
  43
+        ///   The old <see cref="Mode"/>.
  44
+        /// </summary>
  45
+        public Mode OldMode { get; private set; }
  46
+
  47
+        /// <summary>
  48
+        ///   The number of lines added.
  49
+        /// </summary>
  50
+        public int LinesAdded { get; internal set; }
  51
+
  52
+        /// <summary>
  53
+        ///   The number of lines deleted.
  54
+        /// </summary>
  55
+        public int LinesDeleted { get; internal set; }
  56
+
  57
+        /// <summary>
  58
+        ///   The  patch corresponding to these changes.
  59
+        /// </summary>
  60
+        public string Patch
  61
+        {
  62
+            get { return patchBuilder.ToString(); }
  63
+        }
  64
+
  65
+        internal StringBuilder PatchBuilder
  66
+        {
  67
+            get { return patchBuilder; }
  68
+        }
  69
+    }
  70
+}

0 notes on commit 81b1186

Please sign in to comment.
Something went wrong with that request. Please try again.