Search code examples
gitgithubgithub-api

How to cherry-pick through GitHub's API


I'm having trouble performing a simple cherry-pick through the GitHub API. It must be possible, but it's not clear to me how...

I'm developing a chatbot for Slack in C# to help manage Kubernetes environments and GitHub releases, and want to extend it with a hotfix feature. Given an environment, it should create a branch that matches that current release and cherry-pick one or more commit SHA's in there, as supplied by the author of the request through Slack.

All the plumbing is in place. Using the POST /repos/:owner/:repo/git/refs I am able to create the branch that matches the specific release. This means I have both a branch name, commit SHA and tree SHA ready for the next step; cherry-picking one or more commit SHA's into this branch. Using POST /repos/:owner/:repo/git/commits I am able to create a commit, but I'm not sure which tree and/or parent to use - which probably causes the issues I encounter calling POST /repos/:owner/:repo/merges , as it returns me status 409 (merge conflict) where locally, of course, it does not.

The only real example I seem to find is https://github.com/tibdex/github-cherry-pick. However, it does not really match my scenario, and I have a hard time understanding the Git inner workings.

My scenario (latest to oldest);

* commit E (current state of `master`)
* commit D
* commit C (deployed to environment)
* commit B
* commit A

In this scenario, I would like to cherry-pick commit E into a new branch off of commit C, creating a set (A, B, C, E) that I can release.

* commit E (current state of `master`)
* commit D
|
| * commit E (new branch, to be deployed)
|/
* commit C (deployed to environment)
* commit B
* commit A

Basically what I need is a GitHub API version of this bash;

git checkout -b {new-branch-name} {sha}
git cherry-pick {sha}
git push main {new-branch-name}

Any help is appreciated!


Solution

  • Here is how I implemented cherry-pick over the Github API, in pseudo-code:

    // here is a commit, for example, from a pull request:
    listOfCommits = GET /repos/$owner/$repo/pulls/$number/commits
    commit = listOfCommits.head // the first one in the list
    
    // Here is the branch we want to cherry-pick to:
    branch = GET /repos/$owner/$repo/branches/$branchName
    branchSha = branch.commit.sha
    branchTree = branch.commit.commit.tree.sha
    
    // Create a temporary commit on the branch, which extends as a sibling of
    // the commit we want but contains the current tree of the target branch:
    parentSha = commit.parents.head // first parent -- there should only be one
    tempCommit = POST /repos/$owner/$repo/git/commits { "message": "temp",
                                                        "tree": branchTree,
                                                        "parents": [parentSha] }
    
    // Now temporarily force the branch over to that commit
    PATCH /repos/$owner/$repo/git/refs/heads/$refName { sha = tempCommit.sha,
                                                        force = true }
    
    // Merge the commit we want into this mess:
    merge = POST /repos/$owner/$repo/merges { "base": branchName
                                      "head": commit.sha }
    
    // and get that tree!
    mergeTree = merge.commit.tree.sha
    
    // Now that we know what the tree should be, create the cherry-pick commit.
    // Note that branchSha is the original from up at the top.
    cherry = POST /repos/$owner/$repo/git/commits { "message": "looks good!",
                                                    "tree": mergeTree,
                                                    "parents": [branchSha] }
    
    // Replace the temp commit with the real commit:
    PATCH /repos/$owner/$repo/git/refs/heads/$refName { sha = cherry.sha,
                                                        force = true }
    
    // Done!
    

    Git experts, please advise, but I believe this accomplishes:

    git checkout -b {new-branch-name} {sha}
    git cherry-pick {sha}
    git push main {new-branch-name}