-
Notifications
You must be signed in to change notification settings - Fork 130
Provide a way to get last file revisions #1096
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
4d7ef75
ef8bdc8
d16e17f
3945282
c2af4b5
f659c70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -408,29 +408,68 @@ private static Revision normalizeRevision(AggregatedHttpResponse res) { | |||||||||||
| @Override | ||||||||||||
| public CompletableFuture<Map<String, EntryType>> listFiles(String projectName, String repositoryName, | ||||||||||||
| Revision revision, PathPattern pathPattern) { | ||||||||||||
| return listFiles0(projectName, repositoryName, revision, pathPattern, -1, | ||||||||||||
| ArmeriaCentralDogma::listFiles); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @Override | ||||||||||||
| public CompletableFuture<Map<String, Entry<?>>> listFiles(String projectName, String repositoryName, | ||||||||||||
| Revision revision, PathPattern pathPattern, | ||||||||||||
| int includeLastFileRevision) { | ||||||||||||
| return listFiles0(projectName, repositoryName, revision, pathPattern, includeLastFileRevision, | ||||||||||||
| ArmeriaCentralDogma::listFilesWithRevision); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static Map<String, EntryType> listFiles(AggregatedHttpResponse res) { | ||||||||||||
| switch (res.status().code()) { | ||||||||||||
| case 200: | ||||||||||||
| final ImmutableMap.Builder<String, EntryType> builder = ImmutableMap.builder(); | ||||||||||||
| final JsonNode node = toJson(res, JsonNodeType.ARRAY); | ||||||||||||
| node.forEach(e -> builder.put( | ||||||||||||
| getField(e, "path").asText(), | ||||||||||||
| EntryType.valueOf(getField(e, "type").asText()))); | ||||||||||||
| return builder.build(); | ||||||||||||
| case 204: | ||||||||||||
| return ImmutableMap.of(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return handleErrorResponse(res); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private <T> CompletableFuture<T> listFiles0(String projectName, String repositoryName, | ||||||||||||
| Revision revision, PathPattern pathPattern, | ||||||||||||
| int includeLastFileRevision, | ||||||||||||
| Function<AggregatedHttpResponse, T> responseConverter) { | ||||||||||||
| validateProjectAndRepositoryName(projectName, repositoryName); | ||||||||||||
| requireNonNull(revision, "revision"); | ||||||||||||
| requireNonNull(pathPattern, "pathPattern"); | ||||||||||||
| try { | ||||||||||||
| final StringBuilder path = pathBuilder(projectName, repositoryName); | ||||||||||||
| path.append("/list").append(pathPattern.encoded()).append("?revision=").append(revision.major()); | ||||||||||||
| if (includeLastFileRevision >= 1) { | ||||||||||||
| path.append("&includeLastFileRevision=").append(includeLastFileRevision); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return client.execute(headers(HttpMethod.GET, path.toString())) | ||||||||||||
| .aggregate() | ||||||||||||
| .thenApply(ArmeriaCentralDogma::listFiles); | ||||||||||||
| .thenApply(responseConverter); | ||||||||||||
| } catch (Exception e) { | ||||||||||||
| return exceptionallyCompletedFuture(e); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static Map<String, EntryType> listFiles(AggregatedHttpResponse res) { | ||||||||||||
| private static Map<String, Entry<?>> listFilesWithRevision(AggregatedHttpResponse res) { | ||||||||||||
| switch (res.status().code()) { | ||||||||||||
| case 200: | ||||||||||||
| final ImmutableMap.Builder<String, EntryType> builder = ImmutableMap.builder(); | ||||||||||||
| final ImmutableMap.Builder<String, Entry<?>> builder = ImmutableMap.builder(); | ||||||||||||
| final JsonNode node = toJson(res, JsonNodeType.ARRAY); | ||||||||||||
| node.forEach(e -> builder.put( | ||||||||||||
| getField(e, "path").asText(), | ||||||||||||
| EntryType.valueOf(getField(e, "type").asText()))); | ||||||||||||
| for (JsonNode e : node) { | ||||||||||||
| final Revision revision = new Revision(getField(e, "revision").asInt()); | ||||||||||||
| final String path = getField(e, "path").asText(); | ||||||||||||
| final EntryType type = EntryType.valueOf(getField(e, "type").asText()); | ||||||||||||
| final Entry<Object> entry = Entry.ofNullable(revision, path, type, null); | ||||||||||||
| builder.put(path, entry); | ||||||||||||
| } | ||||||||||||
| return builder.build(); | ||||||||||||
| case 204: | ||||||||||||
| return ImmutableMap.of(); | ||||||||||||
|
|
@@ -455,25 +494,26 @@ public <T> CompletableFuture<Entry<T>> getFile(String projectName, String reposi | |||||||||||
|
|
||||||||||||
| return client.execute(headers(HttpMethod.GET, path.toString())) | ||||||||||||
| .aggregate() | ||||||||||||
| .thenApply(res -> getFile(normRev, res, query)); | ||||||||||||
| .thenApply(res -> getFile(res, query)); | ||||||||||||
| }); | ||||||||||||
| } catch (Exception e) { | ||||||||||||
| return exceptionallyCompletedFuture(e); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static <T> Entry<T> getFile(Revision normRev, AggregatedHttpResponse res, Query<T> query) { | ||||||||||||
| private static <T> Entry<T> getFile(AggregatedHttpResponse res, Query<T> query) { | ||||||||||||
| if (res.status().code() == 200) { | ||||||||||||
| final JsonNode node = toJson(res, JsonNodeType.OBJECT); | ||||||||||||
| return toEntry(normRev, node, query.type()); | ||||||||||||
| return toEntry(node, query.type()); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return handleErrorResponse(res); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @Override | ||||||||||||
| public CompletableFuture<Map<String, Entry<?>>> getFiles(String projectName, String repositoryName, | ||||||||||||
| Revision revision, PathPattern pathPattern) { | ||||||||||||
| Revision revision, PathPattern pathPattern, | ||||||||||||
| int includeLastFileRevision) { | ||||||||||||
| validateProjectAndRepositoryName(projectName, repositoryName); | ||||||||||||
| requireNonNull(revision, "revision"); | ||||||||||||
| requireNonNull(pathPattern, "pathPattern"); | ||||||||||||
|
|
@@ -485,27 +525,30 @@ public CompletableFuture<Map<String, Entry<?>>> getFiles(String projectName, Str | |||||||||||
| .append(pathPattern.encoded()) | ||||||||||||
| .append("?revision=") | ||||||||||||
| .append(normRev.major()); | ||||||||||||
| if (includeLastFileRevision >= 1) { | ||||||||||||
| path.append("&includeLastFileRevision=").append(includeLastFileRevision); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return client.execute(headers(HttpMethod.GET, path.toString())) | ||||||||||||
| .aggregate() | ||||||||||||
| .thenApply(res -> getFiles(normRev, res)); | ||||||||||||
| .thenApply(res -> getFiles(res)); | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| }); | ||||||||||||
| } catch (Exception e) { | ||||||||||||
| return exceptionallyCompletedFuture(e); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static Map<String, Entry<?>> getFiles(Revision normRev, AggregatedHttpResponse res) { | ||||||||||||
| private static Map<String, Entry<?>> getFiles(AggregatedHttpResponse res) { | ||||||||||||
| switch (res.status().code()) { | ||||||||||||
| case 200: | ||||||||||||
| final JsonNode node = toJson(res, null); | ||||||||||||
| final ImmutableMap.Builder<String, Entry<?>> builder = ImmutableMap.builder(); | ||||||||||||
| if (node.isObject()) { // Single entry | ||||||||||||
| final Entry<?> entry = toEntry(normRev, node, QueryType.IDENTITY); | ||||||||||||
| final Entry<?> entry = toEntry(node, QueryType.IDENTITY); | ||||||||||||
| builder.put(entry.path(), entry); | ||||||||||||
| } else if (node.isArray()) { // Multiple entries | ||||||||||||
| node.forEach(e -> { | ||||||||||||
| final Entry<?> entry = toEntry(normRev, e, QueryType.IDENTITY); | ||||||||||||
| final Entry<?> entry = toEntry(e, QueryType.IDENTITY); | ||||||||||||
| builder.put(entry.path(), entry); | ||||||||||||
| }); | ||||||||||||
| } else { | ||||||||||||
|
|
@@ -861,8 +904,7 @@ private static <T> Entry<T> watchFile(AggregatedHttpResponse res, QueryType quer | |||||||||||
| switch (res.status().code()) { | ||||||||||||
| case 200: // OK | ||||||||||||
| final JsonNode node = toJson(res, JsonNodeType.OBJECT); | ||||||||||||
| final Revision revision = new Revision(getField(node, "revision").asInt()); | ||||||||||||
| return toEntry(revision, getField(node, "entry"), queryType); | ||||||||||||
| return toEntry(getField(node, "entry"), queryType); | ||||||||||||
| case 304: // Not Modified | ||||||||||||
| return null; | ||||||||||||
| } | ||||||||||||
|
|
@@ -1027,9 +1069,11 @@ private static String toString(AggregatedHttpResponse res) { | |||||||||||
| return res.content(charset); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static <T> Entry<T> toEntry(Revision revision, JsonNode node, QueryType queryType) { | ||||||||||||
| private static <T> Entry<T> toEntry(JsonNode node, QueryType queryType) { | ||||||||||||
| final String entryPath = getField(node, "path").asText(); | ||||||||||||
| final EntryType receivedEntryType = EntryType.valueOf(getField(node, "type").asText()); | ||||||||||||
| final Revision revision = new Revision(getField(node, "revision").asInt()); | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The client may talk to an older Central Dogma instance. Can you make it handle an old server response as well and add a test case for it?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like @ikhoon just moved this logic into this method: centraldogma/common/src/main/java/com/linecorp/centraldogma/internal/api/v1/EntryDto.java Lines 56 to 59 in e072bcc
Line 868 in 584f376
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The entry node always has the revision so it shouldn't be a problem? |
||||||||||||
|
|
||||||||||||
| switch (queryType) { | ||||||||||||
| case IDENTITY_TEXT: | ||||||||||||
| return entryAsText(revision, node, entryPath); | ||||||||||||
|
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| /* | ||
| * Copyright 2025 LINE Corporation | ||
| * | ||
| * LINE Corporation licenses this file to you 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: | ||
| * | ||
| * https://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.linecorp.centraldogma.client; | ||
|
|
||
| import static java.util.Objects.requireNonNull; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.concurrent.CompletableFuture; | ||
|
|
||
| import com.linecorp.centraldogma.common.Entry; | ||
| import com.linecorp.centraldogma.common.PathPattern; | ||
| import com.linecorp.centraldogma.common.Revision; | ||
|
|
||
| /** | ||
| * Prepares to send a {@link CentralDogma#getFiles(String, String, Revision, PathPattern, int)} or | ||
| * {@link CentralDogma#listFiles(String, String, Revision, PathPattern, int)} request to the | ||
| * Central Dogma repository. | ||
| */ | ||
| public final class FilesWithRevisionRequest { | ||
|
|
||
| private final CentralDogmaRepository centralDogmaRepo; | ||
| private final PathPattern pathPattern; | ||
| private final int includeLastFileRevision; | ||
|
|
||
| FilesWithRevisionRequest(CentralDogmaRepository centralDogmaRepo, PathPattern pathPattern, | ||
| int includeLastFileRevision) { | ||
| this.centralDogmaRepo = centralDogmaRepo; | ||
| this.pathPattern = pathPattern; | ||
| this.includeLastFileRevision = includeLastFileRevision; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the list of the files matched by the given path pattern at the {@link Revision#HEAD}. | ||
| * Note that the returned {@link Entry} does not contain the content of the file. | ||
| * | ||
| * @return a {@link Map} of file path and {@link Entry} pairs. | ||
| */ | ||
| public CompletableFuture<Map<String, Entry<?>>> list() { | ||
| return list(Revision.HEAD); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the list of the files matched by the given path pattern at the {@link Revision}. | ||
| * Note that the returned {@link Entry} does not contain the content of the file. | ||
| * | ||
| * @return a {@link Map} of file path and {@link Entry} pairs | ||
| */ | ||
| public CompletableFuture<Map<String, Entry<?>>> list(Revision revision) { | ||
| requireNonNull(revision, "revision"); | ||
| return centralDogmaRepo.centralDogma().listFiles(centralDogmaRepo.projectName(), | ||
| centralDogmaRepo.repositoryName(), | ||
| revision, pathPattern, includeLastFileRevision); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the files matched by the path pattern at the {@link Revision#HEAD}. | ||
| * | ||
| * @return a {@link Map} of file path and {@link Entry} pairs | ||
| */ | ||
| public CompletableFuture<Map<String, Entry<?>>> get() { | ||
| return get(Revision.HEAD); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves the files matched by the path pattern at the {@link Revision}. | ||
| * | ||
| * @return a {@link Map} of file path and {@link Entry} pairs | ||
| */ | ||
| public CompletableFuture<Map<String, Entry<?>>> get(Revision revision) { | ||
| requireNonNull(revision, "revision"); | ||
| return centralDogmaRepo.centralDogma().getFiles(centralDogmaRepo.projectName(), | ||
| centralDogmaRepo.repositoryName(), | ||
| revision, pathPattern, includeLastFileRevision); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) What do you think of setting the default value as
1for both client/server side since it seems like 1 represents "don't search the history"?Personally, I think it makes more sense that:
includeLastFileRevision<0The current implementation seems fine though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If so, should 0 and 1 return the same result?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant it feels more intuitive to me that the parameter represents the number of revisions to go backwards. (i.e.
Revision#backward(int)).I think the current implementation is fine as long as 1) the default value is well defined with validation 2) and the javadocs are clear. It might be helpful if the parameter name better represents the meaning of "check x number of revision including the head revision" as well.