diff --git a/src/App.Utils.cs b/src/App.Utils.cs new file mode 100644 index 00000000..4fc91a5b --- /dev/null +++ b/src/App.Utils.cs @@ -0,0 +1,11 @@ +namespace SourceGit +{ + public static class CommandExtensions + { + public static T Use(this T cmd, Models.ICommandLog log) where T : Commands.Command + { + cmd.Log = log; + return cmd; + } + } +} diff --git a/src/Commands/Archive.cs b/src/Commands/Archive.cs index d4f6241c..5e0919f7 100644 --- a/src/Commands/Archive.cs +++ b/src/Commands/Archive.cs @@ -1,23 +1,12 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Archive : Command { - public Archive(string repo, string revision, string saveTo, Action outputHandler) + public Archive(string repo, string revision, string saveTo) { WorkingDirectory = repo; Context = repo; Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}"; - TraitErrorAsOutput = true; - _outputHandler = outputHandler; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler; } } diff --git a/src/Commands/AssumeUnchanged.cs b/src/Commands/AssumeUnchanged.cs index 1898122a..28f78280 100644 --- a/src/Commands/AssumeUnchanged.cs +++ b/src/Commands/AssumeUnchanged.cs @@ -1,75 +1,14 @@ -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace SourceGit.Commands +namespace SourceGit.Commands { - public partial class AssumeUnchanged + public class AssumeUnchanged : Command { - [GeneratedRegex(@"^(\w)\s+(.+)$")] - private static partial Regex REG_PARSE(); - - class ViewCommand : Command + public AssumeUnchanged(string repo, string file, bool bAdd) { - public ViewCommand(string repo) - { - WorkingDirectory = repo; - Args = "ls-files -v"; - RaiseError = false; - } + var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged"; - public List Result() - { - Exec(); - return _outs; - } - - protected override void OnReadline(string line) - { - var match = REG_PARSE().Match(line); - if (!match.Success) - return; - - if (match.Groups[1].Value == "h") - { - _outs.Add(match.Groups[2].Value); - } - } - - private readonly List _outs = new List(); + WorkingDirectory = repo; + Context = repo; + Args = $"update-index {mode} -- \"{file}\""; } - - class ModCommand : Command - { - public ModCommand(string repo, string file, bool bAdd) - { - var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged"; - - WorkingDirectory = repo; - Context = repo; - Args = $"update-index {mode} -- \"{file}\""; - } - } - - public AssumeUnchanged(string repo) - { - _repo = repo; - } - - public List View() - { - return new ViewCommand(_repo).Result(); - } - - public void Add(string file) - { - new ModCommand(_repo, file, true).Exec(); - } - - public void Remove(string file) - { - new ModCommand(_repo, file, false).Exec(); - } - - private readonly string _repo; } } diff --git a/src/Commands/Blame.cs b/src/Commands/Blame.cs index 291249be..0bb45c98 100644 --- a/src/Commands/Blame.cs +++ b/src/Commands/Blame.cs @@ -21,10 +21,17 @@ namespace SourceGit.Commands public Models.BlameData Result() { - var succ = Exec(); - if (!succ) + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return _result; + + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) { - return new Models.BlameData(); + ParseLine(line); + + if (_result.IsBinary) + break; } if (_needUnifyCommitSHA) @@ -42,13 +49,8 @@ namespace SourceGit.Commands return _result; } - protected override void OnReadline(string line) + private void ParseLine(string line) { - if (_result.IsBinary) - return; - if (string.IsNullOrEmpty(line)) - return; - if (line.IndexOf('\0', StringComparison.Ordinal) >= 0) { _result.IsBinary = true; diff --git a/src/Commands/Branch.cs b/src/Commands/Branch.cs index 2dc8a98d..72dadc17 100644 --- a/src/Commands/Branch.cs +++ b/src/Commands/Branch.cs @@ -11,29 +11,32 @@ return cmd.ReadToEnd().StdOut.Trim(); } - public static bool Create(string repo, string name, string basedOn) + public static bool Create(string repo, string name, string basedOn, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch {name} {basedOn}"; + cmd.Log = log; return cmd.Exec(); } - public static bool Rename(string repo, string name, string to) + public static bool Rename(string repo, string name, string to, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch -M {name} {to}"; + cmd.Log = log; return cmd.Exec(); } - public static bool SetUpstream(string repo, string name, string upstream) + public static bool SetUpstream(string repo, string name, string upstream, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; + cmd.Log = log; if (string.IsNullOrEmpty(upstream)) cmd.Args = $"branch {name} --unset-upstream"; @@ -43,25 +46,27 @@ return cmd.Exec(); } - public static bool DeleteLocal(string repo, string name) + public static bool DeleteLocal(string repo, string name, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch -D {name}"; + cmd.Log = log; return cmd.Exec(); } - public static bool DeleteRemote(string repo, string remote, string name) + public static bool DeleteRemote(string repo, string remote, string name, Models.ICommandLog log) { bool exists = new Remote(repo).HasBranch(remote, name); if (exists) - return new Push(repo, remote, $"refs/heads/{name}", true).Exec(); + return new Push(repo, remote, $"refs/heads/{name}", true).Use(log).Exec(); var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"branch -D -r {remote}/{name}"; + cmd.Log = log; return cmd.Exec(); } } diff --git a/src/Commands/Checkout.cs b/src/Commands/Checkout.cs index 306d62ff..c39c28ae 100644 --- a/src/Commands/Checkout.cs +++ b/src/Commands/Checkout.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; namespace SourceGit.Commands @@ -12,19 +11,15 @@ namespace SourceGit.Commands Context = repo; } - public bool Branch(string branch, Action onProgress) + public bool Branch(string branch) { Args = $"checkout --recurse-submodules --progress {branch}"; - TraitErrorAsOutput = true; - _outputHandler = onProgress; return Exec(); } - public bool Branch(string branch, string basedOn, Action onProgress) + public bool Branch(string branch, string basedOn) { Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}"; - TraitErrorAsOutput = true; - _outputHandler = onProgress; return Exec(); } @@ -62,19 +57,10 @@ namespace SourceGit.Commands return Exec(); } - public bool Commit(string commitId, Action onProgress) + public bool Commit(string commitId) { Args = $"checkout --detach --progress {commitId}"; - TraitErrorAsOutput = true; - _outputHandler = onProgress; return Exec(); } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private Action _outputHandler; } } diff --git a/src/Commands/Clone.cs b/src/Commands/Clone.cs index f2faed14..efec264b 100644 --- a/src/Commands/Clone.cs +++ b/src/Commands/Clone.cs @@ -1,16 +1,11 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Clone : Command { - private readonly Action _notifyProgress; - - public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action ouputHandler) + public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs) { Context = ctx; WorkingDirectory = path; - TraitErrorAsOutput = true; SSHKey = sshKey; Args = "clone --progress --verbose "; @@ -21,13 +16,6 @@ namespace SourceGit.Commands if (!string.IsNullOrEmpty(localName)) Args += localName; - - _notifyProgress = ouputHandler; - } - - protected override void OnReadline(string line) - { - _notifyProgress?.Invoke(line); } } } diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 0fef1235..699cc120 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -32,7 +32,7 @@ namespace SourceGit.Commands public string SSHKey { get; set; } = string.Empty; public string Args { get; set; } = string.Empty; public bool RaiseError { get; set; } = true; - public bool TraitErrorAsOutput { get; set; } = false; + public Models.ICommandLog Log { get; set; } = null; public bool Exec() { @@ -40,10 +40,14 @@ namespace SourceGit.Commands var errs = new List(); var proc = new Process() { StartInfo = start }; + Log?.AppendLine($"$ git {Args}\n"); + proc.OutputDataReceived += (_, e) => { - if (e.Data != null) - OnReadline(e.Data); + if (e.Data == null) + return; + + Log?.AppendLine(e.Data); }; proc.ErrorDataReceived += (_, e) => @@ -54,8 +58,7 @@ namespace SourceGit.Commands return; } - if (TraitErrorAsOutput) - OnReadline(e.Data); + Log?.AppendLine(e.Data); // Ignore progress messages if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal)) @@ -97,6 +100,7 @@ namespace SourceGit.Commands if (RaiseError) Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message)); + Log?.AppendLine(string.Empty); return false; } @@ -114,6 +118,7 @@ namespace SourceGit.Commands int exitCode = proc.ExitCode; proc.Close(); + Log?.AppendLine(string.Empty); if (!CancellationToken.IsCancellationRequested && exitCode != 0) { @@ -162,11 +167,6 @@ namespace SourceGit.Commands return rs; } - protected virtual void OnReadline(string line) - { - // Implemented by derived class - } - private ProcessStartInfo CreateGitStartInfo() { var start = new ProcessStartInfo(); diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs index cb086793..5be08cef 100644 --- a/src/Commands/Commit.cs +++ b/src/Commands/Commit.cs @@ -11,7 +11,6 @@ namespace SourceGit.Commands WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; Args = $"commit --allow-empty --file=\"{_tmpFile}\""; if (amend) Args += " --amend --no-edit"; diff --git a/src/Commands/CompareRevisions.cs b/src/Commands/CompareRevisions.cs index c3206767..7b4a496d 100644 --- a/src/Commands/CompareRevisions.cs +++ b/src/Commands/CompareRevisions.cs @@ -31,12 +31,19 @@ namespace SourceGit.Commands public List Result() { - Exec(); + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return _changes; + + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + ParseLine(line); + _changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal)); return _changes; } - protected override void OnReadline(string line) + private void ParseLine(string line) { var match = REG_FORMAT().Match(line); if (!match.Success) diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs index a279bb84..cd7858a6 100644 --- a/src/Commands/Discard.cs +++ b/src/Commands/Discard.cs @@ -5,13 +5,13 @@ namespace SourceGit.Commands { public static class Discard { - public static void All(string repo, bool includeIgnored) + public static void All(string repo, bool includeIgnored, Models.ICommandLog log) { - new Restore(repo).Exec(); - new Clean(repo, includeIgnored).Exec(); + new Restore(repo).Use(log).Exec(); + new Clean(repo, includeIgnored).Use(log).Exec(); } - public static void Changes(string repo, List changes) + public static void Changes(string repo, List changes, Models.ICommandLog log) { var needClean = new List(); var needCheckout = new List(); @@ -27,13 +27,13 @@ namespace SourceGit.Commands for (int i = 0; i < needClean.Count; i += 10) { var count = Math.Min(10, needClean.Count - i); - new Clean(repo, needClean.GetRange(i, count)).Exec(); + new Clean(repo, needClean.GetRange(i, count)).Use(log).Exec(); } for (int i = 0; i < needCheckout.Count; i += 10) { var count = Math.Min(10, needCheckout.Count - i); - new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules").Exec(); + new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules").Use(log).Exec(); } } } diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs index 06ae8cb6..edf2a6dd 100644 --- a/src/Commands/Fetch.cs +++ b/src/Commands/Fetch.cs @@ -1,15 +1,11 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Fetch : Command { - public Fetch(string repo, string remote, bool noTags, bool force, Action outputHandler) + public Fetch(string repo, string remote, bool noTags, bool force) { - _outputHandler = outputHandler; WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "fetch --progress --verbose "; @@ -24,21 +20,12 @@ namespace SourceGit.Commands Args += remote; } - public Fetch(string repo, Models.Branch local, Models.Branch remote, Action outputHandler) + public Fetch(string repo, Models.Branch local, Models.Branch remote) { - _outputHandler = outputHandler; WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey"); Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}"; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler; } } diff --git a/src/Commands/GC.cs b/src/Commands/GC.cs index 393b915e..0b27f487 100644 --- a/src/Commands/GC.cs +++ b/src/Commands/GC.cs @@ -1,23 +1,12 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class GC : Command { - public GC(string repo, Action outputHandler) + public GC(string repo) { - _outputHandler = outputHandler; WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; Args = "gc --prune=now"; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler; } } diff --git a/src/Commands/GitFlow.cs b/src/Commands/GitFlow.cs index 5e05ed83..833d268d 100644 --- a/src/Commands/GitFlow.cs +++ b/src/Commands/GitFlow.cs @@ -35,17 +35,17 @@ namespace SourceGit.Commands config.ContainsKey("gitflow.prefix.hotfix"); } - public static bool Init(string repo, List branches, string master, string develop, string feature, string release, string hotfix, string version) + public static bool Init(string repo, List branches, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log) { var current = branches.Find(x => x.IsCurrent); var masterBranch = branches.Find(x => x.Name == master); if (masterBranch == null && current != null) - Branch.Create(repo, master, current.Head); + Branch.Create(repo, master, current.Head, log); var devBranch = branches.Find(x => x.Name == develop); if (devBranch == null && current != null) - Branch.Create(repo, develop, current.Head); + Branch.Create(repo, develop, current.Head, log); var config = new Config(repo); config.Set("gitflow.branch.master", master); @@ -61,6 +61,7 @@ namespace SourceGit.Commands init.WorkingDirectory = repo; init.Context = repo; init.Args = "flow init -d"; + init.Log = log; return init.Exec(); } @@ -113,7 +114,7 @@ namespace SourceGit.Commands return rs; } - public static bool Start(string repo, string type, string name) + public static bool Start(string repo, string type, string name, Models.ICommandLog log) { if (!SUPPORTED_BRANCH_TYPES.Contains(type)) { @@ -129,10 +130,11 @@ namespace SourceGit.Commands start.WorkingDirectory = repo; start.Context = repo; start.Args = $"flow {type} start {name}"; + start.Log = log; return start.Exec(); } - public static bool Finish(string repo, string type, string name, bool keepBranch) + public static bool Finish(string repo, string type, string name, bool keepBranch, Models.ICommandLog log) { if (!SUPPORTED_BRANCH_TYPES.Contains(type)) { @@ -149,6 +151,7 @@ namespace SourceGit.Commands finish.WorkingDirectory = repo; finish.Context = repo; finish.Args = $"flow {type} finish {option} {name}"; + finish.Log = log; return finish.Exec(); } diff --git a/src/Commands/IsConflictResolved.cs b/src/Commands/IsConflictResolved.cs index 13bc12ac..9b243451 100644 --- a/src/Commands/IsConflictResolved.cs +++ b/src/Commands/IsConflictResolved.cs @@ -10,5 +10,10 @@ Context = repo; Args = $"diff -a --ignore-cr-at-eol --check {opt}"; } + + public bool Result() + { + return ReadToEnd().IsSuccess; + } } } diff --git a/src/Commands/LFS.cs b/src/Commands/LFS.cs index f7e56486..d5fb9dcf 100644 --- a/src/Commands/LFS.cs +++ b/src/Commands/LFS.cs @@ -12,21 +12,13 @@ namespace SourceGit.Commands class SubCmd : Command { - public SubCmd(string repo, string args, Action onProgress) + public SubCmd(string repo, string args, Models.ICommandLog log) { WorkingDirectory = repo; Context = repo; Args = args; - TraitErrorAsOutput = true; - _outputHandler = onProgress; + Log = log; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler; } public LFS(string repo) @@ -49,30 +41,30 @@ namespace SourceGit.Commands return new SubCmd(_repo, "lfs install --local", null).Exec(); } - public bool Track(string pattern, bool isFilenameMode = false) + public bool Track(string pattern, bool isFilenameMode, Models.ICommandLog log) { var opt = isFilenameMode ? "--filename" : ""; - return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", null).Exec(); + return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).Exec(); } - public void Fetch(string remote, Action outputHandler) + public void Fetch(string remote, Models.ICommandLog log) { - new SubCmd(_repo, $"lfs fetch {remote}", outputHandler).Exec(); + new SubCmd(_repo, $"lfs fetch {remote}", log).Exec(); } - public void Pull(string remote, Action outputHandler) + public void Pull(string remote, Models.ICommandLog log) { - new SubCmd(_repo, $"lfs pull {remote}", outputHandler).Exec(); + new SubCmd(_repo, $"lfs pull {remote}", log).Exec(); } - public void Push(string remote, Action outputHandler) + public void Push(string remote, Models.ICommandLog log) { - new SubCmd(_repo, $"lfs push {remote}", outputHandler).Exec(); + new SubCmd(_repo, $"lfs push {remote}", log).Exec(); } - public void Prune(Action outputHandler) + public void Prune(Models.ICommandLog log) { - new SubCmd(_repo, "lfs prune", outputHandler).Exec(); + new SubCmd(_repo, "lfs prune", log).Exec(); } public List Locks(string remote) diff --git a/src/Commands/Merge.cs b/src/Commands/Merge.cs index bd1f3653..b08377b9 100644 --- a/src/Commands/Merge.cs +++ b/src/Commands/Merge.cs @@ -1,26 +1,21 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; namespace SourceGit.Commands { public class Merge : Command { - public Merge(string repo, string source, string mode, Action outputHandler) + public Merge(string repo, string source, string mode) { - _outputHandler = outputHandler; WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; Args = $"merge --progress {source} {mode}"; } - public Merge(string repo, List targets, bool autoCommit, string strategy, Action outputHandler) + public Merge(string repo, List targets, bool autoCommit, string strategy) { - _outputHandler = outputHandler; WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; var builder = new StringBuilder(); builder.Append("merge --progress "); @@ -37,12 +32,5 @@ namespace SourceGit.Commands Args = builder.ToString(); } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler = null; } } diff --git a/src/Commands/Pull.cs b/src/Commands/Pull.cs index 35a6289a..2695b16b 100644 --- a/src/Commands/Pull.cs +++ b/src/Commands/Pull.cs @@ -1,15 +1,11 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Pull : Command { - public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action outputHandler) + public Pull(string repo, string remote, string branch, bool useRebase, bool noTags) { - _outputHandler = outputHandler; WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "pull --verbose --progress "; @@ -21,12 +17,5 @@ namespace SourceGit.Commands Args += $"{remote} {branch}"; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler; } } diff --git a/src/Commands/Push.cs b/src/Commands/Push.cs index dc81f606..8a5fe33c 100644 --- a/src/Commands/Push.cs +++ b/src/Commands/Push.cs @@ -1,16 +1,11 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Push : Command { - public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force, Action onProgress) + public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force) { - _outputHandler = onProgress; - WorkingDirectory = repo; Context = repo; - TraitErrorAsOutput = true; SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); Args = "push --progress --verbose "; @@ -38,12 +33,5 @@ namespace SourceGit.Commands Args += $"{remote} {refname}"; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private readonly Action _outputHandler = null; } } diff --git a/src/Commands/QueryAssumeUnchangedFiles.cs b/src/Commands/QueryAssumeUnchangedFiles.cs new file mode 100644 index 00000000..b5c23b0b --- /dev/null +++ b/src/Commands/QueryAssumeUnchangedFiles.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace SourceGit.Commands +{ + public partial class QueryAssumeUnchangedFiles : Command + { + [GeneratedRegex(@"^(\w)\s+(.+)$")] + private static partial Regex REG_PARSE(); + + public QueryAssumeUnchangedFiles(string repo) + { + WorkingDirectory = repo; + Args = "ls-files -v"; + RaiseError = false; + } + + public List Result() + { + var outs = new List(); + var rs = ReadToEnd(); + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + var match = REG_PARSE().Match(line); + if (!match.Success) + continue; + + if (match.Groups[1].Value == "h") + outs.Add(match.Groups[2].Value); + } + + return outs; + } + } +} diff --git a/src/Commands/QueryCommitChildren.cs b/src/Commands/QueryCommitChildren.cs index 31fb34f8..4e99ce7a 100644 --- a/src/Commands/QueryCommitChildren.cs +++ b/src/Commands/QueryCommitChildren.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace SourceGit.Commands { @@ -14,17 +15,21 @@ namespace SourceGit.Commands public List Result() { - Exec(); - return _lines; - } + var rs = ReadToEnd(); + var outs = new List(); + if (rs.IsSuccess) + { + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + if (line.Contains(_commit)) + outs.Add(line.Substring(0, 40)); + } + } - protected override void OnReadline(string line) - { - if (line.Contains(_commit)) - _lines.Add(line.Substring(0, 40)); + return outs; } private string _commit; - private List _lines = new List(); } } diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs index 9458e5f5..e85e79c9 100644 --- a/src/Commands/QueryLocalChanges.cs +++ b/src/Commands/QueryLocalChanges.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace SourceGit.Commands @@ -18,21 +19,23 @@ namespace SourceGit.Commands public List Result() { - Exec(); - return _changes; - } + var outs = new List(); + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return outs; - protected override void OnReadline(string line) - { - var match = REG_FORMAT().Match(line); - if (!match.Success) - return; - - var change = new Models.Change() { Path = match.Groups[2].Value }; - var status = match.Groups[1].Value; - - switch (status) + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) { + var match = REG_FORMAT().Match(line); + if (!match.Success) + continue; + + var change = new Models.Change() { Path = match.Groups[2].Value }; + var status = match.Groups[1].Value; + + switch (status) + { case " M": change.Set(Models.ChangeState.None, Models.ChangeState.Modified); break; @@ -145,12 +148,14 @@ namespace SourceGit.Commands change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked); break; default: - return; + break; + } + + if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None) + outs.Add(change); } - _changes.Add(change); + return outs; } - - private readonly List _changes = new List(); } } diff --git a/src/Commands/QueryRemotes.cs b/src/Commands/QueryRemotes.cs index b5b41b4a..7afec74d 100644 --- a/src/Commands/QueryRemotes.cs +++ b/src/Commands/QueryRemotes.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; namespace SourceGit.Commands @@ -17,27 +18,31 @@ namespace SourceGit.Commands public List Result() { - Exec(); - return _loaded; - } + var outs = new List(); + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return outs; - protected override void OnReadline(string line) - { - var match = REG_REMOTE().Match(line); - if (!match.Success) - return; - - var remote = new Models.Remote() + var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) { - Name = match.Groups[1].Value, - URL = match.Groups[2].Value, - }; + var match = REG_REMOTE().Match(line); + if (!match.Success) + continue; - if (_loaded.Find(x => x.Name == remote.Name) != null) - return; - _loaded.Add(remote); + var remote = new Models.Remote() + { + Name = match.Groups[1].Value, + URL = match.Groups[2].Value, + }; + + if (outs.Find(x => x.Name == remote.Name) != null) + continue; + + outs.Add(remote); + } + + return outs; } - - private readonly List _loaded = new List(); } } diff --git a/src/Commands/QueryStashes.cs b/src/Commands/QueryStashes.cs index dd5d10cc..b4067aaf 100644 --- a/src/Commands/QueryStashes.cs +++ b/src/Commands/QueryStashes.cs @@ -14,35 +14,50 @@ namespace SourceGit.Commands public List Result() { - Exec(); - return _stashes; - } + var outs = new List(); + var rs = ReadToEnd(); + if (!rs.IsSuccess) + return outs; - protected override void OnReadline(string line) - { - switch (_nextLineIdx) + var nextPartIdx = 0; + var start = 0; + var end = rs.StdOut.IndexOf('\n', start); + while (end > 0) { - case 0: - _current = new Models.Stash() { SHA = line }; - _stashes.Add(_current); - break; - case 1: - ParseParent(line); - break; - case 2: - _current.Time = ulong.Parse(line); - break; - case 3: - _current.Name = line; - break; - case 4: - _current.Message = line; - break; + var line = rs.StdOut.Substring(start, end - start); + + switch (nextPartIdx) + { + case 0: + _current = new Models.Stash() { SHA = line }; + outs.Add(_current); + break; + case 1: + ParseParent(line); + break; + case 2: + _current.Time = ulong.Parse(line); + break; + case 3: + _current.Name = line; + break; + case 4: + _current.Message = line; + break; + } + + nextPartIdx++; + if (nextPartIdx > 4) + nextPartIdx = 0; + + start = end + 1; + end = rs.StdOut.IndexOf('\n', start); } - _nextLineIdx++; - if (_nextLineIdx > 4) - _nextLineIdx = 0; + if (start < rs.StdOut.Length) + _current.Message = rs.StdOut.Substring(start); + + return outs; } private void ParseParent(string data) @@ -53,8 +68,6 @@ namespace SourceGit.Commands _current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries)); } - private readonly List _stashes = new List(); private Models.Stash _current = null; - private int _nextLineIdx = 0; } } diff --git a/src/Commands/Submodule.cs b/src/Commands/Submodule.cs index 9a273703..e4f35fca 100644 --- a/src/Commands/Submodule.cs +++ b/src/Commands/Submodule.cs @@ -1,6 +1,4 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class Submodule : Command { @@ -10,9 +8,8 @@ namespace SourceGit.Commands Context = repo; } - public bool Add(string url, string relativePath, bool recursive, Action outputHandler) + public bool Add(string url, string relativePath, bool recursive) { - _outputHandler = outputHandler; Args = $"submodule add {url} \"{relativePath}\""; if (!Exec()) return false; @@ -29,7 +26,7 @@ namespace SourceGit.Commands } } - public bool Update(string module, bool init, bool recursive, bool useRemote, Action outputHandler) + public bool Update(string module, bool init, bool recursive, bool useRemote) { Args = "submodule update"; @@ -42,7 +39,6 @@ namespace SourceGit.Commands if (!string.IsNullOrEmpty(module)) Args += $" -- \"{module}\""; - _outputHandler = outputHandler; return Exec(); } @@ -55,12 +51,5 @@ namespace SourceGit.Commands Args = $"rm -rf \"{relativePath}\""; return Exec(); } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private Action _outputHandler; } } diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs index 23dbb11c..6fc8dc34 100644 --- a/src/Commands/Tag.cs +++ b/src/Commands/Tag.cs @@ -1,26 +1,27 @@ -using System.Collections.Generic; -using System.IO; +using System.IO; namespace SourceGit.Commands { public static class Tag { - public static bool Add(string repo, string name, string basedOn) + public static bool Add(string repo, string name, string basedOn, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"tag {name} {basedOn}"; + cmd.Log = log; return cmd.Exec(); } - public static bool Add(string repo, string name, string basedOn, string message, bool sign) + public static bool Add(string repo, string name, string basedOn, string message, bool sign, Models.ICommandLog log) { var param = sign ? "--sign -a" : "--no-sign -a"; var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"tag {param} {name} {basedOn} "; + cmd.Log = log; if (!string.IsNullOrEmpty(message)) { @@ -36,22 +37,14 @@ namespace SourceGit.Commands return cmd.Exec(); } - public static bool Delete(string repo, string name, List remotes) + public static bool Delete(string repo, string name, Models.ICommandLog log) { var cmd = new Command(); cmd.WorkingDirectory = repo; cmd.Context = repo; cmd.Args = $"tag --delete {name}"; - if (!cmd.Exec()) - return false; - - if (remotes != null) - { - foreach (var r in remotes) - new Push(repo, r.Name, $"refs/tags/{name}", true).Exec(); - } - - return true; + cmd.Log = log; + return cmd.Exec(); } } } diff --git a/src/Commands/UpdateRef.cs b/src/Commands/UpdateRef.cs index ba1b3d2f..1e7bb239 100644 --- a/src/Commands/UpdateRef.cs +++ b/src/Commands/UpdateRef.cs @@ -1,23 +1,12 @@ -using System; - -namespace SourceGit.Commands +namespace SourceGit.Commands { public class UpdateRef : Command { - public UpdateRef(string repo, string refName, string toRevision, Action outputHandler) + public UpdateRef(string repo, string refName, string toRevision) { - _outputHandler = outputHandler; - WorkingDirectory = repo; Context = repo; Args = $"update-ref {refName} {toRevision}"; } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private Action _outputHandler; } } diff --git a/src/Commands/Worktree.cs b/src/Commands/Worktree.cs index 960d5501..1198a443 100644 --- a/src/Commands/Worktree.cs +++ b/src/Commands/Worktree.cs @@ -56,7 +56,7 @@ namespace SourceGit.Commands return worktrees; } - public bool Add(string fullpath, string name, bool createNew, string tracking, Action outputHandler) + public bool Add(string fullpath, string name, bool createNew, string tracking) { Args = "worktree add "; @@ -78,14 +78,12 @@ namespace SourceGit.Commands else if (!string.IsNullOrEmpty(name) && !createNew) Args += name; - _outputHandler = outputHandler; return Exec(); } - public bool Prune(Action outputHandler) + public bool Prune() { Args = "worktree prune -v"; - _outputHandler = outputHandler; return Exec(); } @@ -101,22 +99,14 @@ namespace SourceGit.Commands return Exec(); } - public bool Remove(string fullpath, bool force, Action outputHandler) + public bool Remove(string fullpath, bool force) { if (force) Args = $"worktree remove -f \"{fullpath}\""; else Args = $"worktree remove \"{fullpath}\""; - _outputHandler = outputHandler; return Exec(); } - - protected override void OnReadline(string line) - { - _outputHandler?.Invoke(line); - } - - private Action _outputHandler = null; } } diff --git a/src/Models/ConventionalCommitType.cs b/src/Models/ConventionalCommitType.cs index 7fb5ee16..cd09453a 100644 --- a/src/Models/ConventionalCommitType.cs +++ b/src/Models/ConventionalCommitType.cs @@ -22,7 +22,6 @@ namespace SourceGit.Models 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 ConventionalCommitType(string name, string type, string description) diff --git a/src/Models/ICommandLog.cs b/src/Models/ICommandLog.cs new file mode 100644 index 00000000..34ec7031 --- /dev/null +++ b/src/Models/ICommandLog.cs @@ -0,0 +1,7 @@ +namespace SourceGit.Models +{ + public interface ICommandLog + { + void AppendLine(string line); + } +} diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 66589440..a966d47d 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -78,6 +78,7 @@ M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z M976 0h-928A48 48 0 000 48v652a48 48 0 0048 48h416V928H200a48 48 0 000 96h624a48 48 0 000-96H560v-180h416a48 48 0 0048-48V48A48 48 0 00976 0zM928 652H96V96h832v556z M832 464h-68V240a128 128 0 00-128-128h-248a128 128 0 00-128 128v224H192c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32v-384c0-18-14-32-32-32zm-292 237v53a8 8 0 01-8 8h-40a8 8 0 01-8-8v-53a48 48 0 1156 0zm152-237H332V240a56 56 0 0156-56h248a56 56 0 0156 56v224z + M908 366h-25V248a18 18 0 00-0-2 20 20 0 00-5-13L681 7 681 7a19 19 0 00-4-3c-0-0-1-1-1-1a29 29 0 00-4-2L671 1a24 24 0 00-5-1H181a40 40 0 00-40 40v326h-25c-32 0-57 26-57 57v298c0 32 26 57 57 57h25v204c0 22 18 40 40 40H843a40 40 0 0040-40v-204h25c32 0 57-26 57-57V424a57 57 0 00-57-57zM181 40h465v205c0 11 9 20 20 20h177v101H181V40zm413 527c0 89-54 143-134 143-81 0-128-61-128-138 0-82 52-143 132-143 84 0 129 63 129 138zm-440 139V433h62v220h108v52h-170zm690 267H181v-193H843l0 193zm18-280a305 305 0 01-91 15c-50 0-86-12-111-37-25-23-39-59-38-99 0-90 66-142 155-142 35 0 62 7 76 13l-13 49c-15-6-33-12-63-12-51 0-90 29-90 88 0 56 35 89 86 89 14 0 25-2 30-4v-57h-42v-48h101v143zM397 570c0 53 25 91 66 91 42 0 65-40 65-92 0-49-23-91-66-91-42 0-66 40-66 93z M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z M824 645V307c0-56-46-102-102-102h-102V102l-154 154 154 154V307h102v338c-46 20-82 67-82 123 0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123zm-51 195c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72zM384 256c0-72-61-133-133-133-72 0-133 61-133 133 0 56 36 102 82 123v266C154 666 118 712 118 768c0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123V379C348 358 384 312 384 256zM323 768c0 41-31 72-72 72-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72zM251 328c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72z M896 64H128C96 64 64 96 64 128v768c0 32 32 64 64 64h768c32 0 64-32 64-64V128c0-32-32-64-64-64z m-64 736c0 16-17 32-32 32H224c-18 0-32-12-32-32V224c0-16 16-32 32-32h576c15 0 32 16 32 32v576zM512 384c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 664f5394..5d1bb7e1 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -612,6 +612,7 @@ Sort Open in Terminal Use relative time in histories + View Logs WORKTREES ADD WORKTREE PRUNE @@ -697,6 +698,7 @@ Submodule: Use --remote option URL: + Logs Warning Welcome Page Create Group diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 7b1a18e4..a4d33601 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -616,6 +616,7 @@ 排序 在终端中打开 在提交列表中使用相对时间 + 查看命令日志 工作树列表 新增工作树 清理 @@ -701,6 +702,7 @@ 子模块 : 启用 '--remote' 仓库地址 : + 日志列表 警告 起始页 新建分组 @@ -757,4 +759,4 @@ 锁定工作树 移除工作树 解除工作树锁定 - \ No newline at end of file + diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index ce1aae72..581f25f2 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -616,6 +616,7 @@ 排序 在終端機中開啟 在提交列表中使用相對時間 + 檢視 GIT 指令的日誌 工作區列表 新增工作區 清理 @@ -701,6 +702,7 @@ 子模組: 啟用 [--remote] 選項 存放庫網址: + 日誌清單 警告 起始頁 新增群組 @@ -757,4 +759,4 @@ 鎖定工作區 移除工作區 解除鎖定工作區 - \ No newline at end of file + diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs index d6424572..3372208f 100644 --- a/src/ViewModels/AddRemote.cs +++ b/src/ViewModels/AddRemote.cs @@ -93,15 +93,19 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Adding remote ..."; + var log = _repo.CreateLog("Add Remote"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Remote(_repo.FullPath).Add(_name, _url); + var succ = new Commands.Remote(_repo.FullPath).Use(log).Add(_name, _url); if (succ) { - SetProgressDescription("Fetching from added remote ..."); - new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); - new Commands.Fetch(_repo.FullPath, _name, false, false, SetProgressDescription).Exec(); + new Commands.Config(_repo.FullPath).Use(log).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); + new Commands.Fetch(_repo.FullPath, _name, false, false).Use(log).Exec(); } + + log.Complete(); CallUIThread(() => { _repo.MarkFetched(); diff --git a/src/ViewModels/AddSubmodule.cs b/src/ViewModels/AddSubmodule.cs index 657075a9..e9bdedd3 100644 --- a/src/ViewModels/AddSubmodule.cs +++ b/src/ViewModels/AddSubmodule.cs @@ -61,9 +61,14 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Adding submodule..."; + var log = _repo.CreateLog("Add Submodule"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Submodule(_repo.FullPath).Add(_url, _relativePath, Recursive, SetProgressDescription); + var succ = new Commands.Submodule(_repo.FullPath).Use(log).Add(_url, _relativePath, Recursive); + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/AddWorktree.cs b/src/ViewModels/AddWorktree.cs index 6c1c7481..27039f49 100644 --- a/src/ViewModels/AddWorktree.cs +++ b/src/ViewModels/AddWorktree.cs @@ -114,10 +114,15 @@ namespace SourceGit.ViewModels var branchName = _selectedBranch; var tracking = _setTrackingBranch ? SelectedTrackingBranch : string.Empty; + var log = _repo.CreateLog("Add Worktree"); + + Use(log); return Task.Run(() => { - var succ = new Commands.Worktree(_repo.FullPath).Add(_path, branchName, _createNewBranch, tracking, SetProgressDescription); + var succ = new Commands.Worktree(_repo.FullPath).Use(log).Add(_path, branchName, _createNewBranch, tracking); + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Apply.cs b/src/ViewModels/Apply.cs index 60001476..81af8754 100644 --- a/src/ViewModels/Apply.cs +++ b/src/ViewModels/Apply.cs @@ -47,9 +47,12 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Apply patch..."; + var log = _repo.CreateLog("Apply Patch"); return Task.Run(() => { - var succ = new Commands.Apply(_repo.FullPath, _patchFile, _ignoreWhiteSpace, SelectedWhiteSpaceMode.Arg, null).Exec(); + var succ = new Commands.Apply(_repo.FullPath, _patchFile, _ignoreWhiteSpace, SelectedWhiteSpaceMode.Arg, null).Use(log).Exec(); + log.Complete(); + CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/ApplyStash.cs b/src/ViewModels/ApplyStash.cs index 03ce0f43..a3bb8ae2 100644 --- a/src/ViewModels/ApplyStash.cs +++ b/src/ViewModels/ApplyStash.cs @@ -22,7 +22,7 @@ namespace SourceGit.ViewModels set; } = false; - public ApplyStash(string repo, Models.Stash stash) + public ApplyStash(Repository repo, Models.Stash stash) { _repo = repo; Stash = stash; @@ -33,16 +33,18 @@ namespace SourceGit.ViewModels { ProgressDescription = $"Applying stash: {Stash.Name}"; + var log = _repo.CreateLog("Apply Stash"); return Task.Run(() => { - var succ = new Commands.Stash(_repo).Apply(Stash.Name, RestoreIndex); + var succ = new Commands.Stash(_repo.FullPath).Use(log).Apply(Stash.Name, RestoreIndex); if (succ && DropAfterApply) - new Commands.Stash(_repo).Drop(Stash.Name); + new Commands.Stash(_repo.FullPath).Use(log).Drop(Stash.Name); + log.Complete(); return true; }); } - private readonly string _repo; + private readonly Repository _repo; } } diff --git a/src/ViewModels/Archive.cs b/src/ViewModels/Archive.cs index 0a180b71..653a9499 100644 --- a/src/ViewModels/Archive.cs +++ b/src/ViewModels/Archive.cs @@ -51,9 +51,14 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Archiving ..."; + var log = _repo.CreateLog("Archive"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Archive(_repo.FullPath, _revision, _saveFile, SetProgressDescription).Exec(); + var succ = new Commands.Archive(_repo.FullPath, _revision, _saveFile).Use(log).Exec(); + log.Complete(); + CallUIThread(() => { _repo.SetWatcherEnabled(true); diff --git a/src/ViewModels/AssumeUnchangedManager.cs b/src/ViewModels/AssumeUnchangedManager.cs index 0da16552..953cb476 100644 --- a/src/ViewModels/AssumeUnchangedManager.cs +++ b/src/ViewModels/AssumeUnchangedManager.cs @@ -16,11 +16,8 @@ namespace SourceGit.ViewModels Task.Run(() => { - var collect = new Commands.AssumeUnchanged(_repo).View(); - Dispatcher.UIThread.Invoke(() => - { - Files.AddRange(collect); - }); + var collect = new Commands.QueryAssumeUnchangedFiles(_repo).Result(); + Dispatcher.UIThread.Invoke(() => Files.AddRange(collect)); }); } @@ -28,7 +25,7 @@ namespace SourceGit.ViewModels { if (!string.IsNullOrEmpty(file)) { - new Commands.AssumeUnchanged(_repo).Remove(file); + new Commands.AssumeUnchanged(_repo, file, false).Exec(); Files.Remove(file); } } diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index 3334eba4..dc2f4678 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -28,6 +28,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout '{Branch}' ..."; + var log = _repo.CreateLog($"Checkout '{Branch}'"); + Use(log); + return Task.Run(() => { var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); @@ -36,15 +39,14 @@ namespace SourceGit.ViewModels { if (DiscardLocalChanges) { - SetProgressDescription("Discard local changes ..."); - Commands.Discard.All(_repo.FullPath, false); + Commands.Discard.All(_repo.FullPath, false, log); } else { - SetProgressDescription("Stash local changes ..."); - var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH"); + var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH"); if (!succ) { + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; } @@ -53,14 +55,11 @@ namespace SourceGit.ViewModels } } - SetProgressDescription("Checkout branch ..."); - var rs = new Commands.Checkout(_repo.FullPath).Branch(Branch, SetProgressDescription); - + var rs = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch); if (needPopStash) - { - SetProgressDescription("Re-apply local changes..."); - rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); - } + rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + + log.Complete(); CallUIThread(() => { diff --git a/src/ViewModels/CheckoutCommit.cs b/src/ViewModels/CheckoutCommit.cs index 1876a425..8d6f50ac 100644 --- a/src/ViewModels/CheckoutCommit.cs +++ b/src/ViewModels/CheckoutCommit.cs @@ -28,6 +28,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout Commit '{Commit.SHA}' ..."; + var log = _repo.CreateLog("Checkout Commit"); + Use(log); + return Task.Run(() => { var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); @@ -36,15 +39,14 @@ namespace SourceGit.ViewModels { if (DiscardLocalChanges) { - SetProgressDescription("Discard local changes ..."); - Commands.Discard.All(_repo.FullPath, false); + Commands.Discard.All(_repo.FullPath, false, log); } else { - SetProgressDescription("Stash local changes ..."); - var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH"); + var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH"); if (!succ) { + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; } @@ -53,15 +55,11 @@ namespace SourceGit.ViewModels } } - SetProgressDescription("Checkout commit ..."); - var rs = new Commands.Checkout(_repo.FullPath).Commit(Commit.SHA, SetProgressDescription); - + var rs = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA); if (needPopStash) - { - SetProgressDescription("Re-apply local changes..."); - rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); - } + rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return rs; }); diff --git a/src/ViewModels/CherryPick.cs b/src/ViewModels/CherryPick.cs index c3ee07cc..dc172489 100644 --- a/src/ViewModels/CherryPick.cs +++ b/src/ViewModels/CherryPick.cs @@ -70,6 +70,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Cherry-Pick commit(s) ..."; + var log = _repo.CreateLog("Cherry-Pick"); + Use(log); + return Task.Run(() => { if (IsMergeCommit) @@ -79,7 +82,7 @@ namespace SourceGit.ViewModels Targets[0].SHA, !AutoCommit, AppendSourceToMessage, - $"-m {MainlineForMergeCommit + 1}").Exec(); + $"-m {MainlineForMergeCommit + 1}").Use(log).Exec(); } else { @@ -88,9 +91,10 @@ namespace SourceGit.ViewModels string.Join(' ', Targets.ConvertAll(c => c.SHA)), !AutoCommit, AppendSourceToMessage, - string.Empty).Exec(); + string.Empty).Use(log).Exec(); } + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/Cleanup.cs b/src/ViewModels/Cleanup.cs index 7efcf73e..e7c9b73d 100644 --- a/src/ViewModels/Cleanup.cs +++ b/src/ViewModels/Cleanup.cs @@ -15,9 +15,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Cleanup (GC & prune) ..."; + var log = _repo.CreateLog("Cleanup (GC & prune)"); + Use(log); + return Task.Run(() => { - new Commands.GC(_repo.FullPath, SetProgressDescription).Exec(); + new Commands.GC(_repo.FullPath).Use(log).Exec(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/ClearStashes.cs b/src/ViewModels/ClearStashes.cs index dad4059a..bd4ca3ff 100644 --- a/src/ViewModels/ClearStashes.cs +++ b/src/ViewModels/ClearStashes.cs @@ -15,9 +15,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Clear all stashes..."; + var log = _repo.CreateLog("Clear Stashes"); + Use(log); + return Task.Run(() => { - new Commands.Stash(_repo.FullPath).Clear(); + new Commands.Stash(_repo.FullPath).Use(log).Clear(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs index 97c90523..7dc8abe8 100644 --- a/src/ViewModels/Clone.cs +++ b/src/ViewModels/Clone.cs @@ -103,9 +103,13 @@ namespace SourceGit.ViewModels { ProgressDescription = "Clone ..."; + // Create a temp log. + var log = new CommandLog("Clone"); + Use(log); + return Task.Run(() => { - var cmd = new Commands.Clone(_pageId, _parentFolder, _remote, _local, _useSSH ? _sshKey : "", _extraArgs, SetProgressDescription); + var cmd = new Commands.Clone(_pageId, _parentFolder, _remote, _local, _useSSH ? _sshKey : "", _extraArgs).Use(log); if (!cmd.Exec()) return false; @@ -142,12 +146,11 @@ namespace SourceGit.ViewModels { var submoduleList = new Commands.QuerySubmodules(path).Result(); foreach (var submodule in submoduleList) - { - var update = new Commands.Submodule(path); - update.Update(submodule.Path, true, true, false, SetProgressDescription); - } + new Commands.Submodule(path).Use(log).Update(submodule.Path, true, true, false); } + log.Complete(); + CallUIThread(() => { var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); diff --git a/src/ViewModels/CommandLog.cs b/src/ViewModels/CommandLog.cs new file mode 100644 index 00000000..e10aa724 --- /dev/null +++ b/src/ViewModels/CommandLog.cs @@ -0,0 +1,87 @@ +using System; +using System.Text; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class CommandLog : ObservableObject, Models.ICommandLog + { + public string Name + { + get; + private set; + } = string.Empty; + + public DateTime Time + { + get; + } = DateTime.Now; + + public string TimeStr + { + get => Time.ToString("T"); + } + + public bool IsComplete + { + get; + private set; + } = false; + + public string Content + { + get + { + return IsComplete ? _content : _builder.ToString(); + } + } + + public CommandLog(string name) + { + Name = name; + } + + public void Register(Action handler) + { + if (!IsComplete) + _onNewLineReceived += handler; + } + + public void AppendLine(string line = null) + { + var newline = line ?? string.Empty; + + Dispatcher.UIThread.Invoke(() => + { + _builder.AppendLine(newline); + _onNewLineReceived?.Invoke(newline); + }); + } + + public void Complete() + { + IsComplete = true; + + Dispatcher.UIThread.Invoke(() => + { + _content = _builder.ToString(); + _builder.Clear(); + _builder = null; + + OnPropertyChanged(nameof(IsComplete)); + + if (_onNewLineReceived != null) + { + var dumpHandlers = _onNewLineReceived.GetInvocationList(); + foreach (var d in dumpHandlers) + _onNewLineReceived -= (Action)d; + } + }); + } + + private string _content = string.Empty; + private StringBuilder _builder = new StringBuilder(); + private event Action _onNewLineReceived; + } +} diff --git a/src/ViewModels/Conflict.cs b/src/ViewModels/Conflict.cs index 1ccf4a33..8c825081 100644 --- a/src/ViewModels/Conflict.cs +++ b/src/ViewModels/Conflict.cs @@ -46,7 +46,7 @@ _wc = wc; _change = change; - IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).ReadToEnd().IsSuccess; + IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).Result(); var context = wc.InProgressContext; if (context is CherryPickInProgress cherryPick) diff --git a/src/ViewModels/CreateBranch.cs b/src/ViewModels/CreateBranch.cs index 6d7fe27a..d2f6d767 100644 --- a/src/ViewModels/CreateBranch.cs +++ b/src/ViewModels/CreateBranch.cs @@ -92,6 +92,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); var fixedName = FixName(_name); + var log = _repo.CreateLog($"Create Branch '{fixedName}'"); + Use(log); + return Task.Run(() => { var succ = false; @@ -103,15 +106,14 @@ namespace SourceGit.ViewModels { if (DiscardLocalChanges) { - SetProgressDescription("Discard local changes..."); - Commands.Discard.All(_repo.FullPath, false); + Commands.Discard.All(_repo.FullPath, false, log); } else { - SetProgressDescription("Stash local changes"); - succ = new Commands.Stash(_repo.FullPath).Push("CREATE_BRANCH_AUTO_STASH"); + succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CREATE_BRANCH_AUTO_STASH"); if (!succ) { + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; } @@ -120,21 +122,17 @@ namespace SourceGit.ViewModels } } - SetProgressDescription($"Create new branch '{fixedName}'"); - succ = new Commands.Checkout(_repo.FullPath).Branch(fixedName, _baseOnRevision, SetProgressDescription); - + succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision); if (needPopStash) - { - SetProgressDescription("Re-apply local changes..."); - new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); - } + new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); } else { - SetProgressDescription($"Create new branch '{fixedName}'"); - succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision); + succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision, log); } + log.Complete(); + CallUIThread(() => { if (succ && CheckoutAfterCreated) diff --git a/src/ViewModels/CreateTag.cs b/src/ViewModels/CreateTag.cs index 86ae7118..ec6f07ab 100644 --- a/src/ViewModels/CreateTag.cs +++ b/src/ViewModels/CreateTag.cs @@ -83,23 +83,24 @@ namespace SourceGit.ViewModels ProgressDescription = "Create tag..."; var remotes = PushToRemotes ? _repo.Remotes : null; + var log = _repo.CreateLog("Create Tag"); + Use(log); + return Task.Run(() => { bool succ; if (_annotated) - succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag); + succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag, log); else - succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn); + succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, log); if (succ && remotes != null) { foreach (var remote in remotes) - { - SetProgressDescription($"Pushing tag to remote {remote.Name} ..."); - new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false).Exec(); - } + new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false).Use(log).Exec(); } + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/DeleteBranch.cs b/src/ViewModels/DeleteBranch.cs index e7136a0d..7115923e 100644 --- a/src/ViewModels/DeleteBranch.cs +++ b/src/ViewModels/DeleteBranch.cs @@ -48,23 +48,25 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting branch..."; + var log = _repo.CreateLog("Delete Branch"); + Use(log); + return Task.Run(() => { if (Target.IsLocal) { - Commands.Branch.DeleteLocal(_repo.FullPath, Target.Name); + Commands.Branch.DeleteLocal(_repo.FullPath, Target.Name, log); if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null) - { - SetProgressDescription("Deleting remote-tracking branch..."); - Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name); - } + Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name, log); } else { - Commands.Branch.DeleteRemote(_repo.FullPath, Target.Remote, Target.Name); + Commands.Branch.DeleteRemote(_repo.FullPath, Target.Remote, Target.Name, log); } + log.Complete(); + CallUIThread(() => { _repo.MarkBranchesDirtyManually(); diff --git a/src/ViewModels/DeleteMultipleBranches.cs b/src/ViewModels/DeleteMultipleBranches.cs index 87bb3122..9a59db5d 100644 --- a/src/ViewModels/DeleteMultipleBranches.cs +++ b/src/ViewModels/DeleteMultipleBranches.cs @@ -23,25 +23,24 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting multiple branches..."; + var log = _repo.CreateLog("Delete Multiple Branches"); + Use(log); + return Task.Run(() => { if (_isLocal) { foreach (var target in Targets) - { - SetProgressDescription($"Deleting local branch : {target.Name}"); - Commands.Branch.DeleteLocal(_repo.FullPath, target.Name); - } + Commands.Branch.DeleteLocal(_repo.FullPath, target.Name, log); } else { foreach (var target in Targets) - { - SetProgressDescription($"Deleting remote branch : {target.FriendlyName}"); - Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name); - } + Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name, log); } + log.Complete(); + CallUIThread(() => { _repo.MarkBranchesDirtyManually(); diff --git a/src/ViewModels/DeleteRemote.cs b/src/ViewModels/DeleteRemote.cs index e1fba02d..cad34574 100644 --- a/src/ViewModels/DeleteRemote.cs +++ b/src/ViewModels/DeleteRemote.cs @@ -22,9 +22,14 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting remote ..."; + var log = _repo.CreateLog("Delete Remote"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Remote(_repo.FullPath).Delete(Remote.Name); + var succ = new Commands.Remote(_repo.FullPath).Use(log).Delete(Remote.Name); + log.Complete(); + CallUIThread(() => { _repo.MarkBranchesDirtyManually(); diff --git a/src/ViewModels/DeleteSubmodule.cs b/src/ViewModels/DeleteSubmodule.cs index 459b4026..c21cde3b 100644 --- a/src/ViewModels/DeleteSubmodule.cs +++ b/src/ViewModels/DeleteSubmodule.cs @@ -23,9 +23,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Deleting submodule ..."; + var log = _repo.CreateLog("Delete Submodule"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Submodule(_repo.FullPath).Delete(Submodule); + var succ = new Commands.Submodule(_repo.FullPath).Use(log).Delete(Submodule); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/DeleteTag.cs b/src/ViewModels/DeleteTag.cs index 341eb4a2..419c081f 100644 --- a/src/ViewModels/DeleteTag.cs +++ b/src/ViewModels/DeleteTag.cs @@ -28,10 +28,21 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Deleting tag '{Target.Name}' ..."; - var remotes = PushToRemotes ? _repo.Remotes : null; + var remotes = PushToRemotes ? _repo.Remotes : []; + var log = _repo.CreateLog("Delete Tag"); + Use(log); + return Task.Run(() => { - var succ = Commands.Tag.Delete(_repo.FullPath, Target.Name, remotes); + var succ = Commands.Tag.Delete(_repo.FullPath, Target.Name, log); + if (succ) + { + foreach (var r in remotes) + new Commands.Push(_repo.FullPath, r.Name, $"refs/tags/{Target.Name}", true).Use(log).Exec(); + } + + log.Complete(); + CallUIThread(() => { _repo.MarkTagsDirtyManually(); diff --git a/src/ViewModels/Discard.cs b/src/ViewModels/Discard.cs index e5b413db..e007f815 100644 --- a/src/ViewModels/Discard.cs +++ b/src/ViewModels/Discard.cs @@ -65,12 +65,17 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = _changes == null ? "Discard all local changes ..." : $"Discard total {_changes.Count} changes ..."; + var log = _repo.CreateLog("Discard all"); + Use(log); + return Task.Run(() => { if (Mode is DiscardAllMode all) - Commands.Discard.All(_repo.FullPath, all.IncludeIgnored); + Commands.Discard.All(_repo.FullPath, all.IncludeIgnored, log); else - Commands.Discard.Changes(_repo.FullPath, _changes); + Commands.Discard.Changes(_repo.FullPath, _changes, log); + + log.Complete(); CallUIThread(() => { diff --git a/src/ViewModels/DropStash.cs b/src/ViewModels/DropStash.cs index ea6ff277..9b72e827 100644 --- a/src/ViewModels/DropStash.cs +++ b/src/ViewModels/DropStash.cs @@ -6,7 +6,7 @@ namespace SourceGit.ViewModels { public Models.Stash Stash { get; private set; } - public DropStash(string repo, Models.Stash stash) + public DropStash(Repository repo, Models.Stash stash) { _repo = repo; Stash = stash; @@ -17,13 +17,17 @@ namespace SourceGit.ViewModels { ProgressDescription = $"Dropping stash: {Stash.Name}"; + var log = _repo.CreateLog("Drop Stash"); + Use(log); + return Task.Run(() => { - new Commands.Stash(_repo).Drop(Stash.Name); + new Commands.Stash(_repo.FullPath).Use(log).Drop(Stash.Name); + log.Complete(); return true; }); } - private readonly string _repo; + private readonly Repository _repo; } } diff --git a/src/ViewModels/EditRemote.cs b/src/ViewModels/EditRemote.cs index 912c7991..2c63c51a 100644 --- a/src/ViewModels/EditRemote.cs +++ b/src/ViewModels/EditRemote.cs @@ -127,7 +127,6 @@ namespace SourceGit.ViewModels if (pushURL != _url) new Commands.Remote(_repo.FullPath).SetURL(_name, _url, true); - SetProgressDescription("Post processing ..."); new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); CallUIThread(() => _repo.SetWatcherEnabled(true)); diff --git a/src/ViewModels/ExecuteCustomAction.cs b/src/ViewModels/ExecuteCustomAction.cs index 16a6410c..dfc00c9b 100644 --- a/src/ViewModels/ExecuteCustomAction.cs +++ b/src/ViewModels/ExecuteCustomAction.cs @@ -43,7 +43,7 @@ namespace SourceGit.ViewModels return Task.Run(() => { if (CustomAction.WaitForExit) - Commands.ExecuteCustomAction.RunAndWait(_repo.FullPath, CustomAction.Executable, _args, SetProgressDescription); + Commands.ExecuteCustomAction.RunAndWait(_repo.FullPath, CustomAction.Executable, _args, output => CallUIThread(() => ProgressDescription = output)); else Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, _args); diff --git a/src/ViewModels/FastForwardWithoutCheckout.cs b/src/ViewModels/FastForwardWithoutCheckout.cs index 974730a7..5b55a9e3 100644 --- a/src/ViewModels/FastForwardWithoutCheckout.cs +++ b/src/ViewModels/FastForwardWithoutCheckout.cs @@ -29,9 +29,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Fast-Forward ..."; + var log = _repo.CreateLog("Fast-Forward (No checkout)"); + Use(log); + return Task.Run(() => { - new Commands.UpdateRef(_repo.FullPath, Local.FullName, To.FullName, SetProgressDescription).Exec(); + new Commands.UpdateRef(_repo.FullPath, Local.FullName, To.FullName).Use(log).Exec(); + log.Complete(); CallUIThread(() => { _repo.NavigateToCommit(To.Head); diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs index 35e34bb3..437570cf 100644 --- a/src/ViewModels/Fetch.cs +++ b/src/ViewModels/Fetch.cs @@ -65,22 +65,23 @@ namespace SourceGit.ViewModels var notags = _repo.Settings.FetchWithoutTags; var force = _repo.Settings.EnableForceOnFetch; + var log = _repo.CreateLog("Fetch"); + Use(log); + return Task.Run(() => { if (FetchAllRemotes) { foreach (var remote in _repo.Remotes) - { - SetProgressDescription($"Fetching remote: {remote.Name}"); - new Commands.Fetch(_repo.FullPath, remote.Name, notags, force, SetProgressDescription).Exec(); - } + new Commands.Fetch(_repo.FullPath, remote.Name, notags, force).Use(log).Exec(); } else { - SetProgressDescription($"Fetching remote: {SelectedRemote.Name}"); - new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force, SetProgressDescription).Exec(); + new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force).Use(log).Exec(); } + log.Complete(); + CallUIThread(() => { _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.Upstream); diff --git a/src/ViewModels/FetchInto.cs b/src/ViewModels/FetchInto.cs index 878b0416..f3cdacdc 100644 --- a/src/ViewModels/FetchInto.cs +++ b/src/ViewModels/FetchInto.cs @@ -29,9 +29,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Fast-Forward ..."; + var log = _repo.CreateLog($"Fetch Into '{Local.FriendlyName}'"); + Use(log); + return Task.Run(() => { - new Commands.Fetch(_repo.FullPath, Local, Upstream, SetProgressDescription).Exec(); + new Commands.Fetch(_repo.FullPath, Local, Upstream).Use(log).Exec(); + log.Complete(); CallUIThread(() => { _repo.NavigateToBranchDelayed(Upstream.FullName); diff --git a/src/ViewModels/GitFlowFinish.cs b/src/ViewModels/GitFlowFinish.cs index d278dc77..f1de8298 100644 --- a/src/ViewModels/GitFlowFinish.cs +++ b/src/ViewModels/GitFlowFinish.cs @@ -33,11 +33,17 @@ namespace SourceGit.ViewModels public override Task Sure() { _repo.SetWatcherEnabled(false); + + var name = Branch.Name.StartsWith(_prefix) ? Branch.Name.Substring(_prefix.Length) : Branch.Name; + ProgressDescription = $"Git Flow - finishing {_type} {name} ..."; + + var log = _repo.CreateLog("Gitflow - Finish"); + Use(log); + return Task.Run(() => { - var name = Branch.Name.StartsWith(_prefix) ? Branch.Name.Substring(_prefix.Length) : Branch.Name; - SetProgressDescription($"Git Flow - finishing {_type} {name} ..."); - var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, KeepBranch); + var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, KeepBranch, log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/GitFlowStart.cs b/src/ViewModels/GitFlowStart.cs index ec1343d4..fafbecad 100644 --- a/src/ViewModels/GitFlowStart.cs +++ b/src/ViewModels/GitFlowStart.cs @@ -50,10 +50,15 @@ namespace SourceGit.ViewModels public override Task Sure() { _repo.SetWatcherEnabled(false); + ProgressDescription = $"Git Flow - starting {_type} {_name} ..."; + + var log = _repo.CreateLog("Gitflow - Start"); + Use(log); + return Task.Run(() => { - SetProgressDescription($"Git Flow - starting {_type} {_name} ..."); - var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name); + var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name, log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index b0bbff3c..b3216a91 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -302,17 +302,20 @@ namespace SourceGit.ViewModels var picker = await storageProvider.OpenFolderPickerAsync(options); if (picker.Count == 1) { + var log = _repo.CreateLog("Save as Patch"); var succ = false; for (var i = 0; i < selected.Count; i++) { var saveTo = GetPatchFileName(picker[0].Path.LocalPath, selected[i], i); - succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, selected[i].SHA, saveTo).Exec()); + succ = await Task.Run(() => new Commands.FormatPatch(_repo.FullPath, selected[i].SHA, saveTo).Use(log).Exec()); if (!succ) break; } if (succ) App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); + + log.Complete(); } } catch (Exception exception) diff --git a/src/ViewModels/Init.cs b/src/ViewModels/Init.cs index 80a75b9a..3365d485 100644 --- a/src/ViewModels/Init.cs +++ b/src/ViewModels/Init.cs @@ -30,19 +30,24 @@ namespace SourceGit.ViewModels { ProgressDescription = $"Initialize git repository at: '{_targetPath}'"; + var log = new CommandLog("Initialize"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Init(_pageId, _targetPath).Exec(); - if (!succ) - return false; + var succ = new Commands.Init(_pageId, _targetPath).Use(log).Exec(); + log.Complete(); - CallUIThread(() => + if (succ) { - Preferences.Instance.FindOrAddNodeByRepositoryPath(_targetPath, _parentNode, true); - Welcome.Instance.Refresh(); - }); + CallUIThread(() => + { + Preferences.Instance.FindOrAddNodeByRepositoryPath(_targetPath, _parentNode, true); + Welcome.Instance.Refresh(); + }); + } - return true; + return succ; }); } diff --git a/src/ViewModels/InitGitFlow.cs b/src/ViewModels/InitGitFlow.cs index ed5ff3ce..8e8b6342 100644 --- a/src/ViewModels/InitGitFlow.cs +++ b/src/ViewModels/InitGitFlow.cs @@ -106,9 +106,23 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Init git-flow ..."; + var log = _repo.CreateLog("Gitflow - Init"); + Use(log); + return Task.Run(() => { - var succ = Commands.GitFlow.Init(_repo.FullPath, _repo.Branches, _master, _develop, _featurePrefix, _releasePrefix, _hotfixPrefix, _tagPrefix); + var succ = Commands.GitFlow.Init( + _repo.FullPath, + _repo.Branches, + _master, + _develop, + _featurePrefix, + _releasePrefix, + _hotfixPrefix, + _tagPrefix, + log); + + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/LFSFetch.cs b/src/ViewModels/LFSFetch.cs index a3fb1f08..7491fa3e 100644 --- a/src/ViewModels/LFSFetch.cs +++ b/src/ViewModels/LFSFetch.cs @@ -24,9 +24,14 @@ namespace SourceGit.ViewModels { _repo.SetWatcherEnabled(false); ProgressDescription = $"Fetching LFS objects from remote ..."; + + var log = _repo.CreateLog("LFS Fetch"); + Use(log); + return Task.Run(() => { - new Commands.LFS(_repo.FullPath).Fetch(SelectedRemote.Name, SetProgressDescription); + new Commands.LFS(_repo.FullPath).Fetch(SelectedRemote.Name, log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/LFSPrune.cs b/src/ViewModels/LFSPrune.cs index 3475cb81..87d80cfb 100644 --- a/src/ViewModels/LFSPrune.cs +++ b/src/ViewModels/LFSPrune.cs @@ -15,9 +15,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "LFS prune ..."; + var log = _repo.CreateLog("LFS Prune"); + Use(log); + return Task.Run(() => { - new Commands.LFS(_repo.FullPath).Prune(SetProgressDescription); + new Commands.LFS(_repo.FullPath).Prune(log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/LFSPull.cs b/src/ViewModels/LFSPull.cs index b949ddc2..dcf7b82a 100644 --- a/src/ViewModels/LFSPull.cs +++ b/src/ViewModels/LFSPull.cs @@ -24,9 +24,14 @@ namespace SourceGit.ViewModels { _repo.SetWatcherEnabled(false); ProgressDescription = $"Pull LFS objects from remote ..."; + + var log = _repo.CreateLog("LFS Pull"); + Use(log); + return Task.Run(() => { - new Commands.LFS(_repo.FullPath).Pull(SelectedRemote.Name, SetProgressDescription); + new Commands.LFS(_repo.FullPath).Pull(SelectedRemote.Name, log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/LFSPush.cs b/src/ViewModels/LFSPush.cs index 5ea06757..929d79b8 100644 --- a/src/ViewModels/LFSPush.cs +++ b/src/ViewModels/LFSPush.cs @@ -24,9 +24,14 @@ namespace SourceGit.ViewModels { _repo.SetWatcherEnabled(false); ProgressDescription = $"Push LFS objects to remote ..."; + + var log = _repo.CreateLog("LFS Push"); + Use(log); + return Task.Run(() => { - new Commands.LFS(_repo.FullPath).Push(SelectedRemote.Name, SetProgressDescription); + new Commands.LFS(_repo.FullPath).Push(SelectedRemote.Name, log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/LFSTrackCustomPattern.cs b/src/ViewModels/LFSTrackCustomPattern.cs index 777e2d22..c601fc79 100644 --- a/src/ViewModels/LFSTrackCustomPattern.cs +++ b/src/ViewModels/LFSTrackCustomPattern.cs @@ -29,9 +29,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Adding custom LFS tracking pattern ..."; + var log = _repo.CreateLog("LFS Add Custom Pattern"); + Use(log); + return Task.Run(() => { - var succ = new Commands.LFS(_repo.FullPath).Track(_pattern, IsFilename); + var succ = new Commands.LFS(_repo.FullPath).Track(_pattern, IsFilename, log); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Merge.cs b/src/ViewModels/Merge.cs index 8b2ad537..08684601 100644 --- a/src/ViewModels/Merge.cs +++ b/src/ViewModels/Merge.cs @@ -59,9 +59,14 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Merging '{_sourceName}' into '{Into}' ..."; + var log = _repo.CreateLog($"Merging '{_sourceName}' into '{Into}'"); + Use(log); + return Task.Run(() => { - new Commands.Merge(_repo.FullPath, _sourceName, Mode.Arg, SetProgressDescription).Exec(); + new Commands.Merge(_repo.FullPath, _sourceName, Mode.Arg).Use(log).Exec(); + log.Complete(); + CallUIThread(() => { _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName); diff --git a/src/ViewModels/MergeMultiple.cs b/src/ViewModels/MergeMultiple.cs index dd984a15..cdcbfd08 100644 --- a/src/ViewModels/MergeMultiple.cs +++ b/src/ViewModels/MergeMultiple.cs @@ -46,15 +46,18 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Merge head(s) ..."; + var log = _repo.CreateLog("Merge Multiple Heads"); + Use(log); + return Task.Run(() => { var succ = new Commands.Merge( _repo.FullPath, ConvertTargetToMergeSources(), AutoCommit, - Strategy.Arg, - SetProgressDescription).Exec(); + Strategy.Arg).Use(log).Exec(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Popup.cs b/src/ViewModels/Popup.cs index 98a12ca2..90fa48c7 100644 --- a/src/ViewModels/Popup.cs +++ b/src/ViewModels/Popup.cs @@ -52,9 +52,9 @@ namespace SourceGit.ViewModels Dispatcher.UIThread.Invoke(action); } - protected void SetProgressDescription(string description) + protected void Use(CommandLog log) { - CallUIThread(() => ProgressDescription = description); + log.Register(newline => ProgressDescription = newline.Trim()); } private bool _inProgress = false; diff --git a/src/ViewModels/PruneRemote.cs b/src/ViewModels/PruneRemote.cs index 12835761..cd826250 100644 --- a/src/ViewModels/PruneRemote.cs +++ b/src/ViewModels/PruneRemote.cs @@ -22,9 +22,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Run `prune` on remote ..."; + var log = _repo.CreateLog($"Prune Remote '{Remote.Name}'"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Remote(_repo.FullPath).Prune(Remote.Name); + var succ = new Commands.Remote(_repo.FullPath).Use(log).Prune(Remote.Name); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/PruneWorktrees.cs b/src/ViewModels/PruneWorktrees.cs index 754ea4cb..8540e3c2 100644 --- a/src/ViewModels/PruneWorktrees.cs +++ b/src/ViewModels/PruneWorktrees.cs @@ -15,9 +15,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Prune worktrees ..."; + var log = _repo.CreateLog("Prune Worktrees"); + Use(log); + return Task.Run(() => { - new Commands.Worktree(_repo.FullPath).Prune(SetProgressDescription); + new Commands.Worktree(_repo.FullPath).Use(log).Prune(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs index 465a393a..6c40e80f 100644 --- a/src/ViewModels/Pull.cs +++ b/src/ViewModels/Pull.cs @@ -118,6 +118,9 @@ namespace SourceGit.ViewModels { _repo.SetWatcherEnabled(false); + var log = _repo.CreateLog("Pull"); + Use(log); + return Task.Run(() => { var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); @@ -126,15 +129,14 @@ namespace SourceGit.ViewModels { if (DiscardLocalChanges) { - SetProgressDescription("Discard local changes ..."); - Commands.Discard.All(_repo.FullPath, false); + Commands.Discard.All(_repo.FullPath, false, log); } else { - SetProgressDescription("Stash local changes..."); - var succ = new Commands.Stash(_repo.FullPath).Push("PULL_AUTO_STASH"); + var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("PULL_AUTO_STASH"); if (!succ) { + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; } @@ -146,16 +148,14 @@ namespace SourceGit.ViewModels bool rs; if (FetchAllBranches) { - SetProgressDescription($"Fetching remote: {_selectedRemote.Name}..."); rs = new Commands.Fetch( _repo.FullPath, _selectedRemote.Name, NoTags, - false, - SetProgressDescription).Exec(); - + false).Use(log).Exec(); if (!rs) { + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; } @@ -164,39 +164,31 @@ namespace SourceGit.ViewModels // Use merge/rebase instead of pull as fetch is done manually. if (UseRebase) - { - SetProgressDescription($"Rebase {_current.Name} on {_selectedBranch.FriendlyName} ..."); - rs = new Commands.Rebase(_repo.FullPath, _selectedBranch.FriendlyName, false).Exec(); - } + rs = new Commands.Rebase(_repo.FullPath, _selectedBranch.FriendlyName, false).Use(log).Exec(); else - { - SetProgressDescription($"Merge {_selectedBranch.FriendlyName} into {_current.Name} ..."); - rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "", SetProgressDescription).Exec(); - } + rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "").Use(log).Exec(); } else { - SetProgressDescription($"Pull {_selectedRemote.Name}/{_selectedBranch.Name}..."); rs = new Commands.Pull( _repo.FullPath, _selectedRemote.Name, _selectedBranch.Name, UseRebase, - NoTags, - SetProgressDescription).Exec(); + NoTags).Use(log).Exec(); } if (rs && needPopStash) - { - SetProgressDescription("Re-apply local changes..."); - rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); - } + rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + + log.Complete(); CallUIThread(() => { _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName); _repo.SetWatcherEnabled(true); }); + return rs; }); } diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs index fb06c76e..57604958 100644 --- a/src/ViewModels/Push.cs +++ b/src/ViewModels/Push.cs @@ -164,6 +164,9 @@ namespace SourceGit.ViewModels var remoteBranchName = _selectedRemoteBranch.Name; ProgressDescription = $"Push {_selectedLocalBranch.Name} -> {_selectedRemote.Name}/{remoteBranchName} ..."; + var log = _repo.CreateLog("Push"); + Use(log); + return Task.Run(() => { var succ = new Commands.Push( @@ -174,8 +177,9 @@ namespace SourceGit.ViewModels PushAllTags, _repo.Submodules.Count > 0 && CheckSubmodules, _isSetTrackOptionVisible && Tracking, - ForcePush, - SetProgressDescription).Exec(); + ForcePush).Use(log).Exec(); + + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/PushTag.cs b/src/ViewModels/PushTag.cs index de2941d2..129de3e9 100644 --- a/src/ViewModels/PushTag.cs +++ b/src/ViewModels/PushTag.cs @@ -41,6 +41,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Pushing tag ..."; + var log = _repo.CreateLog("Push Tag"); + Use(log); + return Task.Run(() => { var succ = true; @@ -49,18 +52,17 @@ namespace SourceGit.ViewModels { foreach (var remote in _repo.Remotes) { - SetProgressDescription($"Pushing tag to remote {remote.Name} ..."); - succ = new Commands.Push(_repo.FullPath, remote.Name, tag, false).Exec(); + succ = new Commands.Push(_repo.FullPath, remote.Name, tag, false).Use(log).Exec(); if (!succ) break; } } else { - SetProgressDescription($"Pushing tag to remote {SelectedRemote.Name} ..."); - succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false).Exec(); + succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false).Use(log).Exec(); } + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Rebase.cs b/src/ViewModels/Rebase.cs index 963640dc..c6bee413 100644 --- a/src/ViewModels/Rebase.cs +++ b/src/ViewModels/Rebase.cs @@ -47,9 +47,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Rebasing ..."; + var log = _repo.CreateLog("Rebase"); + Use(log); + return Task.Run(() => { - new Commands.Rebase(_repo.FullPath, _revision, AutoStash).Exec(); + new Commands.Rebase(_repo.FullPath, _revision, AutoStash).Use(log).Exec(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/RemoveWorktree.cs b/src/ViewModels/RemoveWorktree.cs index 0a2e620b..1de8360a 100644 --- a/src/ViewModels/RemoveWorktree.cs +++ b/src/ViewModels/RemoveWorktree.cs @@ -26,11 +26,15 @@ namespace SourceGit.ViewModels public override Task Sure() { _repo.SetWatcherEnabled(false); - ProgressDescription = "Remove worktrees ..."; + ProgressDescription = "Remove worktree ..."; + + var log = _repo.CreateLog("Remove worktree"); + Use(log); return Task.Run(() => { - var succ = new Commands.Worktree(_repo.FullPath).Remove(Target.FullPath, Force, SetProgressDescription); + var succ = new Commands.Worktree(_repo.FullPath).Use(log).Remove(Target.FullPath, Force); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/RenameBranch.cs b/src/ViewModels/RenameBranch.cs index f78eb46b..9c7c8742 100644 --- a/src/ViewModels/RenameBranch.cs +++ b/src/ViewModels/RenameBranch.cs @@ -54,10 +54,15 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Rename '{Target.Name}'"; + var log = _repo.CreateLog($"Rename Branch '{Target.Name}'"); + Use(log); + return Task.Run(() => { var oldName = Target.FullName; - var succ = Commands.Branch.Rename(_repo.FullPath, Target.Name, fixedName); + var succ = Commands.Branch.Rename(_repo.FullPath, Target.Name, fixedName, log); + log.Complete(); + CallUIThread(() => { if (succ) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 700cbd0f..36ad9cbc 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -6,6 +6,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -409,6 +410,12 @@ namespace SourceGit.ViewModels set; } = 0; + public AvaloniaList Logs + { + get; + private set; + } = new AvaloniaList(); + public Repository(bool isBare, string path, string gitDir) { IsBare = isBare; @@ -472,6 +479,8 @@ namespace SourceGit.ViewModels public void Close() { SelectedView = null; // Do NOT modify. Used to remove exists widgets for GC.Collect + Logs.Clear(); + _settings.LastCommitMessage = _workingCopy.CommitMessage; var settingsSerialized = JsonSerializer.Serialize(_settings, JsonCodeGen.Default.RepositorySettings); @@ -538,6 +547,13 @@ namespace SourceGit.ViewModels GetOwnerPage()?.StartPopup(popup); } + public CommandLog CreateLog(string name) + { + var log = new CommandLog(name); + Logs.Insert(0, log); + return log; + } + public void RefreshAll() { Task.Run(() => @@ -2560,7 +2576,7 @@ namespace SourceGit.ViewModels Dispatcher.UIThread.Invoke(() => IsAutoFetching = true); foreach (var remote in remotes) - new Commands.Fetch(_fullpath, remote, false, false, null) { RaiseError = false }.Exec(); + new Commands.Fetch(_fullpath, remote, false, false) { RaiseError = false }.Exec(); _lastFetchTime = DateTime.Now; Dispatcher.UIThread.Invoke(() => IsAutoFetching = false); } diff --git a/src/ViewModels/Reset.cs b/src/ViewModels/Reset.cs index 9500f40a..aecd7441 100644 --- a/src/ViewModels/Reset.cs +++ b/src/ViewModels/Reset.cs @@ -36,9 +36,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Reset current branch to {To.SHA} ..."; + var log = _repo.CreateLog($"Reset HEAD to '{To.SHA}'"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Reset(_repo.FullPath, To.SHA, SelectedMode.Arg).Exec(); + var succ = new Commands.Reset(_repo.FullPath, To.SHA, SelectedMode.Arg).Use(log).Exec(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/Revert.cs b/src/ViewModels/Revert.cs index f0eefab1..2c88e093 100644 --- a/src/ViewModels/Revert.cs +++ b/src/ViewModels/Revert.cs @@ -29,9 +29,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Revert commit '{Target.SHA}' ..."; + var log = _repo.CreateLog($"Revert '{Target.SHA}'"); + Use(log); + return Task.Run(() => { - new Commands.Revert(_repo.FullPath, Target.SHA, AutoCommit).Exec(); + new Commands.Revert(_repo.FullPath, Target.SHA, AutoCommit).Use(log).Exec(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs index 955a0d38..7a9477aa 100644 --- a/src/ViewModels/Reword.cs +++ b/src/ViewModels/Reword.cs @@ -37,9 +37,13 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Editing head commit message ..."; + var log = _repo.CreateLog("Reword HEAD"); + Use(log); + return Task.Run(() => { - var succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Run(); + var succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Use(log).Run(); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/ScanRepositories.cs b/src/ViewModels/ScanRepositories.cs index 8d14e36e..b2997947 100644 --- a/src/ViewModels/ScanRepositories.cs +++ b/src/ViewModels/ScanRepositories.cs @@ -97,7 +97,7 @@ namespace SourceGit.ViewModels subdir.Name.Equals(".idea", StringComparison.Ordinal)) continue; - SetProgressDescription($"Scanning {subdir.FullName}..."); + CallUIThread(() => ProgressDescription = $"Scanning {subdir.FullName}..."); var normalizedSelf = subdir.FullName.Replace("\\", "/"); if (_managed.Contains(normalizedSelf)) diff --git a/src/ViewModels/SetUpstream.cs b/src/ViewModels/SetUpstream.cs index 48a03ac0..efdf0377 100644 --- a/src/ViewModels/SetUpstream.cs +++ b/src/ViewModels/SetUpstream.cs @@ -55,17 +55,22 @@ namespace SourceGit.ViewModels public override Task Sure() { - SetProgressDescription("Setting upstream..."); + ProgressDescription = "Setting upstream..."; var upstream = (_unset || SelectedRemoteBranch == null) ? string.Empty : SelectedRemoteBranch.FullName; if (upstream == Local.Upstream) return null; + var log = _repo.CreateLog("Set Upstream"); + Use(log); + return Task.Run(() => { - var succ = Commands.Branch.SetUpstream(_repo.FullPath, Local.Name, upstream.Replace("refs/remotes/", "")); + var succ = Commands.Branch.SetUpstream(_repo.FullPath, Local.Name, upstream.Replace("refs/remotes/", ""), log); if (succ) _repo.RefreshBranches(); + + log.Complete(); return true; }); } diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs index eceb8339..14a2ad2d 100644 --- a/src/ViewModels/Squash.cs +++ b/src/ViewModels/Squash.cs @@ -31,6 +31,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = "Squashing ..."; + var log = _repo.CreateLog("Squash"); + Use(log); + return Task.Run(() => { var autoStashed = false; @@ -38,9 +41,10 @@ namespace SourceGit.ViewModels if (_repo.LocalChangesCount > 0) { - succ = new Commands.Stash(_repo.FullPath).Push("SQUASH_AUTO_STASH"); + succ = new Commands.Stash(_repo.FullPath).Use(log).Push("SQUASH_AUTO_STASH"); if (!succ) { + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return false; } @@ -48,13 +52,14 @@ namespace SourceGit.ViewModels autoStashed = true; } - succ = new Commands.Reset(_repo.FullPath, Target.SHA, "--soft").Exec(); + 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).Run(); + succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Use(log).Run(); if (succ && autoStashed) - new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); + new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}"); + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return succ; }); diff --git a/src/ViewModels/StashChanges.cs b/src/ViewModels/StashChanges.cs index e35aaad0..29ca5717 100644 --- a/src/ViewModels/StashChanges.cs +++ b/src/ViewModels/StashChanges.cs @@ -56,6 +56,9 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Stash changes ..."; + var log = _repo.CreateLog("Stash Local Changes"); + Use(log); + return Task.Run(() => { var succ = false; @@ -66,7 +69,7 @@ namespace SourceGit.ViewModels { if (Native.OS.GitVersion >= Models.GitVersions.STASH_PUSH_ONLY_STAGED) { - succ = new Commands.Stash(_repo.FullPath).PushOnlyStaged(Message, KeepIndex); + succ = new Commands.Stash(_repo.FullPath).Use(log).PushOnlyStaged(Message, KeepIndex); } else { @@ -77,22 +80,23 @@ namespace SourceGit.ViewModels staged.Add(c); } - succ = StashWithChanges(staged); + succ = StashWithChanges(staged, log); } } else { - succ = new Commands.Stash(_repo.FullPath).Push(Message, IncludeUntracked, KeepIndex); + succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, IncludeUntracked, KeepIndex); } } else { - succ = StashWithChanges(_changes); + succ = StashWithChanges(_changes, log); } if (AutoRestore && succ) - succ = new Commands.Stash(_repo.FullPath).Apply("stash@{0}", true); + succ = new Commands.Stash(_repo.FullPath).Use(log).Apply("stash@{0}", true); + log.Complete(); CallUIThread(() => { _repo.MarkWorkingCopyDirtyManually(); @@ -103,7 +107,7 @@ namespace SourceGit.ViewModels }); } - private bool StashWithChanges(List changes) + private bool StashWithChanges(List changes, CommandLog log) { if (changes.Count == 0) return true; @@ -117,7 +121,7 @@ namespace SourceGit.ViewModels var tmpFile = Path.GetTempFileName(); File.WriteAllLines(tmpFile, paths); - succ = new Commands.Stash(_repo.FullPath).Push(Message, tmpFile, KeepIndex); + succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, tmpFile, KeepIndex); File.Delete(tmpFile); } else @@ -126,7 +130,7 @@ namespace SourceGit.ViewModels { var count = Math.Min(10, changes.Count - i); var step = changes.GetRange(i, count); - succ = new Commands.Stash(_repo.FullPath).Push(Message, step, KeepIndex); + succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, step, KeepIndex); if (!succ) break; } diff --git a/src/ViewModels/StashesPage.cs b/src/ViewModels/StashesPage.cs index e69d9bb5..d77b6b3e 100644 --- a/src/ViewModels/StashesPage.cs +++ b/src/ViewModels/StashesPage.cs @@ -147,7 +147,7 @@ namespace SourceGit.ViewModels apply.Click += (_, ev) => { if (_repo.CanCreatePopup()) - _repo.ShowPopup(new ApplyStash(_repo.FullPath, stash)); + _repo.ShowPopup(new ApplyStash(_repo, stash)); ev.Handled = true; }; @@ -157,7 +157,7 @@ namespace SourceGit.ViewModels drop.Click += (_, ev) => { if (_repo.CanCreatePopup()) - _repo.ShowPopup(new DropStash(_repo.FullPath, stash)); + _repo.ShowPopup(new DropStash(_repo, stash)); ev.Handled = true; }; diff --git a/src/ViewModels/UpdateSubmodules.cs b/src/ViewModels/UpdateSubmodules.cs index 3553d1b5..f75cdb9d 100644 --- a/src/ViewModels/UpdateSubmodules.cs +++ b/src/ViewModels/UpdateSubmodules.cs @@ -62,19 +62,21 @@ namespace SourceGit.ViewModels else targets = [SelectedSubmodule]; + var log = _repo.CreateLog("Update Submodule"); + Use(log); + return Task.Run(() => { foreach (var submodule in targets) { - ProgressDescription = $"Updating submodule {submodule} ..."; - new Commands.Submodule(_repo.FullPath).Update( + new Commands.Submodule(_repo.FullPath).Use(log).Update( submodule, EnableInit, EnableRecursive, - EnableRemote, - SetProgressDescription); + EnableRemote); } + log.Complete(); CallUIThread(() => _repo.SetWatcherEnabled(true)); return true; }); diff --git a/src/ViewModels/ViewLogs.cs b/src/ViewModels/ViewLogs.cs new file mode 100644 index 00000000..44c819ce --- /dev/null +++ b/src/ViewModels/ViewLogs.cs @@ -0,0 +1,27 @@ +using Avalonia.Collections; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class ViewLogs : ObservableObject + { + public AvaloniaList Logs + { + get => _repo.Logs; + } + + public CommandLog SelectedLog + { + get => _selectedLog; + set => SetProperty(ref _selectedLog, value); + } + + public ViewLogs(Repository repo) + { + _repo = repo; + } + + private Repository _repo = null; + private CommandLog _selectedLog = null; + } +} diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs index 3d7b9a28..60b8fd11 100644 --- a/src/ViewModels/WorkingCopy.cs +++ b/src/ViewModels/WorkingCopy.cs @@ -711,7 +711,9 @@ namespace SourceGit.ViewModels assumeUnchanged.IsVisible = change.WorkTree != Models.ChangeState.Untracked; assumeUnchanged.Click += (_, e) => { - new Commands.AssumeUnchanged(_repo.FullPath).Add(change.Path); + var log = _repo.CreateLog("Assume File Unchanged"); + new Commands.AssumeUnchanged(_repo.FullPath, change.Path, true).Use(log).Exec(); + log.Complete(); e.Handled = true; }; @@ -805,10 +807,12 @@ namespace SourceGit.ViewModels lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename); lfsTrackThisFile.Click += async (_, e) => { - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true)); + var log = _repo.CreateLog("Track LFS"); + var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true, log)); if (succ) App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!"); + log.Complete(); e.Handled = true; }; lfs.Items.Add(lfsTrackThisFile); @@ -819,10 +823,12 @@ namespace SourceGit.ViewModels lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension); lfsTrackByExtension.Click += async (_, e) => { - var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track("*" + extension)); + var log = _repo.CreateLog("Track LFS"); + 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!"); + log.Complete(); e.Handled = true; }; lfs.Items.Add(lfsTrackByExtension); @@ -1581,9 +1587,11 @@ namespace SourceGit.ViewModels IsStaging = true; _repo.SetWatcherEnabled(false); + + var log = _repo.CreateLog("Stage"); if (count == _unstaged.Count) { - await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec()); + await Task.Run(() => new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).Exec()); } else if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE) { @@ -1593,7 +1601,7 @@ namespace SourceGit.ViewModels var tmpFile = Path.GetTempFileName(); File.WriteAllLines(tmpFile, paths); - await Task.Run(() => new Commands.Add(_repo.FullPath, tmpFile).Exec()); + await Task.Run(() => new Commands.Add(_repo.FullPath, tmpFile).Use(log).Exec()); File.Delete(tmpFile); } else @@ -1605,9 +1613,10 @@ namespace SourceGit.ViewModels 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).Exec()); + await Task.Run(() => new Commands.Add(_repo.FullPath, step).Use(log).Exec()); } } + log.Complete(); _repo.MarkWorkingCopyDirtyManually(); _repo.SetWatcherEnabled(true); IsStaging = false; @@ -1624,22 +1633,26 @@ namespace SourceGit.ViewModels IsUnstaging = true; _repo.SetWatcherEnabled(false); + + var log = _repo.CreateLog("Unstage"); if (_useAmend) { + 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).Exec()); + await Task.Run(() => new Commands.Reset(_repo.FullPath).Use(log).Exec()); } else { for (int i = 0; i < count; i += 10) { var step = changes.GetRange(i, Math.Min(10, count - i)); - await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Exec()); + await Task.Run(() => new Commands.Reset(_repo.FullPath, step).Use(log).Exec()); } } + log.Complete(); _repo.MarkWorkingCopyDirtyManually(); _repo.SetWatcherEnabled(true); IsUnstaging = false; @@ -1703,14 +1716,17 @@ namespace SourceGit.ViewModels _repo.Settings.PushCommitMessage(_commitMessage); _repo.SetWatcherEnabled(false); + var log = _repo.CreateLog("Commit"); Task.Run(() => { var succ = true; if (autoStage && _unstaged.Count > 0) - succ = new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec(); + succ = new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Use(log).Exec(); if (succ) - succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend, _repo.Settings.EnableSignOffForCommit).Run(); + succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend, _repo.Settings.EnableSignOffForCommit).Use(log).Run(); + + log.Complete(); Dispatcher.UIThread.Post(() => { diff --git a/src/Views/RepositoryToolbar.axaml b/src/Views/RepositoryToolbar.axaml index 9f7f1801..c515387f 100644 --- a/src/Views/RepositoryToolbar.axaml +++ b/src/Views/RepositoryToolbar.axaml @@ -21,6 +21,10 @@ + + @@ -102,7 +106,7 @@ + diff --git a/src/Views/RepositoryToolbar.axaml.cs b/src/Views/RepositoryToolbar.axaml.cs index 66b49fc2..dbcf73ec 100644 --- a/src/Views/RepositoryToolbar.axaml.cs +++ b/src/Views/RepositoryToolbar.axaml.cs @@ -128,6 +128,16 @@ namespace SourceGit.Views e.Handled = true; } + + private async void OpenGitLogs(object sender, RoutedEventArgs e) + { + if (DataContext is ViewModels.Repository repo && TopLevel.GetTopLevel(this) is Window owner) + { + var dialog = new ViewLogs() { DataContext = new ViewModels.ViewLogs(repo) }; + await dialog.ShowDialog(owner); + e.Handled = true; + } + } } } diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 1cce96f6..722d6912 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1972,7 +1972,7 @@ namespace SourceGit.Views if (!selection.HasLeftChanges) { - Commands.Discard.Changes(repo.FullPath, [change]); + Commands.Discard.Changes(repo.FullPath, [change], null); } else { diff --git a/src/Views/ViewLogs.axaml b/src/Views/ViewLogs.axaml new file mode 100644 index 00000000..8272078d --- /dev/null +++ b/src/Views/ViewLogs.axaml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/ViewLogs.axaml.cs b/src/Views/ViewLogs.axaml.cs new file mode 100644 index 00000000..624ff21e --- /dev/null +++ b/src/Views/ViewLogs.axaml.cs @@ -0,0 +1,108 @@ +using System; +using System.ComponentModel; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; + +using AvaloniaEdit; +using AvaloniaEdit.Document; +using AvaloniaEdit.Editing; +using AvaloniaEdit.TextMate; + +namespace SourceGit.Views +{ + public class LogContentPresenter : TextEditor + { + public static readonly StyledProperty LogProperty = + AvaloniaProperty.Register(nameof(Log)); + + public ViewModels.CommandLog Log + { + get => GetValue(LogProperty); + set => SetValue(LogProperty, value); + } + + protected override Type StyleKeyOverride => typeof(TextEditor); + + public LogContentPresenter() : base(new TextArea(), new TextDocument()) + { + IsReadOnly = true; + ShowLineNumbers = false; + WordWrap = false; + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto; + VerticalScrollBarVisibility = ScrollBarVisibility.Auto; + + TextArea.TextView.Margin = new Thickness(4, 0); + TextArea.TextView.Options.EnableHyperlinks = true; + TextArea.TextView.Options.EnableEmailHyperlinks = true; + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + + if (_textMate == null) + { + _textMate = Models.TextMateHelper.CreateForEditor(this); + Models.TextMateHelper.SetGrammarByFileName(_textMate, "Log.log"); + } + } + + protected override void OnUnloaded(RoutedEventArgs e) + { + base.OnUnloaded(e); + + if (_textMate != null) + { + _textMate.Dispose(); + _textMate = null; + } + + GC.Collect(); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == LogProperty) + { + if (change.NewValue is ViewModels.CommandLog log) + { + Text = log.Content; + log.Register(OnLogLineReceived); + } + else + { + Text = string.Empty; + } + } + } + + private void OnLogLineReceived(string newline) + { + AppendText("\n"); + AppendText(newline); + } + + private TextMate.Installation _textMate = null; + } + + public partial class ViewLogs : ChromelessWindow + { + public ViewLogs() + { + InitializeComponent(); + } + + private void OnRemoveLog(object sender, RoutedEventArgs e) + { + if (sender is Button { DataContext: ViewModels.CommandLog log } && DataContext is ViewModels.ViewLogs vm) + vm.Logs.Remove(log); + + e.Handled = true; + } + } +}