mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
feat: GitFs for GitLab (#2692)
Since Gitlab does not support using tokens to write to repo, `commitFilesToBranch` will always use the API. This could be changed once GitFS over SSH is implemented. Closes #2549
This commit is contained in:
parent
be65176dc4
commit
975ee2b79b
4 changed files with 468 additions and 253 deletions
|
@ -1,16 +1,21 @@
|
|||
const URL = require('url');
|
||||
const is = require('@sindresorhus/is');
|
||||
const addrs = require('email-addresses');
|
||||
|
||||
const get = require('./gl-got-wrapper');
|
||||
const hostRules = require('../../util/host-rules');
|
||||
const GitStorage = require('../git/storage');
|
||||
const Storage = require('./storage');
|
||||
|
||||
let config = {};
|
||||
let config = {
|
||||
storage: new Storage(),
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getRepos,
|
||||
cleanRepo,
|
||||
initRepo,
|
||||
getRepoStatus: () => ({}),
|
||||
getRepoStatus,
|
||||
getRepoForceRebase,
|
||||
setBaseBranch,
|
||||
// Search
|
||||
|
@ -79,13 +84,25 @@ function urlEscape(str) {
|
|||
}
|
||||
|
||||
function cleanRepo() {
|
||||
// istanbul ignore if
|
||||
if (config.storage) {
|
||||
config.storage.cleanRepo();
|
||||
}
|
||||
// In theory most of this isn't necessary. In practice..
|
||||
get.reset();
|
||||
config = {};
|
||||
}
|
||||
|
||||
// Initialize GitLab by getting base branch
|
||||
async function initRepo({ repository, token, oauth, endpoint, gitAuthor }) {
|
||||
async function initRepo({
|
||||
repository,
|
||||
token,
|
||||
oauth,
|
||||
endpoint,
|
||||
gitAuthor,
|
||||
gitFs,
|
||||
localDir,
|
||||
}) {
|
||||
const opts = hostRules.find(
|
||||
{ platform: 'gitlab' },
|
||||
{ token, endpoint, oauth }
|
||||
|
@ -97,6 +114,8 @@ async function initRepo({ repository, token, oauth, endpoint, gitAuthor }) {
|
|||
config = {};
|
||||
get.reset();
|
||||
config.repository = urlEscape(repository);
|
||||
config.gitFs = gitFs;
|
||||
config.localDir = localDir;
|
||||
if (gitAuthor) {
|
||||
try {
|
||||
config.gitAuthor = addrs.parseOneAddress(gitAuthor);
|
||||
|
@ -123,7 +142,27 @@ async function initRepo({ repository, token, oauth, endpoint, gitAuthor }) {
|
|||
// Discover our user email
|
||||
config.email = (await get(`user`)).body.email;
|
||||
delete config.prList;
|
||||
delete config.fileList;
|
||||
// istanbul ignore if
|
||||
if (config.gitFs) {
|
||||
logger.debug('Enabling Git FS');
|
||||
let { protocol, host } = URL.parse(opts.endpoint);
|
||||
host = host || 'gitlab.com';
|
||||
protocol = protocol || 'https:';
|
||||
const url = URL.format({
|
||||
protocol,
|
||||
auth: 'oauth2:' + (config.forkToken || opts.token),
|
||||
hostname: host,
|
||||
pathname: repository + '.git',
|
||||
});
|
||||
config.storage = new GitStorage();
|
||||
await config.storage.initRepo({
|
||||
...config,
|
||||
url,
|
||||
});
|
||||
} else {
|
||||
config.storage = new Storage();
|
||||
await config.storage.initRepo(config);
|
||||
}
|
||||
await Promise.all([getPrList(), getFileList()]);
|
||||
} catch (err) {
|
||||
logger.debug('Caught initRepo error');
|
||||
|
@ -144,89 +183,22 @@ async function setBaseBranch(branchName) {
|
|||
if (branchName) {
|
||||
logger.debug(`Setting baseBranch to ${branchName}`);
|
||||
config.baseBranch = branchName;
|
||||
delete config.fileList;
|
||||
await getFileList(branchName);
|
||||
await config.storage.setBaseBranch(branchName);
|
||||
}
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
// Get full file list
|
||||
async function getFileList(branchName = config.baseBranch) {
|
||||
if (config.fileList) {
|
||||
return config.fileList;
|
||||
}
|
||||
try {
|
||||
let url = `projects/${
|
||||
config.repository
|
||||
}/repository/tree?ref=${branchName}&per_page=100`;
|
||||
if (!(process.env.RENOVATE_DISABLE_FILE_RECURSION === 'true')) {
|
||||
url += '&recursive=true';
|
||||
}
|
||||
const res = await get(url, { paginate: true });
|
||||
config.fileList = res.body
|
||||
.filter(item => item.type === 'blob' && item.mode !== '120000')
|
||||
.map(item => item.path)
|
||||
.sort();
|
||||
logger.debug(`Retrieved fileList with length ${config.fileList.length}`);
|
||||
} catch (err) {
|
||||
logger.info('Error retrieving git tree - no files detected');
|
||||
config.fileList = [];
|
||||
}
|
||||
return config.fileList;
|
||||
function getFileList(branchName = config.baseBranch) {
|
||||
return config.storage.getFileList(branchName);
|
||||
}
|
||||
|
||||
// Branch
|
||||
|
||||
// Returns true if branch exists, otherwise false
|
||||
async function branchExists(branchName) {
|
||||
logger.debug(`Checking if branch exists: ${branchName}`);
|
||||
try {
|
||||
const url = `projects/${config.repository}/repository/branches/${urlEscape(
|
||||
branchName
|
||||
)}`;
|
||||
const res = await get(url);
|
||||
if (res.statusCode === 200) {
|
||||
logger.debug('Branch exists');
|
||||
return true;
|
||||
}
|
||||
// This probably shouldn't happen
|
||||
logger.debug("Branch doesn't exist");
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// If file not found, then return false
|
||||
logger.debug("Branch doesn't exist");
|
||||
return false;
|
||||
}
|
||||
// Propagate if it's any other error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllRenovateBranches(branchPrefix) {
|
||||
logger.debug(`getAllRenovateBranches(${branchPrefix})`);
|
||||
const allBranches = await get(
|
||||
`projects/${config.repository}/repository/branches`
|
||||
);
|
||||
return allBranches.body.reduce((arr, branch) => {
|
||||
if (branch.name.startsWith(branchPrefix)) {
|
||||
arr.push(branch.name);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async function isBranchStale(branchName) {
|
||||
logger.debug(`isBranchStale(${branchName})`);
|
||||
const branchDetails = await getBranchDetails(branchName);
|
||||
logger.trace({ branchDetails }, 'branchDetails');
|
||||
const parentSha = branchDetails.body.commit.parent_ids[0];
|
||||
logger.debug(`parentSha=${parentSha}`);
|
||||
const baseCommitSHA = await getBaseCommitSHA();
|
||||
logger.debug(`baseCommitSHA=${baseCommitSHA}`);
|
||||
// Return true if the SHAs don't match
|
||||
return parentSha !== baseCommitSHA;
|
||||
function branchExists(branchName) {
|
||||
return config.storage.branchExists(branchName);
|
||||
}
|
||||
|
||||
// Returns the Pull Request for a branch. Null if not exists.
|
||||
|
@ -252,6 +224,79 @@ async function getBranchPr(branchName) {
|
|||
return getPr(pr.iid);
|
||||
}
|
||||
|
||||
function getAllRenovateBranches(branchPrefix) {
|
||||
return config.storage.getAllRenovateBranches(branchPrefix);
|
||||
}
|
||||
|
||||
function isBranchStale(branchName) {
|
||||
return config.storage.isBranchStale(branchName);
|
||||
}
|
||||
|
||||
async function commitFilesToBranch(
|
||||
branchName,
|
||||
files,
|
||||
message,
|
||||
parentBranch = config.baseBranch
|
||||
) {
|
||||
// GitLab does not support push with GitFs
|
||||
let storage = config.storage;
|
||||
// istanbul ignore if
|
||||
if (config.gitFs) {
|
||||
storage = new Storage();
|
||||
storage.initRepo(config);
|
||||
}
|
||||
const res = await storage.commitFilesToBranch(
|
||||
branchName,
|
||||
files,
|
||||
message,
|
||||
parentBranch
|
||||
);
|
||||
// Reopen PR if it previousluy existed and was closed by GitLab when we deleted branch
|
||||
const pr = await getBranchPr(branchName);
|
||||
// istanbul ignore if
|
||||
if (pr) {
|
||||
logger.debug('Reopening PR');
|
||||
await reopenPr(pr.number);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getFile(filePath, branchName) {
|
||||
return config.storage.getFile(filePath, branchName);
|
||||
}
|
||||
|
||||
async function deleteBranch(branchName, closePr = false) {
|
||||
if (closePr) {
|
||||
logger.debug('Closing PR');
|
||||
const pr = await getBranchPr(branchName);
|
||||
// istanbul ignore if
|
||||
if (pr) {
|
||||
await get.put(
|
||||
`projects/${config.repository}/merge_requests/${pr.number}`,
|
||||
{
|
||||
body: {
|
||||
state_event: 'close',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return config.storage.deleteBranch(branchName);
|
||||
}
|
||||
|
||||
function mergeBranch(branchName) {
|
||||
return config.storage.mergeBranch(branchName);
|
||||
}
|
||||
|
||||
function getBranchLastCommitTime(branchName) {
|
||||
return config.storage.getBranchLastCommitTime(branchName);
|
||||
}
|
||||
|
||||
// istanbul ignore next
|
||||
function getRepoStatus() {
|
||||
return config.storage.getRepoStatus();
|
||||
}
|
||||
|
||||
// Returns the combined status for a branch.
|
||||
async function getBranchStatus(branchName, requiredStatusChecks) {
|
||||
logger.debug(`getBranchStatus(${branchName})`);
|
||||
|
@ -264,17 +309,13 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
|
|||
logger.warn({ requiredStatusChecks }, `Unsupported requiredStatusChecks`);
|
||||
return 'failed';
|
||||
}
|
||||
// First, get the branch to find the commit SHA
|
||||
let url = `projects/${config.repository}/repository/branches/${urlEscape(
|
||||
branchName
|
||||
)}`;
|
||||
let res = await get(url);
|
||||
const branchSha = res.body.commit.id;
|
||||
// First, get the branch commit SHA
|
||||
const branchSha = await config.storage.getBranchCommit(branchName);
|
||||
// Now, check the statuses for that commit
|
||||
url = `projects/${
|
||||
const url = `projects/${
|
||||
config.repository
|
||||
}/repository/commits/${branchSha}/statuses`;
|
||||
res = await get(url);
|
||||
const res = await get(url);
|
||||
logger.debug(`Got res with ${res.body.length} results`);
|
||||
if (res.body.length === 0) {
|
||||
// Return 'pending' if we have no status checks
|
||||
|
@ -298,17 +339,13 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
|
|||
}
|
||||
|
||||
async function getBranchStatusCheck(branchName, context) {
|
||||
// First, get the branch to find the commit SHA
|
||||
let url = `projects/${config.repository}/repository/branches/${urlEscape(
|
||||
branchName
|
||||
)}`;
|
||||
let res = await get(url);
|
||||
const branchSha = res.body.commit.id;
|
||||
// First, get the branch commit SHA
|
||||
const branchSha = await config.storage.getBranchCommit(branchName);
|
||||
// Now, check the statuses for that commit
|
||||
url = `projects/${
|
||||
const url = `projects/${
|
||||
config.repository
|
||||
}/repository/commits/${branchSha}/statuses`;
|
||||
res = await get(url);
|
||||
const res = await get(url);
|
||||
logger.debug(`Got res with ${res.body.length} results`);
|
||||
for (const check of res.body) {
|
||||
if (check.name === context) {
|
||||
|
@ -325,14 +362,10 @@ async function setBranchStatus(
|
|||
state,
|
||||
targetUrl
|
||||
) {
|
||||
// First, get the branch to find the commit SHA
|
||||
let url = `projects/${config.repository}/repository/branches/${urlEscape(
|
||||
branchName
|
||||
)}`;
|
||||
const res = await get(url);
|
||||
const branchSha = res.body.commit.id;
|
||||
// First, get the branch commit SHA
|
||||
const branchSha = await config.storage.getBranchCommit(branchName);
|
||||
// Now, check the statuses for that commit
|
||||
url = `projects/${config.repository}/statuses/${branchSha}`;
|
||||
const url = `projects/${config.repository}/statuses/${branchSha}`;
|
||||
const options = {
|
||||
state,
|
||||
description,
|
||||
|
@ -349,60 +382,6 @@ async function setBranchStatus(
|
|||
}
|
||||
}
|
||||
|
||||
async function deleteBranch(branchName, closePr = false) {
|
||||
if (closePr) {
|
||||
logger.debug('Closing PR');
|
||||
const pr = await getBranchPr(branchName);
|
||||
// istanbul ignore if
|
||||
if (pr) {
|
||||
await get.put(
|
||||
`projects/${config.repository}/merge_requests/${pr.number}`,
|
||||
{
|
||||
body: {
|
||||
state_event: 'close',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
await get.delete(
|
||||
`projects/${config.repository}/repository/branches/${urlEscape(branchName)}`
|
||||
);
|
||||
}
|
||||
|
||||
async function mergeBranch(branchName) {
|
||||
logger.debug(`mergeBranch(${branchName}`);
|
||||
const branchURI = encodeURIComponent(branchName);
|
||||
try {
|
||||
await get.post(
|
||||
`projects/${
|
||||
config.repository
|
||||
}/repository/commits/${branchURI}/cherry_pick?branch=${config.baseBranch}`
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({ err }, `Error pushing branch merge for ${branchName}`);
|
||||
throw new Error('Branch automerge failed');
|
||||
}
|
||||
// Update base commit
|
||||
config.baseCommitSHA = null;
|
||||
// Delete branch
|
||||
await deleteBranch(branchName);
|
||||
}
|
||||
|
||||
async function getBranchLastCommitTime(branchName) {
|
||||
try {
|
||||
const res = await get(
|
||||
`projects/${config.repository}/repository/commits?ref_name=${urlEscape(
|
||||
branchName
|
||||
)}`
|
||||
);
|
||||
return new Date(res.body[0].committed_date);
|
||||
} catch (err) {
|
||||
logger.error({ err }, `getBranchLastCommitTime error`);
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
// Issue
|
||||
|
||||
async function getIssueList() {
|
||||
|
@ -780,106 +759,8 @@ function getPrBody(input) {
|
|||
.replace(/\]\(\.\.\/pull\//g, '](../merge_requests/');
|
||||
}
|
||||
|
||||
// Generic File operations
|
||||
|
||||
async function getFile(filePath, branchName) {
|
||||
logger.debug(`getFile(filePath=${filePath}, branchName=${branchName})`);
|
||||
if (!branchName || branchName === config.baseBranch) {
|
||||
if (config.fileList && !config.fileList.includes(filePath)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const url = `projects/${config.repository}/repository/files/${urlEscape(
|
||||
filePath
|
||||
)}?ref=${branchName || config.baseBranch}`;
|
||||
const res = await get(url);
|
||||
return Buffer.from(res.body.content, 'base64').toString();
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// If file not found, then return null JSON
|
||||
return null;
|
||||
}
|
||||
// Propagate if it's any other error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new commit, create branch if not existing
|
||||
async function commitFilesToBranch(
|
||||
branchName,
|
||||
files,
|
||||
message,
|
||||
parentBranch = config.baseBranch
|
||||
) {
|
||||
logger.debug(
|
||||
`commitFilesToBranch('${branchName}', files, message, '${parentBranch})'`
|
||||
);
|
||||
const opts = {
|
||||
body: {
|
||||
branch: branchName,
|
||||
commit_message: message,
|
||||
start_branch: parentBranch,
|
||||
actions: [],
|
||||
},
|
||||
};
|
||||
// istanbul ignore if
|
||||
if (config.gitAuthor) {
|
||||
opts.body.author_name = config.gitAuthor.name;
|
||||
opts.body.author_email = config.gitAuthor.address;
|
||||
}
|
||||
for (const file of files) {
|
||||
const action = {
|
||||
file_path: file.name,
|
||||
content: Buffer.from(file.contents).toString('base64'),
|
||||
encoding: 'base64',
|
||||
};
|
||||
action.action = (await getFile(file.name)) ? 'update' : 'create';
|
||||
opts.body.actions.push(action);
|
||||
}
|
||||
let res = 'created';
|
||||
try {
|
||||
if (await branchExists(branchName)) {
|
||||
logger.debug('Deleting existing branch');
|
||||
await deleteBranch(branchName);
|
||||
res = 'updated';
|
||||
}
|
||||
} catch (err) {
|
||||
// istanbul ignore next
|
||||
logger.info(`Ignoring branch deletion failure`);
|
||||
}
|
||||
logger.debug('Adding commits');
|
||||
await get.post(`projects/${config.repository}/repository/commits`, opts);
|
||||
// Reopen PR if it previousluy existed and was closed by GitLab when we deleted branch
|
||||
const pr = await getBranchPr(branchName);
|
||||
// istanbul ignore if
|
||||
if (pr) {
|
||||
logger.debug('Reopening PR');
|
||||
await reopenPr(pr.number);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// GET /projects/:id/repository/commits
|
||||
async function getCommitMessages() {
|
||||
logger.debug('getCommitMessages');
|
||||
const res = await get(`projects/${config.repository}/repository/commits`);
|
||||
return res.body.map(commit => commit.title);
|
||||
}
|
||||
|
||||
function getBranchDetails(branchName) {
|
||||
const url = `/projects/${config.repository}/repository/branches/${urlEscape(
|
||||
branchName
|
||||
)}`;
|
||||
return get(url);
|
||||
}
|
||||
|
||||
async function getBaseCommitSHA() {
|
||||
if (!config.baseCommitSHA) {
|
||||
const branchDetails = await getBranchDetails(config.baseBranch);
|
||||
config.baseCommitSHA = branchDetails.body.commit.id;
|
||||
}
|
||||
return config.baseCommitSHA;
|
||||
function getCommitMessages() {
|
||||
return config.storage.getCommitMessages();
|
||||
}
|
||||
|
||||
function getVulnerabilityAlerts() {
|
||||
|
|
303
lib/platform/gitlab/storage.js
Normal file
303
lib/platform/gitlab/storage.js
Normal file
|
@ -0,0 +1,303 @@
|
|||
const get = require('./gl-got-wrapper');
|
||||
|
||||
function urlEscape(str) {
|
||||
return str ? str.replace(/\//g, '%2F') : str;
|
||||
}
|
||||
|
||||
class Storage {
|
||||
constructor() {
|
||||
// config
|
||||
let config = {};
|
||||
// cache
|
||||
let baseCommitSHA = null;
|
||||
let branchFiles = {};
|
||||
|
||||
Object.assign(this, {
|
||||
initRepo,
|
||||
cleanRepo,
|
||||
getRepoStatus: () => ({}),
|
||||
branchExists,
|
||||
commitFilesToBranch,
|
||||
createBranch,
|
||||
deleteBranch,
|
||||
getAllRenovateBranches,
|
||||
getBranchCommit,
|
||||
getBranchLastCommitTime,
|
||||
getCommitMessages,
|
||||
getFile,
|
||||
getFileList,
|
||||
isBranchStale,
|
||||
mergeBranch,
|
||||
setBaseBranch,
|
||||
});
|
||||
|
||||
function initRepo(args) {
|
||||
cleanRepo();
|
||||
config = { ...args };
|
||||
}
|
||||
|
||||
function cleanRepo() {
|
||||
baseCommitSHA = null;
|
||||
branchFiles = {};
|
||||
}
|
||||
|
||||
// Branch
|
||||
|
||||
// Returns true if branch exists, otherwise false
|
||||
async function branchExists(branchName) {
|
||||
logger.debug(`Checking if branch exists: ${branchName}`);
|
||||
try {
|
||||
const url = `projects/${
|
||||
config.repository
|
||||
}/repository/branches/${urlEscape(branchName)}`;
|
||||
const res = await get(url);
|
||||
if (res.statusCode === 200) {
|
||||
logger.debug('Branch exists');
|
||||
return true;
|
||||
}
|
||||
// This probably shouldn't happen
|
||||
logger.debug("Branch doesn't exist");
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// If file not found, then return false
|
||||
logger.debug("Branch doesn't exist");
|
||||
return false;
|
||||
}
|
||||
// Propagate if it's any other error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllRenovateBranches(branchPrefix) {
|
||||
logger.debug(`getAllRenovateBranches(${branchPrefix})`);
|
||||
const allBranches = await get(
|
||||
`projects/${config.repository}/repository/branches`
|
||||
);
|
||||
return allBranches.body.reduce((arr, branch) => {
|
||||
if (branch.name.startsWith(branchPrefix)) {
|
||||
arr.push(branch.name);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async function isBranchStale(branchName) {
|
||||
logger.debug(`isBranchStale(${branchName})`);
|
||||
const branchDetails = await getBranchDetails(branchName);
|
||||
logger.trace({ branchDetails }, 'branchDetails');
|
||||
const parentSha = branchDetails.body.commit.parent_ids[0];
|
||||
logger.debug(`parentSha=${parentSha}`);
|
||||
const baseSHA = await getBaseCommitSHA();
|
||||
logger.debug(`baseSHA=${baseSHA}`);
|
||||
// Return true if the SHAs don't match
|
||||
return parentSha !== baseSHA;
|
||||
}
|
||||
|
||||
// Add a new commit, create branch if not existing
|
||||
async function commitFilesToBranch(
|
||||
branchName,
|
||||
files,
|
||||
message,
|
||||
parentBranch = config.baseBranch
|
||||
) {
|
||||
logger.debug(
|
||||
`commitFilesToBranch('${branchName}', files, message, '${parentBranch})'`
|
||||
);
|
||||
const opts = {
|
||||
body: {
|
||||
branch: branchName,
|
||||
commit_message: message,
|
||||
start_branch: parentBranch,
|
||||
actions: [],
|
||||
},
|
||||
};
|
||||
// istanbul ignore if
|
||||
if (config.gitAuthor) {
|
||||
opts.body.author_name = config.gitAuthor.name;
|
||||
opts.body.author_email = config.gitAuthor.address;
|
||||
}
|
||||
for (const file of files) {
|
||||
const action = {
|
||||
file_path: file.name,
|
||||
content: Buffer.from(file.contents).toString('base64'),
|
||||
encoding: 'base64',
|
||||
};
|
||||
action.action = (await getFile(file.name)) ? 'update' : 'create';
|
||||
opts.body.actions.push(action);
|
||||
}
|
||||
let res = 'created';
|
||||
try {
|
||||
if (await branchExists(branchName)) {
|
||||
logger.debug('Deleting existing branch');
|
||||
await deleteBranch(branchName);
|
||||
res = 'updated';
|
||||
}
|
||||
} catch (err) {
|
||||
// istanbul ignore next
|
||||
logger.info(`Ignoring branch deletion failure`);
|
||||
}
|
||||
logger.debug('Adding commits');
|
||||
await get.post(`projects/${config.repository}/repository/commits`, opts);
|
||||
return res;
|
||||
}
|
||||
|
||||
async function createBranch(branchName, sha) {
|
||||
await get.post(
|
||||
`projects/${config.repository}/repository/branches?branch=${urlEscape(
|
||||
branchName
|
||||
)}&ref=${sha}`
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteBranch(branchName) {
|
||||
await get.delete(
|
||||
`projects/${config.repository}/repository/branches/${urlEscape(
|
||||
branchName
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
// Get full file list
|
||||
async function getFileList(branchName = config.baseBranch) {
|
||||
if (branchFiles[branchName]) {
|
||||
return branchFiles[branchName];
|
||||
}
|
||||
try {
|
||||
let url = `projects/${
|
||||
config.repository
|
||||
}/repository/tree?ref=${branchName}&per_page=100`;
|
||||
if (!(process.env.RENOVATE_DISABLE_FILE_RECURSION === 'true')) {
|
||||
url += '&recursive=true';
|
||||
}
|
||||
const res = await get(url, { paginate: true });
|
||||
branchFiles[branchName] = res.body
|
||||
.filter(item => item.type === 'blob' && item.mode !== '120000')
|
||||
.map(item => item.path)
|
||||
.sort();
|
||||
logger.debug(
|
||||
`Retrieved fileList with length ${branchFiles[branchName].length}`
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info('Error retrieving git tree - no files detected');
|
||||
branchFiles[branchName] = [];
|
||||
}
|
||||
return branchFiles[branchName];
|
||||
}
|
||||
|
||||
// Generic File operations
|
||||
|
||||
async function getFile(filePath, branchName) {
|
||||
logger.debug(`getFile(filePath=${filePath}, branchName=${branchName})`);
|
||||
if (!branchName || branchName === config.baseBranch) {
|
||||
if (
|
||||
branchFiles[branchName] &&
|
||||
!branchFiles[branchName].includes(filePath)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const url = `projects/${config.repository}/repository/files/${urlEscape(
|
||||
filePath
|
||||
)}?ref=${branchName || config.baseBranch}`;
|
||||
const res = await get(url);
|
||||
return Buffer.from(res.body.content, 'base64').toString();
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
// If file not found, then return null JSON
|
||||
return null;
|
||||
}
|
||||
// Propagate if it's any other error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// GET /projects/:id/repository/commits
|
||||
async function getCommitMessages() {
|
||||
logger.debug('getCommitMessages');
|
||||
const res = await get(`projects/${config.repository}/repository/commits`);
|
||||
return res.body.map(commit => commit.title);
|
||||
}
|
||||
|
||||
function getBranchDetails(branchName) {
|
||||
const url = `/projects/${
|
||||
config.repository
|
||||
}/repository/branches/${urlEscape(branchName)}`;
|
||||
return get(url);
|
||||
}
|
||||
|
||||
async function getBaseCommitSHA() {
|
||||
if (!baseCommitSHA) {
|
||||
const branchDetails = await getBranchDetails(config.baseBranch);
|
||||
baseCommitSHA = branchDetails.body.commit.id;
|
||||
}
|
||||
return baseCommitSHA;
|
||||
}
|
||||
|
||||
async function mergeBranch(branchName) {
|
||||
logger.debug(`mergeBranch(${branchName}`);
|
||||
const branchURI = encodeURIComponent(branchName);
|
||||
try {
|
||||
await get.post(
|
||||
`projects/${
|
||||
config.repository
|
||||
}/repository/commits/${branchURI}/cherry_pick?branch=${
|
||||
config.baseBranch
|
||||
}`
|
||||
);
|
||||
} catch (err) {
|
||||
logger.info({ err }, `Error pushing branch merge for ${branchName}`);
|
||||
throw new Error('Branch automerge failed');
|
||||
}
|
||||
// Update base commit
|
||||
baseCommitSHA = null;
|
||||
// Delete branch
|
||||
await deleteBranch(branchName);
|
||||
}
|
||||
|
||||
async function getBranchCommit(branchName) {
|
||||
const branchUrl = `projects/${
|
||||
config.repository
|
||||
}/repository/branches/${urlEscape(branchName)}`;
|
||||
try {
|
||||
const branch = (await get(branchUrl)).body;
|
||||
if (branch && branch.commit) {
|
||||
return branch.commit.id;
|
||||
}
|
||||
} catch (err) {
|
||||
// istanbul ignore next
|
||||
logger.error({ err }, `getBranchCommit error`);
|
||||
}
|
||||
// istanbul ignore next
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getBranchLastCommitTime(branchName) {
|
||||
try {
|
||||
const res = await get(
|
||||
`projects/${
|
||||
config.repository
|
||||
}/repository/commits?ref_name=${urlEscape(branchName)}`
|
||||
);
|
||||
return new Date(res.body[0].committed_date);
|
||||
} catch (err) {
|
||||
logger.error({ err }, `getBranchLastCommitTime error`);
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
async function setBaseBranch(branchName) {
|
||||
if (branchName) {
|
||||
logger.debug(`Setting baseBranch to ${branchName}`);
|
||||
config.baseBranch = branchName;
|
||||
branchFiles = {};
|
||||
await getFileList(branchName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Storage;
|
9
test/platform/gitlab/__snapshots__/storage.spec.js.snap
Normal file
9
test/platform/gitlab/__snapshots__/storage.spec.js.snap
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`platform/gitlab/storage createBranch() creates the branch 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
"projects/undefined/repository/branches?branch=renovate%2Fsome-branch&ref=commit",
|
||||
],
|
||||
]
|
||||
`;
|
22
test/platform/gitlab/storage.spec.js
Normal file
22
test/platform/gitlab/storage.spec.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
describe('platform/gitlab/storage', () => {
|
||||
jest.mock('../../../lib/platform/gitlab/gl-got-wrapper');
|
||||
const GitlabStorage = require('../../../lib/platform/gitlab/storage');
|
||||
const GitStorage = require('../../../lib/platform/git/storage');
|
||||
const get = require('../../../lib/platform/gitlab/gl-got-wrapper');
|
||||
it('has same API for git storage', () => {
|
||||
const gitlabMethods = Object.keys(new GitlabStorage()).sort();
|
||||
const gitMethods = Object.keys(new GitStorage()).sort();
|
||||
expect(gitlabMethods).toMatchObject(gitMethods);
|
||||
});
|
||||
it('getRepoStatus exists', async () => {
|
||||
expect((await new GitlabStorage()).getRepoStatus()).toEqual({});
|
||||
});
|
||||
describe('createBranch()', () => {
|
||||
it('creates the branch', async () => {
|
||||
get.post.mockReturnValue({});
|
||||
const storage = new GitlabStorage();
|
||||
await storage.createBranch('renovate/some-branch', 'commit');
|
||||
expect(get.post.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue