Merge "ProjectConfig sections refactoring"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 30a6c39..d2078ab 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -780,6 +780,14 @@
and `-1 Do not have copyright` will block submit, while `+1 Copyright
clear` is required to enable submit.
+[[restart_changes]]
+[NOTE]
+Restart the Gerrit web application and reload all browsers after
+making any database changes to approval categories. Browsers are
+sent the list of known categories when they first visit the site,
+and don't notice changes until the page is closed and opened again,
+or is reloaded.
+
Examples of typical roles in a project
--------------------------------------
@@ -841,6 +849,115 @@
* <<category_push_merge,`Push merge commit`>> to 'refs/heads/*'
+[[examples_cisystem]]
+CI system
+~~~~~~~~~
+
+A typical Continous Integration system should be able to download new changes
+to build and then leave a verdict somehow.
+
+As an example, the popular
+link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[gerrit-trigger plugin]
+for Jenkins/Hudson can set labels at:
+
+* The start of a build
+* A successful build
+* An unstable build (tests fails)
+* A failed build
+
+Usually the range chosen for this verdict is the verify label. Depending on
+the size of your project and discipline of involved developers you might want
+to limit access right to the +1 `Verify` label to the CI system only. That
+way it's guaranteed that submitted commits always get built and pass tests
+successfully.
+
+If the build doesn't complete successfully the CI system can set the
+`Verify` label to -1. However that means that a failed build will block
+submit of the change even if someone else sets `Verify` +1. Depending on the
+project and how much the CI system can be trusted for accurate results, a
+blocking label might not be feasible. A recommended alternative is to set the
+label `Code-review` to -1 instead, as it isn't a blocking label but still
+shows a red label in the Gerrit UI. Optionally; to enable the possibility to
+deliver different results (build error vs unstable for instance) it's also
+possible to set `Code-review` +1 as well.
+
+If pushing new changes is granted, it's possible to automate cherry-pick of
+submitted changes for upload to other branches under certain conditions. This
+is probably not the first step of what a project wants to automate however,
+and so the push right can be found under the optional section.
+
+Suggested access rights to grant, that won't block changes:
+
+* <<category_read,`Read`>> on 'refs/heads/\*' and 'refs/tags/*'
+* <<category_label-Code-Review,`Label: Code review`>> with range '-1' to '0'
+* <<category_label-Verified,`Label: Verify`>> with range '0' to '+1'
+
+Optional access rights to grant:
+
+* <<category_label-Code-Review,`Label: Code review`>> with range '-1' to '+1'
+* <<category_push,`Push`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
+
+
+[[examples_integrator]]
+Integrator
+~~~~~~~~~~
+
+Integrators are like developers but with some additional rights granted due
+to their administrative role in a project. They can upload or push any commit
+with any committer email (not just their own) and they can also create new
+tags and branches.
+
+Suggested access rights to grant:
+
+* <<examples_developer,Developer rights>>
+* <<category_push,`Push`>> to 'refs/heads/*'
+* <<category_push_merge,`Push merge commit`>> to 'refs/heads/*'
+* <<category_forge_committer,`Forge Committer Identity`>> to 'refs/for/refs/heads/\*' and 'refs/changes/*'
+* <<category_create,`Create Reference`>> to 'refs/heads/*'
+* <<category_push_annotated,`Push Annotated Tag`>> to 'refs/tags/*'
+
+
+[[examples_project-owner]]
+Project owner
+~~~~~~~~~~~~~
+
+The project owner is almost like an integrator but with additional destructive
+power in the form of being able to delete branches. Optionally these users
+also have the power to configure access rights in gits assigned to them.
+
+[WARNING]
+These users should be really knowledgable about git, for instance knowing why
+tags never should be removed from a server. This role is granted potentially
+destructive access rights and cleaning up after such a mishap could be time
+consuming!
+
+Suggested access rights to grant:
+
+* <<examples_integrator,Integrator rights>>
+* <<category_push,`Push`>> with the force option to 'refs/heads/\*' and 'refs/tags/*'
+
+Optional access right to grant:
+
+* <<category_owner,`Owner`>> in the gits they mostly work with.
+
+[[examples_administrator]]
+Administrator
+~~~~~~~~~~~~~
+
+The administrator role is the most powerful role known in the Gerrit universe.
+This role may grant itself (or others) any access right, and it already has
+all capabilities granted as well. By default the
+<<administrators,`Administrators` group>> is the group that has this role.
+
+Mandatory access rights:
+
+* <<capability_administrateServer,The `Administrate Server` capability>>
+
+Suggested access rights to grant:
+
+* <<examples_project-owner,Project owner rights>>
+
+
[[conversion_table]]
Conversion table from 2.1.x series to 2.2.x series
--------------------------------------------------
@@ -894,11 +1011,6 @@
Below you find a list of capabilities available:
-* View Connections
-
-* View Queue
-
-
[[capability_administrateServer]]
Administrate Server
~~~~~~~~~~~~~~~~~~~
@@ -942,7 +1054,7 @@
Flush Caches
~~~~~~~~~~~~
-Allow the flushing of Gerrits caches. This capability allows the granted
+Allow the flushing of Gerrit's caches. This capability allows the granted
group to link:cmd-flush-caches.html[flush some or all Gerrit caches via ssh].
[NOTE]
@@ -1016,18 +1128,28 @@
View Caches
~~~~~~~~~~~
-Allow querying for status of Gerrits internal caches. This capability allows
+Allow querying for status of Gerrit's internal caches. This capability allows
the granted group to
link:cmd-show-caches.html[look at some or all Gerrit caches via ssh].
-[[restart_changes]]
-[NOTE]
-Restart the Gerrit web application and reload all browsers after
-making any database changes to approval categories. Browsers are
-sent the list of known categories when they first visit the site,
-and don't notice changes until the page is closed and opened again,
-or is reloaded.
+[[capability_viewConnections]]
+View Connections
+~~~~~~~~~~~~~~~~
+
+Allow querying for status of Gerrit's current client connections. This
+capability allows the granted group to
+link:cmd-show-connections.html[look at Gerrit's current connections via ssh].
+
+
+[[capability_viewQueue]]
+View Queue
+~~~~~~~~~~
+
+Allow querying for status of Gerrit's internal task queue. This capability
+allows the granted group to
+link:cmd-show-queue.html[look at the Gerrit task queue via ssh].
+
GERRIT
------
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 069bcf6..126b2a0 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -29,7 +29,7 @@
------
Caller must be a member of the privileged 'Administrators' group,
or have been granted
-link:access_control.html#capability_viewCaches[the 'View Caches' global capability].
+link:access-control.html#capability_viewCaches[the 'View Caches' global capability].
SCRIPTING
---------
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index 16cd5b4..b5d41bd 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -19,7 +19,8 @@
ACCESS
------
Caller must be a member of the privileged 'Administrators' group,
-or have been granted the 'View Connections' global capability.
+or have been granted
+link:access-control.html#capability_viewConnections[the 'View Connections' global capability].
SCRIPTING
---------
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index 98a954e..f99e342 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -29,7 +29,9 @@
projects, or that do not have a specific project are hidden.
Members of the group 'Administrators', or any group that has been
-granted the 'View Queue' capability can see all queue entries.
+granted
+link:access-control.html#capability_viewQueue[the 'View Queue' capability]
+can see all queue entries.
SCRIPTING
---------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index d02382f..39f0621 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1107,7 +1107,7 @@
+
Valid values are `gitweb`, `cgit`, `disabled` or `custom`.
-[[gitweb.type]]gitweb.revision::
+[[gitweb.revision]]gitweb.revision::
+
Optional pattern to use for constructing the gitweb URL when pointing
at a specific commit when `custom` is used above.
@@ -1115,14 +1115,14 @@
Valid replacements are `${project}` for the project name in Gerrit
and `${commit}` for the SHA1 hash for the commit.
-[[gitweb.type]]gitweb.project::
+[[gitweb.project]]gitweb.project::
+
Optional pattern to use for constructing the gitweb URL when pointing
at a specific project when `custom` is used above.
+
Valid replacements are `${project}` for the project name in Gerrit.
-[[gitweb.type]]gitweb.branch::
+[[gitweb.branch]]gitweb.branch::
+
Optional pattern to use for constructing the gitweb URL when pointing
at a specific branch when `custom` is used above.
@@ -1130,6 +1130,16 @@
Valid replacements are `${project}` for the project name in Gerrit
and `${branch}` for the name of the branch.
+[[gitweb.filehistory]]gitweb.filehistory::
++
+Optional pattern to use for constructing the gitweb URL when pointing
+at the history of a file in a specific branch when `custom` is used
+above.
++
+Valid replacements are `${project}` for the project name in Gerrit,
+`${file}` for the file name and `${branch}` for the name of the
+branch.
+
[[gitweb.linkname]]gitweb.linkname::
+
Optional setting for modifying the link name presented to the user
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 6a6ce16..3aafe8c 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -109,12 +109,9 @@
[[branch]]
branch:'BRANCH'::
+
-Changes for 'BRANCH'. The branch name is the short name shown
-in the web interface, without the traditional 'refs/heads/'
-prefix. This operator is a shorthand for 'refs:'. Searching for
-'branch:master' really means 'ref:refs/heads/master', and searching
-for 'branch:refs/heads/master' is the same as searching for
-'ref:refs/heads/refs/heads/master'.
+Changes for 'BRANCH'. The branch name is either the short name shown
+in the web interface or the full name of the destination branch with
+the traditional 'refs/heads/' prefix.
+
If 'BRANCH' starts with `^` it matches branch names by regular
expression patterns. The
diff --git a/ReleaseNotes/ReleaseNotes-2.3.txt b/ReleaseNotes/ReleaseNotes-2.3.txt
index 20b14c6..965c8e3 100644
--- a/ReleaseNotes/ReleaseNotes-2.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.3.txt
@@ -274,7 +274,7 @@
~~~~~~~~~~~~~
* Allow superprojects to subscribe to submodules updates
+
-The feature introduced in this commit allows superprojects to
+The feature introduced in this release allows superprojects to
subscribe to submodules updates.
+
When a commit is merged to a project, the commit content is scanned
@@ -451,4 +451,24 @@
* Improve 'Push Merge Commit' access right documentation
-* Access control: capabilities
+* Access control: Capabilities documented
+** Administrate Server
+** Create Account
+** Create Group
+** Create Project
+** Flush Caches
+** Kill Task
+** Priority
+** Query Limit
+** Start Replication
+** View caches
+** View connections
+** View queue
+
+* Access control: Example roles documented
+** Contributor
+** Developer
+** CI System
+** Integrator
+** Project owner
+** Administrator
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
index 3a6bca8..74d1962 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeDetail.java
@@ -29,6 +29,7 @@
protected boolean allowsAnonymous;
protected boolean canAbandon;
protected boolean canPublish;
+ protected boolean canRebase;
protected boolean canRestore;
protected boolean canRevert;
protected boolean canDeleteDraft;
@@ -80,6 +81,14 @@
canPublish = a;
}
+ public boolean canRebase() {
+ return canRebase;
+ }
+
+ public void setCanRebase(final boolean a) {
+ canRebase = a;
+ }
+
public boolean canRestore() {
return canRestore;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
index a43ac94..18453ca 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ChangeManageService.java
@@ -44,4 +44,7 @@
@SignInRequired
void deleteDraftChange(PatchSet.Id patchSetId, AsyncCallback<VoidResult> callback);
+
+ @SignInRequired
+ void rebaseChange(PatchSet.Id patchSetId, AsyncCallback<ChangeDetail> callback);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
index d090f27..27f76f6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java
@@ -14,6 +14,8 @@
package com.google.gerrit.client.changes;
+import com.google.gerrit.client.ui.ListenableValue;
+import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.reviewdb.client.Change;
import java.util.HashMap;
@@ -35,6 +37,7 @@
private Change.Id changeId;
private ChangeDetailCache detail;
+ private ListenableValue<ChangeInfo> info;
private StarCache starred;
protected ChangeCache(Change.Id chg) {
@@ -52,6 +55,13 @@
return detail;
}
+ public ListenableValue<ChangeInfo> getChangeInfoCache() {
+ if (info == null) {
+ info = new ListenableValue<ChangeInfo>();
+ }
+ return info;
+ }
+
public StarCache getStarCache() {
if (starred == null) {
starred = new StarCache(changeId);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index 0af35b6..3372096 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -111,6 +111,8 @@
String patchSetInfoParents();
String initialCommit();
+ String buttonRebaseChange();
+
String buttonRevertChangeBegin();
String buttonRevertChangeSend();
String headingRevertMessage();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index 6735b63..ad70674 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -96,6 +96,8 @@
baseDiffItem = Base
autoMerge = Auto Merge
+buttonRebaseChange = Rebase Change
+
buttonRevertChangeBegin = Revert Change
buttonRevertChangeSend = Revert Change
headingRevertMessage = Revert Commit Message:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
index 7245db5..8f48ebd 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java
@@ -19,6 +19,7 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.FocusWidget;
public class ChangeDetailCache extends ListenableValue<ChangeDetail> {
public static class GerritCallback extends
@@ -29,6 +30,27 @@
}
}
+ /*
+ * GerritCallback which will re-enable a FocusWidget
+ * {@link com.google.gwt.user.client.ui.FocusWidget} if we are returning
+ * with a failed result.
+ *
+ * It is up to the caller to handle the original disabling of the Widget.
+ */
+ public static class GerritWidgetCallback extends GerritCallback {
+ private FocusWidget widget;
+
+ public GerritWidgetCallback(FocusWidget widget) {
+ this.widget = widget;
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ widget.setEnabled(true);
+ super.onFailure(caught);
+ }
+ }
+
public static class IgnoreErrorCallback implements AsyncCallback<ChangeDetail> {
@Override
public void onSuccess(ChangeDetail detail) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index 867abdd..8d5e105 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -321,6 +321,8 @@
}
dependenciesPanel.setOpen(depsOpen);
+
+ dependenciesPanel.getHeader().clear();
if (outdated > 0) {
dependenciesPanel.getHeader().add(new InlineLabel(
Util.M.outdatedHeader(outdated)));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
index 943e967..5a568f1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java
@@ -33,7 +33,6 @@
import com.google.gerrit.common.data.ApprovalSummarySet;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ChangeInfo;
-import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.client.ApprovalCategory;
@@ -45,7 +44,6 @@
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.FlowPanel;
@@ -54,8 +52,6 @@
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwtjsonrpc.client.VoidResult;
import java.util.ArrayList;
import java.util.HashSet;
@@ -136,7 +132,7 @@
return;
}
if (cell.getCellIndex() == C_STAR) {
- onStarClick(cell.getRowIndex());
+ // Don't do anything (handled by star itself).
} else if (cell.getCellIndex() == C_OWNER) {
// Don't do anything.
} else if (getRowItem(cell.getRowIndex()) != null) {
@@ -149,23 +145,7 @@
protected void onStarClick(final int row) {
final ChangeInfo c = getRowItem(row);
if (c != null && Gerrit.isSignedIn()) {
- final boolean prior = c.isStarred();
- c.setStarred(!prior);
- setStar(row, c);
-
- final ToggleStarRequest req = new ToggleStarRequest();
- req.toggle(c.getId(), c.isStarred());
- Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
- }
-
- @Override
- public void onFailure(final Throwable caught) {
- super.onFailure(caught);
- c.setStarred(prior);
- setStar(row, c);
- }
- });
+ ChangeCache.get(c.getId()).getStarCache().toggleStar();
}
}
@@ -212,10 +192,13 @@
}
private void populateChangeRow(final int row, final ChangeInfo c) {
+ ChangeCache cache = ChangeCache.get(c.getId());
+ cache.getChangeInfoCache().set(c);
+
final String idstr = c.getKey().abbreviate();
table.setWidget(row, C_ARROW, null);
if (Gerrit.isSignedIn()) {
- setStar(row, c);
+ table.setWidget(row, C_STAR, cache.getStarCache().createStar());
}
table.setWidget(row, C_ID, new TableChangeLink(idstr, c));
@@ -229,7 +212,10 @@
if (! c.isLatest()) {
s += " [OUTDATED]";
table.getRowFormatter().addStyleName(row, Gerrit.RESOURCES.css().outdated());
+ } else {
+ table.getRowFormatter().removeStyleName(row, Gerrit.RESOURCES.css().outdated());
}
+
table.setWidget(row, C_SUBJECT, new TableChangeLink(s, c));
table.setWidget(row, C_OWNER, link(c.getOwner()));
table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey(), c
@@ -244,22 +230,6 @@
return AccountDashboardLink.link(accountCache, id);
}
- private void setStar(final int row, final ChangeInfo c) {
- final ImageResource star;
- if (c.isStarred()) {
- star = Gerrit.RESOURCES.starFilled();
- } else {
- star = Gerrit.RESOURCES.starOpen();
- }
-
- final Widget i = table.getWidget(row, C_STAR);
- if (i instanceof Image) {
- ((Image) i).setResource(star);
- } else {
- table.setWidget(row, C_STAR, new Image(star));
- }
- }
-
public void addSection(final Section s) {
assert s.parent == null;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 199dc9d..50a8a6a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -551,6 +551,19 @@
});
actionsPanel.add(b);
}
+
+ if (changeDetail.canRebase()) {
+ final Button b = new Button(Util.C.buttonRebaseChange());
+ b.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(final ClickEvent event) {
+ b.setEnabled(false);
+ Util.MANAGE_SVC.rebaseChange(patchSet.getId(),
+ new ChangeDetailCache.GerritWidgetCallback(b));
+ }
+ });
+ actionsPanel.add(b);
+ }
}
private void populateDiffAllActions(final PatchSetDetail detail) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java
index dff12f5..7758843 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java
@@ -18,6 +18,7 @@
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.reviewdb.client.Change;
@@ -59,6 +60,10 @@
if (detail != null) {
return detail.isStarred();
}
+ ChangeInfo info = cache.getChangeInfoCache().get();
+ if (info != null) {
+ return info.isStarred();
+ }
return false;
}
@@ -81,6 +86,10 @@
if (detail != null) {
detail.setStarred(s);
}
+ ChangeInfo info = cache.getChangeInfoCache().get();
+ if (info != null) {
+ info.setStarred(s);
+ }
}
public void toggleStar() {
@@ -106,6 +115,7 @@
};
cache.getChangeDetailCache().addValueChangeHandler(starUpdater);
+ cache.getChangeInfoCache().addValueChangeHandler(starUpdater);
this.addValueChangeHandler(starUpdater);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
index 468c6d8..9c4c598 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ChangeQueryServlet.java
@@ -69,6 +69,7 @@
return;
}
+ p.setIncludeComments(get(req, "comments", false));
p.setIncludeCurrentPatchSet(get(req, "current-patch-set", false));
p.setIncludePatchSets(get(req, "patch-sets", false));
p.setIncludeApprovals(get(req, "all-approvals", false));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
index 9f437bf..7de4bc3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitWebConfig.java
@@ -54,6 +54,7 @@
type.setBranch(cfg.getString("gitweb", null, "branch"));
type.setProject(cfg.getString("gitweb", null, "project"));
type.setRevision(cfg.getString("gitweb", null, "revision"));
+ type.setFileHistory(cfg.getString("gitweb", null, "filehistory"));
String pathSeparator = cfg.getString("gitweb", null, "pathSeparator");
if (pathSeparator != null) {
if (pathSeparator.length() == 1) {
@@ -77,6 +78,9 @@
} else if (type.getRevision() == null) {
log.warn("No Pattern specified for gitweb.revision, disabling.");
type = null;
+ } else if (type.getFileHistory() == null) {
+ log.warn("No Pattern specified for gitweb.filehistory, disabling.");
+ type = null;
}
if ((cfgUrl != null && cfgUrl.isEmpty())
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index 23d5865..47a9395 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -129,6 +129,8 @@
detail.setCanRevert(change.getStatus() == Change.Status.MERGED && control.canAddPatchSet());
+ detail.setCanRebase(detail.getChange().getStatus().isOpen() && control.canRebase());
+
detail.setCanEdit(control.getRefControl().canWrite());
if (detail.getChange().getStatus().isOpen()) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
index e53f0bf..4178437 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeManageServiceImpl.java
@@ -24,6 +24,7 @@
class ChangeManageServiceImpl implements ChangeManageService {
private final SubmitAction.Factory submitAction;
private final AbandonChangeHandler.Factory abandonChangeHandlerFactory;
+ private final RebaseChange.Factory rebaseChangeFactory;
private final RestoreChangeHandler.Factory restoreChangeHandlerFactory;
private final RevertChange.Factory revertChangeFactory;
private final PublishAction.Factory publishAction;
@@ -32,12 +33,14 @@
@Inject
ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction,
final AbandonChangeHandler.Factory abandonChangeHandlerFactory,
+ final RebaseChange.Factory rebaseChangeFactory,
final RestoreChangeHandler.Factory restoreChangeHandlerFactory,
final RevertChange.Factory revertChangeFactory,
final PublishAction.Factory publishAction,
final DeleteDraftChange.Factory deleteDraftChangeFactory) {
this.submitAction = patchSetAction;
this.abandonChangeHandlerFactory = abandonChangeHandlerFactory;
+ this.rebaseChangeFactory = rebaseChangeFactory;
this.restoreChangeHandlerFactory = restoreChangeHandlerFactory;
this.revertChangeFactory = revertChangeFactory;
this.publishAction = publishAction;
@@ -54,6 +57,11 @@
abandonChangeHandlerFactory.create(patchSetId, message).to(callback);
}
+ public void rebaseChange(final PatchSet.Id patchSetId,
+ final AsyncCallback<ChangeDetail> callback) {
+ rebaseChangeFactory.create(patchSetId).to(callback);
+ }
+
public void revertChange(final PatchSet.Id patchSetId, final String message,
final AsyncCallback<ChangeDetail> callback) {
revertChangeFactory.create(patchSetId, message).to(callback);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
index 4224293..b672a439 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeModule.java
@@ -31,6 +31,7 @@
factory(AbandonChangeHandler.Factory.class);
factory(RestoreChangeHandler.Factory.class);
factory(RevertChange.Factory.class);
+ factory(RebaseChange.Factory.class);
factory(ChangeDetailFactory.Factory.class);
factory(IncludedInDetailFactory.Factory.class);
factory(PatchSetDetailFactory.Factory.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChange.java
new file mode 100644
index 0000000..3c29074
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/RebaseChange.java
@@ -0,0 +1,110 @@
+// 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.httpd.rpc.changedetail;
+
+import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.ChangeDetail;
+import com.google.gerrit.common.errors.NoSuchEntityException;
+import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ReplicationQueue;
+import com.google.gerrit.server.mail.EmailException;
+import com.google.gerrit.server.mail.RebasedPatchSetSender;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.io.IOException;
+
+class RebaseChange extends Handler<ChangeDetail> {
+ interface Factory {
+ RebaseChange create(PatchSet.Id patchSetId);
+ }
+
+ private final ChangeControl.Factory changeControlFactory;
+ private final ReviewDb db;
+ private final IdentifiedUser currentUser;
+ private final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory;
+
+ private final ChangeDetailFactory.Factory changeDetailFactory;
+ private final ReplicationQueue replication;
+
+ private final PatchSet.Id patchSetId;
+
+ private final ChangeHookRunner hooks;
+
+ private final GitRepositoryManager gitManager;
+ private final PatchSetInfoFactory patchSetInfoFactory;
+
+ private final PersonIdent myIdent;
+
+ private final ApprovalTypes approvalTypes;
+
+ @Inject
+ RebaseChange(final ChangeControl.Factory changeControlFactory,
+ final ReviewDb db, final IdentifiedUser currentUser,
+ final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
+ final ChangeDetailFactory.Factory changeDetailFactory,
+ @Assisted final PatchSet.Id patchSetId, final ChangeHookRunner hooks,
+ final GitRepositoryManager gitManager,
+ final PatchSetInfoFactory patchSetInfoFactory,
+ final ReplicationQueue replication,
+ @GerritPersonIdent final PersonIdent myIdent,
+ final ApprovalTypes approvalTypes) {
+ this.changeControlFactory = changeControlFactory;
+ this.db = db;
+ this.currentUser = currentUser;
+ this.rebasedPatchSetSenderFactory = rebasedPatchSetSenderFactory;
+ this.changeDetailFactory = changeDetailFactory;
+
+ this.patchSetId = patchSetId;
+ this.hooks = hooks;
+ this.gitManager = gitManager;
+
+ this.patchSetInfoFactory = patchSetInfoFactory;
+ this.replication = replication;
+ this.myIdent = myIdent;
+
+ this.approvalTypes = approvalTypes;
+ }
+
+ @Override
+ public ChangeDetail call() throws NoSuchChangeException, OrmException,
+ EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException,
+ MissingObjectException, IncorrectObjectTypeException, IOException,
+ InvalidChangeOperationException {
+
+ ChangeUtil.rebaseChange(patchSetId, currentUser, db,
+ rebasedPatchSetSenderFactory, hooks, gitManager, patchSetInfoFactory,
+ replication, myIdent, changeControlFactory, approvalTypes);
+
+ return changeDetailFactory.create(patchSetId.getParentKey()).call();
+ }
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 42e822f..61e6a97 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -432,6 +432,10 @@
lastUpdatedOn = new Timestamp(System.currentTimeMillis());
}
+ public int getNumberOfPatchSets() {
+ return nbrPatchSets;
+ }
+
public String getSortKey() {
return sortKey;
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
index b62aada..4660291 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ChangeAccess.java
@@ -101,6 +101,16 @@
ResultSet<Change> allClosedNext(char status, String sortKey, int limit)
throws OrmException;
+ @Query("WHERE open = false AND status = ? AND dest = ? AND sortKey > ?"
+ + " ORDER BY sortKey LIMIT ?")
+ ResultSet<Change> byBranchClosedPrev(char status, Branch.NameKey p,
+ String sortKey, int limit) throws OrmException;
+
+ @Query("WHERE open = false AND status = ? AND dest = ? AND sortKey < ?"
+ + " ORDER BY sortKey DESC LIMIT ?")
+ ResultSet<Change> byBranchClosedNext(char status, Branch.NameKey p,
+ String sortKey, int limit) throws OrmException;
+
@Query
ResultSet<Change> all() throws OrmException;
}
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
index 83f7d15..5d0e15c 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_generic.sql
@@ -109,6 +109,10 @@
CREATE INDEX changes_allClosed
ON changes (open, status, sort_key);
+-- covers: byBranchClosedPrev, byBranchClosedNext
+CREATE INDEX changes_byBranchClosed
+ON changes (status, dest_project_name, dest_branch_name, sort_key);
+
CREATE INDEX changes_key
ON changes (change_key);
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index 3afa7ef..97ad126 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -187,6 +187,11 @@
CREATE INDEX changes_byProject
ON changes (dest_project_name);
+-- covers: byBranchClosedPrev, byBranchClosedNext
+CREATE INDEX changes_byBranchClosed
+ON changes (status, dest_project_name, dest_branch_name, sort_key)
+WHERE open = 'N';
+
CREATE INDEX changes_key
ON changes (change_key);
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 19e3e00..3417111 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
@@ -14,11 +14,17 @@
package com.google.gerrit.server;
+import com.google.gerrit.common.data.ApprovalType;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import java.util.Collections;
import java.util.List;
public class ApprovalsUtil {
@@ -33,4 +39,36 @@
}
db.patchSetApprovals().update(approvals);
}
+
+ /**
+ * Moves the PatchSetApprovals to the last PatchSet on the change while
+ * keeping the vetos.
+ *
+ * @param db The review database
+ * @param change Change to update
+ * @param approvalTypes The approval types
+ * @throws OrmException
+ * @throws IOException
+ */
+ public static void copyVetosToLatestPatchSet(final ReviewDb db, Change change,
+ ApprovalTypes approvalTypes) throws OrmException, IOException {
+ PatchSet.Id source;
+ if (change.getNumberOfPatchSets() > 1) {
+ source = new PatchSet.Id(change.getId(), change.getNumberOfPatchSets() - 1);
+ } else {
+ throw new IOException("Previous patch set could not be found");
+ }
+
+ PatchSet.Id dest = change.currPatchSetId();
+ for (PatchSetApproval a : db.patchSetApprovals().byPatchSet(source)) {
+ // ApprovalCategory.SUBMIT is still in db but not relevant in git-store
+ if (!ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
+ final ApprovalType type = approvalTypes.byId(a.getCategoryId());
+ if (type.getCategory().isCopyMinScore() && type.isMaxNegative(a)) {
+ db.patchSetApprovals().insert(
+ Collections.singleton(new PatchSetApproval(dest, a)));
+ }
+ }
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index bbcb14b..9dc572d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -14,10 +14,16 @@
package com.google.gerrit.server;
+import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetAncestor;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.client.TrackingId;
@@ -28,12 +34,16 @@
import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.mail.EmailException;
+import com.google.gerrit.server.mail.RebasedPatchSetSender;
+import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.mail.ReplyToChangeSender;
import com.google.gerrit.server.mail.RevertedSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.AtomicUpdate;
import com.google.gwtorm.server.OrmConcurrencyException;
import com.google.gwtorm.server.OrmException;
@@ -44,8 +54,11 @@
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.FooterLine;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -53,6 +66,8 @@
import org.eclipse.jgit.util.NB;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -159,6 +174,252 @@
opFactory.create(change.getDest()).verifyMergeability(change);
}
+ public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
+ throws OrmException {
+ final int cnt = src.getParentCount();
+ List<PatchSetAncestor> toInsert = new ArrayList<PatchSetAncestor>(cnt);
+ for (int p = 0; p < cnt; p++) {
+ PatchSetAncestor a =
+ new PatchSetAncestor(new PatchSetAncestor.Id(id, p + 1));
+ a.setAncestorRevision(new RevId(src.getParent(p).getId().getName()));
+ toInsert.add(a);
+ }
+ db.patchSetAncestors().insert(toInsert);
+ }
+
+ /**
+ * Rebases a commit
+ *
+ * @param git Repository to find commits in
+ * @param original The commit to rebase
+ * @param base Base to rebase against
+ * @return CommitBuilder the newly rebased commit
+ * @throws IOException Merged failed
+ */
+ public static CommitBuilder rebaseCommit(Repository git, RevCommit original,
+ RevCommit base, PersonIdent committerIdent) throws IOException {
+
+ if (original.getParentCount() == 0) {
+ throw new IOException(
+ "Commits with no parents cannot be rebased (is this the initial commit?).");
+ }
+
+ if (original.getParentCount() > 1) {
+ throw new IOException(
+ "Patch sets with multiple parents cannot be rebased (merge commits)."
+ + " Parents: " + Arrays.toString(original.getParents()));
+ }
+
+ final RevCommit parentCommit = original.getParent(0);
+
+ if (base.equals(parentCommit)) {
+ throw new IOException("Change is already up to date.");
+ }
+
+ final ThreeWayMerger merger = MergeStrategy.RESOLVE.newMerger(git, true);
+ merger.setBase(parentCommit);
+ merger.merge(original, base);
+
+ if (merger.getResultTreeId() == null) {
+ throw new IOException(
+ "The rebase failed since conflicts occured during the merge.");
+ }
+
+ final CommitBuilder rebasedCommitBuilder = new CommitBuilder();
+
+ rebasedCommitBuilder.setTreeId(merger.getResultTreeId());
+ rebasedCommitBuilder.setParentId(base);
+ rebasedCommitBuilder.setAuthor(original.getAuthorIdent());
+ rebasedCommitBuilder.setMessage(original.getFullMessage());
+ rebasedCommitBuilder.setCommitter(committerIdent);
+
+ return rebasedCommitBuilder;
+ }
+
+ public static void rebaseChange(final PatchSet.Id patchSetId,
+ final IdentifiedUser user, final ReviewDb db,
+ RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
+ final ChangeHookRunner hooks, GitRepositoryManager gitManager,
+ final PatchSetInfoFactory patchSetInfoFactory,
+ final ReplicationQueue replication, PersonIdent myIdent,
+ final ChangeControl.Factory changeControlFactory,
+ final ApprovalTypes approvalTypes) throws NoSuchChangeException,
+ EmailException, OrmException, MissingObjectException,
+ IncorrectObjectTypeException, IOException,
+ PatchSetInfoNotAvailableException, InvalidChangeOperationException {
+
+ final Change.Id changeId = patchSetId.getParentKey();
+ final ChangeControl changeControl =
+ changeControlFactory.validateFor(changeId);
+
+ if (!changeControl.canRebase()) {
+ throw new InvalidChangeOperationException(
+ "Cannot rebase: New patch sets are not allowed to be added to change: "
+ + changeId.toString());
+ }
+
+ Change change = changeControl.getChange();
+ final Repository git = gitManager.openRepository(change.getProject());
+ try {
+ final RevWalk revWalk = new RevWalk(git);
+ try {
+ final PatchSet originalPatchSet = db.patchSets().get(patchSetId);
+ RevCommit branchTipCommit = null;
+
+ List<PatchSetAncestor> patchSetAncestors =
+ db.patchSetAncestors().ancestorsOf(patchSetId).toList();
+ if (patchSetAncestors.size() > 1) {
+ throw new IOException(
+ "The patch set you are trying to rebase is dependent on several other patch sets: "
+ + patchSetAncestors.toString());
+ }
+ if (patchSetAncestors.size() == 1) {
+ List<PatchSet> depPatchSetList = db.patchSets()
+ .byRevision(patchSetAncestors.get(0).getAncestorRevision())
+ .toList();
+ if (!depPatchSetList.isEmpty()) {
+ PatchSet depPatchSet = depPatchSetList.get(0);
+
+ Change.Id depChangeId = depPatchSet.getId().getParentKey();
+ Change depChange = db.changes().get(depChangeId);
+
+ if (depChange.getStatus() == Status.ABANDONED) {
+ throw new IOException("Cannot rebase against an abandoned change: "
+ + depChange.getKey().toString());
+ }
+ if (depChange.getStatus().isOpen()) {
+ PatchSet latestDepPatchSet =
+ db.patchSets().get(depChange.currentPatchSetId());
+ if (!depPatchSet.getId().equals(depChange.currentPatchSetId())) {
+ branchTipCommit =
+ revWalk.parseCommit(ObjectId
+ .fromString(latestDepPatchSet.getRevision().get()));
+ } else {
+ throw new IOException(
+ "Change is already based on the latest patch set of the dependent change.");
+ }
+ }
+ }
+ }
+
+ if (branchTipCommit == null) {
+ // We are dependent on a merged PatchSet or have no PatchSet
+ // dependencies at all.
+ Ref destRef = git.getRef(change.getDest().get());
+ if (destRef == null) {
+ throw new IOException(
+ "The destination branch does not exist: "
+ + change.getDest().get());
+ }
+ branchTipCommit = revWalk.parseCommit(destRef.getObjectId());
+ }
+
+ final RevCommit originalCommit =
+ revWalk.parseCommit(ObjectId.fromString(originalPatchSet
+ .getRevision().get()));
+
+ CommitBuilder rebasedCommitBuilder =
+ rebaseCommit(git, originalCommit, branchTipCommit, myIdent);
+
+ final ObjectInserter oi = git.newObjectInserter();
+ final ObjectId rebasedCommitId;
+ try {
+ rebasedCommitId = oi.insert(rebasedCommitBuilder);
+ oi.flush();
+ } finally {
+ oi.release();
+ }
+
+ Change updatedChange =
+ db.changes().atomicUpdate(changeId, new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ if (change.getStatus().isOpen()) {
+ change.nextPatchSetId();
+ return change;
+ } else {
+ return null;
+ }
+ }
+ });
+
+ if (updatedChange == null) {
+ throw new InvalidChangeOperationException("Change is closed: "
+ + change.toString());
+ } else {
+ change = updatedChange;
+ }
+
+ final PatchSet rebasedPatchSet = new PatchSet(change.currPatchSetId());
+ rebasedPatchSet.setCreatedOn(change.getCreatedOn());
+ rebasedPatchSet.setUploader(user.getAccountId());
+ rebasedPatchSet.setRevision(new RevId(rebasedCommitId.getName()));
+
+ insertAncestors(db, rebasedPatchSet.getId(),
+ revWalk.parseCommit(rebasedCommitId));
+
+ db.patchSets().insert(Collections.singleton(rebasedPatchSet));
+ final PatchSetInfo info =
+ patchSetInfoFactory.get(db, rebasedPatchSet.getId());
+
+ change =
+ db.changes().atomicUpdate(change.getId(),
+ new AtomicUpdate<Change>() {
+ @Override
+ public Change update(Change change) {
+ change.setCurrentPatchSet(info);
+ ChangeUtil.updated(change);
+ return change;
+ }
+ });
+
+ final RefUpdate ru = git.updateRef(rebasedPatchSet.getRefName());
+ ru.setNewObjectId(rebasedCommitId);
+ ru.disableRefLog();
+ if (ru.update(revWalk) != RefUpdate.Result.NEW) {
+ throw new IOException("Failed to create ref "
+ + rebasedPatchSet.getRefName() + " in " + git.getDirectory()
+ + ": " + ru.getResult());
+ }
+
+ replication.scheduleUpdate(change.getProject(), ru.getName());
+
+ ApprovalsUtil.copyVetosToLatestPatchSet(db, change, approvalTypes);
+
+ final ChangeMessage cmsg =
+ new ChangeMessage(new ChangeMessage.Key(changeId,
+ ChangeUtil.messageUUID(db)), user.getAccountId(), patchSetId);
+ cmsg.setMessage("Patch Set " + patchSetId.get() + ": Rebased");
+ db.changeMessages().insert(Collections.singleton(cmsg));
+
+ final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
+ final Set<Account.Id> oldCC = new HashSet<Account.Id>();
+
+ for (PatchSetApproval a : db.patchSetApprovals().byChange(change.getId())) {
+ if (a.getValue() != 0) {
+ oldReviewers.add(a.getAccountId());
+ } else {
+ oldCC.add(a.getAccountId());
+ }
+ }
+
+ final ReplacePatchSetSender cm =
+ rebasedPatchSetSenderFactory.create(change);
+ cm.setFrom(user.getAccountId());
+ cm.setPatchSet(rebasedPatchSet);
+ cm.addReviewers(oldReviewers);
+ cm.addExtraCC(oldCC);
+ cm.send();
+
+ hooks.doPatchsetCreatedHook(change, rebasedPatchSet, db);
+ } finally {
+ revWalk.release();
+ }
+ } finally {
+ git.close();
+ }
+ }
+
public static Change.Id revert(final PatchSet.Id patchSetId,
final IdentifiedUser user, final String message, final ReviewDb db,
final RevertedSender.Factory revertedSenderFactory,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index bf741a4..00562b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -44,6 +44,7 @@
import com.google.gerrit.server.mail.CreateChangeSender;
import com.google.gerrit.server.mail.MergeFailSender;
import com.google.gerrit.server.mail.MergedSender;
+import com.google.gerrit.server.mail.RebasedPatchSetSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.mail.RestoredSender;
import com.google.gerrit.server.mail.RevertedSender;
@@ -95,6 +96,7 @@
factory(PublishComments.Factory.class);
factory(PublishDraft.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
+ factory(RebasedPatchSetSender.Factory.class);
factory(AbandonedSender.Factory.class);
factory(RemoveReviewer.Factory.class);
factory(RestoreChange.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 7be2b23..d14820a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1146,7 +1146,7 @@
private void doReplaces() {
for (final ReplaceRequest request : replaceByChange.values()) {
try {
- doReplace(request);
+ doReplace(request, false);
replaceProgress.update(1);
} catch (IOException err) {
log.error("Error computing replacement patch for change "
@@ -1166,7 +1166,7 @@
}
}
- private PatchSet.Id doReplace(final ReplaceRequest request)
+ private PatchSet.Id doReplace(final ReplaceRequest request, boolean ignoreNoChanges)
throws IOException, OrmException {
final RevCommit c = request.newCommit;
rp.getRevWalk().parseBody(c);
@@ -1262,7 +1262,7 @@
final boolean parentsEq = parentsEqual(c, prior);
final boolean authorEq = authorEqual(c, prior);
- if (messageEq && parentsEq && authorEq) {
+ if (messageEq && parentsEq && authorEq && !ignoreNoChanges) {
reject(request.cmd, "no changes made");
return null;
} else {
@@ -1866,7 +1866,6 @@
rw.parseBody(c);
closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
closeProgress.update(1);
- continue;
}
rw.parseBody(c);
@@ -1880,7 +1879,7 @@
}
for (final ReplaceRequest req : toClose) {
- final PatchSet.Id psi = doReplace(req);
+ final PatchSet.Id psi = doReplace(req, true);
if (psi != null) {
closeChange(req.cmd, psi, req.newCommit);
closeProgress.update(1);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
new file mode 100644
index 0000000..8fc8238
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/RebasedPatchSetSender.java
@@ -0,0 +1,40 @@
+// 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.server.mail;
+
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+/** Send notice to reviewers that a change has been rebased. */
+public class RebasedPatchSetSender extends ReplacePatchSetSender {
+ public static interface Factory {
+ RebasedPatchSetSender create(Change change);
+ }
+
+ @Inject
+ public RebasedPatchSetSender(EmailArguments ea,
+ @AnonymousCowardName String anonymousCowardName, SshInfo si,
+ @Assisted Change c) {
+ super(ea, anonymousCowardName, si, c);
+ }
+
+ @Override
+ protected void formatChange() throws EmailException {
+ appendText(velocifyFile("RebasedPatchSet.vm"));
+ }
+}
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 3e65a11..1a50030 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
@@ -199,6 +199,11 @@
return isOwner() && isVisible(db);
}
+ /** Can this user rebase this change? */
+ public boolean canRebase() {
+ return canAddPatchSet();
+ }
+
/** Can this user restore this change? */
public boolean canRestore() {
return canAbandon(); // Anyone who can abandon the change can restore it back
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
index 15712e02c..e5b4143 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
@@ -23,12 +23,11 @@
class BranchPredicate extends OperatorPredicate<ChangeData> {
private final Provider<ReviewDb> dbProvider;
- private final String shortName;
BranchPredicate(Provider<ReviewDb> dbProvider, String branch) {
- super(ChangeQueryBuilder.FIELD_BRANCH, branch);
+ super(ChangeQueryBuilder.FIELD_BRANCH, branch.startsWith(Branch.R_HEADS)
+ ? branch : Branch.R_HEADS + branch);
this.dbProvider = dbProvider;
- this.shortName = branch;
}
@Override
@@ -38,7 +37,7 @@
return false;
}
return change.getDest().get().startsWith(Branch.R_HEADS)
- && shortName.equals(change.getDest().getShortName());
+ && getValue().equals(change.getDest().get());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index f8ee134..bf2e4cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ChangeAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -118,6 +119,102 @@
return a.getValue().compareTo(b.getValue()) >= 0 ? a : b;
}
+ @Rewrite("status:open P=(project:*) B=(branch:*) S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r05_byBranchOpenPrev(
+ @Named("P") final ProjectPredicate p,
+ @Named("B") final BranchPredicate b,
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(500, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byBranchOpenAll(new Branch.NameKey(p.getValueKey(), b
+ .getValue()));
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() //
+ && p.match(cd) //
+ && b.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:open P=(project:*) B=(branch:*) S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r05_byBranchOpenNext(
+ @Named("P") final ProjectPredicate p,
+ @Named("B") final BranchPredicate b,
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(500, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byBranchOpenAll(new Branch.NameKey(p.getValueKey(), b
+ .getValue()));
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus().isOpen() //
+ && p.match(cd) //
+ && b.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:merged P=(project:*) B=(branch:*) S=(sortkey_after:*) L=(limit:*)")
+ public Predicate<ChangeData> r05_byBranchMergedPrev(
+ @Named("P") final ProjectPredicate p,
+ @Named("B") final BranchPredicate b,
+ @Named("S") final SortKeyPredicate.After s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byBranchClosedPrev(Change.Status.MERGED.getCode(), //
+ new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+ && p.match(cd) //
+ && b.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
+ @Rewrite("status:merged P=(project:*) B=(branch:*) S=(sortkey_before:*) L=(limit:*)")
+ public Predicate<ChangeData> r05_byBranchMergedNext(
+ @Named("P") final ProjectPredicate p,
+ @Named("B") final BranchPredicate b,
+ @Named("S") final SortKeyPredicate.Before s,
+ @Named("L") final IntPredicate<ChangeData> l) {
+ return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ @Override
+ ResultSet<Change> scan(ChangeAccess a, String key, int limit)
+ throws OrmException {
+ return a.byBranchClosedNext(Change.Status.MERGED.getCode(), //
+ new Branch.NameKey(p.getValueKey(), b.getValue()), key, limit);
+ }
+
+ @Override
+ public boolean match(ChangeData cd) throws OrmException {
+ return cd.change(dbProvider).getStatus() == Change.Status.MERGED
+ && p.match(cd) //
+ && b.match(cd) //
+ && s.match(cd);
+ }
+ };
+ }
+
@Rewrite("status:open P=(project:*) S=(sortkey_after:*) L=(limit:*)")
public Predicate<ChangeData> r10_byProjectOpenPrev(
@Named("P") final ProjectPredicate p,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index d5b5a26..9caeed3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- public static final Class<Schema_62> C = Schema_62.class;
+ public static final Class<Schema_63> C = Schema_63.class;
public static class Module extends AbstractModule {
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_63.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_63.java
new file mode 100644
index 0000000..761c36a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_63.java
@@ -0,0 +1,48 @@
+// 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.server.schema;
+
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class Schema_63 extends SchemaVersion {
+ @Inject
+ Schema_63(Provider<Schema_62> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ try {
+ if (((JdbcSchema) db).getDialect() instanceof DialectPostgreSQL) {
+ stmt.execute("CREATE INDEX changes_byBranchClosed"
+ + " ON changes (status, dest_project_name, dest_branch_name, sort_key)"
+ + " WHERE open = 'N'");
+ } else {
+ stmt.execute("CREATE INDEX changes_byBranchClosed"
+ + " ON changes (status, dest_project_name, dest_branch_name, sort_key)");
+ }
+ } finally {
+ stmt.close();
+ }
+ }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RebasedPatchSet.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RebasedPatchSet.vm
new file mode 100644
index 0000000..e761627
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RebasedPatchSet.vm
@@ -0,0 +1,54 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example". If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used. If you
+## want to override the default template, copy the .vm.example file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The RebasedPatchSet.vm template will determine the contents of the email
+## related to a user rebasing a patchset for a change through the Gerrit UI.
+## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
+##
+#if($email.reviewerNames)
+Hello $email.joinStrings($email.reviewerNames, ', '),
+
+I'd like you to reexamine a rebased change.#if($email.changeUrl) Please visit
+
+ $email.changeUrl
+
+to look at the new rebased patch set (#$patchSet.patchSetId).
+#end
+#else
+$fromName has created a new patch set by issuing a rebase in Gerrit (#$patchSet.patchSetId).
+#end
+
+Change subject: $change.subject
+......................................................................
+
+$email.changeDetail
+#if($email.sshHost)
+ git pull ssh://$email.sshHost/$projectName $patchSet.refName
+#end