diff --git a/spec/init-spec.js b/spec/init-spec.js index 67706ce5..432c79ca 100644 --- a/spec/init-spec.js +++ b/spec/init-spec.js @@ -2,6 +2,7 @@ const path = require('path'); const temp = require('temp'); const CSON = require('season'); const fs = require('../src/fs'); +const PackageConverter = require('../src/package-converter'); describe('apm init', () => { let languagePath, packagePath, themePath; @@ -210,3 +211,25 @@ describe('apm init', () => { }); }); }); + +describe('PackageConverter.getDownloadUrl', () => { + it('uses HEAD instead of a hardcoded branch name', () => { + const converter = new PackageConverter( + 'https://github.com/textmate/r.tmbundle', + '/tmp/fake-dest' + ); + expect(converter.getDownloadUrl()).toBe( + 'https://github.com/textmate/r.tmbundle/archive/HEAD.tar.gz' + ); + }); + + it('strips trailing .git and slashes before appending archive path', () => { + const converter = new PackageConverter( + 'https://github.com/textmate/r.tmbundle.git/', + '/tmp/fake-dest' + ); + expect(converter.getDownloadUrl()).toBe( + 'https://github.com/textmate/r.tmbundle/archive/HEAD.tar.gz' + ); + }); +}); \ No newline at end of file diff --git a/spec/publish-spec.js b/spec/publish-spec.js index ed92d96d..d2b0a9c2 100644 --- a/spec/publish-spec.js +++ b/spec/publish-spec.js @@ -248,6 +248,41 @@ describe('apm publish', () => { expect(callback.calls.mostRecent().args[0]).toBeUndefined(); }); + it('publishes successfully with --branch flag', async () => { + const packageToPublish = temp.mkdirSync('apm-test-package-'); + const metadata = { + name: 'test', + version: '1.0.0', + "repository": { + "type": "git", + "url": "https://github.com/pulsar-edit/foo" + }, + engines: { + atom: '1' + }, + dependencies: { + foo: '^5' + }, + devDependencies: { + abc: 'git://github.com/user/project.git', + abcd: 'latest', + } + }; + fs.writeFileSync( + path.join(packageToPublish, 'package.json'), + JSON.stringify(metadata) + ); + process.chdir(packageToPublish); + + childProcess.execSync('git init', { cwd: packageToPublish }); + childProcess.execSync('git remote add origin https://github.com/pulsar-edit/foo', { cwd: packageToPublish }); + + const callback = jasmine.createSpy('callback'); + await apmRun(['publish', 'patch', '--branch', 'main'], callback); + expect(requests.length).toBe(1); + expect(callback.calls.mostRecent().args[0]).toBeUndefined(); + }); + it('publishes successfully when the package exists and is being renamed', async () => { spyOn(Publish.prototype, 'packageExists').and.callFake((name) => { // If we're renaming the package, we need to ask the API if the package's @@ -294,3 +329,53 @@ describe('apm publish', () => { expect(callback.calls.mostRecent().args[0]).toBeUndefined(); }); }); + + + +describe('Publish.getDefaultBranch', () => { + let publish; + + beforeEach(() => { + publish = new Publish(); + }); + + it('falls back to main when symbolic-ref is unavailable', () => { + // execSync is bound at import time, so we call getDefaultBranch + // outside a real git repo where symbolic-ref will naturally fail + const repo = { + getConfigValue: jasmine.createSpy('getConfigValue').and.callFake((key) => { + if (key === 'branch.main.remote') return 'origin'; + return null; + }) + }; + expect(publish.getDefaultBranch(repo)).toBe('main'); + }); + + it('falls back to master when main is not configured', () => { + const repo = { + getConfigValue: jasmine.createSpy('getConfigValue').and.callFake((key) => { + if (key === 'branch.master.remote') return 'origin'; + return null; + }) + }; + expect(publish.getDefaultBranch(repo)).toBe('master'); + }); + + it('returns null when no default branch can be determined', () => { + const repo = { + getConfigValue: jasmine.createSpy('getConfigValue').and.returnValue(null) + }; + expect(publish.getDefaultBranch(repo)).toBeNull(); + }); + + it('prefers main over master', () => { + const repo = { + getConfigValue: jasmine.createSpy('getConfigValue').and.callFake((key) => { + if (key === 'branch.main.remote') return 'origin'; + if (key === 'branch.master.remote') return 'origin'; + return null; + }) + }; + expect(publish.getDefaultBranch(repo)).toBe('main'); + }); +}); \ No newline at end of file diff --git a/src/package-converter.js b/src/package-converter.js index a93642f1..d6f4ee34 100644 --- a/src/package-converter.js +++ b/src/package-converter.js @@ -48,7 +48,7 @@ class PackageConverter { getDownloadUrl() { let downloadUrl = this.sourcePath; downloadUrl = downloadUrl.replace(/(\.git)?\/*$/, ''); - return downloadUrl += '/archive/master.tar.gz'; + return downloadUrl += '/archive/HEAD.tar.gz'; } async downloadBundle() { diff --git a/src/publish.js b/src/publish.js index 38d23c99..18b4a82f 100644 --- a/src/publish.js +++ b/src/publish.js @@ -1,5 +1,6 @@ const path = require('path'); +const { execSync } = require('child_process'); const yargs = require('yargs'); const Git = require('git-utils'); @@ -29,6 +30,7 @@ class Publish extends Command { Usage: ppm publish [ | major | minor | patch | build] ppm publish --tag ppm publish --rename + ppm publish --branch Publish a new version of the package in the current working directory. @@ -50,7 +52,8 @@ have published it.\ ); options.alias('h', 'help').describe('help', 'Print this usage message'); options.alias('t', 'tag').string('tag').describe('tag', 'Specify a tag to publish. Must be of the form vx.y.z'); - return options.alias('r', 'rename').string('rename').describe('rename', 'Specify a new name for the package'); + options.alias('r', 'rename').string('rename').describe('rename', 'Specify a new name for the package'); + return options.alias('b', 'branch').string('branch').describe('branch', 'Specify the default branch of the package repository'); } // Create a new version and tag use the `npm version` command. @@ -272,7 +275,25 @@ have published it.\ fs.writeFileSync(metadataPath, `${metadataJson}\n`); } - loadRepository() { + getDefaultBranch(repo) { + try { + const ref = execSync('git symbolic-ref refs/remotes/origin/HEAD', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }).trim(); + const match = ref.match(/refs\/remotes\/origin\/(.+)/); + if (match) { return match[1]; } + } catch { /* symbolic-ref may not exist; fall through to heuristic */ } + + for (const branch of ['main', 'master']) { + if (repo.getConfigValue(`branch.${branch}.remote`)) { + return branch; + } + } + return null; + } + + loadRepository(branch) { let currentBranch, remoteName, upstreamUrl; const currentDirectory = process.cwd(); @@ -281,12 +302,16 @@ have published it.\ throw new Error('Package must be in a Git repository before publishing: https://help.github.com/articles/create-a-repo'); } - currentBranch = repo.getShortHead(); if (currentBranch) { remoteName = repo.getConfigValue(`branch.${currentBranch}.remote`); } - if (remoteName == null) { remoteName = repo.getConfigValue('branch.master.remote'); } + if (remoteName == null) { + const defaultBranch = branch || this.getDefaultBranch(repo); + if (defaultBranch && defaultBranch !== currentBranch) { + remoteName = repo.getConfigValue(`branch.${defaultBranch}.remote`); + } + } if (remoteName) { upstreamUrl = repo.getConfigValue(`remote.${remoteName}.url`); } if (upstreamUrl == null) { upstreamUrl = repo.getConfigValue('remote.origin.url'); } @@ -387,7 +412,7 @@ have published it.\ async run(options) { let pack, originalName; options = this.parseOptions(options.commandArgs); - let {tag, rename} = options.argv; + let {tag, rename, branch} = options.argv; let [version] = options.argv._; // Normalize variables to ensure they are strings with zero length @@ -416,7 +441,7 @@ have published it.\ } try { - this.loadRepository(); + this.loadRepository(branch); } catch (error) { return error; }