Add bare-minimum support for multi-line stash-message

With these changes, a multi-line message cannow be copied-into, viewed and copied-out-of a Stash:
* Simplify parsing in QueryStashes, by passing the `-z` argument to `git stash list` for item separation.
* Query full Message (`%B`) from stash (list-item still shows only first (Subject) line, tooltip shows full Message).
* Add "Copy Message" command in stash-item context-menu.
* Make the "Message:" text-box in StashChanges dialog support multiple lines, without changing the layout.
This commit is contained in:
Göran W 2025-06-18 06:33:09 +02:00
parent ae46728bbc
commit 81e4541d43
4 changed files with 47 additions and 36 deletions

View file

@ -9,7 +9,7 @@ namespace SourceGit.Commands
{ {
WorkingDirectory = repo; WorkingDirectory = repo;
Context = repo; Context = repo;
Args = "stash list --format=%H%n%P%n%ct%n%gd%n%s"; Args = "stash list -z --format=%H%n%P%n%ct%n%gd%n%B";
} }
public List<Models.Stash> Result() public List<Models.Stash> Result()
@ -19,55 +19,54 @@ namespace SourceGit.Commands
if (!rs.IsSuccess) if (!rs.IsSuccess)
return outs; return outs;
var nextPartIdx = 0; var stashes = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
var start = 0; foreach (var stash in stashes)
var end = rs.StdOut.IndexOf('\n', start);
while (end > 0)
{ {
var line = rs.StdOut.Substring(start, end - start); var current = new Models.Stash();
switch (nextPartIdx) var nextPartIdx = 0;
var start = 0;
var end = stash.IndexOf('\n', start);
while (end > 0 && nextPartIdx < 4)
{ {
case 0: var line = stash.Substring(start, end - start);
_current = new Models.Stash() { SHA = line };
outs.Add(_current); switch (nextPartIdx)
break; {
case 1: case 0:
ParseParent(line); current = new Models.Stash() { SHA = line };
break; break;
case 2: case 1:
_current.Time = ulong.Parse(line); ParseParent(line, ref current);
break; break;
case 3: case 2:
_current.Name = line; current.Time = ulong.Parse(line);
break; break;
case 4: case 3:
_current.Message = line; current.Name = line;
break; break;
}
nextPartIdx++;
start = end + 1;
end = stash.IndexOf('\n', start);
} }
nextPartIdx++; if (start < stash.Length)
if (nextPartIdx > 4) current.Message = stash.Substring(start);
nextPartIdx = 0;
start = end + 1; outs.Add(current);
end = rs.StdOut.IndexOf('\n', start);
} }
if (start < rs.StdOut.Length)
_current.Message = rs.StdOut.Substring(start);
return outs; return outs;
} }
private void ParseParent(string data) private void ParseParent(string data, ref Models.Stash current)
{ {
if (data.Length < 8) if (data.Length < 8)
return; return;
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries)); current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
} }
private Models.Stash _current = null;
} }
} }

View file

@ -697,6 +697,7 @@
<x:String x:Key="Text.Stash.TipForSelectedFiles" xml:space="preserve">Both staged and unstaged changes of selected file(s) will be stashed!!!</x:String> <x:String x:Key="Text.Stash.TipForSelectedFiles" xml:space="preserve">Both staged and unstaged changes of selected file(s) will be stashed!!!</x:String>
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Stash Local Changes</x:String> <x:String x:Key="Text.Stash.Title" xml:space="preserve">Stash Local Changes</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Apply</x:String> <x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Apply</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">Copy Message</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Drop</x:String> <x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Drop</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Save as Patch...</x:String> <x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Save as Patch...</x:String>
<x:String x:Key="Text.StashDropConfirm" xml:space="preserve">Drop Stash</x:String> <x:String x:Key="Text.StashDropConfirm" xml:space="preserve">Drop Stash</x:String>

View file

@ -194,11 +194,21 @@ namespace SourceGit.ViewModels
e.Handled = true; e.Handled = true;
}; };
var copy = new MenuItem();
copy.Header = App.Text("StashCM.CopyMessage");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Click += (_, ev) =>
{
App.CopyText(stash.Message);
ev.Handled = true;
};
var menu = new ContextMenu(); var menu = new ContextMenu();
menu.Items.Add(apply); menu.Items.Add(apply);
menu.Items.Add(drop); menu.Items.Add(drop);
menu.Items.Add(new MenuItem { Header = "-" }); menu.Items.Add(new MenuItem { Header = "-" });
menu.Items.Add(patch); menu.Items.Add(patch);
menu.Items.Add(copy);
return menu; return menu;
} }

View file

@ -20,6 +20,7 @@
<TextBox Grid.Row="0" Grid.Column="1" <TextBox Grid.Row="0" Grid.Column="1"
Height="26" Height="26"
CornerRadius="3" CornerRadius="3"
AcceptsReturn="True"
Text="{Binding Message, Mode=TwoWay}" Text="{Binding Message, Mode=TwoWay}"
Watermark="{DynamicResource Text.Stash.Message.Placeholder}" Watermark="{DynamicResource Text.Stash.Message.Placeholder}"
v:AutoFocusBehaviour.IsEnabled="True"/> v:AutoFocusBehaviour.IsEnabled="True"/>