mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-11-01 13:13:21 -07:00
263 lines
9 KiB
C#
263 lines
9 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Diagnostics;
|
||
|
using System.Text.RegularExpressions;
|
||
|
|
||
|
namespace SourceGit.Git {
|
||
|
|
||
|
/// <summary>
|
||
|
/// Git commit information.
|
||
|
/// </summary>
|
||
|
public class Commit {
|
||
|
private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
|
||
|
private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----";
|
||
|
|
||
|
/// <summary>
|
||
|
/// SHA
|
||
|
/// </summary>
|
||
|
public string SHA { get; set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Short SHA.
|
||
|
/// </summary>
|
||
|
public string ShortSHA => SHA.Substring(0, 8);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parent commit SHAs.
|
||
|
/// </summary>
|
||
|
public List<string> Parents { get; set; } = new List<string>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Author
|
||
|
/// </summary>
|
||
|
public User Author { get; set; } = new User();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Committer.
|
||
|
/// </summary>
|
||
|
public User Committer { get; set; } = new User();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Subject
|
||
|
/// </summary>
|
||
|
public string Subject { get; set; } = "";
|
||
|
|
||
|
/// <summary>
|
||
|
/// Extra message.
|
||
|
/// </summary>
|
||
|
public string Message { get; set; } = "";
|
||
|
|
||
|
/// <summary>
|
||
|
/// HEAD commit?
|
||
|
/// </summary>
|
||
|
public bool IsHEAD { get; set; } = false;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Merged in current branch?
|
||
|
/// </summary>
|
||
|
public bool IsMerged { get; set; } = false;
|
||
|
|
||
|
/// <summary>
|
||
|
/// X offset in graph
|
||
|
/// </summary>
|
||
|
public double GraphOffset { get; set; } = 0;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Has decorators.
|
||
|
/// </summary>
|
||
|
public bool HasDecorators => Decorators.Count > 0;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Decorators.
|
||
|
/// </summary>
|
||
|
public List<Decorator> Decorators { get; set; } = new List<Decorator>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Read commits.
|
||
|
/// </summary>
|
||
|
/// <param name="repo">Repository</param>
|
||
|
/// <param name="limit">Limitations</param>
|
||
|
/// <returns>Parsed commits.</returns>
|
||
|
public static List<Commit> Load(Repository repo, string limit) {
|
||
|
List<Commit> commits = new List<Commit>();
|
||
|
Commit current = null;
|
||
|
bool bSkippingGpgsig = false;
|
||
|
bool findHead = false;
|
||
|
|
||
|
repo.RunCommand("log --date-order --decorate=full --pretty=raw " + limit, line => {
|
||
|
if (bSkippingGpgsig) {
|
||
|
if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) bSkippingGpgsig = false;
|
||
|
return;
|
||
|
} else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) {
|
||
|
bSkippingGpgsig = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
|
||
|
if (current != null) {
|
||
|
current.Message = current.Message.TrimEnd();
|
||
|
commits.Add(current);
|
||
|
}
|
||
|
|
||
|
current = new Commit();
|
||
|
ParseSHA(current, line.Substring("commit ".Length));
|
||
|
if (!findHead) findHead = current.IsHEAD;
|
||
|
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");
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (current != null) {
|
||
|
current.Message = current.Message.TrimEnd();
|
||
|
commits.Add(current);
|
||
|
}
|
||
|
|
||
|
if (!findHead && commits.Count > 0) {
|
||
|
var startInfo = new ProcessStartInfo();
|
||
|
startInfo.FileName = Preference.Instance.GitExecutable;
|
||
|
startInfo.Arguments = $"merge-base --is-ancestor {commits[0].SHA} HEAD";
|
||
|
startInfo.WorkingDirectory = repo.Path;
|
||
|
startInfo.UseShellExecute = false;
|
||
|
startInfo.CreateNoWindow = true;
|
||
|
startInfo.RedirectStandardOutput = false;
|
||
|
startInfo.RedirectStandardError = false;
|
||
|
|
||
|
var proc = new Process() { StartInfo = startInfo };
|
||
|
proc.Start();
|
||
|
proc.WaitForExit();
|
||
|
|
||
|
commits[0].IsMerged = proc.ExitCode == 0;
|
||
|
proc.Close();
|
||
|
}
|
||
|
|
||
|
return commits;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get changed file list.
|
||
|
/// </summary>
|
||
|
/// <param name="repo"></param>
|
||
|
/// <returns></returns>
|
||
|
public List<Change> GetChanges(Repository repo) {
|
||
|
var changes = new List<Change>();
|
||
|
var regex = new Regex(@"^[MADRC]\d*\s*.*$");
|
||
|
|
||
|
var errs = repo.RunCommand($"show --name-status {SHA}", line => {
|
||
|
if (!regex.IsMatch(line)) return;
|
||
|
|
||
|
var change = Change.Parse(line, true);
|
||
|
if (change != null) changes.Add(change);
|
||
|
});
|
||
|
|
||
|
if (errs != null) App.RaiseError(errs);
|
||
|
return changes;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get revision files.
|
||
|
/// </summary>
|
||
|
/// <param name="repo"></param>
|
||
|
/// <returns></returns>
|
||
|
public List<string> GetFiles(Repository repo) {
|
||
|
var files = new List<string>();
|
||
|
|
||
|
var errs = repo.RunCommand($"ls-tree --name-only -r {SHA}", line => {
|
||
|
files.Add(line);
|
||
|
});
|
||
|
|
||
|
if (errs != null) App.RaiseError(errs);
|
||
|
return files;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Get file content.
|
||
|
/// </summary>
|
||
|
/// <param name="repo"></param>
|
||
|
/// <param name="file"></param>
|
||
|
/// <returns></returns>
|
||
|
public string GetTextFileContent(Repository repo, string file) {
|
||
|
var data = new List<string>();
|
||
|
var isBinary = false;
|
||
|
var count = 0;
|
||
|
|
||
|
var errs = repo.RunCommand($"show {SHA}:\"{file}\"", line => {
|
||
|
if (isBinary) return;
|
||
|
|
||
|
count++;
|
||
|
if (data.Count >= 1000) return;
|
||
|
|
||
|
if (line.IndexOf('\0') >= 0) {
|
||
|
isBinary = true;
|
||
|
data.Clear();
|
||
|
data.Add("BINARY FILE PREVIEW NOT SUPPORTED!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
data.Add(line);
|
||
|
});
|
||
|
|
||
|
if (!isBinary && count > 1000) {
|
||
|
data.Add("...");
|
||
|
data.Add($"Total {count} lines. Hide {count-1000} lines.");
|
||
|
}
|
||
|
|
||
|
if (errs != null) App.RaiseError(errs);
|
||
|
return string.Join("\n", data);
|
||
|
}
|
||
|
|
||
|
private static void ParseSHA(Commit commit, string data) {
|
||
|
var decoratorStart = data.IndexOf('(');
|
||
|
if (decoratorStart < 0) {
|
||
|
commit.SHA = data.Trim();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
commit.SHA = data.Substring(0, decoratorStart).Trim();
|
||
|
|
||
|
var subs = data.Substring(decoratorStart + 1).Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries);
|
||
|
foreach (var sub in subs) {
|
||
|
var d = sub.Trim();
|
||
|
if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal)) {
|
||
|
commit.Decorators.Add(new Decorator() {
|
||
|
Type = DecoratorType.Tag,
|
||
|
Name = d.Substring(15).Trim()
|
||
|
});
|
||
|
} else if (d.EndsWith("/HEAD")) {
|
||
|
continue;
|
||
|
} else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal)) {
|
||
|
commit.IsHEAD = true;
|
||
|
commit.Decorators.Add(new Decorator() {
|
||
|
Type = DecoratorType.CurrentBranchHead,
|
||
|
Name = d.Substring(19).Trim()
|
||
|
});
|
||
|
} else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) {
|
||
|
commit.Decorators.Add(new Decorator() {
|
||
|
Type = DecoratorType.LocalBranchHead,
|
||
|
Name = d.Substring(11).Trim()
|
||
|
});
|
||
|
} else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) {
|
||
|
commit.Decorators.Add(new Decorator() {
|
||
|
Type = DecoratorType.RemoteBranchHead,
|
||
|
Name = d.Substring(13).Trim()
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|