Merge "MergeOp: Log IntegrationExceptions that are treated as conflict only as warning"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 1c84768..37bf3e1 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3644,7 +3644,10 @@
[[plugins.mandatory]]plugins.mandatory::
+
List of mandatory plugins. If a plugin from this list does not load,
-Gerrit start will fail.
+Gerrit will fail to start.
++
+Disabling and restarting of a mandatory plugin is rejected, but reloading
+of a mandatory plugin is still possible.
[[plugins.jsLoadTimeout]]plugins.jsLoadTimeout::
+
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 48bafdf..9e1744c 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -98,7 +98,7 @@
Tag the plugins:
----
- git submodule foreach git tag -s -m "v$version" "v$version"
+ git submodule foreach '[ "$path" == "modules/jgit" ] || git tag -s -m "v$version" "v$version"'
----
[[build-gerrit]]
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index b326834..1973b00 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -70,6 +70,7 @@
import java.util.TimeZone;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
@@ -381,7 +382,14 @@
throw new BadRequestException(String.format("Base %s doesn't represent a valid SHA-1", base));
}
- RevCommit baseCommit = revWalk.parseCommit(baseObjectId);
+ RevCommit baseCommit;
+ try {
+ baseCommit = revWalk.parseCommit(baseObjectId);
+ } catch (MissingObjectException e) {
+ throw new UnprocessableEntityException(
+ String.format("Base %s doesn't exist", baseObjectId.name()), e);
+ }
+
InternalChangeQuery changeQuery = queryProvider.get();
changeQuery.enforceVisibility(true);
List<ChangeData> changeDatas = changeQuery.byBranchCommit(project, destRef.getName(), base);
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 2d94566..32941ff 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -53,6 +53,7 @@
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.DraftApi;
@@ -974,6 +975,42 @@
}
@Test
+ public void cherryPickToNonExistingBranch() throws Exception {
+ PushOneCommit.Result result = createChange();
+
+ CherryPickInput input = new CherryPickInput();
+ input.message = "foo bar";
+ input.destination = "non-existing";
+ // TODO(ekempin): This should rather result in an UnprocessableEntityException.
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.changes().id(result.getChangeId()).current().cherryPick(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(
+ String.format("Branch %s does not exist.", RefNames.REFS_HEADS + input.destination));
+ }
+
+ @Test
+ public void cherryPickToNonExistingBaseCommit() throws Exception {
+ createBranch(BranchNameKey.create(project, "foo"));
+ PushOneCommit.Result result = createChange();
+
+ CherryPickInput input = new CherryPickInput();
+ input.message = "foo bar";
+ input.destination = "foo";
+ input.base = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ UnprocessableEntityException thrown =
+ assertThrows(
+ UnprocessableEntityException.class,
+ () -> gApi.changes().id(result.getChangeId()).current().cherryPick(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo(String.format("Base %s doesn't exist", input.base));
+ }
+
+ @Test
public void canRebase() throws Exception {
PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
PushOneCommit.Result r1 = push.to("refs/for/master");
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 383687f..25c7738 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -259,12 +259,14 @@
lastNotify: {left: 1, right: 1},
};
+ const rangesCache = new Map();
+
this._processPromise = util.makeCancelable(this._loadHLJS()
.then(() => {
return new Promise(resolve => {
const nextStep = () => {
this._processHandle = null;
- this._processNextLine(state);
+ this._processNextLine(state, rangesCache);
// Move to the next line in the section.
state.lineIndex++;
@@ -321,12 +323,21 @@
* Highlight.js emits and emit a list of text ranges and classes for the
* markers.
* @param {string} str The string of HTML.
+ * @param {Map<string, !Array<!Object>>} rangesCache A map for caching
+ * ranges for each string. A cache is read and written by this method.
+ * Since diff is mostly comparing same file on two sides, there is good rate
+ * of duplication at least for parts that are on left and right parts.
* @return {!Array<!Object>} The list of ranges.
*/
- _rangesFromString(str) {
+ _rangesFromString(str, rangesCache) {
+ const cached = rangesCache.get(str);
+ if (cached) return cached;
+
const div = document.createElement('div');
div.innerHTML = str;
- return this._rangesFromElement(div, 0);
+ const ranges = this._rangesFromElement(div, 0);
+ rangesCache.set(str, ranges);
+ return ranges;
},
_rangesFromElement(elem, offset) {
@@ -357,7 +368,7 @@
* lines).
* @param {!Object} state The processing state for the layer.
*/
- _processNextLine(state) {
+ _processNextLine(state, rangesCache) {
let baseLine;
let revisionLine;
@@ -386,7 +397,8 @@
baseLine = this._workaround(this._baseLanguage, baseLine);
result = this._hljs.highlight(this._baseLanguage, baseLine, true,
state.baseContext);
- this.push('_baseRanges', this._rangesFromString(result.value));
+ this.push('_baseRanges',
+ this._rangesFromString(result.value, rangesCache));
state.baseContext = result.top;
}
@@ -395,7 +407,8 @@
revisionLine = this._workaround(this._revisionLanguage, revisionLine);
result = this._hljs.highlight(this._revisionLanguage, revisionLine,
true, state.revisionContext);
- this.push('_revisionRanges', this._rangesFromString(result.value));
+ this.push('_revisionRanges',
+ this._rangesFromString(result.value, rangesCache));
state.revisionContext = result.top;
}
},
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index 472db21..4e3492f 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -388,10 +388,20 @@
'<span class="non-whtelisted-class">',
'<span class="gr-diff gr-syntax gr-syntax-keyword">public</span>',
'</span>'].join('');
- const result = element._rangesFromString(str);
+ const result = element._rangesFromString(str, new Map());
assert.notEqual(result.length, 0);
});
+ test('_rangesFromString cache same syntax markers', () => {
+ sandbox.spy(element, '_rangesFromElement');
+ const str =
+ '<span class="gr-diff gr-syntax gr-syntax-keyword">public</span>';
+ const cacheMap = new Map();
+ element._rangesFromString(str, cacheMap);
+ element._rangesFromString(str, cacheMap);
+ assert.isTrue(element._rangesFromElement.calledOnce);
+ });
+
test('_isSectionDone', () => {
let state = {sectionIndex: 0, lineIndex: 0};
assert.isFalse(element._isSectionDone(state));