Merge branch 'release/v2025.33'

This commit is contained in:
leo
2025-09-01 10:27:08 +08:00
92 changed files with 1365 additions and 843 deletions

View File

@@ -6,29 +6,35 @@ This document shows the translation status of each locale file in the repository
### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)
### ![de__DE](https://img.shields.io/badge/de__DE-99.65%25-yellow)
### ![de__DE](https://img.shields.io/badge/de__DE-97.96%25-yellow)
<details>
<summary>Missing keys in de_DE.axaml</summary>
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.Status
- Text.BranchTree.Worktree
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.ShowChangesPageByDefault
- Text.ScanRepositories.UseCustomDir
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
- Text.WorkingCopy.NoVerify
- Text.Worktree.Open
</details>
### ![es__ES](https://img.shields.io/badge/es__ES-99.65%25-yellow)
### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen)
<details>
<summary>Missing keys in es_ES.axaml</summary>
- Text.Preferences.General.ShowChangesPageByDefault
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
</details>
### ![fr__FR](https://img.shields.io/badge/fr__FR-80.07%25-yellow)
### ![fr__FR](https://img.shields.io/badge/fr__FR-78.68%25-yellow)
<details>
<summary>Missing keys in fr_FR.axaml</summary>
@@ -48,10 +54,16 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
- Text.BranchCM.ResetToSelectedCommit
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
- Text.BranchTree.URL
- Text.BranchTree.Worktree
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
@@ -76,6 +88,7 @@ This document shows the translation status of each locale file in the repository
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.CommitMessageTextBox.SubjectCount
- Text.Configure.CommitMessageTemplate.BuiltinVars
- Text.Configure.CustomAction.Arguments.Tip
@@ -116,6 +129,9 @@ This document shows the translation status of each locale file in the repository
- Text.Diff.Old
- Text.Diff.Submodule.Deleted
- Text.DirHistories
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeUntracked
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
@@ -125,12 +141,14 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Global.SwitchTab
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Workspaces
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
- Text.MoveSubmodule
- Text.MoveSubmodule.MoveTo
- Text.MoveSubmodule.Submodule
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
@@ -160,6 +178,7 @@ This document shows the translation status of each locale file in the repository
- Text.ResetWithoutCheckout
- Text.ResetWithoutCheckout.MoveTo
- Text.ResetWithoutCheckout.Target
- Text.ScanRepositories.UseCustomDir
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
@@ -205,23 +224,40 @@ This document shows the translation status of each locale file in the repository
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
- Text.WorkingCopy.Conflicts.UseMine
- Text.WorkingCopy.Conflicts.UseTheirs
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.Worktree.Open
</details>
### ![it__IT](https://img.shields.io/badge/it__IT-99.54%25-yellow)
### ![it__IT](https://img.shields.io/badge/it__IT-97.85%25-yellow)
<details>
<summary>Missing keys in it_IT.axaml</summary>
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.Status
- Text.BranchTree.Worktree
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
- Text.ScanRepositories.UseCustomDir
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
- Text.WorkingCopy.NoVerify
- Text.Worktree.Open
</details>
### ![ja__JP](https://img.shields.io/badge/ja__JP-80.07%25-yellow)
### ![ja__JP](https://img.shields.io/badge/ja__JP-78.68%25-yellow)
<details>
<summary>Missing keys in ja_JP.axaml</summary>
@@ -242,10 +278,16 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.WaitingForRange
- Text.BranchCM.CompareWithCurrent
- Text.BranchCM.ResetToSelectedCommit
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
- Text.BranchTree.URL
- Text.BranchTree.Worktree
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
@@ -270,6 +312,7 @@ This document shows the translation status of each locale file in the repository
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.CommitMessageTextBox.SubjectCount
- Text.Configure.CommitMessageTemplate.BuiltinVars
- Text.Configure.CustomAction.Arguments.Tip
@@ -310,6 +353,9 @@ This document shows the translation status of each locale file in the repository
- Text.Diff.Old
- Text.Diff.Submodule.Deleted
- Text.DirHistories
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeUntracked
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
@@ -319,12 +365,14 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Global.SwitchTab
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Workspaces
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
- Text.MoveSubmodule
- Text.MoveSubmodule.MoveTo
- Text.MoveSubmodule.Submodule
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
@@ -355,6 +403,7 @@ This document shows the translation status of each locale file in the repository
- Text.ResetWithoutCheckout
- Text.ResetWithoutCheckout.MoveTo
- Text.ResetWithoutCheckout.Target
- Text.ScanRepositories.UseCustomDir
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
@@ -398,11 +447,13 @@ This document shows the translation status of each locale file in the repository
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
- Text.WorkingCopy.Conflicts.UseMine
- Text.WorkingCopy.Conflicts.UseTheirs
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.Worktree.Open
</details>
### ![pt__BR](https://img.shields.io/badge/pt__BR-73.39%25-red)
### ![pt__BR](https://img.shields.io/badge/pt__BR-72.22%25-red)
<details>
<summary>Missing keys in pt_BR.axaml</summary>
@@ -430,10 +481,16 @@ This document shows the translation status of each locale file in the repository
- Text.BranchCM.CustomAction
- Text.BranchCM.MergeMultiBranches
- Text.BranchCM.ResetToSelectedCommit
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
- Text.BranchTree.URL
- Text.BranchTree.Worktree
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
@@ -463,6 +520,7 @@ This document shows the translation status of each locale file in the repository
- Text.CommitDetail.Info.Children
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.CommitMessageTextBox.SubjectCount
- Text.Configure.CommitMessageTemplate.BuiltinVars
- Text.Configure.CustomAction.Arguments.Tip
@@ -494,7 +552,6 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmRestart.Title
- Text.ConfirmRestart.Message
- Text.CopyFullPath
- Text.CreateBranch.Name.WarnSpace
- Text.CreateBranch.OverwriteExisting
- Text.DeinitSubmodule
- Text.DeinitSubmodule.Force
@@ -514,6 +571,9 @@ This document shows the translation status of each locale file in the repository
- Text.Diff.Old
- Text.Diff.Submodule.Deleted
- Text.DirHistories
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeUntracked
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
@@ -530,8 +590,8 @@ This document shows the translation status of each locale file in the repository
- Text.InProgress.Rebase.StoppedAt
- Text.InProgress.Revert.Head
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Workspaces
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
- Text.Merge.Source
- Text.MergeMultiple
@@ -541,8 +601,10 @@ This document shows the translation status of each locale file in the repository
- Text.MoveSubmodule
- Text.MoveSubmodule.MoveTo
- Text.MoveSubmodule.Submodule
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.AI.Streaming
- Text.Preferences.Appearance.EditorTabWidth
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.DateFormat
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
@@ -586,6 +648,7 @@ This document shows the translation status of each locale file in the repository
- Text.ResetWithoutCheckout
- Text.ResetWithoutCheckout.MoveTo
- Text.ResetWithoutCheckout.Target
- Text.ScanRepositories.UseCustomDir
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
@@ -636,14 +699,16 @@ This document shows the translation status of each locale file in the repository
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
- Text.WorkingCopy.Conflicts.UseMine
- Text.WorkingCopy.Conflicts.UseTheirs
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.WorkingCopy.SignOff
- Text.Worktree.Open
</details>
### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen)
### ![ta__IN](https://img.shields.io/badge/ta__IN-80.18%25-yellow)
### ![ta__IN](https://img.shields.io/badge/ta__IN-78.80%25-yellow)
<details>
<summary>Missing keys in ta_IN.axaml</summary>
@@ -664,10 +729,16 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.WaitingForRange
- Text.BranchCM.CompareWithCurrent
- Text.BranchCM.ResetToSelectedCommit
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
- Text.BranchTree.URL
- Text.BranchTree.Worktree
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
@@ -692,6 +763,7 @@ This document shows the translation status of each locale file in the repository
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.CommitMessageTextBox.SubjectCount
- Text.Configure.CommitMessageTemplate.BuiltinVars
- Text.Configure.CustomAction.Arguments.Tip
@@ -732,6 +804,9 @@ This document shows the translation status of each locale file in the repository
- Text.Diff.Old
- Text.Diff.Submodule.Deleted
- Text.DirHistories
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeUntracked
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
@@ -741,12 +816,14 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Global.SwitchTab
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Workspaces
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
- Text.MoveSubmodule
- Text.MoveSubmodule.MoveTo
- Text.MoveSubmodule.Submodule
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
@@ -776,6 +853,7 @@ This document shows the translation status of each locale file in the repository
- Text.ResetWithoutCheckout
- Text.ResetWithoutCheckout.MoveTo
- Text.ResetWithoutCheckout.Target
- Text.ScanRepositories.UseCustomDir
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
@@ -819,11 +897,13 @@ This document shows the translation status of each locale file in the repository
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
- Text.WorkingCopy.Conflicts.UseMine
- Text.WorkingCopy.Conflicts.UseTheirs
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.Worktree.Open
</details>
### ![uk__UA](https://img.shields.io/badge/uk__UA-81.34%25-yellow)
### ![uk__UA](https://img.shields.io/badge/uk__UA-79.93%25-yellow)
<details>
<summary>Missing keys in uk_UA.axaml</summary>
@@ -843,10 +923,16 @@ This document shows the translation status of each locale file in the repository
- Text.Bisect.Skip
- Text.Bisect.WaitingForRange
- Text.BranchCM.ResetToSelectedCommit
- Text.BranchCM.SwitchToWorktree
- Text.BranchTree.Ahead
- Text.BranchTree.AheadBehind
- Text.BranchTree.Behind
- Text.BranchTree.InvalidUpstream
- Text.BranchTree.Remote
- Text.BranchTree.Status
- Text.BranchTree.Tracking
- Text.BranchTree.URL
- Text.BranchTree.Worktree
- Text.ChangeSubmoduleUrl
- Text.ChangeSubmoduleUrl.Submodule
- Text.ChangeSubmoduleUrl.URL
@@ -871,6 +957,7 @@ This document shows the translation status of each locale file in the repository
- Text.CommitDetail.Changes.Count
- Text.CommitDetail.Info.Key
- Text.CommitDetail.Info.Signer
- Text.CommitMessageTextBox.PasteAndReplaceAll
- Text.CommitMessageTextBox.SubjectCount
- Text.Configure.CommitMessageTemplate.BuiltinVars
- Text.Configure.CustomAction.Arguments.Tip
@@ -907,6 +994,9 @@ This document shows the translation status of each locale file in the repository
- Text.Diff.Old
- Text.Diff.Submodule.Deleted
- Text.DirHistories
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeUntracked
- Text.ExecuteCustomAction.Target
- Text.ExecuteCustomAction.Repository
@@ -916,12 +1006,14 @@ This document shows the translation status of each locale file in the repository
- Text.Hotkeys.Global.SwitchTab
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
- Text.InteractiveRebase.ReorderTip
- Text.Launcher.Workspaces
- Text.Launcher.Pages
- Text.Launcher.Workspaces
- Text.Merge.Edit
- Text.MoveSubmodule
- Text.MoveSubmodule.MoveTo
- Text.MoveSubmodule.Submodule
- Text.Preferences.AI.ReadApiKeyFromEnv
- Text.Preferences.Appearance.UseAutoHideScrollBars
- Text.Preferences.General.EnableCompactFolders
- Text.Preferences.General.ShowChangesPageByDefault
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
@@ -951,6 +1043,7 @@ This document shows the translation status of each locale file in the repository
- Text.ResetWithoutCheckout
- Text.ResetWithoutCheckout.MoveTo
- Text.ResetWithoutCheckout.Target
- Text.ScanRepositories.UseCustomDir
- Text.SetSubmoduleBranch
- Text.SetSubmoduleBranch.Submodule
- Text.SetSubmoduleBranch.Current
@@ -989,7 +1082,9 @@ This document shows the translation status of each locale file in the repository
- Text.WorkingCopy.ClearCommitHistories
- Text.WorkingCopy.ClearCommitHistories.Confirm
- Text.WorkingCopy.ConfirmCommitWithDetachedHead
- Text.WorkingCopy.NoVerify
- Text.WorkingCopy.ResetAuthor
- Text.Worktree.Open
</details>

View File

@@ -1 +1 @@
2025.32
2025.33

View File

@@ -170,6 +170,19 @@ namespace SourceGit
return false;
}
public static async Task<Models.ConfirmEmptyCommitResult> AskConfirmEmptyCommitAsync(bool hasLocalChanges)
{
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
{
var confirm = new Views.ConfirmEmptyCommit();
confirm.TxtMessage.Text = Text(hasLocalChanges ? "ConfirmEmptyCommit.WithLocalChanges" : "ConfirmEmptyCommit.NoLocalChanges");
confirm.BtnStageAllAndCommit.IsVisible = hasLocalChanges;
return await confirm.ShowDialog<Models.ConfirmEmptyCommitResult>(owner);
}
return Models.ConfirmEmptyCommitResult.Cancel;
}
public static void RaiseException(string context, string message)
{
if (Current is App { _launcher: not null } app)

View File

@@ -1,22 +1,39 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace SourceGit.Commands
{
public class Commit : Command
{
public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor)
public Commit(string repo, string message, bool signOff, bool noVerify, bool amend, bool resetAuthor)
{
_tmpFile = Path.GetTempFileName();
_message = message;
WorkingDirectory = repo;
Context = repo;
Args = $"commit --allow-empty --file={_tmpFile.Quoted()}";
var builder = new StringBuilder();
builder.Append("commit --allow-empty --file=");
builder.Append(_tmpFile.Quoted());
builder.Append(' ');
if (signOff)
Args += " --signoff";
builder.Append("--signoff ");
if (noVerify)
builder.Append("--no-verify ");
if (amend)
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
{
builder.Append("--amend ");
if (resetAuthor)
builder.Append("--reset-author ");
builder.Append("--no-edit");
}
Args = builder.ToString();
}
public async Task<bool> RunAsync()

View File

@@ -17,12 +17,15 @@ namespace SourceGit.Commands
var tool = Native.OS.GetDiffMergeTool(true);
if (tool == null)
{
App.RaiseException(Context, "Invalid merge tool in preference setting!");
App.RaiseException(Context, "Invalid diff/merge tool in preference setting!");
return;
}
if (string.IsNullOrEmpty(tool.Cmd))
{
if (!CheckGitConfiguration())
return;
Args = $"difftool -g --no-prompt {_option}";
}
else
@@ -41,6 +44,34 @@ namespace SourceGit.Commands
}
}
private bool CheckGitConfiguration()
{
var config = new Config(WorkingDirectory).ReadAll();
if (config.TryGetValue("diff.guitool", out var guiTool))
return CheckCLIBasedTool(guiTool);
if (config.TryGetValue("merge.guitool", out var mergeGuiTool))
return CheckCLIBasedTool(mergeGuiTool);
if (config.TryGetValue("diff.tool", out var diffTool))
return CheckCLIBasedTool(diffTool);
if (config.TryGetValue("merge.tool", out var mergeTool))
return CheckCLIBasedTool(mergeTool);
App.RaiseException(Context, "Missing git configuration: diff.guitool");
return false;
}
private bool CheckCLIBasedTool(string tool)
{
if (tool.StartsWith("vimdiff", StringComparison.Ordinal) ||
tool.StartsWith("nvimdiff", StringComparison.Ordinal))
{
App.RaiseException(Context, $"CLI based diff tool \"{tool}\" is not supported by this app!");
return false;
}
return true;
}
private Models.DiffOption _option;
}
}

View File

@@ -7,20 +7,21 @@ namespace SourceGit.Commands
{
public class IssueTracker : Command
{
public IssueTracker(string repo, string storage)
public IssueTracker(string repo, bool isShared)
{
WorkingDirectory = repo;
Context = repo;
if (string.IsNullOrEmpty(storage))
if (isShared)
{
_isStorageFileExists = true;
_baseArg = "config --local";
var storage = $"{repo}/.issuetracker";
_isStorageFileExists = File.Exists(storage);
_baseArg = $"config -f {storage.Quoted()}";
}
else
{
_isStorageFileExists = File.Exists(storage);
_baseArg = $"config -f {storage.Quoted()}";
_isStorageFileExists = true;
_baseArg = "config --local";
}
}
@@ -79,12 +80,24 @@ namespace SourceGit.Commands
return false;
}
public async Task<bool> RemoveAsync(Models.IssueTracker rule)
public async Task<bool> UpdateRegexAsync(Models.IssueTracker rule)
{
Args = $"{_baseArg} issuetracker.{rule.Name.Quoted()}.regex {rule.RegexString.Quoted()}";
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> UpdateURLTemplateAsync(Models.IssueTracker rule)
{
Args = $"{_baseArg} issuetracker.{rule.Name.Quoted()}.url {rule.URLTemplate.Quoted()}";
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> RemoveAsync(string name)
{
if (!_isStorageFileExists)
return true;
Args = $"{_baseArg} --remove-section issuetracker.{rule.Name.Quoted()}";
Args = $"{_baseArg} --remove-section issuetracker.{name.Quoted()}";
return await ExecAsync().ConfigureAwait(false);
}

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
namespace SourceGit.Commands
{
@@ -16,13 +17,17 @@ namespace SourceGit.Commands
var tool = Native.OS.GetDiffMergeTool(false);
if (tool == null)
{
App.RaiseException(Context, "Invalid merge tool in preference setting!");
App.RaiseException(Context, "Invalid diff/merge tool in preference setting!");
return false;
}
if (string.IsNullOrEmpty(tool.Cmd))
{
Args = $"mergetool {_file}";
var ok = await CheckGitConfigurationAsync();
if (!ok)
return false;
Args = $"mergetool -g --no-prompt {_file}";
}
else
{
@@ -33,6 +38,28 @@ namespace SourceGit.Commands
return await ExecAsync().ConfigureAwait(false);
}
private async Task<bool> CheckGitConfigurationAsync()
{
var tool = await new Config(WorkingDirectory).GetAsync("merge.guitool");
if (string.IsNullOrEmpty(tool))
tool = await new Config(WorkingDirectory).GetAsync("merge.tool");
if (string.IsNullOrEmpty(tool))
{
App.RaiseException(Context, "Missing git configuration: merge.guitool");
return false;
}
if (tool.StartsWith("vimdiff", StringComparison.Ordinal) ||
tool.StartsWith("nvimdiff", StringComparison.Ordinal))
{
App.RaiseException(Context, $"CLI based merge tool \"{tool}\" is not supported by this app!");
return false;
}
return true;
}
private string _file;
}
}

View File

@@ -15,7 +15,7 @@ namespace SourceGit.Commands
{
WorkingDirectory = repo;
Context = repo;
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)%00%(worktreepath)\"";
}
public async Task<List<Models.Branch>> GetResultAsync()
@@ -26,15 +26,16 @@ namespace SourceGit.Commands
return branches;
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
var remoteHeads = new Dictionary<string, string>();
var mismatched = new HashSet<string>();
var remotes = new Dictionary<string, Models.Branch>();
foreach (var line in lines)
{
var b = ParseLine(line);
var b = ParseLine(line, mismatched);
if (b != null)
{
branches.Add(b);
if (!b.IsLocal)
remoteHeads.Add(b.FullName, b.Head);
remotes.Add(b.FullName, b);
}
}
@@ -42,15 +43,16 @@ namespace SourceGit.Commands
{
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
{
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
if (remotes.TryGetValue(b.Upstream, out var upstream))
{
b.IsUpstreamGone = false;
b.TrackStatus ??= await new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).GetResultAsync().ConfigureAwait(false);
if (mismatched.Contains(b.FullName))
await new QueryTrackStatus(WorkingDirectory).GetResultAsync(b, upstream).ConfigureAwait(false);
}
else
{
b.IsUpstreamGone = true;
b.TrackStatus ??= new Models.BranchTrackStatus();
}
}
}
@@ -58,10 +60,10 @@ namespace SourceGit.Commands
return branches;
}
private Models.Branch ParseLine(string line)
private Models.Branch ParseLine(string line, HashSet<string> mismatched)
{
var parts = line.Split('\0');
if (parts.Length != 6)
if (parts.Length != 7)
return null;
var branch = new Models.Branch();
@@ -103,12 +105,13 @@ namespace SourceGit.Commands
branch.Upstream = parts[4];
branch.IsUpstreamGone = false;
if (!branch.IsLocal ||
string.IsNullOrEmpty(branch.Upstream) ||
string.IsNullOrEmpty(parts[5]) ||
parts[5].Equals("=", StringComparison.Ordinal))
branch.TrackStatus = new Models.BranchTrackStatus();
if (branch.IsLocal &&
!string.IsNullOrEmpty(branch.Upstream) &&
!string.IsNullOrEmpty(parts[5]) &&
!parts[5].Equals("=", StringComparison.Ordinal))
mismatched.Add(branch.FullName);
branch.WorktreePath = parts[6];
return branch;
}
}

View File

@@ -12,7 +12,7 @@ namespace SourceGit.Commands
WorkingDirectory = repo;
Context = repo;
Args = $"log --date-order --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
Args = $"log --topo-order --right-only --max-parents=1 --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
}
public async Task<List<Models.InteractiveCommit>> GetResultAsync()

View File

@@ -0,0 +1,27 @@
using System.IO;
using System.Threading.Tasks;
namespace SourceGit.Commands
{
public class QueryGitCommonDir : Command
{
public QueryGitCommonDir(string workDir)
{
WorkingDirectory = workDir;
Args = "rev-parse --git-common-dir";
RaiseError = false;
}
public async Task<string> GetResultAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess || string.IsNullOrEmpty(rs.StdOut))
return string.Empty;
var dir = rs.StdOut.Trim();
if (Path.IsPathRooted(dir))
return dir;
return Path.GetFullPath(Path.Combine(WorkingDirectory, dir));
}
}
}

View File

@@ -5,31 +5,28 @@ namespace SourceGit.Commands
{
public class QueryTrackStatus : Command
{
public QueryTrackStatus(string repo, string local, string upstream)
public QueryTrackStatus(string repo)
{
WorkingDirectory = repo;
Context = repo;
Args = $"rev-list --left-right {local}...{upstream}";
}
public async Task<Models.BranchTrackStatus> GetResultAsync()
public async Task GetResultAsync(Models.Branch local, Models.Branch remote)
{
var status = new Models.BranchTrackStatus();
Args = $"rev-list --left-right {local.Head}...{remote.Head}";
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return status;
return;
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (line[0] == '>')
status.Behind.Add(line.Substring(1));
local.Behind.Add(line.Substring(1));
else
status.Ahead.Add(line.Substring(1));
local.Ahead.Add(line.Substring(1));
}
return status;
}
}
}

View File

@@ -0,0 +1,28 @@
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace SourceGit.Converters
{
public static class DirtyStateConverters
{
public static readonly FuncValueConverter<Models.DirtyState, IBrush> ToBrush =
new FuncValueConverter<Models.DirtyState, IBrush>(v =>
{
if (v.HasFlag(Models.DirtyState.HasLocalChanges))
return Brushes.Gray;
if (v.HasFlag(Models.DirtyState.HasPendingPullOrPush))
return Brushes.RoyalBlue;
return Brushes.Transparent;
});
public static readonly FuncValueConverter<Models.DirtyState, string> ToDesc =
new FuncValueConverter<Models.DirtyState, string>(v =>
{
if (v.HasFlag(Models.DirtyState.HasLocalChanges))
return " • " + App.Text("DirtyState.HasLocalChanges");
if (v.HasFlag(Models.DirtyState.HasPendingPullOrPush))
return " • " + App.Text("DirtyState.HasPendingPullOrPush");
return " • " + App.Text("DirtyState.UpToDate");
});
}
}

View File

@@ -1,36 +1,14 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SourceGit.Models
{
public class BranchTrackStatus
{
public List<string> Ahead { get; set; } = new List<string>();
public List<string> Behind { get; set; } = new List<string>();
public bool IsVisible => Ahead.Count > 0 || Behind.Count > 0;
public override string ToString()
{
if (Ahead.Count == 0 && Behind.Count == 0)
return string.Empty;
var track = "";
if (Ahead.Count > 0)
track += $"{Ahead.Count}↑";
if (Behind.Count > 0)
track += $" {Behind.Count}↓";
return track.Trim();
}
}
public enum BranchSortMode
{
Name = 0,
CommitterDate,
}
public partial class Branch
public class Branch
{
public string Name { get; set; }
public string FullName { get; set; }
@@ -40,18 +18,34 @@ namespace SourceGit.Models
public bool IsCurrent { get; set; }
public bool IsDetachedHead { get; set; }
public string Upstream { get; set; }
public BranchTrackStatus TrackStatus { get; set; }
public List<string> Ahead { get; set; } = [];
public List<string> Behind { get; set; } = [];
public string Remote { get; set; }
public bool IsUpstreamGone { get; set; }
public string WorktreePath { get; set; }
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
[GeneratedRegex(@"\s+")]
private static partial Regex REG_FIX_NAME();
public static string FixName(string name)
public bool IsTrackStatusVisible
{
return REG_FIX_NAME().Replace(name, "-");
get
{
return Ahead.Count + Behind.Count > 0;
}
}
public string TrackStatusDescription
{
get
{
var ahead = Ahead.Count;
var behind = Behind.Count;
if (ahead > 0)
return behind > 0 ? $"{ahead}↑ {behind}↓" : $"{ahead}↑";
return behind > 0 ? $"{behind}↓" : string.Empty;
}
}
public bool HasWorktree => !IsCurrent && !string.IsNullOrEmpty(WorktreePath);
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
}
}

View File

@@ -16,14 +16,6 @@ namespace SourceGit.Models
ByContent,
}
public enum CommitCheckPassed
{
None = 0,
DetachedHead,
Filter,
FileCount,
}
public class Commit
{
// As retrieved by: git mktree </dev/null

View File

@@ -0,0 +1,9 @@
namespace SourceGit.Models
{
public enum ConfirmEmptyCommitResult
{
Cancel = 0,
StageAllAndCommit,
CreateEmptyCommit,
}
}

View File

@@ -26,12 +26,11 @@ namespace SourceGit.Models
{
try
{
_regex = null;
_regex = new Regex(_regexString, RegexOptions.Multiline);
}
catch
{
// Ignore errors.
_regex = null;
}
}

View File

@@ -117,6 +117,12 @@ namespace SourceGit.Models
set => SetProperty(ref _apiKey, value);
}
public bool ReadApiKeyFromEnv
{
get => _readApiKeyFromEnv;
set => SetProperty(ref _readApiKeyFromEnv, value);
}
public string Model
{
get => _model;
@@ -176,15 +182,19 @@ namespace SourceGit.Models
public async Task ChatAsync(string prompt, string question, CancellationToken cancellation, Action<string> onUpdate)
{
var server = new Uri(_server);
var key = new ApiKeyCredential(_apiKey);
var oaiClient = _server.Contains("openai.azure.com/", StringComparison.Ordinal)
? new AzureOpenAIClient(server, key)
: new OpenAIClient(key, new() { Endpoint = server });
var client = oaiClient.GetChatClient(_model);
var messages = new List<ChatMessage>();
messages.Add(_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt));
messages.Add(new UserChatMessage(question));
var key = _readApiKeyFromEnv ? Environment.GetEnvironmentVariable(_apiKey) : _apiKey;
var endPoint = new Uri(_server);
var credential = new ApiKeyCredential(key);
var client = _server.Contains("openai.azure.com/", StringComparison.Ordinal)
? new AzureOpenAIClient(endPoint, credential)
: new OpenAIClient(credential, new() { Endpoint = endPoint });
var chatClient = client.GetChatClient(_model);
var messages = new List<ChatMessage>()
{
_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt),
new UserChatMessage(question),
};
try
{
@@ -192,7 +202,7 @@ namespace SourceGit.Models
if (_streaming)
{
var updates = client.CompleteChatStreamingAsync(messages, null, cancellation);
var updates = chatClient.CompleteChatStreamingAsync(messages, null, cancellation);
await foreach (var update in updates)
{
@@ -202,7 +212,7 @@ namespace SourceGit.Models
}
else
{
var completion = await client.CompleteChatAsync(messages, null, cancellation);
var completion = await chatClient.CompleteChatAsync(messages, null, cancellation);
if (completion.Value.Content.Count > 0)
rsp.Append(completion.Value.Content[0].Text);
@@ -220,6 +230,7 @@ namespace SourceGit.Models
private string _name;
private string _server;
private string _apiKey;
private bool _readApiKeyFromEnv = false;
private string _model;
private bool _streaming = true;
private string _analyzeDiffPrompt;

View File

@@ -158,6 +158,12 @@ namespace SourceGit.Models
set;
} = false;
public bool NoVerifyOnCommit
{
get;
set;
} = false;
public bool IncludeUntrackedWhenStash
{
get;

View File

@@ -14,7 +14,6 @@
<StreamGeometry x:Key="Icons.CheckCircled">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Changes">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</StreamGeometry>
<StreamGeometry x:Key="Icons.CherryPick">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</StreamGeometry>
<StreamGeometry x:Key="Icons.CircleDown">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Clear">M512 57c251 0 455 204 455 455S763 967 512 967 57 763 57 512 261 57 512 57zm181 274c-11-11-29-11-40 0L512 472 371 331c-11-11-29-11-40 0-11 11-11 29 0 40L471 512 331 653c-11 11-11 29 0 40 11 11 29 11 40 0l141-141 141 141c11 11 29 11 40 0 11-11 11-29 0-40L552 512l141-141c11-11 11-29 0-40z</StreamGeometry>
<StreamGeometry x:Key="Icons.ClearNotifications">M591 907A85 85 0 01427 875h114a299 299 0 0050 32zM725 405c130 0 235 105 235 235s-105 235-235 235-235-105-235-235 105-235 235-235zM512 64a43 43 0 0143 43v24c126 17 229 107 264 225A298 298 0 00725 341l-4 0A235 235 0 00512 213l-5 0c-125 4-224 104-228 229l-0 6v167a211 211 0 01-26 101l-4 7-14 23h211a298 298 0 0050 85l-276-0a77 77 0 01-66-39c-13-22-14-50-2-73l2-4 22-36c10-17 16-37 17-57l0-7v-167C193 287 313 153 469 131V107a43 43 0 0139-43zm345 505L654 771a149 149 0 00202-202zM725 491a149 149 0 00-131 220l202-202A149 149 0 00725 491z</StreamGeometry>
<StreamGeometry x:Key="Icons.Clean">M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z</StreamGeometry>
@@ -125,6 +124,7 @@
<StreamGeometry x:Key="Icons.Submodules">M416 587c21 0 37 17 37 37v299A37 37 0 01416 960h-299a37 37 0 01-37-37v-299c0-21 17-37 37-37h299zm448 0c21 0 37 17 37 37v299A37 37 0 01864 960h-299a37 37 0 01-37-37v-299c0-21 17-37 37-37h299zM758 91l183 189a37 37 0 010 52l-182 188a37 37 0 01-53 1l-183-189a37 37 0 010-52l182-188a37 37 0 0153-1zM416 139c21 0 37 17 37 37v299A37 37 0 01416 512h-299a37 37 0 01-37-37v-299c0-21 17-37 37-37h299z</StreamGeometry>
<StreamGeometry x:Key="Icons.Subject">M512 0C230 0 0 230 0 512c0 145 60 282 166 375L90 1024H512c282 0 512-230 512-512S794 0 512 0z</StreamGeometry>
<StreamGeometry x:Key="Icons.SyntaxHighlight">M875 128h-725A107 107 0 0043 235v555A107 107 0 00149 896h725a107 107 0 00107-107v-555A107 107 0 00875 128zm-115 640h-183v-58l25-3c15 0 19-8 14-24l-22-61H419l-28 82 39 2V768h-166v-58l18-3c18-2 22-11 26-24l125-363-40-4V256h168l160 448 39 3zM506 340l-72 218h145l-71-218h-2z</StreamGeometry>
<StreamGeometry x:Key="Icons.Tabs">M1097 372h-460l-146-299H146a73 73 0 00-73 73v731a73 73 0 0073 73h878a73 73 0 0073-73V372zM146 0h390l146 299h488V878a146 146 0 01-146 146H146a146 146 0 01-146-146V146a146 146 0 01146-146zm439 0h195l146 246h-195l-146-246zm244 0h195a146 146 0 01146 146v100h-195l-146-246z</StreamGeometry>
<StreamGeometry x:Key="Icons.Tag">M177 156c-22 5-33 17-36 37c-10 57-33 258-13 278l445 445c23 23 61 23 84 0l246-246c23-23 23-61 0-84l-445-445C437 120 231 145 177 156zM331 344c-26 26-69 26-95 0c-26-26-26-69 0-95s69-26 95 0C357 276 357 318 331 344z</StreamGeometry>
<StreamGeometry x:Key="Icons.Tag.Add">M683 537h-144v-142h-142V283H239a44 44 0 00-41 41v171a56 56 0 0014 34l321 321a41 41 0 0058 0l174-174a41 41 0 000-58zm-341-109a41 41 0 110-58a41 41 0 010 58zM649 284V142h-69v142h-142v68h142v142h69v-142h142v-68h-142z</StreamGeometry>
<StreamGeometry x:Key="Icons.Tags">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</StreamGeometry>
@@ -147,7 +147,7 @@
<StreamGeometry x:Key="Icons.Window.Maximize">M153 154h768v768h-768v-768zm64 64v640h640v-640h-640z</StreamGeometry>
<StreamGeometry x:Key="Icons.Window.Restore">M796 231v727H64V231h732zm-82 78H146V880h567V309zM229 66H960v732H796v-82h82V148h-567v82h-82V66z</StreamGeometry>
<StreamGeometry x:Key="Icons.WordWrap">M248 221a77 77 0 00-30-21c-18-7-40-10-68-5a224 224 0 00-45 13c-5 2-10 5-15 8l-3 2v68l11-9c10-8 21-14 34-19 13-5 26-7 39-7 12 0 21 3 28 10 6 6 9 16 9 29l-62 9c-14 2-26 6-36 11a80 80 0 00-25 20c-7 8-12 17-15 27-6 21-6 44 1 65a70 70 0 0041 43c10 4 21 6 34 6a80 80 0 0063-28v22h64V298c0-16-2-31-6-44a91 91 0 00-18-33zm-41 121v15c0 8-1 15-4 22a48 48 0 01-24 29 44 44 0 01-33 2 29 29 0 01-10-6 25 25 0 01-6-9 30 30 0 01-2-12c0-5 1-9 2-14a21 21 0 015-9 28 28 0 0110-7 83 83 0 0120-5l42-6zm323-68a144 144 0 00-16-42 87 87 0 00-28-29 75 75 0 00-41-11 73 73 0 00-44 14c-6 5-12 11-17 17V64H326v398h59v-18c8 10 18 17 30 21 6 2 13 3 21 3 16 0 31-4 43-11 12-7 23-18 31-31a147 147 0 0019-46 248 248 0 006-57c0-17-2-33-5-49zm-55 49c0 15-1 28-4 39-2 11-6 20-10 27a41 41 0 01-15 15 37 37 0 01-36 1 44 44 0 01-13-12 59 59 0 01-9-18A76 76 0 01384 352v-33c0-10 1-20 4-29 2-8 6-15 10-22a43 43 0 0115-13 37 37 0 0119-5 35 35 0 0132 18c4 6 7 14 9 23 2 9 3 20 3 31zM154 634a58 58 0 0120-15c14-6 35-7 49-1 7 3 13 6 20 12l21 17V572l-6-4a124 124 0 00-58-14c-20 0-38 4-54 11-16 7-30 17-41 30-12 13-20 29-26 46-6 17-9 36-9 57 0 18 3 36 8 52 6 16 14 30 24 42 10 12 23 21 38 28 15 7 32 10 50 10 15 0 28-2 39-5 11-3 21-8 30-14l5-4v-57l-13 6a26 26 0 01-5 2c-3 1-6 2-8 3-2 1-15 6-15 6-4 2-9 3-14 4a63 63 0 01-38-4 53 53 0 01-20-14 70 70 0 01-13-24 111 111 0 01-5-34c0-13 2-26 5-36 3-10 8-19 14-26zM896 384h-256V320h288c21 1 32 12 32 32v384c0 18-12 32-32 32H504l132 133-45 45-185-185c-16-21-16-25 0-45l185-185L637 576l-128 128H896V384z</StreamGeometry>
<StreamGeometry x:Key="Icons.Workspace">M128 691H6V38h838v160h-64V102H70v525H128zM973 806H154V250h819v557zm-755-64h691V314H218v429zM365 877h448v64h-448z</StreamGeometry>
<StreamGeometry x:Key="Icons.Workspace">M0 512M1024 512M512 0M512 1024M128 691H6V38h838v160h-64V102H70v525H128zM973 806H154V250h819v557zm-755-64h691V314H218v429zM365 877h448v64h-448z</StreamGeometry>
<StreamGeometry x:Key="Icons.Worktree">M853 267H514c-4 0-6-2-9-4l-38-66c-13-21-38-36-64-36H171c-41 0-75 34-75 75v555c0 41 34 75 75 75h683c41 0 75-34 75-75V341c0-41-34-75-75-75zm-683-43h233c4 0 6 2 9 4l38 66c13 21 38 36 64 36H853c6 0 11 4 11 11v75h-704V235c0-6 4-11 11-11zm683 576H171c-6 0-11-4-11-11V480h704V789c0 6-4 11-11 11z</StreamGeometry>
<StreamGeometry x:Key="Icons.Worktree.Add">M896 96 614 96c-58 0-128-19-179-51C422 38 390 19 358 19L262 19 128 19c-70 0-128 58-128 128l0 736c0 70 58 128 128 128l768 0c70 0 128-58 128-128L1024 224C1024 154 966 96 896 96zM704 685 544 685l0 160c0 19-13 32-32 32s-32-13-32-32l0-160L320 685c-19 0-32-13-32-32 0-19 13-32 32-32l160 0L480 461c0-19 13-32 32-32s32 13 32 32l0 160L704 621c19 0 32 13 32 32C736 666 723 685 704 685zM890 326 102 326 102 250c0-32 32-64 64-64l659 0c38 0 64 32 64 64L890 326z</StreamGeometry>
<StreamGeometry x:Key="Icons.Worktrees">M1182 527a91 91 0 00-88-117H92a91 91 0 00-88 117l137 441A80 80 0 00217 1024h752a80 80 0 0076-56zM133 295a31 31 0 0031 31h858a31 31 0 0031-31A93 93 0 00959 203H226a93 93 0 00-94 92zM359 123h467a31 31 0 0031-31A92 92 0 00765 0H421a92 92 0 00-92 92 31 31 0 0031 31z</StreamGeometry>

View File

@@ -268,7 +268,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stashen &amp; wieder anwenden</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Neuer Branch-Name:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Branch-Name</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Leerzeichen werden durch Bindestriche ersetzt.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Lokalen Branch erstellen</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">Überschreibe existierenden Branch</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Tag erstellen...</x:String>
@@ -488,8 +487,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">In Browser öffnen</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">FEHLER</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">INFO</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Arbeitsplätze</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">Tabs</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Arbeitsplätze</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Branch mergen</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">Merge-Nachricht anpassen</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">Ziel-Branch:</x:String>

View File

@@ -70,12 +70,18 @@
<x:String x:Key="Text.BranchCM.Rebase" xml:space="preserve">Rebase ${0}$ on ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Rename ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.ResetToSelectedCommit" xml:space="preserve">Reset ${0}$ to ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.SwitchToWorktree" xml:space="preserve">Switch to ${0}$ (worktree)</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Set Tracking Branch...</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Branch Compare</x:String>
<x:String x:Key="Text.BranchTree.Ahead" xml:space="preserve">{0} commit(s) ahead</x:String>
<x:String x:Key="Text.BranchTree.AheadBehind" xml:space="preserve">{0} commit(s) ahead, {1} commit(s) behind</x:String>
<x:String x:Key="Text.BranchTree.Behind" xml:space="preserve">{0} commit(s) behind</x:String>
<x:String x:Key="Text.BranchTree.InvalidUpstream" xml:space="preserve">Invalid</x:String>
<x:String x:Key="Text.BranchTree.Remote" xml:space="preserve">REMOTE</x:String>
<x:String x:Key="Text.BranchTree.Status" xml:space="preserve">STATUS</x:String>
<x:String x:Key="Text.BranchTree.Tracking" xml:space="preserve">TRACKING</x:String>
<x:String x:Key="Text.BranchTree.URL" xml:space="preserve">URL</x:String>
<x:String x:Key="Text.BranchTree.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCEL</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Reset to Parent Revision</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Reset to This Revision</x:String>
@@ -166,6 +172,7 @@
<x:String x:Key="Text.CommitDetail.Info.Signer" xml:space="preserve">Signer:</x:String>
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">Open in Browser</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Description</x:String>
<x:String x:Key="Text.CommitMessageTextBox.PasteAndReplaceAll" xml:space="preserve">Paste (Replace all)</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectCount" xml:space="preserve">SUBJECT</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Enter commit subject</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Repository Configure</x:String>
@@ -263,7 +270,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash &amp; Reapply</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">New Branch Name:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Enter branch name.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Spaces will be replaced with dashes.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Create Local Branch</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">Overwrite existing branch</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Create Tag...</x:String>
@@ -334,6 +340,9 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Increase Number of Visible Lines</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">SELECT FILE TO VIEW CHANGES</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">Dir History</x:String>
<x:String x:Key="Text.DirtyState.HasLocalChanges" xml:space="preserve">Has Local Changes</x:String>
<x:String x:Key="Text.DirtyState.HasPendingPullOrPush" xml:space="preserve">Mismatched with Upstream</x:String>
<x:String x:Key="Text.DirtyState.UpToDate" xml:space="preserve">Already Up-To-Date</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">Discard Changes</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">All local changes in working copy.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Changes:</x:String>
@@ -483,8 +492,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Open in Browser</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Workspaces</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">Tabs</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Workspaces</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Merge Branch</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">Customize merge message</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">Into:</x:String>
@@ -530,6 +539,7 @@
<x:String x:Key="Text.Preferences.AI.GenerateSubjectPrompt" xml:space="preserve">Generate Subject Prompt</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Model</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Name</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Entered value is the name to load API key from ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Server</x:String>
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">Enable Streaming</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">APPEARANCE</x:String>
@@ -542,6 +552,7 @@
<x:String x:Key="Text.Preferences.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">Use monospace font only in text editor</x:String>
<x:String x:Key="Text.Preferences.Appearance.Theme" xml:space="preserve">Theme</x:String>
<x:String x:Key="Text.Preferences.Appearance.ThemeOverrides" xml:space="preserve">Theme Overrides</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseAutoHideScrollBars" xml:space="preserve">Use auto-hide scrollbars</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseFixedTabWidth" xml:space="preserve">Use fixed tab width in titlebar</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseNativeWindowFrame" xml:space="preserve">Use native window frame</x:String>
<x:String x:Key="Text.Preferences.DiffMerge" xml:space="preserve">DIFF/MERGE TOOL</x:String>
@@ -726,6 +737,7 @@
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">Patch has been saved successfully!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">Scan Repositories</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">Root Dir:</x:String>
<x:String x:Key="Text.ScanRepositories.UseCustomDir" xml:space="preserve">Scan another custom directory</x:String>
<x:String x:Key="Text.SelfUpdate" xml:space="preserve">Check for Updates...</x:String>
<x:String x:Key="Text.SelfUpdate.Available" xml:space="preserve">New version of this software is available: </x:String>
<x:String x:Key="Text.SelfUpdate.Error" xml:space="preserve">Check for updates failed!</x:String>
@@ -858,6 +870,7 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NO RECENT INPUT MESSAGES</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">NO COMMIT TEMPLATES</x:String>
<x:String x:Key="Text.WorkingCopy.NoVerify" xml:space="preserve">No-Verify</x:String>
<x:String x:Key="Text.WorkingCopy.ResetAuthor" xml:space="preserve">Reset Author</x:String>
<x:String x:Key="Text.WorkingCopy.SignOff" xml:space="preserve">SignOff</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String>
@@ -873,6 +886,7 @@
<x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Copy Path</x:String>
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Lock</x:String>
<x:String x:Key="Text.Worktree.Open" xml:space="preserve">Open</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Remove</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Unlock</x:String>
</ResourceDictionary>

View File

@@ -74,12 +74,18 @@
<x:String x:Key="Text.BranchCM.Rebase" xml:space="preserve">Rebase ${0}$ en ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Renombrar ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.ResetToSelectedCommit" xml:space="preserve">Resetear ${0}$ a ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.SwitchToWorktree" xml:space="preserve">Cambiar a ${0}$ (worktree)</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Establecer Rama de Seguimiento...</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Comparar Ramas</x:String>
<x:String x:Key="Text.BranchTree.Ahead" xml:space="preserve">{0} commit(s) adelante</x:String>
<x:String x:Key="Text.BranchTree.AheadBehind" xml:space="preserve">{0} commit(s) adelante, {1} commit(s) detrás</x:String>
<x:String x:Key="Text.BranchTree.Behind" xml:space="preserve">{0} commit(s) detrás</x:String>
<x:String x:Key="Text.BranchTree.InvalidUpstream" xml:space="preserve">Inválido</x:String>
<x:String x:Key="Text.BranchTree.Remote" xml:space="preserve">REMOTO</x:String>
<x:String x:Key="Text.BranchTree.Status" xml:space="preserve">ESTADO</x:String>
<x:String x:Key="Text.BranchTree.Tracking" xml:space="preserve">SEGUIMIENTO</x:String>
<x:String x:Key="Text.BranchTree.URL" xml:space="preserve">URL</x:String>
<x:String x:Key="Text.BranchTree.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">CANCELAR</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Resetear a Revisión Padre</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Resetear a Esta Revisión</x:String>
@@ -170,6 +176,7 @@
<x:String x:Key="Text.CommitDetail.Info.Signer" xml:space="preserve">Firmante:</x:String>
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">Abrir en Navegador</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Descripción</x:String>
<x:String x:Key="Text.CommitMessageTextBox.PasteAndReplaceAll" xml:space="preserve">Pegar (Reemplazar todo)</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectCount" xml:space="preserve">ASUNTO</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Introducir asunto del commit</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Configurar Repositorio</x:String>
@@ -267,7 +274,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash &amp; Reaplicar</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nombre de la Nueva Rama:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Introduzca el nombre de la rama.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Los espacios serán reemplazados con guiones.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Crear Rama Local</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">Sobrescribir la rama existente</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Crear Etiqueta...</x:String>
@@ -338,6 +344,9 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Aumentar Número de Líneas Visibles</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">SELECCIONA ARCHIVO PARA VER CAMBIOS</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">Historial del directorio</x:String>
<x:String x:Key="Text.DirtyState.HasLocalChanges" xml:space="preserve">Tiene Cambios Locales</x:String>
<x:String x:Key="Text.DirtyState.HasPendingPullOrPush" xml:space="preserve">No coincide con Upstream</x:String>
<x:String x:Key="Text.DirtyState.UpToDate" xml:space="preserve">Ya se encuentra actualizado</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">Descartar Cambios</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">Todos los cambios locales en la copia de trabajo.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Cambios:</x:String>
@@ -487,8 +496,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Abrir en el Navegador</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">AVISO</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Espacios de trabajo</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">Páginas</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Espacios de trabajo</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Merge Rama</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">Personalizar mensaje de merge</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">En:</x:String>
@@ -534,6 +543,7 @@
<x:String x:Key="Text.Preferences.AI.GenerateSubjectPrompt" xml:space="preserve">Generar Subject Prompt</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Modelo</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Nombre</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">El valor ingresado es el nombre de la clave API a cargar desde ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Servidor</x:String>
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">Habilitar Transmisión</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">APARIENCIA</x:String>
@@ -546,6 +556,7 @@
<x:String x:Key="Text.Preferences.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">Usar solo fuente monospace en el editor de texto</x:String>
<x:String x:Key="Text.Preferences.Appearance.Theme" xml:space="preserve">Tema</x:String>
<x:String x:Key="Text.Preferences.Appearance.ThemeOverrides" xml:space="preserve">Sobreescritura de temas</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseAutoHideScrollBars" xml:space="preserve">Usar barras de desplazamiento que se oculten automáticamente</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseFixedTabWidth" xml:space="preserve">Usar ancho de pestaña fijo en la barra de título</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseNativeWindowFrame" xml:space="preserve">Usar marco de ventana nativo</x:String>
<x:String x:Key="Text.Preferences.DiffMerge" xml:space="preserve">HERRAMIENTA DIFF/MERGE</x:String>
@@ -559,6 +570,7 @@
<x:String x:Key="Text.Preferences.General.Locale" xml:space="preserve">Idioma</x:String>
<x:String x:Key="Text.Preferences.General.MaxHistoryCommits" xml:space="preserve">Commits en el historial</x:String>
<x:String x:Key="Text.Preferences.General.ShowAuthorTime" xml:space="preserve">Mostrar hora del autor en lugar de la hora del commit en el gráfico</x:String>
<x:String x:Key="Text.Preferences.General.ShowChangesPageByDefault" xml:space="preserve">Mostrar la página `CAMBIOS LOCALES` por defecto</x:String>
<x:String x:Key="Text.Preferences.General.ShowChildren" xml:space="preserve">Mostrar hijos en los detalles de commit</x:String>
<x:String x:Key="Text.Preferences.General.ShowTagsInGraph" xml:space="preserve">Mostrar etiquetas en el gráfico de commit</x:String>
<x:String x:Key="Text.Preferences.General.SubjectGuideLength" xml:space="preserve">Longitud de la guía del asunto</x:String>
@@ -729,6 +741,7 @@
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">¡El parche se ha guardado exitosamente!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">Escanear Repositorios</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">Directorio Raíz:</x:String>
<x:String x:Key="Text.ScanRepositories.UseCustomDir" xml:space="preserve">Escanear otro directorio personalizado</x:String>
<x:String x:Key="Text.SelfUpdate" xml:space="preserve">Buscar Actualizaciones...</x:String>
<x:String x:Key="Text.SelfUpdate.Available" xml:space="preserve">Nueva versión de este software disponible: </x:String>
<x:String x:Key="Text.SelfUpdate.Error" xml:space="preserve">¡Error al buscar actualizaciones!</x:String>
@@ -842,6 +855,8 @@
<x:String x:Key="Text.WorkingCopy.AddToGitIgnore.SingleFile" xml:space="preserve">Ignorar solo este archivo</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Enmendar</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">Puedes hacer stage a este archivo ahora.</x:String>
<x:String x:Key="Text.WorkingCopy.ClearCommitHistories" xml:space="preserve">Limpiar Historial</x:String>
<x:String x:Key="Text.WorkingCopy.ClearCommitHistories.Confirm" xml:space="preserve">¿Estás seguro de querer limpiar todo el historial de los mensajes de commit? Esta acción no se puede deshacer.</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageHelper" xml:space="preserve">Plantilla/Historias</x:String>
@@ -859,6 +874,7 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUIR ARCHIVOS NO RASTREADOS</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NO HAY MENSAJES DE ENTRADA RECIENTES</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">NO HAY PLANTILLAS DE COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.NoVerify" xml:space="preserve">Sin verificar</x:String>
<x:String x:Key="Text.WorkingCopy.ResetAuthor" xml:space="preserve">Restablecer Autor</x:String>
<x:String x:Key="Text.WorkingCopy.SignOff" xml:space="preserve">Firmar</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String>
@@ -874,6 +890,7 @@
<x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Copiar Ruta</x:String>
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Bloquear</x:String>
<x:String x:Key="Text.Worktree.Open" xml:space="preserve">Abrir</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Eliminar</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Desbloquear</x:String>
</ResourceDictionary>

View File

@@ -190,7 +190,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash &amp; Réappliquer</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nom de la nouvelle branche :</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Entrez le nom de la branche.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Les espaces seront remplacés par des tirets.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Créer une branche locale</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Créer un tag...</x:String>
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">Nouveau tag à :</x:String>

View File

@@ -267,7 +267,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stasha e Ripristina</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Nome Nuovo Branch:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Inserisci il nome del branch.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Gli spazi verranno rimpiazzati con dei trattini.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Crea Branch Locale</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">Sovrascrivi branch esistente</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Crea Tag...</x:String>
@@ -487,8 +486,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Apri nel Browser</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERRORE</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">AVVISO</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Workspaces</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">Schede</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Workspaces</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Unisci Branch</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">Personalizza messaggio di merge</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">In:</x:String>

View File

@@ -189,7 +189,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">スタッシュして再適用</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">新しいブランチの名前:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">ブランチの名前を入力</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">スペースはダッシュに置き換えられます。</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">ローカルブランチを作成</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">タグを作成...</x:String>
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">付与されるコミット:</x:String>

View File

@@ -74,12 +74,18 @@
<x:String x:Key="Text.BranchCM.Rebase" xml:space="preserve">Переместить ${0}$ на ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Переименовать ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.ResetToSelectedCommit" xml:space="preserve">Сбросить ${0}$ к ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.SwitchToWorktree" xml:space="preserve">Переключить на ${0}$ (рабочий каталог)</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Отслеживать ветку...</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Сравнение веток</x:String>
<x:String x:Key="Text.BranchTree.Ahead" xml:space="preserve">{0} ревизий вперёд</x:String>
<x:String x:Key="Text.BranchTree.AheadBehind" xml:space="preserve">{0} ревизий вперёд, {1} ревизий назад</x:String>
<x:String x:Key="Text.BranchTree.Behind" xml:space="preserve">{0} ревизий назад</x:String>
<x:String x:Key="Text.BranchTree.InvalidUpstream" xml:space="preserve">Неверно</x:String>
<x:String x:Key="Text.BranchTree.Remote" xml:space="preserve">УДАЛЁННЫЙ</x:String>
<x:String x:Key="Text.BranchTree.Status" xml:space="preserve">СОСТОЯНИЕ</x:String>
<x:String x:Key="Text.BranchTree.Tracking" xml:space="preserve">ОТСЛЕЖИВАНИЕ</x:String>
<x:String x:Key="Text.BranchTree.URL" xml:space="preserve">URL-АДРЕС</x:String>
<x:String x:Key="Text.BranchTree.Worktree" xml:space="preserve">РАБОЧИЙ КАТАЛОГ</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">ОТМЕНА</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">Сбросить родительскую ревизию</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Сбросить эту ревизию</x:String>
@@ -170,6 +176,7 @@
<x:String x:Key="Text.CommitDetail.Info.Signer" xml:space="preserve">Подписант:</x:String>
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">Открыть в браузере</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Описание</x:String>
<x:String x:Key="Text.CommitMessageTextBox.PasteAndReplaceAll" xml:space="preserve">Вставить (заменить всё)</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectCount" xml:space="preserve">СУБЪЕКТ</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Введите тему ревизии</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Настройка репозитория</x:String>
@@ -267,7 +274,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Отложить и применить повторно</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Имя новой ветки:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Введите имя ветки.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Пробелы будут заменены на тире.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Создать локальную ветку</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">Перезаписать существующую ветку</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Создать метку...</x:String>
@@ -338,6 +344,9 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Больше видимых строк</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">ВЫБЕРИТЕ ФАЙЛ ДЛЯ ПРОСМОТРА ИЗМЕНЕНИЙ</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">Каталог историй</x:String>
<x:String x:Key="Text.DirtyState.HasLocalChanges" xml:space="preserve">Есть локальные изменения</x:String>
<x:String x:Key="Text.DirtyState.HasPendingPullOrPush" xml:space="preserve">Не соответствует с исходящим потоком</x:String>
<x:String x:Key="Text.DirtyState.UpToDate" xml:space="preserve">В актуальном состоянии</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">Отклонить изменения</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">Все локальные изменения в рабочей копии.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Изменения:</x:String>
@@ -487,8 +496,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Открыть в браузере</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ОШИБКА</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">УВЕДОМЛЕНИЕ</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Рабочие места</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">Вкладки</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">Рабочие места</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Влить ветку</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">Изменить сообщение слияния</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">В:</x:String>
@@ -534,6 +543,7 @@
<x:String x:Key="Text.Preferences.AI.GenerateSubjectPrompt" xml:space="preserve">Создать запрос на тему</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Модель</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Имя:</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Введённое значение — это имя для загрузки API-ключа из ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Сервер</x:String>
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">Разрешить потоковую передачу</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">ВИД</x:String>
@@ -546,6 +556,7 @@
<x:String x:Key="Text.Preferences.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">В текстовом редакторе используется только моноширный шрифт</x:String>
<x:String x:Key="Text.Preferences.Appearance.Theme" xml:space="preserve">Тема</x:String>
<x:String x:Key="Text.Preferences.Appearance.ThemeOverrides" xml:space="preserve">Переопределение темы</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseAutoHideScrollBars" xml:space="preserve">Автоматически скрывать прокрутку</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseFixedTabWidth" xml:space="preserve">Использовать фиксированную ширину табуляции в строке заголовка.</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseNativeWindowFrame" xml:space="preserve">Использовать системное окно</x:String>
<x:String x:Key="Text.Preferences.DiffMerge" xml:space="preserve">ИНСТРУМЕНТ СРАВНЕНИЙ/СЛИЯНИЯ</x:String>
@@ -730,6 +741,7 @@
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">Заплатка успешно сохранена!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">Обнаружение репозиториев</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">Корневой каталог:</x:String>
<x:String x:Key="Text.ScanRepositories.UseCustomDir" xml:space="preserve">Сканировать другой пользовательский каталог</x:String>
<x:String x:Key="Text.SelfUpdate" xml:space="preserve">Проверить обновления...</x:String>
<x:String x:Key="Text.SelfUpdate.Available" xml:space="preserve">Доступна новая версия программного обеспечения: </x:String>
<x:String x:Key="Text.SelfUpdate.Error" xml:space="preserve">Не удалось проверить наличие обновлений!</x:String>
@@ -862,6 +874,7 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">ВКЛЮЧИТЬ НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">НЕТ ПОСЛЕДНИХ ВХОДНЫХ СООБЩЕНИЙ</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">НЕТ ШАБЛОНОВ РЕВИЗИИ</x:String>
<x:String x:Key="Text.WorkingCopy.NoVerify" xml:space="preserve">Не проверять</x:String>
<x:String x:Key="Text.WorkingCopy.ResetAuthor" xml:space="preserve">Сбросить автора</x:String>
<x:String x:Key="Text.WorkingCopy.SignOff" xml:space="preserve">Завершение работы</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">СФОРМИРОВАННЫЕ</x:String>
@@ -877,6 +890,7 @@
<x:String x:Key="Text.Worktree" xml:space="preserve">РАБОЧИЙ КАТАЛОГ</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Копировать путь</x:String>
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Заблокировать</x:String>
<x:String x:Key="Text.Worktree.Open" xml:space="preserve">Открыть</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Удалить</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Разблокировать</x:String>
</ResourceDictionary>

View File

@@ -189,7 +189,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">பதுக்கிவை &amp; மீண்டும் இடு</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">புதிய கிளை பெயர்:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">கிளை பெயரை உள்ளிடவும்.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">இடைவெளிகள் கோடுகளால் மாற்றப்படும்.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">உள்ளக கிளையை உருவாக்கு</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">குறிச்சொல்லை உருவாக்கு...</x:String>
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">இங்கு புதிய குறிச்சொல்:</x:String>

View File

@@ -194,7 +194,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Сховати та Застосувати</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Назва нової гілки:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Введіть назву гілки.</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">Пробіли будуть замінені на тире.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Створити локальну гілку</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">Створити тег...</x:String>
<x:String x:Key="Text.CreateTag.BasedOn" xml:space="preserve">Новий тег для:</x:String>

View File

@@ -74,12 +74,18 @@
<x:String x:Key="Text.BranchCM.Rebase" xml:space="preserve">变基(rebase) ${0}$ 至 ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重命名 ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.ResetToSelectedCommit" xml:space="preserve">重置 ${0}$ 到 ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.SwitchToWorktree" xml:space="preserve">切换到 ${0}$ (工作树)</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切换上游分支 ...</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比较</x:String>
<x:String x:Key="Text.BranchTree.Ahead" xml:space="preserve">领先 {0} 个提交</x:String>
<x:String x:Key="Text.BranchTree.AheadBehind" xml:space="preserve">领先 {0} 个提交,落后 {1} 个提交</x:String>
<x:String x:Key="Text.BranchTree.Behind" xml:space="preserve">落后 {0} 个提交</x:String>
<x:String x:Key="Text.BranchTree.InvalidUpstream" xml:space="preserve">不存在</x:String>
<x:String x:Key="Text.BranchTree.Remote" xml:space="preserve">远程</x:String>
<x:String x:Key="Text.BranchTree.Status" xml:space="preserve">状态</x:String>
<x:String x:Key="Text.BranchTree.Tracking" xml:space="preserve">上游分支</x:String>
<x:String x:Key="Text.BranchTree.URL" xml:space="preserve">远程地址</x:String>
<x:String x:Key="Text.BranchTree.Worktree" xml:space="preserve">工作树</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重置文件到上一版本</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重置文件到该版本</x:String>
@@ -170,6 +176,7 @@
<x:String x:Key="Text.CommitDetail.Info.Signer" xml:space="preserve">签名者 </x:String>
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">浏览器中查看</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">详细描述</x:String>
<x:String x:Key="Text.CommitMessageTextBox.PasteAndReplaceAll" xml:space="preserve">粘贴(替换全部)</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectCount" xml:space="preserve">主题</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">填写提交信息主题</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">仓库配置</x:String>
@@ -267,7 +274,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">贮藏并自动恢复</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">新分支名 </x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">填写分支名称。</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">空格将被替换为'-'符号</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">创建本地分支</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">允许重置已存在的分支</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">新建标签 ...</x:String>
@@ -338,6 +344,9 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">增加可见的行数</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">请选择需要对比的文件</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">目录内容变更历史</x:String>
<x:String x:Key="Text.DirtyState.HasLocalChanges" xml:space="preserve">未提交的本地变更</x:String>
<x:String x:Key="Text.DirtyState.HasPendingPullOrPush" xml:space="preserve">当前分支HEAD与远端不一致</x:String>
<x:String x:Key="Text.DirtyState.UpToDate" xml:space="preserve">已是最新</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">放弃更改确认</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">所有本仓库未提交的修改。</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">变更 </x:String>
@@ -487,8 +496,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在浏览器中访问</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">出错了</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系统提示</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">工作区列表</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">页面列表</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">工作区列表</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">合并分支</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">编辑合并信息</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目标分支 </x:String>
@@ -534,6 +543,7 @@
<x:String x:Key="Text.Preferences.AI.GenerateSubjectPrompt" xml:space="preserve">Generate Subject Prompt</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">配置名称</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">从环境变量填写环境变量名中读取API密钥</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">服务地址</x:String>
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">启用流式输出</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">外观配置</x:String>
@@ -546,6 +556,7 @@
<x:String x:Key="Text.Preferences.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">仅在文本编辑器中使用等宽字体</x:String>
<x:String x:Key="Text.Preferences.Appearance.Theme" xml:space="preserve">主题</x:String>
<x:String x:Key="Text.Preferences.Appearance.ThemeOverrides" xml:space="preserve">主题自定义</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseAutoHideScrollBars" xml:space="preserve">允许滚动条自动隐藏</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseFixedTabWidth" xml:space="preserve">主标签使用固定宽度</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseNativeWindowFrame" xml:space="preserve">使用系统默认窗体样式</x:String>
<x:String x:Key="Text.Preferences.DiffMerge" xml:space="preserve">对比/合并工具</x:String>
@@ -730,6 +741,7 @@
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">补丁已成功保存!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">扫描仓库</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">根路径 </x:String>
<x:String x:Key="Text.ScanRepositories.UseCustomDir" xml:space="preserve">扫描其他自定义路径</x:String>
<x:String x:Key="Text.SelfUpdate" xml:space="preserve">检测更新...</x:String>
<x:String x:Key="Text.SelfUpdate.Available" xml:space="preserve">检测到软件有版本更新: </x:String>
<x:String x:Key="Text.SelfUpdate.Error" xml:space="preserve">获取最新版本信息失败!</x:String>
@@ -862,6 +874,7 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">没有提交信息记录</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">没有可应用的提交信息模板</x:String>
<x:String x:Key="Text.WorkingCopy.NoVerify" xml:space="preserve">跳过GIT钩子</x:String>
<x:String x:Key="Text.WorkingCopy.ResetAuthor" xml:space="preserve">重置提交者</x:String>
<x:String x:Key="Text.WorkingCopy.SignOff" xml:space="preserve">署名</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暂存</x:String>
@@ -877,6 +890,7 @@
<x:String x:Key="Text.Worktree" xml:space="preserve">本地工作树</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">复制工作树路径</x:String>
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">锁定工作树</x:String>
<x:String x:Key="Text.Worktree.Open" xml:space="preserve">打开工作树</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">移除工作树</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">解除工作树锁定</x:String>
</ResourceDictionary>

View File

@@ -74,12 +74,18 @@
<x:String x:Key="Text.BranchCM.Rebase" xml:space="preserve">重定基底 (rebase) ${0}$ 分支至 ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">重新命名 ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.ResetToSelectedCommit" xml:space="preserve">重設 ${0}$ 至 ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.SwitchToWorktree" xml:space="preserve">切換到 ${0}$ (工作區)</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">切換上游分支...</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">分支比較</x:String>
<x:String x:Key="Text.BranchTree.Ahead" xml:space="preserve">領先 {0} 次提交</x:String>
<x:String x:Key="Text.BranchTree.AheadBehind" xml:space="preserve">領先 {0} 次提交, 落後 {0} 次提交</x:String>
<x:String x:Key="Text.BranchTree.Behind" xml:space="preserve">落後 {0} 次提交</x:String>
<x:String x:Key="Text.BranchTree.InvalidUpstream" xml:space="preserve">無效</x:String>
<x:String x:Key="Text.BranchTree.Remote" xml:space="preserve">遠端</x:String>
<x:String x:Key="Text.BranchTree.Status" xml:space="preserve">狀態</x:String>
<x:String x:Key="Text.BranchTree.Tracking" xml:space="preserve">上游分支</x:String>
<x:String x:Key="Text.BranchTree.URL" xml:space="preserve">遠端網址</x:String>
<x:String x:Key="Text.BranchTree.Worktree" xml:space="preserve">工作區</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">取 消</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutFirstParentRevision" xml:space="preserve">重設檔案到上一版本</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">重設檔案為此版本</x:String>
@@ -170,6 +176,7 @@
<x:String x:Key="Text.CommitDetail.Info.Signer" xml:space="preserve">簽署人:</x:String>
<x:String x:Key="Text.CommitDetail.Info.WebLinks" xml:space="preserve">在瀏覽器中檢視</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">詳細描述</x:String>
<x:String x:Key="Text.CommitMessageTextBox.PasteAndReplaceAll" xml:space="preserve">粘貼(替換所有內容)</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectCount" xml:space="preserve">標題</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">填寫提交訊息標題</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">存放庫設定</x:String>
@@ -267,7 +274,6 @@
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">擱置變更並自動復原</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">新分支名稱:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">輸入分支名稱。</x:String>
<x:String x:Key="Text.CreateBranch.Name.WarnSpace" xml:space="preserve">空格將以英文破折號取代</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">建立本機分支</x:String>
<x:String x:Key="Text.CreateBranch.OverwriteExisting" xml:space="preserve">允許覆寫現有分支</x:String>
<x:String x:Key="Text.CreateTag" xml:space="preserve">新增標籤...</x:String>
@@ -338,6 +344,9 @@
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">增加可見的行數</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">請選擇需要對比的檔案</x:String>
<x:String x:Key="Text.DirHistories" xml:space="preserve">目錄内容變更歷史</x:String>
<x:String x:Key="Text.DirtyState.HasLocalChanges" xml:space="preserve">未提交的本地變更</x:String>
<x:String x:Key="Text.DirtyState.HasPendingPullOrPush" xml:space="preserve">當前分支的 HEAD 與上游不匹配</x:String>
<x:String x:Key="Text.DirtyState.UpToDate" xml:space="preserve">已更新至最新</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">捨棄變更</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">所有本機未提交的變更。</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">變更:</x:String>
@@ -487,8 +496,8 @@
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在瀏覽器中開啟連結</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">發生錯誤</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系統提示</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">工作區列表</x:String>
<x:String x:Key="Text.Launcher.Pages" xml:space="preserve">頁面列表</x:String>
<x:String x:Key="Text.Launcher.Workspaces" xml:space="preserve">工作區列表</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">合併分支</x:String>
<x:String x:Key="Text.Merge.Edit" xml:space="preserve">編輯合併訊息</x:String>
<x:String x:Key="Text.Merge.Into" xml:space="preserve">目標分支:</x:String>
@@ -534,6 +543,7 @@
<x:String x:Key="Text.Preferences.AI.GenerateSubjectPrompt" xml:space="preserve">產生提交訊息提示詞</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">名稱</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">從環境變數中(輸入為環境變數名稱)載入金鑰</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">伺服器</x:String>
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">啟用串流輸出</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">外觀設定</x:String>
@@ -546,6 +556,7 @@
<x:String x:Key="Text.Preferences.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">僅在文字編輯器中使用等寬字型</x:String>
<x:String x:Key="Text.Preferences.Appearance.Theme" xml:space="preserve">佈景主題</x:String>
<x:String x:Key="Text.Preferences.Appearance.ThemeOverrides" xml:space="preserve">自訂主題</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseAutoHideScrollBars" xml:space="preserve">允許滾動條自動隱藏</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseFixedTabWidth" xml:space="preserve">使用固定寬度的分頁標籤</x:String>
<x:String x:Key="Text.Preferences.Appearance.UseNativeWindowFrame" xml:space="preserve">使用系統原生預設視窗樣式</x:String>
<x:String x:Key="Text.Preferences.DiffMerge" xml:space="preserve">對比/合併工具</x:String>
@@ -730,6 +741,7 @@
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">修補檔已成功儲存!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">掃描存放庫</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">頂層目錄:</x:String>
<x:String x:Key="Text.ScanRepositories.UseCustomDir" xml:space="preserve">掃描其他自訂目錄</x:String>
<x:String x:Key="Text.SelfUpdate" xml:space="preserve">檢查更新...</x:String>
<x:String x:Key="Text.SelfUpdate.Available" xml:space="preserve">軟體有版本更新:</x:String>
<x:String x:Key="Text.SelfUpdate.Error" xml:space="preserve">取得最新版本資訊失敗!</x:String>
@@ -862,6 +874,7 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">顯示未追蹤檔案</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">沒有提交訊息記錄</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitTemplates" xml:space="preserve">沒有可套用的提交訊息範本</x:String>
<x:String x:Key="Text.WorkingCopy.NoVerify" xml:space="preserve">繞過 HOOKS 檢查</x:String>
<x:String x:Key="Text.WorkingCopy.ResetAuthor" xml:space="preserve">重設作者</x:String>
<x:String x:Key="Text.WorkingCopy.SignOff" xml:space="preserve">署名</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暫存</x:String>
@@ -877,6 +890,7 @@
<x:String x:Key="Text.Worktree" xml:space="preserve">本機工作區</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">複製工作區路徑</x:String>
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">鎖定工作區</x:String>
<x:String x:Key="Text.Worktree.Open" xml:space="preserve">開啟工作區</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">移除工作區</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">解除鎖定工作區</x:String>
</ResourceDictionary>

View File

@@ -17,6 +17,7 @@
<x:Double x:Key="ScrollBarSize">12</x:Double>
</Style.Resources>
<Setter Property="AllowAutoHide" Value="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseAutoHideScrollBars, Mode=OneWay}"/>
<Setter Property="ShowDelay" Value="0:0:0.1"/>
<Setter Property="HideDelay" Value="0:0:0.2"/>
</Style>
@@ -306,7 +307,6 @@
<Style Selector="TextBlock.group_header_label">
<Setter Property="Foreground" Value="{DynamicResource Brush.FG2}"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="10,0,0,0"/>
</Style>
<Style Selector="TextBlock.table_header">
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/>

View File

@@ -103,7 +103,7 @@ namespace SourceGit.ViewModels
var b = _repo.Branches.Find(x => x.IsLocal && x.Name == Branch);
if (b != null && _repo.HistoriesFilterMode == Models.FilterMode.Included)
_repo.SetBranchFilterMode(b, Models.FilterMode.Included, true, false);
_repo.SetBranchFilterMode(b, Models.FilterMode.Included, false, false);
_repo.MarkBranchesDirtyManually();
_repo.SetWatcherEnabled(true);

View File

@@ -107,7 +107,7 @@ namespace SourceGit.ViewModels
log.Complete();
if (_repo.HistoriesFilterMode == Models.FilterMode.Included)
_repo.SetBranchFilterMode(LocalBranch, Models.FilterMode.Included, true, false);
_repo.SetBranchFilterMode(LocalBranch, Models.FilterMode.Included, false, false);
_repo.MarkBranchesDirtyManually();
_repo.SetWatcherEnabled(true);

View File

@@ -291,7 +291,7 @@ namespace SourceGit.ViewModels
private void Refresh()
{
_changes = null;
_changes = [];
_requestingRevisionFiles = false;
_revisionFiles = null;
@@ -426,9 +426,6 @@ namespace SourceGit.ViewModels
private void RefreshVisibleChanges()
{
if (_changes == null)
return;
if (string.IsNullOrEmpty(_searchChangeFilter))
{
VisibleChanges = _changes;
@@ -578,8 +575,8 @@ namespace SourceGit.ViewModels
private Models.CommitFullMessage _fullMessage = null;
private Models.CommitSignInfo _signInfo = null;
private List<string> _children = null;
private List<Models.Change> _changes = null;
private List<Models.Change> _visibleChanges = null;
private List<Models.Change> _changes = [];
private List<Models.Change> _visibleChanges = [];
private List<Models.Change> _selectedChanges = null;
private string _searchChangeFilter = string.Empty;
private DiffContext _diffContext = null;

View File

@@ -1,40 +0,0 @@
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class ConfirmEmptyCommit
{
public bool HasLocalChanges
{
get;
private set;
}
public string Message
{
get;
private set;
}
public ConfirmEmptyCommit(WorkingCopy wc, bool autoPush, int unstagedCount)
{
_wc = wc;
_autoPush = autoPush;
HasLocalChanges = unstagedCount > 0;
Message = App.Text(HasLocalChanges ? "ConfirmEmptyCommit.WithLocalChanges" : "ConfirmEmptyCommit.NoLocalChanges");
}
public async Task StageAllThenCommitAsync()
{
await _wc.CommitAsync(true, _autoPush, Models.CommitCheckPassed.FileCount);
}
public async Task ContinueAsync()
{
await _wc.CommitAsync(false, _autoPush, Models.CommitCheckPassed.FileCount);
}
private readonly WorkingCopy _wc;
private readonly bool _autoPush;
}
}

View File

@@ -7,7 +7,7 @@ namespace SourceGit.ViewModels
public class CreateBranch : Popup
{
[Required(ErrorMessage = "Branch name is required!")]
[RegularExpression(@"^[\w \-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(CreateBranch), nameof(ValidateBranchName))]
public string Name
{
@@ -101,10 +101,9 @@ namespace SourceGit.ViewModels
{
if (!creator._allowOverwrite)
{
var fixedName = Models.Branch.FixName(name);
foreach (var b in creator._repo.Branches)
{
if (b.FriendlyName.Equals(fixedName, StringComparison.Ordinal))
if (b.FriendlyName.Equals(name, StringComparison.Ordinal))
return new ValidationResult("A branch with same name already exists!");
}
}
@@ -119,8 +118,7 @@ namespace SourceGit.ViewModels
{
_repo.SetWatcherEnabled(false);
var fixedName = Models.Branch.FixName(_name);
var log = _repo.CreateLog($"Create Branch '{fixedName}'");
var log = _repo.CreateLog($"Create Branch '{_name}'");
Use(log);
if (CheckoutAfterCreated)
@@ -166,7 +164,7 @@ namespace SourceGit.ViewModels
succ = await new Commands.Checkout(_repo.FullPath)
.Use(log)
.BranchAsync(fixedName, _baseOnRevision, DiscardLocalChanges, _allowOverwrite);
.BranchAsync(_name, _baseOnRevision, DiscardLocalChanges, _allowOverwrite);
if (succ)
{
@@ -187,7 +185,7 @@ namespace SourceGit.ViewModels
}
else
{
succ = await new Commands.Branch(_repo.FullPath, fixedName)
succ = await new Commands.Branch(_repo.FullPath, _name)
.Use(log)
.CreateAsync(_baseOnRevision, _allowOverwrite);
}
@@ -205,7 +203,7 @@ namespace SourceGit.ViewModels
}
if (autoSetUpstream)
await new Commands.Branch(_repo.FullPath, fixedName)
await new Commands.Branch(_repo.FullPath, _name)
.Use(log)
.SetUpstreamAsync(basedOn);
}
@@ -214,7 +212,7 @@ namespace SourceGit.ViewModels
if (succ && CheckoutAfterCreated)
{
var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{fixedName}" };
var fake = new Models.Branch() { IsLocal = true, FullName = $"refs/heads/{_name}" };
if (BasedOn is Models.Branch { IsLocal: false } based)
fake.Upstream = based.FullName;
@@ -223,8 +221,7 @@ namespace SourceGit.ViewModels
_repo.Settings.ExpandedBranchNodesInSideBar.Add(fake.FullName.Substring(0, folderEndIdx));
if (_repo.HistoriesFilterMode == Models.FilterMode.Included)
_repo.SetBranchFilterMode(fake, Models.FilterMode.Included, true, false);
_repo.SetBranchFilterMode(fake, Models.FilterMode.Included, false, false);
}
_repo.MarkBranchesDirtyManually();

View File

@@ -36,7 +36,7 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _viewContent, value);
}
public FileHistoriesSingleRevision(Repository repo, string file, Models.Commit revision, bool prevIsDiffMode)
public FileHistoriesSingleRevision(string repo, string file, Models.Commit revision, bool prevIsDiffMode)
{
_repo = repo;
_file = file;
@@ -49,7 +49,7 @@ namespace SourceGit.ViewModels
public async Task<bool> ResetToSelectedRevisionAsync()
{
return await new Commands.Checkout(_repo.FullPath)
return await new Commands.Checkout(_repo)
.FileWithRevisionAsync(_file, $"{_revision.SHA}")
.ConfigureAwait(false);
}
@@ -59,13 +59,13 @@ namespace SourceGit.ViewModels
if (_viewContent is not FileHistoriesRevisionFile { CanOpenWithDefaultEditor: true })
return;
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, _file);
var fullPath = Native.OS.GetAbsPath(_repo, _file);
var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? "";
var fileExt = Path.GetExtension(fullPath) ?? "";
var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_revision.SHA.AsSpan(0, 10)}{fileExt}");
await Commands.SaveRevisionFile
.RunAsync(_repo.FullPath, _revision.SHA, _file, tmpFile)
.RunAsync(_repo, _revision.SHA, _file, tmpFile)
.ConfigureAwait(false);
Native.OS.OpenWithDefaultEditor(tmpFile);
@@ -81,7 +81,7 @@ namespace SourceGit.ViewModels
Task.Run(async () =>
{
var objs = await new Commands.QueryRevisionObjects(_repo.FullPath, _revision.SHA, _file)
var objs = await new Commands.QueryRevisionObjects(_repo, _revision.SHA, _file)
.GetResultAsync()
.ConfigureAwait(false);
@@ -100,23 +100,23 @@ namespace SourceGit.ViewModels
{
if (obj.Type == Models.ObjectType.Blob)
{
var isBinary = await new Commands.IsBinary(_repo.FullPath, _revision.SHA, _file).GetResultAsync().ConfigureAwait(false);
var isBinary = await new Commands.IsBinary(_repo, _revision.SHA, _file).GetResultAsync().ConfigureAwait(false);
if (isBinary)
{
var imgDecoder = ImageSource.GetDecoder(_file);
if (imgDecoder != Models.ImageDecoder.None)
{
var source = await ImageSource.FromRevisionAsync(_repo.FullPath, _revision.SHA, _file, imgDecoder).ConfigureAwait(false);
var source = await ImageSource.FromRevisionAsync(_repo, _revision.SHA, _file, imgDecoder).ConfigureAwait(false);
var image = new Models.RevisionImageFile(_file, source.Bitmap, source.Size);
return new FileHistoriesRevisionFile(_file, image, true);
}
var size = await new Commands.QueryFileSize(_repo.FullPath, _file, _revision.SHA).GetResultAsync().ConfigureAwait(false);
var size = await new Commands.QueryFileSize(_repo, _file, _revision.SHA).GetResultAsync().ConfigureAwait(false);
var binaryFile = new Models.RevisionBinaryFile() { Size = size };
return new FileHistoriesRevisionFile(_file, binaryFile, true);
}
var contentStream = await Commands.QueryFileContent.RunAsync(_repo.FullPath, _revision.SHA, _file).ConfigureAwait(false);
var contentStream = await Commands.QueryFileContent.RunAsync(_repo, _revision.SHA, _file).ConfigureAwait(false);
var content = await new StreamReader(contentStream).ReadToEndAsync();
var lfs = Models.LFSObject.Parse(content);
if (lfs != null)
@@ -124,7 +124,7 @@ namespace SourceGit.ViewModels
var imgDecoder = ImageSource.GetDecoder(_file);
if (imgDecoder != Models.ImageDecoder.None)
{
var combined = new RevisionLFSImage(_repo.FullPath, _file, lfs, imgDecoder);
var combined = new RevisionLFSImage(_repo, _file, lfs, imgDecoder);
return new FileHistoriesRevisionFile(_file, combined, true);
}
@@ -138,7 +138,7 @@ namespace SourceGit.ViewModels
if (obj.Type == Models.ObjectType.Commit)
{
var submoduleRoot = Path.Combine(_repo.FullPath, _file);
var submoduleRoot = Path.Combine(_repo, _file);
var commit = await new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false);
var message = commit != null ? await new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false) : null;
var module = new Models.RevisionSubmodule()
@@ -156,10 +156,10 @@ namespace SourceGit.ViewModels
private void SetViewContentAsDiff()
{
var option = new Models.DiffOption(_revision, _file);
ViewContent = new DiffContext(_repo.FullPath, option, _viewContent as DiffContext);
ViewContent = new DiffContext(_repo, option, _viewContent as DiffContext);
}
private Repository _repo = null;
private string _repo = null;
private string _file = null;
private Models.Commit _revision = null;
private bool _isDiffMode = false;
@@ -186,7 +186,7 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _viewContent, value);
}
public FileHistoriesCompareRevisions(Repository repo, string file, Models.Commit start, Models.Commit end)
public FileHistoriesCompareRevisions(string repo, string file, Models.Commit start, Models.Commit end)
{
_repo = repo;
_file = file;
@@ -204,7 +204,7 @@ namespace SourceGit.ViewModels
public async Task<bool> SaveAsPatch(string saveTo)
{
return await Commands.SaveChangesAsPatch
.ProcessRevisionCompareChangesAsync(_repo.FullPath, _changes, _startPoint.SHA, _endPoint.SHA, saveTo)
.ProcessRevisionCompareChangesAsync(_repo, _changes, _startPoint.SHA, _endPoint.SHA, saveTo)
.ConfigureAwait(false);
}
@@ -212,7 +212,7 @@ namespace SourceGit.ViewModels
{
Task.Run(async () =>
{
_changes = await new Commands.CompareRevisions(_repo.FullPath, _startPoint.SHA, _endPoint.SHA, _file).ReadAsync().ConfigureAwait(false);
_changes = await new Commands.CompareRevisions(_repo, _startPoint.SHA, _endPoint.SHA, _file).ReadAsync().ConfigureAwait(false);
if (_changes.Count == 0)
{
Dispatcher.UIThread.Post(() => ViewContent = null);
@@ -220,12 +220,12 @@ namespace SourceGit.ViewModels
else
{
var option = new Models.DiffOption(_startPoint.SHA, _endPoint.SHA, _changes[0]);
Dispatcher.UIThread.Post(() => ViewContent = new DiffContext(_repo.FullPath, option, _viewContent));
Dispatcher.UIThread.Post(() => ViewContent = new DiffContext(_repo, option, _viewContent));
}
});
}
private Repository _repo = null;
private string _repo = null;
private string _file = null;
private Models.Commit _startPoint = null;
private Models.Commit _endPoint = null;
@@ -264,7 +264,7 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _viewContent, value);
}
public FileHistories(Repository repo, string file, string commit = null)
public FileHistories(string repo, string file, string commit = null)
{
if (!string.IsNullOrEmpty(commit))
Title = $"{file} @ {commit}";
@@ -282,7 +282,7 @@ namespace SourceGit.ViewModels
.Append(" -- ")
.Append(file.Quoted());
var commits = await new Commands.QueryCommits(_repo.FullPath, argsBuilder.ToString(), false)
var commits = await new Commands.QueryCommits(_repo, argsBuilder.ToString(), false)
.GetResultAsync()
.ConfigureAwait(false);
@@ -311,7 +311,18 @@ namespace SourceGit.ViewModels
public void NavigateToCommit(Models.Commit commit)
{
_repo.NavigateToCommit(commit.SHA);
var launcher = App.GetLauncher();
if (launcher != null)
{
foreach (var page in launcher.Pages)
{
if (page.Data is Repository repo && repo.FullPath.Equals(_repo, StringComparison.Ordinal))
{
repo.NavigateToCommit(commit.SHA);
break;
}
}
}
}
public string GetCommitFullMessage(Models.Commit commit)
@@ -320,12 +331,12 @@ namespace SourceGit.ViewModels
if (_fullCommitMessages.TryGetValue(sha, out var msg))
return msg;
msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).GetResult();
msg = new Commands.QueryCommitFullMessage(_repo, sha).GetResult();
_fullCommitMessages[sha] = msg;
return msg;
}
private readonly Repository _repo = null;
private readonly string _repo = null;
private bool _isLoading = true;
private bool _prevIsDiffMode = true;
private List<Models.Commit> _commits = null;

View File

@@ -219,12 +219,12 @@ namespace SourceGit.ViewModels
return false;
var lb = _repo.Branches.Find(x => x.IsLocal && x.Upstream == rb.FullName);
if (lb == null || lb.TrackStatus.Ahead.Count > 0)
if (lb == null || lb.Ahead.Count > 0)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CreateBranch(_repo, rb));
}
else if (lb.TrackStatus.Behind.Count > 0)
else if (lb.Behind.Count > 0)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CheckoutAndFastForward(_repo, lb, rb));
@@ -265,7 +265,7 @@ namespace SourceGit.ViewModels
continue;
var lb = _repo.Branches.Find(x => x.IsLocal && x.Upstream == rb.FullName);
if (lb is { TrackStatus.Ahead.Count: 0 })
if (lb.Ahead.Count == 0)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CheckoutAndFastForward(_repo, lb, rb));

View File

@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
@@ -52,7 +53,12 @@ namespace SourceGit.ViewModels
if (string.IsNullOrEmpty(lfs.Oid) || lfs.Size == 0)
return new ImageSource(null, 0);
var stream = await Commands.QueryFileContent.FromLFSAsync(repo, lfs.Oid, lfs.Size).ConfigureAwait(false);
var commonDir = await new Commands.QueryGitCommonDir(repo).GetResultAsync().ConfigureAwait(false);
var localFile = Path.Combine(commonDir, "lfs", "objects", lfs.Oid.Substring(0, 2), lfs.Oid.Substring(2, 2), lfs.Oid);
if (File.Exists(localFile))
return await FromFileAsync(localFile, decoder).ConfigureAwait(false);
await using var stream = await Commands.QueryFileContent.FromLFSAsync(repo, lfs.Oid, lfs.Size).ConfigureAwait(false);
return await Task.Run(() => LoadFromStream(stream, decoder)).ConfigureAwait(false);
}

View File

@@ -107,28 +107,25 @@ namespace SourceGit.ViewModels
get;
} = [];
public InteractiveRebaseItem SelectedItem
public InteractiveRebaseItem PreSelected
{
get => _selectedItem;
set
{
if (SetProperty(ref _selectedItem, value))
DetailContext.Commit = value?.Commit;
}
get => _preSelected;
private set => SetProperty(ref _preSelected, value);
}
public CommitDetail DetailContext
public object Detail
{
get;
get => _detail;
private set => SetProperty(ref _detail, value);
}
public InteractiveRebase(Repository repo, Models.Commit on, InteractiveRebasePrefill prefill = null)
{
_repo = repo;
_commitDetail = new CommitDetail(repo, false);
Current = repo.CurrentBranch;
On = on;
IsLoading = true;
DetailContext = new CommitDetail(repo, false);
Task.Run(async () =>
{
@@ -157,12 +154,29 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Post(() =>
{
Items.AddRange(list);
SelectedItem = selected;
PreSelected = selected;
IsLoading = false;
});
});
}
public void SelectCommits(List<InteractiveRebaseItem> items)
{
if (items.Count == 0)
{
Detail = null;
}
else if (items.Count == 1)
{
_commitDetail.Commit = items[0].Commit;
Detail = _commitDetail;
}
else
{
Detail = new Models.Count(items.Count);
}
}
public void MoveItemUp(InteractiveRebaseItem item)
{
var idx = Items.IndexOf(item);
@@ -171,7 +185,6 @@ namespace SourceGit.ViewModels
var prev = Items[idx - 1];
Items.RemoveAt(idx - 1);
Items.Insert(idx, prev);
SelectedItem = item;
UpdateItems();
}
}
@@ -184,20 +197,26 @@ namespace SourceGit.ViewModels
var next = Items[idx + 1];
Items.RemoveAt(idx + 1);
Items.Insert(idx, next);
SelectedItem = item;
UpdateItems();
}
}
public void ChangeAction(InteractiveRebaseItem item, Models.InteractiveRebaseAction action)
public void ChangeAction(List<InteractiveRebaseItem> selected, Models.InteractiveRebaseAction action)
{
if (!item.CanSquashOrFixup)
if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup)
{
if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup)
return;
foreach (var item in selected)
{
if (item.CanSquashOrFixup)
item.Action = action;
}
}
else
{
foreach (var item in selected)
item.Action = action;
}
item.Action = action;
UpdateItems();
}
@@ -257,6 +276,8 @@ namespace SourceGit.ViewModels
private Repository _repo = null;
private bool _isLoading = false;
private InteractiveRebaseItem _selectedItem = null;
private InteractiveRebaseItem _preSelected = null;
private object _detail = null;
private CommitDetail _commitDetail = null;
}
}

View File

@@ -1,7 +1,8 @@
using System;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@@ -20,10 +21,10 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _data, value);
}
public IBrush DirtyBrush
public Models.DirtyState DirtyState
{
get => _dirtyBrush;
private set => SetProperty(ref _dirtyBrush, value);
get => _dirtyState;
private set => SetProperty(ref _dirtyState, value);
}
public Popup Popup
@@ -66,22 +67,18 @@ namespace SourceGit.ViewModels
public void ChangeDirtyState(Models.DirtyState flag, bool remove)
{
var state = _dirtyState;
if (remove)
{
if (_dirtyState.HasFlag(flag))
_dirtyState -= flag;
if (state.HasFlag(flag))
state -= flag;
}
else
{
_dirtyState |= flag;
state |= flag;
}
if (_dirtyState.HasFlag(Models.DirtyState.HasLocalChanges))
DirtyBrush = Brushes.Gray;
else if (_dirtyState.HasFlag(Models.DirtyState.HasPendingPullOrPush))
DirtyBrush = Brushes.RoyalBlue;
else
DirtyBrush = null;
DirtyState = state;
}
public bool CanCreatePopup()
@@ -127,7 +124,6 @@ namespace SourceGit.ViewModels
private RepositoryNode _node = null;
private object _data = null;
private IBrush _dirtyBrush = null;
private Models.DirtyState _dirtyState = Models.DirtyState.None;
private Popup _popup = null;
}

View File

@@ -32,6 +32,7 @@ namespace SourceGit.ViewModels
{
_launcher = launcher;
UpdateVisiblePages();
SelectedPage = launcher.ActivePage;
}
public void ClearFilter()

View File

@@ -158,6 +158,12 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _useFixedTabWidth, value);
}
public bool UseAutoHideScrollBars
{
get => _useAutoHideScrollBars;
set => SetProperty(ref _useAutoHideScrollBars, value);
}
public bool Check4UpdatesOnStartup
{
get => _check4UpdatesOnStartup;
@@ -706,6 +712,7 @@ namespace SourceGit.ViewModels
private int _maxHistoryCommits = 20000;
private int _subjectGuideLength = 50;
private bool _useFixedTabWidth = true;
private bool _useAutoHideScrollBars = true;
private bool _showAuthorTimeInGraph = false;
private bool _showChildren = false;

View File

@@ -12,7 +12,7 @@ namespace SourceGit.ViewModels
}
[Required(ErrorMessage = "Branch name is required!!!")]
[RegularExpression(@"^[\w \-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(RenameBranch), nameof(ValidateBranchName))]
public string Name
{
@@ -31,10 +31,9 @@ namespace SourceGit.ViewModels
{
if (ctx.ObjectInstance is RenameBranch rename)
{
var fixedName = Models.Branch.FixName(name);
foreach (var b in rename._repo.Branches)
{
if (b.IsLocal && b != rename.Target && b.Name.Equals(fixedName, StringComparison.Ordinal))
if (b.IsLocal && b != rename.Target && b.Name.Equals(name, StringComparison.Ordinal))
return new ValidationResult("A branch with same name already exists!!!");
}
}
@@ -44,8 +43,7 @@ namespace SourceGit.ViewModels
public override async Task<bool> Sure()
{
var fixedName = Models.Branch.FixName(_name);
if (fixedName.Equals(Target.Name, StringComparison.Ordinal))
if (Target.Name.Equals(_name, StringComparison.Ordinal))
return true;
_repo.SetWatcherEnabled(false);
@@ -59,7 +57,7 @@ namespace SourceGit.ViewModels
var succ = await new Commands.Branch(_repo.FullPath, Target.Name)
.Use(log)
.RenameAsync(fixedName);
.RenameAsync(_name);
if (succ)
{
@@ -68,7 +66,7 @@ namespace SourceGit.ViewModels
if (filter.Type == Models.FilterType.LocalBranch &&
filter.Pattern.Equals(oldName, StringComparison.Ordinal))
{
filter.Pattern = $"refs/heads/{fixedName}";
filter.Pattern = $"refs/heads/{_name}";
break;
}
}

View File

@@ -740,39 +740,6 @@ namespace SourceGit.ViewModels
return log;
}
public async Task<Models.IssueTracker> AddIssueTrackerAsync(string name, string regex, string url)
{
var rule = new Models.IssueTracker()
{
IsShared = false,
Name = name,
RegexString = regex,
URLTemplate = url,
};
var succ = await CreateIssueTrackerCommand(false).AddAsync(rule);
if (succ)
{
IssueTrackers.Add(rule);
return rule;
}
return null;
}
public async Task RemoveIssueTrackerAsync(Models.IssueTracker rule)
{
var succ = await CreateIssueTrackerCommand(rule.IsShared).RemoveAsync(rule);
if (succ)
IssueTrackers.Remove(rule);
}
public async Task ChangeIssueTrackerShareModeAsync(Models.IssueTracker rule)
{
await CreateIssueTrackerCommand(!rule.IsShared).RemoveAsync(rule);
await CreateIssueTrackerCommand(rule.IsShared).AddAsync(rule);
}
public void RefreshAll()
{
RefreshCommits();
@@ -786,8 +753,8 @@ namespace SourceGit.ViewModels
Task.Run(async () =>
{
var issuetrackers = new List<Models.IssueTracker>();
await CreateIssueTrackerCommand(true).ReadAllAsync(issuetrackers, true).ConfigureAwait(false);
await CreateIssueTrackerCommand(false).ReadAllAsync(issuetrackers, false).ConfigureAwait(false);
await new Commands.IssueTracker(FullPath, true).ReadAllAsync(issuetrackers, true).ConfigureAwait(false);
await new Commands.IssueTracker(FullPath, false).ReadAllAsync(issuetrackers, false).ConfigureAwait(false);
Dispatcher.UIThread.Post(() =>
{
IssueTrackers.Clear();
@@ -1215,7 +1182,7 @@ namespace SourceGit.ViewModels
if (_workingCopy != null)
_workingCopy.HasRemotes = remotes.Count > 0;
var hasPendingPullOrPush = CurrentBranch?.TrackStatus.IsVisible ?? false;
var hasPendingPullOrPush = CurrentBranch?.IsTrackStatusVisible ?? false;
GetOwnerPage()?.ChangeDirtyState(Models.DirtyState.HasPendingPullOrPush, !hasPendingPullOrPush);
});
}, token);
@@ -1498,9 +1465,9 @@ namespace SourceGit.ViewModels
{
if (b.IsLocal &&
b.Upstream.Equals(branch.FullName, StringComparison.Ordinal) &&
b.TrackStatus.Ahead.Count == 0)
b.Ahead.Count == 0)
{
if (b.TrackStatus.Behind.Count > 0)
if (b.Behind.Count > 0)
ShowPopup(new CheckoutAndFastForward(this, b, branch));
else if (!b.IsCurrent)
await CheckoutBranchAsync(b);
@@ -1743,11 +1710,6 @@ namespace SourceGit.ViewModels
return null;
}
private Commands.IssueTracker CreateIssueTrackerCommand(bool shared)
{
return new Commands.IssueTracker(FullPath, shared ? $"{FullPath}/.issuetracker" : null);
}
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
{
var builder = new BranchTreeNode.Builder(_settings.LocalBranchSortMode, _settings.RemoteBranchSortMode);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Collections;
@@ -114,8 +115,8 @@ namespace SourceGit.ViewModels
public AvaloniaList<Models.IssueTracker> IssueTrackers
{
get => _repo.IssueTrackers;
}
get;
} = [];
public Models.IssueTracker SelectedIssueTracker
{
@@ -176,6 +177,17 @@ namespace SourceGit.ViewModels
HttpProxy = proxy;
if (_cached.TryGetValue("fetch.prune", out var prune))
EnablePruneOnFetch = (prune == "true");
foreach (var rule in _repo.IssueTrackers)
{
IssueTrackers.Add(new()
{
IsShared = rule.IsShared,
Name = rule.Name,
RegexString = rule.RegexString,
URLTemplate = rule.URLTemplate,
});
}
}
public void ClearHttpProxy()
@@ -208,25 +220,28 @@ namespace SourceGit.ViewModels
return outs;
}
public async Task AddIssueTrackerAsync(string name, string regex, string url)
public void AddIssueTracker(string name, string regex, string url)
{
SelectedIssueTracker = await _repo.AddIssueTrackerAsync(name, regex, url);
var rule = new Models.IssueTracker()
{
IsShared = false,
Name = name,
RegexString = regex,
URLTemplate = url,
};
IssueTrackers.Add(rule);
SelectedIssueTracker = rule;
}
public async Task RemoveIssueTrackerAsync()
public void RemoveIssueTracker()
{
if (_selectedIssueTracker is { } rule)
await _repo.RemoveIssueTrackerAsync(rule);
IssueTrackers.Remove(rule);
SelectedIssueTracker = null;
}
public async Task ChangeIssueTrackerShareModeAsync()
{
if (_selectedIssueTracker is { } rule)
await _repo.ChangeIssueTrackerShareModeAsync(rule);
}
public void AddNewCustomAction()
{
SelectedCustomAction = _repo.Settings.AddNewCustomAction();
@@ -259,6 +274,8 @@ namespace SourceGit.ViewModels
await SetIfChangedAsync("user.signingkey", GPGUserSigningKey, "");
await SetIfChangedAsync("http.proxy", HttpProxy, "");
await SetIfChangedAsync("fetch.prune", EnablePruneOnFetch ? "true" : "false", "false");
await ApplyIssueTrackerChangesAsync();
}
private async Task SetIfChangedAsync(string key, string value, string defValue)
@@ -267,6 +284,62 @@ namespace SourceGit.ViewModels
await new Commands.Config(_repo.FullPath).SetAsync(key, value);
}
private async Task ApplyIssueTrackerChangesAsync()
{
var changed = false;
var oldRules = new Dictionary<string, Models.IssueTracker>();
foreach (var rule in _repo.IssueTrackers)
oldRules.Add(rule.Name, rule);
foreach (var rule in IssueTrackers)
{
if (oldRules.TryGetValue(rule.Name, out var old))
{
if (old.IsShared != rule.IsShared)
{
changed = true;
await new Commands.IssueTracker(_repo.FullPath, old.IsShared).RemoveAsync(old.Name);
await new Commands.IssueTracker(_repo.FullPath, rule.IsShared).AddAsync(rule);
}
else
{
if (!old.RegexString.Equals(rule.RegexString, StringComparison.Ordinal))
{
changed = true;
await new Commands.IssueTracker(_repo.FullPath, old.IsShared).UpdateRegexAsync(rule);
}
if (!old.URLTemplate.Equals(rule.URLTemplate, StringComparison.Ordinal))
{
changed = true;
await new Commands.IssueTracker(_repo.FullPath, old.IsShared).UpdateURLTemplateAsync(rule);
}
}
oldRules.Remove(rule.Name);
}
else
{
changed = true;
await new Commands.IssueTracker(_repo.FullPath, rule.IsShared).AddAsync(rule);
}
}
if (oldRules.Count > 0)
{
changed = true;
foreach (var kv in oldRules)
await new Commands.IssueTracker(_repo.FullPath, kv.Value.IsShared).RemoveAsync(kv.Key);
}
if (changed)
{
_repo.IssueTrackers.Clear();
_repo.IssueTrackers.AddRange(IssueTrackers);
}
}
private readonly Repository _repo = null;
private readonly Dictionary<string, string> _cached = null;
private string _httpProxy;

View File

@@ -25,7 +25,7 @@ namespace SourceGit.ViewModels
{
var source = await ImageSource.FromLFSObjectAsync(repo, lfs, decoder).ConfigureAwait(false);
var img = new Models.RevisionImageFile(file, source.Bitmap, source.Size);
Dispatcher.UIThread.Invoke(() => Image = img);
Dispatcher.UIThread.Post(() => Image = img);
});
}

View File

@@ -39,6 +39,7 @@ namespace SourceGit.ViewModels
var changes = await new Commands.QueryLocalChanges(_repo.FullPath, false).GetResultAsync();
var signOff = _repo.Settings.EnableSignOffForCommit;
var noVerify = _repo.Settings.NoVerifyOnCommit;
var needAutoStash = false;
var succ = false;
@@ -64,7 +65,7 @@ namespace SourceGit.ViewModels
}
}
succ = await new Commands.Commit(_repo.FullPath, _message, signOff, true, false)
succ = await new Commands.Commit(_repo.FullPath, _message, signOff, noVerify, true, false)
.Use(log)
.RunAsync();

View File

@@ -8,6 +8,18 @@ namespace SourceGit.ViewModels
{
public class ScanRepositories : Popup
{
public bool UseCustomDir
{
get => _useCustomDir;
set => SetProperty(ref _useCustomDir, value);
}
public string CustomDir
{
get => _customDir;
set => SetProperty(ref _customDir, value);
}
public List<Models.ScanDir> ScanDirs
{
get;
@@ -33,16 +45,28 @@ namespace SourceGit.ViewModels
if (ScanDirs.Count > 0)
_selected = ScanDirs[0];
else
_useCustomDir = true;
GetManagedRepositories(Preferences.Instance.RepositoryNodes, _managed);
}
public override async Task<bool> Sure()
{
ProgressDescription = $"Scan repositories under '{_selected.Path}' ...";
var selectedDir = _useCustomDir ? _customDir : _selected?.Path;
if (string.IsNullOrEmpty(selectedDir))
{
App.RaiseException(null, "Missing root directory to scan!");
return false;
}
if (!Directory.Exists(selectedDir))
return true;
ProgressDescription = $"Scan repositories under '{selectedDir}' ...";
var minDelay = Task.Delay(500);
var rootDir = new DirectoryInfo(_selected.Path);
var rootDir = new DirectoryInfo(selectedDir);
var found = new List<string>();
await GetUnmanagedRepositoriesAsync(rootDir, found, new EnumerationOptions()
@@ -162,6 +186,8 @@ namespace SourceGit.ViewModels
}
private HashSet<string> _managed = new();
private bool _useCustomDir = false;
private string _customDir = string.Empty;
private Models.ScanDir _selected = null;
}
}

View File

@@ -34,6 +34,7 @@ namespace SourceGit.ViewModels
var changes = await new Commands.QueryLocalChanges(_repo.FullPath, false).GetResultAsync();
var signOff = _repo.Settings.EnableSignOffForCommit;
var noVerify = _repo.Settings.NoVerifyOnCommit;
var needAutoStash = false;
var succ = false;
@@ -64,7 +65,7 @@ namespace SourceGit.ViewModels
.ExecAsync();
if (succ)
succ = await new Commands.Commit(_repo.FullPath, _message, signOff, true, false)
succ = await new Commands.Commit(_repo.FullPath, _message, signOff, noVerify, true, false)
.Use(log)
.RunAsync();

View File

@@ -198,24 +198,16 @@ namespace SourceGit.ViewModels
public async Task CheckoutSingleFileAsync(Models.Change change)
{
var fullPath = Native.OS.GetAbsPath(_repo.FullPath, change.Path);
var log = _repo.CreateLog($"Reset File to '{_selectedStash.SHA}'");
var revision = _selectedStash.SHA;
if (_untracked.Contains(change) && _selectedStash.Parents.Count == 3)
revision = _selectedStash.Parents[2];
else if (change.Index == Models.ChangeState.Added && _selectedStash.Parents.Count > 1)
revision = _selectedStash.Parents[1];
if (_untracked.Contains(change))
{
await Commands.SaveRevisionFile.RunAsync(_repo.FullPath, _selectedStash.Parents[2], change.Path, fullPath);
}
else if (change.Index == Models.ChangeState.Added)
{
await Commands.SaveRevisionFile.RunAsync(_repo.FullPath, _selectedStash.SHA, change.Path, fullPath);
}
else
{
await new Commands.Checkout(_repo.FullPath)
var log = _repo.CreateLog($"Reset File to '{_selectedStash.Name}'");
await new Commands.Checkout(_repo.FullPath)
.Use(log)
.FileWithRevisionAsync(change.Path, $"{_selectedStash.SHA}");
}
.FileWithRevisionAsync(change.Path, revision);
log.Complete();
}

View File

@@ -71,6 +71,12 @@ namespace SourceGit.ViewModels
set => _repo.Settings.EnableSignOffForCommit = value;
}
public bool NoVerifyOnCommit
{
get => _repo.Settings.NoVerifyOnCommit;
set => _repo.Settings.NoVerifyOnCommit = value;
}
public bool UseAmend
{
get => _useAmend;
@@ -619,7 +625,7 @@ namespace SourceGit.ViewModels
_repo.Settings.CommitMessages.Clear();
}
public async Task CommitAsync(bool autoStage, bool autoPush, Models.CommitCheckPassed checkPassed = Models.CommitCheckPassed.None)
public async Task CommitAsync(bool autoStage, bool autoPush)
{
if (string.IsNullOrWhiteSpace(_commitMessage))
return;
@@ -636,30 +642,32 @@ namespace SourceGit.ViewModels
return;
}
if (_repo.CurrentBranch is { IsDetachedHead: true } && checkPassed < Models.CommitCheckPassed.DetachedHead)
if (_repo.CurrentBranch is { IsDetachedHead: true })
{
var msg = App.Text("WorkingCopy.ConfirmCommitWithDetachedHead");
var sure = await App.AskConfirmAsync(msg);
if (sure)
await CommitAsync(autoStage, autoPush, Models.CommitCheckPassed.DetachedHead);
return;
if (!sure)
return;
}
if (!string.IsNullOrEmpty(_filter) && _staged.Count > _visibleStaged.Count && checkPassed < Models.CommitCheckPassed.Filter)
if (!string.IsNullOrEmpty(_filter) && _staged.Count > _visibleStaged.Count)
{
var msg = App.Text("WorkingCopy.ConfirmCommitWithFilter", _staged.Count, _visibleStaged.Count, _staged.Count - _visibleStaged.Count);
var sure = await App.AskConfirmAsync(msg);
if (sure)
await CommitAsync(autoStage, autoPush, Models.CommitCheckPassed.Filter);
return;
if (!sure)
return;
}
if (checkPassed < Models.CommitCheckPassed.FileCount && !_useAmend)
if (!_useAmend)
{
if ((!autoStage && _staged.Count == 0) || (autoStage && _cached.Count == 0))
{
await App.ShowDialog(new ConfirmEmptyCommit(this, autoPush, _cached.Count));
return;
var rs = await App.AskConfirmEmptyCommitAsync(_cached.Count > 0);
if (rs == Models.ConfirmEmptyCommitResult.Cancel)
return;
if (rs == Models.ConfirmEmptyCommitResult.StageAllAndCommit)
autoStage = true;
}
}
@@ -667,14 +675,19 @@ namespace SourceGit.ViewModels
_repo.Settings.PushCommitMessage(_commitMessage);
_repo.SetWatcherEnabled(false);
var signOff = _repo.Settings.EnableSignOffForCommit;
var log = _repo.CreateLog("Commit");
var succ = true;
if (autoStage && _unstaged.Count > 0)
succ = await new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).ExecAsync().ConfigureAwait(false);
succ = await new Commands.Add(_repo.FullPath, _repo.IncludeUntracked)
.Use(log)
.ExecAsync()
.ConfigureAwait(false);
if (succ)
succ = await new Commands.Commit(_repo.FullPath, _commitMessage, signOff, _useAmend, _resetAuthor).Use(log).RunAsync().ConfigureAwait(false);
succ = await new Commands.Commit(_repo.FullPath, _commitMessage, EnableSignOff, NoVerifyOnCommit, _useAmend, _resetAuthor)
.Use(log)
.RunAsync()
.ConfigureAwait(false);
log.Complete();

View File

@@ -0,0 +1,63 @@
using System;
using System.Text;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
public class BranchOrTagNameTextBox : TextBox
{
protected override Type StyleKeyOverride => typeof(TextBox);
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
PastingFromClipboard += OnPastingFromClipboard;
}
protected override void OnUnloaded(RoutedEventArgs e)
{
PastingFromClipboard -= OnPastingFromClipboard;
base.OnUnloaded(e);
}
protected override void OnTextInput(TextInputEventArgs e)
{
if (string.IsNullOrEmpty(e.Text))
return;
var builder = new StringBuilder(e.Text.Length);
var chars = e.Text.ToCharArray();
foreach (var ch in chars)
{
if (char.IsWhiteSpace(ch))
builder.Append('-');
else
builder.Append(ch);
}
e.Text = builder.ToString();
base.OnTextInput(e);
}
private async void OnPastingFromClipboard(object sender, RoutedEventArgs e)
{
try
{
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
{
var text = await clipboard.GetTextAsync();
if (!string.IsNullOrEmpty(text))
OnTextInput(new TextInputEventArgs() { Text = text });
}
}
finally
{
e.Handled = true;
}
}
}
}

View File

@@ -43,26 +43,52 @@
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Path Width="12" Height="12" Data="{StaticResource Icons.Branch}"/>
<TextBlock FontWeight="Bold" VerticalAlignment="Center" Margin="8,0,0,0" Text="{Binding FriendlyName}"/>
<TextBlock FontWeight="Bold" VerticalAlignment="Center" Margin="4,0,0,0" Text="{Binding FriendlyName}"/>
<Border Background="Green" Margin="4,0,0,0" CornerRadius="4" VerticalAlignment="Center" IsVisible="{Binding !IsLocal}">
<TextBlock Text="{DynamicResource Text.BranchTree.Remote}" FontSize="12" Classes="primary" Margin="4,1" Foreground="White" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Text.BranchTree.Remote}" FontSize="12" Classes="primary" Margin="4,0" Foreground="White" VerticalAlignment="Center"/>
</Border>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="0,8,0,0"
IsVisible="{Binding Upstream, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Classes="info_label"
<Grid RowDefinitions="Auto,Auto,Auto" ColumnDefinitions="Auto,Auto" Margin="0,8,0,0" IsVisible="{Binding IsLocal, Mode=OneWay}">
<TextBlock Grid.Row="0" Grid.Column="0"
Classes="info_label"
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="{DynamicResource Text.BranchTree.Tracking}"/>
<StackPanel Grid.Row="0" Grid.Column="1"
Margin="8,0,0,0"
Orientation="Horizontal">
<TextBlock Text="{Binding Upstream, Mode=OneWay, Converter={x:Static c:StringConverters.ToFriendlyUpstream}}"
IsVisible="{Binding Upstream, Mode=OneWay, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
<TextBlock IsVisible="{Binding Upstream, Mode=OneWay, Converter={x:Static StringConverters.IsNullOrEmpty}}">
<Run Text="---"/>
<Run Text="(none)" Foreground="{DynamicResource Brush.FG2}"/>
</TextBlock>
<TextBlock Margin="6,0,0,0"
Text="{Binding Upstream, Mode=OneWay, Converter={x:Static c:StringConverters.ToFriendlyUpstream}}"/>
<Border Background="OrangeRed" Margin="4,0,0,0" CornerRadius="4" VerticalAlignment="Center" IsVisible="{Binding IsUpstreamGone}">
<TextBlock Text="{DynamicResource Text.BranchTree.InvalidUpstream}" FontSize="12" Classes="primary" Margin="4,1" Foreground="White" VerticalAlignment="Center"/>
</Border>
</StackPanel>
<Border Background="OrangeRed" Margin="4,0,0,0" CornerRadius="4" VerticalAlignment="Center" IsVisible="{Binding IsUpstreamGone}">
<TextBlock Text="{DynamicResource Text.BranchTree.InvalidUpstream}" FontSize="12" Classes="primary" Margin="4,1" Foreground="White" VerticalAlignment="Center"/>
</Border>
</StackPanel>
<TextBlock Grid.Row="1" Grid.Column="0"
Classes="info_label"
Margin="0,4,0,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="{DynamicResource Text.BranchTree.Status}"
IsVisible="{Binding IsTrackStatusVisible, Mode=OneWay}"/>
<v:BranchTreeNodeTrackStatusTooltip Grid.Row="1" Grid.Column="1"
Margin="8,4,0,0"/>
<TextBlock Grid.Row="2" Grid.Column="0"
Classes="info_label"
Margin="0,4,0,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Text="{DynamicResource Text.BranchTree.Worktree}"
IsVisible="{Binding HasWorktree}"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Margin="8,4,0,0"
Text="{Binding WorktreePath, Mode=OneWay}"
IsVisible="{Binding HasWorktree}"/>
</Grid>
</StackPanel>
</DataTemplate>

View File

@@ -50,25 +50,27 @@ namespace SourceGit.Views
if (node.Backend is Models.Remote)
{
CreateContent(new Thickness(0, 0, 0, 0), "Icons.Remote", false);
CreateContent(new Thickness(0, 0, 0, 0), "Icons.Remote");
}
else if (node.Backend is Models.Branch branch)
{
if (branch.IsCurrent)
CreateContent(new Thickness(0, 0, 0, 0), "Icons.CheckCircled", true);
CreateContent(new Thickness(0, 0, 0, 0), "Icons.CheckCircled", Brushes.Green);
else if (branch.IsLocal && !string.IsNullOrEmpty(branch.WorktreePath))
CreateContent(new Thickness(2, 0, 0, 0), "Icons.Branch", Brushes.DarkCyan);
else
CreateContent(new Thickness(2, 0, 0, 0), "Icons.Branch", false);
CreateContent(new Thickness(2, 0, 0, 0), "Icons.Branch");
}
else
{
if (node.IsExpanded)
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open", false);
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open");
else
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder", false);
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder");
}
}
private void CreateContent(Thickness margin, string iconKey, bool highlight)
private void CreateContent(Thickness margin, string iconKey, IBrush fill = null)
{
if (this.FindResource(iconKey) is not StreamGeometry geo)
return;
@@ -83,8 +85,8 @@ namespace SourceGit.Views
Data = geo,
};
if (highlight)
path.Fill = Brushes.Green;
if (fill != null)
path.Fill = fill;
Content = path;
}
@@ -181,11 +183,11 @@ namespace SourceGit.Views
if (DataContext is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
{
var status = branch.TrackStatus.ToString();
if (!string.IsNullOrEmpty(status))
var desc = branch.TrackStatusDescription;
if (!string.IsNullOrEmpty(desc))
{
_label = new FormattedText(
status,
desc,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily),
@@ -200,6 +202,33 @@ namespace SourceGit.Views
private FormattedText _label = null;
}
public class BranchTreeNodeTrackStatusTooltip : TextBlock
{
protected override Type StyleKeyOverride => typeof(TextBlock);
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
Text = string.Empty;
if (DataContext is not Models.Branch { IsTrackStatusVisible: true } branch)
{
SetCurrentValue(IsVisibleProperty, false);
return;
}
var ahead = branch.Ahead.Count;
var behind = branch.Behind.Count;
if (ahead > 0)
Text = behind > 0 ? App.Text("BranchTree.AheadBehind", ahead, behind) : App.Text("BranchTree.Ahead", ahead);
else
Text = App.Text("BranchTree.Behind", behind);
SetCurrentValue(IsVisibleProperty, true);
}
}
public partial class BranchTree : UserControl
{
public static readonly StyledProperty<List<ViewModels.BranchTreeNode>> NodesProperty =
@@ -605,7 +634,7 @@ namespace SourceGit.Views
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream.FriendlyName);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = branch.TrackStatus.Ahead.Count == 0 && branch.TrackStatus.Behind.Count > 0;
fastForward.IsEnabled = branch.Ahead.Count == 0 && branch.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
if (repo.CanCreatePopup())
@@ -633,27 +662,26 @@ namespace SourceGit.Views
}
else
{
if (!repo.IsBare)
{
var checkout = new MenuItem();
checkout.Header = App.Text("BranchCM.Checkout", branch.Name);
checkout.Icon = App.CreateMenuIcon("Icons.Check");
checkout.Click += async (_, e) =>
{
await repo.CheckoutBranchAsync(branch);
e.Handled = true;
};
menu.Items.Add(checkout);
menu.Items.Add(new MenuItem() { Header = "-" });
}
var hasNoWorktree = string.IsNullOrEmpty(branch.WorktreePath);
var worktree = repo.Worktrees.Find(x => x.Branch == branch.FullName);
if (upstream != null && worktree == null)
var checkout = new MenuItem();
checkout.Header = App.Text(hasNoWorktree ? "BranchCM.Checkout" : "BranchCM.SwitchToWorktree", branch.Name);
checkout.Icon = App.CreateMenuIcon("Icons.Check");
checkout.IsEnabled = !repo.IsBare || !hasNoWorktree;
checkout.Click += async (_, e) =>
{
await repo.CheckoutBranchAsync(branch);
e.Handled = true;
};
menu.Items.Add(checkout);
menu.Items.Add(new MenuItem() { Header = "-" });
if (upstream != null && hasNoWorktree)
{
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream.FriendlyName);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = branch.TrackStatus.Ahead.Count == 0 && branch.TrackStatus.Behind.Count > 0;
fastForward.IsEnabled = branch.Ahead.Count == 0 && branch.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
if (repo.CanCreatePopup())
@@ -665,7 +693,7 @@ namespace SourceGit.Views
var fetchInto = new MenuItem();
fetchInto.Header = App.Text("BranchCM.FetchInto", upstream.FriendlyName, branch.Name);
fetchInto.Icon = App.CreateMenuIcon("Icons.Fetch");
fetchInto.IsEnabled = branch.TrackStatus.Ahead.Count == 0;
fetchInto.IsEnabled = branch.Ahead.Count == 0;
fetchInto.Click += async (_, e) =>
{
if (repo.CanCreatePopup())
@@ -705,7 +733,7 @@ namespace SourceGit.Views
menu.Items.Add(rebase);
}
if (worktree == null)
if (hasNoWorktree)
{
var selectedCommit = repo.GetSelectedCommitInHistory();
if (selectedCommit != null && !selectedCommit.SHA.Equals(branch.Head, StringComparison.Ordinal))

View File

@@ -32,11 +32,11 @@
VerticalAlignment="Center"
CornerRadius="9"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding LocalBranch.TrackStatus.IsVisible}">
IsVisible="{Binding LocalBranch.IsTrackStatusVisible, Mode=OneWay}">
<TextBlock Foreground="{DynamicResource Brush.BadgeFG}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"
Text="{Binding LocalBranch.TrackStatus}"/>
Text="{Binding LocalBranch.TrackStatusDescription, Mode=OneWay}"/>
</Border>
</StackPanel>

View File

@@ -18,7 +18,7 @@
<Grid ColumnDefinitions="96,*">
<v:Avatar Grid.Column="0" Width="64" Height="64" HorizontalAlignment="Right" User="{Binding Author}"/>
<StackPanel Grid.Column="1" Margin="16,0,8,0" Orientation="Vertical">
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.CommitDetail.Info.Author}"/>
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.CommitDetail.Info.Author}"/>
<Grid Margin="0,10,0,8" ColumnDefinitions="Auto,*" ClipToBounds="True">
<v:EnhancedSelectableTextBlock Grid.Column="0" Text="{Binding Author.Name}" Margin="2,0,8,0"/>
<v:EnhancedSelectableTextBlock Grid.Column="1" Text="{Binding Author.Email}" ToolTip.Tip="{Binding Author.Email}" Foreground="{DynamicResource Brush.FG2}" TextTrimming="CharacterEllipsis"/>
@@ -34,7 +34,7 @@
<Grid ColumnDefinitions="96,*" IsVisible="{Binding IsCommitterVisible}">
<v:Avatar Grid.Column="0" Width="64" Height="64" HorizontalAlignment="Right" User="{Binding Committer}"/>
<StackPanel Grid.Column="1" Margin="16,0,8,0" Orientation="Vertical">
<TextBlock Classes="group_header_label" Margin="0" Text="{DynamicResource Text.CommitDetail.Info.Committer}"/>
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.CommitDetail.Info.Committer}"/>
<Grid Margin="0,10,0,8" ColumnDefinitions="Auto,*" ClipToBounds="True">
<v:EnhancedSelectableTextBlock Grid.Column="0" Text="{Binding Committer.Name}" Margin="2,0,8,0"/>
<v:EnhancedSelectableTextBlock Grid.Column="1" Text="{Binding Committer.Email}" ToolTip.Tip="{Binding Committer.Email}" Foreground="{DynamicResource Brush.FG2}" TextTrimming="CharacterEllipsis"/>

View File

@@ -137,7 +137,7 @@ namespace SourceGit.Views
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
App.ShowWindow(new ViewModels.FileHistories(repo, change.Path, commit.SHA));
App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA));
ev.Handled = true;
};

View File

@@ -66,7 +66,7 @@
Background="{DynamicResource Brush.Window}"
BorderThickness="1,0,0,0"
CornerRadius="0,0,4,4">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*" Margin="0,4">
<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,*" Margin="0,4">
<Button Grid.Column="0"
Classes="icon_button"
Width="24"
@@ -105,7 +105,16 @@
<Path Width="13" Height="13" Data="{StaticResource Icons.Copy}"/>
</Button>
<StackPanel Grid.Column="4"
<Button Grid.Column="4"
Classes="icon_button"
Width="24"
Margin="0,0,4,0" Padding="0"
Click="PasteAndReplaceAllText"
ToolTip.Tip="{DynamicResource Text.CommitMessageTextBox.PasteAndReplaceAll}">
<Path Width="13" Height="13" Data="{StaticResource Icons.Paste}"/>
</Button>
<StackPanel Grid.Column="5"
Margin="0,0,6,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"

View File

@@ -356,6 +356,26 @@ namespace SourceGit.Views
e.Handled = true;
}
private async void PasteAndReplaceAllText(object sender, RoutedEventArgs e)
{
try
{
var text = await App.GetClipboardTextAsync();
if (!string.IsNullOrEmpty(text))
{
var parts = text.ReplaceLineEndings("\n").Split("\n", 2);
var subject = parts[0];
Text = parts.Length > 1 ? $"{subject}\n\n{parts[1].Trim()}" : subject;
}
}
catch
{
// Ignore exceptions.
}
e.Handled = true;
}
private TextChangeWay _changingWay = TextChangeWay.None;
}
}

View File

@@ -52,14 +52,13 @@ namespace SourceGit.Views
protected override Size MeasureOverride(Size availableSize)
{
if (DataContext is Models.Commit commit && CurrentBranch is not null)
if (DataContext is Models.Commit commit && CurrentBranch is { } b)
{
var sha = commit.SHA;
var track = CurrentBranch.TrackStatus;
if (track.Ahead.Contains(sha))
if (b.Ahead.Contains(sha))
_status = Status.Ahead;
else if (track.Behind.Contains(sha))
else if (b.Behind.Contains(sha))
_status = Status.Behind;
else
_status = Status.Normal;

View File

@@ -3,10 +3,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:v="using:SourceGit.Views"
xmlns:vm="using:SourceGit.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.ConfirmEmptyCommit"
x:DataType="vm:ConfirmEmptyCommit"
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.Warn}"
@@ -39,15 +37,15 @@
<!-- Body -->
<Border Grid.Row="1" Margin="16">
<TextBlock Text="{Binding Message}" MaxWidth="520" TextWrapping="Wrap"/>
<TextBlock x:Name="TxtMessage" MaxWidth="520" TextWrapping="Wrap"/>
</Border>
<!-- Buttons -->
<StackPanel Grid.Row="2" Margin="0,0,0,16" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Classes="flat"
x:Name="BtnStageAllAndCommit"
Height="30"
Margin="4,0"
IsVisible="{Binding HasLocalChanges}"
Click="StageAllThenCommit"
Content="{DynamicResource Text.ConfirmEmptyCommit.StageAllThenCommit}"
HorizontalContentAlignment="Center"

View File

@@ -9,25 +9,19 @@ namespace SourceGit.Views
InitializeComponent();
}
private async void StageAllThenCommit(object _1, RoutedEventArgs _2)
private void StageAllThenCommit(object _1, RoutedEventArgs _2)
{
if (DataContext is ViewModels.ConfirmEmptyCommit vm)
await vm.StageAllThenCommitAsync();
Close();
Close(Models.ConfirmEmptyCommitResult.StageAllAndCommit);
}
private async void Continue(object _1, RoutedEventArgs _2)
private void Continue(object _1, RoutedEventArgs _2)
{
if (DataContext is ViewModels.ConfirmEmptyCommit vm)
await vm.ContinueAsync();
Close();
Close(Models.ConfirmEmptyCommitResult.CreateEmptyCommit);
}
private void CloseWindow(object _1, RoutedEventArgs _2)
{
Close();
Close(Models.ConfirmEmptyCommitResult.Cancel);
}
}
}

View File

@@ -13,7 +13,7 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.CreateBranch.Title}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,Auto,32,Auto,Auto,Auto" ColumnDefinitions="140,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32,32,Auto,Auto,Auto" ColumnDefinitions="140,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
@@ -48,26 +48,20 @@
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CreateBranch.Name}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="2"
Text="{Binding Name, Mode=TwoWay}"
Watermark="{DynamicResource Text.CreateBranch.Name.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<StackPanel Grid.Row="2" Grid.Column="1"
Orientation="Horizontal"
IsVisible="{Binding Name, Converter={x:Static c:StringConverters.ContainsSpaces}}">
<Path Width="10" Height="10" Data="{StaticResource Icons.Error}" Fill="DarkOrange"/>
<TextBlock Classes="small" Text="{DynamicResource Text.CreateBranch.Name.WarnSpace}" Margin="4,0,0,0"/>
</StackPanel>
<v:BranchOrTagNameTextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="2"
Text="{Binding Name, Mode=TwoWay}"
Watermark="{DynamicResource Text.CreateBranch.Name.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<TextBlock Grid.Row="3" Grid.Column="0"
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CreateBranch.LocalChanges}"
IsVisible="{Binding !IsBareRepository}"/>
<Border Grid.Row="3" Grid.Column="1" MinHeight="32" IsVisible="{Binding !IsBareRepository}">
<Border Grid.Row="2" Grid.Column="1" MinHeight="32" IsVisible="{Binding !IsBareRepository}">
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center">
<RadioButton GroupName="LocalChanges"
Margin="0,0,8,0"
@@ -78,17 +72,17 @@
</WrapPanel>
</Border>
<CheckBox Grid.Row="4" Grid.Column="1"
<CheckBox Grid.Row="3" Grid.Column="1"
Content="{DynamicResource Text.CreateBranch.OverwriteExisting}"
IsChecked="{Binding AllowOverwrite, Mode=TwoWay}"
ToolTip.Tip="checkout -B or branch -f"/>
<CheckBox Grid.Row="5" Grid.Column="1"
<CheckBox Grid.Row="4" Grid.Column="1"
Content="{DynamicResource Text.CreateBranch.Checkout}"
IsChecked="{Binding CheckoutAfterCreated, Mode=TwoWay}"
IsVisible="{Binding !IsBareRepository}"/>
<CheckBox Grid.Row="6" Grid.Column="1"
<CheckBox Grid.Row="5" Grid.Column="1"
Content="{DynamicResource Text.Checkout.RecurseSubmodules}"
IsChecked="{Binding RecurseSubmodules, Mode=TwoWay}"
ToolTip.Tip="--recurse-submodules">

View File

@@ -41,13 +41,13 @@
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CreateTag.Name}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="2"
Text="{Binding TagName, Mode=TwoWay}"
Watermark="{DynamicResource Text.CreateTag.Name.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<v:BranchOrTagNameTextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="2"
Text="{Binding TagName, Mode=TwoWay}"
Watermark="{DynamicResource Text.CreateTag.Name.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"

View File

@@ -23,11 +23,11 @@
VerticalAlignment="Center"
CornerRadius="9"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding Target.TrackStatus.IsVisible}">
IsVisible="{Binding Target.IsTrackStatusVisible}">
<TextBlock Foreground="{DynamicResource Brush.BadgeFG}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"
Text="{Binding Target.TrackStatus}"/>
Text="{Binding Target.TrackStatusDescription}"/>
</Border>
</StackPanel>

View File

@@ -52,11 +52,11 @@
VerticalAlignment="Center"
CornerRadius="9"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding TrackStatus.IsVisible}">
IsVisible="{Binding IsTrackStatusVisible}">
<TextBlock Foreground="{DynamicResource Brush.BadgeFG}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="10"
Text="{Binding TrackStatus}"/>
Text="{Binding TrackStatusDescription, Mode=OneWay}"/>
</Border>
</Grid>
</DataTemplate>

View File

@@ -711,7 +711,7 @@ namespace SourceGit.Views
if (!isHead)
{
if (current.TrackStatus.Ahead.Contains(commit.SHA))
if (current.Ahead.Contains(commit.SHA))
{
var upstream = repo.Branches.Find(x => x.FullName.Equals(current.Upstream, StringComparison.Ordinal));
var pushRevision = new MenuItem();
@@ -911,7 +911,7 @@ namespace SourceGit.Views
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = current.TrackStatus.Ahead.Count == 0 && current.TrackStatus.Behind.Count > 0;
fastForward.IsEnabled = current.Ahead.Count == 0 && current.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
var b = repo.Branches.Find(x => x.FriendlyName == upstream);

View File

@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:c="using:SourceGit.Converters"
xmlns:v="using:SourceGit.Views"
@@ -52,11 +53,12 @@
<Border Grid.Row="2" Margin="8,0,8,8" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
<Grid RowDefinitions="*,3,*">
<v:InteractiveRebaseListBox Grid.Row="0"
x:Name="IRItemListBox"
Focusable="True"
Background="{DynamicResource Brush.Contents}"
ItemsSource="{Binding Items}"
SelectionMode="Single"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectionMode="Multiple"
SelectedItem="{Binding PreSelected, Mode=OneWay}"
SelectionChanged="OnRowsSelectionChanged"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
@@ -176,21 +178,38 @@
BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Brush.Border2}"/>
<Grid Grid.Row="2">
<Path Width="128" Height="128"
Data="{StaticResource Icons.Detail}"
HorizontalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"
IsVisible="{Binding SelectedItem, Converter={x:Static ObjectConverters.IsNull}}"/>
<ContentControl Grid.Row="2">
<ContentControl.Content>
<Binding Path="Detail">
<Binding.TargetNullValue>
<Path Width="128" Height="128"
Data="{StaticResource Icons.Detail}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"/>
</Binding.TargetNullValue>
</Binding>
</ContentControl.Content>
<ContentControl Content="{Binding DetailContext}" IsVisible="{Binding SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:CommitDetail">
<v:CommitDetail/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
<ContentControl.DataTemplates>
<DataTemplate DataType="m:Count">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Width="128" Height="128"
Data="{StaticResource Icons.Detail}"
HorizontalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"/>
<TextBlock HorizontalAlignment="Center"
Margin="0,16"
FontSize="24" FontWeight="Bold"
Foreground="{DynamicResource Brush.FG2}"
Text="{Binding Value, Converter={x:Static c:StringConverters.FormatByResourceKey}, ConverterParameter='Histories.Selected'}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="vm:CommitDetail">
<v:CommitDetail/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</Border>

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
@@ -14,17 +15,19 @@ namespace SourceGit.Views
{
protected override Type StyleKeyOverride => typeof(ListBox);
/// <summary>
/// Prevent ListBox handle the arrow keys.
/// </summary>
/// <param name="e"></param>
protected override void OnKeyDown(KeyEventArgs e)
{
if (DataContext is not ViewModels.InteractiveRebase vm)
if (DataContext is not ViewModels.InteractiveRebase vm || SelectedItems == null)
return;
var item = vm.SelectedItem;
if (item == null)
var items = new List<ViewModels.InteractiveRebaseItem>();
foreach (var item in SelectedItems)
{
if (item is ViewModels.InteractiveRebaseItem rebaseItem)
items.Add(rebaseItem);
}
if (items.Count == 0)
{
base.OnKeyDown(e);
return;
@@ -32,44 +35,54 @@ namespace SourceGit.Views
if (e.Key == Key.P)
{
vm.ChangeAction(item, Models.InteractiveRebaseAction.Pick);
vm.ChangeAction(items, Models.InteractiveRebaseAction.Pick);
MoveSelection(NavigationDirection.Next);
e.Handled = true;
}
else if (e.Key == Key.E)
{
vm.ChangeAction(item, Models.InteractiveRebaseAction.Edit);
vm.ChangeAction(items, Models.InteractiveRebaseAction.Edit);
MoveSelection(NavigationDirection.Next);
e.Handled = true;
}
else if (e.Key == Key.R)
{
vm.ChangeAction(item, Models.InteractiveRebaseAction.Reword);
vm.ChangeAction(items, Models.InteractiveRebaseAction.Reword);
if (items.Count == 1)
this.FindAncestorOfType<InteractiveRebase>()?.OpenCommitMessageEditor(items[0]);
else
MoveSelection(NavigationDirection.Next);
e.Handled = true;
}
else if (e.Key == Key.S)
{
vm.ChangeAction(item, Models.InteractiveRebaseAction.Squash);
vm.ChangeAction(items, Models.InteractiveRebaseAction.Squash);
MoveSelection(NavigationDirection.Next);
e.Handled = true;
}
else if (e.Key == Key.F)
{
vm.ChangeAction(item, Models.InteractiveRebaseAction.Fixup);
vm.ChangeAction(items, Models.InteractiveRebaseAction.Fixup);
MoveSelection(NavigationDirection.Next);
e.Handled = true;
}
else if (e.Key == Key.D)
{
vm.ChangeAction(item, Models.InteractiveRebaseAction.Drop);
vm.ChangeAction(items, Models.InteractiveRebaseAction.Drop);
MoveSelection(NavigationDirection.Next);
e.Handled = true;
}
else if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
else if (e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control) && items.Count == 1)
{
if (e.Key == Key.Up)
{
vm.MoveItemUp(item);
vm.MoveItemUp(items[0]);
e.Handled = true;
}
else if (e.Key == Key.Down)
{
vm.MoveItemDown(item);
vm.MoveItemDown(items[0]);
e.Handled = true;
}
}
@@ -85,14 +98,14 @@ namespace SourceGit.Views
{
CloseOnESC = true;
InitializeComponent();
IRItemListBox?.Focus();
}
protected override void OnLoaded(RoutedEventArgs e)
public void OpenCommitMessageEditor(ViewModels.InteractiveRebaseItem item)
{
base.OnLoaded(e);
var list = this.FindDescendantOfType<InteractiveRebaseListBox>();
list?.Focus();
var dialog = new CommitMessageEditor();
dialog.AsBuiltin(item.FullMessage, msg => item.FullMessage = msg);
dialog.ShowDialog(this);
}
private void CloseWindow(object _1, RoutedEventArgs _2)
@@ -100,21 +113,27 @@ namespace SourceGit.Views
Close();
}
private void OnRowsSelectionChanged(object sender, SelectionChangedEventArgs e)
private void OnRowsSelectionChanged(object _, SelectionChangedEventArgs e)
{
if (!_firstSelectionChangedHandled &&
sender is InteractiveRebaseListBox list &&
list.SelectedItem is ViewModels.InteractiveRebaseItem item)
{
if (DataContext is not ViewModels.InteractiveRebase vm)
return;
var isFirstTimeHere = !_firstSelectionChangedHandled;
if (isFirstTimeHere)
_firstSelectionChangedHandled = true;
if (item.Action == Models.InteractiveRebaseAction.Reword)
{
var dialog = new CommitMessageEditor();
dialog.AsBuiltin(item.FullMessage, msg => item.FullMessage = msg);
dialog.ShowDialog(this);
}
var selected = IRItemListBox.SelectedItems ?? new List<object>();
var items = new List<ViewModels.InteractiveRebaseItem>();
foreach (var item in selected)
{
if (item is ViewModels.InteractiveRebaseItem rebaseItem)
items.Add(rebaseItem);
}
vm.SelectCommits(items);
if (items.Count == 1 && isFirstTimeHere && items[0].Action == Models.InteractiveRebaseAction.Reword)
OpenCommitMessageEditor(items[0]);
}
private void OnSetupRowHeaderDragDrop(object sender, RoutedEventArgs e)
@@ -195,11 +214,7 @@ namespace SourceGit.Views
private void OnOpenCommitMessageEditor(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ViewModels.InteractiveRebaseItem item })
{
var dialog = new CommitMessageEditor();
dialog.AsBuiltin(item.FullMessage, msg => item.FullMessage = msg);
dialog.ShowDialog(this);
}
OpenCommitMessageEditor(item);
e.Handled = true;
}
@@ -247,17 +262,36 @@ namespace SourceGit.Views
menuItem.Icon = new Ellipse() { Width = 14, Height = 14, Fill = iconBrush };
menuItem.Header = header;
menuItem.Tag = hotkey;
menuItem.Click += (_, e) =>
{
if (DataContext is ViewModels.InteractiveRebase vm)
vm.ChangeAction(item, action);
e.Handled = true;
};
menuItem.Click += (_, __) => ChangeItemsAction(item, action);
flyout.Items.Add(menuItem);
}
private void ChangeItemsAction(ViewModels.InteractiveRebaseItem target, Models.InteractiveRebaseAction action)
{
if (DataContext is not ViewModels.InteractiveRebase vm)
return;
var selected = IRItemListBox.SelectedItems ?? new List<object>();
var items = new List<ViewModels.InteractiveRebaseItem>();
foreach (var item in selected)
{
if (item is ViewModels.InteractiveRebaseItem rebaseItem)
items.Add(rebaseItem);
}
if (!items.Contains(target))
{
items.Clear();
items.Add(target);
}
vm.ChangeAction(items, action);
if (items.Count == 1 && action == Models.InteractiveRebaseAction.Reword)
OpenCommitMessageEditor(items[0]);
}
private bool _firstSelectionChangedHandled = false;
}
}

View File

@@ -19,9 +19,9 @@
</Grid.RowDefinitions>
<!-- Custom TitleBar -->
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*,Auto">
<Grid Grid.Row="0" ColumnDefinitions="Auto,32,32,*,Auto">
<!-- Bottom border -->
<Border Grid.Column="0" Grid.ColumnSpan="4"
<Border Grid.Column="0" Grid.ColumnSpan="5"
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
DoubleTapped="MaximizeOrRestoreWindow"
@@ -31,9 +31,9 @@
<Border Grid.Column="0" Width="72" IsVisible="{Binding #ThisControl.HasLeftCaptionButton}"/>
<!-- Menu (Windows/Linux) -->
<Button Grid.Column="0" Classes="icon_button" VerticalAlignment="Bottom" Margin="6,0,2,3" IsVisible="{OnPlatform True, macOS=False}">
<Button Grid.Column="0" Classes="icon_button" VerticalAlignment="Bottom" Margin="6,0,0,3" IsVisible="{OnPlatform True, macOS=False}">
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-8">
<MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-6">
<MenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" InputGesture="Ctrl+,">
<MenuItem.Icon>
<Path Width="14" Height="14" Data="{StaticResource Icons.Settings}"/>
@@ -84,11 +84,27 @@
Fill="{Binding ActiveWorkspace.Brush}"/>
</Button>
<!-- Pages Switcher Toggle Button -->
<Button Grid.Column="2" Classes="icon_button" VerticalAlignment="Bottom" Margin="0,0,0,1" Command="{Binding OpenTabSwitcher}">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource Text.Launcher.Pages}"
VerticalAlignment="Center"/>
<TextBlock Margin="8,0,0,0"
Text="{OnPlatform Ctrl+P, macOS=⌘+P}"
FontSize="11"
Foreground="{DynamicResource MenuFlyoutItemKeyboardAcceleratorTextForeground}"
VerticalAlignment="Center"/>
</StackPanel>
</ToolTip.Tip>
<Path Width="13" Height="13" Margin="0,2,0,0" Data="{StaticResource Icons.Tabs}"/>
</Button>
<!-- Pages Tabs -->
<v:LauncherTabBar Grid.Column="2" Height="30" Margin="0,0,16,0" VerticalAlignment="Bottom"/>
<v:LauncherTabBar Grid.Column="3" Height="30" Margin="0,0,16,0" VerticalAlignment="Bottom"/>
<!-- Caption Buttons (Windows/Linux) -->
<Border Grid.Column="3" Margin="16,0,0,0" IsVisible="{Binding #ThisControl.HasRightCaptionButton}">
<Border Grid.Column="4" Margin="16,0,0,0" IsVisible="{Binding #ThisControl.HasRightCaptionButton}">
<v:CaptionButtons Height="30" VerticalAlignment="Top"/>
</Border>
</Grid>

View File

@@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
@@ -317,6 +318,20 @@ namespace SourceGit.Views
{
var pref = ViewModels.Preferences.Instance;
var menu = new ContextMenu();
menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
menu.VerticalOffset = -6;
var groupHeader = new TextBlock()
{
Text = App.Text("Launcher.Workspaces"),
FontWeight = FontWeight.Bold,
};
var workspaces = new MenuItem();
workspaces.Header = groupHeader;
workspaces.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+P" : "Ctrl+Shift+P";
workspaces.IsEnabled = false;
menu.Items.Add(workspaces);
for (var i = 0; i < pref.Workspaces.Count; i++)
{

View File

@@ -83,7 +83,7 @@
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:LauncherPage">
<Grid ColumnDefinitions="Auto,6,*" Background="Transparent" DoubleTapped="OnItemDoubleTapped">
<Grid ColumnDefinitions="Auto,6,*" Background="Transparent" Tapped="OnItemTapped">
<Path Grid.Column="0"
Width="12" Height="12"
Fill="{Binding Node.Bookmark, Converter={x:Static c:IntConverters.ToBookmarkBrush}}"

View File

@@ -21,7 +21,7 @@ namespace SourceGit.Views
}
}
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
private void OnItemTapped(object sender, TappedEventArgs e)
{
if (DataContext is ViewModels.LauncherPageSwitcher switcher)
{
@@ -32,14 +32,28 @@ namespace SourceGit.Views
private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down && PagesListBox.ItemCount > 0)
if (PagesListBox.ItemCount == 0)
return;
if (e.Key == Key.Down)
{
PagesListBox.Focus(NavigationMethod.Directional);
if (PagesListBox.SelectedIndex < 0)
PagesListBox.SelectedIndex = 0;
else if (PagesListBox.SelectedIndex < PagesListBox.ItemCount)
if (PagesListBox.SelectedIndex < PagesListBox.ItemCount - 1)
PagesListBox.SelectedIndex++;
else
PagesListBox.SelectedIndex = 0;
e.Handled = true;
}
else if (e.Key == Key.Up)
{
PagesListBox.Focus(NavigationMethod.Directional);
if (PagesListBox.SelectedIndex > 0)
PagesListBox.SelectedIndex--;
else
PagesListBox.SelectedIndex = PagesListBox.ItemCount - 1;
e.Handled = true;
}

View File

@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:c="using:SourceGit.Converters"
xmlns:v="using:SourceGit.Views"
@@ -51,7 +52,11 @@
<ToolTip.Tip>
<Grid>
<TextBlock Text="{DynamicResource Text.Welcome}" IsVisible="{Binding !Node.IsRepository}"/>
<TextBlock Text="{Binding Node.Id}" IsVisible="{Binding Node.IsRepository}"/>
<StackPanel Orientation="Horizontal" IsVisible="{Binding Node.IsRepository}">
<TextBlock Text="{Binding Node.Id}"/>
<TextBlock Text="{Binding DirtyState, Converter={x:Static c:DirtyStateConverters.ToDesc}}"
Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</Grid>
</ToolTip.Tip>
@@ -78,8 +83,8 @@
Width="8" Height="8"
Margin="0,0,6,0"
VerticalAlignment="Center"
IsVisible="{Binding DirtyBrush, Converter={x:Static ObjectConverters.IsNotNull}}"
Fill="{Binding DirtyBrush}"/>
IsVisible="{Binding DirtyState, Converter={x:Static ObjectConverters.IsNotNull}, ConverterParameter={x:Static m:DirtyState.None}}"
Fill="{Binding DirtyState, Converter={x:Static c:DirtyStateConverters.ToBrush}}"/>
<TextBlock Grid.Column="1"
Classes="primary"
@@ -147,114 +152,7 @@
<Path Width="8" Height="14" Stretch="Fill" Data="{StaticResource Icons.TriangleRight}"/>
</RepeatButton>
<Button x:Name="PageSelector" Classes="icon_button" Width="16" Height="16" Margin="8,0">
<Button.Flyout>
<Flyout Opened="OnTabsDropdownOpened" Closed="OnTabsDropdownClosed">
<Grid RowDefinitions="28,Auto" KeyDown="OnTabsDropdownKeyDown" LostFocus="OnTabsDropdownLostFocus">
<TextBox Grid.Row="0"
Height="24"
Margin="4,0"
BorderThickness="1"
CornerRadius="12"
Text="{Binding #ThisControl.SearchFilter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center"
KeyDown="OnTabsDropdownSearchBoxKeyDown"
v:AutoFocusBehaviour.IsEnabled="True">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Click="OnClearSearchFilter"
IsVisible="{Binding #ThisControl.SearchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<ListBox Grid.Row="1"
x:Name="TabsDropdownList"
Focusable="True"
Width="200"
MaxHeight="400"
Margin="0,4,0,0"
Background="Transparent"
SelectionMode="Single"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding #ThisControl.SelectablePages}"
SelectedItem="{Binding ActivePage, Mode=OneWay}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="MinHeight" Value="26"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
<Style Selector="ListBox">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Grid/>
</FocusAdornerTemplate>
</Setter>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:LauncherPage">
<Grid ColumnDefinitions="Auto,*" Background="Transparent" Tapped="OnTabsDropdownItemTapped">
<Path Grid.Column="0"
Width="12" Height="12" Margin="12,0"
Fill="{Binding Node.Bookmark, Converter={x:Static c:IntConverters.ToBookmarkBrush}}"
Data="{StaticResource Icons.Bookmark}"
IsVisible="{Binding Node.IsRepository}"
IsHitTestVisible="False"/>
<Path Grid.Column="0"
Width="12" Height="12" Margin="12,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Repositories}"
IsVisible="{Binding !Node.IsRepository}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Classes="primary"
VerticalAlignment="Center"
Text="{Binding Node.Name}"
IsVisible="{Binding Node.IsRepository}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Classes="primary"
VerticalAlignment="Center"
Text="{DynamicResource Text.PageTabBar.Welcome.Title}"
IsVisible="{Binding !Node.IsRepository}"
IsHitTestVisible="False"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Flyout>
</Button.Flyout>
<Path Width="14" Height="14" Data="{StaticResource Icons.CircleDown}"/>
</Button>
<Button Classes="icon_button" Width="16" Height="16" Command="{Binding AddNewTab}">
<Button Classes="icon_button" Width="16" Height="16" Margin="4,0,0,0" Command="{Binding AddNewTab}">
<ToolTip.Tip>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Text.PageTabBar.New}" VerticalAlignment="Center"/>

View File

@@ -1,7 +1,6 @@
using System;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -20,20 +19,6 @@ namespace SourceGit.Views
set => SetValue(IsScrollerVisibleProperty, value);
}
public static readonly StyledProperty<string> SearchFilterProperty =
AvaloniaProperty.Register<LauncherTabBar, string>(nameof(SearchFilter));
public string SearchFilter
{
get => GetValue(SearchFilterProperty);
set => SetValue(SearchFilterProperty, value);
}
public AvaloniaList<ViewModels.LauncherPage> SelectablePages
{
get;
} = [];
public LauncherTabBar()
{
InitializeComponent();
@@ -141,14 +126,6 @@ namespace SourceGit.Views
context.DrawGeometry(fill, stroke, geo);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SearchFilterProperty)
UpdateSelectablePages();
}
private void ScrollTabs(object _, PointerWheelEventArgs e)
{
if (!e.KeyModifiers.HasFlag(KeyModifiers.Shift))
@@ -337,97 +314,6 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnTabsDropdownOpened(object sender, EventArgs e)
{
UpdateSelectablePages();
}
private void OnTabsDropdownClosed(object sender, EventArgs e)
{
SelectablePages.Clear();
SearchFilter = string.Empty;
}
private void OnTabsDropdownKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
{
PageSelector.Flyout?.Hide();
e.Handled = true;
}
else if (e.Key == Key.Enter)
{
if (TabsDropdownList.SelectedItem is ViewModels.LauncherPage page &&
DataContext is ViewModels.Launcher vm)
{
vm.ActivePage = page;
PageSelector.Flyout?.Hide();
e.Handled = true;
}
}
}
private void OnTabsDropdownSearchBoxKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down && TabsDropdownList.ItemCount > 0)
{
TabsDropdownList.Focus(NavigationMethod.Directional);
if (TabsDropdownList.SelectedIndex < 0)
TabsDropdownList.SelectedIndex = 0;
else if (TabsDropdownList.SelectedIndex < TabsDropdownList.ItemCount)
TabsDropdownList.SelectedIndex++;
e.Handled = true;
}
}
private void OnTabsDropdownLostFocus(object sender, RoutedEventArgs e)
{
if (sender is Control { IsFocused: false, IsKeyboardFocusWithin: false })
PageSelector.Flyout?.Hide();
}
private void OnClearSearchFilter(object sender, RoutedEventArgs e)
{
SearchFilter = string.Empty;
}
private void OnTabsDropdownItemTapped(object sender, TappedEventArgs e)
{
if (sender is Control { DataContext: ViewModels.LauncherPage page } &&
DataContext is ViewModels.Launcher vm)
{
vm.ActivePage = page;
PageSelector.Flyout?.Hide();
e.Handled = true;
}
}
private void UpdateSelectablePages()
{
if (DataContext is not ViewModels.Launcher vm)
return;
SelectablePages.Clear();
var pages = vm.Pages;
var filter = SearchFilter?.Trim() ?? "";
if (string.IsNullOrEmpty(filter))
{
SelectablePages.AddRange(pages);
return;
}
foreach (var page in pages)
{
var node = page.Node;
if (node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
(node.IsRepository && node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase)))
SelectablePages.Add(page);
}
}
private bool _pressedTab = false;
private Point _pressedTabPosition = new();
private bool _startDragTab = false;

View File

@@ -169,7 +169,7 @@
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.Appearance}"/>
</TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preferences.Appearance.Theme}"
HorizontalAlignment="Right"
@@ -276,6 +276,11 @@
IsChecked="{Binding UseFixedTabWidth, Mode=TwoWay}"/>
<CheckBox Grid.Row="8" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preferences.Appearance.UseAutoHideScrollBars}"
IsChecked="{Binding UseAutoHideScrollBars, Mode=TwoWay}"/>
<CheckBox Grid.Row="9" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preferences.Appearance.UseNativeWindowFrame}"
IsChecked="{Binding UseSystemWindowFrame, Mode=OneTime}"
@@ -788,7 +793,10 @@
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Model, Mode=TwoWay}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preferences.AI.ApiKey}"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding ApiKey, Mode=TwoWay}" PasswordChar="*"/>
<TextBox Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding ApiKey, Mode=TwoWay}" PasswordChar="*" RevealPassword="{Binding ReadApiKeyFromEnv, Mode=OneWay}"/>
<CheckBox Margin="0,4,0,0"
Content="{DynamicResource Text.Preferences.AI.ReadApiKeyFromEnv}"
IsChecked="{Binding ReadApiKeyFromEnv, Mode=TwoWay}"/>
<TextBlock Margin="0,12,0,0" Text="{DynamicResource Text.Preferences.AI.AnalyzeDiffPrompt}"/>
<TextBox Height="120"

View File

@@ -12,7 +12,7 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.RenameBranch}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32,32,Auto" ColumnDefinitions="120,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
@@ -26,20 +26,13 @@
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.RenameBranch.Name}"/>
<TextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="2"
Text="{Binding Name, Mode=TwoWay}"
Watermark="{DynamicResource Text.RenameBranch.Name.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
<StackPanel Grid.Row="2" Grid.Column="1"
Orientation="Horizontal"
IsVisible="{Binding Name, Converter={x:Static c:StringConverters.ContainsSpaces}}">
<Path Width="10" Height="10" Data="{StaticResource Icons.Error}" Fill="DarkOrange"/>
<TextBlock Classes="small" Text="{DynamicResource Text.CreateBranch.Name.WarnSpace}" Margin="4,0,0,0"/>
</StackPanel>
<v:BranchOrTagNameTextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="2"
Text="{Binding Name, Mode=TwoWay}"
Watermark="{DynamicResource Text.RenameBranch.Name.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -218,10 +218,10 @@
<ToggleButton Grid.Row="0" Classes="group_expander" IsChecked="{Binding IsLocalBranchGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="16,*,Auto,Auto">
<Path Grid.Column="0" Width="11" Height="11" HorizontalAlignment="Left" Data="{StaticResource Icons.Local}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
<Run Text="{DynamicResource Text.Repository.LocalBranches}"/>
<Run Text="{Binding LocalBranchesCount, StringFormat='({0})', Mode=OneWay}"/>
</TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.Repository.LocalBranches}"/>
<TextBlock Classes="group_header_label" Text="{Binding LocalBranchesCount, StringFormat='({0})'}" Margin="4,0,0,0"/>
</StackPanel>
<Button Grid.Column="2"
Classes="icon_button"
Width="14"
@@ -251,10 +251,10 @@
<ToggleButton Grid.Row="2" Classes="group_expander" IsChecked="{Binding IsRemoteGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="16,*,Auto,Auto">
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Data="{StaticResource Icons.Remotes}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
<Run Text="{DynamicResource Text.Repository.Remotes}"/>
<Run Text="{Binding Remotes, Converter={x:Static c:ListConverters.ToCount}, Mode=OneWay}"/>
</TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.Repository.Remotes}"/>
<TextBlock Classes="group_header_label" Text="{Binding Remotes, Converter={x:Static c:ListConverters.ToCount}}" Margin="4,0,0,0"/>
</StackPanel>
<Button Grid.Column="2"
Classes="icon_button"
Width="14"
@@ -284,10 +284,10 @@
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="16,*,Auto,Auto,Auto">
<Path Grid.Column="0" Width="11" Height="11" Margin="2,1,0,0" HorizontalAlignment="Left" Data="{StaticResource Icons.Tags}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
<Run Text="{DynamicResource Text.Repository.Tags}"/>
<Run Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}, Mode=OneWay}"/>
</TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.Repository.Tags}"/>
<TextBlock Classes="group_header_label" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Margin="4,0,0,0"/>
</StackPanel>
<ToggleButton Grid.Column="2"
Classes="show_as_tree"
Width="14"
@@ -329,10 +329,10 @@
<ToggleButton Grid.Row="6" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="16,*,Auto,Auto,Auto">
<Path Grid.Column="0" Width="10" Height="10" Margin="2,0,0,0" HorizontalAlignment="Left" Data="{StaticResource Icons.Submodules}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
<Run Text="{DynamicResource Text.Repository.Submodules}"/>
<Run Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}, Mode=OneWay}"/>
</TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.Repository.Submodules}"/>
<TextBlock Classes="group_header_label" Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}" Margin="4,0,0,0"/>
</StackPanel>
<ToggleButton Grid.Column="2"
Classes="show_as_tree"
Width="14"
@@ -371,10 +371,10 @@
<ToggleButton Grid.Row="8" Classes="group_expander" IsChecked="{Binding IsWorktreeGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="16,*,Auto,Auto">
<Path Grid.Column="0" Width="11" Height="11" Margin="1,0,0,0" HorizontalAlignment="Left" Data="{StaticResource Icons.Worktrees}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Grid.Column="1" Classes="group_header_label" Margin="0">
<Run Text="{DynamicResource Text.Repository.Worktrees}"/>
<Run Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}, Mode=OneWay}"/>
</TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Classes="group_header_label" Text="{DynamicResource Text.Repository.Worktrees}"/>
<TextBlock Classes="group_header_label" Text="{Binding Worktrees, Converter={x:Static c:ListConverters.ToCount}}" Margin="4,0,0,0"/>
</StackPanel>
<Button Grid.Column="2"
Classes="icon_button"
Width="14"
@@ -777,7 +777,7 @@
</MultiBinding>
</Border.IsVisible>
<Grid Height="28" ColumnDefinitions="Auto,*,Auto">
<Grid ColumnDefinitions="Auto,*,Auto">
<Path Grid.Column="0"
Margin="8,0,0,0"
Width="12" Height="12"
@@ -791,10 +791,10 @@
Fill="{DynamicResource Brush.FG2}"
IsVisible="{Binding HistoriesFilterMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:FilterMode.Excluded}}"/>
<ItemsControl Grid.Column="1" Margin="8,0,0,0" ItemsSource="{Binding Settings.HistoriesFilters}">
<ItemsControl Grid.Column="1" Margin="8,4" ItemsSource="{Binding Settings.HistoriesFilters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
<WrapPanel Orientation="Horizontal" ItemHeight="22"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
@@ -804,7 +804,8 @@
Margin="0,0,6,0"
CornerRadius="12"
BorderThickness="1"
BorderBrush="{Binding Mode, Converter={x:Static c:FilterModeConverters.ToBorderBrush}}">
BorderBrush="{Binding Mode, Converter={x:Static c:FilterModeConverters.ToBorderBrush}}"
VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Margin="8,0">
<Path Width="10" Height="10" Data="{StaticResource Icons.Branch}" IsVisible="{Binding IsBranch}"/>
<Path Width="10" Height="10" Data="{StaticResource Icons.Tag}" IsVisible="{Binding !IsBranch}"/>

View File

@@ -190,6 +190,17 @@ namespace SourceGit.Views
{
var menu = new ContextMenu();
var switchTo = new MenuItem();
switchTo.Header = App.Text("Worktree.Open");
switchTo.Icon = App.CreateMenuIcon("Icons.Folder.Open");
switchTo.Click += (_, ev) =>
{
repo.OpenWorktree(worktree);
ev.Handled = true;
};
menu.Items.Add(switchTo);
menu.Items.Add(new MenuItem() { Header = "-" });
if (worktree.IsLocked)
{
var unlock = new MenuItem();

View File

@@ -374,8 +374,7 @@
<CheckBox Grid.Row="7" Grid.Column="1"
Margin="0,4,0,0"
Content="{DynamicResource Text.Configure.IssueTracker.Share}"
IsChecked="{Binding IsShared, Mode=TwoWay}"
Click="OnIssueTrackerIsSharedChanged"/>
IsChecked="{Binding IsShared, Mode=TwoWay}"/>
</Grid>
</DataTemplate>
</ContentControl.DataTemplates>

View File

@@ -50,15 +50,15 @@ namespace SourceGit.Views
e.Handled = true;
}
private async void OnNewCustomIssueTracker(object sender, RoutedEventArgs e)
private void OnNewCustomIssueTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
await vm.AddIssueTrackerAsync("New Issue Tracker", @"#(\d+)", "https://xxx/$1");
vm.AddIssueTracker("New Issue Tracker", @"#(\d+)", "https://xxx/$1");
e.Handled = true;
}
private async void OnAddGitHubIssueTracker(object sender, RoutedEventArgs e)
private void OnAddGitHubIssueTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
@@ -73,17 +73,17 @@ namespace SourceGit.Views
}
}
await vm.AddIssueTrackerAsync("GitHub Issue", @"#(\d+)", link);
vm.AddIssueTracker("GitHub Issue", @"#(\d+)", link);
}
e.Handled = true;
}
private async void OnAddJiraIssueTracker(object sender, RoutedEventArgs e)
private void OnAddJiraIssueTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
await vm.AddIssueTrackerAsync(
vm.AddIssueTracker(
"Jira Tracker",
@"PROJ-(\d+)",
"https://jira.yourcompany.com/browse/PROJ-$1");
@@ -92,11 +92,11 @@ namespace SourceGit.Views
e.Handled = true;
}
private async void OnAddAzureWorkItemTracker(object sender, RoutedEventArgs e)
private void OnAddAzureWorkItemTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
await vm.AddIssueTrackerAsync(
vm.AddIssueTracker(
"Azure DevOps Tracker",
@"#(\d+)",
"https://dev.azure.com/yourcompany/workspace/_workitems/edit/$1");
@@ -105,7 +105,7 @@ namespace SourceGit.Views
e.Handled = true;
}
private async void OnAddGitLabIssueTracker(object sender, RoutedEventArgs e)
private void OnAddGitLabIssueTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
@@ -117,13 +117,13 @@ namespace SourceGit.Views
break;
}
await vm.AddIssueTrackerAsync("GitLab Issue", @"#(\d+)", link);
vm.AddIssueTracker("GitLab Issue", @"#(\d+)", link);
}
e.Handled = true;
}
private async void OnAddGitLabMergeRequestTracker(object sender, RoutedEventArgs e)
private void OnAddGitLabMergeRequestTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
@@ -135,13 +135,13 @@ namespace SourceGit.Views
break;
}
await vm.AddIssueTrackerAsync("GitLab MR", @"!(\d+)", link);
vm.AddIssueTracker("GitLab MR", @"!(\d+)", link);
}
e.Handled = true;
}
private async void OnAddGiteeIssueTracker(object sender, RoutedEventArgs e)
private void OnAddGiteeIssueTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
@@ -156,13 +156,13 @@ namespace SourceGit.Views
}
}
await vm.AddIssueTrackerAsync("Gitee Issue", @"#([0-9A-Z]{6,10})", link);
vm.AddIssueTracker("Gitee Issue", @"#([0-9A-Z]{6,10})", link);
}
e.Handled = true;
}
private async void OnAddGiteePullRequestTracker(object sender, RoutedEventArgs e)
private void OnAddGiteePullRequestTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
@@ -177,17 +177,17 @@ namespace SourceGit.Views
}
}
await vm.AddIssueTrackerAsync("Gitee Pull Request", @"!(\d+)", link);
vm.AddIssueTracker("Gitee Pull Request", @"!(\d+)", link);
}
e.Handled = true;
}
private async void OnAddGerritChangeIdTracker(object sender, RoutedEventArgs e)
private void OnAddGerritChangeIdTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
{
await vm.AddIssueTrackerAsync(
vm.AddIssueTracker(
"Gerrit Change-Id",
@"(I[A-Za-z0-9]{40})",
"https://gerrit.yourcompany.com/q/$1");
@@ -196,18 +196,10 @@ namespace SourceGit.Views
e.Handled = true;
}
private async void OnRemoveIssueTracker(object sender, RoutedEventArgs e)
private void OnRemoveIssueTracker(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
await vm.RemoveIssueTrackerAsync();
e.Handled = true;
}
private async void OnIssueTrackerIsSharedChanged(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.RepositoryConfigure vm)
await vm.ChangeIssueTrackerShareModeAsync();
vm.RemoveIssueTracker();
e.Handled = true;
}

View File

@@ -535,7 +535,7 @@ namespace SourceGit.Views
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
App.ShowWindow(new ViewModels.FileHistories(repo, file.Path, commit.SHA));
App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, file.Path, commit.SHA));
ev.Handled = true;
};

View File

@@ -11,7 +11,7 @@
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.ScanRepositories}"/>
<Grid Margin="0,16,0,0" RowDefinitions="32" ColumnDefinitions="120,*">
<Grid Margin="0,16,0,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
@@ -20,7 +20,8 @@
Height="28" Padding="4,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding ScanDirs, Mode=OneWay}"
SelectedItem="{Binding Selected, Mode=TwoWay}">
SelectedItem="{Binding Selected, Mode=TwoWay}"
IsVisible="{Binding !UseCustomDir, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="m:ScanDir">
<Grid ColumnDefinitions="20,*,Auto">
@@ -38,6 +39,21 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Grid.Row="0" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding CustomDir, Mode=TwoWay}"
IsVisible="{Binding UseCustomDir, Mode=OneWay}">
<TextBox.InnerRightContent>
<Button Classes="icon_button" Width="30" Height="30" Click="OnSelectRootDir">
<Path Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<CheckBox Grid.Row="1" Grid.Column="1"
Content="{DynamicResource Text.ScanRepositories.UseCustomDir}"
IsChecked="{Binding UseCustomDir, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -1,4 +1,8 @@
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
namespace SourceGit.Views
{
@@ -8,5 +12,33 @@ namespace SourceGit.Views
{
InitializeComponent();
}
private async void OnSelectRootDir(object _, RoutedEventArgs e)
{
var provider = TopLevel.GetTopLevel(this)?.StorageProvider;
if (provider == null)
return;
if (DataContext is not ViewModels.ScanRepositories vm)
return;
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
try
{
var selected = await provider.OpenFolderPickerAsync(options);
if (selected.Count == 1)
{
var folder = selected[0];
var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString();
vm.CustomDir = folderPath;
}
}
catch (Exception ex)
{
App.RaiseException(string.Empty, $"Failed to select root scanning directory: {ex.Message}");
}
e.Handled = true;
}
}
}

View File

@@ -248,7 +248,7 @@ namespace SourceGit.Views
histories.Icon = App.CreateMenuIcon("Icons.Histories");
histories.Click += (_, ev) =>
{
App.ShowWindow(new ViewModels.FileHistories(repo, submodule.Path));
App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, submodule.Path));
ev.Handled = true;
};

View File

@@ -12,7 +12,7 @@
<ContentControl Content="{Binding}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:CombinedTextDiff">
<Grid ColumnDefinitions="*,1,8">
<Grid ColumnDefinitions="*,1,12">
<v:CombinedTextDiffPresenter Grid.Column="0"
FileName="{Binding File}"
Foreground="{DynamicResource Brush.FG1}"

View File

@@ -11,6 +11,7 @@ using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -1334,6 +1335,8 @@ namespace SourceGit.Views
public override void Render(DrawingContext context)
{
context.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, Bounds.Width, Bounds.Height));
var total = 0;
if (DataContext is ViewModels.TwoSideTextDiff twoSideDiff)
{
@@ -1371,6 +1374,44 @@ namespace SourceGit.Views
InvalidateVisual();
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
var range = DisplayRange;
if (range == null || range.End == 0)
return;
var total = 0;
if (DataContext is ViewModels.TwoSideTextDiff twoSideDiff)
{
var halfWidth = Bounds.Width * 0.5;
total = Math.Max(twoSideDiff.Old.Count, twoSideDiff.New.Count);
}
else if (DataContext is ViewModels.CombinedTextDiff combined)
{
var data = combined.Data;
total = data.Lines.Count;
}
else
{
return;
}
var height = Bounds.Height;
var startY = range.Start / (total * 1.0) * height;
var endY = range.End / (total * 1.0) * height;
var pressedY = e.GetPosition(this).Y;
if (pressedY >= startY && pressedY <= endY)
return;
var line = Math.Max(1, Math.Min(total, (int)Math.Ceiling(pressedY * total / height)));
if (this.Parent is Control parent)
parent.FindLogicalDescendantOfType<ThemedTextDiffPresenter>()?.ScrollToLine(line);
e.Handled = true;
}
private void RenderSingleSide(DrawingContext context, List<Models.TextDiffLine> lines, double x, double width)
{
var total = lines.Count;
@@ -1414,6 +1455,7 @@ namespace SourceGit.Views
get => GetValue(SelectedChunkProperty);
set => SetValue(SelectedChunkProperty, value);
}
public TextDiffView()
{
InitializeComponent();

View File

@@ -244,7 +244,7 @@
<v:CommitMessageTextBox Grid.Row="2" ShowAdvancedOptions="True" Text="{Binding CommitMessage, Mode=TwoWay}"/>
<!-- Commit Options -->
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto">
<Grid Grid.Row="3" Margin="0,6,0,0" ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto">
<CheckBox Grid.Column="0"
Height="24"
Margin="1,0,0,0"
@@ -256,6 +256,16 @@
ToolTip.VerticalOffset="0"/>
<CheckBox Grid.Column="1"
Height="24"
Margin="12,0,0,0"
HorizontalAlignment="Left"
IsChecked="{Binding NoVerifyOnCommit, Mode=TwoWay}"
Content="{DynamicResource Text.WorkingCopy.NoVerify}"
ToolTip.Tip="--no-verify"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="0"/>
<CheckBox Grid.Column="2"
Height="24"
Margin="12,0,0,0"
HorizontalAlignment="Left"
@@ -265,7 +275,7 @@
ToolTip.Placement="Top"
ToolTip.VerticalOffset="0"/>
<CheckBox Grid.Column="2"
<CheckBox Grid.Column="3"
Height="24"
Margin="12,0,0,0"
HorizontalAlignment="Left"
@@ -276,12 +286,12 @@
ToolTip.Placement="Top"
ToolTip.VerticalOffset="0"/>
<v:LoadingIcon Grid.Column="3"
<v:LoadingIcon Grid.Column="4"
Width="18" Height="18"
HorizontalAlignment="Right"
IsVisible="{Binding IsCommitting}"/>
<SplitButton Grid.Column="4"
<SplitButton Grid.Column="5"
Content="{DynamicResource Text.Repository.Continue}"
Height="28"
Margin="8,0,0,0"
@@ -303,7 +313,7 @@
</SplitButton.Flyout>
</SplitButton>
<Button Grid.Column="4"
<Button Grid.Column="5"
Classes="flat primary"
Content="{DynamicResource Text.WorkingCopy.Commit}"
Height="28"
@@ -336,7 +346,7 @@
</Button>
<!-- Invisible button just to add another hotkey `Ctrl+Shift+Enter` to commit with auto-stage -->
<Button Grid.Column="5"
<Button Grid.Column="6"
Width="0" Height="0"
Background="Transparent"
Click="OnCommitWithAutoStage"
@@ -349,7 +359,7 @@
</Button.IsEnabled>
</Button>
<Button Grid.Column="6"
<Button Grid.Column="7"
Classes="flat"
Content="{DynamicResource Text.WorkingCopy.CommitAndPush}"
Height="28"

View File

@@ -620,7 +620,7 @@ namespace SourceGit.Views
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
App.ShowWindow(new ViewModels.FileHistories(repo, change.Path));
App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path));
e.Handled = true;
};
@@ -1086,7 +1086,7 @@ namespace SourceGit.Views
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
App.ShowWindow(new ViewModels.FileHistories(repo, change.Path));
App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path));
e.Handled = true;
};

View File

@@ -82,7 +82,7 @@
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:Workspace">
<Grid ColumnDefinitions="Auto,*" Background="Transparent" DoubleTapped="OnItemDoubleTapped">
<Grid ColumnDefinitions="Auto,*" Background="Transparent" Tapped="OnItemTapped">
<Path Grid.Column="0"
Width="12" Height="12"
Fill="{Binding Brush}"

View File

@@ -21,7 +21,7 @@ namespace SourceGit.Views
}
}
private void OnItemDoubleTapped(object sender, TappedEventArgs e)
private void OnItemTapped(object sender, TappedEventArgs e)
{
if (DataContext is ViewModels.WorkspaceSwitcher switcher)
{
@@ -32,14 +32,28 @@ namespace SourceGit.Views
private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down && WorkspaceListBox.ItemCount > 0)
if (WorkspaceListBox.ItemCount == 0)
return;
if (e.Key == Key.Down)
{
WorkspaceListBox.Focus(NavigationMethod.Directional);
if (WorkspaceListBox.SelectedIndex < 0)
WorkspaceListBox.SelectedIndex = 0;
else if (WorkspaceListBox.SelectedIndex < WorkspaceListBox.ItemCount)
if (WorkspaceListBox.SelectedIndex < WorkspaceListBox.ItemCount - 1)
WorkspaceListBox.SelectedIndex++;
else
WorkspaceListBox.SelectedIndex = 0;
e.Handled = true;
}
else if (e.Key == Key.Up)
{
WorkspaceListBox.Focus(NavigationMethod.Directional);
if (WorkspaceListBox.SelectedIndex > 0)
WorkspaceListBox.SelectedIndex--;
else
WorkspaceListBox.SelectedIndex = WorkspaceListBox.ItemCount - 1;
e.Handled = true;
}