Skip to content

Commit e1be096

Browse files
Feature: Allow deleting local Git branches (#14059)
1 parent 154b4bd commit e1be096

File tree

9 files changed

+144
-27
lines changed

9 files changed

+144
-27
lines changed

src/Files.App/Converters/MultiBooleanConverter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public static Boolean OrAndConvert(bool a, bool b, bool c)
2222
public static Visibility OrConvertToVisibility(bool a, bool b)
2323
=> (a || b) ? Visibility.Visible : Visibility.Collapsed;
2424

25+
public static Visibility NorConvertToVisibility(bool a, bool b)
26+
=> !(a || b) ? Visibility.Visible : Visibility.Collapsed;
27+
2528
public static Visibility OrNotConvertToVisibility(bool a, bool b)
2629
=> OrConvertToVisibility(a, !b);
2730
}

src/Files.App/Data/Items/BranchItem.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44
namespace Files.App.Data.Items
55
{
6-
public record BranchItem(string Name, bool IsRemote, int? AheadBy, int? BehindBy);
6+
public record BranchItem(string Name, bool IsHead, bool IsRemote, int? AheadBy, int? BehindBy);
77
}

src/Files.App/Data/Models/DirectoryPropertiesViewModel.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public class DirectoryPropertiesViewModel : ObservableObject
1414

1515
private string? _gitRepositoryPath;
1616

17-
private readonly ObservableCollection<string> _localBranches = new();
17+
private readonly ObservableCollection<BranchItem> _localBranches = new();
1818

19-
private readonly ObservableCollection<string> _remoteBranches = new();
19+
private readonly ObservableCollection<BranchItem> _remoteBranches = new();
2020

2121
public bool IsBranchesFlyoutExpaned { get; set; } = false;
2222

@@ -43,9 +43,9 @@ public int SelectedBranchIndex
4343
if (SetProperty(ref _SelectedBranchIndex, value) &&
4444
value != -1 &&
4545
(value != ACTIVE_BRANCH_INDEX || !_ShowLocals) &&
46-
value < BranchesNames.Count)
46+
value < Branches.Count)
4747
{
48-
CheckoutRequested?.Invoke(this, BranchesNames[value]);
48+
CheckoutRequested?.Invoke(this, Branches[value].Name);
4949
}
5050
}
5151
}
@@ -58,7 +58,7 @@ public bool ShowLocals
5858
{
5959
if (SetProperty(ref _ShowLocals, value))
6060
{
61-
OnPropertyChanged(nameof(BranchesNames));
61+
OnPropertyChanged(nameof(Branches));
6262

6363
if (value)
6464
SelectedBranchIndex = ACTIVE_BRANCH_INDEX;
@@ -80,7 +80,7 @@ public string ExtendedStatusInfo
8080
set => SetProperty(ref _ExtendedStatusInfo, value);
8181
}
8282

83-
public ObservableCollection<string> BranchesNames => _ShowLocals
83+
public ObservableCollection<BranchItem> Branches => _ShowLocals
8484
? _localBranches
8585
: _remoteBranches;
8686

@@ -91,7 +91,7 @@ public string ExtendedStatusInfo
9191
public DirectoryPropertiesViewModel()
9292
{
9393
NewBranchCommand = new AsyncRelayCommand(()
94-
=> GitHelpers.CreateNewBranchAsync(_gitRepositoryPath!, _localBranches[ACTIVE_BRANCH_INDEX]));
94+
=> GitHelpers.CreateNewBranchAsync(_gitRepositoryPath!, _localBranches[ACTIVE_BRANCH_INDEX].Name));
9595
}
9696

9797
public void UpdateGitInfo(bool isGitRepository, string? repositoryPath, BranchItem? head)
@@ -128,12 +128,17 @@ public async Task LoadBranches()
128128
foreach (var branch in branches)
129129
{
130130
if (branch.IsRemote)
131-
_remoteBranches.Add(branch.Name);
131+
_remoteBranches.Add(branch);
132132
else
133-
_localBranches.Add(branch.Name);
133+
_localBranches.Add(branch);
134134
}
135135

136136
SelectedBranchIndex = ShowLocals ? ACTIVE_BRANCH_INDEX : -1;
137137
}
138+
139+
public Task ExecuteDeleteBranch(string? branchName)
140+
{
141+
return GitHelpers.DeleteBranchAsync(_gitRepositoryPath, GitBranchDisplayName, branchName);
142+
}
138143
}
139144
}

src/Files.App/Helpers/Dialog/DynamicDialogFactory.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,25 @@ public static DynamicDialog GetFor_GitCannotInitializeqRepositoryHere()
291291
DynamicButtons = DynamicDialogButtons.Primary
292292
});
293293
}
294+
295+
public static DynamicDialog GetFor_DeleteGitBranchConfirmation(string branchName)
296+
{
297+
DynamicDialog dialog = null!;
298+
dialog = new DynamicDialog(new DynamicDialogViewModel()
299+
{
300+
TitleText = "GitDeleteBranch".GetLocalizedResource(),
301+
SubtitleText = string.Format("GitDeleteBranchSubtitle".GetLocalizedResource(), branchName),
302+
PrimaryButtonText = "OK".GetLocalizedResource(),
303+
CloseButtonText = "Cancel".GetLocalizedResource(),
304+
AdditionalData = true,
305+
CloseButtonAction = (vm, e) =>
306+
{
307+
dialog.ViewModel.AdditionalData = false;
308+
vm.HideDialog();
309+
}
310+
});
311+
312+
return dialog;
313+
}
294314
}
295315
}

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3614,6 +3614,12 @@
36143614
<data name="FailedToSetBackground" xml:space="preserve">
36153615
<value>Failed to set the background wallpaper</value>
36163616
</data>
3617+
<data name="GitDeleteBranch" xml:space="preserve">
3618+
<value>Delete Git branch</value>
3619+
</data>
3620+
<data name="GitDeleteBranchSubtitle" xml:space="preserve">
3621+
<value>Are you sure you want to permanently delete "{0}" branch?</value>
3622+
</data>
36173623
<data name="ConnectedToGitHub" xml:space="preserve">
36183624
<value>Connected to GitHub</value>
36193625
</data>

src/Files.App/UserControls/StatusBarControl.xaml

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
66
xmlns:converters="using:Files.App.Converters"
77
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
8+
xmlns:data="using:Files.App.Data.Items"
89
xmlns:helpers="using:Files.App.Helpers"
910
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
1011
xmlns:usercontrols="using:Files.App.UserControls"
@@ -350,9 +351,37 @@
350351
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
351352
IsItemClickEnabled="True"
352353
ItemClick="BranchesList_ItemClick"
353-
ItemsSource="{x:Bind DirectoryPropertiesViewModel.BranchesNames, Mode=OneWay}"
354+
ItemsSource="{x:Bind DirectoryPropertiesViewModel.Branches, Mode=OneWay}"
354355
SelectedIndex="{x:Bind DirectoryPropertiesViewModel.SelectedBranchIndex, Mode=TwoWay}"
355-
SelectionMode="Single" />
356+
SelectionMode="Single">
357+
358+
<ListView.ItemTemplate>
359+
<DataTemplate x:DataType="data:BranchItem">
360+
<Grid HorizontalAlignment="Stretch">
361+
<Grid.ColumnDefinitions>
362+
<ColumnDefinition />
363+
<ColumnDefinition Width="Auto" />
364+
</Grid.ColumnDefinitions>
365+
366+
<TextBlock
367+
VerticalAlignment="Center"
368+
Text="{x:Bind Name}"
369+
TextTrimming="CharacterEllipsis" />
370+
<Button
371+
Grid.Column="1"
372+
AutomationProperties.Name="{helpers:ResourceString Name=Delete}"
373+
Background="Transparent"
374+
BorderBrush="Transparent"
375+
Click="DeleteBranch_Click"
376+
ToolTipService.ToolTip="{helpers:ResourceString Name=Delete}"
377+
Visibility="{x:Bind converters:MultiBooleanConverter.NorConvertToVisibility(IsHead, IsRemote)}">
378+
<FontIcon FontSize="12" Glyph="&#xE74D;" />
379+
</Button>
380+
</Grid>
381+
</DataTemplate>
382+
</ListView.ItemTemplate>
383+
384+
</ListView>
356385
</Grid>
357386
</Flyout>
358387
</Button.Flyout>

src/Files.App/UserControls/StatusBarControl.xaml.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,14 @@ private void BranchesFlyout_Closing(object _, object e)
6868

6969
DirectoryPropertiesViewModel.IsBranchesFlyoutExpaned = false;
7070
}
71+
72+
private async void DeleteBranch_Click(object sender, RoutedEventArgs e)
73+
{
74+
if (DirectoryPropertiesViewModel is null)
75+
return;
76+
77+
BranchesFlyout.Hide();
78+
await DirectoryPropertiesViewModel.ExecuteDeleteBranch(((BranchItem)((Button)sender).DataContext).Name);
79+
}
7180
}
7281
}

src/Files.App/Utils/Git/GitHelpers.cs

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,14 @@ public static async Task<BranchItem[]> GetBranchesNames(string? path)
138138
.OrderByDescending(b => b.Tip?.Committer.When)
139139
.Take(MAX_NUMBER_OF_BRANCHES)
140140
.OrderByDescending(b => b.IsCurrentRepositoryHead)
141-
.Select(b => new BranchItem(b.FriendlyName, b.IsRemote, TryGetTrackingDetails(b)?.AheadBy ?? 0, TryGetTrackingDetails(b)?.BehindBy ?? 0))
141+
.Select(b => new BranchItem(b.FriendlyName, b.IsCurrentRepositoryHead, b.IsRemote, TryGetTrackingDetails(b)?.AheadBy ?? 0, TryGetTrackingDetails(b)?.BehindBy ?? 0))
142142
.ToArray();
143-
}
143+
}
144144
catch (Exception)
145145
{
146146
result = GitOperationResult.GenericError;
147147
}
148-
148+
149149
return (result, branches);
150150
});
151151

@@ -165,7 +165,13 @@ public static async Task<BranchItem[]> GetBranchesNames(string? path)
165165
using var repository = new Repository(path);
166166
var branch = GetValidBranches(repository.Branches).FirstOrDefault(b => b.IsCurrentRepositoryHead);
167167
if (branch is not null)
168-
head = new BranchItem(branch.FriendlyName, branch.IsRemote, TryGetTrackingDetails(branch)?.AheadBy ?? 0, TryGetTrackingDetails(branch)?.BehindBy ?? 0);
168+
head = new BranchItem(
169+
branch.FriendlyName,
170+
branch.IsCurrentRepositoryHead,
171+
branch.IsRemote,
172+
TryGetTrackingDetails(branch)?.AheadBy ?? 0,
173+
TryGetTrackingDetails(branch)?.BehindBy ?? 0
174+
);
169175
}
170176
catch
171177
{
@@ -280,6 +286,45 @@ await Checkout(repositoryPath, viewModel.BasedOn))
280286
IsExecutingGitAction = false;
281287
}
282288

289+
public static async Task DeleteBranchAsync(string? repositoryPath, string? activeBranch, string? branchToDelete)
290+
{
291+
Analytics.TrackEvent("Triggered delete git branch");
292+
293+
if (string.IsNullOrWhiteSpace(repositoryPath) ||
294+
string.IsNullOrWhiteSpace(activeBranch) ||
295+
string.IsNullOrWhiteSpace(branchToDelete) ||
296+
activeBranch.Equals(branchToDelete, StringComparison.OrdinalIgnoreCase) ||
297+
!Repository.IsValid(repositoryPath))
298+
{
299+
return;
300+
}
301+
302+
var dialog = DynamicDialogFactory.GetFor_DeleteGitBranchConfirmation(branchToDelete);
303+
await dialog.TryShowAsync();
304+
if (!(dialog.ViewModel.AdditionalData as bool? ?? false))
305+
return;
306+
307+
IsExecutingGitAction = true;
308+
309+
await PostMethodToThreadWithMessageQueueAsync<GitOperationResult>(() =>
310+
{
311+
try
312+
{
313+
using var repository = new Repository(repositoryPath);
314+
repository.Branches.Remove(branchToDelete);
315+
}
316+
catch (Exception)
317+
{
318+
return GitOperationResult.GenericError;
319+
}
320+
321+
return GitOperationResult.Success;
322+
});
323+
324+
IsExecutingGitAction = false;
325+
}
326+
327+
283328
public static bool ValidateBranchNameForRepository(string branchName, string repositoryPath)
284329
{
285330
if (string.IsNullOrEmpty(branchName) || !Repository.IsValid(repositoryPath))

src/Files.App/Views/Shells/BaseShellPage.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,14 @@ protected async void FilesystemViewModel_DirectoryInfoUpdated(object sender, Eve
243243
? "ItemCount/Text".GetLocalizedResource()
244244
: "ItemsCount/Text".GetLocalizedResource();
245245

246+
BranchItem? headBranch = headBranch = InstanceViewModel.IsGitRepository
247+
? await GitHelpers.GetRepositoryHead(InstanceViewModel.GitRepositoryPath)
248+
: null;
249+
246250
if (InstanceViewModel.GitRepositoryPath != FilesystemViewModel.GitDirectory)
247251
{
248252
InstanceViewModel.GitRepositoryPath = FilesystemViewModel.GitDirectory;
249253

250-
BranchItem? headBranch = headBranch = InstanceViewModel.IsGitRepository
251-
? await GitHelpers.GetRepositoryHead(InstanceViewModel.GitRepositoryPath)
252-
: null;
253-
254254
InstanceViewModel.GitBranchName = headBranch is not null
255255
? headBranch.Name
256256
: string.Empty;
@@ -267,14 +267,14 @@ protected async void FilesystemViewModel_DirectoryInfoUpdated(object sender, Eve
267267
() => GitHelpers.FetchOrigin(InstanceViewModel.GitRepositoryPath),
268268
_gitFetchToken.Token);
269269
}
270+
}
270271

271-
if (!GitHelpers.IsExecutingGitAction)
272-
{
273-
ContentPage.DirectoryPropertiesViewModel.UpdateGitInfo(
274-
InstanceViewModel.IsGitRepository,
275-
InstanceViewModel.GitRepositoryPath,
276-
headBranch);
277-
}
272+
if (!GitHelpers.IsExecutingGitAction)
273+
{
274+
ContentPage.DirectoryPropertiesViewModel.UpdateGitInfo(
275+
InstanceViewModel.IsGitRepository,
276+
InstanceViewModel.GitRepositoryPath,
277+
headBranch);
278278
}
279279

280280
ContentPage.DirectoryPropertiesViewModel.DirectoryItemCount = $"{FilesystemViewModel.FilesAndFolders.Count} {directoryItemCountLocalization}";

0 commit comments

Comments
 (0)