diff --git a/README.md b/README.md index 3829cf54..0990a39d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.47%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.61%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-92.42%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.87%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-92.15%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.73%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.34%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.74%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-91.93%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.35%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-91.67%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.21%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen)](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)>(.*?)", 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)>.*?", 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 {