refactor<*>: rewrite all codes...

This commit is contained in:
leo 2021-04-29 20:05:55 +08:00
parent 89ff8aa744
commit 30ab8ae954
342 changed files with 17208 additions and 19633 deletions

View file

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace SourceGit.Models {
/// <summary>
/// 支持的头像服务器
/// </summary>
public class AvatarServer {
public string Name { get; set; }
public string Url { get; set; }
public static List<AvatarServer> Supported = new List<AvatarServer>() {
new AvatarServer("Gravatar官网", "https://www.gravatar.com/avatar/"),
new AvatarServer("Gravatar中国CDN", "https://cdn.s.loli.top/avatar/"),
};
public AvatarServer(string name, string url) {
Name = name;
Url = url;
}
}
}

12
src/Models/BlameLine.cs Normal file
View file

@ -0,0 +1,12 @@
namespace SourceGit.Models {
/// <summary>
/// 追溯中的行信息
/// </summary>
public class BlameLine {
public string LineNumber { get; set; }
public string CommitSHA { get; set; }
public string Author { get; set; }
public string Time { get; set; }
public string Content { get; set; }
}
}

16
src/Models/Branch.cs Normal file
View file

@ -0,0 +1,16 @@
namespace SourceGit.Models {
/// <summary>
/// 分支数据
/// </summary>
public class Branch {
public string Name { get; set; }
public string FullName { get; set; }
public string Head { get; set; }
public string HeadSubject { get; set; }
public bool IsLocal { get; set; }
public bool IsCurrent { get; set; }
public string Upstream { get; set; }
public string UpstreamTrackStatus { get; set; }
public string Remote { get; set; }
}
}

25
src/Models/CRLFOption.cs Normal file
View file

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace SourceGit.Models {
/// <summary>
/// 自动换行处理方式
/// </summary>
public class CRLFOption {
public string Display { get; set; }
public string Value { get; set; }
public string Desc { get; set; }
public static List<CRLFOption> Supported = new List<CRLFOption>() {
new CRLFOption("TRUE", "true", "Commit as LF, checkout as CRLF"),
new CRLFOption("INPUT", "input", "Only convert for commit"),
new CRLFOption("FALSE", "false", "Do NOT convert"),
};
public CRLFOption(string display, string value, string desc) {
Display = display;
Value = value;
Desc = desc;
}
}
}

74
src/Models/Change.cs Normal file
View file

@ -0,0 +1,74 @@
namespace SourceGit.Models {
/// <summary>
/// Git变更
/// </summary>
public class Change {
/// <summary>
/// 显示模式
/// </summary>
public enum DisplayMode {
Tree,
List,
Grid,
}
/// <summary>
/// 变更状态码
/// </summary>
public enum Status {
None,
Modified,
Added,
Deleted,
Renamed,
Copied,
Unmerged,
Untracked,
}
public Status Index { get; set; }
public Status WorkTree { get; set; } = Status.None;
public string Path { get; set; } = "";
public string OriginalPath { get; set; } = "";
public bool IsAddedToIndex {
get {
if (Index == Status.None || Index == Status.Untracked) return false;
return true;
}
}
public bool IsConflit {
get {
if (Index == Status.Unmerged || WorkTree == Status.Unmerged) return true;
if (Index == Status.Added && WorkTree == Status.Added) return true;
if (Index == Status.Deleted && WorkTree == Status.Deleted) return true;
return false;
}
}
public void Set(Status index, Status workTree = Status.None) {
Index = index;
WorkTree = workTree;
if (index == Status.Renamed || workTree == Status.Renamed) {
var idx = Path.IndexOf('\t');
if (idx >= 0) {
OriginalPath = Path.Substring(0, idx);
Path = Path.Substring(idx + 1);
} else {
idx = Path.IndexOf(" -> ");
if (idx > 0) {
OriginalPath = Path.Substring(0, idx);
Path = Path.Substring(idx + 4);
}
}
}
if (Path[0] == '"') Path = Path.Substring(1, Path.Length - 2);
if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"') OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
}
}
}

21
src/Models/Commit.cs Normal file
View file

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Windows;
namespace SourceGit.Models {
/// <summary>
/// 提交记录
/// </summary>
public class Commit {
public string SHA { get; set; } = "";
public string ShortSHA => SHA.Substring(0, 8);
public User Author { get; set; } = new User();
public User Committer { get; set; } = new User();
public string Subject { get; set; } = "";
public string Message { get; set; } = "";
public List<string> Parents { get; set; } = new List<string>();
public List<Decorator> Decorators { get; set; } = new List<Decorator>();
public bool HasDecorators => Decorators.Count > 0;
public bool IsMerged { get; set; } = false;
public Thickness Margin { get; set; } = new Thickness(0);
}
}

21
src/Models/Decorator.cs Normal file
View file

@ -0,0 +1,21 @@
namespace SourceGit.Models {
/// <summary>
/// 修饰类型
/// </summary>
public enum DecoratorType {
None,
CurrentBranchHead,
LocalBranchHead,
RemoteBranchHead,
Tag,
}
/// <summary>
/// 提交的附加修饰
/// </summary>
public class Decorator {
public DecoratorType Type { get; set; } = DecoratorType.None;
public string Name { get; set; } = "";
}
}

15
src/Models/Exception.cs Normal file
View file

@ -0,0 +1,15 @@
using System;
namespace SourceGit.Models {
/// <summary>
/// 错误通知
/// </summary>
public static class Exception {
public static Action<string> Handler { get; set; }
public static void Raise(string error) {
Handler?.Invoke(error);
}
}
}

View file

@ -0,0 +1,9 @@
namespace SourceGit.Models {
/// <summary>
/// 文件大小变化
/// </summary>
public class FileSizeChange {
public long OldSize = 0;
public long NewSize = 0;
}
}

36
src/Models/GitFlow.cs Normal file
View file

@ -0,0 +1,36 @@
namespace SourceGit.Models {
/// <summary>
/// GitFlow的分支类型
/// </summary>
public enum GitFlowBranchType {
None,
Feature,
Release,
Hotfix,
}
/// <summary>
/// GitFlow相关设置
/// </summary>
public class GitFlow {
public string Feature { get; set; }
public string Release { get; set; }
public string Hotfix { get; set; }
public bool IsEnabled {
get {
return !string.IsNullOrEmpty(Feature)
&& !string.IsNullOrEmpty(Release)
&& !string.IsNullOrEmpty(Hotfix);
}
}
public GitFlowBranchType GetBranchType(string name) {
if (!IsEnabled) return GitFlowBranchType.None;
if (name.StartsWith(Feature)) return GitFlowBranchType.Feature;
if (name.StartsWith(Release)) return GitFlowBranchType.Release;
if (name.StartsWith(Hotfix)) return GitFlowBranchType.Hotfix;
return GitFlowBranchType.None;
}
}
}

12
src/Models/Group.cs Normal file
View file

@ -0,0 +1,12 @@
namespace SourceGit.Models {
/// <summary>
/// 仓库列表分组
/// </summary>
public class Group {
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public string Parent { get; set; } = "";
public bool IsExpanded { get; set; } = false;
}
}

10
src/Models/LFSChange.cs Normal file
View file

@ -0,0 +1,10 @@
namespace SourceGit.Models {
/// <summary>
/// LFS对象变更
/// </summary>
public class LFSChange {
public LFSObject Old;
public LFSObject New;
public bool IsValid => Old != null || New != null;
}
}

9
src/Models/LFSObject.cs Normal file
View file

@ -0,0 +1,9 @@
namespace SourceGit.Models {
/// <summary>
/// LFS对象
/// </summary>
public class LFSObject {
public string OID { get; set; }
public long Size { get; set; }
}
}

22
src/Models/Locale.cs Normal file
View file

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace SourceGit.Models {
/// <summary>
/// 支持的语言
/// </summary>
public class Locale {
public string Name { get; set; }
public string Resource { get; set; }
public static List<Locale> Supported = new List<Locale>() {
new Locale("English", "en_US"),
new Locale("简体中文", "zh_CN"),
};
public Locale(string name, string res) {
Name = name;
Resource = res;
}
}
}

25
src/Models/MergeOption.cs Normal file
View file

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace SourceGit.Models {
/// <summary>
/// 合并方式
/// </summary>
public class MergeOption {
public string Name { get; set; }
public string Desc { get; set; }
public string Arg { get; set; }
public static List<MergeOption> Supported = new List<MergeOption>() {
new MergeOption("Default", "Fast-forward if possible", ""),
new MergeOption("No Fast-forward", "Always create a merge commit", "--no-ff"),
new MergeOption("Squash", "Use '--squash'", "--squash"),
new MergeOption("Don't commit", "Merge without commit", "--no-commit"),
};
public MergeOption(string n, string d, string a) {
Name = n;
Desc = d;
Arg = a;
}
}
}

119
src/Models/MergeTool.cs Normal file
View file

@ -0,0 +1,119 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
namespace SourceGit.Models {
/// <summary>
/// 外部合并工具
/// </summary>
public class MergeTool {
public int Type { get; set; }
public string Name { get; set; }
public string Exec { get; set; }
public string Cmd { get; set; }
public Func<string> Finder { get; set; }
public static List<MergeTool> Supported = new List<MergeTool>() {
new MergeTool(0, "--", "", "", () => ""),
new MergeTool(1, "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", FindVSCode),
new MergeTool(2, "Visual Studio 2017/2019", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" //m", FindVSMerge),
new MergeTool(3, "Tortoise Merge", "TortoiseMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", FindTortoiseMerge),
new MergeTool(4, "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", FindKDiff3),
new MergeTool(5, "Beyond Compare 4", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", FindBCompare),
};
public MergeTool(int type, string name, string exec, string cmd, Func<string> finder) {
Type = type;
Name = name;
Exec = exec;
Cmd = cmd;
Finder = finder;
}
private static string FindVSCode() {
var root = RegistryKey.OpenBaseKey(
RegistryHive.LocalMachine,
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
var vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1");
if (vscode != null) {
return vscode.GetValue("DisplayIcon") as string;
}
vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1");
if (vscode != null) {
return vscode.GetValue("DisplayIcon") as string;
}
vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1");
if (vscode != null) {
return vscode.GetValue("DisplayIcon") as string;
}
vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1");
if (vscode != null) {
return vscode.GetValue("DisplayIcon") as string;
}
return "";
}
private static string FindVSMerge() {
var dir = @"C:\Program Files (x86)\Microsoft Visual Studio";
if (Directory.Exists($"{dir}\\2019")) {
dir += "\\2019";
} else if (Directory.Exists($"{dir}\\2017")) {
dir += "\\2017";
} else {
return "";
}
if (Directory.Exists($"{dir}\\Community")) {
dir += "\\Community";
} else if (Directory.Exists($"{dir}\\Enterprise")) {
dir += "\\Enterprise";
} else if (Directory.Exists($"{dir}\\Professional")) {
dir += "\\Professional";
} else {
return "";
}
return $"{dir}\\Common7\\IDE\\CommonExtensions\\Microsoft\\TeamFoundation\\Team Explorer\\vsDiffMerge.exe";
}
private static string FindTortoiseMerge() {
var root = RegistryKey.OpenBaseKey(
RegistryHive.LocalMachine,
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
var tortoiseSVN = root.OpenSubKey("SOFTWARE\\TortoiseSVN");
if (tortoiseSVN == null) return "";
return tortoiseSVN.GetValue("TMergePath") as string;
}
private static string FindKDiff3() {
var root = RegistryKey.OpenBaseKey(
RegistryHive.LocalMachine,
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
var kdiff = root.OpenSubKey(@"SOFTWARE\KDiff3\diff-ext");
if (kdiff == null) return "";
return kdiff.GetValue("diffcommand") as string;
}
private static string FindBCompare() {
var root = RegistryKey.OpenBaseKey(
RegistryHive.LocalMachine,
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
var bc = root.OpenSubKey(@"SOFTWARE\Scooter Software\Beyond Compare");
if (bc == null) return "";
var exec = bc.GetValue("ExePath") as string;
var dir = Path.GetDirectoryName(exec);
return $"{dir}\\BComp.exe";
}
}
}

21
src/Models/Object.cs Normal file
View file

@ -0,0 +1,21 @@
namespace SourceGit.Models {
/// <summary>
/// 提交中元素类型
/// </summary>
public enum ObjectType {
None,
Blob,
Tree,
Tag,
Commit,
}
/// <summary>
/// Git提交中的元素
/// </summary>
public class Object {
public string SHA { get; set; }
public ObjectType Type { get; set; }
public string Path { get; set; }
}
}

314
src/Models/Preference.cs Normal file
View file

@ -0,0 +1,314 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
#if NET48
using Newtonsoft.Json;
#else
using System.Text.Json;
using System.Text.Json.Serialization;
#endif
namespace SourceGit.Models {
/// <summary>
/// 程序配置
/// </summary>
public class Preference {
private static readonly string SAVE_PATH = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"SourceGit",
"preference_v4.json");
private static Preference instance = null;
/// <summary>
/// 通用配置
/// </summary>
public class GeneralInfo {
/// <summary>
/// 显示语言
/// </summary>
public string Locale { get; set; } = "en_US";
/// <summary>
/// 头像服务器
/// </summary>
public string AvatarServer { get; set; } = "https://www.gravatar.com/avatar/";
/// <summary>
/// 是否启用深色主题
/// </summary>
public bool UseDarkTheme { get; set; } = true;
/// <summary>
/// 启用更新检测
/// </summary>
public bool CheckForUpdate { get; set; } = true;
/// <summary>
/// 上一次检测的时间(用于控制每天仅第一次启动软件时,检测)
/// </summary>
public int LastCheckDay { get; set; } = 0;
/// <summary>
/// 启用自动拉取远程变更每10分钟一次
/// </summary>
public bool AutoFetchRemotes { get; set; } = true;
}
/// <summary>
/// Git配置
/// </summary>
public class GitInfo {
/// <summary>
/// git.exe所在路径
/// </summary>
public string Path { get; set; }
/// <summary>
/// 默认克隆路径
/// </summary>
public string DefaultCloneDir { get; set; }
}
/// <summary>
/// 外部合并工具配置
/// </summary>
public class MergeToolInfo {
/// <summary>
/// 合并工具类型
/// </summary>
public int Type { get; set; } = 0;
/// <summary>
/// 合并工具可执行文件路径
/// </summary>
public string Path { get; set; }
}
/// <summary>
/// 使用设置
/// </summary>
public class WindowInfo {
/// <summary>
/// 最近一次设置的宽度
/// </summary>
public double Width { get; set; } = 800;
/// <summary>
/// 最近一次设置的高度
/// </summary>
public double Height { get; set; } = 600;
/// <summary>
/// 将提交信息面板与提交记录左右排布
/// </summary>
public bool MoveCommitInfoRight { get; set; } = false;
/// <summary>
/// 使用合并Diff视图
/// </summary>
public bool UseCombinedDiff { get; set; } = false;
/// <summary>
/// 未暂存视图中变更显示方式
/// </summary>
public Change.DisplayMode ChangeInUnstaged { get; set; } = Change.DisplayMode.Tree;
/// <summary>
/// 暂存视图中变更显示方式
/// </summary>
public Change.DisplayMode ChangeInStaged { get; set; } = Change.DisplayMode.Tree;
/// <summary>
/// 提交信息视图中变更显示方式
/// </summary>
public Change.DisplayMode ChangeInCommitInfo { get; set; } = Change.DisplayMode.Tree;
}
/// <summary>
/// 全局配置
/// </summary>
[JsonIgnore]
public static Preference Instance {
get {
if (instance == null) return Load();
return instance;
}
}
/// <summary>
/// 检测配置是否
/// </summary>
[JsonIgnore]
public bool IsReady {
get {
return !string.IsNullOrEmpty(Git.Path) && File.Exists(Git.Path);
}
}
#region DATA
public GeneralInfo General { get; set; } = new GeneralInfo();
public GitInfo Git { get; set; } = new GitInfo();
public MergeToolInfo MergeTool { get; set; } = new MergeToolInfo();
public WindowInfo Window { get; set; } = new WindowInfo();
public List<Group> Groups { get; set; } = new List<Group>();
public List<Repository> Repositories { get; set; } = new List<Repository>();
#endregion
#region LOAD_SAVE
public static Preference Load() {
if (!File.Exists(SAVE_PATH)) {
instance = new Preference();
} else {
#if NET48
instance = JsonConvert.DeserializeObject<Preference>(File.ReadAllText(SAVE_PATH));
#else
instance = JsonSerializer.Deserialize<Preference>(File.ReadAllText(SAVE_PATH));
#endif
}
if (!instance.IsReady) {
var reg = RegistryKey.OpenBaseKey(
RegistryHive.LocalMachine,
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
var git = reg.OpenSubKey("SOFTWARE\\GitForWindows");
if (git != null) {
instance.Git.Path = Path.Combine(git.GetValue("InstallPath") as string, "bin", "git.exe");
}
}
return instance;
}
public static void Save() {
var dir = Path.GetDirectoryName(SAVE_PATH);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
#if NET48
var data = JsonConvert.SerializeObject(instance, Formatting.Indented);
#else
var data = JsonSerializer.Serialize(instance, new JsonSerializerOptions() { WriteIndented = true });
#endif
File.WriteAllText(SAVE_PATH, data);
}
#endregion
#region METHOD_ON_GROUPS
public Group AddGroup(string name, string parentId) {
var group = new Group() {
Name = name,
Id = Guid.NewGuid().ToString(),
Parent = parentId,
IsExpanded = false,
};
Groups.Add(group);
Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
return group;
}
public Group FindGroup(string id) {
foreach (var group in Groups) {
if (group.Id == id) return group;
}
return null;
}
public void RenameGroup(string id, string newName) {
foreach (var group in Groups) {
if (group.Id == id) {
group.Name = newName;
break;
}
}
Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
}
public void RemoveGroup(string id) {
int removedIdx = -1;
for (int i = 0; i < Groups.Count; i++) {
if (Groups[i].Id == id) {
removedIdx = i;
break;
}
}
if (removedIdx >= 0) Groups.RemoveAt(removedIdx);
}
public bool IsSubGroup(string parent, string subId) {
if (string.IsNullOrEmpty(parent)) return false;
if (parent == subId) return true;
var g = FindGroup(subId);
if (g == null) return false;
g = FindGroup(g.Parent);
while (g != null) {
if (g.Id == parent) return true;
g = FindGroup(g.Parent);
}
return false;
}
#endregion
#region METHOD_ON_REPOSITORIES
public Repository AddRepository(string path, string gitDir, string groupId) {
var repo = FindRepository(path);
if (repo != null) return repo;
var dir = new DirectoryInfo(path);
repo = new Repository() {
Path = dir.FullName,
GitDir = gitDir,
Name = dir.Name,
GroupId = groupId,
};
Repositories.Add(repo);
Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
return repo;
}
public Repository FindRepository(string path) {
var dir = new DirectoryInfo(path);
foreach (var repo in Repositories) {
if (repo.Path == dir.FullName) return repo;
}
return null;
}
public void RenameRepository(string path, string newName) {
var repo = FindRepository(path);
if (repo == null) return;
repo.Name = newName;
Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
}
public void RemoveRepository(string path) {
var dir = new DirectoryInfo(path);
var removedIdx = -1;
for (int i = 0; i < Repositories.Count; i++) {
if (Repositories[i].Path == dir.FullName) {
removedIdx = i;
break;
}
}
if (removedIdx >= 0) Repositories.RemoveAt(removedIdx);
}
#endregion
}
}

10
src/Models/Remote.cs Normal file
View file

@ -0,0 +1,10 @@
namespace SourceGit.Models {
/// <summary>
/// 远程
/// </summary>
public class Remote {
public string Name { get; set; }
public string URL { get; set; }
}
}

48
src/Models/Repository.cs Normal file
View file

@ -0,0 +1,48 @@
using System.Collections.Generic;
#if NET48
using Newtonsoft.Json;
#else
using System.Text.Json.Serialization;
#endif
namespace SourceGit.Models {
/// <summary>
/// 仓库
/// </summary>
public class Repository {
#region PROPERTIES_SAVED
public string Name { get; set; } = "";
public string Path { get; set; } = "";
public string GitDir { get; set; } = "";
public string GroupId { get; set; } = "";
public int Bookmark { get; set; } = 0;
public List<string> Filters { get; set; } = new List<string>();
public List<string> CommitMessages { get; set; } = new List<string>();
#endregion
#region PROPERTIES_RUNTIME
[JsonIgnore] public List<Remote> Remotes = new List<Remote>();
[JsonIgnore] public List<Branch> Branches = new List<Branch>();
[JsonIgnore] public GitFlow GitFlow = new GitFlow();
#endregion
public void PushCommitMessage(string message) {
if (string.IsNullOrEmpty(message)) return;
int exists = CommitMessages.Count;
if (exists > 0) {
var last = CommitMessages[0];
if (last == message) return;
}
if (exists >= 10) {
CommitMessages.RemoveRange(9, exists - 9);
}
CommitMessages.Insert(0, message);
}
}
}

27
src/Models/ResetMode.cs Normal file
View file

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Windows.Media;
namespace SourceGit.Models {
/// <summary>
/// 重置方式
/// </summary>
public class ResetMode {
public string Name { get; set; }
public string Desc { get; set; }
public string Arg { get; set; }
public Brush Color { get; set; }
public static List<ResetMode> Supported = new List<ResetMode>() {
new ResetMode("Soft", "Keep all changes. Stage differences", "--soft", Brushes.Green),
new ResetMode("Mixed", "Keep all changes. Unstage differences", "--mixed", Brushes.Yellow),
new ResetMode("Hard", "Discard all changes", "--hard", Brushes.Red),
};
public ResetMode(string n, string d, string a, Brush b) {
Name = n;
Desc = d;
Arg = a;
Color = b;
}
}
}

11
src/Models/Stash.cs Normal file
View file

@ -0,0 +1,11 @@
namespace SourceGit.Models {
/// <summary>
/// 贮藏
/// </summary>
public class Stash {
public string Name { get; set; } = "";
public string SHA { get; set; } = "";
public User Author { get; set; } = new User();
public string Message { get; set; } = "";
}
}

10
src/Models/Tag.cs Normal file
View file

@ -0,0 +1,10 @@
namespace SourceGit.Models {
/// <summary>
/// 标签
/// </summary>
public class Tag {
public string Name { get; set; }
public string SHA { get; set; }
public bool IsFiltered { get; set; }
}
}

34
src/Models/TextChanges.cs Normal file
View file

@ -0,0 +1,34 @@
using System.Collections.Generic;
namespace SourceGit.Models {
/// <summary>
/// Diff文本文件变化
/// </summary>
public class TextChanges {
public enum LineMode {
None,
Normal,
Indicator,
Added,
Deleted,
}
public class Line {
public LineMode Mode = LineMode.Normal;
public string Content = "";
public string OldLine = "";
public string NewLine = "";
public Line(LineMode mode, string content, string oldLine, string newLine) {
Mode = mode;
Content = content;
OldLine = oldLine;
NewLine = newLine;
}
}
public bool IsBinary = false;
public List<Line> Lines = new List<Line>();
}
}

9
src/Models/TextLine.cs Normal file
View file

@ -0,0 +1,9 @@
namespace SourceGit.Models {
/// <summary>
/// 文件中的一行内容
/// </summary>
public class TextLine {
public int Number { get; set; }
public string Data { get; set; }
}
}

26
src/Models/User.cs Normal file
View file

@ -0,0 +1,26 @@
using System;
using System.Text.RegularExpressions;
namespace SourceGit.Models {
/// <summary>
/// Git用户
/// </summary>
public class User {
private static readonly Regex REG_FORMAT = new Regex(@"\w+ (.*) <(.*)> (\d{10}) [\+\-]\d+");
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public string Time { get; set; } = "";
public void Parse(string data) {
var match = REG_FORMAT.Match(data);
if (!match.Success) return;
var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(int.Parse(match.Groups[3].Value));
Name = match.Groups[1].Value;
Email = match.Groups[2].Value;
Time = time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
}
}
}

63
src/Models/Version.cs Normal file
View file

@ -0,0 +1,63 @@
using System;
#if NET48
using Newtonsoft.Json;
#else
using System.Text.Json;
using System.Text.Json.Serialization;
#endif
namespace SourceGit.Models {
/// <summary>
/// Gitee开放API中Release信息格式
/// </summary>
public class Version {
#if NET48
[JsonProperty(PropertyName = "id")]
public ulong Id { get; set; }
[JsonProperty(PropertyName = "tag_name")]
public string TagName { get; set; }
[JsonProperty(PropertyName = "target_commitish")]
public string CommitSHA { get; set; }
[JsonProperty(PropertyName = "prerelease")]
public bool PreRelease { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "body")]
public string Body { get; set; }
[JsonProperty(PropertyName = "created_at")]
public DateTime CreatedAt { get; set; }
#else
[JsonPropertyName("id")]
public ulong Id { get; set; }
[JsonPropertyName("tag_name")]
public string TagName { get; set; }
[JsonPropertyName("target_commitish")]
public string CommitSHA { get; set; }
[JsonPropertyName("prerelease")]
public bool PreRelease { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("body")]
public string Body { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; }
#endif
public string PublishTime {
get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); }
}
public string IsPrerelease {
get { return PreRelease ? "YES" : "NO"; }
}
public static Version Load(string data) {
#if NET48
return JsonConvert.DeserializeObject<Version>(data);
#else
return JsonSerializer.Deserialize<Version>(data);
#endif
}
}
}

237
src/Models/Watcher.cs Normal file
View file

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace SourceGit.Models {
/// <summary>
/// 文件系统更新监视
/// </summary>
public class Watcher {
/// <summary>
/// 打开仓库事件
/// </summary>
public static event Action<Repository> Opened;
/// <summary>
/// 跳转到指定提交的事件
/// </summary>
public event Action<string> Navigate;
/// <summary>
/// 工作副本变更
/// </summary>
public event Action WorkingCopyChanged;
/// <summary>
/// 分支数据变更
/// </summary>
public event Action BranchChanged;
/// <summary>
/// 标签变更
/// </summary>
public event Action TagChanged;
/// <summary>
/// 贮藏变更
/// </summary>
public event Action StashChanged;
/// <summary>
/// 子模块变更
/// </summary>
public event Action SubmoduleChanged;
/// <summary>
/// 打开仓库事件
/// </summary>
/// <param name="repo"></param>
public static void Open(Repository repo) {
if (all.ContainsKey(repo.Path)) return;
var watcher = new Watcher();
watcher.Start(repo.Path, repo.GitDir);
all.Add(repo.Path, watcher);
Opened?.Invoke(repo);
}
/// <summary>
/// 停止指定的监视器
/// </summary>
/// <param name="repoPath"></param>
public static void Close(string repoPath) {
if (!all.ContainsKey(repoPath)) return;
all[repoPath].Stop();
all.Remove(repoPath);
}
/// <summary>
/// 取得一个仓库的监视器
/// </summary>
/// <param name="repoPath"></param>
/// <returns></returns>
public static Watcher Get(string repoPath) {
if (all.ContainsKey(repoPath)) return all[repoPath];
return null;
}
/// <summary>
/// 暂停或启用监听
/// </summary>
/// <param name="repoPath"></param>
/// <param name="enabled"></param>
public static void SetEnabled(string repoPath, bool enabled) {
if (all.ContainsKey(repoPath)) {
var watcher = all[repoPath];
if (enabled) {
if (watcher.lockCount > 0) watcher.lockCount--;
} else {
watcher.lockCount++;
}
}
}
/// <summary>
/// 跳转到指定的提交
/// </summary>
/// <param name="commit"></param>
public void NavigateTo(string commit) {
Navigate?.Invoke(commit);
}
/// <summary>
/// 强制刷新
/// </summary>
public void Refresh() {
updateWC = 1;
updateBranch = 1;
updateSubmodules = 1;
updateStashes = 1;
updateTags = 1;
}
/// <summary>
/// 仅强制更新本地变化
/// </summary>
public void RefreshWC() {
updateWC = 0;
WorkingCopyChanged?.Invoke();
}
private void Start(string repo, string gitDir) {
wcWatcher = new FileSystemWatcher();
wcWatcher.Path = repo;
wcWatcher.Filter = "*";
wcWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime;
wcWatcher.IncludeSubdirectories = true;
wcWatcher.Created += OnWorkingCopyChanged;
wcWatcher.Renamed += OnWorkingCopyChanged;
wcWatcher.Changed += OnWorkingCopyChanged;
wcWatcher.Deleted += OnWorkingCopyChanged;
wcWatcher.EnableRaisingEvents = true;
repoWatcher = new FileSystemWatcher();
repoWatcher.Path = gitDir;
repoWatcher.Filter = "*";
repoWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName;
repoWatcher.IncludeSubdirectories = true;
repoWatcher.Created += OnRepositoryChanged;
repoWatcher.Renamed += OnRepositoryChanged;
repoWatcher.Changed += OnRepositoryChanged;
repoWatcher.Deleted += OnRepositoryChanged;
repoWatcher.EnableRaisingEvents = true;
timer = new Timer(Tick, null, 100, 100);
}
private void Stop() {
repoWatcher.EnableRaisingEvents = false;
repoWatcher.Dispose();
repoWatcher = null;
wcWatcher.EnableRaisingEvents = false;
wcWatcher.Dispose();
wcWatcher = null;
timer.Dispose();
timer = null;
Navigate = null;
WorkingCopyChanged = null;
BranchChanged = null;
TagChanged = null;
StashChanged = null;
SubmoduleChanged = null;
}
private void OnRepositoryChanged(object o, FileSystemEventArgs e) {
if (string.IsNullOrEmpty(e.Name)) return;
if (e.Name.StartsWith("modules", StringComparison.Ordinal)) {
updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
} else if (e.Name.StartsWith("refs\\tags", StringComparison.Ordinal)) {
updateTags = DateTime.Now.AddSeconds(.5).ToFileTime();
} else if (e.Name.StartsWith("refs\\stash", StringComparison.Ordinal)) {
updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
} else if (e.Name.Equals("HEAD", StringComparison.Ordinal) ||
e.Name.EndsWith("_HEAD", StringComparison.Ordinal) ||
e.Name.StartsWith("refs\\heads\\", StringComparison.Ordinal) ||
e.Name.StartsWith("refs\\remotes\\", StringComparison.Ordinal)) {
updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
} else if (e.Name.StartsWith("objects\\") || e.Name.Equals("index", StringComparison.Ordinal)) {
updateWC = DateTime.Now.AddSeconds(.5).ToFileTime();
}
}
private void OnWorkingCopyChanged(object o, FileSystemEventArgs e) {
if (string.IsNullOrEmpty(e.Name)) return;
if (e.Name == ".git" || e.Name.StartsWith(".git\\", StringComparison.Ordinal)) return;
updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
}
private void Tick(object sender) {
if (lockCount > 0) return;
var now = DateTime.Now.ToFileTime();
if (updateBranch > 0 && now > updateBranch) {
BranchChanged?.Invoke();
WorkingCopyChanged?.Invoke();
updateBranch = 0;
updateWC = 0;
}
if (updateWC > 0 && now > updateWC) {
WorkingCopyChanged?.Invoke();
updateWC = 0;
}
if (updateSubmodules > 0 && now > updateSubmodules) {
SubmoduleChanged?.Invoke();
updateSubmodules = 0;
}
if (updateStashes > 0 && now > updateStashes) {
StashChanged?.Invoke();
updateStashes = 0;
}
if (updateTags > 0 && now > updateTags) {
TagChanged?.Invoke();
updateTags = 0;
}
}
#region PRIVATES
private static Dictionary<string, Watcher> all = new Dictionary<string, Watcher>();
private FileSystemWatcher repoWatcher = null;
private FileSystemWatcher wcWatcher = null;
private Timer timer = null;
private int lockCount = 0;
private long updateWC = 0;
private long updateBranch = 0;
private long updateSubmodules = 0;
private long updateStashes = 0;
private long updateTags = 0;
#endregion
}
}

View file

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace SourceGit.Models {
/// <summary>
/// 应用补丁时空白字符的处理方式
/// </summary>
public class WhitespaceOption {
public string Name { get; set; }
public string Desc { get; set; }
public string Arg { get; set; }
public static List<WhitespaceOption> Supported = new List<WhitespaceOption>() {
new WhitespaceOption("Apply.NoWarn", "Apply.NoWarn.Desc", "nowarn"),
new WhitespaceOption("Apply.Warn", "Apply.Warn.Desc", "warn"),
new WhitespaceOption("Apply.Error", "Apply.Error.Desc", "error"),
new WhitespaceOption("Apply.ErrorAll", "Apply.ErrorAll.Desc", "error-all")
};
public WhitespaceOption(string n, string d, string a) {
Name = App.Text(n);
Desc = App.Text(d);
Arg = a;
}
}
}