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

Add 'Parent $x' options to the diff base drop down for merge commits.
This way users are able to see changes brought by merge commit in
addition to the merge conflicts resolution.

Two main challenges of this change are:
* server: enable commenting on a Parent-N of a merge commit
* client: pass around the diff-base info which for merge commits can be:
  Auto-Merge, Parent-1, Parent-2, ... , Parent-N

Currently the patch_comments.side field uses two values:
1 = REVISION
0 = PARENT

For non-merge commits, side == 0 means comment of the Base (the parent)
of this patch-set.  For a merge commit, side == 0 means comment on the
Auto-Merge of this (merge) patch-set.

This change uses side == -N to store comment on the Parent-N of this (merge)
patch-set. For Parent-1 the side is -1, for Parent-2 the side is -2, etc..
This avoids need for a schema migration.

In NoteDb the parent number is stored in a new field: "Parent-number: 1".

CommentInfo was extended with the new "parent" field which is 1-based
parent number.  Some REST API endpoints expose a new --parent option to
enable referencing a specific parent of a merge commit.

On the client side we typically pass around two PatchSet.Id's: base and
revision to represent the state of the UI. The base had two meanings:
* when it was non-null it was representing another patch-set
* when it was null it represented the parent for a non-merge commit and
  the auto-merge for a merge commit.

For a merge commit we need to also pass around Parent-N as (diff) base for a
merge commit. To keep the number of changes minimal this change proposes
(re)using the base patch-set for that where its patch-set number is negative.
Therefore, when base is not null but its patch-set number is negative (-N) it
represents Parent-N of the patch-set represented by the revision patch-set.
This is also expressed in the client URL token which uses negative numbers
for parent(s) of a merge commit. For example: -1..2 represents comparison
of the second patch-set against its first parent. -2..2 would represent
comparison of the patch-set 2 against its second parent.

I experimented with introducing a DiffBase class which would be a combination
of a base patch-set plus a parent number:

  class DiffBase {
    PatchSet.Id base;
    int parent;
  }

which would avoid the ugliness of using a base with negative patch-set number.
However, this produced neither a smaller nor a more elegant change. The number
of changed files actually increased and, still, all places where base is used
had to handle the case where base is null and parent is greater than zero.

Bug: Issue 106
Change-Id: If0d7b13fad9051ec2943f6d51c99e84f7d2af708
Also-by: Dariusz Luksza <dariusz@luksza.org>
Also-by: Saša Živkov <zivkov@gmail.com>
This commit is contained in:
Dawid Grzegorczyk 2015-11-07 11:26:02 +01:00 committed by Saša Živkov
parent 38f96373e0
commit 5904524de6
42 changed files with 942 additions and 183 deletions

View File

@ -3508,6 +3508,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
@ -3753,6 +3758,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.
@ -4297,6 +4307,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;
@ -490,6 +494,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")
@ -207,12 +240,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.getUser().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;
@ -426,7 +427,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() {
}
}