Add lists of all open, merged, abandoned changes on the server

To make pagination relatively efficient on the server side we use
a new "sort_key" column in the changes table.  This is a hex string
comprised of the pair lastUpdatedOn,changeId, creating a unique key
we can position in the index on.  Queries operate by performing a
range scan on the index, making them quite simple to execute.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-01-16 18:24:59 -08:00
parent d997d17d40
commit 3cd1a9e395
26 changed files with 569 additions and 35 deletions

View File

@@ -267,7 +267,7 @@ public class Gerrit implements EntryPoint {
if (isSignedIn()) {
History.newItem(Link.MINE);
} else {
History.newItem(Link.ALL);
History.newItem(Link.ALL_OPEN);
}
} else {
History.fireCurrentHistoryState();
@@ -298,8 +298,9 @@ public class Gerrit implements EntryPoint {
MenuBar m;
m = new MenuBar(true);
addLink(m, C.menuAllRecentChanges(), Link.ALL);
addLink(m, C.menuAllUnclaimedChanges(), Link.ALL_UNCLAIMED);
addLink(m, C.menuAllOpen(), Link.ALL_OPEN);
addLink(m, C.menuAllMerged(), Link.ALL_MERGED);
addLink(m, C.menuAllAbandoned(), Link.ALL_ABANDONED);
menuBar.addItem(C.menuAll(), m);
if (signedIn) {

View File

@@ -32,8 +32,9 @@ public interface GerritConstants extends Constants {
String nameAlreadyUsedBody();
String menuAll();
String menuAllUnclaimedChanges();
String menuAllRecentChanges();
String menuAllOpen();
String menuAllMerged();
String menuAllAbandoned();
String menuMine();
String menuMyChanges();

View File

@@ -13,8 +13,9 @@ notFoundBody = The page you requested was not found.
nameAlreadyUsedBody = The name is already in use.
menuAll = All
menuAllUnclaimedChanges = Unclaimed Changes
menuAllRecentChanges = Recent Changes
menuAllOpen = Open
menuAllMerged = Merged
menuAllAbandoned = Abandoned
menuMine = My
menuMyChanges = Changes

View File

@@ -22,6 +22,9 @@ import com.google.gerrit.client.admin.GroupListScreen;
import com.google.gerrit.client.admin.ProjectAdminScreen;
import com.google.gerrit.client.admin.ProjectListScreen;
import com.google.gerrit.client.changes.AccountDashboardScreen;
import com.google.gerrit.client.changes.AllAbandonedChangesScreen;
import com.google.gerrit.client.changes.AllMergedChangesScreen;
import com.google.gerrit.client.changes.AllOpenChangesScreen;
import com.google.gerrit.client.changes.ChangeScreen;
import com.google.gerrit.client.changes.MineDraftsScreen;
import com.google.gerrit.client.changes.MineStarredScreen;
@@ -54,8 +57,9 @@ public class Link implements HistoryListener {
public static final String MINE_STARRED = "mine,starred";
public static final String MINE_DRAFTS = "mine,drafts";
public static final String ALL = "all";
public static final String ALL_OPEN = "all,open";
public static final String ALL_ABANDONED = "all,abandoned,n,z";
public static final String ALL_MERGED = "all,merged,n,z";
public static final String ALL_OPEN = "all,open,n,z";
public static final String ALL_UNCLAIMED = "all,unclaimed";
public static final String ADMIN_PEOPLE = "admin,people";
@@ -140,6 +144,23 @@ public class Link implements HistoryListener {
}
}
if (token.startsWith("all,")) {
p = "all,abandoned,";
if (token.startsWith(p)) {
return new AllAbandonedChangesScreen(skip(p, token));
}
p = "all,merged,";
if (token.startsWith(p)) {
return new AllMergedChangesScreen(skip(p, token));
}
p = "all,open,";
if (token.startsWith(p)) {
return new AllOpenChangesScreen(skip(p, token));
}
}
if (token.startsWith("patch,")) {
p = "patch,sidebyside,";
if (token.startsWith(p))

View File

@@ -0,0 +1,46 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.SingleListChangeInfo;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.rpc.GerritCallback;
public class AllAbandonedChangesScreen extends AllSingleListScreen {
public AllAbandonedChangesScreen(final String positionToken) {
super(Util.C.allAbandonedChanges(), "all,abandoned", positionToken);
}
@Override
protected void loadPrev() {
Util.LIST_SVC.allClosedPrev(Change.Status.ABANDONED, pos, pageSize,
new GerritCallback<SingleListChangeInfo>() {
public void onSuccess(final SingleListChangeInfo result) {
display(result);
}
});
}
@Override
protected void loadNext() {
Util.LIST_SVC.allClosedNext(Change.Status.ABANDONED, pos, pageSize,
new GerritCallback<SingleListChangeInfo>() {
public void onSuccess(final SingleListChangeInfo result) {
display(result);
}
});
}
}

View File

@@ -0,0 +1,46 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.SingleListChangeInfo;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.rpc.GerritCallback;
public class AllMergedChangesScreen extends AllSingleListScreen {
public AllMergedChangesScreen(final String positionToken) {
super(Util.C.allMergedChanges(), "all,merged", positionToken);
}
@Override
protected void loadPrev() {
Util.LIST_SVC.allClosedPrev(Change.Status.MERGED, pos, pageSize,
new GerritCallback<SingleListChangeInfo>() {
public void onSuccess(final SingleListChangeInfo result) {
display(result);
}
});
}
@Override
protected void loadNext() {
Util.LIST_SVC.allClosedNext(Change.Status.MERGED, pos, pageSize,
new GerritCallback<SingleListChangeInfo>() {
public void onSuccess(final SingleListChangeInfo result) {
display(result);
}
});
}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.SingleListChangeInfo;
import com.google.gerrit.client.rpc.GerritCallback;
public class AllOpenChangesScreen extends AllSingleListScreen {
public AllOpenChangesScreen(final String positionToken) {
super(Util.C.allOpenChanges(), "all,open", positionToken);
}
@Override
protected void loadPrev() {
Util.LIST_SVC.allOpenPrev(pos, pageSize,
new GerritCallback<SingleListChangeInfo>() {
public void onSuccess(final SingleListChangeInfo result) {
display(result);
}
});
}
@Override
protected void loadNext() {
Util.LIST_SVC.allOpenNext(pos, pageSize,
new GerritCallback<SingleListChangeInfo>() {
public void onSuccess(final SingleListChangeInfo result) {
display(result);
}
});
}
}

View File

@@ -0,0 +1,121 @@
// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.ChangeInfo;
import com.google.gerrit.client.data.SingleListChangeInfo;
import com.google.gerrit.client.ui.Screen;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import java.util.List;
public abstract class AllSingleListScreen extends Screen {
protected static final String MIN_SORTKEY = "";
protected static final String MAX_SORTKEY = "z";
protected static final int pageSize = 25;
private ChangeTable table;
private ChangeTable.Section section;
protected Hyperlink prev;
protected Hyperlink next;
protected List<ChangeInfo> changes;
protected final String anchorPrefix;
protected boolean useLoadPrev;
protected String pos;
public AllSingleListScreen(final String title, final String anchorToken,
final String positionToken) {
super(title);
anchorPrefix = anchorToken;
useLoadPrev = positionToken.startsWith("p,");
pos = positionToken.substring(2);
}
@Override
public Object getScreenCacheToken() {
return anchorPrefix;
}
@Override
public Screen recycleThis(final Screen newScreen) {
final AllSingleListScreen o = (AllSingleListScreen) newScreen;
useLoadPrev = o.useLoadPrev;
pos = o.pos;
return this;
}
@Override
public void onLoad() {
if (table == null) {
table = new ChangeTable();
section = new ChangeTable.Section();
table.addSection(section);
table.setSavePointerId(anchorPrefix);
add(table);
prev = new Hyperlink(Util.C.pagedChangeListPrev(), true, "");
prev.setVisible(false);
next = new Hyperlink(Util.C.pagedChangeListNext(), true, "");
next.setVisible(false);
final HorizontalPanel buttons = new HorizontalPanel();
buttons.setStyleName("gerrit-ChangeTable-PrevNextLinks");
buttons.add(prev);
buttons.add(next);
add(buttons);
}
super.onLoad();
if (useLoadPrev) {
loadPrev();
} else {
loadNext();
}
}
protected abstract void loadPrev();
protected abstract void loadNext();
protected void display(final SingleListChangeInfo result) {
changes = result.getChanges();
if (!changes.isEmpty()) {
final ChangeInfo f = changes.get(0);
final ChangeInfo l = changes.get(changes.size() - 1);
prev.setTargetHistoryToken(anchorPrefix + ",p," + f.getSortKey());
next.setTargetHistoryToken(anchorPrefix + ",n," + l.getSortKey());
if (useLoadPrev) {
prev.setVisible(!result.isAtEnd());
next.setVisible(!MIN_SORTKEY.equals(pos));
} else {
prev.setVisible(!MAX_SORTKEY.equals(pos));
next.setVisible(!result.isAtEnd());
}
}
table.setAccountInfoCache(result.getAccounts());
section.display(result.getChanges());
table.finishDisplay(true);
}
}

View File

@@ -26,6 +26,9 @@ public interface ChangeConstants extends Constants {
String starredHeading();
String draftsHeading();
String allOpenChanges();
String allAbandonedChanges();
String allMergedChanges();
String changeTableColumnID();
String changeTableColumnSubject();
@@ -72,4 +75,7 @@ public interface ChangeConstants extends Constants {
String buttonPublishCommentsCancel();
String headingCoverMessage();
String headingPatchComments();
String pagedChangeListPrev();
String pagedChangeListNext();
}

View File

@@ -6,6 +6,9 @@ statusLongAbandoned = Abandoned
starredHeading = Starred Changes
draftsHeading = Changes with unpublished drafts
changesRecentlyClosed = Recently closed changes
allOpenChanges = All open changes
allAbandonedChanges = All abandoned changes
allMergedChanges = All merged changes
changeTableColumnID = ID
changeTableColumnSubject = Subject
@@ -52,3 +55,6 @@ buttonPublishCommentsSend = Publish Comments
buttonPublishCommentsCancel = Cancel
headingCoverMessage = Cover Message:
headingPatchComments = Patch Comments:
pagedChangeListPrev = &#x21e6;Prev
pagedChangeListNext = Next&#x21e8;

View File

@@ -26,6 +26,22 @@ import com.google.gwtjsonrpc.client.VoidResult;
import java.util.Set;
public interface ChangeListService extends RemoteJsonService {
/** Get all open changes more recent than pos, fetching at most limit rows. */
void allOpenPrev(String pos, int limit,
AsyncCallback<SingleListChangeInfo> callback);
/** Get all open changes older than pos, fetching at most limit rows. */
void allOpenNext(String pos, int limit,
AsyncCallback<SingleListChangeInfo> callback);
/** Get all closed changes more recent than pos, fetching at most limit rows. */
void allClosedPrev(Change.Status status, String pos, int limit,
AsyncCallback<SingleListChangeInfo> callback);
/** Get all closed changes older than pos, fetching at most limit rows. */
void allClosedNext(Change.Status status, String pos, int limit,
AsyncCallback<SingleListChangeInfo> callback);
/** Get the data to show {@link AccountDashboardScreen} for an account. */
void forAccount(Account.Id id, AsyncCallback<AccountDashboardInfo> callback);

View File

@@ -43,6 +43,56 @@ import java.util.Set;
public class ChangeListServiceImpl extends BaseServiceImplementation implements
ChangeListService {
private static final int MAX_PER_PAGE = 50;
private static int safePageSize(final int pageSize) {
return 0 < pageSize && pageSize <= MAX_PER_PAGE ? pageSize : MAX_PER_PAGE;
}
public void allOpenPrev(final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().allOpenPrev(sortKey, slim);
}
});
}
public void allOpenNext(final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().allOpenNext(sortKey, slim);
}
});
}
public void allClosedPrev(final Change.Status s, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
throws OrmException {
return db.changes().allClosedPrev(s.getCode(), key, lim);
}
});
}
public void allClosedNext(final Change.Status s, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
throws OrmException {
return db.changes().allClosedNext(s.getCode(), key, lim);
}
});
}
public void forAccount(final Account.Id id,
final AsyncCallback<AccountDashboardInfo> callback) {
final Account.Id me = Common.getAccountId();
@@ -77,15 +127,9 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
public void myStarredChanges(
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new Action<SingleListChangeInfo>() {
public SingleListChangeInfo run(final ReviewDb db)
throws OrmException, Failure {
public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
final Account.Id me = Common.getAccountId();
final AccountInfoCacheFactory ac = new AccountInfoCacheFactory(db);
final Account user = ac.get(me);
if (user == null) {
throw new Failure(new NoSuchEntityException());
}
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = starredBy(db, me);
d.setChanges(filter(db.changes().get(starred), starred, ac));
@@ -100,18 +144,11 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
});
}
public void myDraftChanges(
final AsyncCallback<SingleListChangeInfo> callback) {
public void myDraftChanges(final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new Action<SingleListChangeInfo>() {
public SingleListChangeInfo run(final ReviewDb db)
throws OrmException, Failure {
public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
final Account.Id me = Common.getAccountId();
final AccountInfoCacheFactory ac = new AccountInfoCacheFactory(db);
final Account user = ac.get(me);
if (user == null) {
throw new Failure(new NoSuchEntityException());
}
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = starredBy(db, me);
final Set<Change.Id> drafted = draftedBy(db, me);
@@ -207,4 +244,73 @@ public class ChangeListServiceImpl extends BaseServiceImplementation implements
}
return existing;
}
private abstract class QueryNext implements Action<SingleListChangeInfo> {
protected final String pos;
protected final int limit;
protected final int slim;
QueryNext(final int pageSize, final String pos) {
this.pos = pos;
this.limit = safePageSize(pageSize);
this.slim = limit + 1;
}
public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
final Account.Id me = Common.getAccountId();
final AccountInfoCacheFactory ac = new AccountInfoCacheFactory(db);
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = starredBy(db, me);
boolean results = true;
String sortKey = pos;
final ArrayList<ChangeInfo> list = new ArrayList<ChangeInfo>();
while (results && list.size() < slim) {
results = false;
final ResultSet<Change> rs = query(db, slim, sortKey);
for (final Change c : rs) {
results = true;
if (canRead(c)) {
final ChangeInfo ci = new ChangeInfo(c, ac);
ci.setStarred(starred.contains(ci.getId()));
list.add(ci);
if (list.size() == slim) {
rs.close();
break;
}
}
sortKey = c.getSortKey();
}
}
final boolean atEnd = finish(list);
d.setChanges(list, atEnd);
d.setAccounts(ac.create());
return d;
}
boolean finish(final ArrayList<ChangeInfo> list) {
final boolean atEnd = list.size() <= limit;
if (list.size() == slim) {
list.remove(limit);
}
return atEnd;
}
abstract ResultSet<Change> query(final ReviewDb db, final int slim,
String sortKey) throws OrmException;
}
private abstract class QueryPrev extends QueryNext {
QueryPrev(int pageSize, String pos) {
super(pageSize, pos);
}
@Override
boolean finish(final ArrayList<ChangeInfo> list) {
final boolean atEnd = super.finish(list);
Collections.reverse(list);
return atEnd;
}
}
}

View File

@@ -18,7 +18,6 @@ import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.Change;
import java.sql.Timestamp;
import java.util.Date;
public class ChangeInfo {
protected Change.Id id;
@@ -28,6 +27,7 @@ public class ChangeInfo {
protected ProjectInfo project;
protected boolean starred;
protected Timestamp lastUpdatedOn;
protected String sortKey;
protected ChangeInfo() {
}
@@ -39,6 +39,7 @@ public class ChangeInfo {
status = c.getStatus();
project = new ProjectInfo(c.getDest().getParentKey());
lastUpdatedOn = c.getLastUpdatedOn();
sortKey = c.getSortKey();
acc.want(owner);
}
@@ -71,7 +72,11 @@ public class ChangeInfo {
starred = s;
}
public Date getLastUpdatedOn() {
public java.sql.Timestamp getLastUpdatedOn() {
return lastUpdatedOn;
}
public String getSortKey() {
return sortKey;
}
}

View File

@@ -20,6 +20,7 @@ import java.util.List;
public class SingleListChangeInfo {
protected AccountInfoCache accounts;
protected List<ChangeInfo> changes;
protected boolean atEnd;
public SingleListChangeInfo() {
}
@@ -36,7 +37,16 @@ public class SingleListChangeInfo {
return changes;
}
public boolean isAtEnd() {
return atEnd;
}
public void setChanges(List<ChangeInfo> c) {
setChanges(c, true);
}
public void setChanges(List<ChangeInfo> c, boolean end) {
changes = c;
atEnd = end;
}
}

View File

@@ -118,6 +118,10 @@ public final class Change {
@Column
protected Timestamp lastUpdatedOn;
/** A {@link #lastUpdatedOn} ASC,{@link #changeId} ASC for sorting. */
@Column(length = 16)
protected String sortKey;
@Column(name = "owner_account_id")
protected Account.Id owner;
@@ -174,10 +178,18 @@ public final class Change {
return lastUpdatedOn;
}
public void updated() {
public void resetLastUpdatedOn() {
lastUpdatedOn = new Timestamp(System.currentTimeMillis());
}
public String getSortKey() {
return sortKey;
}
public void setSortKey(final String newSortKey) {
sortKey = newSortKey;
}
public Account.Id getOwner() {
return owner;
}

View File

@@ -33,4 +33,18 @@ public interface ChangeAccess extends Access<Change, Change.Id> {
@Query("WHERE dest = ? AND status = '" + Change.STATUS_SUBMITTED
+ "' ORDER BY lastUpdatedOn")
ResultSet<Change> submitted(Branch.NameKey dest) throws OrmException;
@Query("WHERE open = true AND sortKey > ? ORDER BY sortKey LIMIT ?")
ResultSet<Change> allOpenPrev(String sortKey, int limit) throws OrmException;
@Query("WHERE open = true AND sortKey < ? ORDER BY sortKey DESC LIMIT ?")
ResultSet<Change> allOpenNext(String sortKey, int limit) throws OrmException;
@Query("WHERE open = false AND status = ? AND sortKey > ? ORDER BY sortKey LIMIT ?")
ResultSet<Change> allClosedPrev(char status, String sortKey, int limit)
throws OrmException;
@Query("WHERE open = false AND status = ? AND sortKey < ? ORDER BY sortKey DESC LIMIT ?")
ResultSet<Change> allClosedNext(char status, String sortKey, int limit)
throws OrmException;
}

View File

@@ -55,7 +55,7 @@ public class Screen extends FlowPanel {
/** Invoked if this screen is the current screen and the user signs out. */
public void onSignOut() {
if (isRequiresSignIn()) {
History.newItem(Link.ALL);
History.newItem(Link.ALL_OPEN);
}
}

View File

@@ -457,7 +457,7 @@ public class MergeOp {
final PatchSet.Id merged = c.currentPatchSetId();
for (int attempts = 0; attempts < 10; attempts++) {
c.setStatus(Change.Status.MERGED);
c.updated();
ChangeUtil.updated(c);
try {
final Transaction txn = schema.beginTransaction();
schema.changes().update(Collections.singleton(c), txn);
@@ -484,7 +484,7 @@ public class MergeOp {
private void setNew(Change c, ChangeMessage msg) {
for (int attempts = 0; attempts < 10; attempts++) {
c.setStatus(Change.Status.NEW);
c.updated();
ChangeUtil.updated(c);
try {
final Transaction txn = schema.beginTransaction();
schema.changes().update(Collections.singleton(c), txn);

View File

@@ -27,6 +27,7 @@ import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.git.InvalidRepositoryException;
import com.google.gerrit.git.PatchSetImporter;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritServer;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
@@ -107,6 +108,25 @@ public class ImportGerrit1 {
srcs.close();
pm.endTask();
// Compute the sort keys for change entities
//
srcs = query.executeQuery("SELECT change_id FROM changes");
final ArrayList<Change.Id> changesToDo = new ArrayList<Change.Id>();
while (srcs.next()) {
final Change.Id changeId = new Change.Id(srcs.getInt(1));
changesToDo.add(changeId);
}
srcs.close();
pm.start(1);
pm.beginTask("Compute sort keys", changesToDo.size());
for (final Change.Id changeId : changesToDo) {
final Change c = db.changes().get(changeId);
ChangeUtil.computeSortKey(c);
db.changes().update(Collections.singleton(c));
pm.update(1);
}
pm.endTask();
// Rebuild the cached PatchSet information directly from Git.
// There's some oddities in the Gerrit 1 data that we got from
// Google App Engine's data store; the quickest way to fix it

View File

@@ -264,6 +264,19 @@
padding-left: 25px;
}
.gerrit-ChangeTable-PrevNextLinks {
float: right;
padding-right: 5px;
}
.gerrit-ChangeTable-PrevNextLinks td {
width: 5em;
text-align: right;
}
.gerrit-ChangeTable-PrevNextLinks .gwt-Hyperlink {
font-size: 9pt;
color: #2a5db0;
}
/** PatchContentTable **/
.gerrit-PatchContentTable {

View File

@@ -108,7 +108,7 @@ public class ChangeManageServiceImpl extends BaseServiceImplementation
if (ApprovalCategory.SUBMIT.equals(actionType.getCategory().getId())) {
if (change.getStatus() == Change.Status.NEW) {
change.setStatus(Change.Status.SUBMITTED);
change.updated();
ChangeUtil.updated(change);
}
} else {
throw new Failure(new IllegalArgumentException(actionType

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
@@ -47,4 +48,37 @@ public class ChangeUtil {
NB.encodeInt32(raw, 0, uuidPrefix);
NB.encodeInt32(raw, 4, uuidSeq--);
}
public static void updated(final Change c) {
c.resetLastUpdatedOn();
computeSortKey(c);
}
public static void computeSortKey(final Change c) {
// The encoding uses minutes since Wed Oct 1 00:00:00 2008 UTC.
// We overrun approximately 4,085 years later, so ~6093.
//
final long lastUpdatedOn =
(c.getLastUpdatedOn().getTime() / 1000L) - 1222819200L;
final StringBuilder r = new StringBuilder(16);
r.setLength(16);
formatHexInt(r, 0, (int) (lastUpdatedOn / 60));
formatHexInt(r, 8, c.getId().get());
c.setSortKey(r.toString());
}
private static final char[] hexchar =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
'a', 'b', 'c', 'd', 'e', 'f'};
private static void formatHexInt(final StringBuilder dst, final int p, int w) {
int o = p + 7;
while (o >= p && w != 0) {
dst.setCharAt(o--, hexchar[w & 0xf]);
w >>>= 4;
}
while (o >= p) {
dst.setCharAt(o--, '0');
}
}
}

View File

@@ -60,8 +60,8 @@ public class UrlRewriteFilter implements Filter {
staticLinks.put("/unclaimed", Link.MINE_UNCLAIMED);
staticLinks.put("/starred", Link.MINE_STARRED);
staticLinks.put("/all", Link.ALL);
staticLinks.put("/all_unclaimed", Link.ALL_UNCLAIMED);
staticLinks.put("/all", Link.ALL_MERGED);
staticLinks.put("/open", Link.ALL_OPEN);
staticExtensions = new HashSet<String>();

View File

@@ -390,7 +390,7 @@ public class PatchDetailServiceImpl extends BaseServiceImplementation implements
db.changeMessages().insert(Collections.singleton(r.message), txn);
}
r.change.updated();
ChangeUtil.updated(r.change);
db.changes().update(Collections.singleton(r.change), txn);
return r;
}

View File

@@ -580,7 +580,7 @@ class Receive extends AbstractGitCommand {
change.setStatus(Change.Status.NEW);
change.setCurrentPatchSet(imp.getPatchSetInfo());
change.updated();
ChangeUtil.updated(change);
db.changes().update(Collections.singleton(change), txn);
return ps;
}