mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-21 12:15:00 +00:00
Project Location
This commit is contained in:
parent
014e37e505
commit
a1a14f8858
305 changed files with 9783 additions and 9783 deletions
16
src/SourceGit/Models/ApplyWhiteSpaceMode.cs
Normal file
16
src/SourceGit/Models/ApplyWhiteSpaceMode.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class ApplyWhiteSpaceMode
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string Arg { get; set; }
|
||||
|
||||
public ApplyWhiteSpaceMode(string n, string d, string a)
|
||||
{
|
||||
Name = App.Text(n);
|
||||
Desc = App.Text(d);
|
||||
Arg = a;
|
||||
}
|
||||
}
|
||||
}
|
157
src/SourceGit/Models/AvatarManager.cs
Normal file
157
src/SourceGit/Models/AvatarManager.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public interface IAvatarHost
|
||||
{
|
||||
void OnAvatarResourceChanged(string md5);
|
||||
}
|
||||
|
||||
public static class AvatarManager
|
||||
{
|
||||
public static string SelectedServer
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = "https://www.gravatar.com/avatar/";
|
||||
|
||||
static AvatarManager()
|
||||
{
|
||||
_storePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "SourceGit", "avatars");
|
||||
if (!Directory.Exists(_storePath)) Directory.CreateDirectory(_storePath);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var md5 = null as string;
|
||||
|
||||
lock (_synclock)
|
||||
{
|
||||
foreach (var one in _requesting)
|
||||
{
|
||||
md5 = one;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (md5 == null)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
var localFile = Path.Combine(_storePath, md5);
|
||||
var img = null as Bitmap;
|
||||
try
|
||||
{
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) };
|
||||
var task = client.GetAsync($"{SelectedServer}{md5}?d=404");
|
||||
task.Wait();
|
||||
|
||||
var rsp = task.Result;
|
||||
if (rsp.IsSuccessStatusCode)
|
||||
{
|
||||
using (var stream = rsp.Content.ReadAsStream())
|
||||
{
|
||||
using (var writer = File.OpenWrite(localFile))
|
||||
{
|
||||
stream.CopyTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
using (var reader = File.OpenRead(localFile))
|
||||
{
|
||||
img = Bitmap.DecodeToWidth(reader, 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
lock (_synclock)
|
||||
{
|
||||
_requesting.Remove(md5);
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
if (_resources.ContainsKey(md5)) _resources[md5] = img;
|
||||
else _resources.Add(md5, img);
|
||||
NotifyResourceChanged(md5);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void Subscribe(IAvatarHost host)
|
||||
{
|
||||
_avatars.Add(host);
|
||||
}
|
||||
|
||||
public static void Unsubscribe(IAvatarHost host)
|
||||
{
|
||||
_avatars.Remove(host);
|
||||
}
|
||||
|
||||
public static Bitmap Request(string md5, bool forceRefetch = false)
|
||||
{
|
||||
if (forceRefetch)
|
||||
{
|
||||
if (_resources.ContainsKey(md5)) _resources.Remove(md5);
|
||||
|
||||
var localFile = Path.Combine(_storePath, md5);
|
||||
if (File.Exists(localFile)) File.Delete(localFile);
|
||||
|
||||
NotifyResourceChanged(md5);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_resources.ContainsKey(md5)) return _resources[md5];
|
||||
|
||||
var localFile = Path.Combine(_storePath, md5);
|
||||
if (File.Exists(localFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = File.OpenRead(localFile))
|
||||
{
|
||||
var img = Bitmap.DecodeToWidth(stream, 128);
|
||||
_resources.Add(md5, img);
|
||||
return img;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
lock (_synclock)
|
||||
{
|
||||
if (!_requesting.Contains(md5)) _requesting.Add(md5);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void NotifyResourceChanged(string md5)
|
||||
{
|
||||
foreach (var avatar in _avatars)
|
||||
{
|
||||
avatar.OnAvatarResourceChanged(md5);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object _synclock = new object();
|
||||
private static readonly string _storePath = string.Empty;
|
||||
private static readonly List<IAvatarHost> _avatars = new List<IAvatarHost>();
|
||||
private static readonly Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
|
||||
private static readonly HashSet<string> _requesting = new HashSet<string>();
|
||||
}
|
||||
}
|
20
src/SourceGit/Models/Blame.cs
Normal file
20
src/SourceGit/Models/Blame.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class BlameLineInfo
|
||||
{
|
||||
public bool IsFirstInGroup { get; set; } = false;
|
||||
public string CommitSHA { get; set; } = string.Empty;
|
||||
public string Author { get; set; } = string.Empty;
|
||||
public string Time { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class BlameData
|
||||
{
|
||||
public string File { get; set; } = string.Empty;
|
||||
public List<BlameLineInfo> LineInfos { get; set; } = new List<BlameLineInfo>();
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public bool IsBinary { get; set; } = false;
|
||||
}
|
||||
}
|
25
src/SourceGit/Models/Bookmarks.cs
Normal file
25
src/SourceGit/Models/Bookmarks.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public static class Bookmarks
|
||||
{
|
||||
public static readonly Avalonia.Media.IBrush[] Brushes = [
|
||||
Avalonia.Media.Brushes.Transparent,
|
||||
Avalonia.Media.Brushes.Red,
|
||||
Avalonia.Media.Brushes.Orange,
|
||||
Avalonia.Media.Brushes.Gold,
|
||||
Avalonia.Media.Brushes.ForestGreen,
|
||||
Avalonia.Media.Brushes.DarkCyan,
|
||||
Avalonia.Media.Brushes.DeepSkyBlue,
|
||||
Avalonia.Media.Brushes.Purple,
|
||||
];
|
||||
|
||||
public static readonly List<int> Supported = new List<int>();
|
||||
|
||||
static Bookmarks()
|
||||
{
|
||||
for (int i = 0; i < Brushes.Length; i++) Supported.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
14
src/SourceGit/Models/Branch.cs
Normal file
14
src/SourceGit/Models/Branch.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class Branch
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public string Head { 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; }
|
||||
}
|
||||
}
|
201
src/SourceGit/Models/BranchTreeNode.cs
Normal file
201
src/SourceGit/Models/BranchTreeNode.cs
Normal file
|
@ -0,0 +1,201 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia.Collections;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum BranchTreeNodeType
|
||||
{
|
||||
Remote,
|
||||
Folder,
|
||||
Branch,
|
||||
}
|
||||
|
||||
public class BranchTreeNode
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public BranchTreeNodeType Type { get; set; }
|
||||
public object Backend { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
public bool IsFiltered { get; set; }
|
||||
public List<BranchTreeNode> Children { get; set; } = new List<BranchTreeNode>();
|
||||
|
||||
public bool IsUpstreamTrackStatusVisible
|
||||
{
|
||||
get => IsBranch && !string.IsNullOrEmpty((Backend as Branch).UpstreamTrackStatus);
|
||||
}
|
||||
|
||||
public string UpstreamTrackStatus
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Branch ? (Backend as Branch).UpstreamTrackStatus : "";
|
||||
}
|
||||
|
||||
public bool IsRemote
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Remote;
|
||||
}
|
||||
|
||||
public bool IsFolder
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Folder;
|
||||
}
|
||||
|
||||
public bool IsBranch
|
||||
{
|
||||
get => Type == BranchTreeNodeType.Branch;
|
||||
}
|
||||
|
||||
public bool IsCurrent
|
||||
{
|
||||
get => IsBranch && (Backend as Branch).IsCurrent;
|
||||
}
|
||||
|
||||
public class Builder
|
||||
{
|
||||
public List<BranchTreeNode> Locals => _locals;
|
||||
public List<BranchTreeNode> Remotes => _remotes;
|
||||
|
||||
public void Run(List<Branch> branches, List<Remote> remotes)
|
||||
{
|
||||
foreach (var remote in remotes)
|
||||
{
|
||||
var path = $"remote/{remote.Name}";
|
||||
var node = new BranchTreeNode()
|
||||
{
|
||||
Name = remote.Name,
|
||||
Type = BranchTreeNodeType.Remote,
|
||||
Backend = remote,
|
||||
IsExpanded = _expanded.Contains(path),
|
||||
};
|
||||
|
||||
_maps.Add(path, node);
|
||||
_remotes.Add(node);
|
||||
}
|
||||
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
var isFiltered = _filters.Contains(branch.FullName);
|
||||
if (branch.IsLocal)
|
||||
{
|
||||
MakeBranchNode(branch, _locals, "local", isFiltered);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remote = _remotes.Find(x => x.Name == branch.Remote);
|
||||
if (remote != null) MakeBranchNode(branch, remote.Children, $"remote/{remote.Name}", isFiltered);
|
||||
}
|
||||
}
|
||||
|
||||
SortNodes(_locals);
|
||||
SortNodes(_remotes);
|
||||
}
|
||||
|
||||
public void SetFilters(AvaloniaList<string> filters)
|
||||
{
|
||||
_filters.AddRange(filters);
|
||||
}
|
||||
|
||||
public void CollectExpandedNodes(List<BranchTreeNode> nodes, bool isLocal)
|
||||
{
|
||||
CollectExpandedNodes(nodes, isLocal ? "local" : "remote");
|
||||
}
|
||||
|
||||
private void CollectExpandedNodes(List<BranchTreeNode> nodes, string prefix)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var path = prefix + "/" + node.Name;
|
||||
if (node.Type != BranchTreeNodeType.Branch && node.IsExpanded) _expanded.Add(path);
|
||||
CollectExpandedNodes(node.Children, path);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeBranchNode(Branch branch, List<BranchTreeNode> roots, string prefix, bool isFiltered)
|
||||
{
|
||||
var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (subs.Length == 1)
|
||||
{
|
||||
var node = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[0],
|
||||
Type = BranchTreeNodeType.Branch,
|
||||
Backend = branch,
|
||||
IsExpanded = false,
|
||||
IsFiltered = isFiltered,
|
||||
};
|
||||
roots.Add(node);
|
||||
return;
|
||||
}
|
||||
|
||||
BranchTreeNode lastFolder = null;
|
||||
string path = prefix;
|
||||
for (int i = 0; i < subs.Length - 1; i++)
|
||||
{
|
||||
path = string.Concat(path, "/", subs[i]);
|
||||
if (_maps.ContainsKey(path))
|
||||
{
|
||||
lastFolder = _maps[path];
|
||||
}
|
||||
else if (lastFolder == null)
|
||||
{
|
||||
lastFolder = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[i],
|
||||
Type = BranchTreeNodeType.Folder,
|
||||
IsExpanded = branch.IsCurrent || _expanded.Contains(path),
|
||||
};
|
||||
roots.Add(lastFolder);
|
||||
_maps.Add(path, lastFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
var folder = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[i],
|
||||
Type = BranchTreeNodeType.Folder,
|
||||
IsExpanded = branch.IsCurrent || _expanded.Contains(path),
|
||||
};
|
||||
_maps.Add(path, folder);
|
||||
lastFolder.Children.Add(folder);
|
||||
lastFolder = folder;
|
||||
}
|
||||
}
|
||||
|
||||
var last = new BranchTreeNode()
|
||||
{
|
||||
Name = subs[subs.Length - 1],
|
||||
Type = BranchTreeNodeType.Branch,
|
||||
Backend = branch,
|
||||
IsExpanded = false,
|
||||
IsFiltered = isFiltered,
|
||||
};
|
||||
lastFolder.Children.Add(last);
|
||||
}
|
||||
|
||||
private void SortNodes(List<BranchTreeNode> nodes)
|
||||
{
|
||||
nodes.Sort((l, r) =>
|
||||
{
|
||||
if (l.Type == r.Type)
|
||||
{
|
||||
return l.Name.CompareTo(r.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)(l.Type) - (int)(r.Type);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var node in nodes) SortNodes(node.Children);
|
||||
}
|
||||
|
||||
private readonly List<BranchTreeNode> _locals = new List<BranchTreeNode>();
|
||||
private readonly List<BranchTreeNode> _remotes = new List<BranchTreeNode>();
|
||||
private readonly HashSet<string> _expanded = new HashSet<string>();
|
||||
private readonly List<string> _filters = new List<string>();
|
||||
private readonly Dictionary<string, BranchTreeNode> _maps = new Dictionary<string, BranchTreeNode>();
|
||||
}
|
||||
}
|
||||
}
|
24
src/SourceGit/Models/CRLFMode.cs
Normal file
24
src/SourceGit/Models/CRLFMode.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class CRLFMode
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
public string Desc { get; set; }
|
||||
|
||||
public static List<CRLFMode> Supported = new List<CRLFMode>() {
|
||||
new CRLFMode("TRUE", "true", "Commit as LF, checkout as CRLF"),
|
||||
new CRLFMode("INPUT", "input", "Only convert for commit"),
|
||||
new CRLFMode("FALSE", "false", "Do NOT convert"),
|
||||
};
|
||||
|
||||
public CRLFMode(string name, string value, string desc)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
Desc = desc;
|
||||
}
|
||||
}
|
||||
}
|
70
src/SourceGit/Models/Change.cs
Normal file
70
src/SourceGit/Models/Change.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum ChangeViewMode
|
||||
{
|
||||
List,
|
||||
Grid,
|
||||
Tree,
|
||||
}
|
||||
|
||||
public enum ChangeState
|
||||
{
|
||||
None,
|
||||
Modified,
|
||||
Added,
|
||||
Deleted,
|
||||
Renamed,
|
||||
Copied,
|
||||
Unmerged,
|
||||
Untracked
|
||||
}
|
||||
|
||||
public class Change
|
||||
{
|
||||
public ChangeState Index { get; set; }
|
||||
public ChangeState WorkTree { get; set; } = ChangeState.None;
|
||||
public string Path { get; set; } = "";
|
||||
public string OriginalPath { get; set; } = "";
|
||||
|
||||
public bool IsConflit
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Index == ChangeState.Unmerged || WorkTree == ChangeState.Unmerged) return true;
|
||||
if (Index == ChangeState.Added && WorkTree == ChangeState.Added) return true;
|
||||
if (Index == ChangeState.Deleted && WorkTree == ChangeState.Deleted) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
|
||||
{
|
||||
Index = index;
|
||||
WorkTree = workTree;
|
||||
|
||||
if (index == ChangeState.Renamed || workTree == ChangeState.Renamed)
|
||||
{
|
||||
var idx = Path.IndexOf('\t', StringComparison.Ordinal);
|
||||
if (idx >= 0)
|
||||
{
|
||||
OriginalPath = Path.Substring(0, idx);
|
||||
Path = Path.Substring(idx + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
idx = Path.IndexOf(" -> ", StringComparison.Ordinal);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
50
src/SourceGit/Models/Commit.cs
Normal file
50
src/SourceGit/Models/Commit.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class Commit
|
||||
{
|
||||
public string SHA { get; set; } = string.Empty;
|
||||
public User Author { get; set; } = User.Invalid;
|
||||
public ulong AuthorTime { get; set; } = 0;
|
||||
public User Committer { get; set; } = User.Invalid;
|
||||
public ulong CommitterTime { get; set; } = 0;
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
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);
|
||||
|
||||
public string AuthorTimeStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string CommitterTimeStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd HH:mm:ss");
|
||||
public string AuthorTimeShortStr => _utcStart.AddSeconds(AuthorTime).ToString("yyyy/MM/dd");
|
||||
public string CommitterTimeShortStr => _utcStart.AddSeconds(CommitterTime).ToString("yyyy/MM/dd");
|
||||
|
||||
public bool IsCommitterVisible
|
||||
{
|
||||
get => Author != Committer || AuthorTime != CommitterTime;
|
||||
}
|
||||
|
||||
public string FullMessage
|
||||
{
|
||||
get => string.IsNullOrWhiteSpace(Message) ? Subject : $"{Subject}\n\n{Message}";
|
||||
}
|
||||
|
||||
public static void ParseUserAndTime(string data, ref User user, ref ulong time)
|
||||
{
|
||||
var userEndIdx = data.IndexOf('>', StringComparison.Ordinal);
|
||||
if (userEndIdx < 0) return;
|
||||
|
||||
var timeEndIdx = data.IndexOf(' ', userEndIdx + 2);
|
||||
user = User.FindOrAdd(data.Substring(0, userEndIdx));
|
||||
time = timeEndIdx < 0 ? 0 : ulong.Parse(data.Substring(userEndIdx + 2, timeEndIdx - userEndIdx - 2));
|
||||
}
|
||||
|
||||
private static readonly DateTime _utcStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
}
|
244
src/SourceGit/Models/CommitGraph.cs
Normal file
244
src/SourceGit/Models/CommitGraph.cs
Normal file
|
@ -0,0 +1,244 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Avalonia;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class CommitGraph
|
||||
{
|
||||
public class Path
|
||||
{
|
||||
public List<Point> Points = new List<Point>();
|
||||
public int Color = 0;
|
||||
}
|
||||
|
||||
public class PathHelper
|
||||
{
|
||||
public string Next;
|
||||
public bool IsMerged;
|
||||
public double LastX;
|
||||
public double LastY;
|
||||
public double EndY;
|
||||
public Path Path;
|
||||
|
||||
public PathHelper(string next, bool isMerged, int color, Point start)
|
||||
{
|
||||
Next = next;
|
||||
IsMerged = isMerged;
|
||||
LastX = start.X;
|
||||
LastY = start.Y;
|
||||
EndY = LastY;
|
||||
|
||||
Path = new Path();
|
||||
Path.Color = color;
|
||||
Path.Points.Add(start);
|
||||
}
|
||||
|
||||
public PathHelper(string next, bool isMerged, int color, Point start, Point to)
|
||||
{
|
||||
Next = next;
|
||||
IsMerged = isMerged;
|
||||
LastX = to.X;
|
||||
LastY = to.Y;
|
||||
EndY = LastY;
|
||||
|
||||
Path = new Path();
|
||||
Path.Color = color;
|
||||
Path.Points.Add(start);
|
||||
Path.Points.Add(to);
|
||||
}
|
||||
|
||||
public void Add(double x, double y, double halfHeight, bool isEnd = false)
|
||||
{
|
||||
if (x > LastX)
|
||||
{
|
||||
Add(new Point(LastX, LastY));
|
||||
Add(new Point(x, y - halfHeight));
|
||||
if (isEnd) Add(new Point(x, y));
|
||||
}
|
||||
else if (x < LastX)
|
||||
{
|
||||
if (y > LastY + halfHeight) Add(new Point(LastX, LastY + halfHeight));
|
||||
Add(new Point(x, y));
|
||||
}
|
||||
else if (isEnd)
|
||||
{
|
||||
Add(new Point(x, y));
|
||||
}
|
||||
|
||||
LastX = x;
|
||||
LastY = y;
|
||||
}
|
||||
|
||||
private void Add(Point p)
|
||||
{
|
||||
if (EndY < p.Y)
|
||||
{
|
||||
Path.Points.Add(p);
|
||||
EndY = p.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Link
|
||||
{
|
||||
public Point Start;
|
||||
public Point Control;
|
||||
public Point End;
|
||||
public int Color;
|
||||
}
|
||||
|
||||
public class Dot
|
||||
{
|
||||
public Point Center;
|
||||
public int Color;
|
||||
}
|
||||
|
||||
public List<Path> Paths { get; set; } = new List<Path>();
|
||||
public List<Link> Links { get; set; } = new List<Link>();
|
||||
public List<Dot> Dots { get; set; } = new List<Dot>();
|
||||
|
||||
public static CommitGraph Parse(List<Commit> commits, double rowHeight, int colorCount)
|
||||
{
|
||||
double UNIT_WIDTH = 12;
|
||||
double HALF_WIDTH = 6;
|
||||
double UNIT_HEIGHT = rowHeight;
|
||||
double HALF_HEIGHT = rowHeight / 2;
|
||||
|
||||
var temp = new CommitGraph();
|
||||
var unsolved = new List<PathHelper>();
|
||||
var mapUnsolved = new Dictionary<string, PathHelper>();
|
||||
var ended = new List<PathHelper>();
|
||||
var offsetY = -HALF_HEIGHT;
|
||||
var colorIdx = 0;
|
||||
|
||||
foreach (var commit in commits)
|
||||
{
|
||||
var major = null as PathHelper;
|
||||
var isMerged = commit.IsMerged;
|
||||
var oldCount = unsolved.Count;
|
||||
|
||||
// Update current y offset
|
||||
offsetY += UNIT_HEIGHT;
|
||||
|
||||
// Find first curves that links to this commit and marks others that links to this commit ended.
|
||||
double offsetX = -HALF_WIDTH;
|
||||
foreach (var l in unsolved)
|
||||
{
|
||||
if (l.Next == commit.SHA)
|
||||
{
|
||||
if (major == null)
|
||||
{
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = l;
|
||||
|
||||
if (commit.Parents.Count > 0)
|
||||
{
|
||||
major.Next = commit.Parents[0];
|
||||
if (!mapUnsolved.ContainsKey(major.Next)) mapUnsolved.Add(major.Next, major);
|
||||
}
|
||||
else
|
||||
{
|
||||
major.Next = "ENDED";
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
major.Add(offsetX, offsetY, HALF_HEIGHT);
|
||||
}
|
||||
else
|
||||
{
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
isMerged = isMerged || l.IsMerged;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mapUnsolved.ContainsKey(l.Next)) mapUnsolved.Add(l.Next, l);
|
||||
offsetX += UNIT_WIDTH;
|
||||
l.Add(offsetX, offsetY, HALF_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new curve for branch head
|
||||
if (major == null && commit.Parents.Count > 0)
|
||||
{
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = new PathHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
|
||||
unsolved.Add(major);
|
||||
temp.Paths.Add(major.Path);
|
||||
colorIdx = (colorIdx + 1) % colorCount;
|
||||
}
|
||||
|
||||
// Calculate link position of this commit.
|
||||
Point position = new Point(offsetX, offsetY);
|
||||
if (major != null)
|
||||
{
|
||||
major.IsMerged = isMerged;
|
||||
position = new Point(major.LastX, offsetY);
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = major.Path.Color });
|
||||
}
|
||||
else
|
||||
{
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = 0 });
|
||||
}
|
||||
|
||||
// Deal with parents
|
||||
for (int j = 1; j < commit.Parents.Count; j++)
|
||||
{
|
||||
var parent = commit.Parents[j];
|
||||
if (mapUnsolved.ContainsKey(parent))
|
||||
{
|
||||
var l = mapUnsolved[parent];
|
||||
var link = new Link();
|
||||
|
||||
link.Start = position;
|
||||
link.End = new Point(l.LastX, offsetY + HALF_HEIGHT);
|
||||
link.Control = new Point(link.End.X, link.Start.Y);
|
||||
link.Color = l.Path.Color;
|
||||
temp.Links.Add(link);
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetX += UNIT_WIDTH;
|
||||
|
||||
// Create new curve for parent commit that not includes before
|
||||
var l = new PathHelper(commit.Parents[j], isMerged, colorIdx, position, new Point(offsetX, position.Y + HALF_HEIGHT));
|
||||
unsolved.Add(l);
|
||||
temp.Paths.Add(l.Path);
|
||||
colorIdx = (colorIdx + 1) % colorCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove ended curves from unsolved
|
||||
foreach (var l in ended)
|
||||
{
|
||||
l.Add(position.X, position.Y, HALF_HEIGHT, true);
|
||||
unsolved.Remove(l);
|
||||
}
|
||||
|
||||
// Margins & merge state (used by datagrid).
|
||||
commit.IsMerged = isMerged;
|
||||
commit.Margin = new Thickness(Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH), 0, 0, 0);
|
||||
|
||||
// Clean up
|
||||
ended.Clear();
|
||||
mapUnsolved.Clear();
|
||||
}
|
||||
|
||||
// Deal with curves haven't ended yet.
|
||||
for (int i = 0; i < unsolved.Count; i++)
|
||||
{
|
||||
var path = unsolved[i];
|
||||
var endY = (commits.Count - 0.5) * UNIT_HEIGHT;
|
||||
|
||||
if (path.Path.Points.Count == 1 && path.Path.Points[0].Y == endY) continue;
|
||||
path.Add((i + 0.5) * UNIT_WIDTH, endY + HALF_HEIGHT, HALF_HEIGHT, true);
|
||||
}
|
||||
unsolved.Clear();
|
||||
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
27
src/SourceGit/Models/Decorator.cs
Normal file
27
src/SourceGit/Models/Decorator.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum DecoratorType
|
||||
{
|
||||
None,
|
||||
CurrentBranchHead,
|
||||
LocalBranchHead,
|
||||
RemoteBranchHead,
|
||||
Tag,
|
||||
}
|
||||
|
||||
public class Decorator
|
||||
{
|
||||
public DecoratorType Type { get; set; } = DecoratorType.None;
|
||||
public string Name { get; set; } = "";
|
||||
}
|
||||
|
||||
public static class DecoratorResources
|
||||
{
|
||||
public static readonly IBrush[] Backgrounds = [
|
||||
new SolidColorBrush(0xFF02C302),
|
||||
new SolidColorBrush(0xFFFFB835),
|
||||
];
|
||||
}
|
||||
}
|
113
src/SourceGit/Models/DiffOption.cs
Normal file
113
src/SourceGit/Models/DiffOption.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class DiffOption
|
||||
{
|
||||
public Change WorkingCopyChange => _workingCopyChange;
|
||||
public bool IsUnstaged => _isUnstaged;
|
||||
public List<string> Revisions => _revisions;
|
||||
public string Path => _path;
|
||||
public string OrgPath => _orgPath;
|
||||
|
||||
/// <summary>
|
||||
/// Only used for working copy changes
|
||||
/// </summary>
|
||||
/// <param name="change"></param>
|
||||
/// <param name="isUnstaged"></param>
|
||||
public DiffOption(Change change, bool isUnstaged)
|
||||
{
|
||||
_workingCopyChange = change;
|
||||
_isUnstaged = isUnstaged;
|
||||
|
||||
if (isUnstaged)
|
||||
{
|
||||
switch (change.WorkTree)
|
||||
{
|
||||
case ChangeState.Added:
|
||||
case ChangeState.Untracked:
|
||||
_extra = "--no-index";
|
||||
_path = change.Path;
|
||||
_orgPath = "/dev/null";
|
||||
break;
|
||||
default:
|
||||
_path = change.Path;
|
||||
_orgPath = change.OriginalPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_extra = "--cached";
|
||||
_path = change.Path;
|
||||
_orgPath = change.OriginalPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only used for commit changes.
|
||||
/// </summary>
|
||||
/// <param name="commit"></param>
|
||||
/// <param name="change"></param>
|
||||
public DiffOption(Commit commit, Change change)
|
||||
{
|
||||
var baseRevision = commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{commit.SHA}^";
|
||||
_revisions.Add(baseRevision);
|
||||
_revisions.Add(commit.SHA);
|
||||
_path = change.Path;
|
||||
_orgPath = change.OriginalPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Diff with filepath. Used by FileHistories
|
||||
/// </summary>
|
||||
/// <param name="commit"></param>
|
||||
/// <param name="file"></param>
|
||||
public DiffOption(Commit commit, string file)
|
||||
{
|
||||
var baseRevision = commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{commit.SHA}^";
|
||||
_revisions.Add(baseRevision);
|
||||
_revisions.Add(commit.SHA);
|
||||
_path = file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to show differences between two revisions.
|
||||
/// </summary>
|
||||
/// <param name="baseRevision"></param>
|
||||
/// <param name="targetRevision"></param>
|
||||
/// <param name="change"></param>
|
||||
public DiffOption(string baseRevision, string targetRevision, Change change)
|
||||
{
|
||||
_revisions.Add(baseRevision);
|
||||
_revisions.Add(targetRevision);
|
||||
_path = change.Path;
|
||||
_orgPath = change.OriginalPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to diff command arguments.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(_extra)) builder.Append($"{_extra} ");
|
||||
foreach (var r in _revisions) builder.Append($"{r} ");
|
||||
|
||||
builder.Append("-- ");
|
||||
if (!string.IsNullOrEmpty(_orgPath)) builder.Append($"\"{_orgPath}\" ");
|
||||
builder.Append($"\"{_path}\"");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private readonly Change _workingCopyChange = null;
|
||||
private readonly bool _isUnstaged = false;
|
||||
private readonly string _orgPath = string.Empty;
|
||||
private readonly string _path = string.Empty;
|
||||
private readonly string _extra = string.Empty;
|
||||
private readonly List<string> _revisions = new List<string>();
|
||||
}
|
||||
}
|
560
src/SourceGit/Models/DiffResult.cs
Normal file
560
src/SourceGit/Models/DiffResult.cs
Normal file
|
@ -0,0 +1,560 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum TextDiffLineType
|
||||
{
|
||||
None,
|
||||
Normal,
|
||||
Indicator,
|
||||
Added,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
public class TextInlineRange
|
||||
{
|
||||
public int Start { get; set; }
|
||||
public int Count { get; set; }
|
||||
public TextInlineRange(int p, int n) { Start = p; Count = n; }
|
||||
}
|
||||
|
||||
public class TextDiffLine
|
||||
{
|
||||
public TextDiffLineType Type { get; set; } = TextDiffLineType.None;
|
||||
public string Content { get; set; } = "";
|
||||
public int OldLineNumber { get; set; } = 0;
|
||||
public int NewLineNumber { get; set; } = 0;
|
||||
public List<TextInlineRange> Highlights { get; set; } = new List<TextInlineRange>();
|
||||
|
||||
public string OldLine => OldLineNumber == 0 ? string.Empty : OldLineNumber.ToString();
|
||||
public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString();
|
||||
|
||||
public TextDiffLine() { }
|
||||
public TextDiffLine(TextDiffLineType type, string content, int oldLine, int newLine)
|
||||
{
|
||||
Type = type;
|
||||
Content = content;
|
||||
OldLineNumber = oldLine;
|
||||
NewLineNumber = newLine;
|
||||
}
|
||||
}
|
||||
|
||||
public class TextDiffSelection
|
||||
{
|
||||
public int StartLine { get; set; } = 0;
|
||||
public int EndLine { get; set; } = 0;
|
||||
public bool HasChanges { get; set; } = false;
|
||||
public bool HasLeftChanges { get; set; } = false;
|
||||
public int IgnoredAdds { get; set; } = 0;
|
||||
public int IgnoredDeletes { get; set; } = 0;
|
||||
|
||||
public bool IsInRange(int idx)
|
||||
{
|
||||
return idx >= StartLine - 1 && idx < EndLine;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class TextDiff
|
||||
{
|
||||
public string File { get; set; } = string.Empty;
|
||||
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
|
||||
public int MaxLineNumber = 0;
|
||||
|
||||
public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output)
|
||||
{
|
||||
var isTracked = !string.IsNullOrEmpty(fileBlobGuid);
|
||||
var fileGuid = isTracked ? fileBlobGuid.Substring(0, 8) : "00000000";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n');
|
||||
if (!revert && !isTracked) builder.Append("new file mode 100644\n");
|
||||
builder.Append("index 00000000...").Append(fileGuid).Append('\n');
|
||||
builder.Append("--- ").Append((revert || isTracked) ? $"a/{change.Path}\n" : "/dev/null\n");
|
||||
builder.Append("+++ b/").Append(change.Path).Append('\n');
|
||||
|
||||
var additions = selection.EndLine - selection.StartLine;
|
||||
if (selection.StartLine != 1) additions++;
|
||||
|
||||
if (revert)
|
||||
{
|
||||
var totalLines = Lines.Count - 1;
|
||||
builder.Append($"@@ -0,").Append(totalLines - additions).Append(" +0,").Append(totalLines).Append(" @@");
|
||||
for (int i = 1; i <= totalLines; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type != TextDiffLineType.Added) continue;
|
||||
builder.Append(selection.IsInRange(i) ? "\n+" : "\n ").Append(line.Content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("@@ -0,0 +0,").Append(additions).Append(" @@");
|
||||
for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type != TextDiffLineType.Added) continue;
|
||||
builder.Append("\n+").Append(line.Content);
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append("\n\\ No newline at end of file\n");
|
||||
System.IO.File.WriteAllText(output, builder.ToString());
|
||||
}
|
||||
|
||||
public void GeneratePatchFromSelection(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, string output)
|
||||
{
|
||||
var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n');
|
||||
builder.Append("index 00000000...").Append(fileTreeGuid).Append(" 100644\n");
|
||||
builder.Append("--- a/").Append(orgFile).Append('\n');
|
||||
builder.Append("+++ b/").Append(change.Path);
|
||||
|
||||
// If last line of selection is a change. Find one more line.
|
||||
var tail = null as string;
|
||||
if (selection.EndLine < Lines.Count)
|
||||
{
|
||||
var lastLine = Lines[selection.EndLine - 1];
|
||||
if (lastLine.Type == TextDiffLineType.Added || lastLine.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
for (int i = selection.EndLine; i < Lines.Count; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator) break;
|
||||
if (revert)
|
||||
{
|
||||
if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
tail = line.Content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
tail = line.Content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the first line is not indicator.
|
||||
if (Lines[selection.StartLine - 1].Type != TextDiffLineType.Indicator)
|
||||
{
|
||||
var indicator = selection.StartLine - 1;
|
||||
for (int i = selection.StartLine - 2; i >= 0; i--)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator)
|
||||
{
|
||||
indicator = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ignoreAdds = 0;
|
||||
var ignoreRemoves = 0;
|
||||
for (int i = 0; i < indicator; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
ignoreAdds++;
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
ignoreRemoves++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = indicator; i < selection.StartLine - 1; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator)
|
||||
{
|
||||
ProcessIndicatorForPatch(builder, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, tail != null);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
if (revert) builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
if (!revert) builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Normal)
|
||||
{
|
||||
builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Outputs the selected lines.
|
||||
for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator)
|
||||
{
|
||||
if (!ProcessIndicatorForPatch(builder, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, tail != null))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Normal)
|
||||
{
|
||||
builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
builder.Append("\n+").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
builder.Append("\n-").Append(line.Content);
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append("\n ").Append(tail);
|
||||
builder.Append("\n");
|
||||
System.IO.File.WriteAllText(output, builder.ToString());
|
||||
}
|
||||
|
||||
public void GeneratePatchFromSelectionSingleSide(Change change, string fileTreeGuid, TextDiffSelection selection, bool revert, bool isOldSide, string output)
|
||||
{
|
||||
var orgFile = !string.IsNullOrEmpty(change.OriginalPath) ? change.OriginalPath : change.Path;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n');
|
||||
builder.Append("index 00000000...").Append(fileTreeGuid).Append(" 100644\n");
|
||||
builder.Append("--- a/").Append(orgFile).Append('\n');
|
||||
builder.Append("+++ b/").Append(change.Path);
|
||||
|
||||
// If last line of selection is a change. Find one more line.
|
||||
var tail = null as string;
|
||||
if (selection.EndLine < Lines.Count)
|
||||
{
|
||||
var lastLine = Lines[selection.EndLine - 1];
|
||||
if (lastLine.Type == TextDiffLineType.Added || lastLine.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
for (int i = selection.EndLine; i < Lines.Count; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator) break;
|
||||
if (revert)
|
||||
{
|
||||
if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
tail = line.Content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.Type == TextDiffLineType.Normal || line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
tail = line.Content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the first line is not indicator.
|
||||
if (Lines[selection.StartLine - 1].Type != TextDiffLineType.Indicator)
|
||||
{
|
||||
var indicator = selection.StartLine - 1;
|
||||
for (int i = selection.StartLine - 2; i >= 0; i--)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator)
|
||||
{
|
||||
indicator = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ignoreAdds = 0;
|
||||
var ignoreRemoves = 0;
|
||||
for (int i = 0; i < indicator; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
ignoreAdds++;
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
ignoreRemoves++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = indicator; i < selection.StartLine - 1; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator)
|
||||
{
|
||||
ProcessIndicatorForPatchSingleSide(builder, line, i, selection.StartLine, selection.EndLine, ignoreRemoves, ignoreAdds, revert, isOldSide, tail != null);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
if (revert) builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
if (!revert) builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Normal)
|
||||
{
|
||||
builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Outputs the selected lines.
|
||||
for (int i = selection.StartLine - 1; i < selection.EndLine; i++)
|
||||
{
|
||||
var line = Lines[i];
|
||||
if (line.Type == TextDiffLineType.Indicator)
|
||||
{
|
||||
if (!ProcessIndicatorForPatchSingleSide(builder, line, i, selection.StartLine, selection.EndLine, selection.IgnoredDeletes, selection.IgnoredAdds, revert, isOldSide, tail != null))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Normal)
|
||||
{
|
||||
builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Added)
|
||||
{
|
||||
if (isOldSide)
|
||||
{
|
||||
if (revert)
|
||||
{
|
||||
builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
selection.IgnoredAdds++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("\n+").Append(line.Content);
|
||||
}
|
||||
}
|
||||
else if (line.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
if (isOldSide)
|
||||
{
|
||||
builder.Append("\n-").Append(line.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!revert)
|
||||
{
|
||||
builder.Append("\n ").Append(line.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
selection.IgnoredDeletes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append("\n ").Append(tail);
|
||||
builder.Append("\n");
|
||||
System.IO.File.WriteAllText(output, builder.ToString());
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
|
||||
private static partial Regex indicatorRegex();
|
||||
|
||||
private bool ProcessIndicatorForPatch(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed)
|
||||
{
|
||||
|
||||
|
||||
var match = indicatorRegex().Match(indicator.Content);
|
||||
var oldStart = int.Parse(match.Groups[1].Value);
|
||||
var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
|
||||
var oldCount = 0;
|
||||
var newCount = 0;
|
||||
for (int i = idx + 1; i < end; i++)
|
||||
{
|
||||
var test = Lines[i];
|
||||
if (test.Type == TextDiffLineType.Indicator) break;
|
||||
|
||||
if (test.Type == TextDiffLineType.Normal)
|
||||
{
|
||||
oldCount++;
|
||||
newCount++;
|
||||
}
|
||||
else if (test.Type == TextDiffLineType.Added)
|
||||
{
|
||||
if (i < start - 1)
|
||||
{
|
||||
if (revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newCount++;
|
||||
}
|
||||
|
||||
if (i == end - 1 && tailed)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else if (test.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
if (i < start - 1)
|
||||
{
|
||||
if (!revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
oldCount++;
|
||||
}
|
||||
|
||||
if (i == end - 1 && tailed)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldCount == 0 && newCount == 0) return false;
|
||||
|
||||
builder.Append($"\n@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessIndicatorForPatchSingleSide(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool isOldSide, bool tailed)
|
||||
{
|
||||
|
||||
var match = indicatorRegex().Match(indicator.Content);
|
||||
var oldStart = int.Parse(match.Groups[1].Value);
|
||||
var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
|
||||
var oldCount = 0;
|
||||
var newCount = 0;
|
||||
for (int i = idx + 1; i < end; i++)
|
||||
{
|
||||
var test = Lines[i];
|
||||
if (test.Type == TextDiffLineType.Indicator) break;
|
||||
|
||||
if (test.Type == TextDiffLineType.Normal)
|
||||
{
|
||||
oldCount++;
|
||||
newCount++;
|
||||
}
|
||||
else if (test.Type == TextDiffLineType.Added)
|
||||
{
|
||||
if (i < start - 1)
|
||||
{
|
||||
if (revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOldSide)
|
||||
{
|
||||
if (revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == end - 1 && tailed)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else if (test.Type == TextDiffLineType.Deleted)
|
||||
{
|
||||
if (i < start - 1)
|
||||
{
|
||||
if (!revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOldSide)
|
||||
{
|
||||
oldCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == end - 1 && tailed)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldCount == 0 && newCount == 0) return false;
|
||||
|
||||
builder.Append($"\n@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class LFSDiff
|
||||
{
|
||||
public LFSObject Old { get; set; } = new LFSObject();
|
||||
public LFSObject New { get; set; } = new LFSObject();
|
||||
}
|
||||
|
||||
public class BinaryDiff
|
||||
{
|
||||
public long OldSize { get; set; } = 0;
|
||||
public long NewSize { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class DiffResult
|
||||
{
|
||||
public bool IsBinary { get; set; } = false;
|
||||
public bool IsLFS { get; set; } = false;
|
||||
public TextDiff TextDiff { get; set; } = null;
|
||||
public LFSDiff LFSDiff { get; set; } = null;
|
||||
}
|
||||
}
|
66
src/SourceGit/Models/ExternalMergeTools.cs
Normal file
66
src/SourceGit/Models/ExternalMergeTools.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class ExternalMergeTools
|
||||
{
|
||||
public int Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Exec { get; set; }
|
||||
public string Cmd { get; set; }
|
||||
public string DiffCmd { get; set; }
|
||||
|
||||
public static List<ExternalMergeTools> Supported;
|
||||
|
||||
static ExternalMergeTools()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
Supported = new List<ExternalMergeTools>() {
|
||||
new ExternalMergeTools(0, "Custom", "", "", ""),
|
||||
new ExternalMergeTools(1, "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(2, "Visual Studio 2017/2019", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(3, "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""),
|
||||
new ExternalMergeTools(4, "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(5, "Beyond Compare 4", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(6, "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""),
|
||||
};
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Supported = new List<ExternalMergeTools>() {
|
||||
new ExternalMergeTools(0, "Custom", "", "", ""),
|
||||
new ExternalMergeTools(1, "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(2, "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(3, "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(4, "Beyond Compare 4", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
};
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
Supported = new List<ExternalMergeTools>() {
|
||||
new ExternalMergeTools(0, "Custom", "", "", ""),
|
||||
new ExternalMergeTools(1, "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(2, "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
new ExternalMergeTools(3, "Beyond Compare 4", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Supported = new List<ExternalMergeTools>() {
|
||||
new ExternalMergeTools(0, "Custom", "", "", ""),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public ExternalMergeTools(int type, string name, string exec, string cmd, string diffCmd)
|
||||
{
|
||||
Type = type;
|
||||
Name = name;
|
||||
Exec = exec;
|
||||
Cmd = cmd;
|
||||
DiffCmd = diffCmd;
|
||||
}
|
||||
}
|
||||
}
|
36
src/SourceGit/Models/GitFlow.cs
Normal file
36
src/SourceGit/Models/GitFlow.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum GitFlowBranchType
|
||||
{
|
||||
None,
|
||||
Feature,
|
||||
Release,
|
||||
Hotfix,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
8
src/SourceGit/Models/LFSObject.cs
Normal file
8
src/SourceGit/Models/LFSObject.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class LFSObject
|
||||
{
|
||||
public string Oid { get; set; } = string.Empty;
|
||||
public long Size { get; set; } = 0;
|
||||
}
|
||||
}
|
21
src/SourceGit/Models/Locales.cs
Normal file
21
src/SourceGit/Models/Locales.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class Locale
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Key { get; set; }
|
||||
|
||||
public static List<Locale> Supported = new List<Locale>() {
|
||||
new Locale("English", "en_US"),
|
||||
new Locale("简体中文", "zh_CN"),
|
||||
};
|
||||
|
||||
public Locale(string name, string key)
|
||||
{
|
||||
Name = name;
|
||||
Key = key;
|
||||
}
|
||||
}
|
||||
}
|
13
src/SourceGit/Models/Notification.cs
Normal file
13
src/SourceGit/Models/Notification.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class Notification
|
||||
{
|
||||
public bool IsError { get; set; } = false;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public interface INotificationReceiver
|
||||
{
|
||||
void OnReceiveNotification(string ctx, Notification notice);
|
||||
}
|
||||
}
|
18
src/SourceGit/Models/Object.cs
Normal file
18
src/SourceGit/Models/Object.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum ObjectType
|
||||
{
|
||||
None,
|
||||
Blob,
|
||||
Tree,
|
||||
Tag,
|
||||
Commit,
|
||||
}
|
||||
|
||||
public class Object
|
||||
{
|
||||
public string SHA { get; set; }
|
||||
public ObjectType Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
46
src/SourceGit/Models/Remote.cs
Normal file
46
src/SourceGit/Models/Remote.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public partial class Remote
|
||||
{
|
||||
|
||||
[GeneratedRegex(@"^http[s]?://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-]+/[\w\-\.]+\.git$")]
|
||||
private static partial Regex regex1();
|
||||
|
||||
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:[\w\-]+/[\w\-\.]+\.git$")]
|
||||
private static partial Regex regex2();
|
||||
[GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/[\w\-]+/[\w\-\.]+\.git$")]
|
||||
private static partial Regex regex3();
|
||||
|
||||
private static readonly Regex[] URL_FORMATS = [
|
||||
regex1(),
|
||||
regex2(),
|
||||
regex3(),
|
||||
];
|
||||
|
||||
public string Name { get; set; }
|
||||
public string URL { get; set; }
|
||||
|
||||
public static bool IsSSH(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url)) return false;
|
||||
|
||||
for (int i = 1; i < URL_FORMATS.Length; i++)
|
||||
{
|
||||
if (URL_FORMATS[i].IsMatch(url)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsValidURL(string url)
|
||||
{
|
||||
foreach (var fmt in URL_FORMATS)
|
||||
{
|
||||
if (fmt.IsMatch(url)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
23
src/SourceGit/Models/RevisionFile.cs
Normal file
23
src/SourceGit/Models/RevisionFile.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class RevisionBinaryFile
|
||||
{
|
||||
public long Size { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class RevisionTextFile
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class RevisionLFSObject
|
||||
{
|
||||
public LFSObject Object { get; set; }
|
||||
}
|
||||
|
||||
public class RevisionSubmodule
|
||||
{
|
||||
public string SHA { get; set; }
|
||||
}
|
||||
}
|
17
src/SourceGit/Models/Stash.cs
Normal file
17
src/SourceGit/Models/Stash.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class Stash
|
||||
{
|
||||
private static readonly DateTime UTC_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();
|
||||
|
||||
public string Name { get; set; } = "";
|
||||
public string SHA { get; set; } = "";
|
||||
public User Author { get; set; } = User.Invalid;
|
||||
public ulong Time { get; set; } = 0;
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
public string TimeStr => UTC_START.AddSeconds(Time).ToString("yyyy/MM/dd HH:mm:ss");
|
||||
}
|
||||
}
|
144
src/SourceGit/Models/Statistics.cs
Normal file
144
src/SourceGit/Models/Statistics.cs
Normal file
|
@ -0,0 +1,144 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class StatisticsSample
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
public class StatisticsReport
|
||||
{
|
||||
public int Total { get; set; } = 0;
|
||||
public List<StatisticsSample> Samples { get; set; } = new List<StatisticsSample>();
|
||||
public List<StatisticsSample> ByCommitter { get; set; } = new List<StatisticsSample>();
|
||||
|
||||
public void AddCommit(int index, string committer)
|
||||
{
|
||||
Total++;
|
||||
Samples[index].Count++;
|
||||
|
||||
if (_mapByCommitter.ContainsKey(committer))
|
||||
{
|
||||
_mapByCommitter[committer].Count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sample = new StatisticsSample() { Name = committer, Count = 1 };
|
||||
_mapByCommitter.Add(committer, sample);
|
||||
ByCommitter.Add(sample);
|
||||
}
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
ByCommitter.Sort((l, r) => r.Count - l.Count);
|
||||
_mapByCommitter.Clear();
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, StatisticsSample> _mapByCommitter = new Dictionary<string, StatisticsSample>();
|
||||
}
|
||||
|
||||
public class Statistics
|
||||
{
|
||||
public StatisticsReport Year { get; set; } = new StatisticsReport();
|
||||
public StatisticsReport Month { get; set; } = new StatisticsReport();
|
||||
public StatisticsReport Week { get; set; } = new StatisticsReport();
|
||||
|
||||
public Statistics()
|
||||
{
|
||||
_utcStart = DateTime.UnixEpoch;
|
||||
_today = DateTime.Today;
|
||||
_thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24 - _today.Hour * 3600 - _today.Minute * 60 - _today.Second);
|
||||
_thisWeekEnd = _thisWeekStart.AddDays(7);
|
||||
|
||||
string[] monthNames = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
for (int i = 0; i < monthNames.Length; i++)
|
||||
{
|
||||
Year.Samples.Add(new StatisticsSample
|
||||
{
|
||||
Name = monthNames[i],
|
||||
Count = 0,
|
||||
});
|
||||
}
|
||||
|
||||
var monthDays = DateTime.DaysInMonth(_today.Year, _today.Month);
|
||||
for (int i = 0; i < monthDays; i++)
|
||||
{
|
||||
Month.Samples.Add(new StatisticsSample
|
||||
{
|
||||
Name = $"{i + 1}",
|
||||
Count = 0,
|
||||
});
|
||||
}
|
||||
|
||||
string[] weekDayNames = [
|
||||
"SUN",
|
||||
"MON",
|
||||
"TUE",
|
||||
"WED",
|
||||
"THU",
|
||||
"FRI",
|
||||
"SAT",
|
||||
];
|
||||
|
||||
for (int i = 0; i < weekDayNames.Length; i++)
|
||||
{
|
||||
Week.Samples.Add(new StatisticsSample
|
||||
{
|
||||
Name = weekDayNames[i],
|
||||
Count = 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public string Since()
|
||||
{
|
||||
return _today.ToString("yyyy-01-01 00:00:00");
|
||||
}
|
||||
|
||||
public void AddCommit(string committer, double timestamp)
|
||||
{
|
||||
var time = _utcStart.AddSeconds(timestamp).ToLocalTime();
|
||||
if (time.CompareTo(_thisWeekStart) >= 0 && time.CompareTo(_thisWeekEnd) < 0)
|
||||
{
|
||||
Week.AddCommit((int)time.DayOfWeek, committer);
|
||||
}
|
||||
|
||||
if (time.Month == _today.Month)
|
||||
{
|
||||
Month.AddCommit(time.Day - 1, committer);
|
||||
}
|
||||
|
||||
Year.AddCommit(time.Month - 1, committer);
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
Year.Complete();
|
||||
Month.Complete();
|
||||
Week.Complete();
|
||||
}
|
||||
|
||||
private readonly DateTime _utcStart;
|
||||
private readonly DateTime _today;
|
||||
private readonly DateTime _thisWeekStart;
|
||||
private readonly DateTime _thisWeekEnd;
|
||||
}
|
||||
}
|
9
src/SourceGit/Models/Tag.cs
Normal file
9
src/SourceGit/Models/Tag.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class Tag
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string SHA { get; set; }
|
||||
public bool IsFiltered { get; set; }
|
||||
}
|
||||
}
|
320
src/SourceGit/Models/TextInlineChange.cs
Normal file
320
src/SourceGit/Models/TextInlineChange.cs
Normal file
|
@ -0,0 +1,320 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class TextInlineChange
|
||||
{
|
||||
public int DeletedStart { get; set; }
|
||||
public int DeletedCount { get; set; }
|
||||
public int AddedStart { get; set; }
|
||||
public int AddedCount { get; set; }
|
||||
|
||||
class Chunk
|
||||
{
|
||||
public int Hash;
|
||||
public bool Modified;
|
||||
public int Start;
|
||||
public int Size;
|
||||
|
||||
public Chunk(int hash, int start, int size)
|
||||
{
|
||||
Hash = hash;
|
||||
Modified = false;
|
||||
Start = start;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
enum Edit
|
||||
{
|
||||
None,
|
||||
DeletedRight,
|
||||
DeletedLeft,
|
||||
AddedRight,
|
||||
AddedLeft,
|
||||
}
|
||||
|
||||
class EditResult
|
||||
{
|
||||
public Edit State;
|
||||
public int DeleteStart;
|
||||
public int DeleteEnd;
|
||||
public int AddStart;
|
||||
public int AddEnd;
|
||||
}
|
||||
|
||||
public TextInlineChange(int dp, int dc, int ap, int ac)
|
||||
{
|
||||
DeletedStart = dp;
|
||||
DeletedCount = dc;
|
||||
AddedStart = ap;
|
||||
AddedCount = ac;
|
||||
}
|
||||
|
||||
public static List<TextInlineChange> Compare(string oldValue, string newValue)
|
||||
{
|
||||
var hashes = new Dictionary<string, int>();
|
||||
var chunksOld = MakeChunks(hashes, oldValue);
|
||||
var chunksNew = MakeChunks(hashes, newValue);
|
||||
var sizeOld = chunksOld.Count;
|
||||
var sizeNew = chunksNew.Count;
|
||||
var max = sizeOld + sizeNew + 2;
|
||||
var forward = new int[max];
|
||||
var reverse = new int[max];
|
||||
CheckModified(chunksOld, 0, sizeOld, chunksNew, 0, sizeNew, forward, reverse);
|
||||
|
||||
var ret = new List<TextInlineChange>();
|
||||
var posOld = 0;
|
||||
var posNew = 0;
|
||||
var last = null as TextInlineChange;
|
||||
do
|
||||
{
|
||||
while (posOld < sizeOld && posNew < sizeNew && !chunksOld[posOld].Modified && !chunksNew[posNew].Modified)
|
||||
{
|
||||
posOld++;
|
||||
posNew++;
|
||||
}
|
||||
|
||||
var beginOld = posOld;
|
||||
var beginNew = posNew;
|
||||
var countOld = 0;
|
||||
var countNew = 0;
|
||||
for (; posOld < sizeOld && chunksOld[posOld].Modified; posOld++) countOld += chunksOld[posOld].Size;
|
||||
for (; posNew < sizeNew && chunksNew[posNew].Modified; posNew++) countNew += chunksNew[posNew].Size;
|
||||
|
||||
if (countOld + countNew == 0) continue;
|
||||
|
||||
var diff = new TextInlineChange(
|
||||
countOld > 0 ? chunksOld[beginOld].Start : 0,
|
||||
countOld,
|
||||
countNew > 0 ? chunksNew[beginNew].Start : 0,
|
||||
countNew);
|
||||
if (last != null)
|
||||
{
|
||||
var midSizeOld = diff.DeletedStart - last.DeletedStart - last.DeletedCount;
|
||||
var midSizeNew = diff.AddedStart - last.AddedStart - last.AddedCount;
|
||||
if (midSizeOld == 1 && midSizeNew == 1)
|
||||
{
|
||||
last.DeletedCount += (1 + countOld);
|
||||
last.AddedCount += (1 + countNew);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
last = diff;
|
||||
ret.Add(diff);
|
||||
} while (posOld < sizeOld && posNew < sizeNew);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static List<Chunk> MakeChunks(Dictionary<string, int> hashes, string text)
|
||||
{
|
||||
var start = 0;
|
||||
var size = text.Length;
|
||||
var chunks = new List<Chunk>();
|
||||
var delims = new HashSet<char>(" \t+-*/=!,:;.'\"/?|&#@%`<>()[]{}\\".ToCharArray());
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
var ch = text[i];
|
||||
if (delims.Contains(ch))
|
||||
{
|
||||
if (start != i) AddChunk(chunks, hashes, text.Substring(start, i - start), start);
|
||||
AddChunk(chunks, hashes, text.Substring(i, 1), i);
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (start < size) AddChunk(chunks, hashes, text.Substring(start), start);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private static void CheckModified(List<Chunk> chunksOld, int startOld, int endOld, List<Chunk> chunksNew, int startNew, int endNew, int[] forward, int[] reverse)
|
||||
{
|
||||
while (startOld < endOld && startNew < endNew && chunksOld[startOld].Hash == chunksNew[startNew].Hash)
|
||||
{
|
||||
startOld++;
|
||||
startNew++;
|
||||
}
|
||||
|
||||
while (startOld < endOld && startNew < endNew && chunksOld[endOld - 1].Hash == chunksNew[endNew - 1].Hash)
|
||||
{
|
||||
endOld--;
|
||||
endNew--;
|
||||
}
|
||||
|
||||
var lenOld = endOld - startOld;
|
||||
var lenNew = endNew - startNew;
|
||||
if (lenOld > 0 && lenNew > 0)
|
||||
{
|
||||
var rs = CheckModifiedEdit(chunksOld, startOld, endOld, chunksNew, startNew, endNew, forward, reverse);
|
||||
if (rs.State == Edit.None) return;
|
||||
|
||||
if (rs.State == Edit.DeletedRight && rs.DeleteStart - 1 > startOld)
|
||||
{
|
||||
chunksOld[--rs.DeleteStart].Modified = true;
|
||||
}
|
||||
else if (rs.State == Edit.DeletedLeft && rs.DeleteEnd < endOld)
|
||||
{
|
||||
chunksOld[rs.DeleteEnd++].Modified = true;
|
||||
}
|
||||
else if (rs.State == Edit.AddedRight && rs.AddStart - 1 > startNew)
|
||||
{
|
||||
chunksNew[--rs.AddStart].Modified = true;
|
||||
}
|
||||
else if (rs.State == Edit.AddedLeft && rs.AddEnd < endNew)
|
||||
{
|
||||
chunksNew[rs.AddEnd++].Modified = true;
|
||||
}
|
||||
|
||||
CheckModified(chunksOld, startOld, rs.DeleteStart, chunksNew, startNew, rs.AddStart, forward, reverse);
|
||||
CheckModified(chunksOld, rs.DeleteEnd, endOld, chunksNew, rs.AddEnd, endNew, forward, reverse);
|
||||
}
|
||||
else if (lenOld > 0)
|
||||
{
|
||||
for (int i = startOld; i < endOld; i++) chunksOld[i].Modified = true;
|
||||
}
|
||||
else if (lenNew > 0)
|
||||
{
|
||||
for (int i = startNew; i < endNew; i++) chunksNew[i].Modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static EditResult CheckModifiedEdit(List<Chunk> chunksOld, int startOld, int endOld, List<Chunk> chunksNew, int startNew, int endNew, int[] forward, int[] reverse)
|
||||
{
|
||||
var lenOld = endOld - startOld;
|
||||
var lenNew = endNew - startNew;
|
||||
var max = lenOld + lenNew + 1;
|
||||
var half = max / 2;
|
||||
var delta = lenOld - lenNew;
|
||||
var deltaEven = delta % 2 == 0;
|
||||
var rs = new EditResult() { State = Edit.None };
|
||||
|
||||
forward[1 + half] = 0;
|
||||
reverse[1 + half] = lenOld + 1;
|
||||
|
||||
for (int i = 0; i <= half; i++)
|
||||
{
|
||||
|
||||
for (int j = -i; j <= i; j += 2)
|
||||
{
|
||||
var idx = j + half;
|
||||
int o, n;
|
||||
if (j == -i || (j != i && forward[idx - 1] < forward[idx + 1]))
|
||||
{
|
||||
o = forward[idx + 1];
|
||||
rs.State = Edit.AddedRight;
|
||||
}
|
||||
else
|
||||
{
|
||||
o = forward[idx - 1] + 1;
|
||||
rs.State = Edit.DeletedRight;
|
||||
}
|
||||
|
||||
n = o - j;
|
||||
|
||||
var startX = o;
|
||||
var startY = n;
|
||||
while (o < lenOld && n < lenNew && chunksOld[o + startOld].Hash == chunksNew[n + startNew].Hash)
|
||||
{
|
||||
o++;
|
||||
n++;
|
||||
}
|
||||
|
||||
forward[idx] = o;
|
||||
|
||||
if (!deltaEven && j - delta >= -i + 1 && j - delta <= i - 1)
|
||||
{
|
||||
var revIdx = (j - delta) + half;
|
||||
var revOld = reverse[revIdx];
|
||||
int revNew = revOld - j;
|
||||
if (revOld <= o && revNew <= n)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
rs.State = Edit.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
rs.DeleteStart = startX + startOld;
|
||||
rs.DeleteEnd = o + startOld;
|
||||
rs.AddStart = startY + startNew;
|
||||
rs.AddEnd = n + startNew;
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = -i; j <= i; j += 2)
|
||||
{
|
||||
var idx = j + half;
|
||||
int o, n;
|
||||
if (j == -i || (j != i && reverse[idx + 1] <= reverse[idx - 1]))
|
||||
{
|
||||
o = reverse[idx + 1] - 1;
|
||||
rs.State = Edit.DeletedLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
o = reverse[idx - 1];
|
||||
rs.State = Edit.AddedLeft;
|
||||
}
|
||||
|
||||
n = o - (j + delta);
|
||||
|
||||
var endX = o;
|
||||
var endY = n;
|
||||
while (o > 0 && n > 0 && chunksOld[startOld + o - 1].Hash == chunksNew[startNew + n - 1].Hash)
|
||||
{
|
||||
o--;
|
||||
n--;
|
||||
}
|
||||
|
||||
reverse[idx] = o;
|
||||
|
||||
if (deltaEven && j + delta >= -i && j + delta <= i)
|
||||
{
|
||||
var forIdx = (j + delta) + half;
|
||||
var forOld = forward[forIdx];
|
||||
int forNew = forOld - (j + delta);
|
||||
if (forOld >= o && forNew >= n)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
rs.State = Edit.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
rs.DeleteStart = o + startOld;
|
||||
rs.DeleteEnd = endX + startOld;
|
||||
rs.AddStart = n + startNew;
|
||||
rs.AddEnd = endY + startNew;
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rs.State = Edit.None;
|
||||
return rs;
|
||||
}
|
||||
|
||||
private static void AddChunk(List<Chunk> chunks, Dictionary<string, int> hashes, string data, int start)
|
||||
{
|
||||
int hash;
|
||||
if (hashes.TryGetValue(data, out hash))
|
||||
{
|
||||
chunks.Add(new Chunk(hash, start, data.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = hashes.Count;
|
||||
hashes.Add(data, hash);
|
||||
chunks.Add(new Chunk(hash, start, data.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
src/SourceGit/Models/User.cs
Normal file
44
src/SourceGit/Models/User.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public static User Invalid = new User();
|
||||
public static Dictionary<string, User> Caches = new Dictionary<string, User>();
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || !(obj is User)) return false;
|
||||
|
||||
var other = obj as User;
|
||||
return Name == other.Name && Email == other.Email;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public static User FindOrAdd(string data)
|
||||
{
|
||||
if (Caches.ContainsKey(data))
|
||||
{
|
||||
return Caches[data];
|
||||
}
|
||||
else
|
||||
{
|
||||
var nameEndIdx = data.IndexOf('<', System.StringComparison.Ordinal);
|
||||
var name = nameEndIdx >= 2 ? data.Substring(0, nameEndIdx - 1) : string.Empty;
|
||||
var email = data.Substring(nameEndIdx + 1);
|
||||
|
||||
User user = new User() { Name = name, Email = email };
|
||||
Caches.Add(data, user);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
204
src/SourceGit/Models/Watcher.cs
Normal file
204
src/SourceGit/Models/Watcher.cs
Normal file
|
@ -0,0 +1,204 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public interface IRepository
|
||||
{
|
||||
string FullPath { get; set; }
|
||||
string GitDir { get; set; }
|
||||
|
||||
void RefreshBranches();
|
||||
void RefreshTags();
|
||||
void RefreshCommits();
|
||||
void RefreshSubmodules();
|
||||
void RefreshWorkingCopyChanges();
|
||||
void RefreshStashes();
|
||||
}
|
||||
|
||||
public class Watcher : IDisposable
|
||||
{
|
||||
public Watcher(IRepository repo)
|
||||
{
|
||||
_repo = repo;
|
||||
|
||||
_wcWatcher = new FileSystemWatcher();
|
||||
_wcWatcher.Path = _repo.FullPath;
|
||||
_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 = _repo.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);
|
||||
}
|
||||
|
||||
public void SetEnabled(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
if (_lockCount > 0) _lockCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lockCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkBranchDirtyManually()
|
||||
{
|
||||
_updateBranch = DateTime.Now.ToFileTime() - 1;
|
||||
}
|
||||
|
||||
public void MarkWorkingCopyDirtyManually()
|
||||
{
|
||||
_updateWC = DateTime.Now.ToFileTime() - 1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_repoWatcher.EnableRaisingEvents = false;
|
||||
_repoWatcher.Created -= OnRepositoryChanged;
|
||||
_repoWatcher.Renamed -= OnRepositoryChanged;
|
||||
_repoWatcher.Changed -= OnRepositoryChanged;
|
||||
_repoWatcher.Deleted -= OnRepositoryChanged;
|
||||
_repoWatcher.Dispose();
|
||||
_repoWatcher = null;
|
||||
|
||||
_wcWatcher.EnableRaisingEvents = false;
|
||||
_wcWatcher.Created -= OnWorkingCopyChanged;
|
||||
_wcWatcher.Renamed -= OnWorkingCopyChanged;
|
||||
_wcWatcher.Changed -= OnWorkingCopyChanged;
|
||||
_wcWatcher.Deleted -= OnWorkingCopyChanged;
|
||||
_wcWatcher.Dispose();
|
||||
_wcWatcher = null;
|
||||
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void Tick(object sender)
|
||||
{
|
||||
if (_lockCount > 0) return;
|
||||
|
||||
var now = DateTime.Now.ToFileTime();
|
||||
if (_updateBranch > 0 && now > _updateBranch)
|
||||
{
|
||||
_updateBranch = 0;
|
||||
_updateWC = 0;
|
||||
|
||||
if (_updateTags > 0)
|
||||
{
|
||||
_updateTags = 0;
|
||||
Task.Run(() =>
|
||||
{
|
||||
_repo.RefreshTags();
|
||||
_repo.RefreshBranches();
|
||||
_repo.RefreshCommits();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
_repo.RefreshBranches();
|
||||
_repo.RefreshCommits();
|
||||
});
|
||||
}
|
||||
|
||||
Task.Run(_repo.RefreshWorkingCopyChanges);
|
||||
}
|
||||
|
||||
if (_updateWC > 0 && now > _updateWC)
|
||||
{
|
||||
_updateWC = 0;
|
||||
Task.Run(_repo.RefreshWorkingCopyChanges);
|
||||
}
|
||||
|
||||
if (_updateSubmodules > 0 && now > _updateSubmodules)
|
||||
{
|
||||
_updateSubmodules = 0;
|
||||
_repo.RefreshSubmodules();
|
||||
}
|
||||
|
||||
if (_updateStashes > 0 && now > _updateStashes)
|
||||
{
|
||||
_updateStashes = 0;
|
||||
_repo.RefreshStashes();
|
||||
}
|
||||
|
||||
if (_updateTags > 0 && now > _updateTags)
|
||||
{
|
||||
_updateTags = 0;
|
||||
_repo.RefreshTags();
|
||||
_repo.RefreshCommits();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRepositoryChanged(object o, FileSystemEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Name)) return;
|
||||
|
||||
var name = e.Name.Replace("\\", "/");
|
||||
if (name.StartsWith("modules", StringComparison.Ordinal))
|
||||
{
|
||||
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||
}
|
||||
else if (name.StartsWith("refs/tags", StringComparison.Ordinal))
|
||||
{
|
||||
_updateTags = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||
}
|
||||
else if (name.StartsWith("refs/stash", StringComparison.Ordinal))
|
||||
{
|
||||
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||
}
|
||||
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
||||
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
|
||||
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
|
||||
name.StartsWith("worktrees/", StringComparison.Ordinal))
|
||||
{
|
||||
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||
}
|
||||
else if (name.StartsWith("objects/", StringComparison.Ordinal) || name.Equals("index", StringComparison.Ordinal))
|
||||
{
|
||||
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWorkingCopyChanged(object o, FileSystemEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Name)) return;
|
||||
|
||||
var name = e.Name.Replace("\\", "/");
|
||||
if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal)) return;
|
||||
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||
}
|
||||
|
||||
private readonly IRepository _repo = null;
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue