Upgrade project style

This commit is contained in:
ONEO 2020-07-06 10:23:45 +08:00
parent afd22ca85d
commit baa6c1445a
136 changed files with 29 additions and 770 deletions

View file

@ -0,0 +1,274 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace SourceGit.Helpers {
/// <summary>
/// Tools to parse commit graph.
/// </summary>
public class CommitGraphMaker {
/// <summary>
/// Sizes
/// </summary>
public static readonly double UNIT_WIDTH = 12;
public static readonly double HALF_WIDTH = 6;
public static readonly double DOUBLE_WIDTH = 24;
public static readonly double UNIT_HEIGHT = 24;
public static readonly double HALF_HEIGHT = 12;
/// <summary>
/// Colors
/// </summary>
public static Brush[] Colors = new Brush[] {
Brushes.Orange,
Brushes.ForestGreen,
Brushes.Gold,
Brushes.Magenta,
Brushes.Red,
Brushes.Gray,
Brushes.Turquoise,
Brushes.Olive,
};
/// <summary>
/// Helpers to draw lines.
/// </summary>
public class LineHelper {
private double lastX = 0;
private double lastY = 0;
/// <summary>
/// Parent commit id.
/// </summary>
public string Next { get; set; }
/// <summary>
/// Is merged into this tree.
/// </summary>
public bool IsMerged { get; set; }
/// <summary>
/// Points in line
/// </summary>
public List<Point> Points { get; set; }
/// <summary>
/// Brush to draw line
/// </summary>
public Brush Brush { get; set; }
/// <summary>
/// Current horizontal offset.
/// </summary>
public double HorizontalOffset => lastX;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="nextCommitId">Parent commit id</param>
/// <param name="isMerged">Is merged in tree</param>
/// <param name="colorIdx">Color index</param>
/// <param name="startPoint">Start point</param>
public LineHelper(string nextCommitId, bool isMerged, int colorIdx, Point startPoint) {
Next = nextCommitId;
IsMerged = isMerged;
Points = new List<Point>() { startPoint };
Brush = Colors[colorIdx % Colors.Length];
lastX = startPoint.X;
lastY = startPoint.Y;
}
/// <summary>
/// Line to.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="isEnd"></param>
public void AddPoint(double x, double y, bool isEnd = false) {
if (x > lastX) {
Points.Add(new Point(lastX, lastY));
Points.Add(new Point(x, y - HALF_HEIGHT));
} else if (x < lastX) {
Points.Add(new Point(lastX, lastY + HALF_HEIGHT));
Points.Add(new Point(x, y));
}
lastX = x;
lastY = y;
if (isEnd) {
var last = Points.Last();
if (last.X != lastX || last.Y != lastY) Points.Add(new Point(lastX, lastY));
}
}
}
/// <summary>
/// Short link between two commits.
/// </summary>
public struct ShortLink {
public Point Start;
public Point Control;
public Point End;
public Brush Brush;
}
/// <summary>
/// Dot
/// </summary>
public struct Dot {
public double X;
public double Y;
public Brush Color;
}
/// <summary>
/// Independent lines in graph
/// </summary>
public List<LineHelper> Lines { get; set; } = new List<LineHelper>();
/// <summary>
/// Short links.
/// </summary>
public List<ShortLink> Links { get; set; } = new List<ShortLink>();
/// <summary>
/// All dots.
/// </summary>
public List<Dot> Dots { get; set; } = new List<Dot>();
/// <summary>
/// Highlight commit id.
/// </summary>
public string Highlight { get; set; }
/// <summary>
/// Parse commits.
/// </summary>
/// <param name="commits"></param>
/// <returns></returns>
public static CommitGraphMaker Parse(List<Git.Commit> commits) {
CommitGraphMaker maker = new CommitGraphMaker();
List<LineHelper> unsolved = new List<LineHelper>();
List<LineHelper> ended = new List<LineHelper>();
Dictionary<string, LineHelper> currentMap = new Dictionary<string, LineHelper>();
double offsetY = -HALF_HEIGHT;
int colorIdx = 0;
for (int i = 0; i < commits.Count; i++) {
Git.Commit commit = commits[i];
LineHelper major = null;
bool isMerged = commit.IsHEAD || commit.IsMerged;
int oldCount = unsolved.Count;
// 更新Y坐标
offsetY += UNIT_HEIGHT;
// 找到当前的分支的HEAD用于默认选中
if (maker.Highlight == null && commit.IsHEAD) {
maker.Highlight = commit.SHA;
}
// 找到第一个依赖于本提交的树,将其他依赖于本提交的树标记为终止,并对已存在的线路调整(防止线重合)
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 (!currentMap.ContainsKey(major.Next)) currentMap.Add(major.Next, major);
} else {
major.Next = "ENDED";
}
major.AddPoint(offsetX, offsetY);
} else {
ended.Add(l);
}
isMerged = isMerged || l.IsMerged;
} else {
if (!currentMap.ContainsKey(l.Next)) currentMap.Add(l.Next, l);
offsetX += UNIT_WIDTH;
l.AddPoint(offsetX, offsetY);
}
}
// 处理本提交为非当前分支HEAD的情况创建新依赖线路
if (major == null && commit.Parents.Count > 0) {
offsetX += UNIT_WIDTH;
major = new LineHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
unsolved.Add(major);
colorIdx++;
}
// 确定本提交的点的位置
Point position = new Point(offsetX, offsetY);
if (major != null) {
major.IsMerged = isMerged;
position.X = major.HorizontalOffset;
position.Y = offsetY;
maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = major.Brush });
} else {
maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = Brushes.Orange });
}
// 处理本提交的其他依赖
for (int j = 1; j < commit.Parents.Count; j++) {
var parent = commit.Parents[j];
if (currentMap.ContainsKey(parent)) {
var l = currentMap[parent];
var link = new ShortLink();
link.Start = position;
link.End = new Point(l.HorizontalOffset, offsetY + HALF_HEIGHT);
link.Control = new Point(link.End.X, link.Start.Y);
link.Brush = l.Brush;
maker.Links.Add(link);
} else {
offsetX += UNIT_WIDTH;
unsolved.Add(new LineHelper(commit.Parents[j], isMerged, colorIdx, position));
colorIdx++;
}
}
// 处理已终止的线
foreach (var l in ended) {
l.AddPoint(position.X, position.Y, true);
maker.Lines.Add(l);
unsolved.Remove(l);
}
// 加入本次提交
commit.IsMerged = isMerged;
commit.GraphOffset = System.Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH);
// 清理临时数据
ended.Clear();
currentMap.Clear();
}
// 处理尚未终结的线
for (int i = 0; i < unsolved.Count; i++) {
var path = unsolved[i];
path.AddPoint((i + 0.5) * UNIT_WIDTH, (commits.Count - 0.5) * UNIT_HEIGHT, true);
maker.Lines.Add(path);
}
unsolved.Clear();
// 处理默认选中异常
if (maker.Highlight == null && commits.Count > 0) {
maker.Highlight = commits[0].SHA;
}
return maker;
}
}
}

View file

@ -0,0 +1,143 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.Helpers {
/// <summary>
/// Attached properties to TextBox.
/// </summary>
public static class TextBoxHelper {
/// <summary>
/// Placeholder property
/// </summary>
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached(
"Placeholder",
typeof(string),
typeof(TextBoxHelper),
new PropertyMetadata(string.Empty, OnPlaceholderChanged));
/// <summary>
/// Vertical alignment for placeholder.
/// </summary>
public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached(
"PlaceholderBaseline",
typeof(AlignmentY),
typeof(TextBoxHelper),
new PropertyMetadata(AlignmentY.Center));
/// <summary>
/// Property to store generated placeholder brush.
/// </summary>
public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached(
"PlaceholderBrush",
typeof(Brush),
typeof(TextBoxHelper),
new PropertyMetadata(Brushes.Transparent));
/// <summary>
/// Triggered when placeholder changed.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var textBox = d as TextBox;
if (textBox != null) textBox.Loaded += OnTextLoaded;
}
/// <summary>
/// Setter for Placeholder property
/// </summary>
/// <param name="element"></param>
/// <param name="value"></param>
public static void SetPlaceholder(UIElement element, string value) {
element.SetValue(PlaceholderProperty, value);
}
/// <summary>
/// Getter for Placeholder property
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static string GetPlaceholder(UIElement element) {
return (string)element.GetValue(PlaceholderProperty);
}
/// <summary>
/// Setter for PlaceholderBaseline property
/// </summary>
/// <param name="element"></param>
/// <param name="align"></param>
public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) {
element.SetValue(PlaceholderBaselineProperty, align);
}
/// <summary>
/// Setter for PlaceholderBaseline property.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static AlignmentY GetPlaceholderBaseline(UIElement element) {
return (AlignmentY)element.GetValue(PlaceholderBaselineProperty);
}
/// <summary>
/// Setter for PlaceholderBrush property.
/// </summary>
/// <param name="element"></param>
/// <param name="value"></param>
public static void SetPlaceholderBrush(UIElement element, Brush value) {
element.SetValue(PlaceholderBrushProperty, value);
}
/// <summary>
/// Getter for PlaceholderBrush property.
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static Brush GetPlaceholderBrush(UIElement element) {
return (Brush)element.GetValue(PlaceholderBrushProperty);
}
/// <summary>
/// Set placeholder as background when TextBox was loaded.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnTextLoaded(object sender, RoutedEventArgs e) {
var textBox = sender as TextBox;
if (textBox == null) return;
Label placeholder = new Label();
placeholder.Content = textBox.GetValue(PlaceholderProperty);
VisualBrush brush = new VisualBrush();
brush.AlignmentX = AlignmentX.Left;
brush.AlignmentY = GetPlaceholderBaseline(textBox);
brush.TileMode = TileMode.None;
brush.Stretch = Stretch.None;
brush.Opacity = 0.3;
brush.Visual = placeholder;
textBox.SetValue(PlaceholderBrushProperty, brush);
textBox.Background = brush;
textBox.TextChanged += OnTextChanged;
OnTextChanged(textBox, null);
}
/// <summary>
/// Dynamically hide/show placeholder.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnTextChanged(object sender, RoutedEventArgs e) {
var textBox = sender as TextBox;
if (string.IsNullOrEmpty(textBox.Text)) {
textBox.Background = textBox.GetValue(PlaceholderBrushProperty) as Brush;
} else {
textBox.Background = Brushes.Transparent;
}
}
}
}

View file

@ -0,0 +1,329 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SourceGit.Helpers {
/// <summary>
/// Helper class to enable multi-selection of TreeView
/// </summary>
public static class TreeViewHelper {
/// <summary>
/// Definition of EnableMultiSelection property.
/// </summary>
public static readonly DependencyProperty EnableMultiSelectionProperty =
DependencyProperty.RegisterAttached(
"EnableMultiSelection",
typeof(bool),
typeof(TreeViewHelper),
new FrameworkPropertyMetadata(false, OnEnableMultiSelectionChanged));
/// <summary>
/// Getter of EnableMultiSelection
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetEnableMultiSelection(DependencyObject obj) {
return (bool)obj.GetValue(EnableMultiSelectionProperty);
}
/// <summary>
/// Setter of EnableMultiSelection
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetEnableMultiSelection(DependencyObject obj, bool value) {
obj.SetValue(EnableMultiSelectionProperty, value);
}
/// <summary>
/// Definition of SelectedItems
/// </summary>
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached(
"SelectedItems",
typeof(ObservableCollection<TreeViewItem>),
typeof(TreeViewHelper),
new FrameworkPropertyMetadata(null));
/// <summary>
/// Getter of SelectedItems
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static ObservableCollection<TreeViewItem> GetSelectedItems(DependencyObject obj) {
return (ObservableCollection<TreeViewItem>)obj.GetValue(SelectedItemsProperty);
}
/// <summary>
/// Setter of SelectedItems
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetSelectedItems(DependencyObject obj, ObservableCollection<TreeViewItem> value) {
obj.SetValue(SelectedItemsProperty, value);
}
/// <summary>
/// Definition of IsChecked property.
/// </summary>
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached(
"IsChecked",
typeof(bool),
typeof(TreeViewHelper),
new FrameworkPropertyMetadata(false));
/// <summary>
/// Getter of IsChecked Property.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetIsChecked(DependencyObject obj) {
return (bool)obj.GetValue(IsCheckedProperty);
}
/// <summary>
/// Setter of IsChecked property
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetIsChecked(DependencyObject obj, bool value) {
obj.SetValue(IsCheckedProperty, value);
}
/// <summary>
/// Definition of MultiSelectionChangedEvent
/// </summary>
public static readonly RoutedEvent MultiSelectionChangedEvent =
EventManager.RegisterRoutedEvent("MultiSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewHelper));
/// <summary>
/// Add handler for MultiSelectionChanged event.
/// </summary>
/// <param name="d"></param>
/// <param name="handler"></param>
public static void AddMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
var tree = d as TreeView;
if (tree != null) tree.AddHandler(MultiSelectionChangedEvent, handler);
}
/// <summary>
/// Remove handler for MultiSelectionChanged event.
/// </summary>
/// <param name="d"></param>
/// <param name="handler"></param>
public static void RemoveMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
var tree = d as TreeView;
if (tree != null) tree.RemoveHandler(MultiSelectionChangedEvent, handler);
}
/// <summary>
/// Select all items in tree.
/// </summary>
/// <param name="tree"></param>
public static void SelectWholeTree(TreeView tree) {
var selected = GetSelectedItems(tree);
selected.Clear();
SelectAll(selected, tree);
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
}
/// <summary>
/// Selected one item by DataContext
/// </summary>
/// <param name="tree"></param>
/// <param name="obj"></param>
public static void SelectOneByContext(TreeView tree, object obj) {
var item = FindTreeViewItemByDataContext(tree, obj);
if (item != null) {
var selected = GetSelectedItems(tree);
selected.Add(item);
item.SetValue(IsCheckedProperty, true);
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
}
}
/// <summary>
/// Unselect the whole tree.
/// </summary>
/// <param name="tree"></param>
public static void UnselectTree(TreeView tree) {
var selected = GetSelectedItems(tree);
if (selected.Count == 0) return;
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
selected.Clear();
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
}
/// <summary>
/// Hooks when EnableMultiSelection changed.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnEnableMultiSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var tree = d as TreeView;
if (tree != null && (bool)e.NewValue) {
tree.SetValue(SelectedItemsProperty, new ObservableCollection<TreeViewItem>());
tree.PreviewMouseDown += OnTreeMouseDown;
}
}
/// <summary>
/// Preview mouse button select.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void OnTreeMouseDown(object sender, MouseButtonEventArgs e) {
var tree = sender as TreeView;
if (tree == null) return;
var hit = VisualTreeHelper.HitTest(tree, e.GetPosition(tree));
if (hit == null || hit.VisualHit is null) return;
var item = FindTreeViewItem(hit.VisualHit as UIElement);
if (item == null) return;
var selected = GetSelectedItems(tree);
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
if (GetIsChecked(item)) {
selected.Remove(item);
item.SetValue(IsCheckedProperty, false);
} else {
selected.Add(item);
item.SetValue(IsCheckedProperty, true);
}
} else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && selected.Count > 0) {
var last = selected.Last();
if (last == item) return;
var lastPos = last.PointToScreen(new Point(0, 0));
var curPos = item.PointToScreen(new Point(0, 0));
if (lastPos.Y > curPos.Y) {
SelectRange(selected, tree, item, last);
} else {
SelectRange(selected, tree, last, item);
}
selected.Add(item);
item.SetValue(IsCheckedProperty, true);
} else if (e.RightButton == MouseButtonState.Pressed) {
if (GetIsChecked(item)) return;
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
selected.Clear();
selected.Add(item);
item.SetValue(IsCheckedProperty, true);
} else {
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
selected.Clear();
selected.Add(item);
item.SetValue(IsCheckedProperty, true);
}
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
}
/// <summary>
/// Find TreeViewItem by child element.
/// </summary>
/// <param name="item"></param>
/// <param name="child"></param>
/// <returns></returns>
private static TreeViewItem FindTreeViewItem(DependencyObject child) {
if (child == null) return null;
if (child is TreeViewItem) return child as TreeViewItem;
if (child is TreeView) return null;
return FindTreeViewItem(VisualTreeHelper.GetParent(child));
}
/// <summary>
/// Find TreeViewItem by DataContext
/// </summary>
/// <param name="control"></param>
/// <param name="obj"></param>
/// <returns></returns>
private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl control, object obj) {
if (control == null) return null;
if (control.DataContext == obj) return control as TreeViewItem;
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
var found = FindTreeViewItemByDataContext(child, obj);
if (found != null) return found;
}
return null;
}
/// <summary>
/// Select all items.
/// </summary>
/// <param name="selected"></param>
/// <param name="control"></param>
private static void SelectAll(ObservableCollection<TreeViewItem> selected, ItemsControl control) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (child == null) continue;
selected.Add(child);
child.SetValue(IsCheckedProperty, true);
SelectAll(selected, child);
}
}
/// <summary>
/// Select range items between given.
/// </summary>
/// <param name="selected"></param>
/// <param name="control"></param>
/// <param name="from"></param>
/// <param name="to"></param>
/// <param name="started"></param>
private static int SelectRange(ObservableCollection<TreeViewItem> selected, ItemsControl control, TreeViewItem from, TreeViewItem to, int matches = 0) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (child == null) continue;
if (matches == 1) {
if (child == to) return 2;
selected.Add(child);
child.SetValue(IsCheckedProperty, true);
if (TryEndRangeSelection(selected, child, to)) return 2;
} else if (child == from) {
matches = 1;
if (TryEndRangeSelection(selected, child, to)) return 2;
} else {
matches = SelectRange(selected, child, from, to, matches);
if (matches == 2) return 2;
}
}
return matches;
}
private static bool TryEndRangeSelection(ObservableCollection<TreeViewItem> selected, TreeViewItem control, TreeViewItem end) {
for (int i = 0; i < control.Items.Count; i++) {
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (child == null) continue;
if (child == end) {
return true;
} else {
selected.Add(child);
child.SetValue(IsCheckedProperty, true);
var ended = TryEndRangeSelection(selected, child, end);
if (ended) return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,124 @@
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Controls;
namespace SourceGit.Helpers {
/// <summary>
/// Validate clone folder.
/// </summary>
public class CloneFolderRule : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var badPath = "EXISTS and FULL ACCESS CONTROL needed";
var path = value as string;
return Directory.Exists(path) ? ValidationResult.ValidResult : new ValidationResult(false, badPath);
}
}
/// <summary>
/// Validate git remote URL
/// </summary>
public class RemoteUriRule : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var badUrl = "Remote git URL not supported";
return Git.Repository.IsValidUrl(value as string) ? ValidationResult.ValidResult : new ValidationResult(false, badUrl);
}
}
/// <summary>
/// Validate tag name.
/// </summary>
public class RemoteNameRule : ValidationRule {
public Git.Repository Repo { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var regex = new Regex(@"^[\w\-\.]+$");
var name = value as string;
var remotes = Repo.Remotes();
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Remote name can NOT be null");
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for remote. Regex: ^[\\w\\-\\.]+$");
foreach (var t in remotes) {
if (t.Name == name) {
return new ValidationResult(false, $"Remote '{name}' already exists");
}
}
return ValidationResult.ValidResult;
}
}
/// <summary>
/// Validate branch name.
/// </summary>
public class BranchNameRule : ValidationRule {
public Git.Repository Repo { get; set; }
public string Prefix { get; set; } = "";
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var regex = new Regex(@"^[\w\-/\.]+$");
var name = value as string;
var branches = Repo.Branches();
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Branch name can NOT be null");
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for branch. Regex: ^[\\w\\-/\\.]+$");
name = Prefix + name;
foreach (var b in branches) {
if (b.Name == name) {
return new ValidationResult(false, $"Branch '{name}' already exists");
}
}
return ValidationResult.ValidResult;
}
}
/// <summary>
/// Validate tag name.
/// </summary>
public class TagNameRule : ValidationRule {
public Git.Repository Repo { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var regex = new Regex(@"^[\w\-\.]+$");
var name = value as string;
var tags = Repo.Tags();
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Tag name can NOT be null");
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for tag. Regex: ^[\\w\\-\\.]+$");
foreach (var t in tags) {
if (t.Name == name) {
return new ValidationResult(false, $"Tag '{name}' already exists");
}
}
return ValidationResult.ValidResult;
}
}
/// <summary>
/// Required for commit subject.
/// </summary>
public class CommitSubjectRequiredRule : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var subject = value as string;
return string.IsNullOrWhiteSpace(subject) ? new ValidationResult(false, "Commit subject can NOT be empty") : ValidationResult.ValidResult;
}
}
/// <summary>
/// Required for patch file.
/// </summary>
public class PatchFileRequiredRule : ValidationRule {
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
var path = value as string;
var succ = !string.IsNullOrEmpty(path) && File.Exists(path);
return !succ ? new ValidationResult(false, "Invalid path for patch file") : ValidationResult.ValidResult;
}
}
}