Merge "RebaseUtil: Support base revisions which are part of the dest branch"
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index a6babb9..1ad0270 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -228,7 +228,7 @@
[NOTE]
Project owners can be allowed to change the parent of projects that they own
-(see config-gerrit.html#receive.allowProjectOwnersToChangeParent[
+(see link:config-gerrit.html#receive.allowProjectOwnersToChangeParent[
receive.allowProjectOwnersToChangeParent] setting which is `false` by default).
In this case project owners may escape the settings that are enforced by their
parent project by choosing a different parent project.
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt
index 97b58af..15f27db 100644
--- a/Documentation/intro-project-owner.txt
+++ b/Documentation/intro-project-owner.txt
@@ -44,7 +44,7 @@
To see the access rights of your project
- go to the Gerrit Web UI
-- click on the `Projects` > `List` menu entry
+- click on the `BROWSE` > `Repositories` menu entry
- find your project in the project list and click on it
- click on the `Access` menu entry
@@ -150,7 +150,7 @@
group backends.
The Gerrit internal groups can be seen in the Gerrit Web UI by clicking
-on the `Groups` > `List` menu entry. By clicking on a group you can
+on the `BROWSE` > `Groups` menu entry. By clicking on a group you can
edit the group members (`Members` tab) and the group options
(`General` tab).
@@ -168,8 +168,9 @@
`Make group visible to all registered users.`, which defines whether
non-members can see who is member of the group.
-New internal Gerrit groups can be created under `Groups` >
-`Create New Group`. This menu is only available if you have the global
+New internal Gerrit groups can be created under `BROWSE` > `Groups`
+and then clicking on the `CREATE NEW` button in the upper right corner.
+The `CREATE NEW` button is only available if you have the global
capability link:access-control.html#capability_createGroup[Create Group]
assigned.
@@ -238,7 +239,7 @@
To see the options of your project:
. Go to the Gerrit Web UI.
-. Click on the `Projects` > `List` menu entry.
+. Click on the `BROWSE` > `Repositories` menu entry.
. Find your project in the project list and click it.
. Click the `General` menu entry.
@@ -431,7 +432,7 @@
== Branch Administration
As project owner you can administrate the branches of your project in
-the Gerrit Web UI under `Projects` > `List` > <your project> >
+the Gerrit Web UI under `BROWSE` > `Repositories` > <your project> >
`Branches`. In the Web UI link:project-configuration.html#branch-creation[
branch creation] is allowed if you have
link:access-control.html#category_create[Create Reference] access right and
@@ -450,7 +451,7 @@
With Gerrit individual users control their own email subscriptions. By
editing the link:user-notify.html#user[watched projects] in the Web UI
-under `Settings` > `Watched Projects` users can decide which events to
+under `Settings` > `Notifications` users can decide which events to
be informed about by email. The change notifications can be filtered by
link:user-search.html[change search expressions].
@@ -476,8 +477,8 @@
link:user-dashboards.html#project-dashboards[project level]. This way
you can define a view of the changes that are relevant for your
project and share this dashboard with all users. The project dashboards
-can be seen in the Web UI under `Projects` > `List` > <your project> >
-`Dashboards`.
+can be seen in the Web UI under `BROWSE` > `Repositories` > <your project>
+> `Dashboards`.
[[issue-tracker-integration]]
== Issue Tracker Integration
@@ -509,7 +510,7 @@
to Gerrit changes to the issues in the issue tracker system or to
automatically close an issue if the corresponding change is merged.
If installed, project owners may enable/disable the issue tracker
-integration from the Gerrit Web UI under `Projects` > `Lists` >
+integration from the Gerrit Web UI under `BROWSE` > `Repositories` >
<your project> > `General`.
[[comment-links]]
@@ -554,7 +555,7 @@
With the link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers[
reviewers,role=external,window=_blank] plugin it is possible to configure default reviewers who
will be automatically added to each change. The default reviewers can
-be configured in the Gerrit Web UI under `Projects` > `List` >
+be configured in the Gerrit Web UI under `BROWSE` > `Repositories` >
<your project> > `General` in the `reviewers Plugin` section.
The link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers-by-blame[
@@ -565,7 +566,7 @@
touched by the change, since these users should be familiar with the
code and can most likely review the change. How many reviewers the
plugin will add to a change at most can be configured in the Gerrit
-Web UI under `Projects` > `List` > <your project> > `General` in the
+Web UI under `BROWSE` > `Repositories` > <your project> > `General` in the
`reviewers-by-blame Plugin` section.
[[download-commands]]
@@ -650,9 +651,10 @@
[[project-creation]]
=== Project Creation
-New projects can be created in the Gerrit Web UI under `Projects` >
-`Create Project`. The `Create Project` menu entry is only available if
-you have the link:access-control.html#capability_createProject[
+New projects can be created in the Gerrit Web UI under `BROWSE` >
+`Repositories` and then clicking on the `CREATE NEW` button in the
+upper right corner. The `CREATE NEW` button is only available if you
+have the link:access-control.html#capability_createProject[
Create Project] global capability assigned.
Projects can also be created via REST or SSH as described in the
@@ -736,7 +738,7 @@
If the link:https://gerrit-review.googlesource.com/admin/repos/plugins/delete-project[
delete-project,role=external,window=_blank] plugin is installed, projects can be deleted from the
-Gerrit Web UI under `Projects` > `List` > <project> > `General` by
+Gerrit Web UI under `BROWSE` > `Repositories` > <project> > `General` by
clicking on the `Delete` command under `Project Commands`. The `Delete`
command is only available if you have the `Delete Projects` global
capability assigned, or if you own the project and you have the
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 4c61073..108022a9 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -31,7 +31,7 @@
* link:https://github.com/uwolfer/gerrit-intellij-plugin[Gerrit
IntelliJ Plugin,role=external,window=_blank]: Gerrit integration with the
link:http://www.jetbrains.com/idea/[IntelliJ Platform,role=external,window=_blank]
-* link:https://opendev.org/ttygroup/gertty]: Console-based interface for Gerrit
+* link:https://opendev.org/ttygroup/gertty[gertty]: Console-based interface for Gerrit
[[clone]]
== Clone Gerrit Project
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index 55321d3..e290002 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -246,6 +246,7 @@
.collect(toCollection(ArrayList::new));
int cmItr = 0;
+ int lastMatch = 0;
for (CommentInfo comment : sortedCommentInfos) {
// Keep advancing the change message pointer until we associate the comment to the next change
// message in timestamp
@@ -256,6 +257,7 @@
|| (skipAutoGeneratedMessages && isAutoGenerated(cm))) {
cmItr += 1;
} else {
+ lastMatch = cmItr;
break;
}
}
@@ -265,11 +267,11 @@
// In case of no match "cmItr" will never be less than "changeMessages" size, hence the
// changeMessageId won't be set for any comment.
//
- // Reset the search from the beginning, since we can't assume there will always be a match
- // between change messages and comments. This could be the case of imported changes.
+ // Reset the search to the last succesful match, since we can't assume there will always be
+ // a match between change messages and comments. This could be the case of imported changes.
//
// More details here: https://issues.gerritcodereview.com/issues/318079520
- cmItr = 0;
+ cmItr = lastMatch;
}
}
}
diff --git a/java/com/google/gerrit/server/change/IncludedIn.java b/java/com/google/gerrit/server/change/IncludedIn.java
index 1faf121..f104a57 100644
--- a/java/com/google/gerrit/server/change/IncludedIn.java
+++ b/java/com/google/gerrit/server/change/IncludedIn.java
@@ -39,7 +39,6 @@
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -107,10 +106,7 @@
Stream<String> filteredTagsStream =
sortedShortNames(
filterReadableRefs(project, getMatchingRefNames(allMatchingTagsAndBranches, tags)));
- for (Iterator<PluginSetEntryContext<FilterIncludedIn>> pluginFilterIt =
- filterIncludedIn.iterator();
- pluginFilterIt.hasNext(); ) {
- PluginSetEntryContext<FilterIncludedIn> pluginFilter = pluginFilterIt.next();
+ for (PluginSetEntryContext<FilterIncludedIn> pluginFilter : filterIncludedIn) {
filteredBranchesStream =
filteredBranchesStream.filter(pluginFilter.get().getBranchFilter(project, rev));
filteredTagsStream =
diff --git a/java/com/google/gerrit/server/change/RebaseChangeOp.java b/java/com/google/gerrit/server/change/RebaseChangeOp.java
index e94f197..5daac75 100644
--- a/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -45,6 +45,7 @@
import com.google.gerrit.server.git.GroupCollector;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MergeUtilFactory;
+import com.google.gerrit.server.logging.CallerFinder;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.DiffNotAvailableException;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -98,6 +99,7 @@
private final RebaseUtil rebaseUtil;
private final ChangeResource.Factory changeResourceFactory;
private final ChangeNotes.Factory notesFactory;
+ private final CallerFinder callerFinder;
private final ChangeNotes notes;
private final PatchSet originalPatchSet;
@@ -199,6 +201,7 @@
this.notes = notes;
this.projectName = notes.getProjectName();
this.originalPatchSet = originalPatchSet;
+ this.callerFinder = CallerFinder.builder().addTarget(RebaseChangeOp.class).build();
}
@CanIgnoreReturnValue
@@ -378,6 +381,8 @@
}
}
+ logger.atFine().log(
+ "flushing inserter %s", ctx.getRevWalk().getObjectReader().getCreatedFromInserter());
ctx.getRevWalk().getObjectReader().getCreatedFromInserter().flush();
patchSetInserter.updateRepo(ctx);
}
@@ -494,6 +499,9 @@
if (success) {
filesWithGitConflicts = null;
tree = merger.getResultTreeId();
+ logger.atFine().log(
+ "tree of rebased commit: %s (no conflicts, inserter: %s, caller: %s)",
+ tree.name(), merger.getObjectInserter(), callerFinder.findCallerLazy());
} else {
List<String> conflicts = ImmutableList.of();
Map<String, ResolveMerger.MergeFailureReason> failed = ImmutableMap.of();
@@ -551,6 +559,9 @@
"BASE",
ctx.getRevWalk().parseCommit(base),
mergeResults);
+ logger.atFine().log(
+ "tree of rebased commit: %s (with conflicts, inserter: %s, caller: %s)",
+ tree.name(), ctx.getInserter(), callerFinder.findCallerLazy());
}
List<ObjectId> parents = new ArrayList<>();
@@ -581,11 +592,10 @@
new PersonIdent(
cb.getAuthor(), cb.getCommitter().getWhen(), cb.getCommitter().getTimeZone()));
}
- logger.atFine().log(
- "tree of rebased commit: %s (inserter: %s)", tree.name(), ctx.getInserter());
ObjectId objectId = ctx.getInserter().insert(cb);
CodeReviewCommit commit = ((CodeReviewRevWalk) ctx.getRevWalk()).parseCommit(objectId);
commit.setFilesWithGitConflicts(filesWithGitConflicts);
+ logger.atFine().log("rebased commit=%s", commit.name());
return commit;
}
}
diff --git a/java/com/google/gerrit/server/change/WalkSorter.java b/java/com/google/gerrit/server/change/WalkSorter.java
index 6c903b8..cc87ea8 100644
--- a/java/com/google/gerrit/server/change/WalkSorter.java
+++ b/java/com/google/gerrit/server/change/WalkSorter.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Ordering;
+import com.google.common.collect.SetMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.entities.PatchSet;
@@ -146,8 +147,8 @@
Set<RevCommit> commits = byCommit.keySet();
ListMultimap<RevCommit, RevCommit> children = collectChildren(commits);
- ListMultimap<RevCommit, RevCommit> pending =
- MultimapBuilder.hashKeys().arrayListValues().build();
+ SetMultimap<RevCommit, RevCommit> pending =
+ MultimapBuilder.hashKeys().hashSetValues().build();
Deque<RevCommit> todo = new ArrayDeque<>();
RevFlag done = rw.newFlag("done");
@@ -156,6 +157,7 @@
int found = 0;
RevCommit c;
List<PatchSetData> result = new ArrayList<>(expected);
+ int maxPopSize = commits.size() * commits.size();
while (found < expected && (c = rw.next()) != null) {
if (!commits.contains(c)) {
continue;
@@ -164,9 +166,15 @@
todo.add(c);
int i = 0;
while (!todo.isEmpty()) {
- // Sanity check: we can't pop more than N pending commits, otherwise
- // we have an infinite loop due to programmer error or something.
- checkState(++i <= commits.size(), "Too many pending steps while sorting %s", commits);
+ // Sanity check: in the worst case scenario, each commit can add all previous commits in
+ // the todo queue. This can lead to (n-1) + (n-2) + ... +1 iterations of the algorithm.
+ // So, in the worst case we can't pop more than N^2 pending commits, otherwise
+ // we have an infinite loop due to programmer error or something. (actually, it is
+ // (N-1) + (N-2) + (N-3) + (1) + 1 = N/2*(N-1)+1, but N^2 is enough for us.)
+ checkState(
+ ++i <= maxPopSize,
+ "Too many pending steps while sorting %s - can be a problem in the algorithm.",
+ commits);
RevCommit t = todo.removeFirst();
if (t.has(done)) {
continue;
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index 6364fc9..1adbb67 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -1055,6 +1055,12 @@
@Override
public void close() {}
+
+ @Override
+ public String toString() {
+ return String.format(
+ "%s (wrapped inserter: %s)", super.toString(), inserter.toString());
+ }
},
repoConfig);
}
diff --git a/java/com/google/gerrit/server/git/WorkQueue.java b/java/com/google/gerrit/server/git/WorkQueue.java
index 444b5fe..c5e2338 100644
--- a/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/java/com/google/gerrit/server/git/WorkQueue.java
@@ -91,8 +91,8 @@
private final WorkQueue workQueue;
@Inject
- Lifecycle(WorkQueue workQeueue) {
- this.workQueue = workQeueue;
+ Lifecycle(WorkQueue workQueue) {
+ this.workQueue = workQueue;
}
@Override
diff --git a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
index 55639e9..5a439f8 100644
--- a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
@@ -163,7 +163,7 @@
if (denyrcpt.contains(address)
|| denyrcpt.contains(domain)
|| denyrcpt.contains("@" + domain)) {
- logger.atWarning().log("Not emailing %s (prohibited by sendemail.denyrcpt)", address);
+ logger.atInfo().log("Not emailing %s (prohibited by sendemail.denyrcpt)", address);
return true;
}
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java b/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java
index 185a6ac..56b3842 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyPatchUtil.java
@@ -215,8 +215,15 @@
}
private static String decodeIfNecessary(String patch) {
- if (Base64.isBase64(patch)) {
- return new String(org.eclipse.jgit.util.Base64.decode(patch), UTF_8);
+ if (Base64.isBase64(patch.getBytes(UTF_8))) {
+ try {
+ return new String(org.eclipse.jgit.util.Base64.decode(patch), UTF_8);
+ } catch (IllegalArgumentException e) {
+ // It's possible that all the chars in the patch are valid Base64 chars, but the full string
+ // is not a valid Base64 string as expected by jGit. In this case, we assume the patch is
+ // already unencoded.
+ return patch;
+ }
}
return patch;
}
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index 713417d..d9af527 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -476,17 +476,24 @@
actionType,
opts,
t -> {
+ String actionName = opts.actionName().orElse("N/A");
+ String cause = formatCause(t);
+
// exceptionPredicate checks for temporary errors for which the operation should be
// retried (e.g. LockFailure). The retry has good chances to succeed.
if (exceptionPredicate.test(t)) {
+ logger.atFine().withCause(t).log(
+ "Retry: %s failed with possibly temporary error (cause = %s)",
+ actionName, cause);
return true;
}
- String actionName = opts.actionName().orElse("N/A");
-
// Exception hooks may identify additional exceptions for retry.
if (exceptionHooks.stream()
.anyMatch(h -> h.shouldRetry(actionType, actionName, t))) {
+ logger.atFine().withCause(t).log(
+ "Retry: %s failed with possibly temporary error (cause = %s)",
+ actionName, cause);
return true;
}
@@ -502,7 +509,6 @@
return false;
}
- String cause = formatCause(t);
if (!TraceContext.isTracing()) {
String traceId = "retry-on-failure-" + new RequestId();
traceContext.addTag(RequestId.Type.TRACE_ID, traceId).forceLogging();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java b/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
index c558a70..78361a1 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ApplyPatchIT.java
@@ -220,6 +220,25 @@
}
@Test
+ public void applyDecodedPatchConsistsOfBase64CharsOnly_success() throws Exception {
+ final String deletedFileName = "file_to_be_deleted";
+ final String deletedFileOriginalContent =
+ "The deletion patch of this file only contain valid Base64 chars.\n"
+ + "However, the patch is not Base64-encoded.\n";
+ final String deletedFileDiff =
+ "diff --git a/file_to_be_deleted b/file_to_be_deleted\n"
+ + "--- a/file_to_be_deleted\n"
+ + "+++ /dev/null\n";
+ initBaseWithFile(deletedFileName, deletedFileOriginalContent);
+ ApplyPatchPatchSetInput in = buildInput(deletedFileDiff);
+
+ ChangeInfo result = applyPatch(in);
+
+ DiffInfo diff = fetchDiffForFile(result, deletedFileName);
+ assertDiffForDeletedFile(diff, deletedFileName, deletedFileOriginalContent);
+ }
+
+ @Test
public void applyGerritBasedPatchWithSingleFile_success() throws Exception {
String head = getHead(repo(), HEAD).name();
createBranchWithRevision(BranchNameKey.create(project, "branch"), head);
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImplTest.java
index 5bdf91f..44fc4bb 100644
--- a/javatests/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImplTest.java
+++ b/javatests/com/google/gerrit/acceptance/testsuite/change/ChangeOperationsImplTest.java
@@ -22,7 +22,6 @@
import static com.google.gerrit.extensions.restapi.testing.BinaryResultSubject.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.MapSubject.assertThatMap;
-import static com.google.gerrit.truth.OptionalSubject.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -49,6 +48,7 @@
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.truth.NullAwareCorrespondence;
+import com.google.gerrit.truth.OptionalSubject;
import com.google.inject.Inject;
import java.util.Map;
import org.eclipse.jgit.lib.ObjectId;
@@ -1629,7 +1629,7 @@
TestHumanComment comment = changeOperations.change(changeId).comment(childCommentUuid).get();
- assertThat(comment.parentUuid()).value().isEqualTo(parentCommentUuid);
+ OptionalSubject.assertThat(comment.parentUuid()).value().isEqualTo(parentCommentUuid);
}
@Test
@@ -1640,7 +1640,7 @@
TestHumanComment comment = changeOperations.change(changeId).comment(childCommentUuid).get();
- assertThat(comment.tag()).value().isEqualTo("tag");
+ OptionalSubject.assertThat(comment.tag()).value().isEqualTo("tag");
}
@Test
@@ -1700,7 +1700,7 @@
TestHumanComment comment =
changeOperations.change(changeId).draftComment(childCommentUuid).get();
- assertThat(comment.parentUuid()).value().isEqualTo(parentCommentUuid);
+ OptionalSubject.assertThat(comment.parentUuid()).value().isEqualTo(parentCommentUuid);
}
@Test
@@ -1730,7 +1730,7 @@
TestRobotComment comment =
changeOperations.change(changeId).robotComment(childCommentUuid).get();
- assertThat(comment.parentUuid()).value().isEqualTo(parentCommentUuid);
+ OptionalSubject.assertThat(comment.parentUuid()).value().isEqualTo(parentCommentUuid);
}
private ChangeInfo getChangeFromServer(Change.Id changeId) throws RestApiException {
diff --git a/javatests/com/google/gerrit/server/change/WalkSorterTest.java b/javatests/com/google/gerrit/server/change/WalkSorterTest.java
index 7775452..0d000a5 100644
--- a/javatests/com/google/gerrit/server/change/WalkSorterTest.java
+++ b/javatests/com/google/gerrit/server/change/WalkSorterTest.java
@@ -86,6 +86,114 @@
}
@Test
+ public void seriesOfMergeChangesInSpecialOrder() throws Exception {
+ TestRepository<Repo> p = newRepo("p");
+ // For this test, the RevWalk in the sortProject must return commits in the following order:
+ // c3, c1, c2, c4.
+ // To achieve this order, the commit timestamps must be set in a specific order - RevWalk
+ // returns them sorted by timestamp, starting from the newest one.
+
+ // timestamp: base + 3
+ RevCommit c1 = p.commit().tick(3).create();
+ // timestamp: base + 2
+ RevCommit c2 = p.commit().tick(-1).parent(c1).create();
+ // timestamp: base + 4
+ RevCommit c3 = p.commit().tick(2).parent(c1).parent(c2).create();
+ // timestamp: base + 1
+ RevCommit c4 = p.commit().tick(-3).parent(c3).create();
+
+ ChangeData cd1 = newChange(p, c1);
+ ChangeData cd2 = newChange(p, c2);
+ ChangeData cd3 = newChange(p, c3);
+ ChangeData cd4 = newChange(p, c4);
+
+ List<ChangeData> changes = ImmutableList.of(cd1, cd2, cd3, cd4);
+ WalkSorter sorter = new WalkSorter(repoManager);
+
+ assertSorted(
+ sorter,
+ changes,
+ ImmutableList.of(
+ patchSetData(cd4, c4),
+ patchSetData(cd3, c3),
+ patchSetData(cd2, c2),
+ patchSetData(cd1, c1)));
+ }
+
+ @Test
+ public void seriesOfMergeChangesWorstCaseForTopoSorting() throws Exception {
+ TestRepository<Repo> p = newRepo("p");
+ // For this test, the RevWalk in the sortProject must return commits in the following order:
+ // c1, c2, c3, c4, c5, c6, c7.
+ // To achieve this order, the commit timestamps must be set in a specific order - RevWalk
+ // returns them sorted by timestamp, starting from the newest one.
+
+ // timestamp: base + 8
+ RevCommit c1 = p.commit().tick(8).create();
+ // timestamp: base + 7
+ RevCommit c2 = p.commit().tick(-1).parent(c1).create();
+ // timestamp: base + 6
+ RevCommit c3 = p.commit().tick(-1).parent(c1).parent(c2).create();
+ // timestamp: base + 5
+ RevCommit c4 = p.commit().tick(-1).parent(c1).parent(c2).parent(c3).create();
+ // timestamp: base + 4
+ RevCommit c5 = p.commit().tick(-1).parent(c1).parent(c2).parent(c3).parent(c4).create();
+ // timestamp: base + 3
+ RevCommit c6 =
+ p.commit().tick(-1).parent(c1).parent(c2).parent(c3).parent(c4).parent(c5).create();
+ // timestamp: base + 2
+ RevCommit c7 =
+ p.commit()
+ .tick(-1)
+ .parent(c1)
+ .parent(c2)
+ .parent(c3)
+ .parent(c4)
+ .parent(c5)
+ .parent(c6)
+ .create();
+ // timestamp: base + 1
+ RevCommit c8 =
+ p.commit()
+ .tick(-1)
+ .parent(c1)
+ .parent(c2)
+ .parent(c3)
+ .parent(c4)
+ .parent(c5)
+ .parent(c6)
+ .parent(c7)
+ .create();
+
+ ChangeData cd1 = newChange(p, c1);
+ ChangeData cd2 = newChange(p, c2);
+ ChangeData cd3 = newChange(p, c3);
+ ChangeData cd4 = newChange(p, c4);
+ ChangeData cd5 = newChange(p, c5);
+ ChangeData cd6 = newChange(p, c6);
+ ChangeData cd7 = newChange(p, c7);
+ ChangeData cd8 = newChange(p, c8);
+
+ List<ChangeData> changes = ImmutableList.of(cd1, cd2, cd3, cd4, cd5, cd6, cd7, cd8);
+ WalkSorter sorter = new WalkSorter(repoManager);
+
+ // Do not use assertSorted because it tests all permutations. We don't need it for this test
+ // and total number of permutations is quite big.
+ assertThat(sorter.sort(changes))
+ .containsExactlyElementsIn(
+ ImmutableList.of(
+ patchSetData(cd8, c8),
+ patchSetData(cd7, c7),
+ patchSetData(cd6, c6),
+ patchSetData(cd5, c5),
+ patchSetData(cd4, c4),
+ patchSetData(cd3, c3),
+ patchSetData(cd2, c2),
+ patchSetData(cd1, c1)))
+ .inOrder();
+ }
+
+ @Test
public void subsetOfSeriesOfChanges() throws Exception {
TestRepository<Repo> p = newRepo("p");
RevCommit c1_1 = p.commit().create();
diff --git a/lib/errorprone/BUILD b/lib/errorprone/BUILD
index 456860a..f95a430 100644
--- a/lib/errorprone/BUILD
+++ b/lib/errorprone/BUILD
@@ -3,6 +3,7 @@
java_library(
name = "annotations",
data = ["//lib:LICENSE-Apache2.0"],
+ neverlink = 1,
visibility = ["//visibility:public"],
exports = ["@error-prone-annotations//jar"],
)
diff --git a/polygerrit-ui/app/constants/constants.ts b/polygerrit-ui/app/constants/constants.ts
index 24fb5c0..0fa58f4 100644
--- a/polygerrit-ui/app/constants/constants.ts
+++ b/polygerrit-ui/app/constants/constants.ts
@@ -263,6 +263,7 @@
email_strategy: EmailStrategy.ATTENTION_SET_ONLY,
default_base_for_merges: DefaultBase.AUTO_MERGE,
allow_browser_notifications: false,
+ allow_suggest_code_while_commenting: true,
diff_page_sidebar: 'NONE',
};
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 405f476..e12f93f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -107,6 +107,7 @@
import {isImageDiff} from '../../../utils/diff-util';
import {formStyles} from '../../../styles/form-styles';
import {NormalizedFileInfo} from '../../change/gr-file-list/gr-file-list';
+import {configModelToken} from '../../../models/config/config-model';
const LOADING_BLAME = 'Loading blame...';
const LOADED_BLAME = 'Blame loaded';
@@ -208,6 +209,10 @@
// Private but used in tests.
@state() isActiveChildView = false;
+ // Whether to allow the "Show Blame button"
+ @state()
+ allowBlame = false;
+
// Private but used in tests.
@state()
loggedIn = false;
@@ -261,6 +266,8 @@
private readonly getViewModel = resolve(this, changeViewModelToken);
+ private readonly getConfigModel = resolve(this, configModelToken);
+
private throttledToggleFileReviewed?: (e: KeyboardEvent) => void;
@state()
@@ -457,6 +464,13 @@
}
);
+ subscribe(
+ this,
+ () => this.getConfigModel().serverConfig$,
+ serverConfig =>
+ (this.allowBlame = serverConfig?.change.allow_blame ?? false)
+ );
+
// When user initially loads the diff view, we want to automatically mark
// the file as reviewed if they have it enabled. We can't observe these
// properties since the method will be called anytime a property updates
@@ -1054,26 +1068,9 @@
}
private renderRightControls() {
- const blameLoaderClass =
- !isMagicPath(this.path) && !isImageDiff(this.diff) ? 'show' : '';
- const blameToggleLabel =
- this.isBlameLoaded && !this.isBlameLoading ? 'Hide blame' : 'Show blame';
const diffModeSelectorClass = !this.diff || this.diff.binary ? 'hide' : '';
return html` <div class="rightControls">
- ${this.renderSidebarTriggers()}
- <span class="blameLoader ${blameLoaderClass}">
- <gr-button
- link=""
- id="toggleBlame"
- title=${this.createTitle(
- Shortcut.TOGGLE_BLAME,
- ShortcutSection.DIFFS
- )}
- ?disabled=${this.isBlameLoading}
- @click=${this.toggleBlame}
- >${blameToggleLabel}</gr-button
- >
- </span>
+ ${this.renderSidebarTriggers()} ${this.renderBlameButton()}
${when(
this.computeCanEdit(),
() => html`
@@ -1143,6 +1140,24 @@
</div>`;
}
+ private renderBlameButton() {
+ if (!this.allowBlame) return;
+ const blameLoaderClass =
+ !isMagicPath(this.path) && !isImageDiff(this.diff) ? 'show' : '';
+ const blameToggleLabel =
+ this.isBlameLoaded && !this.isBlameLoading ? 'Hide blame' : 'Show blame';
+ return html` <span class="blameLoader ${blameLoaderClass}">
+ <gr-button
+ link=""
+ id="toggleBlame"
+ title=${this.createTitle(Shortcut.TOGGLE_BLAME, ShortcutSection.DIFFS)}
+ ?disabled=${this.isBlameLoading}
+ @click=${this.toggleBlame}
+ >${blameToggleLabel}</gr-button
+ >
+ </span>`;
+ }
+
private renderDialogs() {
return html`
<gr-apply-fix-dialog id="applyFixDialog"></gr-apply-fix-dialog>
@@ -1771,12 +1786,13 @@
* Otherwise hide it.
*/
private toggleBlame() {
+ if (!this.allowBlame) return;
assertIsDefined(this.diffHost, 'diffHost');
if (this.isBlameLoaded) {
this.diffHost.clearBlame();
- return;
+ } else {
+ this.loadBlame();
}
- this.loadBlame();
}
private handleToggleHideAllCommentThreads() {
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
index 4d4834e..29ece75 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.ts
@@ -71,6 +71,8 @@
import {navigationToken} from '../../core/gr-navigation/gr-navigation';
import {getDocUrl, rootUrl} from '../../../utils/url-util';
import {configModelToken} from '../../../models/config/config-model';
+import {SuggestionsProvider} from '../../../api/suggestions';
+import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
const HTTP_AUTH = ['HTTP', 'HTTP_LDAP'];
@@ -118,6 +120,9 @@
@query('#allowBrowserNotifications')
allowBrowserNotifications?: HTMLInputElement;
+ @query('#allowSuggestCodeWhileCommenting')
+ allowSuggestCodeWhileCommenting?: HTMLInputElement;
+
@query('#disableKeyboardShortcuts')
disableKeyboardShortcuts!: HTMLInputElement;
@@ -196,6 +201,9 @@
@state() private docsBaseUrl = '';
+ @state()
+ suggestionsProvider?: SuggestionsProvider;
+
// private but used in test
public _testOnly_loadingPromise?: Promise<void>;
@@ -212,6 +220,8 @@
private readonly getConfigModel = resolve(this, configModelToken);
+ private readonly getPluginLoader = resolve(this, pluginLoaderToken);
+
constructor() {
super();
subscribe(
@@ -265,6 +275,14 @@
// we need to manually calling scrollIntoView when hash changed
document.addEventListener('location-change', this.handleLocationChange);
fireTitleChange('Settings');
+ this.getPluginLoader()
+ .awaitPluginsLoaded()
+ .then(() => {
+ const suggestionsPlugins =
+ this.getPluginLoader().pluginsModel.getState().suggestionsPlugins;
+ // We currently support results from only 1 provider.
+ this.suggestionsProvider = suggestionsPlugins?.[0]?.provider;
+ });
}
override firstUpdated() {
@@ -451,6 +469,7 @@
${this.renderTheme()} ${this.renderChangesPerPages()}
${this.renderDateTimeFormat()} ${this.renderEmailNotification()}
${this.renderEmailFormat()} ${this.renderBrowserNotifications()}
+ ${this.renderGenerateSuggestionWhenCommenting()}
${this.renderDefaultBaseForMerges()}
${this.renderRelativeDateInChangeTable()} ${this.renderDiffView()}
${this.renderShowSizeBarsInFileList()}
@@ -867,6 +886,45 @@
`;
}
+ private renderGenerateSuggestionWhenCommenting() {
+ if (
+ !this.flagsService.isEnabled(KnownExperimentId.ML_SUGGESTED_EDIT) ||
+ !this.suggestionsProvider
+ )
+ return nothing;
+ return html`
+ <section id="allowSuggestCodeWhileCommentingSection">
+ <div class="title">
+ <label for="allowSuggestCodeWhileCommenting"
+ >Allow generating suggestions while commenting</label
+ >
+ <a
+ href=${getDocUrl(
+ this.docsBaseUrl,
+ 'user-suggest-edits.html#_generate_suggestion'
+ )}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ <gr-icon icon="help" title="read documentation"></gr-icon>
+ </a>
+ </div>
+ <span class="value">
+ <input
+ id="allowSuggestCodeWhileCommenting"
+ type="checkbox"
+ ?checked=${this.localPrefs.allow_suggest_code_while_commenting}
+ @change=${() => {
+ this.localPrefs.allow_suggest_code_while_commenting =
+ this.allowSuggestCodeWhileCommenting!.checked;
+ this.prefsChanged = true;
+ }}
+ />
+ </span>
+ </section>
+ `;
+ }
+
private renderDefaultBaseForMerges() {
if (!this.localPrefs.default_base_for_merges) return nothing;
return nothing;
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 3e341b8..9b83f3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -377,6 +377,19 @@
this.generateSuggestEdit();
}
);
+ subscribe(
+ this,
+ () => this.getUserModel().preferences$,
+ prefs => {
+ if (
+ this.generateSuggestion !==
+ !!prefs.allow_suggest_code_while_commenting
+ ) {
+ this.generateSuggestion =
+ !!prefs.allow_suggest_code_while_commenting;
+ }
+ }
+ );
}
}
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index b66eb3c..cba3a05 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -462,6 +462,7 @@
export function createChangeConfig(): ChangeConfigInfo {
return {
+ allow_blame: true,
large_change: 500,
// The default update_delay is 5 minutes, but we don't want to accidentally
// start polling in tests
@@ -708,6 +709,7 @@
changes_per_page: 10,
email_strategy: EmailStrategy.ENABLED,
allow_browser_notifications: true,
+ allow_suggest_code_while_commenting: true,
};
}
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 9992f8b..4f23dbd 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -1335,6 +1335,7 @@
// The email_format doesn't mentioned in doc, but exists in Java class GeneralPreferencesInfo
email_format?: EmailFormat;
allow_browser_notifications?: boolean;
+ allow_suggest_code_while_commenting?: boolean;
diff_page_sidebar?: DiffPageSidebar;
}
diff --git a/polygerrit-ui/app/utils/change-util.ts b/polygerrit-ui/app/utils/change-util.ts
index 5079abd..a78254d 100644
--- a/polygerrit-ui/app/utils/change-util.ts
+++ b/polygerrit-ui/app/utils/change-util.ts
@@ -87,10 +87,8 @@
change: ChangeInfo,
options?: ChangeStatusesOptions
): ChangeStates[] {
- const states = [];
- if (change.revert_of) {
- states.push(ChangeStates.REVERT);
- }
+ const states: ChangeStates[] = [];
+
if (change.status === ChangeStatus.MERGED) {
if (options?.revertingChangeStatus === ChangeStatus.MERGED) {
return [ChangeStates.MERGED, ChangeStates.REVERT_SUBMITTED];
@@ -103,6 +101,10 @@
if (change.status === ChangeStatus.ABANDONED) {
return [ChangeStates.ABANDONED];
}
+
+ if (change.revert_of) {
+ states.push(ChangeStates.REVERT);
+ }
if (change.mergeable === false || (options && options.mergeable === false)) {
// 'mergeable' prop may not always exist (@see Issue 6819)
states.push(ChangeStates.MERGE_CONFLICT);
@@ -116,15 +118,29 @@
states.push(ChangeStates.PRIVATE);
}
- // If there are any pre-defined statuses, only return those. Otherwise,
- // will determine the derived status.
- if (states.length || !options) {
+ // The gr-change-list table does not want READY TO SUBMIT or ACTIVE and it
+ // does not pass options.
+ if (!options) {
+ return states;
+ }
+
+ // The change is not submittable if there are conflicts or is WIP/private even
+ // if the submit requirements are ok.
+ if (
+ [
+ ChangeStates.MERGE_CONFLICT,
+ ChangeStates.GIT_CONFLICT,
+ ChangeStates.WIP,
+ ChangeStates.PRIVATE,
+ ].some(unsubmittableState => states.includes(unsubmittableState))
+ ) {
return states;
}
if (change.submittable) {
states.push(ChangeStates.READY_TO_SUBMIT);
- } else {
+ }
+ if (states.length === 0) {
states.push(ChangeStates.ACTIVE);
}
return states;
diff --git a/polygerrit-ui/app/utils/change-util_test.ts b/polygerrit-ui/app/utils/change-util_test.ts
index 6e53c16..51e935e 100644
--- a/polygerrit-ui/app/utils/change-util_test.ts
+++ b/polygerrit-ui/app/utils/change-util_test.ts
@@ -176,6 +176,18 @@
]);
});
+ test('Revert that is submittable', () => {
+ const change = {
+ ...createChange(),
+ revert_of: 123 as NumericChangeId,
+ submittable: true,
+ };
+ assert.deepEqual(changeStatuses(change, {mergeable: true}), [
+ ChangeStates.REVERT,
+ ChangeStates.READY_TO_SUBMIT,
+ ]);
+ });
+
test('Open status with private and wip', () => {
const change = {
...createChange(),
diff --git a/tools/deps.bzl b/tools/deps.bzl
index a0d8715..0a262e0 100644
--- a/tools/deps.bzl
+++ b/tools/deps.bzl
@@ -13,7 +13,7 @@
OW2_VERS = "9.2"
AUTO_COMMON_VERSION = "1.2.1"
AUTO_FACTORY_VERSION = "1.0.1"
-AUTO_VALUE_VERSION = "1.7.4"
+AUTO_VALUE_VERSION = "1.10.4"
AUTO_VALUE_GSON_VERSION = "1.3.1"
PROLOG_VERS = "1.4.4"
PROLOG_REPO = GERRIT
@@ -303,13 +303,13 @@
maven_jar(
name = "auto-value",
artifact = "com.google.auto.value:auto-value:" + AUTO_VALUE_VERSION,
- sha1 = "6b126cb218af768339e4d6e95a9b0ae41f74e73d",
+ sha1 = "90f9629eaa123f88551cc26a64bc386967ee24cc",
)
maven_jar(
name = "auto-value-annotations",
artifact = "com.google.auto.value:auto-value-annotations:" + AUTO_VALUE_VERSION,
- sha1 = "eff48ed53995db2dadf0456426cc1f8700136f86",
+ sha1 = "9679de8286eb0a151db6538ba297a8951c4a1224",
)
maven_jar(
@@ -464,8 +464,8 @@
maven_jar(
name = "httpcore",
- artifact = "org.apache.httpcomponents:httpcore:4.4.4",
- sha1 = "b31526a230871fbe285fbcbe2813f9c0839ae9b0",
+ artifact = "org.apache.httpcomponents:httpcore:4.4.16",
+ sha1 = "51cf043c87253c9f58b539c9f7e44c8894223850",
)
# Test-only dependencies below.
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 4482751..d685fd3 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -61,8 +61,8 @@
maven_jar(
name = "log4j",
- artifact = "ch.qos.reload4j:reload4j:1.2.19",
- sha1 = "4eae9978468c5e885a6fb44df7e2bbc07a20e6ce",
+ artifact = "ch.qos.reload4j:reload4j:1.2.25",
+ sha1 = "45921e383a1001c2a599fc4c6cf59af80cdd1cf1",
)
SLF4J_VERS = "1.7.36"