Skip to content

Commit f370ad1

Browse files
committed
merge with BinarySearchFix
2 parents 570c1d2 + a9a4316 commit f370ad1

14 files changed

Lines changed: 221 additions & 105 deletions

GitContentSearch.Tests/GitContentSearcherTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Moq;
1+
using GitContentSearch.Helpers;
2+
using Moq;
23
using System;
34
using System.IO;
45
using Xunit;
@@ -15,7 +16,7 @@ public void SearchContent_ShouldLogFirstAndLastAppearance_Correctly()
1516
var fileSearcherMock = new Mock<IFileSearcher>();
1617

1718
// Simulate commits
18-
var commits = new[] { "commit1", "commit2", "commit3", "commit4", "commit5" };
19+
var commits = new[] { "commit5", "commit4", "commit3", "commit2", "commit1" };
1920
gitHelperMock.Setup(g => g.GetGitCommits(It.IsAny<string>(), It.IsAny<string>())).Returns(commits);
2021
gitHelperMock.Setup(g => g.GetCommitTime(It.IsAny<string>())).Returns("2023-08-21 12:00:00");
2122

@@ -26,7 +27,7 @@ public void SearchContent_ShouldLogFirstAndLastAppearance_Correctly()
2627
fileSearcherMock.Setup(f => f.SearchInFile(It.Is<string>(file => file.Contains("commit4")), It.IsAny<string>())).Returns(true);
2728
fileSearcherMock.Setup(f => f.SearchInFile(It.Is<string>(file => file.Contains("commit5")), It.IsAny<string>())).Returns(false);
2829

29-
var gitContentSearcher = new GitContentSearcher(gitHelperMock.Object, fileSearcherMock.Object);
30+
var gitContentSearcher = new GitContentSearcher(gitHelperMock.Object, fileSearcherMock.Object, new FileManager());
3031

3132
using (var stringWriter = new StringWriter())
3233
{
@@ -57,7 +58,7 @@ public void SearchContent_ShouldLogCorrectly_WhenStringNotFound()
5758
// Simulate no appearances of the string
5859
fileSearcherMock.Setup(f => f.SearchInFile(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
5960

60-
var gitContentSearcher = new GitContentSearcher(gitHelperMock.Object, fileSearcherMock.Object);
61+
var gitContentSearcher = new GitContentSearcher(gitHelperMock.Object, fileSearcherMock.Object, new FileManager());
6162

6263
using (var stringWriter = new StringWriter())
6364
{
@@ -92,7 +93,7 @@ public void SearchContent_ShouldLogCorrectly_WhenStringAppearsInSingleCommit()
9293
fileSearcherMock.Setup(f => f.SearchInFile(It.Is<string>(file => file.Contains("commit5")), It.IsAny<string>())).Returns(false);
9394

9495

95-
var gitContentSearcher = new GitContentSearcher(gitHelperMock.Object, fileSearcherMock.Object);
96+
var gitContentSearcher = new GitContentSearcher(gitHelperMock.Object, fileSearcherMock.Object, new FileManager());
9697

9798
using (var stringWriter = new StringWriter())
9899
{
@@ -108,4 +109,4 @@ public void SearchContent_ShouldLogCorrectly_WhenStringAppearsInSingleCommit()
108109
}
109110
}
110111
}
111-
}
112+
}

GitContentSearch.Tests/GitHelperTests.cs

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void GetGitCommits_ShouldReturnEmptyArray_OnGitFailure()
1515
var processWrapperMock = new Mock<IProcessWrapper>();
1616

1717
var processResult = new ProcessResult(string.Empty, "Error occurred", 1);
18-
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>())).Returns(processResult);
18+
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>(), null)).Returns(processResult);
1919

2020
var gitHelper = new GitHelper(processWrapperMock.Object);
2121

@@ -34,7 +34,7 @@ public void GetCommitTime_ShouldThrowException_OnProcessFailure()
3434
var processWrapperMock = new Mock<IProcessWrapper>();
3535

3636
var processResult = new ProcessResult(string.Empty, "fatal: bad object invalidCommit", 1);
37-
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>())).Returns(processResult);
37+
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>(), null)).Returns(processResult);
3838

3939
var gitHelper = new GitHelper(processWrapperMock.Object);
4040

@@ -50,7 +50,7 @@ public void GetCommitTime_ShouldReturnCorrectTime_OnSuccess()
5050
var processWrapperMock = new Mock<IProcessWrapper>();
5151

5252
var processResult = new ProcessResult("2023-08-21 12:34:56 +0000", string.Empty, 0);
53-
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>())).Returns(processResult);
53+
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>(), null)).Returns(processResult);
5454

5555
var gitHelper = new GitHelper(processWrapperMock.Object);
5656

@@ -60,58 +60,5 @@ public void GetCommitTime_ShouldReturnCorrectTime_OnSuccess()
6060
// Assert
6161
Assert.Equal("2023-08-21 12:34:56 +0000", result);
6262
}
63-
64-
[Fact]
65-
public void RunGitShow_ShouldCreateOutputFile_OnSuccess()
66-
{
67-
// Arrange
68-
var processWrapperMock = new Mock<IProcessWrapper>();
69-
70-
var fileContent = "Sample file content from git show";
71-
var processResult = new ProcessResult(fileContent, string.Empty, 0);
72-
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>())).Returns(processResult);
73-
74-
var gitHelper = new GitHelper(processWrapperMock.Object);
75-
76-
var outputFilePath = "test_output.txt";
77-
78-
// Ensure the output file does not exist before the test
79-
if (File.Exists(outputFilePath))
80-
File.Delete(outputFilePath);
81-
82-
// Act
83-
gitHelper.RunGitShow("validCommitHash", "path/to/file.txt", outputFilePath);
84-
85-
// Assert
86-
Assert.True(File.Exists(outputFilePath));
87-
var writtenContent = File.ReadAllText(outputFilePath);
88-
Assert.Equal(fileContent, writtenContent);
89-
90-
// Clean up
91-
File.Delete(outputFilePath);
92-
}
93-
94-
[Fact]
95-
public void RunGitShow_ShouldThrowException_OnProcessFailure()
96-
{
97-
// Arrange
98-
var processWrapperMock = new Mock<IProcessWrapper>();
99-
100-
var processResult = new ProcessResult(string.Empty, "fatal: path 'file.txt' does not exist in 'invalidCommitHash'", 1);
101-
processWrapperMock.Setup(pw => pw.Start(It.IsAny<ProcessStartInfo>())).Returns(processResult);
102-
103-
var gitHelper = new GitHelper(processWrapperMock.Object);
104-
105-
var outputFilePath = "test_output.txt";
106-
107-
// Act & Assert
108-
var exception = Assert.Throws<Exception>(() =>
109-
gitHelper.RunGitShow("invalidCommitHash", "file.txt", outputFilePath));
110-
111-
Assert.Equal("Error running git show: fatal: path 'file.txt' does not exist in 'invalidCommitHash'", exception.Message);
112-
113-
// Ensure the output file was not created
114-
Assert.False(File.Exists(outputFilePath));
115-
}
11663
}
117-
}
64+
}

GitContentSearch/GitContentSearcher.cs

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,53 @@
1-
namespace GitContentSearch
1+
using GitContentSearch.Helpers;
2+
3+
namespace GitContentSearch
24
{
3-
public class GitContentSearcher
5+
public class GitContentSearcher : IGitContentSearcher
46
{
57
private readonly IGitHelper _gitHelper;
68
private readonly IFileSearcher _fileSearcher;
9+
private readonly IFileManager _fileManager;
710

8-
public GitContentSearcher(IFileSearcher fileSearcher, IProcessWrapper processWrapper)
9-
{
10-
_gitHelper = new GitHelper(processWrapper);
11-
_fileSearcher = fileSearcher;
12-
}
13-
14-
public GitContentSearcher(IGitHelper gitHelper, IFileSearcher fileSearcher)
11+
public GitContentSearcher(IGitHelper gitHelper, IFileSearcher fileSearcher, IFileManager fileManager)
1512
{
1613
_gitHelper = gitHelper;
1714
_fileSearcher = fileSearcher;
15+
_fileManager = fileManager;
1816
}
1917

2018
public void SearchContent(string filePath, string searchString, string earliestCommit = "", string latestCommit = "", TextWriter? logWriter = null)
2119
{
22-
// If no logWriter is provided, log to both console and file
23-
if (logWriter == null)
24-
{
25-
logWriter = new CompositeTextWriter(
26-
Console.Out,
27-
new StreamWriter("search_log.txt", append: true)
28-
);
29-
}
20+
logWriter ??= new CompositeTextWriter(
21+
Console.Out,
22+
new StreamWriter("search_log.txt", append: true)
23+
);
3024

3125
var commits = _gitHelper.GetGitCommits(earliestCommit, latestCommit);
26+
commits = commits.Reverse().ToArray();
3227

3328
if (commits == null || commits.Length == 0)
3429
{
3530
logWriter.WriteLine("No commits found in the specified range.");
3631
return;
3732
}
3833

34+
int firstMatchIndex = FindFirstMatchIndex(commits, filePath, searchString, logWriter);
35+
int lastMatchIndex = FindLastMatchIndex(commits, filePath, searchString, logWriter, firstMatchIndex);
36+
37+
LogResults(firstMatchIndex, lastMatchIndex, commits, searchString, logWriter);
38+
}
39+
40+
private int FindFirstMatchIndex(string[] commits, string filePath, string searchString, TextWriter logWriter)
41+
{
3942
int left = 0;
4043
int right = commits.Length - 1;
44+
int? firstMatchIndex = null;
4145

4246
while (left <= right)
4347
{
4448
int mid = left + (right - left) / 2;
4549
string commit = commits[mid];
46-
47-
string tempFileName = $"temp_{commit}{Path.GetExtension(filePath)}";
50+
string tempFileName = _fileManager.GenerateTempFileName(commit, filePath);
4851

4952
try
5053
{
@@ -58,46 +61,98 @@ public void SearchContent(string filePath, string searchString, string earliestC
5861
}
5962

6063
bool found = _fileSearcher.SearchInFile(tempFileName, searchString);
64+
string commitTime = GetCommitTime(commit, logWriter);
65+
66+
logWriter.WriteLine($"Checked commit: {commit} at {commitTime}, found: {found}");
67+
logWriter.Flush();
68+
69+
if (found)
70+
{
71+
firstMatchIndex = mid;
72+
right = mid - 1; // Continue searching to the left to find the first match
73+
}
74+
else
75+
{
76+
left = mid + 1;
77+
}
78+
79+
_fileManager.DeleteTempFile(tempFileName);
80+
}
81+
82+
return firstMatchIndex ?? -1;
83+
}
84+
85+
private int FindLastMatchIndex(string[] commits, string filePath, string searchString, TextWriter logWriter, int searchStartIndex)
86+
{
87+
int left = searchStartIndex == -1 ? 0 : searchStartIndex;
88+
int right = commits.Length - 1;
89+
int? lastMatchIndex = null;
90+
91+
while (left <= right)
92+
{
93+
int mid = left + (right - left) / 2;
94+
string commit = commits[mid];
95+
string tempFileName = _fileManager.GenerateTempFileName(commit, filePath);
6196

62-
string commitTime;
6397
try
6498
{
65-
commitTime = _gitHelper.GetCommitTime(commit);
99+
_gitHelper.RunGitShow(commit, filePath, tempFileName);
66100
}
67101
catch (Exception ex)
68102
{
69-
commitTime = $"unknown time ({ex.Message})";
103+
logWriter.WriteLine($"Error retrieving file at commit {commit}: {ex.Message}");
104+
right = mid - 1;
105+
continue;
70106
}
71107

108+
bool found = _fileSearcher.SearchInFile(tempFileName, searchString);
109+
string commitTime = GetCommitTime(commit, logWriter);
110+
72111
logWriter.WriteLine($"Checked commit: {commit} at {commitTime}, found: {found}");
73112
logWriter.Flush();
74113

75114
if (found)
76115
{
77-
left = mid + 1;
116+
lastMatchIndex = mid;
117+
left = mid + 1; // Continue searching to the right to find the last match
78118
}
79119
else
80120
{
81121
right = mid - 1;
82122
}
83123

84-
if (File.Exists(tempFileName))
85-
{
86-
File.Delete(tempFileName);
87-
}
124+
_fileManager.DeleteTempFile(tempFileName);
88125
}
89126

90-
if (right < 0)
127+
return lastMatchIndex ?? -1;
128+
}
129+
130+
private string GetCommitTime(string commit, TextWriter logWriter)
131+
{
132+
try
91133
{
92-
logWriter.WriteLine($"Search string \"{searchString}\" does not appear in any of the checked commits.");
134+
return _gitHelper.GetCommitTime(commit);
135+
}
136+
catch (Exception ex)
137+
{
138+
logWriter.WriteLine($"Error retrieving commit time for {commit}: {ex.Message}");
139+
return "unknown time";
93140
}
94-
else if (left >= commits.Length)
141+
}
142+
143+
private void LogResults(int firstMatchIndex, int lastMatchIndex, string[] commits, string searchString, TextWriter logWriter)
144+
{
145+
if (firstMatchIndex == -1)
95146
{
96-
logWriter.WriteLine($"Search string \"{searchString}\" appears in all checked commits.");
147+
logWriter.WriteLine($"Search string \"{searchString}\" does not appear in any of the checked commits.");
97148
}
98149
else
99150
{
100-
logWriter.WriteLine($"Search string \"{searchString}\" appears in commit {commits[right]}.");
151+
logWriter.WriteLine($"Search string \"{searchString}\" first appears in commit {commits[firstMatchIndex]}.");
152+
if (lastMatchIndex != -1)
153+
{
154+
logWriter.WriteLine($"Search string \"{searchString}\" last appears in commit {commits[lastMatchIndex]}.");
155+
}
101156
}
102157
}
103158
}

GitContentSearch/GitHelper.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public string GetCommitTime(string commitHash)
3535

3636
public void RunGitShow(string commit, string filePath, string outputFile)
3737
{
38+
// Ensure the file path is properly formatted for Git
3839
if (filePath.StartsWith("/"))
3940
{
4041
filePath = filePath.Substring(1); // Remove the leading slash if it exists
@@ -52,14 +53,16 @@ public void RunGitShow(string commit, string filePath, string outputFile)
5253
CreateNoWindow = true
5354
};
5455

55-
var result = _processWrapper.Start(startInfo);
56+
ProcessResult result;
57+
using (var outputStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write))
58+
{
59+
result = _processWrapper.Start(startInfo, outputStream);
60+
}
5661

5762
if (result.ExitCode != 0)
5863
{
5964
throw new Exception($"Error running git show: {result.StandardError}");
6065
}
61-
62-
File.WriteAllText(outputFile, result.StandardOutput);
6366
}
6467

6568
public string[] GetGitCommits(string earliest, string latest)

GitContentSearch/Helpers/CompositeTextWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ protected override void Dispose(bool disposing)
5050

5151
base.Dispose(disposing);
5252
}
53-
}
53+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace GitContentSearch.Helpers
8+
{
9+
public class FileManager : IFileManager
10+
{
11+
public string GenerateTempFileName(string commit, string filePath)
12+
{
13+
return $"temp_{commit}{Path.GetExtension(filePath)}";
14+
}
15+
16+
public void DeleteTempFile(string tempFileName)
17+
{
18+
if (File.Exists(tempFileName))
19+
{
20+
File.Delete(tempFileName);
21+
}
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)