Add REST endpoint to get the commit message of a change
Getting the commit message of a change is surprisingly hard. It's only
possible via the Get Content REST endpoint by invoking it for the
"/COMMIT_MSG" file:
GET /changes/<CHANGE-ID>/revisions/current/files/%2FCOMMIT_MSG/content
The returned content is Base64 encoded, so callers need to decode it.
Make getting the commit message of a change simpler by adding a Get
Commit Message REST endpoint that returns the commit message as JSON.
This new REST endpoint fits well to the already existing Set Commit
Message REST endpoint.
Release-Notes: Added REST endpoint to get the commit message of a change
Change-Id: Ia4b84d695b82ae5cf7a6b9e4fbd0943f1430c5ba
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 4747957..578fabf 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1044,6 +1044,41 @@
}
----
+[[get-message]]
+=== Get Commit Message
+--
+'GET /changes/link:#change-id[\{change-id\}]/message'
+--
+
+Returns the commit message of the change (from the current patch set).
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/message HTTP/1.0
+----
+
+The commit message is returned as a link:#commit-message-info[
+CommitMessageInfo] entity.
+
+Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "subject": "Add feature X",
+ "full_message": "Add Feature X\n\nFeature X helps with foo.\n\nBug: 123\nChange-Id: I10394472cbd17dd12454f229e4f6de00b143a444\n",
+ "footers": {
+ "Bug": "123",
+ "Change-Id": "I10394472cbd17dd12454f229e4f6de00b143a444"
+ }
+ }
+----
+
+----
+
[[set-message]]
=== Set Commit Message
--
@@ -7643,6 +7678,21 @@
link:#web-link-info[WebLinkInfo] entities.
|===========================
+[[commit-message-info]]
+=== CommitMessageInfo
+The `CommitMessageInfo` entity contains information about the commit
+message of a change.
+
+[options="header",cols="1,6"]
+|============================
+|Field Name |Description
+|`subject` |The subject of the change (first line of the commit
+message).
+|`full_message` |Full commit message of the change.
+|`footers` |The footers from the commit message as a map of
+key-value pairs.
+|============================
+
[[commit-message-input]]
=== CommitMessageInput
The `CommitMessageInput` entity contains information for changing
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java b/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java
index a84e3f04..eb714d45 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java
@@ -19,6 +19,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
import com.google.gerrit.common.UsedAt;
import com.google.gerrit.entities.Account;
@@ -246,6 +247,7 @@
*
* @return the {@code Change.Id} of the created change
*/
+ @CanIgnoreReturnValue
public Change.Id create() {
TestChangeCreation changeUpdate = build();
return changeUpdate.changeCreator().applyAndThrowSilently(changeUpdate);
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index a663e25..1c83bc2 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.common.ChangeInfoDifference;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.CommitMessageInfo;
import com.google.gerrit.extensions.common.CommitMessageInput;
import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.PureRevertInfo;
@@ -321,6 +322,8 @@
*/
ChangeEditApi edit() throws RestApiException;
+ CommitMessageInfo getMessage() throws RestApiException;
+
/** Create a new patch set with a new commit message. */
default void setMessage(String message) throws RestApiException {
CommitMessageInput in = new CommitMessageInput();
@@ -717,6 +720,11 @@
}
@Override
+ public CommitMessageInfo getMessage() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void setMessage(CommitMessageInput in) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/common/CommitMessageInfo.java b/java/com/google/gerrit/extensions/common/CommitMessageInfo.java
new file mode 100644
index 0000000..fdd7cc3
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/CommitMessageInfo.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 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.common;
+
+import java.util.Map;
+
+/** Representation of a commit message used in the API. */
+public class CommitMessageInfo {
+ /**
+ * The subject of the change.
+ *
+ * <p>First line of the commit message.
+ */
+ public String subject;
+
+ /** Full commit message of the change. */
+ public String fullMessage;
+
+ /**
+ * The footers from the commit message.
+ *
+ * <p>Key-value pairs from the last paragraph of the commit message.
+ */
+ public Map<String, String> footers;
+}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 33dbf0c..a3df786 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -50,6 +50,7 @@
import com.google.gerrit.extensions.common.ChangeInfoDifference;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.CommitMessageInfo;
import com.google.gerrit.extensions.common.CommitMessageInput;
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.InputWithMessage;
@@ -84,6 +85,7 @@
import com.google.gerrit.server.restapi.change.GetChange;
import com.google.gerrit.server.restapi.change.GetCustomKeyedValues;
import com.google.gerrit.server.restapi.change.GetHashtags;
+import com.google.gerrit.server.restapi.change.GetMessage;
import com.google.gerrit.server.restapi.change.GetMetaDiff;
import com.google.gerrit.server.restapi.change.GetPureRevert;
import com.google.gerrit.server.restapi.change.GetTopic;
@@ -172,6 +174,7 @@
private final DeletePrivate deletePrivate;
private final SetWorkInProgress setWip;
private final SetReadyForReview setReady;
+ private final GetMessage getMessage;
private final PutMessage putMessage;
private final Provider<GetPureRevert> getPureRevertProvider;
private final DynamicOptionParser dynamicOptionParser;
@@ -224,6 +227,7 @@
DeletePrivate deletePrivate,
SetWorkInProgress setWip,
SetReadyForReview setReady,
+ GetMessage getMessage,
PutMessage putMessage,
Provider<GetPureRevert> getPureRevertProvider,
DynamicOptionParser dynamicOptionParser,
@@ -274,6 +278,7 @@
this.deletePrivate = deletePrivate;
this.setWip = setWip;
this.setReady = setReady;
+ this.getMessage = getMessage;
this.putMessage = putMessage;
this.getPureRevertProvider = getPureRevertProvider;
this.dynamicOptionParser = dynamicOptionParser;
@@ -561,6 +566,15 @@
}
@Override
+ public CommitMessageInfo getMessage() throws RestApiException {
+ try {
+ return getMessage.apply(change).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get message", e);
+ }
+ }
+
+ @Override
public void setMessage(CommitMessageInput in) throws RestApiException {
try {
@SuppressWarnings("unused")
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java b/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java
index fbacf39..8c95e93 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeRestApiModule.java
@@ -99,6 +99,7 @@
post(CHANGE_KIND, "index").to(Index.class);
get(CHANGE_KIND, "meta_diff").to(GetMetaDiff.class);
post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class);
+ get(CHANGE_KIND, "message").to(GetMessage.class);
put(CHANGE_KIND, "message").to(PutMessage.class);
child(CHANGE_KIND, "messages").to(ChangeMessages.class);
diff --git a/java/com/google/gerrit/server/restapi/change/GetMessage.java b/java/com/google/gerrit/server/restapi/change/GetMessage.java
new file mode 100644
index 0000000..5715caa
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/GetMessage.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 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 java.util.stream.Collectors.toMap;
+
+import com.google.gerrit.extensions.common.CommitMessageInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.eclipse.jgit.revwalk.FooterLine;
+
+@Singleton
+public class GetMessage implements RestReadView<ChangeResource> {
+ private final ChangeData.Factory changeDataFactory;
+
+ @Inject
+ GetMessage(ChangeData.Factory changeDataFactory) {
+ this.changeDataFactory = changeDataFactory;
+ }
+
+ @Override
+ public Response<CommitMessageInfo> apply(ChangeResource resource)
+ throws AuthException, BadRequestException, ResourceConflictException, Exception {
+ CommitMessageInfo commitMessageInfo = new CommitMessageInfo();
+ commitMessageInfo.subject = resource.getChange().getSubject();
+
+ ChangeData cd = changeDataFactory.create(resource.getNotes());
+ commitMessageInfo.fullMessage = cd.commitMessage();
+ commitMessageInfo.footers =
+ cd.commitFooters().stream().collect(toMap(FooterLine::getKey, FooterLine::getValue));
+
+ return Response.ok(commitMessageInfo);
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 91be148..e10bea1 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -148,6 +148,7 @@
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.CommitMessageInfo;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.common.TrackingIdInfo;
@@ -187,6 +188,7 @@
import com.google.gerrit.server.update.ChangeContext;
import com.google.gerrit.server.update.context.RefUpdateContext;
import com.google.gerrit.server.util.AccountTemplateUtil;
+import com.google.gerrit.server.util.CommitMessageUtil;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.inject.AbstractModule;
@@ -214,6 +216,7 @@
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -3988,6 +3991,22 @@
}
@Test
+ public void getCommitMessage() throws Exception {
+ String subject = "Change Subject";
+ String changeId = "I" + ObjectId.toString(CommitMessageUtil.generateChangeId());
+ String commitMessage =
+ String.format(
+ "%s\n\nFirst Paragraph.\n\nSecond Paragraph\n\nFoo: Bar\nChange-Id: %s\n",
+ subject, changeId);
+ changeOperations.newChange().project(project).commitMessage(commitMessage).create();
+
+ CommitMessageInfo commitMessageInfo = gApi.changes().id(changeId).getMessage();
+ assertThat(commitMessageInfo.subject).isEqualTo(subject);
+ assertThat(commitMessageInfo.fullMessage).isEqualTo(commitMessage);
+ assertThat(commitMessageInfo.footers).containsExactly("Foo", "Bar", "Change-Id", changeId);
+ }
+
+ @Test
public void changeCommitMessage() throws Exception {
// Tests mutating the commit message as both the owner of the change and a regular user with
// addPatchSet permission. Asserts that both cases succeed.
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 75f335a..cc86d02 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -78,6 +78,7 @@
RestCall.get("/changes/%s/meta_diff"),
RestCall.post("/changes/%s/merge"),
RestCall.get("/changes/%s/messages"),
+ RestCall.get("/changes/%s/message"),
RestCall.put("/changes/%s/message"),
RestCall.post("/changes/%s/move"),
RestCall.post("/changes/%s/patch:apply"),