How to check if a branch has been merged into master using github api (or octokit)
Asked Answered
D

3

6

I'd like to check if a feature branch has been merged into my master branch using the github api/Octokit. I havent been able to find any documentation or posts on this topic. Is this possible? Does anyone know of any posts that address this topic?

Dandruff answered 26/10, 2015 at 14:16 Comment(0)
D
5

This is the equivalent to git log master..my-cool-branch using Octokit - you want to see if there are any commits on your branch which are not in master:

var head = "my-cool-branch";
var baseBranch = "master";
var compareBaseToHead = await client.Repository.Commit.Compare(owner, repo, baseBranch, head);

if (compareBaseToHead.TotalCommits == 0)
{
    Console.WriteLine("Branch {0} has been merged into {1}", head, baseBranch);
}
else
{
    Console.WriteLine("Branch {0} has NOT been merged into {1}", head, baseBranch);
}
Data answered 18/3, 2016 at 5:22 Comment(0)
F
2

I actually just finished a script that incorporates this kind of check. The overall goal of my script is to look at all repos in our org and clean up branches that have been merged. The script is written in groovy and uses kohsuke's github-api java client. There was one feature missing from the java client which I have a PR open for. Until that PR is merged and a new version released, you'll have to either omit the branch protection check or do what I did (pull the code yourself and build it with the feature added).

The logic checking whether a branch has been merged and has no new commits to merge is in the cleanupBranches method. Basically, I loop through all CLOSED pull requests (PR) for a given repo. If the pull request was merged, I then do a compare of the PR's head branch against the repo's default branch (not all of ours are named master). If the PR's head branch has 0 commits that haven't been merged to the default branch, it's ok to delete the branch.

I included my entire script just for context. You will notice that I did change some configuration property defaults.

@GrabResolver(name='binrepo', root='https://binrepo.myCompany.com')
@Grab(group='org.kohsuke', module='github-api', version='SNAPSHOT')

import org.kohsuke.github.*

// store some things here so they are available everywhere
class Config {
  static Boolean doDelete = Boolean.valueOf(System.getProperty('branch.cleanup.doDelete', 'false'))
  static String  orgName = System.getProperty('branch.cleanup.gitOrgName', 'myOrg')
  static String  gitApiUrl = System.getProperty('branch.cleanup.gitApiUrl', 'https://git.myCompany.com/api/v3')
  static String  apiKey
}

def executeOnShell(String command, boolean log = false) {
  File workingDir = new File(System.properties.'user.dir')
  def process = new ProcessBuilder(addShellPrefix(command))
      .directory(workingDir)
      .redirectErrorStream(true)
      .start()
  if (log) {
    process.inputStream.eachLine { println it }
  }

  process.waitFor();
}

def addShellPrefix(String command) {
  def commandArray = new String[3]
  commandArray[0] = "sh"
  commandArray[1] = "-c"
  commandArray[2] = command
  return commandArray
}

def allRepos(GHOrganization org, String...repoPrefixes) {
  println "Fetching all repositories under the ${Config.orgName} org that match prefix(es) ${repoPrefixes}"
  return org.getRepositories().entrySet().findAll{ entry ->
    if (repoPrefixes) {
      return repoPrefixes.any{ prefix -> entry.key.startsWith(prefix) }
    } else {
      return true
    }
  }
}

def cleanupBranches(repo) {
  def defaultBranchName = repo.getDefaultBranch()
  def defaultBranch = repo.getBranch(defaultBranchName)
  def deletedBranchNames = []
  def branchesByName = repo.getBranches().entrySet().findAll{ !it.key.equals(defaultBranchName) && !it.value.isProtected() }.collectEntries{[it.key, it.value]}
  def pullRequests = repo.queryPullRequests().base(defaultBranchName).state(GHIssueState.CLOSED).list().withPageSize(100).each{ pr ->
    // loop thru all pull requests that have been closed and also merged
    if (pr.isMerged()) {
      def branch = branchesByName.get(pr.getHead().getRef())
      if (branch) {
        // the branch still exists and has been merged by this PR
        // make sure it doesn't have any unmerged commits
        def compare = repo.getCompare(defaultBranch, branch)
        if (compare.getTotalCommits() == 0) {
          // branch has been merged and there are 0 commits since merge. delete it
          println "Branch ${repo.getName()}/${branch.getName()} has 0 commits not merged to ${defaultBranchName}. Delete it. PR ${pr.getNumber()} : ${pr.getTitle()}"
          if (Config.doDelete) {
            deleteBranch(repo, branch)
          }

          // remove from internal map of branches since the branch has now been deleted in git
          branchesByName.remove(branch.getName())
          deletedBranchNames.push "${repo.getName()}/${branch.getName()}"
        }
      }
    }
  }
  return deletedBranchNames
}

def deleteBranch(repo, branch) {
  // use a simple curl here because the kohsuke library way of doing it requires 2 api calls when just 1 will do here  
  String cmd = "curl -X DELETE -H \"Authorization: token ${Config.apiKey}\" ${Config.gitApiUrl}/repos/${Config.orgName}/${repo.getName()}/git/refs/heads/${branch.getName()}"
  executeOnShell(cmd)
}

if (args.size() < 1) {
  println "Usage: cleanupRepoBranches.groovy <oauthToken> <optionalRepo-name>"
  System.exit(1)
}

Config.apiKey = args[0]

def branchesDeleted = []
def errors = []
GitHub github = GitHub.connectToEnterprise(Config.gitApiUrl, Config.apiKey)
if (args.size() > 1) {
  String repoName = args[1]
  GHRepository repo = github.getRepository("${Config.orgName}/${repoName}")
  branchesDeleted = cleanupBranches(repo)
} else {
  def repoPrefixes = System.getProperty('branch.cleanup.repoPrefixes', 'pref-,pref2-').split(',')
  def answer = System.console().readLine "You have not specified a repoName. If you proceed, this script will list ${Config.doDelete ? 'and delete ' : ''}all branches with a merged pull request and 0 commits left to merge for all repos starting with ${repoPrefixes.join(', ')} in the ${Config.orgName} org. Are you sure? (y/n) "
  if (answer == 'y') {
    println 'ok! here we go!'
    allRepos(github.getOrganization(Config.orgName), repoPrefixes).each { entry ->
      try {
        branchesDeleted += cleanupBranches(entry.value)
      } catch (Exception e) {
        errors.push([ message: "Error processing branches for ${entry.key} repo", ex: e ])
      }
    }
  }
}
println "${branchesDeleted.size()} Branches deleted..."
branchesDeleted.each{ branch -> println branch }
println "${errors.size()} errors..."
errors.each{ error ->
  println error.message
  error.ex.printStackTrace()
  println
}
Finstad answered 22/12, 2016 at 21:15 Comment(0)
S
1

I believe you can get the branch from the pull request, and then, after merge check if the pull request has been merged. More information at:

https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged

Swordtail answered 4/2, 2016 at 12:7 Comment(1)
This is the fastest and most accurate answer if you have a pull request open against the branch. If not, you'll need to look at comparing the branch to master which I'll elaborate on in another reply.Data

© 2022 - 2024 — McMap. All rights reserved.