mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-11 04:22:13 +00:00
[cli] Support multiple remote URLs in Git config (#8145)
Two features that handle a user's local Git config have been shipped: - #8100 - #7910 Both of these features currently pull only from the user's remote origin URL. This covers 90% of cases, but there are cases in which the user has more than one remote URL, and may want to use one other than the origin URL, especially in `vc git connect`. This PR: - Adds support for multiple remote URLs in a user's Git config - Updates `vc git connect` to prompt the user to select a URL if they have multiple remote URLs - Updates `createGitMeta` to send the connected Git repository url by default, origin url otherwise ### 📋 Checklist <!-- Please keep your PR as a Draft until the checklist is complete --> #### Tests - [x] The code changed/added as part of this PR has been covered with tests - [x] All tests pass locally with `yarn test-unit` #### Code Review - [ ] This PR has a concise title and thorough description useful to a reviewer - [ ] Issue from task tracker has a link to this PR
This commit is contained in:
@@ -428,7 +428,7 @@ export default async (client: Client) => {
|
||||
parseMeta(argv['--meta'])
|
||||
);
|
||||
|
||||
const gitMetadata = await createGitMeta(path, output);
|
||||
const gitMetadata = await createGitMeta(path, output, project);
|
||||
|
||||
// Merge dotenv config, `env` from vercel.json, and `--env` / `-e` arguments
|
||||
const deploymentEnv = Object.assign(
|
||||
|
||||
@@ -2,8 +2,9 @@ import chalk from 'chalk';
|
||||
import { join } from 'path';
|
||||
import { Org, Project } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { parseGitConfig, pluckRemoteUrl } from '../../util/create-git-meta';
|
||||
import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import list, { ListChoice } from '../../util/input/list';
|
||||
import { Output } from '../../util/output';
|
||||
import link from '../../util/output/link';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
@@ -64,20 +65,37 @@ export default async function connect(
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
const remoteUrl = pluckRemoteUrl(gitConfig);
|
||||
if (!remoteUrl) {
|
||||
const remoteUrls = pluckRemoteUrls(gitConfig);
|
||||
if (!remoteUrls) {
|
||||
output.error(
|
||||
`No remote origin URL found in your Git config. Make sure you've configured a remote repo in your local Git config. Run ${chalk.cyan(
|
||||
`No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run ${chalk.cyan(
|
||||
'`git remote --help`'
|
||||
)} for more details.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
output.log(`Identified Git remote "origin": ${link(remoteUrl)}`);
|
||||
|
||||
let remoteUrl: string;
|
||||
|
||||
if (Object.keys(remoteUrls).length > 1) {
|
||||
output.log(`Found multiple remote URLs.`);
|
||||
remoteUrl = await selectRemoteUrl(client, remoteUrls);
|
||||
} else {
|
||||
// If only one is found, get it — usually "origin"
|
||||
remoteUrl = Object.values(remoteUrls)[0];
|
||||
}
|
||||
|
||||
if (remoteUrl === '') {
|
||||
output.log('Aborted.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
|
||||
|
||||
const parsedUrl = parseRepoUrl(remoteUrl);
|
||||
if (!parsedUrl) {
|
||||
output.error(
|
||||
`Failed to parse Git repo data from the following remote URL in your Git config: ${link(
|
||||
`Failed to parse Git repo data from the following remote URL: ${link(
|
||||
remoteUrl
|
||||
)}`
|
||||
);
|
||||
@@ -166,3 +184,22 @@ async function confirmRepoConnect(
|
||||
}
|
||||
return shouldReplaceProject;
|
||||
}
|
||||
|
||||
async function selectRemoteUrl(
|
||||
client: Client,
|
||||
remoteUrls: { [key: string]: string }
|
||||
): Promise<string> {
|
||||
let choices: ListChoice[] = [];
|
||||
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {
|
||||
choices.push({
|
||||
name: `${urlValue} ${chalk.gray(`(${urlKey})`)}`,
|
||||
value: urlValue,
|
||||
short: urlKey,
|
||||
});
|
||||
}
|
||||
|
||||
return await list(client, {
|
||||
message: 'Which remote do you want to connect?',
|
||||
choices,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,76 +3,42 @@ import { join } from 'path';
|
||||
import ini from 'ini';
|
||||
import git from 'git-last-commit';
|
||||
import { exec } from 'child_process';
|
||||
import { GitMetadata } from '../types';
|
||||
import { GitMetadata, Project } from '../types';
|
||||
import { Output } from './output';
|
||||
|
||||
export function isDirty(directory: string, output: Output): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
|
||||
let debugMessage = `Failed to determine if Git repo has been modified:`;
|
||||
if (err || stderr) {
|
||||
if (err) debugMessage += `\n${err}`;
|
||||
if (stderr) debugMessage += `\n${stderr.trim()}`;
|
||||
output.debug(debugMessage);
|
||||
return resolve(false);
|
||||
}
|
||||
resolve(stdout.trim().length > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getLastCommit(directory: string): Promise<git.Commit> {
|
||||
return new Promise((resolve, reject) => {
|
||||
git.getLastCommit(
|
||||
(err, commit) => {
|
||||
if (err) return reject(err);
|
||||
resolve(commit);
|
||||
},
|
||||
{ dst: directory }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function parseGitConfig(configPath: string, output: Output) {
|
||||
try {
|
||||
return ini.parse(await fs.readFile(configPath, 'utf-8'));
|
||||
} catch (error) {
|
||||
output.debug(`Error while parsing repo data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function pluckRemoteUrl(gitConfig: {
|
||||
[key: string]: any;
|
||||
}): string | undefined {
|
||||
// Assuming "origin" is the remote url that the user would want to use
|
||||
return gitConfig['remote "origin"']?.url;
|
||||
}
|
||||
|
||||
export async function getRemoteUrl(
|
||||
configPath: string,
|
||||
output: Output
|
||||
): Promise<string | null> {
|
||||
let gitConfig = await parseGitConfig(configPath, output);
|
||||
if (!gitConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originUrl = pluckRemoteUrl(gitConfig);
|
||||
if (originUrl) {
|
||||
return originUrl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function createGitMeta(
|
||||
directory: string,
|
||||
output: Output
|
||||
output: Output,
|
||||
project?: Project | null
|
||||
): Promise<GitMetadata | undefined> {
|
||||
const remoteUrl = await getRemoteUrl(join(directory, '.git/config'), output);
|
||||
// If a Git repository is already connected via `vc git`, use that remote url
|
||||
let remoteUrl;
|
||||
if (project?.link) {
|
||||
// in the form of org/repo
|
||||
const { repo } = project.link;
|
||||
|
||||
const remoteUrls = await getRemoteUrls(
|
||||
join(directory, '.git/config'),
|
||||
output
|
||||
);
|
||||
if (remoteUrls) {
|
||||
for (const urlValue of Object.values(remoteUrls)) {
|
||||
if (urlValue.includes(repo)) {
|
||||
remoteUrl = urlValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't get a remote url from the connected repo, default to the origin url
|
||||
if (!remoteUrl) {
|
||||
remoteUrl = await getOriginUrl(join(directory, '.git/config'), output);
|
||||
}
|
||||
// If we can't get the repo URL, then don't return any metadata
|
||||
if (!remoteUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [commit, dirty] = await Promise.all([
|
||||
getLastCommit(directory).catch(err => {
|
||||
output.debug(
|
||||
@@ -96,3 +62,97 @@ export async function createGitMeta(
|
||||
dirty,
|
||||
};
|
||||
}
|
||||
|
||||
function getLastCommit(directory: string): Promise<git.Commit> {
|
||||
return new Promise((resolve, reject) => {
|
||||
git.getLastCommit(
|
||||
(err, commit) => {
|
||||
if (err) return reject(err);
|
||||
resolve(commit);
|
||||
},
|
||||
{ dst: directory }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function isDirty(directory: string, output: Output): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
exec('git status -s', { cwd: directory }, function (err, stdout, stderr) {
|
||||
let debugMessage = `Failed to determine if Git repo has been modified:`;
|
||||
if (err || stderr) {
|
||||
if (err) debugMessage += `\n${err}`;
|
||||
if (stderr) debugMessage += `\n${stderr.trim()}`;
|
||||
output.debug(debugMessage);
|
||||
return resolve(false);
|
||||
}
|
||||
resolve(stdout.trim().length > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function parseGitConfig(configPath: string, output: Output) {
|
||||
try {
|
||||
return ini.parse(await fs.readFile(configPath, 'utf-8'));
|
||||
} catch (error) {
|
||||
output.debug(`Error while parsing repo data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function pluckRemoteUrls(gitConfig: {
|
||||
[key: string]: any;
|
||||
}): { [key: string]: string } | undefined {
|
||||
let remoteUrls: { [key: string]: string } = {};
|
||||
|
||||
for (const key of Object.keys(gitConfig)) {
|
||||
if (key.includes('remote')) {
|
||||
// ex. remote "origin" — matches origin
|
||||
const remoteName = key.match(/(?<=").*(?=")/g)?.[0];
|
||||
const remoteUrl = gitConfig[key]?.url;
|
||||
if (remoteName && remoteUrl) {
|
||||
remoteUrls[remoteName] = remoteUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(remoteUrls).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return remoteUrls;
|
||||
}
|
||||
|
||||
export async function getRemoteUrls(
|
||||
configPath: string,
|
||||
output: Output
|
||||
): Promise<{ [key: string]: string } | undefined> {
|
||||
const config = await parseGitConfig(configPath, output);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const remoteUrls = pluckRemoteUrls(config);
|
||||
return remoteUrls;
|
||||
}
|
||||
|
||||
export function pluckOriginUrl(gitConfig: {
|
||||
[key: string]: any;
|
||||
}): string | undefined {
|
||||
// Assuming "origin" is the remote url that the user would want to use
|
||||
return gitConfig['remote "origin"']?.url;
|
||||
}
|
||||
|
||||
export async function getOriginUrl(
|
||||
configPath: string,
|
||||
output: Output
|
||||
): Promise<string | null> {
|
||||
let gitConfig = await parseGitConfig(configPath, output);
|
||||
if (!gitConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originUrl = pluckOriginUrl(gitConfig);
|
||||
if (originUrl) {
|
||||
return originUrl;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ interface ListSeparator {
|
||||
separator: string;
|
||||
}
|
||||
|
||||
type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
export type ListChoice = ListEntry | ListSeparator | typeof inquirer.Separator;
|
||||
|
||||
interface ListOptions {
|
||||
message: string;
|
||||
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.gitignore
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.vercel/project.json
generated
vendored
Normal file
4
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/.vercel/project.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "multiple-remotes"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
13
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/user/repo.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[remote "secondary"]
|
||||
url = https://github.com/user/repo2.git
|
||||
fetch = +refs/heads/*:refs/remotes/secondary/*
|
||||
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/commands/git/connect/multiple-remotes/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.gitignore
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.vercel
|
||||
4
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.vercel/project.json
vendored
Normal file
4
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/.vercel/project.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"orgId": "team_dummy",
|
||||
"projectId": "connected-repo"
|
||||
}
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/COMMIT_EDITMSG
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/COMMIT_EDITMSG
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
add hi
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
13
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/user/repo
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[remote "secondary"]
|
||||
url = https://github.com/user/repo2
|
||||
fetch = +refs/heads/*:refs/remotes/secondary/*
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
generated
vendored
Normal file
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
generated
vendored
Normal file
Binary file not shown.
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/80/50816205303e5957b2909083c50677930d5b29
generated
vendored
Normal file
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/80/50816205303e5957b2909083c50677930d5b29
generated
vendored
Normal file
Binary file not shown.
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/b2/e4d98c09ed53967b0a8f67c293c04ca4173438
generated
vendored
Normal file
BIN
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/objects/b2/e4d98c09ed53967b0a8f67c293c04ca4173438
generated
vendored
Normal file
Binary file not shown.
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/refs/heads/master
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/git/refs/heads/master
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
8050816205303e5957b2909083c50677930d5b29
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/index.txt
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/connected-repo/index.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hi
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/HEAD
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/HEAD
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
13
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/config
generated
vendored
Normal file
13
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/config
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = false
|
||||
logallrefupdates = true
|
||||
ignorecase = true
|
||||
precomposeunicode = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/user/repo
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
[remote "secondary"]
|
||||
url = https://github.com/user/repo2
|
||||
fetch = +refs/heads/*:refs/remotes/secondary/*
|
||||
1
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/description
generated
vendored
Normal file
1
packages/cli/test/fixtures/unit/create-git-meta/multiple-remotes/git/description
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
@@ -25,7 +25,7 @@ describe('git', () => {
|
||||
id: 'unlinked',
|
||||
name: 'unlinked',
|
||||
});
|
||||
client.setArgv('projects', 'connect');
|
||||
client.setArgv('git', 'connect');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Set up');
|
||||
@@ -40,7 +40,7 @@ describe('git', () => {
|
||||
client.stdin.write('y\n');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user/repo.git`
|
||||
`Connecting Git remote: https://github.com/user/repo.git`
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
@@ -76,7 +76,7 @@ describe('git', () => {
|
||||
id: 'no-git-config',
|
||||
name: 'no-git-config',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
@@ -98,11 +98,11 @@ describe('git', () => {
|
||||
id: 'no-remote-url',
|
||||
name: 'no-remote-url',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Error! No remote origin URL found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.`
|
||||
`Error! No remote URLs found in your Git config. Make sure you've configured a remote repo in your local Git config. Run \`git remote --help\` for more details.`
|
||||
);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
@@ -121,15 +121,15 @@ describe('git', () => {
|
||||
id: 'bad-remote-url',
|
||||
name: 'bad-remote-url',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const exitCode = await git(client);
|
||||
expect(exitCode).toEqual(1);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": bababooey`
|
||||
`Connecting Git remote: bababooey`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Error! Failed to parse Git repo data from the following remote URL in your Git config: bababooey\n`
|
||||
`Error! Failed to parse Git repo data from the following remote URL: bababooey\n`
|
||||
);
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
@@ -148,11 +148,11 @@ describe('git', () => {
|
||||
id: 'new-connection',
|
||||
name: 'new-connection',
|
||||
});
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user/repo`
|
||||
`Connecting Git remote: https://github.com/user/repo`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> Connected GitHub repository user/repo!\n`
|
||||
@@ -201,11 +201,11 @@ describe('git', () => {
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user2/repo2`
|
||||
`Connecting Git remote: https://github.com/user2/repo2`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> Connected GitHub repository user2/repo2!\n`
|
||||
@@ -253,11 +253,11 @@ describe('git', () => {
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/user/repo`
|
||||
`Connecting Git remote: https://github.com/user/repo`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`> user/repo is already connected to your project.\n`
|
||||
@@ -283,11 +283,11 @@ describe('git', () => {
|
||||
name: 'invalid-repo',
|
||||
});
|
||||
|
||||
client.setArgv('projects', 'connect', '--confirm');
|
||||
client.setArgv('git', 'connect', '--confirm');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
`Identified Git remote "origin": https://github.com/laksfj/asdgklsadkl`
|
||||
`Connecting Git remote: https://github.com/laksfj/asdgklsadkl`
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
`Failed to link laksfj/asdgklsadkl. Make sure there aren't any typos and that you have access to the repository if it's private.`
|
||||
@@ -300,6 +300,56 @@ describe('git', () => {
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
it('should connect the default option of multiple remotes', async () => {
|
||||
const cwd = fixture('multiple-remotes');
|
||||
try {
|
||||
process.chdir(cwd);
|
||||
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
|
||||
useUser();
|
||||
useTeams('team_dummy');
|
||||
useProject({
|
||||
...defaultProject,
|
||||
id: 'multiple-remotes',
|
||||
name: 'multiple-remotes',
|
||||
});
|
||||
|
||||
client.setArgv('git', 'connect');
|
||||
const gitPromise = git(client);
|
||||
|
||||
await expect(client.stderr).toOutput('Found multiple remote URLs.');
|
||||
await expect(client.stderr).toOutput(
|
||||
'Which remote do you want to connect?'
|
||||
);
|
||||
|
||||
client.stdin.write('\r');
|
||||
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connecting Git remote: https://github.com/user/repo.git'
|
||||
);
|
||||
await expect(client.stderr).toOutput(
|
||||
'Connected GitHub repository user/repo!'
|
||||
);
|
||||
|
||||
const exitCode = await gitPromise;
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const project: Project = await client.fetch(
|
||||
`/v8/projects/multiple-remotes`
|
||||
);
|
||||
expect(project.link).toMatchObject({
|
||||
type: 'github',
|
||||
repo: 'user/repo',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
});
|
||||
} finally {
|
||||
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('disconnect', () => {
|
||||
const originalCwd = process.cwd();
|
||||
|
||||
@@ -4,31 +4,58 @@ import os from 'os';
|
||||
import { getWriteableDirectory } from '@vercel/build-utils';
|
||||
import {
|
||||
createGitMeta,
|
||||
getRemoteUrl,
|
||||
getOriginUrl,
|
||||
getRemoteUrls,
|
||||
isDirty,
|
||||
} from '../../../../src/util/create-git-meta';
|
||||
import { client } from '../../../mocks/client';
|
||||
import { parseRepoUrl } from '../../../../src/util/projects/connect-git-provider';
|
||||
import { readOutputStream } from '../../../helpers/read-output-stream';
|
||||
import { useUser } from '../../../mocks/user';
|
||||
import { defaultProject, useProject } from '../../../mocks/project';
|
||||
import { Project } from '../../../../src/types';
|
||||
|
||||
const fixture = (name: string) =>
|
||||
join(__dirname, '../../../fixtures/unit/create-git-meta', name);
|
||||
|
||||
describe('getRemoteUrl', () => {
|
||||
describe('getOriginUrl', () => {
|
||||
it('does not provide data for no-origin', async () => {
|
||||
const configPath = join(fixture('no-origin'), 'git/config');
|
||||
const data = await getRemoteUrl(configPath, client.output);
|
||||
const data = await getOriginUrl(configPath, client.output);
|
||||
expect(data).toBeNull();
|
||||
});
|
||||
it('displays debug message when repo data cannot be parsed', async () => {
|
||||
const dir = await getWriteableDirectory();
|
||||
client.output.debugEnabled = true;
|
||||
const data = await getRemoteUrl(join(dir, 'git/config'), client.output);
|
||||
const data = await getOriginUrl(join(dir, 'git/config'), client.output);
|
||||
expect(data).toBeNull();
|
||||
await expect(client.stderr).toOutput('Error while parsing repo data');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRemoteUrls', () => {
|
||||
it('does not provide data when there are no remote urls', async () => {
|
||||
const configPath = join(fixture('no-origin'), 'git/config');
|
||||
const data = await getRemoteUrls(configPath, client.output);
|
||||
expect(data).toBeUndefined();
|
||||
});
|
||||
it('returns an object when multiple urls are present', async () => {
|
||||
const configPath = join(fixture('multiple-remotes'), 'git/config');
|
||||
const data = await getRemoteUrls(configPath, client.output);
|
||||
expect(data).toMatchObject({
|
||||
origin: 'https://github.com/user/repo',
|
||||
secondary: 'https://github.com/user/repo2',
|
||||
});
|
||||
});
|
||||
it('returns an object for origin url', async () => {
|
||||
const configPath = join(fixture('test-github'), 'git/config');
|
||||
const data = await getRemoteUrls(configPath, client.output);
|
||||
expect(data).toMatchObject({
|
||||
origin: 'https://github.com/user/repo.git',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseRepoUrl', () => {
|
||||
it('should be null when a url does not match the regex', () => {
|
||||
const parsedUrl = parseRepoUrl('https://examplecom/foo');
|
||||
@@ -244,4 +271,45 @@ describe('createGitMeta', () => {
|
||||
await fs.remove(tmpDir);
|
||||
}
|
||||
});
|
||||
it('uses the repo url for a connected project', async () => {
|
||||
const originalCwd = process.cwd();
|
||||
const directory = fixture('connected-repo');
|
||||
try {
|
||||
process.chdir(directory);
|
||||
await fs.rename(join(directory, 'git'), join(directory, '.git'));
|
||||
|
||||
useUser();
|
||||
const project = useProject({
|
||||
...defaultProject,
|
||||
id: 'connected-repo',
|
||||
name: 'connected-repo',
|
||||
});
|
||||
project.project.link = {
|
||||
type: 'github',
|
||||
repo: 'user/repo2',
|
||||
repoId: 1010,
|
||||
gitCredentialId: '',
|
||||
sourceless: true,
|
||||
createdAt: 1656109539791,
|
||||
updatedAt: 1656109539791,
|
||||
};
|
||||
|
||||
const data = await createGitMeta(
|
||||
directory,
|
||||
client.output,
|
||||
project.project as Project
|
||||
);
|
||||
expect(data).toMatchObject({
|
||||
remoteUrl: 'https://github.com/user/repo2',
|
||||
commitAuthorName: 'Matthew Stanciu',
|
||||
commitMessage: 'add hi',
|
||||
commitRef: 'master',
|
||||
commitSha: '8050816205303e5957b2909083c50677930d5b29',
|
||||
dirty: true,
|
||||
});
|
||||
} finally {
|
||||
await fs.rename(join(directory, '.git'), join(directory, 'git'));
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user