Last updated Mar 14, 2024

Using WorkTree API to create commits in a repository

Bitbucket 7.14 introduces a new WorkTree API to create commits and make other changes to a Repository. App developers are strongly encouraged to start developing against this API and offer feedback related to their experience and use cases. This helps the team ensure the new API is as robust and capable as possible, since it will be the only option from 8.0.

Following is an example that demonstrates one such use case where you will create a commit on a new branch in a repository. Assume that the repository in which you shall be performing these changes is available in a variable called repository.

  1. Use GitWorkTreeBuilderFactory to create a GitWorkTreeBuilder and pass a commit hash that must be checked out in the worktree. You can initialize the resulting GitWorkTree by doing a checkout in one of two ways:

    • SPARSE, which (effectively) randomly selects a small number of files (generally 1) to actually check out while not checking out the others. This results in a nearly-empty worktree on disk, where git status shows no changes. Commands like git cherry-pick and git rebase can be used in this type of checkout.

    • NONE, which essentially is git clone --no-checkout followed by git reset to unstage the deletions. This results in a worktree that is empty on disk, but where git status shows every file as deleted. Commands like git merge run fine in this sort of worktree, but others like git cherry-pick and git rebase cannot be used.

    Absence of the option to do a "full" checkout is by design and is for scalability reasons. In this example, you will be using the NONE checkout type. You will later use the builder to create a commit and publish it to a new branch in the repository.

1
2
GitWorkTreeBuilder builder = workTreeBuilderFactory.builder(repository)
        .commit("6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
        .checkoutType(GitCheckoutType.NONE);
  1. You can now make changes in the context of a temporary worktree using the above GitWorkTreeBuilder:
1
2
builder.execute(workTree -> {
    // Merge in a commit from master
    workTree.builder().merge()
            .normal()
            .author(new SimplePerson("committer-name", "committer@email.com"))
            .message("Test merge")
            .commit("2d8897c9ac29ce42c3442cf80ac977057045e7f6")
            .build()
            .call();

    // Add a new file in a directory and create a commit
    workTree.mkdir("new-directory");
    workTree.write("new-directory/some-file.txt", UTF_8,
            writer -> writer.write("This is a new file\n"));

    workTree.builder().add()
            .path("new-directory/some-file.txt")
            .build()
            .call();

    workTree.builder().commit()
            .author(new SimplePerson("committer-name", "committer@email.com"))
            .message("Testing the work tree API")
            .build()
            .call();
});
  1. To publish changes in the temporary GitWorkTree back to repository, you will need a GitWorkTreeRepositoryHookInvoker. There's a convenience implementation, AbstractGitWorkTreeRepositoryHookInvoker that you can use as the base for your implementation.
1
2
class SimpleHookInvoker extends AbstractGitWorkTreeRepositoryHookInvoker<RepositoryHookRequest> {

    private RefChange refChange;

    SimpleHookInvoker(@Nonnull EventPublisher eventPublisher, @Nonnull RepositoryHookService repositoryHookService) {
        super(eventPublisher, repositoryHookService);
    }

    @Nonnull
    RefChange getRefChange() {
        return refChange;
    }

    @Nonnull
    @Override
    protected RepositoryHookRequest createHookRequest(@Nonnull GitWorkTree workTree,
                                                      @Nonnull List<RefChange> refChanges) {
        return new SimpleRepositoryHookRequest.Builder(workTree.getRepository(), FILE_EDIT)
                .refChanges(refChanges)
                .build();
    }

    @Nonnull
    @Override
    protected ApplicationEvent createPostUpdateEvent(@Nonnull RepositoryHookRequest request) {
        refChange = request.getRefChanges().stream().findFirst().orElse(null);
        return new AbstractRepositoryRefsChangedEvent(this, request.getRepository(), request.getRefChanges()) {
        };
    }
}
  1. With a simple implementation of GitWorkTreeRepositoryHookInvoker, you can now publish changes you made to the temporary WorkTree back to repository. Change the builder.execute() call in Step 2 to include the publishing step now. Here is what the completed version looks like:
1
2
RefChange refChange = builder.execute(workTree -> {
    // Merge in a commit from master
    workTree.builder().merge()
            .normal()
            .author(new SimplePerson("committer-name", "committer@email.com"))
            .message("Master merge")
            .commit("2d8897c9ac29ce42c3442cf80ac977057045e7f6")
            .build()
            .call();

    // Add a new file in a directory and create a commit
    workTree.mkdir("new-directory");
    workTree.write("new-directory/some-file.txt", UTF_8,
            writer -> writer.write("This is a new file\n"));

    workTree.builder().add()
            .path("new-directory/some-file.txt")
            .build()
            .call();

    workTree.builder().commit()
            .author(new SimplePerson("committer-name", "committer@email.com"))
            .message("Testing the work tree API")
            .build()
            .call();

    // Publish changes in the temporary worktree back to our repository on a new branch
    HookInvoker invoker = new SimpleHookInvoker(eventPublisher, repositoryHookService);

    workTree.publish(new PublishGitWorkTreeParameters.Builder(invoker)
            .author(new SimplePerson("committer-name", "committer@email.com"))
            .branch("new-branch", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
            .build());

    return invoker.getRefChange();
});

The resulting RefChange object now contains details of the update you just made.

Rate this page: