diff --git a/lib/api/gitlab.js b/lib/api/gitlab.js index 5c4ffb71db5b368f96770aaa0e145c9105e4d97f..f08e37861d6a5675505da668b8c1d8842497c43d 100644 --- a/lib/api/gitlab.js +++ b/lib/api/gitlab.js @@ -10,8 +10,10 @@ module.exports = { findFilePaths, // Branch branchExists, + getBranch, getBranchPr, getBranchStatus, + deleteBranch, // issue addAssignees, addReviewers, @@ -81,6 +83,8 @@ async function initRepo(repoName, token, endpoint, repoLogger) { const res = await glGot(`projects/${config.repoName}`); config.defaultBranch = res.body.default_branch; logger.debug(`${repoName} default branch = ${config.defaultBranch}`); + // Discover our user email + config.email = (await glGot(`user`)).body.email; } catch (err) { logger.error(`GitLab init error: ${JSON.stringify(err)}`); throw err; @@ -122,6 +126,19 @@ async function branchExists(branchName) { } } +// Returns branch object +async function getBranch(branchName) { + logger.debug(`getBranch(${branchName})`); + const url = `projects/${config.repoName}/repository/branches/${branchName}`; + try { + return (await glGot(url)).body; + } catch (err) { + logger.warn(`Failed to getBranch ${branchName}`); + logger.debug(JSON.stringify(err)); + return null; + } +} + // Returns the Pull Request for a branch. Null if not exists. async function getBranchPr(branchName) { logger.debug(`getBranchPr(${branchName})`); @@ -170,6 +187,12 @@ async function getBranchStatus(branchName) { return status; } +async function deleteBranch(branchName) { + await glGot.delete( + `projects/${config.repoName}/repository/branches/${branchName}` + ); +} + // Issue async function addAssignees(prNo, assignees) { @@ -259,8 +282,12 @@ async function getPr(prNo) { logger.debug('pr cannot be merged'); pr.isUnmergeable = true; } - // We can't rebase through GitLab API - pr.canRebase = false; + // Check if the most recent branch commit is by us + // If not then we don't allow it to be rebased, in case someone's changes would be lost + const branch = await getBranch(pr.source_branch); + if (branch && branch.commit.author_email === config.email) { + pr.canRebase = true; + } return pr; } diff --git a/lib/workers/branch.js b/lib/workers/branch.js index 9a91e0e3b6f41ac1fcf3550f5f5b9c17df22075c..01df868f2b7e1a499930354600dc9b116dbcdf60 100644 --- a/lib/workers/branch.js +++ b/lib/workers/branch.js @@ -41,10 +41,15 @@ async function getParentBranch(branchName, config) { if (pr.isUnmergeable) { logger.debug('PR is unmergeable'); if (pr.canRebase) { - // Only supported by GitHub - // Setting parentBranch back to undefined means that we'll use the default branch - logger.debug(`Branch ${branchName} is not mergeable and needs rebasing`); - return undefined; + if (config.platform === 'github') { + // Setting parentBranch back to undefined means that we'll use the default branch + logger.info(`Branch is not mergeable and needs rebasing`); + return undefined; + } else if (config.platform === 'gitlab') { + logger.info(`Deleting unmergeable branch in order to recreate/rebase`); + await config.api.deleteBranch(branchName); + return undefined; + } } // Don't do anything different, but warn logger.warn(`Branch ${branchName} is not mergeable but can't be rebased`); diff --git a/test/workers/branch.spec.js b/test/workers/branch.spec.js index d21e20dafe3f1246138fcee5ca0b0a71a61d388d..20d5ad7e5d60e4e0884854aba8994082f515d873 100644 --- a/test/workers/branch.spec.js +++ b/test/workers/branch.spec.js @@ -22,8 +22,10 @@ describe('workers/branch', () => { const branchName = 'foo'; beforeEach(() => { config = { + platform: 'github', api: { branchExists: jest.fn(() => true), + deleteBranch: jest.fn(), getBranchPr: jest.fn(), getBranchStatus: jest.fn(), isBranchStale: jest.fn(() => false), @@ -68,6 +70,17 @@ describe('workers/branch', () => { undefined ); }); + it('returns undefined if unmergeable and can rebase (gitlab)', async () => { + config.platform = 'gitlab'; + config.api.getBranchPr.mockReturnValue({ + isUnmergeable: true, + canRebase: true, + }); + expect(await branchWorker.getParentBranch(branchName, config)).toBe( + undefined + ); + expect(config.api.deleteBranch.mock.calls.length).toBe(1); + }); it('returns branchName if automerge branch-push and not stale', async () => { config.automergeEnabled = true; config.automergeType = 'branch-push';