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 install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ clone_nemoclaw_ref() {

git init --quiet "$dest"
git -C "$dest" remote add origin https://github.com/NVIDIA/NemoClaw.git
if ! git -C "$dest" fetch --quiet --depth 1 origin "$ref"; then
if ! git -C "$dest" fetch --quiet --depth 1 origin "+${ref}:refs/nemoclaw-install/target"; then
printf "[ERROR] Requested install ref '%s' is not available from https://github.com/NVIDIA/NemoClaw.git.\n" "$ref" >&2
printf " Check NEMOCLAW_INSTALL_TAG/NEMOCLAW_INSTALL_REF and try again.\n" >&2
exit 1
fi
git -C "$dest" -c advice.detachedHead=false checkout --quiet --detach FETCH_HEAD
git -C "$dest" -c advice.detachedHead=false checkout --quiet --detach refs/nemoclaw-install/target
}

exec_installer_from_ref() {
Expand Down
57 changes: 41 additions & 16 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,18 @@ resolve_installer_version() {
printf "%s" "${NEMOCLAW_INSTALL_REF#v}"
return
fi
# Prefer git tags (works in dev clones and CI)
# Prefer the .version file stamped during install: it records the exact
# requested tag, while describe on a shallow clone can resolve a different
# nearby tag.
if [[ -f "${repo_root}/.version" ]]; then
local file_ver
file_ver="$(cat "${repo_root}/.version")"
if [[ -n "$file_ver" ]]; then
printf "%s" "$file_ver"
return
fi
fi
# Fall back to git tags (dev clones and CI have no .version)
if command -v git &>/dev/null && [[ -e "${repo_root}/.git" ]]; then
local git_ver=""
if git_ver="$(git -C "$repo_root" describe --tags --match 'v*' 2>/dev/null)"; then
Expand All @@ -77,15 +88,6 @@ resolve_installer_version() {
fi
fi
fi
# Fall back to .version file (stamped during install)
if [[ -f "${repo_root}/.version" ]]; then
local file_ver
file_ver="$(cat "${repo_root}/.version")"
if [[ -n "$file_ver" ]]; then
printf "%s" "$file_ver"
return
fi
fi
# Last resort: package.json
local package_json="${repo_root}/package.json"
local version=""
Expand Down Expand Up @@ -151,15 +153,32 @@ resolve_release_tag() {
printf "%s" "${NEMOCLAW_INSTALL_TAG:-$DEFAULT_INSTALL_REF}"
}

# Map an install ref to the version string to stamp into .version. Prints the
# semver for an immutable version tag; prints nothing for mutable (lkg/latest)
# or non-version refs so callers fall back to git describe.
resolve_stamped_version() {
local ref="${1:-}" version=""
case "$ref" in
refs/tags/*) version="${ref#refs/tags/}" ;;
*) version="$ref" ;;
esac
version="${version#v}"
if is_mutable_install_ref "$ref" \
|| ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
return 0
fi
printf "%s" "$version"
}

clone_nemoclaw_ref() {
local ref="$1" dest="$2"

git init --quiet "$dest"
git -C "$dest" remote add origin https://github.com/NVIDIA/NemoClaw.git
if ! git -C "$dest" fetch --quiet --depth 1 origin "$ref"; then
if ! git -C "$dest" fetch --quiet --depth 1 origin "+${ref}:refs/nemoclaw-install/target"; then
error "Requested install ref '$ref' is not available from https://github.com/NVIDIA/NemoClaw.git. Check NEMOCLAW_INSTALL_TAG/NEMOCLAW_INSTALL_REF and try again."
fi
git -C "$dest" -c advice.detachedHead=false checkout --quiet --detach FETCH_HEAD
git -C "$dest" -c advice.detachedHead=false checkout --quiet --detach refs/nemoclaw-install/target
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1714,10 +1733,16 @@ install_nemoclaw() {
# --match "v*"` works at runtime (the shallow clone only has the
# single ref we asked for).
git -C "$nemoclaw_src" fetch --depth=1 origin 'refs/tags/v*:refs/tags/v*' 2>/dev/null || true
# Also stamp .version as a fallback for environments where git is
# unavailable or tags are pruned later.
git -C "$nemoclaw_src" describe --tags --match 'v*' 2>/dev/null \
| sed 's/^v//' >"$nemoclaw_src/.version" || true
# Stamp .version from the requested ref so the recorded version matches the
# installed tag, even when a shallow clone cannot name it via describe.
local stamped_version
stamped_version="$(resolve_stamped_version "$release_ref")"
if [[ -n "$stamped_version" ]]; then
printf '%s' "$stamped_version" >"$nemoclaw_src/.version"
else
git -C "$nemoclaw_src" describe --tags --match 'v*' 2>/dev/null \
| sed 's/^v//' >"$nemoclaw_src/.version" || true
fi
if [[ -z "${NEMOCLAW_AGENT:-}" || "${NEMOCLAW_AGENT}" == "openclaw" ]]; then
spin "Preparing OpenClaw package" bash -c "$(declare -f info warn resolve_openclaw_version pre_extract_openclaw); pre_extract_openclaw \"\$1\"" _ "$nemoclaw_src" \
|| warn "Pre-extraction failed — npm install may fail if openclaw tarball is broken"
Expand Down
123 changes: 123 additions & 0 deletions test/install-clone-ref.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,127 @@ describe("installer git checkout", () => {
fs.rmSync(tmp, { recursive: true, force: true });
}
});

it("checks out a lightweight tag at its commit, not the branch tip (#7474)", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-clone-tag-"));
const origin = path.join(tmp, "origin");
fs.mkdirSync(origin);
const git = (args: string[], cwd = origin) => spawnSync("git", args, { cwd, encoding: "utf8" });

try {
expect(git(["init", "--initial-branch=main"]).status).toBe(0);
expect(git(["config", "user.name", "NemoClaw Test"]).status).toBe(0);
expect(git(["config", "user.email", "nemoclaw-test@example.invalid"]).status).toBe(0);
fs.writeFileSync(path.join(origin, "README.md"), "tagged\n");
expect(git(["add", "README.md"]).status).toBe(0);
expect(git(["-c", "commit.gpgsign=false", "commit", "-m", "tagged release"]).status).toBe(0);
const taggedHead = git(["rev-parse", "HEAD"]).stdout.trim();
expect(git(["-c", "tag.gpgSign=false", "tag", "v9.9.9"]).status).toBe(0);
fs.writeFileSync(path.join(origin, "README.md"), "post-release\n");
expect(git(["add", "README.md"]).status).toBe(0);
expect(git(["-c", "commit.gpgsign=false", "commit", "-m", "post release"]).status).toBe(0);
expect(git(["rev-parse", "HEAD"]).stdout.trim()).not.toBe(taggedHead);

for (const [index, installer] of [INSTALLER_PAYLOAD, CURL_PIPE_INSTALLER].entries()) {
const destination = path.join(tmp, `checkout-${index}`);
const result = spawnSync(
"bash",
["-c", 'source "$INSTALLER_UNDER_TEST"\nclone_nemoclaw_ref v9.9.9 "$DESTINATION"'],
{
encoding: "utf8",
env: {
...process.env,
DESTINATION: destination,
GIT_CONFIG_COUNT: "1",
GIT_CONFIG_KEY_0: `url.file://${origin}.insteadOf`,
GIT_CONFIG_VALUE_0: "https://github.com/NVIDIA/NemoClaw.git",
INSTALLER_UNDER_TEST: installer,
},
},
);
expect(result.status, result.stderr).toBe(0);
expect(git(["-C", destination, "rev-parse", "HEAD"], tmp).stdout.trim()).toBe(taggedHead);
expect(git(["-C", destination, "symbolic-ref", "-q", "HEAD"], tmp).status).not.toBe(0);
}
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
});
});

describe("installer version stamping", () => {
const extract = (stdout: string) => stdout.match(/START([\s\S]*?)STOP/)?.[1] ?? null;

it("stamps a requested version tag and defers mutable refs to describe (#7474)", () => {
for (const installer of [INSTALLER_PAYLOAD, CURL_PIPE_INSTALLER]) {
const stamp = (ref: string) => {
const result = spawnSync(
"bash",
[
"-c",
'source "$INSTALLER_UNDER_TEST"\nprintf START\nresolve_stamped_version "$REF"\nprintf STOP',
],
{ encoding: "utf8", env: { ...process.env, INSTALLER_UNDER_TEST: installer, REF: ref } },
);
expect(result.status, result.stderr).toBe(0);
return extract(result.stdout);
};
expect(stamp("v0.0.93")).toBe("0.0.93");
expect(stamp("refs/tags/v1.2.3")).toBe("1.2.3");
expect(stamp("lkg")).toBe("");
expect(stamp("latest")).toBe("");
expect(stamp("main")).toBe("");
}
});

it("reports the stamped .version over a mismatched git describe (#7474)", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-version-"));
const git = (args: string[]) => spawnSync("git", args, { cwd: tmp, encoding: "utf8" });

try {
fs.writeFileSync(path.join(tmp, "package.json"), `${JSON.stringify({ version: "0.0.1" })}\n`);
expect(git(["init", "--initial-branch=main"]).status).toBe(0);
expect(git(["config", "user.name", "NemoClaw Test"]).status).toBe(0);
expect(git(["config", "user.email", "nemoclaw-test@example.invalid"]).status).toBe(0);
fs.writeFileSync(path.join(tmp, "README.md"), "release\n");
expect(git(["add", "."]).status).toBe(0);
expect(git(["-c", "commit.gpgsign=false", "commit", "-m", "release"]).status).toBe(0);
expect(
git(["-c", "tag.gpgSign=false", "tag", "-a", "v0.0.38", "-m", "old release"]).status,
).toBe(0);

const resolve = (installer: string) =>
spawnSync(
"bash",
[
"-c",
'source "$INSTALLER_UNDER_TEST"\nprintf START\nresolve_installer_version\nprintf STOP',
],
{
encoding: "utf8",
env: {
...process.env,
INSTALLER_UNDER_TEST: installer,
NEMOCLAW_REPO_ROOT: tmp,
NEMOCLAW_INSTALL_REF: "",
NEMOCLAW_INSTALL_TAG: "",
},
},
);

for (const installer of [INSTALLER_PAYLOAD, CURL_PIPE_INSTALLER]) {
fs.writeFileSync(path.join(tmp, ".version"), "0.0.93");
const withStamp = resolve(installer);
expect(withStamp.status, withStamp.stderr).toBe(0);
expect(extract(withStamp.stdout)).toBe("0.0.93");

fs.rmSync(path.join(tmp, ".version"));
const withoutStamp = resolve(installer);
expect(withoutStamp.status, withoutStamp.stderr).toBe(0);
expect(extract(withoutStamp.stdout)).toBe("0.0.38");
}
} finally {
fs.rmSync(tmp, { recursive: true, force: true });
}
});
});
2 changes: 1 addition & 1 deletion test/install-preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3404,7 +3404,7 @@ exit 0`,
expect(result.status).not.toBe(0);
expect(output).toMatch(/Requested install ref 'v9\.9\.9' is not available/);
expect(output).toMatch(/Check NEMOCLAW_INSTALL_TAG\/NEMOCLAW_INSTALL_REF/);
expect(fs.readFileSync(gitLog, "utf-8")).toMatch(/fetch --quiet --depth 1 origin v9\.9\.9/);
expect(fs.readFileSync(gitLog, "utf-8")).toMatch(/\+v9\.9\.9:refs\/nemoclaw-install\/target/);
});

it("falls back to the legacy root installer when the selected ref only has the old scripts/install.sh wrapper", () => {
Expand Down
Loading