Open
Show file tree
Hide file tree
Changes from all commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Failed to load files.
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,7 @@ public partial class CompareRevisions : Command
{
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
private static partial Regex REG_FORMAT();
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)\s+(.+)$")]
private static partial Regex REG_RENAME_FORMAT();

public CompareRevisions(string repo, string start, string end)
Expand DownExpand Up@@ -51,7 +51,11 @@ private void ParseLine(string line)
match = REG_RENAME_FORMAT().Match(line);
if (match.Success)
{
var renamed = new Models.Change() { Path = match.Groups[1].Value };
var renamed = new Models.Change()
{
OriginalPath = match.Groups[1].Value,
Path = match.Groups[2].Value
};
renamed.Set(Models.ChangeState.Renamed);
_changes.Add(renamed);
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,7 @@ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method
}
else if (method == Models.CommitSearchMethod.ByFile)
{
search += $"-- \"{filter}\"";
search += $"--follow -- \"{filter}\"";
}
else
{
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace SourceGit.Commands
{
public partial class QueryFilePathInRevision : Command
{
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)\s+(.+)$")]
private static partial Regex REG_RENAME_FORMAT();

public QueryFilePathInRevision(string repo, string revision, string currentPath)
{
WorkingDirectory = repo;
Context = repo;
_revision = revision;
_currentPath = currentPath;
}

public string Result()
{
if (CheckPathExistsInRevision(_currentPath))
return _currentPath;

string mappedPath = FindRenameHistory();
return mappedPath ?? _currentPath;
}

private bool CheckPathExistsInRevision(string path)
{
Args = $"ls-tree -r {_revision} -- \"{path}\"";
var rs = ReadToEnd();
return rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut);
}

private string FindRenameHistory()
{
var fileHistory = BuildFileHistory();
if (fileHistory == null || fileHistory.Count == 0)
return null;

foreach (var entry in fileHistory)
{
if (!IsTargetRevisionBefore(entry.CommitSHA))
continue;

if (CheckPathExistsInRevision(entry.OldPath))
return entry.OldPath;
}

if (fileHistory.Count > 0)
{
var oldestPath = fileHistory[^1].OldPath;
if (CheckPathExistsInRevision(oldestPath))
return oldestPath;
}

return null;
}

private bool IsTargetRevisionBefore(string commitSHA)
{
Args = $"merge-base --is-ancestor {_revision} {commitSHA}";
var rs = ReadToEnd();
return rs.IsSuccess;
}

private List<RenameHistoryEntry> BuildFileHistory()
{
Args = $"log --follow --name-status --pretty=format:\"commit %H\" -M -- \"{_currentPath}\"";
var rs = ReadToEnd();
if (!rs.IsSuccess)
return null;

var result = new List<RenameHistoryEntry>();
var lines = rs.StdOut.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);

string currentCommit = null;
string currentPath = _currentPath;

foreach (var t in lines)
{
var line = t.Trim();

if (line.StartsWith("commit ", StringComparison.Ordinal))
{
currentCommit = line.Substring("commit ".Length);
continue;
}

var match = REG_RENAME_FORMAT().Match(line);
if (match.Success && currentCommit != null)
{
var oldPath = match.Groups[1].Value;
var newPath = match.Groups[2].Value;

if (newPath == currentPath)
{
result.Add(new RenameHistoryEntry
{
CommitSHA = currentCommit,
OldPath = oldPath,
NewPath = newPath
});

currentPath = oldPath;
}
}
}

return result;
}

private class RenameHistoryEntry
{
public string CommitSHA { get; set; }
public string OldPath { get; set; }
public string NewPath { get; set; }
}

private readonly string _revision;
private readonly string _currentPath;
}
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace SourceGit.Models
{
Expand All@@ -9,17 +10,18 @@ public enum ChangeViewMode
Tree,
}

[Flags]
public enum ChangeState
{
None,
Modified,
TypeChanged,
Added,
Deleted,
Renamed,
Copied,
Untracked,
Conflicted,
None = 0,
Modified = 1 << 0,
TypeChanged = 1 << 1,
Added = 1 << 2,
Deleted = 1 << 3,
Renamed = 1 << 4,
Copied = 1 << 5,
Untracked = 1 << 6,
Conflicted = 1 << 7,
}

public enum ConflictReason
Expand DownExpand Up@@ -54,8 +56,8 @@ public class Change
public string ConflictMarker => CONFLICT_MARKERS[(int)ConflictReason];
public string ConflictDesc => CONFLICT_DESCS[(int)ConflictReason];

public string WorkTreeDesc => TYPE_DESCS[(int)WorkTree];
public string IndexDesc => TYPE_DESCS[(int)Index];
public string WorkTreeDesc => TYPE_DESCS[GetPrimaryState(WorkTree)];
public string IndexDesc => TYPE_DESCS[GetPrimaryState(Index)];

public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
{
Expand DownExpand Up@@ -88,18 +90,43 @@ public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
}

private static readonly string[] TYPE_DESCS =
[
"Unknown",
"Modified",
"Type Changed",
"Added",
"Deleted",
"Renamed",
"Copied",
"Untracked",
"Conflict"
];
public static ChangeState GetPrimaryState(ChangeState state)
{
if (state == ChangeState.None)
return ChangeState.None;
if ((state & ChangeState.Conflicted) != 0)
return ChangeState.Conflicted;
if ((state & ChangeState.Untracked) != 0)
return ChangeState.Untracked;
if ((state & ChangeState.Renamed) != 0)
return ChangeState.Renamed;
if ((state & ChangeState.Copied) != 0)
return ChangeState.Copied;
if ((state & ChangeState.Deleted) != 0)
return ChangeState.Deleted;
if ((state & ChangeState.Added) != 0)
return ChangeState.Added;
if ((state & ChangeState.TypeChanged) != 0)
return ChangeState.TypeChanged;
if ((state & ChangeState.Modified) != 0)
return ChangeState.Modified;

return ChangeState.None;
}

private static readonly Dictionary<ChangeState, string> TYPE_DESCS = new Dictionary<ChangeState, string>
{
{ ChangeState.None, "Unknown" },
{ ChangeState.Modified, "Modified" },
{ ChangeState.TypeChanged, "Type Changed" },
{ ChangeState.Added, "Added" },
{ ChangeState.Deleted, "Deleted" },
{ ChangeState.Renamed, "Renamed" },
{ ChangeState.Copied, "Copied" },
{ ChangeState.Untracked, "Untracked" },
{ ChangeState.Conflicted, "Conflict" }
};

private static readonly string[] CONFLICT_MARKERS =
[
string.Empty,
Expand Down
Loading
Loading