diff --git a/README.md b/README.md
index 3829cf54..0990a39d 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)
@@ -132,7 +132,7 @@ For **Linux** users:
## OpenAI
-This software supports using OpenAI or other AI service that has an OpenAI comaptible HTTP API to generate commit message. You need configurate the service in `Preference` window.
+This software supports using OpenAI or other AI service that has an OpenAI compatible HTTP API to generate commit message. You need configurate the service in `Preference` window.
For `OpenAI`:
diff --git a/TRANSLATION.md b/TRANSLATION.md
index 84411404..04e121fc 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -1,44 +1,29 @@
-### de_DE.axaml: 99.47%
+### de_DE.axaml: 99.34%
Missing Keys
-- Text.BranchCM.CustomAction
-- Text.Configure.CustomAction.Scope.Branch
+- Text.BranchUpstreamInvalid
- Text.Configure.CustomAction.WaitForExit
-- Text.Repository.Notifications.Clear
+- Text.Diff.First
+- Text.Diff.Last
+- Text.Preferences.AI.Streaming
-### es_ES.axaml: 97.61%
-
-
-
-Missing Keys
-
-- Text.AIAssistant.Regen
-- Text.AIAssistant.Use
-- Text.ApplyStash
-- Text.ApplyStash.DropAfterApply
-- Text.ApplyStash.RestoreIndex
-- Text.ApplyStash.Stash
-- Text.BranchCM.CustomAction
-- Text.Clone.RecurseSubmodules
-- Text.Configure.CustomAction.Scope.Branch
-- Text.Configure.CustomAction.WaitForExit
-- Text.CreateBranch.Name.WarnSpace
-- Text.DeleteRepositoryNode.Path
-- Text.DeleteRepositoryNode.TipForGroup
-- Text.DeleteRepositoryNode.TipForRepository
-- Text.Repository.Notifications.Clear
-- Text.Stash.AutoRestore
-- Text.Stash.AutoRestore.Tip
-- Text.WorkingCopy.SignOff
-
-
-
-### fr_FR.axaml: 92.42%
+### es_ES.axaml: 99.74%
+
+
+
+Missing Keys
+
+- Text.Diff.First
+- Text.Diff.Last
+
+
+
+### fr_FR.axaml: 91.93%
@@ -51,6 +36,7 @@
- Text.ApplyStash.RestoreIndex
- Text.ApplyStash.Stash
- Text.BranchCM.CustomAction
+- Text.BranchUpstreamInvalid
- Text.Clone.RecurseSubmodules
- Text.Configure.CustomAction.Scope.Branch
- Text.Configure.CustomAction.WaitForExit
@@ -58,6 +44,8 @@
- Text.DeleteRepositoryNode.Path
- Text.DeleteRepositoryNode.TipForGroup
- Text.DeleteRepositoryNode.TipForRepository
+- Text.Diff.First
+- Text.Diff.Last
- Text.InProgress.CherryPick.Head
- Text.InProgress.Merge.Operating
- Text.InProgress.Rebase.StoppedAt
@@ -67,6 +55,7 @@
- Text.MergeMultiple.CommitChanges
- Text.MergeMultiple.Strategy
- Text.MergeMultiple.Targets
+- Text.Preferences.AI.Streaming
- Text.Preferences.Appearance.FontSize
- Text.Preferences.Appearance.FontSize.Default
- Text.Preferences.Appearance.FontSize.Editor
@@ -104,7 +93,7 @@
-### it_IT.axaml: 97.87%
+### it_IT.axaml: 97.35%
@@ -117,19 +106,23 @@
- Text.ApplyStash.RestoreIndex
- Text.ApplyStash.Stash
- Text.BranchCM.CustomAction
+- Text.BranchUpstreamInvalid
- Text.Clone.RecurseSubmodules
- Text.Configure.CustomAction.Scope.Branch
- Text.Configure.CustomAction.WaitForExit
- Text.DeleteRepositoryNode.Path
- Text.DeleteRepositoryNode.TipForGroup
- Text.DeleteRepositoryNode.TipForRepository
+- Text.Diff.First
+- Text.Diff.Last
+- Text.Preferences.AI.Streaming
- Text.Repository.Notifications.Clear
- Text.Stash.AutoRestore
- Text.Stash.AutoRestore.Tip
-### pt_BR.axaml: 92.15%
+### pt_BR.axaml: 91.67%
@@ -143,6 +136,7 @@
- Text.ApplyStash.Stash
- Text.BranchCM.CustomAction
- Text.BranchCM.MergeMultiBranches
+- Text.BranchUpstreamInvalid
- Text.Clone.RecurseSubmodules
- Text.CommitCM.Merge
- Text.CommitCM.MergeMultiple
@@ -156,6 +150,8 @@
- Text.DeleteRepositoryNode.Path
- Text.DeleteRepositoryNode.TipForGroup
- Text.DeleteRepositoryNode.TipForRepository
+- Text.Diff.First
+- Text.Diff.Last
- Text.Diff.UseBlockNavigation
- Text.Fetch.Force
- Text.FileCM.ResolveUsing
@@ -169,6 +165,7 @@
- Text.MergeMultiple.CommitChanges
- Text.MergeMultiple.Strategy
- Text.MergeMultiple.Targets
+- Text.Preferences.AI.Streaming
- Text.Preferences.General.DateFormat
- Text.Preferences.General.ShowChildren
- Text.Preferences.Git.SSLVerify
@@ -197,14 +194,18 @@
-### ru_RU.axaml: 99.73%
+### ru_RU.axaml: 99.21%
Missing Keys
- Text.BranchCM.CustomAction
+- Text.BranchUpstreamInvalid
- Text.Configure.CustomAction.Scope.Branch
+- Text.Diff.First
+- Text.Diff.Last
+- Text.Preferences.AI.Streaming
diff --git a/VERSION b/VERSION
index 15689348..bb11b3b9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2025.05
\ No newline at end of file
+2025.06
\ No newline at end of file
diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs
index e1b55b68..b2aa803d 100644
--- a/src/Commands/Add.cs
+++ b/src/Commands/Add.cs
@@ -12,7 +12,7 @@ namespace SourceGit.Commands
Args = includeUntracked ? "add ." : "add -u .";
}
- public Add(string repo, List changes)
+ public Add(string repo, List changes)
{
WorkingDirectory = repo;
Context = repo;
@@ -22,7 +22,7 @@ namespace SourceGit.Commands
foreach (var c in changes)
{
builder.Append(" \"");
- builder.Append(c.Path);
+ builder.Append(c);
builder.Append("\"");
}
Args = builder.ToString();
diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs
index 4b18a561..df61fdd2 100644
--- a/src/Commands/GenerateCommitMessage.cs
+++ b/src/Commands/GenerateCommitMessage.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
-using System.Text.RegularExpressions;
using System.Threading;
using Avalonia.Threading;
@@ -36,6 +35,8 @@ namespace SourceGit.Commands
{
try
{
+ _onResponse?.Invoke("Waiting for pre-file analyzing to completed...\n\n");
+
var responseBuilder = new StringBuilder();
var summaryBuilder = new StringBuilder();
foreach (var change in _changes)
@@ -49,18 +50,17 @@ namespace SourceGit.Commands
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)));
+ {
+ responseBuilder.Append(update);
+ summaryBuilder.Append(update);
+
+ _onResponse?.Invoke($"Waiting for pre-file analyzing to completed...\n\n{responseBuilder}");
+ });
}
responseBuilder.Append("\n");
@@ -74,15 +74,15 @@ namespace SourceGit.Commands
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}"))));
+ {
+ subjectBuilder.Append(update);
+ _onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}");
+ });
}
catch (Exception e)
{
@@ -90,67 +90,10 @@ namespace SourceGit.Commands
}
}
- private void ProcessChatResponse(
- string update,
- ref bool hasFirstValidChar,
- StringBuilder thinkingBuffer,
- params (StringBuilder builder, Action callback)[] outputs)
- {
- if (!hasFirstValidChar)
- {
- update = update.TrimStart();
- if (string.IsNullOrEmpty(update))
- return;
- if (update.StartsWith("<", StringComparison.Ordinal))
- thinkingBuffer.Append(update);
- hasFirstValidChar = true;
- }
-
- if (thinkingBuffer.Length > 0)
- thinkingBuffer.Append(update);
-
- 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;
- }
-
- match = REG_THINK_START.Match(thinkingBuffer.ToString());
- if (!match.Success)
- {
- foreach (var output in outputs)
- output.builder.Append(thinkingBuffer);
- thinkingBuffer.Clear();
- return;
- }
- }
-
- 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 _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/QueryBranches.cs b/src/Commands/QueryBranches.cs
index 95f97214..44438cef 100644
--- a/src/Commands/QueryBranches.cs
+++ b/src/Commands/QueryBranches.cs
@@ -25,11 +25,22 @@ namespace SourceGit.Commands
return branches;
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+ var remoteBranches = new HashSet();
foreach (var line in lines)
{
var b = ParseLine(line);
if (b != null)
+ {
branches.Add(b);
+ if (!b.IsLocal)
+ remoteBranches.Add(b.FullName);
+ }
+ }
+
+ foreach (var b in branches)
+ {
+ if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
+ b.IsUpsteamGone = !remoteBranches.Contains(b.Upstream);
}
return branches;
@@ -75,6 +86,7 @@ namespace SourceGit.Commands
branch.Head = parts[1];
branch.IsCurrent = parts[2] == "*";
branch.Upstream = parts[3];
+ branch.IsUpsteamGone = false;
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
diff --git a/src/Converters/BoolConverters.cs b/src/Converters/BoolConverters.cs
index 2d738700..3563fb37 100644
--- a/src/Converters/BoolConverters.cs
+++ b/src/Converters/BoolConverters.cs
@@ -1,4 +1,5 @@
using Avalonia.Data.Converters;
+using Avalonia.Media;
namespace SourceGit.Converters
{
@@ -6,5 +7,8 @@ namespace SourceGit.Converters
{
public static readonly FuncValueConverter ToPageTabWidth =
new FuncValueConverter(x => x ? 200 : double.NaN);
+
+ public static readonly FuncValueConverter IsBoldToFontWeight =
+ new FuncValueConverter(x => x ? FontWeight.Bold : FontWeight.Normal);
}
}
diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs
index 0ba320c1..2d0ae5b2 100644
--- a/src/Models/Branch.cs
+++ b/src/Models/Branch.cs
@@ -34,6 +34,7 @@ namespace SourceGit.Models
public string Upstream { get; set; }
public BranchTrackStatus TrackStatus { get; set; }
public string Remote { get; set; }
+ public bool IsUpsteamGone { get; set; }
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
}
diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs
index a6648c11..264230c6 100644
--- a/src/Models/OpenAI.cs
+++ b/src/Models/OpenAI.cs
@@ -1,5 +1,8 @@
using System;
using System.ClientModel;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using Azure.AI.OpenAI;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -8,6 +11,91 @@ using OpenAI.Chat;
namespace SourceGit.Models
{
+ public partial class OpenAIResponse
+ {
+ public OpenAIResponse(Action onUpdate)
+ {
+ _onUpdate = onUpdate;
+ }
+
+ public void Append(string text)
+ {
+ var buffer = text;
+
+ if (_thinkTail.Length > 0)
+ {
+ _thinkTail.Append(buffer);
+ buffer = _thinkTail.ToString();
+ _thinkTail.Clear();
+ }
+
+ buffer = REG_COT().Replace(buffer, "");
+
+ var startIdx = buffer.IndexOf('<', StringComparison.Ordinal);
+ if (startIdx >= 0)
+ {
+ if (startIdx > 0)
+ OnReceive(buffer.Substring(0, startIdx));
+
+ var endIdx = buffer.IndexOf(">", startIdx + 1, StringComparison.Ordinal);
+ if (endIdx <= startIdx)
+ {
+ if (buffer.Length - startIdx <= 15)
+ _thinkTail.Append(buffer.Substring(startIdx));
+ else
+ OnReceive(buffer.Substring(startIdx));
+ }
+ else if (endIdx < startIdx + 15)
+ {
+ var tag = buffer.Substring(startIdx + 1, endIdx - startIdx - 1);
+ if (_thinkTags.Contains(tag))
+ _thinkTail.Append(buffer.Substring(startIdx));
+ else
+ OnReceive(buffer.Substring(startIdx));
+ }
+ else
+ {
+ OnReceive(buffer.Substring(startIdx));
+ }
+ }
+ else
+ {
+ OnReceive(buffer);
+ }
+ }
+
+ public void End()
+ {
+ if (_thinkTail.Length > 0)
+ {
+ OnReceive(_thinkTail.ToString());
+ _thinkTail.Clear();
+ }
+ }
+
+ private void OnReceive(string text)
+ {
+ if (!_hasTrimmedStart)
+ {
+ text = text.TrimStart();
+ if (string.IsNullOrEmpty(text))
+ return;
+
+ _hasTrimmedStart = true;
+ }
+
+ _onUpdate.Invoke(text);
+ }
+
+ [GeneratedRegex(@"<(think|thought|thinking|thought_chain)>.*?\1>", RegexOptions.Singleline)]
+ private static partial Regex REG_COT();
+
+ private Action _onUpdate = null;
+ private StringBuilder _thinkTail = new StringBuilder();
+ private HashSet _thinkTags = ["think", "thought", "thinking", "thought_chain"];
+ private bool _hasTrimmedStart = false;
+ }
+
public class OpenAIService : ObservableObject
{
public string Name
@@ -42,6 +130,12 @@ namespace SourceGit.Models
set => SetProperty(ref _model, value);
}
+ public bool Streaming
+ {
+ get => _streaming;
+ set => SetProperty(ref _streaming, value);
+ }
+
public string AnalyzeDiffPrompt
{
get => _analyzeDiffPrompt;
@@ -89,32 +183,47 @@ namespace SourceGit.Models
public void Chat(string prompt, string question, CancellationToken cancellation, Action onUpdate)
{
- Uri server = new(Server);
- ApiKeyCredential key = new(ApiKey);
- ChatClient client = null;
- if (Server.Contains("openai.azure.com/", StringComparison.Ordinal))
+ var server = new Uri(_server);
+ var key = new ApiKeyCredential(_apiKey);
+ var client = null as ChatClient;
+ if (_server.Contains("openai.azure.com/", StringComparison.Ordinal))
{
var azure = new AzureOpenAIClient(server, key);
- client = azure.GetChatClient(Model);
+ client = azure.GetChatClient(_model);
}
else
{
var openai = new OpenAIClient(key, new() { Endpoint = server });
- client = openai.GetChatClient(Model);
+ client = openai.GetChatClient(_model);
}
+ var messages = new List();
+ messages.Add(_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt));
+ messages.Add(new UserChatMessage(question));
+
try
{
- var updates = client.CompleteChatStreaming([
- _model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt),
- new UserChatMessage(question),
- ], null, cancellation);
+ var rsp = new OpenAIResponse(onUpdate);
- foreach (var update in updates)
+ if (_streaming)
{
- if (update.ContentUpdate.Count > 0)
- onUpdate.Invoke(update.ContentUpdate[0].Text);
+ var updates = client.CompleteChatStreaming(messages, null, cancellation);
+
+ foreach (var update in updates)
+ {
+ if (update.ContentUpdate.Count > 0)
+ rsp.Append(update.ContentUpdate[0].Text);
+ }
}
+ else
+ {
+ var completion = client.CompleteChat(messages, null, cancellation);
+
+ if (completion.Value.Content.Count > 0)
+ rsp.Append(completion.Value.Content[0].Text);
+ }
+
+ rsp.End();
}
catch
{
@@ -127,6 +236,7 @@ namespace SourceGit.Models
private string _server;
private string _apiKey;
private string _model;
+ private bool _streaming = true;
private string _analyzeDiffPrompt;
private string _generateSubjectPrompt;
}
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index 556c99ea..44742fb8 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -104,6 +104,18 @@ namespace SourceGit.Models
set;
} = false;
+ public bool PushToRemoteWhenCreateTag
+ {
+ get;
+ set;
+ } = true;
+
+ public bool PushToRemoteWhenDeleteTag
+ {
+ get;
+ set;
+ } = false;
+
public DealWithLocalChanges DealWithLocalChangesOnCreateBranch
{
get;
diff --git a/src/Models/ShellOrTerminal.cs b/src/Models/ShellOrTerminal.cs
index 4f0222e8..3ada2cf9 100644
--- a/src/Models/ShellOrTerminal.cs
+++ b/src/Models/ShellOrTerminal.cs
@@ -57,6 +57,7 @@ namespace SourceGit.Models
new ShellOrTerminal("mate-terminal", "MATE Terminal", "mate-terminal"),
new ShellOrTerminal("foot", "Foot", "foot"),
new ShellOrTerminal("wezterm", "WezTerm", "wezterm"),
+ new ShellOrTerminal("ptyxis", "Ptyxis", "ptyxis"),
new ShellOrTerminal("custom", "Custom", ""),
};
}
diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml
index 8e865660..9426d20a 100644
--- a/src/Resources/Icons.axaml
+++ b/src/Resources/Icons.axaml
@@ -5,9 +5,11 @@
M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z
M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z
M832 64H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V96c0-18-14-32-32-32zM736 596 624 502 506 596V131h230v318z
+ M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z
M757 226a143 143 0 00-55 276 96 96 0 01-88 59h-191a187 187 0 00-96 27V312a143 143 0 10-96 0v399a143 143 0 10103 2 96 96 0 0188-59h191a191 191 0 00187-151 143 143 0 00-43-279zM280 130a48 48 0 110 96 48 48 0 010-96zm0 764a48 48 0 110-96 48 48 0 010 96zM757 417a48 48 0 110-96 48 48 0 010 96z
M896 128h-64V64c0-35-29-64-64-64s-64 29-64 64v64h-64c-35 0-64 29-64 64s29 64 64 64h64v64c0 35 29 64 64 64s64-29 64-64V256h64c35 0 64-29 64-64s-29-64-64-64zm-204 307C673 481 628 512 576 512H448c-47 0-90 13-128 35V372C394 346 448 275 448 192c0-106-86-192-192-192S64 86 64 192c0 83 54 154 128 180v280c-74 26-128 97-128 180c0 106 86 192 192 192s192-86 192-192c0-67-34-125-84-159c22-20 52-33 84-33h128c122 0 223-85 249-199c-19 4-37 7-57 7c-26 0-51-5-76-13zM256 128c35 0 64 29 64 64s-29 64-64 64s-64-29-64-64s29-64 64-64zm0 768c-35 0-64-29-64-64s29-64 64-64s64 29 64 64s-29 64-64 64z
M512 597m-1 0a1 1 0 103 0a1 1 0 10-3 0ZM810 393 732 315 448 600 293 444 214 522l156 156 78 78 362-362z
+ M512 32C246 32 32 250 32 512s218 480 480 480 480-218 480-480S774 32 512 32zm269 381L496 698c-26 26-61 26-83 0L243 528c-26-26-26-61 0-83s61-26 83 0l128 128 240-240c26-26 61-26 83 0 26 19 26 54 3 80z
M747 467c29 0 56 4 82 12v-363c0-47-38-84-84-84H125c-47 0-84 38-84 84v707c0 47 38 84 84 84h375a287 287 0 01-43-152c0-160 129-289 289-289zm-531-250h438c19 0 34 15 34 34s-15 34-34 34H216c-19 0-34-15-34-34s15-34 34-34zm0 179h263c19 0 34 15 34 34s-15 34-34 34H216c-19 0-34-15-34-34s15-34 34-34zm131 247h-131c-19 0-34-15-34-34s15-34 34-34h131c19 0 34 15 34 34s-15 34-34 34zM747 521c-130 0-236 106-236 236S617 992 747 992s236-106 236-236S877 521 747 521zm11 386v-65h-130c-12 0-22-10-22-22s10-22 22-22h260l-130 108zm108-192H606l130-108v65h130c12 0 22 10 22 22s-10 22-22 22z
M529 511c115 0 212 79 239 185h224a62 62 0 017 123l-7 0-224 0a247 247 0 01-479 0H65a62 62 0 01-7-123l7-0h224a247 247 0 01239-185zm0 124a124 124 0 100 247 124 124 0 000-247zm0-618c32 0 58 24 61 55l0 7V206c89 11 165 45 225 103a74 74 0 0122 45l0 9v87a62 62 0 01-123 7l-0-7v-65l-6-4c-43-33-97-51-163-53l-17-0c-74 0-133 18-180 54l-6 4v65a62 62 0 01-55 61l-7 0a62 62 0 01-61-55l-0-7V362c0-20 8-39 23-53 60-58 135-92 224-103V79c0-34 28-62 62-62z
M512 926c-229 0-414-186-414-414S283 98 512 98s414 186 414 414-186 414-414 414zm0-73c189 0 341-153 341-341S701 171 512 171 171 323 171 512s153 341 341 341zm-6-192L284 439l52-52 171 171 171-171L728 439l-222 222z
@@ -118,6 +120,7 @@
M996 452 572 28A96 96 0 00504 0H96C43 0 0 43 0 96v408a96 96 0 0028 68l424 424c37 37 98 37 136 0l408-408c37-37 37-98 0-136zM224 320c-53 0-96-43-96-96s43-96 96-96 96 43 96 96-43 96-96 96zm1028 268L844 996c-37 37-98 37-136 0l-1-1L1055 647c34-34 53-79 53-127s-19-93-53-127L663 0h97a96 96 0 0168 28l424 424c37 37 37 98 0 136z
M765 118 629 239l-16 137-186 160 54 59 183-168 144 4 136-129 47-43-175-12L827 67zM489 404c-66 0-124 55-124 125s54 121 124 121c66 0 120-55 120-121H489l23-121c-8-4-16-4-23-4zM695 525c0 114-93 207-206 207s-206-94-206-207 93-207 206-207c16 0 27 0 43 4l43-207c-27-4-54-8-85-8-229 0-416 188-416 419s187 419 416 419c225 0 408-180 416-403v-12l-210-4z
M144 112h736c18 0 32 14 32 32v736c0 18-14 32-32 32H144c-18 0-32-14-32-32V144c0-18 14-32 32-32zm112 211v72a9 9 0 003 7L386 509 259 615a9 9 0 00-3 7v72a9 9 0 0015 7L493 516a9 9 0 000-14l-222-186a9 9 0 00-15 7zM522 624a10 10 0 00-10 10v60a10 10 0 0010 10h237a10 10 0 0010-10v-60a10 10 0 00-10-10H522z
+ M170 831 513 489 855 831 960 726 512 278 64 726 170 831zM512 278h448v-128h-896v128h448z
M897 673v13c0 51-42 93-93 93h-10c-1 0-2 0-2 0H220c-23 0-42 19-42 42v13c0 23 19 42 42 42h552c14 0 26 12 26 26 0 14-12 26-26 26H220c-51 0-93-42-93-93v-13c0-51 42-93 93-93h20c1-0 2-0 2-0h562c23 0 42-19 42-42v-13c0-11-5-22-13-29-8-7-17-11-28-10H660c-14 0-26-12-26-26 0-14 12-26 26-26h144c24-1 47 7 65 24 18 17 29 42 29 67zM479 98c-112 0-203 91-203 203 0 44 14 85 38 118l132 208c15 24 50 24 66 0l133-209c23-33 37-73 37-117 0-112-91-203-203-203zm0 327c-68 0-122-55-122-122s55-122 122-122 122 55 122 122-55 122-122 122z
M912 800a48 48 0 1 1 0 96h-416a48 48 0 1 1 0-96h416z m-704-704A112 112 0 0 1 256 309.184V480h80a48 48 0 0 1 0 96H256v224h81.664a48 48 0 1 1 0 96H256a96 96 0 0 1-96-96V309.248A112 112 0 0 1 208 96z m704 384a48 48 0 1 1 0 96h-416a48 48 0 0 1 0-96h416z m0-320a48 48 0 1 1 0 96h-416a48 48 0 0 1 0-96h416z
M30 0 30 30 0 15z
diff --git a/src/Resources/Images/ShellIcons/ptyxis.png b/src/Resources/Images/ShellIcons/ptyxis.png
new file mode 100644
index 00000000..9202f6e1
Binary files /dev/null and b/src/Resources/Images/ShellIcons/ptyxis.png differ
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index b6458822..c16c90b5 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -59,6 +59,7 @@
Mit HEAD vergleichen
Mit Worktree vergleichen
Branch-Namen kopieren
+ Benutzerdefinierte Aktion
Lösche ${0}$...
Lösche alle ausgewählten {0} Branches
Alle Änderungen verwerfen
@@ -159,6 +160,7 @@
Ausführbare Datei:
Name:
Geltungsbereich:
+ Branch
Commit
Repository
Email Adresse
@@ -586,6 +588,7 @@
LOKALE BRANCHES
Zum HEAD wechseln
Erstelle Branch
+ BENACHRICHTIGUNGEN LÖSCHEN
Nur aktuellen Branch im Graphen hervorheben
Öffne in {0}
Öffne in externen Tools
@@ -710,7 +713,7 @@
Öffne alle Repositories
Öffne Repository
Öffne Terminal
- Klon Standardordner erneut nach Repositories durchsuchen
+ Klon Standardordner erneut nach Repositories durchsuchen
Suche Repositories...
Sortieren
Änderungen
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index cd266666..1a83a4da 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -72,6 +72,7 @@
Rename ${0}$...
Set Tracking Branch...
Branch Compare
+ Invalid upstream!
Bytes
CANCEL
Reset to This Revision
@@ -249,7 +250,9 @@
OLD
Copy
File Mode Changed
+ First Difference
Ignore Whitespace Change
+ Last Difference
LFS OBJECT CHANGE
Next Difference
NO CHANGES OR ONLY EOL CHANGES
@@ -459,6 +462,7 @@
Model
Name
Server
+ Enable Streaming
APPEARANCE
Default Font
Font Size
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml
index a0f35814..24170683 100644
--- a/src/Resources/Locales/es_ES.axaml
+++ b/src/Resources/Locales/es_ES.axaml
@@ -22,7 +22,9 @@
Rama de Seguimiento:
Seguimiento de rama remota
Asistente OpenAI
+ RE-GENERAR
Usar OpenAI para generar mensaje de commit
+ APLICAR CÓMO MENSAJE DE COMMIT
Aplicar Patch
Error
Genera errores y se niega a aplicar el patch
@@ -37,6 +39,10 @@
Advertencia
Genera advertencias para algunos de estos errores, pero aplica
Espacios en Blanco:
+ Aplicar Stash
+ Borrar después de aplicar
+ Restaurar los cambios del índice
+ Stash:
Archivar...
Guardar Archivo en:
Seleccionar ruta del archivo
@@ -53,6 +59,7 @@
Comparar con HEAD
Comparar con Worktree
Copiar Nombre de Rama
+ Acción personalizada
Eliminar ${0}$...
Eliminar {0} ramas seleccionadas
Descartar todos los cambios
@@ -68,6 +75,7 @@
Renombrar ${0}$...
Establecer Rama de Seguimiento...
Comparar Ramas
+ ¡Upstream inválido!
Bytes
CANCELAR
Resetear a Esta Revisión
@@ -100,6 +108,7 @@
Nombre Local:
Nombre del repositorio. Opcional.
Carpeta Padre:
+ Inicializar y actualizar submodulos
URL del Repositorio:
CERRAR
Editor
@@ -152,8 +161,10 @@
Archivo Ejecutable:
Nombre:
Alcance:
+ Rama
Commit
Repositorio
+ Esperar la acción de salida
Dirección de Email
Dirección de email
GIT
@@ -202,6 +213,7 @@
Stash & Reaplicar
Nombre de la Nueva Rama:
Introduzca el nombre de la rama.
+ Los espacios serán reemplazados con guiones.
Crear Rama Local
Crear Etiqueta...
Nueva Etiqueta En:
@@ -225,8 +237,11 @@
Estás intentando eliminar múltiples ramas a la vez. ¡Asegúrate de revisar antes de tomar acción!
Eliminar Remoto
Remoto:
+ Ruta:
Destino:
+ Todos los hijos serán removidos de la lista.
Confirmar Eliminación de Grupo
+ ¡Esto solo lo removera de la lista, no del disco!
Confirmar Eliminación de Repositorio
Eliminar Submódulo
Ruta del Submódulo:
@@ -449,6 +464,7 @@
Modelo
Nombre
Servidor
+ Activar Transmisión
APARIENCIA
Fuente por defecto
Tamaño de fuente
@@ -576,6 +592,7 @@
RAMAS LOCALES
Navegar a HEAD
Crear Rama
+ LIMPIAR NOTIFICACIONES
Resaltar solo la rama actual en el gráfico
Abrir en {0}
Abrir en Herramientas Externas
@@ -642,6 +659,8 @@
Ruta de almacenamiento de la clave privada SSH
INICIAR
Stash
+ Restaurar automáticamente después del stashing
+ Tus archivos de trabajo permanecen sin cambios, pero se guarda un stash.
Incluir archivos no rastreados
Mantener archivos staged
Mensaje:
@@ -721,6 +740,7 @@
INCLUIR ARCHIVOS NO RASTREADOS
NO HAY MENSAJES DE ENTRADA RECIENTES
NO HAY PLANTILLAS DE COMMIT
+ Firmar
STAGED
UNSTAGE
UNSTAGE TODO
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 4db909c9..acfdefd2 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -75,6 +75,7 @@
重命名 ${0}$...
切换上游分支 ...
分支比较
+ 跟踪的上游分支不存在或已删除!
字节
取 消
重置文件到该版本
@@ -252,7 +253,9 @@
原始大小
复制
文件权限已变化
+ 首个差异
忽略空白符号变化
+ 最后一个差异
LFS对象变更
下一个差异
没有变更或仅有换行符差异
@@ -462,6 +465,7 @@
模型
配置名称
服务地址
+ 启用流式输出
外观配置
缺省字体
字体大小
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index a3fe5c19..dd9092a4 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -75,6 +75,7 @@
重新命名 ${0}$...
切換上游分支...
分支比較
+ 追蹤上游分支不存在或已刪除!
位元組
取 消
重設檔案為此版本
@@ -252,7 +253,9 @@
原始大小
複製
檔案權限已變更
+ 第一個差異
忽略空白符號變化
+ 最後一個差異
LFS 物件變更
下一個差異
沒有變更或僅有換行字元差異
@@ -456,12 +459,13 @@
{0} 年前
偏好設定
AI
- 伺服器
+ 分析變更差異提示詞
API 金鑰
+ 產生提交訊息提示詞
模型
名稱
- 分析變更差異提示詞
- 產生提交訊息提示詞
+ 伺服器
+ 啟用串流輸出
外觀設定
預設字型
字型大小
diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj
index 7cceee7e..4183511e 100644
--- a/src/SourceGit.csproj
+++ b/src/SourceGit.csproj
@@ -48,10 +48,10 @@
-
+
-
-
+
+
diff --git a/src/ViewModels/BlockNavigation.cs b/src/ViewModels/BlockNavigation.cs
index 709338f8..9a5a926c 100644
--- a/src/ViewModels/BlockNavigation.cs
+++ b/src/ViewModels/BlockNavigation.cs
@@ -101,12 +101,12 @@ namespace SourceGit.ViewModels
return (_current >= 0 && _current < Blocks.Count) ? Blocks[_current] : null;
}
- public Block GotoNext()
+ public Block GotoFirst()
{
if (Blocks.Count == 0)
return null;
- Current = (_current + 1) % Blocks.Count;
+ Current = 0;
return Blocks[_current];
}
@@ -115,7 +115,29 @@ namespace SourceGit.ViewModels
if (Blocks.Count == 0)
return null;
- Current = _current == -1 ? Blocks.Count - 1 : (_current - 1 + Blocks.Count) % Blocks.Count;
+ if (_current == -1)
+ Current = 0;
+ else if (_current > 0)
+ Current = _current - 1;
+ return Blocks[_current];
+ }
+
+ public Block GotoNext()
+ {
+ if (Blocks.Count == 0)
+ return null;
+
+ if (_current < Blocks.Count - 1)
+ Current = _current + 1;
+ return Blocks[_current];
+ }
+
+ public Block GotoLast()
+ {
+ if (Blocks.Count == 0)
+ return null;
+
+ Current = Blocks.Count - 1;
return Blocks[_current];
}
diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs
index 5c42f729..6c1d2e04 100644
--- a/src/ViewModels/BranchTreeNode.cs
+++ b/src/ViewModels/BranchTreeNode.cs
@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
-
using Avalonia;
-using Avalonia.Media;
-
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@@ -45,6 +42,11 @@ namespace SourceGit.ViewModels
get => Backend is Models.Branch { IsCurrent: true };
}
+ public bool ShowUpstreamGoneTip
+ {
+ get => Backend is Models.Branch { IsUpsteamGone: true };
+ }
+
public string Tooltip
{
get => Backend is Models.Branch b ? b.FriendlyName : null;
diff --git a/src/ViewModels/CreateTag.cs b/src/ViewModels/CreateTag.cs
index af9dcf99..a6d7255b 100644
--- a/src/ViewModels/CreateTag.cs
+++ b/src/ViewModels/CreateTag.cs
@@ -39,11 +39,11 @@ namespace SourceGit.ViewModels
set;
} = false;
- public bool PushToAllRemotes
+ public bool PushToRemotes
{
- get;
- set;
- } = true;
+ get => _repo.Settings.PushToRemoteWhenCreateTag;
+ set => _repo.Settings.PushToRemoteWhenCreateTag = value;
+ }
public CreateTag(Repository repo, Models.Branch branch)
{
@@ -82,6 +82,7 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false);
ProgressDescription = "Create tag...";
+ var remotes = PushToRemotes ? _repo.Remotes : null;
return Task.Run(() =>
{
bool succ;
@@ -90,9 +91,9 @@ namespace SourceGit.ViewModels
else
succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn);
- if (succ && PushToAllRemotes)
+ if (succ && remotes != null)
{
- foreach (var remote in _repo.Remotes)
+ foreach (var remote in remotes)
{
SetProgressDescription($"Pushing tag to remote {remote.Name} ...");
new Commands.Push(_repo.FullPath, remote.Name, _tagName, false).Exec();
diff --git a/src/ViewModels/DeleteTag.cs b/src/ViewModels/DeleteTag.cs
index 7b53e798..341eb4a2 100644
--- a/src/ViewModels/DeleteTag.cs
+++ b/src/ViewModels/DeleteTag.cs
@@ -10,17 +10,16 @@ namespace SourceGit.ViewModels
private set;
}
- public bool ShouldPushToRemote
+ public bool PushToRemotes
{
- get;
- set;
+ get => _repo.Settings.PushToRemoteWhenDeleteTag;
+ set => _repo.Settings.PushToRemoteWhenDeleteTag = value;
}
public DeleteTag(Repository repo, Models.Tag tag)
{
_repo = repo;
Target = tag;
- ShouldPushToRemote = true;
View = new Views.DeleteTag() { DataContext = this };
}
@@ -29,9 +28,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Deleting tag '{Target.Name}' ...";
+ var remotes = PushToRemotes ? _repo.Remotes : null;
return Task.Run(() =>
{
- var remotes = ShouldPushToRemote ? _repo.Remotes : null;
var succ = Commands.Tag.Delete(_repo.FullPath, Target.Name, remotes);
CallUIThread(() =>
{
diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs
index d816d0b8..1094012e 100644
--- a/src/ViewModels/Fetch.cs
+++ b/src/ViewModels/Fetch.cs
@@ -38,7 +38,24 @@ namespace SourceGit.ViewModels
{
_repo = repo;
_fetchAllRemotes = preferedRemote == null;
- SelectedRemote = preferedRemote != null ? preferedRemote : _repo.Remotes[0];
+
+ if (preferedRemote != null)
+ {
+ SelectedRemote = preferedRemote;
+ }
+ else if (!string.IsNullOrEmpty(_repo.Settings.DefaultRemote))
+ {
+ var def = _repo.Remotes.Find(r => r.Name == _repo.Settings.DefaultRemote);
+ if (def != null)
+ SelectedRemote = def;
+ else
+ SelectedRemote = _repo.Remotes[0];
+ }
+ else
+ {
+ SelectedRemote = _repo.Remotes[0];
+ }
+
View = new Views.Fetch() { DataContext = this };
}
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index 19e48b13..6e8a5290 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -1161,6 +1161,10 @@ namespace SourceGit.ViewModels
public void OpenSubmodule(string submodule)
{
+ var selfPage = GetOwnerPage();
+ if (selfPage == null)
+ return;
+
var root = Path.GetFullPath(Path.Combine(_fullpath, submodule));
var normalizedPath = root.Replace("\\", "/");
@@ -1171,12 +1175,12 @@ namespace SourceGit.ViewModels
{
Id = normalizedPath,
Name = Path.GetFileName(normalizedPath),
- Bookmark = 0,
+ Bookmark = selfPage.Node.Bookmark,
IsRepository = true,
};
}
- App.GetLauncer()?.OpenRepositoryInTab(node, null);
+ App.GetLauncer().OpenRepositoryInTab(node, null);
}
public void AddWorktree()
diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs
index a7c04937..cf23b6d8 100644
--- a/src/ViewModels/RepositoryConfigure.cs
+++ b/src/ViewModels/RepositoryConfigure.cs
@@ -60,6 +60,12 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value);
}
+ public bool EnablePruneOnFetch
+ {
+ get;
+ set;
+ }
+
public bool EnableAutoFetch
{
get => _repo.Settings.EnableAutoFetch;
@@ -153,6 +159,8 @@ namespace SourceGit.ViewModels
GPGUserSigningKey = signingKey;
if (_cached.TryGetValue("http.proxy", out var proxy))
HttpProxy = proxy;
+ if (_cached.TryGetValue("fetch.prune", out var prune))
+ EnablePruneOnFetch = (prune == "true");
}
public void ClearHttpProxy()
@@ -286,6 +294,7 @@ namespace SourceGit.ViewModels
SetIfChanged("tag.gpgsign", GPGTagSigningEnabled ? "true" : "false", "false");
SetIfChanged("user.signingkey", GPGUserSigningKey, "");
SetIfChanged("http.proxy", HttpProxy, "");
+ SetIfChanged("fetch.prune", EnablePruneOnFetch ? "true" : "false", "false");
}
private void SetIfChanged(string key, string value, string defValue)
diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs
index 7ac13dc2..ce2a2ac1 100644
--- a/src/ViewModels/WorkingCopy.cs
+++ b/src/ViewModels/WorkingCopy.cs
@@ -109,7 +109,7 @@ namespace SourceGit.ViewModels
if (_isLoadingData)
return;
- VisibleUnstaged = GetVisibleUnstagedChanges();
+ VisibleUnstaged = GetVisibleUnstagedChanges(_unstaged);
SelectedUnstaged = [];
}
}
@@ -214,9 +214,11 @@ namespace SourceGit.ViewModels
OnPropertyChanged(nameof(SelectedStaged));
_visibleUnstaged.Clear();
- _unstaged.Clear();
OnPropertyChanged(nameof(VisibleUnstaged));
+ _unstaged.Clear();
+ OnPropertyChanged(nameof(Unstaged));
+
_staged.Clear();
OnPropertyChanged(nameof(Staged));
@@ -231,25 +233,10 @@ namespace SourceGit.ViewModels
// Just force refresh selected changes.
Dispatcher.UIThread.Invoke(() =>
{
- if (_selectedUnstaged.Count == 1)
- SetDetail(_selectedUnstaged[0], true);
- else if (_selectedStaged.Count == 1)
- SetDetail(_selectedStaged[0], false);
- else
- SetDetail(null, false);
-
- var inProgress = null as InProgressContext;
- if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD")))
- inProgress = new CherryPickInProgress(_repo);
- 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);
- else if (File.Exists(Path.Combine(_repo.GitDir, "MERGE_HEAD")))
- inProgress = new MergeInProgress(_repo);
-
HasUnsolvedConflicts = _cached.Find(x => x.IsConflit) != null;
- InProgressContext = inProgress;
+
+ UpdateDetail();
+ UpdateInProgressState();
});
return;
@@ -282,9 +269,7 @@ namespace SourceGit.ViewModels
}
}
- _unstaged = unstaged;
-
- var visibleUnstaged = GetVisibleUnstagedChanges();
+ var visibleUnstaged = GetVisibleUnstagedChanges(unstaged);
var selectedUnstaged = new List();
foreach (var c in visibleUnstaged)
{
@@ -305,37 +290,14 @@ namespace SourceGit.ViewModels
_isLoadingData = true;
HasUnsolvedConflicts = hasConflict;
VisibleUnstaged = visibleUnstaged;
+ Unstaged = unstaged;
Staged = staged;
SelectedUnstaged = selectedUnstaged;
SelectedStaged = selectedStaged;
_isLoadingData = false;
- if (selectedUnstaged.Count == 1)
- SetDetail(selectedUnstaged[0], true);
- else if (selectedStaged.Count == 1)
- SetDetail(selectedStaged[0], false);
- else
- SetDetail(null, false);
-
- var inProgress = null as InProgressContext;
- if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD")))
- inProgress = new CherryPickInProgress(_repo);
- 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);
- else if (File.Exists(Path.Combine(_repo.GitDir, "MERGE_HEAD")))
- inProgress = new MergeInProgress(_repo);
-
- InProgressContext = inProgress;
-
- // Try to load merge message from MERGE_MSG
- if (string.IsNullOrEmpty(_commitMessage))
- {
- var mergeMsgFile = Path.Combine(_repo.GitDir, "MERGE_MSG");
- if (File.Exists(mergeMsgFile))
- CommitMessage = File.ReadAllText(mergeMsgFile);
- }
+ UpdateDetail();
+ UpdateInProgressState();
});
}
@@ -391,38 +353,80 @@ namespace SourceGit.ViewModels
public async void UseTheirs(List changes)
{
+ _repo.SetWatcherEnabled(false);
+
var files = new List();
+ var needStage = new List();
+
foreach (var change in changes)
{
- if (change.IsConflit)
+ if (!change.IsConflit)
+ continue;
+
+ if (change.WorkTree == Models.ChangeState.Deleted)
+ {
+ var fullpath = Path.Combine(_repo.FullPath, change.Path);
+ if (File.Exists(fullpath))
+ File.Delete(fullpath);
+
+ needStage.Add(change.Path);
+ }
+ else
+ {
files.Add(change.Path);
+ }
}
- _repo.SetWatcherEnabled(false);
- var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseTheirs(files));
- if (succ)
+ if (files.Count > 0)
{
- await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec());
+ var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseTheirs(files));
+ if (succ)
+ needStage.AddRange(files);
}
+
+ if (needStage.Count > 0)
+ await Task.Run(() => new Commands.Add(_repo.FullPath, needStage).Exec());
+
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
}
public async void UseMine(List changes)
{
+ _repo.SetWatcherEnabled(false);
+
var files = new List();
+ var needStage = new List();
+
foreach (var change in changes)
{
- if (change.IsConflit)
+ if (!change.IsConflit)
+ continue;
+
+ if (change.Index == Models.ChangeState.Deleted)
+ {
+ var fullpath = Path.Combine(_repo.FullPath, change.Path);
+ if (File.Exists(fullpath))
+ File.Delete(fullpath);
+
+ needStage.Add(change.Path);
+ }
+ else
+ {
files.Add(change.Path);
+ }
}
- _repo.SetWatcherEnabled(false);
- var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseMine(files));
- if (succ)
+ if (files.Count > 0)
{
- await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec());
+ var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseMine(files));
+ if (succ)
+ needStage.AddRange(files);
}
+
+ if (needStage.Count > 0)
+ await Task.Run(() => new Commands.Add(_repo.FullPath, needStage).Exec());
+
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
}
@@ -1456,14 +1460,14 @@ namespace SourceGit.ViewModels
}
}
- private List GetVisibleUnstagedChanges()
+ private List GetVisibleUnstagedChanges(List unstaged)
{
if (string.IsNullOrEmpty(_unstagedFilter))
- return _unstaged;
+ return unstaged;
var visible = new List();
- foreach (var c in _unstaged)
+ foreach (var c in unstaged)
{
if (c.Path.Contains(_unstagedFilter, StringComparison.OrdinalIgnoreCase))
visible.Add(c);
@@ -1487,9 +1491,61 @@ namespace SourceGit.ViewModels
return rs;
}
+ private void UpdateDetail()
+ {
+ if (_selectedUnstaged.Count == 1)
+ SetDetail(_selectedUnstaged[0], true);
+ else if (_selectedStaged.Count == 1)
+ SetDetail(_selectedStaged[0], false);
+ else
+ SetDetail(null, false);
+ }
+
+ private void UpdateInProgressState()
+ {
+ if (string.IsNullOrEmpty(_commitMessage))
+ {
+ var mergeMsgFile = Path.Combine(_repo.GitDir, "MERGE_MSG");
+ if (File.Exists(mergeMsgFile))
+ CommitMessage = File.ReadAllText(mergeMsgFile);
+ }
+
+ if (File.Exists(Path.Combine(_repo.GitDir, "CHERRY_PICK_HEAD")))
+ {
+ InProgressContext = new CherryPickInProgress(_repo);
+ }
+ else if (Directory.Exists(Path.Combine(_repo.GitDir, "rebase-merge")) || Directory.Exists(Path.Combine(_repo.GitDir, "rebase-apply")))
+ {
+ var rebasing = new RebaseInProgress(_repo);
+ InProgressContext = rebasing;
+
+ if (string.IsNullOrEmpty(_commitMessage))
+ {
+ var rebaseMsgFile = Path.Combine(_repo.GitDir, "rebase-merge", "message");
+ if (File.Exists(rebaseMsgFile))
+ CommitMessage = File.ReadAllText(rebaseMsgFile);
+ else if (rebasing.StoppedAt != null)
+ CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, rebasing.StoppedAt.SHA).Result();
+ }
+ }
+ else if (File.Exists(Path.Combine(_repo.GitDir, "REVERT_HEAD")))
+ {
+ InProgressContext = new RevertInProgress(_repo);
+ }
+ else if (File.Exists(Path.Combine(_repo.GitDir, "MERGE_HEAD")))
+ {
+ InProgressContext = new MergeInProgress(_repo);
+ }
+ else
+ {
+ InProgressContext = null;
+ }
+ }
+
private async void StageChanges(List changes, Models.Change next)
{
- if (changes.Count == 0)
+ var count = changes.Count;
+ if (count == 0)
return;
// Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
@@ -1497,7 +1553,7 @@ namespace SourceGit.ViewModels
IsStaging = true;
_repo.SetWatcherEnabled(false);
- if (changes.Count == _unstaged.Count)
+ if (count == _unstaged.Count)
{
await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec());
}
@@ -1514,10 +1570,13 @@ namespace SourceGit.ViewModels
}
else
{
- for (int i = 0; i < changes.Count; i += 10)
+ var paths = new List();
+ foreach (var c in changes)
+ paths.Add(c.Path);
+
+ for (int i = 0; i < count; i += 10)
{
- var count = Math.Min(10, changes.Count - i);
- var step = changes.GetRange(i, count);
+ var step = paths.GetRange(i, Math.Min(10, count - i));
await Task.Run(() => new Commands.Add(_repo.FullPath, step).Exec());
}
}
diff --git a/src/Views/BranchTree.axaml b/src/Views/BranchTree.axaml
index b7ac725b..0ac09e6c 100644
--- a/src/Views/BranchTree.axaml
+++ b/src/Views/BranchTree.axaml
@@ -57,17 +57,21 @@
IsExpanded="{Binding IsExpanded}"/>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ IsChecked="{Binding PushToRemotes, Mode=TwoWay}"/>
diff --git a/src/Views/DeleteTag.axaml b/src/Views/DeleteTag.axaml
index 702a7f2a..23c19d35 100644
--- a/src/Views/DeleteTag.axaml
+++ b/src/Views/DeleteTag.axaml
@@ -23,7 +23,7 @@
+ IsChecked="{Binding PushToRemotes, Mode=TwoWay}"/>
diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml
index aa75c2a0..fccb949d 100644
--- a/src/Views/DiffView.axaml
+++ b/src/Views/DiffView.axaml
@@ -34,6 +34,20 @@
+
+
+
+
();
+ textDiff?.GotoFirstChange();
+ e.Handled = true;
+ }
+
private void OnGotoPrevChange(object _, RoutedEventArgs e)
{
var textDiff = this.FindDescendantOfType();
@@ -24,5 +31,12 @@ namespace SourceGit.Views
textDiff?.GotoNextChange();
e.Handled = true;
}
+
+ private void OnGotoLastChange(object _, RoutedEventArgs e)
+ {
+ var textDiff = this.FindDescendantOfType();
+ textDiff?.GotoLastChange();
+ e.Handled = true;
+ }
}
}
diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml
index 1d282ad9..3bdd150a 100644
--- a/src/Views/Preferences.axaml
+++ b/src/Views/Preferences.axaml
@@ -616,6 +616,10 @@
Text="{Binding GenerateSubjectPrompt, Mode=TwoWay}"
AcceptsReturn="true"
TextWrapping="Wrap"/>
+
+
diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml
index 6b7cdc12..f6a02c49 100644
--- a/src/Views/RepositoryConfigure.axaml
+++ b/src/Views/RepositoryConfigure.axaml
@@ -44,7 +44,7 @@
-
+
-
+
+
+
@@ -340,7 +344,7 @@
-
+
diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs
index 28fe81a8..323fde03 100644
--- a/src/Views/TextDiffView.axaml.cs
+++ b/src/Views/TextDiffView.axaml.cs
@@ -543,6 +543,21 @@ namespace SourceGit.Views
{
}
+ public void GotoFirstChange()
+ {
+ var blockNavigation = BlockNavigation;
+ if (blockNavigation != null)
+ {
+ var prev = blockNavigation.GotoFirst();
+ if (prev != null)
+ {
+ TextArea.Caret.Line = prev.Start;
+ ScrollToLine(prev.Start);
+ }
+ }
+ // NOTE: Not implemented (button hidden) for non-block navigation.
+ }
+
public void GotoPrevChange()
{
var blockNavigation = BlockNavigation;
@@ -641,6 +656,21 @@ namespace SourceGit.Views
}
}
+ public void GotoLastChange()
+ {
+ var blockNavigation = BlockNavigation;
+ if (blockNavigation != null)
+ {
+ var next = blockNavigation.GotoLast();
+ if (next != null)
+ {
+ TextArea.Caret.Line = next.Start;
+ ScrollToLine(next.Start);
+ }
+ }
+ // NOTE: Not implemented (button hidden) for non-block navigation.
+ }
+
public override void Render(DrawingContext context)
{
base.Render(context);
@@ -1682,6 +1712,19 @@ namespace SourceGit.Views
InitializeComponent();
}
+ public void GotoFirstChange()
+ {
+ var presenter = this.FindDescendantOfType();
+ if (presenter == null)
+ return;
+
+ presenter.GotoFirstChange();
+ if (presenter is SingleSideTextDiffPresenter singleSide)
+ singleSide.ForceSyncScrollOffset();
+
+ BlockNavigationIndicator = BlockNavigation?.Indicator ?? string.Empty;
+ }
+
public void GotoPrevChange()
{
var presenter = this.FindDescendantOfType();
@@ -1708,6 +1751,19 @@ namespace SourceGit.Views
BlockNavigationIndicator = BlockNavigation?.Indicator ?? string.Empty;
}
+ public void GotoLastChange()
+ {
+ var presenter = this.FindDescendantOfType();
+ if (presenter == null)
+ return;
+
+ presenter.GotoLastChange();
+ if (presenter is SingleSideTextDiffPresenter singleSide)
+ singleSide.ForceSyncScrollOffset();
+
+ BlockNavigationIndicator = BlockNavigation?.Indicator ?? string.Empty;
+ }
+
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
@@ -1796,7 +1852,7 @@ namespace SourceGit.Views
if (!selection.HasLeftChanges)
{
- new Commands.Add(repo.FullPath, [change]).Exec();
+ new Commands.Add(repo.FullPath, [change.Path]).Exec();
}
else
{