diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs index cc19def..f684835 100644 --- a/SharedAssemblyInfo.cs +++ b/SharedAssemblyInfo.cs @@ -9,8 +9,6 @@ [assembly: AssemblyCopyright("Copyright © John Gietzen 2012")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] - [assembly: ComVisible(false)] - [assembly: AssemblyVersion("2.1.*")] [assembly: AssemblyFileVersion("2.1.0.0")] diff --git a/WebGitNet.SearchProviders/PathSearchProvider.cs b/WebGitNet.SearchProviders/PathSearchProvider.cs index 7fa9f27..35d261e 100644 --- a/WebGitNet.SearchProviders/PathSearchProvider.cs +++ b/WebGitNet.SearchProviders/PathSearchProvider.cs @@ -20,6 +20,18 @@ public Task> Search(SearchQuery query, FileManager fileManag } } + public IEnumerable Search(SearchQuery query, FileManager fileManager) + { + var repos = from dir in fileManager.DirectoryInfo.EnumerateDirectories() + let repoInfo = GitUtilities.GetRepoInfo(dir.FullName) + where repoInfo.IsGitRepo + select repoInfo; + + return from repo in repos + from searchResult in Search(query, repo, includeRepoName: true) + select searchResult; + } + private IEnumerable Search(SearchQuery query, RepoInfo repo, bool includeRepoName = false) { TreeView tree; @@ -79,17 +91,5 @@ private IEnumerable Search(SearchQuery query, RepoInfo repo, bool } } } - - public IEnumerable Search(SearchQuery query, FileManager fileManager) - { - var repos = from dir in fileManager.DirectoryInfo.EnumerateDirectories() - let repoInfo = GitUtilities.GetRepoInfo(dir.FullName) - where repoInfo.IsGitRepo - select repoInfo; - - return from repo in repos - from searchResult in Search(query, repo, includeRepoName: true) - select searchResult; - } } } diff --git a/WebGitNet.SearchProviders/RepoGrepSearchProvider.cs b/WebGitNet.SearchProviders/RepoGrepSearchProvider.cs index 67a1b4f..7ae76e2 100644 --- a/WebGitNet.SearchProviders/RepoGrepSearchProvider.cs +++ b/WebGitNet.SearchProviders/RepoGrepSearchProvider.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; - using Search; using System.Threading.Tasks; + using Search; public class RepoGrepSearchProvider : ISearchProvider { @@ -20,6 +20,18 @@ public Task> Search(SearchQuery query, FileManager fileManag } } + public IEnumerable Search(SearchQuery query, FileManager fileManager) + { + var repos = from dir in fileManager.DirectoryInfo.EnumerateDirectories() + let repoInfo = GitUtilities.GetRepoInfo(dir.FullName) + where repoInfo.IsGitRepo + select repoInfo; + + return from repo in repos + from searchResult in Search(query, repo, includeRepoName: true) + select searchResult; + } + private IEnumerable Search(SearchQuery query, RepoInfo repo, bool includeRepoName = false) { var allTerms = string.Join(" --or ", query.Terms.Select(t => "-e " + GitUtilities.Q(t))); @@ -47,17 +59,5 @@ group searchLine by filePath into g Lines = g.ToList(), }; } - - public IEnumerable Search(SearchQuery query, FileManager fileManager) - { - var repos = from dir in fileManager.DirectoryInfo.EnumerateDirectories() - let repoInfo = GitUtilities.GetRepoInfo(dir.FullName) - where repoInfo.IsGitRepo - select repoInfo; - - return from repo in repos - from searchResult in Search(query, repo, includeRepoName: true) - select searchResult; - } } } diff --git a/WebGitNet.SearchProviders/RepoInfoSearchProvider.cs b/WebGitNet.SearchProviders/RepoInfoSearchProvider.cs index d50eab0..4de86e0 100644 --- a/WebGitNet.SearchProviders/RepoInfoSearchProvider.cs +++ b/WebGitNet.SearchProviders/RepoInfoSearchProvider.cs @@ -3,9 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; - using System.Text; - using WebGitNet.Search; using System.Threading.Tasks; + using WebGitNet.Search; public class RepoInfoSearchProvider : ISearchProvider { diff --git a/WebGitNet.SharedLib/BreadCrumbTrail.cs b/WebGitNet.SharedLib/BreadCrumbTrail.cs index 49120bc..be2efc5 100644 --- a/WebGitNet.SharedLib/BreadCrumbTrail.cs +++ b/WebGitNet.SharedLib/BreadCrumbTrail.cs @@ -77,9 +77,9 @@ IEnumerator IEnumerable.GetEnumerator() public sealed class BreadCrumb { - private readonly string name; - private readonly string controller; private readonly string action; + private readonly string controller; + private readonly string name; private readonly object routeValues; public BreadCrumb(string name, string controller, string action, object routeValues) @@ -108,11 +108,11 @@ public BreadCrumb(string name, string controller, string action, object routeVal this.routeValues = routeValues; } - public string Name + public string Action { get { - return this.name; + return this.action; } } @@ -124,11 +124,11 @@ public string Controller } } - public string Action + public string Name { get { - return this.action; + return this.name; } } diff --git a/WebGitNet.SharedLib/DiffInfo.cs b/WebGitNet.SharedLib/DiffInfo.cs index 3fa7213..00fb993 100644 --- a/WebGitNet.SharedLib/DiffInfo.cs +++ b/WebGitNet.SharedLib/DiffInfo.cs @@ -14,14 +14,14 @@ namespace WebGitNet public class DiffInfo { + private readonly bool added; + private readonly bool copied; + private readonly bool deleted; + private readonly string destinationFile; private readonly IList headers; private readonly IList lines; - private readonly string sourceFile; - private readonly string destinationFile; private readonly bool renamed; - private readonly bool copied; - private readonly bool added; - private readonly bool deleted; + private readonly string sourceFile; public DiffInfo(IList lines) { @@ -48,19 +48,19 @@ public DiffInfo(IList lines) this.deleted = this.headers.Any(h => h.StartsWith("deleted file")); } - public IList Headers + public bool Added { - get { return this.headers; } + get { return this.added; } } - public IList Lines + public bool Copied { - get { return this.lines; } + get { return this.copied; } } - public string SourceFile + public bool Deleted { - get { return this.sourceFile; } + get { return this.deleted; } } public string DestinationFile @@ -68,24 +68,24 @@ public string DestinationFile get { return this.destinationFile; } } - public bool Renamed + public IList Headers { - get { return this.renamed; } + get { return this.headers; } } - public bool Copied + public IList Lines { - get { return this.copied; } + get { return this.lines; } } - public bool Added + public bool Renamed { - get { return this.added; } + get { return this.renamed; } } - public bool Deleted + public string SourceFile { - get { return this.deleted; } + get { return this.sourceFile; } } } } diff --git a/WebGitNet.SharedLib/FileManager.cs b/WebGitNet.SharedLib/FileManager.cs index 564eafd..925850c 100644 --- a/WebGitNet.SharedLib/FileManager.cs +++ b/WebGitNet.SharedLib/FileManager.cs @@ -11,8 +11,8 @@ namespace WebGitNet public class FileManager { - private readonly string rootPath; private readonly DirectoryInfo dirInfo; + private readonly string rootPath; public FileManager(string path) { diff --git a/WebGitNet.SharedLib/GitErrorException.cs b/WebGitNet.SharedLib/GitErrorException.cs index 5228cf8..0e0b48a 100644 --- a/WebGitNet.SharedLib/GitErrorException.cs +++ b/WebGitNet.SharedLib/GitErrorException.cs @@ -12,8 +12,8 @@ namespace WebGitNet public class GitErrorException : Exception { private readonly string command; - private readonly int exitCode; private readonly string errorOutput; + private readonly int exitCode; public GitErrorException(string command, string workingDir, int exitCode, string errorOutput) : base("Fatal error executing git command in '" + workingDir + "'." + Environment.NewLine + errorOutput) @@ -28,14 +28,14 @@ public string Command get { return this.command; } } - public int ExitCode + public string ErrorOutput { - get { return this.exitCode; } + get { return this.errorOutput; } } - public string ErrorOutput + public int ExitCode { - get { return this.errorOutput; } + get { return this.exitCode; } } } } diff --git a/WebGitNet.SharedLib/GitRef.cs b/WebGitNet.SharedLib/GitRef.cs index 67bc52d..507b6c1 100644 --- a/WebGitNet.SharedLib/GitRef.cs +++ b/WebGitNet.SharedLib/GitRef.cs @@ -54,12 +54,12 @@ public GitRef(string shaId, string refPath) throw new ArgumentException("The ref path specified is not recognized.", "refPath"); } - public string ShaId { get; private set; } - public string Name { get; private set; } public string RefPath { get; private set; } public RefType RefType { get; private set; } + + public string ShaId { get; private set; } } } diff --git a/WebGitNet.SharedLib/GitUtilities.cs b/WebGitNet.SharedLib/GitUtilities.cs index 4026d55..b302883 100644 --- a/WebGitNet.SharedLib/GitUtilities.cs +++ b/WebGitNet.SharedLib/GitUtilities.cs @@ -21,42 +21,29 @@ namespace WebGitNet public static class GitUtilities { + public static Encoding DefaultEncoding + { + get { return Encoding.GetEncoding(28591); } + } + public static string EmptyTreeHash { get { return "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; } } - public static Encoding DefaultEncoding + public static int CountCommits(string repoPath, string @object = null, bool allRefs = false) { - get { return Encoding.GetEncoding(28591); } + @object = @object ?? "HEAD"; + var results = Execute(string.Format("shortlog -s{0} {1}", allRefs ? " --all" : "", Q(@object)), repoPath); + return (from r in results.Split("\n".ToArray(), StringSplitOptions.RemoveEmptyEntries) + let count = r.Split("\t".ToArray(), StringSplitOptions.RemoveEmptyEntries)[0] + select int.Parse(count.Trim())).Sum(); } - /// - /// Quotes and Escapes a command-line argument for Git and Bash. - /// - public static string Q(string argument) + public static void CreateRepo(string repoPath) { - var result = new StringBuilder(argument.Length + 10); - result.Append("\""); - for (int i = 0; i < argument.Length; i++) - { - var ch = argument[i]; - switch (ch) - { - case '\\': - case '\"': - result.Append('\\'); - result.Append(ch); - break; - - default: - result.Append(ch); - break; - } - } - - result.Append("\""); - return result.ToString(); + var workingDir = Path.GetDirectoryName(repoPath); + var results = Execute(string.Format("init --bare {0}", Q(repoPath)), workingDir, trustErrorCode: true); } public static string Execute(string command, string workingDir, Encoding outputEncoding = null, bool trustErrorCode = false) @@ -87,82 +74,173 @@ public static string Execute(string command, string workingDir, Encoding outputE } } - public static Process Start(string command, string workingDir, bool redirectInput = false, bool redirectError = false, Encoding outputEncoding = null) + public static void ExecutePostCreateHook(string repoPath) { - var git = WebConfigurationManager.AppSettings["GitCommand"]; - var startInfo = new ProcessStartInfo(git, command) + var sh = WebConfigurationManager.AppSettings["ShCommand"]; + + // If 'sh.exe' is not configured, derive the path relative to the git.exe command path. + if (string.IsNullOrEmpty(sh)) { - WorkingDirectory = workingDir, - RedirectStandardInput = redirectInput, - RedirectStandardOutput = true, - RedirectStandardError = redirectError, - StandardOutputEncoding = outputEncoding ?? DefaultEncoding, + var git = WebConfigurationManager.AppSettings["GitCommand"]; + sh = Path.Combine(Path.GetDirectoryName(git), "sh.exe"); + } + + // Find the path of the post-create hook. + var repositories = WebConfigurationManager.AppSettings["RepositoriesPath"]; + var hookRelativePath = WebConfigurationManager.AppSettings["PostCreateHook"]; + + // If the hook path is not configured, default to a path of "post-create", relative to the repository directory. + if (string.IsNullOrEmpty(hookRelativePath)) + { + hookRelativePath = "post-create"; + } + + // Get the full path info for the hook file, and ensure that it exists. + var hookFile = new FileInfo(Path.Combine(repositories, hookRelativePath)); + if (!hookFile.Exists) + { + return; + } + + // Prepare to start sh.exe like: `sh.exe -- "C:\Path\To\Hook-Script"`. + var startInfo = new ProcessStartInfo(sh, string.Format("-- {0}", Q(hookFile.FullName))) + { + WorkingDirectory = repoPath, UseShellExecute = false, CreateNoWindow = true, }; - Process process = null, returnProcess = null; - IDisposable trace = null, traceClosure = null; + startInfo.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("PATH") + Path.PathSeparator + Path.GetDirectoryName(sh); + + // Start the script and wait for exit. + using (var script = Process.Start(startInfo)) + { + script.WaitForExit(); + } + } + + public static List GetAllRefs(string repoPath) + { + var result = Execute("show-ref", repoPath); + return (from l in result.Split("\n".ToArray(), StringSplitOptions.RemoveEmptyEntries) + let parts = l.Split(' ') + select new GitRef(parts[0], parts[1])).ToList(); + } + + public static MemoryStream GetBlob(string repoPath, string tree, string path) + { + MemoryStream blob = null; try { - returnProcess = process = new Process(); - process.StartInfo = startInfo; - process.EnableRaisingEvents = true; - process.Exited += (s, e) => + blob = new MemoryStream(); + using (var git = StartGetBlob(repoPath, tree, path)) { - if (traceClosure != null) + var buffer = new byte[1048576]; + var readCount = 0; + while ((readCount = git.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length)) > 0) { - traceClosure.Dispose(); + blob.Write(buffer, 0, readCount); } - }; + } - try + blob.Seek(0, SeekOrigin.Begin); + + var tempBlob = blob; + blob = null; + return tempBlob; + } + finally + { + if (blob != null) { - var profiler = MiniProfiler.Current; - if (profiler != null) - { - traceClosure = trace = profiler.Step("git " + command); - } + blob.Dispose(); + } + } + } - process.Start(); + public static List GetDiffInfo(string repoPath, string parentCommit, string childCommit) + { + var diffs = new List(); + List diffLines = null; - trace = process = null; - return returnProcess; - } - finally + Action addLastDiff = () => + { + if (diffLines != null) { - if (trace != null) - { - trace.Dispose(); - } + diffs.Add(new DiffInfo(diffLines)); } - } - finally + }; + + using (var git = Start(string.Format("diff-tree --patch -r --raw --src-prefix=\"a:/\" --dst-prefix=\"b:/\" {0} {1}", Q(parentCommit), Q(childCommit)), repoPath)) { - if (process != null) + while (!git.StandardOutput.EndOfStream) { - process.Dispose(); + var line = git.StandardOutput.ReadLine(); + + if (diffLines == null && !line.StartsWith("diff")) + { + continue; + } + + if (line.StartsWith("diff")) + { + addLastDiff(); + diffLines = new List { line }; + } + else + { + diffLines.Add(line); + } } } + + addLastDiff(); + + return diffs; } - public static void ToggleArchived(string repoPath) + public static string[] GetFilePaths(string repoPath, string @object = null, string filter = null) { - var file = Path.Combine(RepoInfoPath(repoPath), "archived"); + @object = @object ?? "HEAD"; + var results = Execute(string.Format("git ls-tree -r -z --full-name --name-only {0}", Q(@object)), repoPath); - if (File.Exists(file)) + return results.Split('\0'); + } + + public static List GetLogEntries(string repoPath, int count, int skip = 0, string @object = null, bool allRefs = false) + { + if (count < 0) { - File.Delete(file); + throw new ArgumentOutOfRangeException("count"); } - else + + if (skip < 0) { - File.Create(file).Close(); + throw new ArgumentOutOfRangeException("skip"); } - } - public static void UpdateServerInfo(string repoPath) - { - Execute("update-server-info", repoPath); + @object = @object ?? "HEAD"; + var results = Execute(string.Format("log -n {0} --encoding=UTF-8 -z --date-order --format=\"format:commit %H%ntree %T%nparent %P%nauthor %an%nauthor mail %ae%nauthor date %aD%ncommitter %cn%ncommitter mail %ce%ncommitter date %cD%nsubject %s%n%b%x00\"{1} {2}", count + skip, allRefs ? " --all" : "", Q(@object)), repoPath, Encoding.UTF8); + + Func parseResults = result => + { + var commit = ParseResultLine("commit ", result, out result); + var tree = ParseResultLine("tree ", result, out result); + var parent = ParseResultLine("parent ", result, out result); + var author = ParseResultLine("author ", result, out result); + var authorEmail = ParseResultLine("author mail ", result, out result); + var authorDate = ParseResultLine("author date ", result, out result); + var committer = ParseResultLine("committer ", result, out result); + var committerEmail = ParseResultLine("committer mail ", result, out result); + var committerDate = ParseResultLine("committer date ", result, out result); + var subject = ParseResultLine("subject ", result, out result); + var body = result; + + return new LogEntry(commit, tree, parent, author, authorEmail, authorDate, committer, committerEmail, committerDate, subject, body); + }; + + return (from r in results.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries).Skip(skip) + select parseResults(r)).ToList(); } public static RepoInfo GetRepoInfo(string repoPath) @@ -208,448 +286,239 @@ public static RepoInfo GetRepoInfo(string repoPath) }; } - private static bool IsRepoDirectory(string repoPath) - { - // We use this method rather than 'git rev-parse --git-dir' or similar, because it takes - // 0.0036 as much time. - return ( - Directory.Exists(Path.Combine(repoPath, "refs")) && - Directory.Exists(Path.Combine(repoPath, "info")) && - Directory.Exists(Path.Combine(repoPath, "objects")) && - File.Exists(Path.Combine(repoPath, "HEAD"))); - } - - public static List GetAllRefs(string repoPath) - { - var result = Execute("show-ref", repoPath); - return (from l in result.Split("\n".ToArray(), StringSplitOptions.RemoveEmptyEntries) - let parts = l.Split(' ') - select new GitRef(parts[0], parts[1])).ToList(); - } - - public static RefValidationResult ValidateRef(string repoPath, string refName) + public static TreeView GetTreeInfo(string repoPath, string tree, string path = null, bool recurse = false) { - if (refName == "HEAD") + if (string.IsNullOrEmpty(tree)) { - return RefValidationResult.Valid; + throw new ArgumentNullException("tree"); } - if (string.IsNullOrWhiteSpace(refName)) + if (!Regex.IsMatch(tree, "^[-.a-zA-Z0-9]+$")) { - return RefValidationResult.Invalid; + throw new ArgumentOutOfRangeException("tree", "tree mush be the id of a tree-ish object."); } - String results; - int exitCode; - - using (var git = Start(string.Format("show-ref --heads --tags -- {0}", Q(refName)), repoPath)) + path = path ?? string.Empty; + string results; + if (recurse) { - results = git.StandardOutput.ReadToEnd(); - git.WaitForExit(); - exitCode = git.ExitCode; + results = + Execute(string.Format("ls-tree -l -r -z {0}:{1}", Q(tree), Q(path)), repoPath, Encoding.UTF8, trustErrorCode: true) + + '\0' + + Execute(string.Format("ls-tree -l -r -d -z {0}:{1}", Q(tree), Q(path)), repoPath, Encoding.UTF8, trustErrorCode: true); } - - if (exitCode != 0) + else { - return RefValidationResult.Invalid; + results = Execute(string.Format("ls-tree -l -z {0}:{1}", Q(tree), Q(path)), repoPath, Encoding.UTF8, trustErrorCode: true); } - if (results.TrimEnd('\n').IndexOf('\n') >= 0) + Func parseResults = result => { - return RefValidationResult.Ambiguous; - } - - return RefValidationResult.Valid; - } - - public static int CountCommits(string repoPath, string @object = null, bool allRefs = false) - { - @object = @object ?? "HEAD"; - var results = Execute(string.Format("shortlog -s{0} {1}", allRefs ? " --all" : "", Q(@object)), repoPath); - return (from r in results.Split("\n".ToArray(), StringSplitOptions.RemoveEmptyEntries) - let count = r.Split("\t".ToArray(), StringSplitOptions.RemoveEmptyEntries)[0] - select int.Parse(count.Trim())).Sum(); - } - - public static string[] GetFilePaths(string repoPath, string @object = null, string filter = null) - { - @object = @object ?? "HEAD"; - var results = Execute(string.Format("git ls-tree -r -z --full-name --name-only {0}", Q(@object)), repoPath); + var mode = ParseTreePart(result, "[ ]+", out result); + var type = ParseTreePart(result, "[ ]+", out result); + var hash = ParseTreePart(result, "[ ]+", out result); + var size = ParseTreePart(result, "\\t+", out result); + var name = result; - return results.Split('\0'); - } + return new ObjectInfo( + (ObjectType)Enum.Parse(typeof(ObjectType), type, ignoreCase: true), + hash, + size == "-" ? (int?)null : int.Parse(size), + name); + }; - public static List GetLogEntries(string repoPath, int count, int skip = 0, string @object = null, bool allRefs = false) - { - if (count < 0) + var objects = from r in results.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries) + select parseResults(r); + IDictionary submodules = null; + try { - throw new ArgumentOutOfRangeException("count"); + var gitmodules = Execute(string.Format("show {0}:.gitmodules", Q(tree)), repoPath, Encoding.UTF8, trustErrorCode: true); + submodules = GetSubmoduleUrls(gitmodules, path); } - - if (skip < 0) + catch { - throw new ArgumentOutOfRangeException("skip"); + // .gitmodules file does not exist } - @object = @object ?? "HEAD"; - var results = Execute(string.Format("log -n {0} --encoding=UTF-8 -z --date-order --format=\"format:commit %H%ntree %T%nparent %P%nauthor %an%nauthor mail %ae%nauthor date %aD%ncommitter %cn%ncommitter mail %ce%ncommitter date %cD%nsubject %s%n%b%x00\"{1} {2}", count + skip, allRefs ? " --all" : "", Q(@object)), repoPath, Encoding.UTF8); - - Func parseResults = result => - { - var commit = ParseResultLine("commit ", result, out result); - var tree = ParseResultLine("tree ", result, out result); - var parent = ParseResultLine("parent ", result, out result); - var author = ParseResultLine("author ", result, out result); - var authorEmail = ParseResultLine("author mail ", result, out result); - var authorDate = ParseResultLine("author date ", result, out result); - var committer = ParseResultLine("committer ", result, out result); - var committerEmail = ParseResultLine("committer mail ", result, out result); - var committerDate = ParseResultLine("committer date ", result, out result); - var subject = ParseResultLine("subject ", result, out result); - var body = result; - - return new LogEntry(commit, tree, parent, author, authorEmail, authorDate, committer, committerEmail, committerDate, subject, body); - }; - - return (from r in results.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries).Skip(skip) - select parseResults(r)).ToList(); + return new TreeView(tree, path, objects, submodules); } - private class Author + public static List GetUserImpacts(string repoPath) { - public string Name { get; set; } + var renames = LoadRenames(repoPath); + var ignores = LoadIgnores(repoPath); - public string Email { get; set; } + string impactData; + using (var git = Start("log -z --format=%x01%H%x1e%ai%x1e%ae%x1e%an%x02 --numstat", repoPath, outputEncoding: Encoding.UTF8)) + { + impactData = git.StandardOutput.ReadToEnd(); + } + + var individualImpacts = from imp in impactData.Split("\x01".ToArray(), StringSplitOptions.RemoveEmptyEntries) + select ParseUserImpact(imp, renames, ignores); + + return individualImpacts.ToList(); } - private static Author Rename(Author author, IList entries) + /// + /// Quotes and Escapes a command-line argument for Git and Bash. + /// + public static string Q(string argument) { - Func getField = (f, a) => + var result = new StringBuilder(argument.Length + 10); + result.Append("\""); + for (int i = 0; i < argument.Length; i++) { - if (f == RenameField.Name) + var ch = argument[i]; + switch (ch) { - return a.Name; - } + case '\\': + case '\"': + result.Append('\\'); + result.Append(ch); + break; - if (f == RenameField.Email) - { - return a.Email; + default: + result.Append(ch); + break; } + } - return null; + result.Append("\""); + return result.ToString(); + } + + public static Process Start(string command, string workingDir, bool redirectInput = false, bool redirectError = false, Encoding outputEncoding = null) + { + var git = WebConfigurationManager.AppSettings["GitCommand"]; + var startInfo = new ProcessStartInfo(git, command) + { + WorkingDirectory = workingDir, + RedirectStandardInput = redirectInput, + RedirectStandardOutput = true, + RedirectStandardError = redirectError, + StandardOutputEncoding = outputEncoding ?? DefaultEncoding, + UseShellExecute = false, + CreateNoWindow = true, }; - Action setField = (f, a, v) => + Process process = null, returnProcess = null; + IDisposable trace = null, traceClosure = null; + try { - if (f == RenameField.Name) + returnProcess = process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + process.Exited += (s, e) => { - a.Name = v; - } + if (traceClosure != null) + { + traceClosure.Dispose(); + } + }; - if (f == RenameField.Email) + try { - a.Email = v; - } - - return; - }; + var profiler = MiniProfiler.Current; + if (profiler != null) + { + traceClosure = trace = profiler.Step("git " + command); + } - author = new Author { Name = author.Name, Email = author.Email }; + process.Start(); - foreach (var entry in entries) + trace = process = null; + return returnProcess; + } + finally + { + if (trace != null) + { + trace.Dispose(); + } + } + } + finally { - switch (entry.RenameStyle) + if (process != null) { - case RenameStyle.Exact: - if (getField(entry.SourceField, author) == entry.Match) - { - foreach (var dest in entry.Destinations) - { - setField(dest.Field, author, dest.Replacement); - } - } - - break; - - case RenameStyle.CaseInsensitive: - if (entry.Match.Equals(getField(entry.SourceField, author), StringComparison.CurrentCultureIgnoreCase)) - { - foreach (var dest in entry.Destinations) - { - setField(dest.Field, author, dest.Replacement); - } - } - - break; - - case RenameStyle.Regex: - if (Regex.IsMatch(getField(entry.SourceField, author), entry.Match)) - { - var newAuthor = new Author { Name = author.Name, Email = author.Email }; - foreach (var dest in entry.Destinations) - { - setField(dest.Field, newAuthor, Regex.Replace(getField(entry.SourceField, author), entry.Match, dest.Replacement)); - } - - author = newAuthor; - } - - break; + process.Dispose(); } } - - return author; } - public static List GetUserImpacts(string repoPath) + public static Process StartGetBlob(string repoPath, string tree, string path) { - var renames = LoadRenames(repoPath); - var ignores = LoadIgnores(repoPath); + if (string.IsNullOrEmpty(tree)) + { + throw new ArgumentNullException("tree"); + } - string impactData; - using (var git = Start("log -z --format=%x01%H%x1e%ai%x1e%ae%x1e%an%x02 --numstat", repoPath, outputEncoding: Encoding.UTF8)) + if (string.IsNullOrEmpty(path)) { - impactData = git.StandardOutput.ReadToEnd(); + throw new ArgumentNullException("path"); } - var individualImpacts = from imp in impactData.Split("\x01".ToArray(), StringSplitOptions.RemoveEmptyEntries) - select ParseUserImpact(imp, renames, ignores); + if (!Regex.IsMatch(tree, "^[-a-zA-Z0-9]+$")) + { + throw new ArgumentOutOfRangeException("tree", "tree mush be the id of a tree-ish object."); + } - return individualImpacts.ToList(); + return Start(string.Format("show {0}:{1}", Q(tree), Q(path)), repoPath, redirectInput: false); } - private static UserImpact ParseUserImpact(string impactData, IList renames, IList ignores) + public static void ToggleArchived(string repoPath) { - var impactParts = impactData.Split("\x02".ToArray(), 2); - var header = impactParts[0]; - var body = impactParts[1].TrimStart('\n'); - - var headerParts = header.Split("\x1e".ToArray(), 4); - var hash = headerParts[0]; - var date = headerParts[1]; - var email = headerParts[2]; - var name = headerParts[3]; - - var author = Rename(new Author { Name = name, Email = email }, renames); - - var insertions = 0; - var deletions = 0; - - var entries = body.Split("\0".ToArray(), StringSplitOptions.RemoveEmptyEntries); - foreach (var entry in entries) - { - var entryParts = entry.Split("\t".ToArray(), 3); - - int ins, del; - if (!int.TryParse(entryParts[0], out ins) || !int.TryParse(entryParts[1], out del)) - { - continue; - } - - var path = entryParts[2]; - - bool keepPath = ProcessIgnores(ignores, hash, path); - - if (keepPath) - { - insertions += ins; - deletions += del; - } - } - - var commitDay = DateTimeOffset.Parse(date).ToUniversalTime().Date; - - return new UserImpact - { - Author = author.Name, - Commits = 1, - Insertions = insertions, - Deletions = deletions, - Impact = Math.Max(insertions, deletions), - Date = commitDay, - }; - } - - private static string RepoInfoPath(string repoPath) - { - // Determine if the immediate path is a repository. If so, it is a bare repo - var isBareRepo = IsRepoDirectory(repoPath); - var path = repoPath; - var isNonBareRepo = false; + var file = Path.Combine(RepoInfoPath(repoPath), "archived"); - if (!isBareRepo) + if (File.Exists(file)) { - // Since we didn't find a bare repo, look to see if this is a non-bare repo. - string nonBareRepoPath = Path.Combine(repoPath, ".git"); - isNonBareRepo = IsRepoDirectory(nonBareRepoPath); - - if (isNonBareRepo) - { - path = nonBareRepoPath; - } + File.Delete(file); } - - // Find our settings folder under info. If not there, create it. - path = Path.Combine(path, "info", "webgit.net"); - - if ((isBareRepo || isNonBareRepo) && - (!Directory.Exists(path))) + else { - Directory.CreateDirectory(path); + File.Create(file).Close(); } - - return path; - } - - private static bool IsArchived(string repoPath) - { - return File.Exists(Path.Combine(RepoInfoPath(repoPath), "archived")); } - private static List LoadRenames(string repoPath) - { - var renames = new List(); - - var parentRenames = Path.Combine(new DirectoryInfo(repoPath).Parent.FullName, "renames"); - var renamesFile = Path.Combine(RepoInfoPath(repoPath), "renames"); - - Action readRenames = (file) => - { - if (File.Exists(file)) - { - renames.AddRange(RenameFileParser.Parse(File.ReadAllLines(file))); - } - }; - - readRenames(parentRenames); - readRenames(renamesFile); - - return renames; - } - - private static List LoadIgnores(string repoPath) + public static void UpdateServerInfo(string repoPath) { - var ignoresFile = Path.Combine(RepoInfoPath(repoPath), "ignore"); - - var ignores = new List(); - if (File.Exists(ignoresFile)) - { - ignores.AddRange(IgnoreFileParser.Parse(File.ReadAllLines(ignoresFile))); - } - - return ignores; + Execute("update-server-info", repoPath); } - private static bool ProcessIgnores(IList ignores, string hash, string path) + public static RefValidationResult ValidateRef(string repoPath, string refName) { - for (int i = ignores.Count - 1; i >= 0; i--) + if (refName == "HEAD") { - var ignore = ignores[i]; - if (hash.StartsWith(ignore.CommitHash) && ignore.IsMatch(path)) - { - return ignore.Negated; - } + return RefValidationResult.Valid; } - return true; - } - - public static List GetDiffInfo(string repoPath, string parentCommit, string childCommit) - { - var diffs = new List(); - List diffLines = null; - - Action addLastDiff = () => - { - if (diffLines != null) - { - diffs.Add(new DiffInfo(diffLines)); - } - }; - - using (var git = Start(string.Format("diff-tree --patch -r --raw --src-prefix=\"a:/\" --dst-prefix=\"b:/\" {0} {1}", Q(parentCommit), Q(childCommit)), repoPath)) + if (string.IsNullOrWhiteSpace(refName)) { - while (!git.StandardOutput.EndOfStream) - { - var line = git.StandardOutput.ReadLine(); - - if (diffLines == null && !line.StartsWith("diff")) - { - continue; - } - - if (line.StartsWith("diff")) - { - addLastDiff(); - diffLines = new List { line }; - } - else - { - diffLines.Add(line); - } - } + return RefValidationResult.Invalid; } - addLastDiff(); - - return diffs; - } - - public static TreeView GetTreeInfo(string repoPath, string tree, string path = null, bool recurse = false) - { - if (string.IsNullOrEmpty(tree)) - { - throw new ArgumentNullException("tree"); - } + String results; + int exitCode; - if (!Regex.IsMatch(tree, "^[-.a-zA-Z0-9]+$")) + using (var git = Start(string.Format("show-ref --heads --tags -- {0}", Q(refName)), repoPath)) { - throw new ArgumentOutOfRangeException("tree", "tree mush be the id of a tree-ish object."); + results = git.StandardOutput.ReadToEnd(); + git.WaitForExit(); + exitCode = git.ExitCode; } - path = path ?? string.Empty; - string results; - if (recurse) - { - results = - Execute(string.Format("ls-tree -l -r -z {0}:{1}", Q(tree), Q(path)), repoPath, Encoding.UTF8, trustErrorCode: true) - + '\0' + - Execute(string.Format("ls-tree -l -r -d -z {0}:{1}", Q(tree), Q(path)), repoPath, Encoding.UTF8, trustErrorCode: true); - } - else + if (exitCode != 0) { - results = Execute(string.Format("ls-tree -l -z {0}:{1}", Q(tree), Q(path)), repoPath, Encoding.UTF8, trustErrorCode: true); + return RefValidationResult.Invalid; } - Func parseResults = result => - { - var mode = ParseTreePart(result, "[ ]+", out result); - var type = ParseTreePart(result, "[ ]+", out result); - var hash = ParseTreePart(result, "[ ]+", out result); - var size = ParseTreePart(result, "\\t+", out result); - var name = result; - - return new ObjectInfo( - (ObjectType)Enum.Parse(typeof(ObjectType), type, ignoreCase: true), - hash, - size == "-" ? (int?)null : int.Parse(size), - name); - }; - - var objects = from r in results.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries) - select parseResults(r); - IDictionary submodules = null; - try - { - var gitmodules = Execute(string.Format("show {0}:.gitmodules", Q(tree)), repoPath, Encoding.UTF8, trustErrorCode: true); - submodules = GetSubmoduleUrls(gitmodules, path); - } - catch + if (results.TrimEnd('\n').IndexOf('\n') >= 0) { - // .gitmodules file does not exist + return RefValidationResult.Ambiguous; } - return new TreeView(tree, path, objects, submodules); + return RefValidationResult.Valid; } private static IDictionary GetSubmoduleUrls(string gitmodules, string path) @@ -740,129 +609,260 @@ private static IDictionary GetSubmoduleUrls(string gitmod return submoduleList; } - public static Process StartGetBlob(string repoPath, string tree, string path) + private static bool IsArchived(string repoPath) { - if (string.IsNullOrEmpty(tree)) - { - throw new ArgumentNullException("tree"); - } + return File.Exists(Path.Combine(RepoInfoPath(repoPath), "archived")); + } - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } + private static bool IsRepoDirectory(string repoPath) + { + // We use this method rather than 'git rev-parse --git-dir' or similar, because it takes + // 0.0036 as much time. + return ( + Directory.Exists(Path.Combine(repoPath, "refs")) && + Directory.Exists(Path.Combine(repoPath, "info")) && + Directory.Exists(Path.Combine(repoPath, "objects")) && + File.Exists(Path.Combine(repoPath, "HEAD"))); + } - if (!Regex.IsMatch(tree, "^[-a-zA-Z0-9]+$")) + private static List LoadIgnores(string repoPath) + { + var ignoresFile = Path.Combine(RepoInfoPath(repoPath), "ignore"); + + var ignores = new List(); + if (File.Exists(ignoresFile)) { - throw new ArgumentOutOfRangeException("tree", "tree mush be the id of a tree-ish object."); + ignores.AddRange(IgnoreFileParser.Parse(File.ReadAllLines(ignoresFile))); } - return Start(string.Format("show {0}:{1}", Q(tree), Q(path)), repoPath, redirectInput: false); + return ignores; } - public static MemoryStream GetBlob(string repoPath, string tree, string path) + private static List LoadRenames(string repoPath) { - MemoryStream blob = null; - try - { - blob = new MemoryStream(); - using (var git = StartGetBlob(repoPath, tree, path)) - { - var buffer = new byte[1048576]; - var readCount = 0; - while ((readCount = git.StandardOutput.BaseStream.Read(buffer, 0, buffer.Length)) > 0) - { - blob.Write(buffer, 0, readCount); - } - } + var renames = new List(); - blob.Seek(0, SeekOrigin.Begin); + var parentRenames = Path.Combine(new DirectoryInfo(repoPath).Parent.FullName, "renames"); + var renamesFile = Path.Combine(RepoInfoPath(repoPath), "renames"); - var tempBlob = blob; - blob = null; - return tempBlob; - } - finally + Action readRenames = (file) => { - if (blob != null) + if (File.Exists(file)) { - blob.Dispose(); + renames.AddRange(RenameFileParser.Parse(File.ReadAllLines(file))); } - } + }; + + readRenames(parentRenames); + readRenames(renamesFile); + + return renames; } - public static void CreateRepo(string repoPath) + private static string ParseResultLine(string prefix, string result, out string rest) { - var workingDir = Path.GetDirectoryName(repoPath); - var results = Execute(string.Format("init --bare {0}", Q(repoPath)), workingDir, trustErrorCode: true); + var parts = result.Split(new[] { '\n' }, 2); + rest = parts[1]; + return parts[0].Substring(prefix.Length); } - public static void ExecutePostCreateHook(string repoPath) + private static string ParseTreePart(string result, string delimiterPattern, out string rest) { - var sh = WebConfigurationManager.AppSettings["ShCommand"]; + var match = Regex.Match(result, delimiterPattern); - // If 'sh.exe' is not configured, derive the path relative to the git.exe command path. - if (string.IsNullOrEmpty(sh)) + if (!match.Success) { - var git = WebConfigurationManager.AppSettings["GitCommand"]; - sh = Path.Combine(Path.GetDirectoryName(git), "sh.exe"); + rest = result; + return null; } + else + { + rest = result.Substring(match.Index + match.Length); + return result.Substring(0, match.Index); + } + } - // Find the path of the post-create hook. - var repositories = WebConfigurationManager.AppSettings["RepositoriesPath"]; - var hookRelativePath = WebConfigurationManager.AppSettings["PostCreateHook"]; + private static UserImpact ParseUserImpact(string impactData, IList renames, IList ignores) + { + var impactParts = impactData.Split("\x02".ToArray(), 2); + var header = impactParts[0]; + var body = impactParts[1].TrimStart('\n'); - // If the hook path is not configured, default to a path of "post-create", relative to the repository directory. - if (string.IsNullOrEmpty(hookRelativePath)) + var headerParts = header.Split("\x1e".ToArray(), 4); + var hash = headerParts[0]; + var date = headerParts[1]; + var email = headerParts[2]; + var name = headerParts[3]; + + var author = Rename(new Author { Name = name, Email = email }, renames); + + var insertions = 0; + var deletions = 0; + + var entries = body.Split("\0".ToArray(), StringSplitOptions.RemoveEmptyEntries); + foreach (var entry in entries) { - hookRelativePath = "post-create"; + var entryParts = entry.Split("\t".ToArray(), 3); + + int ins, del; + if (!int.TryParse(entryParts[0], out ins) || !int.TryParse(entryParts[1], out del)) + { + continue; + } + + var path = entryParts[2]; + + bool keepPath = ProcessIgnores(ignores, hash, path); + + if (keepPath) + { + insertions += ins; + deletions += del; + } } - // Get the full path info for the hook file, and ensure that it exists. - var hookFile = new FileInfo(Path.Combine(repositories, hookRelativePath)); - if (!hookFile.Exists) + var commitDay = DateTimeOffset.Parse(date).ToUniversalTime().Date; + + return new UserImpact { - return; + Author = author.Name, + Commits = 1, + Insertions = insertions, + Deletions = deletions, + Impact = Math.Max(insertions, deletions), + Date = commitDay, + }; + } + + private static bool ProcessIgnores(IList ignores, string hash, string path) + { + for (int i = ignores.Count - 1; i >= 0; i--) + { + var ignore = ignores[i]; + if (hash.StartsWith(ignore.CommitHash) && ignore.IsMatch(path)) + { + return ignore.Negated; + } } - // Prepare to start sh.exe like: `sh.exe -- "C:\Path\To\Hook-Script"`. - var startInfo = new ProcessStartInfo(sh, string.Format("-- {0}", Q(hookFile.FullName))) + return true; + } + + private static Author Rename(Author author, IList entries) + { + Func getField = (f, a) => { - WorkingDirectory = repoPath, - UseShellExecute = false, - CreateNoWindow = true, + if (f == RenameField.Name) + { + return a.Name; + } + + if (f == RenameField.Email) + { + return a.Email; + } + + return null; }; - startInfo.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("PATH") + Path.PathSeparator + Path.GetDirectoryName(sh); + Action setField = (f, a, v) => + { + if (f == RenameField.Name) + { + a.Name = v; + } - // Start the script and wait for exit. - using (var script = Process.Start(startInfo)) + if (f == RenameField.Email) + { + a.Email = v; + } + + return; + }; + + author = new Author { Name = author.Name, Email = author.Email }; + + foreach (var entry in entries) { - script.WaitForExit(); + switch (entry.RenameStyle) + { + case RenameStyle.Exact: + if (getField(entry.SourceField, author) == entry.Match) + { + foreach (var dest in entry.Destinations) + { + setField(dest.Field, author, dest.Replacement); + } + } + + break; + + case RenameStyle.CaseInsensitive: + if (entry.Match.Equals(getField(entry.SourceField, author), StringComparison.CurrentCultureIgnoreCase)) + { + foreach (var dest in entry.Destinations) + { + setField(dest.Field, author, dest.Replacement); + } + } + + break; + + case RenameStyle.Regex: + if (Regex.IsMatch(getField(entry.SourceField, author), entry.Match)) + { + var newAuthor = new Author { Name = author.Name, Email = author.Email }; + foreach (var dest in entry.Destinations) + { + setField(dest.Field, newAuthor, Regex.Replace(getField(entry.SourceField, author), entry.Match, dest.Replacement)); + } + + author = newAuthor; + } + + break; + } } - } - private static string ParseResultLine(string prefix, string result, out string rest) - { - var parts = result.Split(new[] { '\n' }, 2); - rest = parts[1]; - return parts[0].Substring(prefix.Length); + return author; } - private static string ParseTreePart(string result, string delimiterPattern, out string rest) + private static string RepoInfoPath(string repoPath) { - var match = Regex.Match(result, delimiterPattern); + // Determine if the immediate path is a repository. If so, it is a bare repo + var isBareRepo = IsRepoDirectory(repoPath); + var path = repoPath; + var isNonBareRepo = false; - if (!match.Success) + if (!isBareRepo) { - rest = result; - return null; + // Since we didn't find a bare repo, look to see if this is a non-bare repo. + string nonBareRepoPath = Path.Combine(repoPath, ".git"); + isNonBareRepo = IsRepoDirectory(nonBareRepoPath); + + if (isNonBareRepo) + { + path = nonBareRepoPath; + } } - else + + // Find our settings folder under info. If not there, create it. + path = Path.Combine(path, "info", "webgit.net"); + + if ((isBareRepo || isNonBareRepo) && + (!Directory.Exists(path))) { - rest = result.Substring(match.Index + match.Length); - return result.Substring(0, match.Index); + Directory.CreateDirectory(path); } + + return path; + } + + private class Author + { + public string Email { get; set; } + + public string Name { get; set; } } } } diff --git a/WebGitNet.SharedLib/IgnoreEntry.cs b/WebGitNet.SharedLib/IgnoreEntry.cs index 1d86528..5bd52a4 100644 --- a/WebGitNet.SharedLib/IgnoreEntry.cs +++ b/WebGitNet.SharedLib/IgnoreEntry.cs @@ -18,16 +18,14 @@ public class IgnoreEntry public string CommitHash { get; set; } + public bool Negated { get; set; } + public string PathGlobs { get { return this.pathGlobs; } set { this.pathGlobs = value; this.pathRegexes = null; } } - public bool Negated { get; set; } - - public bool Rooted { get; set; } - public ReadOnlyCollection PathRegexes { get @@ -42,6 +40,8 @@ public ReadOnlyCollection PathRegexes } } + public bool Rooted { get; set; } + public bool IsMatch(string path) { var parts = path.Split('/'); diff --git a/WebGitNet.SharedLib/LogEntry.cs b/WebGitNet.SharedLib/LogEntry.cs index d459e03..9de2567 100644 --- a/WebGitNet.SharedLib/LogEntry.cs +++ b/WebGitNet.SharedLib/LogEntry.cs @@ -13,17 +13,17 @@ namespace WebGitNet public class LogEntry { - private readonly string commitHash; - private readonly string tree; - private readonly IList parents; private readonly string author; - private readonly string authorEmail; private readonly DateTimeOffset authorDate; + private readonly string authorEmail; + private readonly string body; + private readonly string commitHash; private readonly string committer; - private readonly string committerEmail; private readonly DateTimeOffset committerDate; + private readonly string committerEmail; + private readonly IList parents; private readonly string subject; - private readonly string body; + private readonly string tree; public LogEntry(string commitHash, string tree, string parents, string author, string authorEmail, string authorDate, string committer, string committerEmail, string committerDate, string subject, string body) { @@ -42,59 +42,59 @@ public LogEntry(string commitHash, string tree, string parents, string author, s this.body = body; } - public string CommitHash + public string Author { get { - return this.commitHash; + return this.author; } } - public string Tree + public DateTimeOffset AuthorDate { get { - return this.tree; + return this.authorDate; } } - public IList Parents + public string AuthorEmail { get { - return this.parents; + return this.authorEmail; } } - public string Author + public string Body { get { - return this.author; + return this.body; } } - public string AuthorEmail + public string CommitHash { get { - return this.authorEmail; + return this.commitHash; } } - public DateTimeOffset AuthorDate + public string Committer { get { - return this.authorDate; + return this.committer; } } - public string Committer + public DateTimeOffset CommitterDate { get { - return this.committer; + return this.committerDate; } } @@ -106,11 +106,11 @@ public string CommitterEmail } } - public DateTimeOffset CommitterDate + public IList Parents { get { - return this.committerDate; + return this.parents; } } @@ -122,11 +122,11 @@ public string Subject } } - public string Body + public string Tree { get { - return this.body; + return this.tree; } } } diff --git a/WebGitNet.SharedLib/ObjectInfo.cs b/WebGitNet.SharedLib/ObjectInfo.cs index efc44c2..a6389c3 100644 --- a/WebGitNet.SharedLib/ObjectInfo.cs +++ b/WebGitNet.SharedLib/ObjectInfo.cs @@ -9,10 +9,10 @@ namespace WebGitNet { public class ObjectInfo { - private readonly ObjectType objectType; private readonly string hash; - private readonly int? size; private readonly string name; + private readonly ObjectType objectType; + private readonly int? size; public ObjectInfo(ObjectType objectType, string hash, int? size, string name) { @@ -22,24 +22,24 @@ public ObjectInfo(ObjectType objectType, string hash, int? size, string name) this.name = name; } - public ObjectType ObjectType + public string Hash { - get { return this.objectType; } + get { return this.hash; } } - public string Hash + public string Name { - get { return this.hash; } + get { return this.name; } } - public int? Size + public ObjectType ObjectType { - get { return this.size; } + get { return this.objectType; } } - public string Name + public int? Size { - get { return this.name; } + get { return this.size; } } } } diff --git a/WebGitNet.SharedLib/RenameEntry.cs b/WebGitNet.SharedLib/RenameEntry.cs index 0a416fa..cc8e6f4 100644 --- a/WebGitNet.SharedLib/RenameEntry.cs +++ b/WebGitNet.SharedLib/RenameEntry.cs @@ -23,13 +23,13 @@ public enum RenameStyle public class RenameEntry { - public RenameStyle RenameStyle { get; set; } - - public RenameField SourceField { get; set; } + public Destination[] Destinations { get; set; } public string Match { get; set; } - public Destination[] Destinations { get; set; } + public RenameStyle RenameStyle { get; set; } + + public RenameField SourceField { get; set; } public class Destination { diff --git a/WebGitNet.SharedLib/RenameFileParser.cs b/WebGitNet.SharedLib/RenameFileParser.cs index 48e457c..7318173 100644 --- a/WebGitNet.SharedLib/RenameFileParser.cs +++ b/WebGitNet.SharedLib/RenameFileParser.cs @@ -14,18 +14,6 @@ namespace WebGitNet public static class RenameFileParser { - private enum Terminal - { - Field, - SearchType, - Assignment, - Seperator, - Definition, - String, - Whitespace, - EndOfLine, - } - private static readonly Dictionary terminals = new Dictionary { { Terminal.Field, "name|email" }, @@ -38,11 +26,39 @@ private enum Terminal { Terminal.EndOfLine, @"$" }, }; + private enum Terminal + { + Field, + SearchType, + Assignment, + Seperator, + Definition, + String, + Whitespace, + EndOfLine, + } + public static List Parse(string[] lines) { return lines.Select(l => Parse(l)).Where(l => l != null).ToList(); } + private static string Accept(Terminal terminal, ref string subject) + { + var regex = terminals[terminal]; + + var match = Regex.Match(subject, "^" + regex, RegexOptions.IgnoreCase); + if (match.Success) + { + subject = subject.Substring(match.Length); + return match.Value; + } + else + { + return null; + } + } + private static RenameEntry Parse(string line) { if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith("#")) @@ -131,22 +147,6 @@ private static RenameEntry Parse(string line) }; } - private static string Accept(Terminal terminal, ref string subject) - { - var regex = terminals[terminal]; - - var match = Regex.Match(subject, "^" + regex, RegexOptions.IgnoreCase); - if (match.Success) - { - subject = subject.Substring(match.Length); - return match.Value; - } - else - { - return null; - } - } - private static string Require(Terminal terminal, ref string subject) { var match = Accept(terminal, ref subject); diff --git a/WebGitNet.SharedLib/RenameFileSyntaxException.cs b/WebGitNet.SharedLib/RenameFileSyntaxException.cs index bd1124b..9d93f53 100644 --- a/WebGitNet.SharedLib/RenameFileSyntaxException.cs +++ b/WebGitNet.SharedLib/RenameFileSyntaxException.cs @@ -8,9 +8,6 @@ namespace WebGitNet { using System; - using System.Collections.Generic; - using System.Linq; - using System.Web; public class RenameFileSyntaxException : Exception { diff --git a/WebGitNet.SharedLib/RepoInfo.cs b/WebGitNet.SharedLib/RepoInfo.cs index 118a846..d6d3af1 100644 --- a/WebGitNet.SharedLib/RepoInfo.cs +++ b/WebGitNet.SharedLib/RepoInfo.cs @@ -2,16 +2,16 @@ { public class RepoInfo { - public string Name { get; set; } - public string Description { get; set; } - public bool IsGitRepo { get; set; } - - public string RepoPath { get; set; } - public bool IsArchived { get; set; } public bool IsBare { get; set; } + + public bool IsGitRepo { get; set; } + + public string Name { get; set; } + + public string RepoPath { get; set; } } } diff --git a/WebGitNet.SharedLib/ResourceInfo.cs b/WebGitNet.SharedLib/ResourceInfo.cs index a56c0d8..c2c60e2 100644 --- a/WebGitNet.SharedLib/ResourceInfo.cs +++ b/WebGitNet.SharedLib/ResourceInfo.cs @@ -11,14 +11,14 @@ namespace WebGitNet public class ResourceInfo { - public ResourceType Type { get; set; } + public FileSystemInfo FileSystemInfo { get; set; } + + public string FullPath { get; set; } public string LocalPath { get; set; } public string Name { get; set; } - public string FullPath { get; set; } - - public FileSystemInfo FileSystemInfo { get; set; } + public ResourceType Type { get; set; } } } diff --git a/WebGitNet.SharedLib/RouteCollectionHelpers.cs b/WebGitNet.SharedLib/RouteCollectionHelpers.cs index 44c5e31..6ac2863 100644 --- a/WebGitNet.SharedLib/RouteCollectionHelpers.cs +++ b/WebGitNet.SharedLib/RouteCollectionHelpers.cs @@ -7,35 +7,12 @@ namespace WebGitNet { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; + using System.Reflection; using System.Web.Mvc; using System.Web.Routing; - using System.Reflection; public static class RouteCollectionHelpers { - public static void MapResource(this RouteCollection routes, string resourcePath, string contentType) - { - var assembly = Assembly.GetCallingAssembly(); - var assemblyName = assembly.GetName().Name; - var resourceName = assembly.GetResourceName(resourcePath); - - routes.MapRoute( - assemblyName + " " + resourcePath, - resourcePath, - new - { - assemblyName, - resourceName, - contentType, - controller = "PluginContent", - action = "Resource" - }); - } - public static string GetResourceName(this Assembly assembly, string virtualPath) { if (string.IsNullOrEmpty(virtualPath)) @@ -58,5 +35,24 @@ public static string GetResourceName(this Assembly assembly, string virtualPath) .Replace(@"/", ".") .Replace(@"\", "."); } + + public static void MapResource(this RouteCollection routes, string resourcePath, string contentType) + { + var assembly = Assembly.GetCallingAssembly(); + var assemblyName = assembly.GetName().Name; + var resourceName = assembly.GetResourceName(resourcePath); + + routes.MapRoute( + assemblyName + " " + resourcePath, + resourcePath, + new + { + assemblyName, + resourceName, + contentType, + controller = "PluginContent", + action = "Resource" + }); + } } } diff --git a/WebGitNet.SharedLib/Search/SearchLine.cs b/WebGitNet.SharedLib/Search/SearchLine.cs index 8849bf4..2a4ae66 100644 --- a/WebGitNet.SharedLib/Search/SearchLine.cs +++ b/WebGitNet.SharedLib/Search/SearchLine.cs @@ -2,8 +2,8 @@ { public class SearchLine { - public int? LineNumber { get; set; } - public string Line { get; set; } + + public int? LineNumber { get; set; } } } diff --git a/WebGitNet.SharedLib/Search/SearchResult.cs b/WebGitNet.SharedLib/Search/SearchResult.cs index 3f5d0dc..099b017 100644 --- a/WebGitNet.SharedLib/Search/SearchResult.cs +++ b/WebGitNet.SharedLib/Search/SearchResult.cs @@ -11,14 +11,14 @@ namespace WebGitNet.Search public class SearchResult { - public string LinkText { get; set; } + public string ActionName { get; set; } public string ControllerName { get; set; } - public string ActionName { get; set; } + public IList Lines { get; set; } - public object RouteValues { get; set; } + public string LinkText { get; set; } - public IList Lines { get; set; } + public object RouteValues { get; set; } } } diff --git a/WebGitNet.SharedLib/SharedControllerBase.cs b/WebGitNet.SharedLib/SharedControllerBase.cs index b9cfc1f..1a47af7 100644 --- a/WebGitNet.SharedLib/SharedControllerBase.cs +++ b/WebGitNet.SharedLib/SharedControllerBase.cs @@ -12,8 +12,8 @@ namespace WebGitNet public abstract partial class SharedControllerBase : Controller { - private readonly FileManager fileManager; private readonly BreadCrumbTrail breadCrumbs; + private readonly FileManager fileManager; public SharedControllerBase() { @@ -25,9 +25,12 @@ public SharedControllerBase() ViewBag.BreadCrumbs = this.breadCrumbs; } - protected void AddRepoBreadCrumb(string repo) + public BreadCrumbTrail BreadCrumbs { - this.BreadCrumbs.Append("Browse", "ViewRepo", repo, new { repo }); + get + { + return this.breadCrumbs; + } } public FileManager FileManager @@ -38,12 +41,9 @@ public FileManager FileManager } } - public BreadCrumbTrail BreadCrumbs + protected void AddRepoBreadCrumb(string repo) { - get - { - return this.breadCrumbs; - } + this.BreadCrumbs.Append("Browse", "ViewRepo", repo, new { repo }); } } } diff --git a/WebGitNet.SharedLib/SharedControllerBase.profiling.cs b/WebGitNet.SharedLib/SharedControllerBase.profiling.cs index 7001c94..89f4e12 100644 --- a/WebGitNet.SharedLib/SharedControllerBase.profiling.cs +++ b/WebGitNet.SharedLib/SharedControllerBase.profiling.cs @@ -8,9 +8,6 @@ namespace WebGitNet { using System; - using System.Collections.Generic; - using System.Linq; - using System.Web; using System.Web.Mvc; using StackExchange.Profiling; @@ -19,12 +16,6 @@ public partial class SharedControllerBase private IDisposable actionExecuting; private IDisposable resultExecuting; - protected override void OnActionExecuting(ActionExecutingContext filterContext) - { - this.actionExecuting = MiniProfiler.StepStatic("Action Executing"); - base.OnActionExecuting(filterContext); - } - protected override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); @@ -35,10 +26,10 @@ protected override void OnActionExecuted(ActionExecutedContext filterContext) } } - protected override void OnResultExecuting(ResultExecutingContext filterContext) + protected override void OnActionExecuting(ActionExecutingContext filterContext) { - this.resultExecuting = MiniProfiler.StepStatic("Result Executing"); - base.OnResultExecuting(filterContext); + this.actionExecuting = MiniProfiler.StepStatic("Action Executing"); + base.OnActionExecuting(filterContext); } protected override void OnResultExecuted(ResultExecutedContext filterContext) @@ -50,5 +41,11 @@ protected override void OnResultExecuted(ResultExecutedContext filterContext) this.resultExecuting = null; } } + + protected override void OnResultExecuting(ResultExecutingContext filterContext) + { + this.resultExecuting = MiniProfiler.StepStatic("Result Executing"); + base.OnResultExecuting(filterContext); + } } } \ No newline at end of file diff --git a/WebGitNet.SharedLib/SubmoduleInfo.cs b/WebGitNet.SharedLib/SubmoduleInfo.cs index 9883a6d..896ebdc 100644 --- a/WebGitNet.SharedLib/SubmoduleInfo.cs +++ b/WebGitNet.SharedLib/SubmoduleInfo.cs @@ -6,32 +6,27 @@ namespace WebGitNet { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - /// /// TODO: Update summary. /// public class SubmoduleInfo - { - public string Path + { + public SubmoduleInfo(string path, string url) { - get; - private set; + this.Path = path; + this.Url = url; } - public string Url + public string Path { - get; - private set; + get; + private set; } - public SubmoduleInfo(string path, string url) + public string Url { - this.Path = path; - this.Url = url; + get; + private set; } } } diff --git a/WebGitNet.SharedLib/TreeView.cs b/WebGitNet.SharedLib/TreeView.cs index f5c7500..4f06a21 100644 --- a/WebGitNet.SharedLib/TreeView.cs +++ b/WebGitNet.SharedLib/TreeView.cs @@ -10,14 +10,13 @@ namespace WebGitNet using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - using System; public class TreeView { - private readonly string tree; - private readonly string path; private readonly ReadOnlyCollection objects; + private readonly string path; private readonly IDictionary submodules; + private readonly string tree; public TreeView(string tree, string path, IEnumerable objects, IDictionary submodules) { @@ -35,9 +34,9 @@ public TreeView(string tree, string path, IEnumerable objects, IDict this.submodules = submodules; } - public string Tree + public IList Objects { - get { return this.tree; } + get { return this.objects; } } public string Path @@ -45,14 +44,14 @@ public string Path get { return this.path; } } - public IList Objects + public IDictionary Submodules { - get { return this.objects; } + get { return this.submodules; } } - public IDictionary Submodules + public string Tree { - get { return this.submodules; } + get { return this.tree; } } } } diff --git a/WebGitNet.SharedLib/UserImpact.cs b/WebGitNet.SharedLib/UserImpact.cs index 0326949..03b918e 100644 --- a/WebGitNet.SharedLib/UserImpact.cs +++ b/WebGitNet.SharedLib/UserImpact.cs @@ -15,12 +15,12 @@ public class UserImpact public int Commits { get; set; } - public int Insertions { get; set; } + public DateTime Date { get; set; } public int Deletions { get; set; } public int Impact { get; set; } - public DateTime Date { get; set; } + public int Insertions { get; set; } } } diff --git a/WebGitNet/ActionResults/GitCommandResult.cs b/WebGitNet/ActionResults/GitCommandResult.cs index 9deb585..0119540 100644 --- a/WebGitNet/ActionResults/GitCommandResult.cs +++ b/WebGitNet/ActionResults/GitCommandResult.cs @@ -54,14 +54,14 @@ public override void ExecuteResult(ControllerContext context) response.BinaryWrite(commandData); } - private static byte[] PacketFormat(string packet) + private static byte[] PacketFlush() { - return GitUtilities.DefaultEncoding.GetBytes((packet.Length + 4).ToString("X").ToLower().PadLeft(4, '0') + packet); + return new[] { (byte)'0', (byte)'0', (byte)'0', (byte)'0' }; } - private static byte[] PacketFlush() + private static byte[] PacketFormat(string packet) { - return new[] { (byte)'0', (byte)'0', (byte)'0', (byte)'0' }; + return GitUtilities.DefaultEncoding.GetBytes((packet.Length + 4).ToString("X").ToLower().PadLeft(4, '0') + packet); } } } \ No newline at end of file diff --git a/WebGitNet/ActionResults/GitFileResult.cs b/WebGitNet/ActionResults/GitFileResult.cs index 629b3f7..54a9f5e 100644 --- a/WebGitNet/ActionResults/GitFileResult.cs +++ b/WebGitNet/ActionResults/GitFileResult.cs @@ -11,10 +11,10 @@ namespace WebGitNet.ActionResults public class GitFileResult : ActionResult { + private readonly string contentType; + private readonly string path; private readonly string repoPath; private readonly string tree; - private readonly string path; - private readonly string contentType; public GitFileResult(string repoPath, string tree, string path, string contentType = null) { diff --git a/WebGitNet/ActionResults/GitStreamResult.cs b/WebGitNet/ActionResults/GitStreamResult.cs index 0a639eb..898ab00 100644 --- a/WebGitNet/ActionResults/GitStreamResult.cs +++ b/WebGitNet/ActionResults/GitStreamResult.cs @@ -15,8 +15,8 @@ namespace WebGitNet.ActionResults public class GitStreamResult : ActionResult { - private readonly string commandFormat; private readonly string action; + private readonly string commandFormat; private readonly string repoPath; public GitStreamResult(string commandFormat, string action, string repoPath) diff --git a/WebGitNet/Controllers/BrowseController.cs b/WebGitNet/Controllers/BrowseController.cs index b0a2b49..0558560 100644 --- a/WebGitNet/Controllers/BrowseController.cs +++ b/WebGitNet/Controllers/BrowseController.cs @@ -33,30 +33,64 @@ public ActionResult Index(bool archived = false) return View(repos); } - public ActionResult ViewRepo(string repo) + public ActionResult ViewBlob(string repo, string @object, string path, bool raw = false) { var resourceInfo = this.FileManager.GetResourceInfo(repo); - if (resourceInfo.Type != ResourceType.Directory) + if (resourceInfo.Type != ResourceType.Directory || string.IsNullOrEmpty(path)) { return HttpNotFound(); } - var repoInfo = GitUtilities.GetRepoInfo(resourceInfo.FullPath); - if (!repoInfo.IsGitRepo) + var fileName = Path.GetFileName(path); + var containingPath = path.Substring(0, path.Length - fileName.Length); + + TreeView items; + try + { + items = GitUtilities.GetTreeInfo(resourceInfo.FullPath, @object, containingPath); + } + catch (GitErrorException) { return HttpNotFound(); } + if (!items.Objects.Any(o => o.Name == fileName)) + { + return HttpNotFound(); + } + + var contentType = MimeUtilities.GetMimeType(fileName); + + if (raw) + { + return new GitFileResult(resourceInfo.FullPath, @object, path, contentType); + } + AddRepoBreadCrumb(repo); + this.BreadCrumbs.Append("Browse", "ViewTree", @object, new { repo, @object, path = string.Empty }); + var paths = BreadCrumbTrail.EnumeratePath(path, TrailingSlashBehavior.LeaveOffLastTrailingSlash).ToList(); + this.BreadCrumbs.Append("Browse", "ViewTree", paths.Take(paths.Count() - 1), p => p.Key, p => new { repo, @object, path = p.Value }); + this.BreadCrumbs.Append("Browse", "ViewBlob", paths.Last().Key, new { repo, @object, path = paths.Last().Value }); - var lastCommit = GitUtilities.GetLogEntries(resourceInfo.FullPath, 1).FirstOrDefault(); + ViewBag.RepoName = resourceInfo.Name; + ViewBag.Tree = @object; + ViewBag.Path = path; + ViewBag.FileName = fileName; + ViewBag.ContentType = contentType; + string model = null; - ViewBag.RepoInfo = GitUtilities.GetRepoInfo(resourceInfo.FullPath); - ViewBag.LastCommit = lastCommit; - ViewBag.CurrentTree = lastCommit != null ? GitUtilities.GetTreeInfo(resourceInfo.FullPath, "HEAD") : null; - ViewBag.Refs = GitUtilities.GetAllRefs(resourceInfo.FullPath); + if (contentType.StartsWith("text/") || contentType == "application/xml" || Regex.IsMatch(contentType, @"^application/.*\+xml$")) + { + using (var blob = GitUtilities.GetBlob(resourceInfo.FullPath, @object, path)) + { + using (var reader = new StreamReader(blob, detectEncodingFromByteOrderMarks: true)) + { + model = reader.ReadToEnd(); + } + } + } - return View(); + return View((object)model); } public ActionResult ViewCommit(string repo, string @object) @@ -115,7 +149,7 @@ public ActionResult ViewCommits(string repo, string @object = null, int page = 1 return View(commits); } - public ActionResult ViewTree(string repo, string @object, string path) + public ActionResult ViewRepo(string repo) { var resourceInfo = this.FileManager.GetResourceInfo(repo); if (resourceInfo.Type != ResourceType.Directory) @@ -123,85 +157,51 @@ public ActionResult ViewTree(string repo, string @object, string path) return HttpNotFound(); } - TreeView items; - try - { - items = GitUtilities.GetTreeInfo(resourceInfo.FullPath, @object, path); - } - catch (GitErrorException) + var repoInfo = GitUtilities.GetRepoInfo(resourceInfo.FullPath); + if (!repoInfo.IsGitRepo) { return HttpNotFound(); } AddRepoBreadCrumb(repo); - this.BreadCrumbs.Append("Browse", "ViewTree", @object, new { repo, @object, path = string.Empty }); - this.BreadCrumbs.Append("Browse", "ViewTree", BreadCrumbTrail.EnumeratePath(path), p => p.Key, p => new { repo, @object, path = p.Value }); - ViewBag.RepoName = resourceInfo.Name; - ViewBag.Tree = @object; - ViewBag.Path = path ?? string.Empty; + var lastCommit = GitUtilities.GetLogEntries(resourceInfo.FullPath, 1).FirstOrDefault(); - return View(items); + ViewBag.RepoInfo = GitUtilities.GetRepoInfo(resourceInfo.FullPath); + ViewBag.LastCommit = lastCommit; + ViewBag.CurrentTree = lastCommit != null ? GitUtilities.GetTreeInfo(resourceInfo.FullPath, "HEAD") : null; + ViewBag.Refs = GitUtilities.GetAllRefs(resourceInfo.FullPath); + + return View(); } - public ActionResult ViewBlob(string repo, string @object, string path, bool raw = false) + public ActionResult ViewTree(string repo, string @object, string path) { var resourceInfo = this.FileManager.GetResourceInfo(repo); - if (resourceInfo.Type != ResourceType.Directory || string.IsNullOrEmpty(path)) + if (resourceInfo.Type != ResourceType.Directory) { return HttpNotFound(); } - var fileName = Path.GetFileName(path); - var containingPath = path.Substring(0, path.Length - fileName.Length); - TreeView items; try { - items = GitUtilities.GetTreeInfo(resourceInfo.FullPath, @object, containingPath); + items = GitUtilities.GetTreeInfo(resourceInfo.FullPath, @object, path); } catch (GitErrorException) { return HttpNotFound(); } - if (!items.Objects.Any(o => o.Name == fileName)) - { - return HttpNotFound(); - } - - var contentType = MimeUtilities.GetMimeType(fileName); - - if (raw) - { - return new GitFileResult(resourceInfo.FullPath, @object, path, contentType); - } - AddRepoBreadCrumb(repo); this.BreadCrumbs.Append("Browse", "ViewTree", @object, new { repo, @object, path = string.Empty }); - var paths = BreadCrumbTrail.EnumeratePath(path, TrailingSlashBehavior.LeaveOffLastTrailingSlash).ToList(); - this.BreadCrumbs.Append("Browse", "ViewTree", paths.Take(paths.Count() - 1), p => p.Key, p => new { repo, @object, path = p.Value }); - this.BreadCrumbs.Append("Browse", "ViewBlob", paths.Last().Key, new { repo, @object, path = paths.Last().Value }); + this.BreadCrumbs.Append("Browse", "ViewTree", BreadCrumbTrail.EnumeratePath(path), p => p.Key, p => new { repo, @object, path = p.Value }); ViewBag.RepoName = resourceInfo.Name; ViewBag.Tree = @object; - ViewBag.Path = path; - ViewBag.FileName = fileName; - ViewBag.ContentType = contentType; - string model = null; - - if (contentType.StartsWith("text/") || contentType == "application/xml" || Regex.IsMatch(contentType, @"^application/.*\+xml$")) - { - using (var blob = GitUtilities.GetBlob(resourceInfo.FullPath, @object, path)) - { - using (var reader = new StreamReader(blob, detectEncodingFromByteOrderMarks: true)) - { - model = reader.ReadToEnd(); - } - } - } + ViewBag.Path = path ?? string.Empty; - return View((object)model); + return View(items); } public class RouteRegisterer : IRouteRegisterer diff --git a/WebGitNet/Controllers/GraphController.cs b/WebGitNet/Controllers/GraphController.cs index da1f00a..4bd90ff 100644 --- a/WebGitNet/Controllers/GraphController.cs +++ b/WebGitNet/Controllers/GraphController.cs @@ -7,7 +7,6 @@ namespace WebGitNet.Controllers { - using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; @@ -16,35 +15,6 @@ namespace WebGitNet.Controllers public class GraphController : SharedControllerBase { - public ActionResult ViewGraph(string repo, int page = 1) - { - var resourceInfo = this.FileManager.GetResourceInfo(repo); - if (resourceInfo.Type != ResourceType.Directory || page < 1) - { - return HttpNotFound(); - } - - const int PageSize = 50; - int skip = PageSize * (page - 1); - var count = GitUtilities.CountCommits(resourceInfo.FullPath, allRefs: true); - - if (skip >= count) - { - return HttpNotFound(); - } - - this.BreadCrumbs.Append("Browse", "Index", "Browse"); - AddRepoBreadCrumb(repo); - this.BreadCrumbs.Append("Graph", "ViewGraph", "View Graph", new { repo }); - - var commits = GetLogEntries(resourceInfo.FullPath, skip + PageSize).Skip(skip).ToList(); - - ViewBag.PaginationInfo = new PaginationInfo(page, (count + PageSize - 1) / PageSize, "Graph", "ViewGraph", new { repo }); - ViewBag.RepoName = resourceInfo.Name; - - return View(commits); - } - public List GetLogEntries(string path, int count) { var nodeColors = new Dictionary(); @@ -76,6 +46,35 @@ public List GetLogEntries(string path, int count) return results; } + public ActionResult ViewGraph(string repo, int page = 1) + { + var resourceInfo = this.FileManager.GetResourceInfo(repo); + if (resourceInfo.Type != ResourceType.Directory || page < 1) + { + return HttpNotFound(); + } + + const int PageSize = 50; + int skip = PageSize * (page - 1); + var count = GitUtilities.CountCommits(resourceInfo.FullPath, allRefs: true); + + if (skip >= count) + { + return HttpNotFound(); + } + + this.BreadCrumbs.Append("Browse", "Index", "Browse"); + AddRepoBreadCrumb(repo); + this.BreadCrumbs.Append("Graph", "ViewGraph", "View Graph", new { repo }); + + var commits = GetLogEntries(resourceInfo.FullPath, skip + PageSize).Skip(skip).ToList(); + + ViewBag.PaginationInfo = new PaginationInfo(page, (count + PageSize - 1) / PageSize, "Graph", "ViewGraph", new { repo }); + ViewBag.RepoName = resourceInfo.Name; + + return View(commits); + } + private static int ColorNode(LogEntry entry, Dictionary nodeColors, ref int colorNumber) { int color; @@ -139,6 +138,20 @@ private List BuildOutgoing(List incoming, LogEntry entry) return outgoing; } + public class RouteRegisterer : IRouteRegisterer + { + public void RegisterRoutes(RouteCollection routes) + { + routes.MapRoute( + "View Graph", + "browse/{repo}/graph", + new { controller = "Graph", action = "ViewGraph", routeName = "View Graph", routeIcon = "branch" }); + + routes.MapResource("Content/graph.css", "text/css"); + routes.MapResource("Scripts/graph.js", "text/javascript"); + } + } + private class LogEntryComparer : IComparer { private static readonly LogEntryComparer instance = new LogEntryComparer(); @@ -164,19 +177,5 @@ public int Compare(LogEntry x, LogEntry y) return c; } } - - public class RouteRegisterer : IRouteRegisterer - { - public void RegisterRoutes(RouteCollection routes) - { - routes.MapRoute( - "View Graph", - "browse/{repo}/graph", - new { controller = "Graph", action = "ViewGraph", routeName = "View Graph", routeIcon = "branch" }); - - routes.MapResource("Content/graph.css", "text/css"); - routes.MapResource("Scripts/graph.js", "text/javascript"); - } - } } } \ No newline at end of file diff --git a/WebGitNet/Controllers/ServiceRpcController.cs b/WebGitNet/Controllers/ServiceRpcController.cs index 97bebbc..47b0826 100644 --- a/WebGitNet/Controllers/ServiceRpcController.cs +++ b/WebGitNet/Controllers/ServiceRpcController.cs @@ -14,15 +14,15 @@ namespace WebGitNet.Controllers public class ServiceRpcController : SharedControllerBase { [HttpPost] - public ActionResult UploadPack(string url) + public ActionResult ReceivePack(string url) { - return this.ServiceRpc(url, "upload-pack"); + return this.ServiceRpc(url, "receive-pack"); } [HttpPost] - public ActionResult ReceivePack(string url) + public ActionResult UploadPack(string url) { - return this.ServiceRpc(url, "receive-pack"); + return this.ServiceRpc(url, "upload-pack"); } private ActionResult ServiceRpc(string url, string action) diff --git a/WebGitNet/Global.asax.cs b/WebGitNet/Global.asax.cs index d869eab..df29ea8 100644 --- a/WebGitNet/Global.asax.cs +++ b/WebGitNet/Global.asax.cs @@ -40,6 +40,11 @@ public static void RegisterRoutes(RouteCollection routes) new { controller = "Browse", action = "Index" }); } + protected void Application_End() + { + container.Dispose(); + } + protected void Application_Start() { Bootstrap(); @@ -56,11 +61,6 @@ protected void Application_Start() HostingEnvironment.RegisterVirtualPathProvider(new ResourceVirtualPathProvider()); } - protected void Application_End() - { - container.Dispose(); - } - private static void Bootstrap() { var directoryFilter = new AssemblyFilter(HostingEnvironment.MapPath("~/Plugins")); diff --git a/WebGitNet/HtmlHelpers.cs b/WebGitNet/HtmlHelpers.cs index 82eace6..fce19c9 100644 --- a/WebGitNet/HtmlHelpers.cs +++ b/WebGitNet/HtmlHelpers.cs @@ -18,30 +18,6 @@ namespace WebGitNet public static class HtmlHelpers { - public static MvcHtmlString Markdown(this HtmlHelper html, string markdown) - { - var markdownParser = new Markdown(true); - - return new MvcHtmlString(markdownParser.Transform(markdown)); - } - - public static MvcHtmlString Gravatar(this HtmlHelper html, string email, string name, int size = 72) - { - var fallBack = WebConfigurationManager.AppSettings["GravatarFallBack"]; - if (string.IsNullOrEmpty(fallBack)) - { - fallBack = "mm"; - } - - var imgUrl = string.Format( - "https://secure.gravatar.com/avatar/{0}.png?s={1}&d={2}&r=g", - HashString(email), - size, - fallBack); - - return new MvcHtmlString(string.Format("\"\"", size, html.AttributeEncode(name), html.AttributeEncode(imgUrl))); - } - public static IEnumerable FindSatisfiableRoutes(this HtmlHelper html, object routeData = null) { var routes = html.RouteCollection; @@ -59,38 +35,62 @@ orderby name select route; } - public static string GetName(this Route route) + public static string GetIcon(this Route route) { if (route == null || route.Defaults == null) { return null; } - var routeName = route.Defaults["routeName"]; + var routeIcon = route.Defaults["routeIcon"]; - if (routeName == null) + if (routeIcon == null) { return null; } - return routeName.ToString(); + return routeIcon.ToString(); } - public static string GetIcon(this Route route) + public static string GetName(this Route route) { if (route == null || route.Defaults == null) { return null; } - var routeIcon = route.Defaults["routeIcon"]; + var routeName = route.Defaults["routeName"]; - if (routeIcon == null) + if (routeName == null) { return null; } - return routeIcon.ToString(); + return routeName.ToString(); + } + + public static MvcHtmlString Gravatar(this HtmlHelper html, string email, string name, int size = 72) + { + var fallBack = WebConfigurationManager.AppSettings["GravatarFallBack"]; + if (string.IsNullOrEmpty(fallBack)) + { + fallBack = "mm"; + } + + var imgUrl = string.Format( + "https://secure.gravatar.com/avatar/{0}.png?s={1}&d={2}&r=g", + HashString(email), + size, + fallBack); + + return new MvcHtmlString(string.Format("\"\"", size, html.AttributeEncode(name), html.AttributeEncode(imgUrl))); + } + + public static MvcHtmlString Markdown(this HtmlHelper html, string markdown) + { + var markdownParser = new Markdown(true); + + return new MvcHtmlString(markdownParser.Transform(markdown)); } private static string HashString(string value) diff --git a/WebGitNet/Models/CreateRepoRequest.cs b/WebGitNet/Models/CreateRepoRequest.cs index 7005ef6..7230e46 100644 --- a/WebGitNet/Models/CreateRepoRequest.cs +++ b/WebGitNet/Models/CreateRepoRequest.cs @@ -11,9 +11,9 @@ namespace WebGitNet.Models public class CreateRepoRequest { + public string Description { get; set; } + [Required] public string RepoName { get; set; } - - public string Description { get; set; } } } diff --git a/WebGitNet/Models/GraphEntry.cs b/WebGitNet/Models/GraphEntry.cs index 3bf655d..abd86ab 100644 --- a/WebGitNet/Models/GraphEntry.cs +++ b/WebGitNet/Models/GraphEntry.cs @@ -7,19 +7,18 @@ namespace WebGitNet.Models { - using System; using System.Collections.Generic; public class GraphEntry { - public LogEntry LogEntry { get; set; } + public List IncomingNodes { get; set; } - public List Refs { get; set; } + public LogEntry LogEntry { get; set; } public NodeInfo Node { get; set; } public List ParentNodes { get; set; } - public List IncomingNodes { get; set; } + public List Refs { get; set; } } } \ No newline at end of file diff --git a/WebGitNet/Models/ImpactWeek.cs b/WebGitNet/Models/ImpactWeek.cs index 0596024..540aabe 100644 --- a/WebGitNet/Models/ImpactWeek.cs +++ b/WebGitNet/Models/ImpactWeek.cs @@ -12,8 +12,8 @@ namespace WebGitNet.Models public class ImpactWeek { - public DateTime Week { get; set; } - public List Impacts { get; set; } + + public DateTime Week { get; set; } } } diff --git a/WebGitNet/Models/NodeInfo.cs b/WebGitNet/Models/NodeInfo.cs index 0ed332f..5152064 100644 --- a/WebGitNet/Models/NodeInfo.cs +++ b/WebGitNet/Models/NodeInfo.cs @@ -7,17 +7,12 @@ namespace WebGitNet.Models { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - public class NodeInfo { - public string Hash { get; set; } - public int Color { get; set; } + public string Hash { get; set; } + public override string ToString() { return this.Hash + ":" + this.Color; diff --git a/WebGitNet/Models/PaginationInfo.cs b/WebGitNet/Models/PaginationInfo.cs index 1c6983e..6f9c46a 100644 --- a/WebGitNet/Models/PaginationInfo.cs +++ b/WebGitNet/Models/PaginationInfo.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace WebGitNet.Models +namespace WebGitNet.Models { public class PaginationInfo { + private readonly string actionName; + private readonly string controllerName; private readonly int page; private readonly int pageCount; - private readonly string controllerName; - private readonly string actionName; - private readonly object routeValues; private readonly string routeKey; + private readonly object routeValues; public PaginationInfo(int page, int pageCount, string controllerName, string actionName, object routeValues, string routeKey = "page") { @@ -23,14 +19,9 @@ public PaginationInfo(int page, int pageCount, string controllerName, string act this.routeKey = routeKey; } - public int Page - { - get { return this.page; } - } - - public int PageCount + public string ActionName { - get { return this.pageCount; } + get { return this.actionName; } } public string ControllerName @@ -38,19 +29,24 @@ public string ControllerName get { return this.controllerName; } } - public string ActionName + public int Page { - get { return this.actionName; } + get { return this.page; } } - public object RouteValues + public int PageCount { - get { return this.routeValues; } + get { return this.pageCount; } } public string RouteKey { get { return this.routeKey; } } + + public object RouteValues + { + get { return this.routeValues; } + } } } \ No newline at end of file diff --git a/WebGitNet/ResourceRazorViewEngine.cs b/WebGitNet/ResourceRazorViewEngine.cs index 268448c..6cd59f5 100644 --- a/WebGitNet/ResourceRazorViewEngine.cs +++ b/WebGitNet/ResourceRazorViewEngine.cs @@ -12,6 +12,14 @@ namespace WebGitNet public class ResourceRazorViewEngine : RazorViewEngine { + private static readonly string[] areaViewLocationFormats = new[] + { + "~/Views/Plugins/{0}/Areas/{{2}}/{{1}}/{{0}}.cshtml", + "~/Views/Plugins/{0}/Areas/{{2}}/{{1}}/{{0}}.vbhtml", + "~/Views/Plugins/{0}/Areas/{{2}}/Shared/{{0}}.cshtml", + "~/Views/Plugins/{0}/Areas/{{2}}/Shared/{{0}}.vbhtml", + }; + private static readonly string[] empty = new string[0]; private static readonly string[] viewLocationFormats = new[] @@ -22,14 +30,6 @@ public class ResourceRazorViewEngine : RazorViewEngine "~/Views/Plugins/{0}/Shared/{{0}}.vbhtml", }; - private static readonly string[] areaViewLocationFormats = new[] - { - "~/Views/Plugins/{0}/Areas/{{2}}/{{1}}/{{0}}.cshtml", - "~/Views/Plugins/{0}/Areas/{{2}}/{{1}}/{{0}}.vbhtml", - "~/Views/Plugins/{0}/Areas/{{2}}/Shared/{{0}}.cshtml", - "~/Views/Plugins/{0}/Areas/{{2}}/Shared/{{0}}.vbhtml", - }; - private readonly object syncRoot = new object(); public ResourceRazorViewEngine() @@ -37,6 +37,28 @@ public ResourceRazorViewEngine() ClearFormats(); } + public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) + { + lock (this.syncRoot) + { + this.SetFormats(controllerContext); + var result = base.FindPartialView(controllerContext, partialViewName, useCache); + this.ClearFormats(); + return result; + } + } + + public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) + { + lock (this.syncRoot) + { + this.SetFormats(controllerContext); + var result = base.FindView(controllerContext, viewName, masterName, useCache); + this.ClearFormats(); + return result; + } + } + private void ClearFormats() { this.ViewLocationFormats = @@ -61,27 +83,5 @@ private void SetFormats(ControllerContext controllerContext) this.AreaMasterLocationFormats = this.AreaPartialViewLocationFormats = areaViewLocationFormats.Select(f => string.Format(f, assemblyName)).ToArray(); } - - public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) - { - lock (this.syncRoot) - { - this.SetFormats(controllerContext); - var result = base.FindView(controllerContext, viewName, masterName, useCache); - this.ClearFormats(); - return result; - } - } - - public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) - { - lock (this.syncRoot) - { - this.SetFormats(controllerContext); - var result = base.FindPartialView(controllerContext, partialViewName, useCache); - this.ClearFormats(); - return result; - } - } } } \ No newline at end of file diff --git a/WebGitNet/ResourceVirtualPathProvider.cs b/WebGitNet/ResourceVirtualPathProvider.cs index 1d1e13a..c9f9bdf 100644 --- a/WebGitNet/ResourceVirtualPathProvider.cs +++ b/WebGitNet/ResourceVirtualPathProvider.cs @@ -1,15 +1,13 @@ namespace WebGitNet { using System; - using System.Collections.Generic; + using System.Collections; + using System.IO; using System.Linq; + using System.Reflection; using System.Web; - using System.Web.Mvc; - using System.Web.Hosting; using System.Web.Caching; - using System.Collections; - using System.Reflection; - using System.IO; + using System.Web.Hosting; public class ResourceVirtualPathProvider : VirtualPathProvider { @@ -21,21 +19,6 @@ public override bool FileExists(string virtualPath) return isPlugin || base.FileExists(virtualPath); } - public override VirtualFile GetFile(string virtualPath) - { - string resourceName; - var assembly = FindAssembly(virtualPath, out resourceName); - - if (assembly != null) - { - return new AssemblyResourceVirtualFile(virtualPath, assembly, resourceName); - } - else - { - return base.GetFile(virtualPath); - } - } - public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { var isPlugin = IsPluginPath(virtualPath); @@ -49,11 +32,19 @@ public override CacheDependency GetCacheDependency(string virtualPath, IEnumerab } } - private bool IsPluginPath(string virtualPath) + public override VirtualFile GetFile(string virtualPath) { string resourceName; var assembly = FindAssembly(virtualPath, out resourceName); - return assembly != null; + + if (assembly != null) + { + return new AssemblyResourceVirtualFile(virtualPath, assembly, resourceName); + } + else + { + return base.GetFile(virtualPath); + } } private Assembly FindAssembly(string virtualPath, out string resourceName) @@ -92,6 +83,13 @@ where a.GetName().Name == assemblyName } } + private bool IsPluginPath(string virtualPath) + { + string resourceName; + var assembly = FindAssembly(virtualPath, out resourceName); + return assembly != null; + } + private class AssemblyResourceVirtualFile : VirtualFile { private Assembly assembly; diff --git a/WebGitNet/Scripts/repo-impact.js b/WebGitNet/Scripts/repo-impact.js index d218d9c..271298f 100644 --- a/WebGitNet/Scripts/repo-impact.js +++ b/WebGitNet/Scripts/repo-impact.js @@ -18,7 +18,6 @@ function readValues(column, normalize) { } function buildGraph(div) { - var labels = readValues("Author"); var commits = readValues("Commits", function (a) { return +a; }); var insertions = readValues("Insertions", function (a) { return +a; }); diff --git a/WebGitNet/Views/Web.config b/WebGitNet/Views/Web.config index ea6b0f9..42e62fd 100644 --- a/WebGitNet/Views/Web.config +++ b/WebGitNet/Views/Web.config @@ -27,7 +27,7 @@ - + - + diff --git a/WebGitNet/WindsorControllerFactory.cs b/WebGitNet/WindsorControllerFactory.cs index dc37853..cf6cbf4 100644 --- a/WebGitNet/WindsorControllerFactory.cs +++ b/WebGitNet/WindsorControllerFactory.cs @@ -23,11 +23,6 @@ public WindsorControllerFactory(IKernel kernel) this.kernel = kernel; } - public void ReleaseController(IController controller) - { - kernel.ReleaseComponent(controller); - } - public IController CreateController(RequestContext requestContext, string controllerName) { controllerName = @@ -47,5 +42,10 @@ public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestC { return SessionStateBehavior.Default; } + + public void ReleaseController(IController controller) + { + kernel.ReleaseComponent(controller); + } } }