diff --git a/README.md b/README.md
index e922c476..67ee5258 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@
## Translation Status
-[](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md)
+[](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md)
> [!NOTE]
> You can find the missing keys in [TRANSLATION.md](TRANSLATION.md)
@@ -136,11 +136,11 @@ This software supports using OpenAI or other AI service that has an OpenAI comap
For `OpenAI`:
-* `Server` must be `https://api.openai.com/v1/chat/completions`
+* `Server` must be `https://api.openai.com/v1`
For other AI service:
-* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1/chat/completions`. For example, when using `Ollama`, it should be `http://localhost:11434/v1/chat/completions` instead of `http://localhost:11434/api/generate`
+* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1`. For example, when using `Ollama`, it should be `http://localhost:11434/v1` instead of `http://localhost:11434/api/generate`
* The `API Key` is optional that depends on the service
## External Tools
diff --git a/TRANSLATION.md b/TRANSLATION.md
index aa42d82b..9f7a1c80 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -1,29 +1,66 @@
-### de_DE.axaml: 100.00%
+### de_DE.axaml: 98.13%
Missing Keys
-
+- Text.AIAssistant.Regen
+- Text.AIAssistant.Use
+- Text.ApplyStash
+- Text.ApplyStash.DropAfterApply
+- Text.ApplyStash.RestoreIndex
+- Text.ApplyStash.Stash
+- Text.Clone.RecurseSubmodules
+- Text.CreateBranch.Name.WarnSpace
+- Text.DeleteRepositoryNode.Path
+- Text.DeleteRepositoryNode.TipForGroup
+- Text.DeleteRepositoryNode.TipForRepository
+- Text.Stash.AutoRestore
+- Text.Stash.AutoRestore.Tip
+- Text.WorkingCopy.SignOff
-### es_ES.axaml: 100.00%
+### es_ES.axaml: 98.13%
Missing Keys
-
+- Text.AIAssistant.Regen
+- Text.AIAssistant.Use
+- Text.ApplyStash
+- Text.ApplyStash.DropAfterApply
+- Text.ApplyStash.RestoreIndex
+- Text.ApplyStash.Stash
+- Text.Clone.RecurseSubmodules
+- Text.CreateBranch.Name.WarnSpace
+- Text.DeleteRepositoryNode.Path
+- Text.DeleteRepositoryNode.TipForGroup
+- Text.DeleteRepositoryNode.TipForRepository
+- Text.Stash.AutoRestore
+- Text.Stash.AutoRestore.Tip
+- Text.WorkingCopy.SignOff
-### fr_FR.axaml: 94.69%
+### fr_FR.axaml: 92.91%
Missing Keys
+- Text.AIAssistant.Regen
+- Text.AIAssistant.Use
+- Text.ApplyStash
+- Text.ApplyStash.DropAfterApply
+- Text.ApplyStash.RestoreIndex
+- Text.ApplyStash.Stash
+- Text.Clone.RecurseSubmodules
+- Text.CreateBranch.Name.WarnSpace
+- Text.DeleteRepositoryNode.Path
+- Text.DeleteRepositoryNode.TipForGroup
+- Text.DeleteRepositoryNode.TipForRepository
- Text.InProgress.CherryPick.Head
- Text.InProgress.Merge.Operating
- Text.InProgress.Rebase.StoppedAt
@@ -62,81 +99,58 @@
- Text.SetUpstream.Unset
- Text.SetUpstream.Upstream
- Text.SHALinkCM.NavigateTo
+- Text.Stash.AutoRestore
+- Text.Stash.AutoRestore.Tip
- Text.WorkingCopy.CommitToEdit
+- Text.WorkingCopy.SignOff
-### it_IT.axaml: 93.32%
-
-
-
-Missing Keys
-
-- Text.BranchCM.MergeMultiBranches
-- Text.CommitCM.Merge
-- Text.CommitCM.MergeMultiple
-- Text.CommitDetail.Files.Search
-- Text.CommitDetail.Info.Children
-- Text.Configure.IssueTracker.AddSampleGiteeIssue
-- Text.Configure.IssueTracker.AddSampleGiteePullRequest
-- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
-- Text.Configure.OpenAI.Preferred
-- Text.Configure.OpenAI.Preferred.Tip
-- Text.Diff.UseBlockNavigation
-- Text.Fetch.Force
-- Text.FileCM.ResolveUsing
-- Text.InProgress.CherryPick.Head
-- Text.InProgress.Merge.Operating
-- Text.InProgress.Rebase.StoppedAt
-- Text.InProgress.Revert.Head
-- Text.Merge.Source
-- Text.MergeMultiple
-- Text.MergeMultiple.CommitChanges
-- Text.MergeMultiple.Strategy
-- Text.MergeMultiple.Targets
-- Text.Preferences.General.DateFormat
-- Text.Preferences.General.ShowChildren
-- Text.Preferences.Git.SSLVerify
-- Text.Repository.FilterCommits
-- Text.Repository.FilterCommits.Default
-- Text.Repository.FilterCommits.Exclude
-- Text.Repository.FilterCommits.Include
-- Text.Repository.HistoriesLayout
-- Text.Repository.HistoriesLayout.Horizontal
-- Text.Repository.HistoriesLayout.Vertical
-- Text.Repository.HistoriesOrder
-- Text.Repository.HistoriesOrder.ByDate
-- Text.Repository.HistoriesOrder.Topo
-- Text.Repository.OnlyHighlightCurrentBranchInHistories
-- Text.Repository.Skip
-- Text.Repository.Tags.OrderByCreatorDate
-- Text.Repository.Tags.OrderByNameAsc
-- Text.Repository.Tags.OrderByNameDes
-- Text.Repository.Tags.Sort
-- Text.Repository.UseRelativeTimeInHistories
-- Text.SetUpstream
-- Text.SetUpstream.Local
-- Text.SetUpstream.Unset
-- Text.SetUpstream.Upstream
-- Text.SHALinkCM.CopySHA
-- Text.SHALinkCM.NavigateTo
-- Text.WorkingCopy.CommitToEdit
-
-
-
-### pt_BR.axaml: 94.41%
+### it_IT.axaml: 98.40%
Missing Keys
+- Text.AIAssistant.Regen
+- Text.AIAssistant.Use
+- Text.ApplyStash
+- Text.ApplyStash.DropAfterApply
+- Text.ApplyStash.RestoreIndex
+- Text.ApplyStash.Stash
+- Text.Clone.RecurseSubmodules
+- Text.DeleteRepositoryNode.Path
+- Text.DeleteRepositoryNode.TipForGroup
+- Text.DeleteRepositoryNode.TipForRepository
+- Text.Stash.AutoRestore
+- Text.Stash.AutoRestore.Tip
+
+
+
+### pt_BR.axaml: 92.65%
+
+
+
+Missing Keys
+
+- Text.AIAssistant.Regen
+- Text.AIAssistant.Use
+- Text.ApplyStash
+- Text.ApplyStash.DropAfterApply
+- Text.ApplyStash.RestoreIndex
+- Text.ApplyStash.Stash
- Text.BranchCM.MergeMultiBranches
+- Text.Clone.RecurseSubmodules
- Text.CommitCM.Merge
- Text.CommitCM.MergeMultiple
- Text.CommitDetail.Files.Search
- Text.CommitDetail.Info.Children
- Text.Configure.IssueTracker.AddSampleGiteeIssue
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
+- Text.CreateBranch.Name.WarnSpace
+- Text.DeleteRepositoryNode.Path
+- Text.DeleteRepositoryNode.TipForGroup
+- Text.DeleteRepositoryNode.TipForRepository
- Text.Diff.UseBlockNavigation
- Text.Fetch.Force
- Text.FileCM.ResolveUsing
@@ -170,17 +184,33 @@
- Text.SetUpstream.Unset
- Text.SetUpstream.Upstream
- Text.SHALinkCM.NavigateTo
+- Text.Stash.AutoRestore
+- Text.Stash.AutoRestore.Tip
- Text.WorkingCopy.CommitToEdit
+- Text.WorkingCopy.SignOff
-### ru_RU.axaml: 100.00%
+### ru_RU.axaml: 98.13%
Missing Keys
-
+- Text.AIAssistant.Regen
+- Text.AIAssistant.Use
+- Text.ApplyStash
+- Text.ApplyStash.DropAfterApply
+- Text.ApplyStash.RestoreIndex
+- Text.ApplyStash.Stash
+- Text.Clone.RecurseSubmodules
+- Text.CreateBranch.Name.WarnSpace
+- Text.DeleteRepositoryNode.Path
+- Text.DeleteRepositoryNode.TipForGroup
+- Text.DeleteRepositoryNode.TipForRepository
+- Text.Stash.AutoRestore
+- Text.Stash.AutoRestore.Tip
+- Text.WorkingCopy.SignOff
diff --git a/VERSION b/VERSION
index d5cfddc3..4ee298df 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2025.03
\ No newline at end of file
+2025.04
\ No newline at end of file
diff --git a/build/scripts/localization-check.js b/build/scripts/localization-check.js
index 45db82be..ed89a5e8 100644
--- a/build/scripts/localization-check.js
+++ b/build/scripts/localization-check.js
@@ -25,7 +25,7 @@ async function calculateTranslationRate() {
const files = (await fs.readdir(localesDir)).filter(file => file !== 'en_US.axaml' && file.endsWith('.axaml'));
// Add en_US badge first
- badges.push(`[](TRANSLATION.md)`);
+ badges.push(`[](TRANSLATION.md)`);
for (const file of files) {
const filePath = path.join(localesDir, file);
@@ -40,8 +40,12 @@ async function calculateTranslationRate() {
// Add badges
const locale = file.replace('.axaml', '').replace('_', '__');
- const badgeColor = translationRate === 100 ? 'brightgreen' : translationRate >= 75 ? 'yellow' : 'red';
- badges.push(`[}%25-${badgeColor})](TRANSLATION.md)`);
+ if (translationRate === 100) {
+ badges.push(`[](TRANSLATION.md)`);
+ } else {
+ const badgeColor = translationRate >= 75 ? 'yellow' : 'red';
+ badges.push(`[}%25-${badgeColor})](TRANSLATION.md)`);
+ }
}
console.log(translationRates.join('\n\n'));
diff --git a/src/App.JsonCodeGen.cs b/src/App.JsonCodeGen.cs
index f37e269c..9cad0792 100644
--- a/src/App.JsonCodeGen.cs
+++ b/src/App.JsonCodeGen.cs
@@ -46,8 +46,6 @@ namespace SourceGit
[JsonSerializable(typeof(Models.ExternalToolPaths))]
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
[JsonSerializable(typeof(Models.JetBrainsState))]
- [JsonSerializable(typeof(Models.OpenAIChatRequest))]
- [JsonSerializable(typeof(Models.OpenAIChatResponse))]
[JsonSerializable(typeof(Models.ThemeOverrides))]
[JsonSerializable(typeof(Models.Version))]
[JsonSerializable(typeof(Models.RepositorySettings))]
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index cca9f2ea..95b396de 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using Avalonia;
@@ -332,17 +334,16 @@ namespace SourceGit
builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
builder.Append("----------------------------\n");
builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
- builder.Append($"OS: {Environment.OSVersion.ToString()}\n");
+ builder.Append($"OS: {Environment.OSVersion}\n");
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
builder.Append($"Source: {ex.Source}\n");
+ builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n");
+ builder.Append($"User: {Environment.UserName}\n");
+ builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n");
+ builder.Append($"Exception Time: {DateTime.Now}\n");
+ builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n");
builder.Append($"---------------------------\n\n");
- builder.Append(ex.StackTrace);
- while (ex.InnerException != null)
- {
- ex = ex.InnerException;
- builder.Append($"\n\nInnerException::: {ex.GetType().FullName}: {ex.Message}\n");
- builder.Append(ex.StackTrace);
- }
+ builder.Append(ex);
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs
index 683b8846..f2faed14 100644
--- a/src/Commands/Clone.cs
+++ b/src/Commands/Clone.cs
@@ -12,7 +12,7 @@ namespace SourceGit.Commands
WorkingDirectory = path;
TraitErrorAsOutput = true;
SSHKey = sshKey;
- Args = "clone --progress --verbose --recurse-submodules ";
+ Args = "clone --progress --verbose ";
if (!string.IsNullOrEmpty(extraArgs))
Args += $"{extraArgs} ";
diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs
index c4674c8e..8a6f2832 100644
--- a/src/Commands/CompareRevisions.cs
+++ b/src/Commands/CompareRevisions.cs
@@ -18,6 +18,15 @@ namespace SourceGit.Commands
Args = $"diff --name-status {based} {end}";
}
+ public CompareRevisions(string repo, string start, string end, string path)
+ {
+ WorkingDirectory = repo;
+ Context = repo;
+
+ var based = string.IsNullOrEmpty(start) ? "-R" : start;
+ Args = $"diff --name-status {based} {end} -- \"{path}\"";
+ }
+
public List Result()
{
Exec();
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index 1c3e78cb..06ae8cb6 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -4,7 +4,7 @@ namespace SourceGit.Commands
{
public class Fetch : Command
{
- public Fetch(string repo, string remote, bool noTags, bool prune, bool force, Action outputHandler)
+ public Fetch(string repo, string remote, bool noTags, bool force, Action outputHandler)
{
_outputHandler = outputHandler;
WorkingDirectory = repo;
@@ -21,9 +21,6 @@ namespace SourceGit.Commands
if (force)
Args += "--force ";
- if (prune)
- Args += "--prune ";
-
Args += remote;
}
diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs
index e4f25f38..4b18a561 100644
--- a/src/Commands/GenerateCommitMessage.cs
+++ b/src/Commands/GenerateCommitMessage.cs
@@ -1,8 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
+using Avalonia.Threading;
+
namespace SourceGit.Commands
{
///
@@ -20,82 +23,134 @@ namespace SourceGit.Commands
}
}
- public GenerateCommitMessage(Models.OpenAIService service, string repo, List changes, CancellationToken cancelToken, Action onProgress)
+ public GenerateCommitMessage(Models.OpenAIService service, string repo, List changes, CancellationToken cancelToken, Action onResponse)
{
_service = service;
_repo = repo;
_changes = changes;
_cancelToken = cancelToken;
- _onProgress = onProgress;
+ _onResponse = onResponse;
}
- public string Result()
+ public void Exec()
{
try
{
- var summarybuilder = new StringBuilder();
- var bodyBuilder = new StringBuilder();
+ var responseBuilder = new StringBuilder();
+ var summaryBuilder = new StringBuilder();
foreach (var change in _changes)
{
if (_cancelToken.IsCancellationRequested)
- return "";
+ return;
- _onProgress?.Invoke($"Analyzing {change.Path}...");
+ responseBuilder.Append("- ");
+ summaryBuilder.Append("- ");
- var summary = GenerateChangeSummary(change);
- summarybuilder.Append("- ");
- summarybuilder.Append(summary);
- summarybuilder.Append("(file: ");
- summarybuilder.Append(change.Path);
- summarybuilder.Append(")");
- summarybuilder.AppendLine();
+ var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
+ if (rs.IsSuccess)
+ {
+ var hasFirstValidChar = false;
+ var thinkingBuffer = new StringBuilder();
+ _service.Chat(
+ _service.AnalyzeDiffPrompt,
+ $"Here is the `git diff` output: {rs.StdOut}",
+ _cancelToken,
+ update =>
+ ProcessChatResponse(update, ref hasFirstValidChar, thinkingBuffer,
+ (responseBuilder, text =>
+ _onResponse?.Invoke(
+ $"Waiting for pre-file analyzing to completed...\n\n{text}")),
+ (summaryBuilder, null)));
+ }
- bodyBuilder.Append("- ");
- bodyBuilder.Append(summary);
- bodyBuilder.AppendLine();
+ responseBuilder.Append("\n");
+ summaryBuilder.Append("(file: ");
+ summaryBuilder.Append(change.Path);
+ summaryBuilder.Append(")\n");
}
if (_cancelToken.IsCancellationRequested)
- return "";
+ return;
- _onProgress?.Invoke($"Generating commit message...");
-
- var body = bodyBuilder.ToString();
- var subject = GenerateSubject(summarybuilder.ToString());
- return string.Format("{0}\n\n{1}", subject, body);
+ var responseBody = responseBuilder.ToString();
+ var subjectBuilder = new StringBuilder();
+ var hasSubjectFirstValidChar = false;
+ var subjectThinkingBuffer = new StringBuilder();
+ _service.Chat(
+ _service.GenerateSubjectPrompt,
+ $"Here are the summaries changes:\n{summaryBuilder}",
+ _cancelToken,
+ update =>
+ ProcessChatResponse(update, ref hasSubjectFirstValidChar, subjectThinkingBuffer,
+ (subjectBuilder, text => _onResponse?.Invoke($"{text}\n\n{responseBody}"))));
}
catch (Exception e)
{
- App.RaiseException(_repo, $"Failed to generate commit message: {e}");
- return "";
+ Dispatcher.UIThread.Post(() => App.RaiseException(_repo, $"Failed to generate commit message: {e}"));
}
}
- private string GenerateChangeSummary(Models.Change change)
+ private void ProcessChatResponse(
+ string update,
+ ref bool hasFirstValidChar,
+ StringBuilder thinkingBuffer,
+ params (StringBuilder builder, Action callback)[] outputs)
{
- var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
- var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
+ if (!hasFirstValidChar)
+ {
+ update = update.TrimStart();
+ if (string.IsNullOrEmpty(update))
+ return;
+ if (update.StartsWith("<", StringComparison.Ordinal))
+ thinkingBuffer.Append(update);
+ hasFirstValidChar = true;
+ }
- var rsp = _service.Chat(_service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken);
- if (rsp != null && rsp.Choices.Count > 0)
- return rsp.Choices[0].Message.Content;
+ if (thinkingBuffer.Length > 0)
+ thinkingBuffer.Append(update);
- return string.Empty;
- }
+ if (thinkingBuffer.Length > 15)
+ {
+ var match = REG_COT.Match(thinkingBuffer.ToString());
+ if (match.Success)
+ {
+ update = REG_COT.Replace(thinkingBuffer.ToString(), "").TrimStart();
+ if (update.Length > 0)
+ {
+ foreach (var output in outputs)
+ output.builder.Append(update);
+ thinkingBuffer.Clear();
+ }
+ return;
+ }
- private string GenerateSubject(string summary)
- {
- var rsp = _service.Chat(_service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken);
- if (rsp != null && rsp.Choices.Count > 0)
- return rsp.Choices[0].Message.Content;
+ match = REG_THINK_START.Match(thinkingBuffer.ToString());
+ if (!match.Success)
+ {
+ foreach (var output in outputs)
+ output.builder.Append(thinkingBuffer);
+ thinkingBuffer.Clear();
+ return;
+ }
+ }
- return string.Empty;
+ if (thinkingBuffer.Length == 0)
+ {
+ foreach (var output in outputs)
+ {
+ output.builder.Append(update);
+ output.callback?.Invoke(output.builder.ToString());
+ }
+ }
}
private Models.OpenAIService _service;
private string _repo;
private List _changes;
private CancellationToken _cancelToken;
- private Action _onProgress;
+ private Action _onResponse;
+
+ private static readonly Regex REG_COT = new(@"^<(think|thought|thinking|thought_chain)>(.*?)\1>", RegexOptions.Singleline);
+ private static readonly Regex REG_THINK_START = new(@"^<(think|thought|thinking|thought_chain)>", RegexOptions.Singleline);
}
}
diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs
index 732530f5..a4efa4b6 100644
--- a/src/Commands/Pull.cs
+++ b/src/Commands/Pull.cs
@@ -4,7 +4,7 @@ namespace SourceGit.Commands
{
public class Pull : Command
{
- public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, bool prune, Action outputHandler)
+ public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action outputHandler)
{
_outputHandler = outputHandler;
WorkingDirectory = repo;
@@ -17,8 +17,6 @@ namespace SourceGit.Commands
Args += "--rebase ";
if (noTags)
Args += "--no-tags ";
- if (prune)
- Args += "--prune ";
Args += $"{remote} {branch}";
}
diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs
index 6318f331..312c068f 100644
--- a/src/Commands/QueryCommits.cs
+++ b/src/Commands/QueryCommits.cs
@@ -18,9 +18,13 @@ namespace SourceGit.Commands
{
string search = onlyCurrentBranch ? string.Empty : "--branches --remotes ";
- if (method == Models.CommitSearchMethod.ByUser)
+ if (method == Models.CommitSearchMethod.ByAuthor)
{
- search += $"-i --author=\"{filter}\" --committer=\"{filter}\"";
+ search += $"-i --author=\"{filter}\"";
+ }
+ else if (method == Models.CommitSearchMethod.ByCommitter)
+ {
+ search += $"-i --committer=\"{filter}\"";
}
else if (method == Models.CommitSearchMethod.ByFile)
{
diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs
index 5fd6e3d5..6ebfa8b1 100644
--- a/src/Commands/QuerySubmodules.cs
+++ b/src/Commands/QuerySubmodules.cs
@@ -24,8 +24,6 @@ namespace SourceGit.Commands
{
var submodules = new List();
var rs = ReadToEnd();
- if (!rs.IsSuccess)
- return submodules;
var builder = new StringBuilder();
var lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
diff --git a/src/Commands/Stash.cs b/src/Commands/Stash.cs
index 1cbf4b2a..7acfdf38 100644
--- a/src/Commands/Stash.cs
+++ b/src/Commands/Stash.cs
@@ -30,7 +30,7 @@ namespace SourceGit.Commands
public bool Push(string message, List changes, bool keepIndex)
{
var builder = new StringBuilder();
- builder.Append("stash push ");
+ builder.Append("stash push --include-untracked ");
if (keepIndex)
builder.Append("--keep-index ");
builder.Append("-m \"");
@@ -47,7 +47,7 @@ namespace SourceGit.Commands
public bool Push(string message, string pathspecFromFile, bool keepIndex)
{
var builder = new StringBuilder();
- builder.Append("stash push --pathspec-from-file=\"");
+ builder.Append("stash push --include-untracked --pathspec-from-file=\"");
builder.Append(pathspecFromFile);
builder.Append("\" ");
if (keepIndex)
@@ -73,21 +73,22 @@ namespace SourceGit.Commands
return Exec();
}
- public bool Apply(string name)
+ public bool Apply(string name, bool restoreIndex)
{
- Args = $"stash apply --index -q {name}";
+ var opts = restoreIndex ? "--index" : string.Empty;
+ Args = $"stash apply -q {opts} \"{name}\"";
return Exec();
}
public bool Pop(string name)
{
- Args = $"stash pop --index -q {name}";
+ Args = $"stash pop -q \"{name}\"";
return Exec();
}
public bool Drop(string name)
{
- Args = $"stash drop -q {name}";
+ Args = $"stash drop -q \"{name}\"";
return Exec();
}
diff --git a/src/Commands/Worktree.cs b/src/Commands/Worktree.cs
index 7516b1e3..27c0e28e 100644
--- a/src/Commands/Worktree.cs
+++ b/src/Commands/Worktree.cs
@@ -73,6 +73,8 @@ namespace SourceGit.Commands
if (!string.IsNullOrEmpty(tracking))
Args += tracking;
+ else if (!string.IsNullOrEmpty(name) && !createNew)
+ Args += name;
_outputHandler = outputHandler;
return Exec();
diff --git a/src/Converters/StringConverters.cs b/src/Converters/StringConverters.cs
index e6f4237c..5e4608c5 100644
--- a/src/Converters/StringConverters.cs
+++ b/src/Converters/StringConverters.cs
@@ -78,5 +78,8 @@ namespace SourceGit.Converters
return v.Substring(13);
return v;
});
+
+ public static readonly FuncValueConverter ContainsSpaces =
+ new FuncValueConverter(v => v != null && v.Contains(' '));
}
}
diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs
index 9bc7f0c3..f015130a 100644
--- a/src/Models/Commit.cs
+++ b/src/Models/Commit.cs
@@ -8,7 +8,8 @@ namespace SourceGit.Models
{
public enum CommitSearchMethod
{
- ByUser,
+ ByAuthor,
+ ByCommitter,
ByMessage,
ByFile,
}
diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs
index df67ff66..a6648c11 100644
--- a/src/Models/OpenAI.cs
+++ b/src/Models/OpenAI.cs
@@ -1,81 +1,13 @@
using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
+using System.ClientModel;
using System.Threading;
-
+using Azure.AI.OpenAI;
using CommunityToolkit.Mvvm.ComponentModel;
+using OpenAI;
+using OpenAI.Chat;
namespace SourceGit.Models
{
- public class OpenAIChatMessage
- {
- [JsonPropertyName("role")]
- public string Role
- {
- get;
- set;
- }
-
- [JsonPropertyName("content")]
- public string Content
- {
- get;
- set;
- }
- }
-
- public class OpenAIChatChoice
- {
- [JsonPropertyName("index")]
- public int Index
- {
- get;
- set;
- }
-
- [JsonPropertyName("message")]
- public OpenAIChatMessage Message
- {
- get;
- set;
- }
- }
-
- public class OpenAIChatResponse
- {
- [JsonPropertyName("choices")]
- public List Choices
- {
- get;
- set;
- } = [];
- }
-
- public class OpenAIChatRequest
- {
- [JsonPropertyName("model")]
- public string Model
- {
- get;
- set;
- }
-
- [JsonPropertyName("messages")]
- public List Messages
- {
- get;
- set;
- } = [];
-
- public void AddMessage(string role, string content)
- {
- Messages.Add(new OpenAIChatMessage { Role = role, Content = content });
- }
- }
-
public class OpenAIService : ObservableObject
{
public string Name
@@ -87,7 +19,15 @@ namespace SourceGit.Models
public string Server
{
get => _server;
- set => SetProperty(ref _server, value);
+ set
+ {
+ // migrate old server value
+ if (!string.IsNullOrEmpty(value) && value.EndsWith("/chat/completions", StringComparison.Ordinal))
+ {
+ value = value.Substring(0, value.Length - "/chat/completions".Length);
+ }
+ SetProperty(ref _server, value);
+ }
}
public string ApiKey
@@ -147,45 +87,39 @@ namespace SourceGit.Models
""";
}
- public OpenAIChatResponse Chat(string prompt, string question, CancellationToken cancellation)
+ public void Chat(string prompt, string question, CancellationToken cancellation, Action onUpdate)
{
- var chat = new OpenAIChatRequest() { Model = Model };
- chat.AddMessage("user", prompt);
- chat.AddMessage("user", question);
-
- var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(60) };
- if (!string.IsNullOrEmpty(ApiKey))
+ Uri server = new(Server);
+ ApiKeyCredential key = new(ApiKey);
+ ChatClient client = null;
+ if (Server.Contains("openai.azure.com/", StringComparison.Ordinal))
{
- if (Server.Contains("openai.azure.com/", StringComparison.Ordinal))
- client.DefaultRequestHeaders.Add("api-key", ApiKey);
- else
- client.DefaultRequestHeaders.Add("Authorization", $"Bearer {ApiKey}");
+ var azure = new AzureOpenAIClient(server, key);
+ client = azure.GetChatClient(Model);
+ }
+ else
+ {
+ var openai = new OpenAIClient(key, new() { Endpoint = server });
+ client = openai.GetChatClient(Model);
}
- var req = new StringContent(JsonSerializer.Serialize(chat, JsonCodeGen.Default.OpenAIChatRequest), Encoding.UTF8, "application/json");
try
{
- var task = client.PostAsync(Server, req, cancellation);
- task.Wait(cancellation);
+ var updates = client.CompleteChatStreaming([
+ _model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt),
+ new UserChatMessage(question),
+ ], null, cancellation);
- var rsp = task.Result;
- var reader = rsp.Content.ReadAsStringAsync(cancellation);
- reader.Wait(cancellation);
-
- var body = reader.Result;
- if (!rsp.IsSuccessStatusCode)
+ foreach (var update in updates)
{
- throw new Exception($"AI service returns error code {rsp.StatusCode}. Body: {body ?? string.Empty}");
+ if (update.ContentUpdate.Count > 0)
+ onUpdate.Invoke(update.ContentUpdate[0].Text);
}
-
- return JsonSerializer.Deserialize(reader.Result, JsonCodeGen.Default.OpenAIChatResponse);
}
catch
{
- if (cancellation.IsCancellationRequested)
- return null;
-
- throw;
+ if (!cancellation.IsCancellationRequested)
+ throw;
}
}
diff --git a/src/Models/Remote.cs b/src/Models/Remote.cs
index 3c452460..dbe8cfa7 100644
--- a/src/Models/Remote.cs
+++ b/src/Models/Remote.cs
@@ -10,10 +10,10 @@ namespace SourceGit.Models
private static partial Regex REG_HTTPS();
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-/~%]+/[\w\-\.%]+(\.git)?$")]
private static partial Regex REG_SSH1();
- [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~]+/[\w\-\.]+(\.git)?$")]
+ [GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-/~%]+/[\w\-\.%]+(\.git)?$")]
private static partial Regex REG_SSH2();
- [GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/~]+/[\w\-\.]+)\.git$")]
+ [GeneratedRegex(@"^git@([\w\.\-]+):([\w\-/~%]+/[\w\-\.%]+)\.git$")]
private static partial Regex REG_TO_VISIT_URL_CAPTURE();
private static readonly Regex[] URL_FORMATS = [
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index 1d0b3c10..556c99ea 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -56,12 +56,6 @@ namespace SourceGit.Models
set;
} = DealWithLocalChanges.DoNothing;
- public bool EnablePruneOnFetch
- {
- get;
- set;
- } = false;
-
public bool EnableForceOnFetch
{
get;
@@ -188,6 +182,12 @@ namespace SourceGit.Models
set;
} = false;
+ public bool AutoRestoreAfterStash
+ {
+ get;
+ set;
+ } = false;
+
public string PreferedOpenAIService
{
get;
diff --git a/src/Models/ShellOrTerminal.cs b/src/Models/ShellOrTerminal.cs
index 1decdcfa..4f0222e8 100644
--- a/src/Models/ShellOrTerminal.cs
+++ b/src/Models/ShellOrTerminal.cs
@@ -42,6 +42,7 @@ namespace SourceGit.Models
new ShellOrTerminal("mac-terminal", "Terminal", ""),
new ShellOrTerminal("iterm2", "iTerm", ""),
new ShellOrTerminal("warp", "Warp", ""),
+ new ShellOrTerminal("ghostty", "Ghostty", "")
};
}
else
diff --git a/src/Models/TemplateEngine.cs b/src/Models/TemplateEngine.cs
index 6b5f525d..8472750c 100644
--- a/src/Models/TemplateEngine.cs
+++ b/src/Models/TemplateEngine.cs
@@ -313,7 +313,7 @@ namespace SourceGit.Models
private static bool IsNameChar(char c)
{
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
// (?) notice or log if variable is not found
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index c9e6abad..633ef5eb 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -44,6 +44,8 @@ namespace SourceGit.Native
return "iTerm";
case "warp":
return "Warp";
+ case "ghostty":
+ return "Ghostty";
}
return string.Empty;
diff --git a/src/Native/OS.cs b/src/Native/OS.cs
index 177bbf9f..3a688654 100644
--- a/src/Native/OS.cs
+++ b/src/Native/OS.cs
@@ -25,7 +25,8 @@ namespace SourceGit.Native
void OpenWithDefaultEditor(string file);
}
- public static string DataDir {
+ public static string DataDir
+ {
get;
private set;
} = string.Empty;
@@ -61,12 +62,14 @@ namespace SourceGit.Native
private set;
} = new Version(0, 0, 0);
- public static string ShellOrTerminal {
+ public static string ShellOrTerminal
+ {
get;
set;
} = string.Empty;
- public static List ExternalTools {
+ public static List ExternalTools
+ {
get;
set;
} = [];
diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs
index 10f2970a..11b6bd13 100644
--- a/src/Native/Windows.cs
+++ b/src/Native/Windows.cs
@@ -152,7 +152,7 @@ namespace SourceGit.Native
public void OpenBrowser(string url)
{
- var info = new ProcessStartInfo("cmd", $"/c start {url}");
+ var info = new ProcessStartInfo("cmd", $"/c start \"\" \"{url}\"");
info.CreateNoWindow = true;
Process.Start(info);
}
diff --git a/src/Resources/Images/ShellIcons/ghostty.png b/src/Resources/Images/ShellIcons/ghostty.png
new file mode 100644
index 00000000..e394a517
Binary files /dev/null and b/src/Resources/Images/ShellIcons/ghostty.png differ
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index 5143fc37..fe961d14 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -160,8 +160,6 @@
Remotes automatisch fetchen
Minute(n)
Standard Remote
- Aktivere --prune beim fetchen
- Aktiviere --signoff für Commits
TICKETSYSTEM
Beispiel für Gitee Issue Regel einfügen
Beispiel für Gitee Pull Request Regel einfügen
@@ -478,6 +476,7 @@
Klon Standardordner
Benutzer Email
Globale Git Benutzer Email
+ Aktivere --prune beim fetchen
Installationspfad
Aktiviere HTTP SSL Verifizierung
Benutzername
@@ -570,8 +569,8 @@
Horizontal
Vertikal
COMMIT SORTIERUNG
- Commit Zeitpunkt (--date-order)
- Topologie (--topo-order)
+ Commit Zeitpunkt
+ Topologie
LOKALE BRANCHES
Zum HEAD wechseln
Aktiviere '--first-parent' Option
@@ -583,10 +582,11 @@
REMOTES
REMOTE HINZUFÜGEN
Commit suchen
+ Autor
+ Committer
Dateiname
Commit-Nachricht
SHA
- Autor & Committer
Aktueller Branch
Zeige Tags als Baum
ÜBERSPRINGEN
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index d06f438d..018b54d7 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -19,7 +19,9 @@
Track Branch:
Tracking remote branch
AI Assistant
+ RE-GENERATE
Use AI to generate commit message
+ APPLY AS COMMIT MESSAGE
Patch
Error
Raise errors and refuses to apply the patch
@@ -34,6 +36,10 @@
Warn
Outputs warnings for a few such errors, but applies
Whitespace:
+ Apply Stash
+ Delete after applying
+ Reinstate the index's changes
+ Stash:
Archive...
Save Archive To:
Select archive file path
@@ -97,6 +103,7 @@
Local Name:
Repository name. Optional.
Parent Folder:
+ Initialize & update submodules
Repository URL:
CLOSE
Editor
@@ -157,8 +164,6 @@
Fetch remotes automatically
Minute(s)
Default Remote
- Enable --prune on fetch
- Enable --signoff for commit
ISSUE TRACKER
Add Sample Gitee Issue Rule
Add Sample Gitee Pull Request Rule
@@ -201,6 +206,7 @@
Stash & Reapply
New Branch Name:
Enter branch name.
+ Spaces will be replaced with dashes.
Create Local Branch
Create Tag...
New Tag At:
@@ -224,8 +230,11 @@
You are trying to delete multiple branches at one time. Be sure to double-check before taking action!
Delete Remote
Remote:
+ Path:
Target:
+ All children will be removed from list.
Confirm Deleting Group
+ This will only remove it from list, not from disk!
Confirm Deleting Repository
Delete Submodule
Submodule Path:
@@ -273,7 +282,7 @@
Fast-Forward (without checkout)
Fetch
Fetch all remotes
- Override refs check
+ Force override local refs
Fetch without tags
Remote:
Fetch Remote Changes
@@ -475,6 +484,7 @@
Default Clone Dir
User Email
Global git user email
+ Enable --prune on fetch
Install Path
Enable HTTP SSL Verify
User Name
@@ -567,8 +577,8 @@
Horizontal
Vertical
COMMITS ORDER
- Commit Date (--date-order)
- Topologically (--topo-order)
+ Commit Date
+ Topologically
LOCAL BRANCHES
Navigate to HEAD
Enable '--first-parent' Option
@@ -580,10 +590,11 @@
REMOTES
ADD REMOTE
Search Commit
+ Author
+ Committer
File
Message
SHA
- Author & Committer
Current Branch
Show Tags as Tree
SKIP
@@ -638,6 +649,8 @@
Private SSH key store path
START
Stash
+ Auto-restore after stashing
+ Your working files remain unchanged, but a stash is saved.
Include untracked files
Keep staged files
Message:
@@ -717,6 +730,7 @@
INCLUDE UNTRACKED FILES
NO RECENT INPUT MESSAGES
NO COMMIT TEMPLATES
+ SignOff
STAGED
UNSTAGE
UNSTAGE ALL
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml
index 74b2f224..3e926c63 100644
--- a/src/Resources/Locales/es_ES.axaml
+++ b/src/Resources/Locales/es_ES.axaml
@@ -160,8 +160,6 @@
Fetch remotos automáticamente
Minuto(s)
Remoto por Defecto
- Habilitar --prune para fetch
- Habilitar --signoff para commit
SEGUIMIENTO DE INCIDENCIAS
Añadir Regla de Ejemplo para Incidencias de Gitee
Añadir Regla de Ejemplo para Pull Requests de Gitee
@@ -479,6 +477,7 @@
Directorio de clonado por defecto
Email de usuario
Email global del usuario git
+ Habilitar --prune para fetch
Ruta de instalación
Habilitar verificación HTTP SSL
Nombre de usuario
@@ -571,8 +570,8 @@
Horizontal
Vertical
ORDEN DE COMMITS
- Fecha de Commit (--date-order)
- Topológicamente (--topo-order)
+ Fecha de Commit
+ Topológicamente
RAMAS LOCALES
Navegar a HEAD
Habilitar Opción '--first-parent'
@@ -584,10 +583,11 @@
REMOTOS
AÑADIR REMOTO
Buscar Commit
+ Autor
+ Committer
Archivo
Mensaje
SHA
- Autor & Committer
Rama Actual
Mostrar Etiquetas como Árbol
OMITIR
@@ -707,7 +707,7 @@
Ignorar archivos *{0} en la misma carpeta
Ignorar archivos en la misma carpeta
Ignorar solo este archivo
- Enmendar (Amend)
+ Enmendar
Puedes stagear este archivo ahora.
COMMIT
COMMIT & PUSH
diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml
index e4bb9c26..da20a5ee 100644
--- a/src/Resources/Locales/fr_FR.axaml
+++ b/src/Resources/Locales/fr_FR.axaml
@@ -161,8 +161,6 @@
Fetch les dépôts distants automatiquement
minute(s)
Dépôt par défaut
- Activer --prune pour fetch
- Activer --signoff pour commit
SUIVI DES PROBLÈMES
Ajouter une règle d'exemple Gitee
Ajouter une règle d'exemple pour Pull Request Gitee
@@ -467,6 +465,7 @@
Répertoire de clônage par défaut
E-mail utilsateur
E-mail utilsateur global
+ Activer --prune pour fetch
Chemin d'installation
Nom d'utilisateur
Nom d'utilisateur global
@@ -560,10 +559,11 @@
DEPOTS DISTANTS
AJOUTER DEPOT DISTANT
Rechercher un commit
+ Auteur
+ Committer
Fichier
Message
SHA
- Auteur & Committer
Branche actuelle
Voir les Tags en tant qu'arbre
Statistiques
diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml
index 72e2aa28..95f7bd1d 100644
--- a/src/Resources/Locales/it_IT.axaml
+++ b/src/Resources/Locales/it_IT.axaml
@@ -12,7 +12,7 @@
• Il codice sorgente è disponibile su
Client GUI Git open source e gratuito
Aggiungi Worktree
- Cosa fare il checkout:
+ Di cosa fare il checkout:
Branch esistente
Crea nuovo branch
Posizione:
@@ -60,8 +60,9 @@
Recupera ${0}$ in ${1}$...
Git Flow - Completa ${0}$
Unisci ${0}$ in ${1}$...
- Recupera ${0}$
- Recupera ${0}$ in ${1}$...
+ Unisci i {0} branch selezionati in quello corrente
+ Scarica ${0}$
+ Scarica ${0}$ in ${1}$...
Invia ${0}$
Riallinea ${0}$ su ${1}$...
Rinomina ${0}$...
@@ -73,7 +74,7 @@
Ripristina la Revisione Padre
Genera messaggio di commit
CAMBIA MODALITÀ DI VISUALIZZAZIONE
- Mostra come elenco di file e directory
+ Mostra come elenco di file e cartelle
Mostra come elenco di percorsi
Mostra come albero del filesystem
Checkout Branch
@@ -84,13 +85,13 @@
Modifiche Locali:
Scarta
Non fare nulla
- Stash e Ripristina
+ Stasha e Ripristina
Cherry Pick
Aggiungi sorgente al messaggio di commit
Commit(s):
Conferma tutte le modifiche
Mainline:
- Di solito non è possibile cherry-pick su una fusione perché non si sa quale lato della fusione deve essere considerato il mainline. Questa opzione consente di riprodurre la modifica relativa al genitore specificato.
+ Di solito non è possibile fare cherry-pick sdi una unione perché non si sa quale lato deve essere considerato il mainline. Questa opzione consente di riprodurre la modifica relativa al genitore specificato.
Cancella Stash
Stai per cancellare tutti gli stash. Sei sicuro di voler continuare?
Clona Repository Remoto
@@ -110,22 +111,26 @@
Copia Info
Copia SHA
Azione Personalizzata
- Rebase Interattivo ${0}$ fino a Qui
+ Riallinea Interattivamente ${0}$ fino a Qui
+ Unisci a ${0}$
+ Unisci ...
Riallinea ${0}$ fino a Qui
Ripristina ${0}$ fino a Qui
Annulla Commit
Modifica
Salva come Patch...
- Unisci al Genitore
- Unisci Commit Figli fino a Qui
+ Compatta nel Genitore
+ Compatta Commit Figli fino a Qui
MODIFICHE
Cerca Modifiche...
FILE
File LFS
+ Cerca File...
Sottomodulo
INFORMAZIONI
AUTORE
MODIFICATO
+ FIGLI
CHI HA COMMITTATO
Controlla i riferimenti che contengono questo commit
IL COMMIT È CONTENUTO DA
@@ -155,21 +160,21 @@
Recupera automaticamente i remoti
Minuto/i
Remoto Predefinito
- Abilita --prune durante il fetch
- Abilita --signoff per i commit
TRACCIAMENTO ISSUE
- Aggiungi Regola Esempio per GitHub
- Aggiungi Regola Esempio per Jira
- Aggiungi Regola Esempio per Issue GitLab
- Aggiungi Regola Esempio per Merge Request GitLab
+ Aggiungi una regola di esempio per un Issue Gitee
+ Aggiungi una regola di esempio per un Pull Request Gitee
+ Aggiungi una regola di esempio per GitHub
+ Aggiungi una regola di esempio per Jira
+ Aggiungi una regola di esempio per Issue GitLab
+ Aggiungi una regola di esempio per una Merge Request GitLab
Nuova Regola
Espressione Regex Issue:
Nome Regola:
URL Risultato:
Utilizza $1, $2 per accedere ai valori dei gruppi regex.
AI
- Servizio Preferito:
- Se il 'Servizio Preferito' è impostato, SourceGit utilizzerà solo quello per questo repository. In caso contrario, se sono disponibili più servizi, verrà mostrato un menu contestuale per sceglierne uno.
+ Servizio preferito:
+ Se il 'Servizio Preferito' é impostato, SourceGit utilizzerà solo quello per questo repository. Altrimenti, se ci sono più servizi disponibili, verrà mostrato un menu contestuale per sceglierne uno.
Proxy HTTP
Proxy HTTP usato da questo repository
Nome Utente
@@ -194,9 +199,10 @@
Modifiche Locali:
Scarta
Non Fare Nulla
- Stash e Ripristina
+ Stasha e Ripristina
Nome Nuovo Branch:
Inserisci il nome del branch.
+ Gli spazi verranno rimpiazzati con dei trattini.
Crea Branch Locale
Crea Tag...
Nuovo Tag Su:
@@ -238,6 +244,7 @@
Differenza Successiva
NESSUNA MODIFICA O SOLO CAMBIAMENTI DI FINE LINEA
Differenza Precedente
+ Abilita la navigazione a blocchi
Salva come Patch
Mostra Simboli Nascosti
Diff Affiancato
@@ -268,6 +275,7 @@
Avanzamento Veloce (senza verifica)
Recupera
Recupera da tutti i remoti
+ Forza la sovrascrittura dei riferimenti locali
Recupera senza tag
Remoto:
Recupera Modifiche Remote
@@ -276,15 +284,16 @@
Scarta {0} file...
Scarta Modifiche nelle Righe Selezionate
Apri Strumento di Merge Esterno
+ Risolvi Usando ${0}$
Salva come Patch...
- Staging
- Staging {0} file
- Staging Modifiche nelle Righe Selezionate
- Stash...
- Stash {0} file...
- Rimuovi dallo Staging
- Rimuovi dallo Staging {0} file
- Rimuovi dallo Staging Modifiche nelle Righe Selezionate
+ Stage
+ Stage di {0} file
+ Stage delle Modifiche nelle Righe Selezionate
+ Stasha...
+ Stasha {0} file...
+ Rimuovi da Stage
+ Rimuovi da Stage {0} file
+ Rimuovi le Righe Selezionate da Stage
Usa Il Loro (checkout --theirs)
Usa Il Mio (checkout --ours)
Cronologia File
@@ -297,22 +306,22 @@
Prefisso Feature:
FLOW - Completa Feature
FLOW - Completa Hotfix
- FLOW - Completa Release
+ FLOW - Completa Rilascio
Target:
Hotfix:
Prefisso Hotfix:
Inizializza Git-Flow
Mantieni branch
Branch di Produzione:
- Release:
- Prefisso Release:
+ Rilascio:
+ Prefisso Rilascio:
Inizia Feature...
FLOW - Inizia Feature
Inizia Hotfix...
FLOW - Inizia Hotfix
Inserisci nome
- Inizia Release...
- FLOW - Inizia Release
+ Inizia Rilascio...
+ FLOW - Inizia Rilascio
Prefisso Tag Versione:
Git LFS
Aggiungi Modello di Tracciamento...
@@ -323,28 +332,28 @@
Recupera Oggetti LFS
Esegui `git lfs fetch` per scaricare gli oggetti Git LFS. Questo non aggiorna la copia di lavoro.
Installa hook di Git LFS
- Mostra Bloccaggi
+ Mostra Blocchi
Nessun File Bloccato
Blocca
- Mostra solo i miei bloccaggi
- Bloccaggi LFS
+ Mostra solo i miei blocchi
+ Blocchi LFS
Sblocca
Forza Sblocco
Elimina
Esegui `git lfs prune` per eliminare vecchi file LFS dallo storage locale
- Pull
- Pull Oggetti LFS
+ Scarica
+ Scarica Oggetti LFS
Esegui `git lfs pull` per scaricare tutti i file LFS per il ref corrente e fare il checkout
- Push
- Push Oggetti LFS
+ Invia
+ Invia Oggetti LFS
Invia grandi file in coda al punto finale di Git LFS
Remoto:
Traccia file con nome '{0}'
Traccia tutti i file *{0}
- Storico
+ STORICO
AUTORE
ORA AUTORE
- GRAFICO & OGGETTO
+ GRAFICO E OGGETTO
SHA
ORA COMMIT
{0} COMMIT SELEZIONATI
@@ -359,19 +368,19 @@
Vai alla pagina precedente
Vai alla pagina successiva
Crea una nuova pagina
- Apri la finestra di preferenze
+ Apri la finestra delle preferenze
REPOSITORY
- Conferma le modifiche in fase
- Conferma e invia le modifiche in fase
- Aggiungi tutte le modifiche e conferma
+ Committa le modifiche in tsage
+ Committa e invia le modifiche in stage
+ Fai lo stage di tutte le modifiche e committa
Crea un nuovo branch dal commit selezionato
Scarta le modifiche selezionate
Recupera, avvia direttamente
Modalità Dashboard (Predefinita)
- Recupera e integra, avvia direttamente
+ Scarica, avvia direttamente
Invia, avvia direttamente
- Forza il ricaricamento di questo repository
- Aggiungi/Rimuovi le modifiche selezionate
+ Forza l'aggiornamento di questo repository
+ Aggiungi/Rimuovi da stage le modifiche selezionate
Modalità ricerca commit
Passa a 'Modifiche'
Passa a 'Storico'
@@ -381,16 +390,20 @@
Trova il prossimo risultato
Trova il risultato precedente
Apri il pannello di ricerca
- Aggiungi
+ Aggiungi in stage
Rimuovi
Scarta
Inizializza Repository
Percorso:
Cherry-Pick in corso.
- Richiesta di merge in corso.
- Rebase in corso.
- Revert in corso.
- Rebase Interattivo
+ Elaborando il commit
+ Unione in corso.
+ Unendo
+ Riallineamento in corso.
+ Interrotto a
+ Ripristino in corso.
+ Ripristinando il commit
+ Riallinea Interattivamente
Branch di destinazione:
Su:
Apri nel Browser
@@ -399,11 +412,16 @@
AVVISO
Unisci Branch
In:
- Opzione di Merge:
+ Opzione di Unione:
+ Sorgente:
+ Unione (multipla)
+ Commit di tutte le modifiche
+ Strategia:
+ Obiettivi:
Sposta Nodo Repository
Seleziona nodo padre per:
Nome:
- Git NON è configurato. Vai su [Preferenze] e configurarlo prima.
+ Git NON è configurato. Prima vai su [Preferenze] per configurarlo.
Apri Cartella Dati App
Apri con...
Opzionale.
@@ -428,7 +446,7 @@
AI
Analizza il Prompt Differenza
Chiave API
- Genera Prompt Soggetto
+ Genera Prompt Oggetto
Modello
Nome
Server
@@ -449,20 +467,24 @@
Strumento
GENERALE
Controlla aggiornamenti all'avvio
+ Formato data
Lingua
Numero massimo di commit nella cronologia
- Mostra l'orario dell'autore anziché quello del commit nel grafico
- Lunghezza Guida Soggetto
+ Mostra nel grafico l'orario dell'autore anziché quello del commit
+ Mostra i figli nei dettagli del commit
+ Lunghezza Guida Oggetto
GIT
Abilita Auto CRLF
Cartella predefinita per cloni
Email Utente
- Email globale utente Git
+ Email utente Git globale
+ Abilita --prune durante il fetch
Percorso Installazione
Nome Utente
- Nome globale utente Git
+ Nome utente Git globale
Versione di Git
- Git (>= 2.23.0) è richiesto da questa applicazione
+ Questa applicazione richiede Git (>= 2.23.0)
+ Abilita la verifica HTTP SSL
FIRMA GPG
Firma GPG per commit
Firma GPG per tag
@@ -479,21 +501,21 @@
Destinazione:
Potatura Worktrees
Potatura delle informazioni di worktree in `$GIT_DIR/worktrees`
- Pull
+ Scarica
Branch Remoto:
Recupera tutti i branch
In:
Modifiche Locali:
Scarta
Non fare nulla
- Accantona e Riapplica
+ Stasha e Riapplica
Recupera senza tag
Remoto:
- Pull (Fetch & Merge)
- Usa rebase anziché merge
- Push
- Assicurati che i submoduli siano stati spinti
- Forza il push
+ Scarica (Recupera e Unisci)
+ Riallineare anziché unire
+ Invia
+ Assicurati che i sottomoduli siano stati inviati
+ Forza l'invio
Branch Locale:
Remoto:
Invia modifiche al remoto
@@ -505,10 +527,10 @@
Remoto:
Tag:
Esci
- Rebase Branch Corrente
- Accantona & Riapplica modifiche locali
+ Riallinea Branch Corrente
+ Stasha e Riapplica modifiche locali
Su:
- Rebase:
+ Riallinea:
Aggiorna
Aggiungi Remoto
Modifica Remoto
@@ -531,7 +553,7 @@
Branch:
ANNULLA
Recupero automatico delle modifiche dai remoti...
- Pulizia (GC & Potatura)
+ Pulizia (GC e Potatura)
Esegui il comando `git gc` per questo repository.
Cancella tutto
Configura questo repository
@@ -539,32 +561,50 @@
Azioni Personalizzate
Nessuna Azione Personalizzata
Abilita opzione '--reflog'
- Apri nel Browser File
- Cerca Branch/Tag/Submodule
+ Apri nell'Esplora File
+ Cerca Branch/Tag/Sottomodulo
FILTRATO DA:
+ Visibilità nel grafico
+ Non impostato
+ Nascondi nel grafico dei commit
+ Filtra nel grafico dei commit
+ LAYOUT
+ Orizzontale
+ Verticale
+ Ordine dei commit
+ Per data del commit
+ Topologicamente
BRANCH LOCALI
Vai a HEAD
Abilita opzione '--first-parent'
Crea Branch
+ Evidenzia nel grafico solo il branch corrente
Apri in {0}
Apri in Strumenti Esterni
Aggiorna
REMOTI
AGGIUNGI REMOTO
Cerca Commit
+ Autore
+ Committente
File
Messaggio
SHA
- Autore & Committente
Branch Corrente
Mostra Tag come Albero
+ SALTA
Statistiche
- SUBMODULE
- AGGIUNGI SUBMODULE
- AGGIORNA SUBMODULE
+ SOTTOMODULI
+ AGGIUNGI SOTTOMODULI
+ AGGIORNA SOTTOMODULI
TAG
NUOVO TAG
+ Per data di creazione
+ Per nome (ascendente)
+ Per nome (discendente)
+ Ordina
Apri nel Terminale
+ Usa tempo relativo nello storico
WORKTREE
AGGIUNGI WORKTREE
POTATURA
@@ -573,10 +613,10 @@
Modalità Reset:
Sposta a:
Branch Corrente:
- Mostra nel File Explorer
- Revert Commit
+ Mostra nell'Esplora File
+ Ripristina Commit
Commit:
- Commit delle modifiche di revert
+ Commit delle modifiche di ripristino
Modifica Messaggio di Commit
Usa 'Shift+Enter' per inserire una nuova riga. 'Enter' è il tasto rapido per il pulsante OK
In esecuzione. Attendere...
@@ -592,27 +632,33 @@
Salta questa versione
Aggiornamento Software
Non ci sono aggiornamenti disponibili.
- Squash Commit
+ Imposta il Branch
+ Branch:
+ Rimuovi upstream
+ Upstream:
+ Copia SHA
+ Vai a
+ Compatta Commit
In:
Chiave Privata SSH:
Percorso per la chiave SSH privata
AVVIA
- Accantona
+ Stasha
Includi file non tracciati
- Mantieni file indicizzati
+ Mantieni file in stage
Messaggio:
- Opzionale. Nome di questo accantonamento
- Solo modifiche indicizzate
- Sia le modifiche indicizzate che quelle non indicizzate dei file selezionati saranno accantonate!!!
- Accantona Modifiche Locali
+ Opzionale. Nome di questo stash
+ Solo modifiche in stage
+ Sia le modifiche in stage che quelle non in stage dei file selezionati saranno stashate!!!
+ Stasha Modifiche Locali
Applica
Elimina
Estrai
- Elimina Accantonamento
+ Elimina Stash
Elimina:
- Accantonamenti
+ STASH
MODIFICHE
- ACCANTONAMENTI
+ STASH
Statistiche
COMMIT
COMMITTER
@@ -621,14 +667,14 @@
COMMIT:
AUTORI:
PANORAMICA
- SUBMODULE
- Aggiungi Submodule
+ SOTTOMODULI
+ Aggiungi Sottomodulo
Copia Percorso Relativo
- Recupera submodule annidati
- Apri Repository Submodule
+ Recupera sottomoduli annidati
+ Apri Repository del Sottomodulo
Percorso Relativo:
Cartella relativa per memorizzare questo modulo.
- Elimina Submodule
+ Elimina Sottomodulo
OK
Copia Nome Tag
Copia Messaggio Tag
@@ -636,11 +682,11 @@
Unisci ${0}$ in ${1}$...
Invia ${0}$...
URL:
- Aggiorna Submodule
- Tutti i submodule
+ Aggiorna Sottomoduli
+ Tutti i sottomoduli
Inizializza se necessario
Ricorsivamente
- Submodule:
+ Sottomodulo:
Usa opzione --remote
Avviso
Pagina di Benvenuto
@@ -648,7 +694,7 @@
Crea Sottogruppo
Clona Repository
Elimina
- TRASCINA & RILASCIA CARTELLA SUPPORTATO. RAGGRUPPAMENTI PERSONALIZZATI SUPPORTATI.
+ TRASCINA E RILASCIA CARTELLA SUPPORTATO. RAGGRUPPAMENTI PERSONALIZZATI SUPPORTATI.
Modifica
Sposta in un Altro Gruppo
Apri Tutti i Repository
@@ -657,34 +703,36 @@
Riscansiona Repository nella Cartella Clone Predefinita
Cerca Repository...
Ordina
- Modifiche
+ MODIFICHE LOCALI
Git Ignore
Ignora tutti i file *{0}
Ignora i file *{0} nella stessa cartella
Ignora i file nella stessa cartella
Ignora solo questo file
Modifica
- Puoi indicizzare questo file ora.
+ Puoi aggiungere in stage questo file ora.
COMMIT
- COMMIT & PUSH
+ COMMIT E INVIA
Template/Storico
Attiva evento click
- Indica tutte le modifiche e fai il commit
- Commit vuoto rilevato! Vuoi continuare (--allow-empty)?
+ Commit (Modifica)
+ Stage di tutte le modifiche e fai il commit
+ Trovato un commit vuoto! Vuoi continuare (--allow-empty)?
CONFLITTI RILEVATI
CONFLITTI NEI FILE RISOLTI
INCLUDI FILE NON TRACCIATI
NESSUN MESSAGGIO RECENTE INSERITO
NESSUN TEMPLATE DI COMMIT
- INDICIZZATI
- RIMUOVI DALL'INDICIZZAZIONE
- RIMUOVI TUTTO DALL'INDICIZZAZIONE
- NON INDICIZZATI
- INDICIZZA
- INDICIZZA TUTTO
+ IN STAGE
+ RIMUOVI DA STAGE
+ RIMUOVI TUTTO DA STAGE
+ NON IN STAGE
+ FAI LO STAGE
+ FAI LO STAGE DI TUTTO
VISUALIZZA COME NON MODIFICATO
Template: ${0}$
- Clicca con il tasto destro sul file(i) selezionato, quindi scegli come risolvere i conflitti.
+ Clicca con il tasto destro sul(i) file selezionato, quindi scegli come risolvere i conflitti.
+ SignOff
WORKSPACE:
Configura Workspaces...
WORKTREE
diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml
index 8b17bdaf..dee8565b 100644
--- a/src/Resources/Locales/pt_BR.axaml
+++ b/src/Resources/Locales/pt_BR.axaml
@@ -180,8 +180,6 @@
Buscar remotos automaticamente
Minuto(s)
Remoto padrão
- Habilita --prune ao buscar
- Habilita --signoff para commits
RASTREADOR DE PROBLEMAS
Adicionar Regra de Exemplo do Github
Adicionar Regra de Exemplo do Jira
@@ -481,6 +479,7 @@
Diretório de Clone Padrão
Email do Usuário
Email global do usuário git
+ Habilita --prune ao buscar
Caminho de Instalação
Nome do Usuário
Nome global do usuário git
@@ -567,8 +566,8 @@
Desfazer
Esconder no gráfico de commit
Incluir no gráfico de commit
- Data do Commit (--date-order)
- Topologicamente (--topo-order)
+ Data do Commit
+ Topologicamente
BRANCHES LOCAIS
Navegar para HEAD
Habilitar opção '--first-parent'
@@ -579,10 +578,11 @@
REMOTOS
ADICIONAR REMOTO
Pesquisar Commit
+ Autor
+ Committer
Arquivo
Mensagem
SHA
- Autor & Committer
Branch Atual
Exibir Tags como Árvore
Estatísticas
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index 75650a28..27a2d360 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -159,9 +159,7 @@
GIT
Автоматическое скачивание изменений
Минут(а/ы)
- Разрешить '--signoff' для ревизии
Внешний репозиторий по умолчанию
- Разрешить '--prune' при скачивании
ОТСЛЕЖИВАНИЕ ПРОБЛЕМ
Добавить пример правила для тем в Gitee
Добавить пример правила запроса скачивания из Gitee
@@ -479,6 +477,7 @@
Каталог клонирования по умолчанию
Электроная почта пользователя
Общая электроная почта пользователя git
+ Разрешить '--prune' при скачивании
Путь установки
Разрешить верификацию HTTP SSL
Имя пользователя
@@ -572,8 +571,8 @@
Горизонтально
Вертикально
ЗАПРОС РЕВИЗИЙ
- Дата ревизии (--date-order)
- Топологически (--topo-order)
+ Дата ревизии
+ Топологически
ЛОКАЛЬНЫЕ ВЕТКИ
Навигация по ГОЛОВЕ (HEAD)
Включить опцию --first-parent
@@ -585,10 +584,11 @@
ВНЕШНИЕ РЕПОЗИТОРИИ
ДОБАВИТЬ ВНЕШНИЙ РЕПОЗИТОРИЙ
Поиск ревизии
+ Автор
+ исполнитель
Файл
Сообщение
SHA
- Автор и исполнитель
Текущая ветка
Показывать метки как катлог
ПРОПУСТИТЬ
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index b5541376..66d1f37e 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -22,7 +22,9 @@
跟踪分支
设置上游跟踪分支
AI助手
+ 重新生成
使用AI助手生成提交信息
+ 应用本次生成
应用补丁(apply)
错误
输出错误,并终止应用补丁
@@ -37,6 +39,10 @@
警告
应用补丁,输出关于空白符的警告
空白符号处理 :
+ 应用贮藏
+ 在成功应用后丢弃该贮藏
+ 恢复索引中已暂存的变化
+ 已选贮藏 :
存档(archive) ...
存档文件路径:
选择存档文件的存放路径
@@ -100,6 +106,7 @@
本地仓库名 :
本地仓库目录的名字,选填。
父级目录 :
+ 初始化并更新子模块
远程仓库 :
关闭
提交信息编辑器
@@ -160,8 +167,6 @@
启用定时自动拉取远程更新
分钟
默认远程
- 提交信息追加署名 (--signoff)
- 拉取更新时启用修剪(--prune)
ISSUE追踪
新增匹配Gitee议题规则
新增匹配Gitee合并请求规则
@@ -204,6 +209,7 @@
贮藏并自动恢复
新分支名 :
填写分支名称。
+ 空格将被替换为'-'符号
创建本地分支
新建标签 ...
标签位于 :
@@ -227,8 +233,11 @@
您正在尝试一次性删除多个分支,请务必仔细检查后再执行操作!
删除远程确认
远程名 :
+ 路径 :
目标 :
+ 所有子节点将被同时从列表中移除。
删除分组确认
+ 仅从列表中移除,不会删除硬盘中的文件!
删除仓库确认
删除子模块确认
子模块路径 :
@@ -276,7 +285,7 @@
快进(fast-forward,无需checkout)
拉取(fetch)
拉取所有的远程仓库
- 覆盖REF检查
+ 强制覆盖本地REFs
不拉取远程标签
远程仓库 :
拉取远程仓库内容
@@ -479,6 +488,7 @@
默认克隆路径
邮箱
默认GIT用户邮箱
+ 拉取更新时启用修剪(--prune)
安装路径
启用HTTP SSL验证
用户名
@@ -571,8 +581,8 @@
水平排布
竖直排布
提交列表排序规则
- 按提交时间 (--date-order)
- 按拓扑排序 (--topo-order)
+ 按提交时间
+ 按拓扑排序
本地分支
定位HEAD
启用 --first-parent 过滤选项
@@ -584,10 +594,11 @@
远程列表
添加远程
查找提交
+ 作者
+ 提交者
文件
提交信息
提交指纹
- 作者及提交者
仅在当前分支中查找
以树型结构展示
跳过此提交
@@ -642,6 +653,8 @@
SSH密钥文件
开 始
贮藏(stash)
+ 贮藏后自动恢复工作区
+ 工作区文件保持未修改状态,但贮藏内容已保存。
包含未跟踪的文件
保留暂存区文件
信息 :
@@ -707,7 +720,7 @@
忽略同目录下所有 *{0} 文件
忽略同目录下所有文件
忽略本文件
- 修补(--amend)
+ 修补
现在您已可将其加入暂存区中
提交
提交并推送
@@ -721,6 +734,7 @@
显示未跟踪文件
没有提交信息记录
没有可应用的提交信息模板
+ 署名
已暂存
从暂存区移除选中
从暂存区移除所有
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index d2395e58..f8255947 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -22,7 +22,9 @@
追蹤分支
設定遠端追蹤分支
AI 助理
+ 重新產生
使用 AI 產生提交訊息
+ 套用為提交訊息
套用修補檔 (apply patch)
錯誤
輸出錯誤,並中止套用修補檔
@@ -37,6 +39,10 @@
警告
套用修補檔,輸出關於空白字元的警告
空白字元處理:
+ 套用擱置
+ 在成功套用后捨棄擱置
+ 恢復索引中已暫存的變更
+ 已選擇擱置 :
封存 (archive)...
封存檔案路徑:
選擇封存檔案的儲存路徑
@@ -100,6 +106,7 @@
本機存放庫名稱:
本機存放庫目錄的名稱,選填。
父級目錄:
+ 初始化並複製子模組
遠端存放庫:
關閉
提交訊息編輯器
@@ -160,8 +167,6 @@
啟用定時自動提取 (fetch) 遠端更新
分鐘
預設遠端存放庫
- 提交訊息追加署名 (--signoff)
- 拉取變更時進行清理 (--prune)
Issue 追蹤
新增符合 Gitee 議題規則
新增符合 Gitee 合併請求規則
@@ -204,6 +209,7 @@
擱置變更並自動復原
新分支名稱:
輸入分支名稱。
+ 空格將以英文破折號取代
建立本機分支
新增標籤...
標籤位於:
@@ -227,8 +233,11 @@
您正在嘗試一次性刪除多個分支,請務必仔細檢查後再刪除!
刪除遠端確認
遠端名稱:
+ 路徑:
目標:
+ 所有子節點都會從清單中移除。
刪除群組確認
+ 只會從清單中移除,而不會刪除磁碟中的檔案!
刪除存放庫確認
刪除子模組確認
子模組路徑:
@@ -276,7 +285,7 @@
快進 (fast-forward,無需 checkout)
提取 (fetch)
提取所有的遠端存放庫
- 覆寫 REFs 檢查
+ 強制覆寫本機 REFs
不提取遠端標籤
遠端存放庫:
提取遠端存放庫內容
@@ -478,6 +487,7 @@
預設複製 (clone) 路徑
電子郵件
預設 Git 使用者電子郵件
+ 拉取變更時進行清理 (--prune)
安裝路徑
啟用 HTTP SSL 驗證
使用者名稱
@@ -570,8 +580,8 @@
橫向顯示
縱向顯示
提交顯示順序
- 依提交時間排序 (--date-order)
- 依拓撲排序 (--topo-order)
+ 依提交時間排序
+ 依拓撲排序
本機分支
回到 HEAD
啟用 [--first-parent] 選項
@@ -583,10 +593,11 @@
遠端列表
新增遠端
搜尋提交
+ 作者
+ 提交者
檔案
提交訊息
提交編號
- 作者及提交者
僅搜尋目前分支
以樹型結構展示
跳過此提交
@@ -641,6 +652,8 @@
SSH 金鑰檔案
開 始
擱置變更 (stash)
+ 暫存後自動復原工作區
+ 工作區檔案保持未修改,但暫存內容已儲存。
包含未追蹤的檔案
保留已暫存的變更
擱置變更訊息:
@@ -706,7 +719,7 @@
忽略同路徑下所有 *{0} 檔案
忽略同路徑下所有檔案
忽略本檔案
- 修補 (--amend)
+ 修補
現在您已可將其加入暫存區中
提 交
提交並推送
@@ -720,6 +733,7 @@
顯示未追蹤檔案
沒有提交訊息記錄
沒有可套用的提交訊息範本
+ 署名
已暫存
取消暫存選取的檔案
取消暫存所有檔案
diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml
index 6335c635..9b43d8af 100644
--- a/src/Resources/Styles.axaml
+++ b/src/Resources/Styles.axaml
@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="using:SourceGit"
xmlns:vm="using:SourceGit.ViewModels"
+ xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
xmlns:ae="using:AvaloniaEdit"
xmlns:aee="using:AvaloniaEdit.Editing"
@@ -844,7 +845,19 @@
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
- FontSize="11"/>
+ FontSize="11"
+ IsVisible="{TemplateBinding (v:MenuItemExtension.Command), Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
+
+
+
-
+
+
diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs
index 2ca7449f..d6424572 100644
--- a/src/ViewModels/AddRemote.cs
+++ b/src/ViewModels/AddRemote.cs
@@ -100,7 +100,7 @@ namespace SourceGit.ViewModels
{
SetProgressDescription("Fetching from added remote ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
- new Commands.Fetch(_repo.FullPath, _name, false, false, false, SetProgressDescription).Exec();
+ new Commands.Fetch(_repo.FullPath, _name, false, false, SetProgressDescription).Exec();
}
CallUIThread(() =>
{
diff --git a/src/ViewModels/AddWorktree.cs b/src/ViewModels/AddWorktree.cs
index cf736029..6c1c7481 100644
--- a/src/ViewModels/AddWorktree.cs
+++ b/src/ViewModels/AddWorktree.cs
@@ -12,7 +12,7 @@ namespace SourceGit.ViewModels
public string Path
{
get => _path;
- set => SetProperty(ref _path, value);
+ set => SetProperty(ref _path, value, true);
}
public bool CreateNewBranch
diff --git a/src/ViewModels/ApplyStash.cs b/src/ViewModels/ApplyStash.cs
new file mode 100644
index 00000000..03ce0f43
--- /dev/null
+++ b/src/ViewModels/ApplyStash.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+
+namespace SourceGit.ViewModels
+{
+ public class ApplyStash : Popup
+ {
+ public Models.Stash Stash
+ {
+ get;
+ private set;
+ }
+
+ public bool RestoreIndex
+ {
+ get;
+ set;
+ } = true;
+
+ public bool DropAfterApply
+ {
+ get;
+ set;
+ } = false;
+
+ public ApplyStash(string repo, Models.Stash stash)
+ {
+ _repo = repo;
+ Stash = stash;
+ View = new Views.ApplyStash() { DataContext = this };
+ }
+
+ public override Task Sure()
+ {
+ ProgressDescription = $"Applying stash: {Stash.Name}";
+
+ return Task.Run(() =>
+ {
+ var succ = new Commands.Stash(_repo).Apply(Stash.Name, RestoreIndex);
+ if (succ && DropAfterApply)
+ new Commands.Stash(_repo).Drop(Stash.Name);
+
+ return true;
+ });
+ }
+
+ private readonly string _repo;
+ }
+}
diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs
index 98d3bf41..3123d62a 100644
--- a/src/ViewModels/Clone.cs
+++ b/src/ViewModels/Clone.cs
@@ -53,6 +53,12 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _extraArgs, value);
}
+ public bool InitAndUpdateSubmodules
+ {
+ get;
+ set;
+ } = true;
+
public Clone(string pageId)
{
_pageId = pageId;
@@ -127,6 +133,17 @@ namespace SourceGit.ViewModels
config.Set("remote.origin.sshkey", _sshKey);
}
+ // individually update submodule (if any)
+ if (InitAndUpdateSubmodules)
+ {
+ var submoduleList = new Commands.QuerySubmodules(path).Result();
+ foreach (var submodule in submoduleList)
+ {
+ var update = new Commands.Submodule(path);
+ update.Update(submodule.Path, true, true, false, SetProgressDescription);
+ }
+ }
+
CallUIThread(() =>
{
var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true);
diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs
index b67a453a..a9698a07 100644
--- a/src/ViewModels/CreateBranch.cs
+++ b/src/ViewModels/CreateBranch.cs
@@ -6,7 +6,7 @@ namespace SourceGit.ViewModels
public class CreateBranch : Popup
{
[Required(ErrorMessage = "Branch name is required!")]
- [RegularExpression(@"^[\w\-/\.#]+$", ErrorMessage = "Bad branch name format!")]
+ [RegularExpression(@"^[\w \-/\.#]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(CreateBranch), nameof(ValidateBranchName))]
public string Name
{
@@ -74,9 +74,10 @@ namespace SourceGit.ViewModels
if (creator == null)
return new ValidationResult("Missing runtime context to create branch!");
+ var fixedName = creator.FixName(name);
foreach (var b in creator._repo.Branches)
{
- if (b.FriendlyName == name)
+ if (b.FriendlyName == fixedName)
return new ValidationResult("A branch with same name already exists!");
}
@@ -86,6 +87,8 @@ namespace SourceGit.ViewModels
public override Task Sure()
{
_repo.SetWatcherEnabled(false);
+
+ var fixedName = FixName(_name);
return Task.Run(() =>
{
var succ = false;
@@ -114,8 +117,8 @@ namespace SourceGit.ViewModels
}
}
- SetProgressDescription($"Create new branch '{_name}'");
- succ = new Commands.Checkout(_repo.FullPath).Branch(_name, _baseOnRevision, SetProgressDescription);
+ SetProgressDescription($"Create new branch '{fixedName}'");
+ succ = new Commands.Checkout(_repo.FullPath).Branch(fixedName, _baseOnRevision, SetProgressDescription);
if (needPopStash)
{
@@ -125,15 +128,15 @@ namespace SourceGit.ViewModels
}
else
{
- SetProgressDescription($"Create new branch '{_name}'");
- succ = Commands.Branch.Create(_repo.FullPath, _name, _baseOnRevision);
+ SetProgressDescription($"Create new branch '{fixedName}'");
+ succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision);
}
CallUIThread(() =>
{
if (succ && CheckoutAfterCreated)
{
- var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{_name}" };
+ var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{fixedName}" };
if (BasedOn is Models.Branch based && !based.IsLocal)
fake.Upstream = based.FullName;
@@ -153,6 +156,15 @@ namespace SourceGit.ViewModels
});
}
+ private string FixName(string name)
+ {
+ if (!name.Contains(' '))
+ return name;
+
+ var parts = name.Split(' ', System.StringSplitOptions.RemoveEmptyEntries);
+ return string.Join("-", parts);
+ }
+
private readonly Repository _repo = null;
private string _name = null;
private readonly string _baseOnRevision = null;
diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs
index 2d907edd..d816d0b8 100644
--- a/src/ViewModels/Fetch.cs
+++ b/src/ViewModels/Fetch.cs
@@ -47,7 +47,6 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false);
var notags = _repo.Settings.FetchWithoutTags;
- var prune = _repo.Settings.EnablePruneOnFetch;
var force = _repo.Settings.EnableForceOnFetch;
return Task.Run(() =>
{
@@ -56,13 +55,13 @@ namespace SourceGit.ViewModels
foreach (var remote in _repo.Remotes)
{
SetProgressDescription($"Fetching remote: {remote.Name}");
- new Commands.Fetch(_repo.FullPath, remote.Name, notags, prune, force, SetProgressDescription).Exec();
+ new Commands.Fetch(_repo.FullPath, remote.Name, notags, force, SetProgressDescription).Exec();
}
}
else
{
SetProgressDescription($"Fetching remote: {SelectedRemote.Name}");
- new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, prune, force, SetProgressDescription).Exec();
+ new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force, SetProgressDescription).Exec();
}
CallUIThread(() =>
diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs
index ede73cd1..7e248274 100644
--- a/src/ViewModels/FileHistories.cs
+++ b/src/ViewModels/FileHistories.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using Avalonia.Collections;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
@@ -17,36 +18,14 @@ namespace SourceGit.ViewModels
public object Content { get; set; } = content;
}
- public partial class FileHistories : ObservableObject
+ public partial class FileHistoriesSingleRevision : ObservableObject
{
- public bool IsLoading
+ public bool IsDiffMode
{
- get => _isLoading;
- private set => SetProperty(ref _isLoading, value);
- }
-
- public List Commits
- {
- get => _commits;
- set => SetProperty(ref _commits, value);
- }
-
- public Models.Commit SelectedCommit
- {
- get => _selectedCommit;
+ get => _isDiffMode;
set
{
- if (SetProperty(ref _selectedCommit, value))
- RefreshViewContent();
- }
- }
-
- public bool IsViewContent
- {
- get => _isViewContent;
- set
- {
- if (SetProperty(ref _isViewContent, value))
+ if (SetProperty(ref _isDiffMode, value))
RefreshViewContent();
}
}
@@ -54,55 +33,36 @@ namespace SourceGit.ViewModels
public object ViewContent
{
get => _viewContent;
- private set => SetProperty(ref _viewContent, value);
+ set => SetProperty(ref _viewContent, value);
}
- public FileHistories(Repository repo, string file, string commit = null)
+ public FileHistoriesSingleRevision(Repository repo, string file, Models.Commit revision, bool prevIsDiffMode)
{
_repo = repo;
_file = file;
+ _revision = revision;
+ _isDiffMode = prevIsDiffMode;
+ _viewContent = null;
- Task.Run(() =>
- {
- var based = commit ?? string.Empty;
- var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {based} -- \"{file}\"", false).Result();
- Dispatcher.UIThread.Invoke(() =>
- {
- IsLoading = false;
- Commits = commits;
- if (commits.Count > 0)
- SelectedCommit = commits[0];
- });
- });
- }
-
- public void NavigateToCommit(Models.Commit commit)
- {
- _repo.NavigateToCommit(commit.SHA);
+ RefreshViewContent();
}
public void ResetToSelectedRevision()
{
- new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_selectedCommit.SHA}");
+ new Commands.Checkout(_repo.FullPath).FileWithRevision(_file, $"{_revision.SHA}");
}
private void RefreshViewContent()
{
- if (_selectedCommit == null)
- {
- ViewContent = null;
- return;
- }
-
- if (_isViewContent)
- SetViewContentAsRevisionFile();
- else
+ if (_isDiffMode)
SetViewContentAsDiff();
+ else
+ SetViewContentAsRevisionFile();
}
private void SetViewContentAsRevisionFile()
{
- var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _selectedCommit.SHA, _file).Result();
+ var objs = new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file).Result();
if (objs.Count == 0)
{
ViewContent = new FileHistoriesRevisionFile(_file, null);
@@ -115,13 +75,13 @@ namespace SourceGit.ViewModels
case Models.ObjectType.Blob:
Task.Run(() =>
{
- var isBinary = new Commands.IsBinary(_repo.FullPath, _selectedCommit.SHA, _file).Result();
+ var isBinary = new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).Result();
if (isBinary)
{
var ext = Path.GetExtension(_file);
if (IMG_EXTS.Contains(ext))
{
- var stream = Commands.QueryFileContent.Run(_repo.FullPath, _selectedCommit.SHA, _file);
+ var stream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file);
var fileSize = stream.Length;
var bitmap = fileSize > 0 ? new Bitmap(stream) : null;
var imageType = Path.GetExtension(_file).TrimStart('.').ToUpper(CultureInfo.CurrentCulture);
@@ -130,7 +90,7 @@ namespace SourceGit.ViewModels
}
else
{
- var size = new Commands.QueryFileSize(_repo.FullPath, _file, _selectedCommit.SHA).Result();
+ var size = new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).Result();
var binaryFile = new Models.RevisionBinaryFile() { Size = size };
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, binaryFile));
}
@@ -138,7 +98,7 @@ namespace SourceGit.ViewModels
return;
}
- var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _selectedCommit.SHA, _file);
+ var contentStream = Commands.QueryFileContent.Run(_repo.FullPath, _revision.SHA, _file);
var content = new StreamReader(contentStream).ReadToEnd();
var matchLFS = REG_LFS_FORMAT().Match(content);
if (matchLFS.Success)
@@ -181,7 +141,7 @@ namespace SourceGit.ViewModels
private void SetViewContentAsDiff()
{
- var option = new Models.DiffOption(_selectedCommit, _file);
+ var option = new Models.DiffOption(_revision, _file);
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
}
@@ -193,12 +153,155 @@ namespace SourceGit.ViewModels
".ico", ".bmp", ".jpg", ".png", ".jpeg", ".webp"
};
+ private Repository _repo = null;
+ private string _file = null;
+ private Models.Commit _revision = null;
+ private bool _isDiffMode = true;
+ private object _viewContent = null;
+ }
+
+ public class FileHistoriesCompareRevisions : ObservableObject
+ {
+ public Models.Commit StartPoint
+ {
+ get => _startPoint;
+ set => SetProperty(ref _startPoint, value);
+ }
+
+ public Models.Commit EndPoint
+ {
+ get => _endPoint;
+ set => SetProperty(ref _endPoint, value);
+ }
+
+ public DiffContext ViewContent
+ {
+ get => _viewContent;
+ set => SetProperty(ref _viewContent, value);
+ }
+
+ public FileHistoriesCompareRevisions(Repository repo, string file, Models.Commit start, Models.Commit end)
+ {
+ _repo = repo;
+ _file = file;
+ _startPoint = start;
+ _endPoint = end;
+ RefreshViewContent();
+ }
+
+ public void Swap()
+ {
+ (StartPoint, EndPoint) = (_endPoint, _startPoint);
+ RefreshViewContent();
+ }
+
+ public Task SaveAsPatch(string saveTo)
+ {
+ return Task.Run(() =>
+ {
+ Commands.SaveChangesAsPatch.ProcessRevisionCompareChanges(_repo.FullPath, _changes, _startPoint.SHA, _endPoint.SHA, saveTo);
+ return true;
+ });
+ }
+
+ private void RefreshViewContent()
+ {
+ Task.Run(() =>
+ {
+ _changes = new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).Result();
+ if (_changes.Count == 0)
+ {
+ Dispatcher.UIThread.Invoke(() => ViewContent = null);
+ return;
+ }
+
+ var option = new Models.DiffOption(_startPoint.SHA, _endPoint.SHA, _changes[0]);
+ Dispatcher.UIThread.Invoke(() => ViewContent = new DiffContext(_repo.FullPath, option, _viewContent));
+ });
+ }
+
+ private Repository _repo = null;
+ private string _file = null;
+ private Models.Commit _startPoint = null;
+ private Models.Commit _endPoint = null;
+ private List _changes = [];
+ private DiffContext _viewContent = null;
+ }
+
+ public class FileHistories : ObservableObject
+ {
+ public bool IsLoading
+ {
+ get => _isLoading;
+ private set => SetProperty(ref _isLoading, value);
+ }
+
+ public List Commits
+ {
+ get => _commits;
+ set => SetProperty(ref _commits, value);
+ }
+
+ public AvaloniaList SelectedCommits
+ {
+ get;
+ set;
+ } = [];
+
+ public object ViewContent
+ {
+ get => _viewContent;
+ private set => SetProperty(ref _viewContent, value);
+ }
+
+ public FileHistories(Repository repo, string file, string commit = null)
+ {
+ _repo = repo;
+ _file = file;
+
+ Task.Run(() =>
+ {
+ var based = commit ?? string.Empty;
+ var commits = new Commands.QueryCommits(_repo.FullPath, $"--date-order -n 10000 {based} -- \"{file}\"", false).Result();
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ IsLoading = false;
+ Commits = commits;
+ if (Commits.Count > 0)
+ SelectedCommits.Add(Commits[0]);
+ });
+ });
+
+ SelectedCommits.CollectionChanged += (_, _) =>
+ {
+ if (_viewContent is FileHistoriesSingleRevision singleRevision)
+ _prevIsDiffMode = singleRevision.IsDiffMode;
+
+ switch (SelectedCommits.Count)
+ {
+ case 1:
+ ViewContent = new FileHistoriesSingleRevision(_repo, _file, SelectedCommits[0], _prevIsDiffMode);
+ break;
+ case 2:
+ ViewContent = new FileHistoriesCompareRevisions(_repo, _file, SelectedCommits[0], SelectedCommits[1]);
+ break;
+ default:
+ ViewContent = SelectedCommits.Count;
+ break;
+ }
+ };
+ }
+
+ public void NavigateToCommit(Models.Commit commit)
+ {
+ _repo.NavigateToCommit(commit.SHA);
+ }
+
private readonly Repository _repo = null;
private readonly string _file = null;
private bool _isLoading = true;
+ private bool _prevIsDiffMode = true;
private List _commits = null;
- private Models.Commit _selectedCommit = null;
- private bool _isViewContent = false;
private object _viewContent = null;
}
}
diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs
index b5754d33..03f216de 100644
--- a/src/ViewModels/Histories.cs
+++ b/src/ViewModels/Histories.cs
@@ -285,7 +285,7 @@ namespace SourceGit.ViewModels
if (canCherryPick || canMerge)
multipleMenu.Items.Add(new MenuItem() { Header = "-" });
- }
+ }
var saveToPatchMultiple = new MenuItem();
saveToPatchMultiple.Icon = App.CreateMenuIcon("Icons.Diff");
@@ -589,7 +589,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(interactiveRebase);
menu.Items.Add(new MenuItem() { Header = "-" });
}
- }
+ }
if (current.Head != commit.SHA)
{
@@ -937,7 +937,7 @@ namespace SourceGit.ViewModels
submenu.Items.Add(finish);
submenu.Items.Add(new MenuItem() { Header = "-" });
}
- }
+ }
var copy = new MenuItem();
copy.Header = App.Text("BranchCM.CopyName");
@@ -983,7 +983,7 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
submenu.Items.Add(merge);
- }
+ }
var rename = new MenuItem();
rename.Header = new Views.NameHighlightedTextBlock("BranchCM.Rename", branch.Name);
@@ -1025,7 +1025,7 @@ namespace SourceGit.ViewModels
submenu.Items.Add(finish);
submenu.Items.Add(new MenuItem() { Header = "-" });
}
- }
+ }
var copy = new MenuItem();
copy.Header = App.Text("BranchCM.CopyName");
@@ -1131,7 +1131,7 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
submenu.Items.Add(merge);
- }
+ }
var delete = new MenuItem();
delete.Header = new Views.NameHighlightedTextBlock("TagCM.Delete", tag.Name);
diff --git a/src/ViewModels/InProgressContexts.cs b/src/ViewModels/InProgressContexts.cs
index 6099c2b9..2892b7cc 100644
--- a/src/ViewModels/InProgressContexts.cs
+++ b/src/ViewModels/InProgressContexts.cs
@@ -107,8 +107,12 @@ namespace SourceGit.ViewModels
{
_gitDir = repo.GitDir;
- var stoppedSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "stopped-sha")).Trim();
- StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).Result() ?? new Models.Commit() { SHA = stoppedSHA };
+ var stoppedSHAPath = Path.Combine(repo.GitDir, "rebase-merge", "stopped-sha");
+ if (File.Exists(stoppedSHAPath))
+ {
+ var stoppedSHA = File.ReadAllText(stoppedSHAPath).Trim();
+ StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).Result() ?? new Models.Commit() { SHA = stoppedSHA };
+ }
var ontoSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "onto")).Trim();
Onto = new Commands.QuerySingleCommit(repo.FullPath, ontoSHA).Result() ?? new Models.Commit() { SHA = ontoSHA };
diff --git a/src/ViewModels/LauncherPage.cs b/src/ViewModels/LauncherPage.cs
index 498c1865..b8138aca 100644
--- a/src/ViewModels/LauncherPage.cs
+++ b/src/ViewModels/LauncherPage.cs
@@ -59,7 +59,9 @@ namespace SourceGit.ViewModels
public void StartPopup(Popup popup)
{
Popup = popup;
- ProcessPopup();
+
+ if (popup.CanStartDirectly())
+ ProcessPopup();
}
public async void ProcessPopup()
diff --git a/src/ViewModels/Merge.cs b/src/ViewModels/Merge.cs
index d07ee9b7..174bb1e1 100644
--- a/src/ViewModels/Merge.cs
+++ b/src/ViewModels/Merge.cs
@@ -61,9 +61,9 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
- var succ = new Commands.Merge(_repo.FullPath, _sourceName, SelectedMode.Arg, SetProgressDescription).Exec();
+ new Commands.Merge(_repo.FullPath, _sourceName, SelectedMode.Arg, SetProgressDescription).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true));
- return succ;
+ return true;
});
}
diff --git a/src/ViewModels/Popup.cs b/src/ViewModels/Popup.cs
index ff74df51..98a12ca2 100644
--- a/src/ViewModels/Popup.cs
+++ b/src/ViewModels/Popup.cs
@@ -37,6 +37,11 @@ namespace SourceGit.ViewModels
return !HasErrors;
}
+ public virtual bool CanStartDirectly()
+ {
+ return true;
+ }
+
public virtual Task Sure()
{
return null;
diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs
index e7c62980..52f98d87 100644
--- a/src/ViewModels/Pull.cs
+++ b/src/ViewModels/Pull.cs
@@ -151,7 +151,6 @@ namespace SourceGit.ViewModels
_repo.FullPath,
_selectedRemote.Name,
NoTags,
- _repo.Settings.EnablePruneOnFetch,
false,
SetProgressDescription).Exec();
@@ -184,7 +183,6 @@ namespace SourceGit.ViewModels
_selectedBranch.Name,
UseRebase,
NoTags,
- _repo.Settings.EnablePruneOnFetch,
SetProgressDescription).Exec();
}
diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs
index 004ae7b6..1f18b38e 100644
--- a/src/ViewModels/Push.cs
+++ b/src/ViewModels/Push.cs
@@ -152,6 +152,11 @@ namespace SourceGit.ViewModels
View = new Views.Push() { DataContext = this };
}
+ public override bool CanStartDirectly()
+ {
+ return !string.IsNullOrEmpty(_selectedRemoteBranch?.Head);
+ }
+
public override Task Sure()
{
_repo.SetWatcherEnabled(false);
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index 7f619f1e..44de8c35 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -733,12 +733,15 @@ namespace SourceGit.ViewModels
visible.Add(commit);
break;
case 1:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByUser, _onlySearchCommitsInCurrentBranch).Result();
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByAuthor, _onlySearchCommitsInCurrentBranch).Result();
break;
case 2:
- visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByMessage, _onlySearchCommitsInCurrentBranch).Result();
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByCommitter, _onlySearchCommitsInCurrentBranch).Result();
break;
case 3:
+ visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByMessage, _onlySearchCommitsInCurrentBranch).Result();
+ break;
+ case 4:
visible = new Commands.QueryCommits(_fullpath, _searchCommitFilter, Models.CommitSearchMethod.ByFile, _onlySearchCommitsInCurrentBranch).Result();
break;
}
@@ -936,7 +939,7 @@ namespace SourceGit.ViewModels
RemoteBranchTrees = builder.Remotes;
if (_workingCopy != null)
- _workingCopy.CanCommitWithPush = _currentBranch != null && !string.IsNullOrEmpty(_currentBranch.Upstream);
+ _workingCopy.HasRemotes = remotes.Count > 0;
});
}
@@ -1196,6 +1199,25 @@ namespace SourceGit.ViewModels
App.GetLauncer()?.OpenRepositoryInTab(node, null);
}
+ public AvaloniaList GetPreferedOpenAIServices()
+ {
+ var services = Preferences.Instance.OpenAIServices;
+ if (services == null || services.Count == 0)
+ return [];
+
+ if (services.Count == 1)
+ return services;
+
+ var prefered = _settings.PreferedOpenAIService;
+ foreach (var service in services)
+ {
+ if (service.Name.Equals(prefered, StringComparison.Ordinal))
+ return [service];
+ }
+
+ return services;
+ }
+
public ContextMenu CreateContextMenuForGitFlow()
{
var menu = new ContextMenu();
@@ -1497,7 +1519,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(fastForward);
menu.Items.Add(pull);
}
- }
+ }
menu.Items.Add(push);
}
@@ -1515,7 +1537,7 @@ namespace SourceGit.ViewModels
};
menu.Items.Add(checkout);
menu.Items.Add(new MenuItem() { Header = "-" });
- }
+ }
var worktree = _worktrees.Find(x => x.Branch == branch.FullName);
var upstream = _branches.Find(x => x.FullName == branch.Upstream);
@@ -1574,7 +1596,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(merge);
menu.Items.Add(rebase);
- }
+ }
var compareWithHead = new MenuItem();
compareWithHead.Header = App.Text("BranchCM.CompareWithHead");
@@ -1626,7 +1648,7 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(finish);
}
- }
+ }
var rename = new MenuItem();
rename.Header = new Views.NameHighlightedTextBlock("BranchCM.Rename", branch.Name);
@@ -1699,7 +1721,7 @@ namespace SourceGit.ViewModels
};
menu.Items.Add(tracking);
}
- }
+ }
var archive = new MenuItem();
archive.Icon = App.CreateMenuIcon("Icons.Archive");
@@ -2358,7 +2380,7 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Invoke(() => IsAutoFetching = true);
foreach (var remote in remotes)
- new Commands.Fetch(_fullpath, remote, false, _settings.EnablePruneOnFetch, false, null) { RaiseError = false }.Exec();
+ new Commands.Fetch(_fullpath, remote, false, false, null) { RaiseError = false }.Exec();
_lastFetchTime = DateTime.Now;
Dispatcher.UIThread.Invoke(() => IsAutoFetching = false);
}
@@ -2382,7 +2404,7 @@ namespace SourceGit.ViewModels
private bool _isSearching = false;
private bool _isSearchLoadingVisible = false;
private bool _isSearchCommitSuggestionOpen = false;
- private int _searchCommitFilterType = 2;
+ private int _searchCommitFilterType = 3;
private bool _onlySearchCommitsInCurrentBranch = false;
private string _searchCommitFilter = string.Empty;
private List _searchedCommits = new List();
diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs
index 88a485bc..a7c04937 100644
--- a/src/ViewModels/RepositoryConfigure.cs
+++ b/src/ViewModels/RepositoryConfigure.cs
@@ -60,18 +60,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value);
}
- public bool EnableSignOffForCommit
- {
- get => _repo.Settings.EnableSignOffForCommit;
- set => _repo.Settings.EnableSignOffForCommit = value;
- }
-
- public bool EnablePruneOnFetch
- {
- get => _repo.Settings.EnablePruneOnFetch;
- set => _repo.Settings.EnablePruneOnFetch = value;
- }
-
public bool EnableAutoFetch
{
get => _repo.Settings.EnableAutoFetch;
diff --git a/src/ViewModels/StashChanges.cs b/src/ViewModels/StashChanges.cs
index fd937bdd..33ebb1f3 100644
--- a/src/ViewModels/StashChanges.cs
+++ b/src/ViewModels/StashChanges.cs
@@ -36,6 +36,12 @@ namespace SourceGit.ViewModels
set => _repo.Settings.KeepIndexWhenStash = value;
}
+ public bool AutoRestore
+ {
+ get => _repo.Settings.AutoRestoreAfterStash;
+ set => _repo.Settings.AutoRestoreAfterStash = value;
+ }
+
public StashChanges(Repository repo, List changes, bool hasSelectedFiles)
{
_repo = repo;
@@ -84,6 +90,9 @@ namespace SourceGit.ViewModels
succ = StashWithChanges(_changes);
}
+ if (AutoRestore && succ)
+ succ = new Commands.Stash(_repo.FullPath).Apply("stash@{0}", true);
+
CallUIThread(() =>
{
_repo.MarkWorkingCopyDirtyManually();
diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs
index e5755a91..4a3bf933 100644
--- a/src/ViewModels/StashesPage.cs
+++ b/src/ViewModels/StashesPage.cs
@@ -141,15 +141,9 @@ namespace SourceGit.ViewModels
apply.Header = App.Text("StashCM.Apply");
apply.Click += (_, ev) =>
{
- Task.Run(() => new Commands.Stash(_repo.FullPath).Apply(stash.Name));
- ev.Handled = true;
- };
+ if (_repo.CanCreatePopup())
+ _repo.ShowPopup(new ApplyStash(_repo.FullPath, stash));
- var pop = new MenuItem();
- pop.Header = App.Text("StashCM.Pop");
- pop.Click += (_, ev) =>
- {
- Task.Run(() => new Commands.Stash(_repo.FullPath).Pop(stash.Name));
ev.Handled = true;
};
@@ -165,7 +159,6 @@ namespace SourceGit.ViewModels
var menu = new ContextMenu();
menu.Items.Add(apply);
- menu.Items.Add(pop);
menu.Items.Add(drop);
return menu;
}
diff --git a/src/ViewModels/UpdateSubmodules.cs b/src/ViewModels/UpdateSubmodules.cs
index d1c433e2..3553d1b5 100644
--- a/src/ViewModels/UpdateSubmodules.cs
+++ b/src/ViewModels/UpdateSubmodules.cs
@@ -56,25 +56,24 @@ namespace SourceGit.ViewModels
{
_repo.SetWatcherEnabled(false);
- string target = string.Empty;
+ List targets;
if (_updateAll)
- {
- ProgressDescription = "Updating submodules ...";
- }
+ targets = Submodules;
else
- {
- target = SelectedSubmodule;
- ProgressDescription = $"Updating submodule {target} ...";
- }
+ targets = [SelectedSubmodule];
return Task.Run(() =>
{
- new Commands.Submodule(_repo.FullPath).Update(
- target,
- EnableInit,
- EnableRecursive,
- EnableRemote,
- SetProgressDescription);
+ foreach (var submodule in targets)
+ {
+ ProgressDescription = $"Updating submodule {submodule} ...";
+ new Commands.Submodule(_repo.FullPath).Update(
+ submodule,
+ EnableInit,
+ EnableRecursive,
+ EnableRemote,
+ SetProgressDescription);
+ }
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs
index 67fd6990..ecb3c0d6 100644
--- a/src/ViewModels/WorkingCopy.cs
+++ b/src/ViewModels/WorkingCopy.cs
@@ -26,10 +26,10 @@ namespace SourceGit.ViewModels
}
}
- public bool CanCommitWithPush
+ public bool HasRemotes
{
- get => _canCommitWithPush;
- set => SetProperty(ref _canCommitWithPush, value);
+ get => _hasRemotes;
+ set => SetProperty(ref _hasRemotes, value);
}
public bool HasUnsolvedConflicts
@@ -62,6 +62,12 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _isCommitting, value);
}
+ public bool EnableSignOff
+ {
+ get => _repo.Settings.EnableSignOffForCommit;
+ set => _repo.Settings.EnableSignOffForCommit = value;
+ }
+
public bool UseAmend
{
get => _useAmend;
@@ -93,12 +99,34 @@ namespace SourceGit.ViewModels
}
}
+ public string UnstagedFilter
+ {
+ get => _unstagedFilter;
+ set
+ {
+ if (SetProperty(ref _unstagedFilter, value))
+ {
+ if (_isLoadingData)
+ return;
+
+ VisibleUnstaged = GetVisibleUnstagedChanges();
+ SelectedUnstaged = [];
+ }
+ }
+ }
+
public List Unstaged
{
get => _unstaged;
private set => SetProperty(ref _unstaged, value);
}
+ public List VisibleUnstaged
+ {
+ get => _visibleUnstaged;
+ private set => SetProperty(ref _visibleUnstaged, value);
+ }
+
public List Staged
{
get => _staged;
@@ -185,8 +213,9 @@ namespace SourceGit.ViewModels
_selectedStaged.Clear();
OnPropertyChanged(nameof(SelectedStaged));
+ _visibleUnstaged.Clear();
_unstaged.Clear();
- OnPropertyChanged(nameof(Unstaged));
+ OnPropertyChanged(nameof(VisibleUnstaged));
_staged.Clear();
OnPropertyChanged(nameof(Staged));
@@ -212,7 +241,7 @@ namespace SourceGit.ViewModels
var inProgress = null as InProgressContext;
if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD")))
inProgress = new CherryPickInProgress(_repo);
- else if (File.Exists(Path.Combine(_repo.GitDir, "REBASE_HEAD")) && Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge")))
+ else if (Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge")) || Directory.Exists(Path.Combine(_repo.GitDir, "rebase-apply")))
inProgress = new RebaseInProgress(_repo);
else if (File.Exists(Path.Combine(_repo.GitDir, "REVERT_HEAD")))
inProgress = new RevertInProgress(_repo);
@@ -243,7 +272,6 @@ namespace SourceGit.ViewModels
}
var unstaged = new List();
- var selectedUnstaged = new List();
var hasConflict = false;
foreach (var c in changes)
{
@@ -251,12 +279,19 @@ namespace SourceGit.ViewModels
{
unstaged.Add(c);
hasConflict |= c.IsConflit;
-
- if (lastSelectedUnstaged.Contains(c.Path))
- selectedUnstaged.Add(c);
}
}
+ _unstaged = unstaged;
+
+ var visibleUnstaged = GetVisibleUnstagedChanges();
+ var selectedUnstaged = new List();
+ foreach (var c in visibleUnstaged)
+ {
+ if (lastSelectedUnstaged.Contains(c.Path))
+ selectedUnstaged.Add(c);
+ }
+
var staged = GetStagedChanges();
var selectedStaged = new List();
foreach (var c in staged)
@@ -269,7 +304,7 @@ namespace SourceGit.ViewModels
{
_isLoadingData = true;
HasUnsolvedConflicts = hasConflict;
- Unstaged = unstaged;
+ VisibleUnstaged = visibleUnstaged;
Staged = staged;
SelectedUnstaged = selectedUnstaged;
SelectedStaged = selectedStaged;
@@ -285,7 +320,7 @@ namespace SourceGit.ViewModels
var inProgress = null as InProgressContext;
if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD")))
inProgress = new CherryPickInProgress(_repo);
- else if (File.Exists(Path.Combine(_repo.GitDir, "REBASE_HEAD")) && Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge")))
+ else if (Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge")) || Directory.Exists(Path.Combine(_repo.GitDir, "rebase-apply")))
inProgress = new RebaseInProgress(_repo);
else if (File.Exists(Path.Combine(_repo.GitDir, "REVERT_HEAD")))
inProgress = new RevertInProgress(_repo);
@@ -330,46 +365,7 @@ namespace SourceGit.ViewModels
public void StageAll()
{
- StageChanges(_unstaged, null);
- }
-
- public async void StageChanges(List changes, Models.Change next)
- {
- if (_unstaged.Count == 0 || changes.Count == 0)
- return;
-
- // Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
- _selectedUnstaged = next != null ? [next] : [];
-
- IsStaging = true;
- _repo.SetWatcherEnabled(false);
- if (changes.Count == _unstaged.Count)
- {
- 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();
- 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)
- {
- var count = Math.Min(10, changes.Count - i);
- var step = changes.GetRange(i, count);
- await Task.Run(() => new Commands.Add(_repo.FullPath, step).Exec());
- }
- }
- _repo.MarkWorkingCopyDirtyManually();
- _repo.SetWatcherEnabled(true);
- IsStaging = false;
+ StageChanges(_visibleUnstaged, null);
}
public void UnstageSelected(Models.Change next)
@@ -382,47 +378,15 @@ namespace SourceGit.ViewModels
UnstageChanges(_staged, null);
}
- public async void UnstageChanges(List changes, Models.Change next)
- {
- if (_staged.Count == 0 || changes.Count == 0)
- return;
-
- // Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
- _selectedStaged = next != null ? [next] : [];
-
- IsUnstaging = true;
- _repo.SetWatcherEnabled(false);
- if (_useAmend)
- {
- await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
- }
- else if (changes.Count == _staged.Count)
- {
- await Task.Run(() => new Commands.Reset(_repo.FullPath).Exec());
- }
- else
- {
- for (int i = 0; i < changes.Count; i += 10)
- {
- var count = Math.Min(10, changes.Count - i);
- var step = changes.GetRange(i, count);
- await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Exec());
- }
- }
- _repo.MarkWorkingCopyDirtyManually();
- _repo.SetWatcherEnabled(true);
- IsUnstaging = false;
- }
-
public void Discard(List changes)
{
if (_repo.CanCreatePopup())
- {
- if (changes.Count == _unstaged.Count && _staged.Count == 0)
- _repo.ShowPopup(new Discard(_repo));
- else
- _repo.ShowPopup(new Discard(_repo, changes));
- }
+ _repo.ShowPopup(new Discard(_repo, changes));
+ }
+
+ public void ClearUnstagedFilter()
+ {
+ UnstagedFilter = string.Empty;
}
public async void UseTheirs(List changes)
@@ -1067,7 +1031,7 @@ namespace SourceGit.ViewModels
var menu = new ContextMenu();
var ai = null as MenuItem;
- var services = GetPreferedOpenAIServices();
+ var services = _repo.GetPreferedOpenAIServices();
if (services.Count > 0)
{
ai = new MenuItem();
@@ -1078,7 +1042,7 @@ namespace SourceGit.ViewModels
{
ai.Click += (_, e) =>
{
- var dialog = new Views.AIAssistant(services[0], _repo.FullPath, _selectedStaged, generated => CommitMessage = generated);
+ var dialog = new Views.AIAssistant(services[0], _repo.FullPath, this, _selectedStaged);
App.OpenDialog(dialog);
e.Handled = true;
};
@@ -1093,7 +1057,7 @@ namespace SourceGit.ViewModels
item.Header = service.Name;
item.Click += (_, e) =>
{
- var dialog = new Views.AIAssistant(dup, _repo.FullPath, _selectedStaged, generated => CommitMessage = generated);
+ var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _selectedStaged);
App.OpenDialog(dialog);
e.Handled = true;
};
@@ -1458,7 +1422,7 @@ namespace SourceGit.ViewModels
return null;
}
- var services = GetPreferedOpenAIServices();
+ var services = _repo.GetPreferedOpenAIServices();
if (services.Count == 0)
{
App.RaiseException(_repo.FullPath, "Bad configuration for OpenAI");
@@ -1467,7 +1431,7 @@ namespace SourceGit.ViewModels
if (services.Count == 1)
{
- var dialog = new Views.AIAssistant(services[0], _repo.FullPath, _staged, generated => CommitMessage = generated);
+ var dialog = new Views.AIAssistant(services[0], _repo.FullPath, this, _staged);
App.OpenDialog(dialog);
return null;
}
@@ -1483,7 +1447,7 @@ namespace SourceGit.ViewModels
item.Header = service.Name;
item.Click += (_, e) =>
{
- var dialog = new Views.AIAssistant(dup, _repo.FullPath, _staged, generated => CommitMessage = generated);
+ var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _staged);
App.OpenDialog(dialog);
e.Handled = true;
};
@@ -1495,6 +1459,22 @@ namespace SourceGit.ViewModels
}
}
+ private List GetVisibleUnstagedChanges()
+ {
+ if (string.IsNullOrEmpty(_unstagedFilter))
+ return _unstaged;
+
+ var visible = new List();
+
+ foreach (var c in _unstaged)
+ {
+ if (c.Path.Contains(_unstagedFilter, StringComparison.OrdinalIgnoreCase))
+ visible.Add(c);
+ }
+
+ return visible;
+ }
+
private List GetStagedChanges()
{
if (_useAmend)
@@ -1510,6 +1490,77 @@ namespace SourceGit.ViewModels
return rs;
}
+ private async void StageChanges(List changes, Models.Change next)
+ {
+ if (changes.Count == 0)
+ return;
+
+ // Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
+ _selectedUnstaged = next != null ? [next] : [];
+
+ IsStaging = true;
+ _repo.SetWatcherEnabled(false);
+ if (changes.Count == _unstaged.Count)
+ {
+ 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();
+ 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)
+ {
+ var count = Math.Min(10, changes.Count - i);
+ var step = changes.GetRange(i, count);
+ await Task.Run(() => new Commands.Add(_repo.FullPath, step).Exec());
+ }
+ }
+ _repo.MarkWorkingCopyDirtyManually();
+ _repo.SetWatcherEnabled(true);
+ IsStaging = false;
+ }
+
+ private async void UnstageChanges(List changes, Models.Change next)
+ {
+ if (changes.Count == 0)
+ return;
+
+ // Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
+ _selectedStaged = next != null ? [next] : [];
+
+ IsUnstaging = true;
+ _repo.SetWatcherEnabled(false);
+ if (_useAmend)
+ {
+ await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
+ }
+ else if (changes.Count == _staged.Count)
+ {
+ await Task.Run(() => new Commands.Reset(_repo.FullPath).Exec());
+ }
+ else
+ {
+ for (int i = 0; i < changes.Count; i += 10)
+ {
+ var count = Math.Min(10, changes.Count - i);
+ var step = changes.GetRange(i, count);
+ await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Exec());
+ }
+ }
+ _repo.MarkWorkingCopyDirtyManually();
+ _repo.SetWatcherEnabled(true);
+ IsUnstaging = false;
+ }
+
private void SetDetail(Models.Change change, bool isUnstaged)
{
if (_isLoadingData)
@@ -1599,39 +1650,22 @@ namespace SourceGit.ViewModels
return false;
}
- private IList GetPreferedOpenAIServices()
- {
- var services = Preferences.Instance.OpenAIServices;
- if (services == null || services.Count == 0)
- return [];
-
- if (services.Count == 1)
- return services;
-
- var prefered = _repo.Settings.PreferedOpenAIService;
- foreach (var service in services)
- {
- if (service.Name.Equals(prefered, StringComparison.Ordinal))
- return [service];
- }
-
- return services;
- }
-
private Repository _repo = null;
private bool _isLoadingData = false;
private bool _isStaging = false;
private bool _isUnstaging = false;
private bool _isCommitting = false;
private bool _useAmend = false;
- private bool _canCommitWithPush = false;
+ private bool _hasRemotes = false;
private List _cached = [];
private List _unstaged = [];
+ private List _visibleUnstaged = [];
private List _staged = [];
private List _selectedUnstaged = [];
private List _selectedStaged = [];
private int _count = 0;
private object _detailContext = null;
+ private string _unstagedFilter = string.Empty;
private string _commitMessage = string.Empty;
private bool _hasUnsolvedConflicts = false;
diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml
index a273b240..e07c3a3e 100644
--- a/src/Views/AIAssistant.axaml
+++ b/src/Views/AIAssistant.axaml
@@ -10,7 +10,7 @@
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.AIAssistant}"
- Width="400" SizeToContent="Height"
+ Width="520" SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterOwner">
@@ -36,18 +36,33 @@
IsVisible="{OnPlatform True, macOS=False}"/>
-
-
+
+
-
-
+
+
+
+
+
+
+
+
diff --git a/src/Views/AIAssistant.axaml.cs b/src/Views/AIAssistant.axaml.cs
index d81335eb..73fea708 100644
--- a/src/Views/AIAssistant.axaml.cs
+++ b/src/Views/AIAssistant.axaml.cs
@@ -2,27 +2,111 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Media;
using Avalonia.Threading;
+using AvaloniaEdit;
+using AvaloniaEdit.Document;
+using AvaloniaEdit.Editing;
+using AvaloniaEdit.TextMate;
namespace SourceGit.Views
{
+ public class AIResponseView : TextEditor
+ {
+ protected override Type StyleKeyOverride => typeof(TextEditor);
+
+ public AIResponseView() : base(new TextArea(), new TextDocument())
+ {
+ IsReadOnly = true;
+ ShowLineNumbers = false;
+ WordWrap = true;
+ HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
+ VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
+
+ TextArea.TextView.Margin = new Thickness(4, 0);
+ TextArea.TextView.Options.EnableHyperlinks = false;
+ TextArea.TextView.Options.EnableEmailHyperlinks = false;
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+
+ TextArea.TextView.ContextRequested += OnTextViewContextRequested;
+
+ if (_textMate == null)
+ {
+ _textMate = Models.TextMateHelper.CreateForEditor(this);
+ Models.TextMateHelper.SetGrammarByFileName(_textMate, "README.md");
+ }
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs e)
+ {
+ base.OnUnloaded(e);
+
+ TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
+
+ if (_textMate != null)
+ {
+ _textMate.Dispose();
+ _textMate = null;
+ }
+
+ GC.Collect();
+ }
+
+ private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
+ {
+ var selected = SelectedText;
+ if (string.IsNullOrEmpty(selected))
+ return;
+
+ var copy = new MenuItem() { Header = App.Text("Copy") };
+ copy.Click += (_, ev) =>
+ {
+ App.CopyText(selected);
+ ev.Handled = true;
+ };
+
+ if (this.FindResource("Icons.Copy") is Geometry geo)
+ {
+ copy.Icon = new Avalonia.Controls.Shapes.Path()
+ {
+ Width = 10,
+ Height = 10,
+ Stretch = Stretch.Uniform,
+ Data = geo,
+ };
+ }
+
+ var menu = new ContextMenu();
+ menu.Items.Add(copy);
+ menu.Open(TextArea.TextView);
+
+ e.Handled = true;
+ }
+
+ private TextMate.Installation _textMate = null;
+ }
+
public partial class AIAssistant : ChromelessWindow
{
public AIAssistant()
{
- _cancel = new CancellationTokenSource();
InitializeComponent();
}
- public AIAssistant(Models.OpenAIService service, string repo, List changes, Action onDone)
+ public AIAssistant(Models.OpenAIService service, string repo, ViewModels.WorkingCopy wc, List changes)
{
_service = service;
_repo = repo;
+ _wc = wc;
_changes = changes;
- _onDone = onDone;
- _cancel = new CancellationTokenSource();
InitializeComponent();
}
@@ -30,39 +114,63 @@ namespace SourceGit.Views
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
-
- if (string.IsNullOrEmpty(_repo))
- return;
-
- Task.Run(() =>
- {
- var message = new Commands.GenerateCommitMessage(_service, _repo, _changes, _cancel.Token, SetDescription).Result();
- if (_cancel.IsCancellationRequested)
- return;
-
- Dispatcher.UIThread.Invoke(() =>
- {
- _onDone?.Invoke(message);
- Close();
- });
- }, _cancel.Token);
+ Generate();
}
protected override void OnClosing(WindowClosingEventArgs e)
{
base.OnClosing(e);
- _cancel.Cancel();
+ _cancel?.Cancel();
}
- private void SetDescription(string message)
+ private void OnGenerateCommitMessage(object sender, RoutedEventArgs e)
{
- Dispatcher.UIThread.Invoke(() => ProgressMessage.Text = message);
+ if (_wc != null)
+ _wc.CommitMessage = TxtResponse.Text;
+
+ Close();
+ }
+
+ private void OnRegen(object sender, RoutedEventArgs e)
+ {
+ TxtResponse.Text = string.Empty;
+ Generate();
+ e.Handled = true;
+ }
+
+ private void Generate()
+ {
+ if (_repo == null)
+ return;
+
+ IconInProgress.IsVisible = true;
+ BtnGenerateCommitMessage.IsEnabled = false;
+ BtnRegenerate.IsEnabled = false;
+
+ _cancel = new CancellationTokenSource();
+ Task.Run(() =>
+ {
+ new Commands.GenerateCommitMessage(_service, _repo, _changes, _cancel.Token, message =>
+ {
+ Dispatcher.UIThread.Invoke(() => TxtResponse.Text = message);
+ }).Exec();
+
+ if (!_cancel.IsCancellationRequested)
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ IconInProgress.IsVisible = false;
+ BtnGenerateCommitMessage.IsEnabled = true;
+ BtnRegenerate.IsEnabled = true;
+ });
+ }
+ }, _cancel.Token);
}
private Models.OpenAIService _service;
private string _repo;
+ private ViewModels.WorkingCopy _wc;
private List _changes;
- private Action _onDone;
private CancellationTokenSource _cancel;
}
}
diff --git a/src/Views/ApplyStash.axaml b/src/Views/ApplyStash.axaml
new file mode 100644
index 00000000..44a97f42
--- /dev/null
+++ b/src/Views/ApplyStash.axaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/ApplyStash.axaml.cs b/src/Views/ApplyStash.axaml.cs
new file mode 100644
index 00000000..07b6cf90
--- /dev/null
+++ b/src/Views/ApplyStash.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia.Controls;
+
+namespace SourceGit.Views
+{
+ public partial class ApplyStash : UserControl
+ {
+ public ApplyStash()
+ {
+ InitializeComponent();
+ }
+ }
+}
+
diff --git a/src/Views/Clone.axaml b/src/Views/Clone.axaml
index 25c46a00..84d1dd74 100644
--- a/src/Views/Clone.axaml
+++ b/src/Views/Clone.axaml
@@ -10,7 +10,7 @@
-
+
+
+
diff --git a/src/Views/CommitMessagePresenter.cs b/src/Views/CommitMessagePresenter.cs
index 0ae3d6cf..dc8a3bb7 100644
--- a/src/Views/CommitMessagePresenter.cs
+++ b/src/Views/CommitMessagePresenter.cs
@@ -15,7 +15,7 @@ namespace SourceGit.Views
{
public partial class CommitMessagePresenter : SelectableTextBlock
{
- [GeneratedRegex(@"\b([0-9a-fA-F]{10,40})\b")]
+ [GeneratedRegex(@"\b([0-9a-fA-F]{6,40})\b")]
private static partial Regex REG_SHA_FORMAT();
public static readonly StyledProperty MessageProperty =
@@ -172,8 +172,11 @@ namespace SourceGit.Views
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
+ var point = e.GetCurrentPoint(this);
+
if (_lastHover != null)
{
+ var link = _lastHover.Link;
e.Pointer.Capture(null);
if (_lastHover.IsCommitSHA)
@@ -181,9 +184,6 @@ namespace SourceGit.Views
var parentView = this.FindAncestorOfType();
if (parentView is { DataContext: ViewModels.CommitDetail detail })
{
- var point = e.GetCurrentPoint(this);
- var link = _lastHover.Link;
-
if (point.Properties.IsLeftButtonPressed)
{
detail.NavigateTo(_lastHover.Link);
@@ -217,9 +217,6 @@ namespace SourceGit.Views
}
else
{
- var point = e.GetCurrentPoint(this);
- var link = _lastHover.Link;
-
if (point.Properties.IsLeftButtonPressed)
{
Native.OS.OpenBrowser(link);
@@ -255,6 +252,49 @@ namespace SourceGit.Views
return;
}
+ if (point.Properties.IsLeftButtonPressed && e.ClickCount == 3)
+ {
+ var text = Inlines?.Text;
+ if (string.IsNullOrEmpty(text))
+ {
+ e.Handled = true;
+ return;
+ }
+
+ var position = e.GetPosition(this) - new Point(Padding.Left, Padding.Top);
+ var x = Math.Min(Math.Max(position.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0));
+ var y = Math.Min(Math.Max(position.Y, 0), Math.Max(TextLayout.Height, 0));
+ position = new Point(x, y);
+
+ var textPos = TextLayout.HitTestPoint(position).TextPosition;
+ var lineStart = 0;
+ var lineEnd = text.IndexOf('\n', lineStart);
+ if (lineEnd <= 0)
+ {
+ lineEnd = text.Length;
+ }
+ else
+ {
+ while (lineEnd < textPos)
+ {
+ lineStart = lineEnd + 1;
+ lineEnd = text.IndexOf('\n', lineStart);
+ if (lineEnd == -1)
+ {
+ lineEnd = text.Length;
+ break;
+ }
+ }
+ }
+
+ SetCurrentValue(SelectionStartProperty, lineStart);
+ SetCurrentValue(SelectionEndProperty, lineEnd);
+
+ e.Pointer.Capture(this);
+ e.Handled = true;
+ return;
+ }
+
base.OnPointerPressed(e);
}
diff --git a/src/Views/CreateBranch.axaml b/src/Views/CreateBranch.axaml
index 49bfda8d..dff42516 100644
--- a/src/Views/CreateBranch.axaml
+++ b/src/Views/CreateBranch.axaml
@@ -20,6 +20,7 @@
+
+
+
+
+
-
-
+
-
diff --git a/src/Views/DeleteRepositoryNode.axaml b/src/Views/DeleteRepositoryNode.axaml
index f4e041e5..30a3728e 100644
--- a/src/Views/DeleteRepositoryNode.axaml
+++ b/src/Views/DeleteRepositoryNode.axaml
@@ -17,12 +17,12 @@
Text="{DynamicResource Text.DeleteRepositoryNode.TitleForRepository}"
IsVisible="{Binding Node.IsRepository}"/>
-
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml
index 23a141c3..aa75c2a0 100644
--- a/src/Views/DiffView.axaml
+++ b/src/Views/DiffView.axaml
@@ -129,6 +129,7 @@
diff --git a/src/Views/FileHistories.axaml b/src/Views/FileHistories.axaml
index b0706d24..124b2e00 100644
--- a/src/Views/FileHistories.axaml
+++ b/src/Views/FileHistories.axaml
@@ -56,8 +56,8 @@
Margin="8,4,4,8"
BorderBrush="{DynamicResource Brush.Border2}"
ItemsSource="{Binding Commits}"
- SelectedItem="{Binding SelectedCommit, Mode=TwoWay}"
- SelectionMode="Single"
+ SelectedItems="{Binding SelectedCommits, Mode=TwoWay}"
+ SelectionMode="Multiple"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto">
@@ -107,63 +107,132 @@
BorderThickness="1,0,0,0"
BorderBrush="{DynamicResource Brush.Border0}"/>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Views/FileHistories.axaml.cs b/src/Views/FileHistories.axaml.cs
index 9d74892b..be5affc3 100644
--- a/src/Views/FileHistories.axaml.cs
+++ b/src/Views/FileHistories.axaml.cs
@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
+using Avalonia.Platform.Storage;
namespace SourceGit.Views
{
@@ -22,11 +23,11 @@ namespace SourceGit.Views
e.Handled = true;
}
- private void OnResetToSelectedRevision(object _, RoutedEventArgs e)
+ private void OnResetToSelectedRevision(object sender, RoutedEventArgs e)
{
- if (DataContext is ViewModels.FileHistories vm)
+ if (sender is Button { DataContext: ViewModels.FileHistoriesSingleRevision single })
{
- vm.ResetToSelectedRevision();
+ single.ResetToSelectedRevision();
NotifyDonePanel.IsVisible = true;
}
@@ -38,5 +39,23 @@ namespace SourceGit.Views
NotifyDonePanel.IsVisible = false;
e.Handled = true;
}
+
+ private async void OnSaveAsPatch(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button { DataContext: ViewModels.FileHistoriesCompareRevisions compare })
+ {
+ var options = new FilePickerSaveOptions();
+ options.Title = App.Text("FileCM.SaveAsPatch");
+ options.DefaultExtension = ".patch";
+ options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }];
+
+ var storageFile = await this.StorageProvider.SaveFilePickerAsync(options);
+ if (storageFile != null)
+ await compare.SaveAsPatch(storageFile.Path.LocalPath);
+
+ NotifyDonePanel.IsVisible = true;
+ e.Handled = true;
+ }
+ }
}
}
diff --git a/src/Views/ImageContainer.cs b/src/Views/ImageContainer.cs
index aecea0b2..b62746ed 100644
--- a/src/Views/ImageContainer.cs
+++ b/src/Views/ImageContainer.cs
@@ -92,6 +92,20 @@ namespace SourceGit.Views
return availableSize;
}
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ if (Image is { } image)
+ {
+ var imageSize = image.Size;
+ var scaleW = finalSize.Width / imageSize.Width;
+ var scaleH = finalSize.Height / imageSize.Height;
+ var scale = Math.Min(scaleW, scaleH);
+ return new Size(scale * imageSize.Width, scale * imageSize.Height);
+ }
+
+ return base.ArrangeOverride(finalSize);
+ }
}
public class ImageSwipeControl : ImageContainer
diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs
index 832cca80..b5f09d79 100644
--- a/src/Views/Launcher.axaml.cs
+++ b/src/Views/Launcher.axaml.cs
@@ -146,7 +146,8 @@ namespace SourceGit.Views
return;
}
- if (e.Key == Key.Q) {
+ if (e.Key == Key.Q)
+ {
App.Quit(0);
e.Handled = true;
return;
diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml
index 5be41a13..c957d134 100644
--- a/src/Views/LauncherTabBar.axaml
+++ b/src/Views/LauncherTabBar.axaml
@@ -7,7 +7,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.LauncherTabBar"
x:DataType="vm:Launcher">
-
+
@@ -96,7 +96,8 @@
-
diff --git a/src/Views/LauncherTabBar.axaml.cs b/src/Views/LauncherTabBar.axaml.cs
index f8c9107c..129ce892 100644
--- a/src/Views/LauncherTabBar.axaml.cs
+++ b/src/Views/LauncherTabBar.axaml.cs
@@ -43,6 +43,9 @@ namespace SourceGit.Views
if (containerEndX < startX || containerEndX > endX)
continue;
+ if (OuterNewTabBtn.IsVisible && i == count - 1)
+ break;
+
var separatorX = containerEndX - startX + LauncherTabsScroller.Bounds.X;
context.DrawLine(separatorPen, new Point(separatorX, separatorY), new Point(separatorX, separatorY + 20));
}
@@ -88,7 +91,7 @@ namespace SourceGit.Views
x = drawRightX - 6;
}
- if (drawRightX < LauncherTabsScroller.Bounds.Right)
+ if (drawRightX <= LauncherTabsScroller.Bounds.Right)
{
ctx.LineTo(new Point(x, y));
x = drawRightX;
@@ -146,11 +149,15 @@ namespace SourceGit.Views
LeftScrollIndicator.IsEnabled = LauncherTabsScroller.Offset.X > 0;
RightScrollIndicator.IsVisible = true;
RightScrollIndicator.IsEnabled = LauncherTabsScroller.Offset.X < LauncherTabsScroller.Extent.Width - LauncherTabsScroller.Viewport.Width;
+ InnerNewTabBtn.IsVisible = false;
+ OuterNewTabBtn.IsVisible = true;
}
else
{
LeftScrollIndicator.IsVisible = false;
RightScrollIndicator.IsVisible = false;
+ InnerNewTabBtn.IsVisible = true;
+ OuterNewTabBtn.IsVisible = false;
}
InvalidateVisual();
diff --git a/src/Views/MenuItemExtension.cs b/src/Views/MenuItemExtension.cs
new file mode 100644
index 00000000..1c23b2ea
--- /dev/null
+++ b/src/Views/MenuItemExtension.cs
@@ -0,0 +1,12 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data;
+
+namespace SourceGit.Views
+{
+ public class MenuItemExtension : AvaloniaObject
+ {
+ public static readonly AttachedProperty CommandProperty =
+ AvaloniaProperty.RegisterAttached("Command", string.Empty, false, BindingMode.OneWay);
+ }
+}
diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml
index 7f3633ad..1d282ad9 100644
--- a/src/Views/Preferences.axaml
+++ b/src/Views/Preferences.axaml
@@ -255,7 +255,7 @@
-
+
+
+
diff --git a/src/Views/Preferences.axaml.cs b/src/Views/Preferences.axaml.cs
index 2bc5c571..4696b4a7 100644
--- a/src/Views/Preferences.axaml.cs
+++ b/src/Views/Preferences.axaml.cs
@@ -28,6 +28,12 @@ namespace SourceGit.Views
set;
} = null;
+ public bool EnablePruneOnFetch
+ {
+ get;
+ set;
+ }
+
public static readonly StyledProperty GitVersionProperty =
AvaloniaProperty.Register(nameof(GitVersion));
@@ -114,6 +120,8 @@ namespace SourceGit.Views
GPGUserKey = signingKey;
if (config.TryGetValue("core.autocrlf", out var crlf))
CRLFMode = Models.CRLFMode.Supported.Find(x => x.Value == crlf);
+ if (config.TryGetValue("fetch.prune", out var pruneOnFetch))
+ EnablePruneOnFetch = (pruneOnFetch == "true");
if (config.TryGetValue("commit.gpgsign", out var gpgCommitSign))
EnableGPGCommitSigning = (gpgCommitSign == "true");
if (config.TryGetValue("tag.gpgsign", out var gpgTagSign))
@@ -157,6 +165,7 @@ namespace SourceGit.Views
SetIfChanged(config, "user.email", DefaultEmail, "");
SetIfChanged(config, "user.signingkey", GPGUserKey, "");
SetIfChanged(config, "core.autocrlf", CRLFMode != null ? CRLFMode.Value : null, null);
+ SetIfChanged(config, "fetch.prune", EnablePruneOnFetch ? "true" : "false", "false");
SetIfChanged(config, "commit.gpgsign", EnableGPGCommitSigning ? "true" : "false", "false");
SetIfChanged(config, "tag.gpgsign", EnableGPGTagSigning ? "true" : "false", "false");
SetIfChanged(config, "http.sslverify", EnableHTTPSSLVerify ? "" : "false", "");
diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml
index 00f9f6ce..74f628c6 100644
--- a/src/Views/Repository.axaml
+++ b/src/Views/Repository.axaml
@@ -479,7 +479,8 @@
SelectedIndex="{Binding SearchCommitFilterType, Mode=TwoWay}">
-
+
+
@@ -597,11 +598,18 @@
-
-
+
+
-
+
diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs
index 7a54b382..bb359040 100644
--- a/src/Views/Repository.axaml.cs
+++ b/src/Views/Repository.axaml.cs
@@ -434,6 +434,7 @@ namespace SourceGit.Views
var dateOrder = new MenuItem();
dateOrder.Header = App.Text("Repository.HistoriesOrder.ByDate");
+ dateOrder.SetValue(MenuItemExtension.CommandProperty, "--date-order");
if (!repo.EnableTopoOrderInHistories)
dateOrder.Icon = App.CreateMenuIcon("Icons.Check");
dateOrder.Click += (_, ev) =>
@@ -444,6 +445,7 @@ namespace SourceGit.Views
var topoOrder = new MenuItem();
topoOrder.Header = App.Text("Repository.HistoriesOrder.Topo");
+ topoOrder.SetValue(MenuItemExtension.CommandProperty, "--top-order");
if (repo.EnableTopoOrderInHistories)
topoOrder.Icon = App.CreateMenuIcon("Icons.Check");
topoOrder.Click += (_, ev) =>
diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml
index 0aef7aa6..5e9374f9 100644
--- a/src/Views/RepositoryConfigure.axaml
+++ b/src/Views/RepositoryConfigure.axaml
@@ -45,7 +45,7 @@
-
+
-
-
-
-
-
+
diff --git a/src/Views/RevisionFileContentViewer.axaml.cs b/src/Views/RevisionFileContentViewer.axaml.cs
index bca6a082..c74f2db2 100644
--- a/src/Views/RevisionFileContentViewer.axaml.cs
+++ b/src/Views/RevisionFileContentViewer.axaml.cs
@@ -1,7 +1,117 @@
+using System;
+
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+
+using AvaloniaEdit;
+using AvaloniaEdit.Document;
+using AvaloniaEdit.Editing;
+using AvaloniaEdit.TextMate;
namespace SourceGit.Views
{
+ public class RevisionTextFileView : TextEditor
+ {
+ protected override Type StyleKeyOverride => typeof(TextEditor);
+
+ public RevisionTextFileView() : base(new TextArea(), new TextDocument())
+ {
+ IsReadOnly = true;
+ ShowLineNumbers = true;
+ WordWrap = false;
+ HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
+ VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
+
+ TextArea.LeftMargins[0].Margin = new Thickness(8, 0);
+ TextArea.TextView.Margin = new Thickness(4, 0);
+ TextArea.TextView.Options.EnableHyperlinks = false;
+ TextArea.TextView.Options.EnableEmailHyperlinks = false;
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+
+ TextArea.TextView.ContextRequested += OnTextViewContextRequested;
+ UpdateTextMate();
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs e)
+ {
+ base.OnUnloaded(e);
+
+ TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
+
+ if (_textMate != null)
+ {
+ _textMate.Dispose();
+ _textMate = null;
+ }
+
+ GC.Collect();
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+
+ if (DataContext is Models.RevisionTextFile source)
+ {
+ UpdateTextMate();
+ Text = source.Content;
+ }
+ else
+ {
+ Text = string.Empty;
+ }
+ }
+
+ private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
+ {
+ var selected = SelectedText;
+ if (string.IsNullOrEmpty(selected))
+ return;
+
+ var copy = new MenuItem() { Header = App.Text("Copy") };
+ copy.Click += (_, ev) =>
+ {
+ App.CopyText(selected);
+ ev.Handled = true;
+ };
+
+ if (this.FindResource("Icons.Copy") is Geometry geo)
+ {
+ copy.Icon = new Avalonia.Controls.Shapes.Path()
+ {
+ Width = 10,
+ Height = 10,
+ Stretch = Stretch.Uniform,
+ Data = geo,
+ };
+ }
+
+ var menu = new ContextMenu();
+ menu.Items.Add(copy);
+ menu.Open(TextArea.TextView);
+
+ e.Handled = true;
+ }
+
+ private void UpdateTextMate()
+ {
+ if (_textMate == null)
+ _textMate = Models.TextMateHelper.CreateForEditor(this);
+
+ if (DataContext is Models.RevisionTextFile file)
+ Models.TextMateHelper.SetGrammarByFileName(_textMate, file.FileName);
+ }
+
+ private TextMate.Installation _textMate = null;
+ }
+
public partial class RevisionFileContentViewer : UserControl
{
public RevisionFileContentViewer()
diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs
index f748fb0d..12dfe23a 100644
--- a/src/Views/RevisionFiles.axaml.cs
+++ b/src/Views/RevisionFiles.axaml.cs
@@ -1,118 +1,8 @@
-using System;
-
-using Avalonia;
using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Media;
-
-using AvaloniaEdit;
-using AvaloniaEdit.Document;
-using AvaloniaEdit.Editing;
-using AvaloniaEdit.TextMate;
namespace SourceGit.Views
{
- public class RevisionTextFileView : TextEditor
- {
- protected override Type StyleKeyOverride => typeof(TextEditor);
-
- public RevisionTextFileView() : base(new TextArea(), new TextDocument())
- {
- IsReadOnly = true;
- ShowLineNumbers = true;
- WordWrap = false;
- HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
- VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
-
- TextArea.LeftMargins[0].Margin = new Thickness(8, 0);
- TextArea.TextView.Margin = new Thickness(4, 0);
- TextArea.TextView.Options.EnableHyperlinks = false;
- TextArea.TextView.Options.EnableEmailHyperlinks = false;
- }
-
- protected override void OnLoaded(RoutedEventArgs e)
- {
- base.OnLoaded(e);
-
- TextArea.TextView.ContextRequested += OnTextViewContextRequested;
- UpdateTextMate();
- }
-
- protected override void OnUnloaded(RoutedEventArgs e)
- {
- base.OnUnloaded(e);
-
- TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
-
- if (_textMate != null)
- {
- _textMate.Dispose();
- _textMate = null;
- }
-
- GC.Collect();
- }
-
- protected override void OnDataContextChanged(EventArgs e)
- {
- base.OnDataContextChanged(e);
-
- if (DataContext is Models.RevisionTextFile source)
- {
- UpdateTextMate();
- Text = source.Content;
- }
- else
- {
- Text = string.Empty;
- }
- }
-
- private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
- {
- var selected = SelectedText;
- if (string.IsNullOrEmpty(selected))
- return;
-
- var copy = new MenuItem() { Header = App.Text("Copy") };
- copy.Click += (_, ev) =>
- {
- App.CopyText(selected);
- ev.Handled = true;
- };
-
- if (this.FindResource("Icons.Copy") is Geometry geo)
- {
- copy.Icon = new Avalonia.Controls.Shapes.Path()
- {
- Width = 10,
- Height = 10,
- Stretch = Stretch.Uniform,
- Data = geo,
- };
- }
-
- var menu = new ContextMenu();
- menu.Items.Add(copy);
- menu.Open(TextArea.TextView);
-
- e.Handled = true;
- }
-
- private void UpdateTextMate()
- {
- if (_textMate == null)
- _textMate = Models.TextMateHelper.CreateForEditor(this);
-
- if (DataContext is Models.RevisionTextFile file)
- Models.TextMateHelper.SetGrammarByFileName(_textMate, file.FileName);
- }
-
- private TextMate.Installation _textMate = null;
- }
-
public partial class RevisionFiles : UserControl
{
public RevisionFiles()
diff --git a/src/Views/StashChanges.axaml b/src/Views/StashChanges.axaml
index 772b09e6..2879c7e7 100644
--- a/src/Views/StashChanges.axaml
+++ b/src/Views/StashChanges.axaml
@@ -11,7 +11,7 @@
-
+
-
+
+
{
- App.CopyText(SelectedText);
+ CopyWithoutIndicators();
ev.Handled = true;
};
@@ -941,6 +960,59 @@ namespace SourceGit.Views
}
}
+ private void CopyWithoutIndicators()
+ {
+ var selection = TextArea.Selection;
+ if (selection.IsEmpty)
+ {
+ App.CopyText(string.Empty);
+ return;
+ }
+
+ var lines = GetLines();
+ var startIdx = Math.Min(selection.StartPosition.Line - 1, lines.Count - 1);
+ var endIdx = Math.Min(selection.EndPosition.Line - 1, lines.Count - 1);
+
+ if (startIdx == endIdx)
+ {
+ var line = lines[startIdx];
+ if (line.Type == Models.TextDiffLineType.Indicator ||
+ line.Type == Models.TextDiffLineType.None)
+ {
+ App.CopyText(string.Empty);
+ return;
+ }
+
+ App.CopyText(SelectedText);
+ return;
+ }
+
+ var builder = new StringBuilder();
+ for (var i = startIdx; i <= endIdx; i++)
+ {
+ var line = lines[i];
+ if (line.Type == Models.TextDiffLineType.Indicator ||
+ line.Type == Models.TextDiffLineType.None)
+ continue;
+
+ if (i == startIdx && selection.StartPosition.Column > 1)
+ {
+ builder.AppendLine(line.Content.Substring(selection.StartPosition.Column - 1));
+ continue;
+ }
+
+ if (i == endIdx && selection.EndPosition.Column < line.Content.Length)
+ {
+ builder.AppendLine(line.Content.Substring(0, selection.EndPosition.Column));
+ continue;
+ }
+
+ builder.AppendLine(line.Content);
+ }
+
+ App.CopyText(builder.ToString());
+ }
+
private TextMate.Installation _textMate = null;
private TextLocation _lastSelectStart = TextLocation.Empty;
private TextLocation _lastSelectEnd = TextLocation.Empty;
diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml
index 74792072..84b60c14 100644
--- a/src/Views/WorkingCopy.axaml
+++ b/src/Views/WorkingCopy.axaml
@@ -25,7 +25,7 @@
-
+
@@ -75,15 +75,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+ IsChecked="{Binding EnableSignOff, Mode=TwoWay}"
+ Content="{DynamicResource Text.WorkingCopy.SignOff}"
+ ToolTip.Tip="--signoff"
+ ToolTip.Placement="Top"
+ ToolTip.VerticalOffset="0"/>
-
+
+
+
+
-