blob: 9a1632992ed2dd52b955b17d1064fd016b74e32c [file] [log] [blame]
// Copyright (C) 2012 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.googlesource.gerrit.plugins.github.git;
import static com.google.gerrit.entities.RefNames.REFS_HEADS;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryResult;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gerrit.server.query.change.ChangeQueryProcessor;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.ChangeIdUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PullRequestCreateChange {
private static final Logger LOG = LoggerFactory.getLogger(PullRequestCreateChange.class);
private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
private final ChangeInserter.Factory changeInserterFactory;
private final PatchSetInserter.Factory patchSetInserterFactory;
private final GenericFactory userFactory;
private final Provider<InternalChangeQuery> queryProvider;
private final BatchUpdate.Factory updateFactory;
private final Provider<ChangeQueryProcessor> qp;
private final ChangeQueryBuilder changeQuery;
private final Sequences sequences;
@Inject
PullRequestCreateChange(
ChangeInserter.Factory changeInserterFactory,
PatchSetInserter.Factory patchSetInserterFactory,
IdentifiedUser.GenericFactory userFactory,
Provider<InternalChangeQuery> queryProvider,
BatchUpdate.Factory batchUpdateFactory,
Provider<ChangeQueryProcessor> qp,
ChangeQueryBuilder changeQuery,
Sequences sequences) {
this.changeInserterFactory = changeInserterFactory;
this.patchSetInserterFactory = patchSetInserterFactory;
this.userFactory = userFactory;
this.queryProvider = queryProvider;
this.updateFactory = batchUpdateFactory;
this.qp = qp;
this.changeQuery = changeQuery;
this.sequences = sequences;
}
public Change.Id addCommitToChange(
final Project project,
final Repository repo,
final String destinationBranch,
final Account.Id pullRequestOwner,
final RevCommit pullRequestCommit,
final String pullRequestMessage,
final String topic)
throws NoSuchChangeException, IOException, InvalidChangeOperationException, UpdateException,
RestApiException {
try (BatchUpdate bu =
updateFactory.create(
project.getNameKey(), userFactory.create(pullRequestOwner), TimeUtil.nowTs())) {
return internalAddCommitToChange(
bu,
project,
repo,
destinationBranch,
pullRequestOwner,
pullRequestCommit,
pullRequestMessage,
topic);
}
}
public Change.Id internalAddCommitToChange(
BatchUpdate bu,
final Project project,
final Repository repo,
final String destinationBranch,
final Account.Id pullRequestOwner,
final RevCommit pullRequestCommit,
final String pullRequestMesage,
final String topic)
throws InvalidChangeOperationException, IOException, UpdateException, RestApiException {
if (destinationBranch == null || destinationBranch.length() == 0) {
throw new InvalidChangeOperationException("Destination branch cannot be null or empty");
}
Ref destRef = repo.findRef(destinationBranch);
if (destRef == null) {
throw new InvalidChangeOperationException("Branch " + destinationBranch + " does not exist.");
}
String pullRequestSha1 = pullRequestCommit.getId().getName();
List<ChangeData> existingChanges = queryChangesForSha1(pullRequestSha1);
if (!existingChanges.isEmpty()) {
LOG.debug(
"Pull request commit ID "
+ pullRequestSha1
+ " has been already uploaded as Change-Id="
+ existingChanges.get(0).getId());
return null;
}
Change.Key changeKey;
final List<String> idList = pullRequestCommit.getFooterLines(CHANGE_ID);
if (!idList.isEmpty()) {
final String idStr = idList.get(idList.size() - 1).trim();
changeKey = Change.key(idStr);
} else {
final ObjectId computedChangeId =
ChangeIdUtil.computeChangeId(
pullRequestCommit.getTree(),
pullRequestCommit,
pullRequestCommit.getAuthorIdent(),
pullRequestCommit.getCommitterIdent(),
pullRequestMesage);
changeKey = Change.key("I" + computedChangeId.name());
}
String branchName = destRef.getName();
List<ChangeData> destChanges =
queryProvider
.get()
.byBranchKey(
BranchNameKey.create(
project.getNameKey(),
branchName.startsWith(REFS_HEADS)
? branchName.substring(REFS_HEADS.length())
: branchName),
changeKey);
if (destChanges.size() > 1) {
throw new InvalidChangeOperationException(
"Multiple Changes with Change-ID "
+ changeKey
+ " already exist on the target branch: cannot add a new patch-set "
+ destinationBranch);
}
if (destChanges.size() == 1) {
// The change key exists on the destination branch: adding a new
// patch-set
ChangeData destChangeData = destChanges.get(0);
Change destChange = destChangeData.change();
insertPatchSet(
bu, repo, destChange, pullRequestCommit, destChangeData.notes(), pullRequestMesage);
return destChange.getId();
}
// Change key not found on destination branch. We can create a new
// change.
return createNewChange(
bu,
changeKey,
project.getNameKey(),
destRef,
pullRequestOwner,
pullRequestCommit,
destinationBranch,
pullRequestMesage,
topic);
}
private List<ChangeData> queryChangesForSha1(String pullRequestSha1) {
QueryResult<ChangeData> results;
try {
results = qp.get().query(changeQuery.commit(pullRequestSha1));
return results.entities();
} catch (QueryParseException e) {
LOG.error(
"Invalid SHA1 " + pullRequestSha1 + ": cannot query changes for this pull request", e);
return Collections.emptyList();
}
}
private void insertPatchSet(
BatchUpdate bu,
Repository git,
Change change,
RevCommit cherryPickCommit,
ChangeNotes changeNotes,
String pullRequestMessage)
throws IOException, UpdateException, RestApiException {
try (RevWalk revWalk = new RevWalk(git)) {
PatchSet.Id psId = ChangeUtil.nextPatchSetId(git, change.currentPatchSetId());
PatchSetInserter patchSetInserter =
patchSetInserterFactory.create(changeNotes, psId, cherryPickCommit);
patchSetInserter.setMessage(pullRequestMessage);
patchSetInserter.setValidate(false);
bu.addOp(change.getId(), patchSetInserter);
bu.execute();
}
}
private Change.Id createNewChange(
BatchUpdate bu,
Change.Key changeKey,
Project.NameKey project,
Ref destRef,
Account.Id pullRequestOwner,
RevCommit pullRequestCommit,
String refName,
String pullRequestMessage,
String topic)
throws UpdateException, RestApiException, IOException {
Change change =
new Change(
changeKey,
Change.id(sequences.nextChangeId()),
pullRequestOwner,
BranchNameKey.create(project, destRef.getName()),
TimeUtil.nowTs());
if (topic != null) {
change.setTopic(topic);
}
ChangeInserter ins = changeInserterFactory.create(change.getId(), pullRequestCommit, refName);
ins.setMessage(pullRequestMessage);
bu.insertChange(ins);
bu.execute();
return ins.getChange().getId();
}
}