docs: explain repository sync boundaries
All checks were successful
labels / labels (pull_request) Successful in 16s
ci / test (pull_request) Successful in 28s

This commit is contained in:
codex-bot-andresmgsl 2026-08-31 16:59:10 +00:00
parent b21a1387a5
commit 04e6ba60e8
3 changed files with 61 additions and 2 deletions

View file

@ -291,6 +291,31 @@ stoke repo clone -o heavy-duty -r stoke ~/src/stoke --depth 1
The stored token is handed to git ephemerally through environment-based config (`GIT_CONFIG_*`): it never appears in the remote URL, on the command line, or in the cloned repository's `.git/config`. Git's output is streamed directly and its exit status is forwarded, so failures behave exactly like a plain `git clone`.
### `stoke repo sync`
Fast-forward an undiverged imported repository from an upstream Git URL.
```text
Options:
-o, --owner <owner> repository owner (required)
-r, --repo <repo> repository name (required)
--from <upstream-url> upstream Git URL (required)
--branch <branch> branch to synchronize (default: the forge repository's default branch)
--tags also create new upstream tags
--dry-run report branch and tag moves without pushing
```
```bash
stoke repo sync -o heavy-duty -r box \
--from https://github.com/heavy-duty/box.git
stoke repo sync -o heavy-duty -r box \
--from https://github.com/heavy-duty/box.git --tags --dry-run
```
The command fetches both branch tips into an ephemeral bare repository and pushes only when the forge tip is an ancestor of the upstream tip. It refuses a diverged tree with both commit SHAs and never offers a force option. With `--tags`, new upstream tags are created; an existing forge tag that points elsewhere is reported and left untouched, and the command exits non-zero after applying any other safe moves. The stored Forgejo token uses the same environment-only Git authentication as `repo clone` and is never written to an argument, remote, or Git config.
This verb deliberately does not merge diverged trees, configure Forgejo pull-mirrors, or copy releases. Follow ceremony's live `docs/UPSTREAM-SYNC.md` procedure for a diverged tree; import a scheduled read-only repository as a pull-mirror; compose release mirroring from `release create` and `release upload`.
### `stoke repo create`
Create a new repository for the authenticated user or an organization.

1
changelog.d/23.md Normal file
View file

@ -0,0 +1 @@
- Added `repo sync` for credential-safe, fast-forward-only branch and tag updates with dry-run and divergence protection. (#23).

View file

@ -7,6 +7,7 @@ const path = require('node:path');
const CLI = path.join(__dirname, '..', 'src', 'cli.js');
const TOKEN = 'stoke-secret-token-for-sync-tests';
const REAL_GIT = execFileSync('which', ['git'], { encoding: 'utf8' }).trim();
function git(args, cwd) {
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
@ -64,7 +65,7 @@ function refSha(repository, ref) {
return result.status === 0 ? result.stdout.trim() : null;
}
function runSync(fx, extra = [], { branch = 'main' } = {}) {
function runSync(fx, extra = [], { branch = 'main', env = {} } = {}) {
const args = [
CLI,
'repo',
@ -77,7 +78,7 @@ function runSync(fx, extra = [], { branch = 'main' } = {}) {
args.push(...extra);
return spawnSync(process.execPath, args, {
encoding: 'utf8',
env: { ...process.env, STOKE_CONFIG_FILE: fx.config },
env: { ...process.env, STOKE_CONFIG_FILE: fx.config, ...env },
});
}
@ -179,3 +180,35 @@ test('repo sync --dry-run reports branch and tag moves without writing', () => {
fx.cleanup();
}
});
test('repo sync keeps the token out of Git argv, output, remotes, and config', () => {
const fx = fixture();
try {
const wrapperDirectory = path.join(fx.root, 'bin');
const argvLog = path.join(fx.root, 'git-argv.log');
const wrapper = path.join(wrapperDirectory, 'git');
fs.mkdirSync(wrapperDirectory);
fs.writeFileSync(wrapper, `#!/bin/sh\nprintf '%s\\n' "$@" >> "$STOKE_TEST_GIT_ARGV"\nexec "${REAL_GIT}" "$@"\n`);
fs.chmodSync(wrapper, 0o755);
const result = runSync(fx, [], {
env: {
PATH: `${wrapperDirectory}:${process.env.PATH}`,
STOKE_TEST_GIT_ARGV: argvLog,
},
});
assert.equal(result.status, 0, result.stderr);
for (const text of [
result.stdout,
result.stderr,
fs.readFileSync(argvLog, 'utf8'),
fs.readFileSync(path.join(fx.forgeRepo, 'config'), 'utf8'),
fs.readFileSync(path.join(fx.upstreamRepo, 'config'), 'utf8'),
]) {
assert.ok(!text.includes(TOKEN), 'token leaked from the environment-only auth path');
}
} finally {
fx.cleanup();
}
});