Merge "Allow creating merge commits with conflicts"
This commit is contained in:
@@ -5502,8 +5502,8 @@ does not specify a Change-Id, a new one is picked for the destination change.
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
As response a link:#cherry-pick-change-info[CherryPickChangeInfo]
|
As response a link:#change-info[ChangeInfo] entity is returned that
|
||||||
entity is returned that describes the resulting cherry-pick change.
|
describes the resulting cherry-pick change.
|
||||||
|
|
||||||
.Response
|
.Response
|
||||||
----
|
----
|
||||||
@@ -6042,10 +6042,21 @@ This ID is equal to the numeric ID of the change that triggered the submission.
|
|||||||
If the change that triggered the submission also has a topic, it will be
|
If the change that triggered the submission also has a topic, it will be
|
||||||
"<id>-<topic>" of the change that triggered the submission.
|
"<id>-<topic>" of the change that triggered the submission.
|
||||||
The callers must not rely on the format of the submission ID.
|
The callers must not rely on the format of the submission ID.
|
||||||
|`cherry_pick_of_change` |optional|
|
|`cherry_pick_of_change` |optional|
|
||||||
The numeric Change-Id of the change that this change was cherry-picked from.
|
The numeric Change-Id of the change that this change was cherry-picked from.
|
||||||
|`cherry_pick_of_patch_set` |optional|
|
|`cherry_pick_of_patch_set`|optional|
|
||||||
The patchset number of the change that this change was cherry-picked from.
|
The patchset number of the change that this change was cherry-picked from.
|
||||||
|
|`contains_git_conflicts` |optional, not set if `false`|
|
||||||
|
Whether the change contains conflicts. +
|
||||||
|
If `true`, some of the file contents of the change contain git conflict
|
||||||
|
markers to indicate the conflicts. +
|
||||||
|
Only set if this change info is returned in response to a request that
|
||||||
|
creates a new change or patch set and conflicts are allowed. In
|
||||||
|
particular this field is only populated if the change info is returned
|
||||||
|
by one of the following REST endpoints: link:#create-change[Create
|
||||||
|
Change], link:#create-merge-patch-set-for-change[Create Merge Patch Set
|
||||||
|
For Change], link:#cherry-pick[Cherry Pick Revision],
|
||||||
|
link:rest-api-project.html#cherry-pick-commit[Cherry Pick Commit]
|
||||||
|==================================
|
|==================================
|
||||||
|
|
||||||
[[change-input]]
|
[[change-input]]
|
||||||
@@ -6124,23 +6135,6 @@ invocations of the REST call are required.
|
|||||||
Which patchset (if any) generated this message.
|
Which patchset (if any) generated this message.
|
||||||
|==================================
|
|==================================
|
||||||
|
|
||||||
[[cherry-pick-change-info]]
|
|
||||||
=== CherryPickChangeInfo
|
|
||||||
The `CherryPickChangeInfo` entity contains information about a
|
|
||||||
cherry-pick change.
|
|
||||||
|
|
||||||
`CherryPickChangeInfo` has the same fields as link:#change-info[
|
|
||||||
ChangeInfo]. In addition `CherryPickChangeInfo` has the following
|
|
||||||
fields:
|
|
||||||
|
|
||||||
[options="header",cols="1,^1,5"]
|
|
||||||
|======================================
|
|
||||||
|Field Name ||Description
|
|
||||||
|`contains_git_conflicts` |optional, not set if `false`|
|
|
||||||
Whether any file in the change contains Git conflict markers.
|
|
||||||
|======================================
|
|
||||||
|
|
||||||
|
|
||||||
[[cherrypick-input]]
|
[[cherrypick-input]]
|
||||||
=== CherryPickInput
|
=== CherryPickInput
|
||||||
The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
|
The `CherryPickInput` entity contains information for cherry-picking a change to a new branch.
|
||||||
@@ -6172,9 +6166,8 @@ If `true`, the cherry-pick uses content merge and succeeds also if
|
|||||||
there are conflicts. If there are conflicts the file contents of the
|
there are conflicts. If there are conflicts the file contents of the
|
||||||
created change contain git conflict markers to indicate the conflicts.
|
created change contain git conflict markers to indicate the conflicts.
|
||||||
Callers can find out if there were conflicts by checking the
|
Callers can find out if there were conflicts by checking the
|
||||||
`contains_git_conflicts` field in the link:#cherry-pick-change-info[
|
`contains_git_conflicts` field in the link:#change-info[ChangeInfo]. If
|
||||||
CherryPickChangeInfo] that is returned by the cherry-pick REST
|
there are conflicts the cherry-pick change is marked as
|
||||||
endpoints. If there are conflicts the cherry-pick change is marked as
|
|
||||||
work-in-progress.
|
work-in-progress.
|
||||||
|===========================
|
|===========================
|
||||||
|
|
||||||
@@ -6804,21 +6797,31 @@ A list of other branch names where this change could merge cleanly
|
|||||||
The `MergeInput` entity contains information about the merge
|
The `MergeInput` entity contains information about the merge
|
||||||
|
|
||||||
[options="header",cols="1,^1,5"]
|
[options="header",cols="1,^1,5"]
|
||||||
|============================
|
|==============================
|
||||||
|Field Name ||Description
|
|Field Name ||Description
|
||||||
|`source` ||
|
|`source` ||
|
||||||
The source to merge from, e.g. a complete or abbreviated commit SHA-1,
|
The source to merge from, e.g. a complete or abbreviated commit SHA-1,
|
||||||
a complete reference name, a short reference name under `refs/heads`, `refs/tags`,
|
a complete reference name, a short reference name under `refs/heads`, `refs/tags`,
|
||||||
or `refs/remotes` namespace, etc.
|
or `refs/remotes` namespace, etc.
|
||||||
|`source_branch` |optional|
|
|`source_branch` |optional|
|
||||||
A branch from which `source` is reachable. If specified,
|
A branch from which `source` is reachable. If specified,
|
||||||
`source` is checked for visibility and reachability against only this
|
`source` is checked for visibility and reachability against only this
|
||||||
branch. This speeds up the operation, especially for large repos with
|
branch. This speeds up the operation, especially for large repos with
|
||||||
many branches.
|
many branches.
|
||||||
|`strategy` |optional|
|
|`strategy` |optional|
|
||||||
The strategy of the merge, can be `recursive`, `resolve`,
|
The strategy of the merge, can be `recursive`, `resolve`,
|
||||||
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|
`simple-two-way-in-core`, `ours` or `theirs`, default will use project settings.
|
||||||
|============================
|
|`allow_conflicts`|optional, defaults to false|
|
||||||
|
If `true`, creating the merge succeeds also if there are conflicts. +
|
||||||
|
If there are conflicts the file contents of the created change contain
|
||||||
|
git conflict markers to indicate the conflicts. +
|
||||||
|
Callers can find out whether there were conflicts by checking the
|
||||||
|
`contains_git_conflicts` field in the link:#change-info[ChangeInfo]. +
|
||||||
|
If there are conflicts the change is marked as work-in-progress. +
|
||||||
|
This option is not supported for all merge strategies (e.g. it's
|
||||||
|
supported for `recursive` and `resolve`, but not for
|
||||||
|
`simple-two-way-in-core`).
|
||||||
|
|==============================
|
||||||
|
|
||||||
[[merge-patch-set-input]]
|
[[merge-patch-set-input]]
|
||||||
=== MergePatchSetInput
|
=== MergePatchSetInput
|
||||||
|
|||||||
@@ -2576,9 +2576,8 @@ commit will be used.
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
As response a link:rest-api-changes.html#cherry-pick-change-info[
|
As response a link:rest-api-changes.html#change-info[ChangeInfo] entity
|
||||||
CherryPickChangeInfo] entity is returned that describes the resulting
|
is returned that describes the resulting cherry-picked change.
|
||||||
cherry-picked change.
|
|
||||||
|
|
||||||
.Response
|
.Response
|
||||||
----
|
----
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
|
|||||||
import com.google.gerrit.extensions.events.GroupIndexedListener;
|
import com.google.gerrit.extensions.events.GroupIndexedListener;
|
||||||
import com.google.gerrit.extensions.events.ProjectIndexedListener;
|
import com.google.gerrit.extensions.events.ProjectIndexedListener;
|
||||||
import com.google.gerrit.extensions.events.RevisionCreatedListener;
|
import com.google.gerrit.extensions.events.RevisionCreatedListener;
|
||||||
|
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
|
||||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||||
import com.google.gerrit.extensions.registration.DynamicSet;
|
import com.google.gerrit.extensions.registration.DynamicSet;
|
||||||
import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
|
import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl;
|
||||||
@@ -69,6 +70,7 @@ public class ExtensionRegistry {
|
|||||||
private final DynamicSet<AccountActivationValidationListener>
|
private final DynamicSet<AccountActivationValidationListener>
|
||||||
accountActivationValidationListeners;
|
accountActivationValidationListeners;
|
||||||
private final DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners;
|
private final DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners;
|
||||||
|
private final DynamicSet<WorkInProgressStateChangedListener> workInProgressStateChangedListeners;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ExtensionRegistry(
|
ExtensionRegistry(
|
||||||
@@ -93,7 +95,8 @@ public class ExtensionRegistry {
|
|||||||
DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
|
DynamicSet<RevisionCreatedListener> revisionCreatedListeners,
|
||||||
DynamicSet<GroupBackend> groupBackends,
|
DynamicSet<GroupBackend> groupBackends,
|
||||||
DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners,
|
DynamicSet<AccountActivationValidationListener> accountActivationValidationListeners,
|
||||||
DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners) {
|
DynamicSet<OnSubmitValidationListener> onSubmitValidationListeners,
|
||||||
|
DynamicSet<WorkInProgressStateChangedListener> workInProgressStateChangedListeners) {
|
||||||
this.accountIndexedListeners = accountIndexedListeners;
|
this.accountIndexedListeners = accountIndexedListeners;
|
||||||
this.changeIndexedListeners = changeIndexedListeners;
|
this.changeIndexedListeners = changeIndexedListeners;
|
||||||
this.groupIndexedListeners = groupIndexedListeners;
|
this.groupIndexedListeners = groupIndexedListeners;
|
||||||
@@ -116,6 +119,7 @@ public class ExtensionRegistry {
|
|||||||
this.groupBackends = groupBackends;
|
this.groupBackends = groupBackends;
|
||||||
this.accountActivationValidationListeners = accountActivationValidationListeners;
|
this.accountActivationValidationListeners = accountActivationValidationListeners;
|
||||||
this.onSubmitValidationListeners = onSubmitValidationListeners;
|
this.onSubmitValidationListeners = onSubmitValidationListeners;
|
||||||
|
this.workInProgressStateChangedListeners = workInProgressStateChangedListeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Registration newRegistration() {
|
public Registration newRegistration() {
|
||||||
@@ -219,6 +223,10 @@ public class ExtensionRegistry {
|
|||||||
return add(onSubmitValidationListeners, onSubmitValidationListener);
|
return add(onSubmitValidationListeners, onSubmitValidationListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Registration add(WorkInProgressStateChangedListener workInProgressStateChangedListener) {
|
||||||
|
return add(workInProgressStateChangedListeners, workInProgressStateChangedListener);
|
||||||
|
}
|
||||||
|
|
||||||
private <T> Registration add(DynamicSet<T> dynamicSet, T extension) {
|
private <T> Registration add(DynamicSet<T> dynamicSet, T extension) {
|
||||||
return add(dynamicSet, extension, "gerrit");
|
return add(dynamicSet, extension, "gerrit");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@ java_library(
|
|||||||
name = "exceptions",
|
name = "exceptions",
|
||||||
srcs = glob(["*.java"]),
|
srcs = glob(["*.java"]),
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = ["//java/com/google/gerrit/entities"],
|
deps = [
|
||||||
|
"//java/com/google/gerrit/entities",
|
||||||
|
"//lib:jgit",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (C) 2020 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.exceptions;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.merge.MergeStrategy;
|
||||||
|
|
||||||
|
public class MergeWithConflictsNotSupportedException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MergeWithConflictsNotSupportedException(MergeStrategy strategy) {
|
||||||
|
super("merge with conflicts is not supported with merge strategy: " + strategy.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,8 @@ public interface Changes {
|
|||||||
|
|
||||||
ChangeApi create(ChangeInput in) throws RestApiException;
|
ChangeApi create(ChangeInput in) throws RestApiException;
|
||||||
|
|
||||||
|
ChangeInfo createAsInfo(ChangeInput in) throws RestApiException;
|
||||||
|
|
||||||
QueryRequest query();
|
QueryRequest query();
|
||||||
|
|
||||||
QueryRequest query(String query);
|
QueryRequest query(String query);
|
||||||
@@ -207,6 +209,11 @@ public interface Changes {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeInfo createAsInfo(ChangeInput in) throws RestApiException {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryRequest query() {
|
public QueryRequest query() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|||||||
@@ -56,6 +56,22 @@ public class ChangeInfo {
|
|||||||
public Integer cherryPickOfChange;
|
public Integer cherryPickOfChange;
|
||||||
public Integer cherryPickOfPatchSet;
|
public Integer cherryPickOfPatchSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the change contains conflicts.
|
||||||
|
*
|
||||||
|
* <p>If {@code true}, some of the file contents of the change contain git conflict markers to
|
||||||
|
* indicate the conflicts.
|
||||||
|
*
|
||||||
|
* <p>Only set if this change info is returned in response to a request that creates a new change
|
||||||
|
* or patch set and conflicts are allowed. In particular this field is only populated if the
|
||||||
|
* change info is returned by one of the following REST endpoints: {@link
|
||||||
|
* com.google.gerrit.server.restapi.change.CreateChange}, {@link
|
||||||
|
* com.google.gerrit.server.restapi.change.CreateMergePatchSet}, {@link
|
||||||
|
* com.google.gerrit.server.restapi.change.CherryPick}, {@link
|
||||||
|
* com.google.gerrit.server.restapi.change.CherryPickCommit}
|
||||||
|
*/
|
||||||
|
public Boolean containsGitConflicts;
|
||||||
|
|
||||||
public int _number;
|
public int _number;
|
||||||
|
|
||||||
public AccountInfo owner;
|
public AccountInfo owner;
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
|
|
||||||
package com.google.gerrit.extensions.common;
|
package com.google.gerrit.extensions.common;
|
||||||
|
|
||||||
public class CherryPickChangeInfo extends ChangeInfo {
|
/**
|
||||||
public Boolean containsGitConflicts;
|
* {@link ChangeInfo} that is returned when a change was cherry-picked.
|
||||||
}
|
*
|
||||||
|
* <p>This class used to define additional fields to {@link ChangeInfo}, but those were pulled up
|
||||||
|
* into {@link ChangeInfo}. Now this class is no longer needed, but we need to keep it for backwards
|
||||||
|
* compatibility of the Java API.
|
||||||
|
*/
|
||||||
|
public class CherryPickChangeInfo extends ChangeInfo {}
|
||||||
|
|||||||
@@ -35,4 +35,12 @@ public class MergeInput {
|
|||||||
* @see org.eclipse.jgit.merge.MergeStrategy
|
* @see org.eclipse.jgit.merge.MergeStrategy
|
||||||
*/
|
*/
|
||||||
public String strategy;
|
public String strategy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the creation of the merge should succeed if there are conflicts.
|
||||||
|
*
|
||||||
|
* <p>If there are conflicts the file contents of the created change contain git conflict markers
|
||||||
|
* to indicate the conflicts.
|
||||||
|
*/
|
||||||
|
public boolean allowConflicts;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import com.google.gerrit.server.config.SysExecutorModule;
|
|||||||
import com.google.gerrit.server.extensions.events.EventUtil;
|
import com.google.gerrit.server.extensions.events.EventUtil;
|
||||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
|
||||||
import com.google.gerrit.server.extensions.events.RevisionCreated;
|
import com.google.gerrit.server.extensions.events.RevisionCreated;
|
||||||
|
import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
|
||||||
import com.google.gerrit.server.git.MergeUtil;
|
import com.google.gerrit.server.git.MergeUtil;
|
||||||
import com.google.gerrit.server.git.PureRevertCache;
|
import com.google.gerrit.server.git.PureRevertCache;
|
||||||
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
|
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
|
||||||
@@ -177,6 +178,7 @@ public class BatchProgramModule extends FactoryModule {
|
|||||||
bind(EventUtil.class).toProvider(Providers.of(null));
|
bind(EventUtil.class).toProvider(Providers.of(null));
|
||||||
bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
|
bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
|
||||||
bind(RevisionCreated.class).toInstance(RevisionCreated.DISABLED);
|
bind(RevisionCreated.class).toInstance(RevisionCreated.DISABLED);
|
||||||
|
bind(WorkInProgressStateChanged.class).toInstance(WorkInProgressStateChanged.DISABLED);
|
||||||
bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
|
bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,15 @@ class ChangesImpl implements Changes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChangeInfo createAsInfo(ChangeInput in) throws RestApiException {
|
||||||
|
try {
|
||||||
|
return createChange.apply(TopLevelResource.INSTANCE, in).value();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw asRestApiException("Cannot create change", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryRequest query() {
|
public QueryRequest query() {
|
||||||
return new QueryRequest() {
|
return new QueryRequest() {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.google.gerrit.server.PatchSetUtil;
|
|||||||
import com.google.gerrit.server.ReviewerSet;
|
import com.google.gerrit.server.ReviewerSet;
|
||||||
import com.google.gerrit.server.events.CommitReceivedEvent;
|
import com.google.gerrit.server.events.CommitReceivedEvent;
|
||||||
import com.google.gerrit.server.extensions.events.RevisionCreated;
|
import com.google.gerrit.server.extensions.events.RevisionCreated;
|
||||||
|
import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
|
||||||
import com.google.gerrit.server.git.validators.CommitValidationException;
|
import com.google.gerrit.server.git.validators.CommitValidationException;
|
||||||
import com.google.gerrit.server.git.validators.CommitValidators;
|
import com.google.gerrit.server.git.validators.CommitValidators;
|
||||||
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
|
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
|
||||||
@@ -74,6 +75,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
private final ApprovalsUtil approvalsUtil;
|
private final ApprovalsUtil approvalsUtil;
|
||||||
private final ChangeMessagesUtil cmUtil;
|
private final ChangeMessagesUtil cmUtil;
|
||||||
private final PatchSetUtil psUtil;
|
private final PatchSetUtil psUtil;
|
||||||
|
private final WorkInProgressStateChanged wipStateChanged;
|
||||||
|
|
||||||
// Assisted-injected fields.
|
// Assisted-injected fields.
|
||||||
private final PatchSet.Id psId;
|
private final PatchSet.Id psId;
|
||||||
@@ -86,6 +88,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
// Fields exposed as setters.
|
// Fields exposed as setters.
|
||||||
private String message;
|
private String message;
|
||||||
private String description;
|
private String description;
|
||||||
|
private Boolean workInProgress;
|
||||||
private boolean validate = true;
|
private boolean validate = true;
|
||||||
private boolean checkAddPatchSetPermission = true;
|
private boolean checkAddPatchSetPermission = true;
|
||||||
private List<String> groups = Collections.emptyList();
|
private List<String> groups = Collections.emptyList();
|
||||||
@@ -99,6 +102,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
private PatchSetInfo patchSetInfo;
|
private PatchSetInfo patchSetInfo;
|
||||||
private ChangeMessage changeMessage;
|
private ChangeMessage changeMessage;
|
||||||
private ReviewerSet oldReviewers;
|
private ReviewerSet oldReviewers;
|
||||||
|
private boolean oldWorkInProgressState;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PatchSetInserter(
|
public PatchSetInserter(
|
||||||
@@ -111,6 +115,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
PatchSetUtil psUtil,
|
PatchSetUtil psUtil,
|
||||||
RevisionCreated revisionCreated,
|
RevisionCreated revisionCreated,
|
||||||
ProjectCache projectCache,
|
ProjectCache projectCache,
|
||||||
|
WorkInProgressStateChanged wipStateChanged,
|
||||||
@Assisted ChangeNotes notes,
|
@Assisted ChangeNotes notes,
|
||||||
@Assisted PatchSet.Id psId,
|
@Assisted PatchSet.Id psId,
|
||||||
@Assisted ObjectId commitId) {
|
@Assisted ObjectId commitId) {
|
||||||
@@ -123,6 +128,7 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
this.psUtil = psUtil;
|
this.psUtil = psUtil;
|
||||||
this.revisionCreated = revisionCreated;
|
this.revisionCreated = revisionCreated;
|
||||||
this.projectCache = projectCache;
|
this.projectCache = projectCache;
|
||||||
|
this.wipStateChanged = wipStateChanged;
|
||||||
|
|
||||||
this.origNotes = notes;
|
this.origNotes = notes;
|
||||||
this.psId = psId;
|
this.psId = psId;
|
||||||
@@ -143,6 +149,11 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PatchSetInserter setWorkInProgress(boolean workInProgress) {
|
||||||
|
this.workInProgress = workInProgress;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PatchSetInserter setValidate(boolean validate) {
|
public PatchSetInserter setValidate(boolean validate) {
|
||||||
this.validate = validate;
|
this.validate = validate;
|
||||||
return this;
|
return this;
|
||||||
@@ -230,6 +241,13 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
changeMessage.setMessage(message);
|
changeMessage.setMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldWorkInProgressState = change.isWorkInProgress();
|
||||||
|
if (workInProgress != null) {
|
||||||
|
change.setWorkInProgress(workInProgress);
|
||||||
|
change.setReviewStarted(!workInProgress);
|
||||||
|
update.setWorkInProgress(workInProgress);
|
||||||
|
}
|
||||||
|
|
||||||
patchSetInfo =
|
patchSetInfo =
|
||||||
patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
|
patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
|
||||||
if (!allowClosed) {
|
if (!allowClosed) {
|
||||||
@@ -265,6 +283,10 @@ public class PatchSetInserter implements BatchUpdateOp {
|
|||||||
if (fireRevisionCreated) {
|
if (fireRevisionCreated) {
|
||||||
revisionCreated.fire(change, patchSet, ctx.getAccount(), ctx.getWhen(), notify);
|
revisionCreated.fire(change, patchSet, ctx.getAccount(), ctx.getWhen(), notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (workInProgress != null && oldWorkInProgressState != workInProgress) {
|
||||||
|
wipStateChanged.fire(change, patchSet, ctx.getAccount(), ctx.getWhen());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validate(RepoContext ctx)
|
private void validate(RepoContext ctx)
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ import java.sql.Timestamp;
|
|||||||
public class WorkInProgressStateChanged {
|
public class WorkInProgressStateChanged {
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
||||||
|
public static final WorkInProgressStateChanged DISABLED =
|
||||||
|
new WorkInProgressStateChanged() {
|
||||||
|
@Override
|
||||||
|
public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {}
|
||||||
|
};
|
||||||
|
|
||||||
private final PluginSetContext<WorkInProgressStateChangedListener> listeners;
|
private final PluginSetContext<WorkInProgressStateChangedListener> listeners;
|
||||||
private final EventUtil util;
|
private final EventUtil util;
|
||||||
|
|
||||||
@@ -48,6 +54,11 @@ public class WorkInProgressStateChanged {
|
|||||||
this.util = util;
|
this.util = util;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WorkInProgressStateChanged() {
|
||||||
|
this.listeners = null;
|
||||||
|
this.util = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {
|
public void fire(Change change, PatchSet patchSet, AccountState account, Timestamp when) {
|
||||||
if (listeners.isEmpty()) {
|
if (listeners.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import com.google.gerrit.entities.LabelId;
|
|||||||
import com.google.gerrit.entities.PatchSet;
|
import com.google.gerrit.entities.PatchSet;
|
||||||
import com.google.gerrit.entities.PatchSetApproval;
|
import com.google.gerrit.entities.PatchSetApproval;
|
||||||
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
|
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
|
||||||
|
import com.google.gerrit.exceptions.MergeWithConflictsNotSupportedException;
|
||||||
import com.google.gerrit.exceptions.StorageException;
|
import com.google.gerrit.exceptions.StorageException;
|
||||||
import com.google.gerrit.extensions.registration.DynamicItem;
|
import com.google.gerrit.extensions.registration.DynamicItem;
|
||||||
import com.google.gerrit.extensions.registration.DynamicSet;
|
import com.google.gerrit.extensions.registration.DynamicSet;
|
||||||
@@ -424,15 +425,16 @@ public class MergeUtil {
|
|||||||
return dc.writeTree(ins);
|
return dc.writeTree(ins);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RevCommit createMergeCommit(
|
public static CodeReviewCommit createMergeCommit(
|
||||||
ObjectInserter inserter,
|
ObjectInserter inserter,
|
||||||
Config repoConfig,
|
Config repoConfig,
|
||||||
RevCommit mergeTip,
|
RevCommit mergeTip,
|
||||||
RevCommit originalCommit,
|
RevCommit originalCommit,
|
||||||
String mergeStrategy,
|
String mergeStrategy,
|
||||||
|
boolean allowConflicts,
|
||||||
PersonIdent committerIndent,
|
PersonIdent committerIndent,
|
||||||
String commitMsg,
|
String commitMsg,
|
||||||
RevWalk rw)
|
CodeReviewRevWalk rw)
|
||||||
throws IOException, MergeIdenticalTreeException, MergeConflictException,
|
throws IOException, MergeIdenticalTreeException, MergeConflictException,
|
||||||
InvalidMergeStrategyException {
|
InvalidMergeStrategyException {
|
||||||
|
|
||||||
@@ -443,22 +445,63 @@ public class MergeUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Merger m = newMerger(inserter, repoConfig, mergeStrategy);
|
Merger m = newMerger(inserter, repoConfig, mergeStrategy);
|
||||||
if (m.merge(false, mergeTip, originalCommit)) {
|
|
||||||
ObjectId tree = m.getResultTreeId();
|
|
||||||
|
|
||||||
CommitBuilder mergeCommit = new CommitBuilder();
|
DirCache dc = DirCache.newInCore();
|
||||||
mergeCommit.setTreeId(tree);
|
if (allowConflicts && m instanceof ResolveMerger) {
|
||||||
mergeCommit.setParentIds(mergeTip, originalCommit);
|
// The DirCache must be set on ResolveMerger before calling
|
||||||
mergeCommit.setAuthor(committerIndent);
|
// ResolveMerger#merge(AnyObjectId...) otherwise the entries in DirCache don't get populated.
|
||||||
mergeCommit.setCommitter(committerIndent);
|
((ResolveMerger) m).setDirCache(dc);
|
||||||
mergeCommit.setMessage(commitMsg);
|
|
||||||
return rw.parseCommit(inserter.insert(mergeCommit));
|
|
||||||
}
|
}
|
||||||
List<String> conflicts = ImmutableList.of();
|
|
||||||
if (m instanceof ResolveMerger) {
|
ObjectId tree;
|
||||||
conflicts = ((ResolveMerger) m).getUnmergedPaths();
|
ImmutableSet<String> filesWithGitConflicts;
|
||||||
|
if (m.merge(false, mergeTip, originalCommit)) {
|
||||||
|
filesWithGitConflicts = null;
|
||||||
|
tree = m.getResultTreeId();
|
||||||
|
} else {
|
||||||
|
List<String> conflicts = ImmutableList.of();
|
||||||
|
if (m instanceof ResolveMerger) {
|
||||||
|
conflicts = ((ResolveMerger) m).getUnmergedPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowConflicts) {
|
||||||
|
throw new MergeConflictException(createConflictMessage(conflicts));
|
||||||
|
}
|
||||||
|
|
||||||
|
// For merging with conflict markers we need a ResolveMerger, double-check that we have one.
|
||||||
|
if (!(m instanceof ResolveMerger)) {
|
||||||
|
throw new MergeWithConflictsNotSupportedException(MergeStrategy.get(mergeStrategy));
|
||||||
|
}
|
||||||
|
Map<String, MergeResult<? extends Sequence>> mergeResults =
|
||||||
|
((ResolveMerger) m).getMergeResults();
|
||||||
|
|
||||||
|
filesWithGitConflicts =
|
||||||
|
mergeResults.entrySet().stream()
|
||||||
|
.filter(e -> e.getValue().containsConflicts())
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.collect(toImmutableSet());
|
||||||
|
|
||||||
|
tree =
|
||||||
|
mergeWithConflicts(
|
||||||
|
rw,
|
||||||
|
inserter,
|
||||||
|
dc,
|
||||||
|
"TARGET BRANCH",
|
||||||
|
mergeTip,
|
||||||
|
"SOURCE BRANCH",
|
||||||
|
originalCommit,
|
||||||
|
mergeResults);
|
||||||
}
|
}
|
||||||
throw new MergeConflictException(createConflictMessage(conflicts));
|
|
||||||
|
CommitBuilder mergeCommit = new CommitBuilder();
|
||||||
|
mergeCommit.setTreeId(tree);
|
||||||
|
mergeCommit.setParentIds(mergeTip, originalCommit);
|
||||||
|
mergeCommit.setAuthor(committerIndent);
|
||||||
|
mergeCommit.setCommitter(committerIndent);
|
||||||
|
mergeCommit.setMessage(commitMsg);
|
||||||
|
CodeReviewCommit commit = rw.parseCommit(inserter.insert(mergeCommit));
|
||||||
|
commit.setFilesWithGitConflicts(filesWithGitConflicts);
|
||||||
|
return commit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String createConflictMessage(List<String> conflicts) {
|
public static String createConflictMessage(List<String> conflicts) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.google.gerrit.entities.PatchSet;
|
|||||||
import com.google.gerrit.entities.Project;
|
import com.google.gerrit.entities.Project;
|
||||||
import com.google.gerrit.entities.RefNames;
|
import com.google.gerrit.entities.RefNames;
|
||||||
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
|
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
|
||||||
|
import com.google.gerrit.exceptions.MergeWithConflictsNotSupportedException;
|
||||||
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||||
import com.google.gerrit.extensions.client.SubmitType;
|
import com.google.gerrit.extensions.client.SubmitType;
|
||||||
@@ -56,6 +57,8 @@ import com.google.gerrit.server.change.ChangeResource;
|
|||||||
import com.google.gerrit.server.change.NotifyResolver;
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.config.AnonymousCowardName;
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
|
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||||
|
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.MergeUtil;
|
import com.google.gerrit.server.git.MergeUtil;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
@@ -186,9 +189,8 @@ public class CreateChange
|
|||||||
|
|
||||||
checkRequiredPermissions(project, input.branch);
|
checkRequiredPermissions(project, input.branch);
|
||||||
|
|
||||||
Change newChange = createNewChange(input, me, projectState, updateFactory);
|
ChangeInfo newChange = createNewChange(input, me, projectState, updateFactory);
|
||||||
ChangeJson json = jsonFactory.noOptions();
|
return Response.created(newChange);
|
||||||
return Response.created(json.format(newChange));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -289,7 +291,7 @@ public class CreateChange
|
|||||||
.check(RefPermission.CREATE_CHANGE);
|
.check(RefPermission.CREATE_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Change createNewChange(
|
private ChangeInfo createNewChange(
|
||||||
ChangeInput input,
|
ChangeInput input,
|
||||||
IdentifiedUser me,
|
IdentifiedUser me,
|
||||||
ProjectState projectState,
|
ProjectState projectState,
|
||||||
@@ -304,7 +306,7 @@ public class CreateChange
|
|||||||
try (Repository git = gitManager.openRepository(projectState.getNameKey());
|
try (Repository git = gitManager.openRepository(projectState.getNameKey());
|
||||||
ObjectInserter oi = git.newObjectInserter();
|
ObjectInserter oi = git.newObjectInserter();
|
||||||
ObjectReader reader = oi.newReader();
|
ObjectReader reader = oi.newReader();
|
||||||
RevWalk rw = new RevWalk(reader)) {
|
CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(reader)) {
|
||||||
PatchSet basePatchSet = null;
|
PatchSet basePatchSet = null;
|
||||||
List<String> groups = Collections.emptyList();
|
List<String> groups = Collections.emptyList();
|
||||||
|
|
||||||
@@ -327,10 +329,15 @@ public class CreateChange
|
|||||||
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
|
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
|
||||||
String commitMessage = getCommitMessage(input.subject, me);
|
String commitMessage = getCommitMessage(input.subject, me);
|
||||||
|
|
||||||
RevCommit c;
|
CodeReviewCommit c;
|
||||||
if (input.merge != null) {
|
if (input.merge != null) {
|
||||||
// create a merge commit
|
// create a merge commit
|
||||||
c = newMergeCommit(git, oi, rw, projectState, mergeTip, input.merge, author, commitMessage);
|
c = newMergeCommit(git, oi, rw, projectState, mergeTip, input.merge, author, commitMessage);
|
||||||
|
if (!c.getFilesWithGitConflicts().isEmpty()) {
|
||||||
|
logger.atFine().log(
|
||||||
|
"merge commit has conflicts in the following files: %s",
|
||||||
|
c.getFilesWithGitConflicts());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// create an empty commit
|
// create an empty commit
|
||||||
c = newCommit(oi, rw, author, mergeTip, commitMessage);
|
c = newCommit(oi, rw, author, mergeTip, commitMessage);
|
||||||
@@ -338,10 +345,10 @@ public class CreateChange
|
|||||||
|
|
||||||
Change.Id changeId = Change.id(seq.nextChangeId());
|
Change.Id changeId = Change.id(seq.nextChangeId());
|
||||||
ChangeInserter ins = changeInserterFactory.create(changeId, c, input.branch);
|
ChangeInserter ins = changeInserterFactory.create(changeId, c, input.branch);
|
||||||
ins.setMessage(String.format("Uploaded patch set %s.", ins.getPatchSetId().get()));
|
ins.setMessage(messageForNewChange(ins.getPatchSetId(), c));
|
||||||
ins.setTopic(input.topic);
|
ins.setTopic(input.topic);
|
||||||
ins.setPrivate(input.isPrivate);
|
ins.setPrivate(input.isPrivate);
|
||||||
ins.setWorkInProgress(input.workInProgress);
|
ins.setWorkInProgress(input.workInProgress || !c.getFilesWithGitConflicts().isEmpty());
|
||||||
ins.setGroups(groups);
|
ins.setGroups(groups);
|
||||||
try (BatchUpdate bu = updateFactory.create(projectState.getNameKey(), me, now)) {
|
try (BatchUpdate bu = updateFactory.create(projectState.getNameKey(), me, now)) {
|
||||||
bu.setRepository(git, rw, oi);
|
bu.setRepository(git, rw, oi);
|
||||||
@@ -351,8 +358,10 @@ public class CreateChange
|
|||||||
bu.insertChange(ins);
|
bu.insertChange(ins);
|
||||||
bu.execute();
|
bu.execute();
|
||||||
}
|
}
|
||||||
return ins.getChange();
|
ChangeInfo changeInfo = jsonFactory.noOptions().format(ins.getChange());
|
||||||
} catch (InvalidMergeStrategyException e) {
|
changeInfo.containsGitConflicts = !c.getFilesWithGitConflicts().isEmpty() ? true : null;
|
||||||
|
return changeInfo;
|
||||||
|
} catch (InvalidMergeStrategyException | MergeWithConflictsNotSupportedException e) {
|
||||||
throw new BadRequestException(e.getMessage());
|
throw new BadRequestException(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,9 +478,9 @@ public class CreateChange
|
|||||||
return commitMessage;
|
return commitMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RevCommit newCommit(
|
private static CodeReviewCommit newCommit(
|
||||||
ObjectInserter oi,
|
ObjectInserter oi,
|
||||||
RevWalk rw,
|
CodeReviewRevWalk rw,
|
||||||
PersonIdent authorIdent,
|
PersonIdent authorIdent,
|
||||||
RevCommit mergeTip,
|
RevCommit mergeTip,
|
||||||
String commitMessage)
|
String commitMessage)
|
||||||
@@ -490,10 +499,10 @@ public class CreateChange
|
|||||||
return rw.parseCommit(insert(oi, commit));
|
return rw.parseCommit(insert(oi, commit));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit newMergeCommit(
|
private CodeReviewCommit newMergeCommit(
|
||||||
Repository repo,
|
Repository repo,
|
||||||
ObjectInserter oi,
|
ObjectInserter oi,
|
||||||
RevWalk rw,
|
CodeReviewRevWalk rw,
|
||||||
ProjectState projectState,
|
ProjectState projectState,
|
||||||
RevCommit mergeTip,
|
RevCommit mergeTip,
|
||||||
MergeInput merge,
|
MergeInput merge,
|
||||||
@@ -501,7 +510,8 @@ public class CreateChange
|
|||||||
String commitMessage)
|
String commitMessage)
|
||||||
throws RestApiException, IOException {
|
throws RestApiException, IOException {
|
||||||
logger.atFine().log(
|
logger.atFine().log(
|
||||||
"Creating merge commit: source = %s, strategy = %s", merge.source, merge.strategy);
|
"Creating merge commit: source = %s, strategy = %s, allowConflicts",
|
||||||
|
merge.source, merge.strategy, merge.allowConflicts);
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(merge.source)) {
|
if (Strings.isNullOrEmpty(merge.source)) {
|
||||||
throw new BadRequestException("merge.source must be non-empty");
|
throw new BadRequestException("merge.source must be non-empty");
|
||||||
@@ -531,6 +541,7 @@ public class CreateChange
|
|||||||
mergeTip,
|
mergeTip,
|
||||||
sourceCommit,
|
sourceCommit,
|
||||||
mergeStrategy,
|
mergeStrategy,
|
||||||
|
merge.allowConflicts,
|
||||||
authorIdent,
|
authorIdent,
|
||||||
commitMessage,
|
commitMessage,
|
||||||
rw);
|
rw);
|
||||||
@@ -540,6 +551,20 @@ public class CreateChange
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String messageForNewChange(PatchSet.Id patchSetId, CodeReviewCommit commit) {
|
||||||
|
StringBuilder stringBuilder =
|
||||||
|
new StringBuilder(String.format("Uploaded patch set %s.", patchSetId.get()));
|
||||||
|
|
||||||
|
if (!commit.getFilesWithGitConflicts().isEmpty()) {
|
||||||
|
stringBuilder.append("\n\nThe following files contain Git conflicts:\n");
|
||||||
|
commit.getFilesWithGitConflicts().stream()
|
||||||
|
.sorted()
|
||||||
|
.forEach(filePath -> stringBuilder.append("* ").append(filePath).append("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private static ObjectId insert(ObjectInserter inserter, CommitBuilder commit) throws IOException {
|
private static ObjectId insert(ObjectInserter inserter, CommitBuilder commit) throws IOException {
|
||||||
ObjectId id = inserter.insert(commit);
|
ObjectId id = inserter.insert(commit);
|
||||||
inserter.flush();
|
inserter.flush();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.google.gerrit.entities.Change;
|
|||||||
import com.google.gerrit.entities.PatchSet;
|
import com.google.gerrit.entities.PatchSet;
|
||||||
import com.google.gerrit.entities.Project;
|
import com.google.gerrit.entities.Project;
|
||||||
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
|
import com.google.gerrit.exceptions.InvalidMergeStrategyException;
|
||||||
|
import com.google.gerrit.exceptions.MergeWithConflictsNotSupportedException;
|
||||||
import com.google.gerrit.extensions.client.ListChangesOption;
|
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.MergeInput;
|
import com.google.gerrit.extensions.common.MergeInput;
|
||||||
@@ -44,6 +45,8 @@ import com.google.gerrit.server.change.ChangeJson;
|
|||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
import com.google.gerrit.server.change.NotifyResolver;
|
import com.google.gerrit.server.change.NotifyResolver;
|
||||||
import com.google.gerrit.server.change.PatchSetInserter;
|
import com.google.gerrit.server.change.PatchSetInserter;
|
||||||
|
import com.google.gerrit.server.git.CodeReviewCommit;
|
||||||
|
import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
import com.google.gerrit.server.git.MergeUtil;
|
import com.google.gerrit.server.git.MergeUtil;
|
||||||
import com.google.gerrit.server.notedb.ChangeNotes;
|
import com.google.gerrit.server.notedb.ChangeNotes;
|
||||||
@@ -71,7 +74,6 @@ import org.eclipse.jgit.lib.PersonIdent;
|
|||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
|
||||||
import org.eclipse.jgit.util.ChangeIdUtil;
|
import org.eclipse.jgit.util.ChangeIdUtil;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -141,7 +143,7 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
|
|||||||
try (Repository git = gitManager.openRepository(project);
|
try (Repository git = gitManager.openRepository(project);
|
||||||
ObjectInserter oi = git.newObjectInserter();
|
ObjectInserter oi = git.newObjectInserter();
|
||||||
ObjectReader reader = oi.newReader();
|
ObjectReader reader = oi.newReader();
|
||||||
RevWalk rw = new RevWalk(reader)) {
|
CodeReviewRevWalk rw = CodeReviewCommit.newRevWalk(reader)) {
|
||||||
|
|
||||||
RevCommit sourceCommit = MergeUtil.resolveCommit(git, rw, merge.source);
|
RevCommit sourceCommit = MergeUtil.resolveCommit(git, rw, merge.source);
|
||||||
if (!commits.canRead(projectState, git, sourceCommit)) {
|
if (!commits.canRead(projectState, git, sourceCommit)) {
|
||||||
@@ -162,7 +164,7 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
|
|||||||
Timestamp now = TimeUtil.nowTs();
|
Timestamp now = TimeUtil.nowTs();
|
||||||
IdentifiedUser me = user.get().asIdentifiedUser();
|
IdentifiedUser me = user.get().asIdentifiedUser();
|
||||||
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
|
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
|
||||||
RevCommit newCommit =
|
CodeReviewCommit newCommit =
|
||||||
createMergeCommit(
|
createMergeCommit(
|
||||||
in,
|
in,
|
||||||
projectState,
|
projectState,
|
||||||
@@ -182,7 +184,8 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
|
|||||||
bu.setRepository(git, rw, oi);
|
bu.setRepository(git, rw, oi);
|
||||||
bu.setNotify(NotifyResolver.Result.none());
|
bu.setNotify(NotifyResolver.Result.none());
|
||||||
psInserter
|
psInserter
|
||||||
.setMessage("Uploaded patch set " + nextPsId.get() + ".")
|
.setMessage(messageForChange(nextPsId, newCommit))
|
||||||
|
.setWorkInProgress(!newCommit.getFilesWithGitConflicts().isEmpty())
|
||||||
.setCheckAddPatchSetPermission(false);
|
.setCheckAddPatchSetPermission(false);
|
||||||
if (groups != null) {
|
if (groups != null) {
|
||||||
psInserter.setGroups(groups);
|
psInserter.setGroups(groups);
|
||||||
@@ -192,8 +195,11 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChangeJson json = jsonFactory.create(ListChangesOption.CURRENT_REVISION);
|
ChangeJson json = jsonFactory.create(ListChangesOption.CURRENT_REVISION);
|
||||||
return Response.ok(json.format(psInserter.getChange()));
|
ChangeInfo changeInfo = json.format(psInserter.getChange());
|
||||||
} catch (InvalidMergeStrategyException e) {
|
changeInfo.containsGitConflicts =
|
||||||
|
!newCommit.getFilesWithGitConflicts().isEmpty() ? true : null;
|
||||||
|
return Response.ok(changeInfo);
|
||||||
|
} catch (InvalidMergeStrategyException | MergeWithConflictsNotSupportedException e) {
|
||||||
throw new BadRequestException(e.getMessage());
|
throw new BadRequestException(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,13 +219,13 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
|
|||||||
return psUtil.current(change);
|
return psUtil.current(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit createMergeCommit(
|
private CodeReviewCommit createMergeCommit(
|
||||||
MergePatchSetInput in,
|
MergePatchSetInput in,
|
||||||
ProjectState projectState,
|
ProjectState projectState,
|
||||||
BranchNameKey dest,
|
BranchNameKey dest,
|
||||||
Repository git,
|
Repository git,
|
||||||
ObjectInserter oi,
|
ObjectInserter oi,
|
||||||
RevWalk rw,
|
CodeReviewRevWalk rw,
|
||||||
RevCommit currentPsCommit,
|
RevCommit currentPsCommit,
|
||||||
RevCommit sourceCommit,
|
RevCommit sourceCommit,
|
||||||
PersonIdent author,
|
PersonIdent author,
|
||||||
@@ -258,6 +264,28 @@ public class CreateMergePatchSet implements RestModifyView<ChangeResource, Merge
|
|||||||
mergeUtilFactory.create(projectState).mergeStrategyName());
|
mergeUtilFactory.create(projectState).mergeStrategyName());
|
||||||
|
|
||||||
return MergeUtil.createMergeCommit(
|
return MergeUtil.createMergeCommit(
|
||||||
oi, git.getConfig(), mergeTip, sourceCommit, mergeStrategy, author, commitMsg, rw);
|
oi,
|
||||||
|
git.getConfig(),
|
||||||
|
mergeTip,
|
||||||
|
sourceCommit,
|
||||||
|
mergeStrategy,
|
||||||
|
in.merge.allowConflicts,
|
||||||
|
author,
|
||||||
|
commitMsg,
|
||||||
|
rw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String messageForChange(PatchSet.Id patchSetId, CodeReviewCommit commit) {
|
||||||
|
StringBuilder stringBuilder =
|
||||||
|
new StringBuilder(String.format("Uploaded patch set %s.", patchSetId.get()));
|
||||||
|
|
||||||
|
if (!commit.getFilesWithGitConflicts().isEmpty()) {
|
||||||
|
stringBuilder.append("\n\nThe following files contain Git conflicts:\n");
|
||||||
|
commit.getFilesWithGitConflicts().stream()
|
||||||
|
.sorted()
|
||||||
|
.forEach(filePath -> stringBuilder.append("* ").append(filePath).append("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS
|
|||||||
import static com.google.gerrit.extensions.client.ReviewerState.CC;
|
import static com.google.gerrit.extensions.client.ReviewerState.CC;
|
||||||
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
|
import static com.google.gerrit.extensions.client.ReviewerState.REMOVED;
|
||||||
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
|
import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
|
||||||
|
import static com.google.gerrit.git.ObjectIds.abbreviateName;
|
||||||
import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
|
import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
|
||||||
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
|
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
|
||||||
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
|
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
|
||||||
@@ -144,8 +145,10 @@ import com.google.gerrit.extensions.common.MergeInput;
|
|||||||
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
import com.google.gerrit.extensions.common.MergePatchSetInput;
|
||||||
import com.google.gerrit.extensions.common.RevisionInfo;
|
import com.google.gerrit.extensions.common.RevisionInfo;
|
||||||
import com.google.gerrit.extensions.common.TrackingIdInfo;
|
import com.google.gerrit.extensions.common.TrackingIdInfo;
|
||||||
|
import com.google.gerrit.extensions.events.WorkInProgressStateChangedListener;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
@@ -180,6 +183,7 @@ import com.google.gerrit.testing.FakeEmailSender.Message;
|
|||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -3207,13 +3211,29 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
MergePatchSetInput in = new MergePatchSetInput();
|
MergePatchSetInput in = new MergePatchSetInput();
|
||||||
in.merge = mergeInput;
|
in.merge = mergeInput;
|
||||||
in.subject = "update change by merge ps2";
|
in.subject = "update change by merge ps2";
|
||||||
gApi.changes().id(changeId).createMergePatchSet(in);
|
|
||||||
|
TestWorkInProgressStateChangedListener wipStateChangedListener =
|
||||||
|
new TestWorkInProgressStateChangedListener();
|
||||||
|
try (Registration registration =
|
||||||
|
extensionRegistry.newRegistration().add(wipStateChangedListener)) {
|
||||||
|
ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
|
||||||
|
assertThat(changeInfo.subject).isEqualTo(in.subject);
|
||||||
|
assertThat(changeInfo.containsGitConflicts).isNull();
|
||||||
|
assertThat(changeInfo.workInProgress).isNull();
|
||||||
|
}
|
||||||
|
assertThat(wipStateChangedListener.invoked).isFalse();
|
||||||
|
|
||||||
|
// To get the revisions, we must retrieve the change with more change options.
|
||||||
ChangeInfo changeInfo =
|
ChangeInfo changeInfo =
|
||||||
gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
|
gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
|
||||||
assertThat(changeInfo.revisions).hasSize(2);
|
assertThat(changeInfo.revisions).hasSize(2);
|
||||||
assertThat(changeInfo.subject).isEqualTo(in.subject);
|
|
||||||
assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
|
assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
|
||||||
.isEqualTo(parent);
|
.isEqualTo(parent);
|
||||||
|
|
||||||
|
// Verify the message that has been posted on the change.
|
||||||
|
List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
|
||||||
|
assertThat(messages).hasSize(2);
|
||||||
|
assertThat(Iterables.getLast(messages).message).isEqualTo("Uploaded patch set 2.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -3251,6 +3271,142 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
assertThat(thrown).hasMessageThat().isEqualTo("merge conflict(s):\n" + fileName);
|
assertThat(thrown).hasMessageThat().isEqualTo("merge conflict(s):\n" + fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMergePatchSet_ConflictAllowed() throws Exception {
|
||||||
|
RevCommit initialHead = projectOperations.project(project).getHead("master");
|
||||||
|
createBranch("dev");
|
||||||
|
|
||||||
|
// create a change for master
|
||||||
|
String changeId = createChange().getChangeId();
|
||||||
|
|
||||||
|
String fileName = "shared.txt";
|
||||||
|
String sourceSubject = "source change";
|
||||||
|
String sourceContent = "source content";
|
||||||
|
String targetSubject = "target change";
|
||||||
|
String targetContent = "target content";
|
||||||
|
testRepo.reset(initialHead);
|
||||||
|
PushOneCommit.Result currentMaster =
|
||||||
|
pushFactory
|
||||||
|
.create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
|
||||||
|
.to("refs/heads/master");
|
||||||
|
currentMaster.assertOkStatus();
|
||||||
|
String parent = currentMaster.getCommit().getName();
|
||||||
|
|
||||||
|
// push a commit into dev branch
|
||||||
|
testRepo.reset(initialHead);
|
||||||
|
PushOneCommit.Result changeA =
|
||||||
|
pushFactory
|
||||||
|
.create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
|
||||||
|
.to("refs/heads/dev");
|
||||||
|
changeA.assertOkStatus();
|
||||||
|
MergeInput mergeInput = new MergeInput();
|
||||||
|
mergeInput.source = "dev";
|
||||||
|
mergeInput.allowConflicts = true;
|
||||||
|
MergePatchSetInput in = new MergePatchSetInput();
|
||||||
|
in.merge = mergeInput;
|
||||||
|
in.subject = "update change by merge ps2";
|
||||||
|
|
||||||
|
TestWorkInProgressStateChangedListener wipStateChangedListener =
|
||||||
|
new TestWorkInProgressStateChangedListener();
|
||||||
|
try (Registration registration =
|
||||||
|
extensionRegistry.newRegistration().add(wipStateChangedListener)) {
|
||||||
|
ChangeInfo changeInfo = gApi.changes().id(changeId).createMergePatchSet(in);
|
||||||
|
assertThat(changeInfo.subject).isEqualTo(in.subject);
|
||||||
|
assertThat(changeInfo.containsGitConflicts).isTrue();
|
||||||
|
assertThat(changeInfo.workInProgress).isTrue();
|
||||||
|
}
|
||||||
|
assertThat(wipStateChangedListener.invoked).isTrue();
|
||||||
|
assertThat(wipStateChangedListener.wip).isTrue();
|
||||||
|
|
||||||
|
// To get the revisions, we must retrieve the change with more change options.
|
||||||
|
ChangeInfo changeInfo =
|
||||||
|
gApi.changes().id(changeId).get(ALL_REVISIONS, CURRENT_COMMIT, CURRENT_REVISION);
|
||||||
|
assertThat(changeInfo.revisions).hasSize(2);
|
||||||
|
assertThat(changeInfo.revisions.get(changeInfo.currentRevision).commit.parents.get(0).commit)
|
||||||
|
.isEqualTo(parent);
|
||||||
|
|
||||||
|
// Verify that the file content in the created patch set is correct.
|
||||||
|
// We expect that it has conflict markers to indicate the conflict.
|
||||||
|
BinaryResult bin = gApi.changes().id(changeId).current().file(fileName).content();
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
bin.writeTo(os);
|
||||||
|
String fileContent = new String(os.toByteArray(), UTF_8);
|
||||||
|
String sourceSha1 = abbreviateName(changeA.getCommit(), 6);
|
||||||
|
String targetSha1 = abbreviateName(currentMaster.getCommit(), 6);
|
||||||
|
assertThat(fileContent)
|
||||||
|
.isEqualTo(
|
||||||
|
"<<<<<<< TARGET BRANCH ("
|
||||||
|
+ targetSha1
|
||||||
|
+ " "
|
||||||
|
+ targetSubject
|
||||||
|
+ ")\n"
|
||||||
|
+ targetContent
|
||||||
|
+ "\n"
|
||||||
|
+ "=======\n"
|
||||||
|
+ sourceContent
|
||||||
|
+ "\n"
|
||||||
|
+ ">>>>>>> SOURCE BRANCH ("
|
||||||
|
+ sourceSha1
|
||||||
|
+ " "
|
||||||
|
+ sourceSubject
|
||||||
|
+ ")\n");
|
||||||
|
|
||||||
|
// Verify the message that has been posted on the change.
|
||||||
|
List<ChangeMessageInfo> messages = gApi.changes().id(changeId).messages();
|
||||||
|
assertThat(messages).hasSize(2);
|
||||||
|
assertThat(Iterables.getLast(messages).message)
|
||||||
|
.isEqualTo(
|
||||||
|
"Uploaded patch set 2.\n\n"
|
||||||
|
+ "The following files contain Git conflicts:\n"
|
||||||
|
+ "* "
|
||||||
|
+ fileName
|
||||||
|
+ "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMergePatchSet_ConflictAllowedNotSupportedByMergeStrategy() throws Exception {
|
||||||
|
RevCommit initialHead = projectOperations.project(project).getHead("master");
|
||||||
|
createBranch("dev");
|
||||||
|
|
||||||
|
// create a change for master
|
||||||
|
String changeId = createChange().getChangeId();
|
||||||
|
|
||||||
|
String fileName = "shared.txt";
|
||||||
|
String sourceSubject = "source change";
|
||||||
|
String sourceContent = "source content";
|
||||||
|
String targetSubject = "target change";
|
||||||
|
String targetContent = "target content";
|
||||||
|
testRepo.reset(initialHead);
|
||||||
|
PushOneCommit.Result currentMaster =
|
||||||
|
pushFactory
|
||||||
|
.create(admin.newIdent(), testRepo, targetSubject, fileName, targetContent)
|
||||||
|
.to("refs/heads/master");
|
||||||
|
currentMaster.assertOkStatus();
|
||||||
|
|
||||||
|
// push a commit into dev branch
|
||||||
|
testRepo.reset(initialHead);
|
||||||
|
PushOneCommit.Result changeA =
|
||||||
|
pushFactory
|
||||||
|
.create(user.newIdent(), testRepo, sourceSubject, fileName, sourceContent)
|
||||||
|
.to("refs/heads/dev");
|
||||||
|
changeA.assertOkStatus();
|
||||||
|
MergeInput mergeInput = new MergeInput();
|
||||||
|
mergeInput.source = "dev";
|
||||||
|
mergeInput.allowConflicts = true;
|
||||||
|
mergeInput.strategy = "simple-two-way-in-core";
|
||||||
|
MergePatchSetInput in = new MergePatchSetInput();
|
||||||
|
in.merge = mergeInput;
|
||||||
|
in.subject = "update change by merge ps2";
|
||||||
|
|
||||||
|
BadRequestException ex =
|
||||||
|
assertThrows(
|
||||||
|
BadRequestException.class, () -> gApi.changes().id(changeId).createMergePatchSet(in));
|
||||||
|
assertThat(ex)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"merge with conflicts is not supported with merge strategy: " + mergeInput.strategy);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createMergePatchSetInheritParent() throws Exception {
|
public void createMergePatchSetInheritParent() throws Exception {
|
||||||
RevCommit initialHead = projectOperations.project(project).getHead("master");
|
RevCommit initialHead = projectOperations.project(project).getHead("master");
|
||||||
@@ -4535,4 +4691,17 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
private interface AddReviewerCaller {
|
private interface AddReviewerCaller {
|
||||||
void call(String changeId, String reviewer) throws RestApiException;
|
void call(String changeId, String reviewer) throws RestApiException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TestWorkInProgressStateChangedListener
|
||||||
|
implements WorkInProgressStateChangedListener {
|
||||||
|
boolean invoked;
|
||||||
|
Boolean wip;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWorkInProgressStateChanged(Event event) {
|
||||||
|
this.invoked = true;
|
||||||
|
this.wip =
|
||||||
|
event.getChange().workInProgress != null ? event.getChange().workInProgress : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,15 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
|
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
|
||||||
import static com.google.gerrit.common.data.Permission.READ;
|
import static com.google.gerrit.common.data.Permission.READ;
|
||||||
import static com.google.gerrit.entities.RefNames.changeMetaRef;
|
import static com.google.gerrit.entities.RefNames.changeMetaRef;
|
||||||
|
import static com.google.gerrit.git.ObjectIds.abbreviateName;
|
||||||
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
||||||
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
|
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
|
import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.PushOneCommit;
|
import com.google.gerrit.acceptance.PushOneCommit;
|
||||||
import com.google.gerrit.acceptance.PushOneCommit.Result;
|
import com.google.gerrit.acceptance.PushOneCommit.Result;
|
||||||
@@ -44,9 +47,11 @@ import com.google.gerrit.extensions.client.ChangeStatus;
|
|||||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInput;
|
import com.google.gerrit.extensions.common.ChangeInput;
|
||||||
|
import com.google.gerrit.extensions.common.ChangeMessageInfo;
|
||||||
import com.google.gerrit.extensions.common.MergeInput;
|
import com.google.gerrit.extensions.common.MergeInput;
|
||||||
import com.google.gerrit.extensions.restapi.AuthException;
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.extensions.restapi.BinaryResult;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
@@ -54,6 +59,7 @@ import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
|||||||
import com.google.gerrit.server.submit.ChangeAlreadyMergedException;
|
import com.google.gerrit.server.submit.ChangeAlreadyMergedException;
|
||||||
import com.google.gerrit.testing.FakeEmailSender.Message;
|
import com.google.gerrit.testing.FakeEmailSender.Message;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -141,6 +147,11 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
ChangeInfo info = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
|
ChangeInfo info = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
|
||||||
assertThat(info.revisions.get(info.currentRevision).commit.message)
|
assertThat(info.revisions.get(info.currentRevision).commit.message)
|
||||||
.contains("Change-Id: " + info.changeId);
|
.contains("Change-Id: " + info.changeId);
|
||||||
|
|
||||||
|
// Verify the message that has been posted on the change.
|
||||||
|
List<ChangeMessageInfo> messages = gApi.changes().id(info._number).messages();
|
||||||
|
assertThat(messages).hasSize(1);
|
||||||
|
assertThat(Iterables.getOnlyElement(messages).message).isEqualTo("Uploaded patch set 1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -364,7 +375,12 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
public void createMergeChange() throws Exception {
|
public void createMergeChange() throws Exception {
|
||||||
changeInTwoBranches("branchA", "a.txt", "branchB", "b.txt");
|
changeInTwoBranches("branchA", "a.txt", "branchB", "b.txt");
|
||||||
ChangeInput in = newMergeChangeInput("branchA", "branchB", "");
|
ChangeInput in = newMergeChangeInput("branchA", "branchB", "");
|
||||||
assertCreateSucceeds(in);
|
ChangeInfo change = assertCreateSucceeds(in);
|
||||||
|
|
||||||
|
// Verify the message that has been posted on the change.
|
||||||
|
List<ChangeMessageInfo> messages = gApi.changes().id(change._number).messages();
|
||||||
|
assertThat(messages).hasSize(1);
|
||||||
|
assertThat(Iterables.getOnlyElement(messages).message).isEqualTo("Uploaded patch set 1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -381,6 +397,87 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
assertCreateSucceeds(in);
|
assertCreateSucceeds(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMergeChange_ConflictsAllowed() throws Exception {
|
||||||
|
String fileName = "shared.txt";
|
||||||
|
String sourceBranch = "sourceBranch";
|
||||||
|
String sourceSubject = "source change";
|
||||||
|
String sourceContent = "source content";
|
||||||
|
String targetBranch = "targetBranch";
|
||||||
|
String targetSubject = "target change";
|
||||||
|
String targetContent = "target content";
|
||||||
|
changeInTwoBranches(
|
||||||
|
sourceBranch,
|
||||||
|
sourceSubject,
|
||||||
|
fileName,
|
||||||
|
sourceContent,
|
||||||
|
targetBranch,
|
||||||
|
targetSubject,
|
||||||
|
fileName,
|
||||||
|
targetContent);
|
||||||
|
ChangeInput in = newMergeChangeInput(targetBranch, sourceBranch, "", true);
|
||||||
|
ChangeInfo change = assertCreateSucceedsWithConflicts(in);
|
||||||
|
|
||||||
|
// Verify that the file content in the created change is correct.
|
||||||
|
// We expect that it has conflict markers to indicate the conflict.
|
||||||
|
BinaryResult bin = gApi.changes().id(change._number).current().file(fileName).content();
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
bin.writeTo(os);
|
||||||
|
String fileContent = new String(os.toByteArray(), UTF_8);
|
||||||
|
String sourceSha1 = abbreviateName(projectOperations.project(project).getHead(sourceBranch), 6);
|
||||||
|
String targetSha1 = abbreviateName(projectOperations.project(project).getHead(targetBranch), 6);
|
||||||
|
assertThat(fileContent)
|
||||||
|
.isEqualTo(
|
||||||
|
"<<<<<<< TARGET BRANCH ("
|
||||||
|
+ targetSha1
|
||||||
|
+ " "
|
||||||
|
+ targetSubject
|
||||||
|
+ ")\n"
|
||||||
|
+ targetContent
|
||||||
|
+ "\n"
|
||||||
|
+ "=======\n"
|
||||||
|
+ sourceContent
|
||||||
|
+ "\n"
|
||||||
|
+ ">>>>>>> SOURCE BRANCH ("
|
||||||
|
+ sourceSha1
|
||||||
|
+ " "
|
||||||
|
+ sourceSubject
|
||||||
|
+ ")\n");
|
||||||
|
|
||||||
|
// Verify the message that has been posted on the change.
|
||||||
|
List<ChangeMessageInfo> messages = gApi.changes().id(change._number).messages();
|
||||||
|
assertThat(messages).hasSize(1);
|
||||||
|
assertThat(Iterables.getOnlyElement(messages).message)
|
||||||
|
.isEqualTo(
|
||||||
|
"Uploaded patch set 1.\n\n"
|
||||||
|
+ "The following files contain Git conflicts:\n"
|
||||||
|
+ "* "
|
||||||
|
+ fileName
|
||||||
|
+ "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMergeChange_ConflictAllowedNotSupportedByMergeStrategy() throws Exception {
|
||||||
|
String fileName = "shared.txt";
|
||||||
|
String sourceBranch = "sourceBranch";
|
||||||
|
String targetBranch = "targetBranch";
|
||||||
|
changeInTwoBranches(
|
||||||
|
sourceBranch,
|
||||||
|
"source change",
|
||||||
|
fileName,
|
||||||
|
"source content",
|
||||||
|
targetBranch,
|
||||||
|
"target change",
|
||||||
|
fileName,
|
||||||
|
"target content");
|
||||||
|
String mergeStrategy = "simple-two-way-in-core";
|
||||||
|
ChangeInput in = newMergeChangeInput(targetBranch, sourceBranch, mergeStrategy, true);
|
||||||
|
assertCreateFails(
|
||||||
|
in,
|
||||||
|
BadRequestException.class,
|
||||||
|
"merge with conflicts is not supported with merge strategy: " + mergeStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createMergeChangeFailsWithConflictIfThereAreTooManyCommonPredecessors()
|
public void createMergeChangeFailsWithConflictIfThereAreTooManyCommonPredecessors()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@@ -732,6 +829,26 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
assertThat(out.revisions).hasSize(1);
|
assertThat(out.revisions).hasSize(1);
|
||||||
assertThat(out.submitted).isNull();
|
assertThat(out.submitted).isNull();
|
||||||
|
assertThat(out.containsGitConflicts).isNull();
|
||||||
|
assertThat(in.status).isEqualTo(ChangeStatus.NEW);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeInfo assertCreateSucceedsWithConflicts(ChangeInput in) throws Exception {
|
||||||
|
ChangeInfo out = gApi.changes().createAsInfo(in);
|
||||||
|
assertThat(out.project).isEqualTo(in.project);
|
||||||
|
assertThat(RefNames.fullName(out.branch)).isEqualTo(RefNames.fullName(in.branch));
|
||||||
|
assertThat(out.subject).isEqualTo(in.subject.split("\n")[0]);
|
||||||
|
assertThat(out.topic).isEqualTo(in.topic);
|
||||||
|
assertThat(out.status).isEqualTo(in.status);
|
||||||
|
if (in.isPrivate) {
|
||||||
|
assertThat(out.isPrivate).isTrue();
|
||||||
|
} else {
|
||||||
|
assertThat(out.isPrivate).isNull();
|
||||||
|
}
|
||||||
|
assertThat(out.submitted).isNull();
|
||||||
|
assertThat(out.containsGitConflicts).isTrue();
|
||||||
|
assertThat(out.workInProgress).isTrue();
|
||||||
assertThat(in.status).isEqualTo(ChangeStatus.NEW);
|
assertThat(in.status).isEqualTo(ChangeStatus.NEW);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -764,6 +881,11 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ChangeInput newMergeChangeInput(String targetBranch, String sourceRef, String strategy) {
|
private ChangeInput newMergeChangeInput(String targetBranch, String sourceRef, String strategy) {
|
||||||
|
return newMergeChangeInput(targetBranch, sourceRef, strategy, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChangeInput newMergeChangeInput(
|
||||||
|
String targetBranch, String sourceRef, String strategy, boolean allowConflicts) {
|
||||||
// create a merge change from branchA to master in gerrit
|
// create a merge change from branchA to master in gerrit
|
||||||
ChangeInput in = new ChangeInput();
|
ChangeInput in = new ChangeInput();
|
||||||
in.project = project.get();
|
in.project = project.get();
|
||||||
@@ -776,6 +898,7 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
if (!Strings.isNullOrEmpty(strategy)) {
|
if (!Strings.isNullOrEmpty(strategy)) {
|
||||||
in.merge.strategy = strategy;
|
in.merge.strategy = strategy;
|
||||||
}
|
}
|
||||||
|
in.merge.allowConflicts = allowConflicts;
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,6 +914,34 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
*/
|
*/
|
||||||
private Map<String, Result> changeInTwoBranches(
|
private Map<String, Result> changeInTwoBranches(
|
||||||
String branchA, String fileA, String branchB, String fileB) throws Exception {
|
String branchA, String fileA, String branchB, String fileB) throws Exception {
|
||||||
|
return changeInTwoBranches(
|
||||||
|
branchA, "change A", fileA, "A content", branchB, "change B", fileB, "B content");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty commit in master, two new branches with one commit each.
|
||||||
|
*
|
||||||
|
* @param branchA name of first branch to create
|
||||||
|
* @param subjectA commit message subject for the change on branchA
|
||||||
|
* @param fileA name of file to commit to branchA
|
||||||
|
* @param contentA file content to commit to branchA
|
||||||
|
* @param branchB name of second branch to create
|
||||||
|
* @param subjectB commit message subject for the change on branchB
|
||||||
|
* @param fileB name of file to commit to branchB
|
||||||
|
* @param contentB file content to commit to branchB
|
||||||
|
* @return A {@code Map} of branchName => commit result.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private Map<String, Result> changeInTwoBranches(
|
||||||
|
String branchA,
|
||||||
|
String subjectA,
|
||||||
|
String fileA,
|
||||||
|
String contentA,
|
||||||
|
String branchB,
|
||||||
|
String subjectB,
|
||||||
|
String fileB,
|
||||||
|
String contentB)
|
||||||
|
throws Exception {
|
||||||
// create a initial commit in master
|
// create a initial commit in master
|
||||||
Result initialCommit =
|
Result initialCommit =
|
||||||
pushFactory
|
pushFactory
|
||||||
@@ -805,13 +956,13 @@ public class CreateChangeIT extends AbstractDaemonTest {
|
|||||||
// create a commit in branchA
|
// create a commit in branchA
|
||||||
Result changeA =
|
Result changeA =
|
||||||
pushFactory
|
pushFactory
|
||||||
.create(user.newIdent(), testRepo, "change A", fileA, "A content")
|
.create(user.newIdent(), testRepo, subjectA, fileA, contentA)
|
||||||
.to("refs/heads/" + branchA);
|
.to("refs/heads/" + branchA);
|
||||||
changeA.assertOkStatus();
|
changeA.assertOkStatus();
|
||||||
|
|
||||||
// create a commit in branchB
|
// create a commit in branchB
|
||||||
PushOneCommit commitB =
|
PushOneCommit commitB =
|
||||||
pushFactory.create(user.newIdent(), testRepo, "change B", fileB, "B content");
|
pushFactory.create(user.newIdent(), testRepo, subjectB, fileB, contentB);
|
||||||
commitB.setParent(initialCommit.getCommit());
|
commitB.setParent(initialCommit.getCommit());
|
||||||
Result changeB = commitB.to("refs/heads/" + branchB);
|
Result changeB = commitB.to("refs/heads/" + branchB);
|
||||||
changeB.assertOkStatus();
|
changeB.assertOkStatus();
|
||||||
|
|||||||
Reference in New Issue
Block a user