Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,297,991
Community Members
 
Community Events
165
Community Groups

Rebase against github using ScriptRunner

Hi,

I have forked a repository from github and imported it into our Bitbucket instance.

I edited one file and pushed to our Bitbucket repository.

I'm now trying to automate a rebase against the GitHub repository but have no knowledge at all on Groovy nor Bitbucket API. After hours of research, I have made this Custom Schedule Job inline script which does not work of course:

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.bitbucket.repository.Repository
import com.atlassian.bitbucket.repository.RepositoryService
import com.atlassian.bitbucket.repository.RefService
import com.atlassian.bitbucket.repository.Branch
import com.atlassian.bitbucket.user.ApplicationUser
import com.atlassian.bitbucket.user.UserService
import com.atlassian.bitbucket.scm.git.command.GitRebaseCommandParameters
import com.atlassian.bitbucket.scm.git.command.GitExtendedCommandFactory

def repositoryService = ComponentLocator.getComponent(RepositoryService.class)
def refService = ComponentLocator.getComponent(RefService.class)
def cmdFactory = ComponentLocator.getComponent(GitExtendedCommandFactory.class)
def userService = ComponentLocator.getComponent(UserService.class)

Repository rep = repositoryService.getBySlug("RUST", "crates.io-mirror")
Branch master = refService.getDefaultBranch(rep)
ApplicationUser user = userService.findUserByEmail("the_committer@my_company.com")

def paramsBuilder = new GitRebaseCommandParameters.Builder(master, "https://github.com/rust-lang/crates.io-index.git")
paramsBuilder.dryRun(true).committer(user)

def rebase = cmdFactory.rebase(rep, paramsBuilder.build())
rebase.call()

This gives me the following error:

2021-03-18 15:27:06,990 ERROR [c.o.s.j.AbstractCustomScheduledJob]: *************************************************************************************
2021-03-18 15:27:06,995 ERROR [c.o.s.j.AbstractCustomScheduledJob]: Script job: 'Update crates.io mirror' failed
com.atlassian.bitbucket.validation.ArgumentValidationException: The specified upstream, 'https://github.com/rust-lang/crates.io-index.git', is not valid. The upstream must be specified as a full 40-character SHA-1.
at com.atlassian.stash.internal.scm.git.DefaultGitExtendedCommandFactory.rebase(DefaultGitExtendedCommandFactory.java:236)
at com.atlassian.plugin.util.ContextClassLoaderSettingInvocationHandler.invoke(ContextClassLoaderSettingInvocationHandler.java:26)
at com.sun.proxy.$Proxy438.rebase(Unknown Source)
at com.atlassian.bitbucket.scm.git.command.GitExtendedCommandFactory$rebase.call(Unknown Source)
at Script16.run(Script16.groovy:23)
at com.atlassian.stash.internal.scm.git.DefaultGitExtendedCommandFactory.rebase(DefaultGitExtendedCommandFactory.java:236)
at com.atlassian.plugin.util.ContextClassLoaderSettingInvocationHandler.invoke(ContextClassLoaderSettingInvocationHandler.java:26)
at com.sun.proxy.$Proxy438.rebase(Unknown Source)
at com.atlassian.bitbucket.scm.git.command.GitExtendedCommandFactory$rebase.call(Unknown Source)
at Script16.run(Script16.groovy:23)

So the upstream parameters is a SHA-1 string, so I guess I can't refer to a repository outside Bitbucket this way.

How can I do this rebase against a public GitHub repository?

1 answer

Hi @Geobert Quach 

I am afraid it is not possible to rebase a branch on a different repository.

My suggestion is, instead of importing a fork and commiting new work on it, to have separate branches to use for mirroring and development.

I have managed to achieve what you want to do using Scriptrunner's 'Mirror GitHub repositories', then fetching any changes into a buffer/proxy branch where I could rebase on.

In total, my bitbucket server had 2 repositories:

  • A repo that is created from the mirroring. You should not add more branches here
  • The repo that contains your development work. It contains 2 branches:
    • The branch were you will be commiting your changes. The script will be rebasing this branch on the public GitHub repository.
    • a proxy branch that will be used as a workaround, in order to run the rebase command. It will be copying over from the mirror. You should not push any changes here, but only have the script using it to fetch and rebase.

This is the script that worked for me:

import com.atlassian.bitbucket.i18n.I18nService
import com.atlassian.bitbucket.repository.RepositoryBranchesRequest
import com.atlassian.bitbucket.scm.CommandOutputHandler
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory
import com.atlassian.bitbucket.scm.git.command.fetch.GitFetchTagMode
import com.atlassian.bitbucket.util.Page
import com.atlassian.bitbucket.util.PageProvider
import com.atlassian.bitbucket.util.PageRequest
import com.atlassian.bitbucket.util.PagedIterable
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.bitbucket.repository.Repository
import com.atlassian.bitbucket.repository.RepositoryService
import com.atlassian.bitbucket.repository.RefService
import com.atlassian.bitbucket.repository.Branch
import com.atlassian.bitbucket.user.ApplicationUser
import com.atlassian.bitbucket.user.UserService
import com.atlassian.bitbucket.scm.git.command.GitRebaseCommandParameters
import com.atlassian.bitbucket.scm.git.command.GitExtendedCommandFactory
import com.atlassian.stash.internal.scm.git.command.updateref.UpdateRefCommandExitHandler
import com.atlassian.utils.process.ProcessException
import com.atlassian.utils.process.Watchdog
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl

def repositoryService = ComponentLocator.getComponent(RepositoryService)
def refService = ComponentLocator.getComponent(RefService)
def userService = ComponentLocator.getComponent(UserService)
def extendedCommandFactory = ComponentLocator.getComponent(GitExtendedCommandFactory)
def i18nService = ComponentLocator.getComponent(I18nService)

// A bitbucket project that contains both repositories (mirror and dev).
String PROJECT_KEY = 'MIR'

// the repo that contains the branch to be rebased.
String TARGET_REPO_NAME = 'develop'

// The branch that you want to use for development and rebase occasionally.
String REBASED_BRANCH_NAME = 'main'

// We cannot rebase from a different repository. Will use a buffer or proxy branch, in the same repo. We will fetch any new commits from the mirror here, and rebase on this one
// This branch should be read only for other users, as we should not push other commits here
String BUFFER_BRANCH_NAME = 'mirror'

// Another repo, set to mirror a github repo
String MIRROR_REPO_NAME = 'test'

// The name of the branch in the mirror repo that we originally want to rebase on
String MIRROR_BRANCH_NAME = 'main'

/**

Overview of branches and actions

SERVER | REPO | BRANCH
-------------------------------
GitHub | MIRROR_REPO_NAME | MIRROR_BRANCH_NAME
↓ Scriptrunner will be mirroring this into bitbucket (Use 'Mirror GitHub Repositories' to set it up)

BitBucket | MIRROR_REPO_NAME | MIRROR_BRANCH_NAME
↓ This script will fetch any new changes from the mirror, into the repo that we want to rebase

BitBucket | TARGET_REPO_NAME | BUFFER_BRANCH_NAME
↓ This script will rebase REBASED_BRANCH_NAME on BUFFER_BRANCH_NAME

BitBucket | TARGET_REPO_NAME | REBASED_BRANCH_NAME

*/
class FetchOutputHandler implements CommandOutputHandler<Void> {

@Override
Void getOutput() { return null }

@Override
void process(InputStream output) throws ProcessException {}

@Override
void complete() throws ProcessException {}

@Override
void setWatchdog(Watchdog watchdog) {}
}

Branch getBranch(RefService refService, Repository repository, String branchName) {
def repositoryBranchesRequest = new RepositoryBranchesRequest.Builder(repository).build()
new PagedIterable<Branch>(new PageProvider<Branch>() {
@Override
Page<Branch> get(PageRequest pageRequest) {
refService.getBranches(repositoryBranchesRequest, pageRequest) as Page<Branch>
}
}, 1000).iterator().find { Branch it -> it.displayId == branchName }
}

Repository targetRep = repositoryService.getBySlug(PROJECT_KEY, TARGET_REPO_NAME)
assert targetRep : 'target repository not found'

Repository mirrorRep = repositoryService.getBySlug(PROJECT_KEY, MIRROR_REPO_NAME)
assert mirrorRep : "source repository not found"

Branch targetBranch = getBranch(refService, targetRep, REBASED_BRANCH_NAME)
//if you want to rebase the default branch, use the next line instead, for better performance
//Branch targetBranch = get refService.getDefaultBranch(targetRep)
log.debug("Will rebase $targetBranch.displayId ($targetBranch.id) in $targetRep.project.key / $targetRep.slug ")

Branch bufferBranch = getBranch(refService, targetRep, BUFFER_BRANCH_NAME)
assert bufferBranch : 'buffer branch not found'
log.debug("on to buffer branch $bufferBranch.displayId ($bufferBranch.id) in $targetRep.project.key / $targetRep.slug")

Branch mirrorBranch = getBranch(refService, mirrorRep, MIRROR_BRANCH_NAME)
assert mirrorBranch : 'source branch not found'
log.debug("Mirror branch is $mirrorBranch.displayId ($mirrorBranch.id) in $mirrorRep.project.key / $mirrorRep.slug")

def latestCommit = targetBranch.latestCommit
log.debug("Latest commit in target branch is $latestCommit")

def latestMirrorCommit = mirrorBranch.latestCommit
log.debug("Latest commit in mirror branch is $latestMirrorCommit")

ApplicationUser user = userService.findUserByNameOrEmail("admin")
assert user : "user not found"

ApplicationUser committer = userService.findUserByNameOrEmail("admin")
GitCommandBuilderFactory gitCommandBuilderFactory = ScriptRunnerImpl.getOsgiService(GitCommandBuilderFactory)

// Fetching any new commits from the MIRROR_BRANCH
def fetchCommand = gitCommandBuilderFactory.builder(targetRep).fetch().repository(mirrorRep)
.refspec(mirrorBranch.id)
.tags(GitFetchTagMode.NO_TAGS)
.build(new FetchOutputHandler())
fetchCommand.setTimeout(60)
fetchCommand.call()

// Updating refs to complete pulling the changes into BUFFER_BRANCH
def updateRefCommand = gitCommandBuilderFactory.builder(targetRep).updateRef()
.set(bufferBranch.id, mirrorBranch.latestCommit)
.author(user) //for reflogs
.exitHandler(new UpdateRefCommandExitHandler(i18nService, targetRep))
.oldValue(bufferBranch.latestCommit)
.build(new FetchOutputHandler())
//optionally set a timeout in seconds
updateRefCommand.setTimeout(60)
updateRefCommand.call()

// Rebasing REBASED_BRANCH on to BUFFER_BRANCH
def rebaseCommand = extendedCommandFactory.rebase(targetRep,
new GitRebaseCommandParameters.Builder(targetBranch, latestMirrorCommit)
.committer(committer)
.commitRequired(true)
.upstreamRepository(targetRep)
.build())
//optionally set a timeout in seconds
rebaseCommand.setTimeout(60)
rebaseCommand.call()

I hope this helps. I appreciate this setup might be more complicated than expected, but this was the best way I could find around the problems and limitations that I mentioned above.

Please let us know if you have any further questions or issues.

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Published in Bitbucket

Git push size limits are coming to Bitbucket Cloud starting April 4th, 2022

Beginning on April 4th, we will be implementing push limits. This means that your push cannot be completed if it is over 3.5 GB. If you do attempt to complete a push that is over 3.5 GB, it will fail...

2,219 views 2 9
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you