| 1 |
// Settling conflicts with oxy's one-command answers (spec §5.7): keep one |
| 2 |
// side of a binary file's or subrepo pin's replace conflict (oxygen#140), |
| 3 |
// and keep every side of two changes adding lines at the same place |
| 4 |
// (oxygen#121). The choices and the words live here, apart from VS Code, |
| 5 |
// so they are tested. |
| 6 |
|
| 7 |
import { ConflictInfo, KeptBoth } from "./oxy"; |
| 8 |
import { candidateBase } from "./markers"; |
| 9 |
|
| 10 |
/** One way to settle a conflict, as the quick pick offers it. */ |
| 11 |
export interface SettleChoice { |
| 12 |
label: string; |
| 13 |
description?: string; |
| 14 |
detail?: string; |
| 15 |
/** keep-side: write that side's version and settle; keep-current: settle with the working copy; open-subrepo: move the subrepo first. */ |
| 16 |
action: "keep-side" | "keep-current" | "open-subrepo"; |
| 17 |
/** The side's ChangeId, for keep-side. */ |
| 18 |
change?: string; |
| 19 |
} |
| 20 |
|
| 21 |
/** Whether a conflict is a replace conflict: a binary file or subrepo pin changed two ways, with no markers to edit. */ |
| 22 |
export function isReplaceConflict(c: ConflictInfo): boolean { |
| 23 |
return c.kind === "replace" && !!c.path; |
| 24 |
} |
| 25 |
|
| 26 |
/** |
| 27 |
* The choices for a replace conflict. A file offers each side (the version |
| 28 |
* the side's change made); a subrepo pin offers the pin the nested |
| 29 |
* repository holds now, since oxy settles a pin with what the working copy |
| 30 |
* holds there, or opening it to move it first. `subjects` maps a side's |
| 31 |
* ChangeId to its subject line, where known. |
| 32 |
*/ |
| 33 |
export function replaceChoices(c: ConflictInfo, subrepo: boolean, subjects: Map<string, string> = new Map()): SettleChoice[] { |
| 34 |
if (subrepo) { |
| 35 |
return [ |
| 36 |
{ label: "$(check) Keep the pin the subrepo holds now", description: c.path, detail: "Settles the pin conflict with the nested repository's current state (oxy resolve)", action: "keep-current" }, |
| 37 |
{ label: "$(folder-opened) Open the subrepo to move it first", detail: "Check out the side you want inside it, then come back and keep that pin", action: "open-subrepo" }, |
| 38 |
]; |
| 39 |
} |
| 40 |
const sides: SettleChoice[] = (c.changes ?? []).map((id, i) => ({ |
| 41 |
label: `$(check) Keep side ${i + 1}`, |
| 42 |
description: `${id.slice(0, 8)}${subjects.get(id) ? ` "${subjects.get(id)}"` : ""}`, |
| 43 |
detail: `The version change ${id.slice(0, 8)} made replaces every other`, |
| 44 |
action: "keep-side", |
| 45 |
change: id, |
| 46 |
})); |
| 47 |
return [ |
| 48 |
...sides, |
| 49 |
{ label: "$(edit) Keep what the working copy holds now", description: c.path, detail: "Settles with the file as it is on disk; its .oxy-conflict-N candidates are removed (oxy resolve)", action: "keep-current" }, |
| 50 |
]; |
| 51 |
} |
| 52 |
|
| 53 |
/** The `.oxy-conflict-N` candidates of `path` among a directory's entries (names, not paths). */ |
| 54 |
export function candidatesOf(name: string, entries: string[]): string[] { |
| 55 |
return entries.filter((e) => candidateBase(e) === name); |
| 56 |
} |
| 57 |
|
| 58 |
/** The modal question before withdrawing a tag. */ |
| 59 |
export function withdrawQuestion(tag: string): { message: string; detail: string } { |
| 60 |
return { |
| 61 |
message: `Withdraw tag ${tag}?`, |
| 62 |
detail: |
| 63 |
"The withdrawal is signed with your key and kept beside the tag's creation, and oxy push sends it to remotes that have the tag. It is logged, not erased, and the name can never be used again.", |
| 64 |
}; |
| 65 |
} |
| 66 |
|
| 67 |
/** One line saying what `oxy resolve --both` did. */ |
| 68 |
export function keptBothSummary(r: KeptBoth): { message: string; settled: boolean; warn: boolean } { |
| 69 |
const kept = r.kept.reduce((n, k) => n + k.conflicts.length, 0); |
| 70 |
const files = new Set(r.kept.map((k) => k.path)).size; |
| 71 |
const parts: string[] = []; |
| 72 |
if (kept > 0) { |
| 73 |
parts.push(`kept every side of ${kept} conflict${kept === 1 ? "" : "s"} in ${files} file${files === 1 ? "" : "s"}`); |
| 74 |
} |
| 75 |
for (const l of r.left) { |
| 76 |
parts.push(`${l.path} conflict ${l.conflict} keeps its markers: ${l.why}`); |
| 77 |
} |
| 78 |
if (parts.length === 0) { |
| 79 |
return { message: "no conflict where two changes only add lines at the same place", settled: false, warn: true }; |
| 80 |
} |
| 81 |
return { message: parts.join("; "), settled: kept > 0, warn: r.left.length > 0 }; |
| 82 |
} |