docs: explain repository sync boundaries
This commit is contained in:
parent
b21a1387a5
commit
04e6ba60e8
3 changed files with 61 additions and 2 deletions
25
README.md
25
README.md
|
|
@ -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`.
|
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`
|
### `stoke repo create`
|
||||||
|
|
||||||
Create a new repository for the authenticated user or an organization.
|
Create a new repository for the authenticated user or an organization.
|
||||||
|
|
|
||||||
1
changelog.d/23.md
Normal file
1
changelog.d/23.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
- Added `repo sync` for credential-safe, fast-forward-only branch and tag updates with dry-run and divergence protection. (#23).
|
||||||
|
|
@ -7,6 +7,7 @@ const path = require('node:path');
|
||||||
|
|
||||||
const CLI = path.join(__dirname, '..', 'src', 'cli.js');
|
const CLI = path.join(__dirname, '..', 'src', 'cli.js');
|
||||||
const TOKEN = 'stoke-secret-token-for-sync-tests';
|
const TOKEN = 'stoke-secret-token-for-sync-tests';
|
||||||
|
const REAL_GIT = execFileSync('which', ['git'], { encoding: 'utf8' }).trim();
|
||||||
|
|
||||||
function git(args, cwd) {
|
function git(args, cwd) {
|
||||||
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
|
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
|
||||||
|
|
@ -64,7 +65,7 @@ function refSha(repository, ref) {
|
||||||
return result.status === 0 ? result.stdout.trim() : null;
|
return result.status === 0 ? result.stdout.trim() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runSync(fx, extra = [], { branch = 'main' } = {}) {
|
function runSync(fx, extra = [], { branch = 'main', env = {} } = {}) {
|
||||||
const args = [
|
const args = [
|
||||||
CLI,
|
CLI,
|
||||||
'repo',
|
'repo',
|
||||||
|
|
@ -77,7 +78,7 @@ function runSync(fx, extra = [], { branch = 'main' } = {}) {
|
||||||
args.push(...extra);
|
args.push(...extra);
|
||||||
return spawnSync(process.execPath, args, {
|
return spawnSync(process.execPath, args, {
|
||||||
encoding: 'utf8',
|
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();
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue