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
4 changes: 2 additions & 2 deletions COMPATIBILITY.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libra"
version = "0.23.57"
version = "0.23.58"
edition = "2024"
license = "MIT"
description = "AI agent-native version control system with Git on-disk compatibility, SQLite-backed metadata, and tiered cloud storage"
Expand Down
6 changes: 4 additions & 2 deletions docs/commands/fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ one source ref and may map it to an exact local destination (`<src>:<dst>`). Whe
explicit refspec is given, `remote.<name>.fetch` entries are honored; if none exist,
all advertised branches use the default `refs/remotes/<name>/*` mapping.

Fetch supports SSH, HTTPS, local file, and `git://` transports. Vault-backed SSH keys
are loaded automatically when configured via `vault.ssh.<remote>.privkey`.
Fetch supports SSH, HTTPS, local file, Git v2 bundle files, and `git://`
transports. A remote URL that points at a bundle is re-read on every fetch
(including `--prune` and `--dry-run`). Vault-backed SSH keys are loaded
automatically when configured via `vault.ssh.<remote>.privkey`.

## Global Config Schema Guard

Expand Down
2 changes: 1 addition & 1 deletion docs/commands/pull.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ libra pull [--ff-only] [--ff] [--no-ff] [--squash] [--no-commit] [--commit] [--a

## Description

`libra pull` combines `fetch` and the same merge engine used by `libra merge`. It downloads new objects, updates remote-tracking refs, and then integrates the selected upstream into the current branch.
`libra pull` combines `fetch` and the same merge engine used by `libra merge`. It downloads new objects, updates remote-tracking refs, and then integrates the selected upstream into the current branch. A remote URL that points at a Git v2 bundle is re-read on every pull, so replacing the bundle file can fast-forward the current branch.

With `--rebase` (`-r`), the integration step instead replays local-only commits on top of the fetched upstream tip. This is equivalent to `libra fetch` followed by `libra rebase <upstream>`.

Expand Down
2 changes: 1 addition & 1 deletion docs/commands/zh-CN/fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ libra fetch [OPTIONS] [<repository> [<refspec>]]

不带参数调用时,它从当前分支配置的 upstream 获取。已配置的本地 upstream(`branch.<name>.remote=.`)会在任何网络或 `FETCH_HEAD` 写入前被拒绝(`LBR-CLI-003`,退出 129);Git 2.54 的 `fetch` 可以对本地 upstream 操作——该支持延后到 [issues/480 HP-16](https://github.com/libra-tools/libra/issues/480)。显式仓库参数 `.` 仍走现有的 `remote '.' not found`。给出 `--all` 时,会依次获取每个已配置远程。指定某个 `<repository>` 时,只联系该远程。可选 `<refspec>` 选择一个源引用,并可用 `<src>:<dst>` 精确映射到本地目标。未显式给出 refspec 时会遵守 `remote.<name>.fetch`;该配置不存在时才回退为把所有远程分支映射到 `refs/remotes/<name>/*`。

Fetch 支持 SSH、HTTPS、本地文件和 `git://` 传输。配置了 `vault.ssh.<remote>.privkey` 时,会自动加载 vault-backed SSH 密钥。
Fetch 支持 SSH、HTTPS、本地文件、Git v2 bundle 文件和 `git://` 传输。远程 URL 指向 bundle 时,每次 fetch(含 `--prune` 与 `--dry-run`)都会重新读取该文件。配置了 `vault.ssh.<remote>.privkey` 时,会自动加载 vault-backed SSH 密钥。

## 全局配置 Schema 保护

Expand Down
2 changes: 1 addition & 1 deletion docs/commands/zh-CN/pull.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ libra pull [--ff-only] [--ff] [--no-ff] [--squash] [--no-commit] [--commit] [--a

## 说明

`libra pull` 组合了 `fetch` 和 `libra merge` 使用的同一合并引擎。它下载新对象,更新远程跟踪引用,然后将选中的 upstream 集成到当前分支。
`libra pull` 组合了 `fetch` 和 `libra merge` 使用的同一合并引擎。它下载新对象,更新远程跟踪引用,然后将选中的 upstream 集成到当前分支。远程 URL 指向 Git v2 bundle 时,每次 pull 都会重新读取该文件,替换 bundle 后可以快进当前分支。

使用 `--rebase`(`-r`)时,集成步骤会改为在获取到的 upstream tip 之上重放仅本地提交。这等价于 `libra fetch` 后跟 `libra rebase <upstream>`。

Expand Down
18 changes: 9 additions & 9 deletions docs/development/plan/issues/474.md
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,7 @@ ER-CL-02 接入 / 豁免清单(2026-09-23):

**Task type:** `implementation`

**Lifecycle / Acceptance:** `pending` / 空
**Lifecycle / Acceptance:** `in-progress` / `locally-accepted`

**Description:** 按 ADR-CL-06 第 3 条,当 remote url 指向 bundle 文件时,`fetch`/`pull` 读取该文件的当前内容作为广告与 pack,支持普通、`--prune` 与 `--dry-run`。唯一行为轴是「bundle remote 的增量更新」。

Expand All @@ -1290,17 +1290,17 @@ ER-CL-02 接入 / 豁免清单(2026-09-23):

**Acceptance criteria:**

- [ ] 判据矩阵 M-BFETCH H1(克隆后 fetch)。
- [ ] 替换 bundle 后 fetch 与 pull 通过 M-BFETCH H2。
- [ ] bundle 缺失通过 M-BFETCH H3。
- [ ] `--prune` 与 `--dry-run` 通过 M-BFETCH H4。
- [ ] ER-06a 同步:`docs/commands/fetch.md`、`docs/commands/pull.md` 与 zh-CN、`COMPATIBILITY.md:223`、`:225` 原行追加、网站 `fetch.en.md`、`pull.en.md`。
- [x] 判据矩阵 M-BFETCH H1(克隆后 fetch)。
- [x] 替换 bundle 后 fetch 与 pull 通过 M-BFETCH H2。
- [x] bundle 缺失通过 M-BFETCH H3。
- [x] `--prune` 与 `--dry-run` 通过 M-BFETCH H4。
- [x] ER-06a 同步:`docs/commands/fetch.md`、`docs/commands/pull.md` 与 zh-CN、`COMPATIBILITY.md:223`、`:225` 原行追加、网站 `fetch.en.md`、`pull.en.md`。

**Verification:**

- [ ] `source .env.test && source .env.live-test && cargo nextest run --test command_test fetch_test`(new:`test_fetch_from_bundle_remote_matrix`)
- [ ] `source .env.test && source .env.live-test && cargo nextest run --test command_test pull_test`(new:`test_pull_from_bundle_remote_fast_forward`)
- [ ] `source .env.test && source .env.live-test && cargo nextest run --test compat_ledger_schema`
- [x] `source .env.test && source .env.live-test && cargo nextest run --test command_test fetch_test`(new:`test_fetch_from_bundle_remote_matrix`)
- [x] `source .env.test && source .env.live-test && cargo nextest run --test command_test pull_test`(new:`test_pull_from_bundle_remote_fast_forward`)
- [x] `source .env.test && source .env.live-test && cargo nextest run --test compat_ledger_schema`

**Full-suite trigger:** `none`

Expand Down
7 changes: 4 additions & 3 deletions docs/development/plan/plan-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
|---|---|---|---|
| [`issues/470.md`](issues/470.md) | 工作树物化丢失可执行位与 mode 变化检测 | 未启动 | FM-01/02/05(3 卡) |
| [`issues/473.md`](issues/473.md) | `init` 与 Git 对齐 | 未启动 | IN-01..IN-12(12 卡) |
| [`issues/474.md`](issues/474.md) | clone 浅克隆完整性、bundle 源、bare 与 mirror 对齐 | 实施中 | CL-01 `done`/`complete`(`v0.23.47`);CL-02 `done`/`complete`(`v0.23.48`);CL-03 C 组 `v0.23.49`(`compat-network-remotes` 重跑排队中);CL-04..CL-09 `locally-accepted`(未 bump) |
| [`issues/474.md`](issues/474.md) | clone 浅克隆完整性、bundle 源、bare 与 mirror 对齐 | 实施中 | CL-01 `done`/`complete`(`v0.23.47`);CL-02 `done`/`complete`(`v0.23.48`);CL-03 C 组 `v0.23.49`(`compat-network-remotes` 重跑排队中);CL-04..CL-10 `locally-accepted`(未 bump) |
| [`issues/475.md`](issues/475.md) | `config` Git 兼容参数层对齐 | 未启动 | CF-01..CF-15(15 卡) |
| [`issues/476.md`](issues/476.md) | 工作树命令族与 Git 对齐 | **实施中** | WT-02 `v0.23.29` / WT-04 `v0.23.30` / WT-08 `v0.23.31` / WT-09 `v0.23.32` / WT-10 `v0.23.33` / WT-11 `v0.23.34` / WT-01 `v0.23.35`(`done`/`remote-pending`);WT-03 受 DEP-WT-08 阻塞;intent-to-add 已迁至 plan-20260918 |
| [`issues/477.md`](issues/477.md) | 历史改写命令族与 Git 对齐 | **已收口** | HF-01..HF-31(31 卡)全 `done/complete`,聚合发布 v0.22.49;子 issue #495 |
Expand Down Expand Up @@ -191,7 +191,8 @@
| **CL-07 shallow Git 源克隆** | **`in-progress`/`locally-accepted`** | **未 bump(等 CL-06 发布窗口)** |
| **CL-08 bundle create 写入 HEAD** | **`in-progress`/`locally-accepted`** | **未 bump(等 CL-07 发布窗口)** |
| **CL-09 clone 接受 bundle 源** | **`in-progress`/`locally-accepted`** | **未 bump(等 CL-08 发布窗口)** |
| CL-10..CL-15 | `pending` | 未 bump |
| **CL-10 以 bundle 为 remote 的 fetch/pull** | **`in-progress`/`locally-accepted`** | **未 bump(等 CL-09 发布窗口)** |
| CL-11..CL-15 | `pending` | 未 bump |

### 3.6 issues/476(工作树命令族)

Expand Down Expand Up @@ -233,7 +234,7 @@ SBX-01..05 `done/locally-accepted`;**发布步按 DEFER-SBX-06 正式延后**

- **当前正在执行:** `plan-20260907` B3-00(发布者:本会话执行该卡的 Agent)。`plan-20260925` SCAP-01 / SCAP-02 已 `done`/`complete`(`v0.23.55` / `fa3849e`,D 组全绿)。
- **SCAP-01 与 B3-00:** SCAP-01 已收口,`runtime.rs` 写集互斥结束;B3-00 继续在 `main` 上推进。
- **下一步:** 完成 B3-00 focused VER / T-1 全量与发布。`issues/474` 的 `CL-09`..`CL-15` 正按窗口发布(合并后即开下一卡,不等待 release CDN)。
- **下一步:** 完成 B3-00 focused VER / T-1 全量与发布。`issues/474` 的 `CL-10`..`CL-15` 正按窗口发布(合并后即开下一卡,不等待 release CDN)。
- **并行窗口(不在本执行指针):** `issues/476` WT-03 仍等 DEP-WT-08;`plan-20260918` 其余 add 卡、`plan-20260819` M2 仍登记为实施中,但不抢本卡的 `fsck.rs` 写集。

---
Expand Down
2 changes: 1 addition & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $ErrorActionPreference = "Stop"
# One of the release version surfaces. `compat_version_surface_sync` pins it
# to Cargo.toml: this value is substituted verbatim into the download URL, so
# a stale value silently installs an old binary when -Version is not given.
$DefaultVersion = "v0.23.57"
$DefaultVersion = "v0.23.58"
# Public-only trust anchor for stable-manifest verification. It deliberately
# has no environment override: the install-smoke harness rewrites these
# clearly-marked constants in a temporary COPY of this script.
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ INSTALL_DIR="${LIBRA_INSTALL_DIR:-$LIBRA_HOME/bin}"
# user opts in with LIBRA_ALLOW_FALLBACK=1. Default behaviour is fail-fast so
# offline installs cannot silently regress to a stale version. Bump this on
# every release so the opt-in fallback remains useful.
DEFAULT_VERSION="v0.23.57"
DEFAULT_VERSION="v0.23.58"
# Public-only trust anchor for stable-manifest verification. It deliberately
# has no environment override: the install-smoke harness rewrites these
# clearly-marked constants in a temporary COPY of this script, never through
Expand Down
106 changes: 106 additions & 0 deletions tests/command/fetch_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2184,3 +2184,109 @@ fn test_fetch_depth_local_git_boundaries() {
"fetch depth 2 boundary"
);
}

fn rev_parse_cli(repo: &Path, rev: &str) -> String {
let output = run_libra_command(&["rev-parse", rev], repo);
assert_cli_success(&output, &format!("rev-parse {rev}"));
String::from_utf8_lossy(&output.stdout).trim().to_string()
}

fn commit_file_via_cli(repo: &Path, name: &str, contents: &str, message: &str) {
fs::write(repo.join(name), contents).expect("write commit file");
assert_cli_success(
&run_libra_command(&["add", name], repo),
&format!("add {name}"),
);
assert_cli_success(
&run_libra_command(&["commit", "-m", message, "--no-verify"], repo),
message,
);
}

/// M-BFETCH H1–H4: fetch against a bundle remote.
#[test]
fn test_fetch_from_bundle_remote_matrix() {
let src = create_committed_repo_via_cli();
assert_cli_success(
&run_libra_command(&["branch", "dev"], src.path()),
"branch dev",
);
let parent = tempdir().expect("bundle parent");
let bundle = parent.path().join("remote.bundle");
assert_cli_success(
&run_libra_command(
&["bundle", "create", bundle.to_str().unwrap(), "--all"],
src.path(),
),
"create bundle",
);
let dest = parent.path().join("cloned");
assert_cli_success(
&run_libra_command(
&["clone", bundle.to_str().unwrap(), dest.to_str().unwrap()],
parent.path(),
),
"clone from bundle",
);

let h1 = run_libra_command(&["fetch"], dest.as_path());
assert_cli_success(&h1, "H1 fetch after clone");

let old_main = rev_parse_cli(&dest, "refs/remotes/origin/main");
assert_cli_success(
&run_libra_command(&["rev-parse", "refs/remotes/origin/dev"], &dest),
"H4 origin/dev exists before prune",
);

commit_file_via_cli(src.path(), "next.txt", "next\n", "next");
assert_cli_success(
&run_libra_command(
&["bundle", "create", bundle.to_str().unwrap(), "--all"],
src.path(),
),
"replace bundle with new commit",
);
let new_src = rev_parse_cli(src.path(), "HEAD");

let dry = run_libra_command(&["fetch", "--dry-run"], dest.as_path());
assert_cli_success(&dry, "H4 fetch --dry-run");
assert_eq!(
rev_parse_cli(&dest, "refs/remotes/origin/main"),
old_main,
"H4 dry-run must not update tracking"
);

let h2 = run_libra_command(&["fetch"], dest.as_path());
assert_cli_success(&h2, "H2 fetch after bundle replace");
assert_eq!(
rev_parse_cli(&dest, "refs/remotes/origin/main"),
new_src,
"H2 tracking must move to the new bundle tip"
);

// Recreate a main-only bundle so origin/dev is no longer advertised.
assert_cli_success(
&run_libra_command(
&["bundle", "create", bundle.to_str().unwrap(), "main"],
src.path(),
),
"replace bundle without dev",
);
let prune = run_libra_command(&["fetch", "--prune"], dest.as_path());
assert_cli_success(&prune, "H4 fetch --prune");
let pruned = run_libra_command(&["rev-parse", "refs/remotes/origin/dev"], &dest);
assert!(!pruned.status.success(), "H4 prune must drop origin/dev");

fs::remove_file(&bundle).expect("delete bundle");
let h3 = run_libra_command(&["fetch"], dest.as_path());
assert!(!h3.status.success(), "H3 missing bundle");
let h3_text = format!(
"{}{}",
String::from_utf8_lossy(&h3.stdout),
String::from_utf8_lossy(&h3.stderr)
);
assert!(
h3_text.contains("does not exist") && h3_text.to_ascii_lowercase().contains("bundle"),
"H3 must say the bundle does not exist: {h3_text}"
);
}
55 changes: 55 additions & 0 deletions tests/command/pull_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1727,3 +1727,58 @@ async fn test_pull_fast_forward_materializes_executable_bit() {
"pull fast-forward must materialize the execute bit"
);
}

/// M-BFETCH H2: pull fast-forwards from a replaced bundle remote.
#[test]
fn test_pull_from_bundle_remote_fast_forward() {
let src = create_committed_repo_via_cli();
let parent = tempdir().expect("bundle parent");
let bundle = parent.path().join("remote.bundle");
assert_cli_success(
&run_libra_command(
&["bundle", "create", bundle.to_str().unwrap(), "--all"],
src.path(),
),
"create bundle",
);
let dest = parent.path().join("cloned");
assert_cli_success(
&run_libra_command(
&["clone", bundle.to_str().unwrap(), dest.to_str().unwrap()],
parent.path(),
),
"clone from bundle",
);
let old_head = run_libra_command(&["rev-parse", "HEAD"], &dest);
assert_cli_success(&old_head, "old HEAD");
let old = String::from_utf8_lossy(&old_head.stdout).trim().to_string();

fs::write(src.path().join("next.txt"), "next\n").expect("next file");
assert_cli_success(
&run_libra_command(&["add", "next.txt"], src.path()),
"add next",
);
assert_cli_success(
&run_libra_command(&["commit", "-m", "next", "--no-verify"], src.path()),
"commit next",
);
assert_cli_success(
&run_libra_command(
&["bundle", "create", bundle.to_str().unwrap(), "--all"],
src.path(),
),
"replace bundle",
);
let new_src = run_libra_command(&["rev-parse", "HEAD"], src.path());
assert_cli_success(&new_src, "src HEAD");
let expected = String::from_utf8_lossy(&new_src.stdout).trim().to_string();

let pull = run_libra_command(&["pull"], &dest);
assert_cli_success(&pull, "H2 pull from replaced bundle");
let new_head = run_libra_command(&["rev-parse", "HEAD"], &dest);
assert_cli_success(&new_head, "new HEAD");
let got = String::from_utf8_lossy(&new_head.stdout).trim().to_string();
assert_ne!(got, old, "H2 pull must move HEAD");
assert_eq!(got, expected, "H2 pull must fast-forward to the new tip");
assert!(dest.join("next.txt").exists(), "H2 pull restores new file");
}
Loading