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

77
SourceGit/UI/About.xaml Normal file
View file

@ -0,0 +1,77 @@
<Window x:Class="SourceGit.UI.About"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="280" Width="400"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
<!-- Enable WindowChrome Feature -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32" GlassFrameThickness="0 0 0 1" ResizeBorderThickness="8" ResizeGripDirection="None" NonClientFrameEdges="None" />
</WindowChrome.WindowChrome>
<!-- Window Layout -->
<Border Background="{StaticResource Brush.BG1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Titlebar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Title -->
<Label Content="ABOUT" FontWeight="Light"/>
<!-- Close Button -->
<Button Click="Quit" Width="32" Grid.Column="2" WindowChrome.IsHitTestVisibleInChrome="True">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</Grid>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="90"/>
<RowDefinition Height="40"/>
<RowDefinition Height="32"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,6,0,0">
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}" Fill="#FFF05133"/>
</StackPanel>
<Label Grid.Row="1" Content="SourceGit - OPEN SOURCE GIT CLIENT" HorizontalContentAlignment="Center" VerticalContentAlignment="Bottom" FontSize="18" FontWeight="Bold"/>
<Label Grid.Row="2" Content="{Binding ElementName=me, Path=Version}" HorizontalContentAlignment="Center" FontSize="11"/>
<Label Grid.Row="3" HorizontalContentAlignment="Center" FontSize="11">
<Hyperlink RequestNavigate="OpenSource" NavigateUri="https://gitee.com/sourcegit/SourceGit.git">
<Run Text="https://gitee.com/sourcegit/SourceGit.git"/>
</Hyperlink>
</Label>
<Label Grid.Row="4" Content="Copyright © sourcegit 2020. All rights reserved." HorizontalContentAlignment="Center" FontSize="11"/>
</Grid>
</Grid>
</Border>
</Window>

View file

@ -0,0 +1,46 @@
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Navigation;
namespace SourceGit.UI {
/// <summary>
/// About dialog
/// </summary>
public partial class About : Window {
/// <summary>
/// Current app version
/// </summary>
public string Version {
get {
return "VERSION : " + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
}
}
/// <summary>
/// Constructor
/// </summary>
public About() {
InitializeComponent();
}
/// <summary>
/// Open source code link
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OpenSource(object sender, RequestNavigateEventArgs e) {
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
e.Handled = true;
}
/// <summary>
/// Close this dialog
/// </summary>
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
}
}

85
SourceGit/UI/Apply.xaml Normal file
View file

@ -0,0 +1,85 @@
<UserControl x:Class="SourceGit.UI.Apply"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="192" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Apply Patch"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Patch File :"/>
<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<TextBox
Grid.Column="0"
x:Name="txtPatchFile"
Height="24"
helpers:TextBoxHelper.Placeholder="Select .patch file to apply">
<TextBox.Text>
<Binding Path="PatchFile" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:PatchFileRequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Grid.Column="1" Width="24" Height="24" Click="FindPatchFile" Padding="0" BorderThickness="1" Style="{StaticResource Style.Button.Bordered}">
<Path Width="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Whitespace :"/>
<ComboBox x:Name="combWhitespaceOptions" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Label Content="{Binding Name}" Padding="4,0"/>
<Label Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

112
SourceGit/UI/Apply.xaml.cs Normal file
View file

@ -0,0 +1,112 @@
using Microsoft.Win32;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Apply patch dialog
/// </summary>
public partial class Apply : UserControl {
private Git.Repository repo = null;
/// <summary>
/// Whitespace option.
/// </summary>
public class WhitespaceOption {
public string Name { get; set; }
public string Desc { get; set; }
public string Arg { get; set; }
public WhitespaceOption(string n, string d, string a) {
Name = n;
Desc = d;
Arg = a;
}
}
/// <summary>
/// Path of file to be patched.
/// </summary>
public string PatchFile { get; set; }
/// <summary>
/// Constructor.
/// </summary>
public Apply(Git.Repository opened) {
repo = opened;
InitializeComponent();
combWhitespaceOptions.ItemsSource = new WhitespaceOption[] {
new WhitespaceOption("No Warn", "Turns off the trailing whitespace warning", "nowarn"),
new WhitespaceOption("Warn", "Outputs warnings for a few such errors, but applies", "warn"),
new WhitespaceOption("Error", "Raise errors and refuses to apply the patch", "error"),
new WhitespaceOption("Error All", "Similar to 'error', but shows more", "error-all"),
};
combWhitespaceOptions.SelectedIndex = 0;
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="opened"></param>
public static void Show(Git.Repository opened) {
PopupManager.Show(new Apply(opened));
}
/// <summary>
/// Open file browser dialog for select a file to patch.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FindPatchFile(object sender, RoutedEventArgs e) {
var dialog = new OpenFileDialog();
dialog.Filter = "Patch File|*.patch";
dialog.Title = "Select Patch File";
dialog.InitialDirectory = repo.Path;
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == true) {
PatchFile = dialog.FileName;
txtPatchFile.Text = dialog.FileName;
}
}
/// <summary>
/// Start apply selected path.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
txtPatchFile.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtPatchFile)) return;
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
var mode = combWhitespaceOptions.SelectedItem as WhitespaceOption;
await Task.Run(() => repo.Apply(PatchFile, mode.Arg));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel options.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

201
SourceGit/UI/Blame.xaml Normal file
View file

@ -0,0 +1,201 @@
<Window x:Class="SourceGit.UI.Blame"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="600" Width="800">
<!-- Enable WindowChrome -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
</WindowChrome.WindowChrome>
<!-- Window Content -->
<Border Background="{StaticResource Brush.BG1}">
<!-- Fix Maximize BUG -->
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Margin" Value="6"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Margin" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Logo & TITLE -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Path
Width="20" Height="20" Margin="6,-1,2,0"
Style="{StaticResource Style.Icon}"
Data="{StaticResource Icon.Git}"
Fill="#FFF05133"
WindowChrome.IsHitTestVisibleInChrome="True"
MouseLeftButtonDown="LogoMouseButtonDown"/>
<Label Content="SOURCE GIT - BLAME" FontWeight="Light"/>
</StackPanel>
<!-- Options -->
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
</Button>
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path>
<Path.Style>
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button Click="Quit" Width="32">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</StackPanel>
</Grid>
<!-- Blame file -->
<Border Grid.Row="1" Padding="2,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="blameFile" HorizontalAlignment="Left" FontSize="11" Foreground="{StaticResource Brush.FG2}" FontFamily="Consolas"/>
<Label Grid.Column="1" HorizontalAlignment="Right" Foreground="{StaticResource Brush.FG2}" FontSize="11" Content="Use right mouse button to view commit information."/>
</Grid>
</Border>
<!-- Content -->
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" ClipToBounds="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="lineNumber"
Grid.Column="0"
AcceptsReturn="True"
AcceptsTab="True"
BorderThickness="0"
Background="Transparent"
IsReadOnly="True"
Margin="4,0,4,0"
FontSize="13"
HorizontalContentAlignment="Right"
VerticalAlignment="Stretch"
FontFamily="Consolas"/>
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
<RichTextBox
x:Name="content"
Grid.Column="2"
AcceptsReturn="True"
AcceptsTab="True"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent"
Foreground="{StaticResource Brush.FG}"
Height="Auto"
FontSize="13"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
RenderOptions.ClearTypeHint="Enabled"
ScrollViewer.ScrollChanged="SyncScrollChanged"
PreviewMouseWheel="MouseWheelOnContent"
SizeChanged="ContentSizeChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontFamily="Consolas">
<FlowDocument PageWidth="0"/>
</RichTextBox>
<!-- Loading tip -->
<Path x:Name="loading" Grid.ColumnSpan="5" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
<Path.Style>
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
</Style>
</Path.Style>
</Path>
<!-- Popup to show commit info -->
<Popup x:Name="popup" Grid.ColumnSpan="5" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG1}">
<Grid Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="COMMIT SHA" Foreground="{StaticResource Brush.FG2}"/>
<Label Grid.Row="0" Grid.Column="1" x:Name="commitID"/>
<Label Grid.Row="1" Grid.Column="0" Content="AUTHOR" Foreground="{StaticResource Brush.FG2}"/>
<Label Grid.Row="1" Grid.Column="1" x:Name="authorName"/>
<Label Grid.Row="2" Grid.Column="0" Content="MODIFY TIME" Foreground="{StaticResource Brush.FG2}"/>
<Label Grid.Row="2" Grid.Column="1" x:Name="authorTime"/>
</Grid>
</Border>
</Popup>
</Grid>
</Border>
</Grid>
</Border>
</Window>

211
SourceGit/UI/Blame.xaml.cs Normal file
View file

@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Viewer to show git-blame
/// </summary>
public partial class Blame : Window {
/// <summary>
/// Background color for blocks.
/// </summary>
public static Brush[] BG = new Brush[] {
Brushes.Transparent,
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
};
/// <summary>
/// Constructor
/// </summary>
/// <param name="repo"></param>
/// <param name="file"></param>
/// <param name="revision"></param>
public Blame(Git.Repository repo, string file, string revision) {
InitializeComponent();
double minWidth = content.ActualWidth;
// Move to center.
var parent = App.Current.MainWindow;
Left = parent.Left + (parent.Width - Width) * 0.5;
Top = parent.Top + (parent.Height - Height) * 0.5;
// Show loading.
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
loading.Visibility = Visibility.Visible;
// Layout content
blameFile.Content = $"{file}@{revision.Substring(0, 8)}";
Task.Run(() => {
var blame = repo.BlameFile(file, revision);
Dispatcher.Invoke(() => {
content.Document.Blocks.Clear();
if (blame.IsBinary) {
lineNumber.Text = "0";
Paragraph p = new Paragraph(new Run("BINARY FILE BLAME NOT SUPPORTED!!!"));
p.Margin = new Thickness(0);
p.Padding = new Thickness(0);
p.LineHeight = 1;
p.Background = Brushes.Transparent;
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
p.FontStyle = FontStyles.Normal;
content.Document.Blocks.Add(p);
} else {
List<string> numbers = new List<string>();
for (int i = 0; i < blame.LineCount; i++) numbers.Add(i.ToString());
lineNumber.Text = string.Join("\n", numbers);
numbers.Clear();
for (int i = 0; i < blame.Blocks.Count; i++) {
var frag = blame.Blocks[i];
var idx = i;
Paragraph p = new Paragraph(new Run(frag.Content));
p.DataContext = frag;
p.Margin = new Thickness(0);
p.Padding = new Thickness(0);
p.LineHeight = 1;
p.Background = BG[i % 2];
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
p.FontStyle = FontStyles.Normal;
p.MouseRightButtonDown += (sender, ev) => {
Hyperlink link = new Hyperlink(new Run(frag.CommitSHA));
link.ToolTip = "CLICK TO GO";
link.Click += (o, e) => {
repo.OnNavigateCommit?.Invoke(frag.CommitSHA);
e.Handled = true;
};
foreach (var block in content.Document.Blocks) {
var paragraph = block as Paragraph;
if ((paragraph.DataContext as Git.Blame.Block).CommitSHA == frag.CommitSHA) {
paragraph.Background = Brushes.Green;
} else {
paragraph.Background = BG[i % 2];
}
}
commitID.Content = link;
authorName.Content = frag.Author;
authorTime.Content = frag.Time;
popup.IsOpen = true;
};
var formatter = new FormattedText(
frag.Content,
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(content.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch),
content.FontSize,
Brushes.Black,
new NumberSubstitution(),
TextFormattingMode.Ideal);
if (minWidth < formatter.Width) {
content.Document.PageWidth = formatter.Width + 16;
minWidth = formatter.Width;
}
content.Document.Blocks.Add(p);
}
}
// Hide loading.
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
loading.Visibility = Visibility.Collapsed;
});
});
}
/// <summary>
/// Click logo
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) {
var element = e.OriginalSource as FrameworkElement;
if (element == null) return;
var pos = PointToScreen(new Point(0, 33));
SystemCommands.ShowSystemMenu(this, pos);
}
/// <summary>
/// Minimize
/// </summary>
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
/// <summary>
/// Maximize/Restore
/// </summary>
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
/// <summary>
/// Quit
/// </summary>
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
/// <summary>
/// Sync scroll
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SyncScrollChanged(object sender, ScrollChangedEventArgs e) {
if (e.VerticalChange != 0) {
var margin = new Thickness(4, -e.VerticalOffset, 4, 0);
lineNumber.Margin = margin;
}
}
/// <summary>
/// Mouse wheel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MouseWheelOnContent(object sender, MouseWheelEventArgs e) {
if (e.Delta > 0) {
content.LineUp();
} else {
content.LineDown();
}
e.Handled = true;
}
/// <summary>
/// Content size changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ContentSizeChanged(object sender, SizeChangedEventArgs e) {
if (content.Document.PageWidth < content.ActualWidth) {
content.Document.PageWidth = content.ActualWidth;
}
}
}
}

View file

@ -0,0 +1,45 @@
<UserControl x:Class="SourceGit.UI.CherryPick"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Cherry Pick"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Commit :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}" Margin="4,0"/>
<Label x:Name="desc"/>
</StackPanel>
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkCommitChanges" IsChecked="True" Content="Commit the changes"/>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,54 @@
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Cherry pick commit dialog.
/// </summary>
public partial class CherryPick : UserControl {
private Git.Repository repo = null;
private string commitSHA = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened"></param>
/// <param name="commit"></param>
public CherryPick(Git.Repository opened, Git.Commit commit) {
InitializeComponent();
repo = opened;
commitSHA = commit.SHA;
desc.Content = $"{commit.ShortSHA} {commit.Subject}";
}
/// <summary>
/// Display this dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="commit"></param>
public static void Show(Git.Repository repo, Git.Commit commit) {
PopupManager.Show(new CherryPick(repo, commit));
}
/// <summary>
/// Start pick.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start(object sender, RoutedEventArgs e) {
repo.CherryPick(commitSHA, chkCommitChanges.IsChecked != true);
PopupManager.Close();
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

98
SourceGit/UI/Clone.xaml Normal file
View file

@ -0,0 +1,98 @@
<UserControl x:Class="SourceGit.UI.Clone"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
Width="500" Height="192">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Clone Remote Repository"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Repository URL :"/>
<TextBox x:Name="txtUrl" Grid.Row="2" Grid.Column="1"
Height="24"
helpers:TextBoxHelper.Placeholder="Git Repository URL">
<TextBox.Text>
<Binding Path="RemoteUri" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:RemoteUriRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Parent Folder :"/>
<Grid Grid.Row="3" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
x:Name="txtParentFolder"
Height="24"
helpers:TextBoxHelper.Placeholder="Folder to contain this repository">
<TextBox.Text>
<Binding Path="ParentFolder" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:CloneFolderRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Grid.Column="1" Width="24" Height="24" Padding="0" BorderThickness="1" Click="SelectParentFolder" Style="{StaticResource Style.Button.Bordered}">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Local Name :"/>
<TextBox Grid.Row="4" Grid.Column="1"
VerticalContentAlignment="Center"
Height="24"
helpers:TextBoxHelper.Placeholder="Repository name. Optional."
Text="{Binding LocalName, ElementName=me, Mode=TwoWay}">
</TextBox>
<Grid Grid.Row="6" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

115
SourceGit/UI/Clone.xaml.cs Normal file
View file

@ -0,0 +1,115 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Clone dialog.
/// </summary>
public partial class Clone : UserControl {
/// <summary>
/// Remote repository
/// </summary>
public string RemoteUri { get; set; }
/// <summary>
/// Parent folder.
/// </summary>
public string ParentFolder { get; set; }
/// <summary>
/// Local name.
/// </summary>
public string LocalName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
public Clone() {
ParentFolder = App.Preference.GitDefaultCloneDir;
InitializeComponent();
}
/// <summary>
/// Show clone dialog.
/// </summary>
public static void Show() {
PopupManager.Show(new Clone());
}
/// <summary>
/// Select parent folder.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SelectParentFolder(object sender, RoutedEventArgs e) {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.Description = "Git Repository URL";
dialog.RootFolder = Environment.SpecialFolder.MyComputer;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
txtParentFolder.Text = dialog.SelectedPath;
}
}
/// <summary>
/// Start clone
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start(object sender, RoutedEventArgs e) {
txtUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtUrl)) return;
txtParentFolder.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtParentFolder)) return;
string repoName;
if (string.IsNullOrWhiteSpace(LocalName)) {
var from = RemoteUri.LastIndexOfAny(new char[] { '\\', '/' });
if (from <= 0) return;
var name = RemoteUri.Substring(from + 1);
repoName = name.Replace(".git", "");
} else {
repoName = LocalName;
}
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
Task.Run(() => {
var repo = Git.Repository.Clone(RemoteUri, ParentFolder, repoName, msg => Dispatcher.Invoke(() => statusMsg.Content = msg));
if (repo == null) {
PopupManager.Unlock();
Dispatcher.Invoke(() => {
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
});
} else {
Dispatcher.Invoke(() => PopupManager.Close(true));
repo.Open();
}
});
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,408 @@
<UserControl x:Class="SourceGit.UI.CommitViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:source="clr-namespace:SourceGit"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:git="clr-namespace:SourceGit.Git"
xmlns:converters="clr-namespace:SourceGit.Converters"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Unloaded="Cleanup">
<TabControl>
<TabItem Header="INFORMATION">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition x:Name="committerRow" Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="16"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="96"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="96"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- SHA -->
<Label Grid.Row="0" Grid.Column="0" Content="SHA" HorizontalAlignment="Right" Opacity=".5"/>
<TextBox Grid.Row="0" Grid.Column="1"
x:Name="SHA"
IsReadOnly="True"
Background="Transparent"
BorderThickness="0"
Margin="11,0,0,0"/>
<!-- Refs -->
<Label x:Name="lblRefs" Grid.Row="0" Grid.Column="2" Content="REFS" HorizontalAlignment="Right" Opacity=".5"/>
<ItemsControl Grid.Row="0" Grid.Column="3" x:Name="refs" Margin="8,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="BG" Height="16" Margin="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{StaticResource Brush.BG5}">
<Path x:Name="Icon" Width="8" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
</Border>
<Label x:Name="Name" Grid.Column="1" Content="{Binding Name}" FontSize="11" Padding="4,0" Foreground="Black"/>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.Tag}">
<Setter TargetName="BG" Property="Background" Value="#FF02C302"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Tag}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.LocalBranchHead}">
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Branch}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.RemoteBranchHead}">
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Remote}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.CurrentBranchHead}">
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Check}"/>
<Setter TargetName="Icon" Property="Fill" Value="Orange"/>
<Setter TargetName="Name" Property="FontWeight" Value="Bold"/>
<Setter TargetName="Name" Property="Foreground" Value="{StaticResource Brush.FG}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- PARENTS -->
<Label Grid.Row="1" Grid.Column="0" Content="PARENTS" HorizontalAlignment="Right" Opacity=".5"/>
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" x:Name="parents" Margin="8,0,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Margin="0,0,8,0">
<Hyperlink
RequestNavigate="NavigateParent"
NavigateUri="{Binding .}"
ToolTip="NAVIGATE TO COMMIT">
<Run Text="{Binding .}"/>
</Hyperlink>
</Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- AUTHOR -->
<Label Grid.Row="2" Grid.Column="0" Content="AUTHOR" HorizontalAlignment="Right" Opacity=".5"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="author"
IsReadOnly="True"
Background="Transparent"
AcceptsReturn="True"
BorderThickness="0"
Margin="11,0,0,0"/>
<!-- AUTHOR TIME -->
<Label Grid.Row="2" Grid.Column="2" Content="AUTHOR TIME" HorizontalAlignment="Right" Opacity=".5"/>
<TextBox Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="3" x:Name="authorTime"
IsReadOnly="True"
Background="Transparent"
AcceptsReturn="True"
BorderThickness="0"
Margin="8,0,0,0"/>
<!-- COMMITTER -->
<Label Grid.Row="3" Grid.Column="0" Content="COMMITTER" HorizontalAlignment="Right" Opacity=".5"/>
<TextBox Grid.Row="3" Grid.Column="1" x:Name="committer"
IsReadOnly="True"
Background="Transparent"
AcceptsReturn="True"
BorderThickness="0"
Margin="11,0,0,0"/>
<!-- COMMIT TIME -->
<Label Grid.Row="3" Grid.Column="2" Content="COMMIT TIME" HorizontalAlignment="Right" Opacity=".5"/>
<TextBox Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="3" x:Name="committerTime"
IsReadOnly="True"
Background="Transparent"
AcceptsReturn="True"
BorderThickness="0"
Margin="8,0,0,0"/>
<Rectangle Grid.Row="4" Grid.ColumnSpan="4" Height="1" Margin="8,0" Fill="{StaticResource Brush.Border2}"/>
<!-- SUBJECT -->
<Label Grid.Row="5" Grid.Column="0" Content="SUBJECT" HorizontalAlignment="Right" Opacity=".5"/>
<TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3"
x:Name="subject"
IsReadOnly="True"
Background="Transparent"
BorderThickness="0"
Margin="8,0,16,0"/>
<!-- MESSAGE -->
<Label Grid.Row="6" Grid.Column="0" Content="DESCRIPTION" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity=".5"/>
<TextBox Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3"
x:Name="message"
IsReadOnly="True"
Background="Transparent"
BorderThickness="0"
VerticalAlignment="Center"
FontSize="11"
Margin="11,8,0,0"/>
<Rectangle Grid.Row="7" Grid.ColumnSpan="4" Height="1" Margin="8,0" Fill="{StaticResource Brush.Border2}"/>
<!-- CHANGELIST -->
<Label Grid.Row="8" Grid.Column="0" Content="CHANGED" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity=".5"/>
<DataGrid
Grid.Row="8"
Grid.Column="1"
Grid.ColumnSpan="3"
x:Name="changeList1"
RowHeight="20"
Margin="11,2,0,2">
<DataGrid.Resources>
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="22">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="2,0,4,0">
<TextBlock Text="{Binding ., Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="ContextMenuOpening" Handler="ChangeListContextMenuOpening"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</TabItem>
<!-- CHANGES -->
<TabItem Header="CHANGES">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Margin="2,0">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.Resources>
<converters:BoolToCollapsed x:Key="BoolToCollapsed"/>
<converters:InverseBoolToCollapsed x:Key="InverseBoolToCollapsed"/>
</Grid.Resources>
<Grid Grid.Row="0" Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" Background="{StaticResource Brush.BG3}"/>
<Path Grid.Column="0" Width="14" Height="14" Fill="{StaticResource Brush.FG2}" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
<TextBox Grid.Column="1" x:Name="txtChangeFilter" BorderThickness="0" helpers:TextBoxHelper.Placeholder="Search File ..." TextChanged="SearchChangeFileTextChanged"/>
<ToggleButton
Grid.Column="2"
x:Name="toggleSwitchMode"
Margin="4,0,0,0"
ToolTip="SWITCH TO LIST/TREE VIEW"
Style="{StaticResource Style.ToggleButton.ListOrTree}"
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIUseListInChanges, Mode=TwoWay}"/>
</Grid>
<TreeView
Grid.Row="1"
x:Name="changeTree"
FontFamily="Consolas"
Visibility="{Binding ElementName=toggleSwitchMode, Path=IsChecked, Converter={StaticResource InverseBoolToCollapsed}}"
Background="{StaticResource Brush.BG2}"
SelectedItemChanged="ChangeTreeItemSelected"
PreviewMouseWheel="TreeMouseWheel">
<TreeView.Resources>
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Height="24">
<Border x:Name="status" Width="14" Height="14" Visibility="Collapsed" Background="{Binding Change, Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="0,0,4,0">
<TextBlock Text="{Binding Change, Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="10" RenderOptions.BitmapScalingMode="HighQuality"/>
</Border>
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontSize="11"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFile}" Value="True">
<Setter TargetName="status" Property="Visibility" Value="Visible"/>
<Setter TargetName="icon" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFile}" Value="False"/>
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<DataGrid
Grid.Row="1"
x:Name="changeList2"
Visibility="{Binding ElementName=toggleSwitchMode, Path=IsChecked, Converter={StaticResource BoolToCollapsed}}"
RowHeight="24"
SelectionChanged="ChangeListSelectionChanged"
SelectionMode="Single"
SelectionUnit="FullRow"
Background="{StaticResource Brush.BG2}">
<DataGrid.Resources>
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="22">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="2,0,4,0">
<TextBlock Text="{Binding ., Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="ContextMenuOpening" Handler="ChangeListContextMenuOpening"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
<local:DiffViewer Grid.Column="2" x:Name="diffViewer" Background="{StaticResource Brush.BG3}"/>
</Grid>
</TabItem>
<!-- FILE TREE -->
<TabItem Header="FILES">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="400"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Margin="2" Background="{StaticResource Brush.BG2}">
<TreeView x:Name="fileTree" SelectedItemChanged="FileTreeItemSelected" FontFamily="Consolas" PreviewMouseWheel="TreeMouseWheel">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Height="24">
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="6,0,0,0" FontSize="11"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFile}" Value="True">
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.File}"/>
<Setter TargetName="icon" Property="Fill" Value="{StaticResource Brush.FG}"/>
<Setter TargetName="icon" Property="Opacity" Value=".75"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFile}" Value="False"/>
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Border>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
<Border Grid.Column="2" BorderThickness="1" Margin="2,0" BorderBrush="{StaticResource Brush.Border2}">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TextBlock FontSize="10pt"
FontFamily="Consolas"
Padding="8"
Opacity="0.8"
Background="{StaticResource Brush.BG2}"
Foreground="{StaticResource Brush.FG}"
x:Name="filePreview"/>
</ScrollViewer>
</Border>
</Grid>
</TabItem>
</TabControl>
</UserControl>

View file

@ -0,0 +1,468 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
namespace SourceGit.UI {
/// <summary>
/// Commit detail viewer
/// </summary>
public partial class CommitViewer : UserControl {
private Git.Repository repo = null;
private Git.Commit commit = null;
private List<Git.Change> cachedChanges = new List<Git.Change>();
private List<Git.Change> displayChanges = new List<Git.Change>();
private string changeFilter = null;
/// <summary>
/// Node for file tree.
/// </summary>
public class Node {
public string FilePath { get; set; } = "";
public string OriginalPath { get; set; } = "";
public string Name { get; set; } = "";
public bool IsFile { get; set; } = false;
public bool IsNodeExpanded { get; set; } = true;
public Git.Change Change { get; set; } = null;
public List<Node> Children { get; set; } = new List<Node>();
}
/// <summary>
/// Constructor.
/// </summary>
public CommitViewer() {
InitializeComponent();
}
#region DATA
public void SetData(Git.Repository opened, Git.Commit selected) {
repo = opened;
commit = selected;
SetBaseInfo(commit);
Task.Run(() => {
cachedChanges.Clear();
cachedChanges = commit.GetChanges(repo);
Dispatcher.Invoke(() => {
changeList1.ItemsSource = null;
changeList1.ItemsSource = cachedChanges;
});
LayoutChanges();
SetRevisionFiles(commit.GetFiles(repo));
});
}
private void Cleanup(object sender, RoutedEventArgs e) {
fileTree.ItemsSource = null;
changeList1.ItemsSource = null;
changeList2.ItemsSource = null;
displayChanges.Clear();
cachedChanges.Clear();
diffViewer.Reset();
}
#endregion
#region BASE_INFO
private void SetBaseInfo(Git.Commit commit) {
var parentIds = new List<string>();
foreach (var p in commit.Parents) parentIds.Add(p.Substring(0, 8));
SHA.Text = commit.SHA;
refs.ItemsSource = commit.Decorators;
parents.ItemsSource = parentIds;
author.Text = $"{commit.Author.Name} <{commit.Author.Email}>";
authorTime.Text = commit.Author.Time;
committer.Text = $"{commit.Committer.Name} <{commit.Committer.Email}>";
committerTime.Text = commit.Committer.Time;
subject.Text = commit.Subject;
message.Text = commit.Message.Trim();
if (commit.Decorators.Count == 0) lblRefs.Visibility = Visibility.Collapsed;
else lblRefs.Visibility = Visibility.Visible;
if (commit.Committer.Email == commit.Author.Email && commit.Committer.Time == commit.Author.Time) {
committerRow.Height = new GridLength(0);
} else {
committerRow.Height = GridLength.Auto;
}
}
private void NavigateParent(object sender, RequestNavigateEventArgs e) {
repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString);
e.Handled = true;
}
#endregion
#region CHANGES
private void LayoutChanges() {
displayChanges.Clear();
if (string.IsNullOrEmpty(changeFilter)) {
displayChanges.AddRange(cachedChanges);
} else {
foreach (var c in cachedChanges) {
if (c.Path.ToUpper().Contains(changeFilter)) displayChanges.Add(c);
}
}
List<Node> changeTreeSource = new List<Node>();
Dictionary<string, Node> folders = new Dictionary<string, Node>();
bool isDefaultExpanded = displayChanges.Count < 50;
foreach (var c in displayChanges) {
var sepIdx = c.Path.IndexOf('/');
if (sepIdx == -1) {
Node node = new Node();
node.FilePath = c.Path;
node.IsFile = true;
node.Name = c.Path;
node.Change = c;
node.IsNodeExpanded = isDefaultExpanded;
if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath;
changeTreeSource.Add(node);
} else {
Node lastFolder = null;
var start = 0;
while (sepIdx != -1) {
var folder = c.Path.Substring(0, sepIdx);
if (folders.ContainsKey(folder)) {
lastFolder = folders[folder];
} else if (lastFolder == null) {
lastFolder = new Node();
lastFolder.FilePath = folder;
lastFolder.Name = folder.Substring(start);
lastFolder.IsNodeExpanded = isDefaultExpanded;
changeTreeSource.Add(lastFolder);
folders.Add(folder, lastFolder);
} else {
var folderNode = new Node();
folderNode.FilePath = folder;
folderNode.Name = folder.Substring(start);
folderNode.IsNodeExpanded = isDefaultExpanded;
folders.Add(folder, folderNode);
lastFolder.Children.Add(folderNode);
lastFolder = folderNode;
}
start = sepIdx + 1;
sepIdx = c.Path.IndexOf('/', start);
}
Node node = new Node();
node.FilePath = c.Path;
node.Name = c.Path.Substring(start);
node.IsFile = true;
node.Change = c;
if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath;
lastFolder.Children.Add(node);
}
}
folders.Clear();
SortTreeNodes(changeTreeSource);
Dispatcher.Invoke(() => {
changeList2.ItemsSource = null;
changeList2.ItemsSource = displayChanges;
changeTree.ItemsSource = changeTreeSource;
diffViewer.Reset();
});
}
private void SearchChangeFileTextChanged(object sender, TextChangedEventArgs e) {
changeFilter = txtChangeFilter.Text.ToUpper();
Task.Run(() => LayoutChanges());
}
private async void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
diffViewer.Reset();
var node = e.NewValue as Node;
if (node == null || !node.IsFile) return;
var start = $"{commit.SHA}^";
if (commit.Parents.Count == 0) {
start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
}
List<string> data = new List<string>();
await Task.Run(() => {
data = repo.Diff(start, commit.SHA, node.FilePath, node.OriginalPath);
});
diffViewer.SetData(data, node.FilePath, node.OriginalPath);
}
private async void ChangeListSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var change = e.AddedItems[0] as Git.Change;
if (change == null) return;
var start = $"{commit.SHA}^";
if (commit.Parents.Count == 0) {
start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
}
List<string> data = new List<string>();
await Task.Run(() => {
data = repo.Diff(start, commit.SHA, change.Path, change.OriginalPath);
});
diffViewer.SetData(data, change.Path, change.OriginalPath);
}
private void ChangeListContextMenuOpening(object sender, ContextMenuEventArgs e) {
var row = sender as DataGridRow;
if (row == null) return;
var change = row.DataContext as Git.Change;
if (change == null) return;
var path = change.Path;
var menu = new ContextMenu();
if (change.Index != Git.Change.Status.Deleted) {
MenuItem history = new MenuItem();
history.Header = "File History";
history.Click += (o, ev) => {
var viewer = new FileHistories(repo, path);
viewer.Show();
};
menu.Items.Add(history);
MenuItem blame = new MenuItem();
blame.Header = "Blame";
blame.Click += (obj, ev) => {
Blame viewer = new Blame(repo, path, commit.SHA);
viewer.Show();
};
menu.Items.Add(blame);
MenuItem explore = new MenuItem();
explore.Header = "Reveal in File Explorer";
explore.Click += (o, ev) => {
var absPath = Path.GetFullPath(repo.Path + "\\" + path);
Process.Start("explorer", $"/select,{absPath}");
e.Handled = true;
};
menu.Items.Add(explore);
MenuItem saveAs = new MenuItem();
saveAs.Header = "Save As ...";
saveAs.Click += (obj, ev) => {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.Description = change.Path;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
var savePath = Path.Combine(dialog.SelectedPath, Path.GetFileName(path));
repo.RunAndRedirect($"show {commit.SHA}:\"{path}\"", savePath);
}
};
menu.Items.Add(saveAs);
}
MenuItem copyPath = new MenuItem();
copyPath.Header = "Copy Path";
copyPath.Click += (obj, ev) => {
Clipboard.SetText(path);
};
menu.Items.Add(copyPath);
menu.IsOpen = true;
e.Handled = true;
}
#endregion
#region FILES
private void SetRevisionFiles(List<string> files) {
List<Node> fileTreeSource = new List<Node>();
Dictionary<string, Node> folders = new Dictionary<string, Node>();
foreach (var path in files) {
var sepIdx = path.IndexOf("/");
if (sepIdx == -1) {
Node node = new Node();
node.FilePath = path;
node.Name = path;
node.IsFile = true;
node.IsNodeExpanded = false;
fileTreeSource.Add(node);
} else {
Node lastFolder = null;
var start = 0;
while (sepIdx != -1) {
var folder = path.Substring(0, sepIdx);
if (folders.ContainsKey(folder)) {
lastFolder = folders[folder];
} else if (lastFolder == null) {
lastFolder = new Node();
lastFolder.FilePath = folder;
lastFolder.Name = folder.Substring(start);
lastFolder.IsNodeExpanded = false;
fileTreeSource.Add(lastFolder);
folders.Add(folder, lastFolder);
} else {
var folderNode = new Node();
folderNode.FilePath = folder;
folderNode.Name = folder.Substring(start);
folderNode.IsNodeExpanded = false;
folders.Add(folder, folderNode);
lastFolder.Children.Add(folderNode);
lastFolder = folderNode;
}
start = sepIdx + 1;
sepIdx = path.IndexOf('/', start);
}
Node node = new Node();
node.FilePath = path;
node.Name = path.Substring(start);
node.IsFile = true;
node.IsNodeExpanded = false;
lastFolder.Children.Add(node);
}
}
folders.Clear();
SortTreeNodes(fileTreeSource);
Dispatcher.Invoke(() => {
fileTree.ItemsSource = fileTreeSource;
filePreview.Text = "";
});
}
private async void FileTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
filePreview.Text = "";
var node = e.NewValue as Node;
if (node == null || !node.IsFile) return;
await Task.Run(() => {
var data = commit.GetTextFileContent(repo, node.FilePath);
Dispatcher.Invoke(() => filePreview.Text = data);
});
}
#endregion
#region TREE_COMMON
private void SortTreeNodes(List<Node> list) {
list.Sort((l, r) => {
if (l.IsFile) {
return r.IsFile ? l.Name.CompareTo(r.Name) : 1;
} else {
return r.IsFile ? -1 : l.Name.CompareTo(r.Name);
}
});
foreach (var sub in list) {
if (sub.Children.Count > 0) SortTreeNodes(sub.Children);
}
}
private ScrollViewer GetScrollViewer(FrameworkElement owner) {
if (owner == null) return null;
if (owner is ScrollViewer) return owner as ScrollViewer;
int n = VisualTreeHelper.GetChildrenCount(owner);
for (int i = 0; i < n; i++) {
var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement;
var deep = GetScrollViewer(child);
if (deep != null) return deep;
}
return null;
}
private void TreeMouseWheel(object sender, MouseWheelEventArgs e) {
var scroll = GetScrollViewer(sender as TreeView);
if (scroll == null) return;
if (e.Delta > 0) {
scroll.LineUp();
} else {
scroll.LineDown();
}
e.Handled = true;
}
private void TreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
var item = sender as TreeViewItem;
if (item == null) return;
var node = item.DataContext as Node;
if (node == null || !node.IsFile) return;
item.IsSelected = true;
ContextMenu menu = new ContextMenu();
if (node.Change == null || node.Change.Index != Git.Change.Status.Deleted) {
MenuItem history = new MenuItem();
history.Header = "File History";
history.Click += (o, ev) => {
var viewer = new FileHistories(repo, node.FilePath);
viewer.Show();
};
menu.Items.Add(history);
MenuItem blame = new MenuItem();
blame.Header = "Blame";
blame.Click += (obj, ev) => {
Blame viewer = new Blame(repo, node.FilePath, commit.SHA);
viewer.Show();
};
menu.Items.Add(blame);
MenuItem explore = new MenuItem();
explore.Header = "Reveal in File Explorer";
explore.Click += (o, ev) => {
var path = Path.GetFullPath(repo.Path + "\\" + node.FilePath);
Process.Start("explorer", $"/select,{path}");
e.Handled = true;
};
menu.Items.Add(explore);
MenuItem saveAs = new MenuItem();
saveAs.Header = "Save As ...";
saveAs.Click += (obj, ev) => {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.Description = node.FilePath;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
var path = Path.Combine(dialog.SelectedPath, node.Name);
repo.RunAndRedirect($"show {commit.SHA}:\"{node.FilePath}\"", path);
}
};
menu.Items.Add(saveAs);
}
MenuItem copyPath = new MenuItem();
copyPath.Header = "Copy Path";
copyPath.Click += (obj, ev) => {
Clipboard.SetText(node.FilePath);
};
menu.Items.Add(copyPath);
menu.IsOpen = true;
e.Handled = true;
}
#endregion
}
}

View file

@ -0,0 +1,92 @@
<UserControl x:Class="SourceGit.UI.CreateBranch"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
d:DesignHeight="192" d:DesignWidth="500" Height="224" Width="500">
<UserControl.Resources>
<converters:InverseBool x:Key="InverseBool"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Create Local Branch"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Based On :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path x:Name="basedOnType" Width="12" Style="{StaticResource Style.Icon}"/>
<Label x:Name="basedOnDesc" VerticalAlignment="Center" Content="master"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="New Branch Name :"/>
<TextBox Grid.Row="3" Grid.Column="1"
x:Name="txtName"
VerticalContentAlignment="Center"
Height="24"
helpers:TextBoxHelper.Placeholder="Enter branch name.">
<TextBox.Text>
<Binding ElementName="me" Path="BranchName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<helpers:BranchNameRule x:Name="nameValidator"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Local Changes :"/>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
<RadioButton Content="Stash &amp; Reapply" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me}"/>
<RadioButton Content="Discard" Margin="8,0,0,0" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me, Mode=OneWay, Converter={StaticResource InverseBool}}"/>
</StackPanel>
<CheckBox Grid.Row="5" Grid.Column="1"
x:Name="chkCheckout"
VerticalAlignment="Center"
IsChecked="True"
Content="Check out after created"/>
<Grid Grid.Row="7" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="8" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Create branch dialog
/// </summary>
public partial class CreateBranch : UserControl {
private Git.Repository repo = null;
private string based = null;
/// <summary>
/// New branch name.
/// </summary>
public string BranchName {
get;
set;
}
/// <summary>
/// Auto Stash
/// </summary>
public bool AutoStash { get; set; } = false;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
public CreateBranch(Git.Repository opened) {
InitializeComponent();
repo = opened;
nameValidator.Repo = opened;
}
/// <summary>
/// Create branch based on current head.
/// </summary>
/// <param name="repo"></param>
public static void Show(Git.Repository repo) {
var current = repo.CurrentBranch();
if (current != null) Show(repo, current);
}
/// <summary>
/// Create branch base on existed one.
/// </summary>
/// <param name="repo"></param>
/// <param name="branch"></param>
public static void Show(Git.Repository repo, Git.Branch branch) {
var dialog = new CreateBranch(repo);
dialog.based = branch.Name;
dialog.basedOnType.Data = dialog.FindResource("Icon.Branch") as Geometry;
dialog.basedOnDesc.Content = branch.Name;
if (!branch.IsLocal) dialog.txtName.Text = branch.Name.Substring(branch.Remote.Length + 1);
PopupManager.Show(dialog);
}
/// <summary>
/// Create branch based on tag.
/// </summary>
/// <param name="repo"></param>
/// <param name="tag"></param>
public static void Show(Git.Repository repo, Git.Tag tag) {
var dialog = new CreateBranch(repo);
dialog.based = tag.Name;
dialog.basedOnType.Data = dialog.FindResource("Icon.Tag") as Geometry;
dialog.basedOnDesc.Content = tag.Name;
PopupManager.Show(dialog);
}
/// <summary>
/// Create branch based on commit.
/// </summary>
/// <param name="repo"></param>
/// <param name="commit"></param>
public static void Show(Git.Repository repo, Git.Commit commit) {
var dialog = new CreateBranch(repo);
dialog.based = commit.SHA;
dialog.basedOnType.Data = dialog.FindResource("Icon.Commit") as Geometry;
dialog.basedOnDesc.Content = $"{commit.ShortSHA} {commit.Subject}";
PopupManager.Show(dialog);
}
/// <summary>
/// Start create branch.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtName)) return;
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
bool checkout = chkCheckout.IsChecked == true;
await Task.Run(() => {
if (checkout) {
bool stashed = false;
if (repo.LocalChanges().Count > 0 && AutoStash) {
Git.Stash.Push(repo, true, "CREATE BRANCH AUTO STASH", new List<string>());
stashed = true;
}
repo.Checkout($"-b {BranchName} {based}");
if (stashed) {
var stashes = repo.Stashes();
if (stashes.Count > 0) stashes[0].Pop(repo);
}
} else {
Git.Branch.Create(repo, BranchName, based);
}
});
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,68 @@
<UserControl x:Class="SourceGit.UI.CreateTag"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="224" d:DesignWidth="500" Width="500" Height="224">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="64"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Create Tag"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="New Tag At :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" x:Name="basedOnType" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}"/>
<Label x:Name="basedOnDesc" VerticalAlignment="Center" Content="xxx"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Tag Name :"/>
<TextBox Grid.Row="3" Grid.Column="1"
x:Name="tagName"
Height="24"
helpers:TextBoxHelper.Placeholder="Recommanded format v1.0.0-alpha">
<TextBox.Text>
<Binding ElementName="me" Path="TagName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<helpers:TagNameRule x:Name="nameValidator"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,4" Content="Tag Message :"/>
<TextBox Grid.Row="4" Grid.Column="1"
x:Name="tagMessage"
Height="56"
AcceptsReturn="True"
helpers:TextBoxHelper.Placeholder="Optional"/>
<Grid Grid.Row="6" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,92 @@
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SourceGit.UI {
/// <summary>
/// Create tag dialog
/// </summary>
public partial class CreateTag : UserControl {
private Git.Repository repo = null;
private string based = null;
/// <summary>
/// Tag name
/// </summary>
public string TagName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo"></param>
public CreateTag(Git.Repository opened) {
InitializeComponent();
repo = opened;
nameValidator.Repo = opened;
}
/// <summary>
/// Create tag using current branch.
/// </summary>
/// <param name="repo">Opened repository.</param>
public static void Show(Git.Repository repo) {
Show(repo, repo.Branches().First(b => b.IsCurrent));
}
/// <summary>
/// Create tag using branch
/// </summary>
/// <param name="repo"></param>
/// <param name="branch"></param>
public static void Show(Git.Repository repo, Git.Branch branch) {
if (branch == null) {
App.RaiseError("Empty repository!");
return;
}
var dialog = new CreateTag(repo);
dialog.based = branch.Head;
dialog.basedOnType.Data = dialog.FindResource("Icon.Branch") as Geometry;
dialog.basedOnDesc.Content = branch.Name;
PopupManager.Show(dialog);
}
/// <summary>
/// Create tag using commit.
/// </summary>
/// <param name="repo"></param>
/// <param name="commit"></param>
public static void Show(Git.Repository repo, Git.Commit commit) {
var dialog = new CreateTag(repo);
dialog.based = commit.SHA;
dialog.basedOnType.Data = dialog.FindResource("Icon.Commit") as Geometry;
dialog.basedOnDesc.Content = $"{commit.ShortSHA} {commit.Subject}";
PopupManager.Show(dialog);
}
/// <summary>
/// Start to create tag.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start(object sender, RoutedEventArgs e) {
tagName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(tagName)) return;
Git.Tag.Add(repo, TagName, based, tagMessage.Text);
PopupManager.Close();
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

450
SourceGit/UI/Dashboard.xaml Normal file
View file

@ -0,0 +1,450 @@
<UserControl x:Class="SourceGit.UI.Dashboard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:source="clr-namespace:SourceGit"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:git="clr-namespace:SourceGit.Git"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Unloaded="Cleanup">
<UserControl.Resources>
<RoutedUICommand x:Key="OpenSearchBarCommand" Text="OpenSearchBar"/>
<RoutedUICommand x:Key="HideSearchBarCommand" Text="HideSearchBar"/>
</UserControl.Resources>
<UserControl.InputBindings>
<KeyBinding Key="F" Modifiers="Ctrl" Command="{StaticResource OpenSearchBarCommand}"/>
<KeyBinding Key="ESC" Command="{StaticResource HideSearchBarCommand}"/>
</UserControl.InputBindings>
<UserControl.CommandBindings>
<CommandBinding Command="{StaticResource OpenSearchBarCommand}" Executed="OpenSearchBar"/>
<CommandBinding Command="{StaticResource HideSearchBarCommand}" Executed="HideSearchBar"/>
</UserControl.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- TitleBar -->
<Grid Grid.Row="0" Panel.ZIndex="9999">
<Border Background="{StaticResource Brush.BG1}">
<Border.Effect>
<DropShadowEffect ShadowDepth="2" Direction="270" Opacity=".3"/>
</Border.Effect>
</Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Navigation -->
<StackPanel Grid.Column="0" Margin="8,0,0,0" Orientation="Horizontal" HorizontalAlignment="Left">
<Button Click="Close" ToolTip="Back To Welcome" Padding="0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Manager}"/>
<Label Content="Repositories"/>
</StackPanel>
</Button>
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Navigator}"/>
<Path Margin="6,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}"/>
<Label x:Name="repoName"/>
</StackPanel>
<!-- Common Git Options -->
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Click="OpenFetch" Margin="4,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Fetch}"/>
<Label Content="Fetch"/>
</StackPanel>
</Button>
<Button Click="OpenPull" Margin="4,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Pull}"/>
<Label Content="Pull"/>
</StackPanel>
</Button>
<Button Click="OpenPush" Margin="4,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Push}"/>
<Label Content="Push"/>
</StackPanel>
</Button>
<Button Click="OpenStash" Margin="2,0,0,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.SaveStash}"/>
<Label Content="Stash"/>
</StackPanel>
</Button>
<Button Click="OpenApply" Margin="4,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Apply}"/>
<Label Content="Apply"/>
</StackPanel>
</Button>
</StackPanel>
<!-- External Options -->
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Click="OpenSearch" Margin="4,0" ToolTip="Search Commit">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
<Label Content="Search"/>
</StackPanel>
</Button>
<Button Click="OpenExplorer" Margin="4,0" ToolTip="Open In File Browser">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder.Open}"/>
<Label Content="Explore"/>
</StackPanel>
</Button>
<Button Click="OpenTerminal" Margin="4,0" ToolTip="Open Git Bash">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Terminal}"/>
<Label Content="Terminal"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Grid>
<!-- Main body -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="300"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left panel -->
<Grid Grid.Column="0" x:Name="main" Background="{StaticResource Brush.BG4}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.Resources>
<converters:BoolToCollapsed x:Key="Bool2Collapsed"/>
</Grid.Resources>
<!-- WORKSPACE -->
<Label Grid.Row="0" Margin="4,0,0,0" Content="WORKSPACE" Style="{StaticResource Style.Label.GroupHeader}" />
<ListView
Grid.Row="1"
x:Name="workspace"
Background="{StaticResource Brush.BG3}"
Style="{StaticResource Style.ListView.Borderless}">
<ListViewItem x:Name="historiesSwitch" Selected="SwitchHistories" IsSelected="True">
<StackPanel Margin="16,0,0,0" Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Histories}"/>
<Label Margin="4,0,0,0" Content="Histories"/>
</StackPanel>
</ListViewItem>
<ListViewItem x:Name="workingCopySwitch" Selected="SwitchWorkingCopy">
<Grid Margin="16,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.WorkingCopy}"/>
<Label Grid.Column="1" Margin="4,0,0,0" Content="Commit"/>
<Border Grid.Column="2" x:Name="localChangesBadge" Style="{StaticResource Style.Border.Badge}">
<Label x:Name="localChangesCount" Margin="4,-2,4,-2" Content="999" FontSize="10"/>
</Border>
</Grid>
</ListViewItem>
<ListViewItem x:Name="stashesSwitch" Selected="SwitchStashes">
<Grid Margin="16,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Stashes}"/>
<Label Grid.Column="1" Margin="4,0,0,0" Content="Stashes"/>
<Border Grid.Column="2" x:Name="stashBadge" Style="{StaticResource Style.Border.Badge}">
<Label x:Name="stashCount" Margin="4,-2,4,-2" Content="999" FontSize="10"/>
</Border>
</Grid>
</ListViewItem>
</ListView>
<!-- LOCAL BRANCHES -->
<Grid Grid.Row="2" Margin="4,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="LOCAL BRANCHES" Style="{StaticResource Style.Label.GroupHeader}"/>
<Button Grid.Column="1" Click="OpenGitFlow" Background="Transparent" ToolTip="GIT FLOW">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Flow}"/>
</Button>
<Button Grid.Column="3" Click="OpenNewBranch" Background="Transparent" ToolTip="NEW BRANCH">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Branch.Add}"/>
</Button>
</Grid>
<TreeView
Grid.Row="3"
x:Name="localBranchTree"
Background="{StaticResource Brush.BG3}"
FontFamily="Consolas"
LostFocus="TreeLostFocus"
SelectedItemChanged="LocalBranchSelected"
PreviewMouseWheel="TreeMouseWheel">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="LocalBranchContextMenuOpening"/>
<EventSetter Event="MouseDoubleClick" Handler="LocalBranchMouseDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label Grid.Column="1" x:Name="name" Content="{Binding Name}" Padding="4,0,0,0"/>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Border Style="{StaticResource Style.Border.Badge}" Visibility="{Binding TrackVisibility}">
<Label Margin="4,-2,4,-2" Content="{Binding Branch.UpstreamTrack}" FontSize="10"/>
</Border>
<ToggleButton
Visibility="{Binding FilterVisibility}"
IsChecked="{Binding IsFiltered, Mode=OneWay}"
Checked="FilterChanged"
Unchecked="FilterChanged"
Style="{StaticResource Style.ToggleButton.Filter}"
ToolTip="FILTER"/>
</StackPanel>
</Grid>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter TargetName="name" Property="FontWeight" Value="ExtraBold"/>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Check}"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
<Condition Binding="{Binding IsExpanded}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
<Condition Binding="{Binding IsExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<!-- REMOTES -->
<Grid Grid.Row="4" Margin="4,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="REMOTES" Style="{StaticResource Style.Label.GroupHeader}"/>
<Button Grid.Column="1" Click="OpenRemote" ToolTip="ADD REMOTE">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Remote.Add}"/>
</Button>
</Grid>
<TreeView
Grid.Row="5"
x:Name="remoteBranchTree"
Background="{StaticResource Brush.BG3}"
FontFamily="Consolas"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectedItemChanged="RemoteBranchSelected"
LostFocus="TreeLostFocus"
PreviewMouseWheel="TreeMouseWheel">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="RemoteContextMenuOpening"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:RemoteNode}" ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
<TextBlock Grid.Column="1" x:Name="name" Text="{Binding Name}" Padding="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}" ClipToBounds="True"/>
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:BranchNode}" ItemsSource="{Binding Children}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<TextBlock Grid.Column="1" x:Name="name" Text="{Binding Name}" Padding="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}" ClipToBounds="True"/>
<ToggleButton
Grid.Column="2"
Visibility="{Binding FilterVisibility}"
IsChecked="{Binding IsFiltered, Mode=OneWay}"
Checked="FilterChanged"
Unchecked="FilterChanged"
Style="{StaticResource Style.ToggleButton.Filter}"
ToolTip="FILTER"/>
</Grid>
<HierarchicalDataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
<Condition Binding="{Binding IsExpanded}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
<Condition Binding="{Binding IsExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<!-- TAGS -->
<ToggleButton
x:Name="tagListToggle"
Grid.Row="6"
Style="{StaticResource Style.ToggleButton.Expender}"
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIShowTags, Mode=TwoWay}">
<Grid Margin="4,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="tagCount" Content="TAGS" Style="{StaticResource Style.Label.GroupHeader}"/>
<Button Grid.Column="1" Click="OpenNewTag" ToolTip="NEW TAG">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag.Add}"/>
</Button>
</Grid>
</ToggleButton>
<ListView
Grid.Row="7"
x:Name="tagList"
Visibility="{Binding ElementName=tagListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
Background="{StaticResource Brush.BG3}"
Height="200"
LostFocus="TagLostFocus"
SelectionChanged="TagSelectionChanged"
ContextMenuOpening="TagContextMenuOpening"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Style="{StaticResource Style.ListView.Borderless}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type git:Tag}">
<Grid Margin="16, 0, 0, 0" Height="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}"/>
<Label Grid.Column="1" Margin="4,0,0,0" Content="{Binding Name}" Padding="4,0,0,0"/>
<ToggleButton
Grid.Column="2"
IsChecked="{Binding IsFiltered, Mode=TwoWay}"
Checked="FilterChanged"
Unchecked="FilterChanged"
Style="{StaticResource Style.ToggleButton.Filter}"
ToolTip="FILTER"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
<!-- Right -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Abort panel -->
<Grid x:Name="abortPanel" Grid.Row="0" Background="LightGoldenrodYellow" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="txtMergeProcessing" FontWeight="DemiBold" Foreground="{StaticResource Brush.BG4}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button x:Name="btnResolve" Click="Resolve" Content="RESOLVE" Margin="4">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.Bordered}">
<Setter Property="Background" Value="{StaticResource Brush.BG1}"/>
<Setter Property="Margin" Value="2"/>
</Style>
</Button.Style>
</Button>
<Button x:Name="btnContinue" Click="Continue" Content="CONTINUE" Style="{StaticResource Style.Button.AccentBordered}" Margin="4"/>
<Button Grid.Column="3" Click="Abort" Content="ABORT" Style="{StaticResource Style.Button.Bordered}" Foreground="{StaticResource Brush.BG1}" Margin="4"/>
</StackPanel>
</Grid>
<!-- Others -->
<local:Histories Grid.Row="1" x:Name="histories" Visibility="Visible"/>
<local:WorkingCopy Grid.Row="1" x:Name="commits" Visibility="Collapsed"/>
<local:Stashes Grid.Row="1" x:Name="stashes" Visibility="Collapsed"/>
</Grid>
</Grid>
<!-- Popups -->
<local:PopupManager Grid.Row="1"/>
</Grid>
</UserControl>

View file

@ -0,0 +1,984 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace SourceGit.UI {
/// <summary>
/// Branch node in tree.
/// </summary>
public class BranchNode {
public string Name { get; set; }
public Git.Branch Branch { get; set; }
public bool IsExpanded { get; set; }
public bool IsCurrent => Branch != null ? Branch.IsCurrent : false;
public bool IsFiltered => Branch != null ? Branch.IsFiltered : false;
public string Track => Branch != null ? Branch.UpstreamTrack : "";
public Visibility FilterVisibility => Branch == null ? Visibility.Collapsed : Visibility.Visible;
public Visibility TrackVisibility => (Branch != null && !Branch.IsSameWithUpstream) ? Visibility.Visible : Visibility.Collapsed;
public List<BranchNode> Children { get; set; }
}
/// <summary>
/// Remote node in tree.
/// </summary>
public class RemoteNode {
public string Name { get; set; }
public bool IsExpanded { get; set; }
public List<BranchNode> Children { get; set; }
}
/// <summary>
/// Dashboard for opened repository.
/// </summary>
public partial class Dashboard : UserControl {
private Git.Repository repo = null;
private List<BranchNode> cachedLocalBranches = new List<BranchNode>();
private List<RemoteNode> cachedRemotes = new List<RemoteNode>();
private string abortCommand = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo">Opened repository.</param>
public Dashboard(Git.Repository opened) {
opened.OnWorkingCopyChanged = UpdateLocalChanges;
opened.OnTagChanged = UpdateTags;
opened.OnStashChanged = UpdateStashes;
opened.OnBranchChanged = () => UpdateBranches(false);
opened.OnCommitsChanged = UpdateHistories;
opened.OnNavigateCommit = commit => {
Dispatcher.Invoke(() => {
workspace.SelectedItem = historiesSwitch;
histories.Navigate(commit);
});
};
InitializeComponent();
repo = opened;
repoName.Content = repo.Name;
histories.Repo = opened;
commits.Repo = opened;
UpdateBranches();
UpdateHistories();
UpdateLocalChanges();
UpdateStashes();
UpdateTags();
}
#region DATA_UPDATE
private void UpdateHistories() {
Dispatcher.Invoke(() => {
histories.SetLoadingEnabled(true);
});
Task.Run(() => {
var args = "-5000 ";
if (repo.LogFilters.Count > 0) {
args = args + string.Join(" ", repo.LogFilters);
} else {
args = args + "--branches --remotes --tags";
}
var commits = repo.Commits(args);
histories.SetCommits(commits);
});
}
private void UpdateLocalChanges() {
Task.Run(() => {
var changes = repo.LocalChanges();
var conflicts = commits.SetData(changes);
Dispatcher.Invoke(() => {
localChangesBadge.Visibility = changes.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
localChangesCount.Content = changes.Count;
btnContinue.Visibility = conflicts ? Visibility.Collapsed : Visibility.Visible;
DetectMergeState();
});
});
}
private void UpdateStashes() {
Task.Run(() => {
var data = repo.Stashes();
Dispatcher.Invoke(() => {
stashBadge.Visibility = data.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
stashCount.Content = data.Count;
stashes.SetData(repo, data);
});
});
}
private void BackupBranchNodeExpandState(Dictionary<string, bool> states, List<BranchNode> nodes, string prefix) {
foreach (var node in nodes) {
var path = prefix + "/" + node.Name;
states.Add(path, node.IsExpanded);
BackupBranchNodeExpandState(states, node.Children, path);
}
}
private void MakeBranchNode(Git.Branch branch, List<BranchNode> collection, Dictionary<string, BranchNode> folders, Dictionary<string, bool> expandStates, string prefix) {
var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (!branch.IsLocal) {
if (subs.Length < 2) return;
subs = subs.Skip(1).ToArray();
}
branch.IsFiltered = repo.LogFilters.Contains(branch.FullName);
if (subs.Length == 1) {
var node = new BranchNode() {
Name = subs[0],
Branch = branch,
Children = new List<BranchNode>(),
};
collection.Add(node);
} else {
BranchNode lastFolder = null;
string path = prefix;
for (int i = 0; i < subs.Length - 1; i++) {
path = path + "/" + subs[i];
if (folders.ContainsKey(path)) {
lastFolder = folders[path];
} else if (lastFolder == null) {
lastFolder = new BranchNode() {
Name = subs[i],
IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false,
Children = new List<BranchNode>(),
};
collection.Add(lastFolder);
folders.Add(path, lastFolder);
} else {
var folder = new BranchNode() {
Name = subs[i],
IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false,
Children = new List<BranchNode>(),
};
lastFolder.Children.Add(folder);
folders.Add(path, folder);
lastFolder = folder;
}
}
BranchNode node = new BranchNode();
node.Name = subs[subs.Length - 1];
node.Branch = branch;
node.Children = new List<BranchNode>();
lastFolder.Children.Add(node);
}
}
private void SortBranchNodes(List<BranchNode> collection) {
collection.Sort((l, r) => {
if (l.Branch != null) {
return r.Branch != null ? l.Branch.Name.CompareTo(r.Branch.Name) : -1;
} else {
return r.Branch == null ? l.Name.CompareTo(r.Name) : 1;
}
});
foreach (var sub in collection) {
if (sub.Children.Count > 0) SortBranchNodes(sub.Children);
}
}
private void UpdateBranches(bool force = true) {
Task.Run(() => {
var branches = repo.Branches(force);
var localBranchNodes = new List<BranchNode>();
var remoteNodes = new List<RemoteNode>();
var remoteMap = new Dictionary<string, RemoteNode>();
var folders = new Dictionary<string, BranchNode>();
var states = new Dictionary<string, bool>();
BackupBranchNodeExpandState(states, cachedLocalBranches, "locals");
foreach (var r in cachedRemotes) {
var prefix = $"remotes/{r.Name}";
states.Add(prefix, r.IsExpanded);
BackupBranchNodeExpandState(states, r.Children, prefix);
}
foreach (var b in branches) {
if (b.IsLocal) {
MakeBranchNode(b, localBranchNodes, folders, states, "locals");
} else {
RemoteNode remote = null;
if (!remoteMap.ContainsKey(b.Remote)) {
var key = "remotes/" + b.Remote;
remote = new RemoteNode() {
Name = b.Remote,
IsExpanded = states.ContainsKey(key) ? states[key] : false,
Children = new List<BranchNode>(),
};
remoteNodes.Add(remote);
remoteMap.Add(b.Remote, remote);
} else {
remote = remoteMap[b.Remote];
}
MakeBranchNode(b, remote.Children, folders, states, "remotes");
}
}
SortBranchNodes(localBranchNodes);
foreach (var r in remoteNodes) SortBranchNodes(r.Children);
cachedLocalBranches = localBranchNodes;
cachedRemotes = remoteNodes;
Dispatcher.Invoke(() => {
localBranchTree.ItemsSource = localBranchNodes;
remoteBranchTree.ItemsSource = remoteNodes;
});
});
}
private void UpdateTags() {
Task.Run(() => {
var tags = repo.Tags(true);
foreach (var t in tags) t.IsFiltered = repo.LogFilters.Contains(t.Name);
Dispatcher.Invoke(() => {
tagCount.Content = $"TAGS ({tags.Count})";
tagList.ItemsSource = tags;
});
});
}
private void Cleanup(object sender, RoutedEventArgs e) {
localBranchTree.ItemsSource = null;
remoteBranchTree.ItemsSource = null;
tagList.ItemsSource = null;
cachedLocalBranches.Clear();
cachedRemotes.Clear();
}
#endregion
#region TOOLBAR
private void Close(object sender, RoutedEventArgs e) {
if (PopupManager.IsLocked()) return;
PopupManager.Close();
cachedLocalBranches.Clear();
cachedRemotes.Clear();
repo.Close();
}
private void OpenFetch(object sender, RoutedEventArgs e) {
Fetch.Show(repo);
}
private void OpenPull(object sender, RoutedEventArgs e) {
Pull.Show(repo);
}
private void OpenPush(object sender, RoutedEventArgs e) {
Push.Show(repo);
}
private void OpenStash(object sender, RoutedEventArgs e) {
Stash.Show(repo, new List<string>());
}
private void OpenApply(object sender, RoutedEventArgs e) {
Apply.Show(repo);
}
private void OpenSearch(object sender, RoutedEventArgs e) {
workspace.SelectedItem = historiesSwitch;
if (histories.searchBar.Margin.Top == 0) {
histories.HideSearchBar();
} else {
histories.OpenSearchBar();
}
}
private void OpenExplorer(object sender, RoutedEventArgs e) {
Process.Start(repo.Path);
}
private void OpenTerminal(object sender, RoutedEventArgs e) {
var bash = Path.Combine(App.Preference.GitExecutable, "..", "bash.exe");
if (!File.Exists(bash)) {
App.RaiseError("Can NOT locate bash.exe. Make sure bash.exe exists under the same folder with git.exe");
return;
}
var start = new ProcessStartInfo();
start.WorkingDirectory = repo.Path;
start.FileName = bash;
Process.Start(start);
}
#endregion
#region HOT_KEYS
public void OpenSearchBar(object sender, ExecutedRoutedEventArgs e) {
workspace.SelectedItem = historiesSwitch;
histories.OpenSearchBar();
}
public void HideSearchBar(object sender, ExecutedRoutedEventArgs e) {
if (histories.Visibility == Visibility.Visible) {
histories.HideSearchBar();
}
}
#endregion
#region MERGE_ABORTS
public void DetectMergeState() {
var gitDir = Path.Combine(repo.Path, ".git");
var cherryPickMerge = Path.Combine(gitDir, "CHERRY_PICK_HEAD");
var rebaseMerge = Path.Combine(gitDir, "REBASE_HEAD");
var revertMerge = Path.Combine(gitDir, "REVERT_HEAD");
var otherMerge = Path.Combine(gitDir, "MERGE_HEAD");
if (File.Exists(cherryPickMerge)) {
abortCommand = "cherry-pick";
txtMergeProcessing.Content = "Cherry-Pick merge request detected! Press 'Abort' to restore original HEAD";
} else if (File.Exists(rebaseMerge)) {
abortCommand = "rebase";
txtMergeProcessing.Content = "Rebase merge request detected! Press 'Abort' to restore original HEAD";
} else if (File.Exists(revertMerge)) {
abortCommand = "revert";
txtMergeProcessing.Content = "Revert merge request detected! Press 'Abort' to restore original HEAD";
} else if (File.Exists(otherMerge)) {
abortCommand = "merge";
txtMergeProcessing.Content = "Merge request detected! Press 'Abort' to restore original HEAD";
} else {
abortCommand = null;
}
if (abortCommand != null) {
abortPanel.Visibility = Visibility.Visible;
if (commits.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Collapsed;
} else {
btnResolve.Visibility = Visibility.Visible;
}
commits.LoadMergeMessage();
} else {
abortPanel.Visibility = Visibility.Collapsed;
}
}
private void Resolve(object sender, RoutedEventArgs e) {
workspace.SelectedItem = workingCopySwitch;
}
private async void Continue(object sender, RoutedEventArgs e) {
if (abortCommand == null) return;
await Task.Run(() => {
repo.SetWatcherEnabled(false);
var errs = repo.RunCommand($"-c core.editor=true {abortCommand} --continue", null);
repo.AssertCommand(errs);
});
commits.ClearMessage();
}
private async void Abort(object sender, RoutedEventArgs e) {
if (abortCommand == null) return;
await Task.Run(() => {
repo.SetWatcherEnabled(false);
var errs = repo.RunCommand($"{abortCommand} --abort", null);
repo.AssertCommand(errs);
});
commits.ClearMessage();
}
#endregion
#region WORKSPACE
private void SwitchWorkingCopy(object sender, RoutedEventArgs e) {
if (commits == null || histories == null || stashes == null) return;
commits.Visibility = Visibility.Visible;
histories.Visibility = Visibility.Collapsed;
stashes.Visibility = Visibility.Collapsed;
if (abortPanel.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Collapsed;
}
}
private void SwitchHistories(object sender, RoutedEventArgs e) {
if (commits == null || histories == null || stashes == null) return;
commits.Visibility = Visibility.Collapsed;
histories.Visibility = Visibility.Visible;
stashes.Visibility = Visibility.Collapsed;
if (abortPanel.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Visible;
}
}
private void SwitchStashes(object sender, RoutedEventArgs e) {
if (commits == null || histories == null || stashes == null) return;
commits.Visibility = Visibility.Collapsed;
histories.Visibility = Visibility.Collapsed;
stashes.Visibility = Visibility.Visible;
if (abortPanel.Visibility == Visibility.Visible) {
btnResolve.Visibility = Visibility.Visible;
}
}
#endregion
#region LOCAL_BRANCHES
private void OpenNewBranch(object sender, RoutedEventArgs e) {
CreateBranch.Show(repo);
}
private void OpenGitFlow(object sender, RoutedEventArgs ev) {
var button = sender as Button;
if (button.ContextMenu == null) {
button.ContextMenu = new ContextMenu();
button.ContextMenu.PlacementTarget = button;
button.ContextMenu.Placement = PlacementMode.Bottom;
button.ContextMenu.StaysOpen = false;
button.ContextMenu.Focusable = true;
} else {
button.ContextMenu.Items.Clear();
}
if (repo.IsGitFlowEnabled()) {
var startFeature = new MenuItem();
startFeature.Header = "Start Feature ...";
startFeature.Click += (o, e) => {
GitFlowStartBranch.Show(repo, Git.Branch.Type.Feature);
e.Handled = true;
};
var startRelease = new MenuItem();
startRelease.Header = "Start Release ...";
startRelease.Click += (o, e) => {
GitFlowStartBranch.Show(repo, Git.Branch.Type.Release);
e.Handled = true;
};
var startHotfix = new MenuItem();
startHotfix.Header = "Start Hotfix ...";
startHotfix.Click += (o, e) => {
GitFlowStartBranch.Show(repo, Git.Branch.Type.Hotfix);
e.Handled = true;
};
button.ContextMenu.Items.Add(startFeature);
button.ContextMenu.Items.Add(startRelease);
button.ContextMenu.Items.Add(startHotfix);
} else {
var init = new MenuItem();
init.Header = "Initialize Git-Flow";
init.Click += (o, e) => {
GitFlowSetup.Show(repo);
e.Handled = true;
};
button.ContextMenu.Items.Add(init);
}
button.ContextMenu.IsOpen = true;
ev.Handled = true;
}
private void LocalBranchSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
var node = e.NewValue as BranchNode;
if (node == null || node.Branch == null) return;
repo.OnNavigateCommit?.Invoke(node.Branch.Head);
}
private void LocalBranchMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var node = (sender as TreeViewItem).DataContext as BranchNode;
if (node == null || node.Branch == null) return;
Task.Run(() => repo.Checkout(node.Branch.Name));
}
private void LocalBranchContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var node = (sender as TreeViewItem).DataContext as BranchNode;
if (node == null || node.Branch == null) return;
var menu = new ContextMenu();
var branch = node.Branch;
var push = new MenuItem();
push.Header = $"Push '{branch.Name}'";
push.Click += (o, e) => {
Push.Show(repo, branch);
e.Handled = true;
};
if (branch.IsCurrent) {
var discard = new MenuItem();
discard.Header = "Discard all changes";
discard.Click += (o, e) => {
Discard.Show(repo, null);
e.Handled = true;
};
menu.Items.Add(discard);
menu.Items.Add(new Separator());
if (!string.IsNullOrEmpty(branch.Upstream)) {
var upstream = branch.Upstream.Substring(13);
var fastForward = new MenuItem();
fastForward.Header = $"Fast-Forward to '{upstream}'";
fastForward.Click += (o, e) => {
Merge.StartDirectly(repo, upstream, branch.Name);
e.Handled = true;
};
var pull = new MenuItem();
pull.Header = $"Pull '{upstream}'";
pull.Click += (o, e) => {
Pull.Show(repo);
e.Handled = true;
};
menu.Items.Add(fastForward);
menu.Items.Add(pull);
}
menu.Items.Add(push);
} else {
var current = repo.CurrentBranch();
var checkout = new MenuItem();
checkout.Header = $"Checkout {branch.Name}";
checkout.Click += (o, e) => {
Task.Run(() => repo.Checkout(node.Branch.Name));
e.Handled = true;
};
menu.Items.Add(checkout);
menu.Items.Add(new Separator());
menu.Items.Add(push);
var merge = new MenuItem();
merge.Header = $"Merge '{branch.Name}' into '{current.Name}'";
merge.Click += (o, e) => {
Merge.Show(repo, branch.Name, current.Name);
e.Handled = true;
};
menu.Items.Add(merge);
var rebase = new MenuItem();
rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'";
rebase.Click += (o, e) => {
Rebase.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(rebase);
}
if (branch.Kind != Git.Branch.Type.Normal) {
menu.Items.Add(new Separator());
var icon = new System.Windows.Shapes.Path();
icon.Style = FindResource("Style.Icon") as Style;
icon.Data = FindResource("Icon.Flow") as Geometry;
icon.Width = 10;
var finish = new MenuItem();
finish.Header = $"Git Flow - Finish '{branch.Name}'";
finish.Icon = icon;
finish.Click += (o, e) => {
GitFlowFinishBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(finish);
}
var rename = new MenuItem();
rename.Header = $"Rename '{branch.Name}'";
rename.Click += (o, e) => {
RenameBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(new Separator());
menu.Items.Add(rename);
var delete = new MenuItem();
delete.Header = $"Delete '{branch.Name}'";
delete.IsEnabled = !branch.IsCurrent;
delete.Click += (o, e) => {
DeleteBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(delete);
menu.Items.Add(new Separator());
var createBranch = new MenuItem();
createBranch.Header = "Create Branch";
createBranch.Click += (o, e) => {
CreateBranch.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(createBranch);
var createTag = new MenuItem();
createTag.Header = "Create Tag";
createTag.Click += (o, e) => {
CreateTag.Show(repo, branch);
e.Handled = true;
};
menu.Items.Add(createTag);
menu.Items.Add(new Separator());
var copy = new MenuItem();
copy.Header = "Copy Branch Name";
copy.Click += (o, e) => {
Clipboard.SetText(branch.Name);
e.Handled = true;
};
menu.Items.Add(copy);
menu.IsOpen = true;
ev.Handled = true;
}
#endregion
#region REMOTE_BRANCHES
private void OpenRemote(object sender, RoutedEventArgs e) {
Remote.Show(repo);
}
private void OpenRemoteContextMenu(RemoteNode node) {
var fetch = new MenuItem();
fetch.Header = $"Fetch '{node.Name}'";
fetch.Click += (o, e) => {
Fetch.Show(repo, node.Name);
e.Handled = true;
};
var edit = new MenuItem();
edit.Header = $"Edit '{node.Name}'";
edit.Click += (o, e) => {
var remotes = repo.Remotes();
var found = remotes.Find(r => r.Name == node.Name);
if (found != null) Remote.Show(repo, found);
e.Handled = true;
};
var delete = new MenuItem();
delete.Header = $"Delete '{node.Name}'";
delete.Click += (o, e) => {
DeleteRemote.Show(repo, node.Name);
e.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Remote URL";
copy.Click += (o, e) => {
var remotes = repo.Remotes();
var found = remotes.Find(r => r.Name == node.Name);
if (found != null) Clipboard.SetText(found.URL);
e.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(fetch);
menu.Items.Add(new Separator());
menu.Items.Add(edit);
menu.Items.Add(delete);
menu.Items.Add(new Separator());
menu.Items.Add(copy);
menu.IsOpen = true;
}
private void OpenRemoteBranchContextMenu(BranchNode node) {
var branch = node.Branch;
var current = repo.CurrentBranch();
if (current == null) return;
var checkout = new MenuItem();
checkout.Header = $"Checkout '{branch.Name}'";
checkout.Click += (o, e) => {
var branches = repo.Branches();
var tracked = null as Git.Branch;
var upstream = $"refs/remotes/{branch.Name}";
foreach (var b in branches) {
if (b.IsLocal && b.Upstream == upstream) {
tracked = b;
break;
}
}
if (tracked == null) {
CreateBranch.Show(repo, branch);
} else if (!tracked.IsCurrent) {
Task.Run(() => repo.Checkout(tracked.Name));
}
e.Handled = true;
};
var pull = new MenuItem();
pull.Header = $"Pull '{branch.Name}' into '{current.Name}'";
pull.Click += (o, e) => {
Pull.Show(repo, branch.Name);
e.Handled = true;
};
var merge = new MenuItem();
merge.Header = $"Merge '{branch.Name}' into '{current.Name}'";
merge.Click += (o, e) => {
Merge.Show(repo, branch.Name, current.Name);
e.Handled = true;
};
var rebase = new MenuItem();
rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'";
rebase.Click += (o, e) => {
Rebase.Show(repo, branch);
e.Handled = true;
};
var delete = new MenuItem();
delete.Header = $"Delete '{branch.Name}'";
delete.Click += (o, e) => {
DeleteBranch.Show(repo, branch);
e.Handled = true;
};
var createBranch = new MenuItem();
createBranch.Header = "Create New Branch";
createBranch.Click += (o, e) => {
CreateBranch.Show(repo, branch);
e.Handled = true;
};
var createTag = new MenuItem();
createTag.Header = "Create New Tag";
createTag.Click += (o, e) => {
CreateTag.Show(repo, branch);
e.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Branch Name";
copy.Click += (o, e) => {
Clipboard.SetText(branch.Name);
e.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(checkout);
menu.Items.Add(new Separator());
menu.Items.Add(pull);
menu.Items.Add(merge);
menu.Items.Add(rebase);
menu.Items.Add(new Separator());
menu.Items.Add(delete);
menu.Items.Add(new Separator());
menu.Items.Add(createBranch);
menu.Items.Add(createTag);
menu.Items.Add(new Separator());
menu.Items.Add(copy);
menu.IsOpen = true;
}
private void RemoteBranchSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
var node = e.NewValue as BranchNode;
if (node == null || node.Branch == null) return;
repo.OnNavigateCommit?.Invoke(node.Branch.Head);
}
private void RemoteContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var remoteNode = (sender as TreeViewItem).DataContext as RemoteNode;
if (remoteNode != null) {
OpenRemoteContextMenu(remoteNode);
ev.Handled = true;
return;
}
var branchNode = (sender as TreeViewItem).DataContext as BranchNode;
if (branchNode != null && branchNode.Branch != null) {
OpenRemoteBranchContextMenu(branchNode);
ev.Handled = true;
return;
}
}
#endregion
#region TAGS
private void OpenNewTag(object sender, RoutedEventArgs e) {
CreateTag.Show(repo);
}
private void TagLostFocus(object sender, RoutedEventArgs e) {
(sender as ListView).UnselectAll();
}
private void TagSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count == 1) {
var item = e.AddedItems[0] as Git.Tag;
repo.OnNavigateCommit?.Invoke(item.SHA);
}
}
private void TagContextMenuOpening(object sender, ContextMenuEventArgs e) {
var tag = (sender as ListView).SelectedItem as Git.Tag;
if (tag == null) return;
var createBranch = new MenuItem();
createBranch.Header = "Create New Branch";
createBranch.Click += (o, ev) => {
CreateBranch.Show(repo, tag);
ev.Handled = true;
};
var pushTag = new MenuItem();
pushTag.Header = $"Push '{tag.Name}'";
pushTag.Click += (o, ev) => {
PushTag.Show(repo, tag);
ev.Handled = true;
};
var deleteTag = new MenuItem();
deleteTag.Header = $"Delete '{tag.Name}'";
deleteTag.Click += (o, ev) => {
DeleteTag.Show(repo, tag);
ev.Handled = true;
};
var copy = new MenuItem();
copy.Header = "Copy Name";
copy.Click += (o, ev) => {
Clipboard.SetText(tag.Name);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(createBranch);
menu.Items.Add(new Separator());
menu.Items.Add(pushTag);
menu.Items.Add(deleteTag);
menu.Items.Add(new Separator());
menu.Items.Add(copy);
menu.IsOpen = true;
e.Handled = true;
}
#endregion
#region TREES
private TreeViewItem FindTreeViewItem(ItemsControl item, BranchNode node) {
if (item == null) return null;
var data = item.DataContext as BranchNode;
if (data == node) return item as TreeViewItem;
for (int i = 0; i < item.Items.Count; i++) {
var childContainer = item.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
var child = FindTreeViewItem(childContainer, node);
if (child != null) return child;
}
return null;
}
private void TreeLostFocus(object sender, RoutedEventArgs e) {
var tree = sender as TreeView;
var remote = tree.SelectedItem as RemoteNode;
if (remote != null) {
var remoteItem = tree.ItemContainerGenerator.ContainerFromItem(remote) as TreeViewItem;
if (remoteItem != null) remoteItem.IsSelected = false;
return;
}
var node = tree.SelectedItem as BranchNode;
if (node == null) return;
var item = FindTreeViewItem(tree, node);
if (item != null) item.IsSelected = false;
}
private ScrollViewer GetScrollViewer(FrameworkElement owner) {
if (owner == null) return null;
if (owner is ScrollViewer) return owner as ScrollViewer;
int n = VisualTreeHelper.GetChildrenCount(owner);
for (int i = 0; i < n; i++) {
var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement;
var deep = GetScrollViewer(child);
if (deep != null) return deep;
}
return null;
}
private void TreeMouseWheel(object sender, MouseWheelEventArgs e) {
var scroll = GetScrollViewer(sender as TreeView);
if (scroll == null) return;
if (e.Delta > 0) {
scroll.LineUp();
} else {
scroll.LineDown();
}
e.Handled = true;
}
#endregion
#region FILETER
private void FilterChanged(object sender, RoutedEventArgs e) {
var toggle = sender as ToggleButton;
if (toggle == null) return;
if (toggle.DataContext is BranchNode) {
var branch = (toggle.DataContext as BranchNode).Branch;
if (branch == null) return;
if (toggle.IsChecked == true) {
if (!repo.LogFilters.Contains(branch.FullName)) {
repo.LogFilters.Add(branch.FullName);
}
if (!string.IsNullOrEmpty(branch.Upstream) && !repo.LogFilters.Contains(branch.Upstream)) {
repo.LogFilters.Add(branch.Upstream);
UpdateBranches(false);
}
} else {
repo.LogFilters.Remove(branch.FullName);
if (!string.IsNullOrEmpty(branch.Upstream)) {
repo.LogFilters.Remove(branch.Upstream);
UpdateBranches(false);
}
}
}
if (toggle.DataContext is Git.Tag) {
var tag = toggle.DataContext as Git.Tag;
if (toggle.IsChecked == true) {
if (!repo.LogFilters.Contains(tag.Name)) {
repo.LogFilters.Add(tag.Name);
}
} else {
repo.LogFilters.Remove(tag.Name);
}
}
UpdateHistories();
}
#endregion
}
}

View file

@ -0,0 +1,51 @@
<UserControl x:Class="SourceGit.UI.DeleteBranch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SourceGit.UI"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Branch"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Branch :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}" Margin="4,0"/>
<Label x:Name="branchName"/>
</StackPanel>
<Grid Grid.Row="4" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,66 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Confirm to delete branch
/// </summary>
public partial class DeleteBranch : UserControl {
private Git.Repository repo = null;
private Git.Branch branch = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository.</param>
/// <param name="target">Branch to be deleted.</param>
public DeleteBranch(Git.Repository opened, Git.Branch target) {
InitializeComponent();
repo = opened;
branch = target;
branchName.Content = target.Name;
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="opened"></param>
/// <param name="branch"></param>
public static void Show(Git.Repository opened, Git.Branch branch) {
PopupManager.Show(new DeleteBranch(opened, branch));
}
/// <summary>
/// Delete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => branch.Delete(repo));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,50 @@
<UserControl x:Class="SourceGit.UI.DeleteRemote"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Remote"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}" Margin="4,0"/>
<Label x:Name="remoteName"/>
</StackPanel>
<Grid Grid.Row="4" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,67 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Confirm to delete a remote
/// </summary>
public partial class DeleteRemote : UserControl {
private Git.Repository repo = null;
private string remote = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="target">Remote to be deleted</param>
public DeleteRemote(Git.Repository opened, string target) {
InitializeComponent();
repo = opened;
remote = target;
remoteName.Content = target;
}
/// <summary>
/// Show this dialog
/// </summary>
/// <param name="opened"></param>
/// <param name="remote"></param>
public static void Show(Git.Repository opened, string remote) {
PopupManager.Show(new DeleteRemote(opened, remote));
}
/// <summary>
/// Delete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => Git.Remote.Delete(repo, remote));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,53 @@
<UserControl x:Class="SourceGit.UI.DeleteTag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Tag"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Tag :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}" Margin="4,0"/>
<Label x:Name="tagName"/>
</StackPanel>
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkWithRemote" Content="Delete from remote repositories"/>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,69 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Delete tag dialog.
/// </summary>
public partial class DeleteTag : UserControl {
private Git.Repository repo = null;
private Git.Tag tag = null;
/// <summary>
/// Constructor
/// </summary>
/// <param name="repo">Opened repo</param>
/// <param name="tag">Delete tag</param>
public DeleteTag(Git.Repository repo, Git.Tag tag) {
this.repo = repo;
this.tag = tag;
InitializeComponent();
tagName.Content = tag.Name;
}
/// <summary>
/// Display this dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="tag"></param>
public static void Show(Git.Repository repo, Git.Tag tag) {
PopupManager.Show(new DeleteTag(repo, tag));
}
/// <summary>
/// Start request.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
var push = chkWithRemote.IsChecked == true;
await Task.Run(() => Git.Tag.Delete(repo, tag.Name, push));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,139 @@
<UserControl x:Class="SourceGit.UI.DiffViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
FontFamily="Consolas">
<Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,4">
<StackPanel x:Name="orgFileNamePanel" Orientation="Horizontal">
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
<TextBlock x:Name="orgFileName" Margin="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
<TextBlock Margin="8,0" VerticalAlignment="Center" Text="→" Foreground="{StaticResource Brush.FG}"/>
</StackPanel>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
<TextBlock x:Name="fileName" Margin="4,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
</StackPanel>
</Border>
<Grid Grid.Row="1" ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*" MinWidth="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="leftLineNumber"
Grid.Column="0"
AcceptsReturn="True"
AcceptsTab="True"
BorderThickness="0"
Background="Transparent"
IsReadOnly="True"
Margin="4,0,4,0"
FontSize="13"
HorizontalContentAlignment="Right"
VerticalAlignment="Stretch"/>
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
<RichTextBox
x:Name="leftText"
Grid.Column="2"
AcceptsReturn="True"
AcceptsTab="True"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent"
Foreground="{StaticResource Brush.FG}"
Height="Auto"
FontSize="13"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
RenderOptions.ClearTypeHint="Enabled"
ScrollViewer.ScrollChanged="OnViewerScroll"
PreviewMouseWheel="OnViewerMouseWheel"
SizeChanged="LeftSizeChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RichTextBox.Document>
<FlowDocument PageWidth="0"/>
</RichTextBox.Document>
</RichTextBox>
</Grid>
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.Border2}"/>
<Grid Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox
x:Name="rightLineNumber"
Grid.Column="0"
AcceptsReturn="True"
AcceptsTab="True"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent"
Margin="4,0,4,0"
FontSize="13"
HorizontalContentAlignment="Right"
VerticalAlignment="Stretch"/>
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
<RichTextBox
x:Name="rightText"
Grid.Column="2"
AcceptsReturn="True"
AcceptsTab="True"
IsReadOnly="True"
BorderThickness="0"
Background="Transparent"
Foreground="{StaticResource Brush.FG}"
Height="Auto"
FontSize="13"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
RenderOptions.ClearTypeHint="Enabled"
ScrollViewer.ScrollChanged="OnViewerScroll"
PreviewMouseWheel="OnViewerMouseWheel"
SizeChanged="RightSizeChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RichTextBox.Document>
<FlowDocument PageWidth="0"/>
</RichTextBox.Document>
</RichTextBox>
</Grid>
</Grid>
<Border x:Name="mask" Grid.RowSpan="2" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Diff}"/>
<Label Margin="0,8,0,0" Content="SELECT FILE TO VIEW CHANGES" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
</Border>
</UserControl>

View file

@ -0,0 +1,294 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace SourceGit.UI {
/// <summary>
/// Viewer for git diff
/// </summary>
public partial class DiffViewer : UserControl {
private double minWidth = 0;
/// <summary>
/// Line mode.
/// </summary>
public enum LineMode {
Normal,
Indicator,
Empty,
Added,
Deleted,
}
/// <summary>
/// Constructor
/// </summary>
public DiffViewer() {
InitializeComponent();
Reset();
}
/// <summary>
///
/// </summary>
/// <param name="lines"></param>
/// <param name="file"></param>
/// <param name="orgFile"></param>
public void SetData(List<string> lines, string file, string orgFile = null) {
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
fileName.Text = file;
if (!string.IsNullOrEmpty(orgFile)) {
orgFileNamePanel.Visibility = Visibility.Visible;
orgFileName.Text = orgFile;
} else {
orgFileNamePanel.Visibility = Visibility.Collapsed;
}
leftText.Document.Blocks.Clear();
rightText.Document.Blocks.Clear();
leftLineNumber.Text = "";
rightLineNumber.Text = "";
Regex regex = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@", RegexOptions.None);
bool started = false;
List<Paragraph> leftData = new List<Paragraph>();
List<Paragraph> rightData = new List<Paragraph>();
List<string> leftNumbers = new List<string>();
List<string> rightNumbers = new List<string>();
int leftLine = 0;
int rightLine = 0;
bool bLastLeft = true;
foreach (var line in lines) {
if (!started) {
var match = regex.Match(line);
if (!match.Success) continue;
MakeParagraph(leftData, line, LineMode.Indicator);
MakeParagraph(rightData, line, LineMode.Indicator);
leftNumbers.Add("");
rightNumbers.Add("");
leftLine = int.Parse(match.Groups[1].Value);
rightLine = int.Parse(match.Groups[2].Value);
started = true;
continue;
}
if (line[0] == '-') {
MakeParagraph(leftData, line.Substring(1), LineMode.Deleted);
leftNumbers.Add(leftLine.ToString());
leftLine++;
bLastLeft = true;
} else if (line[0] == '+') {
MakeParagraph(rightData, line.Substring(1), LineMode.Added);
rightNumbers.Add(rightLine.ToString());
rightLine++;
bLastLeft = false;
} else if (line[0] == '\\') {
if (bLastLeft) {
MakeParagraph(leftData, line.Substring(1), LineMode.Indicator);
leftNumbers.Add("");
} else {
MakeParagraph(rightData, line.Substring(1), LineMode.Indicator);
rightNumbers.Add("");
}
} else {
FitBothSide(leftData, leftNumbers, rightData, rightNumbers);
bLastLeft = true;
var match = regex.Match(line);
if (match.Success) {
MakeParagraph(leftData, line, LineMode.Indicator);
MakeParagraph(rightData, line, LineMode.Indicator);
leftNumbers.Add("");
rightNumbers.Add("");
leftLine = int.Parse(match.Groups[1].Value);
rightLine = int.Parse(match.Groups[2].Value);
} else {
var data = line.Substring(1);
MakeParagraph(leftData, data, LineMode.Normal);
MakeParagraph(rightData, data, LineMode.Normal);
leftNumbers.Add(leftLine.ToString());
rightNumbers.Add(rightLine.ToString());
leftLine++;
rightLine++;
}
}
}
FitBothSide(leftData, leftNumbers, rightData, rightNumbers);
if (leftData.Count == 0) {
MakeParagraph(leftData, "NOT SUPPORTED OR NO DATA", LineMode.Indicator);
MakeParagraph(rightData, "NOT SUPPORTED OR NO DATA", LineMode.Indicator);
leftNumbers.Add("");
rightNumbers.Add("");
}
leftLineNumber.Text = string.Join("\n", leftNumbers);
rightLineNumber.Text = string.Join("\n", rightNumbers);
leftText.Document.PageWidth = minWidth + 16;
rightText.Document.PageWidth = minWidth + 16;
leftText.Document.Blocks.AddRange(leftData);
rightText.Document.Blocks.AddRange(rightData);
leftText.ScrollToHome();
mask.Visibility = Visibility.Collapsed;
}
/// <summary>
/// Reset data.
/// </summary>
public void Reset() {
mask.Visibility = Visibility.Visible;
}
/// <summary>
/// Make paragraph.
/// </summary>
/// <param name="collection"></param>
/// <param name="content"></param>
/// <param name="mode"></param>
private void MakeParagraph(List<Paragraph> collection, string content, LineMode mode) {
Paragraph p = new Paragraph(new Run(content));
p.Margin = new Thickness(0);
p.Padding = new Thickness();
p.LineHeight = 1;
p.Background = Brushes.Transparent;
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
p.FontStyle = FontStyles.Normal;
switch (mode) {
case LineMode.Normal:
break;
case LineMode.Indicator:
p.Foreground = Brushes.Gray;
p.FontStyle = FontStyles.Italic;
break;
case LineMode.Empty:
p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
break;
case LineMode.Added:
p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
break;
case LineMode.Deleted:
p.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
break;
}
var formatter = new FormattedText(
content,
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(leftText.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch),
leftText.FontSize,
Brushes.Black,
new NumberSubstitution(),
TextFormattingMode.Ideal);
if (minWidth < formatter.Width) minWidth = formatter.Width;
collection.Add(p);
}
/// <summary>
/// Fit both side with empty lines.
/// </summary>
/// <param name="left"></param>
/// <param name="leftNumbers"></param>
/// <param name="right"></param>
/// <param name="rightNumbers"></param>
private void FitBothSide(List<Paragraph> left, List<string> leftNumbers, List<Paragraph> right, List<string> rightNumbers) {
int leftCount = left.Count;
int rightCount = right.Count;
int diff = 0;
List<Paragraph> fitContent = null;
List<string> fitNumber = null;
if (leftCount > rightCount) {
diff = leftCount - rightCount;
fitContent = right;
fitNumber = rightNumbers;
} else if (rightCount > leftCount) {
diff = rightCount - leftCount;
fitContent = left;
fitNumber = leftNumbers;
}
for (int i = 0; i < diff; i++) {
MakeParagraph(fitContent, "", LineMode.Empty);
fitNumber.Add("");
}
}
/// <summary>
/// Sync scroll both sides.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerScroll(object sender, ScrollChangedEventArgs e) {
if (e.VerticalChange != 0) {
if (leftText.VerticalOffset != e.VerticalOffset) {
leftText.ScrollToVerticalOffset(e.VerticalOffset);
}
if (rightText.VerticalOffset != e.VerticalOffset) {
rightText.ScrollToVerticalOffset(e.VerticalOffset);
}
leftLineNumber.Margin = new Thickness(4, -e.VerticalOffset, 4, 0);
rightLineNumber.Margin = new Thickness(4, -e.VerticalOffset, 4, 0);
} else {
if (leftText.HorizontalOffset != e.HorizontalOffset) {
leftText.ScrollToHorizontalOffset(e.HorizontalOffset);
}
if (rightText.HorizontalOffset != e.HorizontalOffset) {
rightText.ScrollToHorizontalOffset(e.HorizontalOffset);
}
}
}
/// <summary>
/// Scroll using mouse wheel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) {
var text = sender as RichTextBox;
if (text == null) return;
if (e.Delta > 0) {
text.LineUp();
} else {
text.LineDown();
}
e.Handled = true;
}
private void LeftSizeChanged(object sender, SizeChangedEventArgs e) {
if (leftText.Document.PageWidth < leftText.ActualWidth) {
leftText.Document.PageWidth = leftText.ActualWidth;
}
}
private void RightSizeChanged(object sender, SizeChangedEventArgs e) {
if (rightText.Document.PageWidth < rightText.ActualWidth) {
rightText.Document.PageWidth = rightText.ActualWidth;
}
}
}
}

55
SourceGit/UI/Discard.xaml Normal file
View file

@ -0,0 +1,55 @@
<UserControl x:Class="SourceGit.UI.Discard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SourceGit.UI"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Discard Changes"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Changes :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path x:Name="icon" Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}" Fill="{StaticResource Brush.FG2}" Margin="4,0"/>
<Label x:Name="txtPath"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="1" Content="You can't undo this action!!!" Foreground="{StaticResource Brush.FG2}"/>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Confirm to discard changes dialog.
/// </summary>
public partial class Discard : UserControl {
private Git.Repository repo = null;
private List<Git.Change> changes = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened"></param>
/// <param name="targets"></param>
public Discard(Git.Repository opened, List<Git.Change> targets) {
repo = opened;
changes = targets;
InitializeComponent();
if (changes == null || changes.Count == 0) {
txtPath.Content = "All local changes in working copy.";
icon.Data = FindResource("Icon.Folder") as Geometry;
} else if (changes.Count == 1) {
txtPath.Content = changes[0].Path;
} else {
txtPath.Content = $"Total {changes.Count} changes ...";
}
}
/// <summary>
/// Show this dialog
/// </summary>
/// <param name="opened"></param>
/// <param name="targets"></param>
public static void Show(Git.Repository opened, List<Git.Change> targets) {
PopupManager.Show(new Discard(opened, targets));
}
private async void Sure(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => repo.Discard(changes));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

78
SourceGit/UI/Fetch.xaml Normal file
View file

@ -0,0 +1,78 @@
<UserControl x:Class="SourceGit.UI.Fetch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:git="clr-namespace:SourceGit.Git"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<converters:InverseBool x:Key="InverseBool"/>
</Grid.Resources>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Fetch Remote Changes"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
<ComboBox x:Name="combRemotes" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" IsEnabled="{Binding ElementName=chkFetchAll, Path=IsChecked, Converter={StaticResource InverseBool}}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type git:Remote}">
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
<Label Content="{Binding Name}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Row="3" Grid.Column="1"
x:Name="chkFetchAll"
IsChecked="True"
Content="Fetch all remotes"/>
<CheckBox Grid.Row="4" Grid.Column="1"
x:Name="chkPrune"
IsChecked="True"
Content="Prune remote dead branches"/>
<Grid Grid.Row="6" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,85 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Fetch dialog.
/// </summary>
public partial class Fetch : UserControl {
private Git.Repository repo = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="preferRemote">Prefer selected remote.</param>
public Fetch(Git.Repository opened, string preferRemote) {
repo = opened;
InitializeComponent();
Task.Run(() => {
var remotes = repo.Remotes();
Dispatcher.Invoke(() => {
combRemotes.ItemsSource = remotes;
if (preferRemote != null) {
combRemotes.SelectedIndex = remotes.FindIndex(r => r.Name == preferRemote);
chkFetchAll.IsChecked = false;
} else {
combRemotes.SelectedIndex = 0;
chkFetchAll.IsChecked = true;
}
});
});
}
/// <summary>
/// Show fetch dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="preferRemote"></param>
public static void Show(Git.Repository repo, string preferRemote = null) {
PopupManager.Show(new Fetch(repo, preferRemote));
}
/// <summary>
/// Start fetch
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
bool prune = chkPrune.IsChecked == true;
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
if (chkFetchAll.IsChecked == true) {
await Task.Run(() => repo.Fetch(null, prune, msg => Dispatcher.Invoke(() => statusMsg.Content = msg)));
} else {
var remote = combRemotes.SelectedItem as Git.Remote;
await Task.Run(() => repo.Fetch(remote, prune, msg => Dispatcher.Invoke(() => statusMsg.Content = msg)));
}
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,169 @@
<Window x:Class="SourceGit.UI.FileHistories"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:git="clr-namespace:SourceGit.Git"
mc:Ignorable="d"
Height="600" Width="800">
<!-- Enable WindowChrome -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
</WindowChrome.WindowChrome>
<!-- Layout Window -->
<Border Background="{StaticResource Brush.BG1}">
<!-- Fix Maximize BUG -->
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Margin" Value="6"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Margin" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Titlebar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- LOGO & TITLE -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Path
Width="20" Height="20" Margin="6,-1,2,0"
Style="{StaticResource Style.Icon}"
Data="{StaticResource Icon.Git}"
Fill="#FFF05133"
WindowChrome.IsHitTestVisibleInChrome="True"
MouseLeftButtonDown="LogoMouseButtonDown"/>
<Label Content="SOURCE GIT - FILE HISTORIES" FontWeight="Light"/>
</StackPanel>
<!-- Options -->
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
</Button>
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path>
<Path.Style>
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button Click="Quit" Width="32">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</StackPanel>
</Grid>
<!-- Body -->
<Border Grid.Row="1" ClipToBounds="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<DataGrid
x:Name="commitList"
Margin="2,0,0,0"
Grid.Column="0"
Background="{StaticResource Brush.BG2}"
BorderThickness="0"
SelectionMode="Single"
SelectionChanged="CommitSelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="{StaticResource Brush.BG4}" BorderThickness="0,0,0,1" Padding="4">
<StackPanel Orientation="Vertical" Margin="2" MaxWidth="290">
<Grid TextBlock.FontSize="11" TextBlock.Foreground="{StaticResource Brush.FG2}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="72"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Padding="0">
<Hyperlink RequestNavigate="NavigateToCommit" NavigateUri="{Binding SHA}" Foreground="DarkOrange" ToolTip="GOTO COMMIT">
<Run Text="{Binding ShortSHA, Mode=OneWay}"/>
</Hyperlink>
</Label>
<TextBlock Grid.Column="1" Text="{Binding Author.Name}"/>
<TextBlock Grid.Column="2" Text="{Binding Author.Time}"/>
</Grid>
<TextBlock MaxWidth="280" Foreground="{StaticResource Brush.FG}" Text="{Binding Subject}" TextAlignment="Left" Padding="0" Margin="0,8,2,0"/>
</StackPanel>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- Loading tip -->
<Path x:Name="loading" Grid.Column="0" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
<Path.Style>
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
</Style>
</Path.Style>
</Path>
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.BG4}"/>
<local:DiffViewer x:Name="diff" Grid.Column="2" Margin="2,0"/>
</Grid>
</Border>
</Grid>
</Border>
</Window>

View file

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
namespace SourceGit.UI {
/// <summary>
/// File histories panel.
/// </summary>
public partial class FileHistories : Window {
private Git.Repository repo = null;
private string file = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo"></param>
/// <param name="file"></param>
public FileHistories(Git.Repository repo, string file) {
this.repo = repo;
this.file = file;
InitializeComponent();
// Move to center
var parent = App.Current.MainWindow;
Left = parent.Left + (parent.Width - Width) * 0.5;
Top = parent.Top + (parent.Height - Height) * 0.5;
// Show loading
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
loading.Visibility = Visibility.Visible;
// Load commits
Task.Run(() => {
var commits = repo.Commits($"-n 10000 -- \"{file}\"");
Dispatcher.Invoke(() => {
commitList.ItemsSource = commits;
commitList.SelectedIndex = 0;
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
loading.Visibility = Visibility.Collapsed;
});
});
}
/// <summary>
/// Logo click
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) {
var element = e.OriginalSource as FrameworkElement;
if (element == null) return;
var pos = PointToScreen(new Point(0, 33));
SystemCommands.ShowSystemMenu(this, pos);
}
/// <summary>
/// Minimize
/// </summary>
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
/// <summary>
/// Maximize/Restore
/// </summary>
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
/// <summary>
/// Quit
/// </summary>
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
/// <summary>
/// Commit selection change event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void CommitSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var commit = e.AddedItems[0] as Git.Commit;
var start = $"{commit.SHA}^";
if (commit.Parents.Count == 0) start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
List<string> data = new List<string>();
await Task.Run(() => {
data = repo.Diff(start, commit.SHA, file);
});
diff.SetData(data, $"{file} @ {commit.ShortSHA}");
}
/// <summary>
/// Navigate to given string
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void NavigateToCommit(object sender, RequestNavigateEventArgs e) {
repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString);
e.Handled = true;
}
}
}

View file

@ -0,0 +1,50 @@
<UserControl x:Class="SourceGit.UI.GitFlowFinishBranch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" x:Name="txtTitle" FontWeight="DemiBold" FontSize="18"/>
<Label Grid.Row="2" Grid.Column="0" x:Name="txtBranchType" HorizontalAlignment="Right"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}" Margin="4,0"/>
<Label x:Name="txtBranchName"/>
</StackPanel>
<Grid Grid.Row="4" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,87 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Confirm finish git-flow branch dialog
/// </summary>
public partial class GitFlowFinishBranch : UserControl {
private Git.Repository repo = null;
private Git.Branch branch = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo"></param>
/// <param name="branch"></param>
public GitFlowFinishBranch(Git.Repository repo, Git.Branch branch) {
this.repo = repo;
this.branch = branch;
InitializeComponent();
switch (branch.Kind) {
case Git.Branch.Type.Feature:
txtTitle.Content = "Git Flow - Finish Feature";
txtBranchType.Content = "Feature :";
break;
case Git.Branch.Type.Release:
txtTitle.Content = "Git Flow - Finish Release";
txtBranchType.Content = "Release :";
break;
case Git.Branch.Type.Hotfix:
txtTitle.Content = "Git Flow - Finish Hotfix";
txtBranchType.Content = "Hotfix :";
break;
default:
PopupManager.Close();
return;
}
txtBranchName.Content = branch.Name;
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="branch"></param>
public static void Show(Git.Repository repo, Git.Branch branch) {
PopupManager.Show(new GitFlowFinishBranch(repo, branch));
}
/// <summary>
/// Do finish
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => repo.FinishGitFlowBranch(branch));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel finish
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,68 @@
<UserControl x:Class="SourceGit.UI.GitFlowSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="304" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Git Flow - Initialize"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Production Branch :"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="txtMaster" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="master" Height="24" Text="master"/>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Development Branch :"/>
<TextBox Grid.Row="3" Grid.Column="1" x:Name="txtDevelop" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="develop" Height="24" Text="develop"/>
<Label Grid.Row="5" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Feature Prefix :"/>
<TextBox Grid.Row="5" Grid.Column="1" x:Name="txtFeature" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="feature/" Height="24" Text="feature/"/>
<Label Grid.Row="6" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Release Prefix :"/>
<TextBox Grid.Row="6" Grid.Column="1" x:Name="txtRelease" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="release/" Height="24" Text="release/"/>
<Label Grid.Row="7" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Hotfix Prefix :"/>
<TextBox Grid.Row="7" Grid.Column="1" x:Name="txtHotfix" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="hotfix/" Height="24" Text="hotfix/"/>
<Label Grid.Row="8" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Version Tag Prefix :"/>
<TextBox Grid.Row="8" Grid.Column="1" x:Name="txtVersion" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="Optional." Height="24" Text=""/>
<Grid Grid.Row="10" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="txtValidation" Foreground="Red"/>
<Button Grid.Column="1" x:Name="btnSure" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="11" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,134 @@
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Dialog to initialize git flow.
/// </summary>
public partial class GitFlowSetup : UserControl {
private Git.Repository repo = null;
private Regex regex = new Regex(@"^[\w\-/\.]+$");
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened"></param>
public GitFlowSetup(Git.Repository opened) {
repo = opened;
InitializeComponent();
}
/// <summary>
/// Open this dialog.
/// </summary>
/// <param name="repo"></param>
public static void Show(Git.Repository repo) {
PopupManager.Show(new GitFlowSetup(repo));
}
/// <summary>
/// Start to initialize git-flow.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
var master = txtMaster.Text;
var dev = txtDevelop.Text;
var feature = txtFeature.Text;
var release = txtRelease.Text;
var hotfix = txtHotfix.Text;
var version = txtVersion.Text;
await Task.Run(() => repo.EnableGitFlow(master, dev, feature, release, hotfix, version));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
/// <summary>
/// Validate input names.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ValidateNames(object sender, TextChangedEventArgs e) {
if (!IsLoaded) return;
var master = txtMaster.Text;
var dev = txtDevelop.Text;
var feature = txtFeature.Text;
var release = txtRelease.Text;
var hotfix = txtHotfix.Text;
if (!ValidateBranch("Production", master)) return;
if (!ValidateBranch("Development", dev)) return;
if (dev == master) {
txtValidation.Content = "Development branch is same with production!";
btnSure.IsEnabled = false;
return;
}
if (!ValidatePrefix("Feature", feature)) return;
if (!ValidatePrefix("Release", release)) return;
if (!ValidatePrefix("Hotfix", hotfix)) return;
txtValidation.Content = "";
btnSure.IsEnabled = true;
}
private bool ValidateBranch(string type, string name) {
if (string.IsNullOrEmpty(name)) {
txtValidation.Content = $"{type} branch name can't be empty";
btnSure.IsEnabled = false;
return false;
}
if (!regex.IsMatch(name)) {
txtValidation.Content = $"{type} branch name contains invalid characters.";
btnSure.IsEnabled = false;
return false;
}
return true;
}
private bool ValidatePrefix(string type, string prefix) {
if (string.IsNullOrEmpty(prefix)) {
txtValidation.Content = $"{type} prefix is required!";
btnSure.IsEnabled = false;
return false;
}
if (!regex.IsMatch(prefix)) {
txtValidation.Content = $"{type} prefix contains invalid characters.";
btnSure.IsEnabled = false;
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,57 @@
<UserControl x:Class="SourceGit.UI.GitFlowStartBranch"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" x:Name="txtTitle" FontWeight="DemiBold" FontSize="18"/>
<Label Grid.Row="2" Grid.Column="0" x:Name="txtPrefix" HorizontalAlignment="Right" FontSize="16" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="txtName" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="Enter name" Height="24">
<TextBox.Text>
<Binding ElementName="me" Path="SubName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<helpers:BranchNameRule x:Name="nameValidator"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Grid Grid.Row="4" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,103 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Start git-flow branch dialog.
/// </summary>
public partial class GitFlowStartBranch : UserControl {
private Git.Repository repo = null;
private Git.Branch.Type type = Git.Branch.Type.Feature;
/// <summary>
/// Sub-name for this git-flow branch.
/// </summary>
public string SubName {
get;
set;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo"></param>
/// <param name="type"></param>
public GitFlowStartBranch(Git.Repository repo, Git.Branch.Type type) {
this.repo = repo;
this.type = type;
InitializeComponent();
nameValidator.Repo = repo;
switch (type) {
case Git.Branch.Type.Feature:
var featurePrefix = repo.GetFeaturePrefix();
txtTitle.Content = "Git Flow - Start Feature";
txtPrefix.Content = featurePrefix;
nameValidator.Prefix = featurePrefix;
break;
case Git.Branch.Type.Release:
var releasePrefix = repo.GetReleasePrefix();
txtTitle.Content = "Git Flow - Start Release";
txtPrefix.Content = releasePrefix;
nameValidator.Prefix = releasePrefix;
break;
case Git.Branch.Type.Hotfix:
var hotfixPrefix = repo.GetHotfixPrefix();
txtTitle.Content = "Git Flow - Start Hotfix";
txtPrefix.Content = hotfixPrefix;
nameValidator.Prefix = hotfixPrefix;
break;
default:
PopupManager.Close();
return;
}
}
/// <summary>
/// Display this dialog
/// </summary>
/// <param name="repo"></param>
/// <param name="type"></param>
public static void Show(Git.Repository repo, Git.Branch.Type type) {
PopupManager.Show(new GitFlowStartBranch(repo, type));
}
/// <summary>
/// Start git-flow branch
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtName)) return;
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => repo.StartGitFlowBranch(type, SubName));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

187
SourceGit/UI/Histories.xaml Normal file
View file

@ -0,0 +1,187 @@
<UserControl x:Class="SourceGit.UI.Histories"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:git="clr-namespace:SourceGit.Git"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
xmlns:sourcegit="clr-namespace:SourceGit"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Unloaded="Cleanup">
<Grid x:Name="layout">
<!-- List Panel (SearchBar + DataGrid) -->
<Grid x:Name="commitListPanel" Background="{StaticResource Brush.BG2}" ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Loading Tip -->
<Path x:Name="loading" Grid.RowSpan="2" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
<Path.Style>
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
</Style>
</Path.Style>
</Path>
<!-- SearchBar -->
<Grid x:Name="searchBar" Margin="0,-32,0,0" Grid.Row="0">
<TextBox x:Name="txtSearch" Margin="4" Height="24" Padding="0,0,16,0" helpers:TextBoxHelper.Placeholder="SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT" PreviewKeyDown="PreviewSearchKeyDown"/>
<Button HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,8,0" Style="{StaticResource Style.Button.HighlightHover}" ToolTip="CLEAR" Click="ClearSearch">
<Path Width="10" Height="10" Fill="{StaticResource Brush.Border1}" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</Grid>
<!-- Commit DataGrid -->
<DataGrid
Grid.Row="1"
x:Name="commitList"
RowHeight="{x:Static helpers:CommitGraphMaker.UNIT_HEIGHT}"
ScrollViewer.ScrollChanged="CommitListScrolled"
SelectionChanged="CommitSelectChanged"
SelectionUnit="FullRow">
<DataGrid.Resources>
<converters:IndentToMargin x:Key="CommitTitleMargin"/>
<Style x:Key="Style.DataGridText" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Padding" Value="16,0"/>
</Style>
<Style x:Key="Style.DataGridText.NoPadding" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="*" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="{Binding GraphOffset, Converter={StaticResource CommitTitleMargin}}">
<ItemsControl x:Name="Decorator" ItemsSource="{Binding Decorators}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type git:Decorator}">
<Border x:Name="BG" Height="16" Margin="2,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{StaticResource Brush.BG5}">
<Path x:Name="Icon" Width="8" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
</Border>
<TextBlock x:Name="Name" Grid.Column="1" Text="{Binding Name}" FontSize="11" Padding="4,0" Foreground="Black" VerticalAlignment="Center" TextWrapping="NoWrap"/>
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.Tag}">
<Setter TargetName="BG" Property="Background" Value="#FF02C302"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Tag}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.LocalBranchHead}">
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Branch}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.RemoteBranchHead}">
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Remote}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.CurrentBranchHead}">
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Check}"/>
<Setter TargetName="Icon" Property="Fill" Value="Orange"/>
<Setter TargetName="Name" Property="FontWeight" Value="Bold"/>
<Setter TargetName="Name" Property="Foreground" Value="{StaticResource Brush.FG}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding Subject}" VerticalAlignment="Center" Margin="2,0,0,0"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding HasDecorators}" Value="False">
<Setter TargetName="Decorator" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="100" IsReadOnly="True" Binding="{Binding Committer.Name}" ElementStyle="{StaticResource Style.DataGridText}"/>
<DataGridTextColumn Width="84" IsReadOnly="True" Binding="{Binding ShortSHA}" ElementStyle="{StaticResource Style.DataGridText}"/>
<DataGridTextColumn Width="128" IsReadOnly="True" Binding="{Binding Committer.Time}" ElementStyle="{StaticResource Style.DataGridText.NoPadding}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="ContextMenuOpening" Handler="CommitContextMenuOpening"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMerged}" Value="False">
<Setter Property="Opacity" Value=".4"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
<!-- Commit Graph -->
<Border Grid.Row="1" Margin="0,0,310,0" ClipToBounds="True" IsHitTestVisible="False">
<Canvas x:Name="commitGraph"/>
</Border>
</Grid>
<!-- Split -->
<GridSplitter x:Name="splitter" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG1}"/>
<!-- Detail for selected commit -->
<Grid x:Name="commitDetailPanel" Background="{StaticResource Brush.BG1}">
<!-- Selected commit detail -->
<local:CommitViewer x:Name="commitViewer"/>
<!-- Mask for select multi rows in commit list -->
<Border x:Name="mask4MultiSelection" Background="{StaticResource Brush.BG1}" Visibility="Collapsed">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
<Path Width="160" Height="160" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}"/>
<Label x:Name="txtTotalSelected" Margin="0,16,0,0" FontSize="24" FontWeight="UltraBold" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<!-- SWITCH LAYOUT -->
<ToggleButton
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,4,8,0"
Style="{StaticResource Style.ToggleButton.Orientation}"
ToolTip="Toggle Horizontal/Vertical Layout"
IsChecked="{Binding Source={x:Static sourcegit:App.Preference}, Path=UIUseHorizontalLayout, Mode=TwoWay}"
Checked="ChangeOrientation" Unchecked="ChangeOrientation"/>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,649 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SourceGit.UI {
/// <summary>
/// Commit histories viewer
/// </summary>
public partial class Histories : UserControl {
/// <summary>
/// Current opened repository.
/// </summary>
public Git.Repository Repo { get; set; }
/// <summary>
/// Cached commits.
/// </summary>
private List<Git.Commit> cachedCommits = new List<Git.Commit>();
/// <summary>
/// Is in search mode?
/// </summary>
private bool isSearchMode = false;
/// <summary>
/// Regex to test search input.
/// </summary>
private Regex commitRegex = new Regex(@"^[0-9a-f]{6,40}$", RegexOptions.None);
/// <summary>
/// Constructor
/// </summary>
public Histories() {
InitializeComponent();
ChangeOrientation(null, null);
}
/// <summary>
/// Navigate to given commit.
/// </summary>
/// <param name="commit"></param>
public void Navigate(string commit) {
if (string.IsNullOrEmpty(commit)) return;
foreach (var item in commitList.ItemsSource) {
var c = item as Git.Commit;
if (c.SHA.StartsWith(commit)) {
commitList.SelectedItem = c;
commitList.ScrollIntoView(c);
return;
}
}
}
/// <summary>
/// Loading tips.
/// </summary>
/// <param name="enabled"></param>
public void SetLoadingEnabled(bool enabled) {
if (enabled) {
loading.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
} else {
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
loading.Visibility = Visibility.Collapsed;
}
}
#region DATA
public void SetCommits(List<Git.Commit> commits) {
cachedCommits = commits;
if (isSearchMode) return;
var maker = Helpers.CommitGraphMaker.Parse(commits);
Dispatcher.Invoke(() => {
commitGraph.Children.Clear();
isSearchMode = false;
txtSearch.Text = "";
// Draw all lines.
foreach (var path in maker.Lines) {
var size = path.Points.Count;
var geo = new StreamGeometry();
var last = path.Points[0];
using (var ctx = geo.Open()) {
ctx.BeginFigure(last, false, false);
for (int i = 1; i < size; i++) {
var cur = path.Points[i];
if (cur.X > last.X) {
ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur, true, false);
} else if (cur.X < last.X) {
if (i < size - 1) {
cur.Y += Helpers.CommitGraphMaker.HALF_HEIGHT;
var midY = (last.Y + cur.Y) / 2;
var midX = (last.X + cur.X) / 2;
ctx.PolyQuadraticBezierTo(new Point[] {
new Point(last.X, midY),
new Point(midX, midY),
new Point(cur.X, midY),
cur
}, true, false);
} else {
ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur, true, false);
}
} else {
ctx.LineTo(cur, true, false);
}
last = cur;
}
}
geo.Freeze();
var p = new Path();
p.Data = geo;
p.Stroke = path.Brush;
p.StrokeThickness = 2;
commitGraph.Children.Add(p);
}
maker.Lines.Clear();
// Draw short links
foreach (var link in maker.Links) {
var geo = new StreamGeometry();
using (var ctx = geo.Open()) {
ctx.BeginFigure(link.Start, false, false);
ctx.QuadraticBezierTo(link.Control, link.End, true, false);
}
geo.Freeze();
var p = new Path();
p.Data = geo;
p.Stroke = link.Brush;
p.StrokeThickness = 2;
commitGraph.Children.Add(p);
}
maker.Links.Clear();
// Draw points.
foreach (var dot in maker.Dots) {
var ellipse = new Ellipse();
ellipse.Height = 6;
ellipse.Width = 6;
ellipse.Fill = dot.Color;
ellipse.SetValue(Canvas.LeftProperty, dot.X);
ellipse.SetValue(Canvas.TopProperty, dot.Y);
commitGraph.Children.Add(ellipse);
}
maker.Dots.Clear();
commitList.ItemsSource = new List<Git.Commit>(cachedCommits);
Navigate(maker.Highlight);
SetLoadingEnabled(false);
});
}
public void SetSearchResult(List<Git.Commit> commits) {
isSearchMode = true;
foreach (var c in commits) c.GraphOffset = 0;
Dispatcher.Invoke(() => {
commitGraph.Children.Clear();
commitList.ItemsSource = new List<Git.Commit>(commits);
SetLoadingEnabled(false);
});
}
private void Cleanup(object sender, RoutedEventArgs e) {
commitGraph.Children.Clear();
commitList.ItemsSource = null;
cachedCommits.Clear();
}
#endregion
#region SEARCH_BAR
public void OpenSearchBar() {
if (searchBar.Margin.Top == 0) return;
ThicknessAnimation anim = new ThicknessAnimation();
anim.From = new Thickness(0, -32, 0, 0);
anim.To = new Thickness(0);
anim.Duration = TimeSpan.FromSeconds(.3);
searchBar.BeginAnimation(Grid.MarginProperty, anim);
txtSearch.Focus();
}
public void HideSearchBar() {
if (searchBar.Margin.Top != 0) return;
ClearSearch(null, null);
ThicknessAnimation anim = new ThicknessAnimation();
anim.From = new Thickness(0);
anim.To = new Thickness(0, -32, 0, 0);
anim.Duration = TimeSpan.FromSeconds(.3);
searchBar.BeginAnimation(Grid.MarginProperty, anim);
}
private void ClearSearch(object sender, RoutedEventArgs e) {
txtSearch.Text = "";
if (isSearchMode) {
isSearchMode = false;
SetLoadingEnabled(true);
Task.Run(() => SetCommits(cachedCommits));
}
}
private void PreviewSearchKeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Enter) {
string search = txtSearch.Text;
if (string.IsNullOrEmpty(search)) {
ClearSearch(sender, e);
} else if (commitRegex.IsMatch(search)) {
SetLoadingEnabled(true);
Task.Run(() => {
var commits = Repo.Commits($"search -n 1");
SetSearchResult(commits);
});
} else {
SetLoadingEnabled(true);
Task.Run(() => {
List<Git.Commit> found = new List<Git.Commit>();
foreach (var commit in cachedCommits) {
if (commit.Subject.IndexOf(search) >= 0 ||
(commit.Author != null && commit.Author.Name == search) ||
(commit.Committer != null && commit.Committer.Name == search) ||
commit.Message.IndexOf(search) >= 0) {
found.Add(commit);
}
}
SetSearchResult(found);
});
}
}
}
#endregion
#region COMMIT_DATAGRID_AND_GRAPH
private void CommitListScrolled(object sender, ScrollChangedEventArgs e) {
commitGraph.Margin = new Thickness(0, -e.VerticalOffset * Helpers.CommitGraphMaker.UNIT_HEIGHT, 0, 0);
}
private void CommitSelectChanged(object sender, SelectionChangedEventArgs e) {
mask4MultiSelection.Visibility = Visibility.Collapsed;
var selected = commitList.SelectedItems;
if (selected.Count == 1) {
var commit = selected[0] as Git.Commit;
if (commit != null) commitViewer.SetData(Repo, commit);
} else if (selected.Count > 1) {
mask4MultiSelection.Visibility = Visibility.Visible;
txtTotalSelected.Content = $"SELECTED {selected.Count} COMMITS";
}
}
private MenuItem GetCurrentBranchContextMenu(Git.Branch branch) {
var icon = new Path();
icon.Style = FindResource("Style.Icon") as Style;
icon.Data = FindResource("Icon.Branch") as Geometry;
icon.VerticalAlignment = VerticalAlignment.Bottom;
icon.Width = 10;
var submenu = new MenuItem();
submenu.Header = branch.Name;
submenu.Icon = icon;
if (!string.IsNullOrEmpty(branch.Upstream)) {
var upstream = branch.Upstream.Substring(13);
var fastForward = new MenuItem();
fastForward.Header = $"Fast-Forward to '{upstream}'";
fastForward.Click += (o, e) => {
Merge.StartDirectly(Repo, upstream, branch.Name);
e.Handled = true;
};
submenu.Items.Add(fastForward);
var pull = new MenuItem();
pull.Header = $"Pull '{upstream}' ...";
pull.Click += (o, e) => {
Pull.Show(Repo);
e.Handled = true;
};
submenu.Items.Add(pull);
}
var push = new MenuItem();
push.Header = $"Push '{branch.Name}' ...";
push.Click += (o, e) => {
Push.Show(Repo, branch);
e.Handled = true;
};
submenu.Items.Add(push);
submenu.Items.Add(new Separator());
if (branch.Kind != Git.Branch.Type.Normal) {
var flowIcon = new Path();
flowIcon.Style = FindResource("Style.Icon") as Style;
flowIcon.Data = FindResource("Icon.Flow") as Geometry;
flowIcon.Width = 10;
var finish = new MenuItem();
finish.Header = $"Git Flow - Finish '{branch.Name}'";
finish.Icon = flowIcon;
finish.Click += (o, e) => {
GitFlowFinishBranch.Show(Repo, branch);
e.Handled = true;
};
submenu.Items.Add(finish);
submenu.Items.Add(new Separator());
}
var rename = new MenuItem();
rename.Header = "Rename ...";
rename.Click += (o, e) => {
RenameBranch.Show(Repo, branch);
e.Handled = true;
};
submenu.Items.Add(rename);
return submenu;
}
private MenuItem GetOtherBranchContextMenu(Git.Branch current, Git.Branch branch, bool merged) {
var icon = new Path();
icon.Style = FindResource("Style.Icon") as Style;
icon.Data = FindResource("Icon.Branch") as Geometry;
icon.VerticalAlignment = VerticalAlignment.Bottom;
icon.Width = 10;
var submenu = new MenuItem();
submenu.Header = branch.Name;
submenu.Icon = icon;
var checkout = new MenuItem();
checkout.Header = $"Checkout '{branch.Name}'";
checkout.Click += (o, e) => {
if (branch.IsLocal) {
Task.Run(() => Repo.Checkout(branch.Name));
} else {
var upstream = $"refs/remotes/{branch.Name}";
var tracked = Repo.Branches().Find(b => b.IsLocal && b.Upstream == upstream);
if (tracked == null) {
CreateBranch.Show(Repo, branch);
} else if (!tracked.IsCurrent) {
Task.Run(() => Repo.Checkout(tracked.Name));
}
}
e.Handled = true;
};
submenu.Items.Add(checkout);
var merge = new MenuItem();
merge.Header = $"Merge into '{current.Name}' ...";
merge.IsEnabled = !merged;
merge.Click += (o, e) => {
Merge.Show(Repo, branch.Name, current.Name);
e.Handled = true;
};
submenu.Items.Add(merge);
submenu.Items.Add(new Separator());
if (branch.Kind != Git.Branch.Type.Normal) {
var flowIcon = new Path();
flowIcon.Style = FindResource("Style.Icon") as Style;
flowIcon.Data = FindResource("Icon.Flow") as Geometry;
flowIcon.Width = 10;
var finish = new MenuItem();
finish.Header = $"Git Flow - Finish '{branch.Name}'";
finish.Icon = flowIcon;
finish.Click += (o, e) => {
GitFlowFinishBranch.Show(Repo, branch);
e.Handled = true;
};
submenu.Items.Add(finish);
submenu.Items.Add(new Separator());
}
var rename = new MenuItem();
rename.Header = "Rename ...";
rename.Visibility = branch.IsLocal ? Visibility.Visible : Visibility.Collapsed;
rename.Click += (o, e) => {
RenameBranch.Show(Repo, current);
e.Handled = true;
};
submenu.Items.Add(rename);
var delete = new MenuItem();
delete.Header = "Delete ...";
delete.Click += (o, e) => {
DeleteBranch.Show(Repo, branch);
};
submenu.Items.Add(delete);
return submenu;
}
private MenuItem GetTagContextMenu(Git.Tag tag) {
var icon = new Path();
icon.Style = FindResource("Style.Icon") as Style;
icon.Data = FindResource("Icon.Tag") as Geometry;
icon.Width = 10;
var submenu = new MenuItem();
submenu.Header = tag.Name;
submenu.Icon = icon;
submenu.MinWidth = 200;
var push = new MenuItem();
push.Header = "Push ...";
push.Click += (o, e) => {
PushTag.Show(Repo, tag);
e.Handled = true;
};
submenu.Items.Add(push);
var delete = new MenuItem();
delete.Header = "Delete ...";
delete.Click += (o, e) => {
DeleteTag.Show(Repo, tag);
e.Handled = true;
};
submenu.Items.Add(delete);
return submenu;
}
private void CommitContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var row = sender as DataGridRow;
if (row == null) return;
var commit = row.DataContext as Git.Commit;
if (commit == null) return;
commitList.SelectedItem = commit;
var current = Repo.CurrentBranch();
if (current == null) return;
var menu = new ContextMenu();
menu.MinWidth = 200;
// Decorators.
{
var localBranchContextMenus = new List<MenuItem>();
var remoteBranchContextMenus = new List<MenuItem>();
var tagContextMenus = new List<MenuItem>();
foreach (var d in commit.Decorators) {
if (d.Type == Git.DecoratorType.CurrentBranchHead) {
menu.Items.Add(GetCurrentBranchContextMenu(current));
} else if (d.Type == Git.DecoratorType.LocalBranchHead) {
var branch = Repo.Branches().Find(b => b.Name == d.Name);
if (branch != null) {
localBranchContextMenus.Add(GetOtherBranchContextMenu(current, branch, commit.IsMerged));
}
} else if (d.Type == Git.DecoratorType.RemoteBranchHead) {
var branch = Repo.Branches().Find(b => b.Name == d.Name);
if (branch != null) {
remoteBranchContextMenus.Add(GetOtherBranchContextMenu(current, branch, commit.IsMerged));
}
} else if (d.Type == Git.DecoratorType.Tag) {
var tag = Repo.Tags().Find(t => t.Name == d.Name);
if (tag != null) tagContextMenus.Add(GetTagContextMenu(tag));
}
}
foreach (var m in localBranchContextMenus) menu.Items.Add(m);
foreach (var m in remoteBranchContextMenus) menu.Items.Add(m);
if (menu.Items.Count > 0) menu.Items.Add(new Separator());
if (tagContextMenus.Count > 0) {
foreach (var m in tagContextMenus) menu.Items.Add(m);
menu.Items.Add(new Separator());
}
}
// Reset
var reset = new MenuItem();
reset.Header = $"Reset '{current.Name}' To Here";
reset.Visibility = commit.IsHEAD ? Visibility.Collapsed : Visibility.Visible;
reset.Click += (o, e) => {
Reset.Show(Repo, commit);
e.Handled = true;
};
menu.Items.Add(reset);
// Rebase or interactive rebase
var rebase = new MenuItem();
rebase.Header = commit.IsMerged ? $"Interactive Rebase '{current.Name}' From Here" : $"Rebase '{current.Name}' To Here";
rebase.Visibility = commit.IsHEAD ? Visibility.Collapsed : Visibility.Visible;
rebase.Click += (o, e) => {
if (commit.IsMerged) {
if (Repo.LocalChanges().Count > 0) {
App.RaiseError("You have local changes!!!");
e.Handled = true;
return;
}
var dialog = new InteractiveRebase(Repo, commit);
dialog.Owner = App.Current.MainWindow;
dialog.ShowDialog();
} else {
Rebase.Show(Repo, commit);
}
e.Handled = true;
};
menu.Items.Add(rebase);
// Cherry-Pick
var cherryPick = new MenuItem();
cherryPick.Header = "Cherry-Pick This Commit";
cherryPick.Visibility = commit.IsMerged ? Visibility.Collapsed : Visibility.Visible;
cherryPick.Click += (o, e) => {
CherryPick.Show(Repo, commit);
e.Handled = true;
};
menu.Items.Add(cherryPick);
// Revert commit
var revert = new MenuItem();
revert.Header = "Revert commit";
revert.Visibility = !commit.IsMerged ? Visibility.Collapsed : Visibility.Visible;
revert.Click += (o, e) => {
Revert.Show(Repo, commit);
e.Handled = true;
};
menu.Items.Add(revert);
menu.Items.Add(new Separator());
// Common
var createBranch = new MenuItem();
createBranch.Header = "Create Branch";
createBranch.Click += (o, e) => {
CreateBranch.Show(Repo, commit);
e.Handled = true;
};
menu.Items.Add(createBranch);
var createTag = new MenuItem();
createTag.Header = "Create Tag";
createTag.Click += (o, e) => {
CreateTag.Show(Repo, commit);
e.Handled = true;
};
menu.Items.Add(createTag);
menu.Items.Add(new Separator());
// Save as patch
var patch = new MenuItem();
patch.Header = "Save As Patch";
patch.Click += (o, e) => {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
Repo.RunCommand($"format-patch {commit.SHA} -1 -o \"{dialog.SelectedPath}\"", null);
}
};
menu.Items.Add(patch);
menu.Items.Add(new Separator());
// Copy SHA
var copySHA = new MenuItem();
copySHA.Header = "Copy Commit SHA";
copySHA.Click += (o, e) => {
Clipboard.SetText(commit.SHA);
};
menu.Items.Add(copySHA);
// Copy info
var copyInfo = new MenuItem();
copyInfo.Header = "Copy Commit Info";
copyInfo.Click += (o, e) => {
Clipboard.SetText(string.Format(
"SHA: {0}\nTITLE: {1}\nAUTHOR: {2} <{3}>\nTIME: {4}",
commit.SHA, commit.Subject, commit.Committer.Name, commit.Committer.Email, commit.Committer.Time));
};
menu.Items.Add(copyInfo);
menu.IsOpen = true;
ev.Handled = true;
}
#endregion
#region LAYOUT
private void ChangeOrientation(object sender, RoutedEventArgs e) {
if (commitDetailPanel == null || splitter == null || commitListPanel == null) return;
layout.RowDefinitions.Clear();
layout.ColumnDefinitions.Clear();
if (App.Preference.UIUseHorizontalLayout) {
layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 });
layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(2) });
layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 });
Grid.SetRow(commitListPanel, 0);
Grid.SetRow(splitter, 0);
Grid.SetRow(commitDetailPanel, 0);
Grid.SetColumn(commitListPanel, 0);
Grid.SetColumn(splitter, 1);
Grid.SetColumn(commitDetailPanel, 2);
} else {
layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), MinHeight = 100 });
layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(2) });
layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), MinHeight = 100 });
Grid.SetRow(commitListPanel, 0);
Grid.SetRow(splitter, 1);
Grid.SetRow(commitDetailPanel, 2);
Grid.SetColumn(commitListPanel, 0);
Grid.SetColumn(splitter, 0);
Grid.SetColumn(commitDetailPanel, 0);
}
layout.InvalidateVisual();
}
#endregion
}
}

53
SourceGit/UI/Init.xaml Normal file
View file

@ -0,0 +1,53 @@
<UserControl x:Class="SourceGit.UI.Init"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Initialize Repository"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Path :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}" Margin="4,0"/>
<Label x:Name="txtPath"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="1" Content="Invalid repository detected. Run `git init` under this path?" Foreground="{StaticResource Brush.FG2}"/>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

73
SourceGit/UI/Init.xaml.cs Normal file
View file

@ -0,0 +1,73 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// `git init` confirm panel.
/// </summary>
public partial class Init : UserControl {
private string workingDir = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="path"></param>
public Init(string path) {
workingDir = path;
InitializeComponent();
txtPath.Content = path;
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="path"></param>
public static void Show(string path) {
PopupManager.Show(new Init(path));
}
/// <summary>
/// Do `git init`
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => {
var errs = Git.Repository.RunCommand(workingDir, "init -q", null);
if (errs != null) {
App.RaiseError(errs);
} else {
App.Preference.AddRepository(workingDir, "");
}
});
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
var repo = App.Preference.FindRepository(workingDir);
if (repo != null) repo.Open();
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,256 @@
<Window x:Class="SourceGit.UI.InteractiveRebase"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
Height="600" Width="800">
<!-- Enable WindowChrome -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
</WindowChrome.WindowChrome>
<!-- Window Content -->
<Border Background="{StaticResource Brush.BG1}">
<!-- Fix Maximize BUG -->
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Margin" Value="6"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Margin" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<!-- Title bar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Logo & TITLE -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Image
Source="pack://application:,,,/Resources/App.png"
Width="20" Height="20" Margin="6,-1,2,0"
VerticalAlignment="Center"
RenderOptions.BitmapScalingMode="HighQuality"
WindowChrome.IsHitTestVisibleInChrome="True"
MouseLeftButtonDown="LogoMouseButtonDown"/>
<Label Content="SOURCE GIT - INTERACTIVE REBASE" FontWeight="Light"/>
</StackPanel>
<!-- Options -->
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
</Button>
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path>
<Path.Style>
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button Click="Quit" Width="32">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</StackPanel>
</Grid>
<!-- Commit List -->
<ListView Grid.Row="1"
x:Name="commitList"
Background="Transparent"
Style="{StaticResource Style.ListView.Borderless}"
ItemsSource="{Binding ElementName=me, Path=Items}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectionChanged="CommitSelectionChanged"
BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:InteractiveRebaseItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="128"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox
Grid.Column="0"
ItemsSource="{Binding Source={x:Static local:InteractiveRebaseModeInfo.Supported}}"
SelectedIndex="{Binding Path=Mode, Mode=TwoWay}"
BorderThickness="0">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:InteractiveRebaseModeInfo}">
<Grid Height="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" Width="12" Height="12" Margin="4,0,0,0" Fill="{Binding Theme}" Style="{StaticResource Style.Icon}" Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
<Label Grid.Column="1" Content="{Binding Title}" Padding="4,0"/>
<Label Grid.Column="2" Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Column="1">
<ContentControl x:Name="MessageEditorAnchor" MouseDoubleClick="PopupMessageEditor">
<TextBlock Text="{Binding Subject, Mode=TwoWay}" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
</ContentControl>
<Popup x:Name="MessageEditor" Placement="Bottom" IsOpen="{Binding IsEditorOpened}" VerticalOffset="1" Height="150" Width="400" PlacementTarget="{Binding ElementName=MessageEditorAnchor}">
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG4}">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border1}" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="1"/>
<RowDefinition Height="79"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding EditSubject, Mode=TwoWay}" Height="32" Padding="2,0" helpers:TextBoxHelper.Placeholder="Enter commit subject" BorderThickness="0"/>
<Rectangle Grid.Row="1" Height="1" Fill="{StaticResource Brush.FG}" Opacity="0.1"/>
<TextBox Grid.Row="2" Text="{Binding EditMessage, Mode=TwoWay}" TextChanged="CommitMessageChanged" Height="79" ScrollViewer.VerticalScrollBarVisibility="Auto" Padding="2" helpers:TextBoxHelper.Placeholder="Enter commit description. Optional" helpers:TextBoxHelper.PlaceholderBaseline="Top" BorderThickness="0" AcceptsReturn="True" AcceptsTab="True" TextWrapping="Wrap"/>
</Grid>
</Border>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
<Button Click="ApplyMessageEdit" Height="24" Style="{StaticResource Style.Button.AccentBordered}" BorderThickness="1" Content="APPLY" Margin="8,0"/>
<Button Click="HideMessageEditor" Height="24" Style="{StaticResource Style.Button.Bordered}" BorderThickness="1" Content="CANCEL"/>
</StackPanel>
</Grid>
</Border>
</Popup>
</Grid>
<TextBlock
Grid.Column="2"
Foreground="{StaticResource Brush.FG}"
VerticalAlignment="Center"
Text="{Binding Commit.Committer.Name}"/>
<TextBlock
Grid.Column="3"
Foreground="{StaticResource Brush.FG}"
VerticalAlignment="Center"
Text="{Binding Commit.Committer.Time}"/>
<StackPanel
Grid.Column="4"
Orientation="Horizontal"
Margin="4,0">
<Button Click="MoveUp" ToolTip="MOVE UP">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/>
</Button>
<Button Click="MoveDown" ToolTip="MOVE DOWN" Margin="4,0,0,0">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/>
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Loading Tip -->
<Grid Grid.Row="1" IsHitTestVisible="False">
<!-- Loading tip -->
<Path x:Name="loading" Grid.ColumnSpan="5" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5" Visibility="Hidden">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
<Path.Style>
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
</Style>
</Path.Style>
</Path>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
<!-- Commit Detail -->
<local:CommitViewer x:Name="commitViewer" Grid.Row="3" Background="{StaticResource Brush.BG4}"/>
<!-- Options Bar -->
<Grid Grid.Row="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="4"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="4,0,24,0">
<Label Grid.Column="0" Content="Rebase :"/>
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="branch"/>
<Label Grid.Column="2" Content="On :" Margin="8,0,0,0"/>
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}"/>
<Label x:Name="on"/>
</StackPanel>
<Button Grid.Column="1" Height="26" Click="Start" Style="{StaticResource Style.Button.AccentBordered}" Content="REBASE"/>
<Button Grid.Column="3" Height="26" Click="Cancel" Style="{StaticResource Style.Button.Bordered}" Content="CANCEL"/>
</Grid>
</Grid>
</Border>
</Window>

View file

@ -0,0 +1,299 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SourceGit.UI {
/// <summary>
/// Rebase mode.
/// </summary>
public enum InteractiveRebaseMode {
Pick,
Reword,
Squash,
Fixup,
Drop,
}
/// <summary>
/// Rebase mode information to display in UI.
/// </summary>
public class InteractiveRebaseModeInfo {
public InteractiveRebaseMode Mode { get; set; }
public string Title { get; set; }
public string Desc { get; set; }
public Brush Theme { get; set; }
public InteractiveRebaseModeInfo(InteractiveRebaseMode mode, string title, string desc, Brush brush) {
Mode = mode;
Title = title;
Desc = desc;
Theme = brush;
}
public static List<InteractiveRebaseModeInfo> Supported = new List<InteractiveRebaseModeInfo>() {
new InteractiveRebaseModeInfo(InteractiveRebaseMode.Pick, "Pick", "Use this commit", Brushes.Green),
new InteractiveRebaseModeInfo(InteractiveRebaseMode.Reword, "Reword", "Edit the commit message", Brushes.Yellow),
new InteractiveRebaseModeInfo(InteractiveRebaseMode.Squash, "Squash", "Meld into previous commit", App.Preference.UIUseLightTheme ? Brushes.Gray : Brushes.White),
new InteractiveRebaseModeInfo(InteractiveRebaseMode.Fixup, "Fixup", "Like 'Squash' but discard log message", App.Preference.UIUseLightTheme ? Brushes.Gray : Brushes.White),
new InteractiveRebaseModeInfo(InteractiveRebaseMode.Drop, "Drop", "Remove commit", Brushes.Red),
};
}
/// <summary>
/// Rebase item.
/// </summary>
public class InteractiveRebaseItem : INotifyPropertyChanged {
private InteractiveRebaseMode mode = InteractiveRebaseMode.Pick;
private bool isEditorOpened = false;
private string editSubject = null;
private string editMsg = null;
public event PropertyChangedEventHandler PropertyChanged;
public Git.Commit Commit { get; set; }
public int Mode {
get { return (int)mode; }
set {
if (value != (int)mode) {
mode = (InteractiveRebaseMode)value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Mode"));
}
}
}
public bool IsEditorOpened {
get { return isEditorOpened; }
set {
if (value != isEditorOpened) {
isEditorOpened = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("IsEditorOpened"));
}
}
}
public string Subject {
get { return Commit.Subject; }
set {
if (value != Commit.Subject) {
Commit.Subject = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Subject"));
}
}
}
public string EditSubject {
get { return editSubject; }
set {
if (value != editMsg) {
editSubject = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("EditSubject"));
}
}
}
public string EditMessage {
get { return editMsg; }
set {
if (value != editMsg) {
editMsg = value;
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("EditMessage"));
}
}
}
}
/// <summary>
/// Interactive rebase panel.
/// </summary>
public partial class InteractiveRebase : Window {
private Git.Repository repo = null;
private string from = null;
/// <summary>
/// Edit commit list.
/// </summary>
public ObservableCollection<InteractiveRebaseItem> Items {
get;
set;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened"></param>
/// <param name="start"></param>
public InteractiveRebase(Git.Repository opened, Git.Commit start) {
repo = opened;
Items = new ObservableCollection<InteractiveRebaseItem>();
from = start.ShortSHA;
InitializeComponent();
branch.Content = opened.CurrentBranch().Name;
on.Content = $"{start.ShortSHA} {start.Subject}";
Task.Run(() => {
var commits = repo.Commits($"{start.SHA}..HEAD");
commits.Add(start);
Dispatcher.Invoke(() => {
Items.Clear();
foreach (var c in commits) Items.Add(new InteractiveRebaseItem() { Commit = c });
});
});
}
#region WINDOW_COMMANDS
private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) {
var element = e.OriginalSource as FrameworkElement;
if (element == null) return;
var pos = PointToScreen(new Point(0, 33));
SystemCommands.ShowSystemMenu(this, pos);
}
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
private void Quit(object sender, RoutedEventArgs e) {
Close();
}
#endregion
private void CommitSelectionChanged(object sender, SelectionChangedEventArgs e) {
foreach (var obj in e.RemovedItems) {
var item = obj as InteractiveRebaseItem;
if (item != null) item.IsEditorOpened = false;
}
if (e.AddedItems.Count == 1) {
var item = e.AddedItems[0] as InteractiveRebaseItem;
if (item != null) commitViewer.SetData(repo, item.Commit);
}
}
private void PopupMessageEditor(object sender, MouseButtonEventArgs e) {
var item = (sender as Control).DataContext as InteractiveRebaseItem;
if (item == null) return;
item.EditSubject = item.Commit.Subject;
item.EditMessage = item.Commit.Message;
item.IsEditorOpened = true;
}
private void HideMessageEditor(object sender, RoutedEventArgs e) {
var item = (sender as Button).DataContext as InteractiveRebaseItem;
if (item == null) return;
item.IsEditorOpened = false;
}
private void ApplyMessageEdit(object sender, RoutedEventArgs e) {
var item = (sender as Button).DataContext as InteractiveRebaseItem;
if (item == null) return;
item.Subject = item.EditSubject;
item.Commit.Message = item.EditMessage;
item.Mode = (int)InteractiveRebaseMode.Reword;
item.IsEditorOpened = false;
}
private void CommitMessageChanged(object sender, TextChangedEventArgs e) {
(sender as TextBox).ScrollToEnd();
}
private void MoveUp(object sender, RoutedEventArgs e) {
var item = (sender as Button).DataContext as InteractiveRebaseItem;
if (item == null) return;
var idx = -1;
for (int i = 0; i < Items.Count; i++) {
if (Items[i].Commit.SHA == item.Commit.SHA) {
idx = i;
break;
}
}
if (idx > 0) {
Items.RemoveAt(idx);
Items.Insert(idx - 1, item);
}
}
private void MoveDown(object sender, RoutedEventArgs e) {
var item = (sender as Button).DataContext as InteractiveRebaseItem;
if (item == null) return;
var idx = -1;
for (int i = 0; i < Items.Count; i++) {
if (Items[i].Commit.SHA == item.Commit.SHA) {
idx = i;
break;
}
}
if (idx < Items.Count - 1) {
Items.RemoveAt(idx);
Items.Insert(idx + 1, item);
}
}
private void Start(object sender, RoutedEventArgs e) {
var stream = new FileStream(App.InteractiveRebaseTodo, FileMode.Create);
var writer = new StreamWriter(stream);
for (int i = Items.Count - 1; i >= 0; i--) {
var item = Items[i];
switch ((InteractiveRebaseMode)item.Mode) {
case InteractiveRebaseMode.Pick:
writer.WriteLine($"pick {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Reword:
writer.WriteLine($"reword {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Squash:
writer.WriteLine($"squash {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Fixup:
writer.WriteLine($"fixup {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Drop:
writer.WriteLine($"drop {item.Commit.ShortSHA} {item.Subject}");
break;
}
}
writer.Flush();
stream.Flush();
writer.Close();
stream.Close();
repo.SetWatcherEnabled(false);
var errs = repo.RunCommand($"-c sequence.editor=\"\\\"{App.InteractiveRebaseScript}\\\"\" rebase -i {from}^", null);
repo.AssertCommand(errs);
Close();
}
private void Cancel(object sender, RoutedEventArgs e) {
Close();
}
}
}

149
SourceGit/UI/Launcher.xaml Normal file
View file

@ -0,0 +1,149 @@
<Window x:Class="SourceGit.UI.Launcher"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:source="clr-namespace:SourceGit"
mc:Ignorable="d"
MinWidth="800" MinHeight="600"
Title="Source Git"
Width="{Binding Source={x:Static source:App.Preference}, Path=UIMainWindowWidth, Mode=TwoWay}"
Height="{Binding Source={x:Static source:App.Preference}, Path=UIMainWindowHeight, Mode=TwoWay}"
WindowStartupLocation="CenterScreen">
<!-- Enable WindowChrome Feature -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
</WindowChrome.WindowChrome>
<!-- Window Layout -->
<Border Background="{StaticResource Brush.BG1}">
<!-- Fix Maximize BUG -->
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Margin" Value="6"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Margin" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Titlebar -->
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Logo & Title -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Path
Width="20" Height="20" Margin="6,-1,2,0"
Style="{StaticResource Style.Icon}"
Data="{StaticResource Icon.Git}"
Fill="#FFF05133"
WindowChrome.IsHitTestVisibleInChrome="True"
MouseLeftButtonDown="LogoMouseButtonDown"/>
<Label Content="SOURCE GIT" FontWeight="Light"/>
</StackPanel>
<!-- Commands -->
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<Button Click="ShowPreference" Width="24" ToolTip="PREFERENCE">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Preference}"/>
</Button>
<Rectangle Width="1" Height="18" Margin="6,0" VerticalAlignment="Center" Fill="{StaticResource Brush.Border1}"/>
<Button Click="ShowAbout" Width="24" ToolTip="ABOUT">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}"/>
</Button>
<Rectangle Width="1" Height="18" Margin="6,0" VerticalAlignment="Center" Fill="{StaticResource Brush.Border1}"/>
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
</Button>
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
<Path>
<Path.Style>
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
</DataTrigger>
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Button>
<Button Click="Quit" Width="32">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</StackPanel>
</Grid>
<!-- Body -->
<ContentControl Grid.Row="1" x:Name="body"/>
<!-- Alerts -->
<ScrollViewer Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl Margin="4" Width="300" Height="Auto" ItemsSource="{Binding Alerts, ElementName=me}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="item" Height="Auto" Width="300" Margin="0,8,0,0">
<Border Background="{StaticResource Brush.BG1}">
<Border.Effect>
<DropShadowEffect BlurRadius="8" ShadowDepth="2" Direction="270"/>
</Border.Effect>
</Border>
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding Title}" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Margin="6,8" Text="{Binding Message}" Foreground="{StaticResource Brush.FG}" TextWrapping="Wrap"/>
<Button
Grid.Row="2"
Margin="4,0"
Click="RemoveAlert"
Content="CLOSE"
Style="{StaticResource Style.Button.AccentBordered}"
BorderBrush="{StaticResource Brush.FG}"
VerticalAlignment="Center"
HorizontalAlignment="Right"/>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Border>
</Window>

View file

@ -0,0 +1,140 @@
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SourceGit.UI {
/// <summary>
/// Main window for this app.
/// </summary>
public partial class Launcher : Window {
/// <summary>
/// Alert data.
/// </summary>
public class Alert {
public string Title { get; set; }
public string Message { get; set; }
}
/// <summary>
/// Alerts.
/// </summary>
public ObservableCollection<Alert> Alerts { get; set; } = new ObservableCollection<Alert>();
/// <summary>
/// Constructor
/// </summary>
public Launcher() {
Git.Repository.OnOpen = ShowDashboard;
Git.Repository.OnClose = ShowManager;
App.OnError = msg => ShowAlert(new Alert() { Title = "ERROR", Message = msg });
InitializeComponent();
ShowManager();
}
#region LAYOUT_CONTENT
/// <summary>
/// Show manager.
/// </summary>
private void ShowManager() {
Dispatcher.Invoke(() => {
body.Content = new Manager();
});
}
/// <summary>
/// Show dashboard.
/// </summary>
/// <param name="repo"></param>
private void ShowDashboard(Git.Repository repo) {
Dispatcher.Invoke(() => {
if (body.Content is Dashboard) return;
body.Content = new Dashboard(repo);
});
}
/// <summary>
/// Open preference dialog.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ShowPreference(object sender, RoutedEventArgs e) {
Preference.Show();
}
/// <summary>
/// Open about dialog.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ShowAbout(object sender, RoutedEventArgs e) {
var about = new About();
about.Owner = this;
about.ShowDialog();
}
/// <summary>
/// Show alert.
/// </summary>
/// <param name="alert"></param>
private void ShowAlert(Alert alert) {
Dispatcher.Invoke(() => Alerts.Add(alert));
}
/// <summary>
/// Remove an alert.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RemoveAlert(object sender, RoutedEventArgs e) {
var alert = (sender as Button).DataContext as Alert;
Alerts.Remove(alert);
}
#endregion
#region WINDOW_COMMANDS
/// <summary>
/// Minimize
/// </summary>
private void Minimize(object sender, RoutedEventArgs e) {
SystemCommands.MinimizeWindow(this);
}
/// <summary>
/// Maximize/Restore
/// </summary>
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
if (WindowState == WindowState.Normal) {
SystemCommands.MaximizeWindow(this);
} else {
SystemCommands.RestoreWindow(this);
}
}
/// <summary>
/// Quit
/// </summary>
private void Quit(object sender, RoutedEventArgs e) {
App.Current.Shutdown();
}
/// <summary>
/// Show system menu when user click logo.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) {
var element = e.OriginalSource as FrameworkElement;
if (element == null) return;
var pos = PointToScreen(new Point(0, 33));
SystemCommands.ShowSystemMenu(this, pos);
}
#endregion
}
}

231
SourceGit/UI/Manager.xaml Normal file
View file

@ -0,0 +1,231 @@
<UserControl
x:Class="SourceGit.UI.Manager"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:git="clr-namespace:SourceGit.Git"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Toolbar -->
<Grid Grid.Row="0" Panel.ZIndex="9999">
<Border Background="{StaticResource Brush.BG1}">
<Border.Effect>
<DropShadowEffect ShadowDepth="2" Direction="270" Opacity=".3"/>
</Border.Effect>
</Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Navigation -->
<StackPanel Grid.Column="0" Margin="8,0,0,0" Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Manager}"/>
<Label Content="Repositories"/>
</StackPanel>
<!-- Options -->
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Click="OpenOrAddRepo" Style="{StaticResource Style.Button}">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder.Open}"/>
<Label Content="Open Folder"/>
</StackPanel>
</Button>
<Button Click="CloneRepo" Style="{StaticResource Style.Button}" Margin="8,0,0,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Clone}"/>
<Label Content="Clone"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Grid>
<!-- Main Body -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="360"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left panel -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Recent Opened Repositories -->
<Label Grid.Row="0" Margin="8,8,0,0" Content="RECENTLY OPENED" Style="{StaticResource Style.Label.GroupHeader}"/>
<ListView
x:Name="recentOpened"
Grid.Row="1"
Height="Auto"
Margin="0,4"
Background="Transparent"
BorderThickness="0"
Style="{StaticResource Style.ListView.Borderless}"
ItemContainerStyle="{StaticResource Style.ListViewItem.Borderless}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
GotFocus="RecentsGotFocus"
SelectionChanged="RecentsSelectionChanged"
MouseDoubleClick="RecentsMouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type git:Repository}">
<StackPanel Orientation="Horizontal" Height="24">
<Path Height="12" Margin="16,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}"/>
<Label Margin="4,0,0,0" Content="{Binding Name}"/>
<Label FontSize="11" Content="{Binding Path}" Opacity=".5"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Repositories' tree -->
<Label Grid.Row="2" Margin="8,8,0,0" Content="REPOSITORIES" Style="{StaticResource Style.Label.GroupHeader}"/>
<TreeView
x:Name="repositories"
Grid.Row="3"
Margin="0, 4"
AllowDrop="True"
ContextMenuOpening="TreeContextMenuOpening"
Drop="TreeDrop"
GotFocus="TreeGotFocus"
MouseMove="TreeMouseMove">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsExpended, Mode=TwoWay}"/>
<EventSetter Event="Selected" Handler="TreeNodeSelected"/>
<EventSetter Event="DragOver" Handler="TreeNodeDragOver"/>
<EventSetter Event="Drop" Handler="TreeNodeDrop"/>
<EventSetter Event="Expanded" Handler="TreeNodeIsExpandedChanged"/>
<EventSetter Event="Collapsed" Handler="TreeNodeIsExpandedChanged"/>
<EventSetter Event="KeyDown" Handler="TreeNodeKeyDown"/>
<EventSetter Event="ContextMenuOpening" Handler="TreeNodeContextMenuOpening"/>
<EventSetter Event="MouseDoubleClick" Handler="TreeNodeDoubleClick"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Path x:Name="icon" Grid.Column="0" Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder.Fill}"/>
<Label x:Name="name" Grid.Column="1" Margin="4,0,0,0" Content="{Binding Name}" />
<TextBox x:Name="editName" Grid.Column="1" Margin="4,0,0,0" Text="{Binding Name}" Loaded="TreeNodeRenameStart" KeyDown="TreeNodeRenameKeyDown" LostFocus="TreeNodeRenameEnd"/>
</Grid>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsExpended}" Value="True">
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsRepo}" Value="True">
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Git}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEditing}" Value="True">
<Setter TargetName="name" Property="Visibility" Value="Hidden"/>
<Setter TargetName="editName" Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEditing}" Value="False">
<Setter TargetName="name" Property="Visibility" Value="Visible"/>
<Setter TargetName="editName" Property="Visibility" Value="Hidden"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" Background="Transparent"/>
<!-- Right Panel -->
<Grid Grid.Column="2" Background="{StaticResource Brush.BG3}">
<!-- Brief -->
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Name & Path & OpenButton -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="repoName" FontSize="20" FontWeight="Bold"/>
<Label Grid.Column="1" x:Name="repoPath" FontSize="20" FontWeight="Light" Opacity="0.5" VerticalAlignment="Center"/>
<Button Grid.Column="3" Click="OpenRepo" ToolTip="Open">
<Path Width="20" Height="20" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<!-- Status of selected repository -->
<Label Grid.Row="1" Content="STATUS" FontSize="16" Margin="0,16,0,4" FontWeight="Bold" Opacity=".8"/>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Label Content="Local Changes :" Opacity=".5" FontWeight="Bold"/>
<Label x:Name="localChanges" Margin="2,0,0,0"/>
<Label Content="Total Commits :" Opacity=".5" FontWeight="Bold"/>
<Label x:Name="totalCommits" Margin="2,0,0,0"/>
<Label Content="Last Commit :" Opacity=".5" FontWeight="Bold"/>
<Border Background="{StaticResource Brush.BG4}" Height="18" CornerRadius="4" VerticalAlignment="Center">
<Label x:Name="lastCommitId" Foreground="{StaticResource Brush.FG2}" FontFamily="Consolas" Padding="4,0" VerticalAlignment="Center"/>
</Border>
<Label x:Name="lastCommit" Margin="2,0,0,0"/>
</StackPanel>
<!-- README.md -->
<Label Grid.Row="3" Content="README" FontSize="16" Margin="0,16,0,4" FontWeight="Bold" Opacity=".8"/>
<Border Grid.Row="4" Margin="6,0,0,0" BorderBrush="{StaticResource Brush.BG4}" BorderThickness="1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TextBlock FontSize="10pt"
FontFamily="Consolas"
Padding="8"
Opacity="0.8"
Background="{StaticResource Brush.BG2}"
Foreground="{StaticResource Brush.FG}"
x:Name="readme"/>
</ScrollViewer>
</Border>
</Grid>
<!-- Mask -->
<Border x:Name="briefMask" Background="{StaticResource Brush.BG3}" IsHitTestVisible="False">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
<Path Width="160" Height="160" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Book}"/>
<Label Margin="0,8,0,0" Content="DRAG &amp; DROP FOLDER TO LEFT" FontSize="24" FontWeight="UltraBold" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
</Grid>
<!-- Popups -->
<local:PopupManager Grid.Row="1"/>
</Grid>
</UserControl>

View file

@ -0,0 +1,495 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SourceGit.UI {
/// <summary>
/// Repository manager.
/// </summary>
public partial class Manager : UserControl {
private TreeViewItem selectedTreeViewItem = null;
/// <summary>
/// Used to build tree
/// </summary>
public class Node {
public string Id { get; set; }
public string ParentId { get; set; }
public string Name { get; set; }
public bool IsRepo { get; set; }
public bool IsExpended { get; set; }
public bool IsEditing { get; set; }
public List<Node> Children { get; set; } = new List<Node>();
}
/// <summary>
/// Constructor.
/// </summary>
public Manager() {
Loaded += async (o, e) => {
await Task.Delay(500);
GC.Collect();
};
InitializeComponent();
UpdateRecentOpened();
UpdateTree();
}
#region TOOLBAR
/// <summary>
/// Open or add local repository.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OpenOrAddRepo(object sender, RoutedEventArgs e) {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.Description = "Open or init local repository";
dialog.RootFolder = Environment.SpecialFolder.MyComputer;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
CheckAndOpenRepo(dialog.SelectedPath);
}
}
/// <summary>
/// Clone remote repository.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CloneRepo(object sender, RoutedEventArgs e) {
if (MakeSureReady()) Clone.Show();
}
#endregion
#region EVENT_RECENT_LISTVIEW
private void RecentsGotFocus(object sender, RoutedEventArgs e) {
if (selectedTreeViewItem != null) selectedTreeViewItem.IsSelected = false;
selectedTreeViewItem = null;
e.Handled = true;
}
private void RecentsSelectionChanged(object sender, SelectionChangedEventArgs e) {
var recent = recentOpened.SelectedItem as Git.Repository;
if (recent != null) ShowBrief(recent);
e.Handled = true;
}
private void RecentsMouseDoubleClick(object sender, MouseButtonEventArgs e) {
var list = sender as ListView;
var recent = list.SelectedItem as Git.Repository;
if (recent != null) {
CheckAndOpenRepo(recent.Path);
e.Handled = true;
}
}
#endregion
#region EVENT_TREEVIEW
private void TreeGotFocus(object sender, RoutedEventArgs e) {
recentOpened.SelectedItems.Clear();
e.Handled = true;
}
private void TreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
var addFolder = new MenuItem();
addFolder.Header = "Add Folder";
addFolder.Click += (o, ev) => {
var group = App.Preference.AddGroup("New Group", "");
UpdateTree(group.Id);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(addFolder);
menu.IsOpen = true;
e.Handled = true;
}
private void TreeMouseMove(object sender, MouseEventArgs e) {
if (e.LeftButton != MouseButtonState.Pressed) return;
if (selectedTreeViewItem == null) return;
var node = selectedTreeViewItem.DataContext as Node;
if (node == null || !node.IsRepo) return;
DragDrop.DoDragDrop(repositories, selectedTreeViewItem, DragDropEffects.Move);
e.Handled = true;
}
private void TreeDrop(object sender, DragEventArgs e) {
bool needRebuild = false;
if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
if (!MakeSureReady()) return;
string[] paths = e.Data.GetData(DataFormats.FileDrop) as string[];
string group = "";
var node = (sender as TreeViewItem)?.DataContext as Node;
if (node != null) group = node.IsRepo ? node.ParentId : node.Id;
foreach (var path in paths) {
FileInfo info = new FileInfo(path);
if (info.Attributes == FileAttributes.Directory && Git.Repository.IsValid(path)) {
App.Preference.AddRepository(path, group);
needRebuild = true;
}
}
} else if (e.Data.GetDataPresent(typeof(TreeViewItem))) {
var item = e.Data.GetData(typeof(TreeViewItem)) as TreeViewItem;
var node = item.DataContext as Node;
if (node == null || !node.IsRepo) return;
var group = "";
var to = (sender as TreeViewItem)?.DataContext as Node;
if (to != null) group = to.IsRepo ? to.ParentId : to.Id;
App.Preference.FindRepository(node.Id).GroupId = group;
needRebuild = true;
}
if (needRebuild) UpdateTree();
e.Handled = true;
}
#endregion
#region EVENT_TREEVIEWITEM
private void TreeNodeSelected(object sender, RoutedEventArgs e) {
selectedTreeViewItem = sender as TreeViewItem;
var node = selectedTreeViewItem.DataContext as Node;
if (node.IsRepo) {
ShowBrief(App.Preference.FindRepository(node.Id));
} else {
HideBrief();
}
e.Handled = true;
}
private void TreeNodeDoubleClick(object sender, MouseButtonEventArgs e) {
var node = (sender as TreeViewItem).DataContext as Node;
if (node != null && node.IsRepo) {
CheckAndOpenRepo(node.Id);
e.Handled = true;
}
}
private void TreeNodeDragOver(object sender, DragEventArgs e) {
var item = sender as TreeViewItem;
var node = item.DataContext as Node;
if (node != null && !node.IsRepo) item.IsExpanded = true;
e.Handled = true;
}
private void TreeNodeDrop(object sender, DragEventArgs e) {
TreeDrop(sender, e);
}
private void TreeNodeIsExpandedChanged(object sender, RoutedEventArgs e) {
var item = sender as TreeViewItem;
var node = item.DataContext as Node;
if (node != null && !node.IsRepo) {
var group = App.Preference.FindGroup(node.Id);
group.IsExpended = item.IsExpanded;
e.Handled = true;
}
}
private void TreeNodeKeyDown(object sender, KeyEventArgs e) {
if (e.Key != Key.Delete) return;
var node = (sender as TreeViewItem).DataContext as Node;
if (node != null) DeleteNode(node);
e.Handled = true;
}
private void TreeNodeRenameStart(object sender, RoutedEventArgs e) {
var text = sender as TextBox;
if (text.IsVisible) {
text.SelectAll();
text.Focus();
}
e.Handled = true;
}
private void TreeNodeRenameKeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Escape) {
UpdateTree();
e.Handled = true;
} else if (e.Key == Key.Enter) {
TreeNodeRenameEnd(sender, e);
e.Handled = true;
}
}
private void TreeNodeRenameEnd(object sender, RoutedEventArgs e) {
var text = sender as TextBox;
if (string.IsNullOrWhiteSpace(text.Text)) {
UpdateTree();
e.Handled = false;
return;
}
var node = text.DataContext as Node;
if (node != null) {
if (node.IsRepo) {
App.Preference.RenameRepository(node.Id, text.Text);
} else {
App.Preference.RenameGroup(node.Id, text.Text);
}
UpdateRecentOpened();
UpdateTree();
e.Handled = true;
}
}
private void TreeNodeContextMenuOpening(object sender, ContextMenuEventArgs e) {
var item = sender as TreeViewItem;
var node = item.DataContext as Node;
var menu = new ContextMenu();
if (node.IsRepo) {
var open = new MenuItem();
open.Header = "Open";
open.Click += (o, ev) => {
CheckAndOpenRepo(node.Id);
ev.Handled = true;
};
var explore = new MenuItem();
explore.Header = "Open Container Folder";
explore.Click += (o, ev) => {
Process.Start(node.Id);
ev.Handled = true;
};
menu.Items.Add(open);
menu.Items.Add(explore);
} else {
var addSubFolder = new MenuItem();
addSubFolder.Header = "Add Sub-Folder";
addSubFolder.Click += (o, ev) => {
var parent = App.Preference.FindGroup(node.Id);
if (parent != null) parent.IsExpended = true;
var group = App.Preference.AddGroup("New Group", node.Id);
UpdateTree(group.Id);
ev.Handled = true;
};
menu.Items.Add(addSubFolder);
}
var rename = new MenuItem();
rename.Header = "Rename";
rename.Click += (o, ev) => {
UpdateTree(node.Id);
ev.Handled = true;
};
var delete = new MenuItem();
delete.Header = "Delete";
delete.Click += (o, ev) => {
DeleteNode(node);
ev.Handled = true;
};
menu.Items.Add(rename);
menu.Items.Add(delete);
menu.IsOpen = true;
e.Handled = true;
}
#endregion
#region EVENT_BRIEF
private void ShowBrief(Git.Repository repo) {
if (repo == null || !Git.Repository.IsValid(repo.Path)) {
if (Directory.Exists(repo.Path)) {
Init.Show(repo.Path);
} else {
App.RaiseError("Path is NOT valid git repository or has been removed.");
App.Preference.RemoveRepository(repo.Path);
UpdateRecentOpened();
UpdateTree();
}
return;
}
briefMask.Visibility = Visibility.Hidden;
repoName.Content = repo.Name;
repoPath.Content = repo.Path;
Task.Run(() => {
var changes = repo.LocalChanges();
var count = changes.Count;
Dispatcher.Invoke(() => localChanges.Content = count);
});
Task.Run(() => {
var count = repo.TotalCommits();
Dispatcher.Invoke(() => totalCommits.Content = count);
});
Task.Run(() => {
var commits = repo.Commits("-n 1");
Dispatcher.Invoke(() => {
if (commits.Count > 0) {
var c = commits[0];
lastCommitId.Content = c.ShortSHA;
lastCommit.Content = c.Subject;
} else {
lastCommitId.Content = "---";
lastCommit.Content = "";
}
});
});
if (File.Exists(repo.Path + "/README.md")) {
readme.Text = File.ReadAllText(repo.Path + "/README.md");
} else {
readme.Text = "";
}
}
private void HideBrief() {
briefMask.Visibility = Visibility.Visible;
}
private void OpenRepo(object sender, RoutedEventArgs e) {
CheckAndOpenRepo(repoPath.Content as string);
}
#endregion
#region PRIVATES
/// <summary>
/// Make sure git is configured.
/// </summary>
/// <returns></returns>
private bool MakeSureReady() {
if (!App.IsGitConfigured) {
App.RaiseError("Git has NOT been configured.\nPlease to go [Preference] and configure it first.");
return false;
}
return true;
}
/// <summary>
/// Check and open repository
/// </summary>
/// <param name="path"></param>
private void CheckAndOpenRepo(string path) {
if (!MakeSureReady()) return;
if (!Git.Repository.IsValid(path)) {
if (Directory.Exists(path)) {
Init.Show(path);
return;
}
App.RaiseError($"Path[{path}] not exists!");
return;
}
var repo = App.Preference.AddRepository(path, "");
repo.Open();
}
/// <summary>
/// Update recent opened repositories.
/// </summary>
private void UpdateRecentOpened() {
var sorted = App.Preference.Repositories.OrderByDescending(a => a.LastOpenTime).ToList();
var top5 = new List<Git.Repository>();
for (int i = 0; i < sorted.Count && i < 5; i++) {
if (sorted[i].LastOpenTime <= 0) break;
top5.Add(sorted[i]);
}
recentOpened.ItemsSource = top5;
}
/// <summary>
/// Update tree items.
/// </summary>
/// <param name="editingNodeId"></param>
private void UpdateTree(string editingNodeId = null) {
var groupNodes = new Dictionary<string, Node>();
var nodes = new List<Node>();
foreach (var group in App.Preference.Groups) {
Node node = new Node() {
Id = group.Id,
ParentId = group.ParentId,
Name = group.Name,
IsRepo = false,
IsExpended = group.IsExpended,
IsEditing = group.Id == editingNodeId,
};
groupNodes.Add(node.Id, node);
}
nodes.Clear();
foreach (var kv in groupNodes) {
if (groupNodes.ContainsKey(kv.Value.ParentId)) {
groupNodes[kv.Value.ParentId].Children.Add(kv.Value);
} else {
nodes.Add(kv.Value);
}
}
foreach (var repo in App.Preference.Repositories) {
Node node = new Node() {
Id = repo.Path,
ParentId = repo.GroupId,
Name = repo.Name,
IsRepo = true,
IsExpended = false,
IsEditing = repo.Path == editingNodeId,
};
if (groupNodes.ContainsKey(repo.GroupId)) {
groupNodes[repo.GroupId].Children.Add(node);
} else {
nodes.Add(node);
}
}
repositories.ItemsSource = nodes;
}
/// <summary>
/// Delete tree node.
/// </summary>
/// <param name="node"></param>
private void DeleteNode(Node node) {
if (node.IsRepo) {
App.Preference.RemoveRepository(node.Id);
UpdateRecentOpened();
} else {
App.Preference.RemoveGroup(node.Id);
}
UpdateTree();
}
#endregion
}
}

71
SourceGit/UI/Merge.xaml Normal file
View file

@ -0,0 +1,71 @@
<UserControl x:Class="SourceGit.UI.Merge"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SourceGit.UI"
mc:Ignorable="d"
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Merge Branch"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Source Branch :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="sourceBranch"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Into :"/>
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="targetBranch"/>
</StackPanel>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Merge Option :"/>
<ComboBox x:Name="combOptions" Grid.Row="4" Grid.Column="1" VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Label Content="{Binding Name}" Padding="4,0"/>
<Label Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Row="6" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

120
SourceGit/UI/Merge.xaml.cs Normal file
View file

@ -0,0 +1,120 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Merge branch dialog.
/// </summary>
public partial class Merge : UserControl {
private Git.Repository repo = null;
/// <summary>
/// Merge option.
/// </summary>
public class Option {
public string Name { get; set; }
public string Desc { get; set; }
public string Arg { get; set; }
public Option(string n, string d, string a) {
Name = n;
Desc = d;
Arg = a;
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="source">Source branch to merge data from.</param>
/// <param name="dest">Target branch to merge into</param>
public Merge(Git.Repository opened, string source, string dest) {
InitializeComponent();
repo = opened;
sourceBranch.Content = source;
targetBranch.Content = dest;
combOptions.ItemsSource = new Option[] {
new Option("Default", "Fast-forward if possible", ""),
new Option("No Fast-forward", "Always create a merge commit", "--no-ff"),
new Option("Squash", "Use '--squash'", "--squash"),
new Option("Don't commit", "Merge without commit", "--no-commit"),
};
combOptions.SelectedIndex = 0;
}
/// <summary>
/// Display this dialog.
/// </summary>
/// <param name="opened"></param>
/// <param name="source"></param>
/// <param name="dest"></param>
public static void Show(Git.Repository opened, string source, string dest) {
PopupManager.Show(new Merge(opened, source, dest));
}
/// <summary>
/// Start merge directly(Fast-forward).
/// </summary>
/// <param name="opened"></param>
/// <param name="source"></param>
/// <param name="dest"></param>
public static void StartDirectly(Git.Repository opened, string source, string dest) {
var merge = new Merge(opened, source, dest);
PopupManager.Show(merge);
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
merge.statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
merge.status.Visibility = Visibility.Visible;
Task.Run(() => {
opened.Merge(source, "");
merge.Dispatcher.Invoke(() => {
merge.status.Visibility = Visibility.Collapsed;
merge.statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
});
});
}
/// <summary>
/// Start merge
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
var branch = sourceBranch.Content as string;
var opt = combOptions.SelectedItem as Option;
await Task.Run(() => repo.Merge(branch, opt.Arg));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel merge.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,21 @@
<UserControl x:Class="SourceGit.UI.PopupManager"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Visibility="Collapsed">
<Grid ClipToBounds="True">
<Border Background="Transparent" MouseLeftButtonDown="Close"/>
<Grid HorizontalAlignment="Center" VerticalAlignment="Top" Width="Auto" Height="Auto">
<Border Background="{StaticResource Brush.BG1}">
<Border.Effect>
<DropShadowEffect ShadowDepth="1" Opacity=".5" Direction="270" BlurRadius="16"/>
</Border.Effect>
</Border>
<Border x:Name="popupContent" Padding="8" Width="Auto" Height="Auto"/>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,95 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Common popup manager.
/// </summary>
public partial class PopupManager : UserControl {
private static PopupManager instance = null;
private static bool locked = false;
/// <summary>
/// Constructor.
/// </summary>
public PopupManager() {
instance = this;
InitializeComponent();
}
/// <summary>
/// Show content as popup.
/// </summary>
/// <param name="elem"></param>
public static void Show(UIElement elem) {
if (instance == null || locked) return;
var gone = new Thickness(0, -(double)elem.GetValue(HeightProperty) - 16, 0, 0);
ThicknessAnimation anim = new ThicknessAnimation();
anim.Duration = TimeSpan.FromMilliseconds(150);
anim.From = gone;
anim.To = new Thickness(0);
instance.popupContent.Child = elem;
instance.popupContent.Margin = gone;
instance.Visibility = Visibility.Visible;
instance.popupContent.BeginAnimation(MarginProperty, anim);
}
/// <summary>
/// Is current locked.
/// </summary>
/// <returns></returns>
public static bool IsLocked() {
return locked;
}
/// <summary>
/// Lock
/// </summary>
public static void Lock() {
locked = true;
}
/// <summary>
/// Unlock
/// </summary>
public static void Unlock() {
locked = false;
}
/// <summary>
/// Close current popup.
/// </summary>
/// <param name="unlockFirst"></param>
public static void Close(bool unlockFirst = false) {
if (instance == null) return;
if (instance.popupContent.Child == null) return;
if (locked && !unlockFirst) return;
locked = false;
ThicknessAnimation anim = new ThicknessAnimation();
anim.Duration = TimeSpan.FromMilliseconds(150);
anim.From = new Thickness(0);
anim.To = new Thickness(0, -(double)instance.popupContent.Child.GetValue(HeightProperty) - 16, 0, 0);
anim.Completed += (obj, ev) => {
instance.Visibility = Visibility.Collapsed;
instance.popupContent.Child = null;
};
instance.popupContent.BeginAnimation(MarginProperty, anim);
}
/// <summary>
/// Close by click blank area.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Close(object sender, RoutedEventArgs e) {
Close();
}
}
}

View file

@ -0,0 +1,131 @@
<UserControl x:Class="SourceGit.UI.Preference"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
xmlns:app="clr-namespace:SourceGit"
xmlns:git="clr-namespace:SourceGit.Git"
mc:Ignorable="d"
Height="472" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="18"/>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="18"/>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="18"/>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="18"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition MinWidth="200" Width="*"/>
</Grid.ColumnDefinitions>
<!-- 显示 -->
<Label Grid.Row="0" Grid.ColumnSpan="2" Content="Appearance" FontSize="18"/>
<Label Grid.Row="1" Grid.Column="0" Content="Light Theme :" HorizontalAlignment="Right"/>
<CheckBox
Grid.Row="1"
Grid.Column="1"
IsChecked="{Binding Source={x:Static app:App.Preference}, Path=UIUseLightTheme, Mode=TwoWay}"
Content="Restart required"
TextElement.FontStyle="Italic"/>
<!-- GIT相关配置 -->
<Label Grid.Row="3" Grid.ColumnSpan="2" Content="Git Instance" FontSize="18"/>
<Label Grid.Row="4" Grid.Column="0" Content="Install Path :" HorizontalAlignment="Right"/>
<Grid Grid.Row="4" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
x:Name="txtGitPath"
Height="24"
Text="{Binding Source={x:Static app:App.Preference}, Path=GitExecutable, Mode=TwoWay}"
helpers:TextBoxHelper.Placeholder="Input path for git.exe"/>
<Button Grid.Column="1" Width="24" Height="24" Click="SelectGitPath" Padding="0" BorderThickness="1" Style="{StaticResource Style.Button.Bordered}">
<Path Width="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<Label Grid.Row="5" Grid.Column="0" Content="Default Clone Dir :" HorizontalAlignment="Right"/>
<Grid Grid.Row="5" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
x:Name="txtGitCloneDir"
Height="24"
Text="{Binding Source={x:Static app:App.Preference}, Path=GitDefaultCloneDir, Mode=TwoWay}"
helpers:TextBoxHelper.Placeholder="Default path to clone repo into"/>
<Button Grid.Column="1" Width="24" Height="24" Click="SelectDefaultClonePath" Padding="0" BorderThickness="1" Style="{StaticResource Style.Button.Bordered}">
<Path Width="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<!-- Global User -->
<Label Grid.Row="7" Grid.ColumnSpan="2" Content="Global User" FontSize="18"/>
<Label Grid.Row="8" Grid.Column="0" Content="Name :" HorizontalAlignment="Right"/>
<TextBox Grid.Row="8" Grid.Column="1" Height="24" helpers:TextBoxHelper.Placeholder="Global git user name" Text="{Binding ElementName=me, Path=GlobalUser, Mode=TwoWay}"/>
<Label Grid.Row="9" Grid.Column="0" Content="Email :" HorizontalAlignment="Right"/>
<TextBox Grid.Row="9" Grid.Column="1" Height="24" helpers:TextBoxHelper.Placeholder="Global git user email" Text="{Binding ElementName=me, Path=GlobalUserEmail, Mode=TwoWay}"/>
<!-- 合并工具配置 -->
<Label Grid.Row="11" Grid.ColumnSpan="2" Content="Merge Tool" FontSize="18"/>
<Label Grid.Row="12" Grid.Column="0" Content="Choose Merger :" HorizontalAlignment="Right"/>
<ComboBox Grid.Row="12" Grid.Column="1"
Height="24"
Padding="2,0,0,0"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
SelectedIndex="{Binding Source={x:Static app:App.Preference}, Path=MergeTool, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Static git:MergeTool.Supported}}"
DisplayMemberPath="Name"
SelectionChanged="ChangeMergeTool"/>
<Label Grid.Row="13" Grid.Column="0" Content="Install Path :" HorizontalAlignment="Right"/>
<Grid Grid.Row="13" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="28"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
x:Name="txtMergePath"
Height="24"
Text="{Binding Source={x:Static app:App.Preference}, Path=MergeExecutable, Mode=TwoWay}"
helpers:TextBoxHelper.Placeholder="Input path for merge tool"/>
<Button Grid.Column="1" Width="24" Height="24" Click="SelectMergeToolPath" Padding="0" BorderThickness="1" Style="{StaticResource Style.Button.Bordered}">
<Path Width="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
</Button>
</Grid>
<Label Grid.Row="14" Grid.Column="0" Content="Command :" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="14" Grid.Column="1"
x:Name="txtMergeParam"
VerticalAlignment="Center"
Foreground="{StaticResource Brush.FG2}"/>
<Button Grid.Row="16" Grid.Column="1"
Content="CLOSE"
Click="Close"
Width="80"
Style="{StaticResource Style.Button.AccentBordered}"
HorizontalAlignment="Right"/>
</Grid>
</UserControl>

View file

@ -0,0 +1,178 @@
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Preference window.
/// </summary>
public partial class Preference : UserControl {
/// <summary>
/// Git global user name.
/// </summary>
public string GlobalUser {
get;
set;
}
/// <summary>
/// Git global user email.
/// </summary>
public string GlobalUserEmail {
get;
set;
}
/// <summary>
/// Constructor.
/// </summary>
public Preference() {
GlobalUser = GetConfig("user.name");
GlobalUserEmail = GetConfig("user.email");
InitializeComponent();
int mergeType = App.Preference.MergeTool;
var merger = Git.MergeTool.Supported[mergeType];
txtMergePath.IsReadOnly = !merger.IsConfigured;
txtMergeParam.Text = merger.Parameter;
}
/// <summary>
/// Show preference.
/// </summary>
public static void Show() {
PopupManager.Show(new Preference());
}
/// <summary>
/// Close this dialog
/// </summary>
private void Close(object sender, RoutedEventArgs e) {
var oldUser = GetConfig("user.name");
if (oldUser != GlobalUser) SetConfig("user.name", GlobalUser);
var oldEmail = GetConfig("user.email");
if (oldEmail != GlobalUserEmail) SetConfig("user.email", GlobalUserEmail);
PopupManager.Close();
}
/// <summary>
/// Select git executable file path.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SelectGitPath(object sender, RoutedEventArgs e) {
var dialog = new OpenFileDialog();
dialog.Filter = "Git Executable|git.exe";
dialog.FileName = "git.exe";
dialog.Title = "Select Git Executable File";
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == true) {
txtGitPath.Text = dialog.FileName;
App.Preference.GitExecutable = dialog.FileName;
}
}
/// <summary>
/// Set default clone path.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SelectDefaultClonePath(object sender, RoutedEventArgs e) {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.Description = "Select Folder To Clone Repository Into As Default";
dialog.RootFolder = Environment.SpecialFolder.MyComputer;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
txtGitCloneDir.Text = dialog.SelectedPath;
App.Preference.GitDefaultCloneDir = dialog.SelectedPath;
}
}
/// <summary>
/// Choose external merge tool.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ChangeMergeTool(object sender, SelectionChangedEventArgs e) {
if (IsLoaded) {
var t = Git.MergeTool.Supported[App.Preference.MergeTool];
App.Preference.MergeExecutable = t.Finder();
txtMergePath.Text = App.Preference.MergeExecutable;
txtMergeParam.Text = t.Parameter;
txtMergePath.IsReadOnly = !t.IsConfigured;
}
}
/// <summary>
/// Set merge tool executable file path.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SelectMergeToolPath(object sender, RoutedEventArgs e) {
int mergeType = App.Preference.MergeTool;
if (mergeType == 0) return;
var merger = Git.MergeTool.Supported[mergeType];
var dialog = new OpenFileDialog();
dialog.Filter = $"{merger.Name} Executable|{merger.ExecutableName}";
dialog.Title = $"Select {merger.Name} Install Path";
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
dialog.CheckFileExists = true;
if (dialog.ShowDialog() == true) {
txtMergePath.Text = dialog.FileName;
App.Preference.MergeExecutable = dialog.FileName;
}
}
#region CONFIG
private string GetConfig(string key) {
if (!App.IsGitConfigured) return "";
var startInfo = new ProcessStartInfo();
startInfo.FileName = App.Preference.GitExecutable;
startInfo.Arguments = $"config --global {key}";
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.StandardOutputEncoding = Encoding.UTF8;
var proc = new Process() { StartInfo = startInfo };
proc.Start();
var output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit();
proc.Close();
return output.Trim();
}
private void SetConfig(string key, string val) {
if (!App.IsGitConfigured) return;
var startInfo = new ProcessStartInfo();
startInfo.FileName = App.Preference.GitExecutable;
startInfo.Arguments = $"config --global {key} \"{val}\"";
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
var proc = new Process() { StartInfo = startInfo };
proc.Start();
proc.WaitForExit();
proc.Close();
}
#endregion
}
}

100
SourceGit/UI/Pull.xaml Normal file
View file

@ -0,0 +1,100 @@
<UserControl x:Class="SourceGit.UI.Pull"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="248" d:DesignWidth="500" Height="248" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Pull (Fetch &amp; Merge)"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
<ComboBox Grid.Row="2" Grid.Column="1"
x:Name="combRemotes"
VerticalAlignment="Center"
SelectionChanged="RemotesSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
<Label Content="{Binding}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Branch :"/>
<ComboBox Grid.Row="3" Grid.Column="1"
x:Name="combBranches"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label Content="{Binding}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Into :"/>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="txtInto" VerticalAlignment="Center"/>
</StackPanel>
<CheckBox Grid.Row="5" Grid.Column="1"
x:Name="chkRebase"
IsChecked="True"
VerticalAlignment="Center"
Content="Use rebase instead of merge"/>
<CheckBox Grid.Row="6" Grid.Column="1"
x:Name="chkAutoStash"
IsChecked="True"
VerticalAlignment="Center"
Content="Stash &amp; reapply local changes"/>
<Grid Grid.Row="8" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="9" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

131
SourceGit/UI/Pull.xaml.cs Normal file
View file

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Git pull
/// </summary>
public partial class Pull : UserControl {
private Git.Repository repo = null;
private string preferRemote = null;
private string preferBranch = null;
/// <summary>
/// Constructor
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="preferRemoteBranch">Prefered remote branch.</param>
public Pull(Git.Repository opened, string preferRemoteBranch) {
repo = opened;
InitializeComponent();
SetContent(preferRemoteBranch);
}
/// <summary>
/// Display git pull dialog.
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="preferRemoteBranch">Prefered remote branch</param>
public static void Show(Git.Repository opened, string preferRemoteBranch = null) {
PopupManager.Show(new Pull(opened, preferRemoteBranch));
}
/// <summary>
/// Set content.
/// </summary>
private void SetContent(string prefered) {
var branches = repo.Branches();
var remotes = new List<string>();
var current = null as Git.Branch;
foreach (var b in branches) {
if (b.IsLocal) {
if (b.IsCurrent) current = b;
} else {
if (!remotes.Contains(b.Remote)) remotes.Add(b.Remote);
}
}
if (!string.IsNullOrEmpty(prefered)) {
preferRemote = prefered.Substring(0, prefered.IndexOf('/'));
preferBranch = prefered;
} else if (current != null && !string.IsNullOrEmpty(current.Upstream)) {
var upstream = current.Upstream.Substring("refs/remotes/".Length);
preferRemote = upstream.Substring(0, upstream.IndexOf('/'));
preferBranch = upstream;
}
txtInto.Content = current.Name;
combRemotes.ItemsSource = remotes;
combRemotes.SelectedItem = preferRemote;
}
/// <summary>
/// Start pull
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
var remote = combRemotes.SelectedItem as string;
var branch = combBranches.SelectedItem as string;
var rebase = chkRebase.IsChecked == true;
var autoStash = chkAutoStash.IsChecked == true;
if (remote == null || branch == null) return;
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
await Task.Run(() => repo.Pull(remote, branch.Substring(branch.IndexOf('/')+1), msg => Dispatcher.Invoke(() => statusMsg.Content = msg), rebase, autoStash));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
/// <summary>
/// Remote selection changed event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RemotesSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var remote = e.AddedItems[0] as string;
var allBranches = repo.Branches();
var branches = new List<string>();
foreach (var b in allBranches) {
if (!b.IsLocal && b.Remote == remote) {
branches.Add(b.Name);
}
}
combBranches.ItemsSource = branches;
if (remote == preferRemote && preferBranch != null) {
combBranches.SelectedItem = preferBranch;
} else {
combBranches.SelectedIndex = 0;
}
}
}
}

108
SourceGit/UI/Push.xaml Normal file
View file

@ -0,0 +1,108 @@
<UserControl x:Class="SourceGit.UI.Push"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:git="clr-namespace:SourceGit.Git"
mc:Ignorable="d"
d:DesignHeight="248" d:DesignWidth="500" Height="248" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Push Changes To Remote"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Local Branch :"/>
<ComboBox Grid.Row="2" Grid.Column="1"
x:Name="combLocalBranches"
VerticalAlignment="Center"
SelectionChanged="LocalBranchesSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type git:Branch}">
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label Content="{Binding Name}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Remote :"/>
<ComboBox Grid.Row="3" Grid.Column="1"
x:Name="combRemotes"
VerticalAlignment="Center"
SelectionChanged="RemotesSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
<Label Content="{Binding}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="To :"/>
<ComboBox Grid.Row="4" Grid.Column="1"
x:Name="combRemoteBranches"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label Content="{Binding}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Grid.Row="5" Grid.Column="1"
x:Name="chkTags"
VerticalAlignment="Center"
Content="Push all tags"/>
<CheckBox Grid.Row="6" Grid.Column="1"
x:Name="chkForce"
VerticalAlignment="Center"
Content="Force push"/>
<Grid Grid.Row="8" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="9" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

202
SourceGit/UI/Push.xaml.cs Normal file
View file

@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace SourceGit.UI {
/// <summary>
/// Git push dialog
/// </summary>
public partial class Push : UserControl {
private Git.Repository repo = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository.</param>
/// <param name="prefer">Prefered push branch.</param>
public Push(Git.Repository opened, Git.Branch prefer) {
repo = opened;
InitializeComponent();
SetContent(prefer);
}
/// <summary>
/// Show push dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="prefer"></param>
public static void Show(Git.Repository repo, Git.Branch prefer = null) {
PopupManager.Show(new Push(repo, prefer));
}
/// <summary>
/// Show push and start directly.
/// </summary>
/// <param name="repo"></param>
public static void StartDirectly(Git.Repository repo) {
var current = repo.CurrentBranch();
if (current == null || string.IsNullOrEmpty(current.Upstream)) {
App.RaiseError("Current branch has no tracked upstream");
return;
}
var push = new Push(repo, current);
PopupManager.Show(push);
PopupManager.Lock();
var upstream = current.Upstream.Substring(13);
var remoteIdx = upstream.IndexOf('/');
var remote = upstream.Substring(0, remoteIdx);
var remoteBranch = upstream.Substring(remoteIdx + 1);
push.status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
push.statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
Task.Run(() => {
repo.Push(remote, current.Name, remoteBranch, msg => push.Dispatcher.Invoke(() => push.statusMsg.Content = msg));
push.Dispatcher.Invoke(() => {
push.status.Visibility = Visibility.Collapsed;
push.statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
});
});
}
/// <summary>
/// Set content.
/// </summary>
private void SetContent(Git.Branch prefer) {
var allBranches = repo.Branches();
var localBranches = new List<Git.Branch>();
foreach (var b in allBranches) {
if (b.IsLocal) {
localBranches.Add(b);
if (b.IsCurrent && prefer == null) prefer = b;
}
}
combLocalBranches.ItemsSource = localBranches;
combLocalBranches.SelectedItem = prefer;
}
/// <summary>
/// Start push.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
var localBranch = combLocalBranches.SelectedItem as Git.Branch;
var remote = combRemotes.SelectedItem as string;
var remoteBranch = combRemoteBranches.SelectedItem as string;
var track = string.IsNullOrEmpty(localBranch.Upstream);
var tags = chkTags.IsChecked == true;
var force = chkForce.IsChecked == true;
remoteBranch = remoteBranch.Substring($"{remote}/".Length);
if (remoteBranch.Contains(" (new)")) {
remoteBranch = remoteBranch.Substring(0, remoteBranch.Length - 6);
}
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
await Task.Run(() => repo.Push(remote, localBranch.Name, remoteBranch, msg => Dispatcher.Invoke(() => statusMsg.Content = msg), tags, track, force));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
/// <summary>
/// Local branch selection changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LocalBranchesSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var current = e.AddedItems[0] as Git.Branch;
var allRemotes = repo.Remotes();
var remoteNames = new List<string>();
foreach (var r in allRemotes) remoteNames.Add(r.Name);
combRemotes.ItemsSource = null;
combRemotes.ItemsSource = remoteNames;
if (!string.IsNullOrEmpty(current.Upstream)) {
var upstream = current.Upstream.Substring("refs/remotes/".Length);
combRemotes.SelectedItem = upstream.Substring(0, upstream.IndexOf('/'));
} else {
combRemotes.SelectedIndex = 0;
}
}
/// <summary>
/// Remote selection changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RemotesSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var remote = e.AddedItems[0] as string;
var allBranches = repo.Branches();
var branches = new List<string>();
combRemoteBranches.ItemsSource = null;
foreach (var b in allBranches) {
if (!b.IsLocal && b.Remote == remote) {
branches.Add(b.Name);
}
}
var current = combLocalBranches.SelectedItem as Git.Branch;
if (string.IsNullOrEmpty(current.Upstream)) {
var newBranch = $"{remote}/{current.Name} (new)";
branches.Add(newBranch);
combRemoteBranches.ItemsSource = branches;
combRemoteBranches.SelectedItem = newBranch;
} else if (current.Upstream.StartsWith($"refs/remotes/{remote}", StringComparison.Ordinal)) {
combRemoteBranches.ItemsSource = branches;
combRemoteBranches.SelectedItem = current.Upstream.Substring("refs/remotes/".Length);
} else {
var match = $"{remote}/{current.Name}";
foreach (var b in branches) {
if (b == match) {
combRemoteBranches.ItemsSource = branches;
combRemoteBranches.SelectedItem = b;
return;
}
}
var newBranch = $"{remote}/{current.Name} (new)";
branches.Add(newBranch);
combRemoteBranches.ItemsSource = branches;
combRemoteBranches.SelectedItem = newBranch;
}
}
}
}

65
SourceGit/UI/PushTag.xaml Normal file
View file

@ -0,0 +1,65 @@
<UserControl x:Class="SourceGit.UI.PushTag"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Push Tag To Remote"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Tag :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}" Margin="4,0"/>
<Label x:Name="tagName"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
<ComboBox Grid.Row="3" Grid.Column="1"
x:Name="combRemotes"
VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
<Label Content="{Binding Name}" Padding="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,73 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Push tag to remote dialog
/// </summary>
public partial class PushTag : UserControl {
private Git.Repository repo = null;
private Git.Tag tag = null;
/// <summary>
/// Constructor
/// </summary>
/// <param name="repo">Opened repo</param>
/// <param name="tag">Delete tag</param>
public PushTag(Git.Repository repo, Git.Tag tag) {
this.repo = repo;
this.tag = tag;
InitializeComponent();
tagName.Content = tag.Name;
combRemotes.ItemsSource = repo.Remotes();
combRemotes.SelectedIndex = 0;
}
/// <summary>
/// Display this dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="tag"></param>
public static void Show(Git.Repository repo, Git.Tag tag) {
PopupManager.Show(new PushTag(repo, tag));
}
/// <summary>
/// Start request.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
var remote = combRemotes.SelectedItem as Git.Remote;
if (remote == null) return;
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => Git.Tag.Push(repo, tag.Name, remote.Name));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

63
SourceGit/UI/Rebase.xaml Normal file
View file

@ -0,0 +1,63 @@
<UserControl x:Class="SourceGit.UI.Rebase"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Rebase Current Branch"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Rebase :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="branch"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="On :"/>
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
<Path x:Name="type" Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="desc"/>
</StackPanel>
<CheckBox x:Name="chkAutoStash"
Grid.Row="4" Grid.Column="1"
IsChecked="True"
Content="Stash &amp; reapply local changes"/>
<Grid Grid.Row="6" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,87 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Rebase current branch on selected commit/branch
/// </summary>
public partial class Rebase : UserControl {
private Git.Repository repo = null;
private string based = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
public Rebase(Git.Repository opened) {
repo = opened;
InitializeComponent();
}
/// <summary>
/// Rebase current branch on selected branch
/// </summary>
/// <param name="opened"></param>
/// <param name="branch"></param>
public static void Show(Git.Repository opened, Git.Branch branch) {
if (branch == null) return;
var current = opened.CurrentBranch();
if (current == null) return;
var dialog = new Rebase(opened);
dialog.based = branch.Head;
dialog.branch.Content = current.Name;
dialog.type.Data = dialog.FindResource("Icon.Branch") as Geometry;
dialog.desc.Content = branch.Name;
PopupManager.Show(dialog);
}
/// <summary>
/// Rebase current branch on selected commit.
/// </summary>
/// <param name="opened"></param>
/// <param name="commit"></param>
public static void Show(Git.Repository opened, Git.Commit commit) {
var current = opened.CurrentBranch();
if (current == null) return;
var dialog = new Rebase(opened);
dialog.based = commit.SHA;
dialog.branch.Content = current.Name;
dialog.type.Data = dialog.FindResource("Icon.Commit") as Geometry;
dialog.desc.Content = $"{commit.ShortSHA} {commit.Subject}";
PopupManager.Show(dialog);
}
/// <summary>
/// Start rebase.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
var autoStash = chkAutoStash.IsChecked == true;
await Task.Run(() => repo.Rebase(based, autoStash));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

77
SourceGit/UI/Remote.xaml Normal file
View file

@ -0,0 +1,77 @@
<UserControl x:Class="SourceGit.UI.Remote"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" x:Name="title" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Add Remote"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Name :"/>
<TextBox Grid.Row="2" Grid.Column="1"
x:Name="txtName"
VerticalContentAlignment="Center"
Height="24"
helpers:TextBoxHelper.Placeholder="Remote name">
<TextBox.Text>
<Binding Path="RemoteName" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:RemoteNameRule x:Name="nameValidator"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Repository URL :"/>
<TextBox Grid.Row="3" Grid.Column="1"
x:Name="txtUrl"
VerticalContentAlignment="Center"
Height="24"
helpers:TextBoxHelper.Placeholder="Remote git repository URL">
<TextBox.Text>
<Binding Path="RemoteUri" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:RemoteUriRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,94 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Create or edit remote dialog.
/// </summary>
public partial class Remote : UserControl {
private Git.Repository repo = null;
private Git.Remote remote = null;
public string RemoteName { get; set; }
public string RemoteUri { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="editing">Editing remote</param>
public Remote(Git.Repository opened, Git.Remote editing) {
repo = opened;
remote = editing;
if (remote != null) {
RemoteName = remote.Name;
RemoteUri = remote.URL;
}
InitializeComponent();
nameValidator.Repo = repo;
if (remote != null) {
title.Content = "Edit Remote";
} else {
title.Content = "Add New Remote";
}
}
/// <summary>
/// Display this dialog.
/// </summary>
/// <param name="opened"></param>
/// <param name="editing"></param>
public static void Show(Git.Repository opened, Git.Remote editing = null) {
PopupManager.Show(new Remote(opened, editing));
}
/// <summary>
/// Commit request.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtName)) return;
txtUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtUrl)) return;
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => {
if (remote != null) {
remote.Edit(repo, RemoteName, RemoteUri);
} else {
Git.Remote.Add(repo, RemoteName, RemoteUri);
}
});
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

View file

@ -0,0 +1,67 @@
<UserControl x:Class="SourceGit.UI.RenameBranch"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Rename Branch"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Branch :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="txtOldName"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="New Name :"/>
<TextBox Grid.Row="3" Grid.Column="1"
x:Name="txtNewName"
Height="24"
helpers:TextBoxHelper.Placeholder="Unique name for this branch">
<TextBox.Text>
<Binding Path="NewName" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:BranchNameRule x:Name="nameValidator"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,79 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Rename branch dialog.
/// </summary>
public partial class RenameBranch : UserControl {
private Git.Repository repo = null;
private Git.Branch branch = null;
/// <summary>
/// New branch name.
/// </summary>
public string NewName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository.</param>
/// <param name="target">Branch to rename.</param>
public RenameBranch(Git.Repository opened, Git.Branch target) {
repo = opened;
branch = target;
NewName = target.Name;
InitializeComponent();
nameValidator.Repo = opened;
txtOldName.Content = target.Name;
}
/// <summary>
/// Show this dialog
/// </summary>
/// <param name="opened"></param>
/// <param name="branch"></param>
public static void Show(Git.Repository opened, Git.Branch branch) {
PopupManager.Show(new RenameBranch(opened, branch));
}
/// <summary>
/// Rename
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
txtNewName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtNewName)) return;
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => branch.Rename(repo, NewName));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel merge.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

72
SourceGit/UI/Reset.xaml Normal file
View file

@ -0,0 +1,72 @@
<UserControl x:Class="SourceGit.UI.Reset"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Reset Current Branch To Revision"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Current Branch :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
<Label x:Name="branch"/>
</StackPanel>
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Move To :"/>
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}"/>
<Label x:Name="desc"/>
</StackPanel>
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Reset Mode :"/>
<ComboBox x:Name="combMode" Grid.Row="4" Grid.Column="1" VerticalAlignment="Center">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Path Width="12" Height="12" Margin="4,0,0,0" Fill="{Binding Color}" Style="{StaticResource Style.Icon}" Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
<Label Content="{Binding Name}" Padding="4,0"/>
<Label Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid Grid.Row="6" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,99 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Reset branch to revision dialog.
/// </summary>
public partial class Reset : UserControl {
private Git.Repository repo = null;
private string revision = null;
/// <summary>
/// Reset mode.
/// </summary>
public class Mode {
public Brush Color { get; set; }
public string Name { get; set; }
public string Desc { get; set; }
public string Arg { get; set; }
public Mode(Brush b, string n, string d, string a) {
Color = b;
Name = n;
Desc = d;
Arg = a;
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened"></param>
/// <param name="current"></param>
/// <param name="commit"></param>
public Reset(Git.Repository opened, Git.Branch current, Git.Commit commit) {
InitializeComponent();
repo = opened;
revision = commit.SHA;
branch.Content = current.Name;
desc.Content = $"{commit.ShortSHA} {commit.Subject}";
combMode.ItemsSource = new Mode[] {
new Mode(Brushes.Green, "Soft", "Keep all changes. Stage differences", "--soft"),
new Mode(Brushes.Yellow, "Mixed", "Keep all changes. Unstage differences", "--mixed"),
new Mode(Brushes.Red, "Hard", "Discard all changes", "--hard"),
};
combMode.SelectedIndex = 0;
}
/// <summary>
/// Show dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="commit"></param>
public static void Show(Git.Repository repo, Git.Commit commit) {
var current = repo.CurrentBranch();
if (current == null) return;
PopupManager.Show(new Reset(repo, current, commit));
}
/// <summary>
/// Start reset.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
var mode = combMode.SelectedItem as Mode;
if (mode == null) return;
PopupManager.Lock();
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
status.Visibility = Visibility.Visible;
await Task.Run(() => repo.Reset(revision, mode.Arg));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

53
SourceGit/UI/Revert.xaml Normal file
View file

@ -0,0 +1,53 @@
<UserControl x:Class="SourceGit.UI.Revert"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Revert Commit"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Commit :"/>
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<Path x:Name="icon" Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}" Margin="4,0"/>
<Label x:Name="txtDesc"/>
</StackPanel>
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkCommit" Content="Commit revert changes" IsChecked="True"/>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
<Grid Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SourceGit.UI {
/// <summary>
/// Confirm to revert selected commit.
/// </summary>
public partial class Revert : UserControl {
private Git.Repository repo = null;
private string sha = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened">Opened repository</param>
/// <param name="commit">Commit to be reverted</param>
public Revert(Git.Repository opened, Git.Commit commit) {
repo = opened;
sha = commit.SHA;
InitializeComponent();
txtDesc.Content = $"{commit.ShortSHA} {commit.Subject}";
}
/// <summary>
/// Open this dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="commit"></param>
public static void Show(Git.Repository repo, Git.Commit commit) {
PopupManager.Show(new Revert(repo, commit));
}
/// <summary>
/// Start revert.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
bool autoCommit = chkCommit.IsChecked == true;
PopupManager.Lock();
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
await Task.Run(() => repo.Revert(sha, autoCommit));
status.Visibility = Visibility.Collapsed;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
PopupManager.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

46
SourceGit/UI/Stash.xaml Normal file
View file

@ -0,0 +1,46 @@
<UserControl x:Class="SourceGit.UI.Stash"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
<RowDefinition Height="16"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Stash Local Changes"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Message :"/>
<TextBox Grid.Row="2" Grid.Column="1"
x:Name="txtName"
Height="24"
helpers:TextBoxHelper.Placeholder="Optional. Name of this stash"/>
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkIncludeUntracked" IsChecked="True" Content="Include untracked files"/>
<Grid Grid.Row="5" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
</Grid>
</UserControl>

View file

@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Git save stash panel.
/// </summary>
public partial class Stash : UserControl {
private Git.Repository repo = null;
private List<string> files = new List<string>();
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo">Opened repsitory</param>
public Stash(Git.Repository repo, List<string> files) {
this.repo = repo;
this.files = files;
InitializeComponent();
chkIncludeUntracked.IsEnabled = files.Count == 0;
}
/// <summary>
/// Open this dialog.
/// </summary>
/// <param name="repo">Opened repository</param>
/// <param name="files">Special files to stash</param>
public static void Show(Git.Repository repo, List<string> files) {
PopupManager.Show(new Stash(repo, files));
}
/// <summary>
/// Start saving stash.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Start(object sender, RoutedEventArgs e) {
bool includeUntracked = chkIncludeUntracked.IsChecked == true;
string message = txtName.Text;
Git.Stash.Push(repo, includeUntracked, message, files);
PopupManager.Close();
}
/// <summary>
/// Cancel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
PopupManager.Close();
}
}
}

134
SourceGit/UI/Stashes.xaml Normal file
View file

@ -0,0 +1,134 @@
<UserControl x:Class="SourceGit.UI.Stashes"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:git="clr-namespace:SourceGit.Git"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Unloaded="Cleanup">
<Grid Background="{StaticResource Brush.BG3}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left panel -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Stash list -->
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="STASHES" Foreground="{StaticResource Brush.FG2}" FontWeight="Bold"/>
<ListView
Grid.Row="1"
x:Name="stashList"
Background="{StaticResource Brush.BG2}" BorderThickness="0"
SelectedIndex="-1"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectionChanged="StashSelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource Style.ListViewItem.Borderless}">
<Setter Property="Padding" Value="0"/>
<EventSetter Event="ContextMenuOpening" Handler="StashContextMenuOpening"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type git:Stash}">
<Border BorderBrush="{StaticResource Brush.BG4}" BorderThickness="0,0,0,1" Padding="4">
<StackPanel Orientation="Vertical" Margin="2" MaxWidth="290">
<Grid TextBlock.Foreground="{StaticResource Brush.FG2}" TextBlock.FontSize="11">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding SHA}"/>
<TextBlock Grid.Column="1" Text="{Binding Author.Time}"/>
</Grid>
<TextBlock MaxWidth="280" Foreground="{StaticResource Brush.FG}" Text="{Binding Message}" TextAlignment="Left" Padding="0" Margin="0,8,2,0"/>
</StackPanel>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG3}"/>
<!-- Changed file in this stash -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="CHANGES" Foreground="{StaticResource Brush.FG2}" FontWeight="Bold"/>
<Label Grid.Column="1" Content="Untracked files not included" Foreground="{StaticResource Brush.FG2}" FontSize="10"/>
</Grid>
<ListView
x:Name="changeList"
Grid.Row="1"
Background="{StaticResource Brush.BG2}" BorderThickness="0"
SelectedIndex="-1"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
SelectionChanged="FileSelectionChanged">
<ListView.Resources>
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
</ListView.Resources>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource Style.ListViewItem.Borderless}">
<Setter Property="Padding" Value="0"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type git:Change}">
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Width="14" Height="14" Background="{Binding ., Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="0,0,4,0">
<TextBlock Text="{Binding ., Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="10"/>
</Border>
<TextBlock Grid.Column="1" Text="{Binding Path}" Foreground="{StaticResource Brush.FG}" VerticalAlignment="Center" FontSize="11"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG3}"/>
<!-- Right Panel -->
<local:DiffViewer Grid.Column="2" x:Name="diff"/>
</Grid>
</UserControl>

View file

@ -0,0 +1,118 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Stashes viewer.
/// </summary>
public partial class Stashes : UserControl {
private Git.Repository repo = null;
private string selectedStash = null;
/// <summary>
/// File tree node.
/// </summary>
public class Node {
public string FilePath { get; set; } = "";
public string OriginalPath { get; set; } = "";
public string Name { get; set; } = "";
public bool IsFile { get; set; } = false;
public bool IsNodeExpanded { get; set; } = true;
public Git.Change.Status Status { get; set; } = Git.Change.Status.None;
public List<Node> Children { get; set; } = new List<Node>();
}
/// <summary>
/// Constructor.
/// </summary>
public Stashes() {
InitializeComponent();
}
/// <summary>
/// Cleanup
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cleanup(object sender, RoutedEventArgs e) {
stashList.ItemsSource = null;
changeList.ItemsSource = null;
diff.Reset();
}
/// <summary>
/// Set data.
/// </summary>
/// <param name="opened"></param>
/// <param name="stashes"></param>
public void SetData(Git.Repository opened, List<Git.Stash> stashes) {
repo = opened;
selectedStash = null;
stashList.ItemsSource = stashes;
changeList.ItemsSource = null;
diff.Reset();
}
/// <summary>
/// Stash list selection changed event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void StashSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var stash = e.AddedItems[0] as Git.Stash;
if (stash == null) return;
selectedStash = stash.SHA;
diff.Reset();
changeList.ItemsSource = stash.GetChanges(repo);
}
/// <summary>
/// File selection changed in TreeView.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FileSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var change = e.AddedItems[0] as Git.Change;
if (change == null) return;
var data = repo.Diff($"{selectedStash}^", selectedStash, change.Path, change.OriginalPath);
diff.SetData(data, change.Path, change.OriginalPath);
}
/// <summary>
/// Stash context menu.
/// </summary>
/// <param name="sender"></param>
/// <param name="ev"></param>
private void StashContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var stash = (sender as ListViewItem).DataContext as Git.Stash;
if (stash == null) return;
var apply = new MenuItem();
apply.Header = "Apply";
apply.Click += (o, e) => stash.Apply(repo);
var pop = new MenuItem();
pop.Header = "Pop";
pop.Click += (o, e) => stash.Pop(repo);
var delete = new MenuItem();
delete.Header = "Drop";
delete.Click += (o, e) => stash.Drop(repo);
var menu = new ContextMenu();
menu.Items.Add(apply);
menu.Items.Add(pop);
menu.Items.Add(delete);
menu.IsOpen = true;
ev.Handled = true;
}
}
}

View file

@ -0,0 +1,379 @@
<UserControl x:Class="SourceGit.UI.WorkingCopy"
x:Name="me"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:source="clr-namespace:SourceGit"
xmlns:local="clr-namespace:SourceGit.UI"
xmlns:helpers="clr-namespace:SourceGit.Helpers"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:BoolToCollapsed x:Key="BoolToCollapsed"/>
<converters:InverseBoolToCollapsed x:Key="InverseBoolToCollapsed"/>
<converters:FileStatusToColor x:Key="UnstagedStatusConverter" OnlyWorkTree="True"/>
<converters:FileStatusToIcon x:Key="UnstagedStatusIconConverter" OnlyWorkTree="True"/>
<converters:FileStatusToColor x:Key="StagedStatusConverter"/>
<converters:FileStatusToIcon x:Key="StagedStatusIconConverter"/>
</UserControl.Resources>
<Grid Background="{StaticResource Brush.BG3}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Unstaged changes -->
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ToggleButton
Grid.Column="0"
x:Name="toggleUnstangedMode"
Margin="4,0,0,0"
ToolTip="SWITCH TO LIST/TREE VIEW"
Style="{StaticResource Style.ToggleButton.ListOrTree}"
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIUseListInUnstaged, Mode=TwoWay}"/>
<Label Grid.Column="1" Content="UNSTAGED" FontWeight="Bold" Foreground="{StaticResource Brush.FG2}"/>
<Button Grid.Column="3" Click="Stage" Margin="4,0" ToolTip="STAGE" Background="Transparent">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.StageSelected}" Fill="{StaticResource Brush.FG2}"/>
</Button>
<Button Grid.Column="4" Click="StageAll" Margin="4,0" ToolTip="STAGE ALL" Background="Transparent">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.StageAll}" Fill="{StaticResource Brush.FG2}"/>
</Button>
</Grid>
<TreeView
Grid.Row="1"
x:Name="unstagedTree"
Background="{StaticResource Brush.BG2}"
Visibility="{Binding ElementName=toggleUnstangedMode, Path=IsChecked, Converter={StaticResource InverseBoolToCollapsed}}"
FontFamily="Consolas"
PreviewMouseWheel="TreeMouseWheel"
helpers:TreeViewHelper.EnableMultiSelection="True"
helpers:TreeViewHelper.MultiSelectionChanged="UnstagedTreeMultiSelectionChanged">
<TreeView.Resources>
<RoutedUICommand x:Key="SelectWholeTreeCommand" Text="SelectWholeTree"/>
</TreeView.Resources>
<TreeView.InputBindings>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{StaticResource SelectWholeTreeCommand}"/>
</TreeView.InputBindings>
<TreeView.CommandBindings>
<CommandBinding Command="{StaticResource SelectWholeTreeCommand}" Executed="SelectWholeTree"/>
</TreeView.CommandBindings>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.MultiSelectionItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="UnstagedTreeContextMenuOpening"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Height="24">
<Border x:Name="status" Width="14" Height="14" Visibility="Collapsed" Background="{Binding Change, Converter={StaticResource UnstagedStatusConverter}}" CornerRadius="2" Margin="0,0,4,0">
<TextBlock Text="{Binding Change, Converter={StaticResource UnstagedStatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="10" RenderOptions.BitmapScalingMode="HighQuality"/>
</Border>
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontSize="11"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFile}" Value="True">
<Setter TargetName="status" Property="Visibility" Value="Visible"/>
<Setter TargetName="icon" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFile}" Value="False"/>
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<DataGrid
Grid.Row="1"
x:Name="unstagedList"
Visibility="{Binding ElementName=toggleUnstangedMode, Path=IsChecked, Converter={StaticResource BoolToCollapsed}}"
RowHeight="24"
SelectionChanged="UnstagedListSelectionChanged"
SelectionMode="Extended"
SelectionUnit="FullRow"
Background="{StaticResource Brush.BG2}">
<DataGrid.Resources>
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<RoutedUICommand x:Key="SelectWholeDataGridCommand" Text="SelectWholeDataGrid"/>
</DataGrid.Resources>
<DataGrid.InputBindings>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{StaticResource SelectWholeDataGridCommand}"/>
</DataGrid.InputBindings>
<DataGrid.CommandBindings>
<CommandBinding Command="{StaticResource SelectWholeDataGridCommand}" Executed="SelectWholeDataGrid"/>
</DataGrid.CommandBindings>
<DataGrid.Columns>
<DataGridTemplateColumn Width="22">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource UnstagedStatusConverter}}" CornerRadius="2" Margin="2,0,4,0">
<TextBlock Text="{Binding ., Converter={StaticResource UnstagedStatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="ContextMenuOpening" Handler="UnstagedListContextMenuOpening"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
<!-- Staged changes -->
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ToggleButton
Grid.Column="0"
x:Name="toggleStagedMode"
Margin="4,0,0,0"
ToolTip="SWITCH TO LIST/TREE VIEW"
Style="{StaticResource Style.ToggleButton.ListOrTree}"
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIUseListInStaged, Mode=TwoWay}"/>
<Label Grid.Column="1" Content="STAGED" FontWeight="Bold" Foreground="{StaticResource Brush.FG2}"/>
<Button Grid.Column="3" Click="Unstage" ToolTip="UNSTAGE" Margin="4,0" Background="Transparent">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.UnstageSelected}" Fill="{StaticResource Brush.FG2}"/>
</Button>
<Button Grid.Column="4" Click="UnstageAll" ToolTip="UNSTAGE ALL" Margin="4,0" Background="Transparent">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.UnstageAll}" Fill="{StaticResource Brush.FG2}"/>
</Button>
</Grid>
<TreeView
Grid.Row="1"
x:Name="stageTree"
Background="{StaticResource Brush.BG2}"
Visibility="{Binding ElementName=toggleStagedMode, Path=IsChecked, Converter={StaticResource InverseBoolToCollapsed}}"
FontFamily="Consolas"
PreviewMouseWheel="TreeMouseWheel"
helpers:TreeViewHelper.EnableMultiSelection="True"
helpers:TreeViewHelper.MultiSelectionChanged="StageTreeMultiSelectionChanged">
<TreeView.Resources>
<RoutedUICommand x:Key="SelectWholeTreeCommand" Text="SelectWholeTree"/>
</TreeView.Resources>
<TreeView.InputBindings>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{StaticResource SelectWholeTreeCommand}"/>
</TreeView.InputBindings>
<TreeView.CommandBindings>
<CommandBinding Command="{StaticResource SelectWholeTreeCommand}" Executed="SelectWholeTree"/>
</TreeView.CommandBindings>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.MultiSelectionItemContainerStyle}">
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
<EventSetter Event="ContextMenuOpening" Handler="StageTreeContextMenuOpening"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Height="24">
<Border x:Name="status" Width="14" Height="14" Visibility="Collapsed" Background="{Binding Change, Converter={StaticResource StagedStatusConverter}}" CornerRadius="2" Margin="0,0,4,0">
<TextBlock Text="{Binding Change, Converter={StaticResource StagedStatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="10" RenderOptions.BitmapScalingMode="HighQuality"/>
</Border>
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontSize="11"/>
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding IsFile}" Value="True">
<Setter TargetName="status" Property="Visibility" Value="Visible"/>
<Setter TargetName="icon" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFile}" Value="False"/>
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
</MultiDataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<DataGrid
Grid.Row="1"
x:Name="stageList"
Visibility="{Binding ElementName=toggleStagedMode, Path=IsChecked, Converter={StaticResource BoolToCollapsed}}"
RowHeight="24"
SelectionChanged="StagedListSelectionChanged"
SelectionMode="Extended"
SelectionUnit="FullRow"
Background="{StaticResource Brush.BG2}">
<DataGrid.Resources>
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<RoutedUICommand x:Key="SelectWholeDataGridCommand" Text="SelectWholeDataGrid"/>
</DataGrid.Resources>
<DataGrid.InputBindings>
<KeyBinding Key="A" Modifiers="Ctrl" Command="{StaticResource SelectWholeDataGridCommand}"/>
</DataGrid.InputBindings>
<DataGrid.CommandBindings>
<CommandBinding Command="{StaticResource SelectWholeDataGridCommand}" Executed="SelectWholeDataGrid"/>
</DataGrid.CommandBindings>
<DataGrid.Columns>
<DataGridTemplateColumn Width="22">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource StagedStatusConverter}}" CornerRadius="2" Margin="2,0,4,0">
<TextBlock Text="{Binding ., Converter={StaticResource StagedStatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="ContextMenuOpening" Handler="StagedListContextMenuOpening"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG4}"/>
<!-- Right -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="66"/>
<RowDefinition Height="36"/>
</Grid.RowDefinitions>
<!-- Diff -->
<local:DiffViewer Grid.Row="0" x:Name="diffViewer" Margin="8,8,8,4"/>
<!-- Merge -->
<Grid Grid.Row="0" x:Name="mergePanel" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Conflict}" Margin="0,0,0,16"/>
<Label Content="CONFLICTS DETECTED" FontSize="20" HorizontalAlignment="Center" FontWeight="DemiBold" Foreground="{StaticResource Brush.FG2}" Margin="0,0,0,24"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,4">
<Button Click="UseTheirs" Style="{StaticResource Style.Button.Bordered}" Content="USE THEIRS" Height="24"/>
<Button Click="UseMine" Style="{StaticResource Style.Button.Bordered}" Content="USE MINE" Height="24" Margin="8,0"/>
<Button Click="OpenMergeTool" Style="{StaticResource Style.Button.Bordered}" Content="OPEN MERGE" Height="24"/>
</StackPanel>
</StackPanel>
</Grid>
<!-- Commit Message -->
<TextBox
x:Name="txtCommitMsg"
Grid.Row="1"
Height="64"
Margin="8,0"
Padding="2"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
helpers:TextBoxHelper.Placeholder="Enter commit message"
helpers:TextBoxHelper.PlaceholderBaseline="Top"
TextChanged="CommitMessageChanged">
<TextBox.Text>
<Binding ElementName="me" Path="CommitMessage" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<helpers:CommitSubjectRequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!-- Commit Options -->
<Grid Grid.Row="2" Margin="8,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Width="16" Height="16" Margin="0,0,8,0" Click="OpenCommitMessageSelector" ToolTip="MESSAGE HISTORIES">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.List}"/>
</Button>
<CheckBox Grid.Column="1" x:Name="chkAmend" HorizontalAlignment="Left" Content="Amend" Checked="StartAmend"/>
<Button Grid.Column="3" Height="26" Click="Commit" Style="{StaticResource Style.Button.AccentBordered}" Content="COMMIT"/>
<Button Grid.Column="4" x:Name="btnCommitAndPush" Height="26" Click="CommitAndPush" Style="{StaticResource Style.Button.Bordered}" Content="COMMIT &amp; PUSH" Margin="8,0,0,0"/>
</Grid>
</Grid>
</Grid>
</UserControl>

File diff suppressed because it is too large Load diff