Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/nlab-api-docs/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
Expand Down
63 changes: 54 additions & 9 deletions crates/nlab-api/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Expand Down Expand Up @@ -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();
Expand Down
Loading