Merge "Add 'Parent $x' options to diff for merge commits"

This commit is contained in:
Dave Borowitz 2016-07-19 16:27:53 +00:00 committed by Gerrit Code Review
commit 878ed3c90e
42 changed files with 942 additions and 183 deletions

View File

@ -3582,6 +3582,11 @@ of all files (modified or unmodified) that contain that substring
in the path name. This is useful to implement suggestion services
finding a file by partial name.
The integer-valued request parameter `parent` changes the response to return a
list of the files which are different in this commit compared to the given
parent commit. This is useful for supporting review of merge commits. The value
is the 1-based index of the parent's position in the commit object.
.Request
----
GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/files/?reviewed HTTP/1.0
@ -3827,6 +3832,11 @@ If the `intraline` parameter is specified, intraline differences are included in
The `base` parameter can be specified to control the base patch set from which the diff should
be generated.
The integer-valued request parameter `parent` can be specified to control the
parent commit number against which the diff should be generated. This is useful
for supporting review of merge commits. The value is the 1-based index of the
parent's position in the commit object.
[[weblinks-only]]
If the `weblinks-only` parameter is specified, only the diff web links are returned.
@ -4381,6 +4391,9 @@ Not set if returned in a map where the key is the file path.
The side on which the comment was added. +
Allowed values are `REVISION` and `PARENT`. +
If not set, the default is `REVISION`.
|`parent` |optional|
The 1-based parent number. Used only for merge commits when `side == PARENT`.
When not set the comment is for the auto-merge tree.
|`line` |optional|
The number of the line for which the comment was done. +
If range is set, this equals the end line of the range. +

View File

@ -19,9 +19,13 @@ import static com.google.gerrit.acceptance.GitUtil.initSsh;
import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static org.eclipse.jgit.lib.Constants.HEAD;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.primitives.Chars;
@ -497,6 +501,29 @@ public abstract class AbstractDaemonTest {
return result;
}
protected PushOneCommit.Result createMergeCommitChange(String ref)
throws Exception {
ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
PushOneCommit.Result p1 = pushFactory.create(db, admin.getIdent(),
testRepo, "parent 1", ImmutableMap.of("foo", "foo-1", "bar", "bar-1"))
.to(ref);
// reset HEAD in order to create a sibling of the first change
testRepo.reset(initial);
PushOneCommit.Result p2 = pushFactory.create(db, admin.getIdent(),
testRepo, "parent 2", ImmutableMap.of("foo", "foo-2", "bar", "bar-2"))
.to(ref);
PushOneCommit m = pushFactory.create(db, admin.getIdent(), testRepo, "merge",
ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit()));
PushOneCommit.Result result = m.to(ref);
result.assertOkStatus();
return result;
}
protected PushOneCommit.Result createDraftChange() throws Exception {
return pushTo("refs/drafts/master");
}

View File

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.GitUtil.pushHead;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
@ -44,6 +45,7 @@ import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import java.util.List;
import java.util.Map;
public class PushOneCommit {
public static final String SUBJECT = "test commit";
@ -87,6 +89,13 @@ public class PushOneCommit {
@Assisted("fileName") String fileName,
@Assisted("content") String content);
PushOneCommit create(
ReviewDb db,
PersonIdent i,
TestRepository<?> testRepo,
@Assisted String subject,
@Assisted Map<String, String> files);
PushOneCommit create(
ReviewDb db,
PersonIdent i,
@ -123,8 +132,7 @@ public class PushOneCommit {
private final TestRepository<?> testRepo;
private final String subject;
private final String fileName;
private final String content;
private final Map<String, String> files;
private String changeId;
private Tag tag;
private boolean force;
@ -168,6 +176,19 @@ public class PushOneCommit {
db, i, testRepo, subject, fileName, content, null);
}
@AssistedInject
PushOneCommit(ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
@Assisted ReviewDb db,
@Assisted PersonIdent i,
@Assisted TestRepository<?> testRepo,
@Assisted String subject,
@Assisted Map<String, String> files) throws Exception {
this(notesFactory, approvalsUtil, queryProvider, db, i, testRepo,
subject, files, null);
}
@AssistedInject
PushOneCommit(ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
@ -179,14 +200,26 @@ public class PushOneCommit {
@Assisted("fileName") String fileName,
@Assisted("content") String content,
@Nullable @Assisted("changeId") String changeId) throws Exception {
this(notesFactory, approvalsUtil, queryProvider, db, i, testRepo,
subject, ImmutableMap.of(fileName, content), changeId);
}
private PushOneCommit(ChangeNotes.Factory notesFactory,
ApprovalsUtil approvalsUtil,
Provider<InternalChangeQuery> queryProvider,
ReviewDb db,
PersonIdent i,
TestRepository<?> testRepo,
String subject,
Map<String, String> files,
String changeId) throws Exception {
this.db = db;
this.testRepo = testRepo;
this.notesFactory = notesFactory;
this.approvalsUtil = approvalsUtil;
this.queryProvider = queryProvider;
this.subject = subject;
this.fileName = fileName;
this.content = content;
this.files = files;
this.changeId = changeId;
if (changeId != null) {
commitBuilder = testRepo.amendRef("HEAD")
@ -212,12 +245,16 @@ public class PushOneCommit {
}
public Result to(String ref) throws Exception {
commitBuilder.add(fileName, content);
for (Map.Entry<String, String> e : files.entrySet()) {
commitBuilder.add(e.getKey(), e.getValue());
}
return execute(ref);
}
public Result rm(String ref) throws Exception {
commitBuilder.rm(fileName);
for (String fileName : files.keySet()) {
commitBuilder.rm(fileName);
}
return execute(ref);
}

View File

@ -525,6 +525,35 @@ public class RevisionIT extends AbstractDaemonTest {
.isTrue();
}
@Test
public void filesOnMergeCommitChange() throws Exception {
PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
// list files against auto-merge
assertThat(gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.files()
.keySet()
).containsExactly(Patch.COMMIT_MSG, "foo", "bar");
// list files against parent 1
assertThat(gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.files(1)
.keySet()
).containsExactly(Patch.COMMIT_MSG, "bar");
// list files against parent 2
assertThat(gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.files(2)
.keySet()
).containsExactly(Patch.COMMIT_MSG, "foo");
}
@Test
public void diff() throws Exception {
PushOneCommit.Result r = createChange();
@ -537,6 +566,48 @@ public class RevisionIT extends AbstractDaemonTest {
assertThat(diff.metaB.lines).isEqualTo(1);
}
@Test
public void diffOnMergeCommitChange() throws Exception {
PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
DiffInfo diff;
// automerge
diff = gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.file("foo")
.diff();
assertThat(diff.metaA.lines).isEqualTo(5);
assertThat(diff.metaB.lines).isEqualTo(1);
diff = gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.file("bar")
.diff();
assertThat(diff.metaA.lines).isEqualTo(5);
assertThat(diff.metaB.lines).isEqualTo(1);
// parent 1
diff = gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.file("bar")
.diff(1);
assertThat(diff.metaA.lines).isEqualTo(1);
assertThat(diff.metaB.lines).isEqualTo(1);
// parent 2
diff = gApi.changes()
.id(r.getChangeId())
.revision(r.getCommit().name())
.file("foo")
.diff(2);
assertThat(diff.metaA.lines).isEqualTo(1);
assertThat(diff.metaB.lines).isEqualTo(1);
}
@Test
public void content() throws Exception {
PushOneCommit.Result r = createChange();

View File

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@ -56,6 +57,7 @@ import java.util.regex.Pattern;
@NoHttpd
public class CommentsIT extends AbstractDaemonTest {
@Inject
private Provider<ChangesCollection> changes;
@ -87,12 +89,35 @@ public class CommentsIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
String revId = r.getCommit().getName();
DraftInput comment = newDraft("file1", Side.REVISION, line, "comment 1");
String path = "file1";
DraftInput comment = newDraft(path, Side.REVISION, line, "comment 1");
addDraft(changeId, revId, comment);
Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
assertThat(result).hasSize(1);
CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
assertCommentInfo(comment, actual);
assertThat(comment).isEqualTo(infoToDraft(path).apply(actual));
}
}
@Test
public void createDraftOnMergeCommitChange() throws Exception {
for (Integer line : lines) {
PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
String changeId = r.getChangeId();
String revId = r.getCommit().getName();
String path = "file1";
DraftInput c1 = newDraft(path, Side.REVISION, line, "ps-1");
DraftInput c2 = newDraft(path, Side.PARENT, line, "auto-merge of ps-1");
DraftInput c3 = newDraftOnParent(path, 1, line, "parent-1 of ps-1");
DraftInput c4 = newDraftOnParent(path, 2, line, "parent-2 of ps-1");
addDraft(changeId, revId, c1);
addDraft(changeId, revId, c2);
addDraft(changeId, revId, c3);
addDraft(changeId, revId, c4);
Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
assertThat(result).hasSize(1);
assertThat(Lists.transform(result.get(path), infoToDraft(path)))
.containsExactly(c1, c2, c3, c4);
}
}
@ -114,8 +139,31 @@ public class CommentsIT extends AbstractDaemonTest {
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
assertCommentInfo(comment, actual);
assertCommentInfo(actual, getPublishedComment(changeId, revId, actual.id));
assertThat(comment).isEqualTo(infoToInput(file).apply(actual));
assertThat(comment).isEqualTo(infoToInput(file).apply(
getPublishedComment(changeId, revId, actual.id)));
}
}
@Test
public void postCommentOnMergeCommitChange() throws Exception {
for (Integer line : lines) {
final String file = "/COMMIT_MSG";
PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
String changeId = r.getChangeId();
String revId = r.getCommit().getName();
ReviewInput input = new ReviewInput();
CommentInput c1 = newComment(file, Side.REVISION, line, "ps-1");
CommentInput c2 = newComment(file, Side.PARENT, line, "auto-merge of ps-1");
CommentInput c3 = newCommentOnParent(file, 1, line, "parent-1 of ps-1");
CommentInput c4 = newCommentOnParent(file, 2, line, "parent-2 of ps-1");
input.comments = new HashMap<>();
input.comments.put(file, ImmutableList.of(c1, c2, c3, c4));
revision(r).review(input);
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
assertThat(Lists.transform(result.get(file), infoToInput(file)))
.containsExactly(c1, c2, c3, c4);
}
}
@ -129,7 +177,7 @@ public class CommentsIT extends AbstractDaemonTest {
String revId = r.getCommit().getName();
assertThat(getPublishedComments(changeId, revId)).isEmpty();
List<Comment> expectedComments = new ArrayList<>();
List<CommentInput> expectedComments = new ArrayList<>();
for (Integer line : lines) {
ReviewInput input = new ReviewInput();
CommentInput comment = newComment(file, Side.REVISION, line, "comment " + line);
@ -142,10 +190,8 @@ public class CommentsIT extends AbstractDaemonTest {
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
List<CommentInfo> actualComments = result.get(file);
assertThat(actualComments).hasSize(expectedComments.size());
for (int i = 0; i < actualComments.size(); i++) {
assertCommentInfo(expectedComments.get(i), actualComments.get(i));
}
assertThat(Lists.transform(actualComments, infoToInput(file)))
.containsExactlyElementsIn(expectedComments);
}
@Test
@ -155,17 +201,18 @@ public class CommentsIT extends AbstractDaemonTest {
Timestamp origLastUpdated = r.getChange().change().getLastUpdatedOn();
String changeId = r.getChangeId();
String revId = r.getCommit().getName();
DraftInput comment = newDraft("file1", Side.REVISION, line, "comment 1");
String path = "file1";
DraftInput comment = newDraft(path, Side.REVISION, line, "comment 1");
addDraft(changeId, revId, comment);
Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
assertCommentInfo(comment, actual);
assertThat(comment).isEqualTo(infoToDraft(path).apply(actual));
String uuid = actual.id;
comment.message = "updated comment 1";
updateDraft(changeId, revId, comment, uuid);
result = getDraftComments(changeId, revId);
actual = Iterables.getOnlyElement(result.get(comment.path));
assertCommentInfo(comment, actual);
assertThat(comment).isEqualTo(infoToDraft(path).apply(actual));
// Posting a draft comment doesn't cause lastUpdatedOn to change.
assertThat(r.getChange().change().getLastUpdatedOn())
@ -181,7 +228,7 @@ public class CommentsIT extends AbstractDaemonTest {
String revId = r.getCommit().getName();
assertThat(getDraftComments(changeId, revId)).isEmpty();
List<Comment> expectedDrafts = new ArrayList<>();
List<DraftInput> expectedDrafts = new ArrayList<>();
for (Integer line : lines) {
DraftInput comment = newDraft(file, Side.REVISION, line, "comment " + line);
expectedDrafts.add(comment);
@ -191,10 +238,8 @@ public class CommentsIT extends AbstractDaemonTest {
Map<String, List<CommentInfo>> result = getDraftComments(changeId, revId);
assertThat(result).isNotEmpty();
List<CommentInfo> actualComments = result.get(file);
assertThat(actualComments).hasSize(expectedDrafts.size());
for (int i = 0; i < actualComments.size(); i++) {
assertCommentInfo(expectedDrafts.get(i), actualComments.get(i));
}
assertThat(Lists.transform(actualComments, infoToDraft(file)))
.containsExactlyElementsIn(expectedDrafts);
}
@Test
@ -203,11 +248,12 @@ public class CommentsIT extends AbstractDaemonTest {
PushOneCommit.Result r = createChange();
String changeId = r.getChangeId();
String revId = r.getCommit().getName();
String path = "file1";
DraftInput comment = newDraft(
"file1", Side.REVISION, line, "comment 1");
path, Side.REVISION, line, "comment 1");
CommentInfo returned = addDraft(changeId, revId, comment);
CommentInfo actual = getDraftComment(changeId, revId, returned.id);
assertCommentInfo(comment, actual);
assertThat(comment).isEqualTo(infoToDraft(path).apply(actual));
}
}
@ -257,7 +303,9 @@ public class CommentsIT extends AbstractDaemonTest {
Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
assertThat(result).isNotEmpty();
CommentInfo actual = Iterables.getOnlyElement(result.get(comment.path));
assertCommentInfo(comment, actual);
CommentInput ci = infoToInput(file).apply(actual);
ci.updated = comment.updated;
assertThat(comment).isEqualTo(ci);
assertThat(actual.updated)
.isEqualTo(gApi.changes().id(r.getChangeId()).info().created);
@ -597,45 +645,35 @@ public class CommentsIT extends AbstractDaemonTest {
return gApi.changes().id(changeId).revision(revId).draft(uuid).get();
}
private static void assertCommentInfo(Comment expected, CommentInfo actual) {
assertThat(actual.line).isEqualTo(expected.line);
assertThat(actual.message).isEqualTo(expected.message);
assertThat(actual.inReplyTo).isEqualTo(expected.inReplyTo);
assertCommentRange(expected.range, actual.range);
if (actual.side == null && expected.side != null) {
assertThat(Side.REVISION).isEqualTo(expected.side);
}
}
private static void assertCommentRange(Comment.Range expected,
Comment.Range actual) {
if (expected == null) {
assertThat(actual).isNull();
} else {
assertThat(actual).isNotNull();
assertThat(actual.startLine).isEqualTo(expected.startLine);
assertThat(actual.startCharacter).isEqualTo(expected.startCharacter);
assertThat(actual.endLine).isEqualTo(expected.endLine);
assertThat(actual.endCharacter).isEqualTo(expected.endCharacter);
}
}
private static CommentInput newComment(String path, Side side, int line,
String message) {
CommentInput c = new CommentInput();
return populate(c, path, side, line, message);
return populate(c, path, side, null, line, message);
}
private static CommentInput newCommentOnParent(String path, int parent,
int line, String message) {
CommentInput c = new CommentInput();
return populate(c, path, Side.PARENT, Integer.valueOf(parent), line, message);
}
private DraftInput newDraft(String path, Side side, int line,
String message) {
DraftInput d = new DraftInput();
return populate(d, path, side, line, message);
return populate(d, path, side, null, line, message);
}
private DraftInput newDraftOnParent(String path, int parent, int line,
String message) {
DraftInput d = new DraftInput();
return populate(d, path, Side.PARENT, Integer.valueOf(parent), line, message);
}
private static <C extends Comment> C populate(C c, String path, Side side,
int line, String message) {
Integer parent, int line, String message) {
c.path = path;
c.side = side;
c.parent = parent;
c.line = line != 0 ? line : null;
c.message = message;
if (line != 0) {
@ -648,4 +686,38 @@ public class CommentsIT extends AbstractDaemonTest {
}
return c;
}
private static Function<CommentInfo, CommentInput> infoToInput(
final String path) {
return new Function<CommentInfo, CommentInput>() {
@Override
public CommentInput apply(CommentInfo info) {
CommentInput ci = new CommentInput();
ci.path = path;
copy(info, ci);
return ci;
}
};
}
private static Function<CommentInfo, DraftInput> infoToDraft(
final String path) {
return new Function<CommentInfo, DraftInput>() {
@Override
public DraftInput apply(CommentInfo info) {
DraftInput di = new DraftInput();
di.path = path;
copy(info, di);
return di;
}
};
}
private static void copy(Comment from, Comment to) {
to.side = from.side == null ? Side.REVISION : from.side;
to.parent = from.parent;
to.line = from.line;
to.message = from.message;
to.range = from.range;
}
}

View File

@ -16,6 +16,22 @@ package com.google.gerrit.extensions.api.changes;
import com.google.gerrit.extensions.client.Comment;
import java.util.Objects;
public class DraftInput extends Comment {
public String tag;
@Override
public boolean equals(Object o) {
if (super.equals(o)) {
DraftInput di = (DraftInput) o;
return Objects.equals(tag, di.tag);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), tag);
}
}

View File

@ -34,6 +34,11 @@ public interface FileApi {
*/
DiffInfo diff(String base) throws RestApiException;
/**
* @param parent 1-based parent number to diff against
*/
DiffInfo diff(int parent) throws RestApiException;
/**
* Creates a request to retrieve the diff. On the returned request formatting
* options for the diff can be set.
@ -105,6 +110,11 @@ public interface FileApi {
throw new NotImplementedException();
}
@Override
public DiffInfo diff(int parent) throws RestApiException {
throw new NotImplementedException();
}
@Override
public DiffRequest diffRequest() throws RestApiException {
throw new NotImplementedException();

View File

@ -46,6 +46,7 @@ public interface RevisionApi {
Map<String, FileInfo> files() throws RestApiException;
Map<String, FileInfo> files(String base) throws RestApiException;
Map<String, FileInfo> files(int parentNum) throws RestApiException;
FileApi file(String path);
MergeableInfo mergeable() throws RestApiException;
MergeableInfo mergeableOtherBranches() throws RestApiException;
@ -146,6 +147,11 @@ public interface RevisionApi {
throw new NotImplementedException();
}
@Override
public Map<String, FileInfo> files(int parentNum) throws RestApiException {
throw new NotImplementedException();
}
@Override
public Map<String, FileInfo> files() throws RestApiException {
throw new NotImplementedException();

View File

@ -15,6 +15,7 @@
package com.google.gerrit.extensions.client;
import java.sql.Timestamp;
import java.util.Objects;
public abstract class Comment {
/**
@ -27,6 +28,7 @@ public abstract class Comment {
public String id;
public String path;
public Side side;
public Integer parent;
public Integer line;
public Range range;
public String inReplyTo;
@ -38,5 +40,49 @@ public abstract class Comment {
public int startCharacter;
public int endLine;
public int endCharacter;
@Override
public boolean equals(Object o) {
if (o instanceof Range) {
Range r = (Range) o;
return Objects.equals(startLine, r.startLine)
&& Objects.equals(startCharacter, r.startCharacter)
&& Objects.equals(endLine, r.endLine)
&& Objects.equals(endCharacter, r.endCharacter);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(startLine, startCharacter, endLine, endCharacter);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && getClass() == o.getClass()) {
Comment c = (Comment) o;
return Objects.equals(patchSet, c.patchSet)
&& Objects.equals(id, c.id)
&& Objects.equals(path, c.path)
&& Objects.equals(side, c.side)
&& Objects.equals(parent, c.parent)
&& Objects.equals(line, c.line)
&& Objects.equals(range, c.range)
&& Objects.equals(inReplyTo, c.inReplyTo)
&& Objects.equals(updated, c.updated)
&& Objects.equals(message, c.message);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(patchSet, id, path, side, parent, line, range,
inReplyTo, updated, message);
}
}

View File

@ -19,11 +19,10 @@ public enum Side {
REVISION;
public static Side fromShort(short s) {
switch (s) {
case 0:
return PARENT;
case 1:
return REVISION;
if (s <= 0) {
return PARENT;
} else if (s == 1) {
return REVISION;
}
return null;
}

View File

@ -16,7 +16,24 @@ package com.google.gerrit.extensions.common;
import com.google.gerrit.extensions.client.Comment;
import java.util.Objects;
public class CommentInfo extends Comment {
public AccountInfo author;
public String tag;
@Override
public boolean equals(Object o) {
if (super.equals(o)) {
CommentInfo ci = (CommentInfo) o;
return Objects.equals(author, ci.author)
&& Objects.equals(tag, ci.tag);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), author, tag);
}
}

View File

@ -333,12 +333,22 @@ public class ChangeInfo extends JavaScriptObject {
revisionInfo.takeFromEdit(edit);
return revisionInfo;
}
public static RevisionInfo forParent(int number, CommitInfo commit) {
RevisionInfo revisionInfo = createObject().cast();
revisionInfo.takeFromParent(number, commit);
return revisionInfo;
}
private native void takeFromEdit(EditInfo edit) /*-{
this._number = 0;
this.name = edit.name;
this.commit = edit.commit;
this.edit_base = edit.base_revision;
}-*/;
private native void takeFromParent(int number, CommitInfo commit) /*-{
this._number = number;
this.commit = commit;
this.name = this._number;
}-*/;
public final native int _number() /*-{ return this._number; }-*/;
public final native String name() /*-{ return this.name; }-*/;
public final native boolean draft() /*-{ return this.draft || false; }-*/;

View File

@ -981,24 +981,25 @@ public class ChangeScreen extends Screen {
final List<NativeMap<JsArray<CommentInfo>>> comments,
final List<NativeMap<JsArray<CommentInfo>>> drafts) {
DiffApi.list(changeId.get(),
base != null ? base.name() : null,
rev.name(),
group.add(new AsyncCallback<NativeMap<FileInfo>>() {
@Override
public void onSuccess(NativeMap<FileInfo> m) {
files.set(
base != null ? new PatchSet.Id(changeId, base._number()) : null,
new PatchSet.Id(changeId, rev._number()),
style, reply, fileTableMode, edit != null);
files.setValue(m, myLastReply,
comments != null ? comments.get(0) : null,
drafts != null ? drafts.get(0) : null);
}
rev.name(),
base,
group.add(
new AsyncCallback<NativeMap<FileInfo>>() {
@Override
public void onSuccess(NativeMap<FileInfo> m) {
files.set(
base != null ? new PatchSet.Id(changeId, base._number()) : null,
new PatchSet.Id(changeId, rev._number()),
style, reply, fileTableMode, edit != null);
files.setValue(m, myLastReply,
comments != null ? comments.get(0) : null,
drafts != null ? drafts.get(0) : null);
}
@Override
public void onFailure(Throwable caught) {
}
}));
@Override
public void onFailure(Throwable caught) {
}
}));
}
private List<NativeMap<JsArray<CommentInfo>>> loadComments(
@ -1117,7 +1118,6 @@ public class ChangeScreen extends Screen {
}
/**
*
* Resolve a revision or patch set id string to RevisionInfo.
* When this view is created from the changes table, revision
* is passed as a real revision.
@ -1131,8 +1131,17 @@ public class ChangeScreen extends Screen {
*/
private RevisionInfo resolveRevisionOrPatchSetId(ChangeInfo info,
String revOrId, String defaultValue) {
int parentNum;
if (revOrId == null) {
revOrId = defaultValue;
} else if ((parentNum = toParentNum(revOrId)) > 0) {
CommitInfo commitInfo = info.revision(revision).commit();
if (commitInfo != null) {
JsArray<CommitInfo> parents = commitInfo.parents();
if (parents.length() >= parentNum) {
return RevisionInfo.forParent(-parentNum, parents.get(parentNum - 1));
}
}
} else if (!info.revisions().containsKey(revOrId)) {
JsArray<RevisionInfo> list = info.revisions().values();
for (int i = 0; i < list.length(); i++) {
@ -1389,9 +1398,20 @@ public class ChangeScreen extends Screen {
RevisionInfo rev = info.revisions().get(revision);
JsArray<CommitInfo> parents = rev.commit().parents();
diffBase.addItem(
parents.length() > 1 ? Util.C.autoMerge() : Util.C.baseDiffItem(),
"");
if (parents.length() > 1) {
diffBase.addItem(Util.C.autoMerge(), "");
for (int i = 0; i < parents.length(); i++) {
int parentNum = i + 1;
diffBase.addItem(Util.M.diffBaseParent(parentNum),
String.valueOf(-parentNum));
}
int parentNum = toParentNum(base);
if (parentNum > 0) {
selectedIdx = list.length() + parentNum;
}
} else {
diffBase.addItem(Util.C.baseDiffItem(), "");
}
diffBase.setSelectedIndex(selectedIdx);
}
@ -1443,4 +1463,22 @@ public class ChangeScreen extends Screen {
private static String normalize(String r) {
return r != null && !r.isEmpty() ? r : null;
}
/**
* @param parentToken
* @return 1-based parentNum if parentToken is a String which can be parsed as
* a negative integer i.e. "-1", "-2", etc. If parentToken cannot be
* parsed as a negative integer, return zero.
*/
private static int toParentNum(String parentToken) {
try {
int n = Integer.parseInt(parentToken);
if (n < 0) {
return -n;
}
return 0;
} catch (NumberFormatException e) {
return 0;
}
}
}

View File

@ -36,6 +36,7 @@ import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.PatchSet;
@ -712,8 +713,8 @@ public class FileTable extends FlowPanel {
}
private void columnComments(SafeHtmlBuilder sb, FileInfo info) {
JsArray<CommentInfo> cList = get(info.path(), comments);
JsArray<CommentInfo> dList = get(info.path(), drafts);
JsArray<CommentInfo> cList = filterForParent(get(info.path(), comments));
JsArray<CommentInfo> dList = filterForParent(get(info.path(), drafts));
sb.openTd().setStyleName(R.css().draftColumn());
if (dList.length() > 0) {
@ -747,6 +748,20 @@ public class FileTable extends FlowPanel {
sb.closeTd();
}
private JsArray<CommentInfo> filterForParent(JsArray<CommentInfo> list) {
JsArray<CommentInfo> result = JsArray.createArray().cast();
for (CommentInfo c : Natives.asList(list)) {
if (c.side() == Side.REVISION) {
result.push(c);
} else if (base == null && !c.hasParent()) {
result.push(c);
} else if (base != null && c.parent() == -base.get()) {
result.push(c);
}
}
return result;
}
private JsArray<CommentInfo> get(String p, NativeMap<JsArray<CommentInfo>> m) {
JsArray<CommentInfo> r = null;
if (m != null) {

View File

@ -42,4 +42,6 @@ public interface ChangeMessages extends Messages {
String changeQueryPageTitle(String query);
String insertionsAndDeletions(int insertions, int deletions);
String diffBaseParent(int parentNum);
}

View File

@ -23,3 +23,5 @@ changeQueryWindowTitle = {0}
changeQueryPageTitle = Search for {0}
insertionsAndDeletions = +{0}, -{1}
diffBaseParent = Parent {0}

View File

@ -25,9 +25,15 @@ import java.sql.Timestamp;
public class CommentInfo extends JavaScriptObject {
public static CommentInfo create(String path, Side side,
int line, CommentRange range) {
return create(path, side, 0, line, range);
}
public static CommentInfo create(String path, Side side, int parent,
int line, CommentRange range) {
CommentInfo n = createObject().cast();
n.path(path);
n.side(side);
n.parent(parent);
if (range != null) {
n.line(range.endLine());
n.range(range);
@ -41,6 +47,7 @@ public class CommentInfo extends JavaScriptObject {
CommentInfo n = createObject().cast();
n.path(r.path());
n.side(r.side());
n.parent(r.parent());
n.inReplyTo(r.id());
if (r.hasRange()) {
n.line(r.range().endLine());
@ -55,6 +62,7 @@ public class CommentInfo extends JavaScriptObject {
CommentInfo n = createObject().cast();
n.path(s.path());
n.side(s.side());
n.parent(s.parent());
n.id(s.id());
n.inReplyTo(s.inReplyTo());
n.message(s.message());
@ -78,6 +86,8 @@ public class CommentInfo extends JavaScriptObject {
sideRaw(side.toString());
}
private native void sideRaw(String s) /*-{ this.side = s }-*/;
public final native void parent(int n) /*-{ this.parent = n }-*/;
public final native boolean hasParent() /*-{ return this.hasOwnProperty('parent') }-*/;
public final native String path() /*-{ return this.path }-*/;
public final native String id() /*-{ return this.id }-*/;
@ -91,6 +101,7 @@ public class CommentInfo extends JavaScriptObject {
: Side.REVISION;
}
private native String sideRaw() /*-{ return this.side }-*/;
public final native int parent() /*-{ return this.parent }-*/;
public final Timestamp updated() {
Timestamp r = updatedTimestamp();

View File

@ -129,16 +129,29 @@ abstract class CommentManager {
}
Side getStoredSideFromDisplaySide(DisplaySide side) {
return side == DisplaySide.A && base == null ? Side.PARENT : Side.REVISION;
if (side == DisplaySide.A && (base == null || base.get() < 0)) {
return Side.PARENT;
}
return Side.REVISION;
}
int getParentNumFromDisplaySide(DisplaySide side) {
if (side == DisplaySide.A && base != null && base.get() < 0) {
return -base.get();
}
return 0;
}
PatchSet.Id getPatchSetIdFromSide(DisplaySide side) {
return side == DisplaySide.A && base != null ? base : revision;
if (side == DisplaySide.A && base != null && base.get() >= 0) {
return base;
}
return revision;
}
DisplaySide displaySide(CommentInfo info, DisplaySide forSide) {
if (info.side() == Side.PARENT) {
return base == null ? DisplaySide.A : null;
return (base == null || base.get() < 0) ? DisplaySide.A : null;
}
return forSide;
}
@ -179,6 +192,7 @@ abstract class CommentManager {
addDraftBox(side, CommentInfo.create(
getPath(),
getStoredSideFromDisplaySide(side),
getParentNumFromDisplaySide(side),
line,
null)).setEdit(true);
}

View File

@ -20,6 +20,7 @@ import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.rpc.AsyncCallback;
@ -46,13 +47,13 @@ class CommentsCollections {
}
void load(CallbackGroup group) {
if (base != null) {
if (base != null && base.get() > 0) {
CommentApi.comments(base, group.add(publishedBase()));
}
CommentApi.comments(revision, group.add(publishedRevision()));
if (Gerrit.isSignedIn()) {
if (base != null) {
if (base != null && base.get() > 0) {
CommentApi.drafts(base, group.add(draftsBase()));
}
CommentApi.drafts(revision, group.add(draftsRevision()));
@ -60,7 +61,7 @@ class CommentsCollections {
}
boolean hasCommentForPath(String filePath) {
if (base != null) {
if (base != null && base.get() > 0) {
JsArray<CommentInfo> forBase = publishedBaseAll.get(filePath);
if (forBase != null && forBase.length() > 0) {
return true;
@ -91,6 +92,9 @@ class CommentsCollections {
return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
for (String k : result.keySet()) {
result.put(k, filterForParent(result.get(k)));
}
publishedRevisionAll = result;
publishedRevision = sort(result.get(path));
}
@ -101,6 +105,20 @@ class CommentsCollections {
};
}
private JsArray<CommentInfo> filterForParent(JsArray<CommentInfo> list) {
JsArray<CommentInfo> result = JsArray.createArray().cast();
for (CommentInfo c : Natives.asList(list)) {
if (c.side() == Side.REVISION) {
result.push(c);
} else if (base == null && !c.hasParent()) {
result.push(c);
} else if (base != null && c.parent() == -base.get()) {
result.push(c);
}
}
return result;
}
private AsyncCallback<NativeMap<JsArray<CommentInfo>>> draftsBase() {
return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
@ -118,6 +136,9 @@ class CommentsCollections {
return new AsyncCallback<NativeMap<JsArray<CommentInfo>>>() {
@Override
public void onSuccess(NativeMap<JsArray<CommentInfo>> result) {
for (String k : result.keySet()) {
result.put(k, filterForParent(result.get(k)));
}
draftsRevision = sort(result.get(path));
}

View File

@ -17,6 +17,7 @@ package com.google.gerrit.client.diff;
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace.IGNORE_ALL;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.info.FileInfo;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.RestApi;
@ -25,11 +26,15 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.user.client.rpc.AsyncCallback;
public class DiffApi {
public static void list(int id, String base, String revision,
public static void list(int id, String revision, RevisionInfo base,
AsyncCallback<NativeMap<FileInfo>> cb) {
RestApi api = ChangeApi.revision(id, revision).view("files");
if (base != null) {
api.addParameter("base", base);
if (base._number() < 0) {
api.addParameter("parent", -base._number());
} else {
api.addParameter("base", base.name());
}
}
api.get(NativeMap.copyKeysIntoChildren("path", cb));
}
@ -38,7 +43,11 @@ public class DiffApi {
AsyncCallback<NativeMap<FileInfo>> cb) {
RestApi api = ChangeApi.revision(id).view("files");
if (base != null) {
api.addParameter("base", base.get());
if (base.get() < 0) {
api.addParameter("parent", -base.get());
} else {
api.addParameter("base", base.get());
}
}
api.get(NativeMap.copyKeysIntoChildren("path", cb));
}
@ -57,7 +66,11 @@ public class DiffApi {
public DiffApi base(PatchSet.Id id) {
if (id != null) {
call.addParameter("base", id.get());
if (id.get() < 0) {
call.addParameter("parent", -id.get());
} else {
call.addParameter("base", id.get());
}
}
return this;
}

View File

@ -27,6 +27,7 @@ import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.diff.DiffInfo.FileMeta;
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
import com.google.gerrit.client.info.ChangeInfo;
import com.google.gerrit.client.info.ChangeInfo.CommitInfo;
import com.google.gerrit.client.info.ChangeInfo.EditInfo;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.info.FileInfo;
@ -116,6 +117,7 @@ abstract class DiffScreen extends Screen {
private List<HandlerRegistration> handlers;
private PreferencesAction prefsAction;
private int reloadVersionId;
private int parents;
@UiField(provided = true)
Header header;
@ -213,6 +215,8 @@ abstract class DiffScreen extends Screen {
new CommentsCollections(base, revision, path);
comments.load(group2);
countParents(group2);
RestApi call = ChangeApi.detail(changeId.get());
ChangeList.addOptions(call, EnumSet.of(
ListChangesOption.ALL_REVISIONS));
@ -231,7 +235,7 @@ abstract class DiffScreen extends Screen {
revision.get() == info.revision(currentRevision)._number();
JsArray<RevisionInfo> list = info.revisions().values();
RevisionInfo.sortRevisionInfoByNumber(list);
getDiffTable().set(prefs, list, diff, edit != null, current,
getDiffTable().set(prefs, list, parents, diff, edit != null, current,
changeStatus.isOpen(), diff.binary());
header.setChangeInfo(info);
}
@ -245,6 +249,22 @@ abstract class DiffScreen extends Screen {
getScreenLoadCallback(comments)));
}
private void countParents(CallbackGroup cbg) {
ChangeApi.revision(changeId.get(), revision.getId())
.view("commit")
.get(cbg.add(new AsyncCallback<CommitInfo>() {
@Override
public void onSuccess(CommitInfo info) {
parents = info.parents().length();
}
@Override
public void onFailure(Throwable caught) {
parents = 0;
}
}));
}
@Override
public void onShowView() {
super.onShowView();

View File

@ -108,12 +108,12 @@ abstract class DiffTable extends Composite {
patchSetSelectBoxB.setUpBlame(cm, false, rev, path);
}
void set(DiffPreferences prefs, JsArray<RevisionInfo> list, DiffInfo info,
void set(DiffPreferences prefs, JsArray<RevisionInfo> list, int parents, DiffInfo info,
boolean editExists, boolean current, boolean open, boolean binary) {
this.changeType = info.changeType();
patchSetSelectBoxA.setUpPatchSetNav(list, info.metaA(), editExists,
patchSetSelectBoxA.setUpPatchSetNav(list, parents, info.metaA(), editExists,
current, open, binary);
patchSetSelectBoxB.setUpPatchSetNav(list, info.metaB(), editExists,
patchSetSelectBoxB.setUpPatchSetNav(list, parents, info.metaB(), editExists,
current, open, binary);
JsArrayString hdr = info.diffHeader();

View File

@ -18,6 +18,7 @@ import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.blame.BlameInfo;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.info.WebLinkInfo;
import com.google.gerrit.client.patches.PatchUtil;
@ -87,13 +88,29 @@ class PatchSetSelectBox extends Composite {
this.path = path;
}
void setUpPatchSetNav(JsArray<RevisionInfo> list, DiffInfo.FileMeta meta,
void setUpPatchSetNav(JsArray<RevisionInfo> list, int parents, DiffInfo.FileMeta meta,
boolean editExists, boolean current, boolean open, boolean binary) {
InlineHyperlink baseLink = null;
InlineHyperlink selectedLink = null;
if (sideA) {
baseLink = createLink(PatchUtil.C.patchBase(), null);
linkPanel.add(baseLink);
if (parents <= 1) {
InlineHyperlink link = createLink(PatchUtil.C.patchBase(), null);
linkPanel.add(link);
selectedLink = link;
} else {
for (int i = parents; i > 0; i--) {
PatchSet.Id id = new PatchSet.Id(changeId, -i);
InlineHyperlink link = createLink(Util.M.diffBaseParent(i), id);
linkPanel.add(link);
if (revision != null && id.equals(revision)) {
selectedLink = link;
}
}
InlineHyperlink link = createLink(Util.C.autoMerge(), null);
linkPanel.add(link);
if (selectedLink == null) {
selectedLink = link;
}
}
}
for (int i = 0; i < list.length(); i++) {
RevisionInfo r = list.get(i);
@ -106,8 +123,6 @@ class PatchSetSelectBox extends Composite {
}
if (selectedLink != null) {
selectedLink.setStyleName(style.selected());
} else if (sideA) {
baseLink.setStyleName(style.selected());
}
if (meta == null) {

View File

@ -84,6 +84,7 @@ class SideBySideCommentManager extends CommentManager {
addDraftBox(cm.side(), CommentInfo.create(
getPath(),
getStoredSideFromDisplaySide(cm.side()),
getParentNumFromDisplaySide(cm.side()),
line,
CommentRange.create(fromTo))).setEdit(true);
cm.setCursor(fromTo.to());

View File

@ -100,10 +100,9 @@ public final class StoredValues {
PatchListCache plCache = env.getArgs().getPatchListCache();
Change change = getChange(engine);
Project.NameKey project = change.getProject();
ObjectId a = null;
ObjectId b = ObjectId.fromString(ps.getRevision().get());
Whitespace ws = Whitespace.IGNORE_NONE;
PatchListKey plKey = new PatchListKey(a, b, ws);
PatchListKey plKey = PatchListKey.againstDefaultBase(b, ws);
PatchList patchList;
try {
patchList = plCache.get(plKey, project);

View File

@ -365,9 +365,17 @@ public class PatchLineCommentsUtil {
"cannot set RevId for patch set %s on comment %s", ps.getId(), c);
if (c.getRevId() == null) {
try {
c.setRevId(Side.fromShort(c.getSide()) == Side.PARENT
? new RevId(ObjectId.toString(cache.getOldId(change, ps)))
: ps.getRevision());
if (Side.fromShort(c.getSide()) == Side.PARENT) {
if (c.getSide() < 0) {
c.setRevId(new RevId(ObjectId.toString(
cache.getOldId(change, ps, -c.getSide()))));
} else {
c.setRevId(new RevId(ObjectId.toString(
cache.getOldId(change, ps, null))));
}
} else {
c.setRevId(ps.getRevision());
}
} catch (PatchListNotAvailableException e) {
throw new OrmException(e);
}

View File

@ -74,6 +74,15 @@ class FileApiImpl implements FileApi {
}
}
@Override
public DiffInfo diff(int parent) throws RestApiException {
try {
return getDiff.setParent(parent).apply(file).value();
} catch (OrmException | InvalidChangeOperationException | IOException e) {
throw new RestApiException("Cannot retrieve diff", e);
}
}
@Override
public DiffRequest diffRequest() {
return new DiffRequest() {

View File

@ -309,6 +309,17 @@ class RevisionApiImpl implements RevisionApi {
}
}
@SuppressWarnings("unchecked")
@Override
public Map<String, FileInfo> files(int parentNum) throws RestApiException {
try {
return (Map<String, FileInfo>) listFiles.setParent(parentNum)
.apply(revision).value();
} catch (OrmException | IOException e) {
throw new RestApiException("Cannot retrieve files", e);
}
}
@Override
public FileApi file(String path) {
return fileApi.create(files.parse(revision,

View File

@ -126,8 +126,11 @@ class CommentJson {
}
r.id = Url.encode(c.getKey().get());
r.path = c.getKey().getParentKey().getFileName();
if (c.getSide() == 0) {
if (c.getSide() <= 0) {
r.side = Side.PARENT;
if (c.getSide() < 0) {
r.parent = -c.getSide();
}
}
if (c.getLine() > 0) {
r.line = c.getLine();

View File

@ -15,11 +15,11 @@
package com.google.gerrit.server.change;
import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
import static com.google.gerrit.server.change.PutDraftComment.side;
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@ -119,7 +119,7 @@ public class CreateDraftComment implements RestModifyView<RevisionResource, Draf
ChangeUtil.messageUUID(ctx.getDb())),
line, ctx.getAccountId(), Url.decode(in.inReplyTo),
ctx.getWhen());
comment.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
comment.setSide(side(in));
comment.setMessage(in.message.trim());
comment.setRange(in.range);
comment.setTag(in.tag);

View File

@ -14,6 +14,8 @@
package com.google.gerrit.server.change;
import static com.google.gerrit.server.util.GitUtil.getParent;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.common.FileInfo;
@ -21,6 +23,7 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
@ -29,17 +32,24 @@ import com.google.gerrit.server.patch.PatchListNotAvailableException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
@Singleton
public class FileInfoJson {
private final PatchListCache patchListCache;
private final GitRepositoryManager repoManager;
@Inject
FileInfoJson(PatchListCache patchListCache) {
FileInfoJson(
PatchListCache patchListCache,
GitRepositoryManager repoManager) {
this.repoManager = repoManager;
this.patchListCache = patchListCache;
}
@ -54,6 +64,22 @@ public class FileInfoJson {
? null
: ObjectId.fromString(base.getRevision().get());
ObjectId b = ObjectId.fromString(revision.get());
return toFileInfoMap(change, a, b);
}
Map<String, FileInfo> toFileInfoMap(Change change, RevId revision, int parent)
throws RepositoryNotFoundException, IOException,
PatchListNotAvailableException {
ObjectId b = ObjectId.fromString(revision.get());
ObjectId a;
try (Repository git = repoManager.openRepository(change.getProject())) {
a = getParent(git, b, parent);
}
return toFileInfoMap(change, a, b);
}
private Map<String, FileInfo> toFileInfoMap(Change change,
ObjectId a, ObjectId b) throws PatchListNotAvailableException {
PatchList list = patchListCache.get(
new PatchListKey(a, b, Whitespace.IGNORE_NONE), change.getProject());

View File

@ -95,6 +95,9 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
@Option(name = "--base", metaVar = "revision-id")
String base;
@Option(name = "--parent", metaVar = "parent-number")
int parentNum;
@Option(name = "--reviewed")
boolean reviewed;
@ -145,24 +148,33 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
return Response.ok(query(resource));
}
PatchSet basePatchSet = null;
if (base != null) {
RevisionResource baseResource = revisions.parse(
resource.getChangeResource(), IdString.fromDecoded(base));
basePatchSet = baseResource.getPatchSet();
}
Response<Map<String, FileInfo>> r;
try {
Response<Map<String, FileInfo>> r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
resource.getPatchSet().getRevision(),
basePatchSet));
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
if (base != null) {
RevisionResource baseResource = revisions.parse(
resource.getChangeResource(), IdString.fromDecoded(base));
r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
resource.getPatchSet().getRevision(),
baseResource.getPatchSet()));
} else if (parentNum > 0) {
r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
resource.getPatchSet().getRevision(),
parentNum - 1));
} else {
r = Response.ok(fileInfoJson.toFileInfoMap(
resource.getChange(),
resource.getPatchSet()));
}
return r;
} catch (PatchListNotAvailableException e) {
throw new ResourceNotFoundException(e.getMessage());
}
if (resource.isCacheable()) {
r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
}
return r;
}
private void checkOptions() throws BadRequestException {
@ -170,6 +182,9 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
if (base != null) {
supplied++;
}
if (parentNum > 0) {
supplied++;
}
if (reviewed) {
supplied++;
}
@ -177,7 +192,8 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
supplied++;
}
if (supplied > 1) {
throw new BadRequestException("cannot combine base, reviewed, query");
throw new BadRequestException(
"cannot combine base, parent, reviewed, query");
}
}
@ -306,5 +322,10 @@ public class Files implements ChildCollection<RevisionResource, FileResource> {
this.base = base;
return this;
}
public ListFiles setParent(int parentNum) {
this.parentNum = parentNum;
return this;
}
}
}

View File

@ -90,6 +90,9 @@ public class GetDiff implements RestReadView<FileResource> {
@Option(name = "--base", metaVar = "REVISION")
String base;
@Option(name = "--parent", metaVar = "parent-number")
int parentNum;
@Deprecated
@Option(name = "--ignore-whitespace")
IgnoreWhitespace ignoreWhitespace;
@ -121,12 +124,6 @@ public class GetDiff implements RestReadView<FileResource> {
public Response<DiffInfo> apply(FileResource resource)
throws ResourceConflictException, ResourceNotFoundException,
OrmException, AuthException, InvalidChangeOperationException, IOException {
PatchSet basePatchSet = null;
if (base != null) {
RevisionResource baseResource = revisions.parse(
resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
basePatchSet = baseResource.getPatchSet();
}
DiffPreferencesInfo prefs = new DiffPreferencesInfo();
if (whitespace != null) {
prefs.ignoreWhitespace = whitespace;
@ -138,13 +135,35 @@ public class GetDiff implements RestReadView<FileResource> {
prefs.context = context;
prefs.intralineDifference = intraline;
try {
PatchScriptFactory psf = patchScriptFactoryFactory.create(
PatchScriptFactory psf;
PatchSet basePatchSet = null;
if (base != null) {
RevisionResource baseResource = revisions.parse(
resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
basePatchSet = baseResource.getPatchSet();
psf = patchScriptFactoryFactory.create(
resource.getRevision().getControl(),
resource.getPatchKey().getFileName(),
basePatchSet != null ? basePatchSet.getId() : null,
basePatchSet.getId(),
resource.getPatchKey().getParentKey(),
prefs);
} else if (parentNum > 0) {
psf = patchScriptFactoryFactory.create(
resource.getRevision().getControl(),
resource.getPatchKey().getFileName(),
parentNum - 1,
resource.getPatchKey().getParentKey(),
prefs);
} else {
psf = patchScriptFactoryFactory.create(
resource.getRevision().getControl(),
resource.getPatchKey().getFileName(),
null,
resource.getPatchKey().getParentKey(),
prefs);
}
try {
psf.setLoadHistory(false);
psf.setLoadComments(context != DiffPreferencesInfo.WHOLE_FILE_CONTEXT);
PatchScript ps = psf.call();
@ -272,6 +291,11 @@ public class GetDiff implements RestReadView<FileResource> {
return this;
}
public GetDiff setParent(int parentNum) {
this.parentNum = parentNum;
return this;
}
public GetDiff setContext(int context) {
this.context = context;
return this;

View File

@ -17,6 +17,7 @@ package com.google.gerrit.server.change;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
import static com.google.gerrit.server.change.PutDraftComment.side;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -469,7 +470,7 @@ public class PostReview implements RestModifyView<RevisionResource, ReviewInput>
}
e.setStatus(PatchLineComment.Status.PUBLISHED);
e.setWrittenOn(ctx.getWhen());
e.setSide(c.side == Side.PARENT ? (short) 0 : (short) 1);
e.setSide(side(c));
setCommentRevId(e, patchListCache, ctx.getChange(), ps);
e.setMessage(c.message);
e.setTag(in.tag);

View File

@ -19,6 +19,7 @@ import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
import com.google.common.base.Optional;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
@ -163,7 +164,7 @@ public class PutDraftComment implements RestModifyView<DraftCommentResource, Dra
private static PatchLineComment update(PatchLineComment e, DraftInput in,
Timestamp when) {
if (in.side != null) {
e.setSide(in.side == Side.PARENT ? (short) 0 : (short) 1);
e.setSide(side(in));
}
if (in.inReplyTo != null) {
e.setParentUuid(Url.decode(in.inReplyTo));
@ -180,4 +181,11 @@ public class PutDraftComment implements RestModifyView<DraftCommentResource, Dra
}
return e;
}
static short side(Comment c) {
if (c.side == Side.PARENT) {
return (short) (c.parent == null ? 0 : -c.parent.shortValue());
}
return 1;
}
}

View File

@ -82,6 +82,7 @@ public class ChangeNoteUtil {
private static final String FILE = "File";
private static final String LENGTH = "Bytes";
private static final String PARENT = "Parent";
private static final String PARENT_NUMBER = "Parent-number";
private static final String PATCH_SET = "Patch-set";
private static final String REVISION = "Revision";
private static final String UUID = "UUID";
@ -151,11 +152,13 @@ public class ChangeNoteUtil {
int sizeOfNote = note.length;
byte[] psb = PATCH_SET.getBytes(UTF_8);
byte[] bpsb = BASE_PATCH_SET.getBytes(UTF_8);
byte[] bpn = PARENT_NUMBER.getBytes(UTF_8);
RevId revId = new RevId(parseStringField(note, p, changeId, REVISION));
String fileName = null;
PatchSet.Id psId = null;
boolean isForBase = false;
Integer parentNumber = null;
while (p.value < sizeOfNote) {
boolean matchPs = match(note, p, psb);
@ -168,13 +171,16 @@ public class ChangeNoteUtil {
fileName = null;
psId = parsePsId(note, p, changeId, BASE_PATCH_SET);
isForBase = true;
if (match(note, p, bpn)) {
parentNumber = parseParentNumber(note, p, changeId);
}
} else if (psId == null) {
throw parseException(changeId, "missing %s or %s header",
PATCH_SET, BASE_PATCH_SET);
}
PatchLineComment c =
parseComment(note, p, fileName, psId, revId, isForBase, status);
PatchLineComment c = parseComment(
note, p, fileName, psId, revId, isForBase, parentNumber, status);
fileName = c.getKey().getParentKey().getFileName();
if (!seen.add(c.getKey())) {
throw parseException(
@ -187,7 +193,7 @@ public class ChangeNoteUtil {
private PatchLineComment parseComment(byte[] note, MutableInteger curr,
String currentFileName, PatchSet.Id psId, RevId revId, boolean isForBase,
Status status) throws ConfigInvalidException {
Integer parentNumber, Status status) throws ConfigInvalidException {
Change.Id changeId = psId.getParentKey();
// Check if there is a new file.
@ -235,7 +241,13 @@ public class ChangeNoteUtil {
range.getEndLine(), aId, parentUUID, commentTime);
plc.setMessage(message);
plc.setTag(tag);
plc.setSide((short) (isForBase ? 0 : 1));
if (isForBase) {
plc.setSide((short) (parentNumber == null ? 0 : -parentNumber));
} else {
plc.setSide((short) 1);
}
if (range.getStartCharacter() != -1) {
plc.setRange(range);
}
@ -333,6 +345,23 @@ public class ChangeNoteUtil {
return new PatchSet.Id(changeId, patchSetId);
}
private static Integer parseParentNumber(byte[] note, MutableInteger curr,
Change.Id changeId) throws ConfigInvalidException {
checkHeaderLineFormat(note, curr, PARENT_NUMBER, changeId);
int start = RawParseUtils.endOfFooterLineKey(note, curr.value) + 1;
MutableInteger i = new MutableInteger();
int parentNumber = RawParseUtils.parseBase10(note, start, i);
int endOfLine = RawParseUtils.nextLF(note, curr.value);
if (i.value != endOfLine - 1) {
throw parseException(changeId, "could not parse %s", PARENT_NUMBER);
}
checkResult(parentNumber, "parent number", changeId);
curr.value = endOfLine;
return Integer.valueOf(parentNumber);
}
private static String parseFilename(byte[] note, MutableInteger curr,
Change.Id changeId) throws ConfigInvalidException {
checkHeaderLineFormat(note, curr, FILE, changeId);
@ -461,10 +490,13 @@ public class ChangeNoteUtil {
PatchLineComment first = psComments.get(0);
short side = first.getSide();
appendHeaderField(writer, side == 0
appendHeaderField(writer, side <= 0
? BASE_PATCH_SET
: PATCH_SET,
Integer.toString(psId.get()));
if (side < 0) {
appendHeaderField(writer, PARENT_NUMBER, Integer.toString(-side));
}
String currentFilename = null;

View File

@ -28,7 +28,7 @@ public interface PatchListCache {
PatchList get(Change change, PatchSet patchSet)
throws PatchListNotAvailableException;
ObjectId getOldId(Change change, PatchSet patchSet)
ObjectId getOldId(Change change, PatchSet patchSet, Integer parentNum)
throws PatchListNotAvailableException;
IntraLineDiff getIntraLineDiff(IntraLineDiffKey key,

View File

@ -103,6 +103,17 @@ public class PatchListCacheImpl implements PatchListCache {
@Override
public PatchList get(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
return get(change, patchSet, null);
}
@Override
public ObjectId getOldId(Change change, PatchSet patchSet, Integer parentNum)
throws PatchListNotAvailableException {
return get(change, patchSet, parentNum).getOldId();
}
private PatchList get(Change change, PatchSet patchSet, Integer parentNum)
throws PatchListNotAvailableException {
Project.NameKey project = change.getProject();
if (patchSet.getRevision() == null) {
throw new PatchListNotAvailableException(
@ -110,13 +121,10 @@ public class PatchListCacheImpl implements PatchListCache {
}
ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
Whitespace ws = Whitespace.IGNORE_NONE;
return get(new PatchListKey(null, b, ws), project);
}
@Override
public ObjectId getOldId(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
return get(change, patchSet).getOldId();
if (parentNum != null) {
return get(PatchListKey.againstParentNum(parentNum, b, ws), project);
}
return get(PatchListKey.againstDefaultBase(b, ws), project);
}
@Override

View File

@ -32,9 +32,10 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;
public class PatchListKey implements Serializable {
static final long serialVersionUID = 20L;
static final long serialVersionUID = 21L;
public static final BiMap<Whitespace, Character> WHITESPACE_TYPES = ImmutableBiMap.of(
Whitespace.IGNORE_NONE, 'N',
@ -46,7 +47,36 @@ public class PatchListKey implements Serializable {
checkState(WHITESPACE_TYPES.size() == Whitespace.values().length);
}
public static PatchListKey againstDefaultBase(AnyObjectId newId,
Whitespace ws) {
return new PatchListKey(null, newId, ws);
}
public static PatchListKey againstParentNum(int parentNum, AnyObjectId newId,
Whitespace ws) {
return new PatchListKey(parentNum, newId, ws);
}
/**
* Old patch-set ID
* <p>
* When null, it represents the Base of the newId for a non-merge commit.
* <p>
* When newId is a merge commit, null value of the oldId represents either
* the auto-merge commit of the newId or a parent commit of the newId.
* These two cases are distinguished by the parentNum.
*/
private transient ObjectId oldId;
/**
* 1-based parent number when newId is a merge commit
* <p>
* For the auto-merge case this field is null.
* <p>
* Used only when oldId is null and newId is a merge commit
*/
private transient Integer parentNum;
private transient ObjectId newId;
private transient Whitespace whitespace;
@ -56,12 +86,24 @@ public class PatchListKey implements Serializable {
whitespace = ws;
}
private PatchListKey(int parentNum, AnyObjectId b, Whitespace ws) {
this.parentNum = Integer.valueOf(parentNum);
newId = b.copy();
whitespace = ws;
}
/** Old side commit, or null to assume ancestor or combined merge. */
@Nullable
public ObjectId getOldId() {
return oldId;
}
/** Parent number (old side) of the new side (merge) commit */
@Nullable
public Integer getParentNum() {
return parentNum;
}
/** New side commit name. */
public ObjectId getNewId() {
return newId;
@ -73,24 +115,16 @@ public class PatchListKey implements Serializable {
@Override
public int hashCode() {
int h = 0;
if (oldId != null) {
h = h * 31 + oldId.hashCode();
}
h = h * 31 + newId.hashCode();
h = h * 31 + whitespace.name().hashCode();
return h;
return Objects.hash(oldId, parentNum, newId, whitespace);
}
@Override
public boolean equals(final Object o) {
if (o instanceof PatchListKey) {
final PatchListKey k = (PatchListKey) o;
return eq(oldId, k.oldId) //
&& eq(newId, k.newId) //
PatchListKey k = (PatchListKey) o;
return Objects.equals(oldId, k.oldId)
&& Objects.equals(parentNum, k.parentNum)
&& Objects.equals(newId, k.newId)
&& whitespace == k.whitespace;
}
return false;
@ -109,15 +143,9 @@ public class PatchListKey implements Serializable {
return n.toString();
}
private static boolean eq(final ObjectId a, final ObjectId b) {
if (a == null && b == null) {
return true;
}
return a != null && b != null && AnyObjectId.equals(a, b);
}
private void writeObject(final ObjectOutputStream out) throws IOException {
writeCanBeNull(out, oldId);
out.writeInt(parentNum == null ? 0 : parentNum);
writeNotNull(out, newId);
Character c = WHITESPACE_TYPES.get(whitespace);
if (c == null) {
@ -128,6 +156,8 @@ public class PatchListKey implements Serializable {
private void readObject(final ObjectInputStream in) throws IOException {
oldId = readCanBeNull(in);
int n = in.readInt();
parentNum = n == 0 ? null : Integer.valueOf(n);
newId = readNotNull(in);
char t = in.readChar();
whitespace = WHITESPACE_TYPES.inverse().get(t);

View File

@ -163,11 +163,11 @@ public class PatchListLoader implements Callable<PatchList> {
List<DiffEntry> diffEntries = df.scan(aTree, bTree);
Set<String> paths = null;
if (key.getOldId() != null) {
PatchListKey newKey =
new PatchListKey(null, key.getNewId(), key.getWhitespace());
PatchListKey oldKey =
new PatchListKey(null, key.getOldId(), key.getWhitespace());
if (key.getOldId() != null && b.getParentCount() == 1) {
PatchListKey newKey = PatchListKey.againstDefaultBase(
key.getNewId(), key.getWhitespace());
PatchListKey oldKey = PatchListKey.againstDefaultBase(
key.getOldId(), key.getWhitespace());
paths = FluentIterable
.from(patchListCache.get(newKey, project).getPatches())
.append(patchListCache.get(oldKey, project).getPatches())
@ -331,6 +331,11 @@ public class PatchListLoader implements Callable<PatchList> {
return r;
}
case 2:
if (key.getParentNum() != null) {
RevCommit r = b.getParent(key.getParentNum() - 1);
rw.parseBody(r);
return r;
}
return autoMerger.merge(repo, rw, ins, b, mergeStrategy);
default:
// TODO(sop) handle an octopus merge.

View File

@ -15,6 +15,7 @@
package com.google.gerrit.server.patch;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.server.util.GitUtil.getParent;
import com.google.common.base.Optional;
import com.google.gerrit.common.Nullable;
@ -44,9 +45,9 @@ import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
@ -70,6 +71,13 @@ public class PatchScriptFactory implements Callable<PatchScript> {
@Assisted("patchSetA") PatchSet.Id patchSetA,
@Assisted("patchSetB") PatchSet.Id patchSetB,
DiffPreferencesInfo diffPrefs);
PatchScriptFactory create(
ChangeControl control,
String fileName,
int parentNum,
PatchSet.Id patchSetB,
DiffPreferencesInfo diffPrefs);
}
private static final Logger log =
@ -86,6 +94,8 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private final String fileName;
@Nullable
private final PatchSet.Id psa;
@Nullable
private final int parentNum;
private final PatchSet.Id psb;
private final DiffPreferencesInfo diffPrefs;
private final ChangeEditUtil editReader;
@ -103,7 +113,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private List<Patch> history;
private CommentDetail comments;
@Inject
@AssistedInject
PatchScriptFactory(GitRepositoryManager grm,
PatchSetUtil psUtil,
Provider<PatchScriptBuilder> builderFactory,
@ -129,14 +139,45 @@ public class PatchScriptFactory implements Callable<PatchScript> {
this.fileName = fileName;
this.psa = patchSetA;
this.parentNum = -1;
this.psb = patchSetB;
this.diffPrefs = diffPrefs;
changeId = patchSetB.getParentKey();
checkArgument(
patchSetA == null || patchSetA.getParentKey().equals(changeId),
"cannot compare PatchSets from different changes: %s and %s",
patchSetA, patchSetB);
}
@AssistedInject
PatchScriptFactory(GitRepositoryManager grm,
PatchSetUtil psUtil,
Provider<PatchScriptBuilder> builderFactory,
PatchListCache patchListCache,
ReviewDb db,
AccountInfoCacheFactory.Factory aicFactory,
PatchLineCommentsUtil plcUtil,
ChangeEditUtil editReader,
@Assisted ChangeControl control,
@Assisted String fileName,
@Assisted int parentNum,
@Assisted PatchSet.Id patchSetB,
@Assisted DiffPreferencesInfo diffPrefs) {
this.repoManager = grm;
this.psUtil = psUtil;
this.builderFactory = builderFactory;
this.patchListCache = patchListCache;
this.db = db;
this.control = control;
this.aicFactory = aicFactory;
this.plcUtil = plcUtil;
this.editReader = editReader;
this.fileName = fileName;
this.psa = null;
this.parentNum = parentNum;
this.psb = patchSetB;
this.diffPrefs = diffPrefs;
changeId = patchSetB.getParentKey();
checkArgument(parentNum >= 0, "parentNum must be >= 0");
}
public void setLoadHistory(boolean load) {
@ -151,7 +192,9 @@ public class PatchScriptFactory implements Callable<PatchScript> {
public PatchScript call() throws OrmException, NoSuchChangeException,
LargeObjectException, AuthException,
InvalidChangeOperationException, IOException {
validatePatchSetId(psa);
if (parentNum < 0) {
validatePatchSetId(psa);
}
validatePatchSetId(psb);
change = control.getChange();
@ -163,15 +206,19 @@ public class PatchScriptFactory implements Callable<PatchScript> {
? new PatchSet(psb)
: psUtil.get(db, control.getNotes(), psb);
aId = psEntityA != null ? toObjectId(psEntityA) : null;
bId = toObjectId(psEntityB);
if ((psEntityA != null && !control.isPatchVisible(psEntityA, db)) ||
(psEntityB != null && !control.isPatchVisible(psEntityB, db))) {
throw new NoSuchChangeException(changeId);
}
try (Repository git = repoManager.openRepository(project)) {
bId = toObjectId(psEntityB);
if (parentNum < 0) {
aId = psEntityA != null ? toObjectId(psEntityA) : null;
} else {
aId = getParent(git, bId, parentNum);
}
try {
final PatchList list = listFor(keyFor(diffPrefs.ignoreWhitespace));
final PatchScriptBuilder b = newBuilder(list, git);

View File

@ -0,0 +1,51 @@
// Copyright (C) 2016 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.util;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
public class GitUtil {
/**
* @param git
* @param commitId
* @param parentNum
* @return the {@code paretNo} parent of given commit or {@code null}
* when {@code parentNo} exceed number of {@code commitId} parents.
* @throws IncorrectObjectTypeException
* the supplied id is not a commit or an annotated tag.
* @throws IOException
* a pack file or loose object could not be read.
*/
public static RevCommit getParent(Repository git,
ObjectId commitId, int parentNum) throws IOException {
try (RevWalk walk = new RevWalk(git)) {
RevCommit commit = walk.parseCommit(commitId);
if (commit.getParentCount() > parentNum) {
return commit.getParent(parentNum);
}
}
return null;
}
private GitUtil() {
}
}