├── .editorconfig ├── .gitattributes ├── .gitignore ├── GitViz.sln ├── GitViz.sln.DotSettings ├── Logic ├── Commit.cs ├── CommitEdge.cs ├── CommitGraph.cs ├── FsckParser.cs ├── GitCommandExecutor.cs ├── LogParser.cs ├── LogRetriever.cs ├── Logic.csproj ├── Properties │ ├── Annotations.cs │ └── AssemblyInfo.cs ├── Reference.cs ├── RepositoryWatcher.cs ├── Vertex.cs ├── ViewModel.cs └── packages.config ├── README.md ├── SuperHighTechAssets └── AnimatedGifTour.gif ├── Tests ├── FsckParserTests.cs ├── GitCommandExecutorTests.cs ├── JsonExtensions.cs ├── LogParserTests │ ├── ParseCommit.cs │ └── ParseCommits.cs ├── LogRetrieverTests.cs ├── Properties │ └── AssemblyInfo.cs ├── RepositoryWatcherTests.cs ├── SerializedObjectComparer.cs ├── TemporaryFolder.cs ├── TemporaryRepository.cs ├── Tests.csproj └── packages.config └── UI ├── App.config ├── App.xaml ├── App.xaml.cs ├── CommitGraphLayout.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── UI.csproj ├── VertexTemplateSelector.cs ├── packages.config ├── readify.ico └── readify.png /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Visual Studio Extension : http://visualstudiogallery.msdn.microsoft.com/c8bccfe2-650c-4b42-bc5c-845e21f96328 2 | ; See http://editorconfig.org/ for more informations 3 | 4 | ; Top-most EditorConfig file 5 | root = true 6 | 7 | [*.cs] 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | charset = utf-8 12 | insert_final_newline = true 13 | 14 | [*.xaml] 15 | indent_style = space 16 | indent_size = 4 17 | trim_trailing_whitespace = true 18 | charset = utf-8 19 | insert_final_newline = true 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /GitViz.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.20827.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logic", "Logic\Logic.csproj", "{BF9333A3-490D-4C93-9F41-11016C9B324C}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{E9951372-AC02-47B4-BDEF-9518A5442BD1}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UI", "UI\UI.csproj", "{090061EB-F7DB-4160-96D3-4B1A13F7AF7F}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {BF9333A3-490D-4C93-9F41-11016C9B324C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {BF9333A3-490D-4C93-9F41-11016C9B324C}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {BF9333A3-490D-4C93-9F41-11016C9B324C}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {BF9333A3-490D-4C93-9F41-11016C9B324C}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {E9951372-AC02-47B4-BDEF-9518A5442BD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {E9951372-AC02-47B4-BDEF-9518A5442BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {E9951372-AC02-47B4-BDEF-9518A5442BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {E9951372-AC02-47B4-BDEF-9518A5442BD1}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {090061EB-F7DB-4160-96D3-4B1A13F7AF7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {090061EB-F7DB-4160-96D3-4B1A13F7AF7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {090061EB-F7DB-4160-96D3-4B1A13F7AF7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {090061EB-F7DB-4160-96D3-4B1A13F7AF7F}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /GitViz.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /Logic/Commit.cs: -------------------------------------------------------------------------------- 1 | namespace GitViz.Logic 2 | { 3 | public class Commit 4 | { 5 | public string Hash { get; set; } 6 | public string[] ParentHashes { get; set; } 7 | public string[] Refs { get; set; } 8 | public long CommitDate { get; set; } 9 | 10 | public string ShortHash 11 | { 12 | get { return Hash.Substring(0, 7); } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Logic/CommitEdge.cs: -------------------------------------------------------------------------------- 1 | using QuickGraph; 2 | 3 | namespace GitViz.Logic 4 | { 5 | public class CommitEdge : Edge 6 | { 7 | public CommitEdge(Vertex source, Vertex target) 8 | : base(source, target) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Logic/CommitGraph.cs: -------------------------------------------------------------------------------- 1 | using QuickGraph; 2 | 3 | namespace GitViz.Logic 4 | { 5 | public class CommitGraph : BidirectionalGraph 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Logic/FsckParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace GitViz.Logic 5 | { 6 | public class FsckParser 7 | { 8 | public IEnumerable ParseUnreachableCommitsIds(StreamReader fsck) 9 | { 10 | const string prefix = "unreachable commit "; 11 | while (!fsck.EndOfStream) 12 | { 13 | var line = fsck.ReadLine(); 14 | if (line == null || !line.StartsWith(prefix)) continue; 15 | yield return line.Substring(prefix.Length); 16 | } 17 | fsck.Close(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Logic/GitCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace GitViz.Logic 6 | { 7 | public class GitCommandExecutor 8 | { 9 | readonly string _repositoryPath; 10 | 11 | public GitCommandExecutor(string repositoryPath) 12 | { 13 | _repositoryPath = repositoryPath; 14 | } 15 | 16 | public string Execute(string command) 17 | { 18 | var process = CreateProcess(command); 19 | process.WaitForExit(10000); 20 | 21 | if (process.ExitCode == 0) 22 | return process.StandardOutput.ReadToEnd(); 23 | 24 | var errorText = process.StandardError.ReadToEnd(); 25 | throw new ApplicationException(errorText); 26 | } 27 | 28 | public StreamReader ExecuteAndGetOutputStream(string command) 29 | { 30 | var process = CreateProcess(command); 31 | return process.StandardOutput; 32 | } 33 | 34 | Process CreateProcess(string command) 35 | { 36 | var startInfo = new ProcessStartInfo 37 | { 38 | FileName = "git.exe", 39 | Arguments = command, 40 | WorkingDirectory = _repositoryPath, 41 | CreateNoWindow = true, 42 | UseShellExecute = false, 43 | RedirectStandardOutput = true, 44 | RedirectStandardError = true 45 | }; 46 | var process = Process.Start(startInfo); 47 | return process; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Logic/LogParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace GitViz.Logic 8 | { 9 | public class LogParser 10 | { 11 | public readonly string ExpectedOutputFormat = "%ct %H %P %d"; 12 | 13 | public IEnumerable ParseCommits(StreamReader gitLogOutput) 14 | { 15 | while (gitLogOutput.BaseStream != null && !gitLogOutput.EndOfStream) 16 | { 17 | var line = gitLogOutput.ReadLine(); 18 | if (string.IsNullOrWhiteSpace(line)) continue; 19 | yield return ParseCommit(line); 20 | } 21 | gitLogOutput.Close(); 22 | } 23 | 24 | static readonly Regex ParseCommitRegex = new Regex(@"^(?\d*) (?\w{7,40})(?( \w{7,40})+)?([ ]+\((?.*?)\))?"); 25 | 26 | internal static Commit ParseCommit(string logOutputLine) 27 | { 28 | var match = ParseCommitRegex.Match(logOutputLine.Trim()); 29 | 30 | var commitDate = long.Parse(match.Groups["commitDate"].Value); 31 | 32 | var parentHashes = match.Groups["parentHashes"].Success 33 | ? match.Groups["parentHashes"].Value.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries) 34 | : null; 35 | 36 | var refs = match.Groups["refs"].Success 37 | ? match.Groups["refs"] 38 | .Value 39 | .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 40 | .Select(r => r.Trim()) 41 | .ToArray() 42 | : null; 43 | 44 | return new Commit 45 | { 46 | Hash = match.Groups["hash"].Value, 47 | CommitDate = commitDate, 48 | ParentHashes = parentHashes, 49 | Refs = refs 50 | }; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Logic/LogRetriever.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace GitViz.Logic 6 | { 7 | public class LogRetriever 8 | { 9 | readonly GitCommandExecutor _executor; 10 | readonly LogParser _logParser; 11 | readonly FsckParser _fsckParser; 12 | 13 | public LogRetriever( 14 | GitCommandExecutor executor, 15 | LogParser logParser = null, 16 | FsckParser fsckParser = null) 17 | { 18 | _executor = executor; 19 | _logParser = logParser ?? new LogParser(); 20 | _fsckParser = fsckParser ?? new FsckParser(); 21 | } 22 | 23 | public IEnumerable GetRecentCommits(int maxResults = 20) 24 | { 25 | var command = string.Format("log --all --format=\"{0}\" -{1}", _logParser.ExpectedOutputFormat, maxResults); 26 | var log = _executor.ExecuteAndGetOutputStream(command); 27 | return _logParser.ParseCommits(log); 28 | } 29 | 30 | public IEnumerable GetSpecificCommits(IEnumerable hashes) 31 | { 32 | var command = string.Format("log --format=\"{0}\" --no-walk {1}", _logParser.ExpectedOutputFormat, string.Join(" ", hashes)); 33 | var log = _executor.ExecuteAndGetOutputStream(command); 34 | return _logParser.ParseCommits(log); 35 | } 36 | 37 | public IEnumerable GetRecentUnreachableCommitHashes(int maxResults = 20) 38 | { 39 | var fsck = _executor.ExecuteAndGetOutputStream("fsck --no-reflog --unreachable"); 40 | var hashes = _fsckParser.ParseUnreachableCommitsIds(fsck); 41 | foreach (var hash in hashes.Take(maxResults)) 42 | { 43 | yield return hash; 44 | } 45 | fsck.Close(); 46 | } 47 | 48 | public string GetActiveReferenceName() 49 | { 50 | var branches = _executor.Execute("branch"); 51 | return branches 52 | .Split(Environment.NewLine.ToCharArray()) 53 | .Where(l => l.StartsWith("* ")) 54 | .Select(l => l.Substring(2)) 55 | .SingleOrDefault(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Logic/Logic.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BF9333A3-490D-4C93-9F41-11016C9B324C} 8 | Library 9 | Properties 10 | GitViz.Logic 11 | GitViz.Logic 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\GraphSharp.1.1.0.0\lib\net40\GraphSharp.dll 35 | 36 | 37 | ..\packages\GraphSharp.1.1.0.0\lib\net40\GraphSharp.Controls.dll 38 | 39 | 40 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.dll 41 | 42 | 43 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.Data.dll 44 | 45 | 46 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.Graphviz.dll 47 | 48 | 49 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.Serialization.dll 50 | 51 | 52 | 53 | 54 | ..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll 55 | 56 | 57 | ..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll 58 | 59 | 60 | ..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll 61 | 62 | 63 | ..\packages\Rx-PlatformServices.2.1.30214.0\lib\Net45\System.Reactive.PlatformServices.dll 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | ..\packages\WPFExtensions.1.0.0\lib\WPFExtensions.dll 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 100 | -------------------------------------------------------------------------------- /Logic/Properties/Annotations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | #pragma warning disable 1591 4 | // ReSharper disable UnusedMember.Global 5 | // ReSharper disable MemberCanBePrivate.Global 6 | // ReSharper disable UnusedAutoPropertyAccessor.Global 7 | // ReSharper disable IntroduceOptionalParameters.Global 8 | // ReSharper disable MemberCanBeProtected.Global 9 | // ReSharper disable InconsistentNaming 10 | 11 | namespace GitViz.Logic.Annotations 12 | { 13 | /// 14 | /// Indicates that the value of the marked element could be null sometimes, 15 | /// so the check for null is necessary before its usage 16 | /// 17 | /// 18 | /// [CanBeNull] public object Test() { return null; } 19 | /// public void UseTest() { 20 | /// var p = Test(); 21 | /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' 22 | /// } 23 | /// 24 | [AttributeUsage( 25 | AttributeTargets.Method | AttributeTargets.Parameter | 26 | AttributeTargets.Property | AttributeTargets.Delegate | 27 | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 28 | public sealed class CanBeNullAttribute : Attribute { } 29 | 30 | /// 31 | /// Indicates that the value of the marked element could never be null 32 | /// 33 | /// 34 | /// [NotNull] public object Foo() { 35 | /// return null; // Warning: Possible 'null' assignment 36 | /// } 37 | /// 38 | [AttributeUsage( 39 | AttributeTargets.Method | AttributeTargets.Parameter | 40 | AttributeTargets.Property | AttributeTargets.Delegate | 41 | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 42 | public sealed class NotNullAttribute : Attribute { } 43 | 44 | /// 45 | /// Indicates that the marked method builds string by format pattern and (optional) arguments. 46 | /// Parameter, which contains format string, should be given in constructor. The format string 47 | /// should be in -like form 48 | /// 49 | /// 50 | /// [StringFormatMethod("message")] 51 | /// public void ShowError(string message, params object[] args) { /* do something */ } 52 | /// public void Foo() { 53 | /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string 54 | /// } 55 | /// 56 | [AttributeUsage( 57 | AttributeTargets.Constructor | AttributeTargets.Method, 58 | AllowMultiple = false, Inherited = true)] 59 | public sealed class StringFormatMethodAttribute : Attribute 60 | { 61 | /// 62 | /// Specifies which parameter of an annotated method should be treated as format-string 63 | /// 64 | public StringFormatMethodAttribute(string formatParameterName) 65 | { 66 | FormatParameterName = formatParameterName; 67 | } 68 | 69 | public string FormatParameterName { get; private set; } 70 | } 71 | 72 | /// 73 | /// Indicates that the function argument should be string literal and match one 74 | /// of the parameters of the caller function. For example, ReSharper annotates 75 | /// the parameter of 76 | /// 77 | /// 78 | /// public void Foo(string param) { 79 | /// if (param == null) 80 | /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol 81 | /// } 82 | /// 83 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] 84 | public sealed class InvokerParameterNameAttribute : Attribute { } 85 | 86 | /// 87 | /// Indicates that the method is contained in a type that implements 88 | /// interface 89 | /// and this method is used to notify that some property value changed 90 | /// 91 | /// 92 | /// The method should be non-static and conform to one of the supported signatures: 93 | /// 94 | /// NotifyChanged(string) 95 | /// NotifyChanged(params string[]) 96 | /// NotifyChanged{T}(Expression{Func{T}}) 97 | /// NotifyChanged{T,U}(Expression{Func{T,U}}) 98 | /// SetProperty{T}(ref T, T, string) 99 | /// 100 | /// 101 | /// 102 | /// public class Foo : INotifyPropertyChanged { 103 | /// public event PropertyChangedEventHandler PropertyChanged; 104 | /// [NotifyPropertyChangedInvocator] 105 | /// protected virtual void NotifyChanged(string propertyName) { ... } 106 | /// 107 | /// private string _name; 108 | /// public string Name { 109 | /// get { return _name; } 110 | /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } 111 | /// } 112 | /// } 113 | /// 114 | /// Examples of generated notifications: 115 | /// 116 | /// NotifyChanged("Property") 117 | /// NotifyChanged(() => Property) 118 | /// NotifyChanged((VM x) => x.Property) 119 | /// SetProperty(ref myField, value, "Property") 120 | /// 121 | /// 122 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 123 | public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute 124 | { 125 | public NotifyPropertyChangedInvocatorAttribute() { } 126 | public NotifyPropertyChangedInvocatorAttribute(string parameterName) 127 | { 128 | ParameterName = parameterName; 129 | } 130 | 131 | public string ParameterName { get; private set; } 132 | } 133 | 134 | /// 135 | /// Describes dependency between method input and output 136 | /// 137 | /// 138 | ///

Function Definition Table syntax:

139 | /// 140 | /// FDT ::= FDTRow [;FDTRow]* 141 | /// FDTRow ::= Input => Output | Output <= Input 142 | /// Input ::= ParameterName: Value [, Input]* 143 | /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} 144 | /// Value ::= true | false | null | notnull | canbenull 145 | /// 146 | /// If method has single input parameter, it's name could be omitted.
147 | /// Using halt (or void/nothing, which is the same) 148 | /// for method output means that the methos doesn't return normally.
149 | /// canbenull annotation is only applicable for output parameters.
150 | /// You can use multiple [ContractAnnotation] for each FDT row, 151 | /// or use single attribute with rows separated by semicolon.
152 | ///
153 | /// 154 | /// 155 | /// [ContractAnnotation("=> halt")] 156 | /// public void TerminationMethod() 157 | /// 158 | /// 159 | /// [ContractAnnotation("halt <= condition: false")] 160 | /// public void Assert(bool condition, string text) // regular assertion method 161 | /// 162 | /// 163 | /// [ContractAnnotation("s:null => true")] 164 | /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() 165 | /// 166 | /// 167 | /// // A method that returns null if the parameter is null, and not null if the parameter is not null 168 | /// [ContractAnnotation("null => null; notnull => notnull")] 169 | /// public object Transform(object data) 170 | /// 171 | /// 172 | /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] 173 | /// public bool TryParse(string s, out Person result) 174 | /// 175 | /// 176 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] 177 | public sealed class ContractAnnotationAttribute : Attribute 178 | { 179 | public ContractAnnotationAttribute([NotNull] string contract) 180 | : this(contract, false) { } 181 | 182 | public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) 183 | { 184 | Contract = contract; 185 | ForceFullStates = forceFullStates; 186 | } 187 | 188 | public string Contract { get; private set; } 189 | public bool ForceFullStates { get; private set; } 190 | } 191 | 192 | /// 193 | /// Indicates that marked element should be localized or not 194 | /// 195 | /// 196 | /// [LocalizationRequiredAttribute(true)] 197 | /// public class Foo { 198 | /// private string str = "my string"; // Warning: Localizable string 199 | /// } 200 | /// 201 | [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] 202 | public sealed class LocalizationRequiredAttribute : Attribute 203 | { 204 | public LocalizationRequiredAttribute() : this(true) { } 205 | public LocalizationRequiredAttribute(bool required) 206 | { 207 | Required = required; 208 | } 209 | 210 | public bool Required { get; private set; } 211 | } 212 | 213 | /// 214 | /// Indicates that the value of the marked type (or its derivatives) 215 | /// cannot be compared using '==' or '!=' operators and Equals() 216 | /// should be used instead. However, using '==' or '!=' for comparison 217 | /// with null is always permitted. 218 | /// 219 | /// 220 | /// [CannotApplyEqualityOperator] 221 | /// class NoEquality { } 222 | /// class UsesNoEquality { 223 | /// public void Test() { 224 | /// var ca1 = new NoEquality(); 225 | /// var ca2 = new NoEquality(); 226 | /// if (ca1 != null) { // OK 227 | /// bool condition = ca1 == ca2; // Warning 228 | /// } 229 | /// } 230 | /// } 231 | /// 232 | [AttributeUsage( 233 | AttributeTargets.Interface | AttributeTargets.Class | 234 | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] 235 | public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } 236 | 237 | /// 238 | /// When applied to a target attribute, specifies a requirement for any type marked 239 | /// with the target attribute to implement or inherit specific type or types. 240 | /// 241 | /// 242 | /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement 243 | /// public class ComponentAttribute : Attribute { } 244 | /// [Component] // ComponentAttribute requires implementing IComponent interface 245 | /// public class MyComponent : IComponent { } 246 | /// 247 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 248 | [BaseTypeRequired(typeof(Attribute))] 249 | public sealed class BaseTypeRequiredAttribute : Attribute 250 | { 251 | public BaseTypeRequiredAttribute([NotNull] Type baseType) 252 | { 253 | BaseType = baseType; 254 | } 255 | 256 | [NotNull] public Type BaseType { get; private set; } 257 | } 258 | 259 | /// 260 | /// Indicates that the marked symbol is used implicitly 261 | /// (e.g. via reflection, in external library), so this symbol 262 | /// will not be marked as unused (as well as by other usage inspections) 263 | /// 264 | [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] 265 | public sealed class UsedImplicitlyAttribute : Attribute 266 | { 267 | public UsedImplicitlyAttribute() 268 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 269 | 270 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 271 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 272 | 273 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 274 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 275 | 276 | public UsedImplicitlyAttribute( 277 | ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 278 | { 279 | UseKindFlags = useKindFlags; 280 | TargetFlags = targetFlags; 281 | } 282 | 283 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 284 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 285 | } 286 | 287 | /// 288 | /// Should be used on attributes and causes ReSharper 289 | /// to not mark symbols marked with such attributes as unused 290 | /// (as well as by other usage inspections) 291 | /// 292 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 293 | public sealed class MeansImplicitUseAttribute : Attribute 294 | { 295 | public MeansImplicitUseAttribute() 296 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 297 | 298 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 299 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 300 | 301 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 302 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 303 | 304 | public MeansImplicitUseAttribute( 305 | ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) 306 | { 307 | UseKindFlags = useKindFlags; 308 | TargetFlags = targetFlags; 309 | } 310 | 311 | [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } 312 | [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } 313 | } 314 | 315 | [Flags] 316 | public enum ImplicitUseKindFlags 317 | { 318 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 319 | /// Only entity marked with attribute considered used 320 | Access = 1, 321 | /// Indicates implicit assignment to a member 322 | Assign = 2, 323 | /// 324 | /// Indicates implicit instantiation of a type with fixed constructor signature. 325 | /// That means any unused constructor parameters won't be reported as such. 326 | /// 327 | InstantiatedWithFixedConstructorSignature = 4, 328 | /// Indicates implicit instantiation of a type 329 | InstantiatedNoFixedConstructorSignature = 8, 330 | } 331 | 332 | /// 333 | /// Specify what is considered used implicitly 334 | /// when marked with 335 | /// or 336 | /// 337 | [Flags] 338 | public enum ImplicitUseTargetFlags 339 | { 340 | Default = Itself, 341 | Itself = 1, 342 | /// Members of entity marked with attribute are considered used 343 | Members = 2, 344 | /// Entity marked with attribute and all its members considered used 345 | WithMembers = Itself | Members 346 | } 347 | 348 | /// 349 | /// This attribute is intended to mark publicly available API 350 | /// which should not be removed and so is treated as used 351 | /// 352 | [MeansImplicitUse] 353 | public sealed class PublicAPIAttribute : Attribute 354 | { 355 | public PublicAPIAttribute() { } 356 | public PublicAPIAttribute([NotNull] string comment) 357 | { 358 | Comment = comment; 359 | } 360 | 361 | [NotNull] public string Comment { get; private set; } 362 | } 363 | 364 | /// 365 | /// Tells code analysis engine if the parameter is completely handled 366 | /// when the invoked method is on stack. If the parameter is a delegate, 367 | /// indicates that delegate is executed while the method is executed. 368 | /// If the parameter is an enumerable, indicates that it is enumerated 369 | /// while the method is executed 370 | /// 371 | [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] 372 | public sealed class InstantHandleAttribute : Attribute { } 373 | 374 | /// 375 | /// Indicates that a method does not make any observable state changes. 376 | /// The same as System.Diagnostics.Contracts.PureAttribute 377 | /// 378 | /// 379 | /// [Pure] private int Multiply(int x, int y) { return x * y; } 380 | /// public void Foo() { 381 | /// const int a = 2, b = 2; 382 | /// Multiply(a, b); // Waring: Return value of pure method is not used 383 | /// } 384 | /// 385 | [AttributeUsage(AttributeTargets.Method, Inherited = true)] 386 | public sealed class PureAttribute : Attribute { } 387 | 388 | /// 389 | /// Indicates that a parameter is a path to a file or a folder 390 | /// within a web project. Path can be relative or absolute, 391 | /// starting from web root (~) 392 | /// 393 | [AttributeUsage(AttributeTargets.Parameter)] 394 | public class PathReferenceAttribute : Attribute 395 | { 396 | public PathReferenceAttribute() { } 397 | public PathReferenceAttribute([PathReference] string basePath) 398 | { 399 | BasePath = basePath; 400 | } 401 | 402 | [NotNull] public string BasePath { get; private set; } 403 | } 404 | 405 | // ASP.NET MVC attributes 406 | 407 | /// 408 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 409 | /// is an MVC action. If applied to a method, the MVC action name is calculated 410 | /// implicitly from the context. Use this attribute for custom wrappers similar to 411 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) 412 | /// 413 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 414 | public sealed class AspMvcActionAttribute : Attribute 415 | { 416 | public AspMvcActionAttribute() { } 417 | public AspMvcActionAttribute([NotNull] string anonymousProperty) 418 | { 419 | AnonymousProperty = anonymousProperty; 420 | } 421 | 422 | [NotNull] public string AnonymousProperty { get; private set; } 423 | } 424 | 425 | /// 426 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. 427 | /// Use this attribute for custom wrappers similar to 428 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) 429 | /// 430 | [AttributeUsage(AttributeTargets.Parameter)] 431 | public sealed class AspMvcAreaAttribute : PathReferenceAttribute 432 | { 433 | public AspMvcAreaAttribute() { } 434 | public AspMvcAreaAttribute([NotNull] string anonymousProperty) 435 | { 436 | AnonymousProperty = anonymousProperty; 437 | } 438 | 439 | [NotNull] public string AnonymousProperty { get; private set; } 440 | } 441 | 442 | /// 443 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that 444 | /// the parameter is an MVC controller. If applied to a method, 445 | /// the MVC controller name is calculated implicitly from the context. 446 | /// Use this attribute for custom wrappers similar to 447 | /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String) 448 | /// 449 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 450 | public sealed class AspMvcControllerAttribute : Attribute 451 | { 452 | public AspMvcControllerAttribute() { } 453 | public AspMvcControllerAttribute([NotNull] string anonymousProperty) 454 | { 455 | AnonymousProperty = anonymousProperty; 456 | } 457 | 458 | [NotNull] public string AnonymousProperty { get; private set; } 459 | } 460 | 461 | /// 462 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. 463 | /// Use this attribute for custom wrappers similar to 464 | /// System.Web.Mvc.Controller.View(String, String) 465 | /// 466 | [AttributeUsage(AttributeTargets.Parameter)] 467 | public sealed class AspMvcMasterAttribute : Attribute { } 468 | 469 | /// 470 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. 471 | /// Use this attribute for custom wrappers similar to 472 | /// System.Web.Mvc.Controller.View(String, Object) 473 | /// 474 | [AttributeUsage(AttributeTargets.Parameter)] 475 | public sealed class AspMvcModelTypeAttribute : Attribute { } 476 | 477 | /// 478 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that 479 | /// the parameter is an MVC partial view. If applied to a method, 480 | /// the MVC partial view name is calculated implicitly from the context. 481 | /// Use this attribute for custom wrappers similar to 482 | /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String) 483 | /// 484 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 485 | public sealed class AspMvcPartialViewAttribute : PathReferenceAttribute { } 486 | 487 | /// 488 | /// ASP.NET MVC attribute. Allows disabling all inspections 489 | /// for MVC views within a class or a method. 490 | /// 491 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 492 | public sealed class AspMvcSupressViewErrorAttribute : Attribute { } 493 | 494 | /// 495 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. 496 | /// Use this attribute for custom wrappers similar to 497 | /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String) 498 | /// 499 | [AttributeUsage(AttributeTargets.Parameter)] 500 | public sealed class AspMvcDisplayTemplateAttribute : Attribute { } 501 | 502 | /// 503 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. 504 | /// Use this attribute for custom wrappers similar to 505 | /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String) 506 | /// 507 | [AttributeUsage(AttributeTargets.Parameter)] 508 | public sealed class AspMvcEditorTemplateAttribute : Attribute { } 509 | 510 | /// 511 | /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. 512 | /// Use this attribute for custom wrappers similar to 513 | /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String) 514 | /// 515 | [AttributeUsage(AttributeTargets.Parameter)] 516 | public sealed class AspMvcTemplateAttribute : Attribute { } 517 | 518 | /// 519 | /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter 520 | /// is an MVC view. If applied to a method, the MVC view name is calculated implicitly 521 | /// from the context. Use this attribute for custom wrappers similar to 522 | /// System.Web.Mvc.Controller.View(Object) 523 | /// 524 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] 525 | public sealed class AspMvcViewAttribute : PathReferenceAttribute { } 526 | 527 | /// 528 | /// ASP.NET MVC attribute. When applied to a parameter of an attribute, 529 | /// indicates that this parameter is an MVC action name 530 | /// 531 | /// 532 | /// [ActionName("Foo")] 533 | /// public ActionResult Login(string returnUrl) { 534 | /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK 535 | /// return RedirectToAction("Bar"); // Error: Cannot resolve action 536 | /// } 537 | /// 538 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] 539 | public sealed class AspMvcActionSelectorAttribute : Attribute { } 540 | 541 | [AttributeUsage( 542 | AttributeTargets.Parameter | AttributeTargets.Property | 543 | AttributeTargets.Field, Inherited = true)] 544 | public sealed class HtmlElementAttributesAttribute : Attribute 545 | { 546 | public HtmlElementAttributesAttribute() { } 547 | public HtmlElementAttributesAttribute([NotNull] string name) 548 | { 549 | Name = name; 550 | } 551 | 552 | [NotNull] public string Name { get; private set; } 553 | } 554 | 555 | [AttributeUsage( 556 | AttributeTargets.Parameter | AttributeTargets.Field | 557 | AttributeTargets.Property, Inherited = true)] 558 | public sealed class HtmlAttributeValueAttribute : Attribute 559 | { 560 | public HtmlAttributeValueAttribute([NotNull] string name) 561 | { 562 | Name = name; 563 | } 564 | 565 | [NotNull] public string Name { get; private set; } 566 | } 567 | 568 | // Razor attributes 569 | 570 | /// 571 | /// Razor attribute. Indicates that a parameter or a method is a Razor section. 572 | /// Use this attribute for custom wrappers similar to 573 | /// System.Web.WebPages.WebPageBase.RenderSection(String) 574 | /// 575 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Inherited = true)] 576 | public sealed class RazorSectionAttribute : Attribute { } 577 | } -------------------------------------------------------------------------------- /Logic/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Logic")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Logic")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1e3cd963-bb28-4e8b-988f-8f5be0725f77")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | 38 | [assembly: InternalsVisibleTo("GitViz.Tests")] 39 | -------------------------------------------------------------------------------- /Logic/Reference.cs: -------------------------------------------------------------------------------- 1 | namespace GitViz.Logic 2 | { 3 | public class Reference 4 | { 5 | public static string HEAD = "HEAD"; 6 | 7 | public string Name { get; set; } 8 | public bool IsActive { get; set; } 9 | public bool IsHead { get { return Name == HEAD; } } 10 | public bool IsTag { get { return Name.StartsWith("tag:"); } } 11 | public bool IsRemote { get { return Name.Contains("/");} } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Logic/RepositoryWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reactive.Linq; 6 | using System.Threading; 7 | 8 | namespace GitViz.Logic 9 | { 10 | public class RepositoryWatcher : IDisposable 11 | { 12 | public const int DampeningIntervalInMilliseconds = 1000; 13 | 14 | readonly FileSystemWatcher _watcher; 15 | 16 | public event EventHandler ChangeDetected; 17 | 18 | protected virtual void OnChangeDetected() 19 | { 20 | var handler = ChangeDetected; 21 | if (handler != null) handler(this, EventArgs.Empty); 22 | } 23 | 24 | public RepositoryWatcher(string path, Boolean isBare) 25 | { 26 | String gitFolder; 27 | if (isBare) { 28 | gitFolder = path; 29 | } 30 | else 31 | { 32 | gitFolder = Path.Combine(path, @".git"); 33 | } 34 | 35 | _watcher = new FileSystemWatcher(gitFolder) 36 | { 37 | EnableRaisingEvents = true, 38 | IncludeSubdirectories = true 39 | }; 40 | 41 | //using ReactiveExtensions here 42 | //first - we setup an observable for all the various FSW events 43 | var repoChanges = Observable.FromEventPattern(handler => 44 | { 45 | _watcher.Changed += handler; 46 | _watcher.Created += handler; 47 | _watcher.Deleted += handler; 48 | }, 49 | handler => 50 | { 51 | _watcher.Changed -= handler; 52 | _watcher.Created -= handler; 53 | _watcher.Deleted -= handler; 54 | }); 55 | 56 | //next - we wait until our dampening interval has expired without any new FSW events being raised and 57 | //at that point trigger the OnChangeDetected method 58 | repoChanges 59 | .Throttle(TimeSpan.FromMilliseconds(DampeningIntervalInMilliseconds)) 60 | .Subscribe(h => OnChangeDetected()); 61 | } 62 | 63 | public void Dispose() 64 | { 65 | _watcher?.Dispose(); 66 | } 67 | 68 | ~RepositoryWatcher() 69 | { 70 | Dispose(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Logic/Vertex.cs: -------------------------------------------------------------------------------- 1 | namespace GitViz.Logic 2 | { 3 | public class Vertex 4 | { 5 | readonly object _value; 6 | 7 | public Vertex(object value) 8 | { 9 | _value = value; 10 | } 11 | 12 | public Commit Commit 13 | { 14 | get { return _value as Commit; } 15 | } 16 | 17 | public Reference Reference 18 | { 19 | get { return _value as Reference; } 20 | } 21 | 22 | public bool Orphan { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Logic/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using GitViz.Logic.Annotations; 7 | using System; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace GitViz.Logic 11 | { 12 | public class ViewModel : INotifyPropertyChanged 13 | { 14 | string _repositoryPath = ""; 15 | CommitGraph _graph = new CommitGraph(); 16 | RepositoryWatcher _watcher; 17 | 18 | readonly LogParser _parser = new LogParser(); 19 | 20 | public string WindowTitle 21 | { 22 | get 23 | { 24 | return "Readify GitViz (Alpha)" 25 | + (string.IsNullOrWhiteSpace(_repositoryPath) 26 | ? string.Empty 27 | : " - " + Path.GetFileName(_repositoryPath)); 28 | } 29 | } 30 | 31 | public bool IsNewRepository { get; set; } 32 | public string RepositoryPath 33 | { 34 | get { return _repositoryPath; } 35 | set 36 | { 37 | _repositoryPath = value.TrimEnd(); 38 | if (IsValidGitRepository(_repositoryPath)) 39 | { 40 | OnPropertyChanged("WindowTitle"); 41 | var commandExecutor = new GitCommandExecutor(_repositoryPath); 42 | var logRetriever = new LogRetriever(commandExecutor, _parser); 43 | 44 | RefreshGraph(logRetriever); 45 | 46 | IsNewRepository = true; 47 | 48 | _watcher = new RepositoryWatcher(_repositoryPath, IsBareGitRepository(_repositoryPath)); 49 | _watcher.ChangeDetected += (sender, args) => RefreshGraph(logRetriever); 50 | } 51 | else 52 | { 53 | _graph = new CommitGraph(); 54 | OnPropertyChanged("Graph"); 55 | if (_watcher != null) 56 | { 57 | _watcher.Dispose(); 58 | _watcher = null; 59 | } 60 | } 61 | } 62 | } 63 | 64 | void RefreshGraph(LogRetriever logRetriever) 65 | { 66 | var commits = logRetriever.GetRecentCommits().ToArray(); 67 | var activeRefName = logRetriever.GetActiveReferenceName(); 68 | 69 | var reachableCommitHashes = commits.Select(c => c.Hash).ToArray(); 70 | var unreachableHashes = logRetriever.GetRecentUnreachableCommitHashes(); 71 | var unreachableCommits = logRetriever 72 | .GetSpecificCommits(unreachableHashes) 73 | .Where(c => !reachableCommitHashes.Contains(c.Hash)) 74 | .ToArray(); 75 | 76 | _graph = GenerateGraphFromCommits(commits, activeRefName, unreachableCommits); 77 | OnPropertyChanged("Graph"); 78 | } 79 | 80 | CommitGraph GenerateGraphFromCommits(IEnumerable commits, string activeRefName, IEnumerable unreachableCommits) 81 | { 82 | commits = commits.ToList(); 83 | 84 | var graph = new CommitGraph(); 85 | 86 | var commitVertices = commits.Select(c => new Vertex(c)) 87 | .Union(unreachableCommits.Select(c => new Vertex(c) { Orphan = true })) 88 | .ToList(); 89 | 90 | // Add all the vertices 91 | var headVertex = new Vertex(new Reference 92 | { 93 | Name = Reference.HEAD, 94 | }); 95 | 96 | foreach (var commitVertex in commitVertices) 97 | { 98 | graph.AddVertex(commitVertex); 99 | 100 | if (commitVertex.Commit.Refs == null) continue; 101 | var isHeadHere = false; 102 | var isHeadSet = false; 103 | foreach (var refName in commitVertex.Commit.Refs) 104 | { 105 | if (refName == Reference.HEAD) 106 | { 107 | isHeadHere = true; 108 | graph.AddVertex(headVertex); 109 | continue; 110 | } 111 | var refVertex = new Vertex(new Reference 112 | { 113 | Name = refName, 114 | IsActive = refName == activeRefName 115 | }); 116 | graph.AddVertex(refVertex); 117 | graph.AddEdge(new CommitEdge(refVertex, commitVertex)); 118 | if (!refVertex.Reference.IsActive) continue; 119 | isHeadSet = true; 120 | graph.AddEdge(new CommitEdge(headVertex, refVertex)); 121 | } 122 | if (isHeadHere && !isHeadSet) 123 | graph.AddEdge(new CommitEdge(headVertex, commitVertex)); 124 | } 125 | 126 | // Add all the edges 127 | foreach (var commitVertex in commitVertices.Where(c => c.Commit.ParentHashes != null)) 128 | { 129 | foreach (var parentHash in commitVertex.Commit.ParentHashes) 130 | { 131 | var parentVertex = commitVertices.SingleOrDefault(c => c.Commit.Hash == parentHash); 132 | if (parentVertex != null) graph.AddEdge(new CommitEdge(commitVertex, parentVertex)); 133 | } 134 | } 135 | 136 | return graph; 137 | } 138 | 139 | public CommitGraph Graph 140 | { 141 | get { return _graph; } 142 | } 143 | 144 | static bool IsValidGitRepository(string path) 145 | { 146 | return !string.IsNullOrEmpty(path) 147 | && Directory.Exists(path) 148 | && (Directory.Exists(Path.Combine(path, ".git")) || 149 | IsBareGitRepository(path)); 150 | } 151 | 152 | static Boolean IsBareGitRepository(String path) 153 | { 154 | String configFileForBareRepository = Path.Combine(path, "config"); 155 | return File.Exists(configFileForBareRepository) && 156 | Regex.IsMatch(File.ReadAllText(configFileForBareRepository), @"bare\s*=\s*true", RegexOptions.IgnoreCase); 157 | } 158 | 159 | public event PropertyChangedEventHandler PropertyChanged; 160 | 161 | [NotifyPropertyChangedInvocator] 162 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 163 | { 164 | var handler = PropertyChanged; 165 | if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Logic/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GitViz 2 | ====== 3 | 4 | > Psst, hey... are you looking for a .NET Core 3 version? Check out the `netcore` branch. Contributions and PRs on that branch are very welcome! 5 | 6 | ### Type commands. See their effect in real time. Perfect rendering for presentations. 7 | 8 | Among the many things we do at [Readify](http://readify.net) to help people build better software, we teach people about Git. 9 | 10 | For newbies, the concept of commit graphs, references, branches and merges can be a bit hard to visualize. 11 | 12 | Instead of trying to do all this on slides, or in sync on a whiteboard, we wanted to be able to run real commands and see a presentation quality diagram in real-time. This tool does that. 13 | 14 | We also intelligently render unreachable commits to help people understand hard resets and rebases. 15 | 16 | ![Screenshot animation](https://raw.github.com/Readify/GitViz/master/SuperHighTechAssets/AnimatedGifTour.gif) 17 | 18 | ### Where do I get it? 19 | 20 | Pre-built binaries are available at https://github.com/Readify/GitViz/releases 21 | 22 | ### 23 | 24 | ### Release Quality 25 | 26 | __Alpha.__ This entire project so far consists of one Readify guy sitting down the back of a training course and tapping away for a few hours, no more. 27 | 28 | ### What is 'presentation quality'? 29 | 30 | Large. Clear. Projector-optimized and tested colors. Just enough information to be useful. 31 | 32 | ### What this tool does NOT do 33 | 34 | This is not a day-to-day visualizing tool for big, active repositories. It's optimised for repositories with less than 20 commits, for very specific training scenarios in live presentations. 35 | 36 | ### How it works 37 | 38 | Shells out to `git.exe`, and then renders it with the excellent [GraphSharp](http://graphsharp.codeplex.com). 39 | 40 | ### FAQ 41 | #### How can I remove dangling commits? 42 | GitViz shows dangling commits to make it easier to visualise rebases and other history rewrites. To remove those commits to clean things up run (this will delete *all* unreachable objects in the repo, be careful): 43 | 44 | ``` 45 | git reflog expire --expire-unreachable=now --all 46 | git gc --prune=now 47 | ``` 48 | -------------------------------------------------------------------------------- /SuperHighTechAssets/AnimatedGifTour.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Readify/GitViz/2d8b8197af216286b83e87cf629560c79fac2edd/SuperHighTechAssets/AnimatedGifTour.gif -------------------------------------------------------------------------------- /Tests/FsckParserTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using GitViz.Logic; 6 | using NUnit.Framework; 7 | 8 | namespace GitViz.Tests 9 | { 10 | [TestFixture] 11 | public class FsckParserTests 12 | { 13 | static IEnumerable Test(string input) 14 | { 15 | using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(input))) 16 | using (var reader = new StreamReader(stream)) 17 | { 18 | return new FsckParser().ParseUnreachableCommitsIds(reader).ToArray(); 19 | } 20 | } 21 | 22 | [Test] 23 | public void ShouldParseSingleDanglingCommitHash() 24 | { 25 | var results = Test(@"unreachable commit 1928ce6245d999026679aa08353d0aa04b8bf4ca"); 26 | var expected = new[] { "1928ce6245d999026679aa08353d0aa04b8bf4ca" }; 27 | CollectionAssert.AreEqual(expected, results); 28 | } 29 | 30 | [Test] 31 | public void ShouldParseMultipleDanglingCommitHashes() 32 | { 33 | var results = Test(@"unreachable commit 1928ce6245d999026679aa08353d0aa04b8bf4ca 34 | unreachable commit 5d021fe8cc958bc5ec945ec5066b394b7ffcfde8"); 35 | 36 | var expected = new[] 37 | { 38 | "1928ce6245d999026679aa08353d0aa04b8bf4ca", 39 | "5d021fe8cc958bc5ec945ec5066b394b7ffcfde8" 40 | }; 41 | 42 | CollectionAssert.AreEqual(expected, results); 43 | } 44 | 45 | [Test] 46 | public void ShouldIgnoreOtherLines() 47 | { 48 | var results = Test(@"foo 49 | unreachable commit 1928ce6245d999026679aa08353d0aa04b8bf4ca 50 | dangling tree 5d021fe8cc958bc5ec945ec5066b394b7ffcfde8 51 | blah"); 52 | var expected = new[] { "1928ce6245d999026679aa08353d0aa04b8bf4ca" }; 53 | CollectionAssert.AreEqual(expected, results); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/GitCommandExecutorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using GitViz.Logic; 4 | using NUnit.Framework; 5 | 6 | namespace GitViz.Tests 7 | { 8 | [TestFixture] 9 | public class GitCommandExecutorTests 10 | { 11 | [Test] 12 | public void ShouldGitInit() 13 | { 14 | using (var repo = new TemporaryFolder()) 15 | { 16 | var executor = new GitCommandExecutor(repo.Path); 17 | executor.Execute("init"); 18 | 19 | var expectedGitFolderPath = Path.Combine(repo.Path, ".git"); 20 | Assert.IsTrue(Directory.Exists(expectedGitFolderPath)); 21 | } 22 | } 23 | 24 | [Test] 25 | public void ShouldThrowExceptionForFatalError() 26 | { 27 | using (var repo = new TemporaryFolder()) 28 | { 29 | var executor = new GitCommandExecutor(repo.Path); 30 | Assert.Throws(() => executor.Execute("bad command")); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/JsonExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Newtonsoft.Json; 3 | 4 | namespace GitViz.Tests 5 | { 6 | static class JsonExtensions 7 | { 8 | internal static string ToJson(this object value) 9 | { 10 | using (var stringWriter = new StringWriter()) 11 | { 12 | var jsonSerializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore }; 13 | jsonSerializer.Serialize(stringWriter, value); 14 | return stringWriter.ToString().Replace("\"", ""); 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/LogParserTests/ParseCommit.cs: -------------------------------------------------------------------------------- 1 | using GitViz.Logic; 2 | using NUnit.Framework; 3 | 4 | namespace GitViz.Tests.LogParserTests 5 | { 6 | [TestFixture] 7 | public class ParseCommit 8 | { 9 | [TestCase( 10 | "1383697102 4be5ef1", 11 | Description = "Initial commit", 12 | Result = "{Hash:4be5ef1,CommitDate:1383697102,ShortHash:4be5ef1}")] 13 | [TestCase( 14 | "1383697102 4be5ef1 (HEAD, master)", 15 | Description = "Initial commit with head and master", 16 | Result = "{Hash:4be5ef1,Refs:[HEAD,master],CommitDate:1383697102,ShortHash:4be5ef1}")] 17 | [TestCase( 18 | "1383697102 4be5ef1 (HEAD, origin/master, origin/HEAD, master)", 19 | Description = "Initial commit with head and remote master", 20 | Result = "{Hash:4be5ef1,Refs:[HEAD,origin/master,origin/HEAD,master],CommitDate:1383697102,ShortHash:4be5ef1}")] 21 | [TestCase( 22 | "1383697102 4e4224c 4be5ef1", 23 | Description = "Commit with one parent", 24 | Result = "{Hash:4e4224c,ParentHashes:[4be5ef1],CommitDate:1383697102,ShortHash:4e4224c}")] 25 | [TestCase( 26 | "1383697102 d472fda 3c27924 5411a9f", 27 | Description = "Commit with two parents", 28 | Result = "{Hash:d472fda,ParentHashes:[3c27924,5411a9f],CommitDate:1383697102,ShortHash:d472fda}")] 29 | [TestCase( 30 | "1383697102 d472fda 3c27924 5411a9f 6789abc", 31 | Description = "Commit with three parents", 32 | Result = "{Hash:d472fda,ParentHashes:[3c27924,5411a9f,6789abc],CommitDate:1383697102,ShortHash:d472fda}")] 33 | public string Test(string logLine) 34 | { 35 | var commit = LogParser.ParseCommit(logLine); 36 | return commit.ToJson(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/LogParserTests/ParseCommits.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using GitViz.Logic; 6 | using NUnit.Framework; 7 | 8 | namespace GitViz.Tests.LogParserTests 9 | { 10 | [TestFixture] 11 | public class ParseCommits 12 | { 13 | static IEnumerable Test(string input) 14 | { 15 | using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(input))) 16 | using (var reader = new StreamReader(stream)) 17 | { 18 | return new LogParser().ParseCommits(reader).ToArray(); 19 | } 20 | } 21 | 22 | [Test] 23 | public void ShouldParseSingleCommitHash() 24 | { 25 | var results = Test(@"1383697102 4be5ef1"); 26 | 27 | var expected = new[] 28 | { 29 | "{Hash:4be5ef1,CommitDate:1383697102,ShortHash:4be5ef1}" 30 | }; 31 | 32 | CollectionAssert.AreEqual( 33 | expected, 34 | results.Select(c => c.ToJson()).ToArray()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/LogRetrieverTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using GitViz.Logic; 3 | using NUnit.Framework; 4 | 5 | namespace GitViz.Tests 6 | { 7 | [TestFixture] 8 | public class LogRetrieverTests 9 | { 10 | [Test] 11 | public void ShouldReturnSingleRecentCommit() 12 | { 13 | using (var tempFolder = new TemporaryFolder()) 14 | { 15 | var tempRepository = new TemporaryRepository(tempFolder); 16 | tempRepository.RunCommand("init"); 17 | tempRepository.TouchFileAndCommit(); 18 | 19 | var executor = new GitCommandExecutor(tempFolder.Path); 20 | var log = new LogRetriever(executor).GetRecentCommits().ToArray(); 21 | 22 | Assert.AreEqual(1, log.Length); 23 | } 24 | } 25 | 26 | [Test] 27 | public void ShouldReturnSingleRecentCommitWithHashButNoParents() 28 | { 29 | using (var tempFolder = new TemporaryFolder()) 30 | { 31 | var tempRepository = new TemporaryRepository(tempFolder); 32 | tempRepository.RunCommand("init"); 33 | tempRepository.TouchFileAndCommit(); 34 | 35 | var executor = new GitCommandExecutor(tempFolder.Path); 36 | var log = new LogRetriever(executor).GetRecentCommits().ToArray(); 37 | 38 | Assert.AreEqual(1, log.Length); 39 | 40 | var commit = log.Single(); 41 | Assert.IsNotNullOrEmpty(commit.Hash); 42 | Assert.AreEqual(40, commit.Hash.Length); 43 | Assert.IsNull(commit.ParentHashes); 44 | } 45 | } 46 | 47 | [Test] 48 | public void ShouldReturnSingleRecentCommitWithLocalRefs() 49 | { 50 | using (var tempFolder = new TemporaryFolder()) 51 | { 52 | var tempRepository = new TemporaryRepository(tempFolder); 53 | tempRepository.RunCommand("init"); 54 | tempRepository.TouchFileAndCommit(); 55 | 56 | var executor = new GitCommandExecutor(tempFolder.Path); 57 | var log = new LogRetriever(executor).GetRecentCommits().ToArray(); 58 | 59 | var commit = log.Single(); 60 | CollectionAssert.AreEqual(new[] { "HEAD", "master" }, commit.Refs); 61 | } 62 | } 63 | 64 | [Test] 65 | public void ShouldReturnTwoRecentCommits() 66 | { 67 | using (var tempFolder = new TemporaryFolder()) 68 | { 69 | var tempRepository = new TemporaryRepository(tempFolder); 70 | tempRepository.RunCommand("init"); 71 | tempRepository.TouchFileAndCommit(); 72 | tempRepository.TouchFileAndCommit(); 73 | 74 | var executor = new GitCommandExecutor(tempFolder.Path); 75 | var log = new LogRetriever(executor).GetRecentCommits().ToArray(); 76 | 77 | Assert.AreEqual(2, log.Length); 78 | 79 | var commit = log.ElementAt(0); 80 | Assert.IsNotNullOrEmpty(commit.Hash); 81 | Assert.AreEqual(40, commit.Hash.Length); 82 | CollectionAssert.AreEqual(new[] { log.ElementAt(1).Hash }, commit.ParentHashes); 83 | 84 | commit = log.ElementAt(1); 85 | Assert.IsNotNullOrEmpty(commit.Hash); 86 | Assert.AreEqual(40, commit.Hash.Length); 87 | Assert.IsNull(commit.ParentHashes); 88 | } 89 | } 90 | 91 | [Test] 92 | public void ShouldReturnMostRecentCommitFirst() 93 | { 94 | using (var tempFolder = new TemporaryFolder()) 95 | { 96 | var tempRepository = new TemporaryRepository(tempFolder); 97 | tempRepository.RunCommand("init"); 98 | tempRepository.TouchFileAndCommit(); 99 | tempRepository.TouchFileAndCommit(); 100 | 101 | var executor = new GitCommandExecutor(tempFolder.Path); 102 | var log = new LogRetriever(executor).GetRecentCommits().ToArray(); 103 | 104 | var firstCommitReturned = log.ElementAt(0); 105 | var secondCommitReturned = log.ElementAt(1); 106 | 107 | CollectionAssert.AreEqual(new[] { secondCommitReturned.Hash }, firstCommitReturned.ParentHashes); 108 | } 109 | } 110 | 111 | [Test] 112 | public void ShouldLimitNumberOfRecentCommitsRetrieved() 113 | { 114 | using (var tempFolder = new TemporaryFolder()) 115 | { 116 | var tempRepository = new TemporaryRepository(tempFolder); 117 | tempRepository.RunCommand("init"); 118 | for (var i = 0; i < 20; i ++) 119 | tempRepository.TouchFileAndCommit(); 120 | 121 | var executor = new GitCommandExecutor(tempFolder.Path); 122 | var log = new LogRetriever(executor).GetRecentCommits(10).ToArray(); 123 | 124 | Assert.AreEqual(10, log.Length); 125 | } 126 | } 127 | 128 | [Test] 129 | public void ShouldReturnUnreachableCommitHashesAfterRewind() 130 | { 131 | using (var tempFolder = new TemporaryFolder()) 132 | { 133 | var tempRepository = new TemporaryRepository(tempFolder); 134 | var executor = new GitCommandExecutor(tempFolder.Path); 135 | var log = new LogRetriever(executor); 136 | 137 | tempRepository.RunCommand("init"); 138 | tempRepository.TouchFileAndCommit(); 139 | var firstCommitHash = log.GetRecentCommits(1).Single().Hash; 140 | tempRepository.TouchFileAndCommit(); 141 | var secondCommitHash = log.GetRecentCommits(1).Single().Hash; 142 | tempRepository.RunCommand("reset --hard " + firstCommitHash); 143 | 144 | var orphanedHashes = log.GetRecentUnreachableCommitHashes(); 145 | CollectionAssert.AreEqual(new[] { secondCommitHash }, orphanedHashes); 146 | } 147 | } 148 | 149 | [Test] 150 | public void ShouldReturnMultipleUnreachableCommitHashesAfterRewind() 151 | { 152 | using (var tempFolder = new TemporaryFolder()) 153 | { 154 | var tempRepository = new TemporaryRepository(tempFolder); 155 | var executor = new GitCommandExecutor(tempFolder.Path); 156 | var log = new LogRetriever(executor); 157 | 158 | tempRepository.RunCommand("init"); 159 | tempRepository.TouchFileAndCommit(); 160 | var firstCommitHash = log.GetRecentCommits(1).Single().Hash; 161 | 162 | for (var i = 0; i < 10; i++) 163 | tempRepository.TouchFileAndCommit(); 164 | 165 | tempRepository.RunCommand("reset --hard " + firstCommitHash); 166 | 167 | var orphanedHashes = log.GetRecentUnreachableCommitHashes(100); 168 | Assert.AreEqual(10, orphanedHashes.Count()); 169 | } 170 | } 171 | 172 | [Test] 173 | public void ShouldLimitNumberOfUnreachableCommitHashesRetrieved() 174 | { 175 | using (var tempFolder = new TemporaryFolder()) 176 | { 177 | var tempRepository = new TemporaryRepository(tempFolder); 178 | var executor = new GitCommandExecutor(tempFolder.Path); 179 | var log = new LogRetriever(executor); 180 | 181 | tempRepository.RunCommand("init"); 182 | tempRepository.TouchFileAndCommit(); 183 | var firstCommitHash = log.GetRecentCommits(1).Single().Hash; 184 | 185 | for (var i = 0; i < 20; i ++) 186 | tempRepository.TouchFileAndCommit(); 187 | 188 | tempRepository.RunCommand("reset --hard " + firstCommitHash); 189 | 190 | var orphanedHashes = log.GetRecentUnreachableCommitHashes(10); 191 | Assert.AreEqual(10, orphanedHashes.Count()); 192 | } 193 | } 194 | 195 | [Test] 196 | public void ShouldReturnSameDataForSpecificCommitsAsWhenRetrievedViaRecentCommits() 197 | { 198 | using (var tempFolder = new TemporaryFolder()) 199 | { 200 | var tempRepository = new TemporaryRepository(tempFolder); 201 | tempRepository.RunCommand("init"); 202 | tempRepository.TouchFileAndCommit(); 203 | tempRepository.TouchFileAndCommit(); 204 | 205 | var executor = new GitCommandExecutor(tempFolder.Path); 206 | var logRetriever = new LogRetriever(executor); 207 | 208 | var recentCommits = logRetriever.GetRecentCommits().ToArray(); 209 | var recentCommitHashes = recentCommits.Select(c => c.Hash); 210 | 211 | var specificCommits = logRetriever.GetSpecificCommits(recentCommitHashes); 212 | 213 | CollectionAssert.AreEqual(recentCommits, specificCommits, new SerializedObjectComparer()); 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("74e9f26d-6c11-462d-8160-ab98e899361e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Tests/RepositoryWatcherTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using GitViz.Logic; 4 | using NUnit.Framework; 5 | 6 | namespace GitViz.Tests 7 | { 8 | [TestFixture] 9 | public class RepositoryWatcherTests 10 | { 11 | [Test] 12 | public void ShouldDetectChangeWhenCreatingNewBranchButNotSwitching() 13 | { 14 | Test( 15 | repo => { }, 16 | repo => repo.RunCommand("branch foo")); 17 | } 18 | 19 | [Test] 20 | public void ShouldDetectChangeWhenCreatingNewTag() 21 | { 22 | Test( 23 | repo => { }, 24 | repo => repo.RunCommand("tag foo")); 25 | } 26 | 27 | [Test] 28 | public void ShouldDetectChangeWhenCheckingOutADifferentHead() 29 | { 30 | Test( 31 | repo => repo.RunCommand("checkout -b foo"), 32 | repo => repo.RunCommand("checkout master")); 33 | } 34 | 35 | internal void Test(Action preSteps, Action triggerSteps) 36 | { 37 | using (var tempFolder = new TemporaryFolder()) 38 | { 39 | var tempRepository = new TemporaryRepository(tempFolder); 40 | 41 | tempRepository.RunCommand("init"); 42 | tempRepository.TouchFileAndCommit(); 43 | 44 | preSteps(tempRepository); 45 | 46 | // Let everything stablize 47 | Thread.Sleep(RepositoryWatcher.DampeningIntervalInMilliseconds * 2); 48 | 49 | var triggered = false; 50 | var watcher = new RepositoryWatcher(tempFolder.Path, false); 51 | watcher.ChangeDetected += (sender, args) => { triggered = true; }; 52 | 53 | triggerSteps(tempRepository); 54 | 55 | Thread.Sleep(RepositoryWatcher.DampeningIntervalInMilliseconds * 2); 56 | 57 | Assert.IsTrue(triggered); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/SerializedObjectComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace GitViz.Tests 4 | { 5 | public class SerializedObjectComparer : IComparer 6 | { 7 | public int Compare(object x, object y) 8 | { 9 | return string.CompareOrdinal(x.ToJson(), y.ToJson()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/TemporaryFolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | 5 | namespace GitViz.Tests 6 | { 7 | class TemporaryFolder : IDisposable 8 | { 9 | readonly string _path; 10 | 11 | public TemporaryFolder() 12 | { 13 | _path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "test" + DateTimeOffset.UtcNow.Ticks); 14 | Directory.CreateDirectory(_path); 15 | } 16 | 17 | public string Path 18 | { 19 | get { return _path; } 20 | } 21 | 22 | public void Dispose() 23 | { 24 | var triesRemaining = 25; 25 | while (triesRemaining > 0) 26 | { 27 | try 28 | { 29 | Directory.Delete(_path, true); 30 | return; 31 | } 32 | catch 33 | { 34 | triesRemaining --; 35 | Thread.SpinWait(100); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Tests/TemporaryRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using GitViz.Logic; 5 | 6 | namespace GitViz.Tests 7 | { 8 | class TemporaryRepository 9 | { 10 | readonly string _folderPath; 11 | readonly GitCommandExecutor _executor; 12 | 13 | public TemporaryRepository(TemporaryFolder folder) 14 | { 15 | _folderPath = folder.Path; 16 | _executor = new GitCommandExecutor(folder.Path); 17 | } 18 | 19 | public string RunCommand(string command) 20 | { 21 | return _executor.Execute(command); 22 | } 23 | 24 | public void TouchFileAndCommit() 25 | { 26 | var filePath = Path.Combine(_folderPath, "abc.txt"); 27 | File.WriteAllText(filePath, DateTimeOffset.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture)); 28 | 29 | RunCommand("add -A"); 30 | RunCommand("commit -m \"commit\""); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E9951372-AC02-47B4-BDEF-9518A5442BD1} 8 | Library 9 | Properties 10 | GitViz.Tests 11 | GitViz.Tests 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Newtonsoft.Json.5.0.8\lib\net45\Newtonsoft.Json.dll 35 | 36 | 37 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {bf9333a3-490d-4c93-9f41-11016c9b324c} 63 | Logic 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | -------------------------------------------------------------------------------- /Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /UI/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UI/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UI/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace UI 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | private void Application_Startup(object sender, StartupEventArgs e) 17 | { 18 | var wnd = new MainWindow(); 19 | wnd.Show(); 20 | if (e.Args.Length == 1) 21 | wnd.TxtRepositoryPath.Text = e.Args[0]; 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /UI/CommitGraphLayout.cs: -------------------------------------------------------------------------------- 1 | using GitViz.Logic; 2 | using GraphSharp.Controls; 3 | 4 | namespace UI 5 | { 6 | public class CommitGraphLayout : GraphLayout 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UI/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Local Repo Path: 67 | 68 | 69 | 70 | 71 | 72 | 73 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /UI/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Interop; 4 | using GitViz.Logic; 5 | 6 | namespace UI 7 | { 8 | public partial class MainWindow 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | WindowPlacement(); 14 | } 15 | 16 | private void WindowPlacement() 17 | { 18 | Top = SystemParameters.WorkArea.Top; 19 | Left = SystemParameters.WorkArea.Left; 20 | } 21 | 22 | private void BtnOpenRepository_OnClick(object sender, RoutedEventArgs e) 23 | { 24 | var dialog = new System.Windows.Forms.FolderBrowserDialog 25 | { 26 | SelectedPath = string.IsNullOrWhiteSpace(TxtRepositoryPath.Text) 27 | ? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) 28 | : TxtRepositoryPath.Text 29 | }; 30 | var result = dialog.ShowDialog(); 31 | if (result == System.Windows.Forms.DialogResult.OK) 32 | { 33 | TxtRepositoryPath.Text = dialog.SelectedPath; 34 | } 35 | } 36 | 37 | private void Graph_OnSizeChanged(object sender, SizeChangedEventArgs e) 38 | { 39 | var viewModel = (ViewModel)DataContext; 40 | if (!viewModel.IsNewRepository) 41 | return; 42 | viewModel.IsNewRepository = false; 43 | ResizeWindowDependingOnGraphSize(); 44 | } 45 | 46 | public System.Windows.Forms.Screen GetCurrentScreen() 47 | { 48 | return System.Windows.Forms.Screen.FromHandle(new WindowInteropHelper(this).Handle); 49 | } 50 | 51 | private void ResizeWindowDependingOnGraphSize() 52 | { 53 | var currentScreen = GetCurrentScreen(); 54 | Width = Math.Min(Math.Max(graph.ActualWidth + 80, 400), currentScreen.Bounds.Width - Left + currentScreen.Bounds.Left); 55 | Height = Math.Min(Math.Max(graph.ActualHeight + grid.RowDefinitions[0].ActualHeight + 80, 200), currentScreen.Bounds.Height - Top + currentScreen.Bounds.Top); 56 | } 57 | 58 | private void BtnResizeWindow_OnClick(object sender, RoutedEventArgs e) 59 | { 60 | ResizeWindowDependingOnGraphSize(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /UI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("GitViz")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("Readify")] 14 | [assembly: AssemblyProduct("GitViz")] 15 | [assembly: AssemblyCopyright("Copyright © Readify 2013")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /UI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34003 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace UI.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UI.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /UI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /UI/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34003 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace UI.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /UI/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /UI/UI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {090061EB-F7DB-4160-96D3-4B1A13F7AF7F} 8 | WinExe 9 | Properties 10 | UI 11 | GitViz 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | readify.ico 38 | 39 | 40 | 41 | ..\packages\GraphSharp.1.1.0.0\lib\net40\GraphSharp.dll 42 | 43 | 44 | ..\packages\GraphSharp.1.1.0.0\lib\net40\GraphSharp.Controls.dll 45 | 46 | 47 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.dll 48 | 49 | 50 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.Data.dll 51 | 52 | 53 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.Graphviz.dll 54 | 55 | 56 | ..\packages\QuickGraph.3.6.61119.7\lib\net4\QuickGraph.Serialization.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 4.0 69 | 70 | 71 | 72 | 73 | 74 | ..\packages\WPFExtensions.1.0.0\lib\WPFExtensions.dll 75 | 76 | 77 | 78 | 79 | MSBuild:Compile 80 | Designer 81 | 82 | 83 | 84 | MSBuild:Compile 85 | Designer 86 | 87 | 88 | App.xaml 89 | Code 90 | 91 | 92 | 93 | MainWindow.xaml 94 | Code 95 | 96 | 97 | 98 | 99 | Code 100 | 101 | 102 | True 103 | True 104 | Resources.resx 105 | 106 | 107 | True 108 | Settings.settings 109 | True 110 | 111 | 112 | ResXFileCodeGenerator 113 | Resources.Designer.cs 114 | 115 | 116 | 117 | SettingsSingleFileGenerator 118 | Settings.Designer.cs 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {BF9333A3-490D-4C93-9F41-11016C9B324C} 128 | Logic 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 145 | -------------------------------------------------------------------------------- /UI/VertexTemplateSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using GitViz.Logic; 5 | 6 | namespace UI 7 | { 8 | public class VertexTemplateSelector : DataTemplateSelector 9 | { 10 | public DataTemplate CommitTemplate { get; set; } 11 | public DataTemplate ReferenceTemplate { get; set; } 12 | 13 | public override DataTemplate SelectTemplate(object item, DependencyObject container) 14 | { 15 | var vertex = item as Vertex; 16 | if (vertex == null) return base.SelectTemplate(item, container); 17 | 18 | if (vertex.Commit != null) return CommitTemplate; 19 | if (vertex.Reference != null) return ReferenceTemplate; 20 | 21 | throw new NotSupportedException(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UI/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UI/readify.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Readify/GitViz/2d8b8197af216286b83e87cf629560c79fac2edd/UI/readify.ico -------------------------------------------------------------------------------- /UI/readify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Readify/GitViz/2d8b8197af216286b83e87cf629560c79fac2edd/UI/readify.png --------------------------------------------------------------------------------