Merge changes Ic8f371d9,Iae4cffcd,I4213004f

* changes:
  Implement DynamicSet<T>, DynamicMap<T> to provide bindings in Guice
  Automatically register plugin bindings
  Define gerrit-extension-api module
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index fa727b5..5172e03 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -703,6 +703,21 @@
 is already restricted to the correct set of users.
 
 
+[[category_rebase]]
+Rebase
+~~~~~~
+
+This category permits users to rebase changes via the web UI by pushing
+the `Rebase Change` button.
+
+The change owner and submitters can always rebase changes in the web UI
+(even without having the `Rebase` access right assigned).
+
+Users without this access right who are able to upload new patch sets
+can still do the rebase locally and upload the rebased commit as a new
+patch set.
+
+
 [[category_submit]]
 Submit
 ~~~~~~
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 890c964..4fd6b2f 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -16,9 +16,10 @@
 |All > Open           | status:open '(or is:open)'
 |All > Merged         | status:merged
 |All > Abandoned      | status:abandoned
-|My > Dafts           | has:draft
+|My > Drafts          | is:draft
 |My > Watched Changes | status:open is:watched
 |My > Starred Changes | is:starred
+|My > Draft Comments  | has:draft
 |Open changes in Foo  | status:open project:Foo
 |=================================================
 
@@ -230,6 +231,10 @@
 +
 True if the change is other open or submitted, merge pending.
 
+is:draft::
++
+True if the change is a draft.
+
 is:closed::
 +
 True if the change is either merged or abandoned.
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index 71df400..25f4f22a 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -59,6 +59,10 @@
     return "/admin/projects/" + p.get() + ",access";
   }
 
+  public static String toAccountQuery(final String fullname) {
+    return "/q/owner:\"" + KeyUtil.encode(fullname) + "\"," + TOP;
+  }
+
   public static String toAccountDashboard(final AccountInfo acct) {
     return toAccountDashboard(acct.getId());
   }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index f818d7b..20261de 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -30,6 +30,7 @@
   public static final String PUSH_MERGE = "pushMerge";
   public static final String PUSH_TAG = "pushTag";
   public static final String READ = "read";
+  public static final String REBASE = "rebase";
   public static final String SUBMIT = "submit";
 
   private static final List<String> NAMES_LC;
@@ -47,6 +48,7 @@
     NAMES_LC.add(PUSH_MERGE.toLowerCase());
     NAMES_LC.add(PUSH_TAG.toLowerCase());
     NAMES_LC.add(LABEL.toLowerCase());
+    NAMES_LC.add(REBASE.toLowerCase());
     NAMES_LC.add(SUBMIT.toLowerCase());
 
     labelIndex = NAMES_LC.indexOf(Permission.LABEL);
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
index c25c381..f4e85ba 100644
--- a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
@@ -94,6 +94,7 @@
     this.caches = new HashMap<String, CacheProvider<?, ?>>();
   }
 
+  @SuppressWarnings({"rawtypes", "unchecked"})
   private void start() {
     synchronized (lock) {
       if (manager != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index ffa76ed..40ffc7d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -235,6 +235,10 @@
     }
 
     if (matchExact("mine,drafts", token)) {
+      return PageLinks.toChangeQuery("is:draft");
+    }
+
+    if (matchExact("mine,comments", token)) {
       return PageLinks.toChangeQuery("has:draft");
     }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 6dbfeee..fd6ba2d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -548,9 +548,10 @@
     if (signedIn) {
       m = new LinkMenuBar();
       addLink(m, C.menuMyChanges(), PageLinks.MINE);
-      addLink(m, C.menuMyDrafts(), PageLinks.toChangeQuery("has:draft"));
+      addLink(m, C.menuMyDrafts(), PageLinks.toChangeQuery("is:draft"));
       addLink(m, C.menuMyWatchedChanges(), PageLinks.toChangeQuery("is:watched status:open"));
       addLink(m, C.menuMyStarredChanges(), PageLinks.toChangeQuery("is:starred"));
+      addLink(m, C.menuMyDraftComments(), PageLinks.toChangeQuery("has:draft"));
       menuLeft.add(m, C.menuMine());
       menuLeft.selectTab(1);
     } else {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index ee107d0..f716814 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -60,6 +60,7 @@
   String menuMyDrafts();
   String menuMyWatchedChanges();
   String menuMyStarredChanges();
+  String menuMyDraftComments();
 
   String menuDiff();
   String menuDiffCommit();
@@ -96,4 +97,5 @@
   String jumpMineDrafts();
   String jumpMineWatched();
   String jumpMineStarred();
+  String jumpMineDraftComments();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 41db3d5..8e3ca6c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -43,6 +43,7 @@
 menuMyDrafts = Drafts
 menuMyStarredChanges = Starred Changes
 menuMyWatchedChanges = Watched Changes
+menuMyDraftComments = Draft Comments
 
 menuDiff = Differences
 menuDiffCommit = Commit Message
@@ -79,3 +80,4 @@
 jumpMineWatched = Go to watched changes
 jumpMineDrafts = Go to drafts
 jumpMineStarred = Go to starred changes
+jumpMineDraftComments = Go to draft comments
\ No newline at end of file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
index 873045d..a41ff02 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/JumpKeys.java
@@ -55,6 +55,12 @@
       jumps.add(new KeyCommand(0, 'd', Gerrit.C.jumpMineDrafts()) {
         @Override
         public void onKeyPress(final KeyPressEvent event) {
+          Gerrit.display(PageLinks.toChangeQuery("is:draft"));
+        }
+      });
+      jumps.add(new KeyCommand(0, 'c', Gerrit.C.jumpMineDraftComments()) {
+        @Override
+        public void onKeyPress(final KeyPressEvent event) {
           Gerrit.display(PageLinks.toChangeQuery("has:draft"));
         }
       });
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
index b777be7..3bd2e77 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyAgreementsScreen.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.client.account;
 
-import com.google.gerrit.client.FormatUtil;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.FancyFlexTable;
@@ -24,8 +23,6 @@
 import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
-import com.google.gwtexpui.safehtml.client.SafeHtml;
-import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 public class MyAgreementsScreen extends SettingsScreen {
   private AgreementTable agreements;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
index 936bfe5..eaf564f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupInfoScreen.java
@@ -374,39 +374,41 @@
         new GerritCallback<List<AccountGroup.ExternalNameKey>>() {
           @Override
           public void onSuccess(List<AccountGroup.ExternalNameKey> result) {
-            final CellFormatter fmt = externalMatches.getCellFormatter();
+            try {
+              final CellFormatter fmt = externalMatches.getCellFormatter();
 
-            if (result.isEmpty()) {
-              externalMatches.resize(1, 1);
-              externalMatches.setText(0, 0, Util.C.errorNoMatchingGroups());
+              if (result.isEmpty()) {
+                externalMatches.resize(1, 1);
+                externalMatches.setText(0, 0, Util.C.errorNoMatchingGroups());
+                fmt.setStyleName(0, 0, Gerrit.RESOURCES.css().header());
+                return;
+              }
+
+              externalMatches.resize(1 + result.size(), 2);
+
+              externalMatches.setText(0, 0, Util.C.columnGroupName());
+              externalMatches.setText(0, 1, "");
               fmt.setStyleName(0, 0, Gerrit.RESOURCES.css().header());
-              return;
+              fmt.setStyleName(0, 1, Gerrit.RESOURCES.css().header());
+
+              for (int row = 0; row < result.size(); row++) {
+                final AccountGroup.ExternalNameKey key = result.get(row);
+                final Button b = new Button(Util.C.buttonSelectGroup());
+                b.addClickHandler(new ClickHandler() {
+                  @Override
+                  public void onClick(ClickEvent event) {
+                    setExternalGroup(key);
+                  }
+                });
+                externalMatches.setText(1 + row, 0, key.get());
+                externalMatches.setWidget(1 + row, 1, b);
+                fmt.setStyleName(1 + row, 1, Gerrit.RESOURCES.css().rightmost());
+              }
+            } finally {
+              externalMatches.setVisible(true);
+              externalNameFilter.setEnabled(true);
+              externalNameSearch.setEnabled(true);
             }
-
-            externalMatches.resize(1 + result.size(), 2);
-
-            externalMatches.setText(0, 0, Util.C.columnGroupName());
-            externalMatches.setText(0, 1, "");
-            fmt.setStyleName(0, 0, Gerrit.RESOURCES.css().header());
-            fmt.setStyleName(0, 1, Gerrit.RESOURCES.css().header());
-
-            for (int row = 0; row < result.size(); row++) {
-              final AccountGroup.ExternalNameKey key = result.get(row);
-              final Button b = new Button(Util.C.buttonSelectGroup());
-              b.addClickHandler(new ClickHandler() {
-                @Override
-                public void onClick(ClickEvent event) {
-                  setExternalGroup(key);
-                }
-              });
-              externalMatches.setText(1 + row, 0, key.get());
-              externalMatches.setWidget(1 + row, 1, b);
-              fmt.setStyleName(1 + row, 1, Gerrit.RESOURCES.css().rightmost());
-            }
-            externalMatches.setVisible(true);
-
-            externalNameFilter.setEnabled(true);
-            externalNameSearch.setEnabled(true);
           }
 
           @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 7e0edec..4330513 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -102,6 +102,7 @@
 	pushMerge, \
 	pushTag, \
 	read, \
+	rebase, \
 	submit
 create = Create Reference
 forgeAuthor = Forge Author Identity
@@ -112,6 +113,7 @@
 pushMerge = Push Merge Commit
 pushTag = Push Annotated Tag
 read = Read
+rebase = Rebase
 submit = Submit
 
 refErrorEmpty = Reference must be supplied
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index 1372aa2..1b9db39 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
 import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.ChangeLink;
+import com.google.gerrit.client.ui.InlineHyperlink;
 import com.google.gerrit.client.ui.NavigationTable;
 import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
 import com.google.gerrit.client.ui.ProjectLink;
@@ -209,7 +210,9 @@
     if (c.owner() != null && c.owner().name() != null) {
       owner = c.owner().name();
     }
-    table.setText(row, C_OWNER, owner);
+
+    table.setWidget(row, C_OWNER, new InlineHyperlink(owner,
+        PageLinks.toAccountQuery(owner)));
 
     table.setWidget(
         row, C_PROJECT, new ProjectLink(c.project_name_key(), c.status()));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
index 4d1c1f5..1d4d3e2 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/AbandonChangeHandler.java
@@ -65,7 +65,7 @@
       PatchSetInfoNotAvailableException, RepositoryNotFoundException,
       IOException {
     final ReviewResult result =
-        abandonChangeFactory.create(patchSetId, message).call();
+        abandonChangeFactory.create(patchSetId.getParentKey(), message).call();
     if (result.getErrors().size() > 0) {
       throw new NoSuchChangeException(result.getChangeId());
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
index bcb03b0..5d7fe32 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RestoreChangeHandler.java
@@ -64,7 +64,7 @@
       PatchSetInfoNotAvailableException, RepositoryNotFoundException,
       IOException {
     final ReviewResult result =
-        restoreChangeFactory.create(patchSetId, message).call();
+        restoreChangeFactory.create(patchSetId.getParentKey(), message).call();
     if (result.getErrors().size() > 0) {
       throw new NoSuchChangeException(result.getChangeId());
     }
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
index 1df89b7..9a55e0f 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
+++ b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
@@ -76,7 +76,7 @@
   public JsonElement serialize(final Edit src, final Type typeOfSrc,
       final JsonSerializationContext context) {
     if (src == null) {
-      return new JsonNull();
+      return JsonNull.INSTANCE;
     }
     final JsonArray a = new JsonArray();
     add(a, src);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 727207f..274e73ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -30,7 +30,6 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.events.ApprovalAttribute;
 import com.google.gerrit.server.events.ChangeAbandonedEvent;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index c5a66bb..556ae82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 
 import java.io.IOException;
@@ -35,6 +34,16 @@
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Utility functions to manipulate patchset approvals.
+ * <p>
+ * Approvals are overloaded, they represent both approvals and reviewers
+ * which should be CCed on a change.  To ensure that reviewers are not lost
+ * there must always be an approval on each patchset for each reviewer,
+ * even if the reviewer hasn't actually given a score to the change.  To
+ * mark the "no score" case, a dummy approval, which may live in any of
+ * the available categories, with a score of 0 is used.
+ */
 public class ApprovalsUtil {
   private final ReviewDb db;
   private final ApprovalTypes approvalTypes;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
index 1fac8c5..83fa671 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/AbandonChange.java
@@ -35,10 +35,12 @@
 
 import java.util.concurrent.Callable;
 
+import javax.annotation.Nullable;
+
 public class AbandonChange implements Callable<ReviewResult> {
 
   public interface Factory {
-    AbandonChange create(PatchSet.Id patchSetId, String changeComment);
+    AbandonChange create(Change.Id changeId, String changeComment);
   }
 
   private final AbandonedSender.Factory abandonedSenderFactory;
@@ -47,22 +49,22 @@
   private final IdentifiedUser currentUser;
   private final ChangeHooks hooks;
 
-  private final PatchSet.Id patchSetId;
+  private final Change.Id changeId;
   private final String changeComment;
 
   @Inject
   AbandonChange(final AbandonedSender.Factory abandonedSenderFactory,
       final ChangeControl.Factory changeControlFactory, final ReviewDb db,
       final IdentifiedUser currentUser, final ChangeHooks hooks,
-      @Assisted final PatchSet.Id patchSetId,
-      @Assisted final String changeComment) {
+      @Assisted final Change.Id changeId,
+      @Assisted @Nullable final String changeComment) {
     this.abandonedSenderFactory = abandonedSenderFactory;
     this.changeControlFactory = changeControlFactory;
     this.db = db;
     this.currentUser = currentUser;
     this.hooks = hooks;
 
-    this.patchSetId = patchSetId;
+    this.changeId = changeId;
     this.changeComment = changeComment;
   }
 
@@ -70,10 +72,11 @@
   public ReviewResult call() throws EmailException,
       InvalidChangeOperationException, NoSuchChangeException, OrmException {
     final ReviewResult result = new ReviewResult();
-
-    final Change.Id changeId = patchSetId.getParentKey();
     result.setChangeId(changeId);
+
     final ChangeControl control = changeControlFactory.validateFor(changeId);
+    final Change change = db.changes().get(changeId);
+    final PatchSet.Id patchSetId = change.currentPatchSetId();
     final PatchSet patch = db.patchSets().get(patchSetId);
     if (!control.canAbandon()) {
       result.addError(new ReviewResult.Error(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
index 7e52564..966efce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RestoreChange.java
@@ -41,10 +41,12 @@
 import java.io.IOException;
 import java.util.concurrent.Callable;
 
+import javax.annotation.Nullable;
+
 public class RestoreChange implements Callable<ReviewResult> {
 
   public interface Factory {
-    RestoreChange create(PatchSet.Id patchSetId, String changeComment);
+    RestoreChange create(Change.Id changeId, String changeComment);
   }
 
   private final RestoredSender.Factory restoredSenderFactory;
@@ -54,15 +56,15 @@
   private final IdentifiedUser currentUser;
   private final ChangeHooks hooks;
 
-  private final PatchSet.Id patchSetId;
+  private final Change.Id changeId;
   private final String changeComment;
 
   @Inject
   RestoreChange(final RestoredSender.Factory restoredSenderFactory,
       final ChangeControl.Factory changeControlFactory, final ReviewDb db,
       final GitRepositoryManager repoManager, final IdentifiedUser currentUser,
-      final ChangeHooks hooks, @Assisted final PatchSet.Id patchSetId,
-      @Assisted final String changeComment) {
+      final ChangeHooks hooks, @Assisted final Change.Id changeId,
+      @Assisted @Nullable final String changeComment) {
     this.restoredSenderFactory = restoredSenderFactory;
     this.changeControlFactory = changeControlFactory;
     this.db = db;
@@ -70,7 +72,7 @@
     this.currentUser = currentUser;
     this.hooks = hooks;
 
-    this.patchSetId = patchSetId;
+    this.changeId = changeId;
     this.changeComment = changeComment;
   }
 
@@ -79,10 +81,11 @@
       InvalidChangeOperationException, NoSuchChangeException, OrmException,
       RepositoryNotFoundException, IOException {
     final ReviewResult result = new ReviewResult();
-
-    final Change.Id changeId = patchSetId.getParentKey();
     result.setChangeId(changeId);
+
     final ChangeControl control = changeControlFactory.validateFor(changeId);
+    final Change change = db.changes().get(changeId);
+    final PatchSet.Id patchSetId = change.currentPatchSetId();
     if (!control.canRestore()) {
       result.addError(new ReviewResult.Error(
           ReviewResult.Error.Type.RESTORE_NOT_PERMITTED));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index 4307854..8501426 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -40,6 +40,8 @@
 
   /** Exception thrown when a token does not parse correctly. */
   public static class InvalidTokenException extends Exception {
+    private static final long serialVersionUID = 1L;
+
     public InvalidTokenException() {
       super("Invalid token");
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 7652bed..f232c5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -204,7 +204,8 @@
 
   /** Can this user rebase this change? */
   public boolean canRebase() {
-    return isOwner() || getRefControl().canSubmit();
+    return isOwner() || getRefControl().canSubmit()
+        || getRefControl().canRebase();
   }
 
   /** Can this user restore this change? */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index a865603..db370e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -125,6 +125,12 @@
         && canWrite();
   }
 
+  /** @return true if this user can rebase changes on this ref */
+  public boolean canRebase() {
+    return canPerform(Permission.REBASE)
+        && canWrite();
+  }
+
   /** @return true if this user can submit patch sets to this ref */
   public boolean canSubmit() {
     if (GitRepositoryManager.REF_CONFIG.equals(refName)) {
diff --git a/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java b/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java
index ac74147..606e883 100644
--- a/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java
+++ b/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java
@@ -27,7 +27,6 @@
 import com.googlecode.prolog_cafe.lang.Term;
 
 abstract class AbstractCommitUserIdentityPredicate extends Predicate.P3 {
-  private static final long serialVersionUID = 1L;
   private static final SymbolTerm user = SymbolTerm.intern("user", 1);
   private static final SymbolTerm anonymous = SymbolTerm.intern("anonymous");
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
index b32d54c..0e556f3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
@@ -73,6 +73,7 @@
   private GitRepositoryManager repoManager;
   private ReplicationQueue replication;
 
+  @SuppressWarnings("unchecked")
   @Override
   @Before
   public void setUp() throws Exception {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 28e3c25..340db7e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -29,13 +29,11 @@
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.RulesCache;
 import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.cache.ConcurrentHashMapCache;
@@ -44,7 +42,6 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
@@ -375,8 +372,6 @@
   }
 
   private ProjectControl user(String name, AccountGroup.UUID... memberOf) {
-    SchemaFactory<ReviewDb> schema = null;
-    GroupCache groupCache = null;
     String canonicalWebUrl = "http://localhost";
 
     return new ProjectControl(Collections.<AccountGroup.UUID> emptySet(),
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index c8e684f..93d86e5 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -224,7 +224,7 @@
             break;
           } else {
             expect(repoManager.list()).andReturn(
-                new TreeSet<Project.NameKey>(Collections.EMPTY_LIST));
+                new TreeSet<Project.NameKey>(Collections.<Project.NameKey> emptyList()));
           }
         }
       }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index a814111..bc094f9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -39,6 +40,7 @@
 import com.google.gerrit.sshd.args4j.AccountGroupIdHandler;
 import com.google.gerrit.sshd.args4j.AccountGroupUUIDHandler;
 import com.google.gerrit.sshd.args4j.AccountIdHandler;
+import com.google.gerrit.sshd.args4j.ChangeIdHandler;
 import com.google.gerrit.sshd.args4j.ObjectIdHandler;
 import com.google.gerrit.sshd.args4j.PatchSetIdHandler;
 import com.google.gerrit.sshd.args4j.ProjectControlHandler;
@@ -134,6 +136,7 @@
     registerOptionHandler(Account.Id.class, AccountIdHandler.class);
     registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class);
     registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class);
+    registerOptionHandler(Change.Id.class, ChangeIdHandler.class);
     registerOptionHandler(ObjectId.class, ObjectIdHandler.class);
     registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
     registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ChangeIdHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ChangeIdHandler.java
new file mode 100644
index 0000000..0194b91
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ChangeIdHandler.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.args4j;
+
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+public class ChangeIdHandler extends OptionHandler<Change.Id> {
+
+  @Inject
+  private ReviewDb db;
+
+  @Inject
+  public ChangeIdHandler(
+      final ReviewDb db,
+      @Assisted final CmdLineParser parser, @Assisted final OptionDef option,
+      @Assisted final Setter<Change.Id> setter) {
+    super(parser, option, setter);
+    this.db = db;
+  }
+
+  @Override
+  public final int parseArguments(final Parameters params)
+      throws CmdLineException {
+    final String token = params.getParameter(0);
+    final String[] tokens = token.split(",");
+    if (tokens.length != 3) {
+      throw new CmdLineException(owner, "change should be specified as "
+                                 + "<project>,<branch>,<change-id>");
+    }
+
+    try {
+      final Change.Key key = Change.Key.parse(tokens[2]);
+      final Project.NameKey project = new Project.NameKey(tokens[0]);
+      final Branch.NameKey branch = new Branch.NameKey(project, tokens[1]);
+      for (final Change change : db.changes().byBranchKey(branch, key)) {
+        setter.addValue(change.getId());
+        return 1;
+      }
+    } catch (IllegalArgumentException e) {
+      throw new CmdLineException(owner, "Change-Id is not valid");
+    } catch (OrmException e) {
+      throw new CmdLineException(owner, "Database error: " + e.getMessage());
+    }
+
+    throw new CmdLineException(owner, "\"" + token + "\": change not found");
+  }
+
+  @Override
+  public final String getDefaultMetaVariable() {
+    return "CHANGE";
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 74782ed..f38e17e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -202,11 +202,11 @@
 
       if (abandonChange) {
         final ReviewResult result = abandonChangeFactory.create(
-            patchSetId, changeComment).call();
+            patchSetId.getParentKey(), changeComment).call();
         handleReviewResultErrors(result);
       } else if (restoreChange) {
         final ReviewResult result = restoreChangeFactory.create(
-            patchSetId, changeComment).call();
+            patchSetId.getParentKey(), changeComment).call();
         handleReviewResultErrors(result);
       }
       if (submitChange) {