This commit is contained in:
Gadfly 2025-06-03 18:32:49 +05:30 committed by GitHub
commit b4af9ed08d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 420 additions and 93 deletions

View file

@ -8,7 +8,7 @@ namespace SourceGit.Commands
{ {
[GeneratedRegex(@"^([MADC])\s+(.+)$")] [GeneratedRegex(@"^([MADC])\s+(.+)$")]
private static partial Regex REG_FORMAT(); 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(); private static partial Regex REG_RENAME_FORMAT();
public CompareRevisions(string repo, string start, string end) public CompareRevisions(string repo, string start, string end)
@ -51,7 +51,11 @@ namespace SourceGit.Commands
match = REG_RENAME_FORMAT().Match(line); match = REG_RENAME_FORMAT().Match(line);
if (match.Success) 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); renamed.Set(Models.ChangeState.Renamed);
_changes.Add(renamed); _changes.Add(renamed);
} }

View file

@ -43,7 +43,7 @@ namespace SourceGit.Commands
} }
else if (method == Models.CommitSearchMethod.ByFile) else if (method == Models.CommitSearchMethod.ByFile)
{ {
search += $"-- \"{filter}\""; search += $"--follow -- \"{filter}\"";
} }
else else
{ {

View file

@ -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;
}
}

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace SourceGit.Models namespace SourceGit.Models
{ {
@ -9,17 +10,18 @@ namespace SourceGit.Models
Tree, Tree,
} }
[Flags]
public enum ChangeState public enum ChangeState
{ {
None, None = 0,
Modified, Modified = 1 << 0,
TypeChanged, TypeChanged = 1 << 1,
Added, Added = 1 << 2,
Deleted, Deleted = 1 << 3,
Renamed, Renamed = 1 << 4,
Copied, Copied = 1 << 5,
Untracked, Untracked = 1 << 6,
Conflicted, Conflicted = 1 << 7,
} }
public enum ConflictReason public enum ConflictReason
@ -54,8 +56,8 @@ namespace SourceGit.Models
public string ConflictMarker => CONFLICT_MARKERS[(int)ConflictReason]; public string ConflictMarker => CONFLICT_MARKERS[(int)ConflictReason];
public string ConflictDesc => CONFLICT_DESCS[(int)ConflictReason]; public string ConflictDesc => CONFLICT_DESCS[(int)ConflictReason];
public string WorkTreeDesc => TYPE_DESCS[(int)WorkTree]; public string WorkTreeDesc => TYPE_DESCS[GetPrimaryState(WorkTree)];
public string IndexDesc => TYPE_DESCS[(int)Index]; public string IndexDesc => TYPE_DESCS[GetPrimaryState(Index)];
public void Set(ChangeState index, ChangeState workTree = ChangeState.None) public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
{ {
@ -88,18 +90,43 @@ namespace SourceGit.Models
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2); OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
} }
private static readonly string[] TYPE_DESCS = public static ChangeState GetPrimaryState(ChangeState state)
[ {
"Unknown", if (state == ChangeState.None)
"Modified", return ChangeState.None;
"Type Changed", if ((state & ChangeState.Conflicted) != 0)
"Added", return ChangeState.Conflicted;
"Deleted", if ((state & ChangeState.Untracked) != 0)
"Renamed", return ChangeState.Untracked;
"Copied", if ((state & ChangeState.Renamed) != 0)
"Untracked", return ChangeState.Renamed;
"Conflict" 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 = private static readonly string[] CONFLICT_MARKERS =
[ [
string.Empty, string.Empty,

View file

@ -49,7 +49,8 @@ namespace SourceGit.ViewModels
public void ResetToSelectedRevision() public void ResetToSelectedRevision()
{ {
new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_revision.SHA}"); var revisionFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _revision.SHA, _file).Result();
new Commands.Checkout(_repo.FullPath).FileWithRevision(revisionFilePath, $"{_revision.SHA}");
} }
private void RefreshViewContent() private void RefreshViewContent()
@ -62,10 +63,12 @@ namespace SourceGit.ViewModels
private void SetViewContentAsRevisionFile() private void SetViewContentAsRevisionFile()
{ {
var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file).Result(); var revisionFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _revision.SHA, _file).Result();
var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, revisionFilePath).Result();
if (objs.Count == 0) if (objs.Count == 0)
{ {
ViewContent = new FileHistoriesRevisionFile(_file, null); ViewContent = new FileHistoriesRevisionFile(revisionFilePath, null);
return; return;
} }
@ -75,30 +78,29 @@ namespace SourceGit.ViewModels
case Models.ObjectType.Blob: case Models.ObjectType.Blob:
Task.Run(() => Task.Run(() =>
{ {
var isBinary = new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).Result(); var isBinary = new Commands.IsBinary(_repo.FullPath, _revision.SHA, revisionFilePath).Result();
if (isBinary) if (isBinary)
{ {
var ext = Path.GetExtension(_file); var ext = Path.GetExtension(revisionFilePath);
if (IMG_EXTS.Contains(ext)) if (IMG_EXTS.Contains(ext))
{ {
var stream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file); var stream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, revisionFilePath);
var fileSize = stream.Length; var fileSize = stream.Length;
var bitmap = fileSize > 0 ? new Bitmap(stream) : null; var bitmap = fileSize > 0 ? new Bitmap(stream) : null;
var imageType = Path.GetExtension(_file).TrimStart('.').ToUpper(CultureInfo.CurrentCulture); var imageType = Path.GetExtension(revisionFilePath).TrimStart('.').ToUpper(CultureInfo.CurrentCulture);
var image = new Models.RevisionImageFile() { Image = bitmap, FileSize = fileSize, ImageType = imageType }; var image = new Models.RevisionImageFile() { Image = bitmap, FileSize = fileSize, ImageType = imageType };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, image)); Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, image));
} }
else else
{ {
var size = new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).Result(); var size = new Commands.QueryFileSize(_repo.FullPath, revisionFilePath, _revision.SHA).Result();
var binaryFile = new Models.RevisionBinaryFile() { Size = size }; var binaryFile = new Models.RevisionBinaryFile() { Size = size };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, binaryFile)); Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, binaryFile));
} }
return; return;
} }
var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file); var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, revisionFilePath);
var content = new StreamReader(contentStream).ReadToEnd(); var content = new StreamReader(contentStream).ReadToEnd();
var matchLFS = REG_LFS_FORMAT().Match(content); var matchLFS = REG_LFS_FORMAT().Match(content);
if (matchLFS.Success) if (matchLFS.Success)
@ -106,19 +108,19 @@ namespace SourceGit.ViewModels
var lfs = new Models.RevisionLFSObject() { Object = new Models.LFSObject() }; var lfs = new Models.RevisionLFSObject() { Object = new Models.LFSObject() };
lfs.Object.Oid = matchLFS.Groups[1].Value; lfs.Object.Oid = matchLFS.Groups[1].Value;
lfs.Object.Size = long.Parse(matchLFS.Groups[2].Value); lfs.Object.Size = long.Parse(matchLFS.Groups[2].Value);
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, lfs)); Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, lfs));
} }
else else
{ {
var txt = new Models.RevisionTextFile() { FileName = obj.Path, Content = content }; var txt = new Models.RevisionTextFile() { FileName = obj.Path, Content = content };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, txt)); Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, txt));
} }
}); });
break; break;
case Models.ObjectType.Commit: case Models.ObjectType.Commit:
Task.Run(() => Task.Run(() =>
{ {
var submoduleRoot = Path.Combine(_repo.FullPath, _file); var submoduleRoot = Path.Combine(_repo.FullPath, revisionFilePath);
var commit = new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).Result(); var commit = new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).Result();
if (commit != null) if (commit != null)
{ {
@ -128,7 +130,7 @@ namespace SourceGit.ViewModels
Commit = commit, Commit = commit,
FullMessage = new Models.CommitFullMessage { Message = message } FullMessage = new Models.CommitFullMessage { Message = message }
}; };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module)); Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, module));
} }
else else
{ {
@ -137,20 +139,38 @@ namespace SourceGit.ViewModels
Commit = new Models.Commit() { SHA = obj.SHA }, Commit = new Models.Commit() { SHA = obj.SHA },
FullMessage = null FullMessage = null
}; };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module)); Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(revisionFilePath, module));
} }
}); });
break; break;
default: default:
ViewContent = new FileHistoriesRevisionFile(_file, null); ViewContent = new FileHistoriesRevisionFile(revisionFilePath, null);
break; break;
} }
} }
private void SetViewContentAsDiff() private void SetViewContentAsDiff()
{ {
var option = new Models.DiffOption(_revision, _file); var revisionFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _revision.SHA, _file).Result();
if (_revision.Parents.Count > 0)
{
var parentSHA = _revision.Parents[0];
var changes = new Commands.CompareRevisions(_repo.FullPath, parentSHA, _revision.SHA).Result();
foreach (var change in changes)
{
if ((change.WorkTree == Models.ChangeState.Renamed || change.Index == Models.ChangeState.Renamed)
&& change.Path == revisionFilePath)
{
var option = new Models.DiffOption(parentSHA, _revision.SHA, change);
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext); ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
return;
}
}
}
var defaultOption = new Models.DiffOption(_revision, revisionFilePath);
ViewContent = new DiffContext(_repo.FullPath, defaultOption, _viewContent as DiffContext);
} }
[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")] [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 @@ namespace SourceGit.ViewModels
{ {
Task.Run(() => Task.Run(() =>
{ {
var startFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _startPoint.SHA, _file).Result();
var endFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _endPoint.SHA, _file).Result();
var allChanges = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA).Result();
var startCommand = new Commands.QueryRevisionObjects(_repo.FullPath, _startPoint.SHA, startFilePath);
var startResult = startCommand.Result();
bool startFileExists = startResult.Count > 0;
var endCommand = new Commands.QueryRevisionObjects(_repo.FullPath, _endPoint.SHA, endFilePath);
var endResult = endCommand.Result();
bool endFileExists = endResult.Count > 0;
Models.Change renamedChange = null;
foreach (var change in allChanges)
{
if ((change.WorkTree & Models.ChangeState.Renamed) != 0 ||
(change.Index & Models.ChangeState.Renamed) != 0)
{
if (change.Path == endFilePath || change.OriginalPath == startFilePath)
{
renamedChange = change;
break;
}
}
}
bool hasChanges = false;
if (renamedChange != null)
{
if (string.IsNullOrEmpty(renamedChange.OriginalPath))
renamedChange.OriginalPath = startFilePath;
if (string.IsNullOrEmpty(renamedChange.Path))
renamedChange.Path = endFilePath;
bool hasContentChange = (!startFileExists || IsEmptyFile(_repo.FullPath, _startPoint.SHA, startFilePath)) &&
endFileExists && !IsEmptyFile(_repo.FullPath, _endPoint.SHA, endFilePath);
if (!hasContentChange)
hasContentChange = ContainsContentChanges(allChanges, startFilePath, endFilePath);
if (hasContentChange)
{
renamedChange.Index |= Models.ChangeState.Modified;
renamedChange.WorkTree |= Models.ChangeState.Modified;
}
_changes = [renamedChange];
hasChanges = true;
}
else if (startFilePath != endFilePath)
{
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, startFilePath).Result();
if (_changes.Count == 0)
{
var renamed = new Models.Change()
{
OriginalPath = startFilePath,
Path = endFilePath
};
bool hasContentChange = (!startFileExists || IsEmptyFile(_repo.FullPath, _startPoint.SHA, startFilePath)) &&
endFileExists && !IsEmptyFile(_repo.FullPath, _endPoint.SHA, endFilePath);
if (hasContentChange)
renamed.Set(Models.ChangeState.Modified | Models.ChangeState.Renamed);
else
renamed.Set(Models.ChangeState.Renamed);
_changes = [renamed];
hasChanges = true;
}
else
{
foreach (var change in _changes)
{
if (string.IsNullOrEmpty(change.OriginalPath) && change.Path == startFilePath)
{
change.OriginalPath = startFilePath;
change.Path = endFilePath;
change.Index |= Models.ChangeState.Renamed;
change.WorkTree |= Models.ChangeState.Renamed;
}
}
hasChanges = true;
}
}
if (!hasChanges)
{
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, endFilePath).Result();
if (_changes.Count == 0)
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).Result(); _changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).Result();
}
if (_changes.Count == 0) if (_changes.Count == 0)
{ {
Dispatcher.UIThread.Invoke(() => ViewContent = null); Dispatcher.UIThread.Invoke(() => ViewContent = null);
@ -228,6 +346,38 @@ namespace SourceGit.ViewModels
}); });
} }
private bool ContainsContentChanges(List<Models.Change> changes, string startPath, string endPath)
{
foreach (var change in changes)
{
if (change.Path == endPath || change.OriginalPath == startPath)
{
bool hasContentChanges =
(change.WorkTree == Models.ChangeState.Modified ||
change.WorkTree == Models.ChangeState.Added ||
change.Index == Models.ChangeState.Modified ||
change.Index == Models.ChangeState.Added);
if (hasContentChanges)
return true;
}
}
return false;
}
private bool IsEmptyFile(string repoPath, string revision, string filePath)
{
try
{
var contentStream = Commands.QueryFileContent.Run(repoPath, revision, filePath);
return contentStream != null && contentStream.Length == 0;
}
catch
{
return true;
}
}
private Repository _repo = null; private Repository _repo = null;
private string _file = null; private string _file = null;
private Models.Commit _startPoint = null; private Models.Commit _startPoint = null;
@ -270,7 +420,7 @@ namespace SourceGit.ViewModels
Task.Run(() => Task.Run(() =>
{ {
var based = commit ?? string.Empty; var based = commit ?? string.Empty;
var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {based} -- \"{file}\"", false).Result(); var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order --follow -n 10000 {based} -- \"{file}\"", false).Result();
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
IsLoading = false; IsLoading = false;

View file

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
@ -9,53 +9,73 @@ namespace SourceGit.Views
{ {
public class ChangeStatusIcon : Control public class ChangeStatusIcon : Control
{ {
private static readonly string[] INDICATOR = ["?", "±", "T", "+", "", "➜", "❏", "★", "!"]; private static readonly Dictionary<Models.ChangeState, string> INDICATOR = new Dictionary<Models.ChangeState, string>()
private static readonly IBrush[] BACKGROUNDS = [ {
Brushes.Transparent, { Models.ChangeState.None, "?" },
new LinearGradientBrush { Models.ChangeState.Modified, "±" },
{ Models.ChangeState.TypeChanged, "T" },
{ Models.ChangeState.Added, "+" },
{ Models.ChangeState.Deleted, "" },
{ Models.ChangeState.Renamed, "➜" },
{ Models.ChangeState.Copied, "❏" },
{ Models.ChangeState.Untracked, "★" },
{ Models.ChangeState.Conflicted, "!" }
};
private static readonly Dictionary<Models.ChangeState, IBrush> BACKGROUNDS = new Dictionary<Models.ChangeState, IBrush>()
{
{ Models.ChangeState.None, Brushes.Transparent },
{ Models.ChangeState.Modified, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) }, GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
new LinearGradientBrush { Models.ChangeState.TypeChanged, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) }, GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
new LinearGradientBrush { Models.ChangeState.Added, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) }, GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
new LinearGradientBrush { Models.ChangeState.Deleted, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) }, GradientStops = new GradientStops() { new GradientStop(Colors.Tomato, 0), new GradientStop(Color.FromRgb(252, 165, 150), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
new LinearGradientBrush { Models.ChangeState.Renamed, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) }, GradientStops = new GradientStops() { new GradientStop(Colors.Orchid, 0), new GradientStop(Color.FromRgb(248, 161, 245), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
new LinearGradientBrush { Models.ChangeState.Copied, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) }, GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(238, 160, 14), 0), new GradientStop(Color.FromRgb(228, 172, 67), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
new LinearGradientBrush { Models.ChangeState.Untracked, new LinearGradientBrush
{ {
GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) }, GradientStops = new GradientStops() { new GradientStop(Color.FromRgb(47, 185, 47), 0), new GradientStop(Color.FromRgb(75, 189, 75), 1) },
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative), EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative),
}
}, },
Brushes.OrangeRed, { Models.ChangeState.Conflicted, Brushes.OrangeRed },
]; };
public static readonly StyledProperty<bool> IsUnstagedChangeProperty = public static readonly StyledProperty<bool> IsUnstagedChangeProperty =
AvaloniaProperty.Register<ChangeStatusIcon, bool>(nameof(IsUnstagedChange)); AvaloniaProperty.Register<ChangeStatusIcon, bool>(nameof(IsUnstagedChange));
@ -86,13 +106,15 @@ namespace SourceGit.Views
string indicator; string indicator;
if (IsUnstagedChange) if (IsUnstagedChange)
{ {
background = BACKGROUNDS[(int)Change.WorkTree]; var status = Models.Change.GetPrimaryState(Change.WorkTree);
indicator = INDICATOR[(int)Change.WorkTree]; background = BACKGROUNDS[status];
indicator = INDICATOR[status];
} }
else else
{ {
background = BACKGROUNDS[(int)Change.Index]; var status = Models.Change.GetPrimaryState(Change.Index);
indicator = INDICATOR[(int)Change.Index]; background = BACKGROUNDS[status];
indicator = INDICATOR[status];
} }
var txt = new FormattedText( var txt = new FormattedText(