Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 409ca38

Browse files
committedMay 30, 2025··
Merge commit 'a4188806' into fix/file-rename
2 parents 1bd2044 + a418880 commit 409ca38

File tree

6 files changed

+418
-82
lines changed

6 files changed

+418
-82
lines changed
 

‎src/Commands/CompareRevisions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public partial class CompareRevisions : Command
88
{
99
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
1010
private static partial Regex REG_FORMAT();
11-
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
11+
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)\s+(.+)$")]
1212
private static partial Regex REG_RENAME_FORMAT();
1313

1414
public CompareRevisions(string repo, string start, string end)
@@ -51,7 +51,11 @@ private void ParseLine(string line)
5151
match = REG_RENAME_FORMAT().Match(line);
5252
if (match.Success)
5353
{
54-
var renamed = new Models.Change() { Path = match.Groups[1].Value };
54+
var renamed = new Models.Change()
55+
{
56+
OriginalPath = match.Groups[1].Value,
57+
Path = match.Groups[2].Value
58+
};
5559
renamed.Set(Models.ChangeState.Renamed);
5660
_changes.Add(renamed);
5761
}

‎src/Commands/QueryCommits.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method
4343
}
4444
else if (method == Models.CommitSearchMethod.ByFile)
4545
{
46-
search += $"-- \"{filter}\"";
46+
search += $"--follow -- \"{filter}\"";
4747
}
4848
else
4949
{
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text.RegularExpressions;
4+
5+
namespace SourceGit.Commands
6+
{
7+
public partial class QueryFilePathInRevision : Command
8+
{
9+
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)\s+(.+)$")]
10+
private static partial Regex REG_RENAME_FORMAT();
11+
12+
public QueryFilePathInRevision(string repo, string revision, string currentPath)
13+
{
14+
WorkingDirectory = repo;
15+
Context = repo;
16+
_revision = revision;
17+
_currentPath = currentPath;
18+
}
19+
20+
public string Result()
21+
{
22+
if (CheckPathExistsInRevision(_currentPath))
23+
return _currentPath;
24+
25+
string mappedPath = FindRenameHistory();
26+
return mappedPath ?? _currentPath;
27+
}
28+
29+
private bool CheckPathExistsInRevision(string path)
30+
{
31+
Args = $"ls-tree -r {_revision} -- \"{path}\"";
32+
var rs = ReadToEnd();
33+
return rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut);
34+
}
35+
36+
private string FindRenameHistory()
37+
{
38+
var fileHistory = BuildFileHistory();
39+
if (fileHistory == null || fileHistory.Count == 0)
40+
return null;
41+
42+
foreach (var entry in fileHistory)
43+
{
44+
if (!IsTargetRevisionBefore(entry.CommitSHA))
45+
continue;
46+
47+
if (CheckPathExistsInRevision(entry.OldPath))
48+
return entry.OldPath;
49+
}
50+
51+
if (fileHistory.Count > 0)
52+
{
53+
var oldestPath = fileHistory[^1].OldPath;
54+
if (CheckPathExistsInRevision(oldestPath))
55+
return oldestPath;
56+
}
57+
58+
return null;
59+
}
60+
61+
private bool IsTargetRevisionBefore(string commitSHA)
62+
{
63+
Args = $"merge-base --is-ancestor {_revision} {commitSHA}";
64+
var rs = ReadToEnd();
65+
return rs.IsSuccess;
66+
}
67+
68+
private List<RenameHistoryEntry> BuildFileHistory()
69+
{
70+
Args = $"log --follow --name-status --pretty=format:\"commit %H\" -M -- \"{_currentPath}\"";
71+
var rs = ReadToEnd();
72+
if (!rs.IsSuccess)
73+
return null;
74+
75+
var result = new List<RenameHistoryEntry>();
76+
var lines = rs.StdOut.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
77+
78+
string currentCommit = null;
79+
string currentPath = _currentPath;
80+
81+
foreach (var t in lines)
82+
{
83+
var line = t.Trim();
84+
85+
if (line.StartsWith("commit ", StringComparison.Ordinal))
86+
{
87+
currentCommit = line.Substring("commit ".Length);
88+
continue;
89+
}
90+
91+
var match = REG_RENAME_FORMAT().Match(line);
92+
if (match.Success && currentCommit != null)
93+
{
94+
var oldPath = match.Groups[1].Value;
95+
var newPath = match.Groups[2].Value;
96+
97+
if (newPath == currentPath)
98+
{
99+
result.Add(new RenameHistoryEntry
100+
{
101+
CommitSHA = currentCommit,
102+
OldPath = oldPath,
103+
NewPath = newPath
104+
});
105+
106+
currentPath = oldPath;
107+
}
108+
}
109+
}
110+
111+
return result;
112+
}
113+
114+
private class RenameHistoryEntry
115+
{
116+
public string CommitSHA { get; set; }
117+
public string OldPath { get; set; }
118+
public string NewPath { get; set; }
119+
}
120+
121+
private readonly string _revision;
122+
private readonly string _currentPath;
123+
}
124+
}

‎src/Models/Change.cs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ public enum ChangeViewMode
99
Tree,
1010
}
1111

12+
[Flags]
1213
public enum ChangeState
1314
{
14-
None,
15-
Modified,
16-
TypeChanged,
17-
Added,
18-
Deleted,
19-
Renamed,
20-
Copied,
21-
Untracked,
22-
Conflicted,
15+
None = 0,
16+
Modified = 1 << 0,
17+
TypeChanged = 1 << 1,
18+
Added = 1 << 2,
19+
Deleted = 1 << 3,
20+
Renamed = 1 << 4,
21+
Copied = 1 << 5,
22+
Untracked = 1 << 6,
23+
Conflicted = 1 << 7,
2324
}
2425

2526
public enum ConflictReason
@@ -85,6 +86,30 @@ public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
8586
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
8687
}
8788

89+
public static ChangeState GetPrimaryState(ChangeState state)
90+
{
91+
if (state == ChangeState.None)
92+
return ChangeState.None;
93+
if ((state & ChangeState.Conflicted) != 0)
94+
return ChangeState.Conflicted;
95+
if ((state & ChangeState.Untracked) != 0)
96+
return ChangeState.Untracked;
97+
if ((state & ChangeState.Renamed) != 0)
98+
return ChangeState.Renamed;
99+
if ((state & ChangeState.Copied) != 0)
100+
return ChangeState.Copied;
101+
if ((state & ChangeState.Deleted) != 0)
102+
return ChangeState.Deleted;
103+
if ((state & ChangeState.Added) != 0)
104+
return ChangeState.Added;
105+
if ((state & ChangeState.TypeChanged) != 0)
106+
return ChangeState.TypeChanged;
107+
if ((state & ChangeState.Modified) != 0)
108+
return ChangeState.Modified;
109+
110+
return ChangeState.None;
111+
}
112+
88113
private static readonly string[] CONFLICT_MARKERS =
89114
[
90115
string.Empty,

‎src/ViewModels/FileHistories.cs

Lines changed: 172 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public FileHistoriesSingleRevision(Repository repo, string file, Models.Commit r
4949

5050
public void ResetToSelectedRevision()
5151
{
52-
new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_revision.SHA}");
52+
var revisionFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _revision.SHA, _file).Result();
53+
new Commands.Checkout(_repo.FullPath).FileWithRevision(revisionFilePath, $"{_revision.SHA}");
5354
}
5455

5556
private void RefreshViewContent()
@@ -62,10 +63,12 @@ private void RefreshViewContent()
6263

6364
private void SetViewContentAsRevisionFile()
6465
{
65-
var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file).Result();
66+
var revisionFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _revision.SHA, _file).Result();
67+
68+
var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, revisionFilePath).Result();
6669
if (objs.Count == 0)
6770
{
68-
ViewContent = new FileHistoriesRevisionFile(_file, null);
71+
ViewContent = new FileHistoriesRevisionFile(revisionFilePath, null);
6972
return;
7073
}
7174

@@ -75,50 +78,49 @@ private void SetViewContentAsRevisionFile()
7578
case Models.ObjectType.Blob:
7679
Task.Run(() =>
7780
{
78-
var isBinary = new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).Result();
81+
var isBinary = new Commands.IsBinary(_repo.FullPath, _revision.SHA, revisionFilePath).Result();
7982
if (isBinary)
8083
{
81-
var ext = Path.GetExtension(_file);
84+
var ext = Path.GetExtension(revisionFilePath);
8285
if (IMG_EXTS.Contains(ext))
8386
{
84-
var stream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file);
87+
var stream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, revisionFilePath);
8588
var fileSize = stream.Length;
8689
var bitmap = fileSize > 0 ? new Bitmap(stream) : null;
87-
var imageType = Path.GetExtension(_file).TrimStart('.').ToUpper(CultureInfo.CurrentCulture);
90+
var imageType = Path.GetExtension(revisionFilePath).TrimStart('.').ToUpper(CultureInfo.CurrentCulture);
8891
var image = new Models.RevisionImageFile() { Image = bitmap, FileSize = fileSize, ImageType = imageType };
89-
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, image));
92+
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, image));
9093
}
9194
else
9295
{
93-
var size = new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).Result();
96+
var size = new Commands.QueryFileSize(_repo.FullPath, revisionFilePath, _revision.SHA).Result();
9497
var binaryFile = new Models.RevisionBinaryFile() { Size = size };
95-
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, binaryFile));
98+
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, binaryFile));
9699
}
97-
98100
return;
99101
}
100102

101-
var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file);
103+
var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, revisionFilePath);
102104
var content = new StreamReader(contentStream).ReadToEnd();
103105
var matchLFS = REG_LFS_FORMAT().Match(content);
104106
if (matchLFS.Success)
105107
{
106108
var lfs = new Models.RevisionLFSObject() { Object = new Models.LFSObject() };
107109
lfs.Object.Oid = matchLFS.Groups[1].Value;
108110
lfs.Object.Size = long.Parse(matchLFS.Groups[2].Value);
109-
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, lfs));
111+
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, lfs));
110112
}
111113
else
112114
{
113115
var txt = new Models.RevisionTextFile() { FileName = obj.Path, Content = content };
114-
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, txt));
116+
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, txt));
115117
}
116118
});
117119
break;
118120
case Models.ObjectType.Commit:
119121
Task.Run(() =>
120122
{
121-
var submoduleRoot = Path.Combine(_repo.FullPath, _file);
123+
var submoduleRoot = Path.Combine(_repo.FullPath, revisionFilePath);
122124
var commit = new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).Result();
123125
if (commit != null)
124126
{
@@ -128,7 +130,7 @@ private void SetViewContentAsRevisionFile()
128130
Commit = commit,
129131
FullMessage = new Models.CommitFullMessage { Message = message }
130132
};
131-
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module));
133+
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, module));
132134
}
133135
else
134136
{
@@ -137,20 +139,38 @@ private void SetViewContentAsRevisionFile()
137139
Commit = new Models.Commit() { SHA = obj.SHA },
138140
FullMessage = null
139141
};
140-
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module));
142+
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, module));
141143
}
142144
});
143145
break;
144146
default:
145-
ViewContent = new FileHistoriesRevisionFile(_file, null);
147+
ViewContent = new FileHistoriesRevisionFile(revisionFilePath, null);
146148
break;
147149
}
148150
}
149151

150152
private void SetViewContentAsDiff()
151153
{
152-
var option = new Models.DiffOption(_revision, _file);
153-
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
154+
var revisionFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _revision.SHA, _file).Result();
155+
156+
if (_revision.Parents.Count > 0)
157+
{
158+
var parentSHA = _revision.Parents[0];
159+
var changes = new Commands.CompareRevisions(_repo.FullPath, parentSHA, _revision.SHA).Result();
160+
foreach (var change in changes)
161+
{
162+
if ((change.WorkTree == Models.ChangeState.Renamed || change.Index == Models.ChangeState.Renamed)
163+
&& change.Path == revisionFilePath)
164+
{
165+
var option = new Models.DiffOption(parentSHA, _revision.SHA, change);
166+
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
167+
return;
168+
}
169+
}
170+
}
171+
172+
var defaultOption = new Models.DiffOption(_revision, revisionFilePath);
173+
ViewContent = new DiffContext(_repo.FullPath, defaultOption, _viewContent as DiffContext);
154174
}
155175

156176
[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")]
@@ -216,7 +236,105 @@ private void RefreshViewContent()
216236
{
217237
Task.Run(() =>
218238
{
219-
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).Result();
239+
var startFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _startPoint.SHA, _file).Result();
240+
var endFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _endPoint.SHA, _file).Result();
241+
var allChanges = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA).Result();
242+
243+
var startCommand = new Commands.QueryRevisionObjects(_repo.FullPath, _startPoint.SHA, startFilePath);
244+
var startResult = startCommand.Result();
245+
bool startFileExists = startResult.Count > 0;
246+
247+
var endCommand = new Commands.QueryRevisionObjects(_repo.FullPath, _endPoint.SHA, endFilePath);
248+
var endResult = endCommand.Result();
249+
bool endFileExists = endResult.Count > 0;
250+
251+
Models.Change renamedChange = null;
252+
foreach (var change in allChanges)
253+
{
254+
if ((change.WorkTree & Models.ChangeState.Renamed) != 0 ||
255+
(change.Index & Models.ChangeState.Renamed) != 0)
256+
{
257+
if (change.Path == endFilePath || change.OriginalPath == startFilePath)
258+
{
259+
renamedChange = change;
260+
break;
261+
}
262+
}
263+
}
264+
265+
bool hasChanges = false;
266+
267+
if (renamedChange != null)
268+
{
269+
if (string.IsNullOrEmpty(renamedChange.OriginalPath))
270+
renamedChange.OriginalPath = startFilePath;
271+
272+
if (string.IsNullOrEmpty(renamedChange.Path))
273+
renamedChange.Path = endFilePath;
274+
275+
bool hasContentChange = (!startFileExists || IsEmptyFile(_repo.FullPath, _startPoint.SHA, startFilePath)) &&
276+
endFileExists && !IsEmptyFile(_repo.FullPath, _endPoint.SHA, endFilePath);
277+
278+
if (!hasContentChange)
279+
hasContentChange = ContainsContentChanges(allChanges, startFilePath, endFilePath);
280+
281+
if (hasContentChange)
282+
{
283+
renamedChange.Index |= Models.ChangeState.Modified;
284+
renamedChange.WorkTree |= Models.ChangeState.Modified;
285+
}
286+
287+
_changes = [renamedChange];
288+
hasChanges = true;
289+
}
290+
else if (startFilePath != endFilePath)
291+
{
292+
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, startFilePath).Result();
293+
294+
if (_changes.Count == 0)
295+
{
296+
var renamed = new Models.Change()
297+
{
298+
OriginalPath = startFilePath,
299+
Path = endFilePath
300+
};
301+
302+
bool hasContentChange = (!startFileExists || IsEmptyFile(_repo.FullPath, _startPoint.SHA, startFilePath)) &&
303+
endFileExists && !IsEmptyFile(_repo.FullPath, _endPoint.SHA, endFilePath);
304+
305+
if (hasContentChange)
306+
renamed.Set(Models.ChangeState.Modified | Models.ChangeState.Renamed);
307+
else
308+
renamed.Set(Models.ChangeState.Renamed);
309+
310+
_changes = [renamed];
311+
hasChanges = true;
312+
}
313+
else
314+
{
315+
foreach (var change in _changes)
316+
{
317+
if (string.IsNullOrEmpty(change.OriginalPath) && change.Path == startFilePath)
318+
{
319+
change.OriginalPath = startFilePath;
320+
change.Path = endFilePath;
321+
322+
change.Index |= Models.ChangeState.Renamed;
323+
change.WorkTree |= Models.ChangeState.Renamed;
324+
}
325+
}
326+
hasChanges = true;
327+
}
328+
}
329+
330+
if (!hasChanges)
331+
{
332+
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, endFilePath).Result();
333+
334+
if (_changes.Count == 0)
335+
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).Result();
336+
}
337+
220338
if (_changes.Count == 0)
221339
{
222340
Dispatcher.UIThread.Invoke(() => ViewContent = null);
@@ -228,6 +346,38 @@ private void RefreshViewContent()
228346
});
229347
}
230348

349+
private bool ContainsContentChanges(List<Models.Change> changes, string startPath, string endPath)
350+
{
351+
foreach (var change in changes)
352+
{
353+
if (change.Path == endPath || change.OriginalPath == startPath)
354+
{
355+
bool hasContentChanges =
356+
(change.WorkTree == Models.ChangeState.Modified ||
357+
change.WorkTree == Models.ChangeState.Added ||
358+
change.Index == Models.ChangeState.Modified ||
359+
change.Index == Models.ChangeState.Added);
360+
361+
if (hasContentChanges)
362+
return true;
363+
}
364+
}
365+
return false;
366+
}
367+
368+
private bool IsEmptyFile(string repoPath, string revision, string filePath)
369+
{
370+
try
371+
{
372+
var contentStream = Commands.QueryFileContent.Run(repoPath, revision, filePath);
373+
return contentStream != null && contentStream.Length == 0;
374+
}
375+
catch
376+
{
377+
return true;
378+
}
379+
}
380+
231381
private Repository _repo = null;
232382
private string _file = null;
233383
private Models.Commit _startPoint = null;
@@ -270,7 +420,7 @@ public FileHistories(Repository repo, string file, string commit = null)
270420
Task.Run(() =>
271421
{
272422
var based = commit ?? string.Empty;
273-
var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {based} -- \"{file}\"", false).Result();
423+
var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order --follow -n 10000 {based} -- \"{file}\"", false).Result();
274424
Dispatcher.UIThread.Invoke(() =>
275425
{
276426
IsLoading = false;

‎src/Views/ChangeStatusIcon.cs

Lines changed: 81 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Globalization;
3-
44
using Avalonia;
55
using Avalonia.Controls;
66
using Avalonia.Media;
@@ -9,55 +9,86 @@ namespace SourceGit.Views
99
{
1010
public class ChangeStatusIcon : Control
1111
{
12-
private static readonly IBrush[] BACKGROUNDS = [
13-
Brushes.Transparent,
14-
new LinearGradientBrush
15-
{
16-
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
17-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
18-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
12+
private static readonly Dictionary<Models.ChangeState, IBrush> BACKGROUNDS = new Dictionary<Models.ChangeState, IBrush>()
13+
{
14+
{ Models.ChangeState.None, Brushes.Transparent },
15+
{ Models.ChangeState.Modified, new LinearGradientBrush
16+
{
17+
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
18+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
19+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
20+
}
1921
},
20-
new LinearGradientBrush
21-
{
22-
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
23-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
24-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
22+
{ Models.ChangeState.TypeChanged, new LinearGradientBrush
23+
{
24+
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
25+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
26+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
27+
}
2528
},
26-
new LinearGradientBrush
27-
{
28-
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
29-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
30-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
29+
{ Models.ChangeState.Added, new LinearGradientBrush
30+
{
31+
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
32+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
33+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
34+
}
3135
},
32-
new LinearGradientBrush
33-
{
34-
GradientStops = new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) },
35-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
36-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
36+
{ Models.ChangeState.Deleted, new LinearGradientBrush
37+
{
38+
GradientStops = new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) },
39+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
40+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
41+
}
3742
},
38-
new LinearGradientBrush
39-
{
40-
GradientStops = new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) },
41-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
42-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
43+
{ Models.ChangeState.Renamed, new LinearGradientBrush
44+
{
45+
GradientStops = new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) },
46+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
47+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
48+
}
4349
},
44-
new LinearGradientBrush
45-
{
46-
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
47-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
48-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
50+
{ Models.ChangeState.Copied, new LinearGradientBrush
51+
{
52+
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
53+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
54+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
55+
}
4956
},
50-
new LinearGradientBrush
51-
{
52-
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
53-
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
54-
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
57+
{ Models.ChangeState.Untracked, new LinearGradientBrush
58+
{
59+
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
60+
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
61+
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
62+
}
5563
},
56-
Brushes.OrangeRed,
57-
];
64+
{ Models.ChangeState.Conflicted, Brushes.OrangeRed },
65+
};
5866

59-
private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "★", "!"];
60-
private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Untracked", "Conflict"];
67+
private static readonly Dictionary<Models.ChangeState, string> INDICATOR = new Dictionary<Models.ChangeState, string>()
68+
{
69+
{ Models.ChangeState.None, "?" },
70+
{ Models.ChangeState.Modified, "±" },
71+
{ Models.ChangeState.TypeChanged, "T" },
72+
{ Models.ChangeState.Added, "+" },
73+
{ Models.ChangeState.Deleted, "−" },
74+
{ Models.ChangeState.Renamed, "➜" },
75+
{ Models.ChangeState.Copied, "❏" },
76+
{ Models.ChangeState.Untracked, "★" },
77+
{ Models.ChangeState.Conflicted, "!" }
78+
};
79+
80+
private static readonly Dictionary<Models.ChangeState, string> TIPS = new Dictionary<Models.ChangeState, string>()
81+
{
82+
{ Models.ChangeState.None, "Unknown" },
83+
{ Models.ChangeState.Modified, "Modified" },
84+
{ Models.ChangeState.TypeChanged, "Type Changed" },
85+
{ Models.ChangeState.Added, "Added" },
86+
{ Models.ChangeState.Deleted, "Deleted" },
87+
{ Models.ChangeState.Renamed, "Renamed" },
88+
{ Models.ChangeState.Copied, "Copied" },
89+
{ Models.ChangeState.Untracked, "Untracked" },
90+
{ Models.ChangeState.Conflicted, "Conflict" }
91+
};
6192

6293
public static readonly StyledProperty<bool> IsUnstagedChangeProperty =
6394
AvaloniaProperty.Register<ChangeStatusIcon, bool>(nameof(IsUnstagedChange));
@@ -88,13 +119,15 @@ public override void Render(DrawingContext context)
88119
string indicator;
89120
if (IsUnstagedChange)
90121
{
91-
background = BACKGROUNDS[(int)Change.WorkTree];
92-
indicator = INDICATOR[(int)Change.WorkTree];
122+
var status = Models.Change.GetPrimaryState(Change.WorkTree);
123+
background = BACKGROUNDS[status];
124+
indicator = INDICATOR[status];
93125
}
94126
else
95127
{
96-
background = BACKGROUNDS[(int)Change.Index];
97-
indicator = INDICATOR[(int)Change.Index];
128+
var status = Models.Change.GetPrimaryState(Change.Index);
129+
background = BACKGROUNDS[status];
130+
indicator = INDICATOR[status];
98131
}
99132

100133
var txt = new FormattedText(
@@ -130,11 +163,11 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
130163
if (c.IsConflicted)
131164
ToolTip.SetTip(this, $"Conflict ({c.ConflictDesc})");
132165
else
133-
ToolTip.SetTip(this, TIPS[(int)c.WorkTree]);
166+
ToolTip.SetTip(this, TIPS[Models.Change.GetPrimaryState(c.Index)]);
134167
}
135168
else
136169
{
137-
ToolTip.SetTip(this, TIPS[(int)c.Index]);
170+
ToolTip.SetTip(this, TIPS[Models.Change.GetPrimaryState(c.Index)]);
138171
}
139172

140173
InvalidateVisual();

0 commit comments

Comments
 (0)
Please sign in to comment.