mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2025-01-11 23:57:21 -08:00
refactor: git version related commands
* use `--pathspec-from-file=<FILE>` in `git add` command if git >= 2.25.0 * use `--pathspec-from-file=<FILE>` in `git stash push` command if git >= 2.26.0 * use `--staged` in `git stash push` command only if git >= 2.35.0
This commit is contained in:
parent
c939308e4c
commit
b26838ff68
10 changed files with 306 additions and 103 deletions
|
@ -27,5 +27,12 @@ namespace SourceGit.Commands
|
|||
}
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
public Add(string repo, string pathspecFromFile)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"add --pathspec-from-file=\"{pathspecFromFile}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,47 +17,48 @@ namespace SourceGit.Commands
|
|||
return Exec();
|
||||
}
|
||||
|
||||
public bool Push(List<Models.Change> changes, string message, bool onlyStaged, bool keepIndex)
|
||||
public bool Push(string message, List<Models.Change> changes, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push ");
|
||||
if (onlyStaged)
|
||||
builder.Append("--staged ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\" -- ");
|
||||
|
||||
if (onlyStaged)
|
||||
{
|
||||
foreach (var c in changes)
|
||||
builder.Append($"\"{c.Path}\" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
var needAdd = new List<Models.Change>();
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append($"\"{c.Path}\" ");
|
||||
|
||||
if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked)
|
||||
{
|
||||
needAdd.Add(c);
|
||||
if (needAdd.Count > 10)
|
||||
{
|
||||
new Add(WorkingDirectory, needAdd).Exec();
|
||||
needAdd.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needAdd.Count > 0)
|
||||
{
|
||||
new Add(WorkingDirectory, needAdd).Exec();
|
||||
needAdd.Clear();
|
||||
}
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Push(string message, string pathspecFromFile, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push --pathspec-from-file=\"");
|
||||
builder.Append(pathspecFromFile);
|
||||
builder.Append("\" ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\"");
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool PushOnlyStaged(string message, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push --staged ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\"");
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Version : Command
|
||||
{
|
||||
public Version()
|
||||
{
|
||||
Args = "--version";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Query()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess || string.IsNullOrWhiteSpace(rs.StdOut))
|
||||
return string.Empty;
|
||||
return rs.StdOut.Trim().Substring("git version ".Length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static partial class StringConverters
|
||||
public static class StringConverters
|
||||
{
|
||||
public class ToLocaleConverter : IValueConverter
|
||||
{
|
||||
|
@ -68,22 +67,6 @@ namespace SourceGit.Converters
|
|||
public static readonly FuncValueConverter<string, string> ToShortSHA =
|
||||
new FuncValueConverter<string, string>(v => v == null ? string.Empty : (v.Length > 10 ? v.Substring(0, 10) : v));
|
||||
|
||||
public static readonly FuncValueConverter<string, bool> 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;
|
||||
});
|
||||
|
||||
public static readonly FuncValueConverter<string, string> TrimRefsPrefix =
|
||||
new FuncValueConverter<string, string>(v =>
|
||||
{
|
||||
|
@ -95,10 +78,5 @@ namespace SourceGit.Converters
|
|||
return v.Substring(13);
|
||||
return v;
|
||||
});
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
|
25
src/Models/GitVersions.cs
Normal file
25
src/Models/GitVersions.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public static class GitVersions
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimal version of Git that required by this app.
|
||||
/// </summary>
|
||||
public static readonly System.Version MINIMAL = new System.Version(2, 23, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `add` command with the `--pathspec-from-file` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version ADD_WITH_PATHSPECFILE = new System.Version(2, 25, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `stash` command with the `--pathspec-from-file` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version STASH_WITH_PATHSPECFILE = new System.Version(2, 26, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `stash` command with the `--staged` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version STASH_ONLY_STAGED = new System.Version(2, 35, 0);
|
||||
}
|
||||
}
|
107
src/Native/OS.cs
107
src/Native/OS.cs
|
@ -2,12 +2,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
namespace SourceGit.Native
|
||||
{
|
||||
public static class OS
|
||||
public static partial class OS
|
||||
{
|
||||
public interface IBackend
|
||||
{
|
||||
|
@ -23,11 +25,51 @@ namespace SourceGit.Native
|
|||
void OpenWithDefaultEditor(string file);
|
||||
}
|
||||
|
||||
public static string DataDir { get; private set; } = string.Empty;
|
||||
public static string GitExecutable { get; set; } = string.Empty;
|
||||
public static string ShellOrTerminal { get; set; } = string.Empty;
|
||||
public static List<Models.ExternalTool> ExternalTools { get; set; } = [];
|
||||
public static string CustomPathEnv { get; set; } = string.Empty;
|
||||
public static string DataDir {
|
||||
get;
|
||||
private set;
|
||||
} = string.Empty;
|
||||
|
||||
public static string CustomPathEnv
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = string.Empty;
|
||||
|
||||
public static string GitExecutable
|
||||
{
|
||||
get => _gitExecutable;
|
||||
set
|
||||
{
|
||||
if (_gitExecutable != value)
|
||||
{
|
||||
_gitExecutable = value;
|
||||
UpdateGitVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GitVersionString
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = string.Empty;
|
||||
|
||||
public static Version GitVersion
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new Version(0, 0, 0);
|
||||
|
||||
public static string ShellOrTerminal {
|
||||
get;
|
||||
set;
|
||||
} = string.Empty;
|
||||
|
||||
public static List<Models.ExternalTool> ExternalTools {
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
static OS()
|
||||
{
|
||||
|
@ -123,6 +165,59 @@ namespace SourceGit.Native
|
|||
_backend.OpenWithDefaultEditor(file);
|
||||
}
|
||||
|
||||
private static void UpdateGitVersion()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_gitExecutable) || !File.Exists(_gitExecutable))
|
||||
{
|
||||
GitVersionString = string.Empty;
|
||||
GitVersion = new Version(0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = _gitExecutable;
|
||||
start.Arguments = "--version";
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
|
||||
var rs = proc.StandardOutput.ReadToEnd();
|
||||
proc.WaitForExit();
|
||||
if (proc.ExitCode == 0 && !string.IsNullOrWhiteSpace(rs))
|
||||
{
|
||||
GitVersionString = rs.Trim();
|
||||
|
||||
var match = REG_GIT_VERSION().Match(GitVersionString);
|
||||
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);
|
||||
GitVersion = new Version(major, minor, build);
|
||||
GitVersionString = GitVersionString.Substring(11).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
proc.Close();
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^git version[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")]
|
||||
private static partial Regex REG_GIT_VERSION();
|
||||
|
||||
private static IBackend _backend = null;
|
||||
private static string _gitExecutable = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
|
@ -45,37 +47,122 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public override Task<bool> Sure()
|
||||
{
|
||||
var jobs = _changes;
|
||||
if (!HasSelectedFiles && !IncludeUntracked)
|
||||
{
|
||||
jobs = new List<Models.Change>();
|
||||
foreach (var job in _changes)
|
||||
{
|
||||
if (job.WorkTree != Models.ChangeState.Untracked && job.WorkTree != Models.ChangeState.Added)
|
||||
{
|
||||
jobs.Add(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (jobs.Count == 0)
|
||||
return null;
|
||||
|
||||
_repo.SetWatcherEnabled(false);
|
||||
ProgressDescription = $"Stash changes ...";
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var succ = new Commands.Stash(_repo.FullPath).Push(jobs, Message, !HasSelectedFiles && OnlyStaged, KeepIndex);
|
||||
var succ = false;
|
||||
|
||||
if (!HasSelectedFiles)
|
||||
{
|
||||
if (OnlyStaged)
|
||||
{
|
||||
if (Native.OS.GitVersion >= Models.GitVersions.STASH_ONLY_STAGED)
|
||||
{
|
||||
succ = new Commands.Stash(_repo.FullPath).PushOnlyStaged(Message, KeepIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
var staged = new List<Models.Change>();
|
||||
foreach (var c in _changes)
|
||||
{
|
||||
if (c.Index != Models.ChangeState.None && c.Index != Models.ChangeState.Untracked)
|
||||
staged.Add(c);
|
||||
}
|
||||
|
||||
succ = StashWithChanges(staged);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IncludeUntracked)
|
||||
AddUntracked(_changes);
|
||||
succ = StashWithChanges(_changes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddUntracked(_changes);
|
||||
succ = StashWithChanges(_changes);
|
||||
}
|
||||
|
||||
CallUIThread(() =>
|
||||
{
|
||||
_repo.MarkWorkingCopyDirtyManually();
|
||||
_repo.SetWatcherEnabled(true);
|
||||
});
|
||||
|
||||
return succ;
|
||||
});
|
||||
}
|
||||
|
||||
private void AddUntracked(List<Models.Change> changes)
|
||||
{
|
||||
var toBeAdded = new List<Models.Change>();
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked)
|
||||
toBeAdded.Add(c);
|
||||
}
|
||||
|
||||
if (toBeAdded.Count == 0)
|
||||
return;
|
||||
|
||||
if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE)
|
||||
{
|
||||
var paths = new List<string>();
|
||||
foreach (var c in toBeAdded)
|
||||
paths.Add(c.Path);
|
||||
|
||||
var tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(tmpFile, paths);
|
||||
new Commands.Add(_repo.FullPath, tmpFile).Exec();
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < toBeAdded.Count; i += 10)
|
||||
{
|
||||
var count = Math.Min(10, toBeAdded.Count - i);
|
||||
var step = toBeAdded.GetRange(i, count);
|
||||
new Commands.Add(_repo.FullPath, step).Exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool StashWithChanges(List<Models.Change> changes)
|
||||
{
|
||||
if (changes.Count == 0)
|
||||
return true;
|
||||
|
||||
var succ = false;
|
||||
if (Native.OS.GitVersion >= Models.GitVersions.STASH_WITH_PATHSPECFILE)
|
||||
{
|
||||
var paths = new List<string>();
|
||||
foreach (var c in changes)
|
||||
paths.Add(c.Path);
|
||||
|
||||
var tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(tmpFile, paths);
|
||||
succ = new Commands.Stash(_repo.FullPath).Push(Message, tmpFile, KeepIndex);
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < changes.Count; i += 10)
|
||||
{
|
||||
var count = Math.Min(10, changes.Count - i);
|
||||
var step = changes.GetRange(i, count);
|
||||
succ = new Commands.Stash(_repo.FullPath).Push(Message, step, KeepIndex);
|
||||
if (!succ)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return succ;
|
||||
}
|
||||
|
||||
private readonly Repository _repo = null;
|
||||
private readonly List<Models.Change> _changes = null;
|
||||
}
|
||||
|
|
|
@ -347,6 +347,17 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec());
|
||||
}
|
||||
else if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE)
|
||||
{
|
||||
var paths = new List<string>();
|
||||
foreach (var c in changes)
|
||||
paths.Add(c.Path);
|
||||
|
||||
var tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(tmpFile, paths);
|
||||
await Task.Run(() => new Commands.Add(_repo.FullPath, tmpFile).Exec());
|
||||
File.Delete(tmpFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < changes.Count; i += 10)
|
||||
|
|
|
@ -263,7 +263,8 @@
|
|||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Height="28"
|
||||
CornerRadius="3"
|
||||
Text="{Binding GitInstallPath, Mode=TwoWay}">
|
||||
Text="{Binding GitInstallPath, Mode=TwoWay}"
|
||||
TextChanged="OnGitInstallPathChanged">
|
||||
<TextBox.InnerRightContent>
|
||||
<Button Classes="icon_button" Width="30" Height="30" Click="SelectGitExecutable">
|
||||
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
|
||||
|
@ -282,7 +283,7 @@
|
|||
|
||||
<Border Background="Transparent"
|
||||
ToolTip.Tip="{DynamicResource Text.Preference.Git.Invalid}"
|
||||
IsVisible="{Binding #ThisControl.GitVersion, Converter={x:Static c:StringConverters.UnderRecommendGitVersion}}">
|
||||
IsVisible="{Binding #ThisControl.ShowGitVersionWarning}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Error}" Fill="Red"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
|
|
@ -37,6 +37,15 @@ namespace SourceGit.Views
|
|||
set => SetValue(GitVersionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> ShowGitVersionWarningProperty =
|
||||
AvaloniaProperty.Register<Preference, bool>(nameof(ShowGitVersionWarning));
|
||||
|
||||
public bool ShowGitVersionWarning
|
||||
{
|
||||
get => GetValue(ShowGitVersionWarningProperty);
|
||||
set => SetValue(ShowGitVersionWarningProperty, value);
|
||||
}
|
||||
|
||||
public bool EnableGPGCommitSigning
|
||||
{
|
||||
get;
|
||||
|
@ -93,7 +102,6 @@ namespace SourceGit.Views
|
|||
var pref = ViewModels.Preference.Instance;
|
||||
DataContext = pref;
|
||||
|
||||
var ver = string.Empty;
|
||||
if (pref.IsGitConfigured())
|
||||
{
|
||||
var config = new Commands.Config(null).ListAll();
|
||||
|
@ -122,12 +130,10 @@ namespace SourceGit.Views
|
|||
EnableHTTPSSLVerify = sslVerify == "true";
|
||||
else
|
||||
EnableHTTPSSLVerify = true;
|
||||
|
||||
ver = new Commands.Version().Query();
|
||||
}
|
||||
|
||||
UpdateGitVersion();
|
||||
InitializeComponent();
|
||||
GitVersion = ver;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
|
@ -207,7 +213,7 @@ namespace SourceGit.Views
|
|||
if (selected.Count == 1)
|
||||
{
|
||||
ViewModels.Preference.Instance.GitInstallPath = selected[0].Path.LocalPath;
|
||||
GitVersion = new Commands.Version().Query();
|
||||
UpdateGitVersion();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
|
@ -328,6 +334,11 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGitInstallPathChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
UpdateGitVersion();
|
||||
}
|
||||
|
||||
private void OnAddOpenAIService(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var service = new Models.OpenAIService() { Name = "Unnamed Service" };
|
||||
|
@ -346,5 +357,11 @@ namespace SourceGit.Views
|
|||
SelectedOpenAIService = null;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateGitVersion()
|
||||
{
|
||||
GitVersion = Native.OS.GitVersionString;
|
||||
ShowGitVersionWarning = !string.IsNullOrEmpty(GitVersion) && Native.OS.GitVersion < Models.GitVersions.MINIMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue