diff --git a/src/cli.js b/src/cli.js index addb70a..c1888d7 100755 --- a/src/cli.js +++ b/src/cli.js @@ -467,8 +467,6 @@ repo if (!config || !config.url || !config.token) { throw new Error('Not authenticated. Run: stoke auth login'); } - if (!options.branch) throw new Error('--branch is required'); - const base = config.url.replace(/\/+$/, ''); const forgeUrl = `${base}/${encodeURIComponent(options.owner)}/${encodeURIComponent(options.repo)}.git`; const result = syncRepository({ @@ -477,7 +475,11 @@ repo branch: options.branch, env: gitAuthEnv({ ...config, url: base }), }); - console.log(`${result.branch} ${result.oldSha}..${result.newSha}`); + if (result.changed) { + console.log(`${result.branch} ${result.oldSha}..${result.newSha}`); + } else { + console.log(`${result.branch} is up to date at ${result.newSha}`); + } } catch (err) { console.error(`Repository sync failed: ${err.message}`); process.exit(1); diff --git a/src/repo-sync.js b/src/repo-sync.js index 43b5132..3b45379 100644 --- a/src/repo-sync.js +++ b/src/repo-sync.js @@ -19,6 +19,15 @@ function syncRepository({ forgeUrl, upstreamUrl, branch, env }) { try { runGit(['init', '--bare', directory], { cwd: directory, env }); + if (!branch) { + const symbolicHead = runGit(['ls-remote', '--symref', forgeUrl, 'HEAD'], { + cwd: directory, + env, + }).stdout; + const match = symbolicHead.match(/^ref:\s+refs\/heads\/(.+)\s+HEAD$/m); + if (!match) throw new Error('Could not resolve the forge repository default branch'); + branch = match[1]; + } runGit(['fetch', '--no-tags', forgeUrl, `refs/heads/${branch}:${forgeRef}`], { cwd: directory, env, @@ -45,7 +54,7 @@ function syncRepository({ forgeUrl, upstreamUrl, branch, env }) { env, }); } - return { branch, oldSha, newSha }; + return { branch, oldSha, newSha, changed: oldSha !== newSha }; } finally { fs.rmSync(directory, { recursive: true, force: true }); } diff --git a/test/sync.test.js b/test/sync.test.js index 4ef49c1..fd72c2c 100644 --- a/test/sync.test.js +++ b/test/sync.test.js @@ -55,17 +55,18 @@ function fixture() { }; } -function runSync(fx, extra = []) { - return spawnSync(process.execPath, [ +function runSync(fx, extra = [], { branch = 'main' } = {}) { + const args = [ CLI, 'repo', 'sync', '-o', 'o', '-r', 'r', '--from', `file://${fx.upstreamRepo}`, - '--branch', 'main', - ...extra, - ], { + ]; + if (branch) args.push('--branch', branch); + args.push(...extra); + return spawnSync(process.execPath, args, { encoding: 'utf8', env: { ...process.env, STOKE_CONFIG_FILE: fx.config }, }); @@ -83,3 +84,30 @@ test('repo sync fast-forwards an undiverged forge branch', () => { fx.cleanup(); } }); + +test('repo sync resolves an omitted branch from the forge symbolic HEAD', () => { + const fx = fixture(); + try { + const result = runSync(fx, [], { branch: null }); + + assert.equal(result.status, 0, result.stderr); + assert.equal(git(['rev-parse', 'refs/heads/main'], fx.forgeRepo), fx.newSha); + assert.match(result.stdout, new RegExp(`main ${fx.oldSha}\\.\\.${fx.newSha}`)); + } finally { + fx.cleanup(); + } +}); + +test('repo sync reports an already-current branch as a no-op', () => { + const fx = fixture(); + try { + assert.equal(runSync(fx).status, 0); + const result = runSync(fx); + + assert.equal(result.status, 0, result.stderr); + assert.equal(git(['rev-parse', 'refs/heads/main'], fx.forgeRepo), fx.newSha); + assert.match(result.stdout, new RegExp(`main is up to date at ${fx.newSha}`)); + } finally { + fx.cleanup(); + } +});