Merge "Prevent text from overflowing in change_plugins entry point"
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 5ca6353..97124cb 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -7,7 +7,6 @@
* Version stamping
* Custom plugins
* Eclipse project generation.
-* Publishing to maven.
* Test suites for SSH, acceptance, etc.
* tag tests as slow, flaky, etc.
@@ -81,7 +80,17 @@
bazel-bin/gerrit-extension-api/extension-api_deploy.jar
----
-TODO - fix and document deployment to maven
+Install {extension,plugin,gwt}-api to the local maven repository:
+
+----
+ tools/maven/api.sh install bazel
+----
+
+Install gerrit.war to the local maven repository:
+
+----
+ tools/maven/api.sh war_install bazel
+----
=== Plugins
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 8741d80..023841c 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -154,7 +154,7 @@
----
buck clean
buck build --no-cache release docs
- ./tools/maven/api.sh install
+ ./tools/maven/api.sh install <buck|bazel>
----
* Sanity check WAR
@@ -186,13 +186,13 @@
* Push the WAR to Maven Central:
+
----
- ./tools/maven/api.sh war_deploy
+ ./tools/maven/api.sh war_deploy <buck|bazel>
----
* Push the plugin artifacts to Maven Central:
+
----
- ./tools/maven/api.sh deploy
+ ./tools/maven/api.sh deploy <buck|bazel>
----
+
If no artifacts are uploaded, clean the `buck-out` folder and retry:
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index ed04efa..ec79be8 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -64,4 +64,5 @@
title = 'Gerrit Acceptance Test Framework Documentation',
libs = [':lib'],
pkgs = ['com.google.gerrit.acceptance'],
+ visibility = ['//visibility:public'],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 29375fe..e1771ce 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -20,6 +20,7 @@
import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail;
@@ -68,6 +69,7 @@
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NoteDbChangeState;
+import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
@@ -99,6 +101,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class ChangeRebuilderIT extends AbstractDaemonTest {
@@ -592,11 +595,15 @@
ReviewDb db = getUnwrappedDb();
Change c = db.changes().get(id);
// Leave change meta ID alone so DraftCommentNotes does the rebuild.
+ ObjectId badSha =
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
NoteDbChangeState bogusState = new NoteDbChangeState(
- id, NoteDbChangeState.parse(c).getChangeMetaId(),
- ImmutableMap.<Account.Id, ObjectId>of(
- user.getId(),
- ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")));
+ id,
+ PrimaryStorage.REVIEW_DB,
+ Optional.of(
+ NoteDbChangeState.RefState.create(
+ NoteDbChangeState.parse(c).getChangeMetaId(),
+ ImmutableMap.of(user.getId(), badSha))));
c.setNoteDbState(bogusState.toString());
db.changes().update(Collections.singleton(c));
@@ -1088,6 +1095,32 @@
checker.rebuildAndCheckChanges(id);
}
+ @Test
+ public void ignoreChangeMessageBeyondCurrentPatchSet() throws Exception {
+ PushOneCommit.Result r = createChange();
+ PatchSet.Id psId1 = r.getPatchSetId();
+ Change.Id id = psId1.getParentKey();
+ gApi.changes().id(id.get()).current().review(ReviewInput.recommend());
+
+ r = amendChange(r.getChangeId());
+ PatchSet.Id psId2 = r.getPatchSetId();
+
+ assertThat(db.patchSets().byChange(id)).hasSize(2);
+ assertThat(db.changeMessages().byPatchSet(psId2)).hasSize(1);
+ db.patchSets().deleteKeys(Collections.singleton(psId2));
+
+ checker.rebuildAndCheckChanges(psId2.getParentKey());
+ setNotesMigration(true, true);
+
+ ChangeData cd = changeDataFactory.create(db, project, id);
+ assertThat(cd.change().currentPatchSetId()).isEqualTo(psId1);
+ assertThat(cd.patchSets().stream().map(ps -> ps.getId()).collect(toList()))
+ .containsExactly(psId1);
+ PatchSet ps = cd.currentPatchSet();
+ assertThat(ps).isNotNull();
+ assertThat(ps.getId()).isEqualTo(psId1);
+ }
+
private void assertChangesReadOnly(RestApiException e) throws Exception {
Throwable cause = e.getCause();
assertThat(cause).isInstanceOf(UpdateException.class);
diff --git a/gerrit-extension-api/BUILD b/gerrit-extension-api/BUILD
index cbe0e26..6f4df01 100644
--- a/gerrit-extension-api/BUILD
+++ b/gerrit-extension-api/BUILD
@@ -55,4 +55,5 @@
libs = [':api'],
pkgs = ['com.google.gerrit.extensions'],
external_docs = [JGIT_DOC_URL, GUAVA_DOC_URL],
+ visibility = ['//visibility:public'],
)
diff --git a/gerrit-gwtui/BUILD b/gerrit-gwtui/BUILD
index 833ffab..7e692e8 100644
--- a/gerrit-gwtui/BUILD
+++ b/gerrit-gwtui/BUILD
@@ -1,6 +1,6 @@
-load('//tools/bzl:gwt.bzl', 'gwt_module')
+load('//tools/bzl:gwt.bzl', 'gwt_genrule', 'gen_ui_module',
+ 'gwt_user_agent_permutations')
load('//tools/bzl:license.bzl', 'license_test')
-load(':gwt.bzl', 'gwt_binary', 'gwt_genrule', 'gen_ui_module')
gwt_genrule()
gwt_genrule('_r')
@@ -8,6 +8,8 @@
gen_ui_module(name = 'ui_module')
gen_ui_module(name = 'ui_module', suffix = '_r')
+gwt_user_agent_permutations()
+
license_test(
name = "ui_module_license_test",
target = ":ui_module",
diff --git a/gerrit-gwtui/gwt.bzl b/gerrit-gwtui/gwt.bzl
deleted file mode 100644
index 3e83789..0000000
--- a/gerrit-gwtui/gwt.bzl
+++ /dev/null
@@ -1,197 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Port of Buck native gwt_binary() rule. See discussion in context of
-# https://github.com/facebook/buck/issues/109
-load('//tools/bzl:genrule2.bzl', 'genrule2')
-load('//tools/bzl:gwt.bzl', 'gwt_module')
-
-jar_filetype = FileType(['.jar'])
-
-MODULE = 'com.google.gerrit.GerritGwtUI'
-
-GWT_COMPILER = "com.google.gwt.dev.Compiler"
-
-GWT_JVM_ARGS = ['-Xmx512m']
-
-GWT_COMPILER_ARGS = [
- '-XdisableClassMetadata',
-]
-
-GWT_COMPILER_ARGS_RELEASE_MODE = GWT_COMPILER_ARGS + [
- '-XdisableCastChecking',
-]
-
-GWT_TRANSITIVE_DEPS = [
- '//lib/gwt:ant',
- '//lib/gwt:colt',
- '//lib/gwt:javax-validation',
- '//lib/gwt:javax-validation_src',
- '//lib/gwt:jsinterop-annotations',
- '//lib/gwt:jsinterop-annotations_src',
- '//lib/gwt:tapestry',
- '//lib/gwt:w3c-css-sac',
- '//lib/ow2:ow2-asm',
- '//lib/ow2:ow2-asm-analysis',
- '//lib/ow2:ow2-asm-commons',
- '//lib/ow2:ow2-asm-tree',
- '//lib/ow2:ow2-asm-util',
-]
-
-DEPS = GWT_TRANSITIVE_DEPS + [
- '//gerrit-gwtexpui:CSS',
- '//lib:gwtjsonrpc',
- '//lib/gwt:dev',
- '@jgit_src//file',
-]
-
-def _impl(ctx):
- output_zip = ctx.outputs.output
- output_dir = output_zip.path + '.gwt_output'
- deploy_dir = output_zip.path + '.gwt_deploy'
-
- deps = _get_transitive_closure(ctx)
-
- paths = []
- for dep in deps:
- paths.append(dep.path)
-
- cmd = "external/local_jdk/bin/java %s -Dgwt.normalizeTimestamps=true -cp %s %s -war %s -deploy %s " % (
- " ".join(ctx.attr.jvm_args),
- ":".join(paths),
- GWT_COMPILER,
- output_dir,
- deploy_dir,
- )
- cmd += " ".join([
- "-style %s" % ctx.attr.style,
- "-optimize %s" % ctx.attr.optimize,
- "-strict",
- " ".join(ctx.attr.compiler_args),
- " ".join(ctx.attr.modules) + "\n",
- "rm -rf %s/gwt-unitCache\n" % output_dir,
- "root=`pwd`\n",
- "cd %s; $root/%s Cc ../%s $(find .)\n" % (
- output_dir,
- ctx.executable._zip.path,
- output_zip.basename,
- )
- ])
-
- ctx.action(
- inputs = list(deps) + ctx.files._jdk + ctx.files._zip,
- outputs = [output_zip],
- mnemonic = "GwtBinary",
- progress_message = "GWT compiling " + output_zip.short_path,
- command = "set -e\n" + cmd,
- )
-
-def _get_transitive_closure(ctx):
- deps = set()
- for dep in ctx.attr.module_deps:
- deps += dep.java.transitive_runtime_deps
- deps += dep.java.transitive_source_jars
- for dep in ctx.attr.deps:
- if hasattr(dep, 'java'):
- deps += dep.java.transitive_runtime_deps
- elif hasattr(dep, 'files'):
- deps += dep.files
-
- return deps
-
-gwt_binary = rule(
- implementation = _impl,
- attrs = {
- "style": attr.string(default = "OBF"),
- "optimize": attr.string(default = "9"),
- "deps": attr.label_list(allow_files=jar_filetype),
- "modules": attr.string_list(mandatory=True),
- "module_deps": attr.label_list(allow_files=jar_filetype),
- "compiler_args": attr.string_list(),
- "jvm_args": attr.string_list(),
- "_jdk": attr.label(
- default=Label("//tools/defaults:jdk")),
- "_zip": attr.label(
- default=Label("@bazel_tools//tools/zip:zipper"),
- cfg = "host",
- executable=True,
- single_file=True),
- },
- outputs = {
- "output": "%{name}.zip",
- },
-)
-
-def gwt_genrule(suffix = ""):
- dbg = 'ui_dbg' + suffix
- opt = 'ui_opt' + suffix
- module_dep = ':ui_module' + suffix
- args = GWT_COMPILER_ARGS_RELEASE_MODE if suffix == "_r" else GWT_COMPILER_ARGS
-
- genrule2(
- name = 'ui_optdbg' + suffix,
- srcs = [
- ':' + dbg,
- ':' + opt,
- ],
- cmd = 'cd $$TMP;' +
- 'unzip -q $$ROOT/$(location :%s);' % dbg +
- 'mv' +
- ' gerrit_ui/gerrit_ui.nocache.js' +
- ' gerrit_ui/dbg_gerrit_ui.nocache.js;' +
- 'unzip -qo $$ROOT/$(location :%s);' % opt +
- 'mkdir -p $$(dirname $@);' +
- 'zip -qr $$ROOT/$@ .',
- out = 'ui_optdbg' + suffix + '.zip',
- visibility = ['//visibility:public'],
- )
-
- gwt_binary(
- name = opt,
- modules = [MODULE],
- module_deps = [module_dep],
- deps = DEPS,
- compiler_args = args,
- jvm_args = GWT_JVM_ARGS,
- )
-
- gwt_binary(
- name = dbg,
- modules = [MODULE],
- style = 'PRETTY',
- optimize = "0",
- module_deps = [module_dep],
- deps = DEPS,
- compiler_args = GWT_COMPILER_ARGS,
- jvm_args = GWT_JVM_ARGS,
- )
-
-def gen_ui_module(name, suffix = ""):
- gwt_module(
- name = name + suffix,
- srcs = native.glob(['src/main/java/**/*.java']),
- gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
- resources = native.glob(
- ['src/main/java/**/*'],
- exclude = ['src/main/java/**/*.java'] +
- ['src/main/java/%s.gwt.xml' % MODULE.replace('.', '/')]),
- deps = [
- '//gerrit-gwtui-common:diffy_logo',
- '//gerrit-gwtui-common:client',
- '//gerrit-gwtexpui:CSS',
- '//lib/codemirror:codemirror' + suffix,
- '//lib/gwt:user',
- ],
- visibility = ['//visibility:public'],
- )
diff --git a/gerrit-gwtui/gwt.defs b/gerrit-gwtui/gwt.defs
index cd8fa74..85553f2 100644
--- a/gerrit-gwtui/gwt.defs
+++ b/gerrit-gwtui/gwt.defs
@@ -18,14 +18,14 @@
'firefox',
'gecko1_8',
'safari',
- 'msie', 'ie8', 'ie9', 'ie10', 'ie11',
+ 'msie', 'ie8', 'ie9', 'ie10',
'edge',
]
ALIASES = {
'chrome': 'safari',
'firefox': 'gecko1_8',
- 'msie': 'ie11',
- 'edge': 'edge',
+ 'msie': 'ie10',
+ 'edge': 'gecko1_8',
}
MODULE = 'com.google.gerrit.GerritGwtUI'
CPU_COUNT = cpu_count()
@@ -124,7 +124,6 @@
prebuilt_jar(
name = '%s_gwtxml_lib' % gwt_name,
binary_jar = ':%s_gwtxml_gen' % gwt_name,
- gwt_jar = ':%s_gwtxml_gen' % gwt_name,
)
gwt_binary(
name = gwt_name,
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index e2d8372..e231a02 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -103,4 +103,5 @@
'//gerrit-gwtexpui:server',
'//gerrit-reviewdb:server',
],
+ visibility = ['//visibility:public'],
)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index 61f6557..dc229b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -44,7 +44,6 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -1014,7 +1013,7 @@
RevWalk rw, Change.Id id) throws Exception {
Change c = newChanges.get(id);
if (c == null) {
- c = ReviewDbUtil.unwrapDb(db).changes().get(id);
+ c = ChangeNotes.readOneReviewDbChange(db, id);
}
// Pass in preloaded change to controlFor, to avoid:
// - reading from a db that does not belong to this update
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index cb1d1d4..a60b86f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -69,7 +69,6 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -289,7 +288,7 @@
cmUtil.addChangeMessage(ctx.getDb(), update, msg);
if (mergedByPushOp == null) {
- resetChange(ctx, msg);
+ resetChange(ctx);
} else {
mergedByPushOp.setPatchSetProvider(Providers.of(newPatchSet))
.updateChange(ctx);
@@ -333,16 +332,8 @@
return current;
}
- private void resetChange(ChangeContext ctx, ChangeMessage msg)
- throws OrmException {
+ private void resetChange(ChangeContext ctx) {
Change change = ctx.getChange();
- if (change.getStatus().isClosed()) {
- ctx.getDb().patchSets().delete(Collections.singleton(newPatchSet));
- ctx.getDb().changeMessages().delete(Collections.singleton(msg));
- rejectMessage = CHANGE_IS_CLOSED;
- return;
- }
-
if (!change.currentPatchSetId().equals(priorPatchSetId)) {
return;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 8ce5d5e..5ef548c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -337,6 +337,7 @@
for (ChangeIndex i : getWriteIndexes()) {
i.delete(id);
}
+ log.info("Deleted change {} from index.", id.get());
fireChangeDeletedFromIndexEvent(id.get());
return null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 61ebfae..3c7277a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -327,16 +327,16 @@
private Timestamp getLatestTimestamp() {
Ordering<Timestamp> o = Ordering.natural().nullsFirst();
Timestamp ts = null;
- for (ChangeMessage cm : getChangeMessages()) {
+ for (ChangeMessage cm : filterChangeMessages()) {
ts = o.max(ts, cm.getWrittenOn());
}
for (PatchSet ps : getPatchSets()) {
ts = o.max(ts, ps.getCreatedOn());
}
- for (PatchSetApproval psa : getPatchSetApprovals()) {
+ for (PatchSetApproval psa : filterPatchSetApprovals().values()) {
ts = o.max(ts, psa.getGranted());
}
- for (PatchLineComment plc : getPatchLineComments()) {
+ for (PatchLineComment plc : filterPatchLineComments().values()) {
// Ignore draft comments, as they do not show up in the change meta graph.
if (plc.getStatus() != PatchLineComment.Status.DRAFT) {
ts = o.max(ts, plc.getWrittenOn());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 68be2c5..eda50d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -52,6 +52,7 @@
import com.google.gerrit.server.ReviewerStatusUpdate;
import com.google.gerrit.server.git.RefCache;
import com.google.gerrit.server.git.RepoRefCache;
+import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
@@ -95,6 +96,20 @@
+ String.format(fmt, args));
}
+ public static Change readOneReviewDbChange(ReviewDb db, Change.Id id)
+ throws OrmException {
+ return checkNoteDbState(ReviewDbUtil.unwrapDb(db).changes().get(id));
+ }
+
+ private static Change checkNoteDbState(Change c) throws OrmException {
+ NoteDbChangeState s = NoteDbChangeState.parse(c);
+ if (s != null && s.getPrimaryStorage() != PrimaryStorage.REVIEW_DB) {
+ throw new OrmException(
+ "invalid NoteDbChangeState in " + c.getId() + ": " + s);
+ }
+ return c;
+ }
+
@Singleton
public static class Factory {
private final Args args;
@@ -118,7 +133,7 @@
public ChangeNotes createChecked(ReviewDb db, Project.NameKey project,
Change.Id changeId) throws OrmException, NoSuchChangeException {
- Change change = ReviewDbUtil.unwrapDb(db).changes().get(changeId);
+ Change change = readOneReviewDbChange(db, changeId);
if (change == null || !change.getProject().equals(project)) {
throw new NoSuchChangeException(changeId);
}
@@ -142,7 +157,7 @@
private Change loadChangeFromDb(ReviewDb db, Project.NameKey project,
Change.Id changeId) throws OrmException {
- Change change = ReviewDbUtil.unwrapDb(db).changes().get(changeId);
+ Change change = readOneReviewDbChange(db, changeId);
checkArgument(project != null, "project is required");
checkNotNull(change,
"change %s not found in ReviewDb", changeId);
@@ -261,6 +276,7 @@
}
} else {
for (Change change : ReviewDbUtil.unwrapDb(db).changes().all()) {
+ checkNoteDbState(change);
ChangeNotes notes = createFromChangeOnlyWhenNoteDbDisabled(change);
if (predicate.test(notes)) {
m.put(change.getProject(), notes);
@@ -297,9 +313,8 @@
Project.NameKey project) throws OrmException, IOException {
Set<Change.Id> ids = scan(repo);
List<ChangeNotes> changeNotes = new ArrayList<>(ids.size());
- db = ReviewDbUtil.unwrapDb(db);
for (Change.Id id : ids) {
- Change change = db.changes().get(id);
+ Change change = readOneReviewDbChange(db, id);
if (change == null) {
log.warn("skipping change {} found in project {} " +
"but not in ReviewDb",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 430103c..37fc9f9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -60,6 +60,7 @@
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.ReviewerStatusUpdate;
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
@@ -73,6 +74,8 @@
import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.Charset;
@@ -91,8 +94,13 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.function.Function;
class ChangeNotesParser {
+ private static final Logger log =
+ LoggerFactory.getLogger(ChangeNotesParser.class);
+
// Sentinel RevId indicating a mutable field on a patch set was parsed, but
// the parser does not yet know its commit SHA-1.
private static final RevId PARTIAL_PATCH_SET =
@@ -232,8 +240,8 @@
private Multimap<PatchSet.Id, PatchSetApproval> buildApprovals() {
Multimap<PatchSet.Id, PatchSetApproval> result = ArrayListMultimap.create();
for (PatchSetApproval a : approvals.values()) {
- if (patchSetStates.get(a.getPatchSetId()) == PatchSetState.DELETED) {
- continue; // Patch set was explicitly deleted.
+ if (!patchSets.containsKey(a.getPatchSetId())) {
+ continue; // Patch set deleted or missing.
} else if (allPastReviewers.contains(a.getAccountId())
&& !reviewers.containsRow(a.getAccountId())) {
continue; // Reviewer was explicitly removed.
@@ -283,10 +291,6 @@
}
PatchSet.Id psId = parsePatchSetId(commit);
- if (currentPatchSetId == null || psId.get() > currentPatchSetId.get()) {
- currentPatchSetId = psId;
- }
-
PatchSetState psState = parsePatchSetState(commit);
if (psState != null) {
if (!patchSetStates.containsKey(psId)) {
@@ -874,18 +878,16 @@
}
}
- private void updatePatchSetStates() throws ConfigInvalidException {
- for (PatchSet ps : patchSets.values()) {
+ private void updatePatchSetStates() {
+ Set<PatchSet.Id> missing = new TreeSet<>(ReviewDbUtil.intKeyOrdering());
+ for (Iterator<PatchSet> it = patchSets.values().iterator();
+ it.hasNext();) {
+ PatchSet ps = it.next();
if (ps.getRevision().equals(PARTIAL_PATCH_SET)) {
- throw parseException("No %s found for patch set %s",
- FOOTER_COMMIT, ps.getPatchSetId());
+ missing.add(ps.getId());
+ it.remove();
}
}
- if (patchSetStates.isEmpty()) {
- return;
- }
-
- boolean deleted = false;
for (Map.Entry<PatchSet.Id, PatchSetState> e : patchSetStates.entrySet()) {
switch (e.getValue()) {
case PUBLISHED:
@@ -893,7 +895,6 @@
break;
case DELETED:
- deleted = true;
patchSets.remove(e.getKey());
break;
@@ -905,15 +906,11 @@
break;
}
}
- if (!deleted) {
- return;
- }
// Post-process other collections to remove items corresponding to deleted
- // patch sets. This is safer than trying to prevent insertion, as it will
- // also filter out items racily added after the patch set was deleted.
- //
- // Approvals are filtered in buildApprovals().
+ // (or otherwise missing) patch sets. This is safer than trying to prevent
+ // insertion, as it will also filter out items racily added after the patch
+ // set was deleted.
NavigableSet<PatchSet.Id> all = patchSets.navigableKeySet();
if (!all.isEmpty()) {
currentPatchSetId = all.last();
@@ -922,19 +919,35 @@
}
changeMessagesByPatchSet.keys().retainAll(all);
- for (Iterator<ChangeMessage> it = allChangeMessages.iterator();
- it.hasNext();) {
- if (!all.contains(it.next().getPatchSetId())) {
+ int pruned = pruneEntitiesForMissingPatchSets(
+ allChangeMessages, ChangeMessage::getPatchSetId, missing);
+ pruned += pruneEntitiesForMissingPatchSets(
+ comments.values(), c -> new PatchSet.Id(id, c.key.patchSetId), missing);
+ pruned += pruneEntitiesForMissingPatchSets(
+ approvals.values(), PatchSetApproval::getPatchSetId, missing);
+
+ if (!missing.isEmpty()) {
+ log.warn(
+ "ignoring {} additional entities due to missing patch sets: {}",
+ pruned, missing);
+ }
+ }
+
+ private <T> int pruneEntitiesForMissingPatchSets(
+ Iterable<T> ents, Function<T, PatchSet.Id> psIdFunc,
+ Set<PatchSet.Id> missing) {
+ int pruned = 0;
+ for (Iterator<T> it = ents.iterator(); it.hasNext();) {
+ PatchSet.Id psId = psIdFunc.apply(it.next());
+ if (!patchSets.containsKey(psId)) {
+ pruned++;
+ missing.add(psId);
it.remove();
+ } else if (deletedPatchSets.contains(psId)) {
+ it.remove(); // Not an error we need to report, don't increment pruned.
}
}
- for (Iterator<Comment> it = comments.values().iterator();
- it.hasNext();) {
- PatchSet.Id psId = new PatchSet.Id(id, it.next().key.patchSetId);
- if (!all.contains(psId)) {
- it.remove();
- }
- }
+ return pruned;
}
private void checkMandatoryFooters() throws ConfigInvalidException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
index ad54f02..2fefd72 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
@@ -16,30 +16,30 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
-import static java.util.Comparator.comparing;
+import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.NOTE_DB;
+import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB;
+import static org.eclipse.jgit.lib.ObjectId.zeroId;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.git.RefCache;
-
-import org.eclipse.jgit.lib.ObjectId;
-
import java.io.IOException;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
/**
* The state of all relevant NoteDb refs across all repos corresponding to a
@@ -48,13 +48,35 @@
* Stored serialized in the {@code Change#noteDbState} field, and used to
* determine whether the state in NoteDb is out of date.
* <p>
- * Serialized in the form:
- * <pre>
- * [meta-sha],[account1]=[drafts-sha],[account2]=[drafts-sha]...
- * </pre>
+ * Serialized in one of the forms:
+ * <ul>
+ * <li>[meta-sha],[account1]=[drafts-sha],[account2]=[drafts-sha]...
+ * <li>R[meta-sha],[account1]=[drafts-sha],[account2]=[drafts-sha]...
+ * <li>N
+ * </ul>
+ *
* in numeric account ID order, with hex SHA-1s for human readability.
*/
public class NoteDbChangeState {
+ public static final String NOTE_DB_PRIMARY_STATE = "N";
+
+ public enum PrimaryStorage {
+ REVIEW_DB('R', true),
+ NOTE_DB('N', false);
+
+ private final char code;
+ private final boolean writeToReviewDb;
+
+ private PrimaryStorage(char code, boolean writeToReviewDb) {
+ this.code = code;
+ this.writeToReviewDb = writeToReviewDb;
+ }
+
+ public boolean writeToReviewDb() {
+ return writeToReviewDb;
+ }
+ }
+
@AutoValue
public abstract static class Delta {
static Delta create(Change.Id changeId, Optional<ObjectId> newChangeMetaId,
@@ -73,31 +95,89 @@
abstract ImmutableMap<Account.Id, ObjectId> newDraftIds();
}
+ @AutoValue
+ public abstract static class RefState {
+ @VisibleForTesting
+ public static RefState create(ObjectId changeMetaId,
+ Map<Account.Id, ObjectId> draftIds) {
+ return new AutoValue_NoteDbChangeState_RefState(
+ changeMetaId.copy(),
+ ImmutableMap.copyOf(
+ Maps.filterValues(draftIds, id -> !zeroId().equals(id))));
+ }
+
+ private static Optional<RefState> parse(Change.Id changeId,
+ List<String> parts) {
+ checkArgument(!parts.isEmpty(),
+ "missing state string for change %s", changeId);
+ ObjectId changeMetaId = ObjectId.fromString(parts.get(0));
+ Map<Account.Id, ObjectId> draftIds =
+ Maps.newHashMapWithExpectedSize(parts.size() - 1);
+ Splitter s = Splitter.on('=');
+ for (int i = 1; i < parts.size(); i++) {
+ String p = parts.get(i);
+ List<String> draftParts = s.splitToList(p);
+ checkArgument(draftParts.size() == 2,
+ "invalid draft state part for change %s: %s", changeId, p);
+ draftIds.put(Account.Id.parse(draftParts.get(0)),
+ ObjectId.fromString(draftParts.get(1)));
+ }
+ return Optional.of(create(changeMetaId, draftIds));
+ }
+
+ abstract ObjectId changeMetaId();
+ abstract ImmutableMap<Account.Id, ObjectId> draftIds();
+
+ @Override
+ public String toString() {
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ StringBuilder appendTo(StringBuilder sb) {
+ sb.append(changeMetaId().name());
+ for (Account.Id id : ReviewDbUtil.intKeyOrdering()
+ .sortedCopy(draftIds().keySet())) {
+ sb.append(',')
+ .append(id.get())
+ .append('=')
+ .append(draftIds().get(id).name());
+ }
+ return sb;
+ }
+ }
+
public static NoteDbChangeState parse(Change c) {
- return parse(c.getId(), c.getNoteDbState());
+ return c != null ? parse(c.getId(), c.getNoteDbState()) : null;
}
@VisibleForTesting
public static NoteDbChangeState parse(Change.Id id, String str) {
- if (str == null) {
+ if (Strings.isNullOrEmpty(str)) {
+ // Return null rather than Optional as this is what goes in the field in
+ // ReviewDb.
return null;
}
List<String> parts = Splitter.on(',').splitToList(str);
- checkArgument(!parts.isEmpty(),
- "invalid state string for change %s: %s", id, str);
- ObjectId changeMetaId = ObjectId.fromString(parts.get(0));
- Map<Account.Id, ObjectId> draftIds =
- Maps.newHashMapWithExpectedSize(parts.size() - 1);
- Splitter s = Splitter.on('=');
- for (int i = 1; i < parts.size(); i++) {
- String p = parts.get(i);
- List<String> draftParts = s.splitToList(p);
- checkArgument(draftParts.size() == 2,
- "invalid draft state part for change %s: %s", id, p);
- draftIds.put(Account.Id.parse(draftParts.get(0)),
- ObjectId.fromString(draftParts.get(1)));
+
+ // Only valid NOTE_DB state is "N".
+ String first = parts.get(0);
+ if (parts.size() == 1 && first.charAt(0) == NOTE_DB.code) {
+ return new NoteDbChangeState(id, NOTE_DB, Optional.empty());
}
- return new NoteDbChangeState(id, changeMetaId, draftIds);
+
+ // Otherwise it must be REVIEW_DB, either "R,<RefState>" or just
+ // "<RefState>". Allow length > 0 for forward compatibility.
+ if (first.length() > 0) {
+ Optional<RefState> refState;
+ if (first.charAt(0) == REVIEW_DB.code) {
+ refState = RefState.parse(id, parts.subList(1, parts.size()));
+ } else {
+ refState = RefState.parse(id, parts);
+ }
+ return new NoteDbChangeState(id, REVIEW_DB, refState);
+ }
+ throw new IllegalArgumentException(
+ "invalid state string for change " + id + ": " + str);
}
public static NoteDbChangeState applyDelta(Change change, Delta delta) {
@@ -112,6 +192,10 @@
return null;
}
NoteDbChangeState oldState = parse(change.getId(), oldStr);
+ if (oldState != null && oldState.getPrimaryStorage() == NOTE_DB) {
+ // NOTE_DB state doesn't include RefState, so applying a delta is a no-op.
+ return oldState;
+ }
ObjectId changeMetaId;
if (delta.newChangeMetaId().isPresent()) {
@@ -121,12 +205,12 @@
return null;
}
} else {
- changeMetaId = oldState.changeMetaId;
+ changeMetaId = oldState.getChangeMetaId();
}
Map<Account.Id, ObjectId> draftIds = new HashMap<>();
if (oldState != null) {
- draftIds.putAll(oldState.draftIds);
+ draftIds.putAll(oldState.getDraftIds());
}
for (Map.Entry<Account.Id, ObjectId> e : delta.newDraftIds().entrySet()) {
if (e.getValue().equals(ObjectId.zeroId())) {
@@ -137,7 +221,11 @@
}
NoteDbChangeState state = new NoteDbChangeState(
- change.getId(), changeMetaId, draftIds);
+ change.getId(),
+ oldState != null
+ ? oldState.getPrimaryStorage()
+ : REVIEW_DB,
+ Optional.of(RefState.create(changeMetaId, draftIds)));
change.setNoteDbState(state.toString());
return state;
}
@@ -160,38 +248,47 @@
return state.areDraftsUpToDate(draftsRepoRefs, accountId);
}
- public static String toString(ObjectId changeMetaId,
- Map<Account.Id, ObjectId> draftIds) {
- List<Account.Id> accountIds = Lists.newArrayList(draftIds.keySet());
- Collections.sort(accountIds, comparing(Account.Id::get));
- StringBuilder sb = new StringBuilder(changeMetaId.name());
- for (Account.Id id : accountIds) {
- sb.append(',')
- .append(id.get())
- .append('=')
- .append(draftIds.get(id).name());
+ private final Change.Id changeId;
+ private final PrimaryStorage primaryStorage;
+ private final Optional<RefState> refState;
+
+ public NoteDbChangeState(
+ Change.Id changeId,
+ PrimaryStorage primaryStorage,
+ Optional<RefState> refState) {
+ this.changeId = checkNotNull(changeId);
+ this.primaryStorage = checkNotNull(primaryStorage);
+ this.refState = refState;
+
+ switch (primaryStorage) {
+ case REVIEW_DB:
+ checkArgument(
+ refState.isPresent(),
+ "expected RefState for change %s with primary storage %s",
+ changeId, primaryStorage);
+ break;
+ case NOTE_DB:
+ checkArgument(
+ !refState.isPresent(),
+ "expected no RefState for change %s with primary storage %s",
+ changeId, primaryStorage);
+ break;
+ default:
+ throw new IllegalStateException(
+ "invalid PrimaryStorage: " + primaryStorage);
}
- return sb.toString();
}
- private final Change.Id changeId;
- private final ObjectId changeMetaId;
- private final ImmutableMap<Account.Id, ObjectId> draftIds;
-
- public NoteDbChangeState(Change.Id changeId, ObjectId changeMetaId,
- Map<Account.Id, ObjectId> draftIds) {
- this.changeId = checkNotNull(changeId);
- this.changeMetaId = checkNotNull(changeMetaId);
- this.draftIds = ImmutableMap.copyOf(Maps.filterValues(
- draftIds, Predicates.not(Predicates.equalTo(ObjectId.zeroId()))));
+ public PrimaryStorage getPrimaryStorage() {
+ return primaryStorage;
}
public boolean isChangeUpToDate(RefCache changeRepoRefs) throws IOException {
Optional<ObjectId> id = changeRepoRefs.get(changeMetaRef(changeId));
if (!id.isPresent()) {
- return changeMetaId.equals(ObjectId.zeroId());
+ return getChangeMetaId().equals(ObjectId.zeroId());
}
- return id.get().equals(changeMetaId);
+ return id.get().equals(getChangeMetaId());
}
public boolean areDraftsUpToDate(RefCache draftsRepoRefs, Account.Id accountId)
@@ -199,9 +296,9 @@
Optional<ObjectId> id =
draftsRepoRefs.get(refsDraftComments(changeId, accountId));
if (!id.isPresent()) {
- return !draftIds.containsKey(accountId);
+ return !getDraftIds().containsKey(accountId);
}
- return id.get().equals(draftIds.get(accountId));
+ return id.get().equals(getDraftIds().get(accountId));
}
public boolean isUpToDate(RefCache changeRepoRefs, RefCache draftsRepoRefs)
@@ -209,7 +306,7 @@
if (!isChangeUpToDate(changeRepoRefs)) {
return false;
}
- for (Account.Id accountId : draftIds.keySet()) {
+ for (Account.Id accountId : getDraftIds().keySet()) {
if (!areDraftsUpToDate(draftsRepoRefs, accountId)) {
return false;
}
@@ -224,16 +321,36 @@
@VisibleForTesting
public ObjectId getChangeMetaId() {
- return changeMetaId;
+ return refState().changeMetaId();
}
@VisibleForTesting
ImmutableMap<Account.Id, ObjectId> getDraftIds() {
- return draftIds;
+ return refState().draftIds();
+ }
+
+ @VisibleForTesting
+ Optional<RefState> getRefState() {
+ return refState;
+ }
+
+ private RefState refState() {
+ checkState(refState.isPresent(),
+ "state for %s has no RefState: %s", changeId, this);
+ return refState.get();
}
@Override
public String toString() {
- return toString(changeMetaId, draftIds);
+ switch (primaryStorage) {
+ case REVIEW_DB:
+ // Don't include enum field, just IDs (though parse would accept it).
+ return refState().toString();
+ case NOTE_DB:
+ return NOTE_DB_PRIMARY_STATE;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported PrimaryStorage: " + primaryStorage);
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
index b6e25a0..b3aa420 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -54,6 +54,7 @@
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.ChangeDraftUpdate;
import com.google.gerrit.server.notedb.ChangeNoteUtil;
+import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NoteDbChangeState;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
@@ -184,7 +185,7 @@
public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId)
throws NoSuchChangeException, IOException, OrmException {
db = ReviewDbUtil.unwrapDb(db);
- Change change = db.changes().get(changeId);
+ Change change = ChangeNotes.readOneReviewDbChange(db, changeId);
if (change == null) {
throw new NoSuchChangeException(changeId);
}
@@ -200,7 +201,7 @@
NoteDbUpdateManager manager) throws NoSuchChangeException, OrmException,
IOException {
db = ReviewDbUtil.unwrapDb(db);
- Change change = db.changes().get(changeId);
+ Change change = ChangeNotes.readOneReviewDbChange(db, changeId);
if (change == null) {
throw new NoSuchChangeException(changeId);
}
@@ -319,10 +320,11 @@
List<Event> msgEvents = parseChangeMessage(msg, change, noteDbChange);
if (msg.getPatchSetId() != null) {
PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId());
- if (pse != null) {
- for (Event e : msgEvents) {
- e.addDep(pse);
- }
+ if (pse == null) {
+ continue; // Ignore events for missing patch sets.
+ }
+ for (Event e : msgEvents) {
+ e.addDep(pse);
}
}
events.addAll(msgEvents);
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
index 3042287..6dc37b8 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
@@ -54,7 +54,7 @@
{/if}
{if $coverLetter}
- {call .Pre}{param content: $coverLetter /}{/call}
+ <div style="white-space:pre-wrap">{$coverLetter}</div>
{/if}
<ul style="{$ulStyle}">
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index 97bf864..faa3105 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -328,6 +328,10 @@
throws Exception {
Change c1 = TestChanges.newChange(
new Project.NameKey("project"), new Account.Id(100));
+ PatchSet ps = new PatchSet(c1.currentPatchSetId());
+ ps.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ ps.setUploader(accountId);
+ ps.setCreatedOn(TimeUtil.nowTs());
PatchSetApproval a = new PatchSetApproval(
new PatchSetApproval.Key(
c1.currentPatchSetId(), accountId, new LabelId("Code-Review")),
@@ -338,16 +342,16 @@
c2.setLastUpdatedOn(a.getGranted());
// Both ReviewDb, exact match required.
- ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(),
+ ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(ps),
approvals(a), comments(), reviewers(), REVIEW_DB);
- ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(),
+ ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(ps),
approvals(a), comments(), reviewers(), REVIEW_DB);
assertDiffs(b1, b2,
"effective last updated time differs for Change.Id " + c1.getId() + ":"
- + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}");
+ + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:12.0}");
// NoteDb allows latest timestamp from all entities in bundle.
- b2 = new ChangeBundle(c2, messages(), patchSets(),
+ b2 = new ChangeBundle(c2, messages(), patchSets(ps),
approvals(a), comments(), reviewers(), NOTE_DB);
assertNoDiffs(b1, b2);
}
@@ -356,6 +360,10 @@
public void diffChangesIgnoresChangeTimestampIfAnyOtherEntitiesExist() {
Change c1 = TestChanges.newChange(
new Project.NameKey("project"), new Account.Id(100));
+ PatchSet ps = new PatchSet(c1.currentPatchSetId());
+ ps.setRevision(new RevId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ ps.setUploader(accountId);
+ ps.setCreatedOn(TimeUtil.nowTs());
PatchSetApproval a = new PatchSetApproval(
new PatchSetApproval.Key(
c1.currentPatchSetId(), accountId, new LabelId("Code-Review")),
@@ -368,9 +376,9 @@
// ReviewDb has later lastUpdatedOn timestamp than NoteDb, allowed since
// NoteDb matches the latest timestamp of a non-Change entity.
- ChangeBundle b1 = new ChangeBundle(c2, messages(), patchSets(),
+ ChangeBundle b1 = new ChangeBundle(c2, messages(), patchSets(ps),
approvals(a), comments(), reviewers(), REVIEW_DB);
- ChangeBundle b2 = new ChangeBundle(c1, messages(), patchSets(),
+ ChangeBundle b2 = new ChangeBundle(c1, messages(), patchSets(ps),
approvals(a), comments(), reviewers(), NOTE_DB);
assertThat(b1.getChange().getLastUpdatedOn())
.isGreaterThan(b2.getChange().getLastUpdatedOn());
@@ -384,7 +392,7 @@
assertDiffs(b1, b2,
"effective last updated time differs for Change.Id " + c1.getId()
+ " in NoteDb vs. ReviewDb:"
- + " {2009-09-30 17:00:06.0} != {2009-09-30 17:00:12.0}");
+ + " {2009-09-30 17:00:12.0} != {2009-09-30 17:00:18.0}");
}
@Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 92ba426..29c11ff 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -19,7 +19,6 @@
import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
-import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail;
@@ -251,7 +250,7 @@
assertThat(psa2.getAccountId().get()).isEqualTo(1);
assertThat(psa2.getLabel()).isEqualTo("Code-Review");
assertThat(psa2.getValue()).isEqualTo((short) +1);
- assertThat(psa2.getGranted()).isEqualTo(truncate(after(c, 3000)));
+ assertThat(psa2.getGranted()).isEqualTo(truncate(after(c, 4000)));
}
@Test
@@ -873,10 +872,6 @@
assertThat(ts4).isGreaterThan(ts3);
incrementPatchSet(c);
- RevCommit commit = tr.commit().message("PS2").create();
- update = newUpdate(c, changeOwner);
- update.setCommit(rw, commit);
- update.commit();
Timestamp ts5 = newNotes(c).getChange().getLastUpdatedOn();
assertThat(ts5).isGreaterThan(ts4);
@@ -983,11 +978,7 @@
assertThat(ps1.getUploader()).isEqualTo(changeOwner.getAccountId());
// ps2 by other user
- incrementPatchSet(c);
- RevCommit commit = tr.commit().message("PS2").create();
- ChangeUpdate update = newUpdate(c, otherUser);
- update.setCommit(rw, commit);
- update.commit();
+ RevCommit commit = incrementPatchSet(c, otherUser);
notes = newNotes(c);
PatchSet ps2 = notes.getCurrentPatchSet();
assertThat(ps2.getId()).isEqualTo(new PatchSet.Id(c.getId(), 2));
@@ -998,10 +989,11 @@
assertThat(ps2.getRevision().get()).isNotEqualTo(ps1.getRevision());
assertThat(ps2.getRevision().get()).isEqualTo(commit.name());
assertThat(ps2.getUploader()).isEqualTo(otherUser.getAccountId());
- assertThat(ps2.getCreatedOn()).isEqualTo(update.getWhen());
+ assertThat(ps2.getCreatedOn())
+ .isEqualTo(notes.getChange().getLastUpdatedOn());
// comment on ps1, current patch set is still ps2
- update = newUpdate(c, changeOwner);
+ ChangeUpdate update = newUpdate(c, changeOwner);
update.setPatchSetId(ps1.getId());
update.setChangeMessage("Comment on old patch set.");
update.commit();
@@ -1014,8 +1006,7 @@
Change c = newChange();
PatchSet.Id psId1 = c.currentPatchSetId();
- // ps2
- incrementPatchSet(c);
+ incrementCurrentPatchSetFieldOnly(c);
PatchSet.Id psId2 = c.currentPatchSetId();
RevCommit commit = tr.commit().message("PS2").create();
ChangeUpdate update = newUpdate(c, changeOwner);
@@ -1074,8 +1065,7 @@
assertThat(notes.getPatchSets().get(psId1).getGroups())
.containsExactly("a", "b").inOrder();
- // ps2
- incrementPatchSet(c);
+ incrementCurrentPatchSetFieldOnly(c);
PatchSet.Id psId2 = c.currentPatchSetId();
update = newUpdate(c, changeOwner);
update.setCommit(rw, tr.commit().message("PS2").create());
@@ -1101,7 +1091,7 @@
// ps2 with push cert
Change c = newChange();
PatchSet.Id psId1 = c.currentPatchSetId();
- incrementPatchSet(c);
+ incrementCurrentPatchSetFieldOnly(c);
PatchSet.Id psId2 = c.currentPatchSetId();
ChangeUpdate update = newUpdate(c, changeOwner);
update.setPatchSetId(psId2);
@@ -1656,6 +1646,9 @@
public void patchLineCommentNotesFormatMultiplePatchSetsSameRevId()
throws Exception {
Change c = newChange();
+ PatchSet.Id psId1 = c.currentPatchSetId();
+ incrementPatchSet(c);
+ PatchSet.Id psId2 = c.currentPatchSetId();
String uuid1 = "uuid1";
String uuid2 = "uuid2";
String uuid3 = "uuid3";
@@ -1667,9 +1660,6 @@
Timestamp time = TimeUtil.nowTs();
RevId revId = new RevId("abcd1234abcd1234abcd1234abcd1234abcd1234");
- PatchSet.Id psId1 = c.currentPatchSetId();
- PatchSet.Id psId2 = new PatchSet.Id(c.getId(), psId1.get() + 1);
-
Comment comment1 =
newComment(psId1, "file1", uuid1, range1, range1.getEndLine(),
otherUser, null, time, message1, (short) 0, revId.get());
@@ -2497,6 +2487,35 @@
assertThat(msg.getRealAuthor()).isEqualTo(changeOwner.getAccountId());
}
+ @Test
+ public void ignoreEntitiesBeyondCurrentPatchSet() throws Exception {
+ Change c = newChange();
+ ChangeNotes notes = newNotes(c);
+ int numMessages = notes.getChangeMessages().size();
+ int numPatchSets = notes.getPatchSets().size();
+ int numApprovals = notes.getApprovals().size();
+ int numComments = notes.getComments().size();
+
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setPatchSetId(
+ new PatchSet.Id(c.getId(), c.currentPatchSetId().get() + 1));
+ update.setChangeMessage("Should be ignored");
+ update.putApproval("Code-Review", (short) 2);
+ CommentRange range = new CommentRange(1, 1, 2, 1);
+ Comment comment = newComment(update.getPatchSetId(), "filename",
+ "uuid", range, range.getEndLine(), changeOwner, null,
+ new Timestamp(update.getWhen().getTime()), "comment", (short) 1,
+ "abcd1234abcd1234abcd1234abcd1234abcd1234");
+ update.putComment(Status.PUBLISHED, comment);
+ update.commit();
+
+ notes = newNotes(c);
+ assertThat(notes.getChangeMessages()).hasSize(numMessages);
+ assertThat(notes.getPatchSets()).hasSize(numPatchSets);
+ assertThat(notes.getApprovals()).hasSize(numApprovals);
+ assertThat(notes.getComments()).hasSize(numComments);
+ }
+
private boolean testJson() {
return noteUtil.getWriteJson();
}
@@ -2529,4 +2548,24 @@
.isNotNull();
assertThat(cause.getMessage()).isEqualTo(expectedMsg);
}
+
+ private void incrementCurrentPatchSetFieldOnly(Change c) {
+ TestChanges.incrementPatchSet(c);
+ }
+
+ private RevCommit incrementPatchSet(Change c) throws Exception {
+ return incrementPatchSet(c, userFactory.create(c.getOwner()));
+ }
+
+ private RevCommit incrementPatchSet(Change c, IdentifiedUser user)
+ throws Exception {
+ incrementCurrentPatchSetFieldOnly(c);
+ RevCommit commit = tr.commit()
+ .message("PS" + c.currentPatchSetId().get())
+ .create();
+ ChangeUpdate update = newUpdate(c, user);
+ update.setCommit(rw, commit);
+ update.commit();
+ return tr.parseBody(commit);
+ }
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java
index f2bf2be..e3613e3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.notedb.NoteDbChangeState.applyDelta;
import static com.google.gerrit.server.notedb.NoteDbChangeState.parse;
+import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.NOTE_DB;
+import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB;
import static org.eclipse.jgit.lib.ObjectId.zeroId;
import com.google.common.collect.ImmutableMap;
@@ -48,30 +50,44 @@
ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee");
@Test
- public void parseWithoutDrafts() {
+ public void parseReviewDbWithoutDrafts() {
NoteDbChangeState state = parse(new Change.Id(1), SHA1.name());
-
+ assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB);
assertThat(state.getChangeId()).isEqualTo(new Change.Id(1));
assertThat(state.getChangeMetaId()).isEqualTo(SHA1);
assertThat(state.getDraftIds()).isEmpty();
+ assertThat(state.toString()).isEqualTo(SHA1.name());
+ state = parse(new Change.Id(1), "R," + SHA1.name());
+ assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB);
+ assertThat(state.getChangeId()).isEqualTo(new Change.Id(1));
+ assertThat(state.getChangeMetaId()).isEqualTo(SHA1);
+ assertThat(state.getDraftIds()).isEmpty();
assertThat(state.toString()).isEqualTo(SHA1.name());
}
@Test
- public void parseWithDrafts() {
- NoteDbChangeState state = parse(
- new Change.Id(1),
- SHA1.name() + ",2003=" + SHA2.name() + ",1001=" + SHA3.name());
-
+ public void parseReviewDbWithDrafts() {
+ String str = SHA1.name() + ",2003=" + SHA2.name() + ",1001=" + SHA3.name();
+ String expected =
+ SHA1.name() + ",1001=" + SHA3.name() + ",2003=" + SHA2.name();
+ NoteDbChangeState state = parse(new Change.Id(1), str);
+ assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB);
assertThat(state.getChangeId()).isEqualTo(new Change.Id(1));
assertThat(state.getChangeMetaId()).isEqualTo(SHA1);
assertThat(state.getDraftIds()).containsExactly(
new Account.Id(1001), SHA3,
new Account.Id(2003), SHA2);
+ assertThat(state.toString()).isEqualTo(expected);
- assertThat(state.toString()).isEqualTo(
- SHA1.name() + ",1001=" + SHA3.name() + ",2003=" + SHA2.name());
+ state = parse(new Change.Id(1), "R," + str);
+ assertThat(state.getPrimaryStorage()).isEqualTo(REVIEW_DB);
+ assertThat(state.getChangeId()).isEqualTo(new Change.Id(1));
+ assertThat(state.getChangeMetaId()).isEqualTo(SHA1);
+ assertThat(state.getDraftIds()).containsExactly(
+ new Account.Id(1001), SHA3,
+ new Account.Id(2003), SHA2);
+ assertThat(state.toString()).isEqualTo(expected);
}
@Test
@@ -127,6 +143,27 @@
SHA3.name() + ",1001=" + SHA2.name());
}
+ @Test
+ public void parseNoteDbPrimary() {
+ NoteDbChangeState state = parse(new Change.Id(1), "N");
+ assertThat(state.getPrimaryStorage()).isEqualTo(NOTE_DB);
+ assertThat(state.getRefState().isPresent()).isFalse();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void parseInvalidPrimaryStorage() {
+ parse(new Change.Id(1), "X");
+ }
+
+ @Test
+ public void applyDeltaToNoteDbPrimaryIsNoOp() {
+ Change c = newChange();
+ c.setNoteDbState("N");
+ applyDelta(c, Delta.create(c.getId(), metaId(SHA1),
+ drafts(new Account.Id(1001), SHA2)));
+ assertThat(c.getNoteDbState()).isEqualTo("N");
+ }
+
private static Change newChange() {
return TestChanges.newChange(
new Project.NameKey("project"), new Account.Id(12345));
diff --git a/lib/codemirror/cm.bzl b/lib/codemirror/cm.bzl
index 581bb568..6400725 100644
--- a/lib/codemirror/cm.bzl
+++ b/lib/codemirror/cm.bzl
@@ -243,7 +243,7 @@
'@codemirror_original//jar',
'@codemirror_minified//jar',
],
- out = 'cm%s.js' % suffix,
+ outs = ['cm%s.js' % suffix],
)
# Main CSS
@@ -261,7 +261,7 @@
'@codemirror_original//jar',
'@codemirror_minified//jar',
],
- out = 'cm%s.css' % suffix,
+ outs = ['cm%s.css' % suffix],
)
# Modes
@@ -279,7 +279,7 @@
'@codemirror_original//jar',
'@codemirror_minified//jar',
],
- out = 'mode_%s%s.js' % (n, suffix),
+ outs = ['mode_%s%s.js' % (n, suffix)],
)
# Themes
@@ -297,7 +297,7 @@
'@codemirror_original//jar',
'@codemirror_minified//jar',
],
- out = 'theme_%s%s.css' % (n, suffix),
+ outs = ['theme_%s%s.css' % (n, suffix)],
)
# Merge Addon bundled with diff-match-patch
@@ -321,7 +321,7 @@
'@codemirror_original//jar',
'@codemirror_minified//jar',
],
- out = 'addon_merge%s.js' % suffix,
+ outs = ['addon_merge%s.js' % suffix],
)
# Jar packaging
@@ -347,7 +347,7 @@
] + [
':theme_%s%s' % (n, suffix) for n in CM_THEMES
],
- outs = [ 'codemirror%s.jar' % suffix ],
+ outs = ['codemirror%s.jar' % suffix],
)
native.java_import(
diff --git a/lib/js/BUILD b/lib/js/BUILD
index 6758ca1..71fa94f 100644
--- a/lib/js/BUILD
+++ b/lib/js/BUILD
@@ -32,18 +32,3 @@
srcs = [ "//lib/highlightjs:highlight.min.js" ],
data = ['//lib:LICENSE-highlightjs',],
)
-
-bower_component(
- name = 'iron-test-helpers',
- seed = True,
-)
-
-bower_component(
- name = 'test-fixture',
- seed = True,
-)
-
-bower_component(
- name = 'web-component-tester',
- seed = True,
-)
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index d90a7cc..9c9a5a9 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -7,6 +7,21 @@
load("//tools/bzl:js.bzl", "bower_archive")
def load_bower_archives():
bower_archive(
+ name = "accessibility-developer-tools",
+ package = "accessibility-developer-tools",
+ version = "2.11.0",
+ sha1 = "792cb24b649dafb316e7e536f8ae65d0d7b52bab")
+ bower_archive(
+ name = "async",
+ package = "async",
+ version = "1.5.2",
+ sha1 = "1ec975d3b3834646a7e3d4b7e68118b90ed72508")
+ bower_archive(
+ name = "chai",
+ package = "chai",
+ version = "3.5.0",
+ sha1 = "849ad3ee7c77506548b7b5db603a4e150b9431aa")
+ bower_archive(
name = "iron-a11y-announcer",
package = "iron-a11y-announcer",
version = "1.0.5",
@@ -52,11 +67,36 @@
version = "1.1.1",
sha1 = "480423380be0536f948735d91bc472f6e7ced5b4")
bower_archive(
+ name = "lodash",
+ package = "lodash",
+ version = "3.10.1",
+ sha1 = "2f207a8293c4c554bf6cf071241f7a00dc513d3a")
+ bower_archive(
+ name = "mocha",
+ package = "mocha",
+ version = "2.5.3",
+ sha1 = "22ef0d1f43ba5e2241369c501ac648f00c0440c0")
+ bower_archive(
name = "neon-animation",
package = "neon-animation",
version = "1.2.4",
sha1 = "e8ccbb930c4b7ff470b1450baa901618888a7fd3")
bower_archive(
+ name = "sinon-chai",
+ package = "sinon-chai",
+ version = "2.8.0",
+ sha1 = "0464b5d944fdf8116bb23e0b02ecfbac945b3517")
+ bower_archive(
+ name = "sinonjs",
+ package = "sinonjs",
+ version = "1.17.1",
+ sha1 = "a26a6aab7358807de52ba738770f6ac709afd240")
+ bower_archive(
+ name = "stacky",
+ package = "stacky",
+ version = "1.3.2",
+ sha1 = "d6c07a0112ab2e9677fe085933744466a89232fb")
+ bower_archive(
name = "web-animations-js",
package = "web-animations-js",
version = "2.2.2",
diff --git a/lib/js/bower_components.bzl b/lib/js/bower_components.bzl
index 480d6ce..74515e1 100644
--- a/lib/js/bower_components.bzl
+++ b/lib/js/bower_components.bzl
@@ -7,6 +7,18 @@
load("//tools/bzl:js.bzl", "bower_component")
def define_bower_components():
bower_component(
+ name = "accessibility-developer-tools",
+ license = "//lib:LICENSE-Apache2.0",
+ )
+ bower_component(
+ name = "async",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
+ name = "chai",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
name = "es6-promise",
license = "//lib:LICENSE-polymer",
seed = True,
@@ -112,6 +124,12 @@
seed = True,
)
bower_component(
+ name = "iron-test-helpers",
+ license = "//lib:LICENSE-polymer",
+ deps = [ ":polymer" ],
+ seed = True,
+ )
+ bower_component(
name = "iron-validatable-behavior",
license = "//lib:LICENSE-polymer",
deps = [
@@ -120,6 +138,14 @@
],
)
bower_component(
+ name = "lodash",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
+ name = "mocha",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
name = "moment",
license = "//lib:LICENSE-moment",
seed = True,
@@ -153,10 +179,43 @@
seed = True,
)
bower_component(
+ name = "sinon-chai",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
+ name = "sinonjs",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
+ name = "stacky",
+ license = "//lib:LICENSE-polymer",
+ )
+ bower_component(
+ name = "test-fixture",
+ license = "//lib:LICENSE-polymer",
+ seed = True,
+ )
+ bower_component(
name = "web-animations-js",
license = "//lib:LICENSE-Apache2.0",
)
bower_component(
+ name = "web-component-tester",
+ license = "//lib:LICENSE-polymer",
+ deps = [
+ ":accessibility-developer-tools",
+ ":async",
+ ":chai",
+ ":lodash",
+ ":mocha",
+ ":sinon-chai",
+ ":sinonjs",
+ ":stacky",
+ ":test-fixture",
+ ],
+ seed = True,
+ )
+ bower_component(
name = "webcomponentsjs",
license = "//lib:LICENSE-polymer",
)
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html
index 98f2b18..588fff5 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.html
@@ -52,7 +52,8 @@
change="[[change]]"
filter="[[filter]]"
placeholder="[[placeholder]]"
- on-add="_handleAdd">
+ on-add="_handleAdd"
+ on-input-keydown="_handleInputKeydown">
</gr-account-entry>
</template>
<script src="gr-account-list.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
index 4e0ff94..2e3200f 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
@@ -37,6 +37,10 @@
'remove': '_handleRemove',
},
+ get accountChips() {
+ return Polymer.dom(this.root).querySelectorAll('gr-account-chip');
+ },
+
get focusStart() {
return this.$.entry.focusStart;
},
@@ -104,6 +108,19 @@
e.detail.account);
},
+ _handleInputKeydown: function(e) {
+ var input = e.detail.input;
+ if (input.selectionStart !== input.selectionEnd ||
+ input.selectionStart !== 0) {
+ return;
+ }
+ switch (e.detail.keyCode) {
+ case 8: // Backspace
+ this.splice('accounts', this.accounts.length - 1, 1);
+ break;
+ }
+ },
+
additions: function() {
return this.accounts.filter(function(account) {
return account._pendingAdd;
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
index 7092c4e..cbfe1ff 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list_test.html
@@ -228,5 +228,34 @@
},
]);
});
+
+ suite('keyboard interactions', function() {
+ var sandbox;
+ setup(function() {
+ sandbox = sinon.sandbox.create();
+ });
+
+ teardown(function() {
+ sandbox.restore();
+ });
+
+ test('backspace from input removes account iff cursor is in start pos',
+ function(done) {
+ var input = element.$.entry.$.input;
+ sandbox.stub(element.$.entry, '_getReviewerSuggestions');
+ sandbox.stub(input, '_updateSuggestions');
+ input.text = 'test';
+ MockInteractions.focus(input.$.input);
+ flush(function() {
+ assert.equal(element.accounts.length, 2);
+ MockInteractions.pressAndReleaseKeyOn(input.$.input, 8); // Backspace
+ assert.equal(element.accounts.length, 2);
+ input.text = '';
+ MockInteractions.pressAndReleaseKeyOn(input.$.input, 8); // Backspace
+ assert.equal(element.accounts.length, 1);
+ done();
+ });
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 4528db1..6eeb6df 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -18,6 +18,7 @@
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
+<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
@@ -210,7 +211,7 @@
}
}
</style>
- <div class="container loading" hidden$="{{!_loading}}">Loading...</div>
+ <div class="container loading" hidden$="[[!_loading]]">Loading...</div>
<div class="container" hidden$="{{_loading}}">
<div class="header">
<span class="header-title">
@@ -235,7 +236,7 @@
<div id="change_plugins"></div>
</div>
<div class="changeInfo-column mainChangeInfo">
- <div class="commitActions" hidden$="[[!_loggedIn]]"">
+ <div class="commitActions" hidden$="[[!_loggedIn]]">
<gr-button
class="reply"
secondary
@@ -265,18 +266,24 @@
<div class="relatedChanges">
<gr-related-changes-list id="relatedChanges"
change="[[_change]]"
- patch-num="[[_computeLatestPatchNum(_allPatchSets)]]"></gr-related-changes-list>
+ patch-num="[[_computeLatestPatchNum(_allPatchSets)]]">
+ </gr-related-changes-list>
</div>
</div>
</div>
</section>
- <section class$="patchInfo [[_computePatchInfoClass(_patchRange.patchNum, _allPatchSets)]]">
+ <section class$="patchInfo [[_computePatchInfoClass(_patchRange.patchNum,
+ _allPatchSets)]]">
<div class="patchInfo-header">
<div>
- <label class="patchSelectLabel" for="patchSetSelect">Patch set</label>
- <select id="patchSetSelect" on-change="_handlePatchChange">
- <template is="dom-repeat" items="[[_allPatchSets]]" as="patchNumber">
- <option value$="[[patchNumber]]" selected$="[[_computePatchIndexIsSelected(index, _patchRange.patchNum)]]">
+ <label class="patchSelectLabel" for="patchSetSelect">
+ Patch set
+ </label>
+ <select id="patchSetSelect" bind-value="{{_selectedPatchSet}}"
+ is="gr-select" on-change="_handlePatchChange">
+ <template is="dom-repeat" items="[[_allPatchSets]]"
+ as="patchNumber">
+ <option value$="[[patchNumber]]">
<span>[[patchNumber]]</span>
/
<span>[[_computeLatestPatchNum(_allPatchSets)]]</span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index f6ecc9e..bac25be 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -73,7 +73,10 @@
type: String,
value: '',
},
- _patchRange: Object,
+ _patchRange: {
+ type: Object,
+ observer: '_updateSelected',
+ },
_allPatchSets: {
type: Array,
computed: '_computeAllPatchSets(_change)',
@@ -89,6 +92,7 @@
value: 'Reply',
computed: '_computeReplyButtonLabel(_diffDrafts.*)',
},
+ _selectedPatchSet: String,
_initialLoadComplete: {
type: Boolean,
value: false,
@@ -458,6 +462,8 @@
this._patchRange.patchNum ||
this._computeLatestPatchNum(this._allPatchSets));
+ this._updateSelected();
+
var title = change.subject + ' (' + change.change_id.substr(0, 9) + ')';
this.fire('title-change', {title: title});
},
@@ -528,10 +534,6 @@
}
},
- _computePatchIndexIsSelected: function(index, patchNum) {
- return this._allPatchSets[index] == patchNum;
- },
-
_computeLabelNames: function(labels) {
return Object.keys(labels).sort();
},
@@ -776,5 +778,9 @@
this.$.fileList.reload(),
]);
},
+
+ _updateSelected: function() {
+ this._selectedPatchSet = this._patchRange.patchNum;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 8358e40..e61d6b0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -277,12 +277,10 @@
var optionEls = Polymer.dom(element.root).querySelectorAll(
'.patchInfo-header option');
assert.equal(optionEls.length, 4);
- assert.isFalse(element.$$('.patchInfo-header option[value="1"]')
- .hasAttribute('selected'));
- assert.isTrue(element.$$('.patchInfo-header option[value="2"]')
- .hasAttribute('selected'));
- assert.isFalse(element.$$('.patchInfo-header option[value="3"]')
- .hasAttribute('selected'));
+ var select = element.$$('.patchInfo-header #patchSetSelect').bindValue;
+ assert.notEqual(select, 1);
+ assert.equal(select, 2);
+ assert.notEqual(select, 3);
assert.equal(optionEls[3].value, 13);
var showStub = sandbox.stub(page, 'show');
@@ -329,12 +327,12 @@
var optionEls = Polymer.dom(element.root).querySelectorAll(
'.patchInfo-header option');
assert.equal(optionEls.length, 4);
- assert.isFalse(element.$$('.patchInfo-header option[value="1"]')
- .hasAttribute('selected'));
- assert.isTrue(element.$$('.patchInfo-header option[value="2"]')
- .hasAttribute('selected'));
- assert.isFalse(element.$$('.patchInfo-header option[value="3"]')
- .hasAttribute('selected'));
+ assert.notEqual(
+ element.$$('.patchInfo-header #patchSetSelect').bindValue, 1);
+ assert.equal(
+ element.$$('.patchInfo-header #patchSetSelect').bindValue, 2);
+ assert.notEqual(
+ element.$$('.patchInfo-header #patchSetSelect').bindValue, 3);
assert.equal(optionEls[3].value, 13);
var showStub = sandbox.stub(page, 'show');
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 007c0fc..f11cca2 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -22,6 +22,7 @@
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../shared/gr-select/gr-select.html">
<dom-module id="gr-file-list">
<template>
@@ -182,13 +183,17 @@
<span class="separator">/</span>
<label>
Diff against
- <select id="patchChange" on-change="_handlePatchChange">
+ <select id="patchChange" bind-value="{{_diffAgainst}}" is="gr-select"
+ on-change="_handlePatchChange">
<option value="PARENT">Base</option>
- <template is="dom-repeat" items="[[_computePatchSets(revisions, patchRange.*)]]" as="patchNum">
- <option
- value$="[[patchNum]]"
- selected$="[[_computePatchSetSelected(patchNum, patchRange.basePatchNum)]]"
- disabled$="[[_computePatchSetDisabled(patchNum, patchRange.patchNum)]]">[[patchNum]]</option>
+ <template
+ is="dom-repeat"
+ items="[[_computePatchSets(revisions, patchRange.*)]]"
+ as="patchNum">
+ <option value$="[[patchNum]]" disabled$=
+ "[[_computePatchSetDisabled(patchNum, patchRange.patchNum)]]">
+ [[patchNum]]
+ </option>
</template>
</select>
</label>
@@ -207,7 +212,8 @@
<div class$="[[_computeClass('status', file.__path)]]">
[[_computeFileStatus(file.status)]]
</div>
- <a class="path" href$="[[_computeDiffURL(changeNum, patchRange, file.__path)]]">
+ <a class="path"
+ href$="[[_computeDiffURL(changeNum, patchRange, file.__path)]]">
<div title$="[[_computeFileDisplayName(file.__path)]]">
[[_computeFileDisplayName(file.__path)]]
</div>
@@ -217,7 +223,9 @@
</div>
</a>
<div class="comments">
- <span class="drafts">[[_computeDraftsString(drafts, patchRange.patchNum, file.__path)]]</span>
+ <span class="drafts">
+ [[_computeDraftsString(drafts, patchRange.patchNum, file.__path)]]
+ </span>
[[_computeCommentsString(comments, patchRange.patchNum, file.__path)]]
</div>
<div class$="[[_computeClass('stats', file.__path)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 340cb63..bf6dcf3 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -25,7 +25,10 @@
is: 'gr-file-list',
properties: {
- patchRange: Object,
+ patchRange: {
+ type: Object,
+ observer: '_updateSelected',
+ },
patchNum: String,
changeNum: String,
comments: Object,
@@ -58,6 +61,7 @@
type: Array,
value: function() { return []; },
},
+ _diffAgainst: String,
_diffPrefs: Object,
_userPrefs: Object,
_localPrefs: Object,
@@ -181,10 +185,6 @@
return parseInt(patchNum, 10) >= parseInt(currentPatchNum, 10);
},
- _computePatchSetSelected: function(patchNum, basePatchNum) {
- return parseInt(patchNum, 10) === parseInt(basePatchNum, 10);
- },
-
_handleHiddenChange: function(e) {
var model = e.model;
model.set('file.__expanded', !model.file.__expanded);
@@ -527,6 +527,10 @@
this._numFilesShown = this._files.length;
},
+ _updateSelected: function(patchRange) {
+ this._diffAgainst = patchRange.basePatchNum;
+ },
+
/**
* _getDiffViewMode: Get the diff view (side-by-side or unified) based on
* the current state.
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 0caa6be..b530bae 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -436,7 +436,7 @@
document.getElementById('blank').restore();
});
- test('show/hide diffs disabled for large amounds of files', function(done) {
+ test('show/hide diffs disabled for large amounts of files', function(done) {
element._files = [];
element.changeNum = '42';
element.patchRange = {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index 2abd0a8..410a813 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -215,7 +215,9 @@
_handleTextareaKeydown: function(e) {
switch (e.keyCode) {
case 27: // 'esc'
- this._handleCancel(e);
+ if (this._messageText.length === 0) {
+ this._handleCancel(e);
+ }
break;
case 83: // 's'
if (e.ctrlKey) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index 11de0ba..0ad3e11 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -47,6 +47,7 @@
suite('gr-diff-comment tests', function() {
var element;
+ var sandbox;
setup(function() {
stub('gr-rest-api-interface', {
getAccount: function() { return Promise.resolve(null); },
@@ -62,6 +63,11 @@
message: 'is this a crossover episode!?',
updated: '2015-12-08 19:48:33.843000000',
};
+ sandbox = sinon.sandbox.create();
+ });
+
+ teardown(function() {
+ sandbox.restore();
});
test('collapsible comments', function() {
@@ -149,6 +155,22 @@
assert.isFalse(isVisible(element.$$('.collapsedContent')),
'header middle content is is not visible');
});
+
+ test('esc does not close comment unless text is empty', function(done) {
+ element.editing = true;
+ element._messageText = 'test';
+ var textarea = element.$.editTextarea;
+ var closeSpy = sandbox.spy(element, '_handleCancel');
+
+ flush(function() {
+ MockInteractions.pressAndReleaseKeyOn(textarea, 27); // esc
+ assert.isFalse(closeSpy.called);
+ element._messageText = '';
+ MockInteractions.pressAndReleaseKeyOn(textarea, 27); // esc
+ assert.isTrue(closeSpy.called);
+ done();
+ });
+ });
});
suite('gr-diff-comment draft tests', function() {
@@ -321,6 +343,7 @@
MockInteractions.tap(element.$$('.cancel'));
MockInteractions.tap(element.$$('.discard'));
element.flushDebouncer('fire-update');
+ element._messageText = '';
MockInteractions.pressAndReleaseKeyOn(element.$.editTextarea, 27); // esc
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 0816501..855f45a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -90,6 +90,10 @@
.full-width {
width: 100%;
}
+ .full-width .contentText {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ }
.lineNum,
.content {
/* Set font size based the user's diff preference. */
@@ -138,10 +142,6 @@
background-color: #fef;
color: #849;
}
- .contentText {
- white-space: pre-wrap;
- word-wrap: break-word;
- }
.contextControl gr-button {
display: inline-block;
font-family: var(--monospace-font-family);
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
index c496703..54bccb3 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<dom-module id="gr-patch-range-select">
@@ -28,12 +28,11 @@
</style>
Patch set:
<span class="patchRange">
- <select id="leftPatchSelect" on-change="_handlePatchChange">
- <option value="PARENT"
- selected$="[[_computeLeftSelected('PARENT', patchRange)]]">Base</option>
+ <select id="leftPatchSelect" bind-value="{{_leftSelected}}"
+ on-change="_handlePatchChange" is="gr-select">
+ <option value="PARENT">Base</option>
<template is="dom-repeat" items="{{availablePatches}}" as="patchNum">
<option value$="[[patchNum]]"
- selected$="[[_computeLeftSelected(patchNum, patchRange)]]"
disabled$="[[_computeLeftDisabled(patchNum, patchRange)]]">[[patchNum]]</option>
</template>
</select>
@@ -46,10 +45,10 @@
</span>
→
<span class="patchRange">
- <select id="rightPatchSelect" on-change="_handlePatchChange">
+ <select id="rightPatchSelect" bind-value="{{_rightSelected}}"
+ on-change="_handlePatchChange" is="gr-select">
<template is="dom-repeat" items="{{availablePatches}}" as="patchNum">
<option value$="[[patchNum]]"
- selected$="[[_computeRightSelected(patchNum, patchRange)]]"
disabled$="[[_computeRightDisabled(patchNum, patchRange)]]">[[patchNum]]</option>
</template>
</select>
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index b50043e..350429f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -21,13 +21,23 @@
availablePatches: Array,
changeNum: String,
filesWeblinks: Object,
- patchRange: Object,
path: String,
+ patchRange: {
+ type: Object,
+ observer: '_updateSelected'
+ },
+ _rightSelected: String,
+ _leftSelected: String,
+ },
+
+ _updateSelected: function() {
+ this._rightSelected = this.patchRange.patchNum;
+ this._leftSelected = this.patchRange.basePatchNum;
},
_handlePatchChange: function(e) {
- var leftPatch = this.$.leftPatchSelect.value;
- var rightPatch = this.$.rightPatchSelect.value;
+ var leftPatch = this._leftSelected;
+ var rightPatch = this._rightSelected;
var rangeStr = rightPatch;
if (leftPatch != 'PARENT') {
rangeStr = leftPatch + '..' + rangeStr;
@@ -36,14 +46,6 @@
e.target.blur();
},
- _computeLeftSelected: function(patchNum, patchRange) {
- return patchNum == patchRange.basePatchNum;
- },
-
- _computeRightSelected: function(patchNum, patchRange) {
- return patchNum == patchRange.patchNum;
- },
-
_computeLeftDisabled: function(patchNum, patchRange) {
return parseInt(patchNum, 10) >= parseInt(patchRange.patchNum, 10);
},
@@ -52,5 +54,18 @@
if (patchRange.basePatchNum == 'PARENT') { return false; }
return parseInt(patchNum, 10) <= parseInt(patchRange.basePatchNum, 10);
},
+
+ // On page load, the dom-if for options getting added occurs after
+ // the value was set in the select. This ensures that after they
+ // are loaded, the correct value will get selected. I attempted to
+ // debounce these, but because they are detecting two different
+ // events, sometimes the timing was off and one ended up missing.
+ _synchronizeSelectionRight: function() {
+ this.$.rightPatchSelect.value = this._rightSelected;
+ },
+
+ _synchronizeSelectionLeft: function() {
+ this.$.leftPatchSelect.value = this._leftSelected;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index 95789bc..68eeaa9 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -67,6 +67,10 @@
element.changeNum = '42';
element.path = 'path/to/file.txt';
element.availablePatches = ['1', '2', '3'];
+ element.patchRange = {
+ basePatchNum: 'PARENT',
+ patchNum: '3',
+ };
flushAsynchronousOperations();
var numEvents = 0;
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 601a056..1c9e010 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -31,6 +31,13 @@
* @event cancel
*/
+ /**
+ * Fired on keydown to allow for custom hooks into autocomplete textbox
+ * behavior.
+ *
+ * @event input-keydown
+ */
+
properties: {
/**
@@ -212,6 +219,7 @@
this._commit();
break;
}
+ this.fire('input-keydown', {keyCode: e.keyCode, input: this.$.input});
},
_cancel: function() {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 431c795..03fa8e43 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -297,5 +297,13 @@
focusSpy.restore();
commitSpy.restore();
});
+
+ test('input-keydown event fired', function() {
+ var listener = sinon.spy();
+ element.addEventListener('input-keydown', listener);
+ MockInteractions.pressAndReleaseKeyOn(element.$.input, 9); // tab
+ flushAsynchronousOperations();
+ assert.isTrue(listener.called);
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index cb852fd..a3eccb8 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -50,27 +50,19 @@
_contentOrConfigChanged: function(content, config) {
var output = Polymer.dom(this.$.output);
output.textContent = '';
- var parser = new GrLinkTextParser(config, function(text, href, html) {
+ var parser = new GrLinkTextParser(
+ config, function(text, href, fragment) {
if (href) {
var a = document.createElement('a');
a.href = href;
a.textContent = text;
a.target = '_blank';
output.appendChild(a);
- } else if (html) {
- var fragment = document.createDocumentFragment();
- // Create temporary div to hold the nodes in.
- var div = document.createElement('div');
- div.innerHTML = html;
- while (div.firstChild) {
- fragment.appendChild(div.firstChild);
- }
+ } else if (fragment) {
output.appendChild(fragment);
- } else {
- output.appendChild(document.createTextNode(text));
}
});
parser.parse(content);
- }
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index 5203520..f530331 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -123,6 +123,35 @@
assert.equal(linkEl2.textContent, 'Issue 3450');
});
+ test('Change-Id pattern parsed before bug pattern', function() {
+ // "Change-Id:" pattern.
+ var changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
+ var prefix = 'Change-Id: ';
+
+ // "Issue/Bug" pattern.
+ var bug = 'Issue 3650';
+
+ var changeUrl = '/q/' + changeID;
+ var bugUrl = 'https://code.google.com/p/gerrit/issues/detail?id=3650';
+
+ element.content = prefix + changeID + bug;
+
+ var textNode = element.$.output.childNodes[0];
+ var changeLinkEl = element.$.output.childNodes[1];
+ var bugLinkEl = element.$.output.childNodes[2];
+
+ assert.equal(textNode.textContent, prefix);
+
+ assert.equal(changeLinkEl.target, '_blank');
+ assert.isTrue(changeLinkEl.href.endsWith(changeUrl));
+ assert.equal(changeLinkEl.textContent, changeID);
+
+ assert.equal(bugLinkEl.target, '_blank');
+ assert.equal(bugLinkEl.href, bugUrl);
+ assert.equal(bugLinkEl.textContent, 'Issue 3650');
+ });
+
+
test('html field in link config', function() {
element.content = 'google:do a barrel roll';
var linkEl = element.$.output.childNodes[0];
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index b4b1678..5e1ff62 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -27,13 +27,83 @@
this.callback(text, href);
};
-GrLinkTextParser.prototype.addHTML = function(html) {
- this.callback(null, null, html);
+GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
+ this.sortArrayReverse(outputArray);
+ var fragment = document.createDocumentFragment();
+ var cursor = text.length;
+
+ // Start inserting linkified URLs from the end of the String. That way, the
+ // string positions of the items don't change as we iterate through.
+ outputArray.forEach(function(item) {
+ // Add any text between the current linkified item and the item added before
+ // if it exists.
+ if (item.position + item.length !== cursor) {
+ fragment.insertBefore(
+ document.createTextNode(
+ text.slice(item.position + item.length, cursor)),
+ fragment.firstChild);
+ }
+ fragment.insertBefore(item.html, fragment.firstChild);
+ cursor = item.position;
+ });
+
+ // Add the beginning portion at the end.
+ if (cursor !== 0) {
+ fragment.insertBefore(
+ document.createTextNode(text.slice(0, cursor)), fragment.firstChild);
+ }
+
+ this.callback(null, null, fragment);
+};
+
+GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) {
+ outputArray.sort(function(a, b) {return b.position - a.position});
+};
+
+GrLinkTextParser.prototype.addItem =
+ function(text, href, html, position, length, outputArray) {
+ var htmlOutput = '';
+
+ if (href) {
+ var a = document.createElement('a');
+ a.href = href;
+ a.textContent = text;
+ a.target = '_blank';
+ htmlOutput = a;
+ } else if (html) {
+ var fragment = document.createDocumentFragment();
+ // Create temporary div to hold the nodes in.
+ var div = document.createElement('div');
+ div.innerHTML = html;
+ while (div.firstChild) {
+ fragment.appendChild(div.firstChild);
+ }
+ htmlOutput = fragment;
+ }
+
+ outputArray.push({
+ html: htmlOutput,
+ position: position,
+ length: length,
+ });
+};
+
+GrLinkTextParser.prototype.addLink =
+ function(text, href, position, length, outputArray) {
+ if (!text) {
+ return;
+ }
+ this.addItem(text, href, null, position, length, outputArray);
+};
+
+GrLinkTextParser.prototype.addHTML =
+ function(html, position, length, outputArray) {
+ this.addItem(null, null, html, position, length, outputArray);
};
GrLinkTextParser.prototype.parse = function(text) {
linkify(text, {
- callback: this.parseChunk.bind(this)
+ callback: this.parseChunk.bind(this),
});
};
@@ -46,6 +116,8 @@
};
GrLinkTextParser.prototype.parseLinks = function(text, patterns) {
+ // The outputArray is used to store all of the matches found for all patterns.
+ var outputArray = [];
for (var p in patterns) {
if (patterns[p].enabled != null && patterns[p].enabled == false) {
continue;
@@ -66,22 +138,29 @@
var pattern = new RegExp(patterns[p].match, 'g');
var match;
- while ((match = pattern.exec(text)) != null) {
- var before = text.substr(0, match.index);
- this.addText(before);
- text = text.substr(match.index + match[0].length);
+ var textToCheck = text;
+ var susbtrIndex = 0;
+
+ while ((match = pattern.exec(textToCheck)) != null) {
+ textToCheck = textToCheck.substr(match.index + match[0].length);
var result = match[0].replace(pattern,
patterns[p].html || patterns[p].link);
if (patterns[p].html) {
- this.addHTML(result);
+ this.addHTML(
+ result, susbtrIndex + match.index, match[0].length, outputArray);
} else if (patterns[p].link) {
- this.addText(match[0], result);
+ this.addLink(match[0], result,
+ susbtrIndex + match.index, match[0].length, outputArray);
} else {
throw Error('linkconfig entry ' + p +
' doesn’t contain a link or html attribute.');
}
+
+ // Update the substring location so we know where we are in relation to
+ // the initial full text string.
+ susbtrIndex = susbtrIndex + match.index + match[0].length;
}
}
- this.addText(text);
+ this.processLinks(text, outputArray);
};
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
index 9e14f08..bef260e9 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select.js
@@ -16,40 +16,33 @@
Polymer({
is: 'gr-select',
-
extends: 'select',
-
properties: {
bindValue: {
type: String,
notify: true,
+ observer: '_updateValue',
},
},
- observers: [
- '_valueChanged(bindValue)',
- ],
+ listeners: {
+ change: '_valueChanged',
+ 'dom-change': '_updateValue',
+ },
- attached: function() {
- this.addEventListener('change', function() {
- this.bindValue = this.value;
- });
+ _updateValue: function() {
+ if (this.bindValue) {
+ this.value = this.bindValue;
+ }
+ },
+
+ _valueChanged: function() {
+ this.bindValue = this.value;
},
ready: function() {
// If not set via the property, set bind-value to the element value.
if (!this.bindValue) { this.bindValue = this.value; }
},
-
- _valueChanged: function(bindValue) {
- var options = Polymer.dom(this.root).querySelectorAll('option');
- for (var i = 0; i < options.length; i++) {
- if (options[i].getAttribute('value') === bindValue + '') {
- options[i].setAttribute('selected', true);
- this.value = bindValue;
- break;
- }
- }
- },
});
})();
diff --git a/polygerrit-ui/app/index.html b/polygerrit-ui/app/index.html
index 0e00f77..a5cddad 100644
--- a/polygerrit-ui/app/index.html
+++ b/polygerrit-ui/app/index.html
@@ -23,6 +23,7 @@
<link rel="stylesheet" href="/styles/fonts.css">
<link rel="stylesheet" href="/styles/main.css">
<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="preload" href="/elements/gr-app.js">
<link rel="import" href="/elements/gr-app.html">
<body unresolved>
diff --git a/tools/bzl/gwt.bzl b/tools/bzl/gwt.bzl
index c27e28f..27e0740 100644
--- a/tools/bzl/gwt.bzl
+++ b/tools/bzl/gwt.bzl
@@ -12,10 +12,72 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# GWT Rules Skylark rules for building [GWT](http://www.gwtproject.org/)
-# modules using Bazel.
+# Port of Buck native gwt_binary() rule. See discussion in context of
+# https://github.com/facebook/buck/issues/109
+load('//tools/bzl:genrule2.bzl', 'genrule2')
load('//tools/bzl:java.bzl', 'java_library2')
+jar_filetype = FileType(['.jar'])
+
+BROWSERS = [
+ 'chrome',
+ 'firefox',
+ 'gecko1_8',
+ 'safari',
+ 'msie', 'ie8', 'ie9', 'ie10',
+ 'edge',
+]
+ALIASES = {
+ 'chrome': 'safari',
+ 'firefox': 'gecko1_8',
+ 'msie': 'ie10',
+ 'edge': 'gecko1_8',
+}
+
+MODULE = 'com.google.gerrit.GerritGwtUI'
+
+GWT_COMPILER = "com.google.gwt.dev.Compiler"
+
+GWT_JVM_ARGS = ['-Xmx512m']
+
+GWT_COMPILER_ARGS = [
+ '-XdisableClassMetadata',
+]
+
+GWT_COMPILER_ARGS_RELEASE_MODE = GWT_COMPILER_ARGS + [
+ '-XdisableCastChecking',
+]
+
+GWT_TRANSITIVE_DEPS = [
+ '//lib/gwt:ant',
+ '//lib/gwt:colt',
+ '//lib/gwt:javax-validation',
+ '//lib/gwt:javax-validation_src',
+ '//lib/gwt:jsinterop-annotations',
+ '//lib/gwt:jsinterop-annotations_src',
+ '//lib/gwt:tapestry',
+ '//lib/gwt:w3c-css-sac',
+ '//lib/ow2:ow2-asm',
+ '//lib/ow2:ow2-asm-analysis',
+ '//lib/ow2:ow2-asm-commons',
+ '//lib/ow2:ow2-asm-tree',
+ '//lib/ow2:ow2-asm-util',
+]
+
+DEPS = GWT_TRANSITIVE_DEPS + [
+ '//gerrit-gwtexpui:CSS',
+ '//lib:gwtjsonrpc',
+ '//lib/gwt:dev',
+ '@jgit_src//file',
+]
+
+USER_AGENT_XML = """<module rename-to='gerrit_ui'>
+<inherits name='%s'/>
+<set-property name='user.agent' value='%s'/>
+<set-property name='locale' value='default'/>
+</module>
+"""
+
def gwt_module(gwt_xml=None, resources=[], srcs=[], **kwargs):
if gwt_xml:
resources += [gwt_xml]
@@ -24,3 +86,201 @@
srcs = srcs,
resources = resources,
**kwargs)
+
+def _gwt_user_agent_module(ctx):
+ """Generate user agent specific GWT module."""
+ if not ctx.attr.user_agent:
+ return None
+
+ ua = ctx.attr.user_agent
+ impl = ua
+ if ua in ALIASES:
+ impl = ALIASES[ua]
+
+ # intermediate artifact: user agent speific GWT xml file
+ gwt_user_agent_xml = ctx.new_file(ctx.label.name + "_gwt.xml")
+ ctx.file_action(output = gwt_user_agent_xml,
+ content=USER_AGENT_XML % (MODULE, impl))
+
+ # intermediate artifact: user agent specific zip with GWT module
+ gwt_user_agent_zip = ctx.new_file(ctx.label.name + "_gwt.zip")
+ gwt = '%s_%s.gwt.xml' % (MODULE.replace('.', '/'), ua)
+ dir = gwt_user_agent_zip.path + ".dir"
+ cmd = " && ".join([
+ "p=$PWD",
+ "mkdir -p %s" % dir,
+ "cd %s" % dir,
+ "mkdir -p $(dirname %s)" % gwt,
+ "cp $p/%s %s" % (gwt_user_agent_xml.path, gwt),
+ "$p/%s cC $p/%s $(find . | sed 's|^./||')" % (ctx.executable._zip.path, gwt_user_agent_zip.path)
+ ])
+ ctx.action(
+ inputs = [gwt_user_agent_xml] + ctx.files._zip,
+ outputs = [gwt_user_agent_zip],
+ command = cmd,
+ mnemonic = "GenerateUserAgentGWTModule")
+
+ return struct(
+ zip=gwt_user_agent_zip,
+ module=MODULE + '_' + ua
+ )
+
+def _gwt_binary_impl(ctx):
+ module = MODULE
+ output_zip = ctx.outputs.output
+ output_dir = output_zip.path + '.gwt_output'
+ deploy_dir = output_zip.path + '.gwt_deploy'
+
+ deps = _get_transitive_closure(ctx)
+
+ paths = []
+ for dep in deps:
+ paths.append(dep.path)
+
+ gwt_user_agent_modules = []
+ ua = _gwt_user_agent_module(ctx)
+ if ua:
+ paths.append(ua.zip.path)
+ gwt_user_agent_modules.append(ua.zip)
+ module = ua.module
+
+ cmd = "external/local_jdk/bin/java %s -Dgwt.normalizeTimestamps=true -cp %s %s -war %s -deploy %s " % (
+ " ".join(ctx.attr.jvm_args),
+ ":".join(paths),
+ GWT_COMPILER,
+ output_dir,
+ deploy_dir,
+ )
+ # TODO(davido): clean up command concatenation
+ cmd += " ".join([
+ "-style %s" % ctx.attr.style,
+ "-optimize %s" % ctx.attr.optimize,
+ "-strict",
+ " ".join(ctx.attr.compiler_args),
+ module + "\n",
+ "rm -rf %s/gwt-unitCache\n" % output_dir,
+ "root=`pwd`\n",
+ "cd %s; $root/%s Cc ../%s $(find .)\n" % (
+ output_dir,
+ ctx.executable._zip.path,
+ output_zip.basename,
+ )
+ ])
+
+ ctx.action(
+ inputs = list(deps) + ctx.files._jdk + ctx.files._zip + gwt_user_agent_modules,
+ outputs = [output_zip],
+ mnemonic = "GwtBinary",
+ progress_message = "GWT compiling " + output_zip.short_path,
+ command = "set -e\n" + cmd,
+ )
+
+def _get_transitive_closure(ctx):
+ deps = set()
+ for dep in ctx.attr.module_deps:
+ deps += dep.java.transitive_runtime_deps
+ deps += dep.java.transitive_source_jars
+ for dep in ctx.attr.deps:
+ if hasattr(dep, 'java'):
+ deps += dep.java.transitive_runtime_deps
+ elif hasattr(dep, 'files'):
+ deps += dep.files
+
+ return deps
+
+gwt_binary = rule(
+ implementation = _gwt_binary_impl,
+ attrs = {
+ "user_agent": attr.string(),
+ "style": attr.string(default = "OBF"),
+ "optimize": attr.string(default = "9"),
+ "deps": attr.label_list(allow_files=jar_filetype),
+ "module_deps": attr.label_list(allow_files=jar_filetype),
+ "compiler_args": attr.string_list(),
+ "jvm_args": attr.string_list(),
+ "_jdk": attr.label(
+ default=Label("//tools/defaults:jdk")),
+ "_zip": attr.label(
+ default=Label("@bazel_tools//tools/zip:zipper"),
+ cfg = "host",
+ executable=True,
+ single_file=True),
+ },
+ outputs = {
+ "output": "%{name}.zip",
+ },
+)
+
+def gwt_genrule(suffix = ""):
+ dbg = 'ui_dbg' + suffix
+ opt = 'ui_opt' + suffix
+ module_dep = ':ui_module' + suffix
+ args = GWT_COMPILER_ARGS_RELEASE_MODE if suffix == "_r" else GWT_COMPILER_ARGS
+
+ genrule2(
+ name = 'ui_optdbg' + suffix,
+ srcs = [
+ ':' + dbg,
+ ':' + opt,
+ ],
+ cmd = 'cd $$TMP;' +
+ 'unzip -q $$ROOT/$(location :%s);' % dbg +
+ 'mv' +
+ ' gerrit_ui/gerrit_ui.nocache.js' +
+ ' gerrit_ui/dbg_gerrit_ui.nocache.js;' +
+ 'unzip -qo $$ROOT/$(location :%s);' % opt +
+ 'mkdir -p $$(dirname $@);' +
+ 'zip -qr $$ROOT/$@ .',
+ out = 'ui_optdbg' + suffix + '.zip',
+ visibility = ['//visibility:public'],
+ )
+
+ gwt_binary(
+ name = opt,
+ module_deps = [module_dep],
+ deps = DEPS,
+ compiler_args = args,
+ jvm_args = GWT_JVM_ARGS,
+ )
+
+ gwt_binary(
+ name = dbg,
+ style = 'PRETTY',
+ optimize = "0",
+ module_deps = [module_dep],
+ deps = DEPS,
+ compiler_args = GWT_COMPILER_ARGS,
+ jvm_args = GWT_JVM_ARGS,
+ )
+
+def gen_ui_module(name, suffix = ""):
+ gwt_module(
+ name = name + suffix,
+ srcs = native.glob(['src/main/java/**/*.java']),
+ gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
+ resources = native.glob(
+ ['src/main/java/**/*'],
+ exclude = ['src/main/java/**/*.java'] +
+ ['src/main/java/%s.gwt.xml' % MODULE.replace('.', '/')]),
+ deps = [
+ '//gerrit-gwtui-common:diffy_logo',
+ '//gerrit-gwtui-common:client',
+ '//gerrit-gwtexpui:CSS',
+ '//lib/codemirror:codemirror' + suffix,
+ '//lib/gwt:user',
+ ],
+ visibility = ['//visibility:public'],
+ )
+
+def gwt_user_agent_permutations():
+ for ua in BROWSERS:
+ gwt_binary(
+ name = "ui_%s" % ua,
+ user_agent = ua,
+ style = 'PRETTY',
+ optimize = "0",
+ module_deps = [':ui_module'],
+ deps = DEPS,
+ compiler_args = GWT_COMPILER_ARGS,
+ jvm_args = GWT_JVM_ARGS,
+ )
diff --git a/tools/js/bower2bazel.py b/tools/js/bower2bazel.py
index 33f7320..08a997f 100755
--- a/tools/js/bower2bazel.py
+++ b/tools/js/bower2bazel.py
@@ -39,6 +39,7 @@
# TODO(hanwen): remove these, and add appropriate license files under //lib
"BSD": "polymer",
"MIT": "polymer",
+ "BSD-3-Clause": "polymer",
}
# list of licenses for packages that don't specify one in their bower.json file.
@@ -47,8 +48,11 @@
"fetch": "fetch",
"moment": "moment",
"page": "page.js",
+ "lodash": "polymer", # MIT, actually.
"promise-polyfill": "promise-polyfill",
"webcomponentsjs": "polymer", # self-identifies as BSD.
+ "sinon-chai": "polymer", # WTFPL & BSD.
+ "sinonjs": "polymer", # BSD.
}
@@ -201,7 +205,7 @@
license = license_map.get(license, license)
else:
if pkg_name not in package_licenses:
- msg = "package %s does not specify license." % pkg_name
+ msg = "package %s does not specify license: %s" % (pkg_name, pkg)
sys.stderr.write(msg)
raise Exception(msg)
license = package_licenses[pkg_name]
diff --git a/tools/maven/BUCK b/tools/maven/BUCK
index 322b5a2..0541fc0 100644
--- a/tools/maven/BUCK
+++ b/tools/maven/BUCK
@@ -1,6 +1,6 @@
-include_defs('//VERSION')
include_defs('//tools/maven/package.defs')
include_defs('//tools/maven/repository.defs')
+include_defs('//version.bzl')
if GERRIT_VERSION.endswith('-SNAPSHOT'):
URL = MAVEN_SNAPSHOT_URL
diff --git a/tools/maven/BUILD b/tools/maven/BUILD
new file mode 100644
index 0000000..14eb2be
--- /dev/null
+++ b/tools/maven/BUILD
@@ -0,0 +1,31 @@
+load('//:version.bzl', 'GERRIT_VERSION')
+load('//tools/maven:package.bzl', 'maven_package')
+
+MAVEN_REPOSITORY = 'sonatype-nexus-staging'
+# TODO(davido): support snapshot repositories
+MAVEN_RELEASE_URL = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
+
+maven_package(
+ repository = MAVEN_REPOSITORY,
+ url = MAVEN_RELEASE_URL,
+ version = GERRIT_VERSION,
+ jar = {
+ 'gerrit-acceptance-framework': '//gerrit-acceptance-framework:acceptance-framework_deploy.jar',
+ 'gerrit-extension-api': '//gerrit-extension-api:extension-api_deploy.jar',
+ 'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api_deploy.jar',
+ 'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api_deploy.jar',
+ },
+ src = {
+ 'gerrit-acceptance-framework': '//gerrit-acceptance-framework:liblib-src.jar',
+ 'gerrit-extension-api': '//gerrit-extension-api:libapi-src.jar',
+ 'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api-sources_deploy.jar',
+ 'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api-source_deploy.jar',
+ },
+ doc = {
+ 'gerrit-acceptance-framework': '//gerrit-acceptance-framework:acceptance-framework-javadoc',
+ 'gerrit-extension-api': '//gerrit-extension-api:extension-api-javadoc',
+ 'gerrit-plugin-api': '//gerrit-plugin-api:plugin-api-javadoc',
+ 'gerrit-plugin-gwtui': '//gerrit-plugin-gwtui:gwtui-api-javadoc',
+ },
+ war = {'gerrit-war': '//:release'},
+)
diff --git a/tools/maven/api.sh b/tools/maven/api.sh
index c7ce65e..93b5f2e 100755
--- a/tools/maven/api.sh
+++ b/tools/maven/api.sh
@@ -14,16 +14,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-if [[ "$#" == "0" ]] ; then
+if [[ "$#" != "2" ]] ; then
cat <<EOF
-Usage: run "$0 COMMAND" from the top of your workspace, where
-COMMAND is one of
+Usage: run "$0 COMMAND BUILD-TOOL" from the top of your workspace,
+where COMMAND is one of
install
deploy
war_install
war_deploy
+and BUILD-TOOL is one of
+
+ buck
+ bazel
Set VERBOSE in the environment to get more information.
EOF
@@ -54,16 +58,33 @@
;;
esac
+case "$2" in
+bazel)
+ buildProc=bazel
+ ;;
+buck)
+ buildProc=buck
+ ;;
+*)
+ echo "unknown build-tool $2. Should be buck or bazel."
+ exit 1
+ ;;
+esac
+
if [[ "${VERBOSE:-x}" != "x" ]]; then
set -o xtrace
fi
-buck build //tools/maven:gen_${command} || \
- { echo "buck failed to build gen_${command}. Use VERBOSE=1 for more info" ; exit 1 ; }
+$buildProc build //tools/maven:gen_${command} || \
+ { echo "$buildProc failed to build gen_${command}. Use VERBOSE=1 for more info" ; exit 1 ; }
-script="./buck-out/gen/tools/maven/gen_${command}/${command}.sh"
-
-# The PEX wrapper does some funky exit handling, so even if the script
-# does "exit(0)", the return status is '1'. So we can't tell if the
-# following invocation was successful.
-${script}
+if [[ "$buildProc" = "bazel" ]]; then
+ script="./bazel-genfiles/tools/maven/${command}.sh"
+ ${script}
+else
+ script="./buck-out/gen/tools/maven/gen_${command}/${command}.sh"
+ # The PEX wrapper does some funky exit handling, so even if the script
+ # does "exit(0)", the return status is '1'. So we can't tell if the
+ # following invocation was successful.
+ ${script}
+fi
diff --git a/tools/maven/package.bzl b/tools/maven/package.bzl
new file mode 100644
index 0000000..c996ac5
--- /dev/null
+++ b/tools/maven/package.bzl
@@ -0,0 +1,93 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+sh_bang_template = (' && '.join([
+ "echo '#!/bin/bash -eu' > $@",
+ 'echo "# this script should run from the root of your workspace." >> $@',
+ 'echo "" >> $@',
+ "echo 'if [[ -n \"$$$${VERBOSE:-}\" ]]; then set -x ; fi' >> $@",
+ 'echo "" >> $@',
+ 'echo %s >> $@',
+ 'echo "" >> $@',
+ 'echo %s >> $@']))
+
+def maven_package(
+ version,
+ repository = None,
+ url = None,
+ jar = {},
+ src = {},
+ doc = {},
+ war = {}):
+
+ build_cmd = ['bazel', 'build']
+ mvn_cmd = ['python', 'tools/maven/mvn.py', '-v', version]
+ api_cmd = mvn_cmd[:]
+ api_targets = []
+ for type,d in [('jar', jar), ('java-source', src), ('javadoc', doc)]:
+ for a,t in sorted(d.items()):
+ api_cmd.append('-s %s:%s:$(location %s)' % (a,type,t))
+ api_targets.append(t)
+
+ native.genrule(
+ name = 'gen_api_install',
+ cmd = sh_bang_template % (
+ ' '.join(build_cmd + api_targets),
+ ' '.join(api_cmd + ['-a', 'install'])),
+ srcs = api_targets,
+ outs = ['api_install.sh'],
+ executable = True,
+ )
+
+ if repository and url:
+ native.genrule(
+ name = 'gen_api_deploy',
+ cmd = sh_bang_template % (
+ ' '.join(build_cmd + api_targets),
+ ' '.join(api_cmd + ['-a', 'deploy',
+ '--repository', repository,
+ '--url', url])),
+ srcs = api_targets,
+ outs = ['api_deploy.sh'],
+ executable = True,
+ )
+
+ war_cmd = mvn_cmd[:]
+ war_targets = []
+ for a,t in sorted(war.items()):
+ war_cmd.append('-s %s:war:$(location %s)' % (a,t))
+ war_targets.append(t)
+
+ native.genrule(
+ name = 'gen_war_install',
+ cmd = sh_bang_template % (' '.join(build_cmd + war_targets),
+ ' '.join(war_cmd + ['-a', 'install'])),
+ srcs = war_targets,
+ outs = ['war_install.sh'],
+ executable = True,
+ )
+
+ if repository and url:
+ native.genrule(
+ name = 'gen_war_deploy',
+ cmd = sh_bang_template % (
+ ' '.join(build_cmd + war_targets),
+ ' '.join(war_cmd + [
+ '-a', 'deploy',
+ '--repository', repository,
+ '--url', url])),
+ srcs = war_targets,
+ outs = ['war_deploy.sh'],
+ executable = True,
+ )
diff --git a/tools/version.py b/tools/version.py
index 9f03a59..eac2700 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -53,7 +53,7 @@
replace_in_file(pom, src_pattern)
src_pattern = re.compile(r"^(GERRIT_VERSION = ')([-.\w]+)(')$", re.MULTILINE)
-replace_in_file('VERSION', src_pattern)
+replace_in_file('version.bzl', src_pattern)
src_pattern = re.compile(r'^(\s*-DarchetypeVersion=)([-.\w]+)(\s*\\)$',
re.MULTILINE)
diff --git a/VERSION b/version.bzl
similarity index 100%
rename from VERSION
rename to version.bzl