diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt index 97a09cfa12..ac7be2ce19 100644 --- a/Documentation/rest-api-changes.txt +++ b/Documentation/rest-api-changes.txt @@ -2403,6 +2403,87 @@ As response the change's hashtags are returned as a list of strings. ] ---- +[[list-change-messages]] +=== List Change Messages +-- +'GET /changes/link:#change-id[\{change-id\}]/messages' +-- + +Lists all the messages of a change including link:#detailed-accounts[detailed account information]. + +.Request +---- + GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/messages +---- + +As response a list of link:#change-message-info[ChangeMessageInfo] entities is returned. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + [ + { + "id": "YH-egE", + "author": { + "_account_id": 1000096, + "name": "John Doe", + "email": "john.doe@example.com", + "username": "jdoe" + }, + "date": "2013-03-23 21:34:02.419000000", + "message": "Patch Set 1:\n\nThis is the first message.", + "_revision_number": 1 + }, + { + "id": "WEEdhU", + "author": { + "_account_id": 1000097, + "name": "Jane Roe", + "email": "jane.roe@example.com", + "username": "jroe" + }, + "date": "2013-03-23 21:36:52.332000000", + "message": "Patch Set 1:\n\nThis is the second message.\n\nWith a line break.", + "_revision_number": 1 + } + ] +---- + +[[get-change-message]] +=== Get Change Message + +Retrieves a change message including link:#detailed-accounts[detailed account information]. + +-- +'GET /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}' +-- + +As response a link:#change-message-info[ChangeMessageInfo] entity is returned. + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + { + "id": "aaee04dcb46bafc8be24d8aa70b3b1beb7df5780", + "author": { + "_account_id": 1000096, + "name": "John Doe", + "email": "john.doe@example.com", + "username": "jdoe" + }, + "date": "2013-03-23 21:34:02.419000000", + "message": "Change message removed by: Administrator; Reason: spam", + "_revision_number": 1 + } +---- [[edit-endpoints]] == Change Edit Endpoints @@ -5366,6 +5447,10 @@ If you need more time to migrate off of old change IDs, please see link:config-gerrit.html#change.api.allowedIdentifier[change.api.allowedIdentifier] for more information on how to enable the use of deprecated identifiers. +[[change-message-id]] +=== \{change-message-id\} +ID of a change message returned in a link:#change-message-info[ChangeMessageInfo]. + [[comment-id]] === \{comment-id\} UUID of a published comment. diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java index 481681eee0..8b2d29b846 100644 --- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java +++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java @@ -19,6 +19,7 @@ import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.extensions.common.CommitMessageInput; import com.google.gerrit.extensions.common.EditInfo; @@ -283,6 +284,25 @@ public interface ChangeApi { /** Check if this change is a pure revert of claimedOriginal (SHA1 in 40 digit hex). */ PureRevertInfo pureRevert(String claimedOriginal) throws RestApiException; + /** + * Get all messages of a change with detailed account info. + * + * @return a list of messages sorted by their creation time. + * @throws RestApiException + */ + List messages() throws RestApiException; + + /** + * Look up a change message of a change by its id. + * + * @param id the id of the change message. Note that in NoteDb, this id is the {@code ObjectId} of + * a commit on the change meta branch. In ReviewDb, it's a UUID generated randomly. That means + * a change message id could be different between NoteDb and ReviewDb. + * @return API for accessing a change message. + * @throws RestApiException if the id is invalid. + */ + ChangeMessageApi message(String id) throws RestApiException; + abstract class SuggestedReviewersRequest { private String query; private int limit; @@ -590,5 +610,15 @@ public interface ChangeApi { public PureRevertInfo pureRevert(String claimedOriginal) throws RestApiException { throw new NotImplementedException(); } + + @Override + public List messages() throws RestApiException { + throw new NotImplementedException(); + } + + @Override + public ChangeMessageApi message(String id) throws RestApiException { + throw new NotImplementedException(); + } } } diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeMessageApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeMessageApi.java new file mode 100644 index 0000000000..eb2d2b9b5b --- /dev/null +++ b/java/com/google/gerrit/extensions/api/changes/ChangeMessageApi.java @@ -0,0 +1,36 @@ +// Copyright (C) 2018 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.extensions.api.changes; + +import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.restapi.NotImplementedException; +import com.google.gerrit.extensions.restapi.RestApiException; + +/** Interface for change message APIs. */ +public interface ChangeMessageApi { + /** Gets one change message. */ + ChangeMessageInfo get() throws RestApiException; + + /** + * A default implementation which allows source compatibility when adding new methods to the + * interface. + */ + class NotImplemented implements ChangeMessageApi { + @Override + public ChangeMessageInfo get() throws RestApiException { + throw new NotImplementedException(); + } + } +} diff --git a/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java b/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java index 735b84f16d..a2e61b834c 100644 --- a/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java +++ b/java/com/google/gerrit/extensions/common/ChangeMessageInfo.java @@ -15,6 +15,7 @@ package com.google.gerrit.extensions.common; import java.sql.Timestamp; +import java.util.Objects; public class ChangeMessageInfo { public String id; @@ -24,4 +25,24 @@ public class ChangeMessageInfo { public Timestamp date; public String message; public Integer _revisionNumber; + + @Override + public boolean equals(Object o) { + if (o instanceof ChangeMessageInfo) { + ChangeMessageInfo cmi = (ChangeMessageInfo) o; + return Objects.equals(id, cmi.id) + && Objects.equals(tag, cmi.tag) + && Objects.equals(author, cmi.author) + && Objects.equals(realAuthor, cmi.realAuthor) + && Objects.equals(date, cmi.date) + && Objects.equals(message, cmi.message) + && Objects.equals(_revisionNumber, cmi._revisionNumber); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(id, tag, author, realAuthor, date, message, _revisionNumber); + } } diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java index 9aae00b8aa..75a9991c8e 100644 --- a/java/com/google/gerrit/server/ChangeMessagesUtil.java +++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java @@ -19,10 +19,12 @@ import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; import com.google.gerrit.common.Nullable; +import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.ChangeMessage; import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.account.AccountLoader; import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeUpdate; import com.google.gerrit.server.notedb.NotesMigration; @@ -141,4 +143,21 @@ public class ChangeMessagesUtil { public static boolean isAutogenerated(@Nullable String tag) { return tag != null && tag.startsWith(AUTOGENERATED_TAG_PREFIX); } + + public static ChangeMessageInfo createChangeMessageInfo( + ChangeMessage message, AccountLoader accountLoader) { + PatchSet.Id patchNum = message.getPatchSetId(); + ChangeMessageInfo cmi = new ChangeMessageInfo(); + cmi.id = message.getKey().get(); + cmi.author = accountLoader.get(message.getAuthor()); + cmi.date = message.getWrittenOn(); + cmi.message = message.getMessage(); + cmi.tag = message.getTag(); + cmi._revisionNumber = patchNum != null ? patchNum.get() : null; + Account.Id realAuthor = message.getRealAuthor(); + if (realAuthor != null) { + cmi.realAuthor = accountLoader.get(realAuthor); + } + return cmi; + } } diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java index 6c6df1d0ba..fb42ed0583 100644 --- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java +++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java @@ -23,6 +23,7 @@ import com.google.gerrit.extensions.api.changes.AddReviewerResult; import com.google.gerrit.extensions.api.changes.AssigneeInput; import com.google.gerrit.extensions.api.changes.ChangeApi; import com.google.gerrit.extensions.api.changes.ChangeEditApi; +import com.google.gerrit.extensions.api.changes.ChangeMessageApi; import com.google.gerrit.extensions.api.changes.Changes; import com.google.gerrit.extensions.api.changes.FixInput; import com.google.gerrit.extensions.api.changes.HashtagsInput; @@ -39,6 +40,7 @@ import com.google.gerrit.extensions.api.changes.TopicInput; import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.AccountInfo; import com.google.gerrit.extensions.common.ChangeInfo; +import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.extensions.common.CommitMessageInput; import com.google.gerrit.extensions.common.EditInfo; @@ -53,11 +55,13 @@ import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.server.StarredChangesUtil; import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException; import com.google.gerrit.server.change.ChangeJson; +import com.google.gerrit.server.change.ChangeMessageResource; import com.google.gerrit.server.change.ChangeResource; import com.google.gerrit.server.change.PureRevert; import com.google.gerrit.server.change.WorkInProgressOp; import com.google.gerrit.server.restapi.change.Abandon; import com.google.gerrit.server.restapi.change.ChangeIncludedIn; +import com.google.gerrit.server.restapi.change.ChangeMessages; import com.google.gerrit.server.restapi.change.Check; import com.google.gerrit.server.restapi.change.CreateMergePatchSet; import com.google.gerrit.server.restapi.change.DeleteAssignee; @@ -111,6 +115,8 @@ class ChangeApiImpl implements ChangeApi { private final Revisions revisions; private final ReviewerApiImpl.Factory reviewerApi; private final RevisionApiImpl.Factory revisionApi; + private final ChangeMessageApiImpl.Factory changeMessageApi; + private final ChangeMessages changeMessages; private final SuggestChangeReviewers suggestReviewers; private final ChangeResource change; private final Abandon abandon; @@ -157,6 +163,8 @@ class ChangeApiImpl implements ChangeApi { Revisions revisions, ReviewerApiImpl.Factory reviewerApi, RevisionApiImpl.Factory revisionApi, + ChangeMessageApiImpl.Factory changeMessageApi, + ChangeMessages changeMessages, SuggestChangeReviewers suggestReviewers, Abandon abandon, Revert revert, @@ -201,6 +209,8 @@ class ChangeApiImpl implements ChangeApi { this.revisions = revisions; this.reviewerApi = reviewerApi; this.revisionApi = revisionApi; + this.changeMessageApi = changeMessageApi; + this.changeMessages = changeMessages; this.suggestReviewers = suggestReviewers; this.abandon = abandon; this.restore = restore; @@ -709,4 +719,23 @@ class ChangeApiImpl implements ChangeApi { throw asRestApiException("Cannot compute pure revert", e); } } + + @Override + public List messages() throws RestApiException { + try { + return changeMessages.list().apply(change); + } catch (Exception e) { + throw asRestApiException("Cannot list change messages", e); + } + } + + @Override + public ChangeMessageApi message(String id) throws RestApiException { + try { + ChangeMessageResource resource = changeMessages.parse(change, IdString.fromDecoded(id)); + return changeMessageApi.create(resource); + } catch (Exception e) { + throw asRestApiException("Cannot parse change message " + id, e); + } + } } diff --git a/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java new file mode 100644 index 0000000000..988ac91a25 --- /dev/null +++ b/java/com/google/gerrit/server/api/changes/ChangeMessageApiImpl.java @@ -0,0 +1,50 @@ +// Copyright (C) 2018 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.api.changes; + +import static com.google.gerrit.server.api.ApiUtil.asRestApiException; + +import com.google.gerrit.extensions.api.changes.ChangeMessageApi; +import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.change.ChangeMessageResource; +import com.google.gerrit.server.restapi.change.GetChangeMessage; +import com.google.inject.Inject; +import com.google.inject.assistedinject.Assisted; + +class ChangeMessageApiImpl implements ChangeMessageApi { + interface Factory { + ChangeMessageApiImpl create(ChangeMessageResource changeMessageResource); + } + + private final GetChangeMessage getChangeMessage; + private final ChangeMessageResource changeMessageResource; + + @Inject + ChangeMessageApiImpl( + GetChangeMessage getChangeMessage, @Assisted ChangeMessageResource changeMessageResource) { + this.getChangeMessage = getChangeMessage; + this.changeMessageResource = changeMessageResource; + } + + @Override + public ChangeMessageInfo get() throws RestApiException { + try { + return getChangeMessage.apply(changeMessageResource); + } catch (Exception e) { + throw asRestApiException("Cannot retrieve change message", e); + } + } +} diff --git a/java/com/google/gerrit/server/api/changes/Module.java b/java/com/google/gerrit/server/api/changes/Module.java index e91d64ae73..0edd58ad38 100644 --- a/java/com/google/gerrit/server/api/changes/Module.java +++ b/java/com/google/gerrit/server/api/changes/Module.java @@ -31,5 +31,6 @@ public class Module extends FactoryModule { factory(ReviewerApiImpl.Factory.class); factory(RevisionReviewerApiImpl.Factory.class); factory(ChangeEditApiImpl.Factory.class); + factory(ChangeMessageApiImpl.Factory.class); } } diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java index 6c039124c9..33dc5cbf30 100644 --- a/java/com/google/gerrit/server/change/ChangeJson.java +++ b/java/com/google/gerrit/server/change/ChangeJson.java @@ -37,6 +37,7 @@ import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_MERGEAB import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE; import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS; import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS; +import static com.google.gerrit.server.ChangeMessagesUtil.createChangeMessageInfo; import static com.google.gerrit.server.CommonConverters.toGitPerson; import static java.util.stream.Collectors.toList; @@ -1189,19 +1190,7 @@ public class ChangeJson { List result = Lists.newArrayListWithCapacity(messages.size()); for (ChangeMessage message : messages) { - PatchSet.Id patchNum = message.getPatchSetId(); - ChangeMessageInfo cmi = new ChangeMessageInfo(); - cmi.id = message.getKey().get(); - cmi.author = accountLoader.get(message.getAuthor()); - cmi.date = message.getWrittenOn(); - cmi.message = message.getMessage(); - cmi.tag = message.getTag(); - cmi._revisionNumber = patchNum != null ? patchNum.get() : null; - Account.Id realAuthor = message.getRealAuthor(); - if (realAuthor != null) { - cmi.realAuthor = accountLoader.get(realAuthor); - } - result.add(cmi); + result.add(createChangeMessageInfo(message, accountLoader)); } return result; } diff --git a/java/com/google/gerrit/server/change/ChangeMessageResource.java b/java/com/google/gerrit/server/change/ChangeMessageResource.java new file mode 100644 index 0000000000..3c9ef34a28 --- /dev/null +++ b/java/com/google/gerrit/server/change/ChangeMessageResource.java @@ -0,0 +1,63 @@ +// Copyright (C) 2018 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.change; + +import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.restapi.RestResource; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.reviewdb.client.Change; +import com.google.inject.TypeLiteral; + +/** A change message resource. */ +public class ChangeMessageResource implements RestResource { + public static final TypeLiteral> CHANGE_MESSAGE_KIND = + new TypeLiteral>() {}; + + private final ChangeResource changeResource; + private final ChangeMessageInfo changeMessage; + private final int changeMessageIndex; + + public ChangeMessageResource( + ChangeResource changeResource, ChangeMessageInfo changeMessage, int changeMessageIndex) { + this.changeResource = changeResource; + this.changeMessage = changeMessage; + this.changeMessageIndex = changeMessageIndex; + } + + public ChangeResource getChangeResource() { + return changeResource; + } + + public ChangeMessageInfo getChangeMessage() { + return changeMessage; + } + + public Change.Id getChangeId() { + return changeResource.getId(); + } + + public String getChangeMessageId() { + return changeMessage.id; + } + + /** + * Gets the index of the change message among all messages of the change sorted by creation time. + * + * @return the index of the change message. + */ + public int getChangeMessageIndex() { + return changeMessageIndex; + } +} diff --git a/java/com/google/gerrit/server/restapi/change/ChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ChangeMessages.java new file mode 100644 index 0000000000..251c66d419 --- /dev/null +++ b/java/com/google/gerrit/server/restapi/change/ChangeMessages.java @@ -0,0 +1,73 @@ +// Copyright (C) 2018 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.restapi.change; + +import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.restapi.ChildCollection; +import com.google.gerrit.extensions.restapi.IdString; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestView; +import com.google.gerrit.server.change.ChangeMessageResource; +import com.google.gerrit.server.change.ChangeResource; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.List; + +@Singleton +public class ChangeMessages implements ChildCollection { + private final DynamicMap> views; + private final ListChangeMessages listChangeMessages; + + @Inject + ChangeMessages( + DynamicMap> views, ListChangeMessages listChangeMessages) { + this.views = views; + this.listChangeMessages = listChangeMessages; + } + + @Override + public DynamicMap> views() { + return views; + } + + @Override + public ListChangeMessages list() { + return listChangeMessages; + } + + @Override + public ChangeMessageResource parse(ChangeResource parent, IdString id) + throws OrmException, ResourceNotFoundException { + String uuid = id.get(); + + List changeMessages = listChangeMessages.apply(parent); + int index = -1; + for (int i = 0; i < changeMessages.size(); ++i) { + ChangeMessageInfo changeMessage = changeMessages.get(i); + if (changeMessage.id.equals(uuid)) { + index = i; + break; + } + } + + if (index < 0) { + throw new ResourceNotFoundException(String.format("change message %s not found", uuid)); + } + + return new ChangeMessageResource(parent, changeMessages.get(index), index); + } +} diff --git a/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java b/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java new file mode 100644 index 0000000000..f55785d161 --- /dev/null +++ b/java/com/google/gerrit/server/restapi/change/GetChangeMessage.java @@ -0,0 +1,29 @@ +// Copyright (C) 2018 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.restapi.change; + +import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.server.change.ChangeMessageResource; +import com.google.inject.Singleton; + +/** Gets one change message. */ +@Singleton +public class GetChangeMessage implements RestReadView { + @Override + public ChangeMessageInfo apply(ChangeMessageResource resource) { + return resource.getChangeMessage(); + } +} diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java new file mode 100644 index 0000000000..cf76ef1c6b --- /dev/null +++ b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java @@ -0,0 +1,61 @@ +// Copyright (C) 2018 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.restapi.change; + +import static com.google.gerrit.server.ChangeMessagesUtil.createChangeMessageInfo; + +import com.google.gerrit.extensions.common.ChangeMessageInfo; +import com.google.gerrit.extensions.restapi.RestReadView; +import com.google.gerrit.reviewdb.client.ChangeMessage; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.ChangeMessagesUtil; +import com.google.gerrit.server.account.AccountLoader; +import com.google.gerrit.server.change.ChangeResource; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; +import java.util.List; +import java.util.stream.Collectors; + +@Singleton +public class ListChangeMessages implements RestReadView { + private final Provider dbProvider; + private final ChangeMessagesUtil changeMessagesUtil; + private final AccountLoader accountLoader; + + @Inject + public ListChangeMessages( + Provider dbProvider, + ChangeMessagesUtil changeMessagesUtil, + AccountLoader.Factory accountLoaderFactory) { + this.dbProvider = dbProvider; + this.changeMessagesUtil = changeMessagesUtil; + this.accountLoader = accountLoaderFactory.create(true); + } + + @Override + public List apply(ChangeResource resource) throws OrmException { + List messages = + changeMessagesUtil.byChange(dbProvider.get(), resource.getNotes()); + List messageInfos = + messages + .stream() + .map(m -> createChangeMessageInfo(m, accountLoader)) + .collect(Collectors.toList()); + accountLoader.fill(); + return messageInfos; + } +} diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java index f04aca3ac1..7955fa59d6 100644 --- a/java/com/google/gerrit/server/restapi/change/Module.java +++ b/java/com/google/gerrit/server/restapi/change/Module.java @@ -15,6 +15,7 @@ package com.google.gerrit.server.restapi.change; import static com.google.gerrit.server.change.ChangeEditResource.CHANGE_EDIT_KIND; +import static com.google.gerrit.server.change.ChangeMessageResource.CHANGE_MESSAGE_KIND; import static com.google.gerrit.server.change.ChangeResource.CHANGE_KIND; import static com.google.gerrit.server.change.CommentResource.COMMENT_KIND; import static com.google.gerrit.server.change.DraftCommentResource.DRAFT_COMMENT_KIND; @@ -65,6 +66,7 @@ public class Module extends RestApiModule { DynamicMap.mapOf(binder(), REVISION_KIND); DynamicMap.mapOf(binder(), CHANGE_EDIT_KIND); DynamicMap.mapOf(binder(), VOTE_KIND); + DynamicMap.mapOf(binder(), CHANGE_MESSAGE_KIND); get(CHANGE_KIND).to(GetChange.class); post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class); @@ -174,6 +176,9 @@ public class Module extends RestApiModule { get(CHANGE_EDIT_KIND, "meta").to(ChangeEdits.GetMeta.class); post(COMMIT_KIND, "cherrypick").to(CherryPickCommit.class); + child(CHANGE_KIND, "messages").to(ChangeMessages.class); + get(CHANGE_MESSAGE_KIND).to(GetChangeMessage.class); + factory(AccountLoader.Factory.class); factory(ChangeEdits.Create.Factory.class); factory(ChangeEdits.DeleteFile.Factory.class); diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java index c55af25ff0..d0b01b74d6 100644 --- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java +++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java @@ -15,16 +15,22 @@ package com.google.gerrit.acceptance.rest.change; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME; import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import static java.util.concurrent.TimeUnit.SECONDS; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.ChangeMessageInfo; import com.google.gerrit.testing.ConfigSuite; import com.google.gerrit.testing.TestTimeUtil; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -95,7 +101,64 @@ public class ChangeMessagesIT extends AbstractDaemonTest { assertThat(actual.tag).isEqualTo(tag); } - private void assertMessage(String expected, String actual) { + @Test + public void listChangeMessages() throws Exception { + int changeNum = createOneChangeWithMultipleChangeMessagesInHistory(); + List messages1 = gApi.changes().id(changeNum).messages(); + List messages2 = + new ArrayList<>(gApi.changes().id(changeNum).get().messages); + assertThat(messages1).containsExactlyElementsIn(messages2).inOrder(); + } + + @Test + public void getOneChangeMessage() throws Exception { + int changeNum = createOneChangeWithMultipleChangeMessagesInHistory(); + List messages = new ArrayList<>(gApi.changes().id(changeNum).get().messages); + + for (ChangeMessageInfo messageInfo : messages) { + String id = messageInfo.id; + assertThat(gApi.changes().id(changeNum).message(id).get()).isEqualTo(messageInfo); + } + } + + private int createOneChangeWithMultipleChangeMessagesInHistory() throws Exception { + setApiUser(user); + // Commit 1: create a change. + PushOneCommit.Result result = createChange(); + String changeId = result.getChangeId(); + // Commit 2: post a review with message "message 1". + setApiUser(admin); + addOneReview(changeId, "message 1"); + // Commit 3: amend a new patch set. + setApiUser(user); + amendChange(changeId); + // Commit 4: post a review with message "message 2". + addOneReview(changeId, "message 2"); + // Commit 5: amend a new patch set. + amendChange(changeId); + // Commit 6: approve the change. + setApiUser(admin); + gApi.changes().id(changeId).current().review(ReviewInput.approve()); + // commit 7: submit the change. + gApi.changes().id(changeId).current().submit(); + + return result.getChange().getId().get(); + } + + private void addOneReview(String changeId, String changeMessage) throws Exception { + ReviewInput.CommentInput c = new ReviewInput.CommentInput(); + c.line = 1; + c.message = "comment 1"; + c.path = FILE_NAME; + + ReviewInput reviewInput = new ReviewInput().label("Code-Review", 1); + reviewInput.comments = ImmutableMap.of(c.path, Lists.newArrayList(c)); + reviewInput.message = changeMessage; + + gApi.changes().id(changeId).current().review(reviewInput); + } + + private static void assertMessage(String expected, String actual) { assertThat(actual).isEqualTo("Patch Set 1:\n\n" + expected); }