feature: supports issue tracker in commit message (#315)

This commit is contained in:
leo 2024-08-05 17:34:49 +08:00
parent fa1f4155da
commit f754b2c63a
No known key found for this signature in database
20 changed files with 563 additions and 85 deletions

View file

@ -88,9 +88,15 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _viewRevisionFileContent, value);
}
public CommitDetail(string repo)
public IssueTrackerRuleSetting IssueTrackerSetting
{
get => _issueTrackerSetting;
}
public CommitDetail(string repo, IssueTrackerRuleSetting issueTrackerSetting)
{
_repo = repo;
_issueTrackerSetting = issueTrackerSetting;
}
public void Cleanup()
@ -242,7 +248,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _issueTrackerSetting) };
window.Show();
ev.Handled = true;
};
@ -305,7 +311,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _issueTrackerSetting) };
window.Show();
ev.Handled = true;
};
@ -457,6 +463,7 @@ namespace SourceGit.ViewModels
};
private string _repo;
private IssueTrackerRuleSetting _issueTrackerSetting = null;
private int _activePageIndex = 0;
private Models.Commit _commit = null;
private string _fullMessage = string.Empty;

View file

@ -54,11 +54,11 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _detailContext, value);
}
public FileHistories(string repo, string file)
public FileHistories(string repo, string file, IssueTrackerRuleSetting issueTrackerSetting)
{
_repo = repo;
_file = file;
_detailContext = new CommitDetail(repo);
_detailContext = new CommitDetail(repo, issueTrackerSetting);
Task.Run(() =>
{

View file

@ -94,7 +94,7 @@ namespace SourceGit.ViewModels
}
else
{
var commitDetail = new CommitDetail(_repo.FullPath);
var commitDetail = new CommitDetail(_repo.FullPath, _repo.IssueTrackerSetting);
commitDetail.Commit = commit;
DetailContext = commitDetail;
}
@ -122,7 +122,7 @@ namespace SourceGit.ViewModels
}
else
{
var commitDetail = new CommitDetail(_repo.FullPath);
var commitDetail = new CommitDetail(_repo.FullPath, _repo.IssueTrackerSetting);
commitDetail.Commit = commit;
DetailContext = commitDetail;
}

View file

@ -114,7 +114,7 @@ namespace SourceGit.ViewModels
Current = current;
On = on;
IsLoading = true;
DetailContext = new CommitDetail(repoPath);
DetailContext = new CommitDetail(repoPath, repo.IssueTrackerSetting);
Task.Run(() =>
{

View file

@ -0,0 +1,164 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class IssueTrackerMatch
{
public int Start { get; set; } = 0;
public int Length { get; set; } = 0;
public string URL { get; set; } = "";
public bool Intersect(int start, int length)
{
if (start == Start)
return true;
if (start < Start)
return start + length > Start;
return start < Start + Length;
}
}
public class IssueTrackerRule : ObservableObject
{
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public string RegexString
{
get => _regexString;
set
{
if (SetProperty(ref _regexString, value))
{
try
{
_regex = null;
_regex = new Regex(_regexString, RegexOptions.Multiline);
}
catch
{
// Ignore errors.
}
}
OnPropertyChanged(nameof(IsRegexValid));
}
}
public bool IsRegexValid
{
get => _regex != null;
}
public string URLTemplate
{
get => _urlTemplate;
set => SetProperty(ref _urlTemplate, value);
}
public void Matches(List<IssueTrackerMatch> outs, string message)
{
if (_regex == null || string.IsNullOrEmpty(_urlTemplate))
return;
var matches = _regex.Matches(message);
for (int i = 0; i < matches.Count; i++)
{
var match = matches[i];
if (!match.Success)
continue;
var start = match.Index;
var len = match.Length;
var intersect = false;
foreach (var exist in outs)
{
if (exist.Intersect(start, len))
{
intersect = true;
break;
}
}
if (intersect)
continue;
var range = new IssueTrackerMatch();
range.Start = start;
range.Length = len;
range.URL = _urlTemplate;
for (int j = 1; j < match.Groups.Count; j++)
{
var group = match.Groups[j];
if (group.Success)
range.URL = range.URL.Replace($"${j}", group.Value);
}
outs.Add(range);
}
}
private string _name;
private string _regexString;
private string _urlTemplate;
private Regex _regex = null;
}
public class IssueTrackerRuleSetting
{
public AvaloniaList<IssueTrackerRule> Rules
{
get;
set;
} = new AvaloniaList<IssueTrackerRule>();
public IssueTrackerRule Add()
{
var rule = new IssueTrackerRule()
{
Name = "New Issue Tracker",
RegexString = "#(\\d+)",
URLTemplate = "https://xxx/$1",
};
Rules.Add(rule);
return rule;
}
public IssueTrackerRule AddGithub(string repoURL)
{
var rule = new IssueTrackerRule()
{
Name = "Github ISSUE",
RegexString = "#(\\d+)",
URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://github.com/username/repository/issues/$1" : $"{repoURL}/issues/$1",
};
Rules.Add(rule);
return rule;
}
public IssueTrackerRule AddJira()
{
var rule = new IssueTrackerRule()
{
Name = "Jira Tracker",
RegexString = "PROJ-(\\d+)",
URLTemplate = "https://jira.yourcompany.com/browse/PROJ-$1",
};
Rules.Add(rule);
return rule;
}
}
}

View file

@ -44,6 +44,11 @@ namespace SourceGit.ViewModels
get => _settings;
}
public IssueTrackerRuleSetting IssueTrackerSetting
{
get => _issueTrackerSetting;
}
public int SelectedViewIndex
{
get => _selectedViewIndex;
@ -319,6 +324,23 @@ namespace SourceGit.ViewModels
_settings = new Models.RepositorySettings();
}
var issueTrackerSettingsFile = Path.Combine(_gitDir, "sourcegit.issuetracker.settings");
if (File.Exists(issueTrackerSettingsFile))
{
try
{
_issueTrackerSetting = JsonSerializer.Deserialize(File.ReadAllText(issueTrackerSettingsFile), JsonCodeGen.Default.IssueTrackerRuleSetting);
}
catch
{
_issueTrackerSetting = new IssueTrackerRuleSetting();
}
}
else
{
_issueTrackerSetting = new IssueTrackerRuleSetting();
}
_watcher = new Models.Watcher(this);
_histories = new Histories(this);
_workingCopy = new WorkingCopy(this);
@ -339,6 +361,10 @@ namespace SourceGit.ViewModels
File.WriteAllText(Path.Combine(_gitDir, "sourcegit.settings"), settingsSerialized);
_settings = null;
var issueTrackerSerialized = JsonSerializer.Serialize(_issueTrackerSetting, JsonCodeGen.Default.IssueTrackerRuleSetting);
File.WriteAllText(Path.Combine(_gitDir, "sourcegit.issuetracker.settings"), issueTrackerSerialized);
_issueTrackerSetting = null;
_watcher.Dispose();
_histories.Cleanup();
_workingCopy.Cleanup();
@ -1960,6 +1986,7 @@ namespace SourceGit.ViewModels
private string _fullpath = string.Empty;
private string _gitDir = string.Empty;
private Models.RepositorySettings _settings = null;
private IssueTrackerRuleSetting _issueTrackerSetting = null;
private Models.Watcher _watcher = null;
private Histories _histories = null;

View file

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@ -41,6 +43,17 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value);
}
public AvaloniaList<IssueTrackerRule> IssueTrackerRules
{
get => _repo.IssueTrackerSetting.Rules;
}
public IssueTrackerRule SelectedIssueTrackerRule
{
get => _selectedIssueTrackerRule;
set => SetProperty(ref _selectedIssueTrackerRule, value);
}
public RepositoryConfigure(Repository repo)
{
_repo = repo;
@ -65,6 +78,39 @@ namespace SourceGit.ViewModels
HttpProxy = string.Empty;
}
public void AddSampleGithubIssueTracker()
{
foreach (var remote in _repo.Remotes)
{
if (remote.URL.Contains("github.com", System.StringComparison.Ordinal))
{
if (remote.TryGetVisitURL(out string url))
{
SelectedIssueTrackerRule = _repo.IssueTrackerSetting.AddGithub(url);
return;
}
}
}
SelectedIssueTrackerRule = _repo.IssueTrackerSetting.AddGithub(null);
}
public void AddSampleJiraIssueTracker()
{
SelectedIssueTrackerRule = _repo.IssueTrackerSetting.AddJira();
}
public void NewIssueTracker()
{
SelectedIssueTrackerRule = _repo.IssueTrackerSetting.Add();
}
public void RemoveSelectedIssueTracker()
{
if (_selectedIssueTrackerRule != null)
_repo.IssueTrackerSetting.Rules.Remove(_selectedIssueTrackerRule);
}
public void Save()
{
SetIfChanged("user.name", UserName);
@ -96,5 +142,6 @@ namespace SourceGit.ViewModels
private readonly Repository _repo = null;
private readonly Dictionary<string, string> _cached = null;
private string _httpProxy;
private IssueTrackerRule _selectedIssueTrackerRule = null;
}
}

View file

@ -541,7 +541,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path, _repo.IssueTrackerSetting) };
window.Show();
e.Handled = true;
};