diff --git a/README.md b/README.md index 2d222996..8c12c646 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ You can find the current translation status in [TRANSLATION.md](https://github.c ## How to Use -**To use this tool, you need to install Git(>=2.23.0) first.** +**To use this tool, you need to install Git(>=2.25.1) first.** You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [Github Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits. diff --git a/TRANSLATION.md b/TRANSLATION.md index 08594b66..94ff684b 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -6,7 +6,7 @@ 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.75%25-yellow) +### ![de__DE](https://img.shields.io/badge/de__DE-96.50%25-yellow)
Missing keys in de_DE.axaml @@ -26,6 +26,7 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Pull.RecurseSubmodules +- Text.Repository.ClearStashes - Text.Repository.ShowSubmodulesAsTree - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo @@ -37,19 +38,13 @@ This document shows the translation status of each locale file in the repository - Text.Submodule.Status.RevisionChanged - Text.Submodule.Status.Unmerged - Text.Submodule.URL +- Text.WorkingCopy.ResetAuthor
-### ![es__ES](https://img.shields.io/badge/es__ES-99.87%25-yellow) +### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen) -
-Missing keys in es_ES.axaml - -- Text.CreateBranch.OverwriteExisting - -
- -### ![fr__FR](https://img.shields.io/badge/fr__FR-92.62%25-yellow) +### ![fr__FR](https://img.shields.io/badge/fr__FR-92.38%25-yellow)
Missing keys in fr_FR.axaml @@ -90,6 +85,7 @@ This document shows the translation status of each locale file in the repository - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName +- Text.Repository.ClearStashes - Text.Repository.Search.ByContent - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs @@ -113,10 +109,11 @@ This document shows the translation status of each locale file in the repository - Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts - Text.WorkingCopy.Conflicts.UseMine - Text.WorkingCopy.Conflicts.UseTheirs +- Text.WorkingCopy.ResetAuthor
-### ![it__IT](https://img.shields.io/badge/it__IT-98.00%25-yellow) +### ![it__IT](https://img.shields.io/badge/it__IT-97.75%25-yellow)
Missing keys in it_IT.axaml @@ -133,14 +130,16 @@ This document shows the translation status of each locale file in the repository - Text.Launcher.Workspaces - Text.Launcher.Pages - Text.Pull.RecurseSubmodules +- Text.Repository.ClearStashes - Text.ResetWithoutCheckout - Text.ResetWithoutCheckout.MoveTo - Text.ResetWithoutCheckout.Target - Text.Submodule.Deinit +- Text.WorkingCopy.ResetAuthor
-### ![ja__JP](https://img.shields.io/badge/ja__JP-92.37%25-yellow) +### ![ja__JP](https://img.shields.io/badge/ja__JP-92.01%25-yellow)
Missing keys in ja_JP.axaml @@ -152,6 +151,7 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Good - Text.Bisect.Skip - Text.Bisect.WaitingForRange +- Text.BranchCM.CompareWithCurrent - Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules - Text.CommitCM.CopyAuthor @@ -181,6 +181,7 @@ This document shows the translation status of each locale file in the repository - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName +- Text.Repository.ClearStashes - Text.Repository.FilterCommits - Text.Repository.Search.ByContent - Text.Repository.ShowSubmodulesAsTree @@ -206,10 +207,11 @@ This document shows the translation status of each locale file in the repository - Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts - Text.WorkingCopy.Conflicts.UseMine - Text.WorkingCopy.Conflicts.UseTheirs +- Text.WorkingCopy.ResetAuthor
-### ![pt__BR](https://img.shields.io/badge/pt__BR-84.23%25-yellow) +### ![pt__BR](https://img.shields.io/badge/pt__BR-84.02%25-yellow)
Missing keys in pt_BR.axaml @@ -294,6 +296,7 @@ This document shows the translation status of each locale file in the repository - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName +- Text.Repository.ClearStashes - Text.Repository.FilterCommits - Text.Repository.HistoriesLayout - Text.Repository.HistoriesLayout.Horizontal @@ -339,13 +342,23 @@ This document shows the translation status of each locale file in the repository - Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts - Text.WorkingCopy.Conflicts.UseMine - Text.WorkingCopy.Conflicts.UseTheirs +- Text.WorkingCopy.ResetAuthor - Text.WorkingCopy.SignOff
-### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen) +### ![ru__RU](https://img.shields.io/badge/ru__RU-99.63%25-yellow) -### ![ta__IN](https://img.shields.io/badge/ta__IN-92.62%25-yellow) +
+Missing keys in ru_RU.axaml + +- Text.BranchCM.CompareWithCurrent +- Text.Repository.ClearStashes +- Text.WorkingCopy.ResetAuthor + +
+ +### ![ta__IN](https://img.shields.io/badge/ta__IN-92.26%25-yellow)
Missing keys in ta_IN.axaml @@ -357,6 +370,7 @@ This document shows the translation status of each locale file in the repository - Text.Bisect.Good - Text.Bisect.Skip - Text.Bisect.WaitingForRange +- Text.BranchCM.CompareWithCurrent - Text.BranchCM.ResetToSelectedCommit - Text.Checkout.RecurseSubmodules - Text.CommitCM.CopyAuthor @@ -386,6 +400,7 @@ This document shows the translation status of each locale file in the repository - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName +- Text.Repository.ClearStashes - Text.Repository.Search.ByContent - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs @@ -409,10 +424,11 @@ This document shows the translation status of each locale file in the repository - Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts - Text.WorkingCopy.Conflicts.UseMine - Text.WorkingCopy.Conflicts.UseTheirs +- Text.WorkingCopy.ResetAuthor
-### ![uk__UA](https://img.shields.io/badge/uk__UA-93.74%25-yellow) +### ![uk__UA](https://img.shields.io/badge/uk__UA-93.51%25-yellow)
Missing keys in uk_UA.axaml @@ -449,6 +465,7 @@ This document shows the translation status of each locale file in the repository - Text.Repository.BranchSort - Text.Repository.BranchSort.ByCommitterDate - Text.Repository.BranchSort.ByName +- Text.Repository.ClearStashes - Text.Repository.Search.ByContent - Text.Repository.ShowSubmodulesAsTree - Text.Repository.ViewLogs @@ -467,6 +484,7 @@ This document shows the translation status of each locale file in the repository - Text.ViewLogs.Clear - Text.ViewLogs.CopyLog - Text.ViewLogs.Delete +- Text.WorkingCopy.ResetAuthor
diff --git a/VERSION b/VERSION index ac98de3d..67a3d7e0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2025.19 \ No newline at end of file +2025.20 \ No newline at end of file diff --git a/src/App.axaml.cs b/src/App.axaml.cs index 0664ee25..411f5cfb 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -393,7 +393,17 @@ namespace SourceGit _ipcChannel = new Models.IpcChannel(); if (!_ipcChannel.IsFirstInstance) { - _ipcChannel.SendToFirstInstance(desktop.Args is { Length: 1 } ? desktop.Args[0] : string.Empty); + var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty; + if (!string.IsNullOrEmpty(arg)) + { + if (arg.StartsWith('"') && arg.EndsWith('"')) + arg = arg.Substring(1, arg.Length - 2).Trim(); + + if (arg.Length > 0 && !Path.IsPathFullyQualified(arg)) + arg = Path.GetFullPath(arg); + } + + _ipcChannel.SendToFirstInstance(arg); Environment.Exit(0); } else diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs index b2aa803d..210eb4b2 100644 --- a/src/Commands/Add.cs +++ b/src/Commands/Add.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Text; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Add : Command { @@ -12,20 +9,11 @@ namespace SourceGit.Commands Args = includeUntracked ? "add ." : "add -u ."; } - public Add(string repo, List changes) + public Add(string repo, Models.Change change) { WorkingDirectory = repo; Context = repo; - - var builder = new StringBuilder(); - builder.Append("add --"); - foreach (var c in changes) - { - builder.Append(" \""); - builder.Append(c); - builder.Append("\""); - } - Args = builder.ToString(); + Args = $"add -- \"{change.Path}\""; } public Add(string repo, string pathspecFromFile) diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs index aa386c2f..d2876740 100644 --- a/src/Commands/Checkout.cs +++ b/src/Commands/Checkout.cs @@ -27,9 +27,9 @@ namespace SourceGit.Commands { var builder = new StringBuilder(); builder.Append("checkout --progress "); - builder.Append(allowOverwrite ? "-B " : "-b "); if (force) builder.Append("--force "); + builder.Append(allowOverwrite ? "-B " : "-b "); builder.Append(branch); builder.Append(" "); builder.Append(basedOn); diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs index 5be08cef..17410bc9 100644 --- a/src/Commands/Commit.cs +++ b/src/Commands/Commit.cs @@ -4,7 +4,7 @@ namespace SourceGit.Commands { public class Commit : Command { - public Commit(string repo, string message, bool amend, bool signOff) + public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor) { _tmpFile = Path.GetTempFileName(); File.WriteAllText(_tmpFile, message); @@ -12,10 +12,10 @@ namespace SourceGit.Commands WorkingDirectory = repo; Context = repo; Args = $"commit --allow-empty --file=\"{_tmpFile}\""; - if (amend) - Args += " --amend --no-edit"; if (signOff) Args += " --signoff"; + if (amend) + Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit"; } public bool Run() diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs index e61fe1e6..f36ca6c9 100644 --- a/src/Commands/Discard.cs +++ b/src/Commands/Discard.cs @@ -8,6 +8,12 @@ namespace SourceGit.Commands { public static class Discard { + /// + /// Discard all local changes (unstaged & staged) + /// + /// + /// + /// public static void All(string repo, bool includeIgnored, Models.ICommandLog log) { var changes = new QueryLocalChanges(repo).Result(); @@ -36,11 +42,18 @@ namespace SourceGit.Commands }); } - new Restore(repo) { Log = log }.Exec(); + new Reset(repo, "HEAD", "--hard") { Log = log }.Exec(); + if (includeIgnored) new Clean(repo) { Log = log }.Exec(); } + /// + /// Discard selected changes (only unstaged). + /// + /// + /// + /// public static void Changes(string repo, List changes, Models.ICommandLog log) { var restores = new List(); @@ -71,10 +84,12 @@ namespace SourceGit.Commands }); } - for (int i = 0; i < restores.Count; i += 10) + if (restores.Count > 0) { - var count = Math.Min(10, restores.Count - i); - new Restore(repo, restores.GetRange(i, count), "--worktree --recurse-submodules") { Log = log }.Exec(); + var pathSpecFile = Path.GetTempFileName(); + File.WriteAllLines(pathSpecFile, restores); + new Restore(repo, pathSpecFile, false) { Log = log }.Exec(); + File.Delete(pathSpecFile); } } } diff --git a/src/Commands/FormatPatch.cs b/src/Commands/FormatPatch.cs index b3ec2e4a..bf850d60 100644 --- a/src/Commands/FormatPatch.cs +++ b/src/Commands/FormatPatch.cs @@ -6,6 +6,7 @@ { WorkingDirectory = repo; Context = repo; + Editor = EditorType.None; Args = $"format-patch {commit} -1 --output=\"{saveTo}\""; } } diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs index 910e5fd2..d0ecd322 100644 --- a/src/Commands/QueryBranches.cs +++ b/src/Commands/QueryBranches.cs @@ -58,7 +58,7 @@ namespace SourceGit.Commands if (b.TrackStatus == null) b.TrackStatus = new Models.BranchTrackStatus(); - } + } } } diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs index 9abf433e..788ed617 100644 --- a/src/Commands/QueryLocalChanges.cs +++ b/src/Commands/QueryLocalChanges.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; + using Avalonia.Threading; namespace SourceGit.Commands @@ -121,19 +122,32 @@ namespace SourceGit.Commands case "CD": change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted); break; - case "DR": - change.Set(Models.ChangeState.Deleted, Models.ChangeState.Renamed); - break; - case "DC": - change.Set(Models.ChangeState.Deleted, Models.ChangeState.Copied); - break; case "DD": + change.ConflictReason = Models.ConflictReason.BothDeleted; + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); + break; case "AU": + change.ConflictReason = Models.ConflictReason.AddedByUs; + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); + break; case "UD": + change.ConflictReason = Models.ConflictReason.DeletedByThem; + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); + break; case "UA": + change.ConflictReason = Models.ConflictReason.AddedByThem; + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); + break; case "DU": + change.ConflictReason = Models.ConflictReason.DeletedByUs; + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); + break; case "AA": + change.ConflictReason = Models.ConflictReason.BothAdded; + change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); + break; case "UU": + change.ConflictReason = Models.ConflictReason.BothModified; change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted); break; case "??": diff --git a/src/Commands/QueryStagedChangesWithAmend.cs b/src/Commands/QueryStagedChangesWithAmend.cs index b20c20dc..78980401 100644 --- a/src/Commands/QueryStagedChangesWithAmend.cs +++ b/src/Commands/QueryStagedChangesWithAmend.cs @@ -24,7 +24,7 @@ namespace SourceGit.Commands var rs = ReadToEnd(); if (!rs.IsSuccess) return []; - + var changes = new List(); var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines) diff --git a/src/Commands/QueryStashChanges.cs b/src/Commands/QueryStashChanges.cs deleted file mode 100644 index 1f6c5b4f..00000000 --- a/src/Commands/QueryStashChanges.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace SourceGit.Commands -{ - /// - /// Query stash changes. Requires git >= 2.32.0 - /// - public partial class QueryStashChanges : Command - { - [GeneratedRegex(@"^([MADC])\s+(.+)$")] - private static partial Regex REG_FORMAT(); - [GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")] - private static partial Regex REG_RENAME_FORMAT(); - - public QueryStashChanges(string repo, string stash) - { - WorkingDirectory = repo; - Context = repo; - Args = $"stash show -u --name-status \"{stash}\""; - } - - public List Result() - { - var rs = ReadToEnd(); - if (!rs.IsSuccess) - return []; - - var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); - var outs = new List(); - foreach (var line in lines) - { - var match = REG_FORMAT().Match(line); - if (!match.Success) - { - match = REG_RENAME_FORMAT().Match(line); - if (match.Success) - { - var renamed = new Models.Change() { Path = match.Groups[1].Value }; - renamed.Set(Models.ChangeState.Renamed); - outs.Add(renamed); - } - - continue; - } - - var change = new Models.Change() { Path = match.Groups[2].Value }; - var status = match.Groups[1].Value; - - switch (status[0]) - { - case 'M': - change.Set(Models.ChangeState.Modified); - outs.Add(change); - break; - case 'A': - change.Set(Models.ChangeState.Added); - outs.Add(change); - break; - case 'D': - change.Set(Models.ChangeState.Deleted); - outs.Add(change); - break; - case 'C': - change.Set(Models.ChangeState.Copied); - outs.Add(change); - break; - } - } - - outs.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal)); - return outs; - } - } -} diff --git a/src/Commands/Reset.cs b/src/Commands/Reset.cs index da272135..6a54533b 100644 --- a/src/Commands/Reset.cs +++ b/src/Commands/Reset.cs @@ -1,33 +1,7 @@ -using System.Collections.Generic; -using System.Text; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Reset : Command { - public Reset(string repo) - { - WorkingDirectory = repo; - Context = repo; - Args = "reset"; - } - - public Reset(string repo, List changes) - { - WorkingDirectory = repo; - Context = repo; - - var builder = new StringBuilder(); - builder.Append("reset --"); - foreach (var c in changes) - { - builder.Append(" \""); - builder.Append(c.Path); - builder.Append("\""); - } - Args = builder.ToString(); - } - public Reset(string repo, string revision, string mode) { WorkingDirectory = repo; diff --git a/src/Commands/Restore.cs b/src/Commands/Restore.cs index 7a363543..663ea975 100644 --- a/src/Commands/Restore.cs +++ b/src/Commands/Restore.cs @@ -1,29 +1,52 @@ -using System.Collections.Generic; -using System.Text; +using System.Text; namespace SourceGit.Commands { public class Restore : Command { - public Restore(string repo) + /// + /// Only used for single staged change. + /// + /// + /// + public Restore(string repo, Models.Change stagedChange) { WorkingDirectory = repo; Context = repo; - Args = "restore . --source=HEAD --staged --worktree --recurse-submodules"; + + var builder = new StringBuilder(); + builder.Append("restore --staged -- \""); + builder.Append(stagedChange.Path); + builder.Append('"'); + + if (stagedChange.Index == Models.ChangeState.Renamed) + { + builder.Append(" \""); + builder.Append(stagedChange.OriginalPath); + builder.Append('"'); + } + + Args = builder.ToString(); } - public Restore(string repo, List files, string extra) + /// + /// Restore changes given in a path-spec file. + /// + /// + /// + /// + public Restore(string repo, string pathspecFile, bool isStaged) { WorkingDirectory = repo; Context = repo; - StringBuilder builder = new StringBuilder(); + var builder = new StringBuilder(); builder.Append("restore "); - if (!string.IsNullOrEmpty(extra)) - builder.Append(extra).Append(" "); - builder.Append("--"); - foreach (var f in files) - builder.Append(' ').Append('"').Append(f).Append('"'); + builder.Append(isStaged ? "--staged " : "--worktree --recurse-submodules "); + builder.Append("--pathspec-from-file=\""); + builder.Append(pathspecFile); + builder.Append('"'); + Args = builder.ToString(); } } diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs index 10a7ba87..017afea0 100644 --- a/src/Commands/Tag.cs +++ b/src/Commands/Tag.cs @@ -28,12 +28,13 @@ namespace SourceGit.Commands string tmp = Path.GetTempFileName(); File.WriteAllText(tmp, message); cmd.Args += $"-F \"{tmp}\""; - } - else - { - cmd.Args += $"-m {name}"; + + var succ = cmd.Exec(); + File.Delete(tmp); + return succ; } + cmd.Args += $"-m {name}"; return cmd.Exec(); } diff --git a/src/Models/Change.cs b/src/Models/Change.cs index 7d772d3e..129678be 100644 --- a/src/Models/Change.cs +++ b/src/Models/Change.cs @@ -22,6 +22,18 @@ namespace SourceGit.Models Conflicted, } + public enum ConflictReason + { + None, + BothDeleted, + AddedByUs, + DeletedByThem, + AddedByThem, + DeletedByUs, + BothAdded, + BothModified, + } + public class ChangeDataForAmend { public string FileMode { get; set; } = ""; @@ -36,7 +48,14 @@ namespace SourceGit.Models public string Path { get; set; } = ""; public string OriginalPath { get; set; } = ""; public ChangeDataForAmend DataForAmend { get; set; } = null; + public ConflictReason ConflictReason { get; set; } = ConflictReason.None; + public bool IsConflicted => WorkTree == ChangeState.Conflicted; + public string ConflictMarker => CONFLICT_MARKERS[(int)ConflictReason]; + public string ConflictDesc => CONFLICT_DESCS[(int)ConflictReason]; + + public string WorkTreeDesc => TYPE_DESCS[(int)WorkTree]; + public string IndexDesc => TYPE_DESCS[(int)Index]; public void Set(ChangeState index, ChangeState workTree = ChangeState.None) { @@ -64,9 +83,44 @@ namespace SourceGit.Models if (Path[0] == '"') Path = Path.Substring(1, Path.Length - 2); - + if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"') OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2); } + + private static readonly string[] TYPE_DESCS = + [ + "Unknown", + "Modified", + "Type Changed", + "Added", + "Deleted", + "Renamed", + "Copied", + "Untracked", + "Conflict" + ]; + private static readonly string[] CONFLICT_MARKERS = + [ + string.Empty, + "DD", + "AU", + "UD", + "UA", + "DU", + "AA", + "UU" + ]; + private static readonly string[] CONFLICT_DESCS = + [ + string.Empty, + "Both deleted", + "Added by us", + "Deleted by them", + "Added by them", + "Deleted by us", + "Both added", + "Both modified" + ]; } } diff --git a/src/Models/GitVersions.cs b/src/Models/GitVersions.cs index 92fd8c59..8aae63a3 100644 --- a/src/Models/GitVersions.cs +++ b/src/Models/GitVersions.cs @@ -5,26 +5,16 @@ /// /// The minimal version of Git that required by this app. /// - public static readonly System.Version MINIMAL = new System.Version(2, 23, 0); - - /// - /// The minimal version of Git that supports the `add` command with the `--pathspec-from-file` option. - /// - public static readonly System.Version ADD_WITH_PATHSPECFILE = new System.Version(2, 25, 0); + public static readonly System.Version MINIMAL = new(2, 25, 1); /// /// The minimal version of Git that supports the `stash push` command with the `--pathspec-from-file` option. /// - public static readonly System.Version STASH_PUSH_WITH_PATHSPECFILE = new System.Version(2, 26, 0); + public static readonly System.Version STASH_PUSH_WITH_PATHSPECFILE = new(2, 26, 0); /// /// The minimal version of Git that supports the `stash push` command with the `--staged` option. /// - public static readonly System.Version STASH_PUSH_ONLY_STAGED = new System.Version(2, 35, 0); - - /// - /// The minimal version of Git that supports the `stash show` command with the `-u` option. - /// - public static readonly System.Version STASH_SHOW_WITH_UNTRACKED = new System.Version(2, 32, 0); + public static readonly System.Version STASH_PUSH_ONLY_STAGED = new(2, 35, 0); } } diff --git a/src/Models/Statistics.cs b/src/Models/Statistics.cs index d982a3ed..a86380c3 100644 --- a/src/Models/Statistics.cs +++ b/src/Models/Statistics.cs @@ -26,25 +26,23 @@ namespace SourceGit.Models public class StatisticsReport { - public static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; - public int Total { get; set; } = 0; - public List Authors { get; set; } = new List(); - public List Series { get; set; } = new List(); - public List XAxes { get; set; } = new List(); - public List YAxes { get; set; } = new List(); + public List Authors { get; set; } = new(); + public List Series { get; set; } = new(); + public List XAxes { get; set; } = new(); + public List YAxes { get; set; } = new(); public StatisticsAuthor SelectedAuthor { get => _selectedAuthor; set => ChangeAuthor(value); } public StatisticsReport(StatisticsMode mode, DateTime start) { _mode = mode; - YAxes = [new Axis() + YAxes.Add(new Axis() { TextSize = 10, MinLimit = 0, SeparatorsPaint = new SolidColorPaint(new SKColor(0x40808080)) { StrokeThickness = 1 } - }]; + }); if (mode == StatisticsMode.ThisWeek) { @@ -72,7 +70,7 @@ namespace SourceGit.Models { Total++; - var normalized = DateTime.MinValue; + DateTime normalized; if (_mode == StatisticsMode.ThisWeek || _mode == StatisticsMode.ThisMonth) normalized = time.Date; else @@ -172,26 +170,27 @@ namespace SourceGit.Models ChangeColor(_fillColor); } - private StatisticsMode _mode = StatisticsMode.All; - private Dictionary _mapUsers = new Dictionary(); - private Dictionary _mapSamples = new Dictionary(); - private Dictionary> _mapUserSamples = new Dictionary>(); + private static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; + private StatisticsMode _mode; + private Dictionary _mapUsers = new(); + private Dictionary _mapSamples = new(); + private Dictionary> _mapUserSamples = new(); private StatisticsAuthor _selectedAuthor = null; private uint _fillColor = 255; } public class Statistics { - public StatisticsReport All { get; set; } - public StatisticsReport Month { get; set; } - public StatisticsReport Week { get; set; } + public StatisticsReport All { get; } + public StatisticsReport Month { get; } + public StatisticsReport Week { get; } public Statistics() { - _today = DateTime.Now.ToLocalTime().Date; - var weekOffset = (7 + (int)_today.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % 7; - _thisWeekStart = _today.AddDays(-weekOffset); - _thisMonthStart = _today.AddDays(1 - _today.Day); + var today = DateTime.Now.ToLocalTime().Date; + var weekOffset = (7 + (int)today.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % 7; + _thisWeekStart = today.AddDays(-weekOffset); + _thisMonthStart = today.AddDays(1 - today.Day); All = new StatisticsReport(StatisticsMode.All, DateTime.MinValue); Month = new StatisticsReport(StatisticsMode.ThisMonth, _thisMonthStart); @@ -200,7 +199,13 @@ namespace SourceGit.Models public void AddCommit(string author, double timestamp) { - var user = User.FindOrAdd(author); + var emailIdx = author.IndexOf('±', StringComparison.Ordinal); + var email = author.Substring(emailIdx + 1).ToLower(CultureInfo.CurrentCulture); + if (!_users.TryGetValue(email, out var user)) + { + user = User.FindOrAdd(author); + _users.Add(email, user); + } var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime(); if (time >= _thisWeekStart) @@ -214,13 +219,15 @@ namespace SourceGit.Models public void Complete() { + _users.Clear(); + All.Complete(); Month.Complete(); Week.Complete(); } - private readonly DateTime _today; private readonly DateTime _thisMonthStart; private readonly DateTime _thisWeekStart; + private readonly Dictionary _users = new(); } } diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 886a07cf..3c900d42 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -50,13 +50,12 @@ Blame BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!! Auschecken von ${0}$... - Mit HEAD vergleichen + Mit ${0}$ vergleichen Mit Worktree vergleichen Branch-Namen kopieren Benutzerdefinierte Aktion Lösche ${0}$... Lösche alle ausgewählten {0} Branches - Alle Änderungen verwerfen Fast-Forward zu ${0}$ Fetche ${0}$ in ${1}$ hinein... Git Flow - Abschließen ${0}$ @@ -502,7 +501,7 @@ Globale Git Benutzer Email Aktivere --prune beim fetchen Aktiviere --ignore-cr-at-eol beim Unterschied - Diese App setzt Git (>= 2.23.0) voraus + Diese App setzt Git (>= 2.25.1) voraus Installationspfad Aktiviere HTTP SSL Verifizierung Benutzername @@ -583,6 +582,7 @@ WEITER Benutzerdefinierte Aktionen Keine benutzerdefinierten Aktionen + Alle Änderungen verwerfen Aktiviere '--reflog' Option Öffne im Datei-Browser Suche Branches/Tags/Submodule diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index c12396ec..4834aef9 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -46,13 +46,12 @@ Blame BLAME ON THIS FILE IS NOT SUPPORTED!!! Checkout ${0}$... - Compare with HEAD + Compare with ${0}$ Compare with Worktree Copy Branch Name Custom Action Delete ${0}$... Delete selected {0} branches - Discard all changes Fast-Forward to ${0}$ Fetch ${0}$ into ${1}$... Git Flow - Finish ${0}$ @@ -512,7 +511,7 @@ Global git user email Enable --prune on fetch Enable --ignore-cr-at-eol in diff - Git (>= 2.23.0) is required by this app + Git (>= 2.25.1) is required by this app Install Path Enable HTTP SSL Verify User Name @@ -590,10 +589,12 @@ Cleanup(GC & Prune) Run `git gc` command for this repository. Clear all + Clear Configure this repository CONTINUE Custom Actions No Custom Actions + Discard all changes Enable '--reflog' Option Open in File Browser Search Branches/Tags/Submodules @@ -781,6 +782,7 @@ INCLUDE UNTRACKED FILES NO RECENT INPUT MESSAGES NO COMMIT TEMPLATES + Reset Author Right-click the selected file(s), and make your choice to resolve conflicts. SignOff STAGED diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 513eeb06..36c60346 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -50,13 +50,12 @@ Blame ¡BLAME EN ESTE ARCHIVO NO SOPORTADO! Checkout ${0}$... - Comparar con HEAD + Comparar con ${0}$ Comparar con Worktree Copiar Nombre de Rama Acción personalizada Eliminar ${0}$... Eliminar {0} ramas seleccionadas - Descartar todos los cambios Fast-Forward a ${0}$ Fetch ${0}$ en ${1}$... Git Flow - Finalizar ${0}$ @@ -221,6 +220,7 @@ Introduzca el nombre de la rama. Los espacios serán reemplazados con guiones. Crear Rama Local + Sobrescribir la rama existente Crear Etiqueta... Nueva Etiqueta En: Firma GPG @@ -515,7 +515,7 @@ 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 + Se requiere Git (>= 2.25.1) para esta aplicación Ruta de instalación Habilitar verificación HTTP SSL Nombre de usuario @@ -593,10 +593,12 @@ Limpiar (GC & Prune) Ejecutar comando `git gc` para este repositorio. Limpiar todo + Limpiar Configurar este repositorio CONTINUAR Acciones Personalizadas No hay ninguna Acción Personalizada + Descartar todos los cambios Habilitar Opción '--reflog' Abrir en el Explorador Buscar Ramas/Etiquetas/Submódulos @@ -784,6 +786,7 @@ INCLUIR ARCHIVOS NO RASTREADOS NO HAY MENSAJES DE ENTRADA RECIENTES NO HAY PLANTILLAS DE COMMIT + Restablecer Autor Haz clic derecho en el(los) archivo(s) seleccionado(s) y elige tu opción para resolver conflictos. Firmar STAGED diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 0c3ab91b..25e6ca3e 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -43,13 +43,12 @@ Blâme LE BLÂME SUR CE FICHIER N'EST PAS SUPPORTÉ!!! Récupérer ${0}$... - Comparer avec HEAD + Comparer avec ${0}$ Comparer avec le worktree Copier le nom de la branche Action personnalisée Supprimer ${0}$... Supprimer {0} branches sélectionnées - Rejeter tous les changements Fast-Forward vers ${0}$ Fetch ${0}$ vers ${1}$... Git Flow - Terminer ${0}$ @@ -484,7 +483,7 @@ E-mail utilsateur E-mail utilsateur global Activer --prune pour fetch - Cette application requière Git (>= 2.23.0) + Cette application requière Git (>= 2.25.1) Chemin d'installation Activer la vérification HTTP SSL Nom d'utilisateur @@ -562,6 +561,7 @@ CONTINUER Actions personnalisées Pas d'actions personnalisées + Rejeter tous les changements Activer l'option '--reflog' Ouvrir dans l'explorateur de fichiers Rechercher Branches/Tags/Submodules diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 23c31c6e..22c4e8fe 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -50,13 +50,12 @@ Attribuisci L'ATTRIBUZIONE SU QUESTO FILE NON È SUPPORTATA!!! Checkout ${0}$... - Confronta con HEAD + Confronta con ${0}$ Confronta con Worktree Copia Nome Branch Azione personalizzata Elimina ${0}$... Elimina i {0} branch selezionati - Scarta tutte le modifiche Avanzamento Veloce a ${0}$ Recupera ${0}$ in ${1}$... Git Flow - Completa ${0}$ @@ -505,7 +504,7 @@ Email utente Git globale Abilita --prune durante il fetch Abilita --ignore-cr-at-eol nel diff - Questa applicazione richiede Git (>= 2.23.0) + Questa applicazione richiede Git (>= 2.25.1) Percorso Installazione Abilita la verifica HTTP SSL Nome Utente @@ -586,6 +585,7 @@ CONTINUA Azioni Personalizzate Nessuna Azione Personalizzata + Scarta tutte le modifiche Abilita opzione '--reflog' Apri nell'Esplora File Cerca Branch/Tag/Sottomodulo diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index ca29b985..945cf2d9 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -43,13 +43,11 @@ Blame BLAMEではこのファイルはサポートされていません!!! ${0}$ をチェックアウトする... - HEADと比較 ワークツリーと比較 ブランチ名をコピー カスタムアクション ${0}$を削除... 選択中の{0}個のブランチを削除 - すべての変更を破棄 ${0}$ へ早送りする ${0}$ から ${1}$ へフェッチする Git Flow - Finish ${0}$ @@ -484,7 +482,7 @@ ユーザー Eメールアドレス グローバルgitのEメールアドレス フェッチ時に--pruneを有効化 - Git (>= 2.23.0) はこのアプリで必要です + Git (>= 2.25.1) はこのアプリで必要です インストール パス HTTP SSL 検証を有効にする ユーザー名 @@ -562,6 +560,7 @@ 続ける カスタムアクション カスタムアクションがありません + すべての変更を破棄 `--reflog` オプションを有効化 ファイルブラウザーで開く ブランチ/タグ/サブモジュールを検索 diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 7ce861e9..a75d6777 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -37,12 +37,11 @@ Blame BLAME NESTE ARQUIVO NÃO É SUPORTADO!!! Checkout ${0}$... - Comparar com HEAD + Comparar com ${0}$ Comparar com Worktree Copiar Nome do Branch Excluir ${0}$... Excluir {0} branches selecionados - Descartar todas as alterações Fast-Forward para ${0}$ Buscar ${0}$ em ${1}$... Git Flow - Finalizar ${0}$ @@ -441,7 +440,7 @@ Email do Usuário Email global do usuário git Habilita --prune ao buscar - Git (>= 2.23.0) é necessário para este aplicativo + Git (>= 2.25.1) é necessário para este aplicativo Caminho de Instalação Nome do Usuário Nome global do usuário git @@ -518,6 +517,7 @@ CONTINUAR Ações customizada Nenhuma ação customizada + Descartar todas as alterações Habilitar opção '--reflog' Abrir no Navegador de Arquivos Pesquisar Branches/Tags/Submódulos diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index bc72fd65..0a6ccba4 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -50,13 +50,11 @@ Расследование РАССЛЕДОВАНИЕ В ЭТОМ ФАЙЛЕ НЕ ПОДДЕРЖИВАЕТСЯ!!! Переключиться на ${0}$... - Сравнить с ГОЛОВОЙ (HEAD) Сравнить с рабочим каталогом Копировать имя ветки Изменить действие Удалить ${0}$... Удалить выбранные {0} ветки - Отклонить все изменения. Перемотать вперёд к ${0}$ Извлечь ${0}$ в ${1}$... Git-процесс - Завершение ${0}$ @@ -516,7 +514,7 @@ Общая электроная почта пользователя git Разрешить (--prune) при скачивании Разрешить (--ignore-cr-at-eol) в сравнении - Для работы программы требуется версия Git (>= 2.23.0) + Для работы программы требуется версия Git (>= 2.25.1) Путь установки Разрешить верификацию HTTP SSL Имя пользователя @@ -598,6 +596,7 @@ ПРОДОЛЖИТЬ Изменить действия Не изменять действия + Отклонить все изменения. Разрешить опцию --reflog Открыть в файловом менеджере Поиск веток, меток и подмодулей diff --git a/src/Resources/Locales/ta_IN.axaml b/src/Resources/Locales/ta_IN.axaml index 71df97c0..e66af3ff 100644 --- a/src/Resources/Locales/ta_IN.axaml +++ b/src/Resources/Locales/ta_IN.axaml @@ -43,13 +43,11 @@ குற்றச்சாட்டு இந்த கோப்பில் குற்றம் சாட்ட ஆதரிக்கப்படவில்லை!!! ${0}$ சரிபார்... - தலையுடன் ஒப்பிடுக பணிமரத்துடன் ஒப்பிடுக கிளை பெயரை நகலெடு தனிப்பயன் செயல் ${0}$ ஐ நீக்கு... தேர்ந்தெடுக்கப்பட்ட {0} கிளைகளை நீக்கு - எல்லா மாற்றங்களையும் நிராகரி ${0}$ இதற்கு வேகமாக முன்னோக்கிச் செல் ${0}$ ஐ ${1}$இல் பெறு... அறிவிலி ஓட்டம் - முடி ${0}$ @@ -484,7 +482,7 @@ பயனர் மின்னஞ்சல் உலகளாவிய அறிவிலி பயனர் மின்னஞ்சல் --prune எடுக்கும்போது இயக்கு - அறிவிலி (>= 2.23.0) இந்த பயன்பாட்டிற்கு தேவைப்படுகிறது + அறிவிலி (>= 2.25.1) இந்த பயன்பாட்டிற்கு தேவைப்படுகிறது நிறுவல் பாதை உஉபநெ பாகுஅ சரிபார்ப்பை இயக்கு பயனர் பெயர் @@ -562,6 +560,7 @@ தொடர்க தனிப்பயன் செயல்கள் தனிப்பயன் செயல்கள் இல்லை + எல்லா மாற்றங்களையும் நிராகரி '--குறிபதிவு' விருப்பத்தை இயக்கு கோப்பு உலாவியில் திற கிளைகள்/குறிச்சொற்கள்/துணைத் தொகுதிகளைத் தேடு diff --git a/src/Resources/Locales/uk_UA.axaml b/src/Resources/Locales/uk_UA.axaml index 297878a6..2e7b399b 100644 --- a/src/Resources/Locales/uk_UA.axaml +++ b/src/Resources/Locales/uk_UA.axaml @@ -43,13 +43,12 @@ Автор рядка ПОШУК АВТОРА РЯДКА ДЛЯ ЦЬОГО ФАЙЛУ НЕ ПІДТРИМУЄТЬСЯ!!! Перейти на ${0}$... - Порівняти з HEAD + Порівняти з ${0}$ Порівняти з робочим деревом Копіювати назву гілки Спеціальна дія Видалити ${0}$... Видалити вибрані {0} гілок - Скасувати всі зміни Перемотати до ${0}$ Отримати ${0}$ в ${1}$... Git Flow - Завершити ${0}$ @@ -488,7 +487,7 @@ Email користувача Глобальний email користувача git Увімкнути --prune при fetch - Git (>= 2.23.0) є обов'язковим для цієї програми + Git (>= 2.25.1) є обов'язковим для цієї програми Шлях встановлення Увімкнути перевірку HTTP SSL Ім'я користувача @@ -566,6 +565,7 @@ ПРОДОВЖИТИ Спеціальні дії Немає спеціальних дій + Скасувати всі зміни Увімкнути опцію '--reflog' Відкрити у файловому менеджері Пошук гілок/тегів/підмодулів diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 00939656..77a55f75 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -50,13 +50,12 @@ 逐行追溯(blame) 选中文件不支持该操作!!! 检出(checkout) ${0}$... - 与当前HEAD比较 + 与当前 ${0}$ 比较 与本地工作树比较 复制分支名 自定义操作 删除 ${0}$... 删除选中的 {0} 个分支 - 放弃所有更改 快进(fast-forward)到 ${0}$ 拉取(fetch) ${0}$ 至 ${1}$... GIT工作流 - 完成 ${0}$ @@ -516,7 +515,7 @@ 默认GIT用户邮箱 拉取更新时启用修剪(--prune) 对比文件时,默认忽略换行符变更 (--ignore-cr-at-eol) - 本软件要求GIT最低版本为2.23.0 + 本软件要求GIT最低版本为2.25.1 安装路径 启用HTTP SSL验证 用户名 @@ -594,10 +593,12 @@ 清理本仓库(GC) 本操作将执行`git gc`命令。 清空过滤规则 + 清空 配置本仓库 下一步 自定义操作 自定义操作未设置 + 放弃所有更改 启用 --reflog 选项 在文件浏览器中打开 快速查找分支/标签/子模块 @@ -785,6 +786,7 @@ 显示未跟踪文件 没有提交信息记录 没有可应用的提交信息模板 + 重置提交者 请选中冲突文件,打开右键菜单,选择合适的解决方式 署名 已暂存 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 026a41ed..aef769de 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -50,13 +50,12 @@ 逐行溯源 (blame) 所選擇的檔案不支援該操作! 簽出 (checkout) ${0}$... - 與目前 HEAD 比較 + 與目前 ${0}$ 比較 與本機工作區比較 複製分支名稱 自訂動作 刪除 ${0}$... 刪除所選的 {0} 個分支 - 捨棄所有變更 快轉 (fast-forward) 到 ${0}$ 提取 (fetch) ${0}$ 到 ${1}$... Git 工作流 - 完成 ${0}$ @@ -516,7 +515,7 @@ 預設 Git 使用者電子郵件 拉取變更時進行清理 (--prune) 對比檔案時,預設忽略行尾的 CR 變更 (--ignore-cr-at-eol) - 本軟體要求 Git 最低版本為 2.23.0 + 本軟體要求 Git 最低版本為 2.25.1 安裝路徑 啟用 HTTP SSL 驗證 使用者名稱 @@ -594,10 +593,12 @@ 清理本存放庫 (GC) 本操作將執行 `git gc` 命令。 清空篩選規則 + 清空 設定本存放庫 下一步 自訂動作 沒有自訂的動作 + 捨棄所有變更 啟用 [--reflog] 選項 在檔案瀏覽器中開啟 快速搜尋分支/標籤/子模組 @@ -785,6 +786,7 @@ 顯示未追蹤檔案 沒有提交訊息記錄 沒有可套用的提交訊息範本 + 重設作者 請選擇發生衝突的檔案,開啟右鍵選單,選擇合適的解決方式 署名 已暫存 diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj index 5e617561..3fe21b1a 100644 --- a/src/SourceGit.csproj +++ b/src/SourceGit.csproj @@ -2,11 +2,9 @@ WinExe net9.0 - true App.manifest App.ico $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)\\..\\VERSION")) - false true true diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs index 245c1dfe..6c061f65 100644 --- a/src/ViewModels/ChangeTreeNode.cs +++ b/src/ViewModels/ChangeTreeNode.cs @@ -17,6 +17,16 @@ namespace SourceGit.ViewModels get => Change == null; } + public bool ShowConflictMarker + { + get => Change is { IsConflicted: true }; + } + + public string ConflictMarker + { + get => Change?.ConflictMarker ?? string.Empty; + } + public bool IsExpanded { get => _isExpanded; diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index bd4f2284..959cc3fe 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -452,7 +452,9 @@ namespace SourceGit.ViewModels var selected = await storageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) { - var saveTo = Path.Combine(selected[0].Path.LocalPath, Path.GetFileName(file.Path)); + var folder = selected[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + var saveTo = Path.Combine(folderPath, Path.GetFileName(file.Path)); Commands.SaveRevisionFile.Run(_repo.FullPath, _commit.SHA, file.Path, saveTo); } } diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index add365a3..bf93b5bc 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -25,6 +25,16 @@ namespace SourceGit.ViewModels public class Conflict { + public string Marker + { + get => _change.ConflictMarker; + } + + public string Description + { + get => _change.ConflictDesc; + } + public object Theirs { get; @@ -41,7 +51,13 @@ namespace SourceGit.ViewModels { get; private set; - } + } = false; + + public bool CanUseExternalMergeTool + { + get; + private set; + } = false; public Conflict(Repository repo, WorkingCopy wc, Models.Change change) { @@ -49,7 +65,11 @@ namespace SourceGit.ViewModels _change = change; var isSubmodule = repo.Submodules.Find(x => x.Path.Equals(change.Path, StringComparison.Ordinal)) != null; - IsResolved = !isSubmodule && new Commands.IsConflictResolved(repo.FullPath, change).Result(); + if (!isSubmodule && (_change.ConflictReason == Models.ConflictReason.BothAdded || _change.ConflictReason == Models.ConflictReason.BothModified)) + { + CanUseExternalMergeTool = true; + IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).Result(); + } var context = wc.InProgressContext; if (context is CherryPickInProgress cherryPick) diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index 2057a13f..5ca1ba5d 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -333,10 +333,12 @@ namespace SourceGit.ViewModels { log = _repo.CreateLog("Save as Patch"); + var folder = picker[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); var succ = false; for (var i = 0; i < selected.Count; i++) { - var saveTo = GetPatchFileName(picker[0].Path.LocalPath, selected[i], i); + var saveTo = GetPatchFileName(folderPath, selected[i], i); succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, selected[i].SHA, saveTo).Use(log).Exec()); if (!succ) break; @@ -691,8 +693,10 @@ namespace SourceGit.ViewModels { log = _repo.CreateLog("Save as Patch"); - var saveTo = GetPatchFileName(selected[0].Path.LocalPath, commit); - var succ = new Commands.FormatPatch(_repo.FullPath, commit.SHA, saveTo).Use(log).Exec(); + var folder = selected[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + var saveTo = GetPatchFileName(folderPath, commit); + var succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, commit.SHA, saveTo).Use(log).Exec()); if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); } diff --git a/src/ViewModels/InteractiveRebase.cs b/src/ViewModels/InteractiveRebase.cs index 1c4d4480..7811014a 100644 --- a/src/ViewModels/InteractiveRebase.cs +++ b/src/ViewModels/InteractiveRebase.cs @@ -178,7 +178,7 @@ namespace SourceGit.ViewModels if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup) return; } - + item.Action = action; UpdateItems(); } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index b02cf36a..fcc4a853 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -639,7 +639,7 @@ namespace SourceGit.ViewModels Task.Run(RefreshWorktrees); Task.Run(RefreshWorkingCopyChanges); Task.Run(RefreshStashes); - + Task.Run(() => { var config = new Commands.Config(_fullpath).ListAll(); @@ -1766,6 +1766,18 @@ namespace SourceGit.ViewModels return menu; } + public void DiscardAllChanges() + { + if (CanCreatePopup()) + ShowPopup(new Discard(this)); + } + + public void ClearStashes() + { + if (CanCreatePopup()) + ShowPopup(new ClearStashes(this)); + } + public ContextMenu CreateContextMenuForLocalBranch(Models.Branch branch) { var menu = new ContextMenu(); @@ -1785,19 +1797,6 @@ namespace SourceGit.ViewModels { if (!IsBare) { - var discard = new MenuItem(); - discard.Header = App.Text("BranchCM.DiscardAll"); - discard.Icon = App.CreateMenuIcon("Icons.Undo"); - discard.Click += (_, e) => - { - if (CanCreatePopup()) - ShowPopup(new Discard(this)); - e.Handled = true; - }; - - menu.Items.Add(discard); - menu.Items.Add(new MenuItem() { Header = "-" }); - if (!string.IsNullOrEmpty(branch.Upstream)) { var upstream = branch.Upstream.Substring(13); @@ -1828,6 +1827,7 @@ namespace SourceGit.ViewModels }; menu.Items.Add(fastForward); + menu.Items.Add(new MenuItem() { Header = "-" }); menu.Items.Add(pull); } } @@ -1866,21 +1866,6 @@ namespace SourceGit.ViewModels }; menu.Items.Add(fastForward); - var selectedCommit = (_histories?.DetailContext as CommitDetail)?.Commit; - if (selectedCommit != null && !selectedCommit.SHA.Equals(branch.Head, StringComparison.Ordinal)) - { - var move = new MenuItem(); - move.Header = App.Text("BranchCM.ResetToSelectedCommit", branch.Name, selectedCommit.SHA.Substring(0, 10)); - move.Icon = App.CreateMenuIcon("Icons.Reset"); - move.Click += (_, e) => - { - if (CanCreatePopup()) - ShowPopup(new ResetWithoutCheckout(this, branch, selectedCommit)); - e.Handled = true; - }; - menu.Items.Add(move); - } - var fetchInto = new MenuItem(); fetchInto.Header = App.Text("BranchCM.FetchInto", upstream.FriendlyName, branch.Name); fetchInto.Icon = App.CreateMenuIcon("Icons.Fetch"); @@ -1924,15 +1909,34 @@ namespace SourceGit.ViewModels menu.Items.Add(rebase); } - var compareWithHead = new MenuItem(); - compareWithHead.Header = App.Text("BranchCM.CompareWithHead"); - compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare"); - compareWithHead.Click += (_, _) => + if (worktree == null) + { + var selectedCommit = (_histories?.DetailContext as CommitDetail)?.Commit; + if (selectedCommit != null && !selectedCommit.SHA.Equals(branch.Head, StringComparison.Ordinal)) + { + var move = new MenuItem(); + move.Header = App.Text("BranchCM.ResetToSelectedCommit", branch.Name, selectedCommit.SHA.Substring(0, 10)); + move.Icon = App.CreateMenuIcon("Icons.Reset"); + move.Click += (_, e) => + { + if (CanCreatePopup()) + ShowPopup(new ResetWithoutCheckout(this, branch, selectedCommit)); + e.Handled = true; + }; + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(move); + } + } + + var compareWithCurrent = new MenuItem(); + compareWithCurrent.Header = App.Text("BranchCM.CompareWithCurrent", _currentBranch.Name); + compareWithCurrent.Icon = App.CreateMenuIcon("Icons.Compare"); + compareWithCurrent.Click += (_, _) => { App.ShowWindow(new BranchCompare(_fullpath, branch, _currentBranch), false); }; menu.Items.Add(new MenuItem() { Header = "-" }); - menu.Items.Add(compareWithHead); + menu.Items.Add(compareWithCurrent); if (_localChangesCount > 0) { diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index 82fa3169..72dd9e58 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -37,9 +37,11 @@ namespace SourceGit.ViewModels var log = _repo.CreateLog("Reword HEAD"); Use(log); + var signOff = _repo.Settings.EnableSignOffForCommit; return Task.Run(() => { - var succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Use(log).Run(); + // For reword (only changes the commit message), disable `--reset-author` + var succ = new Commands.Commit(_repo.FullPath, _message, signOff, true, false).Use(log).Run(); log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index fa9d98e1..8e9bae8b 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -34,6 +34,7 @@ namespace SourceGit.ViewModels return Task.Run(() => { + var signOff = _repo.Settings.EnableSignOffForCommit; var autoStashed = false; var succ = false; @@ -52,7 +53,7 @@ namespace SourceGit.ViewModels succ = new Commands.Reset(_repo.FullPath, Target.SHA, "--soft").Use(log).Exec(); if (succ) - succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Use(log).Run(); + succ = new Commands.Commit(_repo.FullPath, _message, signOff, true, false).Use(log).Run(); if (succ && autoStashed) new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); diff --git a/src/ViewModels/StashChanges.cs b/src/ViewModels/StashChanges.cs index 0b4fa7db..11e449fb 100644 --- a/src/ViewModels/StashChanges.cs +++ b/src/ViewModels/StashChanges.cs @@ -117,16 +117,16 @@ namespace SourceGit.ViewModels foreach (var c in changes) paths.Add(c.Path); - var tmpFile = Path.GetTempFileName(); - File.WriteAllLines(tmpFile, paths); - succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, tmpFile, KeepIndex); - File.Delete(tmpFile); + var pathSpecFile = Path.GetTempFileName(); + File.WriteAllLines(pathSpecFile, paths); + succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, pathSpecFile, KeepIndex); + File.Delete(pathSpecFile); } else { - for (int i = 0; i < changes.Count; i += 10) + for (int i = 0; i < changes.Count; i += 32) { - var count = Math.Min(10, changes.Count - i); + var count = Math.Min(32, changes.Count - i); var step = changes.GetRange(i, count); succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, step, KeepIndex); if (!succ) diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index b974d427..5449e4c1 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -53,34 +53,32 @@ namespace SourceGit.ViewModels if (value == null) { Changes = null; + _untracked.Clear(); } else { Task.Run(() => { - var changes = null as List; + var changes = new Commands.CompareRevisions(_repo.FullPath, $"{value.SHA}^", value.SHA).Result(); + var untracked = new List(); - if (Native.OS.GitVersion >= Models.GitVersions.STASH_SHOW_WITH_UNTRACKED) + if (value.Parents.Count == 3) { - changes = new Commands.QueryStashChanges(_repo.FullPath, value.Name).Result(); - } - else - { - changes = new Commands.CompareRevisions(_repo.FullPath, $"{value.SHA}^", value.SHA).Result(); - if (value.Parents.Count == 3) - { - var untracked = new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, value.Parents[2]).Result(); - var needSort = changes.Count > 0; + untracked = new Commands.CompareRevisions(_repo.FullPath, Models.Commit.EmptyTreeSHA1, value.Parents[2]).Result(); + var needSort = changes.Count > 0 && untracked.Count > 0; - foreach (var c in untracked) - changes.Add(c); + foreach (var c in untracked) + changes.Add(c); - if (needSort) - changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal)); - } + if (needSort) + changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal)); } - Dispatcher.UIThread.Invoke(() => Changes = changes); + Dispatcher.UIThread.Invoke(() => + { + _untracked = untracked; + Changes = changes; + }); }); } } @@ -106,7 +104,7 @@ namespace SourceGit.ViewModels { if (value == null) DiffContext = null; - else if (value.Index == Models.ChangeState.Added && _selectedStash.Parents.Count == 3) + else if (_untracked.Contains(value)) DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], value), _diffContext); else DiffContext = new DiffContext(_repo.FullPath, new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, value), _diffContext); @@ -129,6 +127,7 @@ namespace SourceGit.ViewModels { _stashes?.Clear(); _changes?.Clear(); + _untracked.Clear(); _repo = null; _selectedStash = null; @@ -181,7 +180,7 @@ namespace SourceGit.ViewModels var opts = new List(); foreach (var c in _changes) { - if (c.Index == Models.ChangeState.Added && _selectedStash.Parents.Count == 3) + if (_untracked.Contains(c)) opts.Add(new Models.DiffOption(Models.Commit.EmptyTreeSHA1, _selectedStash.Parents[2], c)); else opts.Add(new Models.DiffOption(_selectedStash.Parents[0], _selectedStash.SHA, c)); @@ -273,12 +272,6 @@ namespace SourceGit.ViewModels return menu; } - public void Clear() - { - if (_repo.CanCreatePopup()) - _repo.ShowPopup(new ClearStashes(_repo)); - } - public void ClearSearchFilter() { SearchFilter = string.Empty; @@ -304,11 +297,12 @@ namespace SourceGit.ViewModels } private Repository _repo = null; - private List _stashes = new List(); - private List _visibleStashes = new List(); + private List _stashes = []; + private List _visibleStashes = []; private string _searchFilter = string.Empty; private Models.Stash _selectedStash = null; private List _changes = null; + private List _untracked = []; private Models.Change _selectedChange = null; private DiffContext _diffContext = null; } diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 7c2d95f9..6469b564 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -91,6 +91,7 @@ namespace SourceGit.ViewModels else { CommitMessage = string.Empty; + ResetAuthor = false; } Staged = GetStagedChanges(); @@ -100,6 +101,12 @@ namespace SourceGit.ViewModels } } + public bool ResetAuthor + { + get => _resetAuthor; + set => SetProperty(ref _resetAuthor, value); + } + public string Filter { get => _filter; @@ -381,7 +388,9 @@ namespace SourceGit.ViewModels if (!change.IsConflicted) continue; - if (change.WorkTree == Models.ChangeState.Deleted) + if (change.ConflictReason == Models.ConflictReason.BothDeleted || + change.ConflictReason == Models.ConflictReason.DeletedByThem || + change.ConflictReason == Models.ConflictReason.AddedByUs) { var fullpath = Path.Combine(_repo.FullPath, change.Path); if (File.Exists(fullpath)) @@ -403,7 +412,12 @@ namespace SourceGit.ViewModels } if (needStage.Count > 0) - await Task.Run(() => new Commands.Add(_repo.FullPath, needStage).Use(log).Exec()); + { + var pathSpecFile = Path.GetTempFileName(); + await File.WriteAllLinesAsync(pathSpecFile, needStage); + await Task.Run(() => new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).Exec()); + File.Delete(pathSpecFile); + } log.Complete(); _repo.MarkWorkingCopyDirtyManually(); @@ -423,7 +437,9 @@ namespace SourceGit.ViewModels if (!change.IsConflicted) continue; - if (change.Index == Models.ChangeState.Deleted) + if (change.ConflictReason == Models.ConflictReason.BothDeleted || + change.ConflictReason == Models.ConflictReason.DeletedByUs || + change.ConflictReason == Models.ConflictReason.AddedByThem) { var fullpath = Path.Combine(_repo.FullPath, change.Path); if (File.Exists(fullpath)) @@ -445,7 +461,12 @@ namespace SourceGit.ViewModels } if (needStage.Count > 0) - await Task.Run(() => new Commands.Add(_repo.FullPath, needStage).Use(log).Exec()); + { + var pathSpecFile = Path.GetTempFileName(); + await File.WriteAllLinesAsync(pathSpecFile, needStage); + await Task.Run(() => new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).Exec()); + File.Delete(pathSpecFile); + } log.Complete(); _repo.MarkWorkingCopyDirtyManually(); @@ -456,7 +477,7 @@ namespace SourceGit.ViewModels { var toolType = Preferences.Instance.ExternalMergeToolType; var toolPath = Preferences.Instance.ExternalMergeToolPath; - var file = change?.Path; // NOTE: With no arg, mergetool runs on on every file with merge conflicts! + var file = change?.Path; // NOTE: With no arg, mergetool runs on every file with merge conflicts! await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, toolType, toolPath, file)); } @@ -759,7 +780,7 @@ namespace SourceGit.ViewModels byParentFolder.IsVisible = !isRooted; byParentFolder.Click += (_, e) => { - var dir = Path.GetDirectoryName(change.Path).Replace("\\", "/"); + var dir = Path.GetDirectoryName(change.Path)!.Replace("\\", "/"); Commands.GitIgnore.Add(_repo.FullPath, dir + "/"); e.Handled = true; }; @@ -781,7 +802,7 @@ namespace SourceGit.ViewModels byExtensionInSameFolder.IsVisible = !isRooted; byExtensionInSameFolder.Click += (_, e) => { - var dir = Path.GetDirectoryName(change.Path).Replace("\\", "/"); + var dir = Path.GetDirectoryName(change.Path)!.Replace("\\", "/"); Commands.GitIgnore.Add(_repo.FullPath, $"{dir}/*{extension}"); e.Handled = true; }; @@ -1608,30 +1629,19 @@ namespace SourceGit.ViewModels { await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).Exec()); } - else if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE) - { - var paths = new List(); - foreach (var c in changes) - paths.Add(c.Path); - - var tmpFile = Path.GetTempFileName(); - File.WriteAllLines(tmpFile, paths); - await Task.Run(() => new Commands.Add(_repo.FullPath, tmpFile).Use(log).Exec()); - File.Delete(tmpFile); - } else { var paths = new List(); foreach (var c in changes) paths.Add(c.Path); - for (int i = 0; i < count; i += 10) - { - var step = paths.GetRange(i, Math.Min(10, count - i)); - await Task.Run(() => new Commands.Add(_repo.FullPath, step).Use(log).Exec()); - } + var pathSpecFile = Path.GetTempFileName(); + await File.WriteAllLinesAsync(pathSpecFile, paths); + await Task.Run(() => new Commands.Add(_repo.FullPath, pathSpecFile).Use(log).Exec()); + File.Delete(pathSpecFile); } log.Complete(); + _repo.MarkWorkingCopyDirtyManually(); _repo.SetWatcherEnabled(true); IsStaging = false; @@ -1655,19 +1665,23 @@ namespace SourceGit.ViewModels log.AppendLine("$ git update-index --index-info "); await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec()); } - else if (count == _staged.Count) - { - await Task.Run(() => new Commands.Reset(_repo.FullPath).Use(log).Exec()); - } else { - for (int i = 0; i < count; i += 10) + var paths = new List(); + foreach (var c in changes) { - var step = changes.GetRange(i, Math.Min(10, count - i)); - await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Use(log).Exec()); + paths.Add(c.Path); + if (c.Index == Models.ChangeState.Renamed) + paths.Add(c.OriginalPath); } + + var pathSpecFile = Path.GetTempFileName(); + await File.WriteAllLinesAsync(pathSpecFile, paths); + await Task.Run(() => new Commands.Restore(_repo.FullPath, pathSpecFile, true).Use(log).Exec()); + File.Delete(pathSpecFile); } log.Complete(); + _repo.MarkWorkingCopyDirtyManually(); _repo.SetWatcherEnabled(true); IsUnstaging = false; @@ -1717,6 +1731,7 @@ namespace SourceGit.ViewModels _repo.Settings.PushCommitMessage(_commitMessage); _repo.SetWatcherEnabled(false); + var signOff = _repo.Settings.EnableSignOffForCommit; var log = _repo.CreateLog("Commit"); Task.Run(() => { @@ -1725,7 +1740,7 @@ namespace SourceGit.ViewModels succ = new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).Exec(); if (succ) - succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend, _repo.Settings.EnableSignOffForCommit).Use(log).Run(); + succ = new Commands.Commit(_repo.FullPath, _commitMessage, signOff, _useAmend, _resetAuthor).Use(log).Run(); log.Complete(); @@ -1762,7 +1777,7 @@ namespace SourceGit.ViewModels { if (old.Count != cur.Count) return true; - + var oldMap = new Dictionary(); foreach (var c in old) oldMap.Add(c.Path, c); @@ -1785,6 +1800,7 @@ namespace SourceGit.ViewModels private bool _isUnstaging = false; private bool _isCommitting = false; private bool _useAmend = false; + private bool _resetAuthor = false; private bool _hasRemotes = false; private List _cached = []; private List _unstaged = []; diff --git a/src/Views/AddWorktree.axaml.cs b/src/Views/AddWorktree.axaml.cs index 30369247..dad947de 100644 --- a/src/Views/AddWorktree.axaml.cs +++ b/src/Views/AddWorktree.axaml.cs @@ -23,7 +23,11 @@ namespace SourceGit.Views { var selected = await toplevel.StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) - TxtLocation.Text = selected[0].Path.LocalPath; + { + var folder = selected[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + TxtLocation.Text = folderPath; + } } catch (Exception exception) { diff --git a/src/Views/ChangeCollectionView.axaml b/src/Views/ChangeCollectionView.axaml index 36de5685..43af3a9a 100644 --- a/src/Views/ChangeCollectionView.axaml +++ b/src/Views/ChangeCollectionView.axaml @@ -37,11 +37,11 @@ SelectionChanged="OnRowSelectionChanged"> - + DataContextChanged="OnRowDataContextChanged"> - - + + + + + + @@ -77,17 +85,17 @@ + DataContextChanged="OnRowDataContextChanged"> - + + + + + DataContextChanged="OnRowDataContextChanged"> - + + + + diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 00499fce..6623a60b 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Documents; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Media; using Avalonia.VisualTree; namespace SourceGit.Views @@ -86,7 +88,7 @@ namespace SourceGit.Views } public static readonly StyledProperty AutoSelectFirstChangeProperty = - AvaloniaProperty.Register(nameof(AutoSelectFirstChange), false); + AvaloniaProperty.Register(nameof(AutoSelectFirstChange)); public bool AutoSelectFirstChange { @@ -229,6 +231,28 @@ namespace SourceGit.Views UpdateSelection(); } + private void OnRowDataContextChanged(object sender, EventArgs e) + { + if (sender is not Control control) + return; + + if (control.DataContext is ViewModels.ChangeTreeNode node) + { + if (node.Change is { } c) + UpdateRowTips(control, c); + else + ToolTip.SetTip(control, node.FullPath); + } + else if (control.DataContext is Models.Change change) + { + UpdateRowTips(control, change); + } + else + { + ToolTip.SetTip(control, null); + } + } + private void OnRowDoubleTapped(object sender, TappedEventArgs e) { var grid = sender as Grid; @@ -466,6 +490,21 @@ namespace SourceGit.Views } } + private void UpdateRowTips(Control control, Models.Change change) + { + var tip = new TextBlock() { TextWrapping = TextWrapping.Wrap }; + tip.Inlines!.Add(new Run(change.Path)); + tip.Inlines!.Add(new Run(" • ") { Foreground = Brushes.Gray }); + tip.Inlines!.Add(new Run(IsUnstagedChange ? change.WorkTreeDesc : change.IndexDesc) { Foreground = Brushes.Gray }); + if (change.IsConflicted) + { + tip.Inlines!.Add(new Run(" • ") { Foreground = Brushes.Gray }); + tip.Inlines!.Add(new Run(change.ConflictDesc) { Foreground = Brushes.Gray }); + } + + ToolTip.SetTip(control, tip); + } + private bool _disableSelectionChangingEvent = false; } } diff --git a/src/Views/ChangeStatusIcon.cs b/src/Views/ChangeStatusIcon.cs index c1185174..d66ac11d 100644 --- a/src/Views/ChangeStatusIcon.cs +++ b/src/Views/ChangeStatusIcon.cs @@ -9,6 +9,7 @@ namespace SourceGit.Views { public class ChangeStatusIcon : Control { + private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "★", "!"]; private static readonly IBrush[] BACKGROUNDS = [ Brushes.Transparent, new LinearGradientBrush @@ -56,9 +57,6 @@ namespace SourceGit.Views Brushes.OrangeRed, ]; - private static readonly string[] INDICATOR = ["?", "±", "T", "+", "−", "➜", "❏", "★", "!"]; - private static readonly string[] TIPS = ["Unknown", "Modified", "Type Changed", "Added", "Deleted", "Renamed", "Copied", "Untracked", "Conflict"]; - public static readonly StyledProperty IsUnstagedChangeProperty = AvaloniaProperty.Register(nameof(IsUnstagedChange)); @@ -116,22 +114,7 @@ namespace SourceGit.Views base.OnPropertyChanged(change); if (change.Property == IsUnstagedChangeProperty || change.Property == ChangeProperty) - { - var isUnstaged = IsUnstagedChange; - var c = Change; - if (c == null) - { - ToolTip.SetTip(this, null); - return; - } - - if (isUnstaged) - ToolTip.SetTip(this, TIPS[(int)c.WorkTree]); - else - ToolTip.SetTip(this, TIPS[(int)c.Index]); - InvalidateVisual(); - } } } } diff --git a/src/Views/Clone.axaml.cs b/src/Views/Clone.axaml.cs index 1c299211..9316721a 100644 --- a/src/Views/Clone.axaml.cs +++ b/src/Views/Clone.axaml.cs @@ -23,7 +23,11 @@ namespace SourceGit.Views { var selected = await toplevel.StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) - TxtParentFolder.Text = selected[0].Path.LocalPath; + { + var folder = selected[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + TxtParentFolder.Text = folderPath; + } } catch (Exception exception) { diff --git a/src/Views/ConfigureWorkspace.axaml.cs b/src/Views/ConfigureWorkspace.axaml.cs index 43b28e2f..06294caf 100644 --- a/src/Views/ConfigureWorkspace.axaml.cs +++ b/src/Views/ConfigureWorkspace.axaml.cs @@ -32,7 +32,9 @@ namespace SourceGit.Views var selected = await StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) { - workspace.Selected.DefaultCloneDir = selected[0].Path.LocalPath; + var folder = selected[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + workspace.Selected.DefaultCloneDir = folderPath; } } catch (Exception ex) diff --git a/src/Views/Conflict.axaml b/src/Views/Conflict.axaml index 6e8aceb3..71bb0b41 100644 --- a/src/Views/Conflict.axaml +++ b/src/Views/Conflict.axaml @@ -14,7 +14,15 @@ - + + + + + + + + + @@ -97,27 +105,27 @@ - - - - + + + + - - - - + + diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 27305aac..15427b93 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -19,20 +19,12 @@ - + - diff --git a/src/Views/Statistics.axaml b/src/Views/Statistics.axaml index 577849df..a2d18393 100644 --- a/src/Views/Statistics.axaml +++ b/src/Views/Statistics.axaml @@ -162,7 +162,9 @@ - + = 36 ? 16 : 4); - var right = (chunk.Combined || !chunk.IsOldSide) ? 16 : (v.Bounds.Width * 0.5f) + 16; + var top = chunk.Y + (chunk.Height >= 36 ? 8 : 2); + var right = (chunk.Combined || !chunk.IsOldSide) ? 26 : (v.Bounds.Width * 0.5f) + 26; v.Popup.Margin = new Thickness(0, top, right, 0); v.Popup.IsVisible = true; }); @@ -1868,7 +1868,7 @@ namespace SourceGit.Views if (!selection.HasLeftChanges) { - new Commands.Add(repo.FullPath, [change.Path]).Exec(); + new Commands.Add(repo.FullPath, change).Exec(); } else { @@ -1929,7 +1929,7 @@ namespace SourceGit.Views if (change.DataForAmend != null) new Commands.UnstageChangesForAmend(repo.FullPath, [change]).Exec(); else - new Commands.Reset(repo.FullPath, [change]).Exec(); + new Commands.Restore(repo.FullPath, change).Exec(); } else { diff --git a/src/Views/WelcomeToolbar.axaml.cs b/src/Views/WelcomeToolbar.axaml.cs index 9e170c1a..e2a130f8 100644 --- a/src/Views/WelcomeToolbar.axaml.cs +++ b/src/Views/WelcomeToolbar.axaml.cs @@ -35,7 +35,11 @@ namespace SourceGit.Views { var selected = await topLevel.StorageProvider.OpenFolderPickerAsync(options); if (selected.Count == 1) - ViewModels.Welcome.Instance.OpenOrInitRepository(selected[0].Path.LocalPath, null, false); + { + var folder = selected[0]; + var folderPath = folder is { Path: { IsAbsoluteUri: true } path } ? path.LocalPath : folder?.Path.ToString(); + ViewModels.Welcome.Instance.OpenOrInitRepository(folderPath, null, false); + } } catch (Exception exception) { diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index ee8619f3..8c600956 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -235,7 +235,7 @@ - + - + + - - -