fix(merge): refuse a merge that would overwrite an untracked file in the target
Before merging (or staging a conflict resolution's git add -A), compare the
branch's newly-added paths against what's currently untracked in the target
working directory. A collision aborts with a new untracked_collision status
naming the path and size, surfaced through merge_task/review_task,
preview_merge/preview_merge_set (which merge-tree alone can't see), and the
UI merge paths via FlashFooterError/ShowErrorAsync instead of a silent
catch{}. git's own preflight already refuses this while the path stays
untracked at merge time; this closes the gap once a path becomes trackable
in between (e.g. an unrelated conflict resolution's git add -A) or across
the continue_merge TOCTOU window.
This commit is contained in:
@@ -42,6 +42,16 @@ public sealed class GitService
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The merge base of two refs, or null when git can't find one (e.g. an unresolvable ref) —
|
||||
/// callers treat that as "can't evaluate", not "no common history".
|
||||
/// </summary>
|
||||
public async Task<string?> MergeBaseAsync(string repoDir, string refA, string refB, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["merge-base", refA, refB], ct);
|
||||
return exitCode == 0 ? stdout.Trim() : null;
|
||||
}
|
||||
|
||||
public async Task WorktreeAddAsync(string repoDir, string branchName, string worktreePath, string baseCommit, CancellationToken ct = default)
|
||||
{
|
||||
await WorktreeAddGate.WaitAsync(ct);
|
||||
@@ -70,9 +80,12 @@ public sealed class GitService
|
||||
}
|
||||
}
|
||||
|
||||
// --untracked-files=all: without it, a brand-new untracked directory collapses into a single
|
||||
// "?? dir/" entry instead of listing the files inside it — callers matching against specific
|
||||
// paths (e.g. the untracked-collision guard) need the individual files.
|
||||
public async Task<string> GetStatusPorcelainAsync(string workingDirectory, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, stdout, stderr) = await RunGitAsync(workingDirectory, ["status", "--porcelain"], ct);
|
||||
var (exitCode, stdout, stderr) = await RunGitAsync(workingDirectory, ["status", "--porcelain", "--untracked-files=all"], ct);
|
||||
if (exitCode != 0)
|
||||
throw new InvalidOperationException($"git status --porcelain failed (exit {exitCode}): {stderr}");
|
||||
return stdout;
|
||||
|
||||
Reference in New Issue
Block a user