This commit is contained in:
Javier Suárez Ruiz 2019-03-10 12:44:57 +01:00
Родитель b1ddd3b4e1
Коммит e15f671dfd
14 изменённых файлов: 573 добавлений и 0 удалений

17
src/VS4Mac.GitHistory.sln Normal file
Просмотреть файл

@ -0,0 +1,17 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VS4Mac.GitHistory", "VS4Mac.GitHistory\VS4Mac.GitHistory.csproj", "{B239FA78-B711-4083-8C97-1A72ACBA5215}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B239FA78-B711-4083-8C97-1A72ACBA5215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B239FA78-B711-4083-8C97-1A72ACBA5215}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B239FA78-B711-4083-8C97-1A72ACBA5215}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B239FA78-B711-4083-8C97-1A72ACBA5215}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,35 @@
using MonoDevelop.Components.Commands;
using MonoDevelop.Ide;
using MonoDevelop.Projects;
using MonoDevelop.VersionControl;
using VS4Mac.GitHistory.Controllers;
using VS4Mac.GitHistory.Views;
namespace VS4Mac.GitHistory.Commands
{
public class OpenGitHistoryCommand : CommandHandler
{
protected override void Run()
{
var projectFile = IdeApp.ProjectOperations.CurrentSelectedItem as ProjectFile;
var gitHistoryView = new GitHistoryView();
var skiaSharpFiddleController = new GitHistoryController(gitHistoryView, projectFile);
IdeApp.Workbench.OpenDocument(gitHistoryView, true);
}
protected override void Update(CommandInfo info)
{
base.Update(info);
if (VersionControlService.IsGloballyDisabled)
{
info.Enabled = false;
return;
}
var projectFile = IdeApp.ProjectOperations.CurrentSelectedItem as ProjectFile;
info.Enabled = projectFile != null;
}
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace VS4Mac.GitHistory.Controllers.Base
{
public interface IController
{
}
}

Просмотреть файл

@ -0,0 +1,32 @@
using System;
using MonoDevelop.Projects;
using VS4Mac.GitHistory.Controllers.Base;
using VS4Mac.GitHistory.Helpers;
using VS4Mac.GitHistory.Views;
namespace VS4Mac.GitHistory.Controllers
{
public class GitHistoryController : IController
{
readonly IGitHistoryView _view;
readonly ProjectFile _projectFile;
public GitHistoryController(IGitHistoryView view, ProjectFile projectFile)
{
_view = view;
_projectFile = projectFile;
view.SetController(this);
}
public string GetUrl()
{
if (!InternetHelper.CheckInternetConnection())
throw new Exception("Can not access the Git history without a internet connection.");
if(_projectFile != null)
return UrlHelper.GetUrl(_projectFile);
return string.Empty;
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using CoreGraphics;
using Gtk;
using MonoDevelop.Components.Mac;
using WebKit;
namespace VS4Mac.GitHistory.Controls
{
public class ExtendedWebView : WKWebView
{
public ExtendedWebView() : base(new CGRect(), new WKWebViewConfiguration()) { }
public Widget GtkWidget => gtkWidget ?? (gtkWidget = GtkMacInterop.NSViewToGtkWidget(this));
Widget gtkWidget;
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using Foundation;
using WebKit;
namespace VS4Mac.GitHistory.Controls
{
public class GitHistoryWebView : ExtendedWebView, IWKNavigationDelegate
{
public GitHistoryWebView()
{
NavigationDelegate = new GitHistoryNavigationDelegate();
}
public WKNavigation InitialNavigation { get; set; }
public void OpenUrl(string uri)
{
var url = new NSUrl(uri);
var request = new NSUrlRequest(url);
InitialNavigation = LoadRequest(request);
}
}
public class GitHistoryNavigationDelegate : WKNavigationDelegate
{
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
var gitHistoryWebView = webView as GitHistoryWebView;
if (navigation == gitHistoryWebView.InitialNavigation)
{
gitHistoryWebView.InitialNavigation = null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using System.Net.NetworkInformation;
namespace VS4Mac.GitHistory.Helpers
{
public static class InternetHelper
{
public static bool CheckInternetConnection()
{
Ping ping = new Ping();
PingReply pingStatus = ping.Send("www.microsoft.com", 1000);
if (pingStatus.Status == IPStatus.Success)
{
return true;
}
return false;
}
}
}

Просмотреть файл

@ -0,0 +1,128 @@
using System;
using System.IO;
using System.Linq;
using MonoDevelop.Core;
using MonoDevelop.Projects;
using VS4Mac.GitHistory.Models;
namespace VS4Mac.GitHistory.Helpers
{
/// <summary>
/// Based on ShowInGitHub addin by Lluis Sanchez.
/// More information: https://github.com/slluis/ShowInGithub
/// </summary>
public static class UrlHelper
{
internal static string GetUrl(ProjectFile projectFile)
{
string result = string.Empty;
try
{
var gitDir = GetGitDir(projectFile.FilePath);
if (gitDir == null)
return null;
var rootDir = Path.GetDirectoryName(gitDir);
var gitModulesFile = Path.Combine(rootDir, ".gitmodules");
GitConfigSection submoduleSection = null;
if (File.Exists(gitModulesFile))
{
var modulesConfig = new GitConfigFile();
modulesConfig.LoadFile(gitModulesFile);
foreach (var section in modulesConfig.Sections)
{
//Checking if file is inside submodule folder
if (section.Type == "submodule" &&
section.GetValue("path") != null &&
Path.GetFullPath(projectFile.FilePath.FileName).StartsWith(Path.Combine(rootDir, Path.Combine(section.GetValue("path").Split('/'))), StringComparison.Ordinal))
{
gitDir = Path.Combine(gitDir, "modules", Path.Combine(section.GetValue("path").Split('/')));
submoduleSection = section;
break;
}
}
}
var file = new GitConfigFile();
file.LoadFile(Path.Combine(gitDir, "config"));
string head = File.ReadAllText(Path.Combine(gitDir, "HEAD")).Trim();
string branch;
string remote = null;
if (head.StartsWith("ref: refs/heads/", StringComparison.CurrentCulture))
{
branch = head.Substring(16);
var sec = file.Sections.FirstOrDefault(s => s.Type == "branch" && s.Name == branch);
if (sec != null)
remote = sec.GetValue("remote");
}
else
{
branch = head;
}
if (string.IsNullOrEmpty(remote))
remote = "origin";
var rref = file.Sections.FirstOrDefault(s => s.Type == "remote" && s.Name == remote);
if (rref == null)
return null;
var url = rref.GetValue("url");
if (url.EndsWith(".git", StringComparison.CurrentCulture))
url = url.Substring(0, url.Length - 4);
string host;
int k = url.IndexOfAny(new[] { ':', '@' });
if (k != -1 && url[k] == '@')
{
k++;
int i = url.IndexOf(':', k);
if (i != -1)
host = url.Substring(k, i - k);
else
return null;
}
else
{
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
host = uri.Host;
else
return null;
}
int j = url.IndexOf(host, StringComparison.CurrentCulture);
var repo = url.Substring(j + host.Length + 1);
var fileRootDir = submoduleSection == null ? rootDir : Path.Combine(rootDir, Path.Combine(submoduleSection.GetValue("path").Split('/')));
string subdir = projectFile.FilePath.ToRelative(fileRootDir);
subdir = subdir.Replace('\\', '/');
result = "https://" + host + "/" + repo + "/blob/" + branch + "/" + subdir;
result = result.Replace("github.com", "github.githistory.xyz");
}
catch(Exception ex)
{
LoggingService.LogError(ex.Message, ex);
}
return result;
}
internal static string GetGitDir(string subdir)
{
var r = Path.GetPathRoot(subdir);
while (!string.IsNullOrEmpty(subdir) && subdir != r)
{
var gd = Path.Combine(subdir, ".git");
if (Directory.Exists(gd))
return gd;
subdir = Path.GetDirectoryName(subdir);
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace VS4Mac.GitHistory.Models
{
public class GitConfigFile
{
List<GitConfigSection> sections = new List<GitConfigSection> ();
public IEnumerable<GitConfigSection> Sections {
get { return sections; }
}
public void LoadFile (string file)
{
LoadContent (File.ReadAllText (file));
}
public void LoadContent (string content)
{
var sr = new StringReader (content);
GitConfigSection section = null;
string line;
while ((line = sr.ReadLine ()) != null) {
if (line.StartsWith("[", StringComparison.CurrentCulture)) {
section = new GitConfigSection ();
int i = line.IndexOfAny (new [] { ' ', ']' }, 1);
if (i == -1)
continue;
section.Type = line.Substring (1, i - 1);
i = line.IndexOf ('"', i);
if (i != -1) {
int j = line.LastIndexOf ('"');
if (j != -1 && j > i)
section.Name = line.Substring (i + 1, j - i - 1);
}
sections.Add (section);
} else if (line.StartsWith("\t", StringComparison.CurrentCulture) && section != null) {
int i = line.IndexOf ('=');
string key = line.Substring (0, i).Trim ();
var value = line.Substring (i + 1).Trim ();
section.SetValue (key, value);
}
};
}
public void SaveFile (string file)
{
File.WriteAllText (file, GetContent ());
}
public string GetContent ()
{
using (StringWriter sw = new StringWriter ()) {
foreach (var s in sections) {
sw.Write ("[" + s.Type);
if (!string.IsNullOrEmpty (s.Name))
sw.Write (" \"" + s.Name + "\"");
sw.WriteLine ("]");
foreach (var k in s.Keys) {
sw.WriteLine ("\t" + k + " = " + s.GetValue (k));
}
}
return sw.ToString ();
}
}
public bool Modified => sections.Any(s => s.Modified);
}
public class GitConfigSection
{
public string Type { get; set; }
public string Name { get; set; }
internal bool Modified { get; set; }
List<Tuple<string,string>> properties = new List<Tuple<string,string>> ();
public IEnumerable<string> Keys {
get { return properties.Select (p => p.Item1); }
}
public string GetValue (string key)
{
return properties.Where (p => p.Item1 == key).Select (p => p.Item2).FirstOrDefault ();
}
public void SetValue (string key, string value)
{
var i = properties.FindIndex (t => t.Item1 == key);
if (i == -1) {
var p = new Tuple<string, string> (key, value);
properties.Add (p);
Modified = true;
} else {
if (properties [i].Item2 != value) {
properties [i] = new Tuple<string, string> (key, value);
Modified = true;
}
}
}
public void RemoveValue (string key)
{
var i = properties.FindIndex (t => t.Item1 == key);
if (i != -1) {
properties.RemoveAt (i);
Modified = true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using System;
using System.Runtime.InteropServices;
using Mono.Addins;
using Mono.Addins.Description;
[assembly: Addin(
"GitHistory",
Namespace = "MonoDevelop",
Version = "0.1"
)]
[assembly: AddinName("Git History")]
[assembly: AddinCategory("IDE extensions")]
[assembly: AddinDescription("Quickly browse the history of a file from any git repository directly from VS4Mac.")]
[assembly: AddinAuthor("Javier Suárez")]
[assembly: AddinUrl("https://github.com/jsuarezruiz/VS4Mac-GitHistory")]
[assembly: CLSCompliant(false)]
[assembly: ComVisible(false)]

Просмотреть файл

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<Addin
id = "MonoDevelop.AssetStudio">
<Header>
<Name>Git History Addin</Name>
<Description>Quickly browse the history of a file from any git repository directly from VS4Mac.</Description>
<Author>Javier Suárez Ruiz</Author>
<Copyright>Javier Suárez Ruiz</Copyright>
<Url>https://github.com/jsuarezruiz/VSMac-GitHistory</Url>
</Header>
<Extension path = "/MonoDevelop/Ide/Commands/VersionControl">
<Command id = "GitHistory.Commands.OpenGitHistory"
defaultHandler = "VS4Mac.GitHistory.Commands.OpenGitHistoryCommand"
_label = "Show Git History" />
</Extension>
<Extension path = "/MonoDevelop/Ide/ContextMenu/ProjectPad/VersionControl">
<SeparatorItem insertafter="MonoDevelop.VersionControl.Commands.SolutionStatus"/>
<Condition id="ItemType" value="IFileItem">
<CommandItem id = "GitHistory.Commands.OpenGitHistory" />
</Condition>
</Extension>
</Addin>

Просмотреть файл

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MonoDevelop.Addins" Version="0.4.7" />
</ItemGroup>
<ItemGroup>
<AddinReference Include="MonoDevelop.VersionControl" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,9 @@
using VS4Mac.GitHistory.Controllers.Base;
namespace VS4Mac.GitHistory.Views.Base
{
public interface IView
{
void SetController(IController controller);
}
}

Просмотреть файл

@ -0,0 +1,106 @@
using System;
using MonoDevelop.Core;
using MonoDevelop.Ide.Gui;
using VS4Mac.GitHistory.Controllers;
using VS4Mac.GitHistory.Controllers.Base;
using VS4Mac.GitHistory.Controls;
using VS4Mac.GitHistory.Views.Base;
using Xwt;
using Xwt.Drawing;
namespace VS4Mac.GitHistory.Views
{
public interface IGitHistoryView : IView
{
}
public class GitHistoryView : AbstractXwtViewContent, IGitHistoryView
{
VBox _mainBox;
GitHistoryWebView _gitHistoryWebView;
Widget _xwtGitHistoryWebView;
Label _errorLabel;
GitHistoryController _controller;
public GitHistoryView()
{
Init();
BuildGui();
}
public override Widget Widget => _mainBox;
public override bool IsViewOnly
{
get
{
return true;
}
}
public override bool IsFile
{
get
{
return false;
}
}
void Init()
{
_mainBox = new VBox();
_gitHistoryWebView = new GitHistoryWebView();
_errorLabel = new Label
{
Font = Font.SystemSansSerifFont.WithSize(18),
TextColor = Styles.SecondaryTextColor,
HorizontalPlacement = WidgetPlacement.Center,
Visible = false
};
}
void BuildGui()
{
ContentName = "Git History";
_xwtGitHistoryWebView = Toolkit.CurrentEngine.WrapWidget(_gitHistoryWebView);
_mainBox.PackStart(_xwtGitHistoryWebView, true);
_mainBox.PackStart(_errorLabel, true);
}
public void SetController(IController controller)
{
_controller = (GitHistoryController)controller;
LoadUrl();
}
void LoadUrl()
{
try
{
var url = _controller.GetUrl();
if (!string.IsNullOrEmpty(url))
_gitHistoryWebView.OpenUrl(url);
else
ShowError("Can not get the Git history of the file.");
}
catch(Exception ex)
{
LoggingService.LogError(ex.Message, ex);
ShowError(ex.Message);
}
}
void ShowError (string errorMessage)
{
_errorLabel.Text = errorMessage;
_errorLabel.Visible = true;
_xwtGitHistoryWebView.Visible = false;
}
}
}