refactor: ux for in progress action (cherry-pick/rebase/revert/merge)

This commit is contained in:
leo 2024-04-01 21:27:08 +08:00
parent 814af539cd
commit 53bcafa5ed
7 changed files with 243 additions and 123 deletions

View file

@ -942,15 +942,6 @@ namespace SourceGit.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Conflict detected! Press &apos;Abort&apos; to restore original HEAD..
/// </summary>
public static string Text_Conflict_Tip {
get {
return ResourceManager.GetString("Text.Conflict.Tip", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy.
/// </summary>
@ -2049,6 +2040,42 @@ namespace SourceGit.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Cherry-Pick in progress. Press &apos;Abort&apos; to restore original HEAD..
/// </summary>
public static string Text_InProgress_CherryPick {
get {
return ResourceManager.GetString("Text.InProgress.CherryPick", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Merge request in progress. Press &apos;Abort&apos; to restore original HEAD..
/// </summary>
public static string Text_InProgress_Merge {
get {
return ResourceManager.GetString("Text.InProgress.Merge", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rebase in progress. Press &apos;Abort&apos; to restore original HEAD..
/// </summary>
public static string Text_InProgress_Rebase {
get {
return ResourceManager.GetString("Text.InProgress.Rebase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Revert in progress. Press &apos;Abort&apos; to restore original HEAD..
/// </summary>
public static string Text_InProgress_Revert {
get {
return ResourceManager.GetString("Text.InProgress.Revert", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Source Git.
/// </summary>

View file

@ -1161,9 +1161,6 @@
<data xml:space="preserve" name="Text.WorkingCopy.IncludeUntracked">
<value>INCLUDE UNTRACKED FILES</value>
</data>
<data xml:space="preserve" name="Text.Conflict.Tip">
<value>Conflict detected! Press 'Abort' to restore original HEAD.</value>
</data>
<data xml:space="preserve" name="Text.ClearStashes">
<value>Clear Stashes</value>
</data>
@ -1338,4 +1335,16 @@
<data name="Text.About.SourceCode" xml:space="preserve">
<value>• Source code can be found at </value>
</data>
<data name="Text.InProgress.CherryPick" xml:space="preserve">
<value>Cherry-Pick in progress. Press 'Abort' to restore original HEAD.</value>
</data>
<data name="Text.InProgress.Rebase" xml:space="preserve">
<value>Rebase in progress. Press 'Abort' to restore original HEAD.</value>
</data>
<data name="Text.InProgress.Revert" xml:space="preserve">
<value>Revert in progress. Press 'Abort' to restore original HEAD.</value>
</data>
<data name="Text.InProgress.Merge" xml:space="preserve">
<value>Merge request in progress. Press 'Abort' to restore original HEAD.</value>
</data>
</root>

View file

@ -1161,9 +1161,6 @@
<data xml:space="preserve" name="Text.WorkingCopy.IncludeUntracked">
<value>INCLUDE UNTRACKED FILES</value>
</data>
<data xml:space="preserve" name="Text.Conflict.Tip">
<value>Conflict detected! Press 'Abort' to restore original HEAD.</value>
</data>
<data xml:space="preserve" name="Text.ClearStashes">
<value>Clear Stashes</value>
</data>
@ -1338,4 +1335,16 @@
<data name="Text.About.SourceCode" xml:space="preserve">
<value>• Source code can be found at </value>
</data>
<data name="Text.InProgress.CherryPick" xml:space="preserve">
<value>Cherry-Pick in progress. Press 'Abort' to restore original HEAD.</value>
</data>
<data name="Text.InProgress.Rebase" xml:space="preserve">
<value>Rebase in progress. Press 'Abort' to restore original HEAD.</value>
</data>
<data name="Text.InProgress.Revert" xml:space="preserve">
<value>Revert in progress. Press 'Abort' to restore original HEAD.</value>
</data>
<data name="Text.InProgress.Merge" xml:space="preserve">
<value>Merge request in progress. Press 'Abort' to restore original HEAD.</value>
</data>
</root>

View file

@ -1158,9 +1158,6 @@
<data xml:space="preserve" name="Text.WorkingCopy.IncludeUntracked">
<value>显示未跟踪文件</value>
</data>
<data xml:space="preserve" name="Text.Conflict.Tip">
<value>检测到本地冲突! 点击【终止合并】回滚到合并操作前的状态。</value>
</data>
<data xml:space="preserve" name="Text.ClearStashes">
<value>丢弃贮藏确认</value>
</data>
@ -1338,4 +1335,16 @@
<data name="Text.Repository.Fleet" xml:space="preserve">
<value>在 Fleet 中打开</value>
</data>
<data name="Text.InProgress.CherryPick" xml:space="preserve">
<value>挑选Cherry-Pick操作进行中。点击【终止】回滚到操作前的状态。</value>
</data>
<data name="Text.InProgress.Rebase" xml:space="preserve">
<value>变基Rebase操作进行中。点击【终止】回滚到操作前的状态。</value>
</data>
<data name="Text.InProgress.Revert" xml:space="preserve">
<value>回滚提交操作进行中。点击【终止】回滚到操作前的状态。</value>
</data>
<data name="Text.InProgress.Merge" xml:space="preserve">
<value>合并操作进行中。点击【终止】回滚到操作前的状态。</value>
</data>
</root>

View file

@ -0,0 +1,89 @@
using System.IO;
namespace SourceGit.ViewModels
{
public abstract class InProgressContext
{
public string Repository
{
get;
set;
}
public string Cmd
{
get;
set;
}
public InProgressContext(string repo, string cmd)
{
Repository = repo;
Cmd = cmd;
}
public void Abort()
{
new Commands.Command()
{
WorkingDirectory = Repository,
Context = Repository,
Args = $"{Cmd} --abort",
}.Exec();
}
public virtual bool Continue()
{
return new Commands.Command()
{
WorkingDirectory = Repository,
Context = Repository,
Args = $"-c core.editor=true {Cmd} --continue",
}.Exec();
}
}
public class CherryPickInProgress : InProgressContext
{
public CherryPickInProgress(string repo) : base(repo, "cherry-pick") { }
}
public class RebaseInProgress : InProgressContext
{
public RebaseInProgress(Repository repo) : base(repo.FullPath, "rebase")
{
_gitDir = repo.GitDir;
}
public override bool Continue()
{
var succ = base.Continue();
if (succ)
{
var rebaseMergeHead = Path.Combine(_gitDir, "REBASE_HEAD");
var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge");
var rebaseApplyFolder = Path.Combine(_gitDir, "rebase-apply");
if (File.Exists(rebaseMergeHead))
File.Delete(rebaseMergeHead);
if (Directory.Exists(rebaseMergeFolder))
Directory.Delete(rebaseMergeFolder);
if (Directory.Exists(rebaseApplyFolder))
Directory.Delete(rebaseApplyFolder);
}
return succ;
}
private string _gitDir;
}
public class RevertInProgress : InProgressContext
{
public RevertInProgress(string repo) : base(repo, "revert") { }
}
public class MergeInProgress : InProgressContext
{
public MergeInProgress(string repo) : base(repo, "merge") { }
}
}

View file

@ -141,20 +141,6 @@ namespace SourceGit.ViewModels
get => _stashesPage == null ? 0 : _stashesPage.Count;
}
[JsonIgnore]
public bool IsConflictBarVisible
{
get => _isConflictBarVisible;
private set => SetProperty(ref _isConflictBarVisible, value);
}
[JsonIgnore]
public bool HasUnsolvedConflict
{
get => _hasUnsolvedConflict;
private set => SetProperty(ref _hasUnsolvedConflict, value);
}
[JsonIgnore]
public bool CanCommitWithPush
{
@ -219,6 +205,20 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _isSubmoduleGroupExpanded, value);
}
[JsonIgnore]
public InProgressContext InProgressContext
{
get => _inProgressContext;
private set => SetProperty(ref _inProgressContext, value);
}
[JsonIgnore]
public bool HasUnsolvedConflicts
{
get => _hasUnsolvedConflicts;
private set => SetProperty(ref _hasUnsolvedConflicts, value);
}
public void Open()
{
_watcher = new Models.Watcher(this);
@ -227,8 +227,8 @@ namespace SourceGit.ViewModels
_stashesPage = new StashesPage(this);
_selectedView = _histories;
_selectedViewIndex = 0;
_isConflictBarVisible = false;
_hasUnsolvedConflict = false;
_inProgressContext = null;
_hasUnsolvedConflicts = false;
Task.Run(() =>
{
@ -262,6 +262,9 @@ namespace SourceGit.ViewModels
_isTagGroupExpanded = false;
_isSubmoduleGroupExpanded = false;
_inProgressContext = null;
_hasUnsolvedConflicts = false;
_remotes.Clear();
_branches.Clear();
_localBranchTrees.Clear();
@ -450,90 +453,34 @@ namespace SourceGit.ViewModels
public async void ContinueMerge()
{
var cherryPickMerge = Path.Combine(_gitDir, "CHERRY_PICK_HEAD");
var rebaseMerge = Path.Combine(_gitDir, "REBASE_HEAD");
var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge");
var revertMerge = Path.Combine(_gitDir, "REVERT_HEAD");
var otherMerge = Path.Combine(_gitDir, "MERGE_HEAD");
var mode = "";
if (File.Exists(cherryPickMerge))
if (_inProgressContext != null)
{
mode = "cherry-pick";
}
else if (File.Exists(rebaseMerge) && Directory.Exists(rebaseMergeFolder))
{
mode = "rebase";
}
else if (File.Exists(revertMerge))
{
mode = "revert";
}
else if (File.Exists(otherMerge))
{
mode = "merge";
SetWatcherEnabled(false);
var succ = await Task.Run(_inProgressContext.Continue);
if (succ && _workingCopy != null)
{
_workingCopy.CommitMessage = string.Empty;
}
SetWatcherEnabled(true);
}
else
{
MarkWorkingCopyDirtyManually();
return;
}
var cmd = new Commands.Command();
cmd.WorkingDirectory = _fullpath;
cmd.Context = _fullpath;
cmd.Args = $"-c core.editor=true {mode} --continue";
SetWatcherEnabled(false);
var succ = await Task.Run(cmd.Exec);
SetWatcherEnabled(true);
if (succ)
{
if (_workingCopy != null)
_workingCopy.CommitMessage = string.Empty;
if (mode == "rebase")
{
if (File.Exists(rebaseMerge))
File.Delete(rebaseMerge);
if (Directory.Exists(rebaseMergeFolder))
Directory.Delete(rebaseMergeFolder);
}
}
}
public async void AbortMerge()
{
var cmd = new Commands.Command();
cmd.WorkingDirectory = _fullpath;
cmd.Context = _fullpath;
if (File.Exists(Path.Combine(_gitDir, "CHERRY_PICK_HEAD")))
if (_inProgressContext != null)
{
cmd.Args = "cherry-pick --abort";
}
else if (File.Exists(Path.Combine(_gitDir, "REBASE_HEAD")))
{
cmd.Args = "rebase --abort";
}
else if (File.Exists(Path.Combine(_gitDir, "REVERT_HEAD")))
{
cmd.Args = "revert --abort";
}
else if (File.Exists(Path.Combine(_gitDir, "MERGE_HEAD")))
{
cmd.Args = "merge --abort";
SetWatcherEnabled(false);
await Task.Run(_inProgressContext.Abort);
SetWatcherEnabled(true);
}
else
{
MarkWorkingCopyDirtyManually();
return;
}
SetWatcherEnabled(false);
await Task.Run(cmd.Exec);
SetWatcherEnabled(true);
}
public void RefreshBranches()
@ -622,30 +569,39 @@ namespace SourceGit.ViewModels
{
var changes = new Commands.QueryLocalChanges(FullPath, _includeUntracked).Result();
var hasUnsolvedConflict = _workingCopy.SetData(changes);
var inProgress = null as InProgressContext;
var cherryPickMerge = Path.Combine(_gitDir, "CHERRY_PICK_HEAD");
var rebaseMerge = Path.Combine(_gitDir, "REBASE_HEAD");
var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge");
var revertMerge = Path.Combine(_gitDir, "REVERT_HEAD");
var otherMerge = Path.Combine(_gitDir, "MERGE_HEAD");
var runningMerge = (File.Exists(cherryPickMerge) ||
(File.Exists(rebaseMerge) && Directory.Exists(rebaseMergeFolder)) ||
File.Exists(revertMerge) ||
File.Exists(otherMerge));
if (!runningMerge)
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);
var applyFolder = Path.Combine(_gitDir, "rebase-apply");
if (Directory.Exists(applyFolder))
Directory.Delete(applyFolder, true);
if (Directory.Exists(rebaseApplyFolder))
Directory.Delete(rebaseApplyFolder, true);
}
Dispatcher.UIThread.Invoke(() =>
{
IsConflictBarVisible = runningMerge;
HasUnsolvedConflict = hasUnsolvedConflict;
InProgressContext = inProgress;
HasUnsolvedConflicts = hasUnsolvedConflict;
OnPropertyChanged(nameof(WorkingCopyChangesCount));
});
}
@ -1359,9 +1315,10 @@ namespace SourceGit.ViewModels
private List<Models.BranchTreeNode> _remoteBranchTrees = new List<Models.BranchTreeNode>();
private List<Models.Tag> _tags = new List<Models.Tag>();
private List<string> _submodules = new List<string>();
private bool _isConflictBarVisible = false;
private bool _hasUnsolvedConflict = false;
private bool _canCommitWithPush = false;
private bool _includeUntracked = true;
private InProgressContext _inProgressContext = null;
private bool _hasUnsolvedConflicts = false;
}
}

View file

@ -464,14 +464,34 @@
<!-- Right -->
<Grid Grid.Column="2" RowDefinitions="Auto,*">
<Grid Height="26" Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto" Background="{DynamicResource Brush.Conflict}" IsVisible="{Binding IsConflictBarVisible}">
<TextBlock Grid.Column="0" Margin="8,0" FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.Conflict.Tip}"/>
<Grid Grid.Row="0" Height="26" ColumnDefinitions="*,Auto,Auto,Auto" Background="{DynamicResource Brush.Conflict}" IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNotNull}}">
<ContentControl Grid.Column="0" Margin="8,0" Content="{Binding InProgressContext}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:CherryPickInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.CherryPick}"/>
</DataTemplate>
<DataTemplate DataType="vm:RebaseInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.Rebase}"/>
</DataTemplate>
<DataTemplate DataType="vm:RevertInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.Revert}"/>
</DataTemplate>
<DataTemplate DataType="vm:MergeInProgress">
<TextBlock FontWeight="Bold" Foreground="{DynamicResource Brush.FG3}" Text="{DynamicResource Text.InProgress.Merge}"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
<Button Grid.Column="1" Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.Repository.Resolve}" Height="20" Padding="8,0" Margin="4,0" Command="{Binding GotoResolve}">
<Button.IsVisible>
<Binding Path="SelectedViewIndex" Converter="{x:Static c:IntConverters.IsNotOne}"/>
</Button.IsVisible>
</Button>
<Button Grid.Column="2" Classes="flat primary" FontWeight="Regular" Content="{DynamicResource Text.Repository.Continue}" Height="20" Padding="8,0" Margin="4,0" Command="{Binding ContinueMerge}" IsVisible="{Binding !HasUnsolvedConflict}"/>
<Button Grid.Column="2" Classes="flat primary" FontWeight="Regular" Content="{DynamicResource Text.Repository.Continue}" Height="20" Padding="8,0" Margin="4,0" Command="{Binding ContinueMerge}" IsVisible="{Binding !HasUnsolvedConflicts}"/>
<Button Grid.Column="3" Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.Repository.Abort}" Height="20" Padding="8,0" Margin="4,0" Command="{Binding AbortMerge}"/>
</Grid>