Merge branch 'stable'

* stable: (30 commits)
  Clarify the upgrade instructions
  Draft release notes for 2.1.5
  documentation: Document why hook --change-url might be missing
  documentation: Fix rendering errors in gerrit.config
  Allow ; and & to seperate parameters in gitweb
  Include a quick summary of the size of a change in email
  Display the size of a patch (lines added/removed)
  Fix clearing of topic during replace
  Fix inherited Read Access +2 not inheriting
  Add some basic RefControl tests for delegated ownership
  Fix branch owner adding exclusive ACL
  Optimize RegexFilePredicate for common matches
  Correct copyright headers to AOSP
  Don't expose /COMMIT_MSG as a modified file in ChangeData
  Don't enable dk.brics.automaton's optional syntax
  Fix NPE while matching file:^ pattern on deleted files
  Fixed unused imports in OutgoingEmail
  Don't show /COMMIT_MSG in change emails
  Fix NPE during merge failure on new branch
  Honor user's syntax coloring preference in unified view
  ...

Conflicts:
	gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
	gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
	gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
	gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
	gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
	gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
	gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java

Change-Id: I80bfdb55872fa4bdf4f751c337b609e7ec809605
This commit is contained in:
Shawn O. Pearce
2010-08-23 11:39:41 -07:00
55 changed files with 1359 additions and 380 deletions

View File

@@ -62,7 +62,7 @@ EXAMPLES
Create a new user account called `watcher`:
====
$ cat ~/.ssh/id_watcher.pub | ssh -p 29418 review.example.com gerrit create-user --ssh-key - watcher
$ cat ~/.ssh/id_watcher.pub | ssh -p 29418 review.example.com gerrit create-account --ssh-key - watcher
====
GERRIT

View File

@@ -735,11 +735,11 @@ Multiple schemes are supported:
+
* `http`
+
HTTP download is allowed.
Authenticated HTTP download is allowed.
+
* `ssh`
+
SSH download is allowed.
Authenticated SSH download is allowed.
+
* `anon_http`
+
@@ -747,18 +747,20 @@ Anonymous HTTP download is allowed.
+
* `anon_git`
+
Anonymous Git download is allowed.
This is not default, it is also necessary to fill gerrit.canonicalGitUrl variable.
Anonymous Git download is allowed. This is not default, it is also
necessary to set <<gerrit.canonicalGitUrl,gerrit.canonicalGitUrl>>
variable.
+
* `repo_download`
+
Gerrit advertises patch set downloads with the `repo download` command,
assuming that all projects managed by this instance are generally worked
on with the repo multi-repository tool.
This is not default, as not all instances will deploy repo.
Gerrit advertises patch set downloads with the `repo download`
command, assuming that all projects managed by this instance are
generally worked on with the repo multi-repository tool. This is
not default, as not all instances will deploy repo.
+
If download.scheme is not specified, SSH, HTTP and Anonymous HTTP downloads are allowed.
If download.scheme is not specified, SSH, HTTP and Anonymous HTTP
downloads are allowed.
[[gerrit]]Section gerrit
~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -78,6 +78,15 @@ the values of hooks.patchsetCreatedHook, hooks.commentAddedHook,
hooks.changeMergedHook and hooks.changeAbandonedHook for the
filenames for the hooks.
Missing Change URLs
-------------------
If link:config-gerrit.html#gerrit.canonicalWebUrl[gerrit.canonicalWebUrl]
is not set in `gerrit.config` the `\--change-url` flag may not be
passed to all hooks. Hooks started out of an SSH context (for example
the patchset-created hook) don't know the server's web URL, unless
this variable is configured.
See Also
--------

View File

@@ -0,0 +1,173 @@
Release notes for Gerrit 2.1.5
==============================
Gerrit 2.1.5 is now available:
link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.5.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.5.war]
This is primarly a bug fix release to 2.1.4, but some additional
new features were included so its named 2.1.5 rather than 2.1.4.1.
Upgrade Instructions
--------------------
If upgrading from version 2.1.4, simply replace the WAR file in
`'site_path'/bin/gerrit.war` and restart Gerrit.
If upgrading from version 2.1.3 or earlier, stop Gerrit, use
`java -jar gerrit.war init -d 'site_path'` to upgrade the schema,
and restart Gerrit.
New Features
------------
Web UI
~~~~~~
* issue 361 Enable commenting on commit messages
+
The commit message of a change can now be commented on inline, and
even compared between patch sets, just like any other file contents.
The message is presented as a magical file called 'Commit Message',
in the first row of every change.
* issue 312 Implement 'Restore Change' to undo 'Abandon Change'
+
Any user who can abandon a change (the change owner, project owner,
or any site administrator) can now restore the change from Abandoned
status back to Review in Progress.
* issue 583 Enable/disable download protocols
+
The new download section in `gerrit.config` controls how the patch
set download links are presented in the web UI. Administrators
can use this section to enable `repo download`, `git://`, or to
disable `http://` style URLs. This section replaces the older
repo.showDownloadCommand.
* issue 499 Display the size of a patch (lines added/removed)
+
A 'diffstat' is shown for each file, summarizing the size of the
change on that file in terms of number of lines added or deleted.
Email Notifications
~~~~~~~~~~~~~~~~~~~
* issue 452 Include a quick summary of the size of a change in email
+
After the file listing, a summary totaling the number of files
changed, lines added, and lines removed is displayed. This may
help reviewers to get a quick estimation on the time required for
them to review the change.
Bug Fixes
---------
Web UI
~~~~~~
* issue 639 Fix keyboard shortcuts under Chrome/Safari
+
Keyboard shortcuts didn't work properly on modern WebKit browsers
like Chrome and Safari. We kept trying to blame this on the browser,
but it was Gerrit Code Review at fault. The UI was using the wrong
listener type to receive keyboard events in comment editors. Fixed.
* Make 'u' go up to the last change listing
+
Previously the 'u' key on a change page was hardcoded to take
the user to their own dashboard. However, if they arrived at the
change through a query such as `is:starred status:open`, this was
quite annoying, as the query had to be started over again to move
to the next matching change. Now the 'u' key goes back to the
query results.
* issue 671 Honor user's syntax coloring preference in unified view
+
The user's syntax coloring preference was always ignored in the
unified view, even though the side-by-side view honored it. Fixed.
* issue 651 Display stars in dependency tables
+
The 'Depends On' and 'Needed By' tables on a change page did not
show the current user's star settings, even though the star icon
is present and will toggle the user's starred flag for that change.
Fixed.
Access Control
~~~~~~~~~~~~~~
* issue 672 Fix branch owner adding exclusive ACL
+
Branch owners could not add exclusive ACLs within their branch
namespace. This was caused by the server trying to match the leading
`-` entered by the branch administrator against patterns that did
not contain `-`, and therefore always failed. Fixed by removing
the magical `-` from the proposed new specification before testing
the access rights.
* '@' in ref specs shouldn't be magical.
+
The dk.brics.automaton package that is used to handle regular
expressions on branch access patterns supports '@' to mean
"any string". We don't want that behavior. Fixed by disabling
the optional features of dk.brics.automaton, thereby making '@'
mean a literal '@' sign as expected.
* issue 668 Fix inherited Read Access +2 not inheriting
+
Upload access (aka Read +2) did not inherit properly from the parent
project (e.g. '\-- All Projects \--') if there was any branch level
Read access control within the local project. This was a coding
bug which failed to consider the project inheritance if any branch
(not just the one being uploaded to) denied upload access.
Misc.
~~~~~
* issue 641 Don't pass null arguments to hooks
+
Some hooks crashed inside of the server during invocation because the
`gerrit.canonicalWebUrl` variable wasn't configured, and the hook
was started out of an SSH or background thread context, so the URL
couldn't be assumed from the current request. The bug was worked
around by not passing the `\--change-url` flag in these cases.
Administrators whose hooks always need the flag should configure
`gerrit.canonicalWebUrl`.
* issue 652 Fix NPE during merge failure on new branch
+
Submitting a change with a missing dependency to a new branch
resulted in a NullPointerException in the server, because the server
tried to create the branch anyway, even though there was no commit
ready because one or more dependencies were missing. Fixed.
* Fix NPE while matching `file:^` pattern on deleted files
+
Sending email notifications crashed with NullPointerException if the
change contained a deleted file and one or more users had a project
watch on that project using a `file:^` pattern in their filter.
Fixed.
* issue 658 Allow to use refspec shortcuts for push replication
+
A push refspec of `refs/heads/\*` in replication.config is now
supported as a shorthand notation for `refs/heads/\*:refs/heads/\*`.
* issue 676 Fix clearing of topic during replace
+
The topic was cleared if a replacement patch set was uploaded without
the topic name. The topic is now left as-is during replacement
if no new topic was supplied. If a new topic is supplied, it is
changed to match the new topic given.
* Allow ; and & to seperate parameters in gitweb
+
gitweb.cgi accepts either ';' or '&' between parameters, but
Gerrit Code Review was only accepting the ';' syntax. Fixed
to support both.
Documentation
~~~~~~~~~~~~~
* Fixed example for gerrit create-account.
* gerrit.sh: Correct /etc/default path in error message
Version
-------
2765ff9e5f821100e9ca671f4d502b5c938457a5

View File

@@ -4,6 +4,7 @@ Gerrit Code Review - Release Notes
[[2_1]]
Version 2.1.x
-------------
* link:ReleaseNotes-2.1.5.html[2.1.5]
* link:ReleaseNotes-2.1.4.html[2.1.4]
* link:ReleaseNotes-2.1.3.html[2.1.3]
* link:ReleaseNotes-2.1.2.5.html[2.1.2.5]

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.client;
import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
import com.google.gerrit.client.auth.userpass.UserPassSignInDialog;
import com.google.gerrit.client.changes.ChangeListScreen;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.LinkMenuBar;
import com.google.gerrit.client.ui.LinkMenuItem;
@@ -81,6 +82,8 @@ public class Gerrit implements EntryPoint {
private static final Dispatcher dispatcher = new Dispatcher();
private static ViewSite<Screen> body;
private static String lastChangeListToken;
static {
SYSTEM_SVC = GWT.create(SystemInfoService.class);
JsonUtil.bind(SYSTEM_SVC, "rpc/SystemInfoService");
@@ -91,6 +94,16 @@ public class Gerrit implements EntryPoint {
Window.Location.reload();
}
public static void displayLastChangeList() {
if (lastChangeListToken != null) {
display(lastChangeListToken);
} else if (isSignedIn()) {
display(PageLinks.MINE);
} else {
display(PageLinks.toChangeQuery("status:open"));
}
}
/**
* Load the screen at the given location, displaying when ready.
* <p>
@@ -365,6 +378,11 @@ public class Gerrit implements EntryPoint {
dispatchHistoryHooks(token);
}
}
if (view instanceof ChangeListScreen) {
lastChangeListToken = token;
}
super.onShowView(view);
view.onShowView();
}

View File

@@ -145,6 +145,7 @@ public interface GerritCss extends CssResource {
String patchSetLink();
String patchSetRevision();
String patchSetUserIdentity();
String patchSizeCell();
String permalink();
String posscore();
String projectAdminApprovalCategoryRangeLine();

View File

@@ -109,7 +109,7 @@ class SshPanel extends Composite {
addTxt = new NpTextArea();
addTxt.setVisibleLines(12);
addTxt.setCharacterWidth(80);
DOM.setElementPropertyBoolean(addTxt.getElement(), "spellcheck", false);
addTxt.setSpellCheck(false);
addKeyBlock.add(addTxt);
final HorizontalPanel buttons = new HorizontalPanel();

View File

@@ -1,4 +1,16 @@
// Copyright 2010 Google Inc. All Rights Reserved.
// Copyright (C) 2010 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.client.admin;

View File

@@ -64,7 +64,7 @@ public class AbandonChangeDialog extends AutoCenterDialogBox implements CloseHan
message = new NpTextArea();
message.setCharacterWidth(60);
message.setVisibleLines(10);
DOM.setElementPropertyBoolean(message.getElement(), "spellcheck", true);
message.setSpellCheck(true);
mwrap.add(message);
final FlowPanel buttonPanel = new FlowPanel();

View File

@@ -25,7 +25,7 @@ import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.reviewdb.Account;
public class AccountDashboardScreen extends Screen {
public class AccountDashboardScreen extends Screen implements ChangeListScreen {
private final Account.Id ownerId;
private ChangeTable table;
private ChangeTable.Section byOwner;

View File

@@ -46,7 +46,7 @@ public interface ChangeConstants extends Constants {
String changeTableStar();
String changeTablePagePrev();
String changeTablePageNext();
String upToDashboard();
String upToChangeList();
String expandCollapseDependencies();
String previousPatchSet();
String nextPatchSet();
@@ -54,11 +54,13 @@ public interface ChangeConstants extends Constants {
String patchTableColumnName();
String patchTableColumnComments();
String patchTableColumnSize();
String patchTableColumnDiff();
String patchTableDiffSideBySide();
String patchTableDiffUnified();
String patchTableDownloadPreImage();
String patchTableDownloadPostImage();
String commitMessage();
String patchTablePrev();
String patchTableNext();

View File

@@ -26,7 +26,7 @@ changeTableOpen = Open change
changeTableStar = Star (or unstar) change
changeTablePagePrev = Previous page of changes
changeTablePageNext = Next page of changes
upToDashboard = Up to dashboard
upToChangeList = Up to change list
expandCollapseDependencies = Expands / Collapses dependencies section
previousPatchSet = Previous patch set
nextPatchSet = Next patch set
@@ -34,11 +34,13 @@ keyPublishComments = Review and publish comments
patchTableColumnName = File Path
patchTableColumnComments = Comments
patchTableColumnSize = Size
patchTableColumnDiff = Diff
patchTableDiffSideBySide = Side-by-Side
patchTableDiffUnified = Unified
patchTableDownloadPreImage = old
patchTableDownloadPostImage = new
commitMessage = Commit Message
patchTablePrev = Previous file
patchTableNext = Next file

View File

@@ -0,0 +1,18 @@
// Copyright (C) 2010 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.client.changes;
public interface ChangeListScreen {
}

View File

@@ -31,6 +31,8 @@ public interface ChangeMessages extends Messages {
String patchTableComments(@PluralCount int count);
String patchTableDrafts(@PluralCount int count);
String patchTableSize_Modify(int insertions, int deletions);
String patchTableSize_Lines(@PluralCount int insertions);
String removeReviewer(String fullName);
String messageWrittenOn(String date);

View File

@@ -12,6 +12,8 @@ submitPatchSet = Submit Patch Set {0}
patchTableComments = {0} comments
patchTableDrafts = {0} drafts
patchTableSize_Modify = +{0}, -{1}
patchTableSize_Lines = {0} lines
removeReviewer = Remove reviewer {0}
messageWrittenOn = on {0}

View File

@@ -3,3 +3,6 @@ patchTableComments = {0} comments
patchTableDrafts[one] = 1 draft
patchTableDrafts = {0} drafts
patchTableSize_Lines[one] = 1 line
patchTableSize_Lines = {0} lines

View File

@@ -22,7 +22,6 @@ import com.google.gerrit.client.ui.ExpandAllCommand;
import com.google.gerrit.client.ui.LinkMenuBar;
import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.ChangeDetail;
@@ -145,7 +144,7 @@ public class ChangeScreen extends Screen {
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
keysNavigation.add(new DashboardKeyCommand(0, 'u', Util.C.upToDashboard()));
keysNavigation.add(new UpToListKeyCommand(0, 'u', Util.C.upToChangeList()));
keysNavigation.add(new ExpandCollapseDependencySectionKeyCommand(0, 'd', Util.C.expandCollapseDependencies()));
if (Gerrit.isSignedIn()) {
@@ -346,18 +345,14 @@ public class ChangeScreen extends Screen {
});
}
public class DashboardKeyCommand extends KeyCommand {
public DashboardKeyCommand(int mask, char key, String help) {
public class UpToListKeyCommand extends KeyCommand {
public UpToListKeyCommand(int mask, char key, String help) {
super(mask, key, help);
}
@Override
public void onKeyPress(final KeyPressEvent event) {
if (Gerrit.isSignedIn()) {
Gerrit.display(PageLinks.MINE);
} else {
Gerrit.display(PageLinks.toChangeQuery("status:open"));
}
Gerrit.displayLastChangeList();
}
}

View File

@@ -1,64 +0,0 @@
// Copyright (C) 2008 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.client.changes;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gerrit.common.data.SingleListChangeInfo;
import com.google.gwt.user.client.rpc.AsyncCallback;
public abstract class MineSingleListScreen extends AccountScreen {
private final String anchor;
private ChangeTable table;
private ChangeTable.Section drafts;
protected MineSingleListScreen(final String historyToken) {
anchor = historyToken;
}
@Override
protected void onInitUI() {
super.onInitUI();
table = new ChangeTable();
drafts = new ChangeTable.Section();
table.addSection(drafts);
table.setSavePointerId(anchor);
add(table);
}
@Override
public void registerKeys() {
super.registerKeys();
table.setRegisterKeys(true);
}
protected AsyncCallback<SingleListChangeInfo> loadCallback() {
return new ScreenLoadCallback<SingleListChangeInfo>(this) {
@Override
protected void preDisplay(final SingleListChangeInfo result) {
display(result);
}
};
}
private void display(final SingleListChangeInfo result) {
table.setAccountInfoCache(result.getAccounts());
drafts.display(result.getChanges());
table.finishDisplay();
}
}

View File

@@ -14,14 +14,12 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountDashboardLink;
import com.google.gerrit.client.ui.ComplexDisclosurePanel;
import com.google.gerrit.client.ui.PatchLink;
import com.google.gerrit.client.ui.PatchLink.SideBySide;
import com.google.gerrit.client.ui.PatchLink.Unified;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.GitwebLink;
import com.google.gerrit.common.data.PatchSetDetail;
@@ -447,9 +445,8 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
@Override
public void onClick(ClickEvent event) {
for (Patch p : detail.getPatches()) {
SideBySide link = new PatchLink.SideBySide(p.getFileName(), p.getKey(), 0, null, null);
Window.open(Window.Location.getPath() + "#"
+ link.getTargetHistoryToken(), "_blank", null);
+ Dispatcher.toPatchSideBySide(p.getKey()), "_blank", null);
}
}
});
@@ -461,9 +458,8 @@ class PatchSetComplexDisclosurePanel extends ComplexDisclosurePanel implements O
@Override
public void onClick(ClickEvent event) {
for (Patch p : detail.getPatches()) {
Unified link = new PatchLink.Unified(p.getFileName(), p.getKey(), 0, null, null);
Window.open(Window.Location.getPath() + "#"
+ link.getTargetHistoryToken(), "_blank", null);
+ Dispatcher.toPatchUnified(p.getKey()), "_blank", null);
}
}
});

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.client.ui.PatchLink;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.Patch.Key;
import com.google.gerrit.reviewdb.Patch.PatchType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -211,7 +212,7 @@ public class PatchTable extends Composite {
// Note: use '/' here and not File.pathSeparator since git paths
// are always separated by /
//
String fileName = patch.getFileName();
String fileName = getDisplayFileName(patch);
int s = fileName.lastIndexOf('/');
if (s >= 0) {
fileName = fileName.substring(s + 1);
@@ -219,6 +220,17 @@ public class PatchTable extends Composite {
return fileName;
}
public static String getDisplayFileName(Patch patch) {
return getDisplayFileName(patch.getKey());
}
public static String getDisplayFileName(Patch.Key patchKey) {
if (Patch.COMMIT_MSG.equals(patchKey.get())) {
return Util.C.commitMessage();
}
return patchKey.get();
}
/**
* Update the reviewed status for the given patch.
*/
@@ -231,7 +243,8 @@ public class PatchTable extends Composite {
private class MyTable extends NavigationTable<Patch> {
private static final int C_PATH = 2;
private static final int C_DRAFT = 3;
private static final int C_SIDEBYSIDE = 4;
private static final int C_SIZE = 4;
private static final int C_SIDEBYSIDE = 5;
private int activeRow = -1;
MyTable() {
@@ -326,12 +339,12 @@ public class PatchTable extends Composite {
Widget nameCol;
if (patch.getPatchType() == Patch.PatchType.UNIFIED) {
nameCol =
new PatchLink.SideBySide(patch.getFileName(), patch.getKey(),
new PatchLink.SideBySide(getDisplayFileName(patch), patch.getKey(),
row - 1, detail, PatchTable.this);
} else {
nameCol =
new PatchLink.Unified(patch.getFileName(), patch.getKey(), row - 1,
detail, PatchTable.this);
new PatchLink.Unified(getDisplayFileName(patch), patch.getKey(),
row - 1, detail, PatchTable.this);
}
if (patch.getSourceFileName() != null) {
final String text;
@@ -393,6 +406,12 @@ public class PatchTable extends Composite {
m.append(Util.C.patchTableColumnComments());
m.closeTd();
// "Size"
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().dataHeader());
m.append(Util.C.patchTableColumnSize());
m.closeTd();
// "Diff"
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().dataHeader());
@@ -423,7 +442,11 @@ public class PatchTable extends Composite {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().changeTypeCell());
if (Patch.COMMIT_MSG.equals(p.getFileName())) {
m.nbsp();
} else {
m.append(p.getChangeType().getCode());
}
m.closeTd();
m.openTd();
@@ -437,6 +460,12 @@ public class PatchTable extends Composite {
appendCommentCount(m, p);
m.closeTd();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().dataCell());
m.addStyleName(Gerrit.RESOURCES.css().patchSizeCell());
appendSize(m, p);
m.closeTd();
switch (p.getPatchType()) {
case UNIFIED:
openlink(m, 2);
@@ -498,6 +527,29 @@ public class PatchTable extends Composite {
m.closeTr();
}
void appendTotals(final SafeHtmlBuilder m, int ins, int dels) {
m.openTr();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().iconCell());
m.addStyleName(Gerrit.RESOURCES.css().noborder());
m.nbsp();
m.closeTd();
m.openTd();
m.setAttribute("colspan", C_SIZE - 1);
m.closeTd();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().dataCell());
m.addStyleName(Gerrit.RESOURCES.css().patchSizeCell());
m.addStyleName(Gerrit.RESOURCES.css().leftMostCell());
m.append(Util.M.patchTableSize_Modify(ins, dels));
m.closeTd();
m.closeTr();
}
void appendCommentCount(final SafeHtmlBuilder m, final Patch p) {
if (p.getCommentCount() > 0) {
m.append(Util.M.patchTableComments(p.getCommentCount()));
@@ -513,6 +565,34 @@ public class PatchTable extends Composite {
}
}
void appendSize(final SafeHtmlBuilder m, final Patch p) {
if (Patch.COMMIT_MSG.equals(p.getFileName())) {
m.nbsp();
return;
}
if (p.getPatchType() == PatchType.UNIFIED) {
int ins = p.getInsertions();
int dels = p.getDeletions();
switch (p.getChangeType()) {
case ADDED:
m.append(Util.M.patchTableSize_Lines(ins));
break;
case DELETED:
m.nbsp();
break;
case MODIFIED:
case COPIED:
case RENAMED:
m.append(Util.M.patchTableSize_Modify(ins, dels));
break;
}
} else {
m.nbsp();
}
}
private void openlink(final SafeHtmlBuilder m, final int colspan) {
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().dataCell());
@@ -584,6 +664,9 @@ public class PatchTable extends Composite {
private double start;
private ProgressBar meter;
private int insertions;
private int deletions;
private DisplayCommand(final List<Patch> list) {
this.table = new MyTable();
this.list = list;
@@ -612,14 +695,19 @@ public class PatchTable extends Composite {
case 0:
if (row == 0) {
table.appendHeader(nc);
table.appendRow(nc, list.get(row++));
}
while (row < list.size()) {
table.appendRow(nc, list.get(row));
Patch p = list.get(row);
insertions += p.getInsertions();
deletions += p.getDeletions();
table.appendRow(nc, p);
if ((++row % 10) == 0 && longRunning()) {
updateMeter();
return true;
}
}
table.appendTotals(nc, insertions, deletions);
table.resetHtml(nc);
nc = null;
stage = 1;

View File

@@ -36,7 +36,6 @@ import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
@@ -201,7 +200,7 @@ public class PublishCommentScreen extends AccountScreen implements
message = new NpTextArea();
message.setCharacterWidth(60);
message.setVisibleLines(10);
DOM.setElementPropertyBoolean(message.getElement(), "spellcheck", true);
message.setSpellCheck(true);
mwrap.add(message);
}
@@ -281,7 +280,8 @@ public class PublishCommentScreen extends AccountScreen implements
draftsPanel.add(panel);
// Parent table can be null here since we are not showing any
// next/previous links
panel.add(new PatchLink.SideBySide(fn, patchKey, 0, null, null));
panel.add(new PatchLink.SideBySide(PatchTable
.getDisplayFileName(patchKey), patchKey, 0, null, null));
priorFile = fn;
}

View File

@@ -24,7 +24,8 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
public class QueryScreen extends PagedSingleListScreen {
public class QueryScreen extends PagedSingleListScreen implements
ChangeListScreen {
public static QueryScreen forQuery(String query) {
return forQuery(query, PageLinks.TOP);
}

View File

@@ -415,6 +415,14 @@
color: #ff5555;
}
.changeTable .patchSizeCell {
text-align: right;
white-space: nowrap;
}
.changeTable td.noborder {
border: none;
}
.changeTable .filePathCell {
white-space: nowrap;
}

View File

@@ -23,9 +23,8 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -79,13 +78,14 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
text.setText(comment.getMessage());
text.setCharacterWidth(INITIAL_COLS);
text.setVisibleLines(INITIAL_LINES);
DOM.setElementPropertyBoolean(text.getElement(), "spellcheck", true);
text.addKeyPressHandler(new KeyPressHandler() {
text.setSpellCheck(true);
text.addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyPress(final KeyPressEvent event) {
if (event.getCharCode() == KeyCodes.KEY_ESCAPE
public void onKeyDown(final KeyDownEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE
&& !event.isAnyModifierKeyDown()) {
event.preventDefault();
if (isNew()) {
onDiscard();
} else {
@@ -96,13 +96,15 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
if ((event.isControlKeyDown() || event.isMetaKeyDown())
&& !event.isAltKeyDown() && !event.isShiftKeyDown()) {
switch (event.getCharCode()) {
switch (event.getNativeKeyCode()) {
case 's':
case 'S':
event.preventDefault();
onSave(NULL_CALLBACK);
return;
case 'd':
case 'D':
event.preventDefault();
if (isNew()) {
onDiscard();
@@ -182,7 +184,13 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
setMessageTextVisible(!inEdit);
edit.setVisible(!inEdit);
text.setVisible(inEdit);
if (inEdit) {
text.setVisible(true);
} else {
text.setFocus(false);
text.setVisible(false);
}
save.setVisible(inEdit);
cancel.setVisible(inEdit && !isNew());
discard.setVisible(inEdit);
@@ -241,6 +249,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
}
comment.setMessage(txt);
text.setFocus(false);
text.setReadOnly(true);
save.setEnabled(false);
cancel.setEnabled(false);
@@ -262,6 +271,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
@Override
public void onFailure(final Throwable caught) {
text.setReadOnly(false);
text.setFocus(true);
save.setEnabled(true);
cancel.setEnabled(true);
discard.setEnabled(true);
@@ -281,10 +291,12 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
private void onDiscard() {
expandTimer.cancel();
if (isNew()) {
text.setFocus(false);
removeUI();
return;
}
text.setFocus(false);
text.setReadOnly(true);
save.setEnabled(false);
cancel.setEnabled(false);
@@ -300,6 +312,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
@Override
public void onFailure(final Throwable caught) {
text.setReadOnly(false);
text.setFocus(true);
save.setEnabled(true);
cancel.setEnabled(true);
discard.setEnabled(true);

View File

@@ -80,9 +80,6 @@ public abstract class PatchScreen extends Screen implements
public Unified(final Patch.Key id, final int patchIndex,
final PatchSetDetail patchSetDetail, final PatchTable patchTable) {
super(id, patchIndex, patchSetDetail, patchTable);
final AccountDiffPreference dp = settingsPanel.getValue();
dp.setSyntaxHighlighting(false);
settingsPanel.setValue(dp);
}
@Override
@@ -395,7 +392,7 @@ public abstract class PatchScreen extends Screen implements
private void onResult(final PatchScript script, final boolean isFirst) {
final Change.Key cid = script.getChangeId();
final String path = patchKey.get();
final String path = PatchTable.getDisplayFileName(patchKey);
String fileName = path;
final int last = fileName.lastIndexOf('/');
if (last >= 0) {

View File

@@ -118,7 +118,7 @@ public abstract class NavigationTable<RowItem> extends FancyFlexTable<RowItem> {
row++;
} else if (sEnd < cTop) {
row--;
} else if (getRowItem(row) != null) {
} else {
break;
}
}

View File

@@ -354,7 +354,7 @@ class GitWebServlet extends HttpServlet {
private static Map<String, String> getParameters(final HttpServletRequest req)
throws UnsupportedEncodingException {
final Map<String, String> params = new HashMap<String, String>();
for (final String pair : req.getQueryString().split(";")) {
for (final String pair : req.getQueryString().split("[&;]")) {
final int eq = pair.indexOf('=');
if (0 < eq) {
String name = pair.substring(0, eq);

View File

@@ -223,8 +223,7 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
for (final Change.Id a : ancestorOrder) {
final Change ac = m.get(a);
if (ac != null) {
aic.want(ac.getOwner());
dependsOn.add(new ChangeInfo(ac));
dependsOn.add(newChangeInfo(ac));
}
}
@@ -232,8 +231,7 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
for (final Change.Id a : descendants) {
final Change ac = m.get(a);
if (ac != null) {
aic.want(ac.getOwner());
neededBy.add(new ChangeInfo(ac));
neededBy.add(newChangeInfo(ac));
}
}
@@ -246,4 +244,15 @@ public class ChangeDetailFactory extends Handler<ChangeDetail> {
detail.setDependsOn(dependsOn);
detail.setNeededBy(neededBy);
}
private ChangeInfo newChangeInfo(final Change ac) {
aic.want(ac.getOwner());
ChangeInfo ci = new ChangeInfo(ac);
ci.setStarred(isStarred(ac));
return ci;
}
private boolean isStarred(final Change ac) {
return control.getCurrentUser().getStarredChanges().contains(ac.getId());
}
}

View File

@@ -65,6 +65,7 @@ class PatchScriptBuilder {
private Repository db;
private Change change;
private AccountDiffPreference diffPrefs;
private boolean againstParent;
private ObjectId aId;
private ObjectId bId;
@@ -101,7 +102,8 @@ class PatchScriptBuilder {
}
}
void setTrees(final ObjectId a, final ObjectId b) {
void setTrees(final boolean ap, final ObjectId a, final ObjectId b) {
againstParent = ap;
aId = a;
bId = b;
}
@@ -364,13 +366,33 @@ class PatchScriptBuilder {
void resolve(final Side other, final ObjectId within) throws IOException {
try {
final boolean reuse;
if (Patch.COMMIT_MSG.equals(path)) {
if (againstParent && (aId == within || within.equals(aId))) {
id = ObjectId.zeroId();
src = Text.EMPTY;
srcContent = Text.NO_BYTES;
mode = FileMode.MISSING;
displayMethod = DisplayMethod.NONE;
} else {
id = within;
src = Text.forCommit(db, within);
srcContent = src.getContent();
if (src == Text.EMPTY) {
mode = FileMode.MISSING;
displayMethod = DisplayMethod.NONE;
} else {
mode = FileMode.REGULAR_FILE;
}
}
reuse = false;
} else {
final TreeWalk tw = find(within);
id = tw != null ? tw.getObjectId(0) : ObjectId.zeroId();
mode = tw != null ? tw.getFileMode(0) : FileMode.MISSING;
final boolean reuse =
other != null && other.id.equals(id) && other.mode == mode;
reuse = other != null && other.id.equals(id) && other.mode == mode;
if (reuse) {
srcContent = other.srcContent;
@@ -394,6 +416,7 @@ class PatchScriptBuilder {
displayMethod = DisplayMethod.IMG;
}
}
}
if (mode == FileMode.MISSING) {
displayMethod = DisplayMethod.NONE;

View File

@@ -177,7 +177,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
b.setRepository(git);
b.setChange(change);
b.setDiffPrefs(dp);
b.setTrees(list.getOldId(), list.getNewId());
b.setTrees(list.isAgainstParent(), list.getOldId(), list.getNewId());
return b;
}

View File

@@ -175,14 +175,14 @@ class AddRefRight extends Handler<ProjectDetail> {
}
}
if (exclusive) {
refPattern = "-" + refPattern;
}
if (!projectControl.controlForRef(refPattern).isOwner()) {
throw new NoSuchRefException(refPattern);
}
if (exclusive) {
refPattern = "-" + refPattern;
}
final AccountGroup group = groupCache.get(groupName);
if (group == null) {
throw new NoSuchGroupException(groupName);

View File

@@ -373,7 +373,7 @@ case "$ACTION" in
printf '%s' "Starting Gerrit Code Review: "
if test 1 = "$NO_START" ; then
echo "Not starting gerrit - NO_START=1 in /etc/default/gerrit"
echo "Not starting gerrit - NO_START=1 in /etc/default/gerritcodereview"
exit 0
fi

View File

@@ -19,6 +19,9 @@ import com.google.gwtorm.client.StringKey;
/** A single modified file in a {@link PatchSet}. */
public final class Patch {
/** Magical file name which represents the commit message. */
public static final String COMMIT_MSG = "/COMMIT_MSG";
public static class Key extends StringKey<PatchSet.Id> {
private static final long serialVersionUID = 1L;
@@ -191,6 +194,12 @@ public final class Patch {
/** Number of drafts by the current user; not persisted in the datastore. */
protected int nbrDrafts;
/** Number of lines added to the file. */
protected int insertions;
/** Number of lines deleted from the file. */
protected int deletions;
/**
* Original if {@link #changeType} is {@link ChangeType#COPIED} or
* {@link ChangeType#RENAMED}.
@@ -229,6 +238,22 @@ public final class Patch {
nbrDrafts = n;
}
public int getInsertions() {
return insertions;
}
public void setInsertions(int n) {
insertions = n;
}
public int getDeletions() {
return deletions;
}
public void setDeletions(int n) {
deletions = n;
}
public ChangeType getChangeType() {
return ChangeType.forCode(changeType);
}

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
@@ -41,6 +42,8 @@ import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
@@ -168,14 +171,23 @@ public class ChangeHookRunner {
* @param change Change to get repo for,
* @return Repository or null.
*/
private Repository getRepo(final Change change) {
private Repository openRepository(final Change change) {
Project.NameKey name = change.getProject();
try {
return repoManager.openRepository(change.getProject().get());
} catch (Exception ex) {
return repoManager.openRepository(name.get());
} catch (RepositoryNotFoundException err) {
log.warn("Cannot open repository " + name.get(), err);
return null;
}
}
private void addArg(List<String> args, String name, String value) {
if (value != null) {
args.add(name);
args.add(value);
}
}
/**
* Fire the Patchset Created Hook.
*
@@ -192,24 +204,15 @@ public class ChangeHookRunner {
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
args.add(patchsetCreatedHook.getAbsolutePath());
addArg(args, "--change", event.change.id);
addArg(args, "--change-url", event.change.url);
addArg(args, "--project", event.change.project);
addArg(args, "--branch", event.change.branch);
addArg(args, "--uploader", getDisplayName(uploader.getAccount()));
addArg(args, "--commit", event.patchSet.revision);
addArg(args, "--patchset", event.patchSet.number);
args.add("--change");
args.add(event.change.id);
args.add("--change-url");
args.add(event.change.url);
args.add("--project");
args.add(event.change.project);
args.add("--branch");
args.add(event.change.branch);
args.add("--uploader");
args.add(getDisplayName(uploader.getAccount()));
args.add("--commit");
args.add(event.patchSet.revision);
args.add("--patchset");
args.add(event.patchSet.number);
runHook(getRepo(change), args);
runHook(openRepository(change), patchsetCreatedHook, args);
}
/**
@@ -240,28 +243,18 @@ public class ChangeHookRunner {
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
args.add(commentAddedHook.getAbsolutePath());
args.add("--change");
args.add(event.change.id);
args.add("--change-url");
args.add(event.change.url);
args.add("--project");
args.add(event.change.project);
args.add("--branch");
args.add(event.change.branch);
args.add("--author");
args.add(getDisplayName(account));
args.add("--commit");
args.add(event.patchSet.revision);
args.add("--comment");
args.add(comment == null ? "" : comment);
addArg(args, "--change", event.change.id);
addArg(args, "--change-url", event.change.url);
addArg(args, "--project", event.change.project);
addArg(args, "--branch", event.change.branch);
addArg(args, "--author", getDisplayName(account));
addArg(args, "--commit", event.patchSet.revision);
addArg(args, "--comment", comment == null ? "" : comment);
for (Map.Entry<ApprovalCategory.Id, ApprovalCategoryValue.Id> approval : approvals.entrySet()) {
args.add("--" + approval.getKey().get());
args.add(Short.toString(approval.getValue().get()));
addArg(args, "--" + approval.getKey().get(), Short.toString(approval.getValue().get()));
}
runHook(getRepo(change), args);
runHook(openRepository(change), commentAddedHook, args);
}
/**
@@ -280,22 +273,14 @@ public class ChangeHookRunner {
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
args.add(changeMergedHook.getAbsolutePath());
addArg(args, "--change", event.change.id);
addArg(args, "--change-url", event.change.url);
addArg(args, "--project", event.change.project);
addArg(args, "--branch", event.change.branch);
addArg(args, "--submitter", getDisplayName(account));
addArg(args, "--commit", event.patchSet.revision);
args.add("--change");
args.add(event.change.id);
args.add("--change-url");
args.add(event.change.url);
args.add("--project");
args.add(event.change.project);
args.add("--branch");
args.add(event.change.branch);
args.add("--submitter");
args.add(getDisplayName(account));
args.add("--commit");
args.add(event.patchSet.revision);
runHook(getRepo(change), args);
runHook(openRepository(change), changeMergedHook, args);
}
/**
@@ -314,22 +299,14 @@ public class ChangeHookRunner {
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
args.add(changeAbandonedHook.getAbsolutePath());
addArg(args, "--change", event.change.id);
addArg(args, "--change-url", event.change.url);
addArg(args, "--project", event.change.project);
addArg(args, "--branch", event.change.branch);
addArg(args, "--abandoner", getDisplayName(account));
addArg(args, "--reason", reason == null ? "" : reason);
args.add("--change");
args.add(event.change.id);
args.add("--change-url");
args.add(event.change.url);
args.add("--project");
args.add(event.change.project);
args.add("--branch");
args.add(event.change.branch);
args.add("--abandoner");
args.add(getDisplayName(account));
args.add("--reason");
args.add(reason == null ? "" : reason);
runHook(getRepo(change), args);
runHook(openRepository(change), changeAbandonedHook, args);
}
/**
@@ -348,22 +325,14 @@ public class ChangeHookRunner {
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
args.add(changeRestoredHook.getAbsolutePath());
addArg(args, "--change", event.change.id);
addArg(args, "--change-url", event.change.url);
addArg(args, "--project", event.change.project);
addArg(args, "--branch", event.change.branch);
addArg(args, "--restorer", getDisplayName(account));
addArg(args, "--reason", reason == null ? "" : reason);
args.add("--change");
args.add(event.change.id);
args.add("--change-url");
args.add(event.change.url);
args.add("--project");
args.add(event.change.project);
args.add("--branch");
args.add(event.change.branch);
args.add("--restorer");
args.add(getDisplayName(account));
args.add("--reason");
args.add(reason == null ? "" : reason);
runHook(getRepo(change), args);
runHook(openRepository(change), changeRestoredHook, args);
}
private void fireEvent(final Change change, final ChangeEvent event) {
@@ -419,50 +388,73 @@ public class ChangeHookRunner {
/**
* Run a hook.
*
* @param repo Repo to run the hook for.
* @param repo repository to run the hook for.
* @param hook the hook to execute.
* @param args Arguments to use to run the hook.
*/
private synchronized void runHook(final Repository repo, final List<String> args) {
if (repo == null) {
log.error("No repo found for hook.");
return;
private synchronized void runHook(Repository repo, File hook,
List<String> args) {
if (repo != null) {
if (hook.exists()) {
hookQueue.execute(new HookTask(repo, hook, args));
} else {
repo.close();
}
}
}
private final class HookTask implements Runnable {
private final Repository repo;
private final File hook;
private final List<String> args;
private HookTask(Repository repo, File hook, List<String> args) {
this.repo = repo;
this.hook = hook;
this.args = args;
}
hookQueue.execute(new Runnable() {
@Override
public void run() {
try {
if (new File(args.get(0)).exists()) {
final ProcessBuilder pb = new ProcessBuilder(args);
final List<String> argv = new ArrayList<String>(1 + args.size());
argv.add(hook.getAbsolutePath());
argv.addAll(args);
final ProcessBuilder pb = new ProcessBuilder(argv);
pb.redirectErrorStream(true);
pb.directory(repo.getDirectory());
final Map<String, String> env = pb.environment();
env.put("GIT_DIR", repo.getDirectory().getAbsolutePath());
Process ps = pb.start();
ps.getOutputStream().close();
BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
BufferedReader br =
new BufferedReader(new InputStreamReader(ps.getInputStream()));
try {
String line;
while ((line = br.readLine()) != null) {
log.info("hook output: " + line);
log.info("hook[" + hook.getName() + "] output: " + line);
}
} finally {
try {
br.close();
} catch (IOException e2) {
} catch (IOException closeErr) {
}
ps.waitFor();
}
}
} catch (Throwable e) {
log.error("Unexpected error during hook execution", e);
} catch (Throwable err) {
log.error("Error running hook " + hook.getAbsolutePath(), err);
} finally {
repo.close();
}
}
});
@Override
public String toString() {
return "hook " + hook.getName();
}
}
}

View File

@@ -847,7 +847,7 @@ public class MergeOp {
}
private void updateBranch() throws MergeException {
if (branchTip == null || branchTip != mergeTip) {
if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
branchUpdate.setForceUpdate(false);
branchUpdate.setNewObjectId(mergeTip);
branchUpdate.setRefLogMessage("merged", true);

View File

@@ -620,12 +620,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
if (split < destBranchName.length()) {
destTopicName = destBranchName.substring(split + 1);
if (destTopicName.isEmpty()) {
destTopicName = null;
}
} else {
// We use empty string here to denote the topic wasn't
// supplied, but the caller used the syntax that allows
// for a topic to be given.
//
destTopicName = "";
destTopicName = null;
}
destBranch = new Branch.NameKey(project.getNameKey(), //
destBranchName.substring(0, split));
@@ -876,7 +875,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
final Change change =
new Change(changeKey, new Change.Id(db.nextChangeId()), me, destBranch);
change.setTopic(destTopicName.isEmpty() ? null : destTopicName);
change.setTopic(destTopicName);
change.nextPatchSetId();
final PatchSet ps = new PatchSet(change.currPatchSetId());
@@ -1177,9 +1176,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
public Change update(Change change) {
if (change.getStatus().isOpen()) {
if (destTopicName != null) {
change.setTopic(destTopicName.isEmpty() //
? null //
: destTopicName);
change.setTopic(destTopicName);
}
change.setStatus(Change.Status.NEW);
change.setCurrentPatchSet(result.info);

View File

@@ -19,6 +19,7 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
@@ -34,6 +35,7 @@ import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
import com.google.gwtorm.client.OrmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -222,9 +224,20 @@ public abstract class ChangeEmail extends OutgoingEmail {
if (patchSet != null) {
detail.append("---\n");
for (PatchListEntry p : getPatchList().getPatches()) {
PatchList patchList = getPatchList();
for (PatchListEntry p : patchList.getPatches()) {
if (Patch.COMMIT_MSG.equals(p.getNewName())) {
continue;
}
detail.append(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
}
detail.append(MessageFormat.format("" //
+ "{0,choice,0#0 files|1#1 file|1<{0} files} changed, " //
+ "{1,choice,0#0 insertions|1#1 insertion|1<{1} insertions}(+), " //
+ "{2,choice,0#0 deletions|1#1 deletion|1<{2} deletions}(-)" //
+ "\n", patchList.getPatches().size() - 1, //
patchList.getInsertions(), //
patchList.getDeletions()));
detail.append("\n");
}
return detail.toString();

View File

@@ -26,6 +26,7 @@ import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -50,9 +51,13 @@ public class CommentSender extends ReplyToChangeSender {
Set<String> paths = new HashSet<String>();
for (PatchLineComment c : plc) {
Patch.Key p = c.getKey().getParentKey();
if (!Patch.COMMIT_MSG.equals(p.getFileName())) {
paths.add(p.getFileName());
}
changeData.setCurrentFilePaths(paths);
}
String[] names = paths.toArray(new String[paths.size()]);
Arrays.sort(names);
changeData.setCurrentFilePaths(names);
}
@Override
@@ -65,7 +70,7 @@ public class CommentSender extends ReplyToChangeSender {
}
@Override
protected void formatChange() throws EmailException {
public void formatChange() throws EmailException {
appendText(velocifyFile("Comment.vm"));
}
@@ -85,9 +90,13 @@ public class CommentSender extends ReplyToChangeSender {
if (!pk.equals(currentFileKey)) {
cmts.append("....................................................\n");
if (Patch.COMMIT_MSG.equals(pk.get())) {
cmts.append("Commit Message\n");
} else {
cmts.append("File ");
cmts.append(pk.get());
cmts.append("\n");
}
currentFileKey = pk;
if (patchList != null) {

View File

@@ -20,10 +20,9 @@ import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.mail.EmailHeader.AddressList;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.VelocityContext;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +39,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** Sends an email to one or more interested parties. */
public abstract class OutgoingEmail {
private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);

View File

@@ -50,6 +50,19 @@ public class PatchFile {
final RevWalk rw = new RevWalk(repo);
final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
if (Patch.COMMIT_MSG.equals(fileName)) {
if (patchList.isAgainstParent()) {
a = Text.EMPTY;
} else {
a = Text.forCommit(repo, patchList.getOldId());
}
b = Text.forCommit(repo, bCommit);
aTree = null;
bTree = null;
} else {
if (patchList.getOldId() != null) {
aTree = rw.parseTree(patchList.getOldId());
} else {
@@ -59,6 +72,7 @@ public class PatchFile {
}
bTree = bCommit.getTree();
}
}
/**
* Extract a line from the file, as a string.

View File

@@ -60,15 +60,28 @@ public class PatchList implements Serializable {
private transient ObjectId oldId;
private transient ObjectId newId;
private transient boolean intralineDifference;
private transient boolean againstParent;
private transient int insertions;
private transient int deletions;
private transient PatchListEntry[] patches;
PatchList(@Nullable final AnyObjectId oldId, final AnyObjectId newId,
final boolean intralineDifference, final PatchListEntry[] patches) {
final boolean intralineDifference, final boolean againstParent,
final PatchListEntry[] patches) {
this.oldId = oldId != null ? oldId.copy() : null;
this.newId = newId.copy();
this.intralineDifference = intralineDifference;
this.againstParent = againstParent;
// We assume index 0 contains the magic commit message entry.
if (patches.length > 1) {
Arrays.sort(patches, 1, patches.length, PATCH_CMP);
}
for (int i = 1; i < patches.length; i++) {
insertions += patches[i].getInsertions();
deletions += patches[i].getDeletions();
}
Arrays.sort(patches, PATCH_CMP);
this.patches = patches;
}
@@ -93,6 +106,21 @@ public class PatchList implements Serializable {
return intralineDifference;
}
/** @return true if {@link #getOldId} is {@link #getNewId}'s ancestor. */
public boolean isAgainstParent() {
return againstParent;
}
/** @return total number of new lines added. */
public int getInsertions() {
return insertions;
}
/** @return total number of lines removed. */
public int getDeletions() {
return deletions;
}
/**
* Get a sorted, modifiable list of all files in this list.
* <p>
@@ -144,6 +172,9 @@ public class PatchList implements Serializable {
writeCanBeNull(out, oldId);
writeNotNull(out, newId);
writeVarInt32(out, intralineDifference ? 1 : 0);
writeVarInt32(out, againstParent ? 1 : 0);
writeVarInt32(out, insertions);
writeVarInt32(out, deletions);
writeVarInt32(out, patches.length);
for (PatchListEntry p : patches) {
p.writeTo(out);
@@ -161,6 +192,9 @@ public class PatchList implements Serializable {
oldId = readCanBeNull(in);
newId = readNotNull(in);
intralineDifference = readVarInt32(in) != 0;
againstParent = readVarInt32(in) != 0;
insertions = readVarInt32(in);
deletions = readVarInt32(in);
final int cnt = readVarInt32(in);
final PatchListEntry[] all = new PatchListEntry[cnt];
for (int i = 0; i < all.length; i++) {

View File

@@ -62,6 +62,7 @@ package com.google.gerrit.server.patch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
@@ -80,6 +81,7 @@ import com.google.inject.name.Named;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.MyersDiff;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextIgnoreAllWhitespace;
@@ -88,7 +90,6 @@ import org.eclipse.jgit.diff.RawTextIgnoreWhitespaceChange;
import org.eclipse.jgit.diff.RenameDetector;
import org.eclipse.jgit.diff.ReplaceEdit;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
@@ -99,6 +100,7 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
@@ -186,15 +188,50 @@ public class PatchListCacheImpl implements PatchListCache {
final Repository repo) throws IOException {
// TODO(jeffschu) correctly handle merge commits
final RevWalk rw = new RevWalk(repo);
final RevCommit b = rw.parseCommit(key.getNewId());
final AnyObjectId a = aFor(key, repo, b);
if (a == null) {
return new PatchList(a, b, computeIntraline, new PatchListEntry[0]);
RawText.Factory rawTextFactory;
switch (key.getWhitespace()) {
case IGNORE_ALL_SPACE:
rawTextFactory = RawTextIgnoreAllWhitespace.FACTORY;
break;
case IGNORE_SPACE_AT_EOL:
rawTextFactory = RawTextIgnoreTrailingWhitespace.FACTORY;
break;
case IGNORE_SPACE_CHANGE:
rawTextFactory = RawTextIgnoreWhitespaceChange.FACTORY;
break;
case IGNORE_NONE:
default:
rawTextFactory = RawText.FACTORY;
break;
}
final RevWalk rw = new RevWalk(repo);
final RevCommit b = rw.parseCommit(key.getNewId());
final RevObject a = aFor(key, repo, rw, b);
if (a == null) {
// This is a merge commit, compared to its ancestor.
//
final PatchListEntry[] entries = new PatchListEntry[1];
entries[0] = newCommitMessage(rawTextFactory, repo, null, b);
return new PatchList(a, b, computeIntraline, true, entries);
}
final boolean againstParent =
b.getParentCount() > 0 && b.getParent(0) == a;
RevCommit aCommit;
RevTree aTree;
if (a instanceof RevCommit) {
aCommit = (RevCommit) a;
aTree = aCommit.getTree();
} else if (a instanceof RevTree) {
aCommit = null;
aTree = (RevTree) a;
} else {
throw new IOException("Unexpected type: " + a.getClass());
}
RevTree aTree = rw.parseTree(a);
RevTree bTree = b.getTree();
final TreeWalk walk = new TreeWalk(repo);
@@ -206,32 +243,53 @@ public class PatchListCacheImpl implements PatchListCache {
DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
df.setRepository(repo);
switch (key.getWhitespace()) {
case IGNORE_ALL_SPACE:
df.setRawTextFactory(RawTextIgnoreAllWhitespace.FACTORY);
break;
case IGNORE_NONE:
df.setRawTextFactory(RawText.FACTORY);
break;
case IGNORE_SPACE_AT_EOL:
df.setRawTextFactory(RawTextIgnoreTrailingWhitespace.FACTORY);
break;
case IGNORE_SPACE_CHANGE:
df.setRawTextFactory(RawTextIgnoreWhitespaceChange.FACTORY);
break;
}
df.setRawTextFactory(rawTextFactory);
RenameDetector rd = new RenameDetector(repo);
rd.addAll(DiffEntry.scan(walk));
List<DiffEntry> diffEntries = rd.compute();
final int cnt = diffEntries.size();
final PatchListEntry[] entries = new PatchListEntry[cnt];
final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
entries[0] = newCommitMessage(rawTextFactory, repo, //
againstParent ? null : aCommit, b);
for (int i = 0; i < cnt; i++) {
FileHeader fh = df.createFileHeader(diffEntries.get(i));
entries[i] = newEntry(repo, aTree, bTree, fh);
entries[1 + i] = newEntry(repo, aTree, bTree, fh);
}
return new PatchList(a, b, computeIntraline, entries);
return new PatchList(a, b, computeIntraline, againstParent, entries);
}
private PatchListEntry newCommitMessage(
final RawText.Factory rawTextFactory, final Repository repo,
final RevCommit aCommit, final RevCommit bCommit) throws IOException {
StringBuilder hdr = new StringBuilder();
hdr.append("diff --git");
if (aCommit != null) {
hdr.append(" a/" + Patch.COMMIT_MSG);
} else {
hdr.append(" " + FileHeader.DEV_NULL);
}
hdr.append(" b/" + Patch.COMMIT_MSG);
hdr.append("\n");
if (aCommit != null) {
hdr.append("--- a/" + Patch.COMMIT_MSG + "\n");
} else {
hdr.append("--- " + FileHeader.DEV_NULL + "\n");
}
hdr.append("+++ b/" + Patch.COMMIT_MSG + "\n");
Text aText = aCommit != null ? Text.forCommit(repo, aCommit) : Text.EMPTY;
Text bText = Text.forCommit(repo, bCommit);
byte[] rawHdr = hdr.toString().getBytes("UTF-8");
RawText aRawText = rawTextFactory.create(aText.getContent());
RawText bRawText = rawTextFactory.create(bText.getContent());
EditList edits = new MyersDiff(aRawText, bRawText).getEdits();
FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
return newEntry(repo, aText, bText, edits, null, null, fh);
}
private PatchListEntry newEntry(Repository repo, RevTree aTree,
@@ -263,9 +321,12 @@ public class PatchListCacheImpl implements PatchListCache {
return new PatchListEntry(fileHeader, edits);
}
Text aContent = null;
Text bContent = null;
return newEntry(repo, null, null, edits, aTree, bTree, fileHeader);
}
private PatchListEntry newEntry(Repository repo, Text aContent,
Text bContent, List<Edit> edits, RevTree aTree, RevTree bTree,
FileHeader fileHeader) throws IOException {
for (int i = 0; i < edits.size(); i++) {
Edit e = edits.get(i);
@@ -545,17 +606,21 @@ public class PatchListCacheImpl implements PatchListCache {
return new Text(ldr.getCachedBytes());
}
private static AnyObjectId aFor(final PatchListKey key,
final Repository repo, final RevCommit b) throws IOException {
private static RevObject aFor(final PatchListKey key,
final Repository repo, final RevWalk rw, final RevCommit b)
throws IOException {
if (key.getOldId() != null) {
return key.getOldId();
return rw.parseAny(key.getOldId());
}
switch (b.getParentCount()) {
case 0:
return emptyTree(repo);
case 1:
return b.getParent(0);
return rw.parseAny(emptyTree(repo));
case 1:{
RevCommit r = b.getParent(0);
rw.parseBody(r);
return r;
}
default:
// merge commit, return null to force combined diff behavior
return null;

View File

@@ -50,7 +50,7 @@ public class PatchListEntry {
static PatchListEntry empty(final String fileName) {
return new PatchListEntry(ChangeType.MODIFIED, PatchType.UNIFIED, null,
fileName, EMPTY_HEADER, Collections.<Edit> emptyList());
fileName, EMPTY_HEADER, Collections.<Edit> emptyList(), 0, 0);
}
private final ChangeType changeType;
@@ -59,6 +59,8 @@ public class PatchListEntry {
private final String newName;
private final byte[] header;
private final List<Edit> edits;
private final int insertions;
private final int deletions;
PatchListEntry(final FileHeader hdr, List<Edit> editList) {
changeType = toChangeType(hdr);
@@ -96,17 +98,29 @@ public class PatchListEntry {
} else {
edits = Collections.unmodifiableList(editList);
}
int ins = 0;
int del = 0;
for (Edit e : editList) {
del += e.getEndA() - e.getBeginA();
ins += e.getEndB() - e.getBeginB();
}
insertions = ins;
deletions = del;
}
private PatchListEntry(final ChangeType changeType,
final PatchType patchType, final String oldName, final String newName,
final byte[] header, final List<Edit> edits) {
final byte[] header, final List<Edit> edits, final int insertions,
final int deletions) {
this.changeType = changeType;
this.patchType = patchType;
this.oldName = oldName;
this.newName = newName;
this.header = header;
this.edits = edits;
this.insertions = insertions;
this.deletions = deletions;
}
public ChangeType getChangeType() {
@@ -129,6 +143,14 @@ public class PatchListEntry {
return edits;
}
public int getInsertions() {
return insertions;
}
public int getDeletions() {
return deletions;
}
public List<String> getHeaderLines() {
final IntList m = RawParseUtils.lineMap(header, 0, header.length);
final List<String> headerLines = new ArrayList<String>(m.size() - 1);
@@ -145,6 +167,8 @@ public class PatchListEntry {
p.setChangeType(getChangeType());
p.setPatchType(getPatchType());
p.setSourceFileName(getOldName());
p.setInsertions(insertions);
p.setDeletions(deletions);
return p;
}
@@ -154,6 +178,8 @@ public class PatchListEntry {
writeString(out, oldName);
writeString(out, newName);
writeBytes(out, header);
writeVarInt32(out, insertions);
writeVarInt32(out, deletions);
writeVarInt32(out, edits.size());
for (final Edit e : edits) {
@@ -184,6 +210,8 @@ public class PatchListEntry {
final String oldName = readString(in);
final String newName = readString(in);
final byte[] hdr = readBytes(in);
final int ins = readVarInt32(in);
final int del = readVarInt32(in);
final int editCount = readVarInt32(in);
final Edit[] editArray = new Edit[editCount];
@@ -201,7 +229,7 @@ public class PatchListEntry {
}
return new PatchListEntry(changeType, patchType, oldName, newName, hdr,
toList(editArray));
toList(editArray), ins, del);
}
private static List<Edit> toList(Edit[] l) {

View File

@@ -35,7 +35,7 @@ import java.io.Serializable;
import javax.annotation.Nullable;
public class PatchListKey implements Serializable {
static final long serialVersionUID = 13L;
static final long serialVersionUID = 15L;
private transient ObjectId oldId;
private transient ObjectId newId;

View File

@@ -17,7 +17,12 @@ package com.google.gerrit.server.patch;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.mozilla.universalchardet.UniversalDetector;
@@ -29,6 +34,7 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.text.SimpleDateFormat;
public class Text extends RawText {
private static final Logger log = LoggerFactory.getLogger(Text.class);
@@ -38,6 +44,75 @@ public class Text extends RawText {
public static final byte[] NO_BYTES = {};
public static final Text EMPTY = new Text(NO_BYTES);
public static Text forCommit(Repository db, AnyObjectId commitId)
throws IOException {
RevWalk rw = new RevWalk(db);
try {
RevCommit c;
if (commitId instanceof RevCommit) {
c = (RevCommit) commitId;
} else {
c = rw.parseCommit(commitId);
}
StringBuilder b = new StringBuilder();
switch (c.getParentCount()) {
case 0:
break;
case 1: {
RevCommit p = c.getParent(0);
rw.parseBody(p);
b.append("Parent: ");
b.append(p.abbreviate(db, 8).name());
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
break;
}
default:
for (int i = 0; i < c.getParentCount(); i++) {
RevCommit p = c.getParent(i);
rw.parseBody(p);
b.append(i == 0 ? "Merge Of: " : " ");
b.append(p.abbreviate(db, 8).name());
b.append(" (");
b.append(p.getShortMessage());
b.append(")\n");
}
}
appendPersonIdent(b, "Author", c.getAuthorIdent());
appendPersonIdent(b, "Commit", c.getCommitterIdent());
b.append("\n");
b.append(c.getFullMessage());
return new Text(b.toString().getBytes("UTF-8"));
} finally {
rw.release();
}
}
private static void appendPersonIdent(StringBuilder b, String field,
PersonIdent person) {
if (person != null) {
b.append(field + ": ");
if (person.getName() != null) {
b.append(" ");
b.append(person.getName());
}
if (person.getEmailAddress() != null) {
b.append(" <");
b.append(person.getEmailAddress());
b.append(">");
}
b.append("\n");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZ");
sdf.setTimeZone(person.getTimeZone());
b.append(field + "Date: ");
b.append(sdf.format(person.getWhen()));
b.append("\n");
}
}
public static String asString(byte[] content, String encoding) {
return new String(content, charset(content, encoding));
}

View File

@@ -177,16 +177,17 @@ public class ProjectControl {
short requireValue) {
final Set<AccountGroup.Id> groups = user.getEffectiveGroups();
int val = Integer.MIN_VALUE;
boolean local = false;
for (final RefRight pr : state.getLocalRights(actionId)) {
if (groups.contains(pr.getAccountGroupId())) {
val = Math.max(pr.getMaxValue(), val);
local = true;
}
}
if (val >= requireValue) {
return true;
}
if (!local && actionId.canInheritFromWildProject()) {
if (actionId.canInheritFromWildProject()) {
for (final RefRight pr : state.getInheritedRights(actionId)) {
if (groups.contains(pr.getAccountGroupId())) {
val = Math.max(pr.getMaxValue(), val);

View File

@@ -97,6 +97,10 @@ public class ProjectState {
return inheritedRights;
}
void setInheritedRights(Collection<RefRight> all) {
inheritedRights = all;
}
private Collection<RefRight> computeInheritedRights() {
if (isSpecialWildProject()) {
return Collections.emptyList();

View File

@@ -576,6 +576,6 @@ public class RefControl {
if (isRE(refPattern)) {
refPattern = refPattern.substring(1);
}
return new RegExp(refPattern);
return new RegExp(refPattern, RegExp.NONE);
}
}

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
@@ -28,6 +29,7 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -38,7 +40,7 @@ public class ChangeData {
private Collection<PatchSet> patches;
private Collection<PatchSetApproval> approvals;
private Collection<PatchSetApproval> currentApprovals;
private Collection<String> currentFiles;
private String[] currentFiles;
private Collection<PatchLineComment> comments;
private Collection<TrackingId> trackingIds;
private CurrentUser visibleTo;
@@ -52,11 +54,11 @@ public class ChangeData {
change = c;
}
public void setCurrentFilePaths(Collection<String> filePaths) {
public void setCurrentFilePaths(String[] filePaths) {
currentFiles = filePaths;
}
public Collection<String> currentFilePaths(Provider<ReviewDb> db,
public String[] currentFilePaths(Provider<ReviewDb> db,
PatchListCache cache) throws OrmException {
if (currentFiles == null) {
Change c = change(db);
@@ -71,22 +73,24 @@ public class ChangeData {
PatchList p = cache.get(c, ps);
List<String> r = new ArrayList<String>(p.getPatches().size());
for (PatchListEntry e : p.getPatches()) {
if (Patch.COMMIT_MSG.equals(e.getNewName())) {
continue;
}
switch (e.getChangeType()) {
case ADDED:
case MODIFIED:
case DELETED:
case COPIED:
r.add(e.getNewName());
break;
case DELETED:
r.add(e.getOldName());
break;
case RENAMED:
r.add(e.getOldName());
r.add(e.getNewName());
break;
}
}
currentFiles = r;
currentFiles = r.toArray(new String[r.size()]);
Arrays.sort(currentFiles);
}
return currentFiles;
}

View File

@@ -1,4 +1,16 @@
// Copyright 2010 Google Inc. All Rights Reserved.
// Copyright (C) 2010 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.query.change;
@@ -8,35 +20,75 @@ import com.google.gerrit.server.query.OperatorPredicate;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import dk.brics.automaton.Automaton;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
import java.util.Arrays;
class RegexFilePredicate extends OperatorPredicate<ChangeData> {
private final Provider<ReviewDb> db;
private final PatchListCache cache;
private final Pattern pattern;
private final RunAutomaton pattern;
private final String prefixBegin;
private final String prefixEnd;
private final int prefixLen;
private final boolean prefixOnly;
RegexFilePredicate(Provider<ReviewDb> db, PatchListCache plc, String re) {
super(ChangeQueryBuilder.FIELD_FILE, re);
this.db = db;
this.cache = plc;
try {
this.pattern = Pattern.compile(re);
} catch (PatternSyntaxException e) {
throw new IllegalArgumentException(e.getMessage());
if (re.startsWith("^")) {
re = re.substring(1);
}
if (re.endsWith("$") && !re.endsWith("\\$")) {
re = re.substring(0, re.length() - 1);
}
Automaton automaton = new RegExp(re).toAutomaton();
prefixBegin = automaton.getCommonPrefix();
prefixLen = prefixBegin.length();
if (0 < prefixLen) {
char max = (char) (prefixBegin.charAt(prefixLen - 1) + 1);
prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
prefixOnly = re.equals(prefixBegin + ".*");
} else {
prefixEnd = "";
prefixOnly = false;
}
pattern = prefixOnly ? null : new RunAutomaton(automaton);
}
@Override
public boolean match(ChangeData object) throws OrmException {
Collection<String> files = object.currentFilePaths(db, cache);
String[] files = object.currentFilePaths(db, cache);
if (files != null) {
for (String path : files) {
if (pattern.matcher(path).find()) {
int begin, end;
if (0 < prefixLen) {
begin = find(files, prefixBegin);
end = find(files, prefixEnd);
} else {
begin = 0;
end = files.length;
}
if (prefixOnly) {
return begin < end;
}
while (begin < end) {
if (pattern.run(files[begin++])) {
return true;
}
}
return false;
} else {
@@ -48,6 +100,11 @@ class RegexFilePredicate extends OperatorPredicate<ChangeData> {
}
}
private static int find(String[] files, String p) {
int r = Arrays.binarySearch(files, p);
return r < 0 ? -(r + 1) : r;
}
@Override
public int getCost() {
return 1;

View File

@@ -1,4 +1,16 @@
// Copyright 2010 Google Inc. All Rights Reserved.
// Copyright (C) 2010 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.query.change;

View File

@@ -0,0 +1,258 @@
// Copyright (C) 2010 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.project;
import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.reviewdb.RefRight.RefPattern;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import junit.framework.TestCase;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.lib.Config;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class RefControlTest extends TestCase {
public void testOwnerProject() {
local.add(grant(OWN, admin, "refs/*", 1));
ProjectControl uBlah = user(devs);
ProjectControl uAdmin = user(devs, admin);
assertFalse("not owner", uBlah.isOwner());
assertTrue("is owner", uAdmin.isOwner());
}
public void testBranchDelegation1() {
local.add(grant(OWN, admin, "refs/*", 1));
local.add(grant(OWN, devs, "refs/heads/x/*", 1));
ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner());
assertTrue("owns ref", uDev.isOwnerAnyRef());
assertOwner("refs/heads/x/*", uDev);
assertOwner("refs/heads/x/y", uDev);
assertOwner("refs/heads/x/y/*", uDev);
assertNotOwner("refs/*", uDev);
assertNotOwner("refs/heads/master", uDev);
}
public void testBranchDelegation2() {
local.add(grant(OWN, admin, "refs/*", 1));
local.add(grant(OWN, devs, "refs/heads/x/*", 1));
local.add(grant(OWN, fixers, "-refs/heads/x/y/*", 1));
ProjectControl uDev = user(devs);
assertFalse("not owner", uDev.isOwner());
assertTrue("owns ref", uDev.isOwnerAnyRef());
assertOwner("refs/heads/x/*", uDev);
assertOwner("refs/heads/x/y", uDev);
assertNotOwner("refs/*", uDev);
assertNotOwner("refs/heads/master", uDev);
assertNotOwner("refs/heads/x/y/*", uDev);
ProjectControl uFix = user(fixers);
assertFalse("not owner", uFix.isOwner());
assertTrue("owns ref", uFix.isOwnerAnyRef());
assertOwner("refs/heads/x/y/*", uFix);
assertOwner("refs/heads/x/y/bar", uFix);
assertNotOwner("refs/heads/x/*", uFix);
assertNotOwner("refs/heads/x/y", uFix);
assertNotOwner("refs/*", uFix);
assertNotOwner("refs/heads/master", uFix);
}
public void testInheritRead_SingleBranchDeniesUpload() {
inherited.add(grant(READ, registered, "refs/*", 1, 2));
local.add(grant(READ, registered, "-refs/heads/foobar", 1, 1));
ProjectControl u = user();
assertTrue("can upload", u.canUploadToAtLeastOneRef());
assertTrue("can upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
assertFalse("deny refs/heads/foobar", //
u.controlForRef("refs/heads/foobar").canUpload());
}
public void testInheritRead_SingleBranchDoesNotOverrideInherited() {
inherited.add(grant(READ, registered, "refs/*", 1, 2));
local.add(grant(READ, registered, "refs/heads/foobar", 1, 1));
ProjectControl u = user();
assertTrue("can upload", u.canUploadToAtLeastOneRef());
assertTrue("can upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
assertTrue("can upload refs/heads/foobar", //
u.controlForRef("refs/heads/foobar").canUpload());
}
public void testCannotUploadToAnyRef() {
inherited.add(grant(READ, registered, "refs/*", 1, 1));
local.add(grant(READ, devs, "refs/heads/*",1,2));
ProjectControl u = user();
assertFalse("cannot upload", u.canUploadToAtLeastOneRef());
assertFalse("cannot upload refs/heads/master", //
u.controlForRef("refs/heads/master").canUpload());
}
// -----------------------------------------------------------------------
private final Project.NameKey projectNameKey = new Project.NameKey("test");
private final AccountGroup.Id admin = new AccountGroup.Id(1);
private final AccountGroup.Id anonymous = new AccountGroup.Id(2);
private final AccountGroup.Id registered = new AccountGroup.Id(3);
private final AccountGroup.Id devs = new AccountGroup.Id(4);
private final AccountGroup.Id fixers = new AccountGroup.Id(5);
private final AuthConfig authConfig;
private final AnonymousUser anonymousUser;
public RefControlTest() {
final SystemConfig systemConfig = SystemConfig.create();
systemConfig.adminGroupId = admin;
systemConfig.anonymousGroupId = anonymous;
systemConfig.registeredGroupId = registered;
systemConfig.batchUsersGroupId = anonymous;
try {
byte[] bin = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
systemConfig.registerEmailPrivateKey = Base64.encodeBase64String(bin);
} catch (UnsupportedEncodingException err) {
throw new RuntimeException("Cannot encode key", err);
}
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(Config.class) //
.annotatedWith(GerritServerConfig.class) //
.toInstance(new Config());
bind(SystemConfig.class).toInstance(systemConfig);
bind(AuthConfig.class);
bind(AnonymousUser.class);
}
});
authConfig = injector.getInstance(AuthConfig.class);
anonymousUser = injector.getInstance(AnonymousUser.class);
}
private List<RefRight> local;
private List<RefRight> inherited;
@Override
protected void setUp() throws Exception {
super.setUp();
local = new ArrayList<RefRight>();
inherited = new ArrayList<RefRight>();
}
private static void assertOwner(String ref, ProjectControl u) {
assertTrue("OWN " + ref, u.controlForRef(ref).isOwner());
}
private static void assertNotOwner(String ref, ProjectControl u) {
assertFalse("NOT OWN " + ref, u.controlForRef(ref).isOwner());
}
private RefRight grant(ApprovalCategory.Id categoryId, AccountGroup.Id group,
String ref, int maxValue) {
return grant(categoryId, group, ref, maxValue, maxValue);
}
private RefRight grant(ApprovalCategory.Id categoryId, AccountGroup.Id group,
String ref, int minValue, int maxValue) {
RefRight right =
new RefRight(new RefRight.Key(projectNameKey, new RefPattern(ref),
categoryId, group));
right.setMinValue((short) minValue);
right.setMaxValue((short) maxValue);
return right;
}
private ProjectControl user(AccountGroup.Id... memberOf) {
return new ProjectControl(new MockUser(memberOf), newProjectState());
}
private ProjectState newProjectState() {
ProjectCache projectCache = null;
Project.NameKey wildProject = null;
ProjectState ps =
new ProjectState(anonymousUser, projectCache, wildProject, new Project(
projectNameKey), local);
ps.setInheritedRights(inherited);
return ps;
}
private class MockUser extends CurrentUser {
private final Set<AccountGroup.Id> groups;
MockUser(AccountGroup.Id[] groupId) {
super(AccessPath.UNKNOWN, RefControlTest.this.authConfig);
groups = new HashSet<AccountGroup.Id>(Arrays.asList(groupId));
groups.add(registered);
groups.add(anonymous);
}
@Override
public Set<AccountGroup.Id> getEffectiveGroups() {
return groups;
}
@Override
public Set<Change.Id> getStarredChanges() {
return Collections.emptySet();
}
@Override
public Collection<AccountProjectWatch> getNotificationFilters() {
return Collections.emptySet();
}
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (C) 2010 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.query.change;
import com.google.gerrit.reviewdb.Change;
import com.google.gwtorm.client.OrmException;
import junit.framework.TestCase;
import java.util.Arrays;
public class RegexFilePredicateTest extends TestCase {
public void testPrefixOnlyOptimization() throws OrmException {
RegexFilePredicate p = predicate("^a/b/.*");
assertTrue(p.match(change("a/b/source.c")));
assertFalse(p.match(change("source.c")));
assertTrue(p.match(change("a/b/source.c", "a/c/test")));
assertFalse(p.match(change("a/bb/source.c")));
}
public void testPrefixReducesSearchSpace() throws OrmException {
RegexFilePredicate p = predicate("^a/b/.*\\.[ch]");
assertTrue(p.match(change("a/b/source.c")));
assertFalse(p.match(change("a/b/source.res")));
assertFalse(p.match(change("source.res")));
assertTrue(p.match(change("a/b/a.a", "a/b/a.d", "a/b/a.h")));
}
public void testFileExtension_Constant() throws OrmException {
RegexFilePredicate p = predicate("^.*\\.res");
assertTrue(p.match(change("test.res")));
assertTrue(p.match(change("foo/bar/test.res")));
assertFalse(p.match(change("test.res.bar")));
}
public void testFileExtension_CharacterGroup() throws OrmException {
RegexFilePredicate p = predicate("^.*\\.[ch]");
assertTrue(p.match(change("test.c")));
assertTrue(p.match(change("test.h")));
assertFalse(p.match(change("test.C")));
}
public void testEndOfString() throws OrmException {
assertTrue(predicate("^a$").match(change("a")));
assertFalse(predicate("^a$").match(change("a$")));
assertFalse(predicate("^a\\$").match(change("a")));
assertTrue(predicate("^a\\$").match(change("a$")));
}
public void testExactMatch() throws OrmException {
RegexFilePredicate p = predicate("^foo.c");
assertTrue(p.match(change("foo.c")));
assertFalse(p.match(change("foo.cc")));
assertFalse(p.match(change("bar.c")));
}
private static RegexFilePredicate predicate(String pattern) {
return new RegexFilePredicate(null, null, pattern);
}
private static ChangeData change(String... files) {
Arrays.sort(files);
ChangeData cd = new ChangeData(new Change.Id(1));
cd.setCurrentFilePaths(files);
return cd;
}
}

View File

@@ -49,7 +49,7 @@ limitations under the License.
<jgitVersion>0.8.4.242-g09130b8</jgitVersion>
<gwtormVersion>1.1.4</gwtormVersion>
<gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.1</gwtexpuiVersion>
<gwtexpuiVersion>1.2.2</gwtexpuiVersion>
<gwtVersion>2.0.4</gwtVersion>
<slf4jVersion>1.6.1</slf4jVersion>
<guiceVersion>2.0</guiceVersion>