Skip to content

Commit c80a9f0

Browse files
nulltokendahlbyk
authored andcommitted
Fix handling of blob mode change
Fix #196
1 parent e7b3e10 commit c80a9f0

File tree

5 files changed

+142
-14
lines changed

5 files changed

+142
-14
lines changed

LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void CanCompareACommitTreeAgainstItsParent()
5555
Assert.Equal(1, changes.Count());
5656
Assert.Equal(1, changes.Added.Count());
5757

58-
TreeEntryChanges treeEntryChanges = changes["1.txt"];
58+
TreeEntryChanges treeEntryChanges = changes["1.txt"].Single();
5959
Assert.False(treeEntryChanges.IsBinaryComparison);
6060

6161
Assert.Equal("1.txt", treeEntryChanges.Path);
@@ -128,7 +128,7 @@ public void CanCompareACommitTreeAgainstATreeWithNoCommonAncestor()
128128

129129
Assert.Equal(9, changes.LinesAdded);
130130
Assert.Equal(2, changes.LinesDeleted);
131-
Assert.Equal(2, changes["readme.txt"].LinesDeleted);
131+
Assert.Equal(2, changes["readme.txt"].Single().LinesDeleted);
132132
}
133133
}
134134

@@ -161,8 +161,8 @@ public void CanDetectTheRenamingOfAModifiedFile()
161161
TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile);
162162

163163
Assert.Equal(1, changes.Count());
164-
Assert.Equal("super-file.txt", changes["super-file.txt"].Path);
165-
Assert.Equal("my-name-does-not-feel-right.txt", changes["super-file.txt"].OldPath);
164+
Assert.Equal("super-file.txt", changes["super-file.txt"].Single().Path);
165+
Assert.Equal("my-name-does-not-feel-right.txt", changes["super-file.txt"].Single().OldPath);
166166
//Assert.Equal(1, changes.FilesRenamed.Count());
167167
}
168168
}
@@ -275,12 +275,12 @@ public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks()
275275
Assert.Equal(1, changes.Deleted.Count());
276276
Assert.Equal(1, changes.Added.Count());
277277

278-
TreeEntryChanges treeEntryChanges = changes["numbers.txt"];
278+
TreeEntryChanges treeEntryChanges = changes["numbers.txt"].Single();
279279

280280
Assert.Equal(3, treeEntryChanges.LinesAdded);
281281
Assert.Equal(1, treeEntryChanges.LinesDeleted);
282282

283-
Assert.Equal(Mode.Nonexistent, changes["my-name-does-not-feel-right.txt"].Mode);
283+
Assert.Equal(Mode.Nonexistent, changes["my-name-does-not-feel-right.txt"].Single().Mode);
284284

285285
var expected = new StringBuilder()
286286
.Append("diff --git a/numbers.txt b/numbers.txt\n")
@@ -354,5 +354,68 @@ public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks()
354354
Assert.Equal(expected.ToString(), changes.Patch);
355355
}
356356
}
357+
358+
[Fact]
359+
public void CanHandleTwoTreeEntryChangesWithTheSamePath()
360+
{
361+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
362+
363+
using (Repository repo = Repository.Init(scd.DirectoryPath))
364+
{
365+
Blob mainContent = CreateBlob(repo, "awesome content\n");
366+
Blob linkContent = CreateBlob(repo, "../../objc/Nu.h");
367+
368+
const string path = "include/Nu/Nu.h";
369+
370+
var tdOld = new TreeDefinition()
371+
.Add(path, linkContent, Mode.SymbolicLink)
372+
.Add("objc/Nu.h", mainContent, Mode.NonExecutableFile);
373+
374+
Tree treeOld = repo.ObjectDatabase.CreateTree(tdOld);
375+
376+
var tdNew = new TreeDefinition()
377+
.Add(path, mainContent, Mode.NonExecutableFile);
378+
379+
Tree treeNew = repo.ObjectDatabase.CreateTree(tdNew);
380+
381+
TreeChanges changes = repo.Diff.Compare(treeOld, treeNew);
382+
383+
if (IsRunningOnLinux())
384+
{
385+
TreeEntryChanges[] tecs = changes[path].ToArray();
386+
Assert.Equal(2, tecs.Length);
387+
388+
TreeEntryChanges change1 = tecs[0];
389+
Assert.Equal(Mode.SymbolicLink, change1.OldMode);
390+
Assert.Equal(Mode.Nonexistent, change1.Mode);
391+
Assert.Equal(ChangeKind.Deleted, change1.Status);
392+
Assert.Equal("include/Nu/Nu.h", change1.Path);
393+
394+
TreeEntryChanges change2 = tecs[1];
395+
Assert.Equal(Mode.Nonexistent, change2.OldMode);
396+
Assert.Equal(Mode.NonExecutableFile, change2.Mode);
397+
Assert.Equal(ChangeKind.Added, change2.Status);
398+
Assert.Equal("include/Nu/Nu.h", change1.Path);
399+
400+
return;
401+
}
402+
403+
Assert.Equal(1, changes[path].Count());
404+
TreeEntryChanges change = changes[path].Single();
405+
406+
Assert.Equal(Mode.SymbolicLink, change.Mode);
407+
Assert.Equal(change.OldMode, change.Mode);
408+
Assert.Equal(ChangeKind.Modified, change.Status);
409+
}
410+
}
411+
412+
private static Blob CreateBlob(Repository repo, string content)
413+
{
414+
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
415+
using (var binReader = new BinaryReader(stream))
416+
{
417+
return repo.ObjectDatabase.CreateBlob(binReader);
418+
}
419+
}
357420
}
358421
}

LibGit2Sharp.Tests/StatusFixture.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Linq;
4+
using System.Text;
45
using LibGit2Sharp.Tests.TestHelpers;
56
using Xunit;
67

@@ -241,5 +242,59 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives()
241242
Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, newStatus.Ignored);
242243
}
243244
}
245+
246+
[Fact]
247+
public void CanHandleTwoStatusEntryChangesWithTheSamePath()
248+
{
249+
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
250+
251+
using (Repository repo = Repository.Init(scd.DirectoryPath))
252+
{
253+
Blob mainContent = CreateBlob(repo, "awesome content\n");
254+
Blob linkContent = CreateBlob(repo, "../../objc/Nu.h");
255+
256+
const string path = "include/Nu/Nu.h";
257+
258+
var tdOld = new TreeDefinition()
259+
.Add(path, linkContent, Mode.SymbolicLink)
260+
.Add("objc/Nu.h", mainContent, Mode.NonExecutableFile);
261+
262+
Tree tree = repo.ObjectDatabase.CreateTree(tdOld);
263+
264+
Commit commit = repo.ObjectDatabase.CreateCommit("A symlink", DummySignature, DummySignature, tree, Enumerable.Empty<Commit>());
265+
repo.Refs.UpdateTarget("HEAD", commit.Id.Sha);
266+
repo.Reset(ResetOptions.Mixed);
267+
268+
string fullPath = Path.Combine(repo.Info.WorkingDirectory, "include/Nu");
269+
Directory.CreateDirectory(fullPath);
270+
271+
File.WriteAllText(Path.Combine(fullPath, "Nu.h"), "awesome content\n");
272+
273+
RepositoryStatus status = repo.Index.RetrieveStatus();
274+
275+
if (IsRunningOnLinux())
276+
{
277+
Assert.Equal(3, status.Count());
278+
Assert.Equal(new[] { Path.Combine(Path.Combine("include", "Nu"), "Nu.h"), Path.Combine("objc", "Nu.h") }, status.Missing.ToArray());
279+
Assert.Equal(0, status.Added.Count());
280+
Assert.Equal(0, status.Modified.Count());
281+
Assert.Equal(Path.Combine(Path.Combine("include", "Nu"), "Nu.h"), status.Untracked.Single());
282+
return;
283+
}
284+
285+
Assert.Equal(2, status.Count());
286+
Assert.Equal(Path.Combine(Path.Combine("include", "Nu"), "Nu.h"), status.Modified.Single());
287+
Assert.Equal(Path.Combine("objc", "Nu.h"), status.Missing.Single());
288+
}
289+
}
290+
291+
private static Blob CreateBlob(Repository repo, string content)
292+
{
293+
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
294+
using (var binReader = new BinaryReader(stream))
295+
{
296+
return repo.ObjectDatabase.CreateBlob(binReader);
297+
}
298+
}
244299
}
245300
}

LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,12 @@ protected static void AssertValueInConfigFile(string configFilePath, string rege
103103
var r = new Regex(regex, RegexOptions.Multiline).Match(text);
104104
Assert.True(r.Success, text);
105105
}
106+
107+
protected static bool IsRunningOnLinux()
108+
{
109+
// see http://mono-project.com/FAQ%3a_Technical#Mono_Platforms
110+
var p = (int)Environment.OSVersion.Platform;
111+
return (p == 4) || (p == 6) || (p == 128);
112+
}
106113
}
107114
}

LibGit2Sharp/TreeChanges.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Text;
56
using LibGit2Sharp.Core;
67
using LibGit2Sharp.Core.Handles;
@@ -13,7 +14,7 @@ namespace LibGit2Sharp
1314
/// </summary>
1415
public class TreeChanges : IEnumerable<TreeEntryChanges>
1516
{
16-
private readonly IDictionary<FilePath, TreeEntryChanges> changes = new Dictionary<FilePath, TreeEntryChanges>();
17+
private readonly IDictionary<FilePath, List<TreeEntryChanges>> changes = new Dictionary<FilePath, List<TreeEntryChanges>>();
1718
private readonly List<TreeEntryChanges> added = new List<TreeEntryChanges>();
1819
private readonly List<TreeEntryChanges> deleted = new List<TreeEntryChanges>();
1920
private readonly List<TreeEntryChanges> modified = new List<TreeEntryChanges>();
@@ -79,7 +80,7 @@ private TreeEntryChanges AddFileChange(GitDiffDelta delta, GitDiffLineOrigin lin
7980
var newFilePath = FilePathMarshaler.FromNative(delta.NewFile.Path);
8081

8182
if (lineorigin != GitDiffLineOrigin.GIT_DIFF_LINE_FILE_HDR)
82-
return this[newFilePath];
83+
return this[newFilePath].Last();
8384

8485
var oldFilePath = FilePathMarshaler.FromNative(delta.OldFile.Path);
8586
var newMode = (Mode)delta.NewFile.Mode;
@@ -90,7 +91,9 @@ private TreeEntryChanges AddFileChange(GitDiffDelta delta, GitDiffLineOrigin lin
9091
var diffFile = new TreeEntryChanges(newFilePath, newMode, newOid, delta.Status, oldFilePath, oldMode, oldOid, delta.IsBinary());
9192

9293
fileDispatcher[delta.Status](this, diffFile);
93-
changes.Add(newFilePath, diffFile);
94+
95+
var newFilePathChanges = this[newFilePath] ?? (changes[newFilePath] = new List<TreeEntryChanges>());
96+
newFilePathChanges.Add(diffFile);
9497
return diffFile;
9598
}
9699

@@ -102,7 +105,7 @@ private TreeEntryChanges AddFileChange(GitDiffDelta delta, GitDiffLineOrigin lin
102105
/// <returns>An <see cref = "IEnumerator{T}" /> object that can be used to iterate through the collection.</returns>
103106
public virtual IEnumerator<TreeEntryChanges> GetEnumerator()
104107
{
105-
return changes.Values.GetEnumerator();
108+
return changes.Values.SelectMany(tec => tec.AsEnumerable()).GetEnumerator();
106109
}
107110

108111
/// <summary>
@@ -119,16 +122,16 @@ IEnumerator IEnumerable.GetEnumerator()
119122
/// <summary>
120123
/// Gets the <see cref = "TreeEntryChanges"/> corresponding to the specified <paramref name = "path"/>.
121124
/// </summary>
122-
public virtual TreeEntryChanges this[string path]
125+
public virtual IEnumerable<TreeEntryChanges> this[string path]
123126
{
124127
get { return this[(FilePath)path]; }
125128
}
126129

127-
private TreeEntryChanges this[FilePath path]
130+
private List<TreeEntryChanges> this[FilePath path]
128131
{
129132
get
130133
{
131-
TreeEntryChanges treeEntryChanges;
134+
List<TreeEntryChanges> treeEntryChanges;
132135
if (changes.TryGetValue(path, out treeEntryChanges))
133136
{
134137
return treeEntryChanges;

LibGit2Sharp/TreeDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public TreeDefinition Add(string targetTreeEntryPath, Blob blob, Mode mode)
136136
{
137137
Ensure.ArgumentNotNull(blob, "blob");
138138
Ensure.ArgumentConformsTo(mode,
139-
m => m.HasAny(new[] { Mode.ExecutableFile, Mode.NonExecutableFile, Mode.NonExecutableGroupWritableFile }), "mode");
139+
m => m.HasAny(new[] { Mode.ExecutableFile, Mode.NonExecutableFile, Mode.NonExecutableGroupWritableFile, Mode.SymbolicLink }), "mode");
140140

141141
TreeEntryDefinition ted = TreeEntryDefinition.From(blob, mode);
142142

0 commit comments

Comments
 (0)