Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/Files.App/Data/Enums/GitCheckoutOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public enum GitCheckoutOptions
/// </summary>
DiscardChanges,

/// <summary>
/// Abort merge and check out to the branch.
/// </summary>
AbortMerge,

/// <summary>
/// No operation to perform.
/// </summary>
Expand Down
47 changes: 47 additions & 0 deletions src/Files.App/Helpers/Dialog/DynamicDialogFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,53 @@ public static DynamicDialog GetFor_GitCheckoutConflicts(string checkoutBranchNam
return dialog;
}

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

var optionsListView = new ListView()
{
ItemsSource = new string[]
{
string.Format(Strings.AbortMergeAndSwitch.GetLocalizedResource(), checkoutBranchName),
string.Format(Strings.StayAndResolveConflicts.GetLocalizedResource(), headBranchName)
},
SelectionMode = ListViewSelectionMode.Single
};
optionsListView.SelectedIndex = 0;

optionsListView.SelectionChanged += (listView, args) =>
{
dialog.ViewModel.AdditionalData = optionsListView.SelectedIndex == 0
? GitCheckoutOptions.AbortMerge
: GitCheckoutOptions.None;
};

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

return dialog;
}

public static DynamicDialog GetFor_GitHubConnectionError()
{
DynamicDialog dialog = new DynamicDialog(new DynamicDialogViewModel()
Expand Down
9 changes: 9 additions & 0 deletions src/Files.App/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3132,6 +3132,15 @@
<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="MergeInProgress" xml:space="preserve">
<value>You have an ongoing merge with unresolved conflicts. You must resolve or abort the merge before switching branches.</value>
</data>
<data name="AbortMergeAndSwitch" xml:space="preserve">
<value>Abort merge and switch to '{0}'</value>
</data>
<data name="StayAndResolveConflicts" xml:space="preserve">
<value>Stay on '{0}' and resolve conflicts</value>
</data>
<data name="SwitchBranch" xml:space="preserve">
<value>Switch branch</value>
</data>
Expand Down
23 changes: 22 additions & 1 deletion src/Files.App/Utils/Git/GitHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,24 @@ public static async Task<bool> Checkout(string? repositoryPath, string? branch)

IsExecutingGitAction = true;

if (repository.RetrieveStatus().IsDirty)
if (repository.Index.Conflicts.Any())
{
var dialog = DynamicDialogFactory.GetFor_GitMergeConflicts(checkoutBranch.FriendlyName, repository.Head.FriendlyName);
await dialog.ShowAsync();

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

switch (resolveConflictOption)
{
case GitCheckoutOptions.None:
IsExecutingGitAction = false;
return false;
case GitCheckoutOptions.AbortMerge:
repository.Reset(ResetMode.Hard);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: repository.Reset() is called with only ResetMode.Hard, but the LibGit2Sharp API requires a Commit object as the second parameter.
Severity: CRITICAL | Confidence: High

🔍 Detailed Analysis

The repository.Reset() method is called with only one parameter (ResetMode.Hard) at src/Files.App/Utils/Git/GitHelpers.cs:210. The LibGit2Sharp API signature for Repository.Reset() requires two parameters: ResetMode and a Commit object. This will result in a runtime exception (e.g., ArgumentException) and application crash when the 'Abort merge and switch' feature is used.

💡 Suggested Fix

Provide the required Commit object to repository.Reset(). For example, use repository.Reset(ResetMode.Hard, repository.Head.Tip); to reset to the current head.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/Files.App/Utils/Git/GitHelpers.cs#L210

Potential issue: The `repository.Reset()` method is called with only one parameter
(`ResetMode.Hard`) at `src/Files.App/Utils/Git/GitHelpers.cs:210`. The `LibGit2Sharp`
API signature for `Repository.Reset()` requires two parameters: `ResetMode` and a
`Commit` object. This will result in a runtime exception (e.g., `ArgumentException`) and
application crash when the 'Abort merge and switch' feature is used.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 2874030

break;
}
}
else if (repository.RetrieveStatus().IsDirty)
{
var dialog = DynamicDialogFactory.GetFor_GitCheckoutConflicts(checkoutBranch.FriendlyName, repository.Head.FriendlyName);
await dialog.ShowAsync();
Expand All @@ -204,6 +221,7 @@ public static async Task<bool> Checkout(string? repositoryPath, string? branch)
switch (resolveConflictOption)
{
case GitCheckoutOptions.None:
IsExecutingGitAction = false;
return false;
case GitCheckoutOptions.DiscardChanges:
options.CheckoutModifiers = CheckoutModifiers.Force;
Expand All @@ -212,7 +230,10 @@ public static async Task<bool> Checkout(string? repositoryPath, string? branch)
case GitCheckoutOptions.StashChanges:
var signature = repository.Config.BuildSignature(DateTimeOffset.Now);
if (signature is null)
{
IsExecutingGitAction = false;
return false;
}

repository.Stashes.Add(signature);

Expand Down
Loading