mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-22 20:37:19 -08:00
feature: simple interactive rebase support (#188)
* Only allow to start interactive rebase from merged commit in current branch * The order of commits in the interactive rebase window is as same as it's in histories page. * Unlike anthor git frontend app `Fork`, you should edit the final message on the last commit rather than the previous commit that will be meld into while squashing commits
This commit is contained in:
parent
6c9f7e6da3
commit
7070a07e15
17 changed files with 816 additions and 7 deletions
|
@ -6,6 +6,7 @@ namespace SourceGit
|
|||
[JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(List<Models.InteractiveRebaseJob>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
[JsonSerializable(typeof(ViewModels.Preference))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
|
|
|
@ -45,7 +45,10 @@ namespace SourceGit
|
|||
{
|
||||
try
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
if (args.Length > 1 && args[0].Equals("--rebase-editor", StringComparison.Ordinal))
|
||||
Environment.Exit(Models.InteractiveRebaseEditor.Process(args[1]));
|
||||
else
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
@ -43,9 +43,7 @@ namespace SourceGit.Commands
|
|||
|
||||
// Force using en_US.UTF-8 locale to avoid GCM crash
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
start.Environment.Add("LANG", "en_US.UTF-8");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(WorkingDirectory))
|
||||
start.WorkingDirectory = WorkingDirectory;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Rebase : Command
|
||||
{
|
||||
|
@ -12,4 +14,17 @@
|
|||
Args += basedOn;
|
||||
}
|
||||
}
|
||||
|
||||
public class InteractiveRebase : Command
|
||||
{
|
||||
public InteractiveRebase(string repo, string basedOn)
|
||||
{
|
||||
var exec = Process.GetCurrentProcess().MainModule.FileName;
|
||||
var editor = $"\\\"{exec}\\\" --rebase-editor";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"-c core.editor=\"{editor}\" -c sequence.editor=\"{editor}\" -c rebase.abbreviateCommands=true rebase -i --autosquash {basedOn}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
51
src/Converters/InteractiveRebaseActionConverters.cs
Normal file
51
src/Converters/InteractiveRebaseActionConverters.cs
Normal file
|
@ -0,0 +1,51 @@
|
|||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static class InteractiveRebaseActionConverters
|
||||
{
|
||||
public static readonly FuncValueConverter<Models.InteractiveRebaseAction, IBrush> ToIconBrush =
|
||||
new FuncValueConverter<Models.InteractiveRebaseAction, IBrush>(v =>
|
||||
{
|
||||
switch (v)
|
||||
{
|
||||
case Models.InteractiveRebaseAction.Pick:
|
||||
return Brushes.Green;
|
||||
case Models.InteractiveRebaseAction.Edit:
|
||||
return Brushes.Orange;
|
||||
case Models.InteractiveRebaseAction.Reword:
|
||||
return Brushes.Orange;
|
||||
case Models.InteractiveRebaseAction.Squash:
|
||||
return Brushes.LightGray;
|
||||
case Models.InteractiveRebaseAction.Fixup:
|
||||
return Brushes.LightGray;
|
||||
default:
|
||||
return Brushes.Red;
|
||||
}
|
||||
});
|
||||
|
||||
public static readonly FuncValueConverter<Models.InteractiveRebaseAction, string> ToName =
|
||||
new FuncValueConverter<Models.InteractiveRebaseAction, string>(v =>
|
||||
{
|
||||
switch (v)
|
||||
{
|
||||
case Models.InteractiveRebaseAction.Pick:
|
||||
return "Pick";
|
||||
case Models.InteractiveRebaseAction.Edit:
|
||||
return "Edit";
|
||||
case Models.InteractiveRebaseAction.Reword:
|
||||
return "Reword";
|
||||
case Models.InteractiveRebaseAction.Squash:
|
||||
return "Squash";
|
||||
case Models.InteractiveRebaseAction.Fixup:
|
||||
return "Fixup";
|
||||
default:
|
||||
return "Drop";
|
||||
}
|
||||
});
|
||||
|
||||
public static readonly FuncValueConverter<Models.InteractiveRebaseAction, bool> CanEditMessage =
|
||||
new FuncValueConverter<Models.InteractiveRebaseAction, bool>(v => v == Models.InteractiveRebaseAction.Reword || v == Models.InteractiveRebaseAction.Squash);
|
||||
}
|
||||
}
|
116
src/Models/InteractiveRebaseEditor.cs
Normal file
116
src/Models/InteractiveRebaseEditor.cs
Normal file
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum InteractiveRebaseAction
|
||||
{
|
||||
Pick,
|
||||
Edit,
|
||||
Reword,
|
||||
Squash,
|
||||
Fixup,
|
||||
Drop,
|
||||
}
|
||||
|
||||
public class InteractiveRebaseJob
|
||||
{
|
||||
public string SHA { get; set; } = string.Empty;
|
||||
public InteractiveRebaseAction Action { get; set; } = InteractiveRebaseAction.Pick;
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public static class InteractiveRebaseEditor
|
||||
{
|
||||
public static int Process(string file)
|
||||
{
|
||||
|
||||
File.AppendAllLines("E:\\unknown.txt", ["------------", file]);
|
||||
|
||||
try
|
||||
{
|
||||
var filename = Path.GetFileName(file);
|
||||
if (filename.Equals("git-rebase-todo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.AppendAllLines("E:\\unknown.txt", ["git-rebase-todo start"]);
|
||||
var dirInfo = new DirectoryInfo(Path.GetDirectoryName(file));
|
||||
if (!dirInfo.Exists || !dirInfo.Name.Equals("rebase-merge", StringComparison.Ordinal))
|
||||
{
|
||||
File.WriteAllLines("E:\\test.txt", ["git-rebase-todo", file]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var jobsFile = Path.Combine(dirInfo.Parent.FullName, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(jobsFile))
|
||||
{
|
||||
File.WriteAllLines("E:\\test.txt", ["git-rebase-todo", file, jobsFile]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var jobs = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.ListInteractiveRebaseJob);
|
||||
var lines = new List<string>();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
switch (job.Action)
|
||||
{
|
||||
case InteractiveRebaseAction.Pick:
|
||||
lines.Add($"p {job.SHA}");
|
||||
break;
|
||||
case InteractiveRebaseAction.Edit:
|
||||
lines.Add($"e {job.SHA}");
|
||||
break;
|
||||
case InteractiveRebaseAction.Reword:
|
||||
lines.Add($"r {job.SHA}");
|
||||
break;
|
||||
case InteractiveRebaseAction.Squash:
|
||||
lines.Add($"s {job.SHA}");
|
||||
break;
|
||||
case InteractiveRebaseAction.Fixup:
|
||||
lines.Add($"f {job.SHA}");
|
||||
break;
|
||||
default:
|
||||
lines.Add($"d {job.SHA}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
File.AppendAllLines("E:\\unknown.txt", ["git-rebase-todo end"]);
|
||||
File.WriteAllLines(file, lines);
|
||||
}
|
||||
else if (filename.Equals("COMMIT_EDITMSG", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.AppendAllLines("E:\\unknown.txt", ["COMMIT_EDITMSG start"]);
|
||||
var jobsFile = Path.Combine(Path.GetDirectoryName(file), "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(jobsFile))
|
||||
return 0;
|
||||
|
||||
var jobs = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.ListInteractiveRebaseJob);
|
||||
var doneFile = Path.Combine(Path.GetDirectoryName(file), "rebase-merge", "done");
|
||||
if (!File.Exists(doneFile))
|
||||
return -1;
|
||||
|
||||
var done = File.ReadAllText(doneFile).Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length > jobs.Count)
|
||||
return -1;
|
||||
|
||||
var job = jobs[done.Length - 1];
|
||||
File.WriteAllText(file, job.Message);
|
||||
|
||||
File.AppendAllLines("E:\\unknown.txt", ["COMMIT_EDITMSG end", File.ReadAllText(doneFile).ReplaceLineEndings("|")]);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.AppendAllLines("E:\\unknown.txt", [file]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,4 +102,5 @@
|
|||
<StreamGeometry x:Key="Icons.Unlock">M832 464H332V240c0-31 25-56 56-56h248c31 0 56 25 56 56v68c0 4 4 8 8 8h56c4 0 8-4 8-8v-68c0-71-57-128-128-128H388c-71 0-128 57-128 128v224h-68c-18 0-32 14-32 32v384c0 18 14 32 32 32h640c18 0 32-14 32-32V496c0-18-14-32-32-32zM540 701v53c0 4-4 8-8 8h-40c-4 0-8-4-8-8v-53c-12-9-20-23-20-39 0-27 22-48 48-48s48 22 48 48c0 16-8 30-20 39z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Track">M897 673v13c0 51-42 93-93 93h-10c-1 0-2 0-2 0H220c-23 0-42 19-42 42v13c0 23 19 42 42 42h552c14 0 26 12 26 26 0 14-12 26-26 26H220c-51 0-93-42-93-93v-13c0-51 42-93 93-93h20c1-0 2-0 2-0h562c23 0 42-19 42-42v-13c0-11-5-22-13-29-8-7-17-11-28-10H660c-14 0-26-12-26-26 0-14 12-26 26-26h144c24-1 47 7 65 24 18 17 29 42 29 67zM479 98c-112 0-203 91-203 203 0 44 14 85 38 118l132 208c15 24 50 24 66 0l133-209c23-33 37-73 37-117 0-112-91-203-203-203zm0 327c-68 0-122-55-122-122s55-122 122-122 122 55 122 122-55 122-122 122z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Whitespace">M416 64H768v64h-64v704h64v64H448v-64h64V512H416a224 224 0 1 1 0-448zM576 832h64V128H576v704zM416 128H512v320H416a160 160 0 0 1 0-320z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.InteractiveRebase">M512 64A447 447 0 0064 512c0 248 200 448 448 448s448-200 448-448S760 64 512 64zM218 295h31c54 0 105 19 145 55 13 12 13 31 3 43a35 35 0 01-22 10 36 36 0 01-21-7 155 155 0 00-103-39h-31a32 32 0 01-31-31c0-18 13-31 30-31zm31 433h-31a32 32 0 01-31-31c0-16 13-31 31-31h31A154 154 0 00403 512 217 217 0 01620 295h75l-93-67a33 33 0 01-7-43 33 33 0 0143-7l205 148-205 148a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67H620a154 154 0 00-154 154c0 122-97 220-217 220zm390 118a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67h-75c-52 0-103-19-143-54-12-12-13-31-1-43a30 30 0 0142-3 151 151 0 00102 39h75L602 599a33 33 0 01-7-43 33 33 0 0143-7l205 148-203 151z</StreamGeometry>
|
||||
</ResourceDictionary>
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Compare with HEAD</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Compare with Worktree</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">Copy SHA</x:String>
|
||||
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Interactive Rebase ${0}$ to Here</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase ${0}$ to Here</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Reset ${0}$ to Here</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Revert Commit</x:String>
|
||||
|
@ -287,6 +288,11 @@
|
|||
<x:String x:Key="Text.InProgress.Merge" xml:space="preserve">Merge request in progress. Press 'Abort' to restore original HEAD.</x:String>
|
||||
<x:String x:Key="Text.InProgress.Rebase" xml:space="preserve">Rebase in progress. Press 'Abort' to restore original HEAD.</x:String>
|
||||
<x:String x:Key="Text.InProgress.Revert" xml:space="preserve">Revert in progress. Press 'Abort' to restore original HEAD.</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interactive Rebase</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Target Branch:</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">On:</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">Move Up</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">Move Down</x:String>
|
||||
<x:String x:Key="Text.Launcher" xml:space="preserve">Source Git</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String>
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">与当前HEAD比较</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">与本地工作树比较</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">复制提交指纹</x:String>
|
||||
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">交互式变基(rebase -i) ${0}$ 到此处</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">变基(rebase) ${0}$ 到此处</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">重置(reset) ${0}$ 到此处</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">回滚此提交</x:String>
|
||||
|
@ -290,6 +291,11 @@
|
|||
<x:String x:Key="Text.InProgress.Merge" xml:space="preserve">合并操作进行中。点击【终止】回滚到操作前的状态。</x:String>
|
||||
<x:String x:Key="Text.InProgress.Rebase" xml:space="preserve">变基(Rebase)操作进行中。点击【终止】回滚到操作前的状态。</x:String>
|
||||
<x:String x:Key="Text.InProgress.Revert" xml:space="preserve">回滚提交操作进行中。点击【终止】回滚到操作前的状态。</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">交互式变基</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">目标分支 :</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">起始提交 :</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">向上移动</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">向下移动</x:String>
|
||||
<x:String x:Key="Text.Launcher" xml:space="preserve">Source Git</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">出错了</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系统提示</x:String>
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">與當前HEAD比較</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">與本地工作樹比較</x:String>
|
||||
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">複製提交指紋</x:String>
|
||||
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">互動式變基(rebase -i) ${0}$ 到此處</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">變基(rebase) ${0}$ 到此處</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">重置(reset) ${0}$ 到此處</x:String>
|
||||
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">回滾此提交</x:String>
|
||||
|
@ -290,6 +291,11 @@
|
|||
<x:String x:Key="Text.InProgress.Merge" xml:space="preserve">合併操作進行中。點選【終止】回滾到操作前的狀態。</x:String>
|
||||
<x:String x:Key="Text.InProgress.Rebase" xml:space="preserve">變基(Rebase)操作進行中。點選【終止】回滾到操作前的狀態。</x:String>
|
||||
<x:String x:Key="Text.InProgress.Revert" xml:space="preserve">回滾提交操作進行中。點選【終止】回滾到操作前的狀態。</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">互動式變基</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">目標分支 :</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">起始提交 :</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">向上移動</x:String>
|
||||
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">向下移動</x:String>
|
||||
<x:String x:Key="Text.Launcher" xml:space="preserve">Source Git</x:String>
|
||||
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">出錯了</x:String>
|
||||
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系統提示</x:String>
|
||||
|
|
|
@ -160,6 +160,11 @@
|
|||
<Setter Property="FontSize" Value="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="FlyoutPresenter">
|
||||
<Setter Property="MaxWidth" Value="1024"/>
|
||||
<Setter Property="MaxHeight" Value="768"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path">
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.FG1}"/>
|
||||
<Setter Property="Stretch" Value="Uniform"/>
|
||||
|
|
|
@ -277,6 +277,18 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(revert);
|
||||
|
||||
var interactiveRebase = new MenuItem();
|
||||
interactiveRebase.Header = new Views.NameHighlightedTextBlock("CommitCM.InteractiveRebase", current.Name);
|
||||
interactiveRebase.Icon = App.CreateMenuIcon("Icons.InteractiveRebase");
|
||||
interactiveRebase.IsVisible = current.Head != commit.SHA;
|
||||
interactiveRebase.Click += (o, e) =>
|
||||
{
|
||||
var dialog = new Views.InteractiveRebase() { DataContext = new InteractiveRebase(_repo, current, commit) };
|
||||
dialog.ShowDialog(App.GetTopLevel() as Window);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(interactiveRebase);
|
||||
}
|
||||
|
||||
if (current.Head != commit.SHA)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
|
@ -57,12 +58,24 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public override bool Continue()
|
||||
{
|
||||
var succ = base.Continue();
|
||||
var exec = Process.GetCurrentProcess().MainModule.FileName;
|
||||
var editor = $"\\\"{exec}\\\" --rebase-editor";
|
||||
|
||||
var succ = new Commands.Command()
|
||||
{
|
||||
WorkingDirectory = Repository,
|
||||
Context = Repository,
|
||||
Args = $"-c core.editor=\"{editor}\" rebase --continue",
|
||||
}.Exec();
|
||||
|
||||
if (succ)
|
||||
{
|
||||
var jobsFile = Path.Combine(_gitDir, "sourcegit_rebase_jobs.json");
|
||||
var rebaseMergeHead = Path.Combine(_gitDir, "REBASE_HEAD");
|
||||
var rebaseMergeFolder = Path.Combine(_gitDir, "rebase-merge");
|
||||
var rebaseApplyFolder = Path.Combine(_gitDir, "rebase-apply");
|
||||
if (File.Exists(jobsFile))
|
||||
File.Delete(jobsFile);
|
||||
if (File.Exists(rebaseMergeHead))
|
||||
File.Delete(rebaseMergeHead);
|
||||
if (Directory.Exists(rebaseMergeFolder))
|
||||
|
|
199
src/ViewModels/InteractiveRebase.cs
Normal file
199
src/ViewModels/InteractiveRebase.cs
Normal file
|
@ -0,0 +1,199 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
public class InteractiveRebaseItem : ObservableObject
|
||||
{
|
||||
public Models.Commit Commit
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Models.InteractiveRebaseAction Action
|
||||
{
|
||||
get => _action;
|
||||
private set => SetProperty(ref _action, value);
|
||||
}
|
||||
|
||||
public string Subject
|
||||
{
|
||||
get => _subject;
|
||||
private set => SetProperty(ref _subject, value);
|
||||
}
|
||||
|
||||
public string FullMessage
|
||||
{
|
||||
get => _fullMessage;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _fullMessage, value))
|
||||
{
|
||||
var normalized = value.ReplaceLineEndings("\n");
|
||||
var idx = normalized.IndexOf("\n\n", StringComparison.Ordinal);
|
||||
if (idx > 0)
|
||||
Subject = normalized.Substring(0, idx).ReplaceLineEndings(" ");
|
||||
else
|
||||
Subject = value.ReplaceLineEndings(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InteractiveRebaseItem(Models.Commit c, string message)
|
||||
{
|
||||
Commit = c;
|
||||
|
||||
_subject = c.Subject;
|
||||
_fullMessage = message;
|
||||
}
|
||||
|
||||
public void SetAction(object param)
|
||||
{
|
||||
Action = (Models.InteractiveRebaseAction)param;
|
||||
}
|
||||
|
||||
private Models.InteractiveRebaseAction _action = Models.InteractiveRebaseAction.Pick;
|
||||
private string _subject = string.Empty;
|
||||
private string _fullMessage = string.Empty;
|
||||
}
|
||||
|
||||
public class InteractiveRebase : ObservableObject
|
||||
{
|
||||
public Models.Branch Current
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Models.Commit On
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get => _isLoading;
|
||||
private set => SetProperty(ref _isLoading, value);
|
||||
}
|
||||
|
||||
public AvaloniaList<InteractiveRebaseItem> Items
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new AvaloniaList<InteractiveRebaseItem>();
|
||||
|
||||
public InteractiveRebaseItem SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedItem, value))
|
||||
DetailContext.Commit = value != null ? value.Commit : null;
|
||||
}
|
||||
}
|
||||
|
||||
public CommitDetail DetailContext
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public InteractiveRebase(Repository repo, Models.Branch current, Models.Commit on)
|
||||
{
|
||||
var repoPath = repo.FullPath;
|
||||
_repo = repo;
|
||||
|
||||
Current = current;
|
||||
On = on;
|
||||
IsLoading = true;
|
||||
DetailContext = new CommitDetail(repoPath);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var commits = new Commands.QueryCommits(repoPath, $"{on.SHA}...HEAD", false).Result();
|
||||
var messages = new Dictionary<string, string>();
|
||||
|
||||
foreach (var c in commits)
|
||||
{
|
||||
var fullMessage = new Commands.QueryCommitFullMessage(repoPath, c.SHA).Result();
|
||||
messages.Add(c.SHA, fullMessage);
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var list = new List<InteractiveRebaseItem>();
|
||||
foreach (var c in commits)
|
||||
list.Add(new InteractiveRebaseItem(c, messages[c.SHA]));
|
||||
|
||||
Items.AddRange(list);
|
||||
IsLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void MoveItemUp(InteractiveRebaseItem item)
|
||||
{
|
||||
var idx = Items.IndexOf(item);
|
||||
if (idx > 0)
|
||||
{
|
||||
var prev = Items[idx - 1];
|
||||
Items.RemoveAt(idx - 1);
|
||||
Items.Insert(idx, prev);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveItemDown(InteractiveRebaseItem item)
|
||||
{
|
||||
var idx = Items.IndexOf(item);
|
||||
if (idx < Items.Count - 1)
|
||||
{
|
||||
var next = Items[idx + 1];
|
||||
Items.RemoveAt(idx + 1);
|
||||
Items.Insert(idx, next);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> Start()
|
||||
{
|
||||
_repo.SetWatcherEnabled(false);
|
||||
|
||||
var saveFile = Path.Combine(_repo.GitDir, "sourcegit_rebase_jobs.json");
|
||||
var jobs = new List<Models.InteractiveRebaseJob>();
|
||||
for (int i = Items.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var item = Items[i];
|
||||
jobs.Add(new Models.InteractiveRebaseJob()
|
||||
{
|
||||
SHA = item.Commit.SHA,
|
||||
Action = item.Action,
|
||||
Message = item.FullMessage,
|
||||
});
|
||||
}
|
||||
File.WriteAllText(saveFile, JsonSerializer.Serialize(jobs, JsonCodeGen.Default.ListInteractiveRebaseJob));
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var succ = new Commands.InteractiveRebase(_repo.FullPath, On.SHA).Exec();
|
||||
if (succ)
|
||||
File.Delete(saveFile);
|
||||
|
||||
Dispatcher.UIThread.Invoke(() => _repo.SetWatcherEnabled(true));
|
||||
return succ;
|
||||
});
|
||||
}
|
||||
|
||||
private Repository _repo = null;
|
||||
private bool _isLoading = false;
|
||||
private InteractiveRebaseItem _selectedItem = null;
|
||||
}
|
||||
}
|
298
src/Views/InteractiveRebase.axaml
Normal file
298
src/Views/InteractiveRebase.axaml
Normal file
|
@ -0,0 +1,298 @@
|
|||
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
|
||||
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:m="using:SourceGit.Models"
|
||||
xmlns:vm="using:SourceGit.ViewModels"
|
||||
xmlns:c="using:SourceGit.Converters"
|
||||
xmlns:v="using:SourceGit.Views"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SourceGit.Views.InteractiveRebase"
|
||||
x:DataType="vm:InteractiveRebase"
|
||||
Title="{DynamicResource Text.InteractiveRebase}"
|
||||
Width="1080" Height="720"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Height="30">
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Background="{DynamicResource Brush.TitleBar}"
|
||||
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
|
||||
PointerPressed="BeginMoveWindow"/>
|
||||
|
||||
<Path Grid.Column="0"
|
||||
Width="14" Height="14"
|
||||
Margin="10,0,0,0"
|
||||
Data="{StaticResource Icons.InteractiveRebase}"
|
||||
IsVisible="{OnPlatform True, macOS=False}"/>
|
||||
|
||||
<Grid Grid.Column="0" Classes="caption_button_box" Margin="2,4,0,0" IsVisible="{OnPlatform False, macOS=True}">
|
||||
<Button Classes="caption_button_macos" Click="CloseWindow">
|
||||
<Grid>
|
||||
<Ellipse Fill="{DynamicResource Brush.MacOS.Close}"/>
|
||||
<Path Height="6" Width="6" Stretch="Fill" Fill="#404040" Stroke="#404040" StrokeThickness="1" Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Classes="bold"
|
||||
Text="{DynamicResource Text.InteractiveRebase}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Classes="caption_button"
|
||||
Click="CloseWindow"
|
||||
IsVisible="{OnPlatform True, macOS=False}">
|
||||
<Path Data="{StaticResource Icons.Window.Close}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Operation Information -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,*" Margin="8">
|
||||
<TextBlock Grid.Column="0" Text="{DynamicResource Text.InteractiveRebase.Target}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Path Grid.Column="1" Width="14" Height="14" Margin="8,0,0,0" Data="{StaticResource Icons.Branch}"/>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Current, Converter={x:Static c:BranchConverters.ToName}}" Margin="8,0,0,0"/>
|
||||
|
||||
<TextBlock Grid.Column="3" Margin="48,0,0,0" Text="{DynamicResource Text.InteractiveRebase.On}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
|
||||
<Path Grid.Column="4" Width="14" Height="14" Margin="8,8,0,0" Data="{StaticResource Icons.Commit}"/>
|
||||
<TextBlock Grid.Column="5" Classes="monospace" VerticalAlignment="Center" Text="{Binding On.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="8,0,0,0"/>
|
||||
<TextBlock Grid.Column="6" VerticalAlignment="Center" Text="{Binding On.Subject}" Margin="4,0,0,0" TextTrimming="CharacterEllipsis"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Body -->
|
||||
<Border Grid.Row="2" Margin="8,0,8,8" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border0}">
|
||||
<Grid RowDefinitions="*,3,*">
|
||||
<DataGrid Grid.Row="0"
|
||||
Background="{DynamicResource Brush.Contents}"
|
||||
ItemsSource="{Binding Items}"
|
||||
SelectionMode="Single"
|
||||
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
CanUserSortColumns="False"
|
||||
DragDrop.AllowDrop="True"
|
||||
IsReadOnly="True"
|
||||
HeadersVisibility="None"
|
||||
Focusable="False"
|
||||
RowHeight="28"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
KeyDown="OnDataGridKeyDown">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Header="Option">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<Button Opacity="1" Margin="4,0,0,0" Padding="8,2" Background="Transparent">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-4">
|
||||
<MenuItem InputGesture="P" Command="{Binding SetAction}" CommandParameter="{x:Static m:InteractiveRebaseAction.Pick}">
|
||||
<MenuItem.Icon>
|
||||
<Ellipse Width="14" Height="14" Fill="Green"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<Grid ColumnDefinitions="64,240">
|
||||
<TextBlock Grid.Column="0" Classes="monospace" Margin="4,0" Text="Pick"/>
|
||||
<TextBlock Grid.Column="1" Text="Use this commit" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem InputGesture="E" Command="{Binding SetAction}" CommandParameter="{x:Static m:InteractiveRebaseAction.Edit}">
|
||||
<MenuItem.Icon>
|
||||
<Ellipse Width="14" Height="14" Fill="Orange"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<Grid ColumnDefinitions="64,240">
|
||||
<TextBlock Grid.Column="0" Classes="monospace" Margin="4,0" Text="Edit"/>
|
||||
<TextBlock Grid.Column="1" Text="Stop for amending" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem InputGesture="R" Command="{Binding SetAction}" CommandParameter="{x:Static m:InteractiveRebaseAction.Reword}">
|
||||
<MenuItem.Icon>
|
||||
<Ellipse Width="14" Height="14" Fill="Orange"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<Grid ColumnDefinitions="64,240">
|
||||
<TextBlock Grid.Column="0" Classes="monospace" Margin="4,0" Text="Reword"/>
|
||||
<TextBlock Grid.Column="1" Text="Edit the commit message" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem InputGesture="S" Command="{Binding SetAction}" CommandParameter="{x:Static m:InteractiveRebaseAction.Squash}">
|
||||
<MenuItem.Icon>
|
||||
<Ellipse Width="14" Height="14" Fill="LightGray"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<Grid ColumnDefinitions="64,240">
|
||||
<TextBlock Grid.Column="0" Classes="monospace" Margin="4,0" Text="Squash"/>
|
||||
<TextBlock Grid.Column="1" Text="Meld into previous commit" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem InputGesture="F" Command="{Binding SetAction}" CommandParameter="{x:Static m:InteractiveRebaseAction.Fixup}">
|
||||
<MenuItem.Icon>
|
||||
<Ellipse Width="14" Height="14" Fill="LightGray"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<Grid ColumnDefinitions="64,240">
|
||||
<TextBlock Grid.Column="0" Classes="monospace" Margin="4,0" Text="Fixup"/>
|
||||
<TextBlock Grid.Column="1" Text="Like 'Squash' but discard message" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem InputGesture="D" Command="{Binding SetAction}" CommandParameter="{x:Static m:InteractiveRebaseAction.Drop}">
|
||||
<MenuItem.Icon>
|
||||
<Ellipse Width="14" Height="14" Fill="Red"/>
|
||||
</MenuItem.Icon>
|
||||
<MenuItem.Header>
|
||||
<Grid ColumnDefinitions="64,240">
|
||||
<TextBlock Grid.Column="0" Classes="monospace" Margin="4,0" Text="Drop"/>
|
||||
<TextBlock Grid.Column="1" Text="Remove commit" Foreground="{DynamicResource Brush.FG2}"/>
|
||||
</Grid>
|
||||
</MenuItem.Header>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Ellipse Grid.Column="0" Width="14" Height="14" Fill="{Binding Action, Converter={x:Static c:InteractiveRebaseActionConverters.ToIconBrush}}"/>
|
||||
<TextBlock Grid.Column="1" Classes="monospace" Margin="8,0" Text="{Binding Action, Converter={x:Static c:InteractiveRebaseActionConverters.ToName}}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="*" Header="SUBJECT">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="icon_button" IsVisible="{Binding Action, Converter={x:Static c:InteractiveRebaseActionConverters.CanEditMessage}}">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedLeft">
|
||||
<Panel Width="600" Height="200">
|
||||
<TextBox Grid.Row="0"
|
||||
CornerRadius="2"
|
||||
AcceptsReturn="True"
|
||||
VerticalContentAlignment="Top"
|
||||
Text="{Binding FullMessage, Mode=TwoWay}"
|
||||
v:AutoFocusBehaviour.IsEnabled="True"/>
|
||||
</Panel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Edit}"/>
|
||||
</Button>
|
||||
<TextBlock Classes="monospace" Text="{Binding Subject}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="AVATAR">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<v:Avatar Width="16" Height="16"
|
||||
Margin="16,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
User="{Binding Commit.Author}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn MaxWidth="100" Header="AUTHOR">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<TextBlock Classes="monospace" Text="{Binding Commit.Author.Name}" Margin="0,0,8,0"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="SHA">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<TextBlock Classes="monospace"
|
||||
Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
Margin="12,0"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="TIME">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<TextBlock Classes="monospace" Text="{Binding Commit.CommitterTimeStr}" Margin="8,0"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="32" Header="MOVE UP">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<Button Classes="icon_button" Click="OnMoveItemUp" ToolTip.Tip="{DynamicResource Text.InteractiveRebase.MoveUp}">
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Up}"/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Width="32" Header="MOVE DOWN">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
|
||||
<Button Classes="icon_button" Click="OnMoveItemDown" ToolTip.Tip="{DynamicResource Text.InteractiveRebase.MoveDown}">
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Down}"/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<v:LoadingIcon Grid.Row="0" Width="48" Height="48" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{Binding IsLoading}"/>
|
||||
|
||||
<GridSplitter Grid.Row="1"
|
||||
MinHeight="1"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
Background="Transparent"/>
|
||||
|
||||
<Border Grid.Row="2" Background="{DynamicResource Brush.Window}">
|
||||
<Path Width="128" Height="128"
|
||||
Data="{StaticResource Icons.Detail}"
|
||||
HorizontalAlignment="Center"
|
||||
Fill="{DynamicResource Brush.FG2}"/>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="2" IsVisible="{Binding SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<ContentControl Content="{Binding DetailContext}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:CommitDetail">
|
||||
<v:CommitDetail/>
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Options -->
|
||||
<Grid Grid.Row="3" ColumnDefinitions="*,Auto,Auto" Margin="8,0,8,8">
|
||||
<ProgressBar x:Name="Running"
|
||||
Grid.Column="0"
|
||||
Margin="0,0,32,0"
|
||||
Background="{DynamicResource Brush.FG2}"
|
||||
Foreground="{DynamicResource Brush.Accent}"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
IsVisible="False"/>
|
||||
<Button Grid.Column="1" Classes="flat primary" MinWidth="80" Content="{DynamicResource Text.Start}" Click="StartJobs"/>
|
||||
<Button Grid.Column="2" Classes="flat" MinWidth="80" Content="{DynamicResource Text.Cancel}" Click="CloseWindow"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</v:ChromelessWindow>
|
79
src/Views/InteractiveRebase.axaml.cs
Normal file
79
src/Views/InteractiveRebase.axaml.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class InteractiveRebase : ChromelessWindow
|
||||
{
|
||||
public InteractiveRebase()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void BeginMoveWindow(object sender, PointerPressedEventArgs e)
|
||||
{
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
|
||||
private void CloseWindow(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnMoveItemUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Control control && DataContext is ViewModels.InteractiveRebase vm)
|
||||
{
|
||||
vm.MoveItemUp(control.DataContext as ViewModels.InteractiveRebaseItem);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMoveItemDown(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Control control && DataContext is ViewModels.InteractiveRebase vm)
|
||||
{
|
||||
vm.MoveItemDown(control.DataContext as ViewModels.InteractiveRebaseItem);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDataGridKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
var datagrid = sender as DataGrid;
|
||||
var item = datagrid.SelectedItem as ViewModels.InteractiveRebaseItem;
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
var vm = DataContext as ViewModels.InteractiveRebase;
|
||||
if (e.Key == Key.P)
|
||||
item.SetAction(Models.InteractiveRebaseAction.Pick);
|
||||
else if (e.Key == Key.E)
|
||||
item.SetAction(Models.InteractiveRebaseAction.Edit);
|
||||
else if (e.Key == Key.R)
|
||||
item.SetAction(Models.InteractiveRebaseAction.Reword);
|
||||
else if (e.Key == Key.S)
|
||||
item.SetAction(Models.InteractiveRebaseAction.Squash);
|
||||
else if (e.Key == Key.F)
|
||||
item.SetAction(Models.InteractiveRebaseAction.Fixup);
|
||||
else if (e.Key == Key.D)
|
||||
item.SetAction(Models.InteractiveRebaseAction.Drop);
|
||||
else if (e.Key == Key.Up && e.KeyModifiers == KeyModifiers.Alt)
|
||||
vm.MoveItemUp(item);
|
||||
else if (e.Key == Key.Down && e.KeyModifiers == KeyModifiers.Alt)
|
||||
vm.MoveItemDown(item);
|
||||
}
|
||||
|
||||
private async void StartJobs(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Running.IsVisible = true;
|
||||
Running.IsIndeterminate = true;
|
||||
var vm = DataContext as ViewModels.InteractiveRebase;
|
||||
await vm.Start();
|
||||
Running.IsIndeterminate = false;
|
||||
Running.IsVisible = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@
|
|||
AcceptsReturn="True"
|
||||
VerticalContentAlignment="Top"
|
||||
Text="{Binding Message, Mode=TwoWay}"
|
||||
v:AutoFocusBehaviour.IsEnabled="True"/>
|
||||
v:AutoFocusBehaviour.IsEnabled="True"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
Loading…
Reference in a new issue