Skip to content
Snippets Groups Projects
Select Git revision
  • deac76b015cb5e25196d2431ff997ae08e005cda
  • main default protected
  • renovate/main-zod-3.x
  • renovate/main-ghcr.io-renovatebot-base-image-10.x
  • renovate/main-ghcr.io-containerbase-devcontainer-13.x
  • next
  • revert-31645-feat/rename-gradle-wrapper-validation-action
  • renovate/main-redis-5.x
  • fix/36615b-branch-reuse-no-cache
  • chore/punycode
  • fix/36615-branch-reuse-bug
  • refactor/pin-new-value
  • feat/36219--git-x509-signing
  • feat/structured-logger
  • hotfix/39.264.1
  • feat/skip-dangling
  • gh-readonly-queue/next/pr-36034-7a061c4ca1024a19e2c295d773d9642625d1c2be
  • hotfix/39.238.3
  • refactor/gitlab-auto-approve
  • feat/template-strings
  • gh-readonly-queue/next/pr-35654-137d934242c784e0c45d4b957362214f0eade1d7
  • 41.28.2
  • 41.28.1
  • 41.28.0
  • 41.27.1
  • 41.27.0
  • 41.26.2
  • 41.26.1
  • 41.26.0
  • 41.25.1
  • 41.25.0
  • 41.24.0
  • 41.23.5
  • 41.23.4
  • 41.23.3
  • 41.23.2
  • 41.23.1
  • 41.23.0
  • 41.22.0
  • 41.21.4
  • 41.21.3
41 results

github.js

Blame
  • gitlab.js 12.17 KiB
    let logger = require('../helpers/logger');
    const glGot = require('gl-got');
    
    const config = {};
    
    module.exports = {
      getRepos,
      initRepo,
      // Search
      findFilePaths,
      // Branch
      branchExists,
      createBranch,
      getBranch,
      getBranchPr,
      getBranchStatus,
      deleteBranch,
      // issue
      addAssignees,
      addReviewers,
      addLabels,
      // PR
      findPr,
      checkForClosedPr,
      createPr,
      getPr,
      updatePr,
      mergePr,
      // file
      commitFilesToBranch,
      getFile,
      getFileContent,
      getFileJson,
      createFile,
      updateFile,
    };
    
    // Get all repositories that the user has access to
    async function getRepos(token, endpoint) {
      logger.debug('getRepos(token, endpoint)');
      if (token) {
        process.env.GITLAB_TOKEN = token;
      } else if (!process.env.GITLAB_TOKEN) {
        throw new Error('No token found for getRepos');
      }
      if (endpoint) {
        process.env.GITLAB_ENDPOINT = endpoint;
      }
      try {
        const res = await glGot('projects');
        return res.body.map(repo => repo.path_with_namespace);
      } catch (err) {
        logger.error(`GitLab getRepos error: ${JSON.stringify(err)}`);
        throw err;
      }
    }
    
    // Initialize GitLab by getting base branch
    async function initRepo(repoName, token, endpoint, repoLogger) {
      if (repoLogger) {
        logger = repoLogger;
      }
      logger.debug(`initRepo(${repoName})`);
      if (token) {
        process.env.GITLAB_TOKEN = token;
      } else if (!process.env.GITLAB_TOKEN) {
        throw new Error(`No token found for GitLab repository ${repoName}`);
      }
      if (token) {
        process.env.GITLAB_TOKEN = token;
      }
      if (endpoint) {
        process.env.GITLAB_ENDPOINT = endpoint;
      }
      try {
        logger.debug(`Determining Gitlab API version`);
        // projects/owned route deprecated in v4
        await glGot(`projects/owned`);
        config.apiVersion = 'v3';
      } catch (err) {
        config.apiVersion = 'v4';
      }
      logger.debug(`Detected Gitlab API ${config.apiVersion}`);
      config.repoName = repoName.replace('/', '%2F');
      try {
        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;
      }
      return config;
    }
    
    // Search
    
    // Returns an array of file paths in current repo matching the fileName
    async function findFilePaths(fileName) {
      logger.debug("Can't find multiple package.json files in GitLab");
      return [fileName];
    }
    
    // Branch
    
    // Returns true if branch exists, otherwise false
    async function branchExists(branchName) {
      logger.debug(`Checking if branch exists: ${branchName}`);
      try {
        const url = `projects/${config.repoName}/repository/branches/${branchName}`;
        const res = await glGot(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;
      }
    }
    
    // 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})`);
      const urlString = `projects/${config.repoName}/merge_requests?state=opened`;
      const res = await glGot(urlString);
      logger.debug(`Got res with ${res.body.length} results`);
      let pr = null;
      res.body.forEach(result => {
        if (result.source_branch === branchName) {
          pr = result;
        }
      });
      if (!pr) {
        return null;
      }
      return getPr(config.apiVersion === 'v3' ? pr.id : pr.iid);
    }
    
    // Returns the combined status for a branch.
    async function getBranchStatus(branchName) {
      logger.debug(`getBranchStatus(${branchName})`);
      // First, get the branch to find the commit SHA
      let url = `projects/${config.repoName}/repository/branches/${branchName}`;
      let res = await glGot(url);
      const branchSha = res.body.commit.id;
      // Now, check the statuses for that commit
      url = `projects/${config.repoName}/repository/commits/${branchSha}/statuses`;
      res = await glGot(url);
      logger.debug(`Got res with ${res.body.length} results`);
      if (res.body.length === 0) {
        // Return 'pending' if we have no status checks
        return 'pending';
      }
      let status = 'success';
      // Return 'success' if all are success
      res.body.forEach(check => {
        // If one is failed then don't overwrite that
        if (status !== 'failure') {
          if (check.status === 'failed') {
            status = 'failure';
          } else if (check.status !== 'success') {
            status = check.status;
          }
        }
      });
      return status;
    }
    
    async function deleteBranch(branchName) {
      await glGot.delete(
        `projects/${config.repoName}/repository/branches/${branchName}`
      );
    }
    
    // Issue
    
    async function addAssignees(prNo, assignees) {
      logger.debug(`Adding assignees ${assignees} to #${prNo}`);
      if (assignees.length > 1) {
        logger.error('Cannot assign more than one assignee to Merge Requests');
      }
      let url = `projects/${config.repoName}/merge_requests/${prNo}`;
      url = `${url}?assignee_id=${assignees[0]}`;
      await glGot.put(url);
    }
    
    async function addReviewers(prNo, reviewers) {
      logger.debug(`addReviewers('${prNo}, '${reviewers})`);
      logger.error('No reviewer functionality in GitLab');
    }
    
    async function addLabels(prNo, labels) {
      logger.debug(`Adding labels ${labels} to #${prNo}`);
      let url = `projects/${config.repoName}/merge_requests/${prNo}`;
      url = `${url}?labels=${labels.join(',')}`;
      await glGot.put(url);
    }
    
    async function findPr(branchName, prTitle, state = 'all') {
      logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
      const urlString = `projects/${config.repoName}/merge_requests?state=${state}`;
      const res = await glGot(urlString);
      let pr = null;
      res.body.forEach(result => {
        if (
          (!prTitle || result.title === prTitle) &&
          result.source_branch === branchName
        ) {
          pr = result;
          // GitHub uses number, GitLab uses iid
          pr.number = pr.id;
          pr.body = pr.description;
          pr.displayNumber = `Merge Request #${pr.iid}`;
          if (pr.state !== 'opened') {
            pr.isClosed = true;
          }
        }
      });
      return pr;
    }
    
    // Pull Request
    async function checkForClosedPr(branchName, prTitle) {
      const pr = await findPr(branchName, prTitle, 'closed');
      if (pr) {
        return true;
      }
      return false;
    }
    
    async function createPr(branchName, title, body) {
      logger.debug(`Creating Merge Request: ${title}`);
      const res = await glGot.post(`projects/${config.repoName}/merge_requests`, {
        body: {
          source_branch: branchName,
          target_branch: config.defaultBranch,
          remove_source_branch: true,
          title,
          description: body,
        },
      });
      const pr = res.body;
      pr.number = pr.id;
      pr.displayNumber = `Merge Request #${pr.iid}`;
      return pr;
    }
    
    async function getPr(prNo) {
      logger.debug(`getPr(${prNo})`);
      const url = `projects/${config.repoName}/merge_requests/${prNo}`;
      const pr = (await glGot(url)).body;
      // Harmonize fields with GitHub
      pr.number = config.apiVersion === 'v3' ? pr.id : pr.iid;
      pr.displayNumber = `Merge Request #${pr.iid}`;
      pr.body = pr.description;
      if (pr.state === 'closed' || pr.state === 'merged') {
        logger.debug('pr is closed');
        pr.isClosed = true;
      }
      if (pr.merge_status === 'cannot_be_merged') {
        logger.debug('pr cannot be merged');
        pr.isUnmergeable = true;
      }
      // 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;
    }
    
    async function updatePr(prNo, title, body) {
      await glGot.put(`projects/${config.repoName}/merge_requests/${prNo}`, {
        body: {
          title,
          description: body,
        },
      });
    }
    
    async function mergePr(pr) {
      await glGot.put(
        `projects/${config.repoName}/merge_requests/${pr.number}/merge`,
        {
          body: {
            should_remove_source_branch: true,
          },
        }
      );
    }
    
    // Generic File operations
    
    async function getFile(filePath, branchName = config.defaultBranch) {
      // Gitlab API v3 support
      let url;
      if (config.apiVersion === 'v3') {
        url = `projects/${config.repoName}/repository/files?file_path=${filePath}&ref=${branchName}`;
      } else {
        url = `projects/${config.repoName}/repository/files/${filePath}?ref=${branchName}`;
      }
      const res = await glGot(url);
      return res.body.content;
    }
    
    async function getFileContent(filePath, branchName) {
      try {
        const file = await getFile(filePath, branchName);
        return new Buffer(file, '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;
      }
    }
    
    async function getFileJson(filePath, branchName) {
      const fileContent = await getFileContent(filePath, branchName);
      return JSON.parse(fileContent);
    }
    
    async function createFile(branchName, filePath, fileContents, message) {
      // Gitlab API v3 support
      let url;
      const opts = {};
      if (config.apiVersion === 'v3') {
        url = `projects/${config.repoName}/repository/files`;
        opts.body = {
          file_path: filePath,
          branch_name: branchName,
          commit_message: message,
          encoding: 'base64',
          content: new Buffer(fileContents).toString('base64'),
        };
      } else {
        url = `projects/${config.repoName}/repository/files/${filePath}`;
        opts.body = {
          branch: branchName,
          commit_message: message,
          encoding: 'base64',
          content: new Buffer(fileContents).toString('base64'),
        };
      }
      await glGot.post(url, opts);
    }
    
    async function updateFile(branchName, filePath, fileContents, message) {
      // Gitlab API v3 support
      let url;
      const opts = {};
      if (config.apiVersion === 'v3') {
        url = `projects/${config.repoName}/repository/files`;
        opts.body = {
          file_path: filePath,
          branch_name: branchName,
          commit_message: message,
          encoding: 'base64',
          content: new Buffer(fileContents).toString('base64'),
        };
      } else {
        url = `projects/${config.repoName}/repository/files/${filePath}`;
        opts.body = {
          branch: branchName,
          commit_message: message,
          encoding: 'base64',
          content: new Buffer(fileContents).toString('base64'),
        };
      }
      await glGot.put(url, opts);
    }
    
    // Add a new commit, create branch if not existing
    async function commitFilesToBranch(
      branchName,
      files,
      message,
      parentBranch = config.defaultBranch
    ) {
      logger.debug(
        `commitFilesToBranch('${branchName}', files, message, '${parentBranch})'`
      );
      if (branchName !== parentBranch) {
        const isBranchExisting = await branchExists(branchName);
        if (isBranchExisting) {
          logger.debug(`Branch ${branchName} already exists`);
        } else {
          logger.debug(`Creating branch ${branchName}`);
          await createBranch(branchName);
        }
      }
      for (const file of files) {
        const existingFile = await getFileContent(file.name, branchName);
        if (existingFile) {
          logger.debug(`${file.name} exists - updating it`);
          await updateFile(branchName, file.name, file.contents, message);
        } else {
          logger.debug(`Creating file ${file.name}`);
          await createFile(branchName, file.name, file.contents, message);
        }
      }
    }
    
    // Internal branch operations
    
    // Creates a new branch with provided commit
    async function createBranch(branchName, ref = config.defaultBranch) {
      // Gitlab API v3 support
      const opts = {};
      if (config.apiVersion === 'v3') {
        opts.body = {
          branch_name: branchName,
          ref,
        };
      } else {
        opts.body = {
          branch: branchName,
          ref,
        };
      }
      await glGot.post(`projects/${config.repoName}/repository/branches`, opts);
    }