mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-21 02:15:00 +00:00
fix: file history comparison with empty and renamed revisions
This commit is contained in:
parent
70ffe9ac71
commit
a418880656
3 changed files with 222 additions and 77 deletions
|
@ -9,17 +9,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
|
||||||
|
@ -81,5 +82,29 @@ namespace SourceGit.Models
|
||||||
if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"')
|
if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"')
|
||||||
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
|
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,47 +238,102 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
var startFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _startPoint.SHA, _file).Result();
|
var startFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _startPoint.SHA, _file).Result();
|
||||||
var endFilePath = new Commands.QueryFilePathInRevision(_repo.FullPath, _endPoint.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 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;
|
Models.Change renamedChange = null;
|
||||||
foreach (var change in allChanges)
|
foreach (var change in allChanges)
|
||||||
{
|
{
|
||||||
if (change.WorkTree != Models.ChangeState.Renamed && change.Index != Models.ChangeState.Renamed)
|
if ((change.WorkTree & Models.ChangeState.Renamed) != 0 ||
|
||||||
continue;
|
(change.Index & Models.ChangeState.Renamed) != 0)
|
||||||
if (change.Path != endFilePath && change.OriginalPath != startFilePath)
|
{
|
||||||
continue;
|
if (change.Path == endFilePath || change.OriginalPath == startFilePath)
|
||||||
|
{
|
||||||
renamedChange = change;
|
renamedChange = change;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasChanges = false;
|
||||||
|
|
||||||
if (renamedChange != null)
|
if (renamedChange != null)
|
||||||
{
|
{
|
||||||
_changes = [renamedChange];
|
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;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
_changes = [renamedChange];
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
else if (startFilePath != endFilePath)
|
||||||
{
|
{
|
||||||
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, startFilePath).Result();
|
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, startFilePath).Result();
|
||||||
|
|
||||||
if (_changes.Count == 0 && startFilePath != endFilePath)
|
if (_changes.Count == 0)
|
||||||
{
|
{
|
||||||
var renamed = new Models.Change()
|
var renamed = new Models.Change()
|
||||||
{
|
{
|
||||||
OriginalPath = startFilePath,
|
OriginalPath = startFilePath,
|
||||||
Path = endFilePath
|
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);
|
renamed.Set(Models.ChangeState.Renamed);
|
||||||
|
|
||||||
_changes = [renamed];
|
_changes = [renamed];
|
||||||
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
else if (_changes.Count == 0)
|
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();
|
_changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, endFilePath).Result();
|
||||||
|
|
||||||
if (_changes.Count == 0)
|
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)
|
||||||
{
|
{
|
||||||
|
@ -291,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;
|
||||||
|
|
|
@ -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,55 +9,86 @@ namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
public class ChangeStatusIcon : Control
|
public class ChangeStatusIcon : Control
|
||||||
{
|
{
|
||||||
private static readonly IBrush[] BACKGROUNDS = [
|
private static readonly Dictionary<Models.ChangeState, IBrush> BACKGROUNDS = new Dictionary<Models.ChangeState, IBrush>()
|
||||||
Brushes.Transparent,
|
{
|
||||||
new LinearGradientBrush
|
{ 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 },
|
||||||
];
|
};
|
||||||
|
|
||||||
private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "★", "!"];
|
private static readonly Dictionary<Models.ChangeState, string> INDICATOR = new Dictionary<Models.ChangeState, string>()
|
||||||
private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Untracked", "Conflict"];
|
{
|
||||||
|
{ Models.ChangeState.None, "?" },
|
||||||
|
{ 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, string> TIPS = new Dictionary<Models.ChangeState, string>()
|
||||||
|
{
|
||||||
|
{ Models.ChangeState.None, "Unknown" },
|
||||||
|
{ Models.ChangeState.Modified, "Modified" },
|
||||||
|
{ Models.ChangeState.TypeChanged, "Type Changed" },
|
||||||
|
{ Models.ChangeState.Added, "Added" },
|
||||||
|
{ Models.ChangeState.Deleted, "Deleted" },
|
||||||
|
{ Models.ChangeState.Renamed, "Renamed" },
|
||||||
|
{ Models.ChangeState.Copied, "Copied" },
|
||||||
|
{ Models.ChangeState.Untracked, "Untracked" },
|
||||||
|
{ Models.ChangeState.Conflicted, "Conflict" }
|
||||||
|
};
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsUnstagedChangeProperty =
|
public static readonly StyledProperty<bool> IsUnstagedChangeProperty =
|
||||||
AvaloniaProperty.Register<ChangeStatusIcon, bool>(nameof(IsUnstagedChange));
|
AvaloniaProperty.Register<ChangeStatusIcon, bool>(nameof(IsUnstagedChange));
|
||||||
|
@ -88,13 +119,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(
|
||||||
|
@ -125,11 +158,11 @@ namespace SourceGit.Views
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUnstaged)
|
var status = isUnstaged ?
|
||||||
ToolTip.SetTip(this, TIPS[(int)c.WorkTree]);
|
Models.Change.GetPrimaryState(c.WorkTree) :
|
||||||
else
|
Models.Change.GetPrimaryState(c.Index);
|
||||||
ToolTip.SetTip(this, TIPS[(int)c.Index]);
|
|
||||||
|
|
||||||
|
ToolTip.SetTip(this, TIPS[status]);
|
||||||
InvalidateVisual();
|
InvalidateVisual();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue