8000 Introduce renames in status · apfunk/libgit2sharp@974ad99 · GitHub
[go: up one dir, main page]

Skip to content

Commit 974ad99

Browse files
author
Edward Thomson
committed
Introduce renames in status
Renames in status require more detailed data than simply paths, in order to collect old path, new path and similarity data. Use git_status_list to collect this data and use a new StatusEntry to store it.
1 parent b51570e commit 974ad99

16 files changed

+735
-73
lines changed

LibGit2Sharp.Tests/IgnoreFixture.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ public void TemporaryRulesShouldApplyUntilCleared()
1616
{
1717
Touch(repo.Info.WorkingDirectory, "Foo.cs", "Bar");
1818

19-
Assert.True(repo.Index.RetrieveStatus().Untracked.Contains("Foo.cs"));
19+
Assert.True(repo.Index.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs"));
2020

2121
repo.Ignore.AddTemporaryRules(new[] { "*.cs" });
2222

23-
Assert.False(repo.Index.RetrieveStatus().Untracked.Contains("Foo.cs"));
23+
Assert.False(repo.Index.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs"));
2424

2525
repo.Ignore.ResetAllTemporaryRules();
2626

27-
Assert.True(repo.Index.RetrieveStatus().Untracked.Contains("Foo.cs"));
27+
Assert.True(repo.Index.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs"));
2828
}
2929
}
3030

LibGit2Sharp.Tests/ResetIndexFixture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void CanResetTheIndexToTheContentOfACommitWithCommittishAsArgument()
9393
"deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt" };
9494

9595
Assert.Equal(expected.Length, newStatus.Where(IsStaged).Count());
96-
Assert.Equal(expected, newStatus.Removed);
96+
Assert.Equal(expected, newStatus.Removed.Select(s => s.FilePath));
9797
}
9898
}
9999

@@ -111,7 +111,7 @@ public void CanResetTheIndexToTheContentOfACommitWithCommitAsArgument()
111111
"deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt" };
112112

113113
Assert.Equal(expected.Length, newStatus.Where(IsStaged).Count());
114-
Assert.Equal(expected, newStatus.Removed);
114+
Assert.Equal(expected, newStatus.Removed.Select(s => s.FilePath));
115115
}
116116
}
117117

LibGit2Sharp.Tests/StatusFixture.cs

Lines changed: 190 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,42 @@ public void CanRetrieveTheStatusOfAFile()
2020
}
2121
}
2222

23+
[Theory]
24+
[InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.Untracked)]
25+
[InlineData(StatusShowOption.WorkDirOnly, FileStatus.Untracked)]
26+
[InlineData(StatusShowOption.IndexOnly, FileStatus.Nonexistent)]
27+
public void CanLimitStatusToWorkDirOnly(StatusShowOption show, FileStatus expected)
28+
{
29+
var clone = CloneStandardTestRepo();
30+
31+
using (var repo = new Repository(clone))
32+
{
33+
Touch(repo.Info.WorkingDirectory, "file.txt", "content");
34+
35+
RepositoryStatus status = repo.Index.RetrieveStatus(new StatusOptions() { Show = show });
36+
Assert.Equal(expected, status["file.txt"].State);
37+
}
38+
}
39+
40+
[Theory]
41+
[InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.Added)]
42+
[InlineData(StatusShowOption.WorkDirOnly, FileStatus.Nonexistent)]
43+
[InlineData(StatusShowOption.IndexOnly, FileStatus.Added)]
44+
public void CanLimitStatusToIndexOnly(StatusShowOption show, FileStatus expected)
45+
{
46+
var clone = CloneStandardTestRepo();
47+
48+
using (var repo = new Repository(clone))
49+
{
50+
Touch(repo.Info.WorkingDirectory, "file.txt", "content");
51+
repo.Index.Stage("file.txt");
52+
53+
RepositoryStatus status = repo.Index.RetrieveStatus(new StatusOptions() { Show = show });
54+
Assert.Equal(expected, status["file.txt"].State);
55+
}
56+
}
57+
58+
2359
[Theory]
2460
[InlineData("file")]
2561
[InlineData("file.txt")]
@@ -75,37 +111,132 @@ public void CanRetrieveTheStatusOfTheWholeWorkingDirectory()
75111

76112
RepositoryStatus status = repo.Index.RetrieveStatus();
77113

78-
Assert.Equal(FileStatus.Staged, status[file]);
114+
Assert.Equal(FileStatus.Staged, status[file].State);
79115

80116
Assert.NotNull(status);
81117
Assert.Equal(6, status.Count());
82118
Assert.True(status.IsDirty);
83119

84-
Assert.Equal("new_untracked_file.txt", status.Untracked.Single());
85-
Assert.Equal("modified_unstaged_file.txt", status.Modified.Single());
86-
Assert.Equal("deleted_unstaged_file.txt", status.Missing.Single());
87-
Assert.Equal("new_tracked_file.txt", status.Added.Single());
88-
Assert.Equal(file, status.Staged.Single());
89-
Assert.Equal("deleted_staged_file.txt", status.Removed.Single());
120+
Assert.Equal("new_untracked_file.txt", status.Untracked.Select(s => s.FilePath).Single());
121+
Assert.Equal("modified_unstaged_file.txt", status.Modified.Select(s => s.FilePath).Single());
122+
Assert.Equal("deleted_unstaged_file.txt", status.Missing.Select(s => s.FilePath).Single());
123+
Assert.Equal("new_tracked_file.txt", status.Added.Select(s => s.FilePath).Single());
124+
Assert.Equal(file, status.Staged.Select(s => s.FilePath).Single());
125+
Assert.Equal("deleted_staged_file.txt", status.Removed.Select(s => s.FilePath).Single());
90126

91127
File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, file),
92128
"Tclem's favorite commit message: boom");
93129

94130
Assert.Equal(FileStatus.Staged | FileStatus.Modified, repo.Index.RetrieveStatus(file));
95131

96132
RepositoryStatus status2 = repo.Index.RetrieveStatus();
97-
Assert.Equal(FileStatus.Staged | FileStatus.Modified, status2[file]);
133+
Assert.Equal(FileStatus.Staged | FileStatus.Modified, status2[file].State);
98134

99135
Assert.NotNull(status2);
100136
Assert.Equal(6, status2.Count());
101137
Assert.True(status2.IsDirty);
102138

103-
Assert.Equal("new_untracked_file.txt", status2.Untracked.Single());
104-
Assert.Equal(new[] { file, "modified_unstaged_file.txt" }, status2.Modified);
105-
Assert.Equal("deleted_unstaged_file.txt", status2.Missing.Single());
106-
Assert.Equal("new_tracked_file.txt", status2.Added.Single());
107-
Assert.Equal(file, status2.Staged.Single());
108-
Assert.Equal("deleted_staged_file.txt", status2.Removed.Single());
139+
Assert.Equal("new_untracked_file.txt", status2.Untracked.Select(s => s.FilePath).Single());
140+
Assert.Equal(new[] { file, "modified_unstaged_file.txt" }, status2.Modified.Select(s => s.FilePath));
141+
Assert.Equal("deleted_unstaged_file.txt", status2.Missing.Select(s => s.FilePath).Single());
142+
Assert.Equal("new_tracked_file.txt", status2.Added.Select(s => s.FilePath).Single());
143+
Assert.Equal(file, status2.Staged.Select(s => s.FilePath).Single());
144+
Assert.Equal("deleted_staged_file.txt", status2.Removed.Select(s => s.FilePath).Single());
145+
}
146+
}
147+
148+
[Fact]
149+
public void CanRetrieveTheStatusOfRenamedFilesInWorkDir()
150+
{
151+
string path = CloneStandardTestRepo();
152+
using (var repo = new Repository(path))
153+
{
154+
Touch(repo.Info.WorkingDirectory, "old_name.txt",
155+
"This is a file with enough data to trigger similarity matching.\r\n" +
156+
"This is a file with enough data to trigger similarity matching.\r\n" +
157+
"This is a file with enough data to trigger similarity matching.\r\n" +
158+
"This is a file with enough data to trigger similarity matching.\r\n");
159+
160+
repo.Index.Stage("old_name.txt");
161+
162+
File.Move(Path.Combine(repo.Info.WorkingDirectory, "old_name.txt"),
163+
Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt"));
164+
165+
RepositoryStatus status = repo.Index.RetrieveStatus(
166+
new StatusOptions()
167+
{
168+
DetectRenamesInIndex = true,
169+
DetectRenamesInWorkDir = true
170+
});
171+
172+
Assert.Equal(FileStatus.Added | FileStatus.RenamedInWorkDir, status["rename_target.txt"].State);
173+
Assert.Equal(100, status["rename_target.txt"].IndexToWorkDirRenameDetails.Similarity);
174+
}
175+
}
176+
177+
[Fact]
178+
public void CanRetrieveTheStatusOfRenamedFilesInIndex()
179+
{
180+
string path = CloneStandardTestRepo();
181+
using (var repo = new Repository(path))
182+
{
183+
File.Move(
184+
Path.Combine(repo.Info.WorkingDirectory, "1.txt"),
185+
Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt"));
186+
187+
repo.Index.Stage("1.txt");
188+
repo.Index.Stage("rename_target.txt");
189+
190+
RepositoryStatus status = repo.Index.RetrieveStatus();
191+
192+
Assert.Equal(FileStatus.RenamedInIndex, status["rename_target.txt"].State);
193+
Assert.Equal(100, status["rename_target.txt"].HeadToIndexRenameDetails.Similarity);
194+
}
195+
}
196+
197+
[Fact]
198+
public void CanDetectedVariousKindsOfRenaming()
199+
{
200+
string path = InitNewRepository();
201+
using (var repo = new Repository(path))
202+
{
203+
Touch(repo.Info.WorkingDirectory, "file.txt",
204+
"This is a file with enough data to trigger similarity matching.\r\n" +
205+
"This is a file with enough data to trigger similarity matching.\r\n" +
206+
"This is a file with enough data to trigger similarity matching.\r\n" +
207+
"This is a file with enough data to trigger similarity matching.\r\n");
208+
209+
repo.Index.Stage("file.txt");
210+
repo.Commit("Initial commit", Constants.Signature, Constants.Signature);
211+
212+
File.Move(Path.Combine(repo.Info.WorkingDirectory, "file.txt"),
213+
Path.Combine(repo.Info.WorkingDirectory, "renamed.txt"));
214+
215+
var opts = new StatusOptions
216+
{
217+
DetectRenamesInIndex = true,
218+
DetectRenamesInWorkDir = true
219+
};
220+
221+
RepositoryStatus status = repo.Index.RetrieveStatus(opts);
222+
223+
// This passes as expected
224+
Assert.Equal(FileStatus.RenamedInWorkDir, status.Single().State);
225+
226+
repo.Index.Stage("file.txt");
227+
repo.Index.Stage("renamed.txt");
228+
229+
status = repo.Index.RetrieveStatus(opts);
230+
231+
Assert.Equal(FileStatus.RenamedInIndex, status.Single().State);
232+
233+
File.Move(Path.Combine(repo.Info.WorkingDirectory, "renamed.txt"),
234+
Path.Combine(repo.Info.WorkingDirectory, "renamed_again.txt"));
235+
236+
status = repo.Index.RetrieveStatus(opts);
237+
238+
Assert.Equal(FileStatus.RenamedInWorkDir | FileStatus.RenamedInIndex,
239+
status.Single().State);
109240
}
110241
}
111242

@@ -154,7 +285,7 @@ public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths()
154285

155286
Assert.Equal(relFilePath, statusEntry.FilePath);
156287

157-
Assert.Equal(statusEntry.FilePath, repoStatus.Added.Single());
288+
Assert.Equal(statusEntry.FilePath, repoStatus.Added.Select(s => s.FilePath).Single());
158289
}
159290
}
160291

@@ -169,15 +300,15 @@ public void RetrievingTheStatusOfAnEmptyRepositoryHonorsTheGitIgnoreDirectives()
169300
Touch(repo.Info.WorkingDirectory, relativePath, "I'm going to be ignored!");
170301

171302
RepositoryStatus status = repo.Index.RetrieveStatus();
172-
Assert.Equal(new[] { relativePath }, status.Untracked);
303+
Assert.Equal(new[] { relativePath }, status.Untracked.Select(s => s.FilePath));
173304

174305
Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine);
175306

176307
RepositoryStatus newStatus = repo.Index.RetrieveStatus();
177-
Assert.Equal(".gitignore", newStatus.Untracked.Single());
308+
Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single());
178309

179310
Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath));
180-
Assert.Equal(new[] { relativePath }, newStatus.Ignored);
311+
Assert.Equal(new[] { relativePath }, newStatus.Ignored.Select(s => s.FilePath));
181312
}
182313
}
183314

@@ -223,7 +354,7 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives()
223354

224355
RepositoryStatus status = repo.Index.RetrieveStatus();
225356

226-
Assert.Equal(new[]{relativePath, "new_untracked_file.txt"}, status.Untracked);
357+
Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, status.Untracked.Select(s => s.FilePath));
227358

228359
Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine);
229360

@@ -263,10 +394,10 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives()
263394
*/
264395

265396
RepositoryStatus newStatus = repo.Index.RetrieveStatus();
266-
Assert.Equal(".gitignore", newStatus.Untracked.Single());
397+
Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single());
267398

268399
Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath));
269-
Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, newStatus.Ignored);
400+
Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, newStatus.Ignored.Select(s => s.FilePath));
270401
}
271402
}
272403

@@ -354,7 +485,7 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroug
354485
Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus("bin/what-about-me.txt"));
355486

356487
RepositoryStatus newStatus = repo.Index.RetrieveStatus();
357-
Assert.Equal(new[] { "bin" + dirSep }, newStatus.Ignored);
488+
Assert.Equal(new[] { "bin" + dirSep }, newStatus.Ignored.Select(s => s.FilePath));
358489

359490
var sb = new StringBuilder();
360491
sb.AppendLine("bin/*");
@@ -366,8 +497,43 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroug
366497

367498
newStatus = repo.Index.RetrieveStatus();
368499

369-
Assert.Equal(new[] { "bin" + dirSep + "look-ma.txt" }, newStatus.Ignored);
370-
Assert.True(newStatus.Untracked.Contains("bin" + dirSep + "what-about-me.txt" ));
500+
Assert.Equal(new[] { "bin" + dirSep + "look-ma.txt" }, newStatus.Ignored.Select(s => s.FilePath));
501+
Assert.True(newStatus.Untracked.Select(s => s.FilePath).Contains("bin" + dirSep + "what-about-me.txt"));
502+
}
503+
}
504+
505+
[Fact]
506+
public void CanRetrieveStatusOfFilesInSubmodule()
507+
{
508+
var path = CloneSubmoduleTestRepo();
509+
using (var repo = new Repository(path))
510+
{
511+
string[] expected = new string[] {
512+
".gitmodules",
513+
"sm_changed_file",
514+
"sm_changed_head",
515+
"sm_changed_index",
516+
"sm_changed_untracked_file",
517+
"sm_missing_commits"
518+
};
519+
520+
RepositoryStatus status = repo.Index.RetrieveStatus();
521+
Assert.Equal(expected, status.Modified.Select(x => x.FilePath).ToArray());
522+
}
523+
}
524+
525+
[Fact]
526+
public void CanExcludeStatusOfFilesInSubmodule()
527+
{
528+
var path = CloneSubmoduleTestRepo();
529+
using (var repo = new Repository(path))
530+
{
531+
string[] expected = new string[] {
532+
".gitmodules",
533+
};
534+
535+
RepositoryStatus status = repo.Index.RetrieveStatus(new StatusOptions() { ExcludeSubmodules = true });
536+
Assert.Equal(expected, status.Modified.Select(x => x.FilePath).ToArray());
371537
}
372538
}
373539
}

LibGit2Sharp/Core/GitStatusEntry.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
using System.Text;
6+
7+
namespace LibGit2Sharp.Core
8+
{
9+
/// <summary>
10+
/// A status entry from libgit2 741A .
11+
/// </summary>
12+
[StructLayout(LayoutKind.Sequential)]
13+
internal class GitStatusEntry
14+
{
15+
/// <summary>
16+
/// Calculated status of a filepath in the working directory considering the current <see cref = "Repository.Index" /> and the <see cref="Repository.Head" />.
17+
/// </summary>
18+
public FileStatus Status;
19+
20+
/// <summary>
21+
/// The difference between the <see cref="Repository.Head" /> and <see cref = "Repository.Index" />.
22+
/// </summary>
23+
public IntPtr HeadToIndexPtr;
24+
25+
/// <summary>
26+
/// The difference between the <see cref = "Repository.Index" /> and the working directory.
27+
/// </summary>
28+
public IntPtr IndexToWorkDirPtr;
29+
}
30+
}

0 commit comments

Comments
 (0)
0