Merge "DeleteTags: document corner case behavior"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 3f0c751..415465e 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -7488,6 +7488,13 @@
patch set is inferred. +
Empty string is used for rebasing directly on top of the target branch,
which effectively breaks dependency towards a parent change.
+|`allow_conflicts`|optional, defaults to false|
+If `true`, the rebase also succeeds if there are conflicts. +
+If there are conflicts the file contents of the rebased patch set 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 returned link:#change-info[ChangeInfo]. +
+If there are conflicts the change is marked as work-in-progress.
|===========================
[[related-change-and-commit-info]]
diff --git a/java/com/google/gerrit/extensions/api/changes/RebaseInput.java b/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
index 5f4a014..10559a3 100644
--- a/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/RebaseInput.java
@@ -16,4 +16,12 @@
public class RebaseInput {
public String base;
+
+ /**
+ * Whether the rebase should succeed if there are conflicts.
+ *
+ * <p>If there are conflicts the file contents of the rebased change contain git conflict markers
+ * to indicate the conflicts.
+ */
+ public boolean allowConflicts;
}
diff --git a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index b419c2f..73e6a4e 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -68,6 +68,8 @@
ChangeApi rebase(RebaseInput in) throws RestApiException;
+ ChangeInfo rebaseAsInfo(RebaseInput in) throws RestApiException;
+
boolean canRebase() throws RestApiException;
RevisionReviewerApi reviewer(String id) throws RestApiException;
@@ -218,6 +220,11 @@
}
@Override
+ public ChangeInfo rebaseAsInfo(RebaseInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public boolean canRebase() throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index 190a97e..7ed2f95 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -83,7 +83,8 @@
* 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}
+ * com.google.gerrit.server.restapi.change.CherryPickCommit}, {@link
+ * com.google.gerrit.server.restapi.change.Rebase}
*/
public Boolean containsGitConflicts;
diff --git a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 04d2e8ae..36d48033 100644
--- a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -282,7 +282,16 @@
@Override
public ChangeApi rebase(RebaseInput in) throws RestApiException {
try {
- return changes.id(rebase.apply(revision, in).value()._number);
+ return changes.id(rebaseAsInfo(in)._number);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot rebase ps", e);
+ }
+ }
+
+ @Override
+ public ChangeInfo rebaseAsInfo(RebaseInput in) throws RestApiException {
+ try {
+ return rebase.apply(revision, in).value();
} catch (Exception e) {
throw asRestApiException("Cannot rebase ps", e);
}
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
index 231359b..a548262 100644
--- a/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -15,8 +15,11 @@
package com.google.gerrit.server.change;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
@@ -26,6 +29,8 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.RebaseUtil.Base;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
import com.google.gerrit.server.git.GroupCollector;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -41,13 +46,27 @@
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jgit.diff.Sequence;
+import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.merge.MergeResult;
+import org.eclipse.jgit.merge.ResolveMerger;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+/**
+ * BatchUpdate operation that rebases a change.
+ *
+ * <p>Can only be executed in a {@link com.google.gerrit.server.update.BatchUpdate} set has a {@link
+ * CodeReviewRevWalk} set as {@link RevWalk} (set via {@link
+ * com.google.gerrit.server.update.BatchUpdate#setRepository(org.eclipse.jgit.lib.Repository,
+ * RevWalk, org.eclipse.jgit.lib.ObjectInserter)}).
+ */
public class RebaseChangeOp implements BatchUpdateOp {
public interface Factory {
RebaseChangeOp create(ChangeNotes notes, PatchSet originalPatchSet, ObjectId baseCommitId);
@@ -69,12 +88,13 @@
private boolean validate = true;
private boolean checkAddPatchSetPermission = true;
private boolean forceContentMerge;
+ private boolean allowConflicts;
private boolean detailedCommitMessage;
private boolean postMessage = true;
private boolean sendEmail = true;
private boolean matchAuthorToCommitterDate = false;
- private RevCommit rebasedCommit;
+ private CodeReviewCommit rebasedCommit;
private PatchSet.Id rebasedPatchSetId;
private PatchSetInserter patchSetInserter;
private PatchSet rebasedPatchSet;
@@ -126,6 +146,19 @@
return this;
}
+ /**
+ * Allows the rebase to succeed if there are conflicts.
+ *
+ * <p>This setting requires that {@link #forceContentMerge} is set {@code true}. If {@link
+ * #forceContentMerge} is {@code false} this setting has no effect.
+ *
+ * @see #setForceContentMerge(boolean)
+ */
+ public RebaseChangeOp setAllowConflicts(boolean allowConflicts) {
+ this.allowConflicts = allowConflicts;
+ return this;
+ }
+
public RebaseChangeOp setDetailedCommitMessage(boolean detailedCommitMessage) {
this.detailedCommitMessage = detailedCommitMessage;
return this;
@@ -186,14 +219,11 @@
.setFireRevisionCreated(fireRevisionCreated)
.setCheckAddPatchSetPermission(checkAddPatchSetPermission)
.setValidate(validate)
- .setSendEmail(sendEmail);
+ .setSendEmail(sendEmail)
+ .setWorkInProgress(!rebasedCommit.getFilesWithGitConflicts().isEmpty());
if (postMessage) {
patchSetInserter.setMessage(
- "Patch Set "
- + rebasedPatchSetId.get()
- + ": Patch Set "
- + originalPatchSet.id().get()
- + " was rebased");
+ messageForRebasedChange(rebasedPatchSetId, originalPatchSet.id(), rebasedCommit));
}
if (base != null && !base.notes().getChange().isMerged()) {
@@ -208,6 +238,24 @@
patchSetInserter.updateRepo(ctx);
}
+ private static String messageForRebasedChange(
+ PatchSet.Id rebasePatchSetId, PatchSet.Id originalPatchSetId, CodeReviewCommit commit) {
+ StringBuilder stringBuilder =
+ new StringBuilder(
+ String.format(
+ "Patch Set %d: Patch Set %d was rebased",
+ rebasePatchSetId.get(), originalPatchSetId.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();
+ }
+
@Override
public boolean updateChange(ChangeContext ctx)
throws ResourceConflictException, IOException, BadRequestException {
@@ -221,7 +269,7 @@
patchSetInserter.postUpdate(ctx);
}
- public RevCommit getRebasedCommit() {
+ public CodeReviewCommit getRebasedCommit() {
checkState(rebasedCommit != null, "getRebasedCommit() only valid after updateRepo");
return rebasedCommit;
}
@@ -254,7 +302,7 @@
* @throws MergeConflictException the rebase failed due to a merge conflict.
* @throws IOException the merge failed for another reason.
*/
- private RevCommit rebaseCommit(
+ private CodeReviewCommit rebaseCommit(
RepoContext ctx, RevCommit original, ObjectId base, String commitMessage)
throws ResourceConflictException, IOException {
RevCommit parentCommit = original.getParent(0);
@@ -266,15 +314,56 @@
ThreeWayMerger merger =
newMergeUtil().newThreeWayMerger(ctx.getInserter(), ctx.getRepoView().getConfig());
merger.setBase(parentCommit);
+
+ DirCache dc = DirCache.newInCore();
+ if (allowConflicts && merger instanceof ResolveMerger) {
+ // The DirCache must be set on ResolveMerger before calling
+ // ResolveMerger#merge(AnyObjectId...) otherwise the entries in DirCache don't get populated.
+ ((ResolveMerger) merger).setDirCache(dc);
+ }
+
boolean success = merger.merge(original, base);
- if (!success || merger.getResultTreeId() == null) {
- throw new MergeConflictException(
- "The change could not be rebased due to a conflict during merge.");
+ ObjectId tree;
+ ImmutableSet<String> filesWithGitConflicts;
+ if (success) {
+ filesWithGitConflicts = null;
+ tree = merger.getResultTreeId();
+ } else {
+ List<String> conflicts = ImmutableList.of();
+ if (merger instanceof ResolveMerger) {
+ conflicts = ((ResolveMerger) merger).getUnmergedPaths();
+ }
+
+ if (!allowConflicts || !(merger instanceof ResolveMerger)) {
+ throw new MergeConflictException(
+ "The change could not be rebased due to a conflict during merge.\n\n"
+ + MergeUtil.createConflictMessage(conflicts));
+ }
+
+ Map<String, MergeResult<? extends Sequence>> mergeResults =
+ ((ResolveMerger) merger).getMergeResults();
+
+ filesWithGitConflicts =
+ mergeResults.entrySet().stream()
+ .filter(e -> e.getValue().containsConflicts())
+ .map(Map.Entry::getKey)
+ .collect(toImmutableSet());
+
+ tree =
+ MergeUtil.mergeWithConflicts(
+ ctx.getRevWalk(),
+ ctx.getInserter(),
+ dc,
+ "PATCH SET",
+ original,
+ "BASE",
+ ctx.getRevWalk().parseCommit(base),
+ mergeResults);
}
CommitBuilder cb = new CommitBuilder();
- cb.setTreeId(merger.getResultTreeId());
+ cb.setTreeId(tree);
cb.setParentId(base);
cb.setAuthor(original.getAuthorIdent());
cb.setMessage(commitMessage);
@@ -290,6 +379,8 @@
}
ObjectId objectId = ctx.getInserter().insert(cb);
ctx.getInserter().flush();
- return ctx.getRevWalk().parseCommit(objectId);
+ CodeReviewCommit commit = ((CodeReviewRevWalk) ctx.getRevWalk()).parseCommit(objectId);
+ commit.setFilesWithGitConflicts(filesWithGitConflicts);
+ return commit;
}
}
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index 8666f26..fd1a017 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -488,6 +488,10 @@
}
public static String createConflictMessage(List<String> conflicts) {
+ if (conflicts.isEmpty()) {
+ return "";
+ }
+
StringBuilder sb = new StringBuilder("merge conflict(s):");
for (String c : conflicts) {
sb.append('\n').append(c);
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index 5753874..a3e0cf0 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -334,7 +334,7 @@
input.allowEmpty,
input.allowConflicts);
} catch (MergeIdenticalTreeException | MergeConflictException e) {
- throw new IntegrationConflictException("Cherry pick failed: " + e.getMessage());
+ throw new IntegrationConflictException("Cherry pick failed: " + e.getMessage(), e);
}
try (BatchUpdate bu = batchUpdateFactory.create(project, identifiedUser, timestamp)) {
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 75ba4c1..cfdf04d 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.change.RebaseUtil;
import com.google.gerrit.server.change.RebaseUtil.Base;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -115,7 +116,7 @@
try (Repository repo = repoManager.openRepository(change.getProject());
ObjectInserter oi = repo.newObjectInserter();
ObjectReader reader = oi.newReader();
- RevWalk rw = new RevWalk(reader);
+ RevWalk rw = CodeReviewCommit.newRevWalk(reader);
BatchUpdate bu =
updateFactory.create(change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
if (!change.isNew()) {
@@ -124,18 +125,23 @@
throw new ResourceConflictException(
"cannot rebase merge commits or commit with no ancestor");
}
- // TODO(dborowitz): Why no notification? This seems wrong; dig up blame.
- bu.setNotify(NotifyResolver.Result.none());
- bu.setRepository(repo, rw, oi);
- bu.addOp(
- change.getId(),
+ RebaseChangeOp rebaseOp =
rebaseFactory
.create(rsrc.getNotes(), rsrc.getPatchSet(), findBaseRev(repo, rw, rsrc, input))
.setForceContentMerge(true)
- .setFireRevisionCreated(true));
+ .setAllowConflicts(input.allowConflicts)
+ .setFireRevisionCreated(true);
+ // TODO(dborowitz): Why no notification? This seems wrong; dig up blame.
+ bu.setNotify(NotifyResolver.Result.none());
+ bu.setRepository(repo, rw, oi);
+ bu.addOp(change.getId(), rebaseOp);
bu.execute();
+
+ ChangeInfo changeInfo = json.create(OPTIONS).format(change.getProject(), change.getId());
+ changeInfo.containsGitConflicts =
+ !rebaseOp.getRebasedCommit().getFilesWithGitConflicts().isEmpty() ? true : null;
+ return Response.ok(changeInfo);
}
- return Response.ok(json.create(OPTIONS).format(change.getProject(), change.getId()));
}
private ObjectId findBaseRev(
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java
similarity index 77%
rename from java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
rename to java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java
index 783b39b..904a16f 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoCreator.java
@@ -21,6 +21,10 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
+import com.google.gerrit.extensions.api.projects.ConfigInfo.ConfigParameterInfo;
+import com.google.gerrit.extensions.api.projects.ConfigInfo.InheritedBooleanInfo;
+import com.google.gerrit.extensions.api.projects.ConfigInfo.MaxObjectSizeLimitInfo;
+import com.google.gerrit.extensions.api.projects.ConfigInfo.SubmitTypeInfo;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -42,9 +46,12 @@
import java.util.Map;
import java.util.TreeMap;
-public class ConfigInfoImpl extends ConfigInfo {
+public class ConfigInfoCreator {
+ /** do not instantiate this class. */
+ private ConfigInfoCreator() {}
+
@SuppressWarnings("deprecation")
- public ConfigInfoImpl(
+ public static ConfigInfo constructInfo(
boolean serverEnableSignedPush,
ProjectState projectState,
CurrentUser user,
@@ -53,8 +60,9 @@
AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
+ ConfigInfo configInfo = new ConfigInfo();
Project p = projectState.getProject();
- this.description = Strings.emptyToNull(p.getDescription());
+ configInfo.description = Strings.emptyToNull(p.getDescription());
ProjectState parentState = Iterables.getFirst(projectState.parents(), null);
for (BooleanProjectConfig cfg : BooleanProjectConfig.values()) {
@@ -63,48 +71,51 @@
if (parentState != null) {
info.inheritedValue = parentState.is(cfg);
}
- BooleanProjectConfigTransformations.set(cfg, this, info);
+ BooleanProjectConfigTransformations.set(cfg, configInfo, info);
}
if (!serverEnableSignedPush) {
- this.enableSignedPush = null;
- this.requireSignedPush = null;
+ configInfo.enableSignedPush = null;
+ configInfo.requireSignedPush = null;
}
- this.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, p);
+ configInfo.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, p);
- this.defaultSubmitType = new SubmitTypeInfo();
- this.defaultSubmitType.value = projectState.getSubmitType();
- this.defaultSubmitType.configuredValue =
+ configInfo.defaultSubmitType = new SubmitTypeInfo();
+ configInfo.defaultSubmitType.value = projectState.getSubmitType();
+ configInfo.defaultSubmitType.configuredValue =
MoreObjects.firstNonNull(
projectState.getConfig().getProject().getSubmitType(), Project.DEFAULT_SUBMIT_TYPE);
ProjectState parent =
projectState.isAllProjects() ? projectState : projectState.parents().get(0);
- this.defaultSubmitType.inheritedValue = parent.getSubmitType();
+ configInfo.defaultSubmitType.inheritedValue = parent.getSubmitType();
- this.submitType = this.defaultSubmitType.value;
+ configInfo.submitType = configInfo.defaultSubmitType.value;
- this.state =
+ configInfo.state =
p.getState() != com.google.gerrit.extensions.client.ProjectState.ACTIVE
? p.getState()
: null;
- this.commentlinks = new LinkedHashMap<>();
+ configInfo.commentlinks = new LinkedHashMap<>();
for (CommentLinkInfo cl : projectState.getCommentLinks()) {
- this.commentlinks.put(cl.name, cl);
+ configInfo.commentlinks.put(cl.name, cl);
}
- pluginConfig = getPluginConfig(projectState, pluginConfigEntries, cfgFactory, allProjects);
+ configInfo.pluginConfig =
+ getPluginConfig(projectState, pluginConfigEntries, cfgFactory, allProjects);
- actions = new TreeMap<>();
+ configInfo.actions = new TreeMap<>();
for (UiAction.Description d : uiActions.from(views, new ProjectResource(projectState, user))) {
- actions.put(d.getId(), new ActionInfo(d));
+ configInfo.actions.put(d.getId(), new ActionInfo(d));
}
- this.extensionPanelNames = projectState.getConfig().getExtensionPanelSections();
+ configInfo.extensionPanelNames = projectState.getConfig().getExtensionPanelSections();
+ return configInfo;
}
- private MaxObjectSizeLimitInfo getMaxObjectSizeLimit(ProjectState projectState, Project p) {
+ private static MaxObjectSizeLimitInfo getMaxObjectSizeLimit(
+ ProjectState projectState, Project p) {
MaxObjectSizeLimitInfo info = new MaxObjectSizeLimitInfo();
EffectiveMaxObjectSizeLimit limit = projectState.getEffectiveMaxObjectSizeLimit();
long value = limit.value;
@@ -114,7 +125,7 @@
return info;
}
- private Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
+ private static Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
ProjectState project,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
@@ -162,7 +173,7 @@
return !pluginConfig.isEmpty() ? pluginConfig : null;
}
- private String getInheritedValue(
+ private static String getInheritedValue(
ProjectState project, PluginConfigFactory cfgFactory, Extension<ProjectConfigEntry> e) {
ProjectConfigEntry configEntry = e.getProvider().get();
ProjectState parent = Iterables.getFirst(project.parents(), null);
diff --git a/java/com/google/gerrit/server/restapi/project/GetConfig.java b/java/com/google/gerrit/server/restapi/project/GetConfig.java
index ad66587..8ffd5ec 100644
--- a/java/com/google/gerrit/server/restapi/project/GetConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/GetConfig.java
@@ -67,7 +67,7 @@
.project(resource.getNameKey())
.test(ProjectPermission.READ_CONFIG);
return Response.ok(
- new ConfigInfoImpl(
+ ConfigInfoCreator.constructInfo(
serverEnableSignedPush,
resource.getProjectState(),
resource.getUser(),
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index 55ea312..afa08cd 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -176,7 +176,7 @@
}
ProjectState state = projectStateFactory.create(projectConfigFactory.read(md).getCacheable());
- return new ConfigInfoImpl(
+ return ConfigInfoCreator.constructInfo(
serverEnableSignedPush,
state,
user.get(),
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 4015ccb..ecfdd8b 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -47,6 +47,7 @@
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.REVIEWER;
+import static com.google.gerrit.git.ObjectIds.abbreviateName;
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.CHANGE_OWNER;
@@ -142,8 +143,10 @@
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
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.BadRequestException;
+import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -178,6 +181,7 @@
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.name.Named;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -1340,9 +1344,102 @@
"If09d8782c1e59dd0b33de2b1ec3595d69cc10ad5");
PushOneCommit.Result r2 = push.to("refs/for/master");
r2.assertOkStatus();
- assertThrows(
- ResourceConflictException.class,
- () -> gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).rebase());
+ ResourceConflictException exception =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(r2.getChangeId()).revision(r2.getCommit().name()).rebase());
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ String.format(
+ "The change could not be rebased due to a conflict during merge.\n\n"
+ + "merge conflict(s):\n%s",
+ PushOneCommit.FILE_NAME));
+ }
+
+ @Test
+ public void rebaseConflict_conflictsAllowed() throws Exception {
+ String patchSetSubject = "patch set change";
+ String patchSetContent = "patch set content";
+ String baseSubject = "base change";
+ String baseContent = "base content";
+
+ PushOneCommit.Result r1 = createChange(baseSubject, PushOneCommit.FILE_NAME, baseContent);
+ gApi.changes()
+ .id(r1.getChangeId())
+ .revision(r1.getCommit().name())
+ .review(ReviewInput.approve());
+ gApi.changes().id(r1.getChangeId()).revision(r1.getCommit().name()).submit();
+
+ testRepo.reset("HEAD~1");
+ PushOneCommit push =
+ pushFactory.create(
+ admin.newIdent(), testRepo, patchSetSubject, PushOneCommit.FILE_NAME, patchSetContent);
+ PushOneCommit.Result r2 = push.to("refs/for/master");
+ r2.assertOkStatus();
+
+ String changeId = r2.getChangeId();
+ RevCommit patchSet = r2.getCommit();
+ RevCommit base = r1.getCommit();
+
+ TestWorkInProgressStateChangedListener wipStateChangedListener =
+ new TestWorkInProgressStateChangedListener();
+ try (Registration registration =
+ extensionRegistry.newRegistration().add(wipStateChangedListener)) {
+ RebaseInput rebaseInput = new RebaseInput();
+ rebaseInput.allowConflicts = true;
+ ChangeInfo changeInfo =
+ gApi.changes().id(changeId).revision(patchSet.name()).rebaseAsInfo(rebaseInput);
+ 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(base.name());
+
+ // 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(PushOneCommit.FILE_NAME).content();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bin.writeTo(os);
+ String fileContent = new String(os.toByteArray(), UTF_8);
+ String patchSetSha1 = abbreviateName(patchSet, 6);
+ String baseSha1 = abbreviateName(base, 6);
+ assertThat(fileContent)
+ .isEqualTo(
+ "<<<<<<< PATCH SET ("
+ + patchSetSha1
+ + " "
+ + patchSetSubject
+ + ")\n"
+ + patchSetContent
+ + "\n"
+ + "=======\n"
+ + baseContent
+ + "\n"
+ + ">>>>>>> BASE ("
+ + baseSha1
+ + " "
+ + baseSubject
+ + ")\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(
+ "Patch Set 2: Patch Set 1 was rebased\n\n"
+ + "The following files contain Git conflicts:\n"
+ + "* "
+ + PushOneCommit.FILE_NAME
+ + "\n");
}
@Test
@@ -4354,4 +4451,17 @@
private interface AddReviewerCaller {
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;
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 2891d4b..cbfbab6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -228,7 +228,9 @@
"Cannot rebase "
+ change2hash
+ ": The change could "
- + "not be rebased due to a conflict during merge.");
+ + "not be rebased due to a conflict during merge.\n\n"
+ + "merge conflict(s):\n"
+ + "a.txt");
break;
case MERGE_ALWAYS:
case MERGE_IF_NECESSARY:
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
index 5eb19df..81c098f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -236,7 +236,9 @@
change2.getChangeId(),
"Cannot rebase "
+ change2.getCommit().name()
- + ": The change could not be rebased due to a conflict during merge.");
+ + ": The change could not be rebased due to a conflict during merge.\n\n"
+ + "merge conflict(s):\n"
+ + "a.txt");
RevCommit head = projectOperations.project(project).getHead("master");
assertThat(head).isEqualTo(headAfterFirstSubmit);
assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
@@ -363,7 +365,9 @@
"Cannot rebase "
+ change2.getCommit().getName()
+ ": "
- + "The change could not be rebased due to a conflict during merge.");
+ + "The change could not be rebased due to a conflict during merge.\n\n"
+ + "merge conflict(s):\n"
+ + "fileName 2");
assertThat(projectOperations.project(project).getHead("master")).isEqualTo(headAfterChange1);
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index 9574c1d..601ea80 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -716,19 +716,10 @@
}
if (lineNumberEl) {
- const ANNOTATE_MAX_LINE_LENGTH = 1000;
- // For performance reason, we skip annotating long lines.
- if (line.text.length < ANNOTATE_MAX_LINE_LENGTH) {
- for (const layer of this.layers) {
- if (typeof layer.annotate === 'function') {
- layer.annotate(contentText, lineNumberEl, line);
- }
+ for (const layer of this.layers) {
+ if (typeof layer.annotate === 'function') {
+ layer.annotate(contentText, lineNumberEl, line);
}
- } else {
- const msg =
- `A line is longer than ${ANNOTATE_MAX_LINE_LENGTH}.` +
- ' Line annotation was skipped.';
- console.warn(msg);
}
} else {
console.error('The lineNumberEl is null, skipping layer annotations.');