Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Added support for local branch checkout #12316

Merged
merged 21 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
},
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
}
}