Merge "Fix gr-registration-dialog test failure in Safari"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index d8214c2..236d30e 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -517,6 +517,61 @@
}
----
+[[create-merge-patch-set-for-change]]
+=== Create Merge Patch Set For Change
+--
+'POST /changes/link:#change-id[\{change-id\}]/merge'
+--
+
+Update an existing change by using a
+link:#merge-patch-set-input[MergePatchSetInput] entity.
+
+Gerrit will create a merge commit based on the information of
+MergePatchSetInput and add a new patch set to the change corresponding
+to the new merge commit.
+
+.Request
+----
+ POST /changes/test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc/merge HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "source": "refs/12/1234/1"
+ }
+----
+
+As response a link:#change-info[ChangeInfo] entity with current revision is
+returned that describes the resulting change.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "id": "test~master~Ic5466d107c5294414710935a8ef3b0180fb848dc",
+ "project": "test",
+ "branch": "master",
+ "hashtags": [],
+ "change_id": "Ic5466d107c5294414710935a8ef3b0180fb848dc",
+ "subject": "Merge dev_branch into master",
+ "status": "NEW",
+ "created": "2016-09-23 18:08:53.238000000",
+ "updated": "2016-09-23 18:09:25.934000000",
+ "submit_type": "MERGE_IF_NECESSARY",
+ "mergeable": true,
+ "insertions": 5,
+ "deletions": 0,
+ "_number": 72,
+ "owner": {
+ "_account_id": 1000000
+ },
+ "current_revision": "27cc4558b5a3d3387dd11ee2df7a117e7e581822"
+ }
+----
+
[[get-change-detail]]
=== Get Change Detail
--
@@ -5367,6 +5422,25 @@
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|============================
+[[merge-patch-set-input]]
+=== MergePatchSetInput
+The `MergePatchSetInput` entity contains information about updating a new
+change by creating a new merge commit.
+
+[options="header",cols="1,^1,5"]
+|==================================
+|Field Name ||Description
+|`subject` |optional|
+The new subject for the change, if not specified, will reuse the current patch
+set's subject
+|`inheritParent` |optional, default to `false`|
+Use the current patch set's first parent as the merge tip when set to `true`.
+Otherwise, use the current branch tip of the destination branch.
+|`merge` ||
+The detail of the source commit for merge as a link:#merge-input[MergeInput]
+entity.
+|==================================
+
[[move-input]]
=== MoveInput
The `MoveInput` entity contains information for moving a change to a new branch.
diff --git a/WORKSPACE b/WORKSPACE
index 4487821..e356c40 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -309,8 +309,8 @@
maven_jar(
name = 'commons_compress',
- artifact = 'org.apache.commons:commons-compress:1.7',
- sha1 = 'ab365c96ee9bc88adcc6fa40d185c8e15a31410d',
+ artifact = 'org.apache.commons:commons-compress:1.12',
+ sha1 = '84caa68576e345eb5e7ae61a0e5a9229eb100d7b',
)
maven_jar(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index dc829dc..7c6ce0e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -61,9 +61,11 @@
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
+import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.common.LabelInfo;
+import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -71,6 +73,7 @@
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -1921,6 +1924,86 @@
+ r1.getChange().getId().id + ".");
}
+ @Test
+ public void testCreateMergePatchSet() throws Exception {
+ PushOneCommit.Result start = pushTo("refs/heads/master");
+ start.assertOkStatus();
+ // create a change for master
+ PushOneCommit.Result r = createChange();
+ r.assertOkStatus();
+ String changeId = r.getChangeId();
+
+ testRepo.reset(start.getCommit());
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+ String parent = currentMaster.getCommit().getName();
+
+ // push a commit into dev branch
+ createBranch(new Branch.NameKey(project, "dev"));
+ PushOneCommit.Result changeA = pushFactory
+ .create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2";
+ gApi.changes().id(changeId).createMergePatchSet(in);
+ ChangeInfo changeInfo = gApi.changes().id(changeId)
+ .get(EnumSet.of(ListChangesOption.ALL_REVISIONS,
+ ListChangesOption.CURRENT_COMMIT,
+ ListChangesOption.CURRENT_REVISION));
+ assertThat(changeInfo.revisions.size()).isEqualTo(2);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(
+ changeInfo.revisions.get(changeInfo.currentRevision).commit.parents
+ .get(0).commit).isEqualTo(parent);
+ }
+
+ @Test
+ public void testCreateMergePatchSetInheritParent() throws Exception {
+ PushOneCommit.Result start = pushTo("refs/heads/master");
+ start.assertOkStatus();
+ // create a change for master
+ PushOneCommit.Result r = createChange();
+ r.assertOkStatus();
+ String changeId = r.getChangeId();
+ String parent = r.getCommit().getParent(0).getName();
+
+ // advance master branch
+ testRepo.reset(start.getCommit());
+ PushOneCommit.Result currentMaster = pushTo("refs/heads/master");
+ currentMaster.assertOkStatus();
+
+ // push a commit into dev branch
+ createBranch(new Branch.NameKey(project, "dev"));
+ PushOneCommit.Result changeA = pushFactory
+ .create(db, user.getIdent(), testRepo, "change A", "A.txt", "A content")
+ .to("refs/heads/dev");
+ changeA.assertOkStatus();
+ MergeInput mergeInput = new MergeInput();
+ mergeInput.source = "dev";
+ MergePatchSetInput in = new MergePatchSetInput();
+ in.merge = mergeInput;
+ in.subject = "update change by merge ps2 inherit parent of ps1";
+ in.inheritParent = true;
+ gApi.changes().id(changeId).createMergePatchSet(in);
+ ChangeInfo changeInfo = gApi.changes().id(changeId)
+ .get(EnumSet.of(ListChangesOption.ALL_REVISIONS,
+ ListChangesOption.CURRENT_COMMIT,
+ ListChangesOption.CURRENT_REVISION));
+
+ assertThat(changeInfo.revisions.size()).isEqualTo(2);
+ assertThat(changeInfo.subject).isEqualTo(in.subject);
+ assertThat(
+ changeInfo.revisions.get(changeInfo.currentRevision).commit.parents
+ .get(0).commit).isEqualTo(parent);
+ assertThat(
+ changeInfo.revisions.get(changeInfo.currentRevision).commit.parents
+ .get(0).commit).isNotEqualTo(currentMaster.getCommit().getName());
+ }
+
private static Iterable<Account.Id> getReviewers(
Collection<AccountInfo> r) {
return Iterables.transform(r, a -> new Account.Id(a._accountId));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 6e2e8b1..1e2fed5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -652,6 +652,58 @@
assertTwoChangesWithSameRevision(r);
}
+ @Test
+ public void pushSameCommitTwice() throws Exception {
+ ProjectConfig config = projectCache.checkedGet(project).getConfig();
+ config.getProject()
+ .setCreateNewChangeForAllNotInTarget(InheritableBoolean.TRUE);
+ saveProjectConfig(project, config);
+
+ PushOneCommit push =
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+ "a.txt", "content");
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+
+ push =
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent");
+ r = push.to("refs/for/master");
+ r.assertOkStatus();
+
+ assertPushRejected(pushHead(testRepo, "refs/for/master", false),
+ "refs/for/master", "commit(s) already exists (as current patchset)");
+ }
+
+ @Test
+ public void pushSameCommitTwiceWhenIndexFailed() throws Exception {
+ ProjectConfig config = projectCache.checkedGet(project).getConfig();
+ config.getProject()
+ .setCreateNewChangeForAllNotInTarget(InheritableBoolean.TRUE);
+ saveProjectConfig(project, config);
+
+ PushOneCommit push =
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+ "a.txt", "content");
+ PushOneCommit.Result r = push.to("refs/for/master");
+ r.assertOkStatus();
+
+ push =
+ pushFactory
+ .create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+ "b.txt", "anotherContent");
+ r = push.to("refs/for/master");
+ r.assertOkStatus();
+
+ indexer.delete(r.getChange().getId());
+
+ assertPushRejected(pushHead(testRepo, "refs/for/master", false),
+ "refs/for/master", "commit(s) already exists (as current patchset)");
+ }
+
private void assertTwoChangesWithSameRevision(PushOneCommit.Result result)
throws Exception {
List<ChangeInfo> changes = query(result.getCommit().name());
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index d9cd562..8e36a77 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -17,6 +17,7 @@
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
@@ -95,6 +96,9 @@
*/
ChangeApi revert(RevertInput in) throws RestApiException;
+ /** Create a merge patch set for the change. */
+ ChangeInfo createMergePatchSet(MergePatchSetInput in) throws RestApiException;
+
List<ChangeInfo> submittedTogether() throws RestApiException;
SubmittedTogetherInfo submittedTogether(
EnumSet<SubmittedTogetherOption> options) throws RestApiException;
@@ -412,5 +416,11 @@
EnumSet<SubmittedTogetherOption> b) throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public ChangeInfo createMergePatchSet(MergePatchSetInput in)
+ throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergePatchSetInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergePatchSetInput.java
new file mode 100644
index 0000000..263b6c4
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/MergePatchSetInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2016 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.google.gerrit.extensions.common;
+
+public class MergePatchSetInput {
+ public String subject;
+ public boolean inheritParent;
+ public MergeInput merge;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index f7cb8f4..7532a11 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -31,6 +31,7 @@
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
@@ -63,7 +64,9 @@
import com.google.gerrit.server.change.Revisions;
import com.google.gerrit.server.change.SubmittedTogether;
import com.google.gerrit.server.change.SuggestChangeReviewers;
+import com.google.gerrit.server.change.CreateMergePatchSet;
import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -91,6 +94,7 @@
private final Abandon abandon;
private final Revert revert;
private final Restore restore;
+ private final CreateMergePatchSet updateByMerge;
private final Provider<SubmittedTogether> submittedTogether;
private final PublishDraftPatchSet.CurrentRevision
publishDraftChange;
@@ -122,6 +126,7 @@
Abandon abandon,
Revert revert,
Restore restore,
+ CreateMergePatchSet updateByMerge,
Provider<SubmittedTogether> submittedTogether,
PublishDraftPatchSet.CurrentRevision publishDraftChange,
DeleteDraftChange deleteDraftChange,
@@ -151,6 +156,7 @@
this.suggestReviewers = suggestReviewers;
this.abandon = abandon;
this.restore = restore;
+ this.updateByMerge = updateByMerge;
this.submittedTogether = submittedTogether;
this.publishDraftChange = publishDraftChange;
this.deleteDraftChange = deleteDraftChange;
@@ -268,6 +274,17 @@
}
@Override
+ public ChangeInfo createMergePatchSet(MergePatchSetInput in)
+ throws RestApiException {
+ try {
+ return updateByMerge.apply(change, in).value();
+ } catch (IOException | UpdateException | InvalidChangeOperationException
+ | NoSuchChangeException | OrmException e) {
+ throw new RestApiException("Cannot update change by merge", e);
+ }
+ }
+
+ @Override
public List<ChangeInfo> submittedTogether() throws RestApiException {
SubmittedTogetherInfo info = submittedTogether(
EnumSet.noneOf(ListChangesOption.class),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateMergePatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateMergePatchSet.java
new file mode 100644
index 0000000..f117179
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateMergePatchSet.java
@@ -0,0 +1,212 @@
+// Copyright (C) 2016 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.google.gerrit.server.change;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.client.ListChangesOption;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.MergePatchSetInput;
+import com.google.gerrit.extensions.common.MergeInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MergeConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.ChangeIdUtil;
+
+import java.io.IOException;
+import java.sql.Timestamp;
+import java.util.EnumSet;
+import java.util.TimeZone;
+
+@Singleton
+public class CreateMergePatchSet implements
+ RestModifyView<ChangeResource, MergePatchSetInput> {
+
+ private final Provider<ReviewDb> db;
+ private final GitRepositoryManager gitManager;
+ private final TimeZone serverTimeZone;
+ private final Provider<CurrentUser> user;
+ private final ChangeJson.Factory jsonFactory;
+ private final PatchSetUtil psUtil;
+ private final MergeUtil.Factory mergeUtilFactory;
+ private final BatchUpdate.Factory batchUpdateFactory;
+ private final PatchSetInserter.Factory patchSetInserterFactory;
+
+ @Inject
+ CreateMergePatchSet(Provider<ReviewDb> db,
+ GitRepositoryManager gitManager,
+ @GerritPersonIdent PersonIdent myIdent,
+ Provider<CurrentUser> user,
+ ChangeJson.Factory json,
+ PatchSetUtil psUtil,
+ MergeUtil.Factory mergeUtilFactory,
+ BatchUpdate.Factory batchUpdateFactory,
+ PatchSetInserter.Factory patchSetInserterFactory) {
+ this.db = db;
+ this.gitManager = gitManager;
+ this.serverTimeZone = myIdent.getTimeZone();
+ this.user = user;
+ this.jsonFactory = json;
+ this.psUtil = psUtil;
+ this.mergeUtilFactory = mergeUtilFactory;
+ this.batchUpdateFactory = batchUpdateFactory;
+ this.patchSetInserterFactory = patchSetInserterFactory;
+ }
+
+ @Override
+ public Response<ChangeInfo> apply(ChangeResource req, MergePatchSetInput in)
+ throws NoSuchChangeException, OrmException, IOException,
+ InvalidChangeOperationException, RestApiException, UpdateException {
+ if (in.merge == null) {
+ throw new BadRequestException("merge field is required");
+ }
+
+ MergeInput merge = in.merge;
+ if (Strings.isNullOrEmpty(merge.source)) {
+ throw new BadRequestException("merge.source must be non-empty");
+ }
+
+ ChangeControl ctl = req.getControl();
+ if (!ctl.isVisible(db.get())) {
+ throw new InvalidChangeOperationException(
+ "Base change not found: " + req.getId());
+ }
+ PatchSet ps = psUtil.current(db.get(), ctl.getNotes());
+ if (!ctl.canAddPatchSet(db.get())) {
+ throw new AuthException("cannot add patch set");
+ }
+
+ ProjectControl projectControl = ctl.getProjectControl();
+ Change change = ctl.getChange();
+ Project.NameKey project = change.getProject();
+ Branch.NameKey dest = change.getDest();
+ try (Repository git = gitManager.openRepository(project);
+ ObjectInserter oi = git.newObjectInserter();
+ RevWalk rw = new RevWalk(oi.newReader())) {
+
+ RevCommit sourceCommit =
+ MergeUtil.resolveCommit(git, rw, merge.source);
+ if (!projectControl.canReadCommit(db.get(), git, sourceCommit)) {
+ throw new ResourceNotFoundException(
+ "cannot find source commit: " + merge.source + " to merge.");
+ }
+
+ RevCommit currentPsCommit =
+ rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
+
+ Timestamp now = TimeUtil.nowTs();
+ IdentifiedUser me = user.get().asIdentifiedUser();
+ PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
+
+ RevCommit newCommit =
+ createMergeCommit(in, projectControl, dest, git, oi, rw,
+ currentPsCommit, sourceCommit, author,
+ ObjectId.fromString(change.getKey().get().substring(1)));
+
+ PatchSet.Id nextPsId = ChangeUtil.nextPatchSetId(ps.getId());
+ PatchSetInserter psInserter =
+ patchSetInserterFactory.create(ctl, nextPsId, newCommit);
+ try (BatchUpdate bu = batchUpdateFactory
+ .create(db.get(), project, me, now)) {
+ bu.setRepository(git, rw, oi);
+ bu.addOp(ctl.getId(), psInserter
+ .setMessage("Uploaded patch set " + nextPsId.get() + ".")
+ .setDraft(ps.isDraft())
+ .setNotify(NotifyHandling.NONE));
+ bu.execute();
+ }
+
+ ChangeJson json =
+ jsonFactory.create(EnumSet.of(ListChangesOption.CURRENT_REVISION));
+ return Response.ok(json.format(psInserter.getChange()));
+ }
+ }
+
+ private RevCommit createMergeCommit(MergePatchSetInput in,
+ ProjectControl projectControl, Branch.NameKey dest, Repository git,
+ ObjectInserter oi, RevWalk rw, RevCommit currentPsCommit,
+ RevCommit sourceCommit, PersonIdent author, ObjectId changeId)
+ throws ResourceNotFoundException, MergeIdenticalTreeException,
+ MergeConflictException, IOException {
+
+ ObjectId parentCommit;
+ if (in.inheritParent) {
+ // inherit first parent from previous patch set
+ parentCommit = currentPsCommit.getParent(0);
+ } else {
+ // get the current branch tip of destination branch
+ Ref destRef = git.getRefDatabase().exactRef(dest.get());
+ if (destRef != null) {
+ parentCommit = destRef.getObjectId();
+ } else {
+ throw new ResourceNotFoundException("cannot find destination branch");
+ }
+ }
+ RevCommit mergeTip = rw.parseCommit(parentCommit);
+
+ String commitMsg;
+ if (Strings.emptyToNull(in.subject) != null) {
+ commitMsg = ChangeIdUtil.insertId(in.subject, changeId);
+ } else {
+ // reuse previous patch set commit message
+ commitMsg = currentPsCommit.getFullMessage();
+ }
+
+ String mergeStrategy = MoreObjects.firstNonNull(
+ Strings.emptyToNull(in.merge.strategy),
+ mergeUtilFactory.create(projectControl.getProjectState())
+ .mergeStrategyName());
+
+ return MergeUtil.createMergeCommit(git, oi, mergeTip, sourceCommit,
+ mergeStrategy, author, commitMsg, rw);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index a52920a..bb76084 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -53,6 +53,7 @@
DynamicMap.mapOf(binder(), VOTE_KIND);
get(CHANGE_KIND).to(GetChange.class);
+ post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class);
get(CHANGE_KIND, "detail").to(GetDetail.class);
get(CHANGE_KIND, "topic").to(GetTopic.class);
get(CHANGE_KIND, "in").to(IncludedIn.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index 49221c9..7457da5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -664,11 +664,9 @@
op.updateRepo(ctx);
}
- if (!repoOnlyOps.isEmpty()) {
- logDebug("Executing updateRepo on {} RepoOnlyOps", ops.size());
- for (RepoOnlyOp op : repoOnlyOps) {
- op.updateRepo(ctx);
- }
+ logDebug("Executing updateRepo on {} RepoOnlyOps", repoOnlyOps.size());
+ for (RepoOnlyOp op : repoOnlyOps) {
+ op.updateRepo(ctx);
}
if (inserter != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index a23b78c..74362b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -101,6 +101,7 @@
import com.google.gerrit.server.git.validators.RefOperationValidationException;
import com.google.gerrit.server.git.validators.RefOperationValidators;
import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration;
@@ -333,6 +334,7 @@
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final NotesMigration notesMigration;
private final ChangeEditUtil editUtil;
+ private final ChangeIndexer indexer;
private final List<ValidationMessage> messages = new ArrayList<>();
private ListMultimap<Error, String> errors = LinkedListMultimap.create();
@@ -377,6 +379,7 @@
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
NotesMigration notesMigration,
ChangeEditUtil editUtil,
+ ChangeIndexer indexer,
BatchUpdate.Factory batchUpdateFactory,
SetHashtagsOp.Factory hashtagsFactory,
ReplaceOp.Factory replaceOpFactory,
@@ -424,6 +427,7 @@
this.notesMigration = notesMigration;
this.editUtil = editUtil;
+ this.indexer = indexer;
this.messageSender = new ReceivePackMessageSender();
@@ -1826,6 +1830,18 @@
return;
}
+ // In case the change look up from the index failed,
+ // double check against the existing refs
+ if (foundInExistingRef(existing.get(p.commit))) {
+ if (pending.size() == 1) {
+ reject(magicBranch.cmd,
+ "commit(s) already exists (as current patchset)");
+ newChanges = Collections.emptyList();
+ return;
+ }
+ itr.remove();
+ continue;
+ }
newChangeIds.add(p.changeKey);
}
newChanges.add(new CreateRequest(p.commit, magicBranch.dest.get()));
@@ -1879,6 +1895,22 @@
}
}
+ private boolean foundInExistingRef(Collection<Ref> existingRefs)
+ throws OrmException {
+ for (Ref ref : existingRefs) {
+ ChangeNotes notes = notesFactory.create(db, project.getNameKey(),
+ Change.Id.fromRef(ref.getName()));
+ Change change = notes.getChange();
+ if (change.getDest().equals(magicBranch.dest)) {
+ logDebug("Found change {} from existing refs.", change.getKey());
+ // reindex the change asynchronously
+ indexer.indexAsync(project.getNameKey(), change.getId());
+ return true;
+ }
+ }
+ return false;
+ }
+
private RevCommit setUpWalkForSelectingChanges() throws IOException {
RevWalk rw = rp.getRevWalk();
RevCommit start = rw.parseCommit(magicBranch.cmd.getNewId());
@@ -2400,10 +2432,12 @@
rw.parseBody(newCommit);
RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
- replaceOp = replaceOpFactory.create(requestScopePropagator,
- projectControl, notes.getChange().getDest(), checkMergedInto,
- priorPatchSet, priorCommit, psId, newCommit, info, groups,
- magicBranch, rp.getPushCertificate());
+ replaceOp = replaceOpFactory
+ .create(projectControl, notes.getChange().getDest(), checkMergedInto,
+ priorPatchSet, priorCommit, psId, newCommit, info, groups,
+ magicBranch, rp.getPushCertificate())
+ .setRequestScopePropagator(requestScopePropagator)
+ .setUpdateRef(false);
bu.addOp(notes.getChangeId(), replaceOp);
if (progress != null) {
bu.addOp(notes.getChangeId(), new ChangeProgressOp(progress));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index a9dcd4b..aece8a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -64,6 +64,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushCertificate;
+import org.eclipse.jgit.transport.ReceiveCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,7 +79,6 @@
public class ReplaceOp extends BatchUpdate.Op {
public interface Factory {
ReplaceOp create(
- RequestScopePropagator requestScopePropagator,
ProjectControl projectControl,
Branch.NameKey dest,
boolean checkMergedInto,
@@ -112,7 +112,6 @@
private final PatchSetUtil psUtil;
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
- private final RequestScopePropagator requestScopePropagator;
private final ProjectControl projectControl;
private final Branch.NameKey dest;
private final boolean checkMergedInto;
@@ -133,6 +132,8 @@
private ChangeMessage msg;
private String rejectMessage;
private MergedByPushOp mergedByPushOp;
+ private RequestScopePropagator requestScopePropagator;
+ private boolean updateRef;
@AssistedInject
ReplaceOp(AccountResolver accountResolver,
@@ -149,7 +150,6 @@
PatchSetUtil psUtil,
ReplacePatchSetSender.Factory replacePatchSetFactory,
@SendEmailExecutor ExecutorService sendEmailExecutor,
- @Assisted RequestScopePropagator requestScopePropagator,
@Assisted ProjectControl projectControl,
@Assisted Branch.NameKey dest,
@Assisted boolean checkMergedInto,
@@ -176,7 +176,6 @@
this.replacePatchSetFactory = replacePatchSetFactory;
this.sendEmailExecutor = sendEmailExecutor;
- this.requestScopePropagator = requestScopePropagator;
this.projectControl = projectControl;
this.dest = dest;
this.checkMergedInto = checkMergedInto;
@@ -188,6 +187,7 @@
this.groups = groups;
this.magicBranch = magicBranch;
this.pushCertificate = pushCertificate;
+ this.updateRef = true;
}
@Override
@@ -203,6 +203,12 @@
requestScopePropagator, patchSetId, mergedInto.getName());
}
}
+
+ if (updateRef) {
+ ctx.addRefUpdate(
+ new ReceiveCommand(ObjectId.zeroId(), commit,
+ patchSetId.toRefName()));
+ }
}
@Override
@@ -366,8 +372,10 @@
// BatchUpdate's perspective there is no ref update. Thus we have to fire it
// manually.
final Account account = ctx.getAccount();
- gitRefUpdated.fire(ctx.getProject(), newPatchSet.getRefName(),
- ObjectId.zeroId(), commit, account);
+ if (!updateRef) {
+ gitRefUpdated.fire(ctx.getProject(), newPatchSet.getRefName(),
+ ObjectId.zeroId(), commit, account);
+ }
if (changeKind != ChangeKind.TRIVIAL_REBASE) {
Runnable sender = new Runnable() {
@@ -454,10 +462,25 @@
return newPatchSet;
}
+ public Change getChange() {
+ return change;
+ }
+
public String getRejectMessage() {
return rejectMessage;
}
+ public ReplaceOp setUpdateRef(boolean updateRef) {
+ this.updateRef = updateRef;
+ return this;
+ }
+
+ public ReplaceOp setRequestScopePropagator(
+ RequestScopePropagator requestScopePropagator) {
+ this.requestScopePropagator = requestScopePropagator;
+ return this;
+ }
+
private Ref findMergedInto(Context ctx, String first, RevCommit commit) {
try {
RefDatabase refDatabase = ctx.getRepository().getRefDatabase();
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 55c07a6..5c2e9b2 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -19,8 +19,8 @@
maven_jar(
name = 'compress',
- id = 'org.apache.commons:commons-compress:1.7',
- sha1 = 'ab365c96ee9bc88adcc6fa40d185c8e15a31410d',
+ id = 'org.apache.commons:commons-compress:1.12',
+ sha1 = '84caa68576e345eb5e7ae61a0e5a9229eb100d7b',
license = 'Apache2.0',
exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
)
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
index 3702c84..c910d8f 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
@@ -22,6 +22,12 @@
properties: {
hasTooltip: Boolean,
+ _isTouchDevice: {
+ type: Boolean,
+ value: function() {
+ return 'ontouchstart' in document.documentElement;
+ },
+ },
_tooltip: Element,
_titleText: String,
},
@@ -29,10 +35,10 @@
attached: function() {
if (!this.hasTooltip) { return; }
- this.addEventListener('mouseover', this._handleShowTooltip.bind(this));
- this.addEventListener('mouseout', this._handleHideTooltip.bind(this));
- this.addEventListener('focusin', this._handleShowTooltip.bind(this));
- this.addEventListener('focusout', this._handleHideTooltip.bind(this));
+ this.addEventListener('mouseenter', this._handleShowTooltip.bind(this));
+ this.addEventListener('mouseleave', this._handleHideTooltip.bind(this));
+ this.addEventListener('tap', this._handleHideTooltip.bind(this));
+
this.listen(window, 'scroll', '_handleWindowScroll');
},
@@ -41,6 +47,8 @@
},
_handleShowTooltip: function(e) {
+ if (this._isTouchDevice) { return; }
+
if (!this.hasAttribute('title') ||
this.getAttribute('title') === '' ||
this._tooltip) {
@@ -66,9 +74,11 @@
},
_handleHideTooltip: function(e) {
+ if (this._isTouchDevice) { return; }
if (!this.hasAttribute('title') ||
- this._titleText == null ||
- this === document.activeElement) { return; }
+ this._titleText == null) {
+ return;
+ }
this.setAttribute('title', this._titleText);
if (this._tooltip && this._tooltip.parentNode) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index f15b554..6e2c058 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -430,5 +430,45 @@
MockInteractions.tap(element.$$('.send'));
});
});
+
+ test('don"t display tooltips on touch devices', function() {
+ element.labels = {
+ Verified: {
+ values: {
+ '-1': 'Fails',
+ ' 0': 'No score',
+ '+1': 'Verified'
+ },
+ default_value: 0
+ },
+ 'Code-Review': {
+ values: {
+ '-2': 'Do not submit',
+ '-1': 'I would prefer that you didn\'t submit this',
+ ' 0': 'No score',
+ '+1': 'Looks good to me, but someone else must approve',
+ '+2': 'Looks good to me, approved'
+ },
+ default_value: 0
+ }
+ };
+ var verifiedBtn = element.$$(
+ 'iron-selector[data-label="Verified"] > ' +
+ 'gr-button[data-value="-1"]');
+
+ // On touch devices, tooltips should not be shown
+ verifiedBtn._isTouchDevice = true;
+ verifiedBtn._handleShowTooltip();
+ assert.isNotOk(verifiedBtn._tooltip);
+ verifiedBtn._handleHideTooltip();
+ assert.isNotOk(verifiedBtn._tooltip);
+
+ // On other devices, tooltips should be shown.
+ verifiedBtn._isTouchDevice = false;
+ verifiedBtn._handleShowTooltip();
+ assert.isOk(verifiedBtn._tooltip);
+ verifiedBtn._handleHideTooltip();
+ assert.isNotOk(verifiedBtn._tooltip);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 685f568..431c795 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -266,11 +266,14 @@
assert.isTrue(commitHandler.called);
});
- test('_focused flag properly triggered', function() {
- flushAsynchronousOperations();
- assert.isFalse(element._focused);
- element.$.input.focus();
- assert.isTrue(element._focused);
+ test('_focused flag properly triggered', function(done) {
+ flush(function() {
+ assert.isFalse(element._focused);
+ var input = element.$$('input');
+ MockInteractions.focus(input);
+ assert.isTrue(element._focused);
+ done();
+ });
});
test('_focused flag shows/hides the suggestions', function() {