feature: git command logs

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2025-04-17 12:30:20 +08:00
parent 928a0ad3c5
commit 8b39df32cc
No known key found for this signature in database
101 changed files with 1040 additions and 573 deletions

11
src/App.Utils.cs Normal file
View file

@ -0,0 +1,11 @@
namespace SourceGit
{
public static class CommandExtensions
{
public static T Use<T>(this T cmd, Models.ICommandLog log) where T : Commands.Command
{
cmd.Log = log;
return cmd;
}
}
}

View file

@ -1,23 +1,12 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class Archive : Command public class Archive : Command
{ {
public Archive(string repo, string revision, string saveTo, Action<string> outputHandler) public Archive(string repo, string revision, string saveTo)
{ {
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}"; Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}";
TraitErrorAsOutput = true;
_outputHandler = outputHandler;
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler;
} }
} }

View file

@ -1,75 +1,14 @@
using System.Collections.Generic; namespace SourceGit.Commands
using System.Text.RegularExpressions;
namespace SourceGit.Commands
{ {
public partial class AssumeUnchanged public class AssumeUnchanged : Command
{ {
[GeneratedRegex(@"^(\w)\s+(.+)$")] public AssumeUnchanged(string repo, string file, bool bAdd)
private static partial Regex REG_PARSE();
class ViewCommand : Command
{ {
public ViewCommand(string repo) var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
{
WorkingDirectory = repo;
Args = "ls-files -v";
RaiseError = false;
}
public List<string> Result() WorkingDirectory = repo;
{ Context = repo;
Exec(); Args = $"update-index {mode} -- \"{file}\"";
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<string> _outs = new List<string>();
} }
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<string> 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;
} }
} }

View file

@ -21,10 +21,17 @@ namespace SourceGit.Commands
public Models.BlameData Result() public Models.BlameData Result()
{ {
var succ = Exec(); var rs = ReadToEnd();
if (!succ) 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) if (_needUnifyCommitSHA)
@ -42,13 +49,8 @@ namespace SourceGit.Commands
return _result; 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) if (line.IndexOf('\0', StringComparison.Ordinal) >= 0)
{ {
_result.IsBinary = true; _result.IsBinary = true;

View file

@ -11,29 +11,32 @@
return cmd.ReadToEnd().StdOut.Trim(); 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(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"branch {name} {basedOn}"; cmd.Args = $"branch {name} {basedOn}";
cmd.Log = log;
return cmd.Exec(); 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(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"branch -M {name} {to}"; cmd.Args = $"branch -M {name} {to}";
cmd.Log = log;
return cmd.Exec(); 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(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Log = log;
if (string.IsNullOrEmpty(upstream)) if (string.IsNullOrEmpty(upstream))
cmd.Args = $"branch {name} --unset-upstream"; cmd.Args = $"branch {name} --unset-upstream";
@ -43,25 +46,27 @@
return cmd.Exec(); 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(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"branch -D {name}"; cmd.Args = $"branch -D {name}";
cmd.Log = log;
return cmd.Exec(); 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); bool exists = new Remote(repo).HasBranch(remote, name);
if (exists) 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(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"branch -D -r {remote}/{name}"; cmd.Args = $"branch -D -r {remote}/{name}";
cmd.Log = log;
return cmd.Exec(); return cmd.Exec();
} }
} }

View file

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace SourceGit.Commands namespace SourceGit.Commands
@ -12,19 +11,15 @@ namespace SourceGit.Commands
Context = repo; Context = repo;
} }
public bool Branch(string branch, Action<string> onProgress) public bool Branch(string branch)
{ {
Args = $"checkout --recurse-submodules --progress {branch}"; Args = $"checkout --recurse-submodules --progress {branch}";
TraitErrorAsOutput = true;
_outputHandler = onProgress;
return Exec(); return Exec();
} }
public bool Branch(string branch, string basedOn, Action<string> onProgress) public bool Branch(string branch, string basedOn)
{ {
Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}"; Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}";
TraitErrorAsOutput = true;
_outputHandler = onProgress;
return Exec(); return Exec();
} }
@ -62,19 +57,10 @@ namespace SourceGit.Commands
return Exec(); return Exec();
} }
public bool Commit(string commitId, Action<string> onProgress) public bool Commit(string commitId)
{ {
Args = $"checkout --detach --progress {commitId}"; Args = $"checkout --detach --progress {commitId}";
TraitErrorAsOutput = true;
_outputHandler = onProgress;
return Exec(); return Exec();
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private Action<string> _outputHandler;
} }
} }

View file

@ -1,16 +1,11 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class Clone : Command public class Clone : Command
{ {
private readonly Action<string> _notifyProgress; public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs)
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action<string> ouputHandler)
{ {
Context = ctx; Context = ctx;
WorkingDirectory = path; WorkingDirectory = path;
TraitErrorAsOutput = true;
SSHKey = sshKey; SSHKey = sshKey;
Args = "clone --progress --verbose "; Args = "clone --progress --verbose ";
@ -21,13 +16,6 @@ namespace SourceGit.Commands
if (!string.IsNullOrEmpty(localName)) if (!string.IsNullOrEmpty(localName))
Args += localName; Args += localName;
_notifyProgress = ouputHandler;
}
protected override void OnReadline(string line)
{
_notifyProgress?.Invoke(line);
} }
} }
} }

View file

@ -32,7 +32,7 @@ namespace SourceGit.Commands
public string SSHKey { get; set; } = string.Empty; public string SSHKey { get; set; } = string.Empty;
public string Args { get; set; } = string.Empty; public string Args { get; set; } = string.Empty;
public bool RaiseError { get; set; } = true; public bool RaiseError { get; set; } = true;
public bool TraitErrorAsOutput { get; set; } = false; public Models.ICommandLog Log { get; set; } = null;
public bool Exec() public bool Exec()
{ {
@ -40,10 +40,14 @@ namespace SourceGit.Commands
var errs = new List<string>(); var errs = new List<string>();
var proc = new Process() { StartInfo = start }; var proc = new Process() { StartInfo = start };
Log?.AppendLine($"$ git {Args}\n");
proc.OutputDataReceived += (_, e) => proc.OutputDataReceived += (_, e) =>
{ {
if (e.Data != null) if (e.Data == null)
OnReadline(e.Data); return;
Log?.AppendLine(e.Data);
}; };
proc.ErrorDataReceived += (_, e) => proc.ErrorDataReceived += (_, e) =>
@ -54,8 +58,7 @@ namespace SourceGit.Commands
return; return;
} }
if (TraitErrorAsOutput) Log?.AppendLine(e.Data);
OnReadline(e.Data);
// Ignore progress messages // Ignore progress messages
if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal)) if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal))
@ -97,6 +100,7 @@ namespace SourceGit.Commands
if (RaiseError) if (RaiseError)
Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message)); Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message));
Log?.AppendLine(string.Empty);
return false; return false;
} }
@ -114,6 +118,7 @@ namespace SourceGit.Commands
int exitCode = proc.ExitCode; int exitCode = proc.ExitCode;
proc.Close(); proc.Close();
Log?.AppendLine(string.Empty);
if (!CancellationToken.IsCancellationRequested && exitCode != 0) if (!CancellationToken.IsCancellationRequested && exitCode != 0)
{ {
@ -162,11 +167,6 @@ namespace SourceGit.Commands
return rs; return rs;
} }
protected virtual void OnReadline(string line)
{
// Implemented by derived class
}
private ProcessStartInfo CreateGitStartInfo() private ProcessStartInfo CreateGitStartInfo()
{ {
var start = new ProcessStartInfo(); var start = new ProcessStartInfo();

View file

@ -11,7 +11,6 @@ namespace SourceGit.Commands
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
Args = $"commit --allow-empty --file=\"{_tmpFile}\""; Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
if (amend) if (amend)
Args += " --amend --no-edit"; Args += " --amend --no-edit";

View file

@ -31,12 +31,19 @@ namespace SourceGit.Commands
public List<Models.Change> Result() public List<Models.Change> 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)); _changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
return _changes; return _changes;
} }
protected override void OnReadline(string line) private void ParseLine(string line)
{ {
var match = REG_FORMAT().Match(line); var match = REG_FORMAT().Match(line);
if (!match.Success) if (!match.Success)

View file

@ -5,13 +5,13 @@ namespace SourceGit.Commands
{ {
public static class Discard 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 Restore(repo).Use(log).Exec();
new Clean(repo, includeIgnored).Exec(); new Clean(repo, includeIgnored).Use(log).Exec();
} }
public static void Changes(string repo, List<Models.Change> changes) public static void Changes(string repo, List<Models.Change> changes, Models.ICommandLog log)
{ {
var needClean = new List<string>(); var needClean = new List<string>();
var needCheckout = new List<string>(); var needCheckout = new List<string>();
@ -27,13 +27,13 @@ namespace SourceGit.Commands
for (int i = 0; i < needClean.Count; i += 10) for (int i = 0; i < needClean.Count; i += 10)
{ {
var count = Math.Min(10, needClean.Count - i); 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) for (int i = 0; i < needCheckout.Count; i += 10)
{ {
var count = Math.Min(10, needCheckout.Count - i); 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();
} }
} }
} }

View file

@ -1,15 +1,11 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class Fetch : Command public class Fetch : Command
{ {
public Fetch(string repo, string remote, bool noTags, bool force, Action<string> outputHandler) public Fetch(string repo, string remote, bool noTags, bool force)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
Args = "fetch --progress --verbose "; Args = "fetch --progress --verbose ";
@ -24,21 +20,12 @@ namespace SourceGit.Commands
Args += remote; Args += remote;
} }
public Fetch(string repo, Models.Branch local, Models.Branch remote, Action<string> outputHandler) public Fetch(string repo, Models.Branch local, Models.Branch remote)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey"); SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey");
Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}"; Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}";
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler;
} }
} }

View file

@ -1,23 +1,12 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class GC : Command public class GC : Command
{ {
public GC(string repo, Action<string> outputHandler) public GC(string repo)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
Args = "gc --prune=now"; Args = "gc --prune=now";
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler;
} }
} }

View file

@ -35,17 +35,17 @@ namespace SourceGit.Commands
config.ContainsKey("gitflow.prefix.hotfix"); config.ContainsKey("gitflow.prefix.hotfix");
} }
public static bool Init(string repo, List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version) public static bool Init(string repo, List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log)
{ {
var current = branches.Find(x => x.IsCurrent); var current = branches.Find(x => x.IsCurrent);
var masterBranch = branches.Find(x => x.Name == master); var masterBranch = branches.Find(x => x.Name == master);
if (masterBranch == null && current != null) 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); var devBranch = branches.Find(x => x.Name == develop);
if (devBranch == null && current != null) if (devBranch == null && current != null)
Branch.Create(repo, develop, current.Head); Branch.Create(repo, develop, current.Head, log);
var config = new Config(repo); var config = new Config(repo);
config.Set("gitflow.branch.master", master); config.Set("gitflow.branch.master", master);
@ -61,6 +61,7 @@ namespace SourceGit.Commands
init.WorkingDirectory = repo; init.WorkingDirectory = repo;
init.Context = repo; init.Context = repo;
init.Args = "flow init -d"; init.Args = "flow init -d";
init.Log = log;
return init.Exec(); return init.Exec();
} }
@ -113,7 +114,7 @@ namespace SourceGit.Commands
return rs; 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)) if (!SUPPORTED_BRANCH_TYPES.Contains(type))
{ {
@ -129,10 +130,11 @@ namespace SourceGit.Commands
start.WorkingDirectory = repo; start.WorkingDirectory = repo;
start.Context = repo; start.Context = repo;
start.Args = $"flow {type} start {name}"; start.Args = $"flow {type} start {name}";
start.Log = log;
return start.Exec(); 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)) if (!SUPPORTED_BRANCH_TYPES.Contains(type))
{ {
@ -149,6 +151,7 @@ namespace SourceGit.Commands
finish.WorkingDirectory = repo; finish.WorkingDirectory = repo;
finish.Context = repo; finish.Context = repo;
finish.Args = $"flow {type} finish {option} {name}"; finish.Args = $"flow {type} finish {option} {name}";
finish.Log = log;
return finish.Exec(); return finish.Exec();
} }

View file

@ -10,5 +10,10 @@
Context = repo; Context = repo;
Args = $"diff -a --ignore-cr-at-eol --check {opt}"; Args = $"diff -a --ignore-cr-at-eol --check {opt}";
} }
public bool Result()
{
return ReadToEnd().IsSuccess;
}
} }
} }

View file

@ -12,21 +12,13 @@ namespace SourceGit.Commands
class SubCmd : Command class SubCmd : Command
{ {
public SubCmd(string repo, string args, Action<string> onProgress) public SubCmd(string repo, string args, Models.ICommandLog log)
{ {
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
Args = args; Args = args;
TraitErrorAsOutput = true; Log = log;
_outputHandler = onProgress;
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler;
} }
public LFS(string repo) public LFS(string repo)
@ -49,30 +41,30 @@ namespace SourceGit.Commands
return new SubCmd(_repo, "lfs install --local", null).Exec(); 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" : ""; 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<string> 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<string> 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<string> 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<string> outputHandler) public void Prune(Models.ICommandLog log)
{ {
new SubCmd(_repo, "lfs prune", outputHandler).Exec(); new SubCmd(_repo, "lfs prune", log).Exec();
} }
public List<Models.LFSLock> Locks(string remote) public List<Models.LFSLock> Locks(string remote)

View file

@ -1,26 +1,21 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace SourceGit.Commands namespace SourceGit.Commands
{ {
public class Merge : Command public class Merge : Command
{ {
public Merge(string repo, string source, string mode, Action<string> outputHandler) public Merge(string repo, string source, string mode)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
Args = $"merge --progress {source} {mode}"; Args = $"merge --progress {source} {mode}";
} }
public Merge(string repo, List<string> targets, bool autoCommit, string strategy, Action<string> outputHandler) public Merge(string repo, List<string> targets, bool autoCommit, string strategy)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append("merge --progress "); builder.Append("merge --progress ");
@ -37,12 +32,5 @@ namespace SourceGit.Commands
Args = builder.ToString(); Args = builder.ToString();
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler = null;
} }
} }

View file

@ -1,15 +1,11 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class Pull : Command public class Pull : Command
{ {
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action<string> outputHandler) public Pull(string repo, string remote, string branch, bool useRebase, bool noTags)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
Args = "pull --verbose --progress "; Args = "pull --verbose --progress ";
@ -21,12 +17,5 @@ namespace SourceGit.Commands
Args += $"{remote} {branch}"; Args += $"{remote} {branch}";
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler;
} }
} }

View file

@ -1,16 +1,11 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class Push : Command public class Push : Command
{ {
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force, Action<string> onProgress) public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force)
{ {
_outputHandler = onProgress;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
TraitErrorAsOutput = true;
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey"); SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
Args = "push --progress --verbose "; Args = "push --progress --verbose ";
@ -38,12 +33,5 @@ namespace SourceGit.Commands
Args += $"{remote} {refname}"; Args += $"{remote} {refname}";
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private readonly Action<string> _outputHandler = null;
} }
} }

View file

@ -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<string> Result()
{
var outs = new List<string>();
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;
}
}
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace SourceGit.Commands namespace SourceGit.Commands
{ {
@ -14,17 +15,21 @@ namespace SourceGit.Commands
public List<string> Result() public List<string> Result()
{ {
Exec(); var rs = ReadToEnd();
return _lines; var outs = new List<string>();
} 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) return outs;
{
if (line.Contains(_commit))
_lines.Add(line.Substring(0, 40));
} }
private string _commit; private string _commit;
private List<string> _lines = new List<string>();
} }
} }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace SourceGit.Commands namespace SourceGit.Commands
@ -18,21 +19,23 @@ namespace SourceGit.Commands
public List<Models.Change> Result() public List<Models.Change> Result()
{ {
Exec(); var outs = new List<Models.Change>();
return _changes; var rs = ReadToEnd();
} if (!rs.IsSuccess)
return outs;
protected override void OnReadline(string line) var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
{ foreach (var line in lines)
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 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": case " M":
change.Set(Models.ChangeState.None, Models.ChangeState.Modified); change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
break; break;
@ -145,12 +148,14 @@ namespace SourceGit.Commands
change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked); change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked);
break; break;
default: 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<Models.Change> _changes = new List<Models.Change>();
} }
} }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace SourceGit.Commands namespace SourceGit.Commands
@ -17,27 +18,31 @@ namespace SourceGit.Commands
public List<Models.Remote> Result() public List<Models.Remote> Result()
{ {
Exec(); var outs = new List<Models.Remote>();
return _loaded; var rs = ReadToEnd();
} if (!rs.IsSuccess)
return outs;
protected override void OnReadline(string line) var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
{ foreach (var line in lines)
var match = REG_REMOTE().Match(line);
if (!match.Success)
return;
var remote = new Models.Remote()
{ {
Name = match.Groups[1].Value, var match = REG_REMOTE().Match(line);
URL = match.Groups[2].Value, if (!match.Success)
}; continue;
if (_loaded.Find(x => x.Name == remote.Name) != null) var remote = new Models.Remote()
return; {
_loaded.Add(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<Models.Remote> _loaded = new List<Models.Remote>();
} }
} }

View file

@ -14,35 +14,50 @@ namespace SourceGit.Commands
public List<Models.Stash> Result() public List<Models.Stash> Result()
{ {
Exec(); var outs = new List<Models.Stash>();
return _stashes; var rs = ReadToEnd();
} if (!rs.IsSuccess)
return outs;
protected override void OnReadline(string line) var nextPartIdx = 0;
{ var start = 0;
switch (_nextLineIdx) var end = rs.StdOut.IndexOf('\n', start);
while (end > 0)
{ {
case 0: var line = rs.StdOut.Substring(start, end - start);
_current = new Models.Stash() { SHA = line };
_stashes.Add(_current); switch (nextPartIdx)
break; {
case 1: case 0:
ParseParent(line); _current = new Models.Stash() { SHA = line };
break; outs.Add(_current);
case 2: break;
_current.Time = ulong.Parse(line); case 1:
break; ParseParent(line);
case 3: break;
_current.Name = line; case 2:
break; _current.Time = ulong.Parse(line);
case 4: break;
_current.Message = line; case 3:
break; _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 (start < rs.StdOut.Length)
if (_nextLineIdx > 4) _current.Message = rs.StdOut.Substring(start);
_nextLineIdx = 0;
return outs;
} }
private void ParseParent(string data) private void ParseParent(string data)
@ -53,8 +68,6 @@ namespace SourceGit.Commands
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries)); _current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
} }
private readonly List<Models.Stash> _stashes = new List<Models.Stash>();
private Models.Stash _current = null; private Models.Stash _current = null;
private int _nextLineIdx = 0;
} }
} }

View file

@ -1,6 +1,4 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class Submodule : Command public class Submodule : Command
{ {
@ -10,9 +8,8 @@ namespace SourceGit.Commands
Context = repo; Context = repo;
} }
public bool Add(string url, string relativePath, bool recursive, Action<string> outputHandler) public bool Add(string url, string relativePath, bool recursive)
{ {
_outputHandler = outputHandler;
Args = $"submodule add {url} \"{relativePath}\""; Args = $"submodule add {url} \"{relativePath}\"";
if (!Exec()) if (!Exec())
return false; return false;
@ -29,7 +26,7 @@ namespace SourceGit.Commands
} }
} }
public bool Update(string module, bool init, bool recursive, bool useRemote, Action<string> outputHandler) public bool Update(string module, bool init, bool recursive, bool useRemote)
{ {
Args = "submodule update"; Args = "submodule update";
@ -42,7 +39,6 @@ namespace SourceGit.Commands
if (!string.IsNullOrEmpty(module)) if (!string.IsNullOrEmpty(module))
Args += $" -- \"{module}\""; Args += $" -- \"{module}\"";
_outputHandler = outputHandler;
return Exec(); return Exec();
} }
@ -55,12 +51,5 @@ namespace SourceGit.Commands
Args = $"rm -rf \"{relativePath}\""; Args = $"rm -rf \"{relativePath}\"";
return Exec(); return Exec();
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private Action<string> _outputHandler;
} }
} }

View file

@ -1,26 +1,27 @@
using System.Collections.Generic; using System.IO;
using System.IO;
namespace SourceGit.Commands namespace SourceGit.Commands
{ {
public static class Tag 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(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"tag {name} {basedOn}"; cmd.Args = $"tag {name} {basedOn}";
cmd.Log = log;
return cmd.Exec(); 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 param = sign ? "--sign -a" : "--no-sign -a";
var cmd = new Command(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"tag {param} {name} {basedOn} "; cmd.Args = $"tag {param} {name} {basedOn} ";
cmd.Log = log;
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
{ {
@ -36,22 +37,14 @@ namespace SourceGit.Commands
return cmd.Exec(); return cmd.Exec();
} }
public static bool Delete(string repo, string name, List<Models.Remote> remotes) public static bool Delete(string repo, string name, Models.ICommandLog log)
{ {
var cmd = new Command(); var cmd = new Command();
cmd.WorkingDirectory = repo; cmd.WorkingDirectory = repo;
cmd.Context = repo; cmd.Context = repo;
cmd.Args = $"tag --delete {name}"; cmd.Args = $"tag --delete {name}";
if (!cmd.Exec()) cmd.Log = log;
return false; return cmd.Exec();
if (remotes != null)
{
foreach (var r in remotes)
new Push(repo, r.Name, $"refs/tags/{name}", true).Exec();
}
return true;
} }
} }
} }

View file

@ -1,23 +1,12 @@
using System; namespace SourceGit.Commands
namespace SourceGit.Commands
{ {
public class UpdateRef : Command public class UpdateRef : Command
{ {
public UpdateRef(string repo, string refName, string toRevision, Action<string> outputHandler) public UpdateRef(string repo, string refName, string toRevision)
{ {
_outputHandler = outputHandler;
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
Args = $"update-ref {refName} {toRevision}"; Args = $"update-ref {refName} {toRevision}";
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private Action<string> _outputHandler;
} }
} }

View file

@ -56,7 +56,7 @@ namespace SourceGit.Commands
return worktrees; return worktrees;
} }
public bool Add(string fullpath, string name, bool createNew, string tracking, Action<string> outputHandler) public bool Add(string fullpath, string name, bool createNew, string tracking)
{ {
Args = "worktree add "; Args = "worktree add ";
@ -78,14 +78,12 @@ namespace SourceGit.Commands
else if (!string.IsNullOrEmpty(name) && !createNew) else if (!string.IsNullOrEmpty(name) && !createNew)
Args += name; Args += name;
_outputHandler = outputHandler;
return Exec(); return Exec();
} }
public bool Prune(Action<string> outputHandler) public bool Prune()
{ {
Args = "worktree prune -v"; Args = "worktree prune -v";
_outputHandler = outputHandler;
return Exec(); return Exec();
} }
@ -101,22 +99,14 @@ namespace SourceGit.Commands
return Exec(); return Exec();
} }
public bool Remove(string fullpath, bool force, Action<string> outputHandler) public bool Remove(string fullpath, bool force)
{ {
if (force) if (force)
Args = $"worktree remove -f \"{fullpath}\""; Args = $"worktree remove -f \"{fullpath}\"";
else else
Args = $"worktree remove \"{fullpath}\""; Args = $"worktree remove \"{fullpath}\"";
_outputHandler = outputHandler;
return Exec(); return Exec();
} }
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
}
private Action<string> _outputHandler = null;
} }
} }

View file

@ -22,7 +22,6 @@ namespace SourceGit.Models
new ConventionalCommitType("Styles", "style", "Elements or code styles without changing the code logic"), new ConventionalCommitType("Styles", "style", "Elements or code styles without changing the code logic"),
new ConventionalCommitType("Tests", "test", "Adding or updating tests"), new ConventionalCommitType("Tests", "test", "Adding or updating tests"),
new ConventionalCommitType("Chores", "chore", "Other changes that don't modify src or test files"), new ConventionalCommitType("Chores", "chore", "Other changes that don't modify src or test files"),
}; };
public ConventionalCommitType(string name, string type, string description) public ConventionalCommitType(string name, string type, string description)

View file

@ -0,0 +1,7 @@
namespace SourceGit.Models
{
public interface ICommandLog
{
void AppendLine(string line);
}
}

View file

@ -78,6 +78,7 @@
<StreamGeometry x:Key="Icons.Loading">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</StreamGeometry> <StreamGeometry x:Key="Icons.Loading">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Local">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</StreamGeometry> <StreamGeometry x:Key="Icons.Local">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Lock">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</StreamGeometry> <StreamGeometry x:Key="Icons.Lock">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Logs">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Menu">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</StreamGeometry> <StreamGeometry x:Key="Icons.Menu">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Merge">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</StreamGeometry> <StreamGeometry x:Key="Icons.Merge">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</StreamGeometry>
<StreamGeometry x:Key="Icons.Modified">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</StreamGeometry> <StreamGeometry x:Key="Icons.Modified">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</StreamGeometry>

View file

@ -612,6 +612,7 @@
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">Sort</x:String> <x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">Sort</x:String>
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Open in Terminal</x:String> <x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Open in Terminal</x:String>
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">Use relative time in histories</x:String> <x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">Use relative time in histories</x:String>
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">View Logs</x:String>
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">WORKTREES</x:String> <x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">WORKTREES</x:String>
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">ADD WORKTREE</x:String> <x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">ADD WORKTREE</x:String>
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">PRUNE</x:String> <x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">PRUNE</x:String>
@ -697,6 +698,7 @@
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Submodule:</x:String> <x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Submodule:</x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Use --remote option</x:String> <x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Use --remote option</x:String>
<x:String x:Key="Text.URL" xml:space="preserve">URL:</x:String> <x:String x:Key="Text.URL" xml:space="preserve">URL:</x:String>
<x:String x:Key="Text.ViewLogs" xml:space="preserve">Logs</x:String>
<x:String x:Key="Text.Warn" xml:space="preserve">Warning</x:String> <x:String x:Key="Text.Warn" xml:space="preserve">Warning</x:String>
<x:String x:Key="Text.Welcome" xml:space="preserve">Welcome Page</x:String> <x:String x:Key="Text.Welcome" xml:space="preserve">Welcome Page</x:String>
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Create Group</x:String> <x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Create Group</x:String>

View file

@ -616,6 +616,7 @@
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">排序</x:String> <x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">排序</x:String>
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">在终端中打开</x:String> <x:String x:Key="Text.Repository.Terminal" xml:space="preserve">在终端中打开</x:String>
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">在提交列表中使用相对时间</x:String> <x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">在提交列表中使用相对时间</x:String>
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">查看命令日志</x:String>
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">工作树列表</x:String> <x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">工作树列表</x:String>
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">新增工作树</x:String> <x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">新增工作树</x:String>
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">清理</x:String> <x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">清理</x:String>
@ -701,6 +702,7 @@
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">子模块 </x:String> <x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">子模块 </x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">启用 '--remote'</x:String> <x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">启用 '--remote'</x:String>
<x:String x:Key="Text.URL" xml:space="preserve">仓库地址 </x:String> <x:String x:Key="Text.URL" xml:space="preserve">仓库地址 </x:String>
<x:String x:Key="Text.ViewLogs" xml:space="preserve">日志列表</x:String>
<x:String x:Key="Text.Warn" xml:space="preserve">警告</x:String> <x:String x:Key="Text.Warn" xml:space="preserve">警告</x:String>
<x:String x:Key="Text.Welcome" xml:space="preserve">起始页</x:String> <x:String x:Key="Text.Welcome" xml:space="preserve">起始页</x:String>
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">新建分组</x:String> <x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">新建分组</x:String>
@ -757,4 +759,4 @@
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">锁定工作树</x:String> <x:String x:Key="Text.Worktree.Lock" xml:space="preserve">锁定工作树</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">移除工作树</x:String> <x:String x:Key="Text.Worktree.Remove" xml:space="preserve">移除工作树</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">解除工作树锁定</x:String> <x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">解除工作树锁定</x:String>
</ResourceDictionary> </ResourceDictionary>

View file

@ -616,6 +616,7 @@
<x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">排序</x:String> <x:String x:Key="Text.Repository.Tags.Sort" xml:space="preserve">排序</x:String>
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">在終端機中開啟</x:String> <x:String x:Key="Text.Repository.Terminal" xml:space="preserve">在終端機中開啟</x:String>
<x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">在提交列表中使用相對時間</x:String> <x:String x:Key="Text.Repository.UseRelativeTimeInHistories" xml:space="preserve">在提交列表中使用相對時間</x:String>
<x:String x:Key="Text.Repository.ViewLogs" xml:space="preserve">檢視 GIT 指令的日誌</x:String>
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">工作區列表</x:String> <x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">工作區列表</x:String>
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">新增工作區</x:String> <x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">新增工作區</x:String>
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">清理</x:String> <x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">清理</x:String>
@ -701,6 +702,7 @@
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">子模組:</x:String> <x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">子模組:</x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">啟用 [--remote] 選項</x:String> <x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">啟用 [--remote] 選項</x:String>
<x:String x:Key="Text.URL" xml:space="preserve">存放庫網址:</x:String> <x:String x:Key="Text.URL" xml:space="preserve">存放庫網址:</x:String>
<x:String x:Key="Text.ViewLogs" xml:space="preserve">日誌清單</x:String>
<x:String x:Key="Text.Warn" xml:space="preserve">警告</x:String> <x:String x:Key="Text.Warn" xml:space="preserve">警告</x:String>
<x:String x:Key="Text.Welcome" xml:space="preserve">起始頁</x:String> <x:String x:Key="Text.Welcome" xml:space="preserve">起始頁</x:String>
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">新增群組</x:String> <x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">新增群組</x:String>
@ -757,4 +759,4 @@
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">鎖定工作區</x:String> <x:String x:Key="Text.Worktree.Lock" xml:space="preserve">鎖定工作區</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">移除工作區</x:String> <x:String x:Key="Text.Worktree.Remove" xml:space="preserve">移除工作區</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">解除鎖定工作區</x:String> <x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">解除鎖定工作區</x:String>
</ResourceDictionary> </ResourceDictionary>

View file

@ -93,15 +93,19 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Adding remote ..."; ProgressDescription = "Adding remote ...";
var log = _repo.CreateLog("Add Remote");
Use(log);
return Task.Run(() => 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) if (succ)
{ {
SetProgressDescription("Fetching from added remote ..."); new Commands.Config(_repo.FullPath).Use(log).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); new Commands.Fetch(_repo.FullPath, _name, false, false).Use(log).Exec();
new Commands.Fetch(_repo.FullPath, _name, false, false, SetProgressDescription).Exec();
} }
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {
_repo.MarkFetched(); _repo.MarkFetched();

View file

@ -61,9 +61,14 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Adding submodule..."; ProgressDescription = "Adding submodule...";
var log = _repo.CreateLog("Add Submodule");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -114,10 +114,15 @@ namespace SourceGit.ViewModels
var branchName = _selectedBranch; var branchName = _selectedBranch;
var tracking = _setTrackingBranch ? SelectedTrackingBranch : string.Empty; var tracking = _setTrackingBranch ? SelectedTrackingBranch : string.Empty;
var log = _repo.CreateLog("Add Worktree");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -47,9 +47,12 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Apply patch..."; ProgressDescription = "Apply patch...";
var log = _repo.CreateLog("Apply Patch");
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -22,7 +22,7 @@ namespace SourceGit.ViewModels
set; set;
} = false; } = false;
public ApplyStash(string repo, Models.Stash stash) public ApplyStash(Repository repo, Models.Stash stash)
{ {
_repo = repo; _repo = repo;
Stash = stash; Stash = stash;
@ -33,16 +33,18 @@ namespace SourceGit.ViewModels
{ {
ProgressDescription = $"Applying stash: {Stash.Name}"; ProgressDescription = $"Applying stash: {Stash.Name}";
var log = _repo.CreateLog("Apply Stash");
return Task.Run(() => 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) if (succ && DropAfterApply)
new Commands.Stash(_repo).Drop(Stash.Name); new Commands.Stash(_repo.FullPath).Use(log).Drop(Stash.Name);
log.Complete();
return true; return true;
}); });
} }
private readonly string _repo; private readonly Repository _repo;
} }
} }

View file

@ -51,9 +51,14 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Archiving ..."; ProgressDescription = "Archiving ...";
var log = _repo.CreateLog("Archive");
Use(log);
return Task.Run(() => 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(() => CallUIThread(() =>
{ {
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);

View file

@ -16,11 +16,8 @@ namespace SourceGit.ViewModels
Task.Run(() => Task.Run(() =>
{ {
var collect = new Commands.AssumeUnchanged(_repo).View(); var collect = new Commands.QueryAssumeUnchangedFiles(_repo).Result();
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() => Files.AddRange(collect));
{
Files.AddRange(collect);
});
}); });
} }
@ -28,7 +25,7 @@ namespace SourceGit.ViewModels
{ {
if (!string.IsNullOrEmpty(file)) if (!string.IsNullOrEmpty(file))
{ {
new Commands.AssumeUnchanged(_repo).Remove(file); new Commands.AssumeUnchanged(_repo, file, false).Exec();
Files.Remove(file); Files.Remove(file);
} }
} }

View file

@ -28,6 +28,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Checkout '{Branch}' ..."; ProgressDescription = $"Checkout '{Branch}' ...";
var log = _repo.CreateLog($"Checkout '{Branch}'");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
@ -36,15 +39,14 @@ namespace SourceGit.ViewModels
{ {
if (DiscardLocalChanges) if (DiscardLocalChanges)
{ {
SetProgressDescription("Discard local changes ..."); Commands.Discard.All(_repo.FullPath, false, log);
Commands.Discard.All(_repo.FullPath, false);
} }
else else
{ {
SetProgressDescription("Stash local changes ..."); var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH");
var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
if (!succ) if (!succ)
{ {
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return false; return false;
} }
@ -53,14 +55,11 @@ namespace SourceGit.ViewModels
} }
} }
SetProgressDescription("Checkout branch ..."); var rs = new Commands.Checkout(_repo.FullPath).Use(log).Branch(Branch);
var rs = new Commands.Checkout(_repo.FullPath).Branch(Branch, SetProgressDescription);
if (needPopStash) if (needPopStash)
{ rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
SetProgressDescription("Re-apply local changes...");
rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); log.Complete();
}
CallUIThread(() => CallUIThread(() =>
{ {

View file

@ -28,6 +28,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Checkout Commit '{Commit.SHA}' ..."; ProgressDescription = $"Checkout Commit '{Commit.SHA}' ...";
var log = _repo.CreateLog("Checkout Commit");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
@ -36,15 +39,14 @@ namespace SourceGit.ViewModels
{ {
if (DiscardLocalChanges) if (DiscardLocalChanges)
{ {
SetProgressDescription("Discard local changes ..."); Commands.Discard.All(_repo.FullPath, false, log);
Commands.Discard.All(_repo.FullPath, false);
} }
else else
{ {
SetProgressDescription("Stash local changes ..."); var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CHECKOUT_AUTO_STASH");
var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
if (!succ) if (!succ)
{ {
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return false; return false;
} }
@ -53,15 +55,11 @@ namespace SourceGit.ViewModels
} }
} }
SetProgressDescription("Checkout commit ..."); var rs = new Commands.Checkout(_repo.FullPath).Use(log).Commit(Commit.SHA);
var rs = new Commands.Checkout(_repo.FullPath).Commit(Commit.SHA, SetProgressDescription);
if (needPopStash) if (needPopStash)
{ rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
SetProgressDescription("Re-apply local changes...");
rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}");
}
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return rs; return rs;
}); });

View file

@ -70,6 +70,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Cherry-Pick commit(s) ..."; ProgressDescription = $"Cherry-Pick commit(s) ...";
var log = _repo.CreateLog("Cherry-Pick");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
if (IsMergeCommit) if (IsMergeCommit)
@ -79,7 +82,7 @@ namespace SourceGit.ViewModels
Targets[0].SHA, Targets[0].SHA,
!AutoCommit, !AutoCommit,
AppendSourceToMessage, AppendSourceToMessage,
$"-m {MainlineForMergeCommit + 1}").Exec(); $"-m {MainlineForMergeCommit + 1}").Use(log).Exec();
} }
else else
{ {
@ -88,9 +91,10 @@ namespace SourceGit.ViewModels
string.Join(' ', Targets.ConvertAll(c => c.SHA)), string.Join(' ', Targets.ConvertAll(c => c.SHA)),
!AutoCommit, !AutoCommit,
AppendSourceToMessage, AppendSourceToMessage,
string.Empty).Exec(); string.Empty).Use(log).Exec();
} }
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -15,9 +15,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Cleanup (GC & prune) ..."; ProgressDescription = "Cleanup (GC & prune) ...";
var log = _repo.CreateLog("Cleanup (GC & prune)");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
new Commands.GC(_repo.FullPath, SetProgressDescription).Exec(); new Commands.GC(_repo.FullPath).Use(log).Exec();
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -15,9 +15,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Clear all stashes..."; ProgressDescription = "Clear all stashes...";
var log = _repo.CreateLog("Clear Stashes");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
new Commands.Stash(_repo.FullPath).Clear(); new Commands.Stash(_repo.FullPath).Use(log).Clear();
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -103,9 +103,13 @@ namespace SourceGit.ViewModels
{ {
ProgressDescription = "Clone ..."; ProgressDescription = "Clone ...";
// Create a temp log.
var log = new CommandLog("Clone");
Use(log);
return Task.Run(() => 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()) if (!cmd.Exec())
return false; return false;
@ -142,12 +146,11 @@ namespace SourceGit.ViewModels
{ {
var submoduleList = new Commands.QuerySubmodules(path).Result(); var submoduleList = new Commands.QuerySubmodules(path).Result();
foreach (var submodule in submoduleList) foreach (var submodule in submoduleList)
{ new Commands.Submodule(path).Use(log).Update(submodule.Path, true, true, false);
var update = new Commands.Submodule(path);
update.Update(submodule.Path, true, true, false, SetProgressDescription);
}
} }
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {
var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true); var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(path, null, true);

View file

@ -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<string> 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<string>)d;
}
});
}
private string _content = string.Empty;
private StringBuilder _builder = new StringBuilder();
private event Action<string> _onNewLineReceived;
}
}

View file

@ -46,7 +46,7 @@
_wc = wc; _wc = wc;
_change = change; _change = change;
IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).ReadToEnd().IsSuccess; IsResolved = new Commands.IsConflictResolved(repo.FullPath, change).Result();
var context = wc.InProgressContext; var context = wc.InProgressContext;
if (context is CherryPickInProgress cherryPick) if (context is CherryPickInProgress cherryPick)

View file

@ -92,6 +92,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
var fixedName = FixName(_name); var fixedName = FixName(_name);
var log = _repo.CreateLog($"Create Branch '{fixedName}'");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var succ = false; var succ = false;
@ -103,15 +106,14 @@ namespace SourceGit.ViewModels
{ {
if (DiscardLocalChanges) if (DiscardLocalChanges)
{ {
SetProgressDescription("Discard local changes..."); Commands.Discard.All(_repo.FullPath, false, log);
Commands.Discard.All(_repo.FullPath, false);
} }
else else
{ {
SetProgressDescription("Stash local changes"); succ = new Commands.Stash(_repo.FullPath).Use(log).Push("CREATE_BRANCH_AUTO_STASH");
succ = new Commands.Stash(_repo.FullPath).Push("CREATE_BRANCH_AUTO_STASH");
if (!succ) if (!succ)
{ {
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return false; return false;
} }
@ -120,21 +122,17 @@ namespace SourceGit.ViewModels
} }
} }
SetProgressDescription($"Create new branch '{fixedName}'"); succ = new Commands.Checkout(_repo.FullPath).Use(log).Branch(fixedName, _baseOnRevision);
succ = new Commands.Checkout(_repo.FullPath).Branch(fixedName, _baseOnRevision, SetProgressDescription);
if (needPopStash) if (needPopStash)
{ new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
SetProgressDescription("Re-apply local changes...");
new Commands.Stash(_repo.FullPath).Pop("stash@{0}");
}
} }
else else
{ {
SetProgressDescription($"Create new branch '{fixedName}'"); succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision, log);
succ = Commands.Branch.Create(_repo.FullPath, fixedName, _baseOnRevision);
} }
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {
if (succ && CheckoutAfterCreated) if (succ && CheckoutAfterCreated)

View file

@ -83,23 +83,24 @@ namespace SourceGit.ViewModels
ProgressDescription = "Create tag..."; ProgressDescription = "Create tag...";
var remotes = PushToRemotes ? _repo.Remotes : null; var remotes = PushToRemotes ? _repo.Remotes : null;
var log = _repo.CreateLog("Create Tag");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
bool succ; bool succ;
if (_annotated) if (_annotated)
succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag); succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag, log);
else else
succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn); succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, log);
if (succ && remotes != null) if (succ && remotes != null)
{ {
foreach (var remote in remotes) foreach (var remote in remotes)
{ new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false).Use(log).Exec();
SetProgressDescription($"Pushing tag to remote {remote.Name} ...");
new Commands.Push(_repo.FullPath, remote.Name, $"refs/tags/{_tagName}", false).Exec();
}
} }
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -48,23 +48,25 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Deleting branch..."; ProgressDescription = "Deleting branch...";
var log = _repo.CreateLog("Delete Branch");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
if (Target.IsLocal) if (Target.IsLocal)
{ {
Commands.Branch.DeleteLocal(_repo.FullPath, Target.Name); Commands.Branch.DeleteLocal(_repo.FullPath, Target.Name, log);
if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null) if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null)
{ Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name, log);
SetProgressDescription("Deleting remote-tracking branch...");
Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name);
}
} }
else else
{ {
Commands.Branch.DeleteRemote(_repo.FullPath, Target.Remote, Target.Name); Commands.Branch.DeleteRemote(_repo.FullPath, Target.Remote, Target.Name, log);
} }
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {
_repo.MarkBranchesDirtyManually(); _repo.MarkBranchesDirtyManually();

View file

@ -23,25 +23,24 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Deleting multiple branches..."; ProgressDescription = "Deleting multiple branches...";
var log = _repo.CreateLog("Delete Multiple Branches");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
if (_isLocal) if (_isLocal)
{ {
foreach (var target in Targets) foreach (var target in Targets)
{ Commands.Branch.DeleteLocal(_repo.FullPath, target.Name, log);
SetProgressDescription($"Deleting local branch : {target.Name}");
Commands.Branch.DeleteLocal(_repo.FullPath, target.Name);
}
} }
else else
{ {
foreach (var target in Targets) foreach (var target in Targets)
{ Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name, log);
SetProgressDescription($"Deleting remote branch : {target.FriendlyName}");
Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name);
}
} }
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {
_repo.MarkBranchesDirtyManually(); _repo.MarkBranchesDirtyManually();

View file

@ -22,9 +22,14 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Deleting remote ..."; ProgressDescription = "Deleting remote ...";
var log = _repo.CreateLog("Delete Remote");
Use(log);
return Task.Run(() => 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(() => CallUIThread(() =>
{ {
_repo.MarkBranchesDirtyManually(); _repo.MarkBranchesDirtyManually();

View file

@ -23,9 +23,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Deleting submodule ..."; ProgressDescription = "Deleting submodule ...";
var log = _repo.CreateLog("Delete Submodule");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -28,10 +28,21 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Deleting tag '{Target.Name}' ..."; 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(() => 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(() => CallUIThread(() =>
{ {
_repo.MarkTagsDirtyManually(); _repo.MarkTagsDirtyManually();

View file

@ -65,12 +65,17 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = _changes == null ? "Discard all local changes ..." : $"Discard total {_changes.Count} changes ..."; ProgressDescription = _changes == null ? "Discard all local changes ..." : $"Discard total {_changes.Count} changes ...";
var log = _repo.CreateLog("Discard all");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
if (Mode is DiscardAllMode all) if (Mode is DiscardAllMode all)
Commands.Discard.All(_repo.FullPath, all.IncludeIgnored); Commands.Discard.All(_repo.FullPath, all.IncludeIgnored, log);
else else
Commands.Discard.Changes(_repo.FullPath, _changes); Commands.Discard.Changes(_repo.FullPath, _changes, log);
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {

View file

@ -6,7 +6,7 @@ namespace SourceGit.ViewModels
{ {
public Models.Stash Stash { get; private set; } public Models.Stash Stash { get; private set; }
public DropStash(string repo, Models.Stash stash) public DropStash(Repository repo, Models.Stash stash)
{ {
_repo = repo; _repo = repo;
Stash = stash; Stash = stash;
@ -17,13 +17,17 @@ namespace SourceGit.ViewModels
{ {
ProgressDescription = $"Dropping stash: {Stash.Name}"; ProgressDescription = $"Dropping stash: {Stash.Name}";
var log = _repo.CreateLog("Drop Stash");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
new Commands.Stash(_repo).Drop(Stash.Name); new Commands.Stash(_repo.FullPath).Use(log).Drop(Stash.Name);
log.Complete();
return true; return true;
}); });
} }
private readonly string _repo; private readonly Repository _repo;
} }
} }

View file

@ -127,7 +127,6 @@ namespace SourceGit.ViewModels
if (pushURL != _url) if (pushURL != _url)
new Commands.Remote(_repo.FullPath).SetURL(_name, _url, true); new Commands.Remote(_repo.FullPath).SetURL(_name, _url, true);
SetProgressDescription("Post processing ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null); new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));

View file

@ -43,7 +43,7 @@ namespace SourceGit.ViewModels
return Task.Run(() => return Task.Run(() =>
{ {
if (CustomAction.WaitForExit) 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 else
Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, _args); Commands.ExecuteCustomAction.Run(_repo.FullPath, CustomAction.Executable, _args);

View file

@ -29,9 +29,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Fast-Forward ..."; ProgressDescription = "Fast-Forward ...";
var log = _repo.CreateLog("Fast-Forward (No checkout)");
Use(log);
return Task.Run(() => 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(() => CallUIThread(() =>
{ {
_repo.NavigateToCommit(To.Head); _repo.NavigateToCommit(To.Head);

View file

@ -65,22 +65,23 @@ namespace SourceGit.ViewModels
var notags = _repo.Settings.FetchWithoutTags; var notags = _repo.Settings.FetchWithoutTags;
var force = _repo.Settings.EnableForceOnFetch; var force = _repo.Settings.EnableForceOnFetch;
var log = _repo.CreateLog("Fetch");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
if (FetchAllRemotes) if (FetchAllRemotes)
{ {
foreach (var remote in _repo.Remotes) foreach (var remote in _repo.Remotes)
{ new Commands.Fetch(_repo.FullPath, remote.Name, notags, force).Use(log).Exec();
SetProgressDescription($"Fetching remote: {remote.Name}");
new Commands.Fetch(_repo.FullPath, remote.Name, notags, force, SetProgressDescription).Exec();
}
} }
else else
{ {
SetProgressDescription($"Fetching remote: {SelectedRemote.Name}"); new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force).Use(log).Exec();
new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, notags, force, SetProgressDescription).Exec();
} }
log.Complete();
CallUIThread(() => CallUIThread(() =>
{ {
_repo.NavigateToBranchDelayed(_repo.CurrentBranch?.Upstream); _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.Upstream);

View file

@ -29,9 +29,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Fast-Forward ..."; ProgressDescription = "Fast-Forward ...";
var log = _repo.CreateLog($"Fetch Into '{Local.FriendlyName}'");
Use(log);
return Task.Run(() => 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(() => CallUIThread(() =>
{ {
_repo.NavigateToBranchDelayed(Upstream.FullName); _repo.NavigateToBranchDelayed(Upstream.FullName);

View file

@ -33,11 +33,17 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure() public override Task<bool> Sure()
{ {
_repo.SetWatcherEnabled(false); _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(() => return Task.Run(() =>
{ {
var name = Branch.Name.StartsWith(_prefix) ? Branch.Name.Substring(_prefix.Length) : Branch.Name; var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, KeepBranch, log);
SetProgressDescription($"Git Flow - finishing {_type} {name} ..."); log.Complete();
var succ = Commands.GitFlow.Finish(_repo.FullPath, _type, name, KeepBranch);
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -50,10 +50,15 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure() public override Task<bool> Sure()
{ {
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Git Flow - starting {_type} {_name} ...";
var log = _repo.CreateLog("Gitflow - Start");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
SetProgressDescription($"Git Flow - starting {_type} {_name} ..."); var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name, log);
var succ = Commands.GitFlow.Start(_repo.FullPath, _type, _name); log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -302,17 +302,20 @@ namespace SourceGit.ViewModels
var picker = await storageProvider.OpenFolderPickerAsync(options); var picker = await storageProvider.OpenFolderPickerAsync(options);
if (picker.Count == 1) if (picker.Count == 1)
{ {
var log = _repo.CreateLog("Save as Patch");
var succ = false; var succ = false;
for (var i = 0; i < selected.Count; i++) for (var i = 0; i < selected.Count; i++)
{ {
var saveTo = GetPatchFileName(picker[0].Path.LocalPath, selected[i], 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) if (!succ)
break; break;
} }
if (succ) if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
log.Complete();
} }
} }
catch (Exception exception) catch (Exception exception)

View file

@ -30,19 +30,24 @@ namespace SourceGit.ViewModels
{ {
ProgressDescription = $"Initialize git repository at: '{_targetPath}'"; ProgressDescription = $"Initialize git repository at: '{_targetPath}'";
var log = new CommandLog("Initialize");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var succ = new Commands.Init(_pageId, _targetPath).Exec(); var succ = new Commands.Init(_pageId, _targetPath).Use(log).Exec();
if (!succ) log.Complete();
return false;
CallUIThread(() => if (succ)
{ {
Preferences.Instance.FindOrAddNodeByRepositoryPath(_targetPath, _parentNode, true); CallUIThread(() =>
Welcome.Instance.Refresh(); {
}); Preferences.Instance.FindOrAddNodeByRepositoryPath(_targetPath, _parentNode, true);
Welcome.Instance.Refresh();
});
}
return true; return succ;
}); });
} }

View file

@ -106,9 +106,23 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Init git-flow ..."; ProgressDescription = "Init git-flow ...";
var log = _repo.CreateLog("Gitflow - Init");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -24,9 +24,14 @@ namespace SourceGit.ViewModels
{ {
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Fetching LFS objects from remote ..."; ProgressDescription = $"Fetching LFS objects from remote ...";
var log = _repo.CreateLog("LFS Fetch");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -15,9 +15,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "LFS prune ..."; ProgressDescription = "LFS prune ...";
var log = _repo.CreateLog("LFS Prune");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
new Commands.LFS(_repo.FullPath).Prune(SetProgressDescription); new Commands.LFS(_repo.FullPath).Prune(log);
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -24,9 +24,14 @@ namespace SourceGit.ViewModels
{ {
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Pull LFS objects from remote ..."; ProgressDescription = $"Pull LFS objects from remote ...";
var log = _repo.CreateLog("LFS Pull");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -24,9 +24,14 @@ namespace SourceGit.ViewModels
{ {
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Push LFS objects to remote ..."; ProgressDescription = $"Push LFS objects to remote ...";
var log = _repo.CreateLog("LFS Push");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -29,9 +29,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Adding custom LFS tracking pattern ..."; ProgressDescription = "Adding custom LFS tracking pattern ...";
var log = _repo.CreateLog("LFS Add Custom Pattern");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -59,9 +59,14 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Merging '{_sourceName}' into '{Into}' ..."; ProgressDescription = $"Merging '{_sourceName}' into '{Into}' ...";
var log = _repo.CreateLog($"Merging '{_sourceName}' into '{Into}'");
Use(log);
return Task.Run(() => 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(() => CallUIThread(() =>
{ {
_repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName); _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName);

View file

@ -46,15 +46,18 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Merge head(s) ..."; ProgressDescription = "Merge head(s) ...";
var log = _repo.CreateLog("Merge Multiple Heads");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var succ = new Commands.Merge( var succ = new Commands.Merge(
_repo.FullPath, _repo.FullPath,
ConvertTargetToMergeSources(), ConvertTargetToMergeSources(),
AutoCommit, AutoCommit,
Strategy.Arg, Strategy.Arg).Use(log).Exec();
SetProgressDescription).Exec();
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -52,9 +52,9 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Invoke(action); 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; private bool _inProgress = false;

View file

@ -22,9 +22,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Run `prune` on remote ..."; ProgressDescription = "Run `prune` on remote ...";
var log = _repo.CreateLog($"Prune Remote '{Remote.Name}'");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -15,9 +15,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Prune worktrees ..."; ProgressDescription = "Prune worktrees ...";
var log = _repo.CreateLog("Prune Worktrees");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
new Commands.Worktree(_repo.FullPath).Prune(SetProgressDescription); new Commands.Worktree(_repo.FullPath).Use(log).Prune();
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -118,6 +118,9 @@ namespace SourceGit.ViewModels
{ {
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
var log = _repo.CreateLog("Pull");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result(); var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
@ -126,15 +129,14 @@ namespace SourceGit.ViewModels
{ {
if (DiscardLocalChanges) if (DiscardLocalChanges)
{ {
SetProgressDescription("Discard local changes ..."); Commands.Discard.All(_repo.FullPath, false, log);
Commands.Discard.All(_repo.FullPath, false);
} }
else else
{ {
SetProgressDescription("Stash local changes..."); var succ = new Commands.Stash(_repo.FullPath).Use(log).Push("PULL_AUTO_STASH");
var succ = new Commands.Stash(_repo.FullPath).Push("PULL_AUTO_STASH");
if (!succ) if (!succ)
{ {
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return false; return false;
} }
@ -146,16 +148,14 @@ namespace SourceGit.ViewModels
bool rs; bool rs;
if (FetchAllBranches) if (FetchAllBranches)
{ {
SetProgressDescription($"Fetching remote: {_selectedRemote.Name}...");
rs = new Commands.Fetch( rs = new Commands.Fetch(
_repo.FullPath, _repo.FullPath,
_selectedRemote.Name, _selectedRemote.Name,
NoTags, NoTags,
false, false).Use(log).Exec();
SetProgressDescription).Exec();
if (!rs) if (!rs)
{ {
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return false; return false;
} }
@ -164,39 +164,31 @@ namespace SourceGit.ViewModels
// Use merge/rebase instead of pull as fetch is done manually. // Use merge/rebase instead of pull as fetch is done manually.
if (UseRebase) if (UseRebase)
{ rs = new Commands.Rebase(_repo.FullPath, _selectedBranch.FriendlyName, false).Use(log).Exec();
SetProgressDescription($"Rebase {_current.Name} on {_selectedBranch.FriendlyName} ...");
rs = new Commands.Rebase(_repo.FullPath, _selectedBranch.FriendlyName, false).Exec();
}
else else
{ rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "").Use(log).Exec();
SetProgressDescription($"Merge {_selectedBranch.FriendlyName} into {_current.Name} ...");
rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "", SetProgressDescription).Exec();
}
} }
else else
{ {
SetProgressDescription($"Pull {_selectedRemote.Name}/{_selectedBranch.Name}...");
rs = new Commands.Pull( rs = new Commands.Pull(
_repo.FullPath, _repo.FullPath,
_selectedRemote.Name, _selectedRemote.Name,
_selectedBranch.Name, _selectedBranch.Name,
UseRebase, UseRebase,
NoTags, NoTags).Use(log).Exec();
SetProgressDescription).Exec();
} }
if (rs && needPopStash) if (rs && needPopStash)
{ rs = new Commands.Stash(_repo.FullPath).Use(log).Pop("stash@{0}");
SetProgressDescription("Re-apply local changes...");
rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}"); log.Complete();
}
CallUIThread(() => CallUIThread(() =>
{ {
_repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName); _repo.NavigateToBranchDelayed(_repo.CurrentBranch?.FullName);
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);
}); });
return rs; return rs;
}); });
} }

View file

@ -164,6 +164,9 @@ namespace SourceGit.ViewModels
var remoteBranchName = _selectedRemoteBranch.Name; var remoteBranchName = _selectedRemoteBranch.Name;
ProgressDescription = $"Push {_selectedLocalBranch.Name} -> {_selectedRemote.Name}/{remoteBranchName} ..."; ProgressDescription = $"Push {_selectedLocalBranch.Name} -> {_selectedRemote.Name}/{remoteBranchName} ...";
var log = _repo.CreateLog("Push");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var succ = new Commands.Push( var succ = new Commands.Push(
@ -174,8 +177,9 @@ namespace SourceGit.ViewModels
PushAllTags, PushAllTags,
_repo.Submodules.Count > 0 && CheckSubmodules, _repo.Submodules.Count > 0 && CheckSubmodules,
_isSetTrackOptionVisible && Tracking, _isSetTrackOptionVisible && Tracking,
ForcePush, ForcePush).Use(log).Exec();
SetProgressDescription).Exec();
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -41,6 +41,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Pushing tag ..."; ProgressDescription = $"Pushing tag ...";
var log = _repo.CreateLog("Push Tag");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var succ = true; var succ = true;
@ -49,18 +52,17 @@ namespace SourceGit.ViewModels
{ {
foreach (var remote in _repo.Remotes) foreach (var remote in _repo.Remotes)
{ {
SetProgressDescription($"Pushing tag to remote {remote.Name} ..."); succ = new Commands.Push(_repo.FullPath, remote.Name, tag, false).Use(log).Exec();
succ = new Commands.Push(_repo.FullPath, remote.Name, tag, false).Exec();
if (!succ) if (!succ)
break; break;
} }
} }
else else
{ {
SetProgressDescription($"Pushing tag to remote {SelectedRemote.Name} ..."); succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false).Use(log).Exec();
succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, tag, false).Exec();
} }
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -47,9 +47,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Rebasing ..."; ProgressDescription = "Rebasing ...";
var log = _repo.CreateLog("Rebase");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -26,11 +26,15 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure() public override Task<bool> Sure()
{ {
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Remove worktrees ..."; ProgressDescription = "Remove worktree ...";
var log = _repo.CreateLog("Remove worktree");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -54,10 +54,15 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Rename '{Target.Name}'"; ProgressDescription = $"Rename '{Target.Name}'";
var log = _repo.CreateLog($"Rename Branch '{Target.Name}'");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var oldName = Target.FullName; 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(() => CallUIThread(() =>
{ {
if (succ) if (succ)

View file

@ -6,6 +6,7 @@ using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@ -409,6 +410,12 @@ namespace SourceGit.ViewModels
set; set;
} = 0; } = 0;
public AvaloniaList<CommandLog> Logs
{
get;
private set;
} = new AvaloniaList<CommandLog>();
public Repository(bool isBare, string path, string gitDir) public Repository(bool isBare, string path, string gitDir)
{ {
IsBare = isBare; IsBare = isBare;
@ -472,6 +479,8 @@ namespace SourceGit.ViewModels
public void Close() public void Close()
{ {
SelectedView = null; // Do NOT modify. Used to remove exists widgets for GC.Collect SelectedView = null; // Do NOT modify. Used to remove exists widgets for GC.Collect
Logs.Clear();
_settings.LastCommitMessage = _workingCopy.CommitMessage; _settings.LastCommitMessage = _workingCopy.CommitMessage;
var settingsSerialized = JsonSerializer.Serialize(_settings, JsonCodeGen.Default.RepositorySettings); var settingsSerialized = JsonSerializer.Serialize(_settings, JsonCodeGen.Default.RepositorySettings);
@ -538,6 +547,13 @@ namespace SourceGit.ViewModels
GetOwnerPage()?.StartPopup(popup); GetOwnerPage()?.StartPopup(popup);
} }
public CommandLog CreateLog(string name)
{
var log = new CommandLog(name);
Logs.Insert(0, log);
return log;
}
public void RefreshAll() public void RefreshAll()
{ {
Task.Run(() => Task.Run(() =>
@ -2560,7 +2576,7 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Invoke(() => IsAutoFetching = true); Dispatcher.UIThread.Invoke(() => IsAutoFetching = true);
foreach (var remote in remotes) 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; _lastFetchTime = DateTime.Now;
Dispatcher.UIThread.Invoke(() => IsAutoFetching = false); Dispatcher.UIThread.Invoke(() => IsAutoFetching = false);
} }

View file

@ -36,9 +36,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Reset current branch to {To.SHA} ..."; ProgressDescription = $"Reset current branch to {To.SHA} ...";
var log = _repo.CreateLog($"Reset HEAD to '{To.SHA}'");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -29,9 +29,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Revert commit '{Target.SHA}' ..."; ProgressDescription = $"Revert commit '{Target.SHA}' ...";
var log = _repo.CreateLog($"Revert '{Target.SHA}'");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -37,9 +37,13 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Editing head commit message ..."; ProgressDescription = $"Editing head commit message ...";
var log = _repo.CreateLog("Reword HEAD");
Use(log);
return Task.Run(() => 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -97,7 +97,7 @@ namespace SourceGit.ViewModels
subdir.Name.Equals(".idea", StringComparison.Ordinal)) subdir.Name.Equals(".idea", StringComparison.Ordinal))
continue; continue;
SetProgressDescription($"Scanning {subdir.FullName}..."); CallUIThread(() => ProgressDescription = $"Scanning {subdir.FullName}...");
var normalizedSelf = subdir.FullName.Replace("\\", "/"); var normalizedSelf = subdir.FullName.Replace("\\", "/");
if (_managed.Contains(normalizedSelf)) if (_managed.Contains(normalizedSelf))

View file

@ -55,17 +55,22 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure() public override Task<bool> Sure()
{ {
SetProgressDescription("Setting upstream..."); ProgressDescription = "Setting upstream...";
var upstream = (_unset || SelectedRemoteBranch == null) ? string.Empty : SelectedRemoteBranch.FullName; var upstream = (_unset || SelectedRemoteBranch == null) ? string.Empty : SelectedRemoteBranch.FullName;
if (upstream == Local.Upstream) if (upstream == Local.Upstream)
return null; return null;
var log = _repo.CreateLog("Set Upstream");
Use(log);
return Task.Run(() => 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) if (succ)
_repo.RefreshBranches(); _repo.RefreshBranches();
log.Complete();
return true; return true;
}); });
} }

View file

@ -31,6 +31,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = "Squashing ..."; ProgressDescription = "Squashing ...";
var log = _repo.CreateLog("Squash");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var autoStashed = false; var autoStashed = false;
@ -38,9 +41,10 @@ namespace SourceGit.ViewModels
if (_repo.LocalChangesCount > 0) 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) if (!succ)
{ {
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return false; return false;
} }
@ -48,13 +52,14 @@ namespace SourceGit.ViewModels
autoStashed = true; 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) 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) 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)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ; return succ;
}); });

View file

@ -56,6 +56,9 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
ProgressDescription = $"Stash changes ..."; ProgressDescription = $"Stash changes ...";
var log = _repo.CreateLog("Stash Local Changes");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
var succ = false; var succ = false;
@ -66,7 +69,7 @@ namespace SourceGit.ViewModels
{ {
if (Native.OS.GitVersion >= Models.GitVersions.STASH_PUSH_ONLY_STAGED) 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 else
{ {
@ -77,22 +80,23 @@ namespace SourceGit.ViewModels
staged.Add(c); staged.Add(c);
} }
succ = StashWithChanges(staged); succ = StashWithChanges(staged, log);
} }
} }
else else
{ {
succ = new Commands.Stash(_repo.FullPath).Push(Message, IncludeUntracked, KeepIndex); succ = new Commands.Stash(_repo.FullPath).Use(log).Push(Message, IncludeUntracked, KeepIndex);
} }
} }
else else
{ {
succ = StashWithChanges(_changes); succ = StashWithChanges(_changes, log);
} }
if (AutoRestore && succ) 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(() => CallUIThread(() =>
{ {
_repo.MarkWorkingCopyDirtyManually(); _repo.MarkWorkingCopyDirtyManually();
@ -103,7 +107,7 @@ namespace SourceGit.ViewModels
}); });
} }
private bool StashWithChanges(List<Models.Change> changes) private bool StashWithChanges(List<Models.Change> changes, CommandLog log)
{ {
if (changes.Count == 0) if (changes.Count == 0)
return true; return true;
@ -117,7 +121,7 @@ namespace SourceGit.ViewModels
var tmpFile = Path.GetTempFileName(); var tmpFile = Path.GetTempFileName();
File.WriteAllLines(tmpFile, paths); 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); File.Delete(tmpFile);
} }
else else
@ -126,7 +130,7 @@ namespace SourceGit.ViewModels
{ {
var count = Math.Min(10, changes.Count - i); var count = Math.Min(10, changes.Count - i);
var step = changes.GetRange(i, count); 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) if (!succ)
break; break;
} }

View file

@ -147,7 +147,7 @@ namespace SourceGit.ViewModels
apply.Click += (_, ev) => apply.Click += (_, ev) =>
{ {
if (_repo.CanCreatePopup()) if (_repo.CanCreatePopup())
_repo.ShowPopup(new ApplyStash(_repo.FullPath, stash)); _repo.ShowPopup(new ApplyStash(_repo, stash));
ev.Handled = true; ev.Handled = true;
}; };
@ -157,7 +157,7 @@ namespace SourceGit.ViewModels
drop.Click += (_, ev) => drop.Click += (_, ev) =>
{ {
if (_repo.CanCreatePopup()) if (_repo.CanCreatePopup())
_repo.ShowPopup(new DropStash(_repo.FullPath, stash)); _repo.ShowPopup(new DropStash(_repo, stash));
ev.Handled = true; ev.Handled = true;
}; };

View file

@ -62,19 +62,21 @@ namespace SourceGit.ViewModels
else else
targets = [SelectedSubmodule]; targets = [SelectedSubmodule];
var log = _repo.CreateLog("Update Submodule");
Use(log);
return Task.Run(() => return Task.Run(() =>
{ {
foreach (var submodule in targets) foreach (var submodule in targets)
{ {
ProgressDescription = $"Updating submodule {submodule} ..."; new Commands.Submodule(_repo.FullPath).Use(log).Update(
new Commands.Submodule(_repo.FullPath).Update(
submodule, submodule,
EnableInit, EnableInit,
EnableRecursive, EnableRecursive,
EnableRemote, EnableRemote);
SetProgressDescription);
} }
log.Complete();
CallUIThread(() => _repo.SetWatcherEnabled(true)); CallUIThread(() => _repo.SetWatcherEnabled(true));
return true; return true;
}); });

View file

@ -0,0 +1,27 @@
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class ViewLogs : ObservableObject
{
public AvaloniaList<CommandLog> 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;
}
}

View file

@ -711,7 +711,9 @@ namespace SourceGit.ViewModels
assumeUnchanged.IsVisible = change.WorkTree != Models.ChangeState.Untracked; assumeUnchanged.IsVisible = change.WorkTree != Models.ChangeState.Untracked;
assumeUnchanged.Click += (_, e) => 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; e.Handled = true;
}; };
@ -805,10 +807,12 @@ namespace SourceGit.ViewModels
lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename); lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename);
lfsTrackThisFile.Click += async (_, e) => 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) if (succ)
App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!"); App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!");
log.Complete();
e.Handled = true; e.Handled = true;
}; };
lfs.Items.Add(lfsTrackThisFile); lfs.Items.Add(lfsTrackThisFile);
@ -819,10 +823,12 @@ namespace SourceGit.ViewModels
lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension); lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension);
lfsTrackByExtension.Click += async (_, e) => 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) if (succ)
App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!"); App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!");
log.Complete();
e.Handled = true; e.Handled = true;
}; };
lfs.Items.Add(lfsTrackByExtension); lfs.Items.Add(lfsTrackByExtension);
@ -1581,9 +1587,11 @@ namespace SourceGit.ViewModels
IsStaging = true; IsStaging = true;
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
var log = _repo.CreateLog("Stage");
if (count == _unstaged.Count) 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) else if (Native.OS.GitVersion >= Models.GitVersions.ADD_WITH_PATHSPECFILE)
{ {
@ -1593,7 +1601,7 @@ namespace SourceGit.ViewModels
var tmpFile = Path.GetTempFileName(); var tmpFile = Path.GetTempFileName();
File.WriteAllLines(tmpFile, paths); 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); File.Delete(tmpFile);
} }
else else
@ -1605,9 +1613,10 @@ namespace SourceGit.ViewModels
for (int i = 0; i < count; i += 10) for (int i = 0; i < count; i += 10)
{ {
var step = paths.GetRange(i, Math.Min(10, count - i)); 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.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);
IsStaging = false; IsStaging = false;
@ -1624,22 +1633,26 @@ namespace SourceGit.ViewModels
IsUnstaging = true; IsUnstaging = true;
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
var log = _repo.CreateLog("Unstage");
if (_useAmend) if (_useAmend)
{ {
log.AppendLine("$ git update-index --index-info ");
await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec()); await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
} }
else if (count == _staged.Count) 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 else
{ {
for (int i = 0; i < count; i += 10) for (int i = 0; i < count; i += 10)
{ {
var step = changes.GetRange(i, Math.Min(10, count - i)); 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.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);
IsUnstaging = false; IsUnstaging = false;
@ -1703,14 +1716,17 @@ namespace SourceGit.ViewModels
_repo.Settings.PushCommitMessage(_commitMessage); _repo.Settings.PushCommitMessage(_commitMessage);
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
var log = _repo.CreateLog("Commit");
Task.Run(() => Task.Run(() =>
{ {
var succ = true; var succ = true;
if (autoStage && _unstaged.Count > 0) 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) 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(() => Dispatcher.UIThread.Post(() =>
{ {

View file

@ -21,6 +21,10 @@
<Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/> <Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/>
</Button> </Button>
<Button Classes="icon_button" Width="32" Click="OpenGitLogs" ToolTip.Tip="{DynamicResource Text.Repository.ViewLogs}">
<Path Width="14" Height="14" Margin="0,1,0,0" Data="{StaticResource Icons.Logs}"/>
</Button>
<Button Classes="icon_button" Width="32" Click="OpenStatistics" ToolTip.Tip="{DynamicResource Text.Repository.Statistics}"> <Button Classes="icon_button" Width="32" Click="OpenStatistics" ToolTip.Tip="{DynamicResource Text.Repository.Statistics}">
<Path Width="13" Height="13" Data="{StaticResource Icons.Statistics}"/> <Path Width="13" Height="13" Data="{StaticResource Icons.Statistics}"/>
</Button> </Button>
@ -102,7 +106,7 @@
<Button Classes="icon_button" Width="32" Margin="8,0,0,0" Command="{Binding Cleanup}" ToolTip.Tip="{DynamicResource Text.Repository.Clean}"> <Button Classes="icon_button" Width="32" Margin="8,0,0,0" Command="{Binding Cleanup}" ToolTip.Tip="{DynamicResource Text.Repository.Clean}">
<Path Width="14" Height="14" Margin="0,1,0,0" Data="{StaticResource Icons.Clean}"/> <Path Width="14" Height="14" Margin="0,1,0,0" Data="{StaticResource Icons.Clean}"/>
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,4,0"> <StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,4,0">

View file

@ -128,6 +128,16 @@ namespace SourceGit.Views
e.Handled = true; 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;
}
}
} }
} }

View file

@ -1972,7 +1972,7 @@ namespace SourceGit.Views
if (!selection.HasLeftChanges) if (!selection.HasLeftChanges)
{ {
Commands.Discard.Changes(repo.FullPath, [change]); Commands.Discard.Changes(repo.FullPath, [change], null);
} }
else else
{ {

112
src/Views/ViewLogs.axaml Normal file
View file

@ -0,0 +1,112 @@
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.ViewLogs"
x:DataType="vm:ViewLogs"
x:Name="ThisControl"
Title="{DynamicResource Text.ViewLogs}"
Icon="/App.ico"
Width="800" Height="500"
CanResize="False"
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
<Border Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
PointerPressed="BeginMoveWindow"/>
<Path Width="14" Height="14"
Margin="10,0,0,0"
HorizontalAlignment="Left"
Data="{StaticResource Icons.Logs}"
IsVisible="{OnPlatform True, macOS=False}"/>
<TextBlock Classes="bold"
Text="{DynamicResource Text.ViewLogs}"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsHitTestVisible="False"/>
<v:CaptionButtons HorizontalAlignment="Right"
IsCloseButtonOnly="True"
IsVisible="{OnPlatform True, macOS=False}"/>
</Grid>
<!-- Body -->
<Grid Grid.Row="1" ColumnDefinitions="300,4,*" Margin="8">
<ListBox Grid.Column="0"
Padding="4"
Background="{DynamicResource Brush.Contents}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"
ItemsSource="{Binding Logs}"
SelectedItem="{Binding SelectedLog, Mode=TwoWay}"
SelectionMode="Single"
Grid.IsSharedSizeScope="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:CommandLog">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="SearchCommitTimeColumn"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Classes="primary"
Text="{Binding Name}"
Margin="4,0,0,0"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
<v:LoadingIcon Grid.Column="1"
Width="14" Height="14"
Margin="2,0,0,0"
IsVisible="{Binding !IsComplete}"/>
<TextBlock Grid.Column="2"
Classes="primary"
Margin="4,0"
Foreground="{DynamicResource Brush.FG2}"
Text="{Binding TimeStr}"
HorizontalAlignment="Right" VerticalAlignment="Center"/>
<Button Grid.Column="3" Classes="icon_button" Click="OnRemoveLog">
<Path Width="14" Height="14" Data="{StaticResource Icons.Clear}"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border Grid.Column="2"
BorderBrush="{DynamicResource Brush.Border2}"
BorderThickness="1"
Background="{DynamicResource Brush.Contents}">
<v:LogContentPresenter Log="{Binding SelectedLog}"
FontFamily="{DynamicResource Fonts.Monospace}"/>
</Border>
</Grid>
</Grid>
</v:ChromelessWindow>

Some files were not shown because too many files have changed in this diff Show more