mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
feature: merge multiple heads (#793)
* feature: allow merging multiple heads * feature: allow merging multiple branches from branch tree
This commit is contained in:
parent
c9c7fb5d5b
commit
dce33fdf60
11 changed files with 232 additions and 10 deletions
|
@ -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)
|
||||
|
|
24
src/Models/MergeStrategy.cs
Normal file
24
src/Models/MergeStrategy.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,7 @@
|
|||
<x:String x:Key="Text.BranchCM.FetchInto" xml:space="preserve">Fetch ${0}$ into ${1}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Finish" xml:space="preserve">Git Flow - Finish ${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Merge" xml:space="preserve">Merge ${0}$ into ${1}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.MergeMultiBranches" xml:space="preserve">Merge selected {0} branches</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Pull" xml:space="preserve">Pull ${0}$</x:String>
|
||||
<x:String x:Key="Text.BranchCM.PullInto" xml:space="preserve">Pull ${0}$ into ${1}$...</x:String>
|
||||
<x:String x:Key="Text.BranchCM.Push" xml:space="preserve">Push ${0}$</x:String>
|
||||
|
@ -110,6 +111,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>
|
||||
|
@ -404,6 +406,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>
|
||||
|
|
|
@ -228,22 +228,28 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
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();
|
||||
|
||||
|
@ -259,9 +265,25 @@ namespace SourceGit.ViewModels
|
|||
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");
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace SourceGit.ViewModels
|
|||
|
||||
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;
|
||||
});
|
||||
|
|
59
src/ViewModels/MergeMultiple.cs
Normal file
59
src/ViewModels/MergeMultiple.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -172,7 +172,7 @@ namespace SourceGit.ViewModels
|
|||
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
|
||||
|
|
|
@ -13,6 +13,7 @@ using Avalonia.Media.Imaging;
|
|||
using Avalonia.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using SourceGit.Models;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
{
|
||||
|
@ -950,6 +951,12 @@ namespace SourceGit.ViewModels
|
|||
PopupHost.ShowPopup(new DeleteMultipleBranches(this, branches, isLocal));
|
||||
}
|
||||
|
||||
public void MergeMultipleBranches(List<Models.Branch> branches)
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new MergeMultiple(this, branches.ConvertAll(b => _histories?.Commits?.Find(c => c.SHA == b.Head))));
|
||||
}
|
||||
|
||||
public void CreateNewTag()
|
||||
{
|
||||
if (_currentBranch == null)
|
||||
|
|
|
@ -405,6 +405,17 @@ namespace SourceGit.Views
|
|||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(deleteMulti);
|
||||
|
||||
var mergeMulti = new MenuItem();
|
||||
mergeMulti.Header = App.Text("BranchCM.MergeMultiBranches", branches.Count);
|
||||
mergeMulti.Icon = App.CreateMenuIcon("Icons.Merge");
|
||||
mergeMulti.Click += (_, ev) =>
|
||||
{
|
||||
repo.MergeMultipleBranches(branches);
|
||||
ev.Handled = true;
|
||||
};
|
||||
menu.Items.Add(mergeMulti);
|
||||
|
||||
menu?.Open(this);
|
||||
}
|
||||
}
|
||||
|
|
79
src/Views/MergeMultiple.axaml
Normal file
79
src/Views/MergeMultiple.axaml
Normal file
|
@ -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
src/Views/MergeMultiple.axaml.cs
Normal file
12
src/Views/MergeMultiple.axaml.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace SourceGit.Views
|
||||
{
|
||||
public partial class MergeMultiple : UserControl
|
||||
{
|
||||
public MergeMultiple()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue