{"record":{"id":"f1c0d16f9f5a4d1b","repo":"jdx/mise","slug":"repos-reason","errorCode":null,"errorMessage":"repos: {}: {reason}","messagePattern":"repos: (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/repos.rs","lineNumber":167,"sourceCode":"        write!(f, \"{}\", file::display_path(&self.path))\n    }\n}\n\npub fn status(requests: &[RepoRequest]) -> Result<Vec<RepoStatus>> {\n    requests.iter().map(status_one).collect()\n}\n\npub fn preflight_statuses(statuses: &[RepoStatus]) -> Result<()> {\n    for status in statuses {\n        match &status.state {\n            RepoState::Dirty => {\n                bail!(\n                    \"repos: {} has local changes; commit, stash, or clean them before bootstrap\",\n                    status.request\n                );\n            }\n            RepoState::Conflict(reason) => {\n                bail!(\"repos: {}: {reason}\", status.request);\n            }\n            RepoState::Current | RepoState::Missing | RepoState::Differs => {}\n        }\n    }\n    Ok(())\n}\n\n/// Apply statuses previously validated with [`preflight_statuses`].\npub(crate) fn apply_statuses(statuses: &[RepoStatus], dry_run: bool) -> Result<()> {\n    for status in statuses {\n        match &status.state {\n            RepoState::Current => {\n                info!(\"repos: {} already current\", status.request);\n            }\n            RepoState::Missing => clone_repo(&status.request, dry_run)?,\n            RepoState::Differs => update_repo(&status.request, dry_run)?,\n            RepoState::Dirty | RepoState::Conflict(_) => unreachable!(\"preflighted above\"),\n        }","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/repos.rs#L149-L185","documentation":"preflight_statuses rejects RepoState::Conflict entries, whose reason string comes from status_one: (1) `path exists and is not a directory` — a file occupies the target path; (2) `path exists and is not a git repository` — a non-empty directory with no .git; (3) `origin does not match configured url` — the clone's origin identifies a different repository than the configured url. Origin comparison is transport-agnostic: `git@host:path`, `ssh://git@host/path`, and `https://host/path` are equivalent, but a different host, owner/path, non-`git` ssh user, explicit port, or insecure `http://`/`git://` is a conflict.","triggerScenarios":"A regular file already exists at the repo path; the directory was pre-created with content but no .git; the clone's origin points at a fork, a different host, an ssh alias (`git@github-work:...`), an explicit port, or a userless ssh form while config declares an explicit-user/https URL.","commonSituations":"Re-cloning a project after forking while config still names upstream; ssh host aliases in ~/.ssh/config vs literal hosts in mise.toml; switching between ssh and https forms of the same repo is fine, switching host or owner is not.","solutions":["For a non-directory or non-git path: move or delete it (`mv ~/src/x ~/src/x.bak`) so bootstrap can clone fresh","For an origin mismatch: point the remote at the configured URL with `git -C <repo> remote set-url origin <configured-url>`, or update the config url to the URL you actually use","Keep owner/path identical across transports — `https://github.com/o/r` matches `git@github.com:o/r.git`, but not `git@gitlab.com:o/r.git` or `ssh://git@github.com:2222/o/r.git`"],"exampleFix":"# before: origin conflict\n$ git -C ~/src/mise remote set-url origin https://github.com/other/mise.git\n# after: align the remote with the configured url\n$ git -C ~/src/mise remote set-url origin https://github.com/jdx/mise.git\n$ mise bootstrap","handlingStrategy":"validation","validationCode":"fn origin_matches(path: &Path, configured_url: &str) -> bool {\n    let out = std::process::Command::new(\"git\")\n        .arg(\"-C\").arg(path)\n        .args([\"config\", \"--get\", \"remote.origin.url\"])\n        .output();\n    matches!(out, Ok(o) if o.status.success() && String::from_utf8_lossy(&o.stdout).trim() == configured_url)\n}\n\n// treat the repo as manageable only when the path is a directory containing .git\n// and the origin matches (ssh/https equivalents of the same host/path are accepted by mise).","typeGuard":"fn path_is_clonable_target(path: &Path) -> bool {\n    !path.exists() || (path.is_dir() && path.join(\".git\").exists())\n}","tryCatchPattern":null,"preventionTips":["After forking or moving a remote, run `git remote set-url origin <url-from-config>` in the clone","Avoid ssh host aliases and explicit ports in origins when config uses the canonical URL (or mirror it exactly)","Remember `http://`, `git://`, userless ssh, and query-string URLs are never treated as equivalent to the configured https/ssh form"],"tags":["mise","bootstrap","repos","git","remote-url","conflict"],"backgroundTag":"git-remote-url-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}