mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-22 04:34:59 +00:00
refactor<*>: rewrite all codes...
This commit is contained in:
parent
89ff8aa744
commit
30ab8ae954
342 changed files with 17208 additions and 19633 deletions
27
src/Commands/Add.cs
Normal file
27
src/Commands/Add.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// `git add`命令
|
||||
/// </summary>
|
||||
public class Add : Command {
|
||||
public Add(string repo) {
|
||||
Cwd = repo;
|
||||
Args = "add .";
|
||||
}
|
||||
|
||||
public Add(string repo, List<string> paths) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("add --");
|
||||
foreach (var p in paths) {
|
||||
builder.Append(" \"");
|
||||
builder.Append(p);
|
||||
builder.Append("\"");
|
||||
}
|
||||
|
||||
Cwd = repo;
|
||||
Args = builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
15
src/Commands/Apply.cs
Normal file
15
src/Commands/Apply.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 应用Patch
|
||||
/// </summary>
|
||||
public class Apply : Command {
|
||||
|
||||
public Apply(string repo, string file, bool ignoreWhitespace, string whitespaceMode) {
|
||||
Cwd = repo;
|
||||
Args = "apply ";
|
||||
if (ignoreWhitespace) Args += "--ignore-whitespace ";
|
||||
else Args += $"--whitespace={whitespaceMode} ";
|
||||
Args += $"\"{file}\"";
|
||||
}
|
||||
}
|
||||
}
|
58
src/Commands/Blame.cs
Normal file
58
src/Commands/Blame.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 逐行追溯
|
||||
/// </summary>
|
||||
public class Blame : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
|
||||
private Data data = new Data();
|
||||
|
||||
public class Data {
|
||||
public List<Models.BlameLine> Lines = new List<Models.BlameLine>();
|
||||
public bool IsBinary = false;
|
||||
}
|
||||
|
||||
public Blame(string repo, string file, string revision) {
|
||||
Cwd = repo;
|
||||
Args = $"blame -t {revision} -- \"{file}\"";
|
||||
}
|
||||
|
||||
public Data Result() {
|
||||
Exec();
|
||||
return data;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
if (data.IsBinary) return;
|
||||
if (string.IsNullOrEmpty(line)) return;
|
||||
|
||||
if (line.IndexOf('\0') >= 0) {
|
||||
data.IsBinary = true;
|
||||
data.Lines.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var commit = match.Groups[1].Value;
|
||||
var author = match.Groups[2].Value;
|
||||
var timestamp = int.Parse(match.Groups[3].Value);
|
||||
var content = match.Groups[4].Value;
|
||||
var when = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(timestamp).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
var blameLine = new Models.BlameLine() {
|
||||
LineNumber = $"{data.Lines.Count+1}",
|
||||
CommitSHA = commit,
|
||||
Author = author,
|
||||
Time = when,
|
||||
Content = content,
|
||||
};
|
||||
|
||||
data.Lines.Add(blameLine);
|
||||
}
|
||||
}
|
||||
}
|
33
src/Commands/Branch.cs
Normal file
33
src/Commands/Branch.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 分支相关操作
|
||||
/// </summary>
|
||||
class Branch : Command {
|
||||
private string target = null;
|
||||
|
||||
public Branch(string repo, string branch) {
|
||||
Cwd = repo;
|
||||
target = branch;
|
||||
}
|
||||
|
||||
public void Create(string basedOn) {
|
||||
Args = $"branch {target} {basedOn}";
|
||||
Exec();
|
||||
}
|
||||
|
||||
public void Rename(string to) {
|
||||
Args = $"branch -M {target} {to}";
|
||||
Exec();
|
||||
}
|
||||
|
||||
public void SetUpstream(string upstream) {
|
||||
Args = $"branch {target} -u {upstream}";
|
||||
Exec();
|
||||
}
|
||||
|
||||
public void Delete() {
|
||||
Args = $"branch -D {target}";
|
||||
Exec();
|
||||
}
|
||||
}
|
||||
}
|
78
src/Commands/Branches.cs
Normal file
78
src/Commands/Branches.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 解析所有的分支
|
||||
/// </summary>
|
||||
public class Branches : Command {
|
||||
private static readonly string PREFIX_LOCAL = "refs/heads/";
|
||||
private static readonly string PREFIX_REMOTE = "refs/remotes/";
|
||||
private static readonly string CMD = "branch -l --all -v --format=\"$%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:track)$%(contents:subject)\"";
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"\$(.*)\$(.*)\$([\* ])\$(.*)\$(.*?)\$(.*)");
|
||||
private static readonly Regex REG_AHEAD = new Regex(@"ahead (\d+)");
|
||||
private static readonly Regex REG_BEHIND = new Regex(@"behind (\d+)");
|
||||
|
||||
private List<Models.Branch> loaded = new List<Models.Branch>();
|
||||
|
||||
public Branches(string path) {
|
||||
Cwd = path;
|
||||
Args = CMD;
|
||||
}
|
||||
|
||||
public List<Models.Branch> Result() {
|
||||
Exec();
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var branch = new Models.Branch();
|
||||
var refName = match.Groups[1].Value;
|
||||
if (refName.EndsWith("/HEAD")) return;
|
||||
|
||||
if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal)) {
|
||||
branch.Name = refName.Substring(PREFIX_LOCAL.Length);
|
||||
branch.IsLocal = true;
|
||||
} else if (refName.StartsWith(PREFIX_REMOTE, StringComparison.Ordinal)) {
|
||||
var name = refName.Substring(PREFIX_REMOTE.Length);
|
||||
branch.Remote = name.Substring(0, name.IndexOf('/'));
|
||||
branch.Name = name.Substring(branch.Remote.Length + 1);
|
||||
branch.IsLocal = false;
|
||||
} else {
|
||||
branch.Name = refName;
|
||||
branch.IsLocal = true;
|
||||
}
|
||||
|
||||
branch.FullName = refName;
|
||||
branch.Head = match.Groups[2].Value;
|
||||
branch.IsCurrent = match.Groups[3].Value == "*";
|
||||
branch.Upstream = match.Groups[4].Value;
|
||||
branch.UpstreamTrackStatus = ParseTrackStatus(match.Groups[5].Value);
|
||||
branch.HeadSubject = match.Groups[6].Value;
|
||||
|
||||
loaded.Add(branch);
|
||||
}
|
||||
|
||||
private string ParseTrackStatus(string data) {
|
||||
if (string.IsNullOrEmpty(data)) return "";
|
||||
|
||||
string track = "";
|
||||
|
||||
var ahead = REG_AHEAD.Match(data);
|
||||
if (ahead.Success) {
|
||||
track += ahead.Groups[1].Value + "↑ ";
|
||||
}
|
||||
|
||||
var behind = REG_BEHIND.Match(data);
|
||||
if (behind.Success) {
|
||||
track += behind.Groups[1].Value + "↓";
|
||||
}
|
||||
|
||||
return track.Trim();
|
||||
}
|
||||
}
|
||||
}
|
46
src/Commands/Checkout.cs
Normal file
46
src/Commands/Checkout.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 检出
|
||||
/// </summary>
|
||||
public class Checkout : Command {
|
||||
|
||||
public Checkout(string repo) {
|
||||
Cwd = repo;
|
||||
}
|
||||
|
||||
public bool Branch(string branch) {
|
||||
Args = $"checkout {branch}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Branch(string branch, string basedOn) {
|
||||
Args = $"checkout -b {branch} {basedOn}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool File(string file, bool useTheirs) {
|
||||
if (useTheirs) {
|
||||
Args = $"checkout --theirs -- \"{file}\"";
|
||||
} else {
|
||||
Args = $"checkout --ours -- \"{file}\"";
|
||||
}
|
||||
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Files(List<string> files) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("checkout -qf --");
|
||||
foreach (var f in files) {
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
}
|
||||
}
|
13
src/Commands/CherryPick.cs
Normal file
13
src/Commands/CherryPick.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 遴选命令
|
||||
/// </summary>
|
||||
public class CherryPick : Command {
|
||||
|
||||
public CherryPick(string repo, string commit, bool noCommit) {
|
||||
var mode = noCommit ? "-n" : "--ff";
|
||||
Cwd = repo;
|
||||
Args = $"cherry-pick {mode} {commit}";
|
||||
}
|
||||
}
|
||||
}
|
12
src/Commands/Clean.cs
Normal file
12
src/Commands/Clean.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 清理指令
|
||||
/// </summary>
|
||||
public class Clean : Command {
|
||||
|
||||
public Clean(string repo) {
|
||||
Cwd = repo;
|
||||
Args = "clean -qfd";
|
||||
}
|
||||
}
|
||||
}
|
26
src/Commands/Clone.cs
Normal file
26
src/Commands/Clone.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 克隆
|
||||
/// </summary>
|
||||
public class Clone : Command {
|
||||
private Action<string> handler = null;
|
||||
|
||||
public Clone(string path, string url, string localName, string extraArgs, Action<string> outputHandler) {
|
||||
Cwd = path;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = "-c credential.helper=manager clone --progress --verbose --recurse-submodules ";
|
||||
handler = outputHandler;
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs)) Args += $"{extraArgs} ";
|
||||
Args += $"{url} ";
|
||||
if (!string.IsNullOrEmpty(localName)) Args += localName;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
handler?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
161
src/Commands/Command.cs
Normal file
161
src/Commands/Command.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 取消命令执行的对象
|
||||
/// </summary>
|
||||
public class Cancellable {
|
||||
public bool IsCancelRequested { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命令接口
|
||||
/// </summary>
|
||||
public class Command {
|
||||
|
||||
/// <summary>
|
||||
/// 读取全部输出时的结果
|
||||
/// </summary>
|
||||
public class ReadToEndResult {
|
||||
public bool IsSuccess { get; set; }
|
||||
public string Output { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行路径
|
||||
/// </summary>
|
||||
public string Cwd { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 参数
|
||||
/// </summary>
|
||||
public string Args { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 使用标准错误输出
|
||||
/// </summary>
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 用于取消命令指行的Token
|
||||
/// </summary>
|
||||
public Cancellable Token { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 运行
|
||||
/// </summary>
|
||||
public bool Exec() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Models.Preference.Instance.Git.Path;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
|
||||
|
||||
var progressFilter = new Regex(@"\d+\%");
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
proc.OutputDataReceived += (o, e) => {
|
||||
if (Token != null && Token.IsCancelRequested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
#if NET48
|
||||
proc.Kill();
|
||||
#else
|
||||
proc.Kill(true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data == null) return;
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
proc.ErrorDataReceived += (o, e) => {
|
||||
if (Token != null && Token.IsCancelRequested) {
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
#if NET48
|
||||
proc.Kill();
|
||||
#else
|
||||
proc.Kill(true);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data == null) return;
|
||||
if (TraitErrorAsOutput) OnReadline(e.Data);
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data)) return;
|
||||
if (progressFilter.IsMatch(e.Data)) return;
|
||||
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return;
|
||||
errs.Add(e.Data);
|
||||
};
|
||||
|
||||
proc.Start();
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
|
||||
if (!isCancelled && exitCode != 0 && errs.Count > 0) {
|
||||
Models.Exception.Raise(string.Join("\n", errs));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 直接读取全部标准输出
|
||||
/// </summary>
|
||||
public ReadToEndResult ReadToEnd() {
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = Models.Preference.Instance.Git.Path;
|
||||
start.Arguments = "--no-pager -c core.quotepath=off " + Args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.RedirectStandardOutput = true;
|
||||
start.RedirectStandardError = true;
|
||||
start.StandardOutputEncoding = Encoding.UTF8;
|
||||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd;
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
proc.Start();
|
||||
|
||||
var rs = new ReadToEndResult();
|
||||
rs.Output = proc.StandardOutput.ReadToEnd();
|
||||
rs.Error = proc.StandardError.ReadToEnd();
|
||||
|
||||
proc.WaitForExit();
|
||||
rs.IsSuccess = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用Exec时的读取函数
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
public virtual void OnReadline(string line) {}
|
||||
}
|
||||
}
|
19
src/Commands/Commit.cs
Normal file
19
src/Commands/Commit.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// `git commit`命令
|
||||
/// </summary>
|
||||
public class Commit : Command {
|
||||
private string msg = null;
|
||||
|
||||
public Commit(string repo, string message, bool amend) {
|
||||
msg = Path.GetTempFileName();
|
||||
File.WriteAllText(msg, message);
|
||||
|
||||
Cwd = repo;
|
||||
Args = $"commit --file=\"{msg}\"";
|
||||
if (amend) Args += " --amend --no-edit";
|
||||
}
|
||||
}
|
||||
}
|
39
src/Commands/CommitChanges.cs
Normal file
39
src/Commands/CommitChanges.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 取得一个提交的变更列表
|
||||
/// </summary>
|
||||
public class CommitChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
private List<Models.Change> changes = new List<Models.Change>();
|
||||
|
||||
public CommitChanges(string cwd, string commit) {
|
||||
Cwd = cwd;
|
||||
Args = $"show --name-status {commit}";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result() {
|
||||
Exec();
|
||||
return changes;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0]) {
|
||||
case 'M': change.Set(Models.Change.Status.Modified); changes.Add(change); break;
|
||||
case 'A': change.Set(Models.Change.Status.Added); changes.Add(change); break;
|
||||
case 'D': change.Set(Models.Change.Status.Deleted); changes.Add(change); break;
|
||||
case 'R': change.Set(Models.Change.Status.Renamed); changes.Add(change); break;
|
||||
case 'C': change.Set(Models.Change.Status.Copied); changes.Add(change); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
src/Commands/CommitRangeChanges.cs
Normal file
39
src/Commands/CommitRangeChanges.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 对比两个提交间的变更
|
||||
/// </summary>
|
||||
public class CommitRangeChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
private List<Models.Change> changes = new List<Models.Change>();
|
||||
|
||||
public CommitRangeChanges(string cwd, string start, string end) {
|
||||
Cwd = cwd;
|
||||
Args = $"diff --name-status {start} {end}";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result() {
|
||||
Exec();
|
||||
return changes;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0]) {
|
||||
case 'M': change.Set(Models.Change.Status.Modified); changes.Add(change); break;
|
||||
case 'A': change.Set(Models.Change.Status.Added); changes.Add(change); break;
|
||||
case 'D': change.Set(Models.Change.Status.Deleted); changes.Add(change); break;
|
||||
case 'R': change.Set(Models.Change.Status.Renamed); changes.Add(change); break;
|
||||
case 'C': change.Set(Models.Change.Status.Copied); changes.Add(change); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
140
src/Commands/Commits.cs
Normal file
140
src/Commands/Commits.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 取得提交列表
|
||||
/// </summary>
|
||||
public class Commits : Command {
|
||||
private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
|
||||
private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----";
|
||||
|
||||
private List<Models.Commit> commits = new List<Models.Commit>();
|
||||
private Models.Commit current = null;
|
||||
private bool isSkipingGpgsig = false;
|
||||
private bool isHeadFounded = false;
|
||||
private bool findFirstMerged = true;
|
||||
|
||||
public Commits(string path, string limits, bool needFindHead = true) {
|
||||
Cwd = path;
|
||||
Args = "log --date-order --decorate=full --pretty=raw " + limits;
|
||||
findFirstMerged = needFindHead;
|
||||
}
|
||||
|
||||
public List<Models.Commit> Result() {
|
||||
Exec();
|
||||
|
||||
if (current != null) {
|
||||
current.Message = current.Message.Trim();
|
||||
commits.Add(current);
|
||||
}
|
||||
|
||||
if (findFirstMerged && !isHeadFounded && commits.Count > 0) {
|
||||
MarkFirstMerged();
|
||||
}
|
||||
|
||||
return commits;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
if (isSkipingGpgsig) {
|
||||
if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) isSkipingGpgsig = false;
|
||||
return;
|
||||
} else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) {
|
||||
isSkipingGpgsig = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
|
||||
if (current != null) {
|
||||
current.Message = current.Message.Trim();
|
||||
commits.Add(current);
|
||||
}
|
||||
|
||||
current = new Models.Commit();
|
||||
line = line.Substring(7);
|
||||
|
||||
var decoratorStart = line.IndexOf('(');
|
||||
if (decoratorStart < 0) {
|
||||
current.SHA = line.Trim();
|
||||
} else {
|
||||
current.SHA = line.Substring(0, decoratorStart).Trim();
|
||||
current.IsMerged = ParseDecorators(current.Decorators, line.Substring(decoratorStart + 1));
|
||||
if (!isHeadFounded) isHeadFounded = current.IsMerged;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (current == null) return;
|
||||
|
||||
if (line.StartsWith("tree ", StringComparison.Ordinal)) {
|
||||
return;
|
||||
} else if (line.StartsWith("parent ", StringComparison.Ordinal)) {
|
||||
current.Parents.Add(line.Substring("parent ".Length));
|
||||
} else if (line.StartsWith("author ", StringComparison.Ordinal)) {
|
||||
current.Author.Parse(line);
|
||||
} else if (line.StartsWith("committer ", StringComparison.Ordinal)) {
|
||||
current.Committer.Parse(line);
|
||||
} else if (string.IsNullOrEmpty(current.Subject)) {
|
||||
current.Subject = line.Trim();
|
||||
} else {
|
||||
current.Message += (line.Trim() + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
private bool ParseDecorators(List<Models.Decorator> decorators, string data) {
|
||||
bool isHeadOfCurrent = false;
|
||||
|
||||
var subs = data.Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var sub in subs) {
|
||||
var d = sub.Trim();
|
||||
if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal)) {
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.Tag,
|
||||
Name = d.Substring(15).Trim(),
|
||||
});
|
||||
} else if (d.EndsWith("/HEAD", StringComparison.Ordinal)) {
|
||||
continue;
|
||||
} else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal)) {
|
||||
isHeadOfCurrent = true;
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.CurrentBranchHead,
|
||||
Name = d.Substring(19).Trim(),
|
||||
});
|
||||
} else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) {
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.LocalBranchHead,
|
||||
Name = d.Substring(11).Trim(),
|
||||
});
|
||||
} else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) {
|
||||
decorators.Add(new Models.Decorator() {
|
||||
Type = Models.DecoratorType.RemoteBranchHead,
|
||||
Name = d.Substring(13).Trim(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return isHeadOfCurrent;
|
||||
}
|
||||
|
||||
private void MarkFirstMerged() {
|
||||
Args = $"log --since=\"{commits.Last().Committer.Time}\" --min-parents=2 --format=\"%H\"";
|
||||
|
||||
var rs = ReadToEnd();
|
||||
var shas = rs.Output.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (shas.Length == 0) return;
|
||||
|
||||
var merges = commits.Where(x => x.Parents.Count > 1).ToList();
|
||||
foreach (var sha in shas) {
|
||||
var c = merges.Find(x => x.SHA == sha);
|
||||
if (c != null) {
|
||||
c.IsMerged = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/Commands/Config.cs
Normal file
37
src/Commands/Config.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// config命令
|
||||
/// </summary>
|
||||
public class Config : Command {
|
||||
|
||||
public Config() {
|
||||
}
|
||||
|
||||
public Config(string repo) {
|
||||
Cwd = repo;
|
||||
}
|
||||
|
||||
public string Get(string key) {
|
||||
Args = $"config {key}";
|
||||
return ReadToEnd().Output.Trim();
|
||||
}
|
||||
|
||||
public bool Set(string key, string val, bool allowEmpty = false) {
|
||||
if (!allowEmpty && string.IsNullOrEmpty(val)) {
|
||||
if (string.IsNullOrEmpty(Cwd)) {
|
||||
Args = $"config --global --unset {key}";
|
||||
} else {
|
||||
Args = $"config --unset {key}";
|
||||
}
|
||||
} else {
|
||||
if (string.IsNullOrEmpty(Cwd)) {
|
||||
Args = $"config --global {key} \"{val}\"";
|
||||
} else {
|
||||
Args = $"config {key} \"{val}\"";
|
||||
}
|
||||
}
|
||||
|
||||
return Exec();
|
||||
}
|
||||
}
|
||||
}
|
61
src/Commands/Diff.cs
Normal file
61
src/Commands/Diff.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// Diff命令(用于文件文件比对)
|
||||
/// </summary>
|
||||
public class Diff : Command {
|
||||
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@");
|
||||
private Models.TextChanges changes = new Models.TextChanges();
|
||||
private int oldLine = 0;
|
||||
private int newLine = 0;
|
||||
|
||||
public Diff(string repo, string args) {
|
||||
Cwd = repo;
|
||||
Args = $"diff --ignore-cr-at-eol {args}";
|
||||
}
|
||||
|
||||
public Models.TextChanges Result() {
|
||||
Exec();
|
||||
if (changes.IsBinary) changes.Lines.Clear();
|
||||
return changes;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
if (changes.IsBinary) return;
|
||||
|
||||
if (changes.Lines.Count == 0) {
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (!match.Success) {
|
||||
if (line.StartsWith("Binary", StringComparison.Ordinal)) changes.IsBinary = true;
|
||||
return;
|
||||
}
|
||||
|
||||
oldLine = int.Parse(match.Groups[1].Value);
|
||||
newLine = int.Parse(match.Groups[2].Value);
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Indicator, line, "", ""));
|
||||
} else {
|
||||
var ch = line[0];
|
||||
if (ch == '-') {
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Deleted, line.Substring(1), $"{oldLine}", ""));
|
||||
oldLine++;
|
||||
} else if (ch == '+') {
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Added, line.Substring(1), "", $"{newLine}"));
|
||||
newLine++;
|
||||
} else if (ch != '\\') {
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (match.Success) {
|
||||
oldLine = int.Parse(match.Groups[1].Value);
|
||||
newLine = int.Parse(match.Groups[2].Value);
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Indicator, line, "", ""));
|
||||
} else {
|
||||
changes.Lines.Add(new Models.TextChanges.Line(Models.TextChanges.LineMode.Normal, line.Substring(1), $"{oldLine}", $"{newLine}"));
|
||||
oldLine++;
|
||||
newLine++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/Commands/Discard.cs
Normal file
37
src/Commands/Discard.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 忽略变更
|
||||
/// </summary>
|
||||
public class Discard {
|
||||
private string repo = null;
|
||||
private List<string> files = new List<string>();
|
||||
|
||||
public Discard(string repo, List<Models.Change> changes) {
|
||||
this.repo = repo;
|
||||
|
||||
if (changes != null && changes.Count > 0) {
|
||||
foreach (var c in changes) {
|
||||
if (c.WorkTree == Models.Change.Status.Untracked || c.WorkTree == Models.Change.Status.Added) continue;
|
||||
files.Add(c.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Exec() {
|
||||
if (files.Count == 0) {
|
||||
new Reset(repo, "HEAD", "--hard").Exec();
|
||||
} else {
|
||||
for (int i = 0; i < files.Count; i += 10) {
|
||||
var count = Math.Min(10, files.Count - i);
|
||||
new Checkout(repo).Files(files.GetRange(i, count));
|
||||
}
|
||||
}
|
||||
|
||||
new Clean(repo).Exec();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
77
src/Commands/Fetch.cs
Normal file
77
src/Commands/Fetch.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 拉取
|
||||
/// </summary>
|
||||
public class Fetch : Command {
|
||||
private Action<string> handler = null;
|
||||
|
||||
public Fetch(string repo, string remote, bool prune, Action<string> outputHandler) {
|
||||
Cwd = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = "-c credential.helper=manager fetch --progress --verbose ";
|
||||
if (prune) Args += "--prune ";
|
||||
Args += remote;
|
||||
handler = outputHandler;
|
||||
AutoFetch.MarkFetched(repo);
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
handler?.Invoke(line);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动拉取(每隔10分钟)
|
||||
/// </summary>
|
||||
public class AutoFetch {
|
||||
private static Dictionary<string, AutoFetch> jobs = new Dictionary<string, AutoFetch>();
|
||||
|
||||
private Fetch cmd = null;
|
||||
private long nextFetchPoint = 0;
|
||||
private Timer timer = null;
|
||||
|
||||
public static void Start(string repo) {
|
||||
if (!Models.Preference.Instance.General.AutoFetchRemotes) return;
|
||||
|
||||
// 只自动更新加入管理列表中的仓库(子模块等不自动更新)
|
||||
var exists = Models.Preference.Instance.FindRepository(repo);
|
||||
if (exists == null) return;
|
||||
|
||||
var job = new AutoFetch(repo);
|
||||
jobs.Add(repo, job);
|
||||
}
|
||||
|
||||
public static void MarkFetched(string repo) {
|
||||
if (!jobs.ContainsKey(repo)) return;
|
||||
jobs[repo].nextFetchPoint = DateTime.Now.AddMinutes(1).ToFileTime();
|
||||
}
|
||||
|
||||
public static void Stop(string repo) {
|
||||
if (!jobs.ContainsKey(repo)) return;
|
||||
|
||||
jobs[repo].timer.Dispose();
|
||||
jobs.Remove(repo);
|
||||
}
|
||||
|
||||
public AutoFetch(string repo) {
|
||||
cmd = new Fetch(repo, "--all", true, null);
|
||||
nextFetchPoint = DateTime.Now.AddMinutes(1).ToFileTime();
|
||||
timer = new Timer(OnTick, null, 60000, 10000);
|
||||
}
|
||||
|
||||
private void OnTick(object o) {
|
||||
var now = DateTime.Now.ToFileTime();
|
||||
if (nextFetchPoint > now) return;
|
||||
|
||||
Models.Watcher.SetEnabled(cmd.Cwd, false);
|
||||
cmd.Exec();
|
||||
nextFetchPoint = DateTime.Now.AddMinutes(1).ToFileTime();
|
||||
Models.Watcher.SetEnabled(cmd.Cwd, true);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Commands/FormatPatch.cs
Normal file
12
src/Commands/FormatPatch.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 将Commit另存为Patch文件
|
||||
/// </summary>
|
||||
public class FormatPatch : Command {
|
||||
|
||||
public FormatPatch(string repo, string commit, string path) {
|
||||
Cwd = repo;
|
||||
Args = $"format-patch {commit} -1 -o \"{path}\"";
|
||||
}
|
||||
}
|
||||
}
|
17
src/Commands/GetRepositoryRootPath.cs
Normal file
17
src/Commands/GetRepositoryRootPath.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 取得一个库的根路径
|
||||
/// </summary>
|
||||
public class GetRepositoryRootPath : Command {
|
||||
public GetRepositoryRootPath(string path) {
|
||||
Cwd = path;
|
||||
Args = "rev-parse --show-toplevel";
|
||||
}
|
||||
|
||||
public string Result() {
|
||||
var rs = ReadToEnd().Output;
|
||||
if (string.IsNullOrEmpty(rs)) return null;
|
||||
return rs.Trim();
|
||||
}
|
||||
}
|
||||
}
|
71
src/Commands/GitFlow.cs
Normal file
71
src/Commands/GitFlow.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// Git-Flow命令
|
||||
/// </summary>
|
||||
public class GitFlow : Command {
|
||||
|
||||
public GitFlow(string repo) {
|
||||
Cwd = repo;
|
||||
}
|
||||
|
||||
public bool Init(string master, string develop, string feature, string release, string hotfix, string version) {
|
||||
var branches = new Branches(Cwd).Result();
|
||||
var current = branches.Find(x => x.IsCurrent);
|
||||
|
||||
var masterBranch = branches.Find(x => x.Name == master);
|
||||
if (masterBranch == null) new Branch(Cwd, master).Create(current.Head);
|
||||
|
||||
var devBranch = branches.Find(x => x.Name == develop);
|
||||
if (devBranch == null) new Branch(Cwd, develop).Create(current.Head);
|
||||
|
||||
var cmd = new Config(Cwd);
|
||||
cmd.Set("gitflow.branch.master", master);
|
||||
cmd.Set("gitflow.branch.develop", develop);
|
||||
cmd.Set("gitflow.prefix.feature", feature);
|
||||
cmd.Set("gitflow.prefix.bugfix", "bugfix/");
|
||||
cmd.Set("gitflow.prefix.release", release);
|
||||
cmd.Set("gitflow.prefix.hotfix", hotfix);
|
||||
cmd.Set("gitflow.prefix.support", "support/");
|
||||
cmd.Set("gitflow.prefix.versiontag", version, true);
|
||||
|
||||
Args = "flow init -d";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public void Start(Models.GitFlowBranchType type, string name) {
|
||||
switch (type) {
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
Args = $"flow feature start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
Args = $"flow release start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
Args = $"flow hotfix start {name}";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Exec();
|
||||
}
|
||||
|
||||
public void Finish(Models.GitFlowBranchType type, string name) {
|
||||
switch (type) {
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
Args = $"flow feature finish {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
Args = $"flow release finish {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
Args = $"flow hotfix finish {name}";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Exec();
|
||||
}
|
||||
}
|
||||
}
|
13
src/Commands/Init.cs
Normal file
13
src/Commands/Init.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 初始化Git仓库
|
||||
/// </summary>
|
||||
public class Init : Command {
|
||||
|
||||
public Init(string workDir) {
|
||||
Cwd = workDir;
|
||||
Args = "init -q";
|
||||
}
|
||||
}
|
||||
}
|
18
src/Commands/IsBinaryFile.cs
Normal file
18
src/Commands/IsBinaryFile.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 查询指定版本下的某文件是否是二进制文件
|
||||
/// </summary>
|
||||
public class IsBinaryFile : Command {
|
||||
private static readonly Regex REG_TEST = new Regex(@"^\-\s+\-\s+.*$");
|
||||
public IsBinaryFile(string repo, string commit, string path) {
|
||||
Cwd = repo;
|
||||
Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
|
||||
}
|
||||
|
||||
public bool Result() {
|
||||
return REG_TEST.IsMatch(ReadToEnd().Output);
|
||||
}
|
||||
}
|
||||
}
|
16
src/Commands/IsLFSFiltered.cs
Normal file
16
src/Commands/IsLFSFiltered.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 检测目录是否被LFS管理
|
||||
/// </summary>
|
||||
public class IsLFSFiltered : Command {
|
||||
public IsLFSFiltered(string cwd, string path) {
|
||||
Cwd = cwd;
|
||||
Args = $"check-attr -a -z \"{path}\"";
|
||||
}
|
||||
|
||||
public bool Result() {
|
||||
var rs = ReadToEnd();
|
||||
return rs.Output.Contains("filter\0lfs");
|
||||
}
|
||||
}
|
||||
}
|
64
src/Commands/LocalChanges.cs
Normal file
64
src/Commands/LocalChanges.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 取得本地工作副本变更
|
||||
/// </summary>
|
||||
public class LocalChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
private List<Models.Change> changes = new List<Models.Change>();
|
||||
|
||||
public LocalChanges(string path) {
|
||||
Cwd = path;
|
||||
Args = "status -uall --ignore-submodules=dirty --porcelain";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result() {
|
||||
Exec();
|
||||
return changes;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status) {
|
||||
case " M": change.Set(Models.Change.Status.None, Models.Change.Status.Modified); break;
|
||||
case " A": change.Set(Models.Change.Status.None, Models.Change.Status.Added); break;
|
||||
case " D": change.Set(Models.Change.Status.None, Models.Change.Status.Deleted); break;
|
||||
case " R": change.Set(Models.Change.Status.None, Models.Change.Status.Renamed); break;
|
||||
case " C": change.Set(Models.Change.Status.None, Models.Change.Status.Copied); break;
|
||||
case "M": change.Set(Models.Change.Status.Modified, Models.Change.Status.None); break;
|
||||
case "MM": change.Set(Models.Change.Status.Modified, Models.Change.Status.Modified); break;
|
||||
case "MD": change.Set(Models.Change.Status.Modified, Models.Change.Status.Deleted); break;
|
||||
case "A": change.Set(Models.Change.Status.Added, Models.Change.Status.None); break;
|
||||
case "AM": change.Set(Models.Change.Status.Added, Models.Change.Status.Modified); break;
|
||||
case "AD": change.Set(Models.Change.Status.Added, Models.Change.Status.Deleted); break;
|
||||
case "D": change.Set(Models.Change.Status.Deleted, Models.Change.Status.None); break;
|
||||
case "R": change.Set(Models.Change.Status.Renamed, Models.Change.Status.None); break;
|
||||
case "RM": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Modified); break;
|
||||
case "RD": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Deleted); break;
|
||||
case "C": change.Set(Models.Change.Status.Copied, Models.Change.Status.None); break;
|
||||
case "CM": change.Set(Models.Change.Status.Copied, Models.Change.Status.Modified); break;
|
||||
case "CD": change.Set(Models.Change.Status.Copied, Models.Change.Status.Deleted); break;
|
||||
case "DR": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Renamed); break;
|
||||
case "DC": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Copied); break;
|
||||
case "DD": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Deleted); break;
|
||||
case "AU": change.Set(Models.Change.Status.Added, Models.Change.Status.Unmerged); break;
|
||||
case "UD": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Deleted); break;
|
||||
case "UA": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Added); break;
|
||||
case "DU": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Unmerged); break;
|
||||
case "AA": change.Set(Models.Change.Status.Added, Models.Change.Status.Added); break;
|
||||
case "UU": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Unmerged); break;
|
||||
case "??": change.Set(Models.Change.Status.Untracked, Models.Change.Status.Untracked); break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
changes.Add(change);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Commands/Merge.cs
Normal file
12
src/Commands/Merge.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 合并分支
|
||||
/// </summary>
|
||||
public class Merge : Command {
|
||||
|
||||
public Merge(string repo, string source, string mode) {
|
||||
Cwd = repo;
|
||||
Args = $"merge {source} {mode}";
|
||||
}
|
||||
}
|
||||
}
|
48
src/Commands/Pull.cs
Normal file
48
src/Commands/Pull.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 拉回
|
||||
/// </summary>
|
||||
public class Pull : Command {
|
||||
private Action<string> handler = null;
|
||||
private bool needStash = false;
|
||||
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool autoStash, Action<string> onProgress) {
|
||||
Cwd = repo;
|
||||
Args = "-c credential.helper=manager pull --verbose --progress --tags ";
|
||||
TraitErrorAsOutput = true;
|
||||
handler = onProgress;
|
||||
|
||||
if (useRebase) Args += "--rebase ";
|
||||
if (autoStash) {
|
||||
if (useRebase) Args += "--autostash ";
|
||||
else needStash = true;
|
||||
}
|
||||
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
||||
public bool Run() {
|
||||
if (needStash) {
|
||||
var changes = new LocalChanges(Cwd).Result();
|
||||
if (changes.Count > 0) {
|
||||
if (!new Stash(Cwd).Push(null, "PULL_AUTO_STASH", true)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
needStash = false;
|
||||
}
|
||||
}
|
||||
|
||||
var succ = Exec();
|
||||
if (needStash) new Stash(Cwd).Pop("stash@{0}");
|
||||
return succ;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
handler?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
39
src/Commands/Push.cs
Normal file
39
src/Commands/Push.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 推送
|
||||
/// </summary>
|
||||
public class Push : Command {
|
||||
private Action<string> handler = null;
|
||||
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool force, bool track, Action<string> onProgress) {
|
||||
Cwd = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
handler = onProgress;
|
||||
Args = "-c credential.helper=manager push --progress --verbose ";
|
||||
|
||||
if (withTags) Args += "--tags ";
|
||||
if (track) Args += "-u ";
|
||||
if (force) Args += "--force-with-lease ";
|
||||
|
||||
Args += $"{remote} {local}:{remoteBranch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string branch) {
|
||||
Cwd = repo;
|
||||
Args = $"-c credential.helper=manager push {remote} --delete {branch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string tag, bool isDelete) {
|
||||
Cwd = repo;
|
||||
Args = $"-c credential.helper=manager push ";
|
||||
if (isDelete) Args += "--delete ";
|
||||
Args += $"{remote} refs/tags/{tag}";
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
handler?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
26
src/Commands/QueryFileContent.cs
Normal file
26
src/Commands/QueryFileContent.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 取得指定提交下的某文件内容
|
||||
/// </summary>
|
||||
public class QueryFileContent : Command {
|
||||
private List<Models.TextLine> lines = new List<Models.TextLine>();
|
||||
private int added = 0;
|
||||
|
||||
public QueryFileContent(string repo, string commit, string path) {
|
||||
Cwd = repo;
|
||||
Args = $"show {commit}:\"{path}\"";
|
||||
}
|
||||
|
||||
public List<Models.TextLine> Result() {
|
||||
Exec();
|
||||
return lines;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
added++;
|
||||
lines.Add(new Models.TextLine() { Number = added, Data = line });
|
||||
}
|
||||
}
|
||||
}
|
50
src/Commands/QueryFileSizeChange.cs
Normal file
50
src/Commands/QueryFileSizeChange.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 查询文件大小变化
|
||||
/// </summary>
|
||||
public class QueryFileSizeChange {
|
||||
|
||||
class QuerySizeCmd : Command {
|
||||
public QuerySizeCmd(string repo, string path, string revision) {
|
||||
Cwd = repo;
|
||||
Args = $"cat-file -s {revision}:\"{path}\"";
|
||||
}
|
||||
|
||||
public long Result() {
|
||||
string data = ReadToEnd().Output;
|
||||
long size;
|
||||
if (!long.TryParse(data, out size)) size = 0;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private Models.FileSizeChange change = new Models.FileSizeChange();
|
||||
|
||||
public QueryFileSizeChange(string repo, string[] revisions, string path, string orgPath) {
|
||||
if (revisions.Length == 0) {
|
||||
change.NewSize = new FileInfo(Path.Combine(repo, path)).Length;
|
||||
change.OldSize = new QuerySizeCmd(repo, path, "HEAD").Result();
|
||||
} else if (revisions.Length == 1) {
|
||||
change.NewSize = new QuerySizeCmd(repo, path, "HEAD").Result();
|
||||
if (string.IsNullOrEmpty(orgPath)) {
|
||||
change.OldSize = new QuerySizeCmd(repo, path, revisions[0]).Result();
|
||||
} else {
|
||||
change.OldSize = new QuerySizeCmd(repo, orgPath, revisions[0]).Result();
|
||||
}
|
||||
} else {
|
||||
change.NewSize = new QuerySizeCmd(repo, path, revisions[0]).Result();
|
||||
if (string.IsNullOrEmpty(orgPath)) {
|
||||
change.OldSize = new QuerySizeCmd(repo, path, revisions[1]).Result();
|
||||
} else {
|
||||
change.OldSize = new QuerySizeCmd(repo, orgPath, revisions[1]).Result();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Models.FileSizeChange Result() {
|
||||
return change;
|
||||
}
|
||||
}
|
||||
}
|
23
src/Commands/QueryGitDir.cs
Normal file
23
src/Commands/QueryGitDir.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 取得GitDir
|
||||
/// </summary>
|
||||
public class QueryGitDir : Command {
|
||||
public QueryGitDir(string workDir) {
|
||||
Cwd = workDir;
|
||||
Args = "rev-parse --git-dir";
|
||||
}
|
||||
|
||||
public string Result() {
|
||||
var rs = ReadToEnd().Output;
|
||||
if (string.IsNullOrEmpty(rs)) return null;
|
||||
|
||||
rs = rs.Trim();
|
||||
if (Path.IsPathRooted(rs)) return rs;
|
||||
return Path.Combine(Cwd, rs);
|
||||
}
|
||||
}
|
||||
}
|
28
src/Commands/QueryLFSObject.cs
Normal file
28
src/Commands/QueryLFSObject.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 取得一个LFS对象的信息
|
||||
/// </summary>
|
||||
public class QueryLFSObject : Command {
|
||||
private Models.LFSObject obj = new Models.LFSObject();
|
||||
|
||||
public QueryLFSObject(string repo, string commit, string path) {
|
||||
Cwd = repo;
|
||||
Args = $"show {commit}:\"{path}\"";
|
||||
}
|
||||
|
||||
public Models.LFSObject Result() {
|
||||
Exec();
|
||||
return obj;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
if (line.StartsWith("oid sha256:", StringComparison.Ordinal)) {
|
||||
obj.OID = line.Substring(11).Trim();
|
||||
} else if (line.StartsWith("size")) {
|
||||
obj.Size = int.Parse(line.Substring(4).Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
src/Commands/QueryLFSObjectChange.cs
Normal file
41
src/Commands/QueryLFSObjectChange.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 查询LFS对象变更
|
||||
/// </summary>
|
||||
public class QueryLFSObjectChange : Command {
|
||||
private Models.LFSChange change = new Models.LFSChange();
|
||||
|
||||
public QueryLFSObjectChange(string repo, string args) {
|
||||
Cwd = repo;
|
||||
Args = $"diff --ignore-cr-at-eol {args}";
|
||||
}
|
||||
|
||||
public Models.LFSChange Result() {
|
||||
Exec();
|
||||
return change;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var ch = line[0];
|
||||
if (ch == '-') {
|
||||
if (change.Old == null) change.Old = new Models.LFSObject();
|
||||
line = line.Substring(1);
|
||||
if (line.StartsWith("oid sha256:")) {
|
||||
change.Old.OID = line.Substring(11);
|
||||
} else if (line.StartsWith("size ")) {
|
||||
change.Old.Size = int.Parse(line.Substring(5));
|
||||
}
|
||||
} else if (ch == '+') {
|
||||
if (change.New == null) change.New = new Models.LFSObject();
|
||||
line = line.Substring(1);
|
||||
if (line.StartsWith("oid sha256:")) {
|
||||
change.New.OID = line.Substring(11);
|
||||
} else if (line.StartsWith("size ")) {
|
||||
change.New.Size = int.Parse(line.Substring(5));
|
||||
}
|
||||
} else if (line.StartsWith(" size ")) {
|
||||
change.New.Size = change.Old.Size = int.Parse(line.Substring(6));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
src/Commands/Rebase.cs
Normal file
14
src/Commands/Rebase.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 变基命令
|
||||
/// </summary>
|
||||
public class Rebase : Command {
|
||||
|
||||
public Rebase(string repo, string basedOn, bool autoStash) {
|
||||
Cwd = repo;
|
||||
Args = "rebase ";
|
||||
if (autoStash) Args += "--autostash ";
|
||||
Args += basedOn;
|
||||
}
|
||||
}
|
||||
}
|
31
src/Commands/Remote.cs
Normal file
31
src/Commands/Remote.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 远程操作
|
||||
/// </summary>
|
||||
public class Remote : Command {
|
||||
|
||||
public Remote(string repo) {
|
||||
Cwd = repo;
|
||||
}
|
||||
|
||||
public bool Add(string name, string url) {
|
||||
Args = $"remote add {name} {url}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Delete(string name) {
|
||||
Args = $"remote remove {name}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Rename(string name, string to) {
|
||||
Args = $"remote rename {name} {to}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool SetURL(string name, string url) {
|
||||
Args = $"remote set-url {name} {url}";
|
||||
return Exec();
|
||||
}
|
||||
}
|
||||
}
|
35
src/Commands/Remotes.cs
Normal file
35
src/Commands/Remotes.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 获取远程列表
|
||||
/// </summary>
|
||||
public class Remotes : Command {
|
||||
private static readonly Regex REG_REMOTE = new Regex(@"^([\w\.\-]+)\s*(\S+).*$");
|
||||
private List<Models.Remote> loaded = new List<Models.Remote>();
|
||||
|
||||
public Remotes(string repo) {
|
||||
Cwd = repo;
|
||||
Args = "remote -v";
|
||||
}
|
||||
|
||||
public List<Models.Remote> Result() {
|
||||
Exec();
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_REMOTE.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var remote = new Models.Remote() {
|
||||
Name = match.Groups[1].Value,
|
||||
URL = match.Groups[2].Value,
|
||||
};
|
||||
|
||||
if (loaded.Find(x => x.Name == remote.Name) != null) return;
|
||||
loaded.Add(remote);
|
||||
}
|
||||
}
|
||||
}
|
33
src/Commands/Reset.cs
Normal file
33
src/Commands/Reset.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 重置命令
|
||||
/// </summary>
|
||||
public class Reset : Command {
|
||||
|
||||
public Reset(string repo) {
|
||||
Cwd = repo;
|
||||
Args = "reset";
|
||||
}
|
||||
|
||||
public Reset(string repo, string revision, string mode) {
|
||||
Cwd = repo;
|
||||
Args = $"reset {mode} {revision}";
|
||||
}
|
||||
|
||||
public Reset(string repo, List<string> files) {
|
||||
Cwd = repo;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("reset --");
|
||||
foreach (var f in files) {
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
13
src/Commands/Revert.cs
Normal file
13
src/Commands/Revert.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 撤销提交
|
||||
/// </summary>
|
||||
public class Revert : Command {
|
||||
|
||||
public Revert(string repo, string commit, bool autoCommit) {
|
||||
Cwd = repo;
|
||||
Args = $"revert {commit} --no-edit";
|
||||
if (!autoCommit) Args += " --no-commit";
|
||||
}
|
||||
}
|
||||
}
|
41
src/Commands/RevisionObjects.cs
Normal file
41
src/Commands/RevisionObjects.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 取出指定Revision下的文件列表
|
||||
/// </summary>
|
||||
public class RevisionObjects : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$");
|
||||
private List<Models.Object> objects = new List<Models.Object>();
|
||||
|
||||
public RevisionObjects(string cwd, string sha) {
|
||||
Cwd = cwd;
|
||||
Args = $"ls-tree -r {sha}";
|
||||
}
|
||||
|
||||
public List<Models.Object> Result() {
|
||||
Exec();
|
||||
return objects;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var obj = new Models.Object();
|
||||
obj.SHA = match.Groups[2].Value;
|
||||
obj.Type = Models.ObjectType.Blob;
|
||||
obj.Path = match.Groups[3].Value;
|
||||
|
||||
switch (match.Groups[1].Value) {
|
||||
case "blob": obj.Type = Models.ObjectType.Blob; break;
|
||||
case "tree": obj.Type = Models.ObjectType.Tree; break;
|
||||
case "tag": obj.Type = Models.ObjectType.Tag; break;
|
||||
case "commit": obj.Type = Models.ObjectType.Commit; break;
|
||||
}
|
||||
|
||||
objects.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
26
src/Commands/SaveChangesToPatch.cs
Normal file
26
src/Commands/SaveChangesToPatch.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 将Changes保存到文件流中
|
||||
/// </summary>
|
||||
public class SaveChangeToStream : Command {
|
||||
private StreamWriter writer = null;
|
||||
|
||||
public SaveChangeToStream(string repo, Models.Change change, StreamWriter to) {
|
||||
Cwd = repo;
|
||||
if (change.WorkTree == Models.Change.Status.Added || change.WorkTree == Models.Change.Status.Untracked) {
|
||||
Args = $"diff --no-index --no-ext-diff --find-renames -- /dev/null \"{change.Path}\"";
|
||||
} else {
|
||||
var pathspec = $"\"{change.Path}\"";
|
||||
if (!string.IsNullOrEmpty(change.OriginalPath)) pathspec = $"\"{change.OriginalPath}\" \"{change.Path}\"";
|
||||
Args = $"diff --binary --no-ext-diff --find-renames --full-index -- {pathspec}";
|
||||
}
|
||||
writer = to;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
writer.WriteLine(line);
|
||||
}
|
||||
}
|
||||
}
|
44
src/Commands/SaveRevisionFile.cs
Normal file
44
src/Commands/SaveRevisionFile.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 保存指定版本的文件
|
||||
/// </summary>
|
||||
public class SaveRevisionFile {
|
||||
private string cwd = "";
|
||||
private string bat = "";
|
||||
|
||||
public SaveRevisionFile(string repo, string path, string sha, string saveTo) {
|
||||
var tmp = Path.GetTempFileName();
|
||||
var cmd = $"\"{Models.Preference.Instance.Git.Path}\" --no-pager ";
|
||||
|
||||
var isLFS = new IsLFSFiltered(repo, path).Result();
|
||||
if (isLFS) {
|
||||
cmd += $"show {sha}:\"{path}\" > {tmp}.lfs\n";
|
||||
cmd += $"\"{Models.Preference.Instance.Git.Path}\" --no-pager lfs smudge < {tmp}.lfs > \"{saveTo}\"\n";
|
||||
} else {
|
||||
cmd += $"show {sha}:\"{path}\" > \"{saveTo}\"\n";
|
||||
}
|
||||
|
||||
cwd = repo;
|
||||
bat = tmp + ".bat";
|
||||
|
||||
File.WriteAllText(bat, cmd);
|
||||
}
|
||||
|
||||
public void Exec() {
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.FileName = bat;
|
||||
starter.WorkingDirectory = cwd;
|
||||
starter.CreateNoWindow = true;
|
||||
starter.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
|
||||
var proc = Process.Start(starter);
|
||||
proc.WaitForExit();
|
||||
proc.Close();
|
||||
|
||||
File.Delete(bat);
|
||||
}
|
||||
}
|
||||
}
|
50
src/Commands/Stash.cs
Normal file
50
src/Commands/Stash.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 单个贮藏相关操作
|
||||
/// </summary>
|
||||
public class Stash : Command {
|
||||
|
||||
public Stash(string repo) {
|
||||
Cwd = repo;
|
||||
}
|
||||
|
||||
public bool Push(List<string> files, string message, bool includeUntracked) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("stash push ");
|
||||
if (includeUntracked) builder.Append("-u ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\" ");
|
||||
|
||||
if (files != null && files.Count > 0) {
|
||||
builder.Append("--");
|
||||
foreach (var f in files) {
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
}
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Apply(string name) {
|
||||
Args = $"stash apply -q {name}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Pop(string name) {
|
||||
Args = $"stash pop -q {name}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Drop(string name) {
|
||||
Args = $"stash drop -q {name}";
|
||||
return Exec();
|
||||
}
|
||||
}
|
||||
}
|
38
src/Commands/StashChanges.cs
Normal file
38
src/Commands/StashChanges.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 查看Stash中的修改
|
||||
/// </summary>
|
||||
public class StashChanges : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
private List<Models.Change> changes = new List<Models.Change>();
|
||||
|
||||
public StashChanges(string repo, string sha) {
|
||||
Cwd = repo;
|
||||
Args = $"diff --name-status --pretty=format: {sha}^ {sha}";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result() {
|
||||
Exec();
|
||||
return changes;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0]) {
|
||||
case 'M': change.Set(Models.Change.Status.Modified); changes.Add(change); break;
|
||||
case 'A': change.Set(Models.Change.Status.Added); changes.Add(change); break;
|
||||
case 'D': change.Set(Models.Change.Status.Deleted); changes.Add(change); break;
|
||||
case 'R': change.Set(Models.Change.Status.Renamed); changes.Add(change); break;
|
||||
case 'C': change.Set(Models.Change.Status.Copied); changes.Add(change); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/Commands/Stashes.cs
Normal file
44
src/Commands/Stashes.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 解析当前仓库中的贮藏
|
||||
/// </summary>
|
||||
public class Stashes : Command {
|
||||
private static readonly Regex REG_STASH = new Regex(@"^Reflog: refs/(stash@\{\d+\}).*$");
|
||||
private List<Models.Stash> parsed = new List<Models.Stash>();
|
||||
private Models.Stash current = null;
|
||||
|
||||
public Stashes(string path) {
|
||||
Cwd = path;
|
||||
Args = "stash list --pretty=raw";
|
||||
}
|
||||
|
||||
public List<Models.Stash> Result() {
|
||||
Exec();
|
||||
if (current != null) parsed.Add(current);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
|
||||
if (current != null && !string.IsNullOrEmpty(current.Name)) parsed.Add(current);
|
||||
current = new Models.Stash() { SHA = line.Substring(7, 8) };
|
||||
return;
|
||||
}
|
||||
|
||||
if (current == null) return;
|
||||
|
||||
if (line.StartsWith("Reflog: refs/stash@", StringComparison.Ordinal)) {
|
||||
var match = REG_STASH.Match(line);
|
||||
if (match.Success) current.Name = match.Groups[1].Value;
|
||||
} else if (line.StartsWith("Reflog message: ", StringComparison.Ordinal)) {
|
||||
current.Message = line.Substring(16);
|
||||
} else if (line.StartsWith("author ", StringComparison.Ordinal)) {
|
||||
current.Author.Parse(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/Commands/Submodule.cs
Normal file
44
src/Commands/Submodule.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 子模块
|
||||
/// </summary>
|
||||
public class Submodule : Command {
|
||||
private Action<string> onProgress = null;
|
||||
|
||||
public Submodule(string cwd) {
|
||||
Cwd = cwd;
|
||||
}
|
||||
|
||||
public bool Add(string url, string path, bool recursive, Action<string> handler) {
|
||||
Args = $"submodule add {url} {path}";
|
||||
onProgress = handler;
|
||||
if (!Exec()) return false;
|
||||
|
||||
if (recursive) {
|
||||
Args = $"submodule update --init --recursive -- {path}";
|
||||
return Exec();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Update() {
|
||||
Args = $"submodule update --rebase --remote";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Delete(string path) {
|
||||
Args = $"submodule deinit -f {path}";
|
||||
if (!Exec()) return false;
|
||||
|
||||
Args = $"rm -rf {path}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
onProgress?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
28
src/Commands/Submodules.cs
Normal file
28
src/Commands/Submodules.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 获取子模块列表
|
||||
/// </summary>
|
||||
public class Submodules : Command {
|
||||
private readonly Regex REG_FORMAT = new Regex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$");
|
||||
private List<string> modules = new List<string>();
|
||||
|
||||
public Submodules(string repo) {
|
||||
Cwd = repo;
|
||||
Args = "submodule status";
|
||||
}
|
||||
|
||||
public List<string> Result() {
|
||||
Exec();
|
||||
return modules;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
modules.Add(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
}
|
42
src/Commands/Tag.cs
Normal file
42
src/Commands/Tag.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
|
||||
/// <summary>
|
||||
/// 标签相关指令
|
||||
/// </summary>
|
||||
public class Tag : Command {
|
||||
|
||||
public Tag(string repo) {
|
||||
Cwd = repo;
|
||||
}
|
||||
|
||||
public bool Add(string name, string basedOn, string message) {
|
||||
Args = $"tag -a {name} {basedOn} ";
|
||||
|
||||
if (!string.IsNullOrEmpty(message)) {
|
||||
string tmp = Path.GetTempFileName();
|
||||
File.WriteAllText(tmp, message);
|
||||
Args += $"-F \"{tmp}\"";
|
||||
} else {
|
||||
Args += $"-m {name}";
|
||||
}
|
||||
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Delete(string name, bool push) {
|
||||
Args = $"tag --delete {name}";
|
||||
if (!Exec()) return false;
|
||||
|
||||
if (push) {
|
||||
var remotes = new Remotes(Cwd).Result();
|
||||
foreach (var r in remotes) {
|
||||
new Push(Cwd, r.Name, name, true).Exec();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
45
src/Commands/Tags.cs
Normal file
45
src/Commands/Tags.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands {
|
||||
/// <summary>
|
||||
/// 解析所有的Tags
|
||||
/// </summary>
|
||||
public class Tags : Command {
|
||||
public static readonly string CMD = "for-each-ref --sort=-creatordate --format=\"$%(refname:short)$%(objectname)$%(*objectname)\" refs/tags";
|
||||
public static readonly Regex REG_FORMAT = new Regex(@"\$(.*)\$(.*)\$(.*)");
|
||||
|
||||
private List<Models.Tag> loaded = new List<Models.Tag>();
|
||||
|
||||
public Tags(string path) {
|
||||
Cwd = path;
|
||||
Args = CMD;
|
||||
}
|
||||
|
||||
public List<Models.Tag> Result() {
|
||||
Exec();
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public override void OnReadline(string line) {
|
||||
var match = REG_FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var name = match.Groups[1].Value;
|
||||
var commit = match.Groups[2].Value;
|
||||
var dereference = match.Groups[3].Value;
|
||||
|
||||
if (string.IsNullOrEmpty(dereference)) {
|
||||
loaded.Add(new Models.Tag() {
|
||||
Name = name,
|
||||
SHA = commit,
|
||||
});
|
||||
} else {
|
||||
loaded.Add(new Models.Tag() {
|
||||
Name = name,
|
||||
SHA = dereference,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue