/changes/: Return more data

Allow more data to be provided in the JSON results from /changes/,
including all revisions of the change (aka patch sets), commit
message data, files, and URLs to fetch the revisions with.

Revisions are indexed by the commit SHA-1, as a commit can only
really appear once per change. The current_revision field points
to the revision structure that is the most recent for this change.

Within a revision structure the fetch map provides a set of URLs and
the corresponding reference that should point to the commit. This can
be used to fetch the change over the Git protocols. All available URL
permutations are made by the server rather than the client, giving the
server more flexibility in the long term to move changes around. This
should one day support moving closed changes out to archive
repositories with different reference names.

$ curl 'http://127.0.0.1:8080/changes/?q=97&format=JSON&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES'
)]}'
[
  {
    "project": "gerrit",
    "branch": "master",
    "id": "I7ea46d2e2ee5c64c0d807677859cfb7d90b8966a",
    "subject": "Use an EventBus to manage star icons",
    "status": "NEW",
    "created": "2012-04-25 00:52:25.580000000",
    "updated": "2012-04-25 00:52:25.586000000",
    "_sortkey": "001c9bf400000061",
    "_number": 97,
    "owner": {
      "name": "Shawn Pearce"
    },
    "current_revision": "184ebe53805e102605d11f6b143486d15c23a09c",
    "revisions": {
      "184ebe53805e102605d11f6b143486d15c23a09c": {
        "_number": 1,
        "fetch": {
          "git": {
            "url": "git://localhost/gerrit",
            "ref": "refs/changes/97/97/1"
          },
          "http": {
            "url": "http://127.0.0.1:8080/gerrit",
            "ref": "refs/changes/97/97/1"
          }
        },
        "commit": {
          "parents": [
            {
              "commit": "1eee2c9d8f352483781e772f35dc586a69ff5646",
              "subject": "Migrate contributor agreements to All-Projects."
            }
          ],
          "author": {
            "name": "Shawn O. Pearce",
            "email": "sop@google.com",
            "date": "2012-04-24 18:08:08.000000000",
            "tz": -420
          },
          "committer": {
            "name": "Shawn O. Pearce",
            "email": "sop@google.com",
            "date": "2012-04-24 18:08:08.000000000",
            "tz": -420
          },
          "subject": "Use an EventBus to manage star icons",
          "message": "Use an EventBus to manage star icons\n\nImage widgets that need to ..."
        },
        "files": {
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java": {
            "lines_deleted": 8
          },
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java": {
            "lines_inserted": 1
          },
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java": {
            "lines_inserted": 11,
            "lines_deleted": 19
          },
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java": {
            "lines_inserted": 23,
            "lines_deleted": 20
          },
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java": {
            "status": "D",
            "lines_deleted": 139
          },
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java": {
            "status": "A",
            "lines_inserted": 204
          },
          "gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java": {
            "lines_deleted": 9
          }
        }
      }
    }
  }
]

Change-Id: I9589cc46b3dfcc43e799fdd40ee8d6c292c4f17b
This commit is contained in:
Shawn O. Pearce 2012-04-07 19:48:25 -07:00
parent 1071ec0015
commit 423c4a972c
6 changed files with 514 additions and 20 deletions

View File

@ -197,10 +197,6 @@ Query for open changes of watched projects:
"owner": {
"name": "John Doe"
},
"labels": {
"Verified": {},
"Code-Review": {}
}
},
{
"project": "demo",
@ -215,10 +211,6 @@ Query for open changes of watched projects:
"owner": {
"name": "John Doe"
},
"labels": {
"Verified": {},
"Code-Review": {}
},
"_more_changes": true
}
----
@ -240,7 +232,7 @@ arrays, one per query in the same order the queries were given in.
Query that retrieves changes for a user's dashboard:
----
GET /changes/?format=JSON&q=is:open+owner:self&q=is:open+reviewer:self+-owner:self&q=is:closed+owner:self+limit:5 HTTP/1.0
GET /changes/?format=JSON&q=is:open+owner:self&q=is:open+reviewer:self+-owner:self&q=is:closed+owner:self+limit:5&o=LABELS HTTP/1.0
HTTP/1.1 200 OK
Content-Disposition: attachment
@ -274,6 +266,127 @@ Query that retrieves changes for a user's dashboard:
]
----
Additional fields can be obtained by adding `o` parameters, each
option requires more database lookups and slows down the query
response time to the client so they are generally disabled by
default. Optional fields are:
* `LABELS`: a summary of each label required for submit, and
approvers that have granted (or rejected) with that label.
* `CURRENT_REVISION`: describe the current revision (patch set)
of the change, including the commit SHA-1 and URLs to fetch from.
* `ALL_REVISIONS`: describe all revisions, not just current.
* `CURRENT_COMMIT`: parse and output all header fields from the
commit object, including message. Only valid when the current
revision or all revisions are selected.
* `ALL_COMMITS`: parse and output all header fields from the
output revisions. If only `CURRENT_REVISION` was requested
then only the current revision's commit data will be output.
* `CURRENT_FILES`: list files modified by the commit, including
basic line counts inserted/deleted per file. Only valid when
the current revision or all revisions are selected.
* `ALL_FILES`: list files modified by the commit, including
basic line counts inserted/deleted per file. If only the
`CURRENT_REVISION` was requested the only that commit's
modified files will be output.
----
GET /changes/?q=97&format=JSON&o=CURRENT_REVISION&o=CURRENT_COMMIT&o=CURRENT_FILES HTTP/1.0
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
[
{
"project": "gerrit",
"branch": "master",
"id": "I7ea46d2e2ee5c64c0d807677859cfb7d90b8966a",
"subject": "Use an EventBus to manage star icons",
"status": "NEW",
"created": "2012-04-25 00:52:25.580000000",
"updated": "2012-04-25 00:52:25.586000000",
"_sortkey": "001c9bf400000061",
"_number": 97,
"owner": {
"name": "Shawn Pearce"
},
"current_revision": "184ebe53805e102605d11f6b143486d15c23a09c",
"revisions": {
"184ebe53805e102605d11f6b143486d15c23a09c": {
"_number": 1,
"fetch": {
"git": {
"url": "git://localhost/gerrit",
"ref": "refs/changes/97/97/1"
},
"http": {
"url": "http://127.0.0.1:8080/gerrit",
"ref": "refs/changes/97/97/1"
}
},
"commit": {
"parents": [
{
"commit": "1eee2c9d8f352483781e772f35dc586a69ff5646",
"subject": "Migrate contributor agreements to All-Projects."
}
],
"author": {
"name": "Shawn O. Pearce",
"email": "sop@google.com",
"date": "2012-04-24 18:08:08.000000000",
"tz": -420
},
"committer": {
"name": "Shawn O. Pearce",
"email": "sop@google.com",
"date": "2012-04-24 18:08:08.000000000",
"tz": -420
},
"subject": "Use an EventBus to manage star icons",
"message": "Use an EventBus to manage star icons\n\nImage widgets that need to ..."
},
"files": {
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeCache.java": {
"lines_deleted": 8
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeDetailCache.java": {
"lines_inserted": 1
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java": {
"lines_inserted": 11,
"lines_deleted": 19
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable.java": {
"lines_inserted": 23,
"lines_deleted": 20
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarCache.java": {
"status": "D",
"lines_deleted": 139
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/StarredChanges.java": {
"status": "A",
"lines_inserted": 204
},
"gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/Screen.java": {
"lines_deleted": 9
}
}
}
}
}
]
----
GERRIT
------

View File

@ -0,0 +1,65 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.common.changes;
import java.util.EnumSet;
/** Output options available when using {@code /changes/} RPCs. */
public enum ListChangesOption {
LABELS(0),
/** Return information on the current patch set of the change. */
CURRENT_REVISION(1),
ALL_REVISIONS(2),
/** If revisions are included, parse the commit object. */
CURRENT_COMMIT(3),
ALL_COMMITS(4),
/** If a patch set is included, include the files of the patch set. */
CURRENT_FILES(5),
ALL_FILES(6);
private final int value;
private ListChangesOption(int v) {
this.value = v;
}
public static EnumSet<ListChangesOption> fromBits(int v) {
EnumSet<ListChangesOption> r = EnumSet.noneOf(ListChangesOption.class);
for (ListChangesOption o : ListChangesOption.values()) {
if ((v & (1 << o.value)) != 0) {
r.add(o);
v &= ~(1 << o.value);
}
if (v == 0) {
return r;
}
}
if (v != 0) {
throw new IllegalArgumentException("unknown " + Integer.toHexString(v));
}
return r;
}
public static int toBits(EnumSet<ListChangesOption> set) {
int r = 0;
for (ListChangesOption o : set) {
r |= 1 << o.value;
}
return r;
}
}

View File

@ -16,9 +16,12 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.rpc.NativeList;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.common.changes.ListChangesOption;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
import java.util.EnumSet;
/** List of changes available from {@code /changes/}. */
public class ChangeList extends NativeList<ChangeInfo> {
private static final String URI = "/changes/";
@ -31,6 +34,7 @@ public class ChangeList extends NativeList<ChangeInfo> {
for (String q : queries) {
call.addParameterRaw("q", KeyUtil.encode(q));
}
addOptions(call, ListChangesOption.LABELS);
call.send(callback);
}
@ -41,6 +45,7 @@ public class ChangeList extends NativeList<ChangeInfo> {
if (limit > 0) {
call.addParameter("n", limit);
}
addOptions(call, ListChangesOption.LABELS);
if (!PagedSingleListScreen.MIN_SORTKEY.equals(sortkey)) {
call.addParameter("P", sortkey);
}
@ -54,12 +59,19 @@ public class ChangeList extends NativeList<ChangeInfo> {
if (limit > 0) {
call.addParameter("n", limit);
}
addOptions(call, ListChangesOption.LABELS);
if (!PagedSingleListScreen.MAX_SORTKEY.equals(sortkey)) {
call.addParameter("N", sortkey);
}
call.send(callback);
}
private static void addOptions(
RestApi call, ListChangesOption option1, ListChangesOption... options) {
EnumSet<ListChangesOption> s = EnumSet.of(option1, options);
call.addParameterRaw("O", Integer.toHexString(ListChangesOption.toBits(s)));
}
private static RestApi newQuery(String query) {
RestApi call = new RestApi(URI);
// The server default is ?q=status:open so don't repeat it.

View File

@ -80,6 +80,10 @@ public class RestApi {
return addParameterRaw(name, String.valueOf(value));
}
public RestApi addParameter(String name, Enum<?> value) {
return addParameterRaw(name, value.name());
}
public RestApi addParameterRaw(String name, String value) {
if (hasQueryParams) {
url.append("&");

View File

@ -82,7 +82,10 @@ public final class Patch {
RENAMED('R'),
/** Path was copied from {@link Patch#getSourceFileName()}. */
COPIED('C');
COPIED('C'),
/** Sufficient amount of content changed to claim the file was written. */
REWRITE('W');
private final char code;

View File

@ -14,31 +14,60 @@
package com.google.gerrit.server.query.change;
import static com.google.gerrit.common.changes.ListChangesOption.ALL_COMMITS;
import static com.google.gerrit.common.changes.ListChangesOption.ALL_FILES;
import static com.google.gerrit.common.changes.ListChangesOption.ALL_REVISIONS;
import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_COMMIT;
import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_FILES;
import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.common.changes.ListChangesOption.LABELS;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.common.changes.ListChangesOption;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.PatchSetInfo.ParentInfo;
import com.google.gerrit.reviewdb.client.UserIdentity;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.AccountAttribute;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.jcraft.jsch.HostKey;
import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.Writer;
@ -46,17 +75,47 @@ import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
public class ListChanges {
private static final Logger log = LoggerFactory.getLogger(ListChanges.class);
@Singleton
static class Urls {
final String git;
final String http;
@Inject
Urls(@GerritServerConfig Config cfg) {
this.git = ensureSlash(cfg.getString("gerrit", null, "canonicalGitUrl"));
this.http = ensureSlash(cfg.getString("gerrit", null, "gitHttpUrl"));
}
private static String ensureSlash(String in) {
if (in != null && !in.endsWith("/")) {
return in + "/";
}
return in;
}
}
private final QueryProcessor imp;
private final Provider<ReviewDb> db;
private final ApprovalTypes approvalTypes;
private final CurrentUser user;
private final AnonymousUser anonymous;
private final ChangeControl.Factory changeControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final PatchListCache patchListCache;
private final SshInfo sshInfo;
private final Provider<String> urlProvider;
private final Urls urls;
private boolean reverse;
private Map<Account.Id, AccountAttribute> accounts;
private Map<Change.Id, ChangeControl> controls;
private EnumSet<ListChangesOption> options;
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
private OutputFormat format = OutputFormat.TEXT;
@ -69,6 +128,16 @@ public class ListChanges {
imp.setLimit(limit);
}
@Option(name = "-o", multiValued = true, usage = "Output options per change")
void addOption(ListChangesOption o) {
options.add(o);
}
@Option(name = "-O", usage = "Output option flags, in hex")
void setOptionFlagsHex(String hex) {
options.addAll(ListChangesOption.fromBits(Integer.parseInt(hex, 16)));
}
@Option(name = "-P", metaVar = "SORTKEY", usage = "Previous changes before SORTKEY")
void setSortKeyAfter(String key) {
// Querying for the prior page of changes requires sortkey_after predicate.
@ -91,14 +160,28 @@ public class ListChanges {
Provider<ReviewDb> db,
ApprovalTypes at,
CurrentUser u,
ChangeControl.Factory cf) {
AnonymousUser au,
ChangeControl.Factory cf,
PatchSetInfoFactory psi,
PatchListCache plc,
SshInfo sshInfo,
@CanonicalWebUrl Provider<String> curl,
Urls urls) {
this.imp = qp;
this.db = db;
this.approvalTypes = at;
this.user = u;
this.anonymous = au;
this.changeControlFactory = cf;
this.patchSetInfoFactory = psi;
this.patchListCache = plc;
this.sshInfo = sshInfo;
this.urlProvider = curl;
this.urls = urls;
accounts = Maps.newHashMap();
controls = Maps.newHashMap();
options = EnumSet.noneOf(ListChangesOption.class);
}
public OutputFormat getFormat() {
@ -204,7 +287,18 @@ public class ListChanges {
out._sortkey = in.getSortKey();
out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
out.labels = labelsFor(cd);
out.labels = options.contains(LABELS) ? labelsFor(cd) : null;
if (options.contains(ALL_REVISIONS) || options.contains(CURRENT_REVISION)) {
out.revisions = revisions(cd);
for (String commit : out.revisions.keySet()) {
if (out.revisions.get(commit).isCurrent) {
out.current_revision = commit;
break;
}
}
}
return out;
}
@ -220,15 +314,30 @@ public class ListChanges {
return a;
}
private ChangeControl control(ChangeData cd) throws OrmException {
ChangeControl ctrl = cd.changeControl();
if (ctrl != null && ctrl.getCurrentUser() == user) {
return ctrl;
}
ctrl = controls.get(cd.getId());
if (ctrl != null) {
return ctrl;
}
try {
ctrl = changeControlFactory.controlFor(cd.change(db));
} catch (NoSuchChangeException e) {
return null;
}
controls.put(cd.getId(), ctrl);
return ctrl;
}
private Map<String, LabelInfo> labelsFor(ChangeData cd) throws OrmException {
Change in = cd.change(db);
ChangeControl ctl = cd.changeControl();
if (ctl == null || ctl.getCurrentUser() != user) {
try {
ctl = changeControlFactory.controlFor(in);
} catch (NoSuchChangeException e) {
return null;
}
ChangeControl ctl = control(cd);
if (ctl == null) {
return Collections.emptyMap();
}
PatchSet ps = cd.currentPatchSet(db);
@ -330,6 +439,148 @@ public class ListChanges {
return false;
}
private Map<String, RevisionInfo> revisions(ChangeData cd) throws OrmException {
ChangeControl ctl = control(cd);
if (ctl == null) {
return Collections.emptyMap();
}
Collection<PatchSet> src;
if (options.contains(ALL_REVISIONS)) {
src = cd.patches(db);
} else {
src = Collections.singletonList(cd.currentPatchSet(db));
}
Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
for (PatchSet in : src) {
if (ctl.isPatchVisible(in, db.get())) {
res.put(in.getRevision().get(), toRevisionInfo(cd, in));
}
}
return res;
}
private RevisionInfo toRevisionInfo(ChangeData cd, PatchSet in)
throws OrmException {
RevisionInfo out = new RevisionInfo();
out.isCurrent = in.getId().equals(cd.change(db).currentPatchSetId());
out._number = in.getId().get();
out.draft = in.isDraft() ? true : null;
out.fetch = makeFetchMap(cd, in);
if (options.contains(ALL_COMMITS)
|| (out.isCurrent && options.contains(CURRENT_COMMIT))) {
try {
PatchSetInfo info = patchSetInfoFactory.get(db.get(), in.getId());
out.commit = new CommitInfo();
out.commit.parents = Lists.newArrayListWithCapacity(info.getParents().size());
out.commit.author = toGitPerson(info.getAuthor());
out.commit.committer = toGitPerson(info.getCommitter());
out.commit.subject = info.getSubject();
out.commit.message = info.getMessage();
for (ParentInfo parent : info.getParents()) {
CommitInfo i = new CommitInfo();
i.commit = parent.id.get();
i.subject = parent.shortMessage;
out.commit.parents.add(i);
}
} catch (PatchSetInfoNotAvailableException e) {
log.warn("Cannot load PatchSetInfo " + in.getId(), e);
}
}
if (options.contains(ALL_FILES)
|| (out.isCurrent && options.contains(CURRENT_FILES))) {
PatchList list;
try {
list = patchListCache.get(cd.change(db), in);
} catch (PatchListNotAvailableException e) {
log.warn("Cannot load PatchList " + in.getId(), e);
list = null;
}
if (list != null) {
out.files = Maps.newTreeMap();
for (PatchListEntry e : list.getPatches()) {
if (Patch.COMMIT_MSG.equals(e.getNewName())) {
continue;
}
FileInfo d = new FileInfo();
d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
? e.getChangeType().getCode()
: null;
d.oldPath = e.getOldName();
if (e.getPatchType() == Patch.PatchType.BINARY) {
d.binary = true;
} else {
d.linesInserted = e.getInsertions() > 0 ? e.getInsertions() : null;
d.linesDeleted = e.getDeletions() > 0 ? e.getDeletions() : null;
}
FileInfo o = out.files.put(e.getNewName(), d);
if (o != null) {
// This should only happen on a delete-add break created by JGit
// when the file was rewritten and too little content survived. Write
// a single record with data from both sides.
d.status = Patch.ChangeType.REWRITE.getCode();
if (o.binary != null && o.binary) {
d.binary = true;
}
if (o.linesInserted != null) {
d.linesInserted = o.linesInserted;
}
if (o.linesDeleted != null) {
d.linesDeleted = o.linesDeleted;
}
}
}
}
}
return out;
}
private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in)
throws OrmException {
Map<String, FetchInfo> r = Maps.newLinkedHashMap();
String refName = in.getRefName();
ChangeControl ctl = control(cd);
if (ctl != null && ctl.forUser(anonymous).isPatchVisible(in, db.get())) {
if (urls.git != null) {
r.put("git", new FetchInfo(urls.git
+ cd.change(db).getProject().get(), refName));
}
}
if (urls.http != null) {
r.put("http", new FetchInfo(urls.http
+ cd.change(db).getProject().get(), refName));
} else {
String http = urlProvider.get();
if (!Strings.isNullOrEmpty(http)) {
r.put("http", new FetchInfo(http
+ cd.change(db).getProject().get(), refName));
}
}
if (!sshInfo.getHostKeys().isEmpty()) {
HostKey host = sshInfo.getHostKeys().get(0);
r.put("ssh", new FetchInfo(String.format(
"ssh://%s/%s",
host.getHost(), cd.change(db).getProject().get()),
refName));
}
return r;
}
private static GitPerson toGitPerson(UserIdentity committer) {
GitPerson p = new GitPerson();
p.name = committer.getName();
p.email = committer.getEmail();
p.date = committer.getDate();
p.tz = committer.getTimeZone();
return p;
}
static class ChangeInfo {
String project;
String branch;
@ -347,9 +598,55 @@ public class ListChanges {
AccountAttribute owner;
Map<String, LabelInfo> labels;
String current_revision;
Map<String, RevisionInfo> revisions;
Boolean _moreChanges;
}
static class RevisionInfo {
private transient boolean isCurrent;
Boolean draft;
int _number;
Map<String, FetchInfo> fetch;
CommitInfo commit;
Map<String, FileInfo> files;
}
static class FetchInfo {
String url;
String ref;
FetchInfo(String url, String ref) {
this.url = url;
this.ref = ref;
}
}
static class GitPerson {
String name;
String email;
Timestamp date;
int tz;
}
static class CommitInfo {
String commit;
List<CommitInfo> parents;
GitPerson author;
GitPerson committer;
String subject;
String message;
}
static class FileInfo {
Character status;
Boolean binary;
String oldPath;
Integer linesInserted;
Integer linesDeleted;
}
static class LabelInfo {
transient SubmitRecord.Label.Status _status;
AccountAttribute approved;