mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-21 12:15:00 +00: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
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue