Skip to content

Commit

Permalink
Feature: Added support for local branch checkout (#12316)
Browse files Browse the repository at this point in the history
  • Loading branch information
ferrariofilippo authored May 11, 2023
1 parent aae3dfe commit a8df728
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 24 deletions.
54 changes: 46 additions & 8 deletions src/Files.App/Helpers/DynamicDialogFactory.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.WinUI;
using Files.App.Dialogs;
using Files.App.Extensions;
using Files.App.Filesystem;
using Files.App.ViewModels.Dialogs;
using Files.Shared.Enums;
using Files.Shared.Extensions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.System;

namespace Files.App.Helpers
Expand Down Expand Up @@ -224,5 +216,51 @@ public static DynamicDialog GetFor_CredentialEntryDialog(string path)

return dialog;
}

public static DynamicDialog GetFor_GitCheckoutConflicts(string checkoutBranchName, string headBranchName)
{
DynamicDialog dialog = null!;

var optionsListView = new ListView()
{
ItemsSource = new string[]
{
string.Format("BringChanges".GetLocalizedResource(), checkoutBranchName),
string.Format("StashChanges".GetLocalizedResource(), headBranchName),
"DiscardChanges".GetLocalizedResource()
},
SelectionMode = ListViewSelectionMode.Single
};
optionsListView.SelectedIndex = 0;

optionsListView.SelectionChanged += (listView, args) =>
{
dialog.ViewModel.AdditionalData = (GitCheckoutOptions)optionsListView.SelectedIndex;
};

dialog = new DynamicDialog(new DynamicDialogViewModel()
{
TitleText = "SwitchBranch".GetLocalizedResource(),
PrimaryButtonText = "Switch".GetLocalizedResource(),
CloseButtonText = "Cancel".GetLocalizedResource(),
SubtitleText = "UncommittedChanges".GetLocalizedResource(),
DisplayControl = new Grid()
{
MinWidth = 250d,
Children =
{
optionsListView
}
},
AdditionalData = GitCheckoutOptions.BringChanges,
CloseButtonAction = (vm, e) =>
{
dialog.ViewModel.AdditionalData = GitCheckoutOptions.None;
vm.HideDialog();
}
});

return dialog;
}
}
}
65 changes: 64 additions & 1 deletion src/Files.App/Helpers/GitHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using LibGit2Sharp;
using Files.App.Filesystem.StorageItems;
using LibGit2Sharp;
using Microsoft.AppCenter.Analytics;

namespace Files.App.Helpers
{
Expand Down Expand Up @@ -32,5 +33,67 @@ public static class GitHelpers
return null;
}
}

public static string[] GetLocalBranchesNames(string? path)
{
if (string.IsNullOrWhiteSpace(path) || !Repository.IsValid(path))
return Array.Empty<string>();

using var repository = new Repository(path);
return repository.Branches
.Where(b => !b.IsRemote)
.OrderByDescending(b => b.IsCurrentRepositoryHead)
.ThenByDescending(b => b.Tip.Committer.When)
.Select(b => b.FriendlyName)
.ToArray();
}

public static async Task<bool> Checkout(string? repositoryPath, string? branch)
{
if (string.IsNullOrWhiteSpace(repositoryPath) || !Repository.IsValid(repositoryPath))
return false;

using var repository = new Repository(repositoryPath);
var checkoutBranch = repository.Branches[branch];
if (checkoutBranch is null)
return false;

var options = new CheckoutOptions();
var isBringingChanges = false;

Analytics.TrackEvent($"Triggered git checkout");

if (repository.RetrieveStatus().IsDirty)
{
var dialog = DynamicDialogFactory.GetFor_GitCheckoutConflicts(checkoutBranch.FriendlyName, repository.Head.FriendlyName);
await dialog.ShowAsync();

var resolveConflictOption = (GitCheckoutOptions)dialog.ViewModel.AdditionalData;

switch (resolveConflictOption)
{
case GitCheckoutOptions.None:
return false;
case GitCheckoutOptions.DiscardChanges:
options.CheckoutModifiers = CheckoutModifiers.Force;
break;
case GitCheckoutOptions.BringChanges:
case GitCheckoutOptions.StashChanges:
repository.Stashes.Add(repository.Config.BuildSignature(DateTimeOffset.Now));

isBringingChanges = resolveConflictOption is GitCheckoutOptions.BringChanges;
break;
}
}

LibGit2Sharp.Commands.Checkout(repository, checkoutBranch, options);

if (isBringingChanges)
{
var lastStashIndex = repository.Stashes.Count() - 1;
repository.Stashes.Pop(lastStashIndex, new StashApplyOptions());
}
return true;
}
}
}
24 changes: 24 additions & 0 deletions src/Files.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3262,4 +3262,28 @@
<data name="SecurityUnableToDisplayPermissions" xml:space="preserve">
<value>Unable to display permissions.</value>
</data>
<data name="StashChanges" xml:space="preserve">
<value>Leave my changes on '{0}'</value>
</data>
<data name="DiscardChanges" xml:space="preserve">
<value>Discard my changes</value>
</data>
<data name="BringChanges" xml:space="preserve">
<value>Bring my changes to '{0}'</value>
</data>
<data name="UncommittedChanges" xml:space="preserve">
<value>You have uncommitted changes on this branch. What would you like to do with them?</value>
</data>
<data name="SwitchBranch" xml:space="preserve">
<value>Switch Branch</value>
</data>
<data name="Branches" xml:space="preserve">
<value>Branches</value>
</data>
<data name="Switch" xml:space="preserve">
<value>Switch</value>
</data>
<data name="NewBranch" xml:space="preserve">
<value>New branch</value>
</data>
</root>
58 changes: 56 additions & 2 deletions src/Files.App/UserControls/StatusBarControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:Files.App.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:helpers="using:Files.App.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="32"
d:DesignWidth="400"
Expand Down Expand Up @@ -53,10 +54,63 @@
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="8">
<TextBlock
<Button
x:Name="GitBranch"
x:Load="{x:Bind DirectoryPropertiesViewModel.GitBranchDisplayName, Mode=OneWay, Converter={StaticResource NullToFalseConverter}}"
Text="{x:Bind DirectoryPropertiesViewModel.GitBranchDisplayName, Mode=OneWay}" />
Background="Transparent"
BorderThickness="0"
Content="{x:Bind DirectoryPropertiesViewModel.GitBranchDisplayName, Mode=OneWay}">
<Button.Flyout>
<Flyout x:Name="BranchesFlyout" Opening="BranchesFlyout_Opening">
<Grid
Width="300"
Height="340"
Margin="-16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- Header -->
<Grid
Grid.Row="0"
Padding="12"
Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<!-- Title -->
<TextBlock
VerticalAlignment="Center"
FontSize="14"
Text="{helpers:ResourceString Name=Branches}" />

<!-- New Branch Button -->
<Button
x:Name="NewBranchButton"
Height="24"
Padding="8,0"
HorizontalAlignment="Right"
x:Load="False"
Content="{helpers:ResourceString Name=NewBranch}"
FontSize="12"
ToolTipService.ToolTip="{helpers:ResourceString Name=NewBranch}" />
</Grid>

<!-- Branches List -->
<ListView
x:Name="BranchesList"
Grid.Row="1"
Padding="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
IsItemClickEnabled="True"
ItemClick="BranchesList_ItemClick"
ItemsSource="{x:Bind DirectoryPropertiesViewModel.BranchesNames, Mode=OneWay}"
SelectedIndex="{x:Bind DirectoryPropertiesViewModel.SelectedBranchIndex, Mode=TwoWay}"
SelectionMode="Single" />
</Grid>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
</UserControl>
11 changes: 10 additions & 1 deletion src/Files.App/UserControls/StatusBarControl.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Files.App.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

Expand Down Expand Up @@ -42,5 +41,15 @@ public StatusBarControl()
{
InitializeComponent();
}

private void BranchesFlyout_Opening(object sender, object e)
{
DirectoryPropertiesViewModel.SelectedBranchIndex = DirectoryPropertiesViewModel.ActiveBranchIndex;
}

private void BranchesList_ItemClick(object sender, ItemClickEventArgs e)
{
BranchesFlyout.Hide();
}
}
}
46 changes: 40 additions & 6 deletions src/Files.App/ViewModels/DirectoryPropertiesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,52 @@ namespace Files.App.ViewModels
{
public class DirectoryPropertiesViewModel : ObservableObject
{
private string directoryItemCount;
public int ActiveBranchIndex { get; private set; }

private string _DirectoryItemCount;
public string DirectoryItemCount
{
get => directoryItemCount;
set => SetProperty(ref directoryItemCount, value);
get => _DirectoryItemCount;
set => SetProperty(ref _DirectoryItemCount, value);
}

private string? gitBranchDisplayName;
private string? _GitBranchDisplayName;
public string? GitBranchDisplayName
{
get => gitBranchDisplayName;
set => SetProperty(ref gitBranchDisplayName, value);
get => _GitBranchDisplayName;
private set => SetProperty(ref _GitBranchDisplayName, value);
}

private int _SelectedBranchIndex;
public int SelectedBranchIndex
{
get => _SelectedBranchIndex;
set
{
if (SetProperty(ref _SelectedBranchIndex, value) && value != -1 && value != ActiveBranchIndex)
CheckoutRequested?.Invoke(this, BranchesNames[value]);
}
}

public ObservableCollection<string> BranchesNames { get; } = new();

public EventHandler<string>? CheckoutRequested;

public void UpdateGitInfo(bool isGitRepository, string activeBranch, string[] branches)
{
GitBranchDisplayName = isGitRepository
? string.Format("Branch".GetLocalizedResource(), activeBranch)
: null;

if (isGitRepository)
{
BranchesNames.Clear();
foreach (var name in branches)
BranchesNames.Add(name);

ActiveBranchIndex = BranchesNames.IndexOf(activeBranch);
SelectedBranchIndex = ActiveBranchIndex;
}
}
}
}
25 changes: 19 additions & 6 deletions src/Files.App/Views/Shells/BaseShellPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,15 @@ public BaseLayout ContentPage
{
if (value != _ContentPage)
{
if (_ContentPage is not null)
_ContentPage.DirectoryPropertiesViewModel.CheckoutRequested -= GitCheckout_Required;

_ContentPage = value;

NotifyPropertyChanged(nameof(ContentPage));
NotifyPropertyChanged(nameof(SlimContentPage));
if (value is not null)
_ContentPage.DirectoryPropertiesViewModel.CheckoutRequested += GitCheckout_Required;
}
}
}
Expand Down Expand Up @@ -219,9 +224,10 @@ protected void FilesystemViewModel_DirectoryInfoUpdated(object sender, EventArgs

InstanceViewModel.GitRepositoryPath = FilesystemViewModel.GitDirectory;

ContentPage.DirectoryPropertiesViewModel.GitBranchDisplayName = InstanceViewModel.IsGitRepository
? string.Format("Branch".GetLocalizedResource(), InstanceViewModel.GitBranchName)
: null;
ContentPage.DirectoryPropertiesViewModel.UpdateGitInfo(
InstanceViewModel.IsGitRepository,
InstanceViewModel.GitBranchName,
GitHelpers.GetLocalBranchesNames(InstanceViewModel.GitRepositoryPath));

ContentPage.DirectoryPropertiesViewModel.DirectoryItemCount = $"{FilesystemViewModel.FilesAndFolders.Count} {directoryItemCountLocalization}";
ContentPage.UpdateSelectionSize();
Expand All @@ -230,9 +236,16 @@ protected void FilesystemViewModel_DirectoryInfoUpdated(object sender, EventArgs
protected void FilesystemViewModel_GitDirectoryUpdated(object sender, EventArgs e)
{
InstanceViewModel.UpdateCurrentBranchName();
ContentPage.DirectoryPropertiesViewModel.GitBranchDisplayName = InstanceViewModel.IsGitRepository
? string.Format("Branch".GetLocalizedResource(), InstanceViewModel.GitBranchName)
: null;
ContentPage.DirectoryPropertiesViewModel.UpdateGitInfo(
InstanceViewModel.IsGitRepository,
InstanceViewModel.GitBranchName,
GitHelpers.GetLocalBranchesNames(InstanceViewModel.GitRepositoryPath));
}

protected async void GitCheckout_Required(object? sender, string branchName)
{
if (!await GitHelpers.Checkout(FilesystemViewModel.GitDirectory, branchName))
_ContentPage.DirectoryPropertiesViewModel.SelectedBranchIndex = _ContentPage.DirectoryPropertiesViewModel.ActiveBranchIndex;
}

protected virtual void Page_Loaded(object sender, RoutedEventArgs e)
Expand Down
10 changes: 10 additions & 0 deletions src/Files.Backend/Enums/GitCheckoutOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Files.Backend.Enums
{
public enum GitCheckoutOptions
{
BringChanges,
StashChanges,
DiscardChanges,
None
}
}

0 comments on commit a8df728

Please sign in to comment.