mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
refactor<Blame>: new blame tool
This commit is contained in:
parent
697879b6a5
commit
a1bfbfe02e
5 changed files with 121 additions and 123 deletions
|
@ -9,6 +9,8 @@ namespace SourceGit.Commands {
|
|||
public class Blame : Command {
|
||||
private static readonly Regex REG_FORMAT = new Regex(@"^\^?([0-9a-f]+)\s+.*\((.*)\s+(\d+)\s+[\-\+]?\d+\s+\d+\) (.*)");
|
||||
private Data data = new Data();
|
||||
private bool needUnifyCommitSHA = false;
|
||||
private int minSHALen = 0;
|
||||
|
||||
public class Data {
|
||||
public List<Models.BlameLine> Lines = new List<Models.BlameLine>();
|
||||
|
@ -22,6 +24,15 @@ namespace SourceGit.Commands {
|
|||
|
||||
public Data Result() {
|
||||
Exec();
|
||||
|
||||
if (needUnifyCommitSHA) {
|
||||
foreach (var line in data.Lines) {
|
||||
if (line.CommitSHA.Length > minSHALen) {
|
||||
line.CommitSHA = line.CommitSHA.Substring(0, minSHALen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -52,6 +63,12 @@ namespace SourceGit.Commands {
|
|||
Content = content,
|
||||
};
|
||||
|
||||
if (line[0] == '^') {
|
||||
needUnifyCommitSHA = true;
|
||||
if (minSHALen == 0) minSHALen = commit.Length;
|
||||
else if (commit.Length < minSHALen) minSHALen = commit.Length;
|
||||
}
|
||||
|
||||
data.Lines.Add(blameLine);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,10 +49,6 @@
|
|||
<sys:String x:Key="Text.Archive.File.Placeholder">Select archive file path</sys:String>
|
||||
|
||||
<sys:String x:Key="Text.Blame">Blame</sys:String>
|
||||
<sys:String x:Key="Text.Blame.Tip">Right click to see commit info</sys:String>
|
||||
<sys:String x:Key="Text.Blame.SHA">COMMIT SHA</sys:String>
|
||||
<sys:String x:Key="Text.Blame.Author">AUTHOR</sys:String>
|
||||
<sys:String x:Key="Text.Blame.ModifyTime">MODIFY TIME</sys:String>
|
||||
|
||||
<sys:String x:Key="Text.Submodule">SUBMODULES</sys:String>
|
||||
<sys:String x:Key="Text.Submodule.Add">Add Submodule</sys:String>
|
||||
|
|
|
@ -48,10 +48,6 @@
|
|||
<sys:String x:Key="Text.Archive.File.Placeholder">选择存档文件的存放路径</sys:String>
|
||||
|
||||
<sys:String x:Key="Text.Blame">逐行追溯</sys:String>
|
||||
<sys:String x:Key="Text.Blame.Tip">右键点击查看所选行修改记录</sys:String>
|
||||
<sys:String x:Key="Text.Blame.SHA">提交指纹</sys:String>
|
||||
<sys:String x:Key="Text.Blame.Author">修改者</sys:String>
|
||||
<sys:String x:Key="Text.Blame.ModifyTime">修改时间</sys:String>
|
||||
|
||||
<sys:String x:Key="Text.Submodule">子模块</sys:String>
|
||||
<sys:String x:Key="Text.Submodule.Add">添加子模块</sys:String>
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title Bar -->
|
||||
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
|
@ -34,8 +34,17 @@
|
|||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{DynamicResource Text.Blame}"/>
|
||||
|
||||
<!-- Description -->
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
x:Name="txtFile"
|
||||
Margin="8,0,0,0"
|
||||
FontFamily="{Binding Source={x:Static models:Preference.Instance}, Path=General.FontFamilyContent, Mode=OneWay}"
|
||||
Foreground="{DynamicResource Brush.FG2}"
|
||||
FontSize="11"/>
|
||||
|
||||
<!-- Window Commands -->
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<StackPanel Grid.Column="4" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<controls:IconButton Click="Minimize" Width="48" IconSize="10" Icon="{StaticResource Icon.Minimize}" HoverBackground="#40000000" Opacity="1"/>
|
||||
<ToggleButton Style="{StaticResource Style.ToggleButton.MaxOrRestore}" Width="48" IsChecked="{Binding ElementName=me, Path=IsMaximized}"/>
|
||||
<controls:IconButton Click="Quit" Width="48" IconSize="10" Icon="{StaticResource Icon.Close}" HoverBackground="Red" Opacity="1"/>
|
||||
|
@ -49,49 +58,72 @@
|
|||
HorizontalAlignment="Stretch"
|
||||
Fill="{DynamicResource Brush.Border0}"/>
|
||||
|
||||
<!-- Description -->
|
||||
<Border Grid.Row="2">
|
||||
<Grid Margin="4,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" x:Name="txtFile" FontFamily="{Binding Source={x:Static models:Preference.Instance}, Path=General.FontFamilyContent, Mode=OneWay}" FontSize="11" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
<TextBlock Grid.Column="1" HorizontalAlignment="Right" Foreground="{DynamicResource Brush.FG2}" FontFamily="{Binding Source={x:Static models:Preference.Instance}, Path=General.FontFamilyContent, Mode=OneWay}" FontSize="11" Text="{DynamicResource Text.Blame.Tip}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Viewer -->
|
||||
<DataGrid
|
||||
Grid.Row="3"
|
||||
Grid.Row="2"
|
||||
x:Name="blame"
|
||||
GridLinesVisibility="Vertical"
|
||||
VerticalGridLinesBrush="{DynamicResource Brush.Border2}"
|
||||
BorderBrush="{DynamicResource Brush.Border2}"
|
||||
BorderThickness="1"
|
||||
FrozenColumnCount="1"
|
||||
FrozenColumnCount="2"
|
||||
RowHeight="16"
|
||||
SelectionUnit="FullRow"
|
||||
SelectionMode="Single"
|
||||
SizeChanged="OnViewerSizeChanged">
|
||||
SizeChanged="OnViewerSizeChanged"
|
||||
SelectionChanged="OnSelectionChanged">
|
||||
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||
<EventSetter Event="RequestBringIntoView" Handler="OnViewerRequestBringIntoView"/>
|
||||
<EventSetter Event="ContextMenuOpening" Handler="OnViewerContextMenuOpening"/>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="Auto" IsReadOnly="True">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Grid x:Name="BG">
|
||||
<Border x:Name="Content" BorderBrush="{DynamicResource Brush.Border2}" BorderThickness="0,1,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="4,0" MaxWidth="300">
|
||||
<TextBlock Text="{Binding Line.CommitSHA}" Foreground="#FFFFB835"/>
|
||||
<TextBlock Text="{Binding Line.Time}" Margin="8,0"/>
|
||||
<TextBlock Text="{Binding Line.Author}" Foreground="#FFFFB835"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsFirstLine}" Value="True">
|
||||
<Setter TargetName="Content" Property="BorderThickness" Value="0"/>
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding IsFirstLineInGroup}" Value="False">
|
||||
<Setter TargetName="Content" Property="Visibility" Value="Hidden"/>
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Width="Auto" IsReadOnly="True" Binding="{Binding Line.LineNumber}" ElementStyle="{StaticResource Style.TextBlock.LineNumber}"/>
|
||||
|
||||
<DataGridTemplateColumn Width="SizeToCells" MinWidth="1" IsReadOnly="True">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{Binding BG}" BorderThickness="0">
|
||||
<Border x:Name="BG" BorderThickness="0">
|
||||
<TextBlock Text="{Binding Line.Content}" Style="{DynamicResource Style.TextBlock.LineContent}"/>
|
||||
</Border>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter TargetName="BG" Property="Background" Value="#44000000"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
@ -111,30 +143,5 @@
|
|||
|
||||
<!-- Loading -->
|
||||
<controls:Loading x:Name="loading" Grid.Row="3" Width="48" Height="48" IsAnimating="True"/>
|
||||
|
||||
<!-- Popup to show commit info -->
|
||||
<Popup x:Name="popup" Grid.Row="3" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
|
||||
<Border BorderBrush="{DynamicResource Brush.Accent1}" BorderThickness="1" Background="{DynamicResource Brush.Popup}">
|
||||
<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>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Text.Blame.SHA}" Foreground="{DynamicResource Brush.FG2}" Margin="4,0"/>
|
||||
<Label Grid.Row="0" Grid.Column="1" x:Name="commitID" Margin="8,0,4,0" Padding="0" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource Text.Blame.Author}" Foreground="{DynamicResource Brush.FG2}" Margin="4,0"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="authorName" Margin="8,0,4,0"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{DynamicResource Text.Blame.ModifyTime}" Foreground="{DynamicResource Brush.FG2}" Margin="4,0"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="authorTime" Margin="8,0,4,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</controls:Window>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
@ -12,33 +10,45 @@ namespace SourceGit.Views {
|
|||
/// 逐行追溯
|
||||
/// </summary>
|
||||
public partial class Blame : Controls.Window {
|
||||
private static readonly Brush[] BG = new Brush[] {
|
||||
Brushes.Transparent,
|
||||
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
|
||||
};
|
||||
|
||||
private string repo = null;
|
||||
private string lastSHA = null;
|
||||
private int lastBG = 1;
|
||||
|
||||
/// <summary>
|
||||
/// DataGrid数据源结构
|
||||
/// </summary>
|
||||
public class Record : INotifyPropertyChanged {
|
||||
private Brush bg = null;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 原始Blame行数据
|
||||
/// </summary>
|
||||
public Models.BlameLine Line { get; set; }
|
||||
public Brush OrgBG { get; set; }
|
||||
public Brush BG {
|
||||
get { return bg; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是第一行
|
||||
/// </summary>
|
||||
public bool IsFirstLine { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 前一行与本行的提交不同
|
||||
/// </summary>
|
||||
public bool IsFirstLineInGroup { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否当前选中,会影响背景色
|
||||
/// </summary>
|
||||
private bool isSelected = false;
|
||||
public bool IsSelected {
|
||||
get { return isSelected; }
|
||||
set {
|
||||
if (value != bg) {
|
||||
bg = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BG"));
|
||||
if (isSelected != value) {
|
||||
isSelected = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsSelected"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blame数据
|
||||
/// </summary>
|
||||
public ObservableCollection<Record> Records { get; set; }
|
||||
|
||||
public Blame(string repo, string file, string revision) {
|
||||
|
@ -67,48 +77,33 @@ namespace SourceGit.Views {
|
|||
notSupport.Visibility = Visibility.Visible;
|
||||
});
|
||||
} else {
|
||||
string lastSHA = null;
|
||||
foreach (var line in rs.Lines) {
|
||||
var r = new Record();
|
||||
r.Line = line;
|
||||
r.BG = GetBG(line.CommitSHA);
|
||||
r.OrgBG = r.BG;
|
||||
r.IsSelected = false;
|
||||
|
||||
if (line.CommitSHA != lastSHA) {
|
||||
lastSHA = line.CommitSHA;
|
||||
r.IsFirstLineInGroup = true;
|
||||
} else {
|
||||
r.IsFirstLineInGroup = false;
|
||||
}
|
||||
|
||||
Records.Add(r);
|
||||
}
|
||||
|
||||
if (Records.Count > 0) Records[0].IsFirstLine = true;
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.IsAnimating = false;
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
|
||||
var formatted = new FormattedText(
|
||||
$"{Records.Count}",
|
||||
CultureInfo.CurrentCulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(blame.FontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||||
12.0,
|
||||
Brushes.Black,
|
||||
VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||
|
||||
var lineNumberWidth = formatted.Width + 16;
|
||||
var minWidth = blame.ActualWidth - lineNumberWidth;
|
||||
if (Records.Count * 16 > blame.ActualHeight) minWidth -= 8;
|
||||
blame.Columns[0].Width = lineNumberWidth;
|
||||
blame.Columns[1].MinWidth = minWidth;
|
||||
blame.ItemsSource = Records;
|
||||
blame.UpdateLayout();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Brush GetBG(string sha) {
|
||||
if (lastSHA != sha) {
|
||||
lastSHA = sha;
|
||||
lastBG = 1 - lastBG;
|
||||
}
|
||||
|
||||
return BG[lastBG];
|
||||
}
|
||||
|
||||
#region WINDOW_COMMANDS
|
||||
private void Minimize(object sender, RoutedEventArgs e) {
|
||||
SystemCommands.MinimizeWindow(this);
|
||||
|
@ -148,8 +143,8 @@ namespace SourceGit.Views {
|
|||
var scroller = GetVisualChild<ScrollViewer>(blame);
|
||||
if (scroller != null && scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
|
||||
|
||||
blame.Columns[1].MinWidth = minWidth;
|
||||
blame.Columns[1].Width = DataGridLength.SizeToCells;
|
||||
blame.Columns[2].MinWidth = minWidth;
|
||||
blame.Columns[2].Width = DataGridLength.SizeToCells;
|
||||
blame.UpdateLayout();
|
||||
}
|
||||
|
||||
|
@ -157,31 +152,18 @@ namespace SourceGit.Views {
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnViewerContextMenuOpening(object sender, ContextMenuEventArgs ev) {
|
||||
var record = (sender as DataGridRow).DataContext as Record;
|
||||
if (record == null) return;
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
var r = blame.SelectedItem as Record;
|
||||
if (r == null) return;
|
||||
|
||||
foreach (var r in Records) {
|
||||
if (r.Line.CommitSHA == record.Line.CommitSHA) {
|
||||
r.BG = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
||||
} else {
|
||||
r.BG = r.OrgBG;
|
||||
Models.Watcher.Get(repo).NavigateTo(r.Line.CommitSHA);
|
||||
|
||||
foreach (var one in Records) {
|
||||
one.IsSelected = one.Line.CommitSHA == r.Line.CommitSHA;
|
||||
}
|
||||
}
|
||||
|
||||
Hyperlink link = new Hyperlink(new Run(record.Line.CommitSHA));
|
||||
link.ToolTip = App.Text("Goto");
|
||||
link.Click += (o, e) => {
|
||||
Models.Watcher.Get(repo).NavigateTo(record.Line.CommitSHA);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
commitID.Content = link;
|
||||
authorName.Text = record.Line.Author;
|
||||
authorTime.Text = record.Line.Time;
|
||||
popup.IsOpen = true;
|
||||
ev.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private string repo = null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue