refactor(*): re-arrange project

This commit is contained in:
leo 2020-08-06 16:01:10 +08:00
parent 7558bbd4f2
commit 5ce9cffcee
140 changed files with 4980 additions and 5003 deletions

82
src/UI/About.xaml Normal file
View file

@ -0,0 +1,82 @@
<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"
Title="About"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
<!-- Enable WindowChrome Feature -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
</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="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- LOGO -->
<Path Width="20" Height="20" Margin="6,-1,2,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}"/>
<!-- Title -->
<Label Grid.Column="1" Content="ABOUT" FontWeight="Light"/>
<!-- Close Button -->
<Button Click="Quit" Width="32" Grid.Column="3" 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>

46
src/UI/About.xaml.cs Normal file
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).ProductVersion;
}
}
/// <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();
}
}
}

72
src/UI/AddSubmodule.xaml Normal file
View file

@ -0,0 +1,72 @@
<UserControl x:Class="SourceGit.UI.AddSubmodule"
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="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="Add Submodule"/>
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="URL :"/>
<TextBox x:Name="txtRepoUrl" Grid.Row="2" Grid.Column="1"
Height="24"
helpers:TextBoxHelper.Placeholder="Git Repository URL">
<TextBox.Text>
<Binding Path="RepoURL" 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 :"/>
<TextBox Grid.Row="3" Grid.Column="1"
x:Name="txtPath"
Height="24"
helpers:TextBoxHelper.Placeholder="Relative foler to store this module. Optional.">
<TextBox.Text>
<Binding Path="LocalPath" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
<Binding.ValidationRules>
<helpers:SubmodulePathRequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<CheckBox Grid.Row="4" Grid.Column="1"
x:Name="chkRecursive"
IsChecked="True"
Content="Fetch nested submodules"/>
<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="Sure" 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,74 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Dialog to add new submodule.
/// </summary>
public partial class AddSubmodule : UserControl {
private Git.Repository repo = null;
/// <summary>
/// Submodule's repository URL.
/// </summary>
public string RepoURL { get; set; }
/// <summary>
/// Submodule's relative path.
/// </summary>
public string LocalPath { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="opened"></param>
public AddSubmodule(Git.Repository opened) {
repo = opened;
InitializeComponent();
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="repo"></param>
public static void Show(Git.Repository repo) {
repo.GetPopupManager()?.Show(new AddSubmodule(repo));
}
#region EVENTS
private void SelectFolder(object sender, RoutedEventArgs e) {
var dialog = new System.Windows.Forms.FolderBrowserDialog();
dialog.Description = "Select Folder To Clone Repository";
dialog.SelectedPath = repo.Path;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
txtPath.Text = dialog.SelectedPath;
}
}
private async void Sure(object sender, RoutedEventArgs e) {
txtRepoUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtRepoUrl)) return;
txtPath.GetBindingExpression(TextBox.TextProperty).UpdateSource();
if (Validation.GetHasError(txtPath)) return;
var recursive = chkRecursive.IsChecked == true;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.AddSubmodule(RepoURL, LocalPath, recursive, msg => {
popup?.UpdateStatus(msg);
}));
popup?.Close(true);
}
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
#endregion
}
}

88
src/UI/Apply.xaml Normal file
View file

@ -0,0 +1,88 @@
<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"
xmlns:converters="clr-namespace:SourceGit.Converters"
mc:Ignorable="d"
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="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" IsEnabled="{Binding ElementName=chkIgnoreWhitespace, Path=IsChecked, Converter={StaticResource InverseBool}}">
<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>
<CheckBox Grid.Row="4" Grid.Column="1"
x:Name="chkIgnoreWhitespace"
IsChecked="True"
Content="Ignore whitespace 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>
</UserControl>

104
src/UI/Apply.xaml.cs Normal file
View file

@ -0,0 +1,104 @@
using Microsoft.Win32;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
var mode = combWhitespaceOptions.SelectedItem as WhitespaceOption;
var ignoreSpaceChanges = chkIgnoreWhitespace.IsChecked == true;
await Task.Run(() => repo.Apply(PatchFile, ignoreSpaceChanges, mode.Arg));
popup?.Close(true);
}
/// <summary>
/// Cancel options.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

208
src/UI/Blame.xaml Normal file
View file

@ -0,0 +1,208 @@
<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"
Title="Blame"
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"
SelectionChanged="ContentSelectionChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontFamily="Consolas">
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</RichTextBox.ContextMenu>
<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>

240
src/UI/Blame.xaml.cs Normal file
View file

@ -0,0 +1,240 @@
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.ContextMenuOpening += (sender, ev) => {
if (!content.Selection.IsEmpty) return;
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;
ev.Handled = 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;
}
}
/// <summary>
/// Auto scroll when selection changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ContentSelectionChanged(object sender, RoutedEventArgs e) {
var doc = sender as RichTextBox;
if (doc == null || doc.IsFocused == false) return;
if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) {
var p = Mouse.GetPosition(doc);
if (p.X <= 8) {
doc.LineLeft();
} else if (p.X >= doc.ActualWidth - 8) {
doc.LineRight();
}
if (p.Y <= 8) {
doc.LineUp();
} else if (p.Y >= doc.ActualHeight - 8) {
doc.LineDown();
}
}
}
}
}

45
src/UI/CherryPick.xaml Normal file
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>

54
src/UI/CherryPick.xaml.cs Normal file
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) {
repo.GetPopupManager()?.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);
repo.GetPopupManager()?.Close();
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

95
src/UI/Clone.xaml Normal file
View file

@ -0,0 +1,95 @@
<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="224">
<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="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>
<Label Grid.Row="5" Grid.Column="0" HorizontalAlignment="Right" Content="Remote Name :"/>
<TextBox Grid.Row="5" Grid.Column="1"
VerticalContentAlignment="Center"
Height="24"
helpers:TextBoxHelper.Placeholder="Remote name. Optional."
Text="{Binding RemoteName, ElementName=me, Mode=TwoWay}">
</TextBox>
<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>
</UserControl>

112
src/UI/Clone.xaml.cs Normal file
View file

@ -0,0 +1,112 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Clone dialog.
/// </summary>
public partial class Clone : UserControl {
private PopupManager popup = null;
/// <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>
/// Remote name.
/// </summary>
public string RemoteName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
public Clone(PopupManager mgr) {
ParentFolder = App.Preference.GitDefaultCloneDir;
popup = mgr;
InitializeComponent();
}
/// <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 async 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;
}
string rName;
if (string.IsNullOrWhiteSpace(RemoteName)){
rName = null;
} else {
rName = RemoteName;
}
popup.Lock();
var repo = await Task.Run(() => {
return Git.Repository.Clone(RemoteUri, ParentFolder, rName, repoName, popup.UpdateStatus);
});
if (repo == null) {
popup.Unlock();
} else {
popup.Close(true);
repo.Open();
}
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
popup.Close();
}
}
}

420
src/UI/CommitViewer.xaml Normal file
View file

@ -0,0 +1,420 @@
<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=".6"/>
<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=".6"/>
<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"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- PARENTS -->
<Label Grid.Row="1" Grid.Column="0" Content="PARENTS" HorizontalAlignment="Right" Opacity=".6"/>
<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=".6"/>
<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=".6"/>
<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=".6"/>
<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=".6"/>
<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=".6"/>
<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=".6"/>
<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=".6"/>
<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"/>
<EventSetter Event="MouseDoubleClick" Handler="ChangeListMouseDoubleClick"/>
</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}">
<Grid>
<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>
<StackPanel x:Name="maskRevision" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
<Path x:Name="iconPreviewRevision" Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}" Fill="{StaticResource Brush.FG2}"/>
<Label x:Name="txtPreviewRevision" Margin="0,16,0,0" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
<StackPanel x:Name="maskPreviewNotSupported" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}" Fill="{StaticResource Brush.FG2}"/>
<Label Margin="0,16,0,0" Content="BINARY FILE DETECTED" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</TabItem>
</TabControl>
</UserControl>

504
src/UI/CommitViewer.xaml.cs Normal file
View file

@ -0,0 +1,504 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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 Git.Commit.Object CommitObject { 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 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";
}
diffViewer.Diff(repo, new DiffViewer.Option() {
RevisionRange = new string[] { start, commit.SHA },
Path = node.FilePath,
OrgPath = node.OriginalPath
});
}
private 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";
}
diffViewer.Diff(repo, new DiffViewer.Option() {
RevisionRange = new string[] { start, commit.SHA },
Path = change.Path,
OrgPath = 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;
}
private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var row = sender as DataGridRow;
if (row == null) return;
var change = row.DataContext as Git.Change;
if (change == null) return;
var viewer = new FileHistories(repo, change.Path);
viewer.Show();
}
#endregion
#region FILES
private void SetRevisionFiles(List<Git.Commit.Object> files) {
List<Node> fileTreeSource = new List<Node>();
Dictionary<string, Node> folders = new Dictionary<string, Node>();
foreach (var obj in files) {
var sepIdx = obj.Path.IndexOf("/");
if (sepIdx == -1) {
Node node = new Node();
node.FilePath = obj.Path;
node.Name = obj.Path;
node.IsFile = true;
node.IsNodeExpanded = false;
node.CommitObject = obj;
fileTreeSource.Add(node);
} else {
Node lastFolder = null;
var start = 0;
while (sepIdx != -1) {
var folder = obj.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 = obj.Path.IndexOf('/', start);
}
Node node = new Node();
node.FilePath = obj.Path;
node.Name = obj.Path.Substring(start);
node.IsFile = true;
node.IsNodeExpanded = false;
node.CommitObject = obj;
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 = "";
maskPreviewNotSupported.Visibility = Visibility.Collapsed;
maskRevision.Visibility = Visibility.Collapsed;
var node = e.NewValue as Node;
if (node == null || !node.IsFile || node.CommitObject == null) return;
switch (node.CommitObject.Kind) {
case Git.Commit.Object.Type.Blob:
await Task.Run(() => {
var isBinary = false;
var data = commit.GetTextFileContent(repo, node.FilePath, out isBinary);
if (isBinary) {
Dispatcher.Invoke(() => maskPreviewNotSupported.Visibility = Visibility.Visible);
} else {
Dispatcher.Invoke(() => filePreview.Text = data);
}
});
break;
case Git.Commit.Object.Type.Tag:
maskRevision.Visibility = Visibility.Visible;
iconPreviewRevision.Data = FindResource("Icon.Tag") as Geometry;
txtPreviewRevision.Content = "TAG: " + node.CommitObject.SHA;
break;
case Git.Commit.Object.Type.Commit:
maskRevision.Visibility = Visibility.Visible;
iconPreviewRevision.Data = FindResource("Icon.Submodule") as Geometry;
txtPreviewRevision.Content = "SUBMODULE: " + node.CommitObject.SHA;
break;
default:
return;
}
}
#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.IsEnabled = node.CommitObject == null || node.CommitObject.Kind == Git.Commit.Object.Type.Blob;
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
}
}

71
src/UI/Configure.xaml Normal file
View file

@ -0,0 +1,71 @@
<UserControl x:Class="SourceGit.UI.Configure"
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="450" d:DesignWidth="500" Width="500" Height="314">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="8"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="18"/>
<RowDefinition Height="36"/>
<RowDefinition Height="8"/>
<RowDefinition Height="102"/>
<RowDefinition Height="18"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 仓库帐号 -->
<Label Grid.Row="0" Grid.ColumnSpan="2" Content="CREDENTIAL" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
<Label Grid.Row="2" Grid.Column="0" Content="User : " HorizontalAlignment="Right"/>
<TextBox
Grid.Row="2"
Grid.Column="1"
Height="24"
Text="{Binding ElementName=me, Path=UserName, Mode=TwoWay}"
helpers:TextBoxHelper.Placeholder="User name for this repository"/>
<Label Grid.Row="3" Grid.Column="0" Content="Email : " HorizontalAlignment="Right"/>
<TextBox
Grid.Row="3"
Grid.Column="1"
Height="24"
Text="{Binding ElementName=me, Path=UserEmail, Mode=TwoWay}"
helpers:TextBoxHelper.Placeholder="Email address"/>
<!-- 提交模板 -->
<Label Grid.Row="5" Grid.ColumnSpan="2" Content="COMMIT TEMPLATE" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
<Label Grid.Row="7" Grid.Column="0" Content="Template : " HorizontalAlignment="Right" VerticalAlignment="Top"/>
<TextBox
Grid.Row="7"
Grid.Column="1"
Height="100"
AcceptsReturn="True"
AcceptsTab="True"
Padding="2"
Text="{Binding ElementName=me, Path=CommitTemplate, Mode=TwoWay}"/>
<!-- 操作 -->
<Grid Grid.Row="9" Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Click="Save" Content="SAVE" Style="{StaticResource Style.Button.AccentBordered}"/>
<Button Grid.Column="3" Click="Close" Content="CLOSE" Style="{StaticResource Style.Button.Bordered}"/>
</Grid>
</Grid>
</UserControl>

70
src/UI/Configure.xaml.cs Normal file
View file

@ -0,0 +1,70 @@
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Repository configuration dialog
/// </summary>
public partial class Configure : UserControl {
private Git.Repository repo = null;
/// <summary>
/// User name for this repository.
/// </summary>
public string UserName { get; set; }
/// <summary>
/// User email for this repository.
/// </summary>
public string UserEmail { get; set; }
/// <summary>
/// Commit template for this repository.
/// </summary>
public string CommitTemplate { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="repo"></param>
public Configure(Git.Repository repo) {
this.repo = repo;
UserName = repo.GetConfig("user.name");
UserEmail = repo.GetConfig("user.email");
CommitTemplate = repo.CommitTemplate;
InitializeComponent();
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="repo"></param>
public static void Show(Git.Repository repo) {
repo.GetPopupManager()?.Show(new Configure(repo));
}
#region EVENTS
private void Save(object sender, RoutedEventArgs e) {
var oldUser = repo.GetConfig("user.name");
if (oldUser != UserName) repo.SetConfig("user.name", UserName);
var oldEmail = repo.GetConfig("user.email");
if (oldEmail != UserEmail) repo.SetConfig("user.email", UserEmail);
if (CommitTemplate != repo.CommitTemplate) {
repo.CommitTemplate = CommitTemplate;
Git.Preference.Save();
}
Close(sender, e);
}
private void Close(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
#endregion
}
}

80
src/UI/CreateBranch.xaml Normal file
View file

@ -0,0 +1,80 @@
<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>
</UserControl>

137
src/UI/CreateBranch.xaml.cs Normal file
View file

@ -0,0 +1,137 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
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);
repo.GetPopupManager()?.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;
repo.GetPopupManager()?.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}";
repo.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
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);
}
});
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

70
src/UI/CreateTag.xaml Normal file
View file

@ -0,0 +1,70 @@
<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"
Padding="2"
AcceptsReturn="True"
helpers:TextBoxHelper.Placeholder="Optional"
helpers:TextBoxHelper.PlaceholderBaseline="Top"/>
<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>

95
src/UI/CreateTag.xaml.cs Normal file
View file

@ -0,0 +1,95 @@
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;
repo.GetPopupManager()?.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}";
repo.GetPopupManager()?.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);
repo.GetPopupManager()?.Close();
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

528
src/UI/Dashboard.xaml Normal file
View file

@ -0,0 +1,528 @@
<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: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="100">
<Border Background="{StaticResource Brush.BG1}" Margin="0,2,0,0">
<Border.Effect>
<DropShadowEffect ShadowDepth="2" Direction="270" Opacity=".5" Color="Black"/>
</Border.Effect>
</Border>
<Grid Background="{StaticResource Brush.BG1}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Browser -->
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="6,0">
<Button Click="OpenExplorer" Margin="4,0,0,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="OpenConfigure" Margin="4,0,0,0" ToolTip="Configure This Repository">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Setting}"/>
<Label Content="Configure"/>
</StackPanel>
</Button>
</StackPanel>
<!-- Common Git Options -->
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="6,0">
<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="4,0">
<StackPanel Orientation="Horizontal">
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.SaveStash}"/>
<Label Content="Stash"/>
</StackPanel>
</Button>
<Button Click="OpenApply">
<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 Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
<Label Content="Search"/>
</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="24"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="24"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="24"/>
<RowDefinition Height="1"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="24"/>
<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}"
SelectionMode="Single">
<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,2,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"
Padding="0"
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,2,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"
Padding="0"
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,2,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>
<Rectangle Grid.Row="7" Height="1" Fill="{StaticResource Brush.BG3}"/>
<DataGrid
Grid.Row="8"
x:Name="tagList"
Visibility="{Binding ElementName=tagListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
Background="{StaticResource Brush.BG3}"
RowHeight="24"
Height="200"
LostFocus="TagLostFocus"
SelectionChanged="TagSelectionChanged"
ContextMenuOpening="TagContextMenuOpening"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Resources>
<Style x:Key="Style.DataGridText.TagName" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="26">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding Name}" ElementStyle="{StaticResource Style.DataGridText.TagName}"/>
<DataGridTemplateColumn Width="16">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton
Grid.Column="2"
IsChecked="{Binding IsFiltered, Mode=TwoWay}"
Checked="FilterChanged"
Unchecked="FilterChanged"
Style="{StaticResource Style.ToggleButton.Filter}"
ToolTip="FILTER"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- SUBMODULES -->
<ToggleButton
x:Name="submoduleListToggle"
Grid.Row="9"
Style="{StaticResource Style.ToggleButton.Expender}"
IsChecked="False">
<Grid Margin="4,0,2,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="16"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" x:Name="submoduleCount" Content="SUBMODULES" Style="{StaticResource Style.Label.GroupHeader}"/>
<Button Grid.Column="1" Click="OpenAddSubmodule" ToolTip="ADD SUBMODULE">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}"/>
</Button>
<Button Grid.Column="3" Click="UpdateSubmodule" ToolTip="UPDATE SUBMODULE">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
</Button>
</Grid>
</ToggleButton>
<DataGrid
Grid.Row="11"
x:Name="submoduleList"
Visibility="{Binding ElementName=submoduleListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
Background="{StaticResource Brush.BG3}"
RowHeight="24"
Height="100"
LostFocus="SubmoduleLostFocus"
ContextMenuOpening="SubmoduleContextMenuOpening"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Resources>
<Style x:Key="Style.DataGridText.SubmodulePath" TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
</Style>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="SubmoduleMouseDoubleClick"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width="26">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding}" ElementStyle="{StaticResource Style.DataGridText.SubmodulePath}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<!-- Splitter -->
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG3}"/>
<!-- Right -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Abort panel -->
<Grid x:Name="abortPanel" Grid.Row="0" Background="{StaticResource Brush.BG7}" 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 x:Name="popupManager" Grid.Row="1"/>
</Grid>
</UserControl>

1068
src/UI/Dashboard.xaml.cs Normal file

File diff suppressed because it is too large Load diff

43
src/UI/DeleteBranch.xaml Normal file
View file

@ -0,0 +1,43 @@
<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>
</UserControl>

View file

@ -0,0 +1,55 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.Show(new DeleteBranch(opened, branch));
}
/// <summary>
/// Delete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => branch.Delete(repo));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

42
src/UI/DeleteRemote.xaml Normal file
View file

@ -0,0 +1,42 @@
<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>
</UserControl>

View file

@ -0,0 +1,56 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.Show(new DeleteRemote(opened, remote));
}
/// <summary>
/// Delete
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => Git.Remote.Delete(repo, remote));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

45
src/UI/DeleteTag.xaml Normal file
View file

@ -0,0 +1,45 @@
<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>
</UserControl>

60
src/UI/DeleteTag.xaml.cs Normal file
View file

@ -0,0 +1,60 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
repo.GetPopupManager()?.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) {
var popup = repo.GetPopupManager();
popup?.Lock();
var push = chkWithRemote.IsChecked == true;
await Task.Run(() => Git.Tag.Delete(repo, tag.Name, push));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

203
src/UI/DiffViewer.xaml Normal file
View file

@ -0,0 +1,203 @@
<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="26"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
<Grid Margin="8,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" 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>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
<TextBlock x:Name="fileName" Margin="4,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
<Path x:Name="loading" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
</StackPanel>
<StackPanel Grid.Column="2" x:Name="diffNavigation" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="26" Click="Go2Next" ToolTip="Next Difference" Background="Transparent">
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/>
</Button>
<Button Click="Go2Prev" ToolTip="Previous Difference" Background="Transparent">
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/>
</Button>
</StackPanel>
</Grid>
</Border>
<Grid x:Name="textChange" 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"
Padding="2,0"
Margin="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"
SelectionChanged="OnViewerSelectionChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RichTextBox.Document>
<FlowDocument PageWidth="0"/>
</RichTextBox.Document>
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</RichTextBox.ContextMenu>
</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"
Padding="2,0"
Margin="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"
SelectionChanged="OnViewerSelectionChanged"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<RichTextBox.Document>
<FlowDocument PageWidth="0"/>
</RichTextBox.Document>
<RichTextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</RichTextBox.ContextMenu>
</RichTextBox>
</Grid>
</Grid>
<Border x:Name="sizeChange" Grid.Row="1" ClipToBounds="True" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<Label Content="BINARY DIFF" Margin="0,0,0,32" FontSize="18" FontWeight="UltraBold" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Center"/>
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Binary}" Fill="{StaticResource Brush.FG2}"/>
<Grid Margin="0,16,0,0" HorizontalAlignment="Center" TextElement.FontSize="18" TextElement.FontWeight="UltraBold">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="OLD :" Foreground="{StaticResource Brush.FG2}"/>
<Label Grid.Row="0" Grid.Column="1" x:Name="txtOldSize" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="0" Content="NEW :" Foreground="{StaticResource Brush.FG2}"/>
<Label Grid.Row="1" Grid.Column="1" x:Name="txtNewSize" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Right"/>
</Grid>
</StackPanel>
</Border>
<Border x:Name="noChange" Grid.Row="1" 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.Check}"/>
<Label Margin="0,8,0,0" Content="NO CHANGES OR ONLY EOL CHANGES" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
<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>

405
src/UI/DiffViewer.xaml.cs Normal file
View file

@ -0,0 +1,405 @@
using System;
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;
namespace SourceGit.UI {
/// <summary>
/// Viewer for git diff
/// </summary>
public partial class DiffViewer : UserControl {
private double minWidth = 0;
/// <summary>
/// Diff options.
/// </summary>
public class Option {
public string[] RevisionRange = new string[] { };
public string Path = "";
public string OrgPath = null;
public string ExtraArgs = "";
}
/// <summary>
/// Constructor
/// </summary>
public DiffViewer() {
InitializeComponent();
Reset();
}
/// <summary>
/// Reset data.
/// </summary>
public void Reset() {
mask.Visibility = Visibility.Visible;
}
/// <summary>
/// Diff with options.
/// </summary>
/// <param name="repo"></param>
/// <param name="opts"></param>
public void Diff(Git.Repository repo, Option opts) {
SetTitle(opts.Path, opts.OrgPath);
loading.Visibility = Visibility.Visible;
mask.Visibility = Visibility.Collapsed;
textChange.Visibility = Visibility.Collapsed;
sizeChange.Visibility = Visibility.Collapsed;
noChange.Visibility = Visibility.Collapsed;
Task.Run(() => {
var args = $"{opts.ExtraArgs} ";
if (opts.RevisionRange.Length > 0) args += $"{opts.RevisionRange[0]} ";
if (opts.RevisionRange.Length > 1) args += $"{opts.RevisionRange[1]} -- ";
if (!string.IsNullOrEmpty(opts.OrgPath)) args += $"\"{opts.OrgPath}\" ";
args += $"\"{opts.Path}\"";
var rs = Git.Diff.Run(repo, args);
if (rs.IsBinary) {
SetSizeChangeData(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath));
} else if (rs.Blocks.Count > 0) {
SetData(rs);
} else {
SetSame();
}
});
}
#region LAYOUT
/// <summary>
/// Show diff title
/// </summary>
/// <param name="file"></param>
/// <param name="orgFile"></param>
private void SetTitle(string file, string orgFile) {
fileName.Text = file;
if (!string.IsNullOrEmpty(orgFile) && orgFile != "/dev/null") {
orgFileNamePanel.Visibility = Visibility.Visible;
orgFileName.Text = orgFile;
} else {
orgFileNamePanel.Visibility = Visibility.Collapsed;
}
}
/// <summary>
/// Show size changes.
/// </summary>
/// <param name="bc"></param>
private void SetSizeChangeData(Git.Diff.BinaryChange bc) {
Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed;
sizeChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Collapsed;
txtNewSize.Content = $"{bc.Size} Bytes";
txtOldSize.Content = $"{bc.PreSize} Bytes";
});
}
/// <summary>
/// Show no changes or only EOL changes.
/// </summary>
private void SetSame() {
Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed;
noChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Collapsed;
});
}
/// <summary>
/// Show diff content.
/// </summary>
/// <param name="rs"></param>
private void SetData(Git.Diff.Result rs) {
Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed;
textChange.Visibility = Visibility.Visible;
diffNavigation.Visibility = Visibility.Visible;
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
leftLineNumber.Text = "";
rightLineNumber.Text = "";
leftText.Document.Blocks.Clear();
rightText.Document.Blocks.Clear();
foreach (var b in rs.Blocks) ShowBlock(b);
leftText.Document.PageWidth = minWidth + 16;
rightText.Document.PageWidth = minWidth + 16;
leftText.ScrollToHome();
});
}
/// <summary>
/// Make paragraph.
/// </summary>
/// <param name="b"></param>
private void ShowBlock(Git.Diff.Block b) {
var content = b.Builder.ToString();
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;
p.DataContext = b;
switch (b.Mode) {
case Git.Diff.LineMode.Normal:
break;
case Git.Diff.LineMode.Indicator:
p.Foreground = Brushes.Gray;
p.FontStyle = FontStyles.Italic;
break;
case Git.Diff.LineMode.Empty:
p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
break;
case Git.Diff.LineMode.Added:
p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
break;
case Git.Diff.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;
switch (b.Side) {
case Git.Diff.Side.Left:
leftText.Document.Blocks.Add(p);
for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) leftLineNumber.AppendText($"{i + b.LeftStart}\n");
else leftLineNumber.AppendText("\n");
}
break;
case Git.Diff.Side.Right:
rightText.Document.Blocks.Add(p);
for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) rightLineNumber.AppendText($"{i + b.RightStart}\n");
else rightLineNumber.AppendText("\n");
}
break;
default:
leftText.Document.Blocks.Add(p);
var cp = new Paragraph(new Run(content));
cp.Margin = new Thickness(0);
cp.Padding = new Thickness();
cp.LineHeight = 1;
cp.Background = p.Background;
cp.Foreground = p.Foreground;
cp.FontStyle = p.FontStyle;
cp.DataContext = b;
rightText.Document.Blocks.Add(cp);
for (int i = 0; i < b.Count; i++) {
if (b.Mode != Git.Diff.LineMode.Indicator) {
leftLineNumber.AppendText($"{i + b.LeftStart}\n");
rightLineNumber.AppendText($"{i + b.RightStart}\n");
} else {
leftLineNumber.AppendText("\n");
rightLineNumber.AppendText("\n");
}
}
break;
}
}
#endregion
#region EVENTS
/// <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(0, -e.VerticalOffset, 0, 0);
rightLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 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;
}
/// <summary>
/// Fix document size for left side.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LeftSizeChanged(object sender, SizeChangedEventArgs e) {
if (leftText.Document.PageWidth < leftText.ActualWidth) {
leftText.Document.PageWidth = leftText.ActualWidth;
}
}
/// <summary>
/// Fix document size for right side.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RightSizeChanged(object sender, SizeChangedEventArgs e) {
if (rightText.Document.PageWidth < rightText.ActualWidth) {
rightText.Document.PageWidth = rightText.ActualWidth;
}
}
/// <summary>
/// Auto scroll when selection changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerSelectionChanged(object sender, RoutedEventArgs e) {
var doc = sender as RichTextBox;
if (doc == null || doc.IsFocused == false) return;
if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) {
var p = Mouse.GetPosition(doc);
if (p.X <= 8) {
doc.LineLeft();
} else if (p.X >= doc.ActualWidth - 8) {
doc.LineRight();
}
if (p.Y <= 8) {
doc.LineUp();
} else if (p.Y >= doc.ActualHeight - 8) {
doc.LineDown();
}
}
}
/// <summary>
/// Go to next difference.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Go2Next(object sender, RoutedEventArgs e) {
Paragraph next = null;
double minTop = 0;
foreach (var p in leftText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top > 17 && block.IsLeftDelete) {
next = p as Paragraph;
minTop = rect.Top;
break;
}
}
foreach (var p in rightText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top > 17 && block.IsRightAdded) {
if (next == null || minTop > rect.Top) {
next = p as Paragraph;
minTop = rect.Top;
}
break;
}
}
if (next != null) {
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16);
}
}
/// <summary>
/// Go to previous difference.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Go2Prev(object sender, RoutedEventArgs e) {
Paragraph next = null;
double maxTop = 0;
var p = leftText.Document.Blocks.LastBlock as Paragraph;
do {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && block.IsLeftDelete) {
next = p;
maxTop = rect.Top;
break;
}
p = p.PreviousBlock as Paragraph;
} while (p != null);
p = rightText.Document.Blocks.LastBlock as Paragraph;
do {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && block.IsRightAdded) {
if (next == null || maxTop < rect.Top) {
next = p;
maxTop = rect.Top;
}
break;
}
p = p.PreviousBlock as Paragraph;
} while (p != null);
if (next != null) {
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16);
}
}
#endregion
}
}

47
src/UI/Discard.xaml Normal file
View file

@ -0,0 +1,47 @@
<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>
</UserControl>

67
src/UI/Discard.xaml.cs Normal file
View file

@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
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) {
opened.GetPopupManager()?.Show(new Discard(opened, targets));
}
/// <summary>
/// Start to discard changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.Discard(changes));
popup?.Close(true);
}
/// <summary>
/// Cancel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

66
src/UI/Fetch.xaml Normal file
View file

@ -0,0 +1,66 @@
<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>
</UserControl>

76
src/UI/Fetch.xaml.cs Normal file
View file

@ -0,0 +1,76 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
repo.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
if (chkFetchAll.IsChecked == true) {
await Task.Run(() => repo.Fetch(null, prune, msg => popup?.UpdateStatus(msg)));
} else {
var remote = combRemotes.SelectedItem as Git.Remote;
await Task.Run(() => repo.Fetch(remote, prune, msg => popup?.UpdateStatus(msg)));
}
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

170
src/UI/FileHistories.xaml Normal file
View file

@ -0,0 +1,170 @@
<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"
Title="File Histories"
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,120 @@
using System;
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 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";
diff.Diff(repo, new DiffViewer.Option() {
RevisionRange = new string[] { start, commit.SHA },
Path = file
});
}
/// <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,42 @@
<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>
</UserControl>

View file

@ -0,0 +1,76 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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:
repo.GetPopupManager()?.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) {
repo.GetPopupManager()?.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) {
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.FinishGitFlowBranch(branch));
popup?.Close(true);
}
/// <summary>
/// Cancel finish
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

60
src/UI/GitFlowSetup.xaml Normal file
View file

@ -0,0 +1,60 @@
<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>
</UserControl>

124
src/UI/GitFlowSetup.xaml.cs Normal file
View file

@ -0,0 +1,124 @@
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
repo.GetPopupManager()?.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) {
var popup = repo.GetPopupManager();
popup?.Lock();
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));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.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,49 @@
<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>
</UserControl>

View file

@ -0,0 +1,92 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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:
repo.GetPopupManager()?.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) {
repo.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.StartGitFlowBranch(type, SubName));
popup?.Close(true);
}
/// <summary>
/// Cancel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

185
src/UI/Histories.xaml Normal file
View file

@ -0,0 +1,185 @@
<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" Background="{StaticResource Brush.BG1}">
<!-- 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"/>
</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="Transparent"/>
<!-- Detail for selected commit -->
<Grid x:Name="commitDetailPanel">
<!-- 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>

653
src/UI/Histories.xaml.cs Normal file
View file

@ -0,0 +1,653 @@
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();
mask4MultiSelection.Visibility = Visibility.Visible;
txtTotalSelected.Content = "SELECT COMMIT TO VIEW DETAIL";
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, StringComparison.OrdinalIgnoreCase) >= 0 ||
(commit.Author != null && commit.Author.Name.Equals(search, StringComparison.OrdinalIgnoreCase)) ||
(commit.Committer != null && commit.Committer.Name.Equals(search, StringComparison.OrdinalIgnoreCase)) ||
commit.Message.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 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
}
}

45
src/UI/Init.xaml Normal file
View file

@ -0,0 +1,45 @@
<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>
</UserControl>

58
src/UI/Init.xaml.cs Normal file
View file

@ -0,0 +1,58 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// `git init` confirm panel.
/// </summary>
public partial class Init : UserControl {
private PopupManager popup = null;
private string workingDir = null;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="mgr"></param>
/// <param name="path"></param>
public Init(PopupManager mgr, string path) {
popup = mgr;
workingDir = path;
InitializeComponent();
txtPath.Content = path;
}
/// <summary>
/// Do `git init`
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Sure(object sender, RoutedEventArgs e) {
popup.Lock();
await Task.Run(() => {
var errs = Git.Repository.RunCommand(workingDir, "init -q", null);
if (errs != null) {
App.RaiseError(errs);
} else {
App.Preference.AddRepository(workingDir, "");
}
});
popup.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) {
popup.Close();
}
}
}

View file

@ -0,0 +1,257 @@
<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"
Title="Interactive Rebase"
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">
<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 - 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,303 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
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 temp = Path.GetTempFileName();
var stream = new FileStream(temp, 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($"p {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Reword:
writer.WriteLine($"r {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Squash:
writer.WriteLine($"s {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Fixup:
writer.WriteLine($"f {item.Commit.ShortSHA} {item.Subject}");
break;
case InteractiveRebaseMode.Drop:
writer.WriteLine($"d {item.Commit.ShortSHA} {item.Subject}");
break;
}
}
writer.Flush();
stream.Flush();
writer.Close();
stream.Close();
repo.SetWatcherEnabled(false);
var editor = Process.GetCurrentProcess().MainModule.FileName;
var errs = repo.RunCommand($"-c sequence.editor=\"\\\"{editor}\\\" --interactive-rebase \\\"{temp}\\\"\" rebase -i {from}^", null);
repo.AssertCommand(errs);
Close();
}
private void Cancel(object sender, RoutedEventArgs e) {
Close();
}
}
}

294
src/UI/Launcher.xaml Normal file
View file

@ -0,0 +1,294 @@
<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>
<!-- Window Content -->
<Grid Background="{StaticResource Brush.BG6}">
<Grid.RowDefinitions>
<RowDefinition Height="32"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="openedTabsColumn" Width="*"/>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Tabs -->
<ScrollViewer x:Name="openedTabsScroller" Grid.Column="0" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
<TabControl x:Name="openedTabs" Padding="0" ItemsSource="{Binding ElementName=me, Path=Tabs}" SizeChanged="OpenedTabsSizeChanged">
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid KeyboardNavigation.TabNavigation="Local" Height="32">
<StackPanel Orientation="Horizontal"
x:Name="HeaderPanel"
Grid.Row="0"
Margin="6,4,0,0"
IsItemsHost="True"
KeyboardNavigation.TabIndex="1"
Background="Transparent" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Style>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="AllowDrop" Value="True"/>
<Setter Property="IsSelected" Value="{Binding IsActive, Mode=TwoWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="Container" Opacity=".7" Height="28" WindowChrome.IsHitTestVisibleInChrome="True">
<Border x:Name="BG" Background="Transparent" BorderThickness="0" BorderBrush="{StaticResource Brush.BG3}" CornerRadius="4,4,0,0">
<Border.Effect>
<DropShadowEffect ShadowDepth="2" Direction="30" Color="Black" Opacity=".3"/>
</Border.Effect>
</Border>
<Path
x:Name="CornerLeft"
Width="4"
Height="4"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="-4,0,0,0"
Data="M 0,4 L 4,4 4,0 C 4,0 4,4 0,4 Z"
Fill="Transparent"/>
<Path
x:Name="CornerRight"
Width="4"
Height="4"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,0,-4,0"
Data="M 0,0 L 0,4 4,4 C 4,4 0,4 0,0 Z"
Fill="Transparent"/>
<ContentPresenter
x:Name="ContentSite"
VerticalAlignment="Center" HorizontalAlignment="Center"
TextElement.Foreground="{DynamicResource Brush.FG}"
TextElement.FontWeight="Bold"
ContentSource="Header"
Margin="4,0"
RecognizesAccessKey="True" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.BG1}"/>
<Setter TargetName="BG" Property="BorderThickness" Value="1,1,1,0"/>
<Setter TargetName="Container" Property="Opacity" Value="1"/>
<Setter TargetName="CornerLeft" Property="Fill" Value="{StaticResource Brush.BG1}"/>
<Setter TargetName="CornerRight" Property="Fill" Value="{StaticResource Brush.BG1}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="False"/>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.BG5}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<EventSetter Event="MouseMove" Handler="TabsMouseMove"/>
<EventSetter Event="Drop" Handler="TabsDrop"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="6,0">
<Path Grid.Column="0" Width="14" Height="14" x:Name="Icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}"/>
<TextBlock Grid.Column="1" Text="{Binding Title}" Foreground="{StaticResource Brush.FG}" Margin="8,0,0,0" FontWeight="Bold">
<TextBlock.ToolTip>
<ToolTip Content="{Binding Tooltip}" FontWeight="Normal"/>
</TextBlock.ToolTip>
</TextBlock>
<Button x:Name="Closer" Margin="8,0,0,0" Grid.Column="2" Click="CloseRepo">
<Button.ToolTip>
<ToolTip Content="CLOSE" FontWeight="Normal"/>
</Button.ToolTip>
<Path Width="8" Height="8" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
</Button>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Repo}" Value="{x:Null}">
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Git}"/>
<Setter TargetName="Icon" Property="Fill" Value="#FFF05133"/>
<Setter TargetName="Closer" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</ScrollViewer>
<!-- Tab scroller -->
<StackPanel x:Name="openedTabsOpts" Grid.Column="1" VerticalAlignment="Bottom" Orientation="Horizontal" Height="28" Margin="4,0,0,0" WindowChrome.IsHitTestVisibleInChrome="True">
<Button Click="ScrollToLeft">
<Path Width="10" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.ScrollLeft}"/>
</Button>
<Button Click="ScrollToRight">
<Path Width="10" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.ScrollRight}"/>
</Button>
</StackPanel>
<!-- Window Command -->
<StackPanel
Grid.Column="2"
Margin="32,0,0,0"
Orientation="Horizontal"
HorizontalAlignment="Right"
Height="32"
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>
<!-- Pages -->
<ItemsControl Grid.Row="1" ItemsSource="{Binding ElementName=me, Path=Tabs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<BooleanToVisibilityConverter x:Key="Bool2Visible"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding Page}" Visibility="{Binding IsActive, Converter={StaticResource Bool2Visible}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Alerts -->
<ScrollViewer
Grid.Row="1"
Margin="0,32,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
Panel.ZIndex="100">
<ItemsControl Margin="4" Width="300" Height="Auto" ItemsSource="{Binding Errors, 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="ERROR" FontWeight="DemiBold"/>
<TextBlock Grid.Row="1" Margin="6,8" Text="{Binding}" Foreground="{StaticResource Brush.FG}" TextWrapping="Wrap"/>
<Button
Grid.Row="2"
Margin="4,0"
Click="RemoveError"
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>

214
src/UI/Launcher.xaml.cs Normal file
View file

@ -0,0 +1,214 @@
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>
/// Tab data.
/// </summary>
public class Tab {
public string Title { get; set; }
public string Tooltip { get; set; }
public bool IsActive { get; set; }
public Git.Repository Repo { get; set; }
public object Page { get; set; }
}
/// <summary>
/// Alerts.
/// </summary>
public ObservableCollection<string> Errors { get; set; } = new ObservableCollection<string>();
/// <summary>
/// Opened tabs.
/// </summary>
public ObservableCollection<Tab> Tabs { get; set; } = new ObservableCollection<Tab>();
/// <summary>
/// Constructor
/// </summary>
public Launcher() {
Git.Repository.OnOpen = repo => {
Dispatcher.Invoke(() => {
var page = new Dashboard(repo);
var tab = new Tab() {
Title = repo.Parent == null ? repo.Name : $"{repo.Parent.Name} : {repo.Name}",
Tooltip = repo.Path,
Repo = repo,
Page = page,
};
repo.SetPopupManager(page.popupManager);
Tabs.Add(tab);
openedTabs.SelectedItem = tab;
});
};
Tabs.Add(new Tab() {
Title = "SOURCE GIT",
Tooltip = "Welcome Page",
Page = new Manager(),
});
InitializeComponent();
openedTabs.SelectedItem = Tabs[0];
}
#region LAYOUT_CONTENT
/// <summary>
/// Close repository tab.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CloseRepo(object sender, RoutedEventArgs e) {
var tab = (sender as Button).DataContext as Tab;
if (tab == null || tab.Repo == null) return;
Tabs.Remove(tab);
tab.Page = null;
tab.Repo.RemovePopup();
tab.Repo.Close();
tab.Repo = null;
}
/// <summary>
/// Open preference dialog.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ShowPreference(object sender, RoutedEventArgs e) {
var dialog = new Preference();
dialog.Owner = this;
dialog.ShowDialog();
}
/// <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>
/// Remove an alert.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RemoveError(object sender, RoutedEventArgs e) {
var alert = (sender as Button).DataContext as string;
Errors.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();
}
#endregion
#region DRAG_DROP
private void TabsMouseMove(object sender, MouseEventArgs e) {
var tab = e.Source as TabItem;
if (tab == null || (tab.DataContext as Tab).Repo == null) return;
if (Mouse.LeftButton == MouseButtonState.Pressed) {
DragDrop.DoDragDrop(tab, tab, DragDropEffects.All);
e.Handled = true;
}
}
private void TabsDrop(object sender, DragEventArgs e) {
var tabItemSrc = e.Data.GetData(typeof(TabItem)) as TabItem;
var tabItemDst = e.Source as TabItem;
if (tabItemSrc.Equals(tabItemDst)) return;
var tabSrc = tabItemSrc.DataContext as Tab;
var tabDst = tabItemDst.DataContext as Tab;
if (tabDst.Repo == null) {
Tabs.Remove(tabSrc);
Tabs.Insert(1, tabSrc);
} else {
int dstIdx = Tabs.IndexOf(tabDst);
Tabs.Remove(tabSrc);
Tabs.Insert(dstIdx, tabSrc);
}
}
#endregion
#region TAB_SCROLL
private void OpenedTabsSizeChanged(object sender, SizeChangedEventArgs e) {
if (openedTabs.ActualWidth > openedTabsColumn.ActualWidth) {
openedTabsOpts.Visibility = Visibility.Visible;
} else {
openedTabsOpts.Visibility = Visibility.Collapsed;
}
}
private void ScrollToLeft(object sender, RoutedEventArgs e) {
openedTabsScroller.LineLeft();
}
private void ScrollToRight(object sender, RoutedEventArgs e) {
openedTabsScroller.LineRight();
}
#endregion
}
/// <summary>
/// Extension methods for repository.
/// </summary>
public static class RepositoryTabBindings {
/// <summary>
/// Bring up tab of repository if it was opened before.
/// </summary>
/// <param name="repo"></param>
/// <returns></returns>
public static bool BringUpTab(this Git.Repository repo) {
var main = App.Current.MainWindow as Launcher;
for (int i = 1; i < main.Tabs.Count; i++) {
var opened = main.Tabs[i];
if (opened.Repo.Path == repo.Path) {
main.openedTabs.SelectedItem = opened;
return true;
}
}
return false;
}
}
}

201
src/UI/Manager.xaml Normal file
View file

@ -0,0 +1,201 @@
<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>
<!-- Main Body -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="360"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left panel -->
<Grid Grid.Column="0" Background="{StaticResource Brush.BG1}">
<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}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
GotFocus="RecentsGotFocus"
SelectionChanged="RecentsSelectionChanged"
MouseDoubleClick="RecentsMouseDoubleClick">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource Style.ListViewItem.Borderless}">
<EventSetter Event="ContextMenuOpening" Handler="RecentsContextMenuOpening"/>
</Style>
</ListView.ItemContainerStyle>
<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}"/>
<TextBlock Margin="4,0" Text="{Binding Name}" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
<TextBlock FontSize="10" Text="{Binding Path}" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Repositories' tree -->
<Grid Grid.Row="2" Margin="8,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="REPOSITORIES" Style="{StaticResource Style.Label.GroupHeader}"/>
<Button Grid.Column="1" Click="CloneRepo" Style="{StaticResource Style.Button}" ToolTip="Clone Remote Repository">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Pull}" Opacity=".8"/>
</Button>
<Button Grid.Column="2" Click="OpenOrAddRepo" Style="{StaticResource Style.Button}" Margin="8,0,2,0" ToolTip="Open Local Repository">
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder.Open}" Opacity=".8"/>
</Button>
</Grid>
<TreeView
x:Name="repositories"
Grid.Row="3"
Margin="0,4"
Padding="0"
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 Height="26">
<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}"/>
<TextBlock x:Name="name" Grid.Column="1" Margin="4,0,0,0" Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" VerticalAlignment="Center"/>
<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="{StaticResource Brush.BG3}"/>
<!-- 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 -->
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label x:Name="repoName" FontSize="20" FontWeight="Bold"/>
<Label x:Name="repoPath" FontSize="20" FontWeight="Light" Opacity="0.5" VerticalAlignment="Center"/>
</StackPanel>
<!-- 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.Git}"/>
<Label Margin="0,32,0,0" Content="WELCOME TO SOURCE GIT :-)" FontSize="24" FontWeight="UltraBold" HorizontalAlignment="Center"/>
</StackPanel>
</Border>
</Grid>
</Grid>
<!-- Popup -->
<local:PopupManager x:Name="popupManager"/>
</Grid>
</UserControl>

523
src/UI/Manager.xaml.cs Normal file
View file

@ -0,0 +1,523 @@
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() {
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()) popupManager.Show(new Clone(popupManager));
}
#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;
}
}
private void RecentsContextMenuOpening(object sender, ContextMenuEventArgs e) {
var repo = (sender as ListViewItem).DataContext as Git.Repository;
if (repo == null) return;
var open = new MenuItem();
open.Header = "Open";
open.Click += (o, ev) => {
CheckAndOpenRepo(repo.Path);
ev.Handled = true;
};
var explore = new MenuItem();
explore.Header = "Open Container Folder";
explore.Click += (o, ev) => {
Process.Start(repo.Path);
ev.Handled = true;
};
var delete = new MenuItem();
delete.Header = "Delete";
delete.Click += (o, ev) => {
App.Preference.RemoveRepository(repo.Path);
UpdateRecentOpened();
UpdateTree();
HideBrief();
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(open);
menu.Items.Add(explore);
menu.Items.Add(delete);
menu.IsOpen = true;
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);
HideBrief();
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)) {
popupManager.Show(new Init(popupManager, 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;
}
#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)) {
popupManager.Show(new Init(popupManager, path));
return;
}
App.RaiseError($"Path[{path}] not exists!");
return;
}
var repo = App.Preference.AddRepository(path, "");
if (!repo.BringUpTab()) 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
}
}

63
src/UI/Merge.xaml Normal file
View file

@ -0,0 +1,63 @@
<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>
</UserControl>

104
src/UI/Merge.xaml.cs Normal file
View file

@ -0,0 +1,104 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.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);
var popup = opened.GetPopupManager();
popup?.Show(merge);
popup?.Lock();
Task.Run(() => {
opened.Merge(source, "");
merge.Dispatcher.Invoke(() => {
popup?.Close(true);
});
});
}
/// <summary>
/// Start merge
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
var popup = repo.GetPopupManager();
popup?.Lock();
var branch = sourceBranch.Content as string;
var opt = combOptions.SelectedItem as Option;
await Task.Run(() => repo.Merge(branch, opt.Arg));
popup?.Close(true);
}
/// <summary>
/// Cancel merge.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

34
src/UI/PopupManager.xaml Normal file
View file

@ -0,0 +1,34 @@
<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"/>
<Border 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>
</Border>
</Grid>
</Grid>
</UserControl>

153
src/UI/PopupManager.xaml.cs Normal file
View file

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SourceGit.UI {
/// <summary>
/// Common popup manager.
/// </summary>
public partial class PopupManager : UserControl {
private bool locked = false;
/// <summary>
/// Constructor.
/// </summary>
public PopupManager() {
InitializeComponent();
}
/// <summary>
/// Show content as popup.
/// </summary>
/// <param name="elem"></param>
public void Show(UIElement elem) {
if (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);
statusMsg.Content = "";
popupContent.Child = elem;
popupContent.Margin = gone;
Visibility = Visibility.Visible;
popupContent.BeginAnimation(MarginProperty, anim);
}
/// <summary>
/// Is current locked.
/// </summary>
/// <returns></returns>
public bool IsLocked() {
return locked;
}
/// <summary>
/// Lock
/// </summary>
public void Lock() {
locked = true;
status.Visibility = Visibility.Visible;
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
anim.RepeatBehavior = RepeatBehavior.Forever;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
}
/// <summary>
/// Unlock
/// </summary>
public void Unlock() {
locked = false;
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
status.Visibility = Visibility.Collapsed;
}
/// <summary>
/// Update status description
/// </summary>
/// <param name="desc"></param>
public void UpdateStatus(string desc) {
Dispatcher.Invoke(() => {
statusMsg.Content = desc;
});
}
/// <summary>
/// Close current popup.
/// </summary>
/// <param name="unlockFirst"></param>
public void Close(bool unlockFirst = false) {
if (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)popupContent.Child.GetValue(HeightProperty) - 16, 0, 0);
anim.Completed += (obj, ev) => {
Visibility = Visibility.Collapsed;
popupContent.Child = null;
};
popupContent.BeginAnimation(MarginProperty, anim);
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
status.Visibility = Visibility.Collapsed;
}
/// <summary>
/// Close by click blank area.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Close(object sender, RoutedEventArgs e) {
Close();
}
}
/// <summary>
/// Extension methods for Git.Repository
/// </summary>
public static class RepositoryPopupBindings {
private static Dictionary<string, PopupManager> popups = new Dictionary<string, PopupManager>();
/// <summary>
/// Set popup manager for given repo.
/// </summary>
/// <param name="repo"></param>
/// <param name="popup"></param>
public static void SetPopupManager(this Git.Repository repo, PopupManager popup) {
if (popups.ContainsKey(repo.Path)) popups[repo.Path] = popup;
else popups.Add(repo.Path, popup);
}
/// <summary>
/// Remove popup manager by given repo.
/// </summary>
/// <param name="repo"></param>
public static void RemovePopup(this Git.Repository repo) {
if (popups.ContainsKey(repo.Path)) {
popups[repo.Path].Unlock();
popups.Remove(repo.Path);
}
}
/// <summary>
///
/// </summary>
/// <param name="repo"></param>
/// <returns></returns>
public static PopupManager GetPopupManager(this Git.Repository repo) {
if (popups.ContainsKey(repo.Path)) return popups[repo.Path];
return null;
}
}
}

189
src/UI/Preference.xaml Normal file
View file

@ -0,0 +1,189 @@
<Window 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="520" Width="500"
Title="Preference"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
<!-- Enable WindowChrome Feature -->
<WindowChrome.WindowChrome>
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
</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="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- LOGO -->
<Path Width="20" Height="20" Margin="6,-1,2,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}" Fill="#FFF05133"/>
<!-- Title -->
<Label Grid.Column="1" Content="Preference" FontWeight="Light"/>
<!-- Close Button -->
<Button Click="Close" Width="32" Grid.Column="3" 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>
<!-- Content -->
<Grid Grid.Row="1" Margin="16,8">
<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="28"/>
<RowDefinition Height="18"/>
<RowDefinition Height="36"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
<RowDefinition Height="28"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition MinWidth="200" Width="*"/>
</Grid.ColumnDefinitions>
<!-- 显示 -->
<Label Grid.Row="0" Grid.ColumnSpan="2" Content="APPEARANCE" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
<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="16" FontWeight="DemiBold" Opacity=".85"/>
<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 SETTING" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
<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="10" Grid.Column="0" Content="Auto CRLF :" HorizontalAlignment="Right"/>
<ComboBox Grid.Row="10" Grid.Column="1"
x:Name="cmbAutoCRLF"
Height="24"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
SelectionChanged="AutoCRLFSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<Label Content="{Binding Value}" Padding="0" VerticalContentAlignment="Center"/>
<Label Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="10" Margin="8,0,0,0" Padding="0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- 合并工具配置 -->
<Label Grid.Row="12" Grid.ColumnSpan="2" Content="MERGE TOOL" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
<Label Grid.Row="13" Grid.Column="0" Content="Merger :" HorizontalAlignment="Right"/>
<ComboBox Grid.Row="13" 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="14" Grid.Column="0" Content="Install Path :" HorizontalAlignment="Right"/>
<Grid Grid.Row="14" 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="15" Grid.Column="0" Content="Command :" HorizontalAlignment="Right"/>
<TextBlock Grid.Row="15" Grid.Column="1"
x:Name="txtMergeParam"
VerticalAlignment="Center"
Foreground="{StaticResource Brush.FG2}"/>
</Grid>
</Grid>
</Border>
</Window>

220
src/UI/Preference.xaml.cs Normal file
View file

@ -0,0 +1,220 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// Preference window.
/// </summary>
public partial class Preference : Window {
/// <summary>
/// Git global user name.
/// </summary>
public string GlobalUser {
get;
set;
}
/// <summary>
/// Git global user email.
/// </summary>
public string GlobalUserEmail {
get;
set;
}
/// <summary>
/// Git core.autocrlf setting.
/// </summary>
public string AutoCRLF {
get;
set;
}
/// <summary>
/// Options for core.autocrlf
/// </summary>
public class AutoCRLFOption {
public string Value { get; set; }
public string Desc { get; set; }
public AutoCRLFOption(string v, string d) {
Value = v;
Desc = d;
}
}
/// <summary>
/// Constructor.
/// </summary>
public Preference() {
GlobalUser = GetConfig("user.name");
GlobalUserEmail = GetConfig("user.email");
AutoCRLF = GetConfig("core.autocrlf");
if (string.IsNullOrEmpty(AutoCRLF)) AutoCRLF = "false";
InitializeComponent();
int mergeType = App.Preference.MergeTool;
var merger = Git.MergeTool.Supported[mergeType];
txtMergePath.IsReadOnly = !merger.IsConfigured;
txtMergeParam.Text = merger.Parameter;
var crlfOptions = new List<AutoCRLFOption>() {
new AutoCRLFOption("true", "Commit as LF, checkout as CRLF"),
new AutoCRLFOption("input", "Only convert for commit"),
new AutoCRLFOption("false", "Do NOT convert"),
};
cmbAutoCRLF.ItemsSource = crlfOptions;
cmbAutoCRLF.SelectedItem = crlfOptions.Find(o => o.Value == AutoCRLF);
}
/// <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);
var oldAutoCRLF = GetConfig("core.autocrlf");
if (oldAutoCRLF != AutoCRLF) SetConfig("core.autocrlf", AutoCRLF);
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;
}
}
/// <summary>
/// Set core.autocrlf
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AutoCRLFSelectionChanged(object sender, SelectionChangedEventArgs e) {
if (e.AddedItems.Count != 1) return;
var mode = e.AddedItems[0] as AutoCRLFOption;
if (mode == null) return;
AutoCRLF = mode.Value;
}
#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
}
}

88
src/UI/Pull.xaml Normal file
View file

@ -0,0 +1,88 @@
<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>
</UserControl>

120
src/UI/Pull.xaml.cs Normal file
View file

@ -0,0 +1,120 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.Pull(remote, branch.Substring(branch.IndexOf('/')+1), msg => popup?.UpdateStatus(msg), rebase, autoStash));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.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;
}
}
}
}

96
src/UI/Push.xaml Normal file
View file

@ -0,0 +1,96 @@
<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>
</UserControl>

184
src/UI/Push.xaml.cs Normal file
View file

@ -0,0 +1,184 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
repo.GetPopupManager()?.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);
var popup = repo.GetPopupManager();
popup?.Show(push);
popup?.Lock();
var upstream = current.Upstream.Substring(13);
var remoteIdx = upstream.IndexOf('/');
var remote = upstream.Substring(0, remoteIdx);
var remoteBranch = upstream.Substring(remoteIdx + 1);
Task.Run(() => {
repo.Push(remote, current.Name, remoteBranch, msg => popup?.UpdateStatus(msg));
push.Dispatcher.Invoke(() => {
popup?.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);
}
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.Push(remote, localBranch.Name, remoteBranch, msg => popup?.UpdateStatus(msg), tags, track, force));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.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;
}
}
}
}

57
src/UI/PushTag.xaml Normal file
View file

@ -0,0 +1,57 @@
<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>
</UserControl>

62
src/UI/PushTag.xaml.cs Normal file
View file

@ -0,0 +1,62 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
repo.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => Git.Tag.Push(repo, tag.Name, remote.Name));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

55
src/UI/Rebase.xaml Normal file
View file

@ -0,0 +1,55 @@
<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>
</UserControl>

81
src/UI/Rebase.xaml.cs Normal file
View file

@ -0,0 +1,81 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
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;
opened.GetPopupManager()?.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}";
opened.GetPopupManager()?.Show(dialog);
}
/// <summary>
/// Start rebase.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Start(object sender, RoutedEventArgs e) {
var popup = repo.GetPopupManager();
popup?.Lock();
var autoStash = chkAutoStash.IsChecked == true;
await Task.Run(() => repo.Rebase(based, autoStash));
popup?.Close(true);
}
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

69
src/UI/Remote.xaml Normal file
View file

@ -0,0 +1,69 @@
<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>
</UserControl>

85
src/UI/Remote.xaml.cs Normal file
View file

@ -0,0 +1,85 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => {
if (remote != null) {
remote.Edit(repo, RemoteName, RemoteUri);
} else {
Git.Remote.Add(repo, RemoteName, RemoteUri);
}
});
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

59
src/UI/RenameBranch.xaml Normal file
View file

@ -0,0 +1,59 @@
<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>
</UserControl>

View file

@ -0,0 +1,68 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
opened.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => branch.Rename(repo, NewName));
popup?.Close(true);
}
/// <summary>
/// Cancel merge.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

64
src/UI/Reset.xaml Normal file
View file

@ -0,0 +1,64 @@
<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>
</UserControl>

89
src/UI/Reset.xaml.cs Normal file
View file

@ -0,0 +1,89 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
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;
repo.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.Reset(revision, mode.Arg));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

45
src/UI/Revert.xaml Normal file
View file

@ -0,0 +1,45 @@
<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>
</UserControl>

58
src/UI/Revert.xaml.cs Normal file
View file

@ -0,0 +1,58 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
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) {
repo.GetPopupManager()?.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;
var popup = repo.GetPopupManager();
popup?.Lock();
await Task.Run(() => repo.Revert(sha, autoCommit));
popup?.Close(true);
}
/// <summary>
/// Cancel.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

46
src/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>

56
src/UI/Stash.xaml.cs Normal file
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) {
repo.GetPopupManager()?.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);
repo.GetPopupManager()?.Close();
}
/// <summary>
/// Cancel
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Cancel(object sender, RoutedEventArgs e) {
repo.GetPopupManager()?.Close();
}
}
}

134
src/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.FG}" Opacity=".4" 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.FG}" Opacity=".4" 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>

121
src/UI/Stashes.xaml.cs Normal file
View file

@ -0,0 +1,121 @@
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;
diff.Diff(repo, new DiffViewer.Option() {
RevisionRange = new string[] { $"{selectedStash}^", selectedStash },
Path = change.Path,
OrgPath = 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;
}
}
}

8
src/UI/Waiting.xaml Normal file
View file

@ -0,0 +1,8 @@
<UserControl x:Class="SourceGit.UI.Waiting"
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="200" Width="500">
</UserControl>

38
src/UI/Waiting.xaml.cs Normal file
View file

@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace SourceGit.UI {
/// <summary>
/// General waiting dialog.
/// </summary>
public partial class Waiting : UserControl {
/// <summary>
/// Constructor.
/// </summary>
public Waiting() {
InitializeComponent();
}
/// <summary>
/// Show this dialog.
/// </summary>
/// <param name="repo"></param>
/// <param name="job"></param>
public static void Show(Git.Repository repo, Action job) {
var dialog = new Waiting();
var popup = repo.GetPopupManager();
popup?.Show(dialog);
popup?.Lock();
Task.Run(() => {
job.Invoke();
dialog.Dispatcher.Invoke(() => {
popup?.Close(true);
});
});
}
}
}

380
src/UI/WorkingCopy.xaml Normal file
View file

@ -0,0 +1,380 @@
<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,4,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.FG}" Opacity=".4"/>
<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}" Opacity=".4"/>
</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}" Opacity=".4"/>
</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,4,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.FG}" Opacity=".4"/>
<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}" Opacity=".4"/>
</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}" Opacity=".4"/>
</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"
GotFocus="CommitMsgGotFocus"
PreviewMouseWheel="CommitMsgPreviewMouseWheel">
<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" Unchecked="EndAmend"/>
<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>

1061
src/UI/WorkingCopy.xaml.cs Normal file

File diff suppressed because it is too large Load diff