mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-20 19:55:00 +00:00
Merge branch 'release/v2025.15'
This commit is contained in:
commit
92f215d039
64 changed files with 1364 additions and 560 deletions
|
@ -39,6 +39,7 @@
|
|||
* Search commits
|
||||
* GitFlow
|
||||
* Git LFS
|
||||
* Bisect
|
||||
* Issue Link
|
||||
* Workspace
|
||||
* Custom Action
|
||||
|
|
103
TRANSLATION.md
103
TRANSLATION.md
|
@ -6,11 +6,18 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
### 
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in de_DE.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
|
@ -29,6 +36,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.Preferences.AI.Streaming
|
||||
- Text.Preferences.Appearance.EditorTabWidth
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.ViewLogs
|
||||
|
@ -43,27 +51,20 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in es_ES.axaml</summary>
|
||||
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in fr_FR.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
|
@ -73,6 +74,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
|
@ -86,11 +88,18 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in it_IT.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
|
@ -102,6 +111,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
|
@ -115,11 +125,18 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ja_JP.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
|
@ -129,6 +146,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.Tags.OrderByNameDes
|
||||
- Text.Repository.ViewLogs
|
||||
|
@ -144,7 +162,7 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in pt_BR.axaml</summary>
|
||||
|
@ -155,6 +173,13 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ApplyStash.DropAfterApply
|
||||
- Text.ApplyStash.RestoreIndex
|
||||
- Text.ApplyStash.Stash
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CustomAction
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.BranchUpstreamInvalid
|
||||
|
@ -201,6 +226,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.Preferences.General.DateFormat
|
||||
- Text.Preferences.General.ShowChildren
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Preferences.Git.SSLVerify
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.HistoriesLayout
|
||||
|
@ -238,28 +264,20 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ru_RU.axaml</summary>
|
||||
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ta_IN.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
|
@ -269,6 +287,7 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.UpdateSubmodules.Target
|
||||
- Text.ViewLogs
|
||||
|
@ -282,16 +301,24 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in uk_UA.axaml</summary>
|
||||
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.ConfigureWorkspace.Name
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2025.14
|
||||
2025.15
|
|
@ -37,10 +37,10 @@ namespace SourceGit
|
|||
}
|
||||
}
|
||||
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => OpenDialog(new Views.Preferences()));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => OpenDialog(new Views.Hotkeys()));
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => ShowWindow(new Views.Hotkeys(), false));
|
||||
public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => OpenDialog(new Views.About()));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => ShowWindow(new Views.About(), false));
|
||||
public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true));
|
||||
public static readonly Command QuitCommand = new Command(_ => Quit(0));
|
||||
public static readonly Command CopyTextBlockCommand = new Command(p =>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}" Gesture="F1"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}" IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||
|
|
|
@ -105,10 +105,36 @@ namespace SourceGit
|
|||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
public static void OpenDialog(Window window)
|
||||
public static void ShowWindow(object data, bool showAsDialog)
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
if (data is Views.ChromelessWindow window)
|
||||
{
|
||||
if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
else
|
||||
window.Show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var dataTypeName = data.GetType().FullName;
|
||||
if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
|
||||
var viewType = Type.GetType(viewTypeName);
|
||||
if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow)))
|
||||
return;
|
||||
|
||||
window = Activator.CreateInstance(viewType) as Views.ChromelessWindow;
|
||||
if (window != null)
|
||||
{
|
||||
window.DataContext = data;
|
||||
if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
else
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RaiseException(string context, string message)
|
||||
|
@ -342,6 +368,14 @@ namespace SourceGit
|
|||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
// Disable tooltip if window is not active.
|
||||
ToolTip.ToolTipOpeningEvent.AddClassHandler<Control>((c, e) =>
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(c);
|
||||
if (topLevel is not Window { IsActive: true })
|
||||
e.Cancel = true;
|
||||
});
|
||||
|
||||
if (TryLaunchAsCoreEditor(desktop))
|
||||
return;
|
||||
|
||||
|
@ -445,7 +479,7 @@ namespace SourceGit
|
|||
if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead))
|
||||
return true;
|
||||
|
||||
var done = File.ReadAllText(doneFile).Trim().Split([ '\r', '\n' ], StringSplitOptions.RemoveEmptyEntries);
|
||||
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length == 0)
|
||||
return true;
|
||||
|
||||
|
@ -598,11 +632,7 @@ namespace SourceGit
|
|||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
{
|
||||
var dialog = new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } };
|
||||
dialog.ShowDialog(owner);
|
||||
}
|
||||
ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
13
src/Commands/Bisect.cs
Normal file
13
src/Commands/Bisect.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Bisect : Command
|
||||
{
|
||||
public Bisect(string repo, string subcmd)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
RaiseError = false;
|
||||
Args = $"bisect {subcmd}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -28,7 +28,9 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
|
||||
if (ignoreWhitespace)
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-all-space --unified={unified} {opt}";
|
||||
else if (Models.DiffOption.IgnoreCRAtEOL)
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
else
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --unified={unified} {opt}";
|
||||
}
|
||||
|
@ -103,7 +105,7 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("-size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan().Slice(6));
|
||||
}
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -114,12 +116,12 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("+size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.New.Size = long.Parse(line.AsSpan().Slice(6));
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(" size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan().Slice(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -11,11 +11,12 @@ namespace SourceGit.Commands
|
|||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} R\d{0,6}\t(.*\t.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
|
||||
public QueryStagedChangesWithAmend(string repo)
|
||||
public QueryStagedChangesWithAmend(string repo, string parent)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "diff-index --cached -M HEAD^";
|
||||
Args = $"diff-index --cached -M {parent}";
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
|
@ -37,6 +38,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
|
@ -54,6 +56,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -88,5 +91,7 @@ namespace SourceGit.Commands
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
private string _parent = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
35
src/Models/Bisect.cs
Normal file
35
src/Models/Bisect.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum BisectState
|
||||
{
|
||||
None = 0,
|
||||
WaitingForRange,
|
||||
Detecting,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BisectCommitFlag
|
||||
{
|
||||
None = 0,
|
||||
Good = 1,
|
||||
Bad = 2,
|
||||
}
|
||||
|
||||
public class Bisect
|
||||
{
|
||||
public HashSet<string> Bads
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public HashSet<string> Goods
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
public string FileMode { get; set; } = "";
|
||||
public string ObjectHash { get; set; } = "";
|
||||
public string ParentSHA { get; set; } = "";
|
||||
}
|
||||
|
||||
public class Change
|
||||
|
|
|
@ -117,6 +117,6 @@ namespace SourceGit.Models
|
|||
public class CommitFullMessage
|
||||
{
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public List<Hyperlink> Links { get; set; } = [];
|
||||
public List<InlineElement> Inlines { get; set; } = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,49 @@
|
|||
namespace SourceGit.Models
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class CommitLink
|
||||
{
|
||||
public string Name { get; set; } = null;
|
||||
public string URLPrefix { get; set; } = null;
|
||||
|
||||
public CommitLink(string name, string prefix)
|
||||
{
|
||||
Name = name;
|
||||
URLPrefix = prefix;
|
||||
}
|
||||
|
||||
public static List<CommitLink> Get(List<Remote> remotes)
|
||||
{
|
||||
var outs = new List<CommitLink>();
|
||||
|
||||
foreach (var remote in remotes)
|
||||
{
|
||||
if (remote.TryGetVisitURL(out var url))
|
||||
{
|
||||
var trimmedUrl = url;
|
||||
if (url.EndsWith(".git"))
|
||||
trimmedUrl = url.Substring(0, url.Length - 4);
|
||||
|
||||
if (url.StartsWith("https://github.com/", StringComparison.Ordinal))
|
||||
outs.Add(new($"Github ({trimmedUrl.Substring(19)})", $"{url}/commit/"));
|
||||
else if (url.StartsWith("https://gitlab.", StringComparison.Ordinal))
|
||||
outs.Add(new($"GitLab ({trimmedUrl.Substring(trimmedUrl.Substring(15).IndexOf('/') + 16)})", $"{url}/-/commit/"));
|
||||
else if (url.StartsWith("https://gitee.com/", StringComparison.Ordinal))
|
||||
outs.Add(new($"Gitee ({trimmedUrl.Substring(18)})", $"{url}/commit/"));
|
||||
else if (url.StartsWith("https://bitbucket.org/", StringComparison.Ordinal))
|
||||
outs.Add(new($"BitBucket ({trimmedUrl.Substring(22)})", $"{url}/commits/"));
|
||||
else if (url.StartsWith("https://codeberg.org/", StringComparison.Ordinal))
|
||||
outs.Add(new($"Codeberg ({trimmedUrl.Substring(21)})", $"{url}/commit/"));
|
||||
else if (url.StartsWith("https://gitea.org/", StringComparison.Ordinal))
|
||||
outs.Add(new($"Gitea ({trimmedUrl.Substring(18)})", $"{url}/commit/"));
|
||||
else if (url.StartsWith("https://git.sr.ht/", StringComparison.Ordinal))
|
||||
outs.Add(new($"sourcehut ({trimmedUrl.Substring(18)})", $"{url}/commit/"));
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,25 +4,24 @@ namespace SourceGit.Models
|
|||
{
|
||||
public class ConventionalCommitType
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public static readonly List<ConventionalCommitType> Supported = new List<ConventionalCommitType>()
|
||||
{
|
||||
new ConventionalCommitType("Features", "feat", "Adding a new feature"),
|
||||
new ConventionalCommitType("Bug Fixes", "fix", "Fixing a bug"),
|
||||
new ConventionalCommitType("Work In Progress", "wip", "Still being developed and not yet complete"),
|
||||
new ConventionalCommitType("Reverts", "revert", "Undoing a previous commit"),
|
||||
new ConventionalCommitType("Code Refactoring", "refactor", "Restructuring code without changing its external behavior"),
|
||||
new ConventionalCommitType("Performance Improvements", "pref", "Improves performance"),
|
||||
new ConventionalCommitType("Builds", "build", "Changes that affect the build system or external dependencies"),
|
||||
new ConventionalCommitType("Continuous Integrations", "ci", "Changes to CI configuration files and scripts"),
|
||||
new ConventionalCommitType("Documentations", "docs", "Updating documentation"),
|
||||
new ConventionalCommitType("Styles", "style", "Elements or code styles without changing the code logic"),
|
||||
new ConventionalCommitType("Tests", "test", "Adding or updating tests"),
|
||||
new ConventionalCommitType("Chores", "chore", "Other changes that don't modify src or test files"),
|
||||
};
|
||||
public static readonly List<ConventionalCommitType> Supported = [
|
||||
new("Features", "feat", "Adding a new feature"),
|
||||
new("Bug Fixes", "fix", "Fixing a bug"),
|
||||
new("Work In Progress", "wip", "Still being developed and not yet complete"),
|
||||
new("Reverts", "revert", "Undoing a previous commit"),
|
||||
new("Code Refactoring", "refactor", "Restructuring code without changing its external behavior"),
|
||||
new("Performance Improvements", "perf", "Improves performance"),
|
||||
new("Builds", "build", "Changes that affect the build system or external dependencies"),
|
||||
new("Continuous Integrations", "ci", "Changes to CI configuration files and scripts"),
|
||||
new("Documentations", "docs", "Updating documentation"),
|
||||
new("Styles", "style", "Elements or code styles without changing the code logic"),
|
||||
new("Tests", "test", "Adding or updating tests"),
|
||||
new("Chores", "chore", "Other changes that don't modify src or test files"),
|
||||
];
|
||||
|
||||
public ConventionalCommitType(string name, string type, string description)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,15 @@ namespace SourceGit.Models
|
|||
{
|
||||
public class DiffOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Enable `--ignore-cr-at-eol` by default?
|
||||
/// </summary>
|
||||
public static bool IgnoreCRAtEOL
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = false;
|
||||
|
||||
public Change WorkingCopyChange => _workingCopyChange;
|
||||
public bool IsUnstaged => _isUnstaged;
|
||||
public List<string> Revisions => _revisions;
|
||||
|
@ -40,7 +49,7 @@ namespace SourceGit.Models
|
|||
else
|
||||
{
|
||||
if (change.DataForAmend != null)
|
||||
_extra = "--cached HEAD^";
|
||||
_extra = $"--cached {change.DataForAmend.ParentSHA}";
|
||||
else
|
||||
_extra = "--cached";
|
||||
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class Hyperlink
|
||||
public enum InlineElementType
|
||||
{
|
||||
None = 0,
|
||||
Keyword,
|
||||
Link,
|
||||
CommitSHA,
|
||||
Code,
|
||||
}
|
||||
|
||||
public class InlineElement
|
||||
{
|
||||
public InlineElementType Type { get; set; } = InlineElementType.None;
|
||||
public int Start { get; set; } = 0;
|
||||
public int Length { get; set; } = 0;
|
||||
public string Link { get; set; } = "";
|
||||
public bool IsCommitSHA { get; set; } = false;
|
||||
|
||||
public Hyperlink(int start, int length, string link, bool isCommitSHA = false)
|
||||
public InlineElement(InlineElementType type, int start, int length, string link)
|
||||
{
|
||||
Type = type;
|
||||
Start = start;
|
||||
Length = length;
|
||||
Link = link;
|
||||
IsCommitSHA = isCommitSHA;
|
||||
}
|
||||
|
||||
public bool Intersect(int start, int length)
|
|
@ -22,7 +22,7 @@ namespace SourceGit.Models
|
|||
_singletoneLock = File.Open(Path.Combine(Native.OS.DataDir, "process.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||
_isFirstInstance = true;
|
||||
_server = new NamedPipeServerStream(
|
||||
"SourceGitIPCChannel",
|
||||
"SourceGitIPCChannel" + Environment.UserName,
|
||||
PipeDirection.In,
|
||||
-1,
|
||||
PipeTransmissionMode.Byte,
|
||||
|
@ -40,7 +40,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
try
|
||||
{
|
||||
using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel", PipeDirection.Out))
|
||||
using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel" + Environment.UserName, PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly))
|
||||
{
|
||||
client.Connect(1000);
|
||||
if (!client.IsConnected)
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace SourceGit.Models
|
|||
set => SetProperty(ref _urlTemplate, value);
|
||||
}
|
||||
|
||||
public void Matches(List<Hyperlink> outs, string message)
|
||||
public void Matches(List<InlineElement> outs, string message)
|
||||
{
|
||||
if (_regex == null || string.IsNullOrEmpty(_urlTemplate))
|
||||
return;
|
||||
|
@ -81,8 +81,7 @@ namespace SourceGit.Models
|
|||
link = link.Replace($"${j}", group.Value);
|
||||
}
|
||||
|
||||
var range = new Hyperlink(start, len, link);
|
||||
outs.Add(range);
|
||||
outs.Add(new InlineElement(InlineElementType.Link, start, len, link));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -168,6 +168,7 @@ namespace SourceGit.Models
|
|||
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||
}
|
||||
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
||||
name.Equals("BISECT_START", StringComparison.Ordinal) ||
|
||||
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
|
||||
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
|
||||
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<StreamGeometry x:Key="Icons.Action">M41 512c0-128 46-241 138-333C271 87 384 41 512 41s241 46 333 138c92 92 138 205 138 333s-46 241-138 333c-92 92-205 138-333 138s-241-46-333-138C87 753 41 640 41 512zm87 0c0 108 36 195 113 271s164 113 271 113c108 0 195-36 271-113s113-164 113-271-36-195-113-271c-77-77-164-113-271-113-108 0-195 36-271 113C164 317 128 404 128 512zm256 148V292l195 113L768 512l-195 113-195 113v-77zm148-113-61 36V440l61 36 61 36-61 36z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.AIAssist">M304 464a128 128 0 01128-128c71 0 128 57 128 128v224a32 32 0 01-64 0V592h-128v95a32 32 0 01-64 0v-224zm64 1v64h128v-64a64 64 0 00-64-64c-35 0-64 29-64 64zM688 337c18 0 32 14 32 32v319a32 32 0 01-32 32c-18 0-32-14-32-32v-319a32 32 0 0132-32zM84 911l60-143A446 446 0 0164 512C64 265 265 64 512 64s448 201 448 448-201 448-448 448c-54 0-105-9-153-27l-242 22a32 32 0 01-32-44zm133-150-53 126 203-18 13 5c41 15 85 23 131 23 212 0 384-172 384-384S724 128 512 128 128 300 128 512c0 82 26 157 69 220l20 29z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Archive">M296 392h64v64h-64zM296 582v160h128V582h-64v-62h-64v62zm80 48v64h-32v-64h32zM360 328h64v64h-64zM296 264h64v64h-64zM360 456h64v64h-64zM360 200h64v64h-64zM855 289 639 73c-6-6-14-9-23-9H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V311c0-9-3-17-9-23zM790 326H602V138L790 326zm2 562H232V136h64v64h64v-64h174v216c0 23 19 42 42 42h216v494z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Bad">M851 755q0 23-16 39l-78 78q-16 16-39 16t-39-16l-168-168-168 168q-16 16-39 16t-39-16l-78-78q-16-16-16-39t16-39l168-168-168-168q-16-16-16-39t16-39l78-78q16-16 39-16t39 16l168 168 168-168q16-16 39-16t39 16l78 78q16 16 16 39t-16 39l-168 168 168 168q16 16 16 39z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Binary">M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Bisect">M128 384a43 43 0 0043-43V213a43 43 0 0143-43h128a43 43 0 000-85H213a128 128 0 00-128 128v128a43 43 0 0043 43zm213 469H213a43 43 0 01-43-43v-128a43 43 0 00-85 0v128a128 128 0 00128 128h128a43 43 0 000-85zm384-299a43 43 0 000-85h-49A171 171 0 00555 347V299a43 43 0 00-85 0v49A171 171 0 00347 469H299a43 43 0 000 85h49A171 171 0 00469 677V725a43 43 0 0085 0v-49A171 171 0 00677 555zm-213 43a85 85 0 1185-85 85 85 0 01-85 85zm384 43a43 43 0 00-43 43v128a43 43 0 01-43 43h-128a43 43 0 000 85h128a128 128 0 00128-128v-128a43 43 0 00-43-43zM811 85h-128a43 43 0 000 85h128a43 43 0 0143 43v128a43 43 0 0085 0V213a128 128 0 00-128-128z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Blame">M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Bookmark">M832 64H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V96c0-18-14-32-32-32zM736 596 624 502 506 596V131h230v318z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Bottom">M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z</StreamGeometry>
|
||||
|
|
|
@ -36,6 +36,13 @@
|
|||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">NO FILES ASSUMED AS UNCHANGED</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">REMOVE</x:String>
|
||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">BINARY FILE NOT SUPPORTED!!!</x:String>
|
||||
<x:String x:Key="Text.Bisect">Bisect</x:String>
|
||||
<x:String x:Key="Text.Bisect.Abort">Abort</x:String>
|
||||
<x:String x:Key="Text.Bisect.Bad">Bad</x:String>
|
||||
<x:String x:Key="Text.Bisect.Detecting">Bisecting. Is current HEAD good or bad?</x:String>
|
||||
<x:String x:Key="Text.Bisect.Good">Good</x:String>
|
||||
<x:String x:Key="Text.Bisect.Skip">Skip</x:String>
|
||||
<x:String x:Key="Text.Bisect.WaitingForRange">Bisecting. Mark current commit as good or bad and checkout another one.</x:String>
|
||||
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME ON THIS FILE IS NOT SUPPORTED!!!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout ${0}$...</x:String>
|
||||
|
@ -490,6 +497,7 @@
|
|||
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">User Email</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">Global git user email</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">Enable --prune on fetch</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.IgnoreCRAtEOLInDiff" xml:space="preserve">Enable --ignore-cr-at-eol in diff</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Invalid" xml:space="preserve">Git (>= 2.23.0) is required by this app</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">Install Path</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">Enable HTTP SSL Verify</x:String>
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">NO HAY ARCHIVOS ASUMIDOS COMO SIN CAMBIOS</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">REMOVER</x:String>
|
||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">¡ARCHIVO BINARIO NO SOPORTADO!</x:String>
|
||||
<x:String x:Key="Text.Bisect">Bisect</x:String>
|
||||
<x:String x:Key="Text.Bisect.Abort">Abortar</x:String>
|
||||
<x:String x:Key="Text.Bisect.Bad">Malo</x:String>
|
||||
<x:String x:Key="Text.Bisect.Detecting">Bisecting. ¿Es el HEAD actual bueno o malo?</x:String>
|
||||
<x:String x:Key="Text.Bisect.Good">Bueno</x:String>
|
||||
<x:String x:Key="Text.Bisect.Skip">Saltar</x:String>
|
||||
<x:String x:Key="Text.Bisect.WaitingForRange">Bisecting. Marcar el commit actual cómo bueno o malo y revisar otro.</x:String>
|
||||
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">¡BLAME EN ESTE ARCHIVO NO SOPORTADO!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout ${0}$...</x:String>
|
||||
|
@ -103,8 +110,11 @@
|
|||
<x:String x:Key="Text.CommitCM.CherryPickMultiple" xml:space="preserve">Cherry-Pick ...</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Comparar con HEAD</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Comparar con Worktree</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyAuthor" xml:space="preserve">Autor</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyCommitter" xml:space="preserve">Committer</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyInfo" xml:space="preserve">Información</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySubject" xml:space="preserve">Asunto</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CustomAction" xml:space="preserve">Acción personalizada</x:String>
|
||||
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Rebase Interactivo ${0}$ hasta Aquí</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Merge" xml:space="preserve">Merge a ${0}$</x:String>
|
||||
|
@ -491,6 +501,7 @@
|
|||
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">Email de usuario</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">Email global del usuario git</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">Habilitar --prune para fetch</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.IgnoreCRAtEOLInDiff" xml:space="preserve">Habilitar --ignore-cr-at-eol en diff</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Invalid" xml:space="preserve">Se requiere Git (>= 2.23.0) para esta aplicación</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">Ruta de instalación</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">Habilitar verificación HTTP SSL</x:String>
|
||||
|
@ -616,6 +627,7 @@
|
|||
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">Ordenar</x:String>
|
||||
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Abrir en Terminal</x:String>
|
||||
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">Usar tiempo relativo en las historias</x:String>
|
||||
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">Ver Logs</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">WORKTREES</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">AÑADIR WORKTREE</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">PRUNE</x:String>
|
||||
|
@ -701,6 +713,10 @@
|
|||
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Submódulo:</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Usar opción --remote</x:String>
|
||||
<x:String x:Key="Text.URL" xml:space="preserve">URL:</x:String>
|
||||
<x:String x:Key="Text.ViewLogs" xml:space="preserve">Logs</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Clear" xml:space="preserve">LIMPIAR TODO</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.CopyLog" xml:space="preserve">Copiar</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Delete" xml:space="preserve">Borrar</x:String>
|
||||
<x:String x:Key="Text.Warn" xml:space="preserve">Advertencia</x:String>
|
||||
<x:String x:Key="Text.Welcome" xml:space="preserve">Página de Bienvenida</x:String>
|
||||
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Crear Grupo</x:String>
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">Отслеживание ветки:</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">Отслеживание внешней ветки</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">Переключиться на:</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">создать новую ветку</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">ветку из списка</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Создать новую ветку</x:String>
|
||||
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Ветку из списка</x:String>
|
||||
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Помощник OpenAI</x:String>
|
||||
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">ПЕРЕСОЗДАТЬ</x:String>
|
||||
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Использовать OpenAI для создания сообщения о ревизии</x:String>
|
||||
|
@ -40,9 +40,16 @@
|
|||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">СПИСОК ПУСТ</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">УДАЛИТЬ</x:String>
|
||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">ДВОИЧНЫЙ ФАЙЛ НЕ ПОДДЕРЖИВАЕТСЯ!!!</x:String>
|
||||
<x:String x:Key="Text.Bisect">Раздвоить</x:String>
|
||||
<x:String x:Key="Text.Bisect.Abort">О</x:String>
|
||||
<x:String x:Key="Text.Bisect.Bad">Плохая</x:String>
|
||||
<x:String x:Key="Text.Bisect.Detecting">Раздвоение. Текущая ГОЛОВА (HEAD) хорошая или плохая?</x:String>
|
||||
<x:String x:Key="Text.Bisect.Good">Хорошая</x:String>
|
||||
<x:String x:Key="Text.Bisect.Skip">Пропустить</x:String>
|
||||
<x:String x:Key="Text.Bisect.WaitingForRange">Раздвоение. Сделать текущую ревизию хорошей или плохой и переключиться на другой.</x:String>
|
||||
<x:String x:Key="Text.Blame" xml:space="preserve">Расследование</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">РАССЛЕДОВАНИЕ В ЭТОМ ФАЙЛЕ НЕ ПОДДЕРЖИВАЕТСЯ!!!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Проверить ${0}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Переключиться на ${0}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Сравнить с ГОЛОВОЙ (HEAD)</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Сравнить с рабочим каталогом</x:String>
|
||||
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Копировать имя ветки</x:String>
|
||||
|
@ -55,8 +62,8 @@
|
|||
<x:String x:Key="Text.BranchCM.Finish" xml:space="preserve">Поток Git - Завершение ${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Merge" xml:space="preserve">Влить ${0}$ в ${1}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.MergeMultiBranches" xml:space="preserve">Влить {0} выделенных веток в текущую</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Pull" xml:space="preserve">Забрать ${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.PullInto" xml:space="preserve">Забрать ${0}$ в ${1}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Pull" xml:space="preserve">Загрузить ${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.PullInto" xml:space="preserve">Загрузить ${0}$ в ${1}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Push" xml:space="preserve">Выложить ${0}$</x:String>
|
||||
<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>
|
||||
|
@ -103,8 +110,11 @@
|
|||
<x:String x:Key="Text.CommitCM.CherryPickMultiple" xml:space="preserve">Применить несколько ревизий ...</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Сравнить c ГОЛОВОЙ (HEAD)</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Сравнить с рабочим каталогом</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyAuthor" xml:space="preserve">Автор</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyCommitter" xml:space="preserve">Ревизор</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopyInfo" xml:space="preserve">Информацию</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">SHA</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySubject" xml:space="preserve">Субъект</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CustomAction" xml:space="preserve">Пользовательское действие</x:String>
|
||||
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Интерактивное перемещение (rebase -i) ${0}$ сюда</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Merge" xml:space="preserve">Влить в ${0}$</x:String>
|
||||
|
@ -136,6 +146,7 @@
|
|||
<x:String x:Key="Text.CommitDetail.Info.SHA" xml:space="preserve">SHA</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.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>
|
||||
<x:String x:Key="Text.Configure.CommitMessageTemplate" xml:space="preserve">ШАБЛОН РЕВИЗИИ</x:String>
|
||||
|
@ -199,7 +210,7 @@
|
|||
<x:String x:Key="Text.CopyPath" xml:space="preserve">Копировать путь</x:String>
|
||||
<x:String x:Key="Text.CreateBranch" xml:space="preserve">Создать ветку...</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.BasedOn" xml:space="preserve">Основан на:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Проверить созданную ветку</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Переключиться на созданную ветку</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Локальные изменения:</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Отклонить</x:String>
|
||||
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Отложить и применить повторно</x:String>
|
||||
|
@ -348,9 +359,9 @@
|
|||
<x:String x:Key="Text.GitLFS.Locks.UnlockForce" xml:space="preserve">Принудительно разблокировать</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Prune" xml:space="preserve">Обрезать</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Prune.Tips" xml:space="preserve">Запустить (git lfs prune), чтобы удалить старые файлы LFS из локального хранилища</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Pull" xml:space="preserve">Забрать</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Pull" xml:space="preserve">Загрузить</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Pull.Tips" xml:space="preserve">Запустить (git lfs pull), чтобы загрузить все файлы LFS Git для текущей ссылки и проверить</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Pull.Title" xml:space="preserve">Забрать объекты LFS</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Pull.Title" xml:space="preserve">Загрузить объекты LFS</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Push" xml:space="preserve">Выложить</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Push.Tips" xml:space="preserve">Отправляйте большие файлы, помещенные в очередь, в конечную точку LFS Git</x:String>
|
||||
<x:String x:Key="Text.GitLFS.Push.Title" xml:space="preserve">Выложить объекты LFS</x:String>
|
||||
|
@ -385,7 +396,7 @@
|
|||
<x:String x:Key="Text.Hotkeys.Repo.Fetch" xml:space="preserve">Извлечение, запускается сразу</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.GoHome" xml:space="preserve">Режим доски (по умолчанию)</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Режим поиска ревизий</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.Pull" xml:space="preserve">Забрать, запускается сразу</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.Pull" xml:space="preserve">Загрузить, запускается сразу</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.Push" xml:space="preserve">Выложить, запускается сразу</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">Принудительно перезагрузить репозиторий</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Подготовленные/Неподготовленные выбранные изменения</x:String>
|
||||
|
@ -490,6 +501,7 @@
|
|||
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">Электроная почта пользователя</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">Общая электроная почта пользователя git</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">Разрешить (--prune) при скачивании</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.IgnoreCRAtEOLInDiff" xml:space="preserve">Разрешить (--ignore-cr-at-eol) в различии</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Invalid" xml:space="preserve">Для работы программы требуется версия Git (>= 2.23.0)</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">Путь установки</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">Разрешить верификацию HTTP SSL</x:String>
|
||||
|
@ -512,16 +524,16 @@
|
|||
<x:String x:Key="Text.PruneRemote.Target" xml:space="preserve">Цель:</x:String>
|
||||
<x:String x:Key="Text.PruneWorktrees" xml:space="preserve">Удалить рабочий каталог</x:String>
|
||||
<x:String x:Key="Text.PruneWorktrees.Tip" xml:space="preserve">Информация об обрезке рабочего каталога в «$GIT_COMMON_DIR/worktrees»</x:String>
|
||||
<x:String x:Key="Text.Pull" xml:space="preserve">Забрать</x:String>
|
||||
<x:String x:Key="Text.Pull" xml:space="preserve">Загрузить</x:String>
|
||||
<x:String x:Key="Text.Pull.Branch" xml:space="preserve">Ветка внешнего репозитория:</x:String>
|
||||
<x:String x:Key="Text.Pull.FetchAllBranches" xml:space="preserve">Извлечь все ветки</x:String>
|
||||
<x:String x:Key="Text.Pull.Into" xml:space="preserve">В:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Локальные изменения:</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Отклонить</x:String>
|
||||
<x:String x:Key="Text.Pull.LocalChanges.StashAndReply" xml:space="preserve">Отложить и применить повторно</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Забрать без меток</x:String>
|
||||
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Загрузить без меток</x:String>
|
||||
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Внешний репозиторий:</x:String>
|
||||
<x:String x:Key="Text.Pull.Title" xml:space="preserve">Забрать (Получить и слить)</x:String>
|
||||
<x:String x:Key="Text.Pull.Title" xml:space="preserve">Загрузить (Получить и слить)</x:String>
|
||||
<x:String x:Key="Text.Pull.UseRebase" xml:space="preserve">Использовать перемещение вместо слияния</x:String>
|
||||
<x:String x:Key="Text.Push" xml:space="preserve">Выложить</x:String>
|
||||
<x:String x:Key="Text.Push.CheckSubmodules" xml:space="preserve">Убедитесь, что подмодули были вставлены</x:String>
|
||||
|
@ -615,6 +627,7 @@
|
|||
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">Сортировать</x:String>
|
||||
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Открыть в терминале</x:String>
|
||||
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">Использовать относительное время в историях</x:String>
|
||||
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">Просмотр логов</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">РАБОЧИЕ КАТАЛОГИ</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">ДОБАВИТЬ РАБОЧИЙ КАТАЛОГ</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">ОБРЕЗАТЬ</x:String>
|
||||
|
@ -700,6 +713,10 @@
|
|||
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Подмодуль:</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Использовать опцию (--remote)</x:String>
|
||||
<x:String x:Key="Text.URL" xml:space="preserve">Сетевой адрес:</x:String>
|
||||
<x:String x:Key="Text.ViewLogs" xml:space="preserve">Логи</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Clear" xml:space="preserve">ОЧИСТИТЬ ВСЁ</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.CopyLog" xml:space="preserve">Копировать</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Delete" xml:space="preserve">Удалить</x:String>
|
||||
<x:String x:Key="Text.Warn" xml:space="preserve">Предупреждение</x:String>
|
||||
<x:String x:Key="Text.Welcome" xml:space="preserve">Приветствие</x:String>
|
||||
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Создать группу</x:String>
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">没有不跟踪更改的文件</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">移除</x:String>
|
||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">二进制文件不支持该操作!!!</x:String>
|
||||
<x:String x:Key="Text.Bisect">二分定位(bisect)</x:String>
|
||||
<x:String x:Key="Text.Bisect.Abort">终止</x:String>
|
||||
<x:String x:Key="Text.Bisect.Bad">标记错误</x:String>
|
||||
<x:String x:Key="Text.Bisect.Detecting">二分定位进行中。当前提交是 '正确' 还是 '错误' ?</x:String>
|
||||
<x:String x:Key="Text.Bisect.Good">标记正确</x:String>
|
||||
<x:String x:Key="Text.Bisect.Skip">无法判定</x:String>
|
||||
<x:String x:Key="Text.Bisect.WaitingForRange">二分定位进行中。请标记当前的提交是 '正确' 还是 '错误',然后检出另一个提交。</x:String>
|
||||
<x:String x:Key="Text.Blame" xml:space="preserve">逐行追溯(blame)</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">选中文件不支持该操作!!!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">检出(checkout) ${0}$...</x:String>
|
||||
|
@ -494,6 +501,7 @@
|
|||
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">邮箱</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">默认GIT用户邮箱</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">拉取更新时启用修剪(--prune)</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.IgnoreCRAtEOLInDiff" xml:space="preserve">对比文件时,默认忽略换行符变更 (--ignore-cr-at-eol)</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Invalid" xml:space="preserve">本软件要求GIT最低版本为2.23.0</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">安装路径</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">启用HTTP SSL验证</x:String>
|
||||
|
|
|
@ -40,6 +40,13 @@
|
|||
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">沒有不追蹤變更的檔案</x:String>
|
||||
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">移除</x:String>
|
||||
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">二進位檔案不支援該操作!</x:String>
|
||||
<x:String x:Key="Text.Bisect">二分搜尋 (bisect)</x:String>
|
||||
<x:String x:Key="Text.Bisect.Abort">中止</x:String>
|
||||
<x:String x:Key="Text.Bisect.Bad">標記為錯誤</x:String>
|
||||
<x:String x:Key="Text.Bisect.Detecting">二分搜尋進行中。目前的提交是「良好」是「錯誤」?</x:String>
|
||||
<x:String x:Key="Text.Bisect.Good">標記為良好</x:String>
|
||||
<x:String x:Key="Text.Bisect.Skip">無法確認</x:String>
|
||||
<x:String x:Key="Text.Bisect.WaitingForRange">二分搜尋進行中。請標記目前的提交為「良好」或「錯誤」,然後簽出另一個提交。</x:String>
|
||||
<x:String x:Key="Text.Blame" xml:space="preserve">逐行溯源 (blame)</x:String>
|
||||
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">所選擇的檔案不支援該操作!</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">簽出 (checkout) ${0}$...</x:String>
|
||||
|
@ -62,7 +69,7 @@
|
|||
<x:String x:Key="Text.BranchCM.Rename" 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.BranchUpstreamInvalid" xml:space="preserve">追蹤上游分支不存在或已刪除!</x:String>
|
||||
<x:String x:Key="Text.BranchUpstreamInvalid" xml:space="preserve">追蹤上游分支不存在或已刪除!</x:String>
|
||||
<x:String x:Key="Text.Bytes" 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>
|
||||
|
@ -161,7 +168,7 @@
|
|||
<x:String x:Key="Text.Configure.Git.AutoFetch" xml:space="preserve">啟用定時自動提取 (fetch) 遠端更新</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.AutoFetchIntervalSuffix" xml:space="preserve">分鐘</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.DefaultRemote" xml:space="preserve">預設遠端存放庫</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.PreferredMergeMode" xml:space="preserve">首選合併模式</x:String>
|
||||
<x:String x:Key="Text.Configure.Git.PreferredMergeMode" xml:space="preserve">預設合併模式</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">Issue 追蹤</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker.AddSampleAzure" xml:space="preserve">新增符合 Azure DevOps 規則</x:String>
|
||||
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGiteeIssue" xml:space="preserve">新增符合 Gitee 議題規則</x:String>
|
||||
|
@ -186,10 +193,10 @@
|
|||
<x:String x:Key="Text.ConfigureWorkspace.Color" xml:space="preserve">顏色</x:String>
|
||||
<x:String x:Key="Text.ConfigureWorkspace.Name" xml:space="preserve">名稱</x:String>
|
||||
<x:String x:Key="Text.ConfigureWorkspace.Restore" xml:space="preserve">啟動時還原上次開啟的存放庫</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.Continue" xml:space="preserve">确认继续</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.Continue" xml:space="preserve">確認繼續</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.NoLocalChanges" xml:space="preserve">未包含任何檔案變更! 您是否仍要提交 (--allow-empty)?</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.StageAllThenCommit" xml:space="preserve">自动暂存并提交</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.WithLocalChanges" xml:space="preserve">未包含任何檔案變更! 您是否仍要提交 (--allow-empty)或者自動暫存全部變更並提交?</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.StageAllThenCommit" xml:space="preserve">自動暫存並提交</x:String>
|
||||
<x:String x:Key="Text.ConfirmEmptyCommit.WithLocalChanges" xml:space="preserve">未包含任何檔案變更! 您是否仍要提交 (--allow-empty) 或者自動暫存全部變更並提交?</x:String>
|
||||
<x:String x:Key="Text.ConventionalCommit" xml:space="preserve">產生約定式提交訊息</x:String>
|
||||
<x:String x:Key="Text.ConventionalCommit.BreakingChanges" xml:space="preserve">破壞性變更:</x:String>
|
||||
<x:String x:Key="Text.ConventionalCommit.ClosedIssue" xml:space="preserve">關閉的 Issue:</x:String>
|
||||
|
@ -250,7 +257,7 @@
|
|||
<x:String x:Key="Text.Diff.Copy" xml:space="preserve">複製</x:String>
|
||||
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">檔案權限已變更</x:String>
|
||||
<x:String x:Key="Text.Diff.First" xml:space="preserve">第一個差異</x:String>
|
||||
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">忽略空白符號變化和EOL</x:String>
|
||||
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">忽略空白符號變化和 EOL</x:String>
|
||||
<x:String x:Key="Text.Diff.Last" xml:space="preserve">最後一個差異</x:String>
|
||||
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS 物件變更</x:String>
|
||||
<x:String x:Key="Text.Diff.Next" xml:space="preserve">下一個差異</x:String>
|
||||
|
@ -306,8 +313,8 @@
|
|||
<x:String x:Key="Text.FileCM.Unstage" xml:space="preserve">取消暫存</x:String>
|
||||
<x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">從暫存中移除 {0} 個檔案</x:String>
|
||||
<x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">取消暫存選取的變更</x:String>
|
||||
<x:String x:Key="Text.FileCM.UseMine" xml:space="preserve">使用我方版本 (checkout --ours)</x:String>
|
||||
<x:String x:Key="Text.FileCM.UseTheirs" xml:space="preserve">使用對方版本 (checkout --theirs)</x:String>
|
||||
<x:String x:Key="Text.FileCM.UseMine" xml:space="preserve">使用我方版本 (ours)</x:String>
|
||||
<x:String x:Key="Text.FileCM.UseTheirs" xml:space="preserve">使用對方版本 (theirs)</x:String>
|
||||
<x:String x:Key="Text.FileHistory" xml:space="preserve">檔案歷史</x:String>
|
||||
<x:String x:Key="Text.FileHistory.FileChange" xml:space="preserve">檔案變更</x:String>
|
||||
<x:String x:Key="Text.FileHistory.FileContent" xml:space="preserve">檔案内容</x:String>
|
||||
|
@ -465,7 +472,7 @@
|
|||
<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>
|
||||
<x:String x:Key="Text.Preferences.Appearance.DefaultFont" xml:space="preserve">預設字型</x:String>
|
||||
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">編輯器制表符寬度</x:String>
|
||||
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">編輯器 Tab 寬度</x:String>
|
||||
<x:String x:Key="Text.Preferences.Appearance.FontSize" xml:space="preserve">字型大小</x:String>
|
||||
<x:String x:Key="Text.Preferences.Appearance.FontSize.Default" xml:space="preserve">預設</x:String>
|
||||
<x:String x:Key="Text.Preferences.Appearance.FontSize.Editor" xml:space="preserve">程式碼</x:String>
|
||||
|
@ -494,6 +501,7 @@
|
|||
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">電子郵件</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">預設 Git 使用者電子郵件</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">拉取變更時進行清理 (--prune)</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.IgnoreCRAtEOLInDiff" xml:space="preserve">對比檔案時,預設忽略行尾的 CR 變更 (--ignore-cr-at-eol)</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Invalid" xml:space="preserve">本軟體要求 Git 最低版本為 2.23.0</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">安裝路徑</x:String>
|
||||
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">啟用 HTTP SSL 驗證</x:String>
|
||||
|
@ -619,7 +627,7 @@
|
|||
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">排序</x:String>
|
||||
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">在終端機中開啟</x:String>
|
||||
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">在提交列表中使用相對時間</x:String>
|
||||
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">檢視 GIT 指令的日誌</x:String>
|
||||
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">檢視 Git 指令記錄</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">工作區列表</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">新增工作區</x:String>
|
||||
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">清理</x:String>
|
||||
|
@ -705,8 +713,8 @@
|
|||
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">子模組:</x:String>
|
||||
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">啟用 [--remote] 選項</x:String>
|
||||
<x:String x:Key="Text.URL" xml:space="preserve">存放庫網址:</x:String>
|
||||
<x:String x:Key="Text.ViewLogs" xml:space="preserve">日誌清單</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Clear" xml:space="preserve">清除所有日誌</x:String>
|
||||
<x:String x:Key="Text.ViewLogs" xml:space="preserve">記錄</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Clear" xml:space="preserve">清除所有記錄</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.CopyLog" xml:space="preserve">複製</x:String>
|
||||
<x:String x:Key="Text.ViewLogs.Delete" xml:space="preserve">刪除</x:String>
|
||||
<x:String x:Key="Text.Warn" xml:space="preserve">警告</x:String>
|
||||
|
@ -738,13 +746,13 @@
|
|||
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">觸發點擊事件</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitToEdit" xml:space="preserve">提交 (修改原始提交)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.CommitWithAutoStage" xml:space="preserve">自動暫存全部變更並提交</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter" xml:space="preserve">您已暫存 {0} 檔案,但只顯示 {1} 檔案 ({2} 檔案被篩選器隱藏)。您要繼續嗎?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">檢測到衝突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.ConfirmCommitWithFilter" xml:space="preserve">您已暫存 {0} 個檔案,但只顯示 {1} 個檔案 ({2} 個檔案被篩選器隱藏)。您確定要繼續提交嗎?</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">偵測到衝突</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeTool" xml:space="preserve">使用外部合併工具開啟</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts" xml:space="preserve">使用外部合併工具開啟</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">檔案衝突已解決</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseMine" xml:space="preserve">使用 MINE</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseTheirs" xml:space="preserve">使用 THEIRS</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseMine" xml:space="preserve">使用我方版本 (ours)</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.Conflicts.UseTheirs" xml:space="preserve">使用對方版本 (theirs)</x:String>
|
||||
<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>
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
<Color x:Key="Color.Diff.AddedHighlight">#A7E1A7</Color>
|
||||
<Color x:Key="Color.Diff.DeletedHighlight">#F19B9D</Color>
|
||||
<Color x:Key="Color.Link">#0000EE</Color>
|
||||
<Color x:Key="Color.InlineCode">#FFE5E5E5</Color>
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
|
@ -51,6 +52,7 @@
|
|||
<Color x:Key="Color.Diff.AddedHighlight">#A0308D3C</Color>
|
||||
<Color x:Key="Color.Diff.DeletedHighlight">#A09F4247</Color>
|
||||
<Color x:Key="Color.Link">#4DAAFC</Color>
|
||||
<Color x:Key="Color.InlineCode">#FF2E2E2E</Color>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
|
@ -79,6 +81,7 @@
|
|||
<SolidColorBrush x:Key="Brush.Diff.AddedHighlight" Color="{DynamicResource Color.Diff.AddedHighlight}"/>
|
||||
<SolidColorBrush x:Key="Brush.Diff.DeletedHighlight" Color="{DynamicResource Color.Diff.DeletedHighlight}"/>
|
||||
<SolidColorBrush x:Key="Brush.Link" Color="{DynamicResource Color.Link}"/>
|
||||
<SolidColorBrush x:Key="Brush.InlineCode" Color="{DynamicResource Color.InlineCode}"/>
|
||||
|
||||
<FontFamily x:Key="Fonts.Default">fonts:Inter#Inter</FontFamily>
|
||||
<FontFamily x:Key="Fonts.Monospace">fonts:SourceGit#JetBrains Mono</FontFamily>
|
||||
|
|
80
src/ViewModels/AIAssistant.cs
Normal file
80
src/ViewModels/AIAssistant.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class AIAssistant : ObservableObject
|
||||
{
|
||||
public bool IsGenerating
|
||||
{
|
||||
get => _isGenerating;
|
||||
private set => SetProperty(ref _isGenerating, value);
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => _text;
|
||||
private set => SetProperty(ref _text, value);
|
||||
}
|
||||
|
||||
public AIAssistant(Repository repo, Models.OpenAIService service, List<Models.Change> changes, Action<string> onApply)
|
||||
{
|
||||
_repo = repo;
|
||||
_service = service;
|
||||
_changes = changes;
|
||||
_onApply = onApply;
|
||||
_cancel = new CancellationTokenSource();
|
||||
|
||||
Gen();
|
||||
}
|
||||
|
||||
public void Regen()
|
||||
{
|
||||
if (_cancel is { IsCancellationRequested: false })
|
||||
_cancel.Cancel();
|
||||
|
||||
Gen();
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
_onApply?.Invoke(Text);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cancel?.Cancel();
|
||||
}
|
||||
|
||||
private void Gen()
|
||||
{
|
||||
Text = string.Empty;
|
||||
IsGenerating = true;
|
||||
|
||||
_cancel = new CancellationTokenSource();
|
||||
Task.Run(() =>
|
||||
{
|
||||
new Commands.GenerateCommitMessage(_service, _repo.FullPath, _changes, _cancel.Token, message =>
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => Text = message);
|
||||
}).Exec();
|
||||
|
||||
Dispatcher.UIThread.Invoke(() => IsGenerating = false);
|
||||
}, _cancel.Token);
|
||||
}
|
||||
|
||||
private readonly Repository _repo = null;
|
||||
private Models.OpenAIService _service = null;
|
||||
private List<Models.Change> _changes = null;
|
||||
private Action<string> _onApply = null;
|
||||
private CancellationTokenSource _cancel = null;
|
||||
private bool _isGenerating = false;
|
||||
private string _text = string.Empty;
|
||||
}
|
||||
}
|
|
@ -134,31 +134,7 @@ namespace SourceGit.ViewModels
|
|||
public CommitDetail(Repository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
|
||||
foreach (var remote in repo.Remotes)
|
||||
{
|
||||
if (remote.TryGetVisitURL(out var url))
|
||||
{
|
||||
var trimmedUrl = url;
|
||||
if (url.EndsWith(".git"))
|
||||
trimmedUrl = url.Substring(0, url.Length - 4);
|
||||
|
||||
if (url.StartsWith("https://github.com/", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"Github ({trimmedUrl.Substring(19)})", URLPrefix = $"{url}/commit/" });
|
||||
else if (url.StartsWith("https://gitlab.", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"GitLab ({trimmedUrl.Substring(trimmedUrl.Substring(15).IndexOf('/') + 16)})", URLPrefix = $"{url}/-/commit/" });
|
||||
else if (url.StartsWith("https://gitee.com/", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"Gitee ({trimmedUrl.Substring(18)})", URLPrefix = $"{url}/commit/" });
|
||||
else if (url.StartsWith("https://bitbucket.org/", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"BitBucket ({trimmedUrl.Substring(22)})", URLPrefix = $"{url}/commits/" });
|
||||
else if (url.StartsWith("https://codeberg.org/", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"Codeberg ({trimmedUrl.Substring(21)})", URLPrefix = $"{url}/commit/" });
|
||||
else if (url.StartsWith("https://gitea.org/", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"Gitea ({trimmedUrl.Substring(18)})", URLPrefix = $"{url}/commit/" });
|
||||
else if (url.StartsWith("https://git.sr.ht/", StringComparison.Ordinal))
|
||||
WebLinks.Add(new Models.CommitLink() { Name = $"sourcehut ({trimmedUrl.Substring(18)})", URLPrefix = $"{url}/commit/" });
|
||||
}
|
||||
}
|
||||
WebLinks = Models.CommitLink.Get(repo.Remotes);
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
|
@ -173,7 +149,6 @@ namespace SourceGit.ViewModels
|
|||
_diffContext = null;
|
||||
_viewRevisionFileContent = null;
|
||||
_cancellationSource = null;
|
||||
WebLinks.Clear();
|
||||
_revisionFiles = null;
|
||||
_revisionFileSearchSuggestion = null;
|
||||
}
|
||||
|
@ -332,8 +307,7 @@ namespace SourceGit.ViewModels
|
|||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _commit.SHA) };
|
||||
window.Show();
|
||||
App.ShowWindow(new FileHistories(_repo, change.Path, _commit.SHA), false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -343,8 +317,7 @@ namespace SourceGit.ViewModels
|
|||
blame.IsEnabled = change.Index != Models.ChangeState.Deleted;
|
||||
blame.Click += (_, ev) =>
|
||||
{
|
||||
var window = new Views.Blame() { DataContext = new Blame(_repo.FullPath, change.Path, _commit.SHA) };
|
||||
window.Show();
|
||||
App.ShowWindow(new Blame(_repo.FullPath, change.Path, _commit.SHA), false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -508,8 +481,7 @@ namespace SourceGit.ViewModels
|
|||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, ev) =>
|
||||
{
|
||||
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _commit.SHA) };
|
||||
window.Show();
|
||||
App.ShowWindow(new FileHistories(_repo, file.Path, _commit.SHA), false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -519,8 +491,7 @@ namespace SourceGit.ViewModels
|
|||
blame.IsEnabled = file.Type == Models.ObjectType.Blob;
|
||||
blame.Click += (_, ev) =>
|
||||
{
|
||||
var window = new Views.Blame() { DataContext = new Blame(_repo.FullPath, file.Path, _commit.SHA) };
|
||||
window.Show();
|
||||
App.ShowWindow(new Blame(_repo.FullPath, file.Path, _commit.SHA), false);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -607,10 +578,10 @@ namespace SourceGit.ViewModels
|
|||
Task.Run(() =>
|
||||
{
|
||||
var message = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result();
|
||||
var links = ParseLinksInMessage(message);
|
||||
var inlines = ParseInlinesInMessage(message);
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Links = links });
|
||||
Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Inlines = inlines });
|
||||
});
|
||||
|
||||
Task.Run(() =>
|
||||
|
@ -662,13 +633,13 @@ namespace SourceGit.ViewModels
|
|||
});
|
||||
}
|
||||
|
||||
private List<Models.Hyperlink> ParseLinksInMessage(string message)
|
||||
private List<Models.InlineElement> ParseInlinesInMessage(string message)
|
||||
{
|
||||
var links = new List<Models.Hyperlink>();
|
||||
var inlines = new List<Models.InlineElement>();
|
||||
if (_repo.Settings.IssueTrackerRules is { Count: > 0 } rules)
|
||||
{
|
||||
foreach (var rule in rules)
|
||||
rule.Matches(links, message);
|
||||
rule.Matches(inlines, message);
|
||||
}
|
||||
|
||||
var matches = REG_SHA_FORMAT().Matches(message);
|
||||
|
@ -681,7 +652,7 @@ namespace SourceGit.ViewModels
|
|||
var start = match.Index;
|
||||
var len = match.Length;
|
||||
var intersect = false;
|
||||
foreach (var link in links)
|
||||
foreach (var link in inlines)
|
||||
{
|
||||
if (link.Intersect(start, len))
|
||||
{
|
||||
|
@ -696,13 +667,13 @@ namespace SourceGit.ViewModels
|
|||
var sha = match.Groups[1].Value;
|
||||
var isCommitSHA = new Commands.IsCommitSHA(_repo.FullPath, sha).Result();
|
||||
if (isCommitSHA)
|
||||
links.Add(new Models.Hyperlink(start, len, sha, true));
|
||||
inlines.Add(new Models.InlineElement(Models.InlineElementType.CommitSHA, start, len, sha));
|
||||
}
|
||||
|
||||
if (links.Count > 0)
|
||||
links.Sort((l, r) => l.Start - r.Start);
|
||||
if (inlines.Count > 0)
|
||||
inlines.Sort((l, r) => l.Start - r.Start);
|
||||
|
||||
return links;
|
||||
return inlines;
|
||||
}
|
||||
|
||||
private void RefreshVisibleChanges()
|
||||
|
|
|
@ -305,11 +305,23 @@ namespace SourceGit.ViewModels
|
|||
_repo.NavigateToCommit(commit.SHA);
|
||||
}
|
||||
|
||||
public string GetCommitFullMessage(Models.Commit commit)
|
||||
{
|
||||
var sha = commit.SHA;
|
||||
if (_fullCommitMessages.TryGetValue(sha, out var msg))
|
||||
return msg;
|
||||
|
||||
msg = new Commands.QueryCommitFullMessage(_repo.FullPath, sha).Result();
|
||||
_fullCommitMessages[sha] = msg;
|
||||
return msg;
|
||||
}
|
||||
|
||||
private readonly Repository _repo = null;
|
||||
private readonly string _file = null;
|
||||
private bool _isLoading = true;
|
||||
private bool _prevIsDiffMode = true;
|
||||
private List<Models.Commit> _commits = null;
|
||||
private Dictionary<string, string> _fullCommitMessages = new Dictionary<string, string>();
|
||||
private object _viewContent = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -62,6 +63,12 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _detailContext, value);
|
||||
}
|
||||
|
||||
public Models.Bisect Bisect
|
||||
{
|
||||
get => _bisect;
|
||||
private set => SetProperty(ref _bisect, value);
|
||||
}
|
||||
|
||||
public GridLength LeftArea
|
||||
{
|
||||
get => _leftArea;
|
||||
|
@ -111,6 +118,37 @@ namespace SourceGit.ViewModels
|
|||
_detailContext = null;
|
||||
}
|
||||
|
||||
public Models.BisectState UpdateBisectInfo()
|
||||
{
|
||||
var test = Path.Combine(_repo.GitDir, "BISECT_START");
|
||||
if (!File.Exists(test))
|
||||
{
|
||||
Bisect = null;
|
||||
return Models.BisectState.None;
|
||||
}
|
||||
|
||||
var info = new Models.Bisect();
|
||||
var dir = Path.Combine(_repo.GitDir, "refs", "bisect");
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
var files = new DirectoryInfo(dir).GetFiles();
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (file.Name.StartsWith("bad"))
|
||||
info.Bads.Add(File.ReadAllText(file.FullName).Trim());
|
||||
else if (file.Name.StartsWith("good"))
|
||||
info.Goods.Add(File.ReadAllText(file.FullName).Trim());
|
||||
}
|
||||
}
|
||||
|
||||
Bisect = info;
|
||||
|
||||
if (info.Bads.Count == 0 || info.Goods.Count == 0)
|
||||
return Models.BisectState.WaitingForRange;
|
||||
else
|
||||
return Models.BisectState.Detecting;
|
||||
}
|
||||
|
||||
public void NavigateTo(string commitSHA)
|
||||
{
|
||||
var commit = _commits.Find(x => x.SHA.StartsWith(commitSHA, StringComparison.Ordinal));
|
||||
|
@ -304,7 +342,7 @@ namespace SourceGit.ViewModels
|
|||
if (picker.Count == 1)
|
||||
{
|
||||
log = _repo.CreateLog("Save as Patch");
|
||||
|
||||
|
||||
var succ = false;
|
||||
for (var i = 0; i < selected.Count; i++)
|
||||
{
|
||||
|
@ -570,11 +608,7 @@ namespace SourceGit.ViewModels
|
|||
return;
|
||||
}
|
||||
|
||||
App.OpenDialog(new Views.InteractiveRebase()
|
||||
{
|
||||
DataContext = new InteractiveRebase(_repo, current, commit)
|
||||
});
|
||||
|
||||
App.ShowWindow(new InteractiveRebase(_repo, current, commit), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -1213,7 +1247,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
builder.Append(".patch");
|
||||
|
||||
return System.IO.Path.Combine(dir, builder.ToString());
|
||||
return Path.Combine(dir, builder.ToString());
|
||||
}
|
||||
|
||||
private Repository _repo = null;
|
||||
|
@ -1224,6 +1258,8 @@ namespace SourceGit.ViewModels
|
|||
private long _navigationId = 0;
|
||||
private object _detailContext = null;
|
||||
|
||||
private Models.Bisect _bisect = null;
|
||||
|
||||
private GridLength _leftArea = new GridLength(1, GridUnitType.Star);
|
||||
private GridLength _rightArea = new GridLength(1, GridUnitType.Star);
|
||||
private GridLength _topArea = new GridLength(1, GridUnitType.Star);
|
||||
|
|
|
@ -380,7 +380,7 @@ namespace SourceGit.ViewModels
|
|||
configure.Header = App.Text("Workspace.Configure");
|
||||
configure.Click += (_, e) =>
|
||||
{
|
||||
App.OpenDialog(new Views.ConfigureWorkspace() { DataContext = new ConfigureWorkspace() });
|
||||
App.ShowWindow(new ConfigureWorkspace(), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(configure);
|
||||
|
|
|
@ -212,6 +212,19 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _useSyntaxHighlighting, value);
|
||||
}
|
||||
|
||||
public bool IgnoreCRAtEOLInDiff
|
||||
{
|
||||
get => Models.DiffOption.IgnoreCRAtEOL;
|
||||
set
|
||||
{
|
||||
if (Models.DiffOption.IgnoreCRAtEOL != value)
|
||||
{
|
||||
Models.DiffOption.IgnoreCRAtEOL = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IgnoreWhitespaceChangesInDiff
|
||||
{
|
||||
get => _ignoreWhitespaceChangesInDiff;
|
||||
|
|
|
@ -114,6 +114,9 @@ namespace SourceGit.ViewModels
|
|||
// Set default selected local branch.
|
||||
if (localBranch != null)
|
||||
{
|
||||
if (LocalBranches.Count == 0)
|
||||
LocalBranches.Add(localBranch);
|
||||
|
||||
_selectedLocalBranch = localBranch;
|
||||
HasSpecifiedLocalBranch = true;
|
||||
}
|
||||
|
|
|
@ -398,6 +398,18 @@ namespace SourceGit.ViewModels
|
|||
get => _workingCopy?.InProgressContext;
|
||||
}
|
||||
|
||||
public Models.BisectState BisectState
|
||||
{
|
||||
get => _bisectState;
|
||||
private set => SetProperty(ref _bisectState, value);
|
||||
}
|
||||
|
||||
public bool IsBisectCommandRunning
|
||||
{
|
||||
get => _isBisectCommandRunning;
|
||||
private set => SetProperty(ref _isBisectCommandRunning, value);
|
||||
}
|
||||
|
||||
public bool IsAutoFetching
|
||||
{
|
||||
get => _isAutoFetching;
|
||||
|
@ -939,6 +951,31 @@ namespace SourceGit.ViewModels
|
|||
return actions;
|
||||
}
|
||||
|
||||
public void Bisect(string subcmd)
|
||||
{
|
||||
IsBisectCommandRunning = true;
|
||||
SetWatcherEnabled(false);
|
||||
|
||||
var log = CreateLog($"Bisect({subcmd})");
|
||||
Task.Run(() =>
|
||||
{
|
||||
var succ = new Commands.Bisect(_fullpath, subcmd).Use(log).Exec();
|
||||
log.Complete();
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (!succ)
|
||||
App.RaiseException(_fullpath, log.Content.Substring(log.Content.IndexOf('\n')).Trim());
|
||||
else if (log.Content.Contains("is the first bad commit"))
|
||||
App.SendNotification(_fullpath, log.Content.Substring(log.Content.IndexOf('\n')).Trim());
|
||||
|
||||
MarkBranchesDirtyManually();
|
||||
SetWatcherEnabled(true);
|
||||
IsBisectCommandRunning = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void RefreshBranches()
|
||||
{
|
||||
var branches = new Commands.QueryBranches(_fullpath).Result();
|
||||
|
@ -1023,6 +1060,8 @@ namespace SourceGit.ViewModels
|
|||
_histories.Commits = commits;
|
||||
_histories.Graph = graph;
|
||||
|
||||
BisectState = _histories.UpdateBisectInfo();
|
||||
|
||||
if (!string.IsNullOrEmpty(_navigateToBranchDelayed))
|
||||
{
|
||||
var branch = _branches.Find(x => x.FullName == _navigateToBranchDelayed);
|
||||
|
@ -1405,8 +1444,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
locks.Click += (_, e) =>
|
||||
{
|
||||
var dialog = new Views.LFSLocks() { DataContext = new LFSLocks(this, _remotes[0].Name) };
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new LFSLocks(this, _remotes[0].Name), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
|
@ -1419,8 +1457,7 @@ namespace SourceGit.ViewModels
|
|||
lockRemote.Header = remoteName;
|
||||
lockRemote.Click += (_, e) =>
|
||||
{
|
||||
var dialog = new Views.LFSLocks() { DataContext = new LFSLocks(this, remoteName) };
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new LFSLocks(this, remoteName), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
locks.Items.Add(lockRemote);
|
||||
|
@ -1706,10 +1743,7 @@ namespace SourceGit.ViewModels
|
|||
compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare");
|
||||
compareWithHead.Click += (_, _) =>
|
||||
{
|
||||
App.OpenDialog(new Views.BranchCompare()
|
||||
{
|
||||
DataContext = new BranchCompare(_fullpath, branch, _currentBranch)
|
||||
});
|
||||
App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false);
|
||||
};
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(compareWithHead);
|
||||
|
@ -1989,10 +2023,7 @@ namespace SourceGit.ViewModels
|
|||
compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare");
|
||||
compareWithHead.Click += (_, _) =>
|
||||
{
|
||||
App.OpenDialog(new Views.BranchCompare()
|
||||
{
|
||||
DataContext = new BranchCompare(_fullpath, branch, _currentBranch)
|
||||
});
|
||||
App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false);
|
||||
};
|
||||
menu.Items.Add(compareWithHead);
|
||||
|
||||
|
@ -2635,6 +2666,9 @@ namespace SourceGit.ViewModels
|
|||
private Timer _autoFetchTimer = null;
|
||||
private DateTime _lastFetchTime = DateTime.MinValue;
|
||||
|
||||
private Models.BisectState _bisectState = Models.BisectState.None;
|
||||
private bool _isBisectCommandRunning = false;
|
||||
|
||||
private string _navigateToBranchDelayed = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,11 +87,8 @@ namespace SourceGit.ViewModels
|
|||
var subdirs = dir.GetDirectories("*", opts);
|
||||
foreach (var subdir in subdirs)
|
||||
{
|
||||
if (subdir.Name.Equals("node_modules", StringComparison.Ordinal) ||
|
||||
subdir.Name.Equals(".svn", StringComparison.Ordinal) ||
|
||||
subdir.Name.Equals(".vs", StringComparison.Ordinal) ||
|
||||
subdir.Name.Equals(".vscode", StringComparison.Ordinal) ||
|
||||
subdir.Name.Equals(".idea", StringComparison.Ordinal))
|
||||
if (subdir.Name.StartsWith(".", StringComparison.Ordinal) ||
|
||||
subdir.Name.Equals("node_modules", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
CallUIThread(() => ProgressDescription = $"Scanning {subdir.FullName}...");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -323,10 +323,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public void OpenAssumeUnchanged()
|
||||
{
|
||||
App.OpenDialog(new Views.AssumeUnchangedManager()
|
||||
{
|
||||
DataContext = new AssumeUnchangedManager(_repo)
|
||||
});
|
||||
App.ShowWindow(new AssumeUnchangedManager(_repo), true);
|
||||
}
|
||||
|
||||
public void StashAll(bool autoStart)
|
||||
|
@ -638,7 +635,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
else if (_inProgressContext is RevertInProgress revert)
|
||||
{
|
||||
useTheirs.Header = App.Text("FileCM.ResolveUsing", revert.Head.SHA.Substring(0, 10) + " (revert)");
|
||||
useTheirs.Header = App.Text("FileCM.ResolveUsing", $"{revert.Head.SHA.AsSpan().Slice(0, 10)} (revert)");
|
||||
useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name);
|
||||
}
|
||||
else if (_inProgressContext is MergeInProgress merge)
|
||||
|
@ -726,8 +723,7 @@ namespace SourceGit.ViewModels
|
|||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, e) =>
|
||||
{
|
||||
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
|
||||
window.Show();
|
||||
App.ShowWindow(new FileHistories(_repo, change.Path), false);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -775,7 +771,7 @@ namespace SourceGit.ViewModels
|
|||
byExtension.Header = App.Text("WorkingCopy.AddToGitIgnore.Extension", extension);
|
||||
byExtension.Click += (_, e) =>
|
||||
{
|
||||
Commands.GitIgnore.Add(_repo.FullPath, "*" + extension);
|
||||
Commands.GitIgnore.Add(_repo.FullPath, $"*{extension}");
|
||||
e.Handled = true;
|
||||
};
|
||||
addToIgnore.Items.Add(byExtension);
|
||||
|
@ -786,7 +782,7 @@ namespace SourceGit.ViewModels
|
|||
byExtensionInSameFolder.Click += (_, e) =>
|
||||
{
|
||||
var dir = Path.GetDirectoryName(change.Path).Replace("\\", "/");
|
||||
Commands.GitIgnore.Add(_repo.FullPath, dir + "/*" + extension);
|
||||
Commands.GitIgnore.Add(_repo.FullPath, $"{dir}/*{extension}");
|
||||
e.Handled = true;
|
||||
};
|
||||
addToIgnore.Items.Add(byExtensionInSameFolder);
|
||||
|
@ -828,7 +824,7 @@ namespace SourceGit.ViewModels
|
|||
lfsTrackByExtension.Click += async (_, e) =>
|
||||
{
|
||||
var log = _repo.CreateLog("Track LFS");
|
||||
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track("*" + extension, false, log));
|
||||
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track($"*{extension}", false, log));
|
||||
if (succ)
|
||||
App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!");
|
||||
|
||||
|
@ -997,7 +993,7 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
else if (_inProgressContext is RevertInProgress revert)
|
||||
{
|
||||
useTheirs.Header = App.Text("FileCM.ResolveUsing", revert.Head.SHA.Substring(0, 10) + " (revert)");
|
||||
useTheirs.Header = App.Text("FileCM.ResolveUsing", $"{revert.Head.SHA.AsSpan().Slice(0, 10)} (revert)");
|
||||
useMine.Header = App.Text("FileCM.ResolveUsing", _repo.CurrentBranch.Name);
|
||||
}
|
||||
else if (_inProgressContext is MergeInProgress merge)
|
||||
|
@ -1093,8 +1089,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
ai.Click += (_, e) =>
|
||||
{
|
||||
var dialog = new Views.AIAssistant(services[0], _repo.FullPath, this, _selectedStaged);
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new AIAssistant(_repo, services[0], _selectedStaged, t => CommitMessage = t), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
|
@ -1108,8 +1103,7 @@ namespace SourceGit.ViewModels
|
|||
item.Header = service.Name;
|
||||
item.Click += (_, e) =>
|
||||
{
|
||||
var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _selectedStaged);
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new AIAssistant(_repo, dup, _selectedStaged, t => CommitMessage = t), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -1193,8 +1187,7 @@ namespace SourceGit.ViewModels
|
|||
history.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
history.Click += (_, e) =>
|
||||
{
|
||||
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
|
||||
window.Show();
|
||||
App.ShowWindow(new FileHistories(_repo, change.Path), false);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -1424,7 +1417,7 @@ namespace SourceGit.ViewModels
|
|||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var prefixLen = home.EndsWith('/') ? home.Length - 1 : home.Length;
|
||||
if (gitTemplate.StartsWith(home, StringComparison.Ordinal))
|
||||
friendlyName = "~" + gitTemplate.Substring(prefixLen);
|
||||
friendlyName = $"~{gitTemplate.AsSpan().Slice(prefixLen)}";
|
||||
}
|
||||
|
||||
var gitTemplateItem = new MenuItem();
|
||||
|
@ -1456,9 +1449,11 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
for (int i = 0; i < historiesCount; i++)
|
||||
{
|
||||
var message = _repo.Settings.CommitMessages[i];
|
||||
var message = _repo.Settings.CommitMessages[i].Trim().ReplaceLineEndings("\n");
|
||||
var subjectEndIdx = message.IndexOf('\n');
|
||||
var subject = subjectEndIdx > 0 ? message.Substring(0, subjectEndIdx) : message;
|
||||
var item = new MenuItem();
|
||||
item.Header = message;
|
||||
item.Header = subject;
|
||||
item.Icon = App.CreateMenuIcon("Icons.Histories");
|
||||
item.Click += (_, e) =>
|
||||
{
|
||||
|
@ -1490,8 +1485,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
if (services.Count == 1)
|
||||
{
|
||||
var dialog = new Views.AIAssistant(services[0], _repo.FullPath, this, _staged);
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new AIAssistant(_repo, services[0], _staged, t => CommitMessage = t), true);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1503,8 +1497,7 @@ namespace SourceGit.ViewModels
|
|||
item.Header = service.Name;
|
||||
item.Click += (_, e) =>
|
||||
{
|
||||
var dialog = new Views.AIAssistant(dup, _repo.FullPath, this, _staged);
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new AIAssistant(_repo, dup, _staged, t => CommitMessage = t), true);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -1533,7 +1526,10 @@ namespace SourceGit.ViewModels
|
|||
private List<Models.Change> GetStagedChanges()
|
||||
{
|
||||
if (_useAmend)
|
||||
return new Commands.QueryStagedChangesWithAmend(_repo.FullPath).Result();
|
||||
{
|
||||
var head = new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").Result();
|
||||
return new Commands.QueryStagedChangesWithAmend(_repo.FullPath, head.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{head.SHA}^").Result();
|
||||
}
|
||||
|
||||
var rs = new List<Models.Change>();
|
||||
foreach (var c in _cached)
|
||||
|
@ -1705,14 +1701,7 @@ namespace SourceGit.ViewModels
|
|||
if (!string.IsNullOrEmpty(_filter) && _staged.Count > _visibleStaged.Count && !confirmWithFilter)
|
||||
{
|
||||
var confirmMessage = App.Text("WorkingCopy.ConfirmCommitWithFilter", _staged.Count, _visibleStaged.Count, _staged.Count - _visibleStaged.Count);
|
||||
App.OpenDialog(new Views.ConfirmCommit()
|
||||
{
|
||||
DataContext = new ConfirmCommit(confirmMessage, () =>
|
||||
{
|
||||
DoCommit(autoStage, autoPush, allowEmpty, true);
|
||||
})
|
||||
});
|
||||
|
||||
App.ShowWindow(new ConfirmCommit(confirmMessage, () => DoCommit(autoStage, autoPush, allowEmpty, true)), true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1720,14 +1709,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
if ((autoStage && _count == 0) || (!autoStage && _staged.Count == 0))
|
||||
{
|
||||
App.OpenDialog(new Views.ConfirmEmptyCommit()
|
||||
{
|
||||
DataContext = new ConfirmEmptyCommit(_count > 0, stageAll =>
|
||||
{
|
||||
DoCommit(stageAll, autoPush, true, confirmWithFilter);
|
||||
})
|
||||
});
|
||||
|
||||
App.ShowWindow(new ConfirmEmptyCommit(_count > 0, stageAll => DoCommit(stageAll, autoPush, true, confirmWithFilter)), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1756,7 +1738,18 @@ namespace SourceGit.ViewModels
|
|||
UseAmend = false;
|
||||
|
||||
if (autoPush && _repo.Remotes.Count > 0)
|
||||
_repo.ShowAndStartPopup(new Push(_repo, null));
|
||||
{
|
||||
if (_repo.CurrentBranch == null)
|
||||
{
|
||||
var currentBranchName = Commands.Branch.ShowCurrent(_repo.FullPath);
|
||||
var tmp = new Models.Branch() { Name = currentBranchName };
|
||||
_repo.ShowAndStartPopup(new Push(_repo, tmp));
|
||||
}
|
||||
else
|
||||
{
|
||||
_repo.ShowAndStartPopup(new Push(_repo, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_repo.MarkBranchesDirtyManually();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
xmlns:c="using:SourceGit.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120"
|
||||
x:Class="SourceGit.Views.AIAssistant"
|
||||
x:DataType="vm:AIAssistant"
|
||||
x:Name="ThisControl"
|
||||
Icon="/App.ico"
|
||||
Title="{DynamicResource Text.AIAssistant}"
|
||||
|
@ -48,20 +49,22 @@
|
|||
<!-- Options -->
|
||||
<Border Grid.Row="2" Margin="0,0,0,8">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<v:LoadingIcon x:Name="IconInProgress" Width="14" Height="14" Margin="0,0,8,0"/>
|
||||
<v:LoadingIcon Width="14" Height="14"
|
||||
Margin="0,0,8,0"
|
||||
IsVisible="{Binding IsGenerating}"/>
|
||||
<Button Classes="flat"
|
||||
x:Name="BtnGenerateCommitMessage"
|
||||
Height="28"
|
||||
Margin="0,0,8,0"
|
||||
Padding="12,0"
|
||||
Content="{DynamicResource Text.AIAssistant.Use}"
|
||||
Click="OnGenerateCommitMessage"/>
|
||||
IsEnabled="{Binding !IsGenerating}"
|
||||
Click="OnApply"/>
|
||||
<Button Classes="flat"
|
||||
x:Name="BtnRegenerate"
|
||||
Height="28"
|
||||
Padding="12,0"
|
||||
Content="{DynamicResource Text.AIAssistant.Regen}"
|
||||
Click="OnRegen"/>
|
||||
IsEnabled="{Binding !IsGenerating}"
|
||||
Command="{Binding Regen}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Document;
|
||||
using AvaloniaEdit.Editing;
|
||||
|
@ -101,76 +99,16 @@ namespace SourceGit.Views
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
public AIAssistant(Models.OpenAIService service, string repo, ViewModels.WorkingCopy wc, List<Models.Change> changes)
|
||||
{
|
||||
_service = service;
|
||||
_repo = repo;
|
||||
_wc = wc;
|
||||
_changes = changes;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
Generate();
|
||||
}
|
||||
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
_cancel?.Cancel();
|
||||
(DataContext as ViewModels.AIAssistant)?.Cancel();
|
||||
}
|
||||
|
||||
private void OnGenerateCommitMessage(object sender, RoutedEventArgs e)
|
||||
private void OnApply(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_wc != null)
|
||||
_wc.CommitMessage = TxtResponse.Text;
|
||||
|
||||
(DataContext as ViewModels.AIAssistant)?.Apply();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnRegen(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TxtResponse.Text = string.Empty;
|
||||
Generate();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Generate()
|
||||
{
|
||||
if (_repo == null)
|
||||
return;
|
||||
|
||||
IconInProgress.IsVisible = true;
|
||||
BtnGenerateCommitMessage.IsEnabled = false;
|
||||
BtnRegenerate.IsEnabled = false;
|
||||
|
||||
_cancel = new CancellationTokenSource();
|
||||
Task.Run(() =>
|
||||
{
|
||||
new Commands.GenerateCommitMessage(_service, _repo, _changes, _cancel.Token, message =>
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => TxtResponse.Text = message);
|
||||
}).Exec();
|
||||
|
||||
if (!_cancel.IsCancellationRequested)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
IconInProgress.IsVisible = false;
|
||||
BtnGenerateCommitMessage.IsEnabled = true;
|
||||
BtnRegenerate.IsEnabled = true;
|
||||
});
|
||||
}
|
||||
}, _cancel.Token);
|
||||
}
|
||||
|
||||
private Models.OpenAIService _service;
|
||||
private string _repo;
|
||||
private ViewModels.WorkingCopy _wc;
|
||||
private List<Models.Change> _changes;
|
||||
private CancellationTokenSource _cancel;
|
||||
}
|
||||
}
|
||||
|
|
140
src/Views/BisectStateIndicator.cs
Normal file
140
src/Views/BisectStateIndicator.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public class BisectStateIndicator : Control
|
||||
{
|
||||
public static readonly StyledProperty<IBrush> BackgroundProperty =
|
||||
AvaloniaProperty.Register<BisectStateIndicator, IBrush>(nameof(Background), Brushes.Transparent);
|
||||
|
||||
public IBrush Background
|
||||
{
|
||||
get => GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> ForegroundProperty =
|
||||
AvaloniaProperty.Register<BisectStateIndicator, IBrush>(nameof(Foreground), Brushes.White);
|
||||
|
||||
public IBrush Foreground
|
||||
{
|
||||
get => GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Models.Bisect> BisectProperty =
|
||||
AvaloniaProperty.Register<BisectStateIndicator, Models.Bisect>(nameof(Bisect));
|
||||
|
||||
public Models.Bisect Bisect
|
||||
{
|
||||
get => GetValue(BisectProperty);
|
||||
set => SetValue(BisectProperty, value);
|
||||
}
|
||||
|
||||
static BisectStateIndicator()
|
||||
{
|
||||
AffectsMeasure<BisectStateIndicator>(BisectProperty);
|
||||
AffectsRender<BisectStateIndicator>(BackgroundProperty, ForegroundProperty);
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
if (_flags == Models.BisectCommitFlag.None)
|
||||
return;
|
||||
|
||||
if (_prefix == null)
|
||||
{
|
||||
_prefix = LoadIcon("Icons.Bisect");
|
||||
_good = LoadIcon("Icons.Check");
|
||||
_bad = LoadIcon("Icons.Bad");
|
||||
}
|
||||
|
||||
var x = 0.0;
|
||||
|
||||
if (_flags.HasFlag(Models.BisectCommitFlag.Good))
|
||||
{
|
||||
RenderImpl(context, Brushes.Green, _good, x);
|
||||
x += 36;
|
||||
}
|
||||
|
||||
if (_flags.HasFlag(Models.BisectCommitFlag.Bad))
|
||||
RenderImpl(context, Brushes.Red, _bad, x);
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var desiredFlags = Models.BisectCommitFlag.None;
|
||||
var desiredWidth = 0.0;
|
||||
if (Bisect is { } bisect && DataContext is Models.Commit commit)
|
||||
{
|
||||
var sha = commit.SHA;
|
||||
if (bisect.Goods.Contains(sha))
|
||||
{
|
||||
desiredFlags |= Models.BisectCommitFlag.Good;
|
||||
desiredWidth = 36;
|
||||
}
|
||||
|
||||
if (bisect.Bads.Contains(sha))
|
||||
{
|
||||
desiredFlags |= Models.BisectCommitFlag.Bad;
|
||||
desiredWidth += 36;
|
||||
}
|
||||
}
|
||||
|
||||
if (desiredFlags != _flags)
|
||||
{
|
||||
_flags = desiredFlags;
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
return new Size(desiredWidth, desiredWidth > 0 ? 16 : 0);
|
||||
}
|
||||
|
||||
private Geometry LoadIcon(string key)
|
||||
{
|
||||
var geo = this.FindResource(key) as StreamGeometry;
|
||||
var drawGeo = geo!.Clone();
|
||||
var iconBounds = drawGeo.Bounds;
|
||||
var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position);
|
||||
var scale = Math.Min(10.0 / iconBounds.Width, 10.0 / iconBounds.Height);
|
||||
var transform = translation * Matrix.CreateScale(scale, scale);
|
||||
if (drawGeo.Transform == null || drawGeo.Transform.Value == Matrix.Identity)
|
||||
drawGeo.Transform = new MatrixTransform(transform);
|
||||
else
|
||||
drawGeo.Transform = new MatrixTransform(drawGeo.Transform.Value * transform);
|
||||
|
||||
return drawGeo;
|
||||
}
|
||||
|
||||
private void RenderImpl(DrawingContext context, IBrush brush, Geometry icon, double x)
|
||||
{
|
||||
var entireRect = new RoundedRect(new Rect(x, 0, 32, 16), new CornerRadius(2));
|
||||
var stateRect = new RoundedRect(new Rect(x + 16, 0, 16, 16), new CornerRadius(0, 2, 2, 0));
|
||||
context.DrawRectangle(Background, new Pen(brush), entireRect);
|
||||
using (context.PushOpacity(.2))
|
||||
context.DrawRectangle(brush, null, stateRect);
|
||||
context.DrawLine(new Pen(brush), new Point(x + 16, 0), new Point(x + 16, 16));
|
||||
|
||||
using (context.PushTransform(Matrix.CreateTranslation(x + 3, 3)))
|
||||
context.DrawGeometry(Foreground, null, _prefix);
|
||||
|
||||
using (context.PushTransform(Matrix.CreateTranslation(x + 19, 3)))
|
||||
context.DrawGeometry(Foreground, null, icon);
|
||||
}
|
||||
|
||||
private Geometry _prefix = null;
|
||||
private Geometry _good = null;
|
||||
private Geometry _bad = null;
|
||||
private Models.BisectCommitFlag _flags = Models.BisectCommitFlag.None;
|
||||
}
|
||||
}
|
|
@ -32,61 +32,63 @@
|
|||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:BranchTreeNode">
|
||||
<Grid Height="24"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
ColumnDefinitions="16,*"
|
||||
ToolTip.Tip="{Binding Tooltip}">
|
||||
<Border Background="Transparent" PointerPressed="OnNodePointerPressed">
|
||||
<Grid Height="24"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
ColumnDefinitions="16,*"
|
||||
ToolTip.Tip="{Binding Tooltip}">
|
||||
|
||||
<!-- Tree Expander -->
|
||||
<v:BranchTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||
IsVisible="{Binding !IsBranch}"/>
|
||||
<!-- Tree Expander -->
|
||||
<v:BranchTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||
IsVisible="{Binding !IsBranch}"/>
|
||||
|
||||
<!-- Content Area (allows double-click) -->
|
||||
<Grid Grid.Column="1"
|
||||
Background="Transparent"
|
||||
ColumnDefinitions="18,*,Auto,Auto"
|
||||
DoubleTapped="OnDoubleTappedBranchNode">
|
||||
<!-- Content Area (allows double-click) -->
|
||||
<Grid Grid.Column="1"
|
||||
Background="Transparent"
|
||||
ColumnDefinitions="18,*,Auto,Auto"
|
||||
DoubleTapped="OnDoubleTappedBranchNode">
|
||||
|
||||
<!-- Icon -->
|
||||
<v:BranchTreeNodeIcon Grid.Column="0"
|
||||
Node="{Binding}"
|
||||
IsExpanded="{Binding IsExpanded}"/>
|
||||
<!-- Icon -->
|
||||
<v:BranchTreeNodeIcon Grid.Column="0"
|
||||
Node="{Binding}"
|
||||
IsExpanded="{Binding IsExpanded}"/>
|
||||
|
||||
<!-- Name -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
Text="{Binding Name}"
|
||||
FontWeight="{Binding IsCurrent, Converter={x:Static c:BoolConverters.IsBoldToFontWeight}}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<!-- Name -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
Text="{Binding Name}"
|
||||
FontWeight="{Binding IsCurrent, Converter={x:Static c:BoolConverters.IsBoldToFontWeight}}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
|
||||
<!-- Upstream invalid tip -->
|
||||
<Border Grid.Column="2"
|
||||
Width="12" Height="12"
|
||||
Margin="8,0"
|
||||
Background="Transparent"
|
||||
ToolTip.Tip="{DynamicResource Text.BranchUpstreamInvalid}"
|
||||
IsVisible="{Binding ShowUpstreamGoneTip}">
|
||||
<Path Data="{StaticResource Icons.Error}" Fill="DarkOrange"/>
|
||||
</Border>
|
||||
<!-- Upstream invalid tip -->
|
||||
<Border Grid.Column="2"
|
||||
Width="12" Height="12"
|
||||
Margin="8,0"
|
||||
Background="Transparent"
|
||||
ToolTip.Tip="{DynamicResource Text.BranchUpstreamInvalid}"
|
||||
IsVisible="{Binding ShowUpstreamGoneTip}">
|
||||
<Path Data="{StaticResource Icons.Error}" Fill="DarkOrange"/>
|
||||
</Border>
|
||||
|
||||
<!-- Tracking status -->
|
||||
<v:BranchTreeNodeTrackStatusPresenter Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource Brush.BadgeFG}"
|
||||
Background="{DynamicResource Brush.Badge}"/>
|
||||
<!-- Tracking status -->
|
||||
<v:BranchTreeNodeTrackStatusPresenter Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||
FontSize="10"
|
||||
Foreground="{DynamicResource Brush.BadgeFG}"
|
||||
Background="{DynamicResource Brush.Badge}"/>
|
||||
|
||||
<!-- Filter Mode Switcher -->
|
||||
<v:FilterModeSwitchButton Grid.Column="3"
|
||||
Margin="0,0,12,0"
|
||||
Mode="{Binding FilterMode}"/>
|
||||
<!-- Filter Mode Switcher -->
|
||||
<v:FilterModeSwitchButton Grid.Column="3"
|
||||
Margin="0,0,12,0"
|
||||
Mode="{Binding FilterMode}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
|
|
@ -318,6 +318,31 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private void OnNodePointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
var p = e.GetCurrentPoint(this);
|
||||
if (!p.Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
if (DataContext is not ViewModels.Repository repo)
|
||||
return;
|
||||
|
||||
if (sender is not Border { DataContext: ViewModels.BranchTreeNode node })
|
||||
return;
|
||||
|
||||
if (node.Backend is not Models.Branch branch)
|
||||
return;
|
||||
|
||||
if (BranchesPresenter.SelectedItems is { Count: > 0 })
|
||||
{
|
||||
var ctrl = OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control;
|
||||
if (e.KeyModifiers.HasFlag(ctrl) || e.KeyModifiers.HasFlag(KeyModifiers.Shift))
|
||||
return;
|
||||
}
|
||||
|
||||
repo.NavigateToCommit(branch.Head);
|
||||
}
|
||||
|
||||
private void OnNodesSelectionChanged(object _, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_disableSelectionChangingEvent)
|
||||
|
@ -343,9 +368,6 @@ namespace SourceGit.Views
|
|||
if (selected == null || selected.Count == 0)
|
||||
return;
|
||||
|
||||
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Branch branch })
|
||||
repo.NavigateToCommit(branch.Head);
|
||||
|
||||
var prev = null as ViewModels.BranchTreeNode;
|
||||
foreach (var row in Rows)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace SourceGit.Views
|
|||
|
||||
public ChromelessWindow()
|
||||
{
|
||||
Focusable = true;
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
if (UseSystemWindowFrame)
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace SourceGit.Views
|
|||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
var links = FullMessage?.Links;
|
||||
var links = FullMessage?.Inlines;
|
||||
if (links == null || links.Count == 0)
|
||||
{
|
||||
Inlines.Add(new Run(message));
|
||||
|
@ -54,7 +54,7 @@ namespace SourceGit.Views
|
|||
inlines.Add(new Run(message.Substring(pos, link.Start - pos)));
|
||||
|
||||
var run = new Run(message.Substring(link.Start, link.Length));
|
||||
run.Classes.Add(link.IsCommitSHA ? "commit_link" : "issue_link");
|
||||
run.Classes.Add(link.Type == Models.InlineElementType.CommitSHA ? "commit_link" : "issue_link");
|
||||
inlines.Add(run);
|
||||
|
||||
pos = link.Start + link.Length;
|
||||
|
@ -87,7 +87,7 @@ namespace SourceGit.Views
|
|||
scrollViewer.LineDown();
|
||||
}
|
||||
}
|
||||
else if (FullMessage is { Links: { Count: > 0 } links })
|
||||
else if (FullMessage is { Inlines: { Count: > 0 } links })
|
||||
{
|
||||
var point = e.GetPosition(this) - new Point(Padding.Left, Padding.Top);
|
||||
var x = Math.Min(Math.Max(point.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0));
|
||||
|
@ -106,7 +106,7 @@ namespace SourceGit.Views
|
|||
SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
|
||||
|
||||
_lastHover = link;
|
||||
if (!link.IsCommitSHA)
|
||||
if (link.Type == Models.InlineElementType.Link)
|
||||
ToolTip.SetTip(this, link.Link);
|
||||
else
|
||||
ProcessHoverCommitLink(link);
|
||||
|
@ -127,7 +127,7 @@ namespace SourceGit.Views
|
|||
var link = _lastHover.Link;
|
||||
e.Pointer.Capture(null);
|
||||
|
||||
if (_lastHover.IsCommitSHA)
|
||||
if (_lastHover.Type == Models.InlineElementType.CommitSHA)
|
||||
{
|
||||
var parentView = this.FindAncestorOfType<CommitBaseInfo>();
|
||||
if (parentView is { DataContext: ViewModels.CommitDetail detail })
|
||||
|
@ -252,7 +252,7 @@ namespace SourceGit.Views
|
|||
ClearHoveredIssueLink();
|
||||
}
|
||||
|
||||
private void ProcessHoverCommitLink(Models.Hyperlink link)
|
||||
private void ProcessHoverCommitLink(Models.InlineElement link)
|
||||
{
|
||||
var sha = link.Link;
|
||||
|
||||
|
@ -301,7 +301,7 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private Models.Hyperlink _lastHover = null;
|
||||
private Models.InlineElement _lastHover = null;
|
||||
private Dictionary<string, Models.Commit> _inlineCommits = new();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,12 +201,7 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnOpenConventionalCommitHelper(object _, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new ConventionalCommitMessageBuilder()
|
||||
{
|
||||
DataContext = new ViewModels.ConventionalCommitMessageBuilder(text => Text = text)
|
||||
};
|
||||
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new ViewModels.ConventionalCommitMessageBuilder(text => Text = text), true);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.TextFormatting;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class CommitSubjectPresenter : TextBlock
|
||||
public partial class CommitSubjectPresenter : Control
|
||||
{
|
||||
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, FontFamily>(nameof(FontFamily));
|
||||
|
||||
public FontFamily FontFamily
|
||||
{
|
||||
get => GetValue(FontFamilyProperty);
|
||||
set => SetValue(FontFamilyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<FontFamily> CodeFontFamilyProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, FontFamily>(nameof(CodeFontFamily));
|
||||
|
||||
public FontFamily CodeFontFamily
|
||||
{
|
||||
get => GetValue(CodeFontFamilyProperty);
|
||||
set => SetValue(CodeFontFamilyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<double> FontSizeProperty =
|
||||
TextBlock.FontSizeProperty.AddOwner<CommitSubjectPresenter>();
|
||||
|
||||
public double FontSize
|
||||
{
|
||||
get => GetValue(FontSizeProperty);
|
||||
set => SetValue(FontSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<FontWeight> FontWeightProperty =
|
||||
TextBlock.FontWeightProperty.AddOwner<CommitSubjectPresenter>();
|
||||
|
||||
public FontWeight FontWeight
|
||||
{
|
||||
get => GetValue(FontWeightProperty);
|
||||
set => SetValue(FontWeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> InlineCodeBackgroundProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, IBrush>(nameof(InlineCodeBackground), Brushes.Transparent);
|
||||
|
||||
public IBrush InlineCodeBackground
|
||||
{
|
||||
get => GetValue(InlineCodeBackgroundProperty);
|
||||
set => SetValue(InlineCodeBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> ForegroundProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, IBrush>(nameof(Foreground), Brushes.White);
|
||||
|
||||
public IBrush Foreground
|
||||
{
|
||||
get => GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush> LinkForegroundProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, IBrush>(nameof(LinkForeground), Brushes.White);
|
||||
|
||||
public IBrush LinkForeground
|
||||
{
|
||||
get => GetValue(LinkForegroundProperty);
|
||||
set => SetValue(LinkForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> SubjectProperty =
|
||||
AvaloniaProperty.Register<CommitSubjectPresenter, string>(nameof(Subject));
|
||||
|
||||
|
@ -31,7 +93,37 @@ namespace SourceGit.Views
|
|||
set => SetValue(IssueTrackerRulesProperty, value);
|
||||
}
|
||||
|
||||
protected override Type StyleKeyOverride => typeof(TextBlock);
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
if (_needRebuildInlines)
|
||||
{
|
||||
_needRebuildInlines = false;
|
||||
GenerateFormattedTextElements();
|
||||
}
|
||||
|
||||
if (_inlines.Count == 0)
|
||||
return;
|
||||
|
||||
var height = Bounds.Height;
|
||||
var width = Bounds.Width;
|
||||
foreach (var inline in _inlines)
|
||||
{
|
||||
if (inline.X > width)
|
||||
return;
|
||||
|
||||
if (inline.Element is { Type: Models.InlineElementType.Code })
|
||||
{
|
||||
var rect = new Rect(inline.X, (height - inline.Text.Height - 2) * 0.5, inline.Text.WidthIncludingTrailingWhitespace + 8, inline.Text.Height + 2);
|
||||
var roundedRect = new RoundedRect(rect, new CornerRadius(4));
|
||||
context.DrawRectangle(InlineCodeBackground, null, roundedRect);
|
||||
context.DrawText(inline.Text, new Point(inline.X + 4, (height - inline.Text.Height) * 0.5));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.DrawText(inline.Text, new Point(inline.X, (height - inline.Text.Height) * 0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
|
@ -39,85 +131,69 @@ namespace SourceGit.Views
|
|||
|
||||
if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty)
|
||||
{
|
||||
Inlines!.Clear();
|
||||
_matches = null;
|
||||
_elements.Clear();
|
||||
ClearHoveredIssueLink();
|
||||
|
||||
var subject = Subject;
|
||||
if (string.IsNullOrEmpty(subject))
|
||||
{
|
||||
_needRebuildInlines = true;
|
||||
InvalidateVisual();
|
||||
return;
|
||||
}
|
||||
|
||||
var keywordMatch = REG_KEYWORD_FORMAT1().Match(subject);
|
||||
if (!keywordMatch.Success)
|
||||
keywordMatch = REG_KEYWORD_FORMAT2().Match(subject);
|
||||
|
||||
if (keywordMatch.Success)
|
||||
_elements.Add(new Models.InlineElement(Models.InlineElementType.Keyword, 0, keywordMatch.Length, string.Empty));
|
||||
|
||||
var codeMatches = REG_INLINECODE_FORMAT().Matches(subject);
|
||||
for (var i = 0; i < codeMatches.Count; i++)
|
||||
{
|
||||
var match = codeMatches[i];
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
var start = match.Index;
|
||||
var len = match.Length;
|
||||
var intersect = false;
|
||||
foreach (var exist in _elements)
|
||||
{
|
||||
if (exist.Intersect(start, len))
|
||||
{
|
||||
intersect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (intersect)
|
||||
continue;
|
||||
|
||||
_elements.Add(new Models.InlineElement(Models.InlineElementType.Code, start, len, string.Empty));
|
||||
}
|
||||
|
||||
var rules = IssueTrackerRules ?? [];
|
||||
var matches = new List<Models.Hyperlink>();
|
||||
foreach (var rule in rules)
|
||||
rule.Matches(matches, subject);
|
||||
rule.Matches(_elements, subject);
|
||||
|
||||
if (matches.Count == 0)
|
||||
{
|
||||
if (keywordMatch.Success)
|
||||
{
|
||||
Inlines.Add(new Run(subject.Substring(0, keywordMatch.Length)) { FontWeight = FontWeight.Bold });
|
||||
Inlines.Add(new Run(subject.Substring(keywordMatch.Length)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Inlines.Add(new Run(subject));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
matches.Sort((l, r) => l.Start - r.Start);
|
||||
_matches = matches;
|
||||
|
||||
var inlines = new List<Inline>();
|
||||
var pos = 0;
|
||||
foreach (var match in matches)
|
||||
{
|
||||
if (match.Start > pos)
|
||||
{
|
||||
if (keywordMatch.Success && pos < keywordMatch.Length)
|
||||
{
|
||||
if (keywordMatch.Length < match.Start)
|
||||
{
|
||||
inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold });
|
||||
inlines.Add(new Run(subject.Substring(keywordMatch.Length, match.Start - keywordMatch.Length)));
|
||||
}
|
||||
else
|
||||
{
|
||||
inlines.Add(new Run(subject.Substring(pos, match.Start - pos)) { FontWeight = FontWeight.Bold });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
inlines.Add(new Run(subject.Substring(pos, match.Start - pos)));
|
||||
}
|
||||
}
|
||||
|
||||
var link = new Run(subject.Substring(match.Start, match.Length));
|
||||
link.Classes.Add("issue_link");
|
||||
inlines.Add(link);
|
||||
|
||||
pos = match.Start + match.Length;
|
||||
}
|
||||
|
||||
if (pos < subject.Length)
|
||||
{
|
||||
if (keywordMatch.Success && pos < keywordMatch.Length)
|
||||
{
|
||||
inlines.Add(new Run(subject.Substring(pos, keywordMatch.Length - pos)) { FontWeight = FontWeight.Bold });
|
||||
inlines.Add(new Run(subject.Substring(keywordMatch.Length)));
|
||||
}
|
||||
else
|
||||
{
|
||||
inlines.Add(new Run(subject.Substring(pos)));
|
||||
}
|
||||
}
|
||||
|
||||
Inlines.AddRange(inlines);
|
||||
_needRebuildInlines = true;
|
||||
InvalidateVisual();
|
||||
}
|
||||
else if (change.Property == FontFamilyProperty ||
|
||||
change.Property == CodeFontFamilyProperty ||
|
||||
change.Property == FontSizeProperty ||
|
||||
change.Property == FontWeightProperty ||
|
||||
change.Property == ForegroundProperty ||
|
||||
change.Property == LinkForegroundProperty)
|
||||
{
|
||||
_needRebuildInlines = true;
|
||||
InvalidateVisual();
|
||||
}
|
||||
else if (change.Property == InlineCodeBackgroundProperty)
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,31 +201,23 @@ namespace SourceGit.Views
|
|||
{
|
||||
base.OnPointerMoved(e);
|
||||
|
||||
if (_matches != null)
|
||||
var point = e.GetPosition(this);
|
||||
foreach (var inline in _inlines)
|
||||
{
|
||||
var point = e.GetPosition(this) - new Point(Padding.Left, Padding.Top);
|
||||
var x = Math.Min(Math.Max(point.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0));
|
||||
var y = Math.Min(Math.Max(point.Y, 0), Math.Max(TextLayout.Height, 0));
|
||||
point = new Point(x, y);
|
||||
if (inline.Element is not { Type: Models.InlineElementType.Link } link)
|
||||
continue;
|
||||
|
||||
var textPosition = TextLayout.HitTestPoint(point).TextPosition;
|
||||
foreach (var match in _matches)
|
||||
{
|
||||
if (!match.Intersect(textPosition, 1))
|
||||
continue;
|
||||
if (inline.X > point.X || inline.X + inline.Text.WidthIncludingTrailingWhitespace < point.X)
|
||||
continue;
|
||||
|
||||
if (match == _lastHover)
|
||||
return;
|
||||
|
||||
_lastHover = match;
|
||||
SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
|
||||
ToolTip.SetTip(this, match.Link);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ClearHoveredIssueLink();
|
||||
_lastHover = link;
|
||||
SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
|
||||
ToolTip.SetTip(this, link.Link);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ClearHoveredIssueLink();
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
|
@ -166,6 +234,94 @@ namespace SourceGit.Views
|
|||
ClearHoveredIssueLink();
|
||||
}
|
||||
|
||||
private void GenerateFormattedTextElements()
|
||||
{
|
||||
_inlines.Clear();
|
||||
|
||||
var subject = Subject;
|
||||
if (string.IsNullOrEmpty(subject))
|
||||
return;
|
||||
|
||||
var fontFamily = FontFamily;
|
||||
var codeFontFamily = CodeFontFamily;
|
||||
var fontSize = FontSize;
|
||||
var foreground = Foreground;
|
||||
var linkForeground = LinkForeground;
|
||||
var typeface = new Typeface(fontFamily, FontStyle.Normal, FontWeight);
|
||||
var codeTypeface = new Typeface(codeFontFamily, FontStyle.Normal, FontWeight);
|
||||
var pos = 0;
|
||||
var x = 0.0;
|
||||
foreach (var elem in _elements)
|
||||
{
|
||||
if (elem.Start > pos)
|
||||
{
|
||||
var normal = new FormattedText(
|
||||
subject.Substring(pos, elem.Start - pos),
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
fontSize,
|
||||
foreground);
|
||||
|
||||
_inlines.Add(new Inline(x, normal, null));
|
||||
x += normal.WidthIncludingTrailingWhitespace;
|
||||
}
|
||||
|
||||
if (elem.Type == Models.InlineElementType.Keyword)
|
||||
{
|
||||
var keyword = new FormattedText(
|
||||
subject.Substring(elem.Start, elem.Length),
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(fontFamily, FontStyle.Normal, FontWeight.Bold),
|
||||
fontSize,
|
||||
foreground);
|
||||
_inlines.Add(new Inline(x, keyword, elem));
|
||||
x += keyword.WidthIncludingTrailingWhitespace;
|
||||
}
|
||||
else if (elem.Type == Models.InlineElementType.Link)
|
||||
{
|
||||
var link = new FormattedText(
|
||||
subject.Substring(elem.Start, elem.Length),
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
fontSize,
|
||||
linkForeground);
|
||||
_inlines.Add(new Inline(x, link, elem));
|
||||
x += link.WidthIncludingTrailingWhitespace;
|
||||
}
|
||||
else if (elem.Type == Models.InlineElementType.Code)
|
||||
{
|
||||
var link = new FormattedText(
|
||||
subject.Substring(elem.Start + 1, elem.Length - 2),
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
codeTypeface,
|
||||
fontSize,
|
||||
foreground);
|
||||
_inlines.Add(new Inline(x, link, elem));
|
||||
x += link.WidthIncludingTrailingWhitespace + 8;
|
||||
}
|
||||
|
||||
pos = elem.Start + elem.Length;
|
||||
}
|
||||
|
||||
if (pos < subject.Length)
|
||||
{
|
||||
var normal = new FormattedText(
|
||||
subject.Substring(pos),
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
fontSize,
|
||||
foreground);
|
||||
|
||||
_inlines.Add(new Inline(x, normal, null));
|
||||
x += normal.WidthIncludingTrailingWhitespace;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearHoveredIssueLink()
|
||||
{
|
||||
if (_lastHover != null)
|
||||
|
@ -176,13 +332,32 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"`.*?`")]
|
||||
private static partial Regex REG_INLINECODE_FORMAT();
|
||||
|
||||
[GeneratedRegex(@"^\[[\w\s]+\]")]
|
||||
private static partial Regex REG_KEYWORD_FORMAT1();
|
||||
|
||||
[GeneratedRegex(@"^\S+([\<\(][\w\s_\-\*,]+[\>\)])?\!?\s?:\s")]
|
||||
private static partial Regex REG_KEYWORD_FORMAT2();
|
||||
|
||||
private List<Models.Hyperlink> _matches = null;
|
||||
private Models.Hyperlink _lastHover = null;
|
||||
private class Inline
|
||||
{
|
||||
public double X { get; set; } = 0;
|
||||
public FormattedText Text { get; set; } = null;
|
||||
public Models.InlineElement Element { get; set; } = null;
|
||||
|
||||
public Inline(double x, FormattedText text, Models.InlineElement elem)
|
||||
{
|
||||
X = x;
|
||||
Text = text;
|
||||
Element = elem;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Models.InlineElement> _elements = [];
|
||||
private List<Inline> _inlines = [];
|
||||
private Models.InlineElement _lastHover = null;
|
||||
private bool _needRebuildInlines = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,9 @@
|
|||
<TextBlock Grid.Column="3" Classes="primary" Text="{Binding AuthorTimeShortStr}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="1" Classes="primary" Text="{Binding Subject}" VerticalAlignment="Bottom"/>
|
||||
<Border Grid.Row="1" Background="Transparent" DataContextChanged="OnCommitSubjectDataContextChanged" PointerMoved="OnCommitSubjectPointerMoved">
|
||||
<TextBlock Classes="primary" Text="{Binding Subject}" VerticalAlignment="Bottom"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
|
@ -57,5 +59,22 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCommitSubjectDataContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (sender is Border border)
|
||||
ToolTip.SetTip(border, null);
|
||||
}
|
||||
|
||||
private void OnCommitSubjectPointerMoved(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is Border { DataContext: Models.Commit commit } border &&
|
||||
DataContext is ViewModels.FileHistories vm)
|
||||
{
|
||||
var tooltip = ToolTip.GetTip(border);
|
||||
if (tooltip == null)
|
||||
ToolTip.SetTip(border, vm.GetCommitFullMessage(commit));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,19 +126,21 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Subject & REFS -->
|
||||
<Border Grid.Column="0"
|
||||
Padding="{Binding Margin}"
|
||||
ClipToBounds="True"
|
||||
Background="Transparent"
|
||||
ToolTip.Tip="{Binding Subject}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*" Margin="2,0,4,0" ClipToBounds="True">
|
||||
<Border Grid.Column="0" Padding="{Binding Margin}" ClipToBounds="True" Background="Transparent">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*" Margin="2,0,4,0" ClipToBounds="True">
|
||||
<v:CommitStatusIndicator Grid.Column="0"
|
||||
CurrentBranch="{Binding $parent[v:Histories].CurrentBranch}"
|
||||
AheadBrush="{DynamicResource Brush.Accent}"
|
||||
BehindBrush="{DynamicResource Brush.FG1}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<v:BisectStateIndicator Grid.Column="1"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
Bisect="{Binding $parent[v:Histories].Bisect}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
<v:CommitRefsPresenter Grid.Column="1"
|
||||
<v:CommitRefsPresenter Grid.Column="2"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
FontFamily="{DynamicResource Fonts.Primary}"
|
||||
|
@ -153,8 +155,12 @@
|
|||
</v:CommitRefsPresenter.UseGraphColor>
|
||||
</v:CommitRefsPresenter>
|
||||
|
||||
<v:CommitSubjectPresenter Grid.Column="2"
|
||||
Classes="primary"
|
||||
<v:CommitSubjectPresenter Grid.Column="3"
|
||||
FontFamily="{DynamicResource Fonts.Primary}"
|
||||
CodeFontFamily="{DynamicResource Fonts.Monospace}"
|
||||
InlineCodeBackground="{DynamicResource Brush.InlineCode}"
|
||||
Foreground="{DynamicResource Brush.FG1}"
|
||||
LinkForeground="{DynamicResource Brush.Link}"
|
||||
Subject="{Binding Subject}"
|
||||
IssueTrackerRules="{Binding $parent[v:Histories].IssueTrackerRules}"
|
||||
FontWeight="{Binding FontWeight}"
|
||||
|
@ -163,10 +169,7 @@
|
|||
</Border>
|
||||
|
||||
<!-- Author -->
|
||||
<Grid Grid.Column="1"
|
||||
ColumnDefinitions="20,*"
|
||||
Background="Transparent"
|
||||
ToolTip.Tip="{Binding Author}">
|
||||
<Grid Grid.Column="1" ColumnDefinitions="20,*" Background="Transparent">
|
||||
<v:Avatar Grid.Column="0"
|
||||
Width="16" Height="16"
|
||||
Margin="4,0,0,0"
|
||||
|
|
|
@ -91,6 +91,15 @@ namespace SourceGit.Views
|
|||
set => SetValue(CurrentBranchProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Models.Bisect> BisectProperty =
|
||||
AvaloniaProperty.Register<Histories, Models.Bisect>(nameof(Bisect));
|
||||
|
||||
public Models.Bisect Bisect
|
||||
{
|
||||
get => GetValue(BisectProperty);
|
||||
set => SetValue(BisectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
|
||||
AvaloniaProperty.Register<Histories, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using Avalonia.Input;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class Hotkeys : ChromelessWindow
|
||||
|
@ -6,5 +8,13 @@ namespace SourceGit.Views
|
|||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (!e.Handled && e.Key == Key.Escape)
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<Path Width="14" Height="14" Data="{StaticResource Icons.Explore}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}">
|
||||
<MenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}" InputGesture="F1">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Hotkeys}"/>
|
||||
</MenuItem.Icon>
|
||||
|
|
|
@ -136,11 +136,18 @@ namespace SourceGit.Views
|
|||
// Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar)
|
||||
if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: (KeyModifiers.Control | KeyModifiers.Shift), Key: Key.P })
|
||||
{
|
||||
App.OpenDialog(new Preferences());
|
||||
App.ShowWindow(new Preferences(), true);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// F1 opens preference dialog (macOS use hotkeys in system menu bar)
|
||||
if (!OperatingSystem.IsMacOS() && e.Key == Key.F1)
|
||||
{
|
||||
App.ShowWindow(new Hotkeys(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Q quits the application (macOS use hotkeys in system menu bar)
|
||||
if (!OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Control && e.Key == Key.Q)
|
||||
{
|
||||
|
|
|
@ -67,15 +67,15 @@ namespace SourceGit.Views
|
|||
presenter.Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var viewTypeName = dataTypeName.Replace("ViewModels", "Views");
|
||||
|
||||
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
|
||||
var viewType = Type.GetType(viewTypeName);
|
||||
if (viewType == null)
|
||||
{
|
||||
presenter.Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var view = Activator.CreateInstance(viewType);
|
||||
presenter.Content = view;
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
<TextBlock Margin="6,0,0,0" FontSize="14" FontWeight="Bold" Text="{DynamicResource Text.Running}"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock x:Name="TxtDesc"
|
||||
HorizontalAlignment="Stretch"
|
||||
<TextBlock HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
|
||||
FontStyle="Italic"
|
||||
|
|
|
@ -21,17 +21,9 @@ namespace SourceGit.Views
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
||||
if (IsVisible)
|
||||
StartAnim();
|
||||
}
|
||||
|
||||
protected override void OnUnloaded(RoutedEventArgs e)
|
||||
{
|
||||
StopAnim();
|
||||
_isUnloading = true;
|
||||
base.OnUnloaded(e);
|
||||
}
|
||||
|
||||
|
@ -41,7 +33,7 @@ namespace SourceGit.Views
|
|||
|
||||
if (change.Property == IsVisibleProperty)
|
||||
{
|
||||
if (IsVisible)
|
||||
if (IsVisible && !_isUnloading)
|
||||
StartAnim();
|
||||
else
|
||||
StopAnim();
|
||||
|
@ -61,5 +53,7 @@ namespace SourceGit.Views
|
|||
Icon.Content = null;
|
||||
ProgressBar.IsIndeterminate = false;
|
||||
}
|
||||
|
||||
private bool _isUnloading = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -273,7 +273,7 @@
|
|||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.Git}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32" ColumnDefinitions="Auto,*">
|
||||
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Preferences.Git.Path}"
|
||||
HorizontalAlignment="Right"
|
||||
|
@ -352,6 +352,11 @@
|
|||
IsChecked="{Binding #ThisControl.EnablePruneOnFetch, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="6" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.Git.IgnoreCRAtEOLInDiff}"
|
||||
IsChecked="{Binding IgnoreCRAtEOLInDiff, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="7" Grid.Column="1"
|
||||
Height="32"
|
||||
Content="{DynamicResource Text.Preferences.Git.SSLVerify}"
|
||||
IsChecked="{Binding #ThisControl.EnableHTTPSSLVerify, Mode=TwoWay}"/>
|
||||
|
|
|
@ -349,9 +349,7 @@ namespace SourceGit.Views
|
|||
if (sender is CheckBox box)
|
||||
{
|
||||
ViewModels.Preferences.Instance.UseSystemWindowFrame = box.IsChecked == true;
|
||||
|
||||
var dialog = new ConfirmRestart();
|
||||
App.OpenDialog(dialog);
|
||||
App.ShowWindow(new ConfirmRestart(), true);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
|
|
|
@ -585,7 +585,7 @@
|
|||
BorderBrush="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Right -->
|
||||
<Grid Grid.Column="2" RowDefinitions="Auto,Auto,*">
|
||||
<Grid Grid.Column="2" RowDefinitions="Auto,Auto,Auto,*">
|
||||
<Grid Grid.Row="0" Height="28" ColumnDefinitions="*,Auto" Background="{DynamicResource Brush.Conflict}" IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<ContentControl Grid.Column="0" Margin="8,0" Content="{Binding InProgressContext}">
|
||||
<ContentControl.DataTemplates>
|
||||
|
@ -665,7 +665,73 @@
|
|||
Command="{Binding AbortMerge}"/>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="1" Background="{DynamicResource Brush.ToolBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<Grid Grid.Row="1"
|
||||
Height="28"
|
||||
ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto"
|
||||
Background="{DynamicResource Brush.Conflict}"
|
||||
IsVisible="{Binding BisectState, Converter={x:Static ObjectConverters.NotEqual}, ConverterParameter={x:Static m:BisectState.None}}">
|
||||
<TextBlock Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="8,0"
|
||||
Foreground="{DynamicResource Brush.ConflictForeground}"
|
||||
Text="{DynamicResource Text.Bisect.WaitingForRange}"
|
||||
IsVisible="{Binding BisectState, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:BisectState.WaitingForRange}}"/>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
FontWeight="Bold"
|
||||
Margin="8,0"
|
||||
Foreground="{DynamicResource Brush.ConflictForeground}"
|
||||
Text="{DynamicResource Text.Bisect.Detecting}"
|
||||
IsVisible="{Binding BisectState, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:BisectState.Detecting}}"/>
|
||||
|
||||
<v:LoadingIcon Grid.Column="1"
|
||||
Width="14" Height="14"
|
||||
Margin="4,0"
|
||||
IsVisible="{Binding IsBisectCommandRunning}"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Classes="flat"
|
||||
Margin="4,0"
|
||||
FontWeight="Regular"
|
||||
BorderThickness="0"
|
||||
Content="{DynamicResource Text.Bisect.Good}"
|
||||
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||
Padding="8,2"
|
||||
Click="OnBisectCommand"
|
||||
Tag="good"/>
|
||||
<Button Grid.Column="3"
|
||||
Classes="flat"
|
||||
Margin="4,0"
|
||||
FontWeight="Regular"
|
||||
BorderThickness="0"
|
||||
Content="{DynamicResource Text.Bisect.Bad}"
|
||||
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||
Padding="8,2"
|
||||
Click="OnBisectCommand"
|
||||
Tag="bad"/>
|
||||
<Button Grid.Column="4"
|
||||
Classes="flat"
|
||||
Margin="4,0"
|
||||
FontWeight="Regular"
|
||||
BorderThickness="0"
|
||||
Content="{DynamicResource Text.Bisect.Skip}"
|
||||
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||
Padding="8,2"
|
||||
Click="OnBisectCommand"
|
||||
Tag="skip"/>
|
||||
<Button Grid.Column="5"
|
||||
Classes="flat"
|
||||
Margin="4,0"
|
||||
FontWeight="Regular"
|
||||
BorderThickness="0"
|
||||
Content="{DynamicResource Text.Bisect.Abort}"
|
||||
IsEnabled="{Binding !IsBisectCommandRunning}"
|
||||
Padding="8,2"
|
||||
Click="OnBisectCommand"
|
||||
Tag="reset"/>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="2" Background="{DynamicResource Brush.ToolBar}" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<Border.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="SelectedViewIndex" Converter="{x:Static c:IntConverters.IsZero}"/>
|
||||
|
@ -721,10 +787,11 @@
|
|||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ContentControl Grid.Row="2" Content="{Binding SelectedView}">
|
||||
<ContentControl Grid.Row="3" Content="{Binding SelectedView}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:Histories">
|
||||
<v:Histories CurrentBranch="{Binding Repo.CurrentBranch}"
|
||||
Bisect="{Binding Bisect}"
|
||||
AuthorNameColumnWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=Layout.HistoriesAuthorColumnWidth, Mode=TwoWay}"
|
||||
IssueTrackerRules="{Binding Repo.Settings.IssueTrackerRules}"
|
||||
OnlyHighlightCurrentBranch="{Binding Repo.OnlyHighlightCurrentBranchInHistories}"
|
||||
|
|
|
@ -429,5 +429,15 @@ namespace SourceGit.Views
|
|||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnBisectCommand(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button &&
|
||||
DataContext is ViewModels.Repository { IsBisectCommandRunning: false } repo &&
|
||||
repo.CanCreatePopup())
|
||||
repo.Bisect(button.Tag as string);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,10 @@
|
|||
<Path Width="14" Height="14" Data="{StaticResource Icons.LFS}"/>
|
||||
</Button>
|
||||
|
||||
<Button Classes="icon_button" Width="32" Margin="8,0,0,0" Click="StartBisect" ToolTip.Tip="{DynamicResource Text.Bisect}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Bisect}"/>
|
||||
</Button>
|
||||
|
||||
<Button Classes="icon_button" Width="32" Margin="8,0,0,0" Click="OpenCustomActionMenu" ToolTip.Tip="{DynamicResource Text.Repository.CustomActions}">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Action}"/>
|
||||
</Button>
|
||||
|
|
|
@ -22,22 +22,20 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
private async void OpenStatistics(object _, RoutedEventArgs e)
|
||||
private void OpenStatistics(object _, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner)
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var dialog = new Statistics() { DataContext = new ViewModels.Statistics(repo.FullPath) };
|
||||
await dialog.ShowDialog(owner);
|
||||
App.ShowWindow(new ViewModels.Statistics(repo.FullPath), true);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async void OpenConfigure(object sender, RoutedEventArgs e)
|
||||
private void OpenConfigure(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner)
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var dialog = new RepositoryConfigure() { DataContext = new ViewModels.RepositoryConfigure(repo) };
|
||||
await dialog.ShowDialog(owner);
|
||||
App.ShowWindow(new ViewModels.RepositoryConfigure(repo), true);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +116,21 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void StartBisect(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository { IsBisectCommandRunning: false } repo &&
|
||||
repo.InProgressContext == null &&
|
||||
repo.CanCreatePopup())
|
||||
{
|
||||
if (repo.LocalChangesCount > 0)
|
||||
App.RaiseException(repo.FullPath, "You have un-committed local changes. Please discard or stash them first.");
|
||||
else
|
||||
repo.Bisect("start");
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OpenCustomActionMenu(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo && sender is Control control)
|
||||
|
@ -129,12 +142,11 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private async void OpenGitLogs(object sender, RoutedEventArgs e)
|
||||
private void OpenGitLogs(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner)
|
||||
if (DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var dialog = new ViewLogs() { DataContext = new ViewModels.ViewLogs(repo) };
|
||||
await dialog.ShowDialog(owner);
|
||||
App.ShowWindow(new ViewModels.ViewLogs(repo), true);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,36 +26,36 @@
|
|||
SelectionChanged="OnRowSelectionChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TagTreeNode">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
Background="Transparent"
|
||||
ContextRequested="OnRowContextRequested"
|
||||
DoubleTapped="OnDoubleTappedNode"
|
||||
ToolTip.Tip="{Binding ToolTip}">
|
||||
<v:TagTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||
IsVisible="{Binding IsFolder}"/>
|
||||
<Border Height="24" Background="Transparent" PointerPressed="OnRowPointerPressed" DoubleTapped="OnDoubleTappedNode" ContextRequested="OnRowContextRequested">
|
||||
<Grid ColumnDefinitions="16,Auto,*,Auto"
|
||||
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{Binding ToolTip}">
|
||||
<v:TagTreeNodeToggleButton Grid.Column="0"
|
||||
Classes="tree_expander"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding IsExpanded, Mode=OneWay}"
|
||||
IsVisible="{Binding IsFolder}"/>
|
||||
|
||||
<v:TagTreeNodeIcon Grid.Column="1"
|
||||
Node="{Binding .}"
|
||||
IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
|
||||
<v:TagTreeNodeIcon Grid.Column="1"
|
||||
Node="{Binding .}"
|
||||
IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
|
||||
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}"
|
||||
Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="2"
|
||||
Classes="primary"
|
||||
Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}"
|
||||
Margin="8,0,0,0"/>
|
||||
|
||||
<ContentControl Grid.Column="3" Content="{Binding Tag}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:Tag">
|
||||
<v:FilterModeSwitchButton Margin="0,0,12,0" Mode="{Binding FilterMode}"/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
<ContentControl Grid.Column="3" Content="{Binding Tag}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="m:Tag">
|
||||
<v:FilterModeSwitchButton Margin="0,0,12,0" Mode="{Binding FilterMode}"/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
@ -69,23 +69,22 @@
|
|||
SelectionChanged="OnRowSelectionChanged">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="m:Tag">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
Background="Transparent"
|
||||
ContextRequested="OnRowContextRequested"
|
||||
ToolTip.Tip="{Binding Message}">
|
||||
<Path Grid.Column="0"
|
||||
Margin="8,0,0,0"
|
||||
Width="12" Height="12"
|
||||
Data="{StaticResource Icons.Tag}"/>
|
||||
<Border Height="24" Background="Transparent" PointerPressed="OnRowPointerPressed" ContextRequested="OnRowContextRequested">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center" ToolTip.Tip="{Binding Message}">
|
||||
<Path Grid.Column="0"
|
||||
Margin="8,0,0,0"
|
||||
Width="12" Height="12"
|
||||
Data="{StaticResource Icons.Tag}"/>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
Text="{Binding Name}"
|
||||
Margin="8,0,0,0"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
Text="{Binding Name}"
|
||||
Margin="8,0,0,0"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
|
||||
<v:FilterModeSwitchButton Grid.Column="2" Margin="0,0,12,0" Mode="{Binding FilterMode}"/>
|
||||
</Grid>
|
||||
<v:FilterModeSwitchButton Grid.Column="2" Margin="0,0,12,0" Mode="{Binding FilterMode}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
|
|
@ -199,15 +199,27 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnDoubleTappedNode(object sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is Grid { DataContext: ViewModels.TagTreeNode node })
|
||||
{
|
||||
if (node.IsFolder)
|
||||
ToggleNodeIsExpanded(node);
|
||||
}
|
||||
if (sender is Control { DataContext: ViewModels.TagTreeNode { IsFolder: true } node })
|
||||
ToggleNodeIsExpanded(node);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnRowPointerPressed(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
var p = e.GetCurrentPoint(this);
|
||||
if (!p.Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
if (DataContext is not ViewModels.Repository repo)
|
||||
return;
|
||||
|
||||
if (sender is Control { DataContext: Models.Tag tag })
|
||||
repo.NavigateToCommit(tag.SHA);
|
||||
else if (sender is Control { DataContext: ViewModels.TagTreeNode { Tag: { } nodeTag } })
|
||||
repo.NavigateToCommit(nodeTag.SHA);
|
||||
}
|
||||
|
||||
private void OnRowContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
var control = sender as Control;
|
||||
|
@ -240,11 +252,8 @@ namespace SourceGit.Views
|
|||
else if (selected is Models.Tag tag)
|
||||
selectedTag = tag;
|
||||
|
||||
if (selectedTag != null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
if (selectedTag != null)
|
||||
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
|
||||
repo.NavigateToCommit(selectedTag.SHA);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeTreeRows(List<ViewModels.TagTreeNode> rows, List<ViewModels.TagTreeNode> nodes)
|
||||
|
|
|
@ -96,6 +96,13 @@
|
|||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Path Grid.Column="0"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Width="48" Height="48"
|
||||
Data="{StaticResource Icons.Empty}"
|
||||
Fill="{DynamicResource Brush.FG2}"
|
||||
IsVisible="{Binding Logs.Count, Converter={x:Static c:IntConverters.IsZero}}"/>
|
||||
|
||||
<Border Grid.Column="2"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue