mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-26 12:55:00 +00:00
feature: Allow show uncommitted changes in commits history like TortoiseHG Workspace
This commit is contained in:
parent
3c5a661fa0
commit
ba8c6382e7
15 changed files with 166 additions and 65 deletions
|
@ -35,6 +35,7 @@ namespace SourceGit.Models
|
|||
|
||||
public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime;
|
||||
public bool IsCurrentHead => Decorators.Find(x => x.Type is DecoratorType.CurrentBranchHead or DecoratorType.CurrentCommitHead) != null;
|
||||
public bool IsWorkCopy => string.IsNullOrWhiteSpace(SHA);
|
||||
|
||||
public double Opacity => IsMerged ? 1 : OpacityForNotMerged;
|
||||
public FontWeight FontWeight => IsCurrentHead ? FontWeight.Bold : FontWeight.Regular;
|
||||
|
|
|
@ -101,6 +101,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
public Point Center;
|
||||
public int Color;
|
||||
public bool IsWorkCopy;
|
||||
}
|
||||
|
||||
public List<Path> Paths { get; set; } = new List<Path>();
|
||||
|
@ -156,6 +157,8 @@ namespace SourceGit.Models
|
|||
|
||||
// Find first curves that links to this commit and marks others that links to this commit ended.
|
||||
double offsetX = -HALF_WIDTH;
|
||||
if (!string.IsNullOrEmpty(commit.SHA))
|
||||
{
|
||||
foreach (var l in unsolved)
|
||||
{
|
||||
if (l.Next == commit.SHA)
|
||||
|
@ -194,6 +197,7 @@ namespace SourceGit.Models
|
|||
l.Add(offsetX, offsetY, HALF_HEIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create new curve for branch head
|
||||
if (major == null && commit.Parents.Count > 0)
|
||||
|
@ -211,11 +215,11 @@ namespace SourceGit.Models
|
|||
{
|
||||
major.IsMerged = isMerged;
|
||||
position = new Point(major.LastX, offsetY);
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = major.Path.Color });
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = major.Path.Color, IsWorkCopy = commit.IsWorkCopy });
|
||||
}
|
||||
else
|
||||
{
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = 0 });
|
||||
temp.Dots.Add(new Dot() { Center = position, Color = 0, IsWorkCopy = commit.IsWorkCopy });
|
||||
}
|
||||
|
||||
// Deal with parents
|
||||
|
|
|
@ -286,6 +286,7 @@
|
|||
<x:String x:Key="Text.Histories.Search" xml:space="preserve">SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT</x:String>
|
||||
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">CLEAR</x:String>
|
||||
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">SELECTED {0} COMMITS</x:String>
|
||||
<x:String x:Key="Text.Histories.UncommittedChanges.Subject" xml:space="preserve">★Uncommitted local changes★</x:String>
|
||||
<x:String x:Key="Text.Hotkeys" xml:space="preserve">Keyboard Shortcuts Reference</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">GLOBAL</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">Cancel current popup</x:String>
|
||||
|
@ -380,6 +381,7 @@
|
|||
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">Default Clone Dir</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">Global git user email</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.ShowUncommittedChangesInHistory" xml:space="preserve">Show uncommitted changes in history</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">Install Path</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">Shell</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">User Name</x:String>
|
||||
|
|
|
@ -289,6 +289,7 @@
|
|||
<x:String x:Key="Text.Histories.Search" xml:space="preserve">查询提交指纹、信息、作者。回车键开始,ESC键取消</x:String>
|
||||
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">清空</x:String>
|
||||
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">已选中 {0} 项提交</x:String>
|
||||
<x:String x:Key="Text.Histories.UncommittedChanges.Subject" xml:space="preserve">★未提交的本地更改★</x:String>
|
||||
<x:String x:Key="Text.Hotkeys" xml:space="preserve">快捷键参考</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">全局快捷键</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">取消弹出面板</x:String>
|
||||
|
@ -383,6 +384,7 @@
|
|||
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">默认克隆路径</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">邮箱</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">默认GIT用户邮箱</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.ShowUncommittedChangesInHistory" xml:space="preserve">顯示歷史中未提交的更改</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">安装路径</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">终端Shell</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">用户名</x:String>
|
||||
|
|
|
@ -289,6 +289,7 @@
|
|||
<x:String x:Key="Text.Histories.Search" xml:space="preserve">查詢提交指紋、資訊、作者。回車鍵開始,ESC鍵取消</x:String>
|
||||
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">清空</x:String>
|
||||
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">已選中 {0} 項提交</x:String>
|
||||
<x:String x:Key="Text.Histories.UncommittedChanges.Subject" xml:space="preserve">★未提交的本地更改★</x:String>
|
||||
<x:String x:Key="Text.Hotkeys" xml:space="preserve">快捷鍵參考</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">全域性快捷鍵</x:String>
|
||||
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">取消彈出面板</x:String>
|
||||
|
@ -383,6 +384,7 @@
|
|||
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">預設克隆路徑</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">郵箱</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">預設GIT使用者郵箱</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.ShowUncommittedChangesInHistory" xml:space="preserve">显示历史记录中未提交的更改</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">安裝路徑</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">終端Shell</x:String>
|
||||
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">使用者名稱</x:String>
|
||||
|
|
|
@ -116,7 +116,14 @@ namespace SourceGit.ViewModels
|
|||
AutoSelectedCommit = commit;
|
||||
NavigationId = _navigationId + 1;
|
||||
|
||||
if (_detailContext is CommitDetail detail)
|
||||
|
||||
if (commit.IsWorkCopy)
|
||||
{
|
||||
var wc = _repo.WorkingCopy ??= new WorkingCopy(_repo);
|
||||
DetailContext = wc;
|
||||
wc.RefreshWorkingCopyChangesAsync();
|
||||
}
|
||||
else if (_detailContext is CommitDetail detail)
|
||||
{
|
||||
detail.Commit = commit;
|
||||
}
|
||||
|
|
|
@ -147,6 +147,12 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _maxHistoryCommits, value);
|
||||
}
|
||||
|
||||
public bool ShowUncommittedChangesInHistory
|
||||
{
|
||||
get => _showUncommittedChangesInHistory;
|
||||
set => SetProperty(ref _showUncommittedChangesInHistory, value);
|
||||
}
|
||||
|
||||
public int SubjectGuideLength
|
||||
{
|
||||
get => _subjectGuideLength;
|
||||
|
@ -515,6 +521,7 @@ namespace SourceGit.ViewModels
|
|||
private LayoutInfo _layout = new LayoutInfo();
|
||||
|
||||
private int _maxHistoryCommits = 20000;
|
||||
private bool _showUncommittedChangesInHistory = false;
|
||||
private int _subjectGuideLength = 50;
|
||||
private bool _restoreTabs = false;
|
||||
private bool _useFixedTabWidth = true;
|
||||
|
|
|
@ -716,6 +716,7 @@ namespace SourceGit.ViewModels
|
|||
Dispatcher.UIThread.Invoke(() => _histories.IsLoading = true);
|
||||
|
||||
var limits = $"-{Preference.Instance.MaxHistoryCommits} ";
|
||||
var showUncommitedChangedInHistory = Preference.Instance.ShowUncommittedChangesInHistory;
|
||||
var validFilters = new List<string>();
|
||||
foreach (var filter in _settings.Filters)
|
||||
{
|
||||
|
@ -762,6 +763,36 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
|
||||
var commits = new Commands.QueryCommits(_fullpath, limits).Result();
|
||||
if (showUncommitedChangedInHistory)
|
||||
{
|
||||
var changes = new Commands.QueryLocalChanges(_fullpath, _includeUntracked).Result();
|
||||
if(changes.Count > 0)
|
||||
{
|
||||
var config = new Commands.Config(_fullpath).ListAll();
|
||||
var currentUser = new Models.User();
|
||||
if (config.TryGetValue("user.name", out var name))
|
||||
currentUser.Name = name;
|
||||
if (config.TryGetValue("user.email", out var email))
|
||||
currentUser.Email = email;
|
||||
var date = (ulong)DateTime.Now.ToUniversalTime().Subtract(DateTime.UnixEpoch).TotalSeconds;
|
||||
commits.Insert(0,new Models.Commit()
|
||||
{
|
||||
Subject = App.Text("Histories.UncommittedChanges.Subject"),
|
||||
CanPullFromUpstream = false,
|
||||
Author = currentUser,
|
||||
Committer = currentUser,
|
||||
Parents = [currentBranch.Head],
|
||||
AuthorTime = date,
|
||||
CommitterTime = date,
|
||||
Decorators = [
|
||||
new Models.Decorator()
|
||||
{
|
||||
Type = Models.DecoratorType.CurrentBranchHead,
|
||||
Name = currentBranch.FriendlyName,
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
var graph = Models.CommitGraph.Parse(commits, canPushCommits, canPullCommits);
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
|
@ -781,41 +812,15 @@ namespace SourceGit.ViewModels
|
|||
Dispatcher.UIThread.Invoke(() => Submodules = submodules);
|
||||
}
|
||||
|
||||
public void RefreshWorkingCopyChanges()
|
||||
public async void RefreshWorkingCopyChanges()
|
||||
{
|
||||
var changes = new Commands.QueryLocalChanges(_fullpath, _includeUntracked).Result();
|
||||
if (_workingCopy == null)
|
||||
return;
|
||||
|
||||
var hasUnsolvedConflict = _workingCopy.SetData(changes);
|
||||
var inProgress = null as InProgressContext;
|
||||
var result = await _workingCopy.RefreshWorkingCopyChangesAsync();
|
||||
|
||||
var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge");
|
||||
var rebaseApplyFolder = Path.Combine(_gitDir, "rebase-apply");
|
||||
if (File.Exists(Path.Combine(_gitDir, "CHERRY_PICK_HEAD")))
|
||||
{
|
||||
inProgress = new CherryPickInProgress(_fullpath);
|
||||
}
|
||||
else if (File.Exists(Path.Combine(_gitDir, "REBASE_HEAD")) && Directory.Exists(rebaseMergeFolder))
|
||||
{
|
||||
inProgress = new RebaseInProgress(this);
|
||||
}
|
||||
else if (File.Exists(Path.Combine(_gitDir, "REVERT_HEAD")))
|
||||
{
|
||||
inProgress = new RevertInProgress(_fullpath);
|
||||
}
|
||||
else if (File.Exists(Path.Combine(_gitDir, "MERGE_HEAD")))
|
||||
{
|
||||
inProgress = new MergeInProgress(_fullpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Directory.Exists(rebaseMergeFolder))
|
||||
Directory.Delete(rebaseMergeFolder, true);
|
||||
|
||||
if (Directory.Exists(rebaseApplyFolder))
|
||||
Directory.Delete(rebaseApplyFolder, true);
|
||||
}
|
||||
var hasUnsolvedConflict = result.HasUnsolvedConflict;
|
||||
var inProgress = result.InProgressContext;
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
|
@ -1957,6 +1962,12 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public WorkingCopy WorkingCopy
|
||||
{
|
||||
get => _workingCopy;
|
||||
set => _workingCopy = value;
|
||||
}
|
||||
|
||||
private string _fullpath = string.Empty;
|
||||
private string _gitDir = string.Empty;
|
||||
private Models.RepositorySettings _settings = null;
|
||||
|
|
|
@ -1280,6 +1280,47 @@ namespace SourceGit.ViewModels
|
|||
});
|
||||
}
|
||||
|
||||
public async Task<(InProgressContext InProgressContext, bool HasUnsolvedConflict)> RefreshWorkingCopyChangesAsync()
|
||||
{
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
var gitDir = _repo.GitDir;
|
||||
var fullpath = _repo.FullPath;
|
||||
var changes = new Commands.QueryLocalChanges(fullpath, _repo.IncludeUntracked).Result();
|
||||
var hasUnsolvedConflict = SetData(changes);
|
||||
var inProgress = null as InProgressContext;
|
||||
|
||||
var rebaseMergeFolder = Path.Combine(gitDir, "rebase-merge");
|
||||
var rebaseApplyFolder = Path.Combine(gitDir, "rebase-apply");
|
||||
if (File.Exists(Path.Combine(gitDir, "CHERRY_PICK_HEAD")))
|
||||
{
|
||||
inProgress = new CherryPickInProgress(fullpath);
|
||||
}
|
||||
else if (File.Exists(Path.Combine(gitDir, "REBASE_HEAD")) && Directory.Exists(rebaseMergeFolder))
|
||||
{
|
||||
inProgress = new RebaseInProgress(_repo);
|
||||
}
|
||||
else if (File.Exists(Path.Combine(gitDir, "REVERT_HEAD")))
|
||||
{
|
||||
inProgress = new RevertInProgress(fullpath);
|
||||
}
|
||||
else if (File.Exists(Path.Combine(gitDir, "MERGE_HEAD")))
|
||||
{
|
||||
inProgress = new MergeInProgress(fullpath);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Directory.Exists(rebaseMergeFolder))
|
||||
Directory.Delete(rebaseMergeFolder, true);
|
||||
|
||||
if (Directory.Exists(rebaseApplyFolder))
|
||||
Directory.Delete(rebaseApplyFolder, true);
|
||||
}
|
||||
return (inProgress, hasUnsolvedConflict);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private Repository _repo = null;
|
||||
private bool _isLoadingData = false;
|
||||
private bool _isStaging = false;
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace SourceGit.Views
|
|||
public Geometry Icon { get; set; } = null;
|
||||
public FormattedText Label { get; set; } = null;
|
||||
public bool IsTag { get; set; } = false;
|
||||
public bool IsWorkCopy { get; internal set; } = false;
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
|
||||
|
@ -117,6 +118,14 @@ namespace SourceGit.Views
|
|||
using (context.PushTransform(Matrix.CreateTranslation(x + 4, 4)))
|
||||
context.DrawGeometry(iconFG, null, item.Icon);
|
||||
|
||||
if (item.IsWorkCopy)
|
||||
{
|
||||
var workCopyBorderRect = new RoundedRect(new Rect(x, 0, 16 + item.Label.Width + 8, 16)
|
||||
, new CornerRadius(2, 0, 0, 2));
|
||||
var workCopyBorderPen = new Pen(item.IsTag ? tagBG : branchBG, 2, new DashStyle() { Dashes = [1, 1], Offset = 1 });
|
||||
context.DrawRectangle(null, workCopyBorderPen, workCopyBorderRect);
|
||||
}
|
||||
|
||||
x += item.Label.Width + 16 + 8 + 4;
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +165,7 @@ namespace SourceGit.Views
|
|||
{
|
||||
Label = label,
|
||||
IsTag = decorator.Type == Models.DecoratorType.Tag,
|
||||
IsWorkCopy = commit.IsWorkCopy,
|
||||
};
|
||||
|
||||
var geo = null as StreamGeometry;
|
||||
|
|
|
@ -189,6 +189,11 @@
|
|||
</ContentControl.Content>
|
||||
|
||||
<ContentControl.DataTemplates>
|
||||
|
||||
<DataTemplate DataType="vm:WorkingCopy">
|
||||
<v:WorkingCopy/>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="vm:CommitDetail">
|
||||
<v:CommitDetail/>
|
||||
</DataTemplate>
|
||||
|
|
|
@ -263,8 +263,12 @@ namespace SourceGit.Views
|
|||
continue;
|
||||
if (dot.Center.Y > bottom)
|
||||
break;
|
||||
|
||||
context.DrawEllipse(dotFill, Models.CommitGraph.Pens[dot.Color], dot.Center, 3, 3);
|
||||
var pen = Models.CommitGraph.Pens[dot.Color];
|
||||
if (dot.IsWorkCopy)
|
||||
{
|
||||
pen = new Pen(pen.Brush, pen.Thickness, s_UncommittedCahngesLineDashStyle);
|
||||
}
|
||||
context.DrawEllipse(dotFill, pen, dot.Center, 5, 5);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +286,6 @@ namespace SourceGit.Views
|
|||
|
||||
var geo = new StreamGeometry();
|
||||
var pen = Models.CommitGraph.Pens[line.Color];
|
||||
|
||||
using (var ctx = geo.Open())
|
||||
{
|
||||
var started = false;
|
||||
|
@ -351,10 +354,12 @@ namespace SourceGit.Views
|
|||
ctx.BeginFigure(link.Start, false);
|
||||
ctx.QuadraticBezierTo(link.Control, link.End);
|
||||
}
|
||||
var pen = Models.CommitGraph.Pens[link.Color];
|
||||
context.DrawGeometry(null, pen, geo);
|
||||
}
|
||||
}
|
||||
|
||||
context.DrawGeometry(null, Models.CommitGraph.Pens[link.Color], geo);
|
||||
}
|
||||
}
|
||||
private readonly static IDashStyle s_UncommittedCahngesLineDashStyle = new DashStyle() { Dashes = [1, 1], Offset = 1 };
|
||||
}
|
||||
|
||||
public partial class Histories : UserControl
|
||||
|
@ -403,7 +408,7 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnCommitDataGridContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (DataContext is ViewModels.Histories histories && sender is DataGrid datagrid)
|
||||
if (DataContext is ViewModels.Histories histories && sender is DataGrid datagrid && datagrid.SelectedItem is Models.Commit { IsWorkCopy: false})
|
||||
{
|
||||
var menu = histories.MakeContextMenu(datagrid);
|
||||
datagrid.OpenContextMenu(menu);
|
||||
|
|
|
@ -244,7 +244,7 @@
|
|||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/>
|
||||
</TabItem.Header>
|
||||
|
||||
<Grid Margin="8" RowDefinitions="32,32,Auto,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
|
||||
<Grid Margin="8" RowDefinitions="32,32,Auto,32,32,32,32,32,Auto,32" ColumnDefinitions="Auto,*">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Text="{DynamicResource Text.Preference.Git.Path}"
|
||||
HorizontalAlignment="Right"
|
||||
|
@ -395,6 +395,10 @@
|
|||
Margin="5,0,0,0"
|
||||
Text="{DynamicResource Text.Preference.Git.AutoFetchIntervalSuffix}" />
|
||||
</Grid>
|
||||
<CheckBox Grid.Row="9" Grid.Column="1"
|
||||
Content="{DynamicResource Text.Preference.Git.ShowUncommittedChangesInHistory}"
|
||||
IsChecked="{Binding ShowUncommittedChangesInHistory, Mode=TwoWay}"
|
||||
/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
</Grid>
|
||||
</ListBoxItem>
|
||||
|
||||
<ListBoxItem>
|
||||
<ListBoxItem IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=!ShowUncommittedChangesInHistory , Mode=OneWay}">
|
||||
<Grid Classes="view_mode" ColumnDefinitions="32,*,Auto">
|
||||
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Send}"/>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.WorkingCopy}"/>
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
<!-- Right -->
|
||||
<Grid Grid.Column="2" Margin="0,4,4,4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" MinHeight="400"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="4"/>
|
||||
<RowDefinition Height="128" MinHeight="100"/>
|
||||
<RowDefinition Height="36"/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue