feat(git): add opt-in for unrelated histories during pull
publish / Build and publish Windows installer (release) Successful in 10m2s
publish / Build and publish Ubuntu AppImage (release) Successful in 10m28s
publish / Build and publish AUR packages (release) Successful in 22m1s

Allow pulling repositories with unrelated commit histories when the user
explicitly opts in. The pull argument construction was refactored and
branch resolution made more robust so the backend can include the
--allow-unrelated-histories flag when requested.

- Extract pull argument logic and add support for allowing unrelated histories.
- Prompt users in the UI to confirm merging separate histories and retry pull.
- Restyle and improve the update toast UI for better layout and responsiveness.
This commit is contained in:
2026-08-30 20:25:15 +02:00
parent 4db6f30461
commit c3762ae7a3
5 changed files with 357 additions and 162 deletions
+53 -8
View File
@@ -2161,6 +2161,10 @@
|| value.includes("fetch first");
}
function isUnrelatedHistoriesError(message: string): boolean {
return message.toLowerCase().includes("refusing to merge unrelated histories");
}
function statusHasConflicts(value: GitStatus | null): boolean {
return (value?.files ?? []).some((file) => file.staged === "conflicted" || file.unstaged === "conflicted");
}
@@ -3650,17 +3654,61 @@
mode: CredentialMode,
) {
errorMessage = "";
await runOperation("Pulling", async () => {
applyStatus(await pull(activeRepoPath, username, password, pullStrategy, selectedRemote || undefined));
await refreshRepositoryViews(activeRepoPath);
const pulled = await pullWithUnrelatedHistoryConfirmation(username, password, "Pulling");
if (pulled) {
trackEvent("repository_pulled", {
from_stored_credential: fromStore ? 1 : 0,
changed_files: status?.files.length ?? 0,
});
});
}
handleRemoteResult("pull", key, fromStore, username, mode);
}
async function pullWithUnrelatedHistoryConfirmation(
username: string,
password: string,
label: string,
): Promise<boolean> {
await runOperation(label, async () => {
applyStatus(await pull(activeRepoPath, username, password, pullStrategy, selectedRemote || undefined));
await refreshRepositoryViews(activeRepoPath);
});
if (!errorMessage || !isUnrelatedHistoriesError(errorMessage)) return !errorMessage;
if (pullStrategy !== "merge") {
errorMessage = appLanguage === "de"
? "Lokales und entferntes Repository haben unabhängige Historien. Wähle in den Sync-Einstellungen die Merge-Strategie, um sie zusammenzuführen."
: "The local and remote repositories have unrelated histories. Choose the Merge strategy in Sync settings to combine them.";
return false;
}
errorMessage = "";
const confirmed = window.confirm(appLanguage === "de"
? "Das lokale und das entfernte Repository besitzen getrennte Commit-Historien.\n\nTrotzdem zusammenführen? Dabei können Merge-Konflikte entstehen."
: "The local and remote repositories have separate commit histories.\n\nMerge them anyway? This may produce merge conflicts.");
if (!confirmed) {
errorMessage = appLanguage === "de"
? "Pull abgebrochen: Die getrennten Historien wurden nicht verändert."
: "Pull cancelled: the separate histories were left unchanged.";
return false;
}
await runOperation(appLanguage === "de" ? "Historien zusammenführen" : "Merging histories", async () => {
applyStatus(await pull(
activeRepoPath,
username,
password,
pullStrategy,
selectedRemote || undefined,
undefined,
true,
));
await refreshRepositoryViews(activeRepoPath);
});
return !errorMessage;
}
async function doActualFetch(
username: string,
password: string,
@@ -3715,10 +3763,7 @@
if (!fromStore) credDialogError = "";
await runOperation("Pulling before push", async () => {
applyStatus(await pull(activeRepoPath, username, password, pullStrategy, selectedRemote || undefined));
await refreshRepositoryViews(activeRepoPath);
});
await pullWithUnrelatedHistoryConfirmation(username, password, "Pulling before push");
if (errorMessage) {
handleRemoteResult("pull", key, fromStore, username, mode);