{"record":{"id":"4a5c91bb06ca6520","repo":"gitbutlerapp/gitbutler","slug":"but-merge-does-not-yet-support-triangular-remotes-fetch","errorCode":null,"errorMessage":"`but merge` does not yet support triangular remotes (fetch `{fetch_remote_name}`, push `{push_remote_name}`). Merge via a pull request instead, or configure a single remote for the target branch.","messagePattern":"`but merge` does not yet support triangular remotes \\(fetch `(.+?)`, push `(.+?)`\\)\\. Merge via a pull request instead, or configure a single remote for the target branch\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-api/src/land/mod.rs","lineNumber":213,"sourceCode":"    let target_branch_name = base_branch.short_name.clone();\n    if target_branch_name.is_empty() {\n        bail!(\"Configured target branch has no branch name\");\n    }\n    let fetch_remote_name = base_branch.remote_name.clone();\n    let push_remote_name = if base_branch.push_remote_name.is_empty() {\n        fetch_remote_name.clone()\n    } else {\n        base_branch.push_remote_name.clone()\n    };\n    if push_remote_name.is_empty() {\n        bail!(\"Configured target branch has no push remote\");\n    }\n\n    // Triangular remotes (fetch remote != push remote) are out of scope for now: the post-land\n    // reconcile reads the fetch remote's tracking ref, so a push to a different remote would not\n    // advance it and the reconcile would silently no-op. Refuse before mutating anything.\n    if push_remote_name != fetch_remote_name {\n        bail!(\n            \"`but merge` does not yet support triangular remotes (fetch `{fetch_remote_name}`, \\\n             push `{push_remote_name}`). Merge via a pull request instead, or configure a single \\\n             remote for the target branch.\"\n        );\n    }\n\n    let target_display = format!(\"{push_remote_name}/{target_branch_name}\");\n    let push_remote_url = if base_branch.push_remote_url.is_empty() {\n        &base_branch.remote_url\n    } else {\n        &base_branch.push_remote_url\n    };\n    let update_target_locally = {\n        let repo = ctx.repo.get()?;\n        remote_points_at_current_repo(&repo, push_remote_url)?\n    };\n\n    // Safety guards a non-CLI caller must not be able to bypass: never publish lower stack segments","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-api/src/land/mod.rs#L195-L231","documentation":"Triangular workflows (fetch from one remote, push to another) are unsupported for `but merge`: the post-land reconcile reads the fetch remote's tracking ref, so pushing to a different remote would not advance it and reconciliation would silently no-op. `branch_land` refuses up front before mutating anything.","triggerScenarios":"Calling `but merge` when the configured target branch has `push_remote_name != fetch_remote_name` (e.g. fetch from `upstream`, push to `origin` fork setup).","commonSituations":"Open-source fork workflows (push to fork, pull from upstream); repos migrated to split push/pull remotes; CI configurations setting distinct push/pull URLs.","solutions":["Configure a single remote for the target branch (same remote for fetch and push), then re-run `but merge`","Land via a pull request instead (push the branch and open a PR on the hosting platform)","Temporarily align remotes (`git remote set-url --push origin <fetch-url>`) if a direct landing is required"],"exampleFix":"// before (triangular)\n{fetch: \"upstream\", push: \"origin\"}\n// after (single remote)\n{fetch: \"origin\", push: \"origin\"}\n// or land via PR instead of `but merge`","handlingStrategy":"validation","validationCode":"let fetch_remote = &base_branch.remote_name;\nlet push_remote = &base_branch.push_remote_name;\nif !push_remote.is_empty() && push_remote != fetch_remote {\n    println!(\"triangular setup unsupported by `but merge`; use a PR\");\n    return Ok(());\n}","typeGuard":null,"tryCatchPattern":"match land_result {\n    Err(e) if e.to_string().contains(\"triangular remotes\") => {\n        open_pull_request_instead();\n    }\n    other => other?,\n}","preventionTips":["Configure fetch and push to the same remote for the target branch when using GitButler landing","For fork workflows, land changes via pull requests rather than `but merge`","Audit remote configuration (`git remote -v`) before setting up the workspace target"],"tags":["git","remotes","triangular-workflow","unsupported","landing"],"backgroundTag":"unsupported-operation","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}