com.googlesrouce.gerrit.plugins.github.git.PullRequestImportJob.java Source code

Java tutorial

Introduction

Here is the source code for com.googlesrouce.gerrit.plugins.github.git.PullRequestImportJob.java

Source

// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.googlesrouce.gerrit.plugins.github.git;

import java.io.IOException;
import java.util.List;

import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHPullRequestCommitDetail;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Change.Id;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.reviewdb.server.AccountExternalIdAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountImpoter;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.googlesource.gerrit.plugins.github.GitHubURL;
import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
import com.googlesource.gerrit.plugins.github.oauth.ScopedProvider;
import com.googlesrouce.gerrit.plugins.github.git.GitJobStatus.Code;

public class PullRequestImportJob implements GitJob, ProgressMonitor {

    public interface Factory {
        PullRequestImportJob create(@Assisted("index") int jobIndex, @Assisted("organisation") String organisation,
                @Assisted("name") String repository, @Assisted int pullRequestId,
                @Assisted PullRequestImportType importType);
    }

    private static final Logger LOG = LoggerFactory.getLogger(PullRequestImportJob.class);

    private static final String TOPIC_FORMAT = "GitHub #%d";

    private final GitHubRepository ghRepository;
    private final GitHubLogin ghLogin;
    private final String organisation;
    private final String repoName;
    private final PullRequestImportType importType;
    private final int prId;
    private final GitRepositoryManager repoMgr;
    private final int jobIndex;
    private PullRequestCreateChange createChange;
    private com.google.gerrit.server.project.ProjectControl.Factory projectControlFactory;
    private Project project;
    private GitJobStatus status;
    private boolean cancelRequested;
    private Provider<ReviewDb> schema;

    private com.google.gerrit.server.account.CreateAccount.Factory createAccountFactory;

    private AccountImpoter accountImporter;

    @Inject
    public PullRequestImportJob(@GitHubURL String gitHubUrl, GitRepositoryManager repoMgr,
            PullRequestCreateChange createChange, ProjectCache projectCache,
            ProjectControl.Factory projectControlFactory, Provider<ReviewDb> schema, AccountImpoter accountImporter,
            GitHubRepository.Factory gitHubRepoFactory, ScopedProvider<GitHubLogin> ghLoginProvider,
            @Assisted("index") int jobIndex, @Assisted("organisation") String organisation,
            @Assisted("name") String repoName, @Assisted int pullRequestId,
            @Assisted PullRequestImportType importType) {
        this.jobIndex = jobIndex;
        this.repoMgr = repoMgr;
        this.ghLogin = ghLoginProvider.get();
        this.organisation = organisation;
        this.repoName = repoName;
        this.importType = importType;
        this.prId = pullRequestId;
        this.createChange = createChange;
        this.projectControlFactory = projectControlFactory;
        this.project = fetchGerritProject(projectCache, organisation, repoName);
        this.ghRepository = gitHubRepoFactory.create(organisation, repoName);
        this.status = new GitJobStatus(jobIndex);
        this.schema = schema;
        this.accountImporter = accountImporter;
    }

    private Project fetchGerritProject(ProjectCache projectCache, String organisation, String repoName) {
        NameKey projectNameKey = Project.NameKey.parse(organisation + "/" + repoName);
        ProjectState projectState = projectCache.get(projectNameKey);
        return projectState.getProject();
    }

    @Override
    public void run() {
        ReviewDb db = schema.get();
        try {
            status.update(GitJobStatus.Code.SYNC);
            exitWhenCancelled();
            GHPullRequest pr = fetchGitHubPullRequestInfo();

            exitWhenCancelled();
            Repository gitRepo = repoMgr.openRepository(new Project.NameKey(organisation + "/" + repoName));
            try {
                exitWhenCancelled();
                fetchGitHubPullRequest(gitRepo, pr);

                exitWhenCancelled();
                List<Id> changeIds = addPullRequestToChange(db, pr, gitRepo);
                status.update(GitJobStatus.Code.COMPLETE, "Imported",
                        "PullRequest imported as Changes " + changeIds);
            } finally {
                gitRepo.close();
            }
            db.commit();
        } catch (JobCancelledException e) {
            status.update(GitJobStatus.Code.CANCELLED);
            try {
                db.rollback();
            } catch (OrmException e1) {
                LOG.error("Error rolling back transation", e1);
            }
        } catch (Exception e) {
            LOG.error("Pull request " + prId + " into repository " + organisation + "/" + repoName + " was failed",
                    e);
            status.update(GitJobStatus.Code.FAILED, "Failed", getErrorDescription(e));
            try {
                db.rollback();
            } catch (OrmException e1) {
                LOG.error("Error rolling back transation", e1);
            }
        } finally {
            db.close();
        }
    }

    private String getErrorDescription(Exception e) {
        return e.getLocalizedMessage();
    }

    private List<Id> addPullRequestToChange(ReviewDb db, GHPullRequest pr, Repository gitRepo) throws Exception {
        String destinationBranch = pr.getBase().getRef();
        List<Id> prChanges = Lists.newArrayList();
        ObjectId baseObjectId = ObjectId.fromString(pr.getBase().getSha());
        ObjectId prHeadObjectId = ObjectId.fromString(pr.getHead().getSha());

        RevWalk walk = new RevWalk(gitRepo);
        walk.markUninteresting(walk.lookupCommit(baseObjectId));
        walk.markStart(walk.lookupCommit(prHeadObjectId));
        walk.sort(RevSort.REVERSE);

        int patchNr = 1;
        for (GHPullRequestCommitDetail ghCommitDetail : pr.listCommits()) {
            status.update(Code.SYNC, "Patch #" + patchNr,
                    "Patch#" + patchNr + ": Inserting PullRequest into Gerrit");
            RevCommit revCommit = walk.parseCommit(ObjectId.fromString(ghCommitDetail.getSha()));

            Account.Id pullRequestOwner;
            // It may happen that the user that created the Pull Request has been
            // removed from GitHub: we assume that the commit author was that user
            // as there are no other choices.
            if (pr.getUser() == null) {
                pullRequestOwner = getOrRegisterAccount(db, ghCommitDetail.getCommit().getAuthor());
            } else {
                pullRequestOwner = getOrRegisterAccount(db, pr.getUser());
            }

            Id changeId = createChange.addCommitToChange(db, project, gitRepo, destinationBranch, pullRequestOwner,
                    revCommit, getChangeMessage(pr), String.format(TOPIC_FORMAT, pr.getNumber()), false);
            if (changeId != null) {
                prChanges.add(changeId);
            }
        }

        return prChanges;
    }

    private com.google.gerrit.reviewdb.client.Account.Id getOrRegisterAccount(ReviewDb db, GitUser author)
            throws BadRequestException, ResourceConflictException, UnprocessableEntityException, OrmException,
            IOException {
        return getOrRegisterAccount(db, author.getName(), author.getName(), author.getEmail());
    }

    private com.google.gerrit.reviewdb.client.Account.Id getOrRegisterAccount(ReviewDb db, GHUser user)
            throws OrmException, BadRequestException, ResourceConflictException, UnprocessableEntityException,
            IOException {
        return getOrRegisterAccount(db, user.getLogin(), user.getName(), user.getEmail());
    }

    private com.google.gerrit.reviewdb.client.Account.Id getOrRegisterAccount(ReviewDb db, String login,
            String name, String email) throws OrmException, BadRequestException, ResourceConflictException,
            UnprocessableEntityException, IOException {
        AccountExternalId.Key userExtKey = new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME, login);
        AccountExternalIdAccess gerritExtIds = db.accountExternalIds();
        AccountExternalId userExtId = gerritExtIds.get(userExtKey);
        if (userExtId == null) {
            return accountImporter.importAccount(login, name, email);
        } else {
            return userExtId.getAccountId();
        }
    }

    private String getChangeMessage(GHPullRequest pr) {
        return "GitHub Pull Request: " + pr.getUrl() + "\n\n" + pr.getTitle() + "\n\n"
                + pr.getBody().replaceAll("\n", "\n\n");
    }

    private void exitWhenCancelled() throws JobCancelledException {
        if (cancelRequested) {
            throw new JobCancelledException();
        }
    }

    private void fetchGitHubPullRequest(Repository gitRepo, GHPullRequest pr)
            throws GitAPIException, InvalidRemoteException, TransportException {
        status.update(Code.SYNC, "Fetching", "Fetching PullRequests from GitHub");

        Git git = Git.wrap(gitRepo);
        FetchCommand fetch = git.fetch();
        fetch.setRemote(ghRepository.getCloneUrl());
        fetch.setRefSpecs(
                new RefSpec("+refs/pull/" + pr.getNumber() + "/head:refs/remotes/origin/pr/" + pr.getNumber()));
        fetch.setProgressMonitor(this);
        fetch.call();
    }

    private GHPullRequest fetchGitHubPullRequestInfo() throws IOException {
        status.update(Code.SYNC, "Fetch GitHub", "Getting PullRequest info");
        GHPullRequest pr = getGHRepository().getPullRequest(prId);
        return pr;
    }

    @Override
    public GitJobStatus getStatus() {
        return status;
    }

    @Override
    public int getIndex() {
        return jobIndex;
    }

    @Override
    public String getOrganisation() {
        return organisation;
    }

    public GHRepository getGHRepository() throws IOException {
        if (ghLogin.getMyself().getLogin().equals(organisation)) {
            return ghLogin.getMyself().getRepository(repoName);
        } else {
            return ghLogin.hub.getOrganization(organisation).getRepository(repoName);
        }
    }

    @Override
    public void cancel() {
        cancelRequested = true;
    }

    @Override
    public String getRepository() {
        return repoName;
    }

    @Override
    public void beginTask(String taskName, int numSteps) {
        status.update(Code.SYNC, taskName, taskName + " ...");
    }

    @Override
    public void endTask() {
    }

    @Override
    public boolean isCancelled() {
        return cancelRequested;
    }

    @Override
    public void start(int tot) {
    }

    @Override
    public void update(int progress) {
    }
}