mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-22 20:37:19 -08:00
refactor: rewrite amend behaviour (#300)
* toggle amend will show changes in HEAD commit * since discard is not compatible with staged changes in `amend` mode, we only allows user to discard unstaged changes
This commit is contained in:
parent
3c5a661fa0
commit
f55a576013
9 changed files with 261 additions and 86 deletions
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
new Clean(repo).Exec();
|
||||
}
|
||||
|
||||
public static void ChangesInWorkTree(string repo, List<Models.Change> changes)
|
||||
public static void Changes(string repo, List<Models.Change> changes)
|
||||
{
|
||||
var needClean = new List<string>();
|
||||
var needCheckout = new List<string>();
|
||||
|
@ -19,13 +19,9 @@ namespace SourceGit.Commands
|
|||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
||||
{
|
||||
needClean.Add(c.Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
needCheckout.Add(c.Path);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < needClean.Count; i += 10)
|
||||
|
@ -40,17 +36,5 @@ namespace SourceGit.Commands
|
|||
new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules").Exec();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ChangesInStaged(string repo, List<Models.Change> changes)
|
||||
{
|
||||
for (int i = 0; i < changes.Count; i += 10)
|
||||
{
|
||||
var count = Math.Min(10, changes.Count - i);
|
||||
var files = new List<string>();
|
||||
for (int j = 0; j < count; j++)
|
||||
files.Add(changes[i + j].Path);
|
||||
new Restore(repo, files, "--staged --worktree --recurse-submodules").Exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
90
src/Commands/QueryStagedChangesWithAmend.cs
Normal file
90
src/Commands/QueryStagedChangesWithAmend.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryStagedChangesWithAmend : Command
|
||||
{
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMTUX])\d{0,6}\t(.*)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} R\d{0,6}\t(.*\t.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
|
||||
public QueryStagedChangesWithAmend(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "diff-index --cached -M HEAD^";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var changes = new List<Models.Change>();
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var change = new Models.Change() {
|
||||
Path = match.Groups[3].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
},
|
||||
};
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
changes.Add(change);
|
||||
continue;
|
||||
}
|
||||
|
||||
match = REG_FORMAT1().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var change = new Models.Change() {
|
||||
Path = match.Groups[4].Value,
|
||||
DataForAmend = new Models.ChangeDataForAmend()
|
||||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
},
|
||||
};
|
||||
|
||||
var type = match.Groups[3].Value;
|
||||
switch (type)
|
||||
{
|
||||
case "A":
|
||||
change.Set(Models.ChangeState.Added);
|
||||
break;
|
||||
case "C":
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
break;
|
||||
case "D":
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "M":
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
break;
|
||||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "U":
|
||||
change.Set(Models.ChangeState.Unmerged);
|
||||
break;
|
||||
}
|
||||
changes.Add(change);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
97
src/Commands/UnstageChangesForAmend.cs
Normal file
97
src/Commands/UnstageChangesForAmend.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class UnstageChangesForAmend
|
||||
{
|
||||
public UnstageChangesForAmend(string repo, List<Models.Change> changes)
|
||||
{
|
||||
_repo = repo;
|
||||
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
_patchBuilder.Append("0 0000000000000000000000000000000000000000\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\0100644 ");
|
||||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||
_patchBuilder.Append("\t");
|
||||
_patchBuilder.Append(c.OriginalPath);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
else if (c.Index == Models.ChangeState.Added)
|
||||
{
|
||||
_patchBuilder.Append("0 0000000000000000000000000000000000000000\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
else if (c.Index == Models.ChangeState.Deleted)
|
||||
{
|
||||
_patchBuilder.Append("100644 ");
|
||||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||
_patchBuilder.Append("\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
_patchBuilder.Append(c.DataForAmend.FileMode);
|
||||
_patchBuilder.Append(" ");
|
||||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||
_patchBuilder.Append("\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Exec()
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.WorkingDirectory = _repo;
|
||||
starter.FileName = Native.OS.GitExecutable;
|
||||
starter.Arguments = "-c core.editor=true update-index --index-info";
|
||||
starter.UseShellExecute = false;
|
||||
starter.CreateNoWindow = true;
|
||||
starter.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
starter.RedirectStandardInput = true;
|
||||
starter.RedirectStandardOutput = false;
|
||||
starter.RedirectStandardError = true;
|
||||
|
||||
try
|
||||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
proc.StandardInput.Write(_patchBuilder.ToString());
|
||||
proc.StandardInput.Close();
|
||||
|
||||
var err = proc.StandardError.ReadToEnd();
|
||||
proc.WaitForExit();
|
||||
var rs = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
if (!rs)
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(_repo, err));
|
||||
|
||||
return rs;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(_repo, "Failed to unstage changes: " + e.Message);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string _repo = "";
|
||||
private StringBuilder _patchBuilder = new StringBuilder();
|
||||
}
|
||||
}
|
|
@ -22,12 +22,19 @@ namespace SourceGit.Models
|
|||
Untracked
|
||||
}
|
||||
|
||||
public class ChangeDataForAmend
|
||||
{
|
||||
public string FileMode { get; set; } = "";
|
||||
public string ObjectHash { get; set; } = "";
|
||||
}
|
||||
|
||||
public class Change
|
||||
{
|
||||
public ChangeState Index { get; set; }
|
||||
public ChangeState Index { get; set; } = ChangeState.None;
|
||||
public ChangeState WorkTree { get; set; } = ChangeState.None;
|
||||
public string Path { get; set; } = "";
|
||||
public string OriginalPath { get; set; } = "";
|
||||
public ChangeDataForAmend DataForAmend { get; set; } = null;
|
||||
|
||||
public bool IsConflit
|
||||
{
|
||||
|
|
|
@ -39,7 +39,11 @@ namespace SourceGit.Models
|
|||
}
|
||||
else
|
||||
{
|
||||
_extra = "--cached";
|
||||
if (change.DataForAmend != null)
|
||||
_extra = "--cached HEAD^";
|
||||
else
|
||||
_extra = "--cached";
|
||||
|
||||
_path = change.Path;
|
||||
_orgPath = change.OriginalPath;
|
||||
}
|
||||
|
|
|
@ -19,11 +19,10 @@ namespace SourceGit.ViewModels
|
|||
View = new Views.Discard { DataContext = this };
|
||||
}
|
||||
|
||||
public Discard(Repository repo, List<Models.Change> changes, bool isUnstaged)
|
||||
public Discard(Repository repo, List<Models.Change> changes)
|
||||
{
|
||||
_repo = repo;
|
||||
_changes = changes;
|
||||
_isUnstaged = isUnstaged;
|
||||
|
||||
if (_changes == null)
|
||||
Mode = new Models.Null();
|
||||
|
@ -44,10 +43,8 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
if (_changes == null)
|
||||
Commands.Discard.All(_repo.FullPath);
|
||||
else if (_isUnstaged)
|
||||
Commands.Discard.ChangesInWorkTree(_repo.FullPath, _changes);
|
||||
else
|
||||
Commands.Discard.ChangesInStaged(_repo.FullPath, _changes);
|
||||
Commands.Discard.Changes(_repo.FullPath, _changes);
|
||||
|
||||
CallUIThread(() =>
|
||||
{
|
||||
|
@ -61,6 +58,5 @@ namespace SourceGit.ViewModels
|
|||
|
||||
private readonly Repository _repo = null;
|
||||
private readonly List<Models.Change> _changes = null;
|
||||
private readonly bool _isUnstaged = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,21 +88,26 @@ namespace SourceGit.ViewModels
|
|||
get => _useAmend;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useAmend, value) && value)
|
||||
if (SetProperty(ref _useAmend, value))
|
||||
{
|
||||
var currentBranch = _repo.CurrentBranch;
|
||||
if (currentBranch == null)
|
||||
if (value)
|
||||
{
|
||||
App.RaiseException(_repo.FullPath, "No commits to amend!!!");
|
||||
_useAmend = false;
|
||||
OnPropertyChanged();
|
||||
return;
|
||||
var currentBranch = _repo.CurrentBranch;
|
||||
if (currentBranch == null)
|
||||
{
|
||||
App.RaiseException(_repo.FullPath, "No commits to amend!!!");
|
||||
_useAmend = false;
|
||||
OnPropertyChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, currentBranch.Head).Result();
|
||||
}
|
||||
|
||||
CommitMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, currentBranch.Head).Result();
|
||||
Staged = GetStagedChanges();
|
||||
SelectedStaged = [];
|
||||
OnPropertyChanged(nameof(IsCommitWithPushVisible));
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(IsCommitWithPushVisible));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,6 +221,8 @@ namespace SourceGit.ViewModels
|
|||
|
||||
public bool SetData(List<Models.Change> changes)
|
||||
{
|
||||
_cached = changes;
|
||||
|
||||
var unstaged = new List<Models.Change>();
|
||||
var staged = new List<Models.Change>();
|
||||
var selectedUnstaged = new List<Models.Change>();
|
||||
|
@ -237,17 +244,6 @@ namespace SourceGit.ViewModels
|
|||
var hasConflict = false;
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.Index == Models.ChangeState.Modified
|
||||
|| c.Index == Models.ChangeState.Added
|
||||
|| c.Index == Models.ChangeState.Deleted
|
||||
|| c.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
staged.Add(c);
|
||||
|
||||
if (lastSelectedStaged.Contains(c.Path))
|
||||
selectedStaged.Add(c);
|
||||
}
|
||||
|
||||
if (c.WorkTree != Models.ChangeState.None)
|
||||
{
|
||||
unstaged.Add(c);
|
||||
|
@ -258,6 +254,13 @@ namespace SourceGit.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
staged = GetStagedChanges();
|
||||
foreach (var c in staged)
|
||||
{
|
||||
if (lastSelectedStaged.Contains(c.Path))
|
||||
selectedStaged.Add(c);
|
||||
}
|
||||
|
||||
_count = changes.Count;
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
|
@ -358,7 +361,11 @@ namespace SourceGit.ViewModels
|
|||
SetDetail(null);
|
||||
IsUnstaging = true;
|
||||
_repo.SetWatcherEnabled(false);
|
||||
if (changes.Count == _staged.Count)
|
||||
if (_useAmend)
|
||||
{
|
||||
await Task.Run(() => new Commands.UnstageChangesForAmend(_repo.FullPath, changes).Exec());
|
||||
}
|
||||
else if (changes.Count == _staged.Count)
|
||||
{
|
||||
await Task.Run(() => new Commands.Reset(_repo.FullPath).Exec());
|
||||
}
|
||||
|
@ -376,24 +383,14 @@ namespace SourceGit.ViewModels
|
|||
IsUnstaging = false;
|
||||
}
|
||||
|
||||
public void Discard(List<Models.Change> changes, bool isUnstaged)
|
||||
public void Discard(List<Models.Change> changes)
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
{
|
||||
if (isUnstaged)
|
||||
{
|
||||
if (changes.Count == _unstaged.Count && _staged.Count == 0)
|
||||
PopupHost.ShowPopup(new Discard(_repo));
|
||||
else
|
||||
PopupHost.ShowPopup(new Discard(_repo, changes, true));
|
||||
}
|
||||
if (changes.Count == _unstaged.Count && _staged.Count == 0)
|
||||
PopupHost.ShowPopup(new Discard(_repo));
|
||||
else
|
||||
{
|
||||
if (changes.Count == _staged.Count && _unstaged.Count == 0)
|
||||
PopupHost.ShowPopup(new Discard(_repo));
|
||||
else
|
||||
PopupHost.ShowPopup(new Discard(_repo, changes, false));
|
||||
}
|
||||
PopupHost.ShowPopup(new Discard(_repo, changes));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -491,7 +488,7 @@ namespace SourceGit.ViewModels
|
|||
discard.Icon = App.CreateMenuIcon("Icons.Undo");
|
||||
discard.Click += (_, e) =>
|
||||
{
|
||||
Discard(_selectedUnstaged, true);
|
||||
Discard(_selectedUnstaged);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -815,7 +812,7 @@ namespace SourceGit.ViewModels
|
|||
discard.Icon = App.CreateMenuIcon("Icons.Undo");
|
||||
discard.Click += (_, e) =>
|
||||
{
|
||||
Discard(_selectedUnstaged, true);
|
||||
Discard(_selectedUnstaged);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
@ -904,15 +901,6 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
|
||||
var discard = new MenuItem();
|
||||
discard.Header = App.Text("FileCM.Discard");
|
||||
discard.Icon = App.CreateMenuIcon("Icons.Undo");
|
||||
discard.Click += (_, e) =>
|
||||
{
|
||||
Discard(_selectedStaged, false);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var stash = new MenuItem();
|
||||
stash.Header = App.Text("FileCM.Stash");
|
||||
stash.Icon = App.CreateMenuIcon("Icons.Stashes");
|
||||
|
@ -971,7 +959,6 @@ namespace SourceGit.ViewModels
|
|||
menu.Items.Add(openWith);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
menu.Items.Add(unstage);
|
||||
menu.Items.Add(discard);
|
||||
menu.Items.Add(stash);
|
||||
menu.Items.Add(patch);
|
||||
menu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
@ -1071,15 +1058,6 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
|
||||
var discard = new MenuItem();
|
||||
discard.Header = App.Text("FileCM.DiscardMulti", _selectedStaged.Count);
|
||||
discard.Icon = App.CreateMenuIcon("Icons.Undo");
|
||||
discard.Click += (_, e) =>
|
||||
{
|
||||
Discard(_selectedStaged, false);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var stash = new MenuItem();
|
||||
stash.Header = App.Text("FileCM.StashMulti", _selectedStaged.Count);
|
||||
stash.Icon = App.CreateMenuIcon("Icons.Stashes");
|
||||
|
@ -1118,7 +1096,6 @@ namespace SourceGit.ViewModels
|
|||
};
|
||||
|
||||
menu.Items.Add(unstage);
|
||||
menu.Items.Add(discard);
|
||||
menu.Items.Add(stash);
|
||||
menu.Items.Add(patch);
|
||||
}
|
||||
|
@ -1162,6 +1139,25 @@ namespace SourceGit.ViewModels
|
|||
return menu;
|
||||
}
|
||||
|
||||
private List<Models.Change> GetStagedChanges()
|
||||
{
|
||||
if (_useAmend)
|
||||
{
|
||||
return new Commands.QueryStagedChangesWithAmend(_repo.FullPath).Result();
|
||||
}
|
||||
else
|
||||
{
|
||||
var rs = new List<Models.Change>();
|
||||
foreach (var c in _cached)
|
||||
{
|
||||
if (c.Index != Models.ChangeState.None &&
|
||||
c.Index != Models.ChangeState.Untracked)
|
||||
rs.Add(c);
|
||||
}
|
||||
return rs;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDetail(Models.Change change)
|
||||
{
|
||||
if (_isLoadingData)
|
||||
|
@ -1287,6 +1283,7 @@ namespace SourceGit.ViewModels
|
|||
private bool _isCommitting = false;
|
||||
private bool _useAmend = false;
|
||||
private bool _canCommitWithPush = false;
|
||||
private List<Models.Change> _cached = [];
|
||||
private List<Models.Change> _unstaged = [];
|
||||
private List<Models.Change> _staged = [];
|
||||
private List<Models.Change> _selectedUnstaged = [];
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
<StackPanel x:Name="Popup" IsVisible="False" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right" Effect="drop-shadow(0 0 6 #40000000)">
|
||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Stage}" Click="OnStageChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange}"/>
|
||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Unstage}" Click="OnUnstageChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange, Converter={x:Static BoolConverters.Not}}"/>
|
||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Discard}" Margin="8,0,0,0" Click="OnDiscardChunk"/>
|
||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Discard}" Margin="8,0,0,0" Click="OnDiscardChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
@ -1273,7 +1273,7 @@ namespace SourceGit.Views
|
|||
return;
|
||||
|
||||
var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy;
|
||||
workcopy?.Discard(new List<Models.Change> { change }, diff.Option.IsUnstaged);
|
||||
workcopy?.Discard(new List<Models.Change> { change });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1303,7 +1303,7 @@ namespace SourceGit.Views
|
|||
diff.GeneratePatchFromSelectionSingleSide(change, treeGuid, selection, true, chunk.IsOldSide, tmpFile);
|
||||
}
|
||||
|
||||
new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", diff.Option.IsUnstaged ? "--reverse" : "--index --reverse").Exec();
|
||||
new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--reverse").Exec();
|
||||
File.Delete(tmpFile);
|
||||
|
||||
repo.MarkWorkingCopyDirtyManually();
|
||||
|
|
Loading…
Reference in a new issue