Merge "Display the originator of each access rule"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 864092e..2fbe58c 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -397,14 +397,43 @@
[[cache_options]]Cache Options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-cache.diff.intraline::
+cache.diff_intraline.maxIdleWorkers::
++
+Number of idle worker threads to maintain for the intraline difference
+computations. There is no upper bound on how many concurrent requests
+can occur at once, if additional threads are started to handle a peak
+load, only this many will remaining idle afterwards.
++
+Default is 1.5x number of available CPUs.
+
+cache.diff_intraline.timeout::
++
+Maximum number of milliseconds to wait for intraline difference data
+before giving up and disabling it for a particular file pair. This is
+a work around for an infinite loop bug in the intraline difference
+implementation. If computation takes longer than the timeout the
+worker thread is terminated and no intraline difference is displayed.
++
+Values should use common unit suffixes to express their setting:
++
+* ms, milliseconds
+* s, sec, second, seconds
+* m, min, minute, minutes
+* h, hr, hour, hours
+
++
+If a unit suffix is not specified, `milliseconds` is assumed.
++
+Default is 5 seconds.
+
+cache.diff_intraline.enabled::
+
Boolean to enable or disable the computation of intraline differences
-when populating a diff cache entry. Changing this setting in the
-server configuration requires flushing the "diff" cache after a
-restart, otherwise older cache entries stored on disk may not reflect
-the current server setting. This flag is provided primarily as a
-backdoor to disable the intraline difference feature if necessary.
+when populating a diff cache entry. This flag is provided primarily
+as a backdoor to disable the intraline difference feature if
+necessary. To maintain backwards compatability with prior versions,
+this setting will fallback to `cache.diff.intraline` if not set in the
+configuration.
+
Default is true, enabled.
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.txt b/ReleaseNotes/ReleaseNotes-2.1.6.txt
new file mode 100644
index 0000000..198a064
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.txt
@@ -0,0 +1,322 @@
+Release notes for Gerrit 2.1.6
+==============================
+
+Gerrit 2.1.6 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.6.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.1.6.war]
+
+Schema Change
+-------------
+
+*WARNING* This release contains multiple schema changes. To upgrade:
+----
+ java -jar gerrit.war init -d site_path
+----
+
+
+New Features
+------------
+
+Web UI
+~~~~~~
+* issue 312 Abandoned changes can now be restored.
+* issue 698 Make date and time fields customizable
+* issue 556 Preference to display patch sets in reverse order
+* issue 584 Allow deleted and/or uncommented files to be skipped
+
+* Use HistogramDiff for content differences
++
+HistogramDiff is an adaptation of Bram Cohen's Patience Difference
+algorithm, and was recently included in the upstream JGit project.
+Patience Difference tends to produce more readable differences for
+source code files, and JGit's HistogramDiff implementation tends to
+run several times faster than the prior Myers O(ND) algorithm.
+
+* Automatic merge file content during submit
++
+Project owners can now enable file-level content merge during submit,
+allowing Gerrit to automatically resolve many path conflict cases.
+This is built upon experimental merge code inherited from JGit,
+and is therefore still experimental in Gerrit.
+
+Change Query
+~~~~~~~~~~~~
+* issue 688 Match branch, topic, project, ref by regular expressions
++
+Similar to other features in Gerrit Code Review, starting any of these
+expressions with \^ will now treat the argument as a regular
+expression instead of an exact string match.
+
+* Search changes by commit messages with `message:` operator.
+
+* issue 729 query: Add a \--all-approvals option to queries
++
+The new flag includes approval information for all patch sets in the
+resulting query output.
+
+Notifications
+~~~~~~~~~~~~
+* Customize email notification templates
++
+Email notifications are now driven by the Velocity template engine,
+and may be modified by the site administrator by editing a template
+file under `'$site_path'/etc/mail`.
+
+* issue 311 Clarify email texts/subject
++
+The default email notification formatting was changed to make the
+subject lines and message bodies more consistent, and easier to
+understand.
+
+* issue 204 Add project list popup under Settings > Watched Projects
++
+The project list panel makes it easier for users to browse all
+projects they have at least READ +1 access to, and add them to their
+watched project set so notifications can be configured.
+
+* stream-event support for all ref-update events
++
+Whenever a ref is updated via either a direct push to a branch or a
+Gerrit change submission, Gerrit will now send a new "ref-updated"
+event to the event stream.
+
+User Management
+~~~~~~~~~~~~~~~
+* SSO via client SSL certificates
++
+A new auth.type of CLIENT_SSL_CERT_LDAP supports authenticating users
+using client SSL certificates. This feature requires using the
+embedded Jetty web server with SSL enabled, and an LDAP directory to
+lookup individual account information.
+
+* issue 503 Inactive acounts may be disabled.
++
+Administrators can manually update the accounts table, setting
+inactive = `Y` to mark user accounts inactive. Inactive accounts
+cannot sign-in, cannot be added as a reviewer, and cannot be added
+to a group.
+
+* Improve the no-interactive-shell error message over SSH
++
+Instead of giving a short 'no shell available' error, Gerrit Code
+Review now prints a banner letting the user know they have
+authenticated successfully, interactive shells are disabled, and how
+to clone a hosted project:
++
+----
+$ ssh -p 29418 review.example.com
+
+ **** Welcome to Gerrit Code Review ****
+
+ Hi A. U. Thor, you have successfully connected over SSH.
+
+ Unfortunately, interactive shells are disabled.
+ To clone a hosted Git repository, use:
+
+ git clone ssh://author@review.example.com:29418/REPOSITORY_NAME.git
+
+Connection to review.example.com closed.
+----
+
+* Configure SSHD maxAuthTries, loginGraceTime, maxConnectionsPerUser
++
+The internal SSH daemon now supports additional configuration
+settings to reduce the risk of abuse.
+
+Administration
+~~~~~~~~~~~~~~
+* issue 558 Allow Access rights to be edited by clicking on them.
+
+* New 'Project Owner' system group to define default rights
++
+The new system group 'Project Owners' can be used in access
+rights to mean any user that is a member of any group that
+has the 'Owner' access category granted within that project.
+This system group is primarily useful in higher level projects
+such as '\-- All Projects \--' to define standard access rights
+for all project owners.
+
+* issue 557 Allow rejection of changes without Change-Id line.
++
+Project owners can set a flag to require all commits to include
+the Gerrit specific 'Change-Id: I...' line during initial upload,
+reducing the risk of confusion when amends need to occur to
+incorporate reviewer feedback.
+
+* issue 613 create-project: Add --permissions-only option
++
+The new flag skips creating the associated Git repository, making the
+new project suitable for use as a parent to inherit permissions from.
+
+* create-project: Optionally create empty initial commit
++
+The `repo` tool used by Android doesn't like to clone an empty Git
+repository, making it difficult to setup a review for the initial file
+contents. create-project can now optionally create an empty initial
+commit, permitting repo to sync the empty project.
+
+* Block off commands on a server for certain user groups.
++
+The upload.allowGroup and receive.allowGroup settings in gerrit.config
+can be used to restrict which users can perform git clone/fetch or git
+push on this server. This can be useful if clone/fetch should be
+limited to only site administrators, while normal users are supposed
+to use to less expensive mirror servers.
+
+* issue 685 Define gerrit.replicateOnStartup to control replication
++
+The automatic replicate every project action that occurs during server
+startup can now be disabled by setting replicateOnStartup = false.
+This is primarily useful for sites with extremely large numbers of
+projects and replication targets, but runs the risk of having a target
+be out of date relative to the master server.
+
+* New non-blocking function category "NoBlock"
++
+Site defined approval categories may now use the function "NoBlock"
+to permit scoring without blocking submission. This is mostly
+useful for automated tools to provide optional feedback on a change.
+
+* Ability to reject commits from entering repository
++
+The Git-note style branch `refs/meta/reject-commits` can be created
+by the project owner or site administrator to define a list of
+commits that must not be pushed into the repository. This can be
+useful after performing a project-wide filter-branch operation to
+prevent the older (pre-filter-branch) history from being reintroduced
+into the repository.
+
+Bug Fixes
+---------
+
+Web UI
+~~~~~~
+* issue 498 Enable Keyboard navigation after change submit
+* issue 691 Make ']' on last file go up to change
+* issue 741 Make ENTER work for 'Create Group'
+* issue 622 Denote a symbolic link in side-by-side viewer
+* issue 612 Display empty branch list when project has no repository
+* issue 672 Fix deleting exclusive branch level rights
+* issue 645 Display 'No difference' between unchanged patchsets
+* Display groups as links to group information
+* Remove ctrl-d keybinding to discard comment, honor browser default
+* Do not auto enable save buttons, wait for changes to be made
+* Disable 'Create Group' button if group name not entered
+* Show commit message in PatchScreen if old patch sets are compared
+* Fixed a number of focus and shortcut bugs in Firefox, Chrome
+
+* issue 487 Work around buggy MyersDiff by killing threads
++
+MyersDiff sometimes locked up in an infinite loop when computing
+the intraline difference information for a file. These threads
+are now killed after an administrator specified timeout
+(cache.diff_intraline.timeout, default is 5 seconds). If the
+timeout is reached the file content is displayed without intraline
+differences. This offers reduced functionality to the end-user, but
+prevents the "path of death" which usually took down a Gerrit server.
+
+* Hide access rights not visible to user
++
+Users were able to view access rights for branches they didn't
+actually have READ +1 permission on. This may have leaked
+information about branches and/or groups to users that shouldn't
+know about code names contained within either string. Users that
+are not project owners may now only view access rights for branches
+they have at least READ +1 permission on.
+
+Change Query
+~~~~~~~~~~~~
+* issue 689 Fix age:4days to parse correctly
+* Make branch: operator slightly less ambiguous
+
+Push Support
+~~~~~~~~~~~~
+* issue 695 Permit changing only the author of a commit
++
+Correcting only the author of a change failed to upload the new patch
+set onto the existing change, as neither the message nor the files
+were modified. Fixed.
+
+* issue 576 Allow Push Branch +3 to force replace a tag
++
+Previously it was not possible to replace a tag object, even if
+`git push \--force` was used. Fixed.
+
+* issue 690 Refuse to run receive-pack if refs/for/branch exists
++
+If a server repository was corrupted by an administrator manually
+creating a reference within the magical refs/for/ namespace, Gerrit
+became confused when changes were uploaded for review. If this case
+occurs push now aborts very early, with a clear error message
+indicating the problem. To recover an administrator must clear the
+refs/for/ namespace manually.
+
+* Allow receive-pack without Read +2 but with Push Head +1
++
+Users who had direct branch push permission but lacked the ability to
+create changes for review were unable to push to a project. Fixed.
+This (finally) makes Gerrit a replacement for Gitosis or Gitolite.
+
+Replication
+~~~~~~~~~~~
+* issue 683 Don't assume authGroup = "Registered Users" in replication
++
+Previously a misconfigured authGroup in replication.config may have
+caused the server to assume "Registered Users" instead of the group(s)
+admin actually wanted. This may have caused the replication to see
+(or not see) the correct set of projects.
+
+* issue 482 Upon replication fail, automatically retry later
++
+If replication fails (for example due to temporary network
+connectivity problems), other pending replication events to the
+same server are deferred and retried later until successful.
+
+* Replicate all refs received from push
++
+Replication now replicates all references, not just those that
+appear under `refs/heads`, `refs/tags`, or `refs/changes`. This
+fix may be relevant if the server supports user-private sandboxes
+such as `refs/dev/'$\{username\}'/*`.
+
+* issue 658 Allow refspec shortcuts (push = master) for replication
+
+User Management
+~~~~~~~~~~~~~~~
+* Ensure proper escaping of LDAP group names
++
+Some special characters may appear in LDAP group names, these must be
+escape when looking up the group information from JNDI, otherwise the
+lookup fails. Fixed by applying the necessary escape sequences.
+
+* Let login fail if user name cannot be set
++
+If the user name for a new account is supposed to import from LDAP
+but cannot because it is already in use by another user on this
+server, the new account won't be created.
+
+Administration
+~~~~~~~~~~~~~~
+* gerrit.sh: actually verify running processes
++
+Previously `gerrit.sh check` claimed a server was running if the
+pid file was present, even if the process itself was dead. It now
+checks `ps` for the process before claiming it is running.
+
+* Don't allow exclusive branch rights to block Owner inheritance
++
+Exclusive branch level rights prevented the a higher level branch
+owner from managing the branch rights, unless they had an additional
+access right for the exclusive rights. Now Owner inheritance cannot
+be blocked, ensuring that the higher level owner can manage their
+entire namespace.
+
+* Allow overriding permissions from parent project
++
+Permissions in the parent project could not be overridden in the
+child project. Permissions can now be overidden if the category,
+group name and reference name all match.
+
+Version
+-------
+ef16a1816f293d00c33de9f90470021e2468a709
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 985c0dd..bfb4625 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -4,6 +4,7 @@
[[2_1]]
Version 2.1.x
-------------
+* link:ReleaseNotes-2.1.6.html[2.1.6]
* 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]
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
index b83bc7d..f4fa721 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GroupAdminService.java
@@ -47,7 +47,7 @@
@SignInRequired
void renameGroup(AccountGroup.Id groupId, String newName,
- AsyncCallback<VoidResult> callback);
+ AsyncCallback<GroupDetail> callback);
@SignInRequired
void changeGroupType(AccountGroup.Id groupId, AccountGroup.Type newType,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index e24405f..24cdee4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -55,6 +55,7 @@
protected List<Patch> history;
protected boolean hugeFile;
protected boolean intralineDifference;
+ protected boolean intralineFailure;
public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
final String nn, final FileMode om, final FileMode nm,
@@ -62,7 +63,7 @@
final SparseFileContent ca, final SparseFileContent cb,
final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
final CommentDetail cd, final List<Patch> hist, final boolean hf,
- final boolean id) {
+ final boolean id, final boolean idf) {
changeId = ck;
changeType = ct;
oldName = on;
@@ -80,6 +81,7 @@
history = hist;
hugeFile = hf;
intralineDifference = id;
+ intralineFailure = idf;
}
protected PatchScript() {
@@ -149,6 +151,10 @@
return intralineDifference;
}
+ public boolean hasIntralineFailure() {
+ return intralineFailure;
+ }
+
public SparseFileContent getA() {
return a;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
index be808fa..b3bd0cb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyWatchesTable.java
@@ -134,9 +134,9 @@
table.setWidget(row, 1, new CheckBox());
table.setWidget(row, 2, fp);
- addNotifyButton(AccountProjectWatch.Type.NEW_CHANGES, info, row, 3);
- addNotifyButton(AccountProjectWatch.Type.COMMENTS, info, row, 4);
- addNotifyButton(AccountProjectWatch.Type.SUBMITS, info, row, 5);
+ addNotifyButton(AccountProjectWatch.NotifyType.NEW_CHANGES, info, row, 3);
+ addNotifyButton(AccountProjectWatch.NotifyType.ALL_COMMENTS, info, row, 4);
+ addNotifyButton(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES, info, row, 5);
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
@@ -148,7 +148,7 @@
setRowItem(row, info);
}
- protected void addNotifyButton(final AccountProjectWatch.Type type,
+ protected void addNotifyButton(final AccountProjectWatch.NotifyType type,
final AccountProjectWatchInfo info, final int row, final int col) {
final CheckBox cbox = new CheckBox();
@@ -157,13 +157,16 @@
public void onClick(final ClickEvent event) {
final boolean oldVal = info.getWatch().isNotify(type);
info.getWatch().setNotify(type, cbox.getValue());
+ cbox.setEnabled(false);
Util.ACCOUNT_SVC.updateProjectWatch(info.getWatch(),
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
+ cbox.setEnabled(true);
}
@Override
public void onFailure(final Throwable caught) {
+ cbox.setEnabled(true);
info.getWatch().setNotify(type, oldVal);
cbox.setValue(oldVal);
super.onFailure(caught);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
index 389f22b..fd06cdb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java
@@ -124,10 +124,10 @@
public void onClick(final ClickEvent event) {
final String newName = groupNameTxt.getText().trim();
Util.GROUP_SVC.renameGroup(groupId, newName,
- new GerritCallback<VoidResult>() {
- public void onSuccess(final VoidResult result) {
+ new GerritCallback<GroupDetail>() {
+ public void onSuccess(final GroupDetail groupDetail) {
saveName.setEnabled(false);
- setPageTitle(Util.M.group(newName));
+ display(groupDetail);
}
});
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index 93e8deb..dbbc9c3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -31,6 +31,7 @@
String patchHistoryTitle();
String disabledOnLargeFiles();
+ String intralineFailure();
String upToChange();
String linePrev();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 90def1d..590007d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -13,6 +13,7 @@
patchHeaderNew = New Version
patchHistoryTitle = Patch History
disabledOnLargeFiles = Disabled on very large source files.
+intralineFailure = Intraline difference not available due to server error.
upToChange = Up to change
linePrev = Previous line
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
index 812a74e..cbe037b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchScreen.java
@@ -15,6 +15,7 @@
package com.google.gerrit.client.patches;
import com.google.gerrit.client.Dispatcher;
+import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.changes.CommitMessageBlock;
@@ -140,6 +141,7 @@
/** Keys that cause an action on this screen */
private KeyCommandSet keysNavigation;
private HandlerRegistration regNavigation;
+ private boolean intralineFailure;
/**
* How this patch should be displayed in the patch screen.
@@ -461,6 +463,17 @@
settingsPanel.getReviewedCheckBox().setValue(true);
setReviewedByCurrentUser(true /* reviewed */);
}
+
+ intralineFailure = isFirst && script.hasIntralineFailure();
+ }
+
+ @Override
+ public void onShowView() {
+ super.onShowView();
+ if (intralineFailure) {
+ intralineFailure = false;
+ new ErrorDialog(PatchUtil.C.intralineFailure()).show();
+ }
}
private void showPatch(final boolean showPatch) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
index 09e2cce..8c1e5df 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectServlet.java
@@ -198,7 +198,7 @@
UploadPack up = new UploadPack(repo);
up.setPackConfig(packConfig);
if (!pc.allRefsAreVisible()) {
- up.setRefFilter(new VisibleRefFilter(repo, pc, db.get()));
+ up.setRefFilter(new VisibleRefFilter(repo, pc, db.get(), true));
}
return up;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
index 870d77c..80fe678 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -163,7 +163,7 @@
}
public void renameGroup(final AccountGroup.Id groupId, final String newName,
- final AsyncCallback<VoidResult> callback) {
+ final AsyncCallback<GroupDetail> callback) {
renameGroupFactory.create(groupId, newName).to(callback);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
index 63b5bce..19cd29f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -22,7 +23,6 @@
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.NoSuchGroupException;
-import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -30,7 +30,7 @@
import java.util.Collections;
-class RenameGroup extends Handler<VoidResult> {
+class RenameGroup extends Handler<GroupDetail> {
interface Factory {
RenameGroup create(AccountGroup.Id id, String newName);
}
@@ -38,6 +38,7 @@
private final ReviewDb db;
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
+ private final GroupDetailFactory.Factory groupDetailFactory;
private final AccountGroup.Id groupId;
private final String newName;
@@ -45,18 +46,18 @@
@Inject
RenameGroup(final ReviewDb db, final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
-
+ final GroupDetailFactory.Factory groupDetailFactory,
@Assisted final AccountGroup.Id groupId, @Assisted final String newName) {
this.db = db;
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
-
+ this.groupDetailFactory = groupDetailFactory;
this.groupId = groupId;
this.newName = newName;
}
@Override
- public VoidResult call() throws OrmException, NameAlreadyUsedException,
+ public GroupDetail call() throws OrmException, NameAlreadyUsedException,
NoSuchGroupException {
final GroupControl ctl = groupControlFactory.validateFor(groupId);
final AccountGroup group = db.accountGroups().get(groupId);
@@ -75,7 +76,7 @@
//
AccountGroupName other = db.accountGroupNames().get(key);
if (other != null && other.getId().equals(groupId)) {
- return VoidResult.INSTANCE;
+ return groupDetailFactory.create(groupId).call();
}
// Otherwise, someone else has this identity.
@@ -94,6 +95,6 @@
groupCache.evict(group);
groupCache.evictAfterRename(old);
- return VoidResult.INSTANCE;
+ return groupDetailFactory.create(groupId).call();
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
index 30d7716..302135e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
@@ -20,12 +20,14 @@
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.AccountDiffPreference;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
-import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
+import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.patch.IntraLineDiff;
+import com.google.gerrit.server.patch.IntraLineDiffKey;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.Text;
@@ -65,6 +67,7 @@
};
private Repository db;
+ private Project.NameKey projectKey;
private ObjectReader reader;
private Change change;
private AccountDiffPreference diffPrefs;
@@ -88,8 +91,9 @@
patchListCache = plc;
}
- void setRepository(final Repository r) {
- db = r;
+ void setRepository(Repository r, Project.NameKey projectKey) {
+ this.db = r;
+ this.projectKey = projectKey;
}
void setChange(final Change c) {
@@ -127,7 +131,8 @@
private PatchScript build(final PatchListEntry content,
final CommentDetail comments, final List<Patch> history)
throws IOException {
- boolean intralineDifference = diffPrefs.isIntralineDifference();
+ boolean intralineDifferenceIsPossible = true;
+ boolean intralineFailure = false;
a.path = oldName(content);
b.path = newName(content);
@@ -137,16 +142,31 @@
edits = new ArrayList<Edit>(content.getEdits());
- if (intralineDifference) {
- if (isModify(content)) {
- IntraLineDiff d = patchListCache.get(a.id, a.src, b.id, b.src, edits);
- if (d != null) {
- edits = new ArrayList<Edit>(d.getEdits());
- } else {
- intralineDifference = false;
+ if (!isModify(content)) {
+ intralineDifferenceIsPossible = false;
+ } else if (diffPrefs.isIntralineDifference()) {
+ IntraLineDiff d =
+ patchListCache.getIntraLineDiff(new IntraLineDiffKey(a.id, a.src,
+ b.id, b.src, edits, projectKey, bId, b.path));
+ if (d != null) {
+ switch (d.getStatus()) {
+ case EDIT_LIST:
+ edits = new ArrayList<Edit>(d.getEdits());
+ break;
+
+ case DISABLED:
+ intralineDifferenceIsPossible = false;
+ break;
+
+ case ERROR:
+ case TIMEOUT:
+ intralineDifferenceIsPossible = false;
+ intralineFailure = true;
+ break;
}
} else {
- intralineDifference = false;
+ intralineDifferenceIsPossible = false;
+ intralineFailure = true;
}
}
@@ -187,10 +207,11 @@
packContent(diffPrefs.getIgnoreWhitespace() != Whitespace.IGNORE_NONE);
}
- return new PatchScript(change.getKey(), content.getChangeType(), content
- .getOldName(), content.getNewName(), a.fileMode, b.fileMode, content
- .getHeaderLines(), diffPrefs, a.dst, b.dst, edits, a.displayMethod,
- b.displayMethod, comments, history, hugeFile, intralineDifference);
+ return new PatchScript(change.getKey(), content.getChangeType(),
+ content.getOldName(), content.getNewName(), a.fileMode, b.fileMode,
+ content.getHeaderLines(), diffPrefs, a.dst, b.dst, edits,
+ a.displayMethod, b.displayMethod, comments, history, hugeFile,
+ intralineDifferenceIsPossible, intralineFailure);
}
private static boolean isModify(PatchListEntry content) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index 377cf49..fcaa0c5 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -173,7 +173,7 @@
private PatchScriptBuilder newBuilder(final PatchList list, Repository git) {
final AccountDiffPreference dp = new AccountDiffPreference(diffPrefs);
final PatchScriptBuilder b = builderFactory.get();
- b.setRepository(git);
+ b.setRepository(git, projectKey);
b.setChange(change);
b.setDiffPrefs(dp);
b.setTrees(list.isAgainstParent(), list.getOldId(), list.getNewId());
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
index 6adab03..96e7dbb 100755
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/gerrit.sh
@@ -425,6 +425,17 @@
fi
fi
+ if test $UID = 0; then
+ PID=`cat "$GERRIT_PID"`
+ if test -f "/proc/${PID}/oom_score_adj" ; then
+ echo -1000 > "/proc/${PID}/oom_score_adj"
+ else
+ if test -f "/proc/${PID}/oom_adj" ; then
+ echo -16 > "/proc/${PID}/oom_adj"
+ fi
+ fi
+ fi
+
TIMEOUT=90 # seconds
sleep 1
while running "$GERRIT_PID" && test $TIMEOUT -gt 0 ; do
@@ -522,8 +533,10 @@
echo
if test -f "$GERRIT_PID" ; then
- echo "Gerrit running pid="`cat "$GERRIT_PID"`
- exit 0
+ if running "$GERRIT_PID" ; then
+ echo "Gerrit running pid="`cat "$GERRIT_PID"`
+ exit 0
+ fi
fi
exit 1
;;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
index 6713d8f..c18ae82 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
@@ -21,8 +21,8 @@
/** An {@link Account} interested in a {@link Project}. */
public final class AccountProjectWatch {
- public enum Type {
- NEW_CHANGES, SUBMITS, COMMENTS
+ public enum NotifyType {
+ NEW_CHANGES, ALL_COMMENTS, SUBMITTED_CHANGES
}
public static final String FILTER_ALL = "*";
@@ -124,46 +124,32 @@
return FILTER_ALL.equals(key.filter.get()) ? null : key.filter.get();
}
- public boolean isNotifyNewChanges() {
- return notifyNewChanges;
- }
+ public boolean isNotify(final NotifyType type) {
+ switch (type) {
+ case NEW_CHANGES:
+ return notifyNewChanges;
- public void setNotifyNewChanges(final boolean a) {
- notifyNewChanges = a;
- }
+ case ALL_COMMENTS:
+ return notifyAllComments;
- public boolean isNotifyAllComments() {
- return notifyAllComments;
- }
-
- public void setNotifyAllComments(final boolean a) {
- notifyAllComments = a;
- }
-
- public boolean isNotifySubmittedChanges() {
- return notifySubmittedChanges;
- }
-
- public void setNotifySubmittedChanges(final boolean a) {
- notifySubmittedChanges = a;
- }
-
- public boolean isNotify(final Type type) {
- switch(type) {
- case NEW_CHANGES: return notifySubmittedChanges;
- case SUBMITS: return notifyNewChanges;
- case COMMENTS: return notifyAllComments;
+ case SUBMITTED_CHANGES:
+ return notifySubmittedChanges;
}
return false;
}
- public void setNotify(final Type type, final boolean v) {
- switch(type) {
- case NEW_CHANGES: notifySubmittedChanges = v;
+ public void setNotify(final NotifyType type, final boolean v) {
+ switch (type) {
+ case NEW_CHANGES:
+ notifyNewChanges = v;
break;
- case SUBMITS: notifyNewChanges = v;
+
+ case ALL_COMMENTS:
+ notifyAllComments = v;
break;
- case COMMENTS: notifyAllComments = v;
+
+ case SUBMITTED_CHANGES:
+ notifySubmittedChanges = v;
break;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
index 33661ab..4a33999 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitProjectImporter.java
@@ -79,6 +79,13 @@
if (FileKey.isGitRepository(f, FS.DETECTED)) {
if (name.equals(".git")) {
+ if ("".equals(prefix)) {
+ // If the git base path is itself a git repository working
+ // directory, this is a bit nonsensical for Gerrit Code Review.
+ // Skip the path and do the next one.
+ messages.warning("Skipping " + f.getAbsolutePath());
+ continue;
+ }
name = prefix.substring(0, prefix.length() - 1);
} else if (name.endsWith(".git")) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
index 369602a..4ac55ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GitRepositoryManager.java
@@ -30,6 +30,9 @@
* environment.
*/
public interface GitRepositoryManager {
+ /** Note tree listing commits we refuse {@code refs/meta/reject-commits} */
+ public static final String REF_REJECT_COMMITS = "refs/meta/reject-commits";
+
/**
* Get (or open) a repository by name.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
index 1c100df..043e7c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
@@ -296,7 +296,7 @@
return Collections.emptyList();
}
try {
- local = new VisibleRefFilter(db, pc, meta).filter(local);
+ local = new VisibleRefFilter(db, pc, meta, true).filter(local);
} finally {
meta.close();
}
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 10efeb1..d9a00890 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
@@ -161,6 +161,7 @@
private final Map<RevCommit, ReplaceRequest> replaceByCommit =
new HashMap<RevCommit, ReplaceRequest>();
+ private Collection<ObjectId> existingObjects;
private Map<ObjectId, Ref> refsById;
private String destTopicName;
@@ -207,8 +208,9 @@
if (!projectControl.allRefsAreVisible()) {
rp.setCheckReferencedObjectsAreReachable(true);
- rp.setRefFilter(new VisibleRefFilter(repo, projectControl, db));
+ rp.setRefFilter(new VisibleRefFilter(repo, projectControl, db, false));
}
+ rp.setRefFilter(new ReceiveCommitsRefFilter(rp.getRefFilter()));
rp.setPreReceiveHook(this);
rp.setPostReceiveHook(this);
@@ -300,8 +302,8 @@
}
}
- if (isHead(c) || isTag(c)) {
- // We only schedule heads and tags for replication.
+ if (!c.getRefName().startsWith(NEW_CHANGE)) {
+ // We only schedule direct refs updates for replication.
// Change refs are scheduled when they are created.
//
replication.scheduleUpdate(project.getNameKey(), c.getRefName());
@@ -728,18 +730,18 @@
* @throws IOException the map cannot be loaded.
*/
private NoteMap loadRejectCommitsMap() throws IOException {
- String rejectNotes = "refs/meta/reject-commits";
try {
- Ref ref = repo.getRef(rejectNotes);
+ Ref ref = repo.getRef(GitRepositoryManager.REF_REJECT_COMMITS);
if (ref == null) {
- return null;
+ return NoteMap.newEmptyMap();
}
RevWalk rw = rp.getRevWalk();
RevCommit map = rw.parseCommit(ref.getObjectId());
return NoteMap.read(rw.getObjectReader(), map);
} catch (IOException badMap) {
- throw new IOException("Cannot load " + rejectNotes, badMap);
+ throw new IOException("Cannot load "
+ + GitRepositoryManager.REF_REJECT_COMMITS, badMap);
}
}
@@ -808,9 +810,9 @@
walk.sort(RevSort.REVERSE, true);
try {
walk.markStart(walk.parseCommit(newChange.getNewId()));
- for (final Ref r : rp.getAdvertisedRefs().values()) {
+ for (ObjectId id : existingObjects()) {
try {
- walk.markUninteresting(walk.parseCommit(r.getObjectId()));
+ walk.markUninteresting(walk.parseCommit(id));
} catch (IOException e) {
continue;
}
@@ -1412,9 +1414,9 @@
walk.sort(RevSort.NONE);
try {
walk.markStart(walk.parseCommit(cmd.getNewId()));
- for (final Ref r : rp.getAdvertisedRefs().values()) {
+ for (ObjectId id : existingObjects()) {
try {
- walk.markUninteresting(walk.parseCommit(r.getObjectId()));
+ walk.markUninteresting(walk.parseCommit(id));
} catch (IOException e) {
continue;
}
@@ -1432,6 +1434,17 @@
}
}
+ private Collection<ObjectId> existingObjects() {
+ if (existingObjects == null) {
+ Map<String, Ref> refs = repo.getAllRefs();
+ existingObjects = new ArrayList<ObjectId>(refs.size());
+ for (Ref r : refs.values()) {
+ existingObjects.add(r.getObjectId());
+ }
+ }
+ return existingObjects;
+ }
+
private boolean validCommit(final RefControl ctl, final ReceiveCommand cmd,
final RevCommit c) throws MissingObjectException, IOException {
rp.getRevWalk().parseBody(c);
@@ -1507,7 +1520,7 @@
}
// Check for banned commits to prevent them from entering the tree again.
- if (rejectCommits != null && rejectCommits.contains(c)) {
+ if (rejectCommits.contains(c)) {
reject(newChange, "contains banned commit " + c.getName());
return false;
}
@@ -1624,10 +1637,10 @@
sendMergedEmail(result);
}
- private Map<ObjectId, Ref> changeRefsById() {
+ private Map<ObjectId, Ref> changeRefsById() throws IOException {
if (refsById == null) {
refsById = new HashMap<ObjectId, Ref>();
- for (final Ref r : repo.getAllRefs().values()) {
+ for (Ref r : repo.getRefDatabase().getRefs("refs/changes/").values()) {
if (PatchSet.isRef(r.getName())) {
refsById.put(r.getObjectId(), r);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsRefFilter.java
new file mode 100644
index 0000000..730305c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsRefFilter.java
@@ -0,0 +1,41 @@
+// 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.git;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.RefFilter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Exposes only the non refs/changes/ reference names. */
+class ReceiveCommitsRefFilter implements RefFilter {
+ private final RefFilter base;
+
+ public ReceiveCommitsRefFilter(RefFilter base) {
+ this.base = base != null ? base : RefFilter.DEFAULT;
+ }
+
+ @Override
+ public Map<String, Ref> filter(Map<String, Ref> refs) {
+ HashMap<String, Ref> r = new HashMap<String, Ref>();
+ for (Map.Entry<String, Ref> e : refs.entrySet()) {
+ if (!e.getKey().startsWith("refs/changes/")) {
+ r.put(e.getKey(), e.getValue());
+ }
+ }
+ return base.filter(r);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index ffc3fd8..8e4d8ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -52,12 +52,15 @@
private final Repository db;
private final ProjectControl projectCtl;
private final ReviewDb reviewDb;
+ private final boolean showChanges;
public VisibleRefFilter(final Repository db,
- final ProjectControl projectControl, final ReviewDb reviewDb) {
+ final ProjectControl projectControl, final ReviewDb reviewDb,
+ final boolean showChanges) {
this.db = db;
this.projectCtl = projectControl;
this.reviewDb = reviewDb;
+ this.showChanges = showChanges;
}
@Override
@@ -99,6 +102,10 @@
}
private Set<Change.Id> visibleChanges() {
+ if (!showChanges) {
+ return Collections.emptySet();
+ }
+
final Project project = projectCtl.getProject();
try {
final Set<Change.Id> visibleChanges = new HashSet<Change.Id>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 71712af..0377291 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.StarredChange;
+import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
@@ -299,7 +300,7 @@
// BCC anyone else who has interest in this project's changes
//
for (final AccountProjectWatch w : getWatches()) {
- if (w.isNotifyAllComments()) {
+ if (w.isNotify(NotifyType.ALL_COMMENTS)) {
add(RecipientType.BCC, w.getAccountId());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index 18bfe976..c14ff1b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -19,6 +19,7 @@
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -61,7 +62,7 @@
// BCC anyone who has interest in this project's changes
//
for (final AccountProjectWatch w : getWatches()) {
- if (w.isNotifyNewChanges()) {
+ if (w.isNotify(NotifyType.NEW_CHANGES)) {
if (owners.contains(w.getAccountId())) {
add(RecipientType.TO, w.getAccountId());
} else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
index 40f4790..b2a1c44 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetApproval;
+import com.google.gerrit.reviewdb.AccountProjectWatch.NotifyType;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -149,7 +150,7 @@
// BCC anyone else who has interest in this project's changes
//
for (final AccountProjectWatch w : getWatches()) {
- if (w.isNotifySubmittedChanges()) {
+ if (w.isNotify(NotifyType.SUBMITTED_CHANGES)) {
add(RecipientType.BCC, w.getAccountId());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java
index f8f339e..3805f8f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiff.java
@@ -14,9 +14,13 @@
package com.google.gerrit.server.patch;
+import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum;
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
+import com.google.gerrit.reviewdb.CodedEnum;
+
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.ReplaceEdit;
@@ -33,10 +37,36 @@
public class IntraLineDiff implements Serializable {
static final long serialVersionUID = IntraLineDiffKey.serialVersionUID;
- private List<Edit> edits;
+ public static enum Status implements CodedEnum {
+ EDIT_LIST('e'), DISABLED('D'), TIMEOUT('T'), ERROR('E');
+
+ private final char code;
+
+ Status(char code) {
+ this.code = code;
+ }
+
+ @Override
+ public char getCode() {
+ return code;
+ }
+ }
+
+ private transient Status status;
+ private transient List<Edit> edits;
+
+ IntraLineDiff(Status status) {
+ this.status = status;
+ this.edits = Collections.emptyList();
+ }
IntraLineDiff(List<Edit> edits) {
- this.edits = edits;
+ this.status = Status.EDIT_LIST;
+ this.edits = Collections.unmodifiableList(edits);
+ }
+
+ public Status getStatus() {
+ return status;
}
public List<Edit> getEdits() {
@@ -44,6 +74,7 @@
}
private void writeObject(final ObjectOutputStream out) throws IOException {
+ writeEnum(out, status);
writeVarInt32(out, edits.size());
for (Edit e : edits) {
writeEdit(out, e);
@@ -61,6 +92,7 @@
}
private void readObject(final ObjectInputStream in) throws IOException {
+ status = readEnum(in, Status.values());
int editCount = readVarInt32(in);
Edit[] editArray = new Edit[editCount];
for (int i = 0; i < editCount; i++) {
@@ -69,11 +101,13 @@
int innerCount = readVarInt32(in);
if (0 < innerCount) {
Edit[] inner = new Edit[innerCount];
- for (int j = 0; j < innerCount; j++)
+ for (int j = 0; j < innerCount; j++) {
inner[j] = readEdit(in);
+ }
editArray[i] = new ReplaceEdit(editArray[i], toList(inner));
}
}
+ edits = toList(editArray);
}
private static void writeEdit(OutputStream out, Edit e) throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
index 899dda5..a8d62fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineDiffKey.java
@@ -17,6 +17,8 @@
import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
+import com.google.gerrit.reviewdb.Project;
+
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.lib.ObjectId;
@@ -27,7 +29,7 @@
import java.util.List;
public class IntraLineDiffKey implements Serializable {
- static final long serialVersionUID = 1L;
+ static final long serialVersionUID = 3L;
private transient ObjectId aId;
private transient ObjectId bId;
@@ -38,14 +40,22 @@
private transient Text bText;
private transient List<Edit> edits;
- IntraLineDiffKey(ObjectId aId, Text aText, ObjectId bId, Text bText,
- List<Edit> edits) {
+ private transient Project.NameKey projectKey;
+ private transient ObjectId commit;
+ private transient String path;
+
+ public IntraLineDiffKey(ObjectId aId, Text aText, ObjectId bId, Text bText,
+ List<Edit> edits, Project.NameKey projectKey, ObjectId commit, String path) {
this.aId = aId;
this.bId = bId;
this.aText = aText;
this.bText = bText;
this.edits = edits;
+
+ this.projectKey = projectKey;
+ this.commit = commit;
+ this.path = path;
}
Text getTextA() {
@@ -60,6 +70,26 @@
return edits;
}
+ ObjectId getBlobA() {
+ return aId;
+ }
+
+ ObjectId getBlobB() {
+ return bId;
+ }
+
+ Project.NameKey getProject() {
+ return projectKey;
+ }
+
+ ObjectId getCommit() {
+ return commit;
+ }
+
+ String getPath() {
+ return path;
+ }
+
@Override
public int hashCode() {
int h = 0;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
new file mode 100644
index 0000000..0ac1af2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/IntraLineLoader.java
@@ -0,0 +1,472 @@
+// Copyright (C) 2009 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.patch;
+
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.MyersDiff;
+import org.eclipse.jgit.diff.ReplaceEdit;
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
+
+class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
+ private static final Logger log = LoggerFactory
+ .getLogger(IntraLineLoader.class);
+
+ private static final Pattern BLANK_LINE_RE = Pattern
+ .compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
+
+ private static final Pattern CONTROL_BLOCK_START_RE = Pattern
+ .compile("[{:][ \\t]*$");
+
+ private final BlockingQueue<Worker> workerPool;
+ private final long timeoutMillis;
+
+ @Inject
+ IntraLineLoader(final @GerritServerConfig Config cfg) {
+ final int workers =
+ cfg.getInt("cache", PatchListCacheImpl.INTRA_NAME, "maxIdleWorkers",
+ Runtime.getRuntime().availableProcessors() * 3 / 2);
+ workerPool = new ArrayBlockingQueue<Worker>(workers, true /* fair */);
+
+ timeoutMillis =
+ ConfigUtil.getTimeUnit(cfg, "cache", PatchListCacheImpl.INTRA_NAME,
+ "timeout", TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS),
+ TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
+ Worker w = workerPool.poll();
+ if (w == null) {
+ w = new Worker();
+ }
+
+ Worker.Result r = w.computeWithTimeout(key, timeoutMillis);
+
+ if (r == Worker.Result.TIMEOUT) {
+ // Don't keep this thread. We have to murder it unsafely, which
+ // means its unable to be reused in the future. Return back a
+ // null result, indicating the cache cannot load this key.
+ //
+ return new IntraLineDiff(IntraLineDiff.Status.TIMEOUT);
+ }
+
+ if (!workerPool.offer(w)) {
+ // If the idle worker pool is full, terminate this thread.
+ //
+ w.end();
+ }
+
+ if (r.error != null) {
+ // If there was an error computing the result, carry it
+ // up to the caller so the cache knows this key is invalid.
+ //
+ throw r.error;
+ }
+
+ return r.diff;
+ }
+
+ private static class Worker {
+ private static final AtomicInteger count = new AtomicInteger(1);
+
+ private final ArrayBlockingQueue<Input> input;
+ private final ArrayBlockingQueue<Result> result;
+ private final Thread thread;
+
+ Worker() {
+ input = new ArrayBlockingQueue<Input>(1);
+ result = new ArrayBlockingQueue<Result>(1);
+
+ thread = new Thread(new Runnable() {
+ public void run() {
+ workerLoop();
+ }
+ });
+ thread.setName("IntraLineDiff-" + count.getAndIncrement());
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ Result computeWithTimeout(IntraLineDiffKey key, long timeoutMillis)
+ throws Exception {
+ if (!input.offer(new Input(key))) {
+ log.error("Cannot enqueue task to thread " + thread.getName());
+ return null;
+ }
+
+ Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS);
+ if (r != null) {
+ return r;
+ } else {
+ log.warn(timeoutMillis + " ms timeout reached for IntraLineDiff"
+ + " in project " + key.getProject().get() //
+ + " on commit " + key.getCommit().name() //
+ + " for path " + key.getPath() //
+ + " comparing " + key.getBlobA().name() //
+ + ".." + key.getBlobB().name() //
+ + ". Killing " + thread.getName());
+ try {
+ thread.stop();
+ } catch (Throwable error) {
+ // Ignore any reason the thread won't stop.
+ log.error("Cannot stop runaway thread " + thread.getName(), error);
+ }
+ return Result.TIMEOUT;
+ }
+ }
+
+ void end() {
+ if (!input.offer(Input.END_THREAD)) {
+ log.error("Cannot gracefully stop thread " + thread.getName());
+ }
+ }
+
+ private void workerLoop() {
+ try {
+ for (;;) {
+ Input in;
+ try {
+ in = input.take();
+ } catch (InterruptedException e) {
+ log.error("Unexpected interrupt on " + thread.getName());
+ continue;
+ }
+
+ if (in == Input.END_THREAD) {
+ return;
+ }
+
+ Result r;
+ try {
+ r = new Result(IntraLineLoader.compute(in.key));
+ } catch (Exception error) {
+ r = new Result(error);
+ }
+
+ if (!result.offer(r)) {
+ log.error("Cannot return result from " + thread.getName());
+ }
+ }
+ } catch (ThreadDeath iHaveBeenShot) {
+ // Handle thread death by gracefully returning to the caller,
+ // allowing the thread to be destroyed.
+ }
+ }
+
+ private static class Input {
+ static final Input END_THREAD = new Input(null);
+
+ final IntraLineDiffKey key;
+
+ Input(IntraLineDiffKey key) {
+ this.key = key;
+ }
+ }
+
+ static class Result {
+ static final Result TIMEOUT = new Result((IntraLineDiff) null);
+
+ final IntraLineDiff diff;
+ final Exception error;
+
+ Result(IntraLineDiff diff) {
+ this.diff = diff;
+ this.error = null;
+ }
+
+ Result(Exception error) {
+ this.diff = null;
+ this.error = error;
+ }
+ }
+ }
+
+ private static IntraLineDiff compute(IntraLineDiffKey key) throws Exception {
+ List<Edit> edits = new ArrayList<Edit>(key.getEdits());
+ Text aContent = key.getTextA();
+ Text bContent = key.getTextB();
+ combineLineEdits(edits, aContent, bContent);
+
+ for (int i = 0; i < edits.size(); i++) {
+ Edit e = edits.get(i);
+
+ if (e.getType() == Edit.Type.REPLACE) {
+ CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
+ CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
+ CharTextComparator cmp = new CharTextComparator();
+
+ List<Edit> wordEdits = MyersDiff.INSTANCE.diff(cmp, a, b);
+
+ // Combine edits that are really close together. If they are
+ // just a few characters apart we tend to get better results
+ // by joining them together and taking the whole span.
+ //
+ for (int j = 0; j < wordEdits.size() - 1;) {
+ Edit c = wordEdits.get(j);
+ Edit n = wordEdits.get(j + 1);
+
+ if (n.getBeginA() - c.getEndA() <= 5
+ || n.getBeginB() - c.getEndB() <= 5) {
+ int ab = c.getBeginA();
+ int ae = n.getEndA();
+
+ int bb = c.getBeginB();
+ int be = n.getEndB();
+
+ if (canCoalesce(a, c.getEndA(), n.getBeginA())
+ && canCoalesce(b, c.getEndB(), n.getBeginB())) {
+ wordEdits.set(j, new Edit(ab, ae, bb, be));
+ wordEdits.remove(j + 1);
+ continue;
+ }
+ }
+
+ j++;
+ }
+
+ // Apply some simple rules to fix up some of the edits. Our
+ // logic above, along with our per-character difference tends
+ // to produce some crazy stuff.
+ //
+ for (int j = 0; j < wordEdits.size(); j++) {
+ Edit c = wordEdits.get(j);
+ int ab = c.getBeginA();
+ int ae = c.getEndA();
+
+ int bb = c.getBeginB();
+ int be = c.getEndB();
+
+ // Sometimes the diff generator produces an INSERT or DELETE
+ // right up against a REPLACE, but we only find this after
+ // we've also played some shifting games on the prior edit.
+ // If that happened to us, coalesce them together so we can
+ // correct this mess for the user. If we don't we wind up
+ // with silly stuff like "es" -> "es = Addresses".
+ //
+ if (1 < j) {
+ Edit p = wordEdits.get(j - 1);
+ if (p.getEndA() == ab || p.getEndB() == bb) {
+ if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
+ ab = p.getBeginA();
+ }
+ if (p.getEndB() == bb && p.getBeginB() < p.getEndB()) {
+ bb = p.getBeginB();
+ }
+ wordEdits.remove(--j);
+ }
+ }
+
+ // We sometimes collapsed an edit together in a strange way,
+ // such that the edges of each text is identical. Fix by
+ // by dropping out that incorrectly replaced region.
+ //
+ while (ab < ae && bb < be && cmp.equals(a, ab, b, bb)) {
+ ab++;
+ bb++;
+ }
+ while (ab < ae && bb < be && cmp.equals(a, ae - 1, b, be - 1)) {
+ ae--;
+ be--;
+ }
+
+ // The leading part of an edit and its trailing part in the same
+ // text might be identical. Slide down that edit and use the tail
+ // rather than the leading bit. If however the edit is only on a
+ // whitespace block try to shift it to the left margin, assuming
+ // that it is an indentation change.
+ //
+ boolean aShift = true;
+ if (ab < ae && isOnlyWhitespace(a, ab, ae)) {
+ int lf = findLF(wordEdits, j, a, ab);
+ if (lf < ab && a.charAt(lf) == '\n') {
+ int nb = lf + 1;
+ int p = 0;
+ while (p < ae - ab) {
+ if (cmp.equals(a, ab + p, a, ab + p))
+ p++;
+ else
+ break;
+ }
+ if (p == ae - ab) {
+ ab = nb;
+ ae = nb + p;
+ aShift = false;
+ }
+ }
+ }
+ if (aShift) {
+ while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
+ && cmp.equals(a, ab - 1, a, ae - 1)) {
+ ab--;
+ ae--;
+ }
+ if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
+ while (ab < ae && ae < a.size() && cmp.equals(a, ab, a, ae)) {
+ ab++;
+ ae++;
+ if (a.charAt(ae - 1) == '\n') {
+ break;
+ }
+ }
+ }
+ }
+
+ boolean bShift = true;
+ if (bb < be && isOnlyWhitespace(b, bb, be)) {
+ int lf = findLF(wordEdits, j, b, bb);
+ if (lf < bb && b.charAt(lf) == '\n') {
+ int nb = lf + 1;
+ int p = 0;
+ while (p < be - bb) {
+ if (cmp.equals(b, bb + p, b, bb + p))
+ p++;
+ else
+ break;
+ }
+ if (p == be - bb) {
+ bb = nb;
+ be = nb + p;
+ bShift = false;
+ }
+ }
+ }
+ if (bShift) {
+ while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
+ && cmp.equals(b, bb - 1, b, be - 1)) {
+ bb--;
+ be--;
+ }
+ if (!b.isLineStart(bb) || !b.contains(bb, be, '\n')) {
+ while (bb < be && be < b.size() && cmp.equals(b, bb, b, be)) {
+ bb++;
+ be++;
+ if (b.charAt(be - 1) == '\n') {
+ break;
+ }
+ }
+ }
+ }
+
+ // If most of a line was modified except the LF was common, make
+ // the LF part of the modification region. This is easier to read.
+ //
+ if (ab < ae //
+ && (ab == 0 || a.charAt(ab - 1) == '\n') //
+ && ae < a.size() && a.charAt(ae) == '\n') {
+ ae++;
+ }
+ if (bb < be //
+ && (bb == 0 || b.charAt(bb - 1) == '\n') //
+ && be < b.size() && b.charAt(be) == '\n') {
+ be++;
+ }
+
+ wordEdits.set(j, new Edit(ab, ae, bb, be));
+ }
+
+ edits.set(i, new ReplaceEdit(e, wordEdits));
+ }
+ }
+
+ return new IntraLineDiff(edits);
+ }
+
+ private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
+ for (int j = 0; j < edits.size() - 1;) {
+ Edit c = edits.get(j);
+ Edit n = edits.get(j + 1);
+
+ // Combine edits that are really close together. Right now our rule
+ // is, coalesce two line edits which are only one line apart if that
+ // common context line is either a "pointless line", or is identical
+ // on both sides and starts a new block of code. These are mostly
+ // block reindents to add or remove control flow operators.
+ //
+ final int ad = n.getBeginA() - c.getEndA();
+ final int bd = n.getBeginB() - c.getEndB();
+ if ((1 <= ad && isBlankLineGap(a, c.getEndA(), n.getBeginA()))
+ || (1 <= bd && isBlankLineGap(b, c.getEndB(), n.getBeginB()))
+ || (ad == 1 && bd == 1 && isControlBlockStart(a, c.getEndA()))) {
+ int ab = c.getBeginA();
+ int ae = n.getEndA();
+
+ int bb = c.getBeginB();
+ int be = n.getEndB();
+
+ edits.set(j, new Edit(ab, ae, bb, be));
+ edits.remove(j + 1);
+ continue;
+ }
+
+ j++;
+ }
+ }
+
+ private static boolean isBlankLineGap(Text a, int b, int e) {
+ for (; b < e; b++) {
+ if (!BLANK_LINE_RE.matcher(a.getString(b)).matches()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isControlBlockStart(Text a, int idx) {
+ return CONTROL_BLOCK_START_RE.matcher(a.getString(idx)).find();
+ }
+
+ private static boolean canCoalesce(CharText a, int b, int e) {
+ while (b < e) {
+ if (a.charAt(b++) == '\n') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int findLF(List<Edit> edits, int j, CharText t, int b) {
+ int lf = b;
+ int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
+ while (limit < lf && t.charAt(lf) != '\n') {
+ lf--;
+ }
+ return lf;
+ }
+
+ private static boolean isOnlyWhitespace(CharText t, final int b, final int e) {
+ for (int c = b; c < e; c++) {
+ if (!Character.isWhitespace(t.charAt(c))) {
+ return false;
+ }
+ }
+ return b < e;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
index c679f4a..a7cf10a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -16,12 +16,6 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
-import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
-
-import org.eclipse.jgit.diff.Edit;
-import org.eclipse.jgit.lib.ObjectId;
-
-import java.util.List;
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
@@ -29,8 +23,5 @@
public PatchList get(Change change, PatchSet patchSet);
- public PatchList get(Change change, PatchSet patchSet, Whitespace whitespace);
-
- public IntraLineDiff get(ObjectId aId, Text aText, ObjectId bId, Text bText,
- List<Edit> edits);
+ public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 087ed51..e1a0a40 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -12,114 +12,32 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
-// Some portions (e.g. outputDiff) below are:
-//
-// Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
-// Copyright (C) 2009, Johannes E. Schindelin
-// Copyright (C) 2009, Johannes Schindelin <johannes.schindelin@gmx.de>
-// and other copyright owners as documented in the project's IP log.
-//
-// This program and the accompanying materials are made available
-// under the terms of the Eclipse Distribution License v1.0 which
-// accompanies this distribution, is reproduced below, and is
-// available at http://www.eclipse.org/org/documents/edl-v10.php
-//
-// All rights reserved.
-//
-// Redistribution and use in source and binary forms, with or
-// without modification, are permitted provided that the following
-// conditions are met:
-//
-// - Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//
-// - Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following
-// disclaimer in the documentation and/or other materials provided
-// with the distribution.
-//
-// - Neither the name of the Eclipse Foundation, Inc. nor the
-// names of its contributors may be used to endorse or promote
-// products derived from this software without specific prior
-// written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
-// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
-// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-//
package com.google.gerrit.server.patch;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
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;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
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.HistogramDiff;
-import org.eclipse.jgit.diff.MyersDiff;
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.diff.RawTextComparator;
-import org.eclipse.jgit.diff.ReplaceEdit;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectReader;
-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;
-import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.eclipse.jgit.util.io.DisabledOutputStream;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
/** Provides a cached list of {@link PatchListEntry}. */
@Singleton
public class PatchListCacheImpl implements PatchListCache {
private static final String FILE_NAME = "diff";
- private static final String INTRA_NAME = "diff_intraline";
-
- private static final Pattern BLANK_LINE_RE =
- Pattern.compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
- private static final Pattern CONTROL_BLOCK_START_RE =
- Pattern.compile("[{:][ \\t]*$");
+ static final String INTRA_NAME = "diff_intraline";
public static Module module() {
return new CacheModule() {
@@ -159,7 +77,9 @@
this.fileCache = fileCache;
this.intraCache = intraCache;
- this.computeIntraline = cfg.getBoolean("cache", "diff", "intraline", true);
+ this.computeIntraline =
+ cfg.getBoolean("cache", INTRA_NAME, "enabled",
+ cfg.getBoolean("cache", "diff", "intraline", true));
}
public PatchList get(final PatchListKey key) {
@@ -167,478 +87,23 @@
}
public PatchList get(final Change change, final PatchSet patchSet) {
- return get(change, patchSet, Whitespace.IGNORE_NONE);
- }
-
- public PatchList get(final Change change, final PatchSet patchSet,
- final Whitespace whitespace) {
final Project.NameKey projectKey = change.getProject();
final ObjectId a = null;
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
- return get(new PatchListKey(projectKey, a, b, whitespace));
+ final Whitespace ws = Whitespace.IGNORE_NONE;
+ return get(new PatchListKey(projectKey, a, b, ws));
}
@Override
- public IntraLineDiff get(ObjectId aId, Text aText, ObjectId bId, Text bText,
- List<Edit> edits) {
+ public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key) {
if (computeIntraline) {
- IntraLineDiffKey key =
- new IntraLineDiffKey(aId, aText, bId, bText, edits);
- return intraCache.get(key);
+ IntraLineDiff d = intraCache.get(key);
+ if (d == null) {
+ d = new IntraLineDiff(IntraLineDiff.Status.ERROR);
+ }
+ return d;
} else {
- return null;
- }
- }
-
- private static RawTextComparator comparatorFor(Whitespace ws) {
- switch (ws) {
- case IGNORE_ALL_SPACE:
- return RawTextComparator.WS_IGNORE_ALL;
-
- case IGNORE_SPACE_AT_EOL:
- return RawTextComparator.WS_IGNORE_TRAILING;
-
- case IGNORE_SPACE_CHANGE:
- return RawTextComparator.WS_IGNORE_CHANGE;
-
- case IGNORE_NONE:
- default:
- return RawTextComparator.DEFAULT;
- }
- }
-
- static class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
- private final GitRepositoryManager repoManager;
-
- @Inject
- PatchListLoader(GitRepositoryManager mgr) {
- repoManager = mgr;
- }
-
- @Override
- public PatchList createEntry(final PatchListKey key) throws Exception {
- final Repository repo = repoManager.openRepository(key.projectKey.get());
- try {
- return readPatchList(key, repo);
- } finally {
- repo.close();
- }
- }
-
- private PatchList readPatchList(final PatchListKey key,
- final Repository repo) throws IOException {
- // TODO(jeffschu) correctly handle merge commits
-
- final RawTextComparator cmp = comparatorFor(key.getWhitespace());
- final ObjectReader reader = repo.newObjectReader();
- try {
- final RevWalk rw = new RevWalk(reader);
- 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(cmp, repo, reader, null, b);
- return new PatchList(a, b, 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 bTree = b.getTree();
-
- final TreeWalk walk = new TreeWalk(reader);
- walk.reset();
- walk.setRecursive(true);
- walk.addTree(aTree);
- walk.addTree(bTree);
- walk.setFilter(TreeFilter.ANY_DIFF);
-
- DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
- df.setRepository(repo);
- df.setDiffComparator(cmp);
- df.setDetectRenames(true);
- List<DiffEntry> diffEntries = df.scan(aTree, bTree);
-
- final int cnt = diffEntries.size();
- final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
- entries[0] = newCommitMessage(cmp, repo, reader, //
- againstParent ? null : aCommit, b);
- for (int i = 0; i < cnt; i++) {
- FileHeader fh = df.toFileHeader(diffEntries.get(i));
- entries[1 + i] = newEntry(aTree, fh);
- }
- return new PatchList(a, b, againstParent, entries);
- } finally {
- reader.release();
- }
- }
-
- private PatchListEntry newCommitMessage(final RawTextComparator cmp,
- final Repository db, final ObjectReader reader,
- 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(db, reader, aCommit) : Text.EMPTY;
- Text bText = Text.forCommit(db, reader, bCommit);
-
- byte[] rawHdr = hdr.toString().getBytes("UTF-8");
- RawText aRawText = new RawText(aText.getContent());
- RawText bRawText = new RawText(bText.getContent());
- EditList edits = new HistogramDiff().diff(cmp, aRawText, bRawText);
- FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
- return new PatchListEntry(fh, edits);
- }
-
- private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader) {
- final FileMode oldMode = fileHeader.getOldMode();
- final FileMode newMode = fileHeader.getNewMode();
-
- if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
-
- if (aTree == null // want combined diff
- || fileHeader.getPatchType() != PatchType.UNIFIED
- || fileHeader.getHunks().isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- }
-
- List<Edit> edits = fileHeader.toEditList();
- if (edits.isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
- } else {
- return new PatchListEntry(fileHeader, edits);
- }
- }
-
- private static RevObject aFor(final PatchListKey key,
- final Repository repo, final RevWalk rw, final RevCommit b)
- throws IOException {
- if (key.getOldId() != null) {
- return rw.parseAny(key.getOldId());
- }
-
- switch (b.getParentCount()) {
- case 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;
- }
- }
-
- private static ObjectId emptyTree(final Repository repo) throws IOException {
- ObjectInserter oi = repo.newObjectInserter();
- try {
- ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
- oi.flush();
- return id;
- } finally {
- oi.release();
- }
- }
- }
-
- static class IntraLineLoader extends
- EntryCreator<IntraLineDiffKey, IntraLineDiff> {
- @Override
- public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
- List<Edit> edits = new ArrayList<Edit>(key.getEdits());
- Text aContent = key.getTextA();
- Text bContent = key.getTextB();
- combineLineEdits(edits, aContent, bContent);
-
- for (int i = 0; i < edits.size(); i++) {
- Edit e = edits.get(i);
-
- if (e.getType() == Edit.Type.REPLACE) {
- CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
- CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
- CharTextComparator cmp = new CharTextComparator();
-
- List<Edit> wordEdits = MyersDiff.INSTANCE.diff(cmp, a, b);
-
- // Combine edits that are really close together. If they are
- // just a few characters apart we tend to get better results
- // by joining them together and taking the whole span.
- //
- for (int j = 0; j < wordEdits.size() - 1;) {
- Edit c = wordEdits.get(j);
- Edit n = wordEdits.get(j + 1);
-
- if (n.getBeginA() - c.getEndA() <= 5
- || n.getBeginB() - c.getEndB() <= 5) {
- int ab = c.getBeginA();
- int ae = n.getEndA();
-
- int bb = c.getBeginB();
- int be = n.getEndB();
-
- if (canCoalesce(a, c.getEndA(), n.getBeginA())
- && canCoalesce(b, c.getEndB(), n.getBeginB())) {
- wordEdits.set(j, new Edit(ab, ae, bb, be));
- wordEdits.remove(j + 1);
- continue;
- }
- }
-
- j++;
- }
-
- // Apply some simple rules to fix up some of the edits. Our
- // logic above, along with our per-character difference tends
- // to produce some crazy stuff.
- //
- for (int j = 0; j < wordEdits.size(); j++) {
- Edit c = wordEdits.get(j);
- int ab = c.getBeginA();
- int ae = c.getEndA();
-
- int bb = c.getBeginB();
- int be = c.getEndB();
-
- // Sometimes the diff generator produces an INSERT or DELETE
- // right up against a REPLACE, but we only find this after
- // we've also played some shifting games on the prior edit.
- // If that happened to us, coalesce them together so we can
- // correct this mess for the user. If we don't we wind up
- // with silly stuff like "es" -> "es = Addresses".
- //
- if (1 < j) {
- Edit p = wordEdits.get(j - 1);
- if (p.getEndA() == ab || p.getEndB() == bb) {
- if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
- ab = p.getBeginA();
- }
- if (p.getEndB() == bb && p.getBeginB() < p.getEndB()) {
- bb = p.getBeginB();
- }
- wordEdits.remove(--j);
- }
- }
-
- // We sometimes collapsed an edit together in a strange way,
- // such that the edges of each text is identical. Fix by
- // by dropping out that incorrectly replaced region.
- //
- while (ab < ae && bb < be && cmp.equals(a, ab, b, bb)) {
- ab++;
- bb++;
- }
- while (ab < ae && bb < be && cmp.equals(a, ae - 1, b, be - 1)) {
- ae--;
- be--;
- }
-
- // The leading part of an edit and its trailing part in the same
- // text might be identical. Slide down that edit and use the tail
- // rather than the leading bit. If however the edit is only on a
- // whitespace block try to shift it to the left margin, assuming
- // that it is an indentation change.
- //
- boolean aShift = true;
- if (ab < ae && isOnlyWhitespace(a, ab, ae)) {
- int lf = findLF(wordEdits, j, a, ab);
- if (lf < ab && a.charAt(lf) == '\n') {
- int nb = lf + 1;
- int p = 0;
- while (p < ae - ab) {
- if (cmp.equals(a, ab + p, a, ab + p))
- p++;
- else
- break;
- }
- if (p == ae - ab) {
- ab = nb;
- ae = nb + p;
- aShift = false;
- }
- }
- }
- if (aShift) {
- while (0 < ab && ab < ae && a.charAt(ab - 1) != '\n'
- && cmp.equals(a, ab - 1, a, ae - 1)) {
- ab--;
- ae--;
- }
- if (!a.isLineStart(ab) || !a.contains(ab, ae, '\n')) {
- while (ab < ae && ae < a.size() && cmp.equals(a, ab, a, ae)) {
- ab++;
- ae++;
- if (a.charAt(ae - 1) == '\n') {
- break;
- }
- }
- }
- }
-
- boolean bShift = true;
- if (bb < be && isOnlyWhitespace(b, bb, be)) {
- int lf = findLF(wordEdits, j, b, bb);
- if (lf < bb && b.charAt(lf) == '\n') {
- int nb = lf + 1;
- int p = 0;
- while (p < be - bb) {
- if (cmp.equals(b, bb + p, b, bb + p))
- p++;
- else
- break;
- }
- if (p == be - bb) {
- bb = nb;
- be = nb + p;
- bShift = false;
- }
- }
- }
- if (bShift) {
- while (0 < bb && bb < be && b.charAt(bb - 1) != '\n'
- && cmp.equals(b, bb - 1, b, be - 1)) {
- bb--;
- be--;
- }
- if (!b.isLineStart(bb) || !b.contains(bb, be, '\n')) {
- while (bb < be && be < b.size() && cmp.equals(b, bb, b, be)) {
- bb++;
- be++;
- if (b.charAt(be - 1) == '\n') {
- break;
- }
- }
- }
- }
-
- // If most of a line was modified except the LF was common, make
- // the LF part of the modification region. This is easier to read.
- //
- if (ab < ae //
- && (ab == 0 || a.charAt(ab - 1) == '\n') //
- && ae < a.size() && a.charAt(ae) == '\n') {
- ae++;
- }
- if (bb < be //
- && (bb == 0 || b.charAt(bb - 1) == '\n') //
- && be < b.size() && b.charAt(be) == '\n') {
- be++;
- }
-
- wordEdits.set(j, new Edit(ab, ae, bb, be));
- }
-
- edits.set(i, new ReplaceEdit(e, wordEdits));
- }
- }
-
- return new IntraLineDiff(edits);
- }
-
- private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
- for (int j = 0; j < edits.size() - 1;) {
- Edit c = edits.get(j);
- Edit n = edits.get(j + 1);
-
- // Combine edits that are really close together. Right now our rule
- // is, coalesce two line edits which are only one line apart if that
- // common context line is either a "pointless line", or is identical
- // on both sides and starts a new block of code. These are mostly
- // block reindents to add or remove control flow operators.
- //
- final int ad = n.getBeginA() - c.getEndA();
- final int bd = n.getBeginB() - c.getEndB();
- if ((1 <= ad && isBlankLineGap(a, c.getEndA(), n.getBeginA()))
- || (1 <= bd && isBlankLineGap(b, c.getEndB(), n.getBeginB()))
- || (ad == 1 && bd == 1 && isControlBlockStart(a, c.getEndA()))) {
- int ab = c.getBeginA();
- int ae = n.getEndA();
-
- int bb = c.getBeginB();
- int be = n.getEndB();
-
- edits.set(j, new Edit(ab, ae, bb, be));
- edits.remove(j + 1);
- continue;
- }
-
- j++;
- }
- }
-
- private static boolean isBlankLineGap(Text a, int b, int e) {
- for (; b < e; b++) {
- if (!BLANK_LINE_RE.matcher(a.getString(b)).matches()) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isControlBlockStart(Text a, int idx) {
- final String l = a.getString(idx);
- return CONTROL_BLOCK_START_RE.matcher(l).find();
- }
-
- private static boolean canCoalesce(CharText a, int b, int e) {
- while (b < e) {
- if (a.charAt(b++) == '\n') {
- return false;
- }
- }
- return true;
- }
-
- private static int findLF(List<Edit> edits, int j, CharText t, int b) {
- int lf = b;
- int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
- while (limit < lf && t.charAt(lf) != '\n') {
- lf--;
- }
- return lf;
- }
-
- private static boolean isOnlyWhitespace(CharText t, final int b, final int e) {
- for (int c = b; c < e; c++) {
- if (!Character.isWhitespace(t.charAt(c))) {
- return false;
- }
- }
- return b < e;
+ return new IntraLineDiff(IntraLineDiff.Status.DISABLED);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
new file mode 100644
index 0000000..d75aec6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -0,0 +1,235 @@
+// Copyright (C) 2009 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.patch;
+
+import com.google.gerrit.reviewdb.Patch;
+import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+
+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.HistogramDiff;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+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;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ PatchListLoader(GitRepositoryManager mgr) {
+ repoManager = mgr;
+ }
+
+ @Override
+ public PatchList createEntry(final PatchListKey key) throws Exception {
+ final Repository repo = repoManager.openRepository(key.projectKey.get());
+ try {
+ return readPatchList(key, repo);
+ } finally {
+ repo.close();
+ }
+ }
+
+ private static RawTextComparator comparatorFor(Whitespace ws) {
+ switch (ws) {
+ case IGNORE_ALL_SPACE:
+ return RawTextComparator.WS_IGNORE_ALL;
+
+ case IGNORE_SPACE_AT_EOL:
+ return RawTextComparator.WS_IGNORE_TRAILING;
+
+ case IGNORE_SPACE_CHANGE:
+ return RawTextComparator.WS_IGNORE_CHANGE;
+
+ case IGNORE_NONE:
+ default:
+ return RawTextComparator.DEFAULT;
+ }
+ }
+
+ private PatchList readPatchList(final PatchListKey key,
+ final Repository repo) throws IOException {
+ // TODO(jeffschu) correctly handle merge commits
+
+ final RawTextComparator cmp = comparatorFor(key.getWhitespace());
+ final ObjectReader reader = repo.newObjectReader();
+ try {
+ final RevWalk rw = new RevWalk(reader);
+ 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(cmp, repo, reader, null, b);
+ return new PatchList(a, b, 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 bTree = b.getTree();
+
+ final TreeWalk walk = new TreeWalk(reader);
+ walk.reset();
+ walk.setRecursive(true);
+ walk.addTree(aTree);
+ walk.addTree(bTree);
+ walk.setFilter(TreeFilter.ANY_DIFF);
+
+ DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
+ df.setRepository(repo);
+ df.setDiffComparator(cmp);
+ df.setDetectRenames(true);
+ List<DiffEntry> diffEntries = df.scan(aTree, bTree);
+
+ final int cnt = diffEntries.size();
+ final PatchListEntry[] entries = new PatchListEntry[1 + cnt];
+ entries[0] = newCommitMessage(cmp, repo, reader, //
+ againstParent ? null : aCommit, b);
+ for (int i = 0; i < cnt; i++) {
+ FileHeader fh = df.toFileHeader(diffEntries.get(i));
+ entries[1 + i] = newEntry(aTree, fh);
+ }
+ return new PatchList(a, b, againstParent, entries);
+ } finally {
+ reader.release();
+ }
+ }
+
+ private PatchListEntry newCommitMessage(final RawTextComparator cmp,
+ final Repository db, final ObjectReader reader,
+ 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(db, reader, aCommit) : Text.EMPTY;
+ Text bText = Text.forCommit(db, reader, bCommit);
+
+ byte[] rawHdr = hdr.toString().getBytes("UTF-8");
+ RawText aRawText = new RawText(aText.getContent());
+ RawText bRawText = new RawText(bText.getContent());
+ EditList edits = new HistogramDiff().diff(cmp, aRawText, bRawText);
+ FileHeader fh = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
+ return new PatchListEntry(fh, edits);
+ }
+
+ private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader) {
+ final FileMode oldMode = fileHeader.getOldMode();
+ final FileMode newMode = fileHeader.getNewMode();
+
+ if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
+
+ if (aTree == null // want combined diff
+ || fileHeader.getPatchType() != PatchType.UNIFIED
+ || fileHeader.getHunks().isEmpty()) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ }
+
+ List<Edit> edits = fileHeader.toEditList();
+ if (edits.isEmpty()) {
+ return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ } else {
+ return new PatchListEntry(fileHeader, edits);
+ }
+ }
+
+ private static RevObject aFor(final PatchListKey key,
+ final Repository repo, final RevWalk rw, final RevCommit b)
+ throws IOException {
+ if (key.getOldId() != null) {
+ return rw.parseAny(key.getOldId());
+ }
+
+ switch (b.getParentCount()) {
+ case 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;
+ }
+ }
+
+ private static ObjectId emptyTree(final Repository repo) throws IOException {
+ ObjectInserter oi = repo.newObjectInserter();
+ try {
+ ObjectId id = oi.insert(Constants.OBJ_TREE, new byte[] {});
+ oi.flush();
+ return id;
+ } finally {
+ oi.release();
+ }
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index a0b9d25..cd87498 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -18,6 +18,8 @@
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
+import com.google.gerrit.reviewdb.Branch;
+import com.google.gerrit.reviewdb.Branch.NameKey;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
@@ -26,6 +28,7 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.MergeOp;
+import com.google.gerrit.server.git.MergeOp.Factory;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.CanSubmitResult;
@@ -51,6 +54,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
public class ReviewCommand extends BaseCommand {
private static final Logger log =
@@ -113,6 +117,8 @@
@Inject
private PublishComments.Factory publishCommentsFactory;
+ private Set<PatchSet.Id> toSubmit = new HashSet<PatchSet.Id>();
+
@Override
public final void start(final Environment env) {
startThread(new CommandRunnable() {
@@ -135,10 +141,42 @@
log.error("internal error while approving " + patchSetId, e);
}
}
+
if (!ok) {
throw new UnloggedFailure(1, "one or more approvals failed;"
+ " review output above");
}
+
+ if (!toSubmit.isEmpty()) {
+ final Set<Branch.NameKey> toMerge = new HashSet<Branch.NameKey>();
+ try {
+ for (PatchSet.Id patchSetId : toSubmit) {
+ ChangeUtil.submit(opFactory, patchSetId, currentUser, db,
+ new MergeQueue() {
+ @Override
+ public void merge(MergeOp.Factory mof, Branch.NameKey branch) {
+ toMerge.add(branch);
+ }
+
+ @Override
+ public void schedule(Branch.NameKey branch) {
+ toMerge.add(branch);
+ }
+
+ @Override
+ public void recheckAfter(Branch.NameKey branch, long delay,
+ TimeUnit delayUnit) {
+ toMerge.add(branch);
+ }
+ });
+ }
+ for (Branch.NameKey branch : toMerge) {
+ merger.merge(opFactory, branch);
+ }
+ } catch (OrmException updateError) {
+ throw new Failure(1, "one or more submits failed", updateError);
+ }
+ }
}
});
}
@@ -170,7 +208,7 @@
changeControl.canSubmit(patchSetId, db, approvalTypes,
functionStateFactory);
if (result == CanSubmitResult.OK) {
- ChangeUtil.submit(opFactory, patchSetId, currentUser, db, merger);
+ toSubmit.add(patchSetId);
} else {
throw error(result.getMessage());
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
index bcc9d19..95b33f7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -42,7 +42,7 @@
final UploadPack up = new UploadPack(repo);
if (!projectControl.allRefsAreVisible()) {
- up.setRefFilter(new VisibleRefFilter(repo, projectControl, db.get()));
+ up.setRefFilter(new VisibleRefFilter(repo, projectControl, db.get(), true));
}
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
diff --git a/pom.xml b/pom.xml
index 6bbf107..c76eab7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,14 +46,14 @@
</issueManagement>
<properties>
- <jgitVersion>0.9.3.197-g51bf8ea</jgitVersion>
+ <jgitVersion>0.9.3.298-g18abb81</jgitVersion>
<gwtormVersion>1.1.4</gwtormVersion>
<gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.2</gwtexpuiVersion>
<gwtVersion>2.0.4</gwtVersion>
<slf4jVersion>1.6.1</slf4jVersion>
<guiceVersion>2.0</guiceVersion>
- <jettyVersion>7.0.2.v20100331</jettyVersion>
+ <jettyVersion>7.2.1.v20101111</jettyVersion>
<keyappletVersion>1.0</keyappletVersion>
<gwt.soyc>false</gwt.soyc>