diff --git a/README.md b/README.md index 50d00f58..2d222996 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ * Search commits * GitFlow * Git LFS +* Bisect * Issue Link * Workspace * Custom Action diff --git a/TRANSLATION.md b/TRANSLATION.md index 866aac65..21706eb2 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,11 +6,18 @@ This document shows the translation status of each locale file in the repository ### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen) -### ![de__DE](https://img.shields.io/badge/de__DE-96.19%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-95.19%25-yellow)
Missing keys in de_DE.axaml +- 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
-### ![es__ES](https://img.shields.io/badge/es__ES-98.95%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) -
-Missing keys in es_ES.axaml - -- Text.CommitCM.CopyAuthor -- Text.CommitCM.CopyCommitter -- Text.CommitCM.CopySubject -- Text.Repository.ViewLogs -- Text.ViewLogs -- Text.ViewLogs.Clear -- Text.ViewLogs.CopyLog -- Text.ViewLogs.Delete - -
- -### ![fr__FR](https://img.shields.io/badge/fr__FR-97.51%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-96.49%25-yellow)
Missing keys in fr_FR.axaml +- 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
-### ![it__IT](https://img.shields.io/badge/it__IT-97.24%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-96.23%25-yellow)
Missing keys in it_IT.axaml +- 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
-### ![ja__JP](https://img.shields.io/badge/ja__JP-97.24%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-96.23%25-yellow)
Missing keys in ja_JP.axaml +- 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
-### ![pt__BR](https://img.shields.io/badge/pt__BR-88.71%25-yellow) +### ![pt__BR](https://img.shields.io/badge/pt__BR-87.79%25-yellow)
Missing keys in pt_BR.axaml @@ -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
-### ![ru__RU](https://img.shields.io/badge/ru__RU-98.82%25-yellow) +### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) -
-Missing keys in ru_RU.axaml - -- 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 - -
- -### ![ta__IN](https://img.shields.io/badge/ta__IN-97.51%25-yellow) +### ![ta__IN](https://img.shields.io/badge/ta__IN-96.49%25-yellow)
Missing keys in ta_IN.axaml +- 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
-### ![uk__UA](https://img.shields.io/badge/uk__UA-98.69%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-97.66%25-yellow)
Missing keys in uk_UA.axaml +- 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 diff --git a/VERSION b/VERSION index 89f9e970..70f2a59f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2025.14 \ No newline at end of file +2025.15 \ No newline at end of file diff --git a/src/App.Commands.cs b/src/App.Commands.cs index 85a75829..22e9fb51 100644 --- a/src/App.Commands.cs +++ b/src/App.Commands.cs @@ -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 => diff --git a/src/App.axaml b/src/App.axaml index 73b97017..186022d5 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -35,7 +35,7 @@ - + diff --git a/src/App.axaml.cs b/src/App.axaml.cs index c659388a..822084dc 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -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((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); }); } diff --git a/src/Commands/Bisect.cs b/src/Commands/Bisect.cs new file mode 100644 index 00000000..a3bf1a97 --- /dev/null +++ b/src/Commands/Bisect.cs @@ -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}"; + } + } +} diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index 65a2a6f5..a60f4cc5 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -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; } diff --git a/src/Commands/QueryStagedChangesWithAmend.cs b/src/Commands/QueryStagedChangesWithAmend.cs index cfea5e35..8f8456c9 100644 --- a/src/Commands/QueryStagedChangesWithAmend.cs +++ b/src/Commands/QueryStagedChangesWithAmend.cs @@ -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 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; } } diff --git a/src/Models/Bisect.cs b/src/Models/Bisect.cs new file mode 100644 index 00000000..d1021113 --- /dev/null +++ b/src/Models/Bisect.cs @@ -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 Bads + { + get; + set; + } = []; + + public HashSet Goods + { + get; + set; + } = []; + } +} diff --git a/src/Models/Change.cs b/src/Models/Change.cs index e9d07181..0c96ec95 100644 --- a/src/Models/Change.cs +++ b/src/Models/Change.cs @@ -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 diff --git a/src/Models/Commit.cs b/src/Models/Commit.cs index 0bad8376..1980e622 100644 --- a/src/Models/Commit.cs +++ b/src/Models/Commit.cs @@ -117,6 +117,6 @@ namespace SourceGit.Models public class CommitFullMessage { public string Message { get; set; } = string.Empty; - public List Links { get; set; } = []; + public List Inlines { get; set; } = []; } } diff --git a/src/Models/CommitLink.cs b/src/Models/CommitLink.cs index 955779a8..544ee3c3 100644 --- a/src/Models/CommitLink.cs +++ b/src/Models/CommitLink.cs @@ -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 Get(List remotes) + { + var outs = new List(); + + 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; + } } } diff --git a/src/Models/ConventionalCommitType.cs b/src/Models/ConventionalCommitType.cs index cd09453a..531a16c0 100644 --- a/src/Models/ConventionalCommitType.cs +++ b/src/Models/ConventionalCommitType.cs @@ -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 Supported = new List() - { - 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 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) { diff --git a/src/Models/DiffOption.cs b/src/Models/DiffOption.cs index 98387e7f..5f491d58 100644 --- a/src/Models/DiffOption.cs +++ b/src/Models/DiffOption.cs @@ -5,6 +5,15 @@ namespace SourceGit.Models { public class DiffOption { + /// + /// Enable `--ignore-cr-at-eol` by default? + /// + public static bool IgnoreCRAtEOL + { + get; + set; + } = false; + public Change WorkingCopyChange => _workingCopyChange; public bool IsUnstaged => _isUnstaged; public List 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"; diff --git a/src/Models/Hyperlink.cs b/src/Models/InlineElement.cs similarity index 60% rename from src/Models/Hyperlink.cs rename to src/Models/InlineElement.cs index 81dc980e..53761403 100644 --- a/src/Models/Hyperlink.cs +++ b/src/Models/InlineElement.cs @@ -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) diff --git a/src/Models/IpcChannel.cs b/src/Models/IpcChannel.cs index 2ecfb771..d47a46bd 100644 --- a/src/Models/IpcChannel.cs +++ b/src/Models/IpcChannel.cs @@ -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) diff --git a/src/Models/IssueTrackerRule.cs b/src/Models/IssueTrackerRule.cs index 29487a16..fe0fe8e0 100644 --- a/src/Models/IssueTrackerRule.cs +++ b/src/Models/IssueTrackerRule.cs @@ -46,7 +46,7 @@ namespace SourceGit.Models set => SetProperty(ref _urlTemplate, value); } - public void Matches(List outs, string message) + public void Matches(List 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)); } } diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index 3ccecad4..e930f412 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -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))) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 51e3d8bf..9da3a51e 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -2,7 +2,9 @@ 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 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 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 + 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 M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z + 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 M128 256h192a64 64 0 110 128H128a64 64 0 110-128zm576 192h192a64 64 0 010 128h-192a64 64 0 010-128zm-576 192h192a64 64 0 010 128H128a64 64 0 010-128zm576 0h192a64 64 0 010 128h-192a64 64 0 010-128zm0-384h192a64 64 0 010 128h-192a64 64 0 010-128zM128 448h192a64 64 0 110 128H128a64 64 0 110-128zm384-320a64 64 0 0164 64v640a64 64 0 01-128 0V192a64 64 0 0164-64z M832 64H192c-18 0-32 14-32 32v832c0 18 14 32 32 32h640c18 0 32-14 32-32V96c0-18-14-32-32-32zM736 596 624 502 506 596V131h230v318z M509 546 780 275 871 366 509 728 147 366 238 275zM509 728h-362v128h724v-128z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 20a70f22..538ac1f1 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -36,6 +36,13 @@ NO FILES ASSUMED AS UNCHANGED REMOVE BINARY FILE NOT SUPPORTED!!! + Bisect + Abort + Bad + Bisecting. Is current HEAD good or bad? + Good + Skip + Bisecting. Mark current commit as good or bad and checkout another one. Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! Checkout ${0}$... @@ -490,6 +497,7 @@ User Email Global git user email Enable --prune on fetch + Enable --ignore-cr-at-eol in diff Git (>= 2.23.0) is required by this app Install Path Enable HTTP SSL Verify diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 204cfc1a..82913d24 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -40,6 +40,13 @@ NO HAY ARCHIVOS ASUMIDOS COMO SIN CAMBIOS REMOVER ¡ARCHIVO BINARIO NO SOPORTADO! + Bisect + Abortar + Malo + Bisecting. ¿Es el HEAD actual bueno o malo? + Bueno + Saltar + Bisecting. Marcar el commit actual cómo bueno o malo y revisar otro. Blame ¡BLAME EN ESTE ARCHIVO NO SOPORTADO! Checkout ${0}$... @@ -103,8 +110,11 @@ Cherry-Pick ... Comparar con HEAD Comparar con Worktree + Autor + Committer Información SHA + Asunto Acción personalizada Rebase Interactivo ${0}$ hasta Aquí Merge a ${0}$ @@ -491,6 +501,7 @@ Email de usuario Email global del usuario git Habilitar --prune para fetch + Habilitar --ignore-cr-at-eol en diff Se requiere Git (>= 2.23.0) para esta aplicación Ruta de instalación Habilitar verificación HTTP SSL @@ -616,6 +627,7 @@ Ordenar Abrir en Terminal Usar tiempo relativo en las historias + Ver Logs WORKTREES AÑADIR WORKTREE PRUNE @@ -701,6 +713,10 @@ Submódulo: Usar opción --remote URL: + Logs + LIMPIAR TODO + Copiar + Borrar Advertencia Página de Bienvenida Crear Grupo diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index b8c86415..a3fe2b3a 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -14,8 +14,8 @@ Отслеживание ветки: Отслеживание внешней ветки Переключиться на: - создать новую ветку - ветку из списка + Создать новую ветку + Ветку из списка Помощник OpenAI ПЕРЕСОЗДАТЬ Использовать OpenAI для создания сообщения о ревизии @@ -40,9 +40,16 @@ СПИСОК ПУСТ УДАЛИТЬ ДВОИЧНЫЙ ФАЙЛ НЕ ПОДДЕРЖИВАЕТСЯ!!! + Раздвоить + О + Плохая + Раздвоение. Текущая ГОЛОВА (HEAD) хорошая или плохая? + Хорошая + Пропустить + Раздвоение. Сделать текущую ревизию хорошей или плохой и переключиться на другой. Расследование РАССЛЕДОВАНИЕ В ЭТОМ ФАЙЛЕ НЕ ПОДДЕРЖИВАЕТСЯ!!! - Проверить ${0}$... + Переключиться на ${0}$... Сравнить с ГОЛОВОЙ (HEAD) Сравнить с рабочим каталогом Копировать имя ветки @@ -55,8 +62,8 @@ Поток Git - Завершение ${0}$ Влить ${0}$ в ${1}$... Влить {0} выделенных веток в текущую - Забрать ${0}$ - Забрать ${0}$ в ${1}$... + Загрузить ${0}$ + Загрузить ${0}$ в ${1}$... Выложить ${0}$ Переместить ${0}$ на ${1}$... Переименовать ${0}$... @@ -103,8 +110,11 @@ Применить несколько ревизий ... Сравнить c ГОЛОВОЙ (HEAD) Сравнить с рабочим каталогом + Автор + Ревизор Информацию SHA + Субъект Пользовательское действие Интерактивное перемещение (rebase -i) ${0}$ сюда Влить в ${0}$ @@ -136,6 +146,7 @@ SHA Открыть в браузере Описание + СУБЪЕКТ Введите тему ревизии Настройка репозитория ШАБЛОН РЕВИЗИИ @@ -199,7 +210,7 @@ Копировать путь Создать ветку... Основан на: - Проверить созданную ветку + Переключиться на созданную ветку Локальные изменения: Отклонить Отложить и применить повторно @@ -348,9 +359,9 @@ Принудительно разблокировать Обрезать Запустить (git lfs prune), чтобы удалить старые файлы LFS из локального хранилища - Забрать + Загрузить Запустить (git lfs pull), чтобы загрузить все файлы LFS Git для текущей ссылки и проверить - Забрать объекты LFS + Загрузить объекты LFS Выложить Отправляйте большие файлы, помещенные в очередь, в конечную точку LFS Git Выложить объекты LFS @@ -385,7 +396,7 @@ Извлечение, запускается сразу Режим доски (по умолчанию) Режим поиска ревизий - Забрать, запускается сразу + Загрузить, запускается сразу Выложить, запускается сразу Принудительно перезагрузить репозиторий Подготовленные/Неподготовленные выбранные изменения @@ -490,6 +501,7 @@ Электроная почта пользователя Общая электроная почта пользователя git Разрешить (--prune) при скачивании + Разрешить (--ignore-cr-at-eol) в различии Для работы программы требуется версия Git (>= 2.23.0) Путь установки Разрешить верификацию HTTP SSL @@ -512,16 +524,16 @@ Цель: Удалить рабочий каталог Информация об обрезке рабочего каталога в «$GIT_COMMON_DIR/worktrees» - Забрать + Загрузить Ветка внешнего репозитория: Извлечь все ветки В: Локальные изменения: Отклонить Отложить и применить повторно - Забрать без меток + Загрузить без меток Внешний репозиторий: - Забрать (Получить и слить) + Загрузить (Получить и слить) Использовать перемещение вместо слияния Выложить Убедитесь, что подмодули были вставлены @@ -615,6 +627,7 @@ Сортировать Открыть в терминале Использовать относительное время в историях + Просмотр логов РАБОЧИЕ КАТАЛОГИ ДОБАВИТЬ РАБОЧИЙ КАТАЛОГ ОБРЕЗАТЬ @@ -700,6 +713,10 @@ Подмодуль: Использовать опцию (--remote) Сетевой адрес: + Логи + ОЧИСТИТЬ ВСЁ + Копировать + Удалить Предупреждение Приветствие Создать группу diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 3d41bccd..39bf38e4 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -40,6 +40,13 @@ 没有不跟踪更改的文件 移除 二进制文件不支持该操作!!! + 二分定位(bisect) + 终止 + 标记错误 + 二分定位进行中。当前提交是 '正确' 还是 '错误' ? + 标记正确 + 无法判定 + 二分定位进行中。请标记当前的提交是 '正确' 还是 '错误',然后检出另一个提交。 逐行追溯(blame) 选中文件不支持该操作!!! 检出(checkout) ${0}$... @@ -494,6 +501,7 @@ 邮箱 默认GIT用户邮箱 拉取更新时启用修剪(--prune) + 对比文件时,默认忽略换行符变更 (--ignore-cr-at-eol) 本软件要求GIT最低版本为2.23.0 安装路径 启用HTTP SSL验证 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 3a10f6ca..181adb69 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -40,6 +40,13 @@ 沒有不追蹤變更的檔案 移除 二進位檔案不支援該操作! + 二分搜尋 (bisect) + 中止 + 標記為錯誤 + 二分搜尋進行中。目前的提交是「良好」是「錯誤」? + 標記為良好 + 無法確認 + 二分搜尋進行中。請標記目前的提交為「良好」或「錯誤」,然後簽出另一個提交。 逐行溯源 (blame) 所選擇的檔案不支援該操作! 簽出 (checkout) ${0}$... @@ -62,7 +69,7 @@ 重新命名 ${0}$... 切換上游分支... 分支比較 - 追蹤上游分支不存在或已刪除! + 追蹤上游分支不存在或已刪除! 位元組 取 消 重設檔案到上一版本 @@ -161,7 +168,7 @@ 啟用定時自動提取 (fetch) 遠端更新 分鐘 預設遠端存放庫 - 首選合併模式 + 預設合併模式 Issue 追蹤 新增符合 Azure DevOps 規則 新增符合 Gitee 議題規則 @@ -186,10 +193,10 @@ 顏色 名稱 啟動時還原上次開啟的存放庫 - 确认继续 + 確認繼續 未包含任何檔案變更! 您是否仍要提交 (--allow-empty)? - 自动暂存并提交 - 未包含任何檔案變更! 您是否仍要提交 (--allow-empty)或者自動暫存全部變更並提交? + 自動暫存並提交 + 未包含任何檔案變更! 您是否仍要提交 (--allow-empty) 或者自動暫存全部變更並提交? 產生約定式提交訊息 破壞性變更: 關閉的 Issue: @@ -250,7 +257,7 @@ 複製 檔案權限已變更 第一個差異 - 忽略空白符號變化和EOL + 忽略空白符號變化和 EOL 最後一個差異 LFS 物件變更 下一個差異 @@ -306,8 +313,8 @@ 取消暫存 從暫存中移除 {0} 個檔案 取消暫存選取的變更 - 使用我方版本 (checkout --ours) - 使用對方版本 (checkout --theirs) + 使用我方版本 (ours) + 使用對方版本 (theirs) 檔案歷史 檔案變更 檔案内容 @@ -465,7 +472,7 @@ 啟用串流輸出 外觀設定 預設字型 - 編輯器制表符寬度 + 編輯器 Tab 寬度 字型大小 預設 程式碼 @@ -494,6 +501,7 @@ 電子郵件 預設 Git 使用者電子郵件 拉取變更時進行清理 (--prune) + 對比檔案時,預設忽略行尾的 CR 變更 (--ignore-cr-at-eol) 本軟體要求 Git 最低版本為 2.23.0 安裝路徑 啟用 HTTP SSL 驗證 @@ -619,7 +627,7 @@ 排序 在終端機中開啟 在提交列表中使用相對時間 - 檢視 GIT 指令的日誌 + 檢視 Git 指令記錄 工作區列表 新增工作區 清理 @@ -705,8 +713,8 @@ 子模組: 啟用 [--remote] 選項 存放庫網址: - 日誌清單 - 清除所有日誌 + 記錄 + 清除所有記錄 複製 刪除 警告 @@ -738,13 +746,13 @@ 觸發點擊事件 提交 (修改原始提交) 自動暫存全部變更並提交 - 您已暫存 {0} 檔案,但只顯示 {1} 檔案 ({2} 檔案被篩選器隱藏)。您要繼續嗎? - 檢測到衝突 + 您已暫存 {0} 個檔案,但只顯示 {1} 個檔案 ({2} 個檔案被篩選器隱藏)。您確定要繼續提交嗎? + 偵測到衝突 使用外部合併工具開啟 使用外部合併工具開啟 檔案衝突已解決 - 使用 MINE - 使用 THEIRS + 使用我方版本 (ours) + 使用對方版本 (theirs) 顯示未追蹤檔案 沒有提交訊息記錄 沒有可套用的提交訊息範本 diff --git a/src/Resources/Themes.axaml b/src/Resources/Themes.axaml index ff4c0374..f33e71d2 100644 --- a/src/Resources/Themes.axaml +++ b/src/Resources/Themes.axaml @@ -25,6 +25,7 @@ #A7E1A7 #F19B9D #0000EE + #FFE5E5E5 @@ -51,6 +52,7 @@ #A0308D3C #A09F4247 #4DAAFC + #FF2E2E2E @@ -79,6 +81,7 @@ + fonts:Inter#Inter fonts:SourceGit#JetBrains Mono diff --git a/src/ViewModels/AIAssistant.cs b/src/ViewModels/AIAssistant.cs new file mode 100644 index 00000000..8756a30b --- /dev/null +++ b/src/ViewModels/AIAssistant.cs @@ -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 changes, Action 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 _changes = null; + private Action _onApply = null; + private CancellationTokenSource _cancel = null; + private bool _isGenerating = false; + private string _text = string.Empty; + } +} diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 81c09472..6581d7bb 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -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 ParseLinksInMessage(string message) + private List ParseInlinesInMessage(string message) { - var links = new List(); + var inlines = new List(); 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() diff --git a/src/ViewModels/FileHistories.cs b/src/ViewModels/FileHistories.cs index 417b816d..9f91205e 100644 --- a/src/ViewModels/FileHistories.cs +++ b/src/ViewModels/FileHistories.cs @@ -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 _commits = null; + private Dictionary _fullCommitMessages = new Dictionary(); private object _viewContent = null; } } diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index a4a3c515..555954d9 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -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); diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 9ae99b33..84ba96e4 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -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); diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index a1830d07..d1e13f38 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -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; diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index 5bbb9858..917935b0 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -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; } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 3ce40751..1db1d6f3 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -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; } } diff --git a/src/ViewModels/ScanRepositories.cs b/src/ViewModels/ScanRepositories.cs index 322c2cda..307694ea 100644 --- a/src/ViewModels/ScanRepositories.cs +++ b/src/ViewModels/ScanRepositories.cs @@ -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}..."); diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 65759412..4d35b5c2 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -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 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(); 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(); diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml index e07c3a3e..c9a37f3b 100644 --- a/src/Views/AIAssistant.axaml +++ b/src/Views/AIAssistant.axaml @@ -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 @@ - + + + diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index dbcf73ec..80b5544d 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -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; } } diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml index b5384c8f..2a575cb3 100644 --- a/src/Views/TagsView.axaml +++ b/src/Views/TagsView.axaml @@ -26,36 +26,36 @@ SelectionChanged="OnRowSelectionChanged"> - - + + + - + - + - - - - - - - - + + + + + + + + + @@ -69,23 +69,22 @@ SelectionChanged="OnRowSelectionChanged"> - - + + + - + - - + + + diff --git a/src/Views/TagsView.axaml.cs b/src/Views/TagsView.axaml.cs index c83cfd28..ba6740c0 100644 --- a/src/Views/TagsView.axaml.cs +++ b/src/Views/TagsView.axaml.cs @@ -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 rows, List nodes) diff --git a/src/Views/ViewLogs.axaml b/src/Views/ViewLogs.axaml index e29a5afd..ccdd1f4d 100644 --- a/src/Views/ViewLogs.axaml +++ b/src/Views/ViewLogs.axaml @@ -96,6 +96,13 @@ + +