Fix: handle branch names conflicts (#3879)

This commit is contained in:
Erez Rokah
2020-06-09 19:03:19 +03:00
committed by GitHub
parent 0bdddfd43b
commit da7fbe0638
6 changed files with 170 additions and 22 deletions

View File

@ -27,6 +27,7 @@ import {
requestWithBackoff,
unsentRequest,
ApiRequest,
throwOnConflictingBranches,
} from 'netlify-cms-lib-util';
import { Octokit } from '@octokit/rest';
@ -1253,8 +1254,45 @@ export default class API {
return result;
}
createBranch(branchName: string, sha: string) {
return this.createRef('heads', branchName, sha);
async backupBranch(branchName: string) {
try {
const existingBranch = await this.getBranch(branchName);
await this.createBranch(
existingBranch.name.replace(
new RegExp(`${CMS_BRANCH_PREFIX}/`),
`${CMS_BRANCH_PREFIX}_${Date.now()}/`,
),
existingBranch.commit.sha,
);
} catch (e) {
console.warn(e);
}
}
async createBranch(branchName: string, sha: string) {
try {
const result = await this.createRef('heads', branchName, sha);
return result;
} catch (e) {
const message = String(e.message || '');
if (message === 'Reference update failed') {
await throwOnConflictingBranches(branchName, name => this.getBranch(name), API_NAME);
} else if (
message === 'Reference already exists' &&
branchName.startsWith(`${CMS_BRANCH_PREFIX}/`)
) {
try {
// this can happen if the branch wasn't deleted when the PR was merged
// we backup the existing branch just in case and patch it with the new sha
await this.backupBranch(branchName);
const result = await this.patchBranch(branchName, sha, { force: true });
return result;
} catch (e) {
console.log(e);
}
}
throw e;
}
}
assertCmsBranch(branchName: string) {

View File

@ -14,8 +14,9 @@ import {
DEFAULT_PR_BODY,
branchFromContentKey,
CMS_BRANCH_PREFIX,
throwOnConflictingBranches,
} from 'netlify-cms-lib-util';
import { trim } from 'lodash';
import { trim, trimStart } from 'lodash';
import introspectionQueryResultData from './fragmentTypes';
import API, { Config, BlobArgs, API_NAME, PullRequestState, MOCK_PULL_REQUEST } from './API';
import * as queries from './queries';
@ -134,10 +135,44 @@ export default class GraphQLAPI extends API {
});
}
mutate(options: MutationOptions<OperationVariables>) {
return this.client.mutate(options).catch(error => {
async mutate(options: MutationOptions<OperationVariables>) {
try {
const result = await this.client.mutate(options);
return result;
} catch (error) {
const errors = error.graphQLErrors;
if (Array.isArray(errors) && errors.some(e => e.message === 'Ref cannot be created.')) {
const refName = options?.variables?.createRefInput?.name || '';
const branchName = trimStart(refName, 'refs/heads/');
if (branchName) {
await throwOnConflictingBranches(branchName, name => this.getBranch(name), API_NAME);
}
} else if (
Array.isArray(errors) &&
errors.some(e =>
new RegExp(
`A ref named "refs/heads/${CMS_BRANCH_PREFIX}/.+?" already exists in the repository.`,
).test(e.message),
)
) {
const refName = options?.variables?.createRefInput?.name || '';
const sha = options?.variables?.createRefInput?.oid || '';
const branchName = trimStart(refName, 'refs/heads/');
if (branchName && branchName.startsWith(`${CMS_BRANCH_PREFIX}/`) && sha) {
try {
// this can happen if the branch wasn't deleted when the PR was merged
// we backup the existing branch just in case an re-run the mutation
await this.backupBranch(branchName);
await this.deleteBranch(branchName);
const result = await this.client.mutate(options);
return result;
} catch (e) {
console.log(e);
}
}
}
throw new APIError(error.message, 500, 'GitHub');
});
}
}
async hasWriteAccess() {