diff --git a/VERSION b/VERSION
index b9d71048..d9316e8b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.14
\ No newline at end of file
+8.15
\ No newline at end of file
diff --git a/src/App.axaml b/src/App.axaml
index 7129fd7e..fff2ca0d 100644
--- a/src/App.axaml
+++ b/src/App.axaml
@@ -19,6 +19,7 @@
+
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index 151c6e92..df491dd0 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -62,9 +62,7 @@ namespace SourceGit
builder.Append(ex.StackTrace);
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
- var file = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "SourceGit",
- $"crash_{time}.log");
+ var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
File.WriteAllText(file, builder.ToString());
}
}
diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs
index 618ff014..6788884c 100644
--- a/src/Commands/QueryCommits.cs
+++ b/src/Commands/QueryCommits.cs
@@ -5,8 +5,8 @@ namespace SourceGit.Commands
{
public class QueryCommits : Command
{
- private const string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
- private const string GPGSIG_END = " -----END PGP SIGNATURE-----";
+ private const string GPGSIG_START = "gpgsig -----BEGIN ";
+ private const string GPGSIG_END = " -----END ";
private readonly List commits = new List();
private Models.Commit current = null;
@@ -17,6 +17,7 @@ namespace SourceGit.Commands
public QueryCommits(string repo, string limits, bool needFindHead = true)
{
WorkingDirectory = repo;
+ Context = repo;
Args = "log --date-order --decorate=full --pretty=raw " + limits;
findFirstMerged = needFindHead;
}
diff --git a/src/Commands/QuerySingleCommit.cs b/src/Commands/QuerySingleCommit.cs
new file mode 100644
index 00000000..5c0fd760
--- /dev/null
+++ b/src/Commands/QuerySingleCommit.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections.Generic;
+
+namespace SourceGit.Commands
+{
+ public class QuerySingleCommit : Command
+ {
+ private const string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
+ private const string GPGSIG_END = " -----END PGP SIGNATURE-----";
+
+ public QuerySingleCommit(string repo, string sha) {
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"show --pretty=raw --decorate=full -s {sha}";
+ }
+
+ public Models.Commit Result()
+ {
+ var succ = Exec();
+ if (!succ)
+ return null;
+
+ _commit.Message.Trim();
+ return _commit;
+ }
+
+ protected override void OnReadline(string line)
+ {
+ if (isSkipingGpgsig)
+ {
+ if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal))
+ isSkipingGpgsig = false;
+ return;
+ }
+ else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal))
+ {
+ isSkipingGpgsig = true;
+ return;
+ }
+
+ if (line.StartsWith("commit ", StringComparison.Ordinal))
+ {
+ line = line.Substring(7);
+
+ var decoratorStart = line.IndexOf('(', StringComparison.Ordinal);
+ if (decoratorStart < 0)
+ {
+ _commit.SHA = line.Trim();
+ }
+ else
+ {
+ _commit.SHA = line.Substring(0, decoratorStart).Trim();
+ ParseDecorators(_commit.Decorators, line.Substring(decoratorStart + 1));
+ }
+
+ return;
+ }
+
+ if (line.StartsWith("tree ", StringComparison.Ordinal))
+ {
+ return;
+ }
+ else if (line.StartsWith("parent ", StringComparison.Ordinal))
+ {
+ _commit.Parents.Add(line.Substring("parent ".Length));
+ }
+ else if (line.StartsWith("author ", StringComparison.Ordinal))
+ {
+ Models.User user = Models.User.Invalid;
+ ulong time = 0;
+ Models.Commit.ParseUserAndTime(line.Substring(7), ref user, ref time);
+ _commit.Author = user;
+ _commit.AuthorTime = time;
+ }
+ else if (line.StartsWith("committer ", StringComparison.Ordinal))
+ {
+ Models.User user = Models.User.Invalid;
+ ulong time = 0;
+ Models.Commit.ParseUserAndTime(line.Substring(10), ref user, ref time);
+ _commit.Committer = user;
+ _commit.CommitterTime = time;
+ }
+ else if (string.IsNullOrEmpty(_commit.Subject))
+ {
+ _commit.Subject = line.Trim();
+ }
+ else
+ {
+ _commit.Message += (line.Trim() + "\n");
+ }
+ }
+
+ private bool ParseDecorators(List decorators, string data)
+ {
+ bool isHeadOfCurrent = false;
+
+ var subs = data.Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var sub in subs)
+ {
+ var d = sub.Trim();
+ if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal))
+ {
+ decorators.Add(new Models.Decorator()
+ {
+ Type = Models.DecoratorType.Tag,
+ Name = d.Substring(15).Trim(),
+ });
+ }
+ else if (d.EndsWith("/HEAD", StringComparison.Ordinal))
+ {
+ continue;
+ }
+ else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal))
+ {
+ isHeadOfCurrent = true;
+ decorators.Add(new Models.Decorator()
+ {
+ Type = Models.DecoratorType.CurrentBranchHead,
+ Name = d.Substring(19).Trim(),
+ });
+ }
+ else if (d.Equals("HEAD"))
+ {
+ isHeadOfCurrent = true;
+ decorators.Add(new Models.Decorator()
+ {
+ Type = Models.DecoratorType.CurrentCommitHead,
+ Name = d.Trim(),
+ });
+ }
+ else if (d.StartsWith("refs/heads/", StringComparison.Ordinal))
+ {
+ decorators.Add(new Models.Decorator()
+ {
+ Type = Models.DecoratorType.LocalBranchHead,
+ Name = d.Substring(11).Trim(),
+ });
+ }
+ else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal))
+ {
+ decorators.Add(new Models.Decorator()
+ {
+ Type = Models.DecoratorType.RemoteBranchHead,
+ Name = d.Substring(13).Trim(),
+ });
+ }
+ }
+
+ decorators.Sort((l, r) =>
+ {
+ if (l.Type != r.Type)
+ {
+ return (int)l.Type - (int)r.Type;
+ }
+ else
+ {
+ return l.Name.CompareTo(r.Name);
+ }
+ });
+
+ return isHeadOfCurrent;
+ }
+
+ private Models.Commit _commit = new Models.Commit();
+ private bool isSkipingGpgsig = false;
+ }
+}
diff --git a/src/Converters/BoolConverters.cs b/src/Converters/BoolConverters.cs
index c860a1a6..2eb8c60a 100644
--- a/src/Converters/BoolConverters.cs
+++ b/src/Converters/BoolConverters.cs
@@ -1,18 +1,17 @@
-using Avalonia.Controls;
-using Avalonia.Data.Converters;
+using Avalonia.Data.Converters;
using Avalonia.Media;
namespace SourceGit.Converters
{
public static class BoolConverters
{
+ public static readonly FuncValueConverter ToPageTabWidth =
+ new FuncValueConverter(x => x ? 200 : double.NaN);
+
public static readonly FuncValueConverter HalfIfFalse =
new FuncValueConverter(x => x ? 1 : 0.5);
public static readonly FuncValueConverter BoldIfTrue =
new FuncValueConverter(x => x ? FontWeight.Bold : FontWeight.Regular);
-
- public static readonly FuncValueConverter ToStarOrAutoGridLength =
- new(value => value ? new GridLength(1, GridUnitType.Star) : new GridLength(1, GridUnitType.Auto));
}
}
diff --git a/src/Converters/StringConverters.cs b/src/Converters/StringConverters.cs
index f743f69f..aa687f23 100644
--- a/src/Converters/StringConverters.cs
+++ b/src/Converters/StringConverters.cs
@@ -1,12 +1,13 @@
using System;
using System.Globalization;
+using System.Text.RegularExpressions;
using Avalonia.Data.Converters;
using Avalonia.Styling;
namespace SourceGit.Converters
{
- public static class StringConverters
+ public static partial class StringConverters
{
public class ToLocaleConverter : IValueConverter
{
@@ -68,6 +69,27 @@ namespace SourceGit.Converters
public static readonly FormatByResourceKeyConverter FormatByResourceKey = new FormatByResourceKeyConverter();
public static readonly FuncValueConverter ToShortSHA =
- new FuncValueConverter(v => v.Length > 10 ? v.Substring(0, 10) : v);
+ new FuncValueConverter(v => v == null ? string.Empty : (v.Length > 10 ? v.Substring(0, 10) : v));
+
+ public static readonly FuncValueConverter UnderRecommendGitVersion =
+ new(v =>
+ {
+ var match = REG_GIT_VERSION().Match(v ?? "");
+ if (match.Success)
+ {
+ var major = int.Parse(match.Groups[1].Value);
+ var minor = int.Parse(match.Groups[2].Value);
+ var build = int.Parse(match.Groups[3].Value);
+
+ return new Version(major, minor, build) < MINIMAL_GIT_VERSION;
+ }
+
+ return true;
+ });
+
+ [GeneratedRegex(@"^[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")]
+ private static partial Regex REG_GIT_VERSION();
+
+ private static readonly Version MINIMAL_GIT_VERSION = new Version(2, 23, 0);
}
}
diff --git a/src/Converters/WindowStateConverters.cs b/src/Converters/WindowStateConverters.cs
index 2c3b2ac6..7122dc1f 100644
--- a/src/Converters/WindowStateConverters.cs
+++ b/src/Converters/WindowStateConverters.cs
@@ -3,7 +3,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data.Converters;
-using Avalonia.Media;
namespace SourceGit.Converters
{
@@ -39,19 +38,6 @@ namespace SourceGit.Converters
}
});
- public static readonly FuncValueConverter ToMaxOrRestoreIcon =
- new FuncValueConverter(state =>
- {
- if (state == WindowState.Maximized)
- {
- return Application.Current?.FindResource("Icons.Window.Restore") as StreamGeometry;
- }
- else
- {
- return Application.Current?.FindResource("Icons.Window.Maximize") as StreamGeometry;
- }
- });
-
public static readonly FuncValueConverter IsNormal =
new FuncValueConverter(state => state == WindowState.Normal);
}
diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs
index fded94e3..50d890e1 100644
--- a/src/Models/AvatarManager.cs
+++ b/src/Models/AvatarManager.cs
@@ -25,7 +25,7 @@ namespace SourceGit.Models
static AvatarManager()
{
- _storePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SourceGit", "avatars");
+ _storePath = Path.Combine(Native.OS.DataDir, "avatars");
if (!Directory.Exists(_storePath))
Directory.CreateDirectory(_storePath);
diff --git a/src/Models/DealWithLocalChanges.cs b/src/Models/DealWithLocalChanges.cs
new file mode 100644
index 00000000..82609642
--- /dev/null
+++ b/src/Models/DealWithLocalChanges.cs
@@ -0,0 +1,9 @@
+namespace SourceGit.Models
+{
+ public enum DealWithLocalChanges
+ {
+ StashAndReaply,
+ Discard,
+ DoNothing,
+ }
+}
diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs
index 8a72b35d..d9d21031 100644
--- a/src/Models/DiffResult.cs
+++ b/src/Models/DiffResult.cs
@@ -582,6 +582,12 @@ namespace SourceGit.Models
public string New { get; set; } = string.Empty;
}
+ public class SubmoduleDiff
+ {
+ public Commit Old { get; set; } = null;
+ public Commit New { get; set; } = null;
+ }
+
public class DiffResult
{
public bool IsBinary { get; set; } = false;
diff --git a/src/ViewModels/FileTreeNode.cs b/src/Models/FileTreeNode.cs
similarity index 83%
rename from src/ViewModels/FileTreeNode.cs
rename to src/Models/FileTreeNode.cs
index ca6d850f..ad1298c9 100644
--- a/src/ViewModels/FileTreeNode.cs
+++ b/src/Models/FileTreeNode.cs
@@ -1,28 +1,20 @@
using System;
using System.Collections.Generic;
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace SourceGit.ViewModels
+namespace SourceGit.Models
{
- public class FileTreeNode : ObservableObject
+ public class FileTreeNode
{
public string FullPath { get; set; } = string.Empty;
public bool IsFolder { get; set; } = false;
+ public bool IsExpanded { get; set; } = false;
public object Backend { get; set; } = null;
public List Children { get; set; } = new List();
- public bool IsExpanded
- {
- get => _isExpanded;
- set => SetProperty(ref _isExpanded, value);
- }
-
- public static List Build(List changes)
+ public static List Build(List changes, bool expanded)
{
var nodes = new List();
var folders = new Dictionary();
- var expanded = changes.Count <= 50;
foreach (var c in changes)
{
@@ -94,11 +86,10 @@ namespace SourceGit.ViewModels
return nodes;
}
- public static List Build(List files)
+ public static List Build(List