Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feature: allow merging multiple heads
  • Loading branch information
czarkoff committed Dec 8, 2024
commit 2518f34b864fa3af649bac0796a1eb1d1b944edf
6 changes: 4 additions & 2 deletions src/Commands/Merge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ namespace SourceGit.Commands
{
public class Merge : Command
{
public Merge(string repo, string source, string mode, Action<string> outputHandler)
public Merge(string repo, string source, string mode, string strategy, Action<string> outputHandler)
{
_outputHandler = outputHandler;
WorkingDirectory = repo;
Context = repo;
TraitErrorAsOutput = true;
Args = $"merge --progress {source} {mode}";
if (strategy != null)
strategy = string.Concat("--strategy=", strategy);
Args = $"merge --progress {strategy} {source} {mode}";
}

protected override void OnReadline(string line)
Expand Down
24 changes: 24 additions & 0 deletions src/Models/MergeStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;

namespace SourceGit.Models
{
public class MergeStrategy
{
public string Name { get; internal set; }
public string Desc { get; internal set; }
public string Arg { get; internal set; }

public static List<MergeStrategy> ForMultiple { get; private set; } = [
new MergeStrategy(string.Empty, "Let Git automatically select a strategy", null),
new MergeStrategy("Octopus", "Attempt merging multiple heads", "octopus"),
new MergeStrategy("Ours", "Record the merge without modifying the tree", "ours"),
];

public MergeStrategy(string n, string d, string a)
{
Name = n;
Desc = d;
Arg = a;
}
}
}
5 changes: 5 additions & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">Copy SHA</x:String>
<x:String x:Key="Text.CommitCM.CustomAction" xml:space="preserve">Custom Action</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.MergeMultiple" xml:space="preserve">Merge ...</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>
Expand Down Expand Up @@ -403,6 +404,10 @@
<x:String x:Key="Text.Merge.Into" xml:space="preserve">Into:</x:String>
<x:String x:Key="Text.Merge.Mode" xml:space="preserve">Merge Option:</x:String>
<x:String x:Key="Text.Merge.Source" xml:space="preserve">Source Branch:</x:String>
<x:String x:Key="Text.MergeMultiple" xml:space="preserve">Merge commits</x:String>
<x:String x:Key="Text.MergeMultiple.Commit" xml:space="preserve">Commit(s):</x:String>
<x:String x:Key="Text.MergeMultiple.CommitChanges" xml:space="preserve">Commit all changes</x:String>
<x:String x:Key="Text.MergeMultiple.Strategy" xml:space="preserve">Strategy:</x:String>
<x:String x:Key="Text.MoveRepositoryNode" xml:space="preserve">Move Repository Node</x:String>
<x:String x:Key="Text.MoveRepositoryNode.Target" xml:space="preserve">Select parent node for:</x:String>
<x:String x:Key="Text.Name" xml:space="preserve">Name:</x:String>
Expand Down
34 changes: 28 additions & 6 deletions src/ViewModels/Histories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,22 +228,28 @@ public ContextMenu MakeContextMenu(ListBox list)
{
var selected = new List<Models.Commit>();
var canCherryPick = true;
var canMerge = true;

foreach (var item in list.SelectedItems)
{
if (item is Models.Commit c)
{
selected.Add(c);

if (c.IsMerged || c.Parents.Count > 1)
if (c.IsMerged)
{
canMerge = false;
canCherryPick = false;
}
else if (c.Parents.Count > 1)
{
canCherryPick = false;
}
}
}

// Sort selected commits in order.
selected.Sort((l, r) =>
{
return _commits.IndexOf(r) - _commits.IndexOf(l);
});
selected.Sort((l, r) => _commits.IndexOf(r) - _commits.IndexOf(l));

var multipleMenu = new ContextMenu();

Expand All @@ -259,9 +265,25 @@ public ContextMenu MakeContextMenu(ListBox list)
e.Handled = true;
};
multipleMenu.Items.Add(cherryPickMultiple);
multipleMenu.Items.Add(new MenuItem() { Header = "-" });
}

if (canMerge)
{
var mergeMultiple = new MenuItem();
mergeMultiple.Header = App.Text("CommitCM.MergeMultiple");
mergeMultiple.Icon = App.CreateMenuIcon("Icons.Merge");
mergeMultiple.Click += (_, e) =>
{
if (PopupHost.CanCreatePopup())
PopupHost.ShowPopup(new MergeMultiple(_repo, selected));
e.Handled = true;
};
multipleMenu.Items.Add(mergeMultiple);
}

if (canCherryPick || canMerge)
multipleMenu.Items.Add(new MenuItem() { Header = "-" });

var saveToPatchMultiple = new MenuItem();
saveToPatchMultiple.Icon = App.CreateMenuIcon("Icons.Diff");
saveToPatchMultiple.Header = App.Text("CommitCM.SaveAsPatch");
Expand Down
2 changes: 1 addition & 1 deletion src/ViewModels/Merge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override Task<bool> Sure()

return Task.Run(() =>
{
var succ = new Commands.Merge(_repo.FullPath, Source, SelectedMode.Arg, SetProgressDescription).Exec();
var succ = new Commands.Merge(_repo.FullPath, Source, SelectedMode.Arg, null, SetProgressDescription).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
});
Expand Down
59 changes: 59 additions & 0 deletions src/ViewModels/MergeMultiple.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using SourceGit.Models;

namespace SourceGit.ViewModels
{
public class MergeMultiple : Popup
{
public List<string> Strategies = ["octopus", "ours"];

public List<Commit> Targets
{
get;
private set;
}

public bool AutoCommit
{
get;
set;
}

public MergeStrategy Strategy
{
get;
set;
}

public MergeMultiple(Repository repo, List<Commit> targets)
{
_repo = repo;
Targets = targets;
AutoCommit = true;
Strategy = MergeStrategy.ForMultiple.Find(s => s.Arg == null);
View = new Views.MergeMultiple() { DataContext = this };
}

public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = "Merge head(s) ...";

return Task.Run(() =>
{
var succ = new Commands.Merge(
_repo.FullPath,
string.Join(" ", Targets.ConvertAll(c => c.Decorators.Find(d => d.Type == DecoratorType.RemoteBranchHead || d.Type == DecoratorType.LocalBranchHead)?.Name ?? c.Decorators.Find(d => d.Type == DecoratorType.Tag)?.Name ?? c.SHA)),
AutoCommit ? string.Empty : "--no-commit",
Strategy?.Arg,
SetProgressDescription).Exec();

CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
});
}

private readonly Repository _repo = null;
}
}
2 changes: 1 addition & 1 deletion src/ViewModels/Pull.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public override Task<bool> Sure()
else
{
SetProgressDescription($"Merge {_selectedBranch.FriendlyName} into {_current.Name} ...");
rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "", SetProgressDescription).Exec();
rs = new Commands.Merge(_repo.FullPath, _selectedBranch.FriendlyName, "", null, SetProgressDescription).Exec();
}
}
else
Expand Down
79 changes: 79 additions & 0 deletions src/Views/MergeMultiple.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<UserControl 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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.MergeMultiple"
x:DataType="vm:MergeMultiple">
<StackPanel Orientation="Vertical" Margin="8,0">
<TextBlock FontSize="18"
Classes="bold"
Text="{DynamicResource Text.MergeMultiple}"/>
<Grid Margin="0,16,0,0" RowDefinitions="Auto,32,32" ColumnDefinitions="100,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.MergeMultiple.Commit}"/>
<ListBox Grid.Row="0" Grid.Column="1"
MinHeight="32" MaxHeight="100"
ItemsSource="{Binding Targets}"
Background="{DynamicResource Brush.Contents}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"
Padding="4"
CornerRadius="4"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="4,0"/>
<Setter Property="Height" Value="26"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
</ListBox.Styles>

<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>

<ListBox.ItemTemplate>
<DataTemplate DataType="m:Commit">
<Grid ColumnDefinitions="14,Auto,*">
<Path Grid.Column="0" Width="14" Height="14" Margin="0,8,0,0" Data="{StaticResource Icons.Commit}"/>
<TextBlock Grid.Column="1" FontFamily="{DynamicResource Fonts.Monospace}" VerticalAlignment="Center" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange" Margin="6,0,4,0"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Subject}" TextTrimming="CharacterEllipsis"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

<CheckBox Grid.Row="1" Grid.Column="1"
Content="{DynamicResource Text.MergeMultiple.CommitChanges}"
IsChecked="{Binding AutoCommit, Mode=TwoWay}"/>

<TextBlock Grid.Row="2" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.MergeMultiple.Strategy}"/>
<ComboBox Grid.Row="2" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding Source={x:Static m:MergeStrategy.ForMultiple}}"
SelectedItem="{Binding Strategy, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="m:MergeStrategy">
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Desc}" Margin="8,0,0,0" FontSize="11" Foreground="{DynamicResource Brush.FG2}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</StackPanel>
</UserControl>
12 changes: 12 additions & 0 deletions src/Views/MergeMultiple.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Avalonia.Controls;

namespace SourceGit.Views
{
public partial class MergeMultiple : UserControl
{
public MergeMultiple()
{
InitializeComponent();
}
}
}