Merge branch 'stable-2.5'

* stable-2.5:
  NPE when strategy is cherry-pick and changeMerge.test enabled
  Fix owner column to link to same status
  Sort groups on the group list screen
  Documentation: Add link to test-submit-rule from the prolog cookbook.
  Do not log RepositoryNotFoundException if non-existing project is accessed
  Documentation: Add submit rule example on how to implement 1+1=2
  Fix OutOfScope exception when auditing ssh auth failure.
  Fix a NPE caused by GerritCall.getMethod returning null.
  Add org.apache.mina as dependency to pom.xml

Conflicts:
	gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java

Change-Id: Id280ca6afebbe299e5841ddf8850184714f70de7
This commit is contained in:
Shawn O. Pearce 2012-10-25 13:29:29 -07:00
commit 6738e5f340
9 changed files with 104 additions and 35 deletions

View File

@ -48,10 +48,8 @@ For interactive testing and playing with Prolog, Gerrit provides the
link:pgm-prolog-shell.html[prolog-shell] program which opens an interactive
Prolog interpreter shell.
NOTE: It is currently *not possible* to test a Prolog program which implements
Gerrit submit rules using the link:pgm-prolog-shell.html[prolog-shell] program.
The reason is that the Prolog environment that exposes facts about a change
requires a lot of Gerrit server environment to be loaded and running.
NOTE: The interactive shell is just a prolog shell, it does not load
a gerrit server environment and thus is not intended for xref:TestingSubmitRules[testing submit rules].
SWI-Prolog
----------
@ -249,6 +247,18 @@ NOTE: If `MyProject` doesn't define its own `submit_rule` Gerrit will invoke the
default implementation of submit rule that is named `gerrit:default_submit` and
its result will be filtered as described above.
[[TestingSubmitRules]]
Testing submit rules
--------------------
The prolog environment running the `submit_rule` is loaded with state describing the
change that is being evaluated. The easiest way to load this state is to test your
`submit_rule` against a real change on a running gerrit instance. The command
link:cmd-test-submit-rule.html[test-submit-rule] loads a specific change and executes
the `submit_rule`. It optionally reads the rule from from `stdin` to facilitate easy testing.
====
cat rules.pl | ssh gerrit_srv gerrit test-submit-rule I45e080b105a50a625cc8e1fb5b357c0bfabe6d68 -s
====
Prolog vs Gerrit plugin for project specific submit rules
---------------------------------------------------------
@ -638,6 +648,42 @@ we have to do that in the `rules.pl` of the `All-Projects` project.
remove_verified_category([H|T], [H|R]) :- remove_verified_category(T, R).
====
Example 12: 1+1=2 Code-Review
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this example we introduce accumulative voting to determine if a change is
submittable or not. We modify the standard Code-Review to be accumulative, and make the
change submittable if the total score is 2 or higher.
The code in this example is very similar to Example 8, with the addition of findall/3
and gerrit:remove_label.
The findall/3 embedded predicate is used to form a list of all objects that satisfy a
specified Goal. In this example it is used to get a list of all the 'Code-Review' scores.
gerrit:remove_label is a built-in helper that is implemented similarly to the
'remove_verified_category' as seen in the previous example.
.rules.pl
[caption=""]
====
sum_list([], 0).
sum_list([H | Rest], Sum) :- sum_list(Rest,Tmp), Sum is H + Tmp.
add_category_min_score(In, Category, Min, P) :-
findall(X, gerrit:commit_label(label(Category,X),R),Z),
sum_list(Z, Sum),
Sum >= Min, !,
P = [label(Category,ok(R)) | In].
add_category_min_score(In, Category,Min,P) :-
P = [label(Category,need(Min)) | In].
submit_rule(S) :-
gerrit:default_submit(X),
X =.. [submit | Ls],
gerrit:remove_label(Ls,label('Code-Review',_),NoCR),
add_category_min_score(NoCR,'Code-Review', 2, Labels),
S =.. [submit | Labels].
====
GERRIT
------
Part of link:index.html[Gerrit Code Review]

View File

@ -60,8 +60,11 @@ public class PageLinks {
}
public static String toAccountQuery(final String fullname) {
String query = op("owner", fullname) + " status:open";
return toChangeQuery(query, TOP);
return toAccountQuery(fullname, Status.NEW);
}
public static String toAccountQuery(String fullname, Status status) {
return toChangeQuery(op("owner", fullname) + " " + status(status), TOP);
}
public static String toChangeQuery(final String query) {
@ -73,17 +76,19 @@ public class PageLinks {
}
public static String projectQuery(Project.NameKey proj, Status status) {
return status(status) + " " + op("project", proj.get());
}
private static String status(Status status) {
switch (status) {
case ABANDONED:
return "status:abandoned " + op("project", proj.get());
return "status:abandoned";
case MERGED:
return "status:merged " + op("project", proj.get());
return "status:merged";
case NEW:
case SUBMITTED:
default:
return "status:open " + op("project", proj.get());
return "status:open";
}
}

View File

@ -212,7 +212,7 @@ public class ChangeTable2 extends NavigationTable<ChangeInfo> {
}
table.setWidget(row, C_OWNER, new InlineHyperlink(owner,
PageLinks.toAccountQuery(owner)));
PageLinks.toAccountQuery(owner, c.status())));
table.setWidget(
row, C_PROJECT, new ProjectLink(c.project_name_key(), c.status()));

View File

@ -125,13 +125,17 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
private void audit() {
try {
GerritCall call = currentCall.get();
Audit note = (Audit) call.getMethod().getAnnotation(Audit.class);
MethodHandle method = call.getMethod();
if (method == null) {
return;
}
Audit note = (Audit) method.getAnnotation(Audit.class);
if (note != null) {
final String sid = call.getWebSession().getToken();
final CurrentUser username = call.getWebSession().getCurrentUser();
final List<Object> args =
extractParams(note, call);
final String what = extractWhat(note, call.getMethod().getName());
final String what = extractWhat(note, method.getName());
final Object result = call.getResult();
audit.dispatch(new AuditEvent(sid, username, what, call.getWhen(), args,

View File

@ -14,6 +14,8 @@
package com.google.gerrit.server.account;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.GroupList;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
@ -24,10 +26,10 @@ import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
public class VisibleGroups {
@ -65,8 +67,7 @@ public class VisibleGroups {
public GroupList get(final Collection<ProjectControl> projects)
throws NoSuchGroupException {
final Set<AccountGroup> groups =
new TreeSet<AccountGroup>(new GroupComparator());
Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap();
for (final ProjectControl projectControl : projects) {
final Set<GroupReference> groupsRefs = projectControl.getAllGroups();
for (final GroupReference groupRef : groupsRefs) {
@ -74,10 +75,10 @@ public class VisibleGroups {
if (group == null) {
throw new NoSuchGroupException(groupRef.getUUID());
}
groups.add(group);
groups.put(group.getGroupUUID(), group);
}
}
return createGroupList(filterGroups(groups));
return createGroupList(filterGroups(groups.values()));
}
/**
@ -89,17 +90,15 @@ public class VisibleGroups {
public GroupList get(final IdentifiedUser user) throws NoSuchGroupException {
if (identifiedUser.get().getAccountId().equals(user.getAccountId())
|| identifiedUser.get().getCapabilities().canAdministrateServer()) {
final Set<AccountGroup.UUID> effective =
user.getEffectiveGroups().getKnownGroups();
final Set<AccountGroup> groups =
new TreeSet<AccountGroup>(new GroupComparator());
for (final AccountGroup.UUID groupId : effective) {
Set<AccountGroup.UUID> mine = user.getEffectiveGroups().getKnownGroups();
Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap();
for (final AccountGroup.UUID groupId : mine) {
AccountGroup group = groupCache.get(groupId);
if (group != null) {
groups.add(group);
groups.put(groupId, group);
}
}
return createGroupList(filterGroups(groups));
return createGroupList(filterGroups(groups.values()));
} else {
throw new NoSuchGroupException("Groups of user '" + user.getAccountId()
+ "' are not visible.");
@ -107,7 +106,7 @@ public class VisibleGroups {
}
private List<AccountGroup> filterGroups(final Iterable<AccountGroup> groups) {
final List<AccountGroup> filteredGroups = new LinkedList<AccountGroup>();
final List<AccountGroup> filteredGroups = Lists.newArrayList();
final boolean isAdmin =
identifiedUser.get().getCapabilities().canAdministrateServer();
for (final AccountGroup group : groups) {
@ -123,6 +122,7 @@ public class VisibleGroups {
}
filteredGroups.add(group);
}
Collections.sort(filteredGroups, new GroupComparator());
return filteredGroups;
}

View File

@ -28,6 +28,7 @@ import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -116,7 +117,9 @@ public class ProjectCacheImpl implements ProjectCache {
}
return state;
} catch (ExecutionException e) {
log.warn(String.format("Cannot read project %s", projectName.get()), e);
if (!(e.getCause() instanceof RepositoryNotFoundException)) {
log.warn(String.format("Cannot read project %s", projectName.get()), e);
}
return null;
}
}

View File

@ -43,6 +43,11 @@ limitations under the License.
<artifactId>org.eclipse.jgit.junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>

View File

@ -101,7 +101,7 @@ class SshLog implements LifecycleListener {
void onLogin() {
async.append(log("LOGIN FROM " + session.get().getRemoteAddressAsString()));
audit("0", "LOGIN", new String[] {});
audit(context.get(), "0", "LOGIN", new String[] {});
}
void onAuthFail(final SshSession sd) {
@ -127,7 +127,7 @@ class SshLog implements LifecycleListener {
}
async.append(event);
audit("FAIL", "AUTH", new String[] {sd.getRemoteAddressAsString()});
audit(null, "FAIL", "AUTH", new String[] {sd.getRemoteAddressAsString()});
}
void onExecute(int exitValue) {
@ -165,7 +165,8 @@ class SshLog implements LifecycleListener {
event.setProperty(P_STATUS, status);
async.append(event);
audit(status, getCommand(commandLine), CommandFactoryProvider.split(commandLine));
audit(context.get(), status, getCommand(commandLine),
CommandFactoryProvider.split(commandLine));
}
private String getCommand(String commandLine) {
@ -176,7 +177,7 @@ class SshLog implements LifecycleListener {
void onLogout() {
async.append(log("LOGOUT"));
audit("0", "LOGOUT", new String[] {});
audit(context.get(), "0", "LOGOUT", new String[] {});
}
private LoggingEvent log(final String msg) {
@ -415,8 +416,7 @@ class SshLog implements LifecycleListener {
}
}
void audit(Object result, String commandName, String[] args) {
final Context ctx = context.get();
void audit(Context ctx, Object result, String commandName, String[] args) {
final String sid = extractSessionId(ctx);
final long created = extractCreated(ctx);
final String what = extractWhat(commandName, args);

View File

@ -559,6 +559,12 @@ limitations under the License.
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>