diff --git a/apps/nlab-api-docs/docs/quick-start.md b/apps/nlab-api-docs/docs/quick-start.md index 2b1df87..eb0a995 100644 --- a/apps/nlab-api-docs/docs/quick-start.md +++ b/apps/nlab-api-docs/docs/quick-start.md @@ -173,6 +173,9 @@ nlab-api generate --project /path/to/frontend --branch another-branch 下次不传 `--branch` 时,CLI 自动切回团队配置分支。 +已有仓库只抓取部分分支时,CLI 显式获取目标分支再创建本地分支,不修改原有 fetch 配置。 +后续更新显式指定远端和分支,仍只允许 fast-forward;目标分支确实不存在或获取失败时保留 Git 原始诊断。 + `generate` 按顺序执行: 1. 获取后端仓库锁;缺失时根据 repository URL clone。 diff --git a/crates/nlab-api/src/repo.rs b/crates/nlab-api/src/repo.rs index 240880a..013dd3a 100644 --- a/crates/nlab-api/src/repo.rs +++ b/crates/nlab-api/src/repo.rs @@ -290,21 +290,18 @@ fn switch_branch(root: &Path, branch: &str, deadline: Instant) -> Result<()> { return run_git(root, ["switch", branch], "switch backend branch", deadline); } + let remote_ref = format!("refs/remotes/origin/{branch}"); + let refspec = format!("refs/heads/{branch}:{remote_ref}"); run_git( root, - ["fetch", "--no-tags", "origin"], - "fetch backend branches", + ["fetch", "--no-tags", "origin", &refspec], + &format!("fetch backend branch {branch}"), deadline, )?; - let remote = format!("origin/{branch}"); - let remote_ref = format!("refs/remotes/{remote}"); - if !ref_exists(root, &remote_ref)? { - bail!("backend branch not found on origin: {branch}"); - } run_git( root, - ["switch", "--track", "-c", branch, &remote], - "create backend tracking branch", + ["switch", "--no-track", "-c", branch, &remote_ref], + "create backend branch", deadline, ) } @@ -595,6 +592,54 @@ mod tests { ); } + #[test] + fn prepare_fetches_target_outside_single_branch_refspec() { + let (root, remote, upstream, backend) = repositories(); + git(&upstream, &["push", "origin", "main:master"]); + git( + root.path(), + &[ + "clone", + "--single-branch", + "--branch", + "feature", + remote.to_str().unwrap(), + backend.to_str().unwrap(), + ], + ); + let fetch = git_text(&backend, ["config", "--get-all", "remote.origin.fetch"]).unwrap(); + assert!(!ref_exists(&backend, "refs/remotes/origin/master").unwrap()); + let deadline = || Instant::now() + Duration::from_secs(10); + { + let prepared = prepare(&backend, None, Some("master"), deadline()).unwrap(); + assert_eq!(prepared.target.branch, "master"); + assert_eq!( + fs::read_to_string(backend.join("contract.java")).unwrap(), + "main" + ); + } + + fs::write(upstream.join("contract.java"), "updated master").unwrap(); + git(&upstream, &["commit", "-am", "update master"]); + git(&upstream, &["push", "origin", "main:master"]); + let commit = git_text(&upstream, ["rev-parse", "HEAD"]).unwrap(); + { + let prepared = prepare(&backend, None, Some("master"), deadline()).unwrap(); + assert_eq!(prepared.target.commit, commit); + assert_eq!( + fs::read_to_string(backend.join("contract.java")).unwrap(), + "updated master" + ); + } + assert_eq!( + git_text(&backend, ["config", "--get-all", "remote.origin.fetch"]).unwrap(), + fetch + ); + assert!(prepare(&backend, None, Some("missing"), deadline()).is_err()); + assert_eq!(current_branch(&backend).unwrap(), "master"); + assert_eq!(git_text(&backend, ["rev-parse", "HEAD"]).unwrap(), commit); + } + #[test] fn prepare_rejects_dirty_repository_and_wrong_origin() { let (root, remote, _upstream, backend) = repositories();