Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Set correct branch when it's not specified in the config #5844

Merged
merged 72 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
47ebd16
feat: add helper for fetching default branch from Github
bytrangle Sep 27, 2021
61904c8
feat: add method for setting default branch
bytrangle Sep 27, 2021
c3f1fc0
fix: set default branch after user has authenticated successfully
bytrangle Sep 27, 2021
90c9f9c
fix: format code
bytrangle Sep 27, 2021
43fbe2d
feat: add unit test for getting default branch name
bytrangle Sep 30, 2021
8436443
feat: add helpers for parsing API responses
bytrangle Oct 11, 2021
3edc3ce
feat(lib-util): add helper for constructing request headers
bytrangle Oct 11, 2021
7ccbac6
feat(lib-util): add helper for constructing full URL for API request
bytrangle Oct 11, 2021
e5407c3
feat(lib-util): store base URLs for each backend
bytrangle Oct 11, 2021
3f0e711
feat(lib-util): add type annotation for the request config
bytrangle Oct 11, 2021
a506b5d
feat(lib-util): add helper for handle API request error
bytrangle Oct 11, 2021
cc5f53a
feat(lib-util): add config for making api request
bytrangle Oct 11, 2021
cec8fbb
feat(lib-util): add api request generator
bytrangle Oct 11, 2021
69d124a
feat(lib-util): add helper for getting default branch name
bytrangle Oct 11, 2021
c726ee0
feat(lib-util): export method for getting default branch name
bytrangle Oct 11, 2021
bbafe77
feat(gh-backend): add a boolean property to check if branch is config…
bytrangle Oct 11, 2021
11e187c
feat(gh-backend): set prop `branch` as `master` when it's missing in …
bytrangle Oct 11, 2021
67904b1
feat(gh-backend): set branch name when it's missing in config
bytrangle Oct 11, 2021
37531e1
feat(gitlab-backend): set branch when it's not in the config
bytrangle Oct 12, 2021
b118aea
feat(bitbucket-backend): set branch when it's not specified in config
bytrangle Oct 12, 2021
fa8e03c
feat(lib-util): allow token type to be undefined
bytrangle Oct 12, 2021
a2add4a
fix: format codes
bytrangle Oct 12, 2021
eb9ce4b
Merge branch 'master' into fix/default-branch
bytrangle Mar 11, 2022
8319d4b
feat(github): removed setDefaultBranch function
bytrangle Mar 12, 2022
87350d9
feat(github): remove function for getting default branch
bytrangle Mar 12, 2022
7b0fda0
Merge branch 'master' into fix/default-branch
bytrangle Mar 12, 2022
542a1d9
Merge branch 'fix/default-branch' of https://github.com/bytrangle/net…
bytrangle Mar 12, 2022
3694c75
fix (github): removed GithubRepo object because it was never used
bytrangle Mar 12, 2022
346ac8b
fix (gitlab test): Repeat response for getting project info 2 times
bytrangle Mar 14, 2022
714a57e
fix(gitlab test): add property `default_branch` to project response
bytrangle Mar 14, 2022
f77dba5
fix(gitlab test): reformat codes
bytrangle Mar 14, 2022
9c14584
feat(lib util api): change function name
bytrangle Mar 17, 2022
acd47a8
feat(lib-util api): Change variable name for storing API roots
bytrangle Mar 17, 2022
98541a3
feat(lib-util api): Add varialbe for storing endpoint constants
bytrangle Mar 17, 2022
7dc9dae
feat(lib-util api): Change the returned value for `getDefaultBranchName`
bytrangle Mar 17, 2022
1ecd946
feat(api test): Import Nock module for mocking API requests
bytrangle Mar 17, 2022
efa741f
feat(api test): Add default values for mocking API
bytrangle Mar 17, 2022
7bf6724
feat(api test): Add mock response to getting a single repo
bytrangle Mar 17, 2022
2dfa3f6
feat(api test): Add function for itnercepting request to get single repo
bytrangle Mar 17, 2022
ef664f6
feat(api test): Add test for gettingDefaultBranchName
bytrangle Mar 17, 2022
89a2806
feat(lib-util): reformat codes
bytrangle Mar 17, 2022
54f4314
Merge branch 'master' into fix/default-branch
erezrokah Mar 25, 2022
183a0c3
feat(jest config): add moduleNameMapper for GitHub and BitBucket
bytrangle Apr 11, 2022
02ef43c
feat(lib-util test): return some modules from backend core for testing
bytrangle Apr 11, 2022
5d1855c
feat(lib-util test): add owner login value for Github's repo response
bytrangle Apr 11, 2022
b03d6a8
feat(lib-util test): change access level for Gitlab to 30
bytrangle Apr 11, 2022
13f0863
feat(lib-util test): add mock response for getting a user
bytrangle Apr 11, 2022
1aba0da
feat(lib-util test): add default config for backend field
bytrangle Apr 11, 2022
1f76df9
feat(lib-util test): rewrite function for mocking API
bytrangle Apr 11, 2022
394dc9b
feat(lib-util test): rewrite function for mocking request for repo
bytrangle Apr 11, 2022
0fc96ab
test(lib-util): rewrite test for the function getDefaultBranchName
bytrangle Apr 11, 2022
6eb2ac3
test(lib-util): add function for resolving backend
bytrangle Apr 11, 2022
6bfcf09
test(lib-util): import 'set' module from Lodash
bytrangle Apr 11, 2022
53f143a
test(lib-util): add helper for constructing url path for each backend
bytrangle Apr 11, 2022
38f73a4
test(lib-util): add function for intercepting API request to authenti…
bytrangle Apr 11, 2022
af9cd9b
test(lib-util): import each backend module
bytrangle Apr 11, 2022
a5da48a
Merge branch 'master' into fix/default-branch
erezrokah Apr 12, 2022
14ddaa7
test(lib-util): add tests that check each backend calls getDefaultBra…
bytrangle Apr 13, 2022
02311f4
Merge branch 'fix/default-branch' of https://github.com/bytrangle/net…
bytrangle Apr 13, 2022
1789e6f
style: format files
erezrokah Apr 13, 2022
34375f4
fix: query branch name before setting Github API service
bytrangle May 17, 2022
7f6de72
fix: reformat implementation module of Github backend
bytrangle May 17, 2022
ec0ff89
fix: remove importing of getDefaultBranchName from lib
bytrangle May 17, 2022
48e7175
fix: removed test for getDefaultBranchName in lib packages
bytrangle May 19, 2022
676631f
fix: removed unused vars in api test for lib package
bytrangle May 19, 2022
614a5b0
feat: retrieve default branch before creating Bitbucket AI instance
bytrangle May 26, 2022
20caf5f
fix: reformat codes in Bitbucket implementation module
bytrangle May 26, 2022
8db27d6
fix: Resolve conflicts from changing cms
bytrangle Mar 18, 2024
ad161df
Merge branch 'master' into fix/default-branch
martinjagodic Mar 18, 2024
d4e4fe3
Merge branch 'main' into fix/default-branch
demshy Apr 2, 2024
c6b05f8
fix: add missing import
demshy Apr 2, 2024
5827471
Merge branch 'main' into fix/default-branch
demshy Apr 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion packages/decap-cms-backend-bitbucket/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type BitbucketStatusComponent = {
status: string;
};

const { fetchWithTimeout: fetch } = unsentRequest;

// Implementation wrapper class
export default class BitbucketBackend implements Implementation {
lock: AsyncLock;
Expand All @@ -72,6 +74,7 @@ export default class BitbucketBackend implements Implementation {
initialWorkflowStatus: string;
};
repo: string;
isBranchConfigured: boolean;
branch: string;
apiRoot: string;
baseUrl: string;
Expand Down Expand Up @@ -111,6 +114,7 @@ export default class BitbucketBackend implements Implementation {

this.repo = config.backend.repo || '';
this.branch = config.backend.branch || 'master';
this.isBranchConfigured = config.backend.branch ? true : false;
this.apiRoot = config.backend.api_root || 'https://api.bitbucket.org/2.0';
this.baseUrl = config.base_url || '';
this.siteId = config.site_id || '';
Expand Down Expand Up @@ -190,6 +194,18 @@ export default class BitbucketBackend implements Implementation {

async authenticate(state: Credentials) {
this.token = state.token as string;
if (!this.isBranchConfigured) {
const repo = await fetch(`${this.apiRoot}/repositories/${this.repo}`, {
headers: {
Authorization: `token ${this.token}`,
},
})
.then(res => res.json())
.catch(() => null);
if (repo) {
this.branch = repo.mainbranch.name;
}
}
this.refreshToken = state.refresh_token;
this.api = new API({
requestFunction: this.apiRequestFunction,
Expand All @@ -216,7 +232,16 @@ export default class BitbucketBackend implements Implementation {
if (!isCollab) {
throw new Error('Your BitBucket user account does not have access to this repo.');
}

// if (!this.isBranchConfigured) {
// const defaultBranchName = await getDefaultBranchName({
// backend: 'bitbucket',
// repo: this.repo,
// token: this.token,
// });
// if (defaultBranchName) {
// this.branch = defaultBranchName;
// }
// }
const user = await this.api.user();

// Authorized user
Expand Down
22 changes: 21 additions & 1 deletion packages/decap-cms-backend-github/src/implementation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export default class GitHub implements Implementation {
initialWorkflowStatus: string;
};
originRepo: string;
isBranchConfigured: boolean;
repo?: string;
openAuthoringEnabled: boolean;
useOpenAuthoring?: boolean;
Expand Down Expand Up @@ -106,7 +107,7 @@ export default class GitHub implements Implementation {
}

this.api = this.options.API || null;

this.isBranchConfigured = config.backend.branch ? true : false;
this.openAuthoringEnabled = config.backend.open_authoring || false;
if (this.openAuthoringEnabled) {
if (!this.options.useWorkflow) {
Expand Down Expand Up @@ -320,6 +321,18 @@ export default class GitHub implements Implementation {

async authenticate(state: Credentials) {
this.token = state.token as string;
// Query the default branch name when the `branch` property is missing
// in the config file
if (!this.isBranchConfigured) {
const repoInfo = await fetch(`${this.apiRoot}/repos/${this.originRepo}`, {
headers: { Authorization: `token ${this.token}` },
})
.then(res => res.json())
.catch(() => null);
if (repoInfo && repoInfo.default_branch) {
this.branch = repoInfo.default_branch;
}
}
const apiCtor = this.useGraphql ? GraphQLAPI : API;
this.api = new apiCtor({
token: this.token,
Expand Down Expand Up @@ -354,6 +367,13 @@ export default class GitHub implements Implementation {
throw new Error('Your GitHub user account does not have access to this repo.');
}

// if (!this.isBranchConfigured) {
// const defaultBranchName = await this.api.getDefaultBranchName()
// if (defaultBranchName) {
// this.branch = defaultBranchName;
// }
// }

// Authorized user
return { ...user, token: state.token as string, useOpenAuthoring: this.useOpenAuthoring };
}
Expand Down
10 changes: 10 additions & 0 deletions packages/decap-cms-backend-gitlab/src/__tests__/gitlab.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const resp = {
access_level: 30,
},
},
default_branch: 'main',
},
readOnly: {
permissions: {
Expand Down Expand Up @@ -194,7 +195,16 @@ describe('gitlab backend', () => {
.reply(200, userResponse || resp.user.success);

api
// The `authenticate` method of the API class from netlify-cms-backend-gitlab
// calls the same endpoint twice for gettng a single project.
// First time through `this.api.hasWriteAccess()
// Second time through the method `getDefaultBranchName` from lib-util
// As a result, we need to repeat the same response twice.
// Otherwise, we'll get an error: "No match for request to
// https://gitlab.com/api/v4"

.get(expectedRepoUrl)
.times(2)
.query(true)
.reply(200, projectResponse || resp.project.success);
}
Expand Down
13 changes: 13 additions & 0 deletions packages/decap-cms-backend-gitlab/src/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
allEntriesByFolder,
filterByExtension,
branchFromContentKey,
getDefaultBranchName,
} from 'decap-cms-lib-util';

import AuthenticationPage from './AuthenticationPage';
Expand Down Expand Up @@ -53,6 +54,7 @@ export default class GitLab implements Implementation {
initialWorkflowStatus: string;
};
repo: string;
isBranchConfigured: boolean;
branch: string;
apiRoot: string;
token: string | null;
Expand Down Expand Up @@ -84,6 +86,7 @@ export default class GitLab implements Implementation {

this.repo = config.backend.repo || '';
this.branch = config.backend.branch || 'master';
this.isBranchConfigured = config.backend.branch ? true : false;
this.apiRoot = config.backend.api_root || 'https://gitlab.com/api/v4';
this.token = '';
this.squashMerges = config.backend.squash_merges || false;
Expand Down Expand Up @@ -150,6 +153,16 @@ export default class GitLab implements Implementation {
throw new Error('Your GitLab user account does not have access to this repo.');
}

if (!this.isBranchConfigured) {
const defaultBranchName = await getDefaultBranchName({
backend: 'gitlab',
repo: this.repo,
token: this.token,
});
if (defaultBranchName) {
this.branch = defaultBranchName;
}
}
// Authorized user
return { ...user, login: user.username, token: state.token as string };
}
Expand Down
154 changes: 154 additions & 0 deletions packages/decap-cms-lib-util/src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ class RateLimitError extends Error {
}
}

async function parseJsonResponse(response: Response) {
const json = await response.json();
if (!response.ok) {
return Promise.reject(json);
}
return json;
}

export function parseResponse(response: Response) {
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.match(/json/)) {
return parseJsonResponse(response);
}
const textPromise = response.text().then(text => {
if (!response.ok) return Promise.reject(text);
return text;
});
return textPromise;
}

export async function requestWithBackoff(
api: API,
req: ApiRequest,
Expand Down Expand Up @@ -96,6 +116,140 @@ export async function requestWithBackoff(
}
}

// Options is an object which contains all the standard network request properties
// for modifying HTTP requests and may contains `params` property

type Param = string | number;

type ParamObject = Record<string, Param>;

type HeaderObj = Record<string, string>;

type HeaderConfig = {
headers?: HeaderObj;
token?: string | undefined;
};

type Backend = 'github' | 'gitlab' | 'bitbucket';

// RequestConfig contains all the standard properties of a Request object and
// several custom properties:
// - "headers" property is an object whose properties and values are string types
// - `token` property to allow passing tokens for users using a private repo.
// - `params` property for customizing response
// - `backend`(compulsory) to specify which backend to be used: Github, Gitlab etc.

type RequestConfig = Omit<RequestInit, 'headers'> &
HeaderConfig & {
backend: Backend;
params?: ParamObject;
};

export const apiRoots = {
github: 'https://api.github.com',
gitlab: 'https://gitlab.com/api/v4',
bitbucket: 'https://api.bitbucket.org/2.0',
};

export const endpointConstants = {
singleRepo: {
bitbucket: '/repositories',
github: '/repos',
gitlab: '/projects',
},
};

const api = {
buildRequest(req: ApiRequest) {
return req;
},
};

function constructUrlWithParams(url: string, params?: ParamObject) {
if (params) {
const paramList = [];
for (const key in params) {
paramList.push(`${key}=${encodeURIComponent(params[key])}`);
}
if (paramList.length) {
url += `?${paramList.join('&')}`;
}
}
return url;
}

async function constructRequestHeaders(headerConfig: HeaderConfig) {
const { token, headers } = headerConfig;
const baseHeaders: HeaderObj = { 'Content-Type': 'application/json; charset=utf-8', ...headers };
if (token) {
baseHeaders['Authorization'] = `token ${token}`;
}
return Promise.resolve(baseHeaders);
}

function handleRequestError(error: FetchError, responseStatus: number, backend: Backend) {
throw new APIError(error.message, responseStatus, backend);
}

export async function apiRequest(
path: string,
config: RequestConfig,
parser = (response: Response) => parseResponse(response),
) {
const { token, backend, ...props } = config;
const options = { cache: 'no-cache', ...props };
const headers = await constructRequestHeaders({ headers: options.headers || {}, token });
const baseUrl = apiRoots[backend];
const url = constructUrlWithParams(`${baseUrl}${path}`, options.params);
let responseStatus = 500;
try {
const req = unsentRequest.fromFetchArguments(url, {
...options,
headers,
}) as unknown as ApiRequest;
const response = await requestWithBackoff(api, req);
responseStatus = response.status;
const parsedResponse = await parser(response);
return parsedResponse;
} catch (error) {
return handleRequestError(error, responseStatus, backend);
}
}

export async function getDefaultBranchName(configs: {
backend: Backend;
repo: string;
token?: string;
}) {
let apiPath;
const { token, backend, repo } = configs;
switch (backend) {
case 'gitlab': {
apiPath = `/projects/${encodeURIComponent(repo)}`;
break;
}
case 'bitbucket': {
apiPath = `/repositories/${repo}`;
break;
}
default: {
apiPath = `/repos/${repo}`;
}
}
const repoInfo = await apiRequest(apiPath, { token, backend });
let defaultBranchName;
if (backend === 'bitbucket') {
const {
mainbranch: { name },
} = repoInfo;
defaultBranchName = name;
} else {
const { default_branch } = repoInfo;
defaultBranchName = default_branch;
}
return defaultBranchName;
}

export async function readFile(
id: string | null | undefined,
fetchContent: () => Promise<string | Blob>,
Expand Down
1 change: 1 addition & 0 deletions packages/decap-cms-lib-util/src/__tests__/api.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as api from '../API';

describe('Api', () => {
describe('getPreviewStatus', () => {
it('should return preview status on matching context', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/decap-cms-lib-util/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
getPreviewStatus,
PreviewState,
requestWithBackoff,
getDefaultBranchName,
throwOnConflictingBranches,
} from './API';
import {
Expand Down Expand Up @@ -148,6 +149,7 @@ export const DecapCmsLibUtil = {
contentKeyFromBranch,
blobToFileObj,
requestWithBackoff,
getDefaultBranchName,
allEntriesByFolder,
AccessTokenError,
throwOnConflictingBranches,
Expand Down Expand Up @@ -204,6 +206,7 @@ export {
contentKeyFromBranch,
blobToFileObj,
requestWithBackoff,
getDefaultBranchName,
allEntriesByFolder,
AccessTokenError,
throwOnConflictingBranches,
Expand Down
Loading