Line data Source code
1 : // Copyright (C) 2020 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.acceptance.testsuite.change;
16 :
17 : import static com.google.common.base.Preconditions.checkState;
18 : import static com.google.common.collect.ImmutableList.toImmutableList;
19 :
20 : import com.google.common.collect.ImmutableList;
21 : import com.google.common.collect.ImmutableSet;
22 : import com.google.common.collect.Streams;
23 : import com.google.gerrit.entities.Account;
24 : import com.google.gerrit.entities.Change;
25 : import com.google.gerrit.entities.PatchSet;
26 : import com.google.gerrit.entities.Project;
27 : import com.google.gerrit.entities.RefNames;
28 : import com.google.gerrit.exceptions.StorageException;
29 : import com.google.gerrit.extensions.restapi.BadRequestException;
30 : import com.google.gerrit.extensions.restapi.RestApiException;
31 : import com.google.gerrit.server.ChangeUtil;
32 : import com.google.gerrit.server.GerritPersonIdent;
33 : import com.google.gerrit.server.IdentifiedUser;
34 : import com.google.gerrit.server.account.AccountResolver;
35 : import com.google.gerrit.server.change.ChangeFinder;
36 : import com.google.gerrit.server.change.ChangeInserter;
37 : import com.google.gerrit.server.change.PatchSetInserter;
38 : import com.google.gerrit.server.edit.tree.TreeCreator;
39 : import com.google.gerrit.server.edit.tree.TreeModification;
40 : import com.google.gerrit.server.git.GitRepositoryManager;
41 : import com.google.gerrit.server.notedb.ChangeNotes;
42 : import com.google.gerrit.server.notedb.Sequences;
43 : import com.google.gerrit.server.project.ProjectCache;
44 : import com.google.gerrit.server.update.BatchUpdate;
45 : import com.google.gerrit.server.update.UpdateException;
46 : import com.google.gerrit.server.util.CommitMessageUtil;
47 : import com.google.gerrit.server.util.time.TimeUtil;
48 : import com.google.inject.Inject;
49 : import java.io.IOException;
50 : import java.time.Instant;
51 : import java.util.Arrays;
52 : import java.util.Objects;
53 : import java.util.Optional;
54 : import org.eclipse.jgit.errors.ConfigInvalidException;
55 : import org.eclipse.jgit.lib.AnyObjectId;
56 : import org.eclipse.jgit.lib.CommitBuilder;
57 : import org.eclipse.jgit.lib.Config;
58 : import org.eclipse.jgit.lib.ObjectId;
59 : import org.eclipse.jgit.lib.ObjectInserter;
60 : import org.eclipse.jgit.lib.PersonIdent;
61 : import org.eclipse.jgit.lib.Ref;
62 : import org.eclipse.jgit.lib.Repository;
63 : import org.eclipse.jgit.merge.MergeStrategy;
64 : import org.eclipse.jgit.merge.Merger;
65 : import org.eclipse.jgit.revwalk.RevCommit;
66 : import org.eclipse.jgit.revwalk.RevWalk;
67 : import org.eclipse.jgit.util.ChangeIdUtil;
68 :
69 : /**
70 : * The implementation of {@link ChangeOperations}.
71 : *
72 : * <p>There is only one implementation of {@link ChangeOperations}. Nevertheless, we keep the
73 : * separation between interface and implementation to enhance clarity.
74 : */
75 : public class ChangeOperationsImpl implements ChangeOperations {
76 : private final Sequences seq;
77 : private final ChangeInserter.Factory changeInserterFactory;
78 : private final PatchSetInserter.Factory patchsetInserterFactory;
79 : private final GitRepositoryManager repositoryManager;
80 : private final AccountResolver resolver;
81 : private final IdentifiedUser.GenericFactory userFactory;
82 : private final PersonIdent serverIdent;
83 : private final BatchUpdate.Factory batchUpdateFactory;
84 : private final ProjectCache projectCache;
85 : private final ChangeFinder changeFinder;
86 : private final PerPatchsetOperationsImpl.Factory perPatchsetOperationsFactory;
87 : private final PerCommentOperationsImpl.Factory perCommentOperationsFactory;
88 : private final PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory;
89 : private final PerRobotCommentOperationsImpl.Factory perRobotCommentOperationsFactory;
90 :
91 : @Inject
92 : public ChangeOperationsImpl(
93 : Sequences seq,
94 : ChangeInserter.Factory changeInserterFactory,
95 : PatchSetInserter.Factory patchsetInserterFactory,
96 : GitRepositoryManager repositoryManager,
97 : AccountResolver resolver,
98 : IdentifiedUser.GenericFactory userFactory,
99 : @GerritPersonIdent PersonIdent serverIdent,
100 : BatchUpdate.Factory batchUpdateFactory,
101 : ProjectCache projectCache,
102 : ChangeFinder changeFinder,
103 : PerPatchsetOperationsImpl.Factory perPatchsetOperationsFactory,
104 : PerCommentOperationsImpl.Factory perCommentOperationsFactory,
105 : PerDraftCommentOperationsImpl.Factory perDraftCommentOperationsFactory,
106 14 : PerRobotCommentOperationsImpl.Factory perRobotCommentOperationsFactory) {
107 14 : this.seq = seq;
108 14 : this.changeInserterFactory = changeInserterFactory;
109 14 : this.patchsetInserterFactory = patchsetInserterFactory;
110 14 : this.repositoryManager = repositoryManager;
111 14 : this.resolver = resolver;
112 14 : this.userFactory = userFactory;
113 14 : this.serverIdent = serverIdent;
114 14 : this.batchUpdateFactory = batchUpdateFactory;
115 14 : this.projectCache = projectCache;
116 14 : this.changeFinder = changeFinder;
117 14 : this.perPatchsetOperationsFactory = perPatchsetOperationsFactory;
118 14 : this.perCommentOperationsFactory = perCommentOperationsFactory;
119 14 : this.perDraftCommentOperationsFactory = perDraftCommentOperationsFactory;
120 14 : this.perRobotCommentOperationsFactory = perRobotCommentOperationsFactory;
121 14 : }
122 :
123 : @Override
124 : public PerChangeOperations change(Change.Id changeId) {
125 9 : return new PerChangeOperationsImpl(changeId);
126 : }
127 :
128 : @Override
129 : public TestChangeCreation.Builder newChange() {
130 14 : return TestChangeCreation.builder(this::createChange);
131 : }
132 :
133 : private Change.Id createChange(TestChangeCreation changeCreation) throws Exception {
134 14 : Change.Id changeId = Change.id(seq.nextChangeId());
135 14 : Project.NameKey project = getTargetProject(changeCreation);
136 :
137 14 : try (Repository repository = repositoryManager.openRepository(project);
138 14 : ObjectInserter objectInserter = repository.newObjectInserter();
139 14 : RevWalk revWalk = new RevWalk(objectInserter.newReader())) {
140 14 : Instant now = TimeUtil.now();
141 14 : IdentifiedUser changeOwner = getChangeOwner(changeCreation);
142 14 : PersonIdent authorAndCommitter = changeOwner.newCommitterIdent(now, serverIdent.getZoneId());
143 14 : ObjectId commitId =
144 14 : createCommit(repository, revWalk, objectInserter, changeCreation, authorAndCommitter);
145 :
146 14 : String refName = RefNames.fullName(changeCreation.branch());
147 14 : ChangeInserter inserter = getChangeInserter(changeId, refName, commitId);
148 14 : changeCreation.topic().ifPresent(t -> inserter.setTopic(t));
149 14 : inserter.setApprovals(changeCreation.approvals());
150 :
151 14 : try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, changeOwner, now)) {
152 14 : batchUpdate.setRepository(repository, revWalk, objectInserter);
153 14 : batchUpdate.insertChange(inserter);
154 14 : batchUpdate.execute();
155 : }
156 14 : return changeId;
157 : }
158 : }
159 :
160 : private Project.NameKey getTargetProject(TestChangeCreation changeCreation) {
161 14 : if (changeCreation.project().isPresent()) {
162 7 : return changeCreation.project().get();
163 : }
164 :
165 8 : return getArbitraryProject();
166 : }
167 :
168 : private Project.NameKey getArbitraryProject() {
169 8 : Project.NameKey allProjectsName = projectCache.getAllProjects().getNameKey();
170 8 : Project.NameKey allUsersName = projectCache.getAllUsers().getNameKey();
171 8 : Optional<Project.NameKey> arbitraryProject =
172 8 : projectCache.all().stream()
173 8 : .filter(
174 : name ->
175 8 : !Objects.equals(name, allProjectsName) && !Objects.equals(name, allUsersName))
176 8 : .findFirst();
177 8 : checkState(
178 8 : arbitraryProject.isPresent(),
179 : "At least one repository must be available on the Gerrit server");
180 8 : return arbitraryProject.get();
181 : }
182 :
183 : private IdentifiedUser getChangeOwner(TestChangeCreation changeCreation)
184 : throws IOException, ConfigInvalidException {
185 14 : if (changeCreation.owner().isPresent()) {
186 2 : return userFactory.create(changeCreation.owner().get());
187 : }
188 :
189 14 : return getArbitraryUser();
190 : }
191 :
192 : private IdentifiedUser getArbitraryUser() throws ConfigInvalidException, IOException {
193 14 : ImmutableSet<Account.Id> foundAccounts = resolver.resolveIgnoreVisibility("").asIdSet();
194 14 : checkState(
195 14 : !foundAccounts.isEmpty(),
196 : "At least one user account must be available on the Gerrit server");
197 14 : return userFactory.create(foundAccounts.iterator().next());
198 : }
199 :
200 : private ObjectId createCommit(
201 : Repository repository,
202 : RevWalk revWalk,
203 : ObjectInserter objectInserter,
204 : TestChangeCreation changeCreation,
205 : PersonIdent authorAndCommitter)
206 : throws IOException, BadRequestException {
207 14 : ImmutableList<ObjectId> parentCommits = getParentCommits(repository, revWalk, changeCreation);
208 14 : TreeCreator treeCreator =
209 14 : getTreeCreator(objectInserter, parentCommits, changeCreation.mergeStrategy());
210 14 : ObjectId tree = createNewTree(repository, treeCreator, changeCreation.treeModifications());
211 14 : String commitMessage = correctCommitMessage(changeCreation.commitMessage());
212 14 : return createCommit(
213 : objectInserter, tree, parentCommits, authorAndCommitter, authorAndCommitter, commitMessage);
214 : }
215 :
216 : private ImmutableList<ObjectId> getParentCommits(
217 : Repository repository, RevWalk revWalk, TestChangeCreation changeCreation) {
218 :
219 14 : return changeCreation
220 14 : .parents()
221 14 : .map(parents -> resolveParents(repository, revWalk, parents))
222 14 : .orElseGet(() -> asImmutableList(getTip(repository, changeCreation.branch())));
223 : }
224 :
225 : private ImmutableList<ObjectId> resolveParents(
226 : Repository repository, RevWalk revWalk, ImmutableList<TestCommitIdentifier> parents) {
227 5 : return parents.stream()
228 5 : .map(parent -> resolveCommit(repository, revWalk, parent))
229 5 : .collect(toImmutableList());
230 : }
231 :
232 : private ObjectId resolveCommit(
233 : Repository repository, RevWalk revWalk, TestCommitIdentifier parentCommit) {
234 5 : switch (parentCommit.getKind()) {
235 : case BRANCH:
236 2 : return resolveBranchTip(repository, parentCommit.branch());
237 : case CHANGE_ID:
238 4 : return resolveChange(parentCommit.changeId());
239 : case COMMIT_SHA_1:
240 1 : return resolveCommitFromSha1(revWalk, parentCommit.commitSha1());
241 : case PATCHSET_ID:
242 3 : return resolvePatchset(parentCommit.patchsetId());
243 : default:
244 0 : throw new IllegalStateException(
245 0 : String.format("No parent behavior implemented for %s.", parentCommit.getKind()));
246 : }
247 : }
248 :
249 : private static ObjectId resolveBranchTip(Repository repository, String branchName) {
250 2 : return getTip(repository, branchName)
251 2 : .orElseThrow(
252 : () ->
253 1 : new IllegalStateException(
254 1 : String.format(
255 : "Tip of branch %s not found and hence can't be used as parent.",
256 : branchName)));
257 : }
258 :
259 : private static Optional<ObjectId> getTip(Repository repository, String branch) {
260 : try {
261 14 : Optional<Ref> ref = Optional.ofNullable(repository.findRef(branch));
262 14 : return ref.map(Ref::getObjectId);
263 0 : } catch (IOException e) {
264 0 : throw new StorageException(e);
265 : }
266 : }
267 :
268 : private ObjectId resolveChange(Change.Id changeId) {
269 4 : Optional<ChangeNotes> changeNotes = changeFinder.findOne(changeId);
270 4 : return changeNotes
271 4 : .map(ChangeNotes::getCurrentPatchSet)
272 4 : .map(PatchSet::commitId)
273 4 : .orElseThrow(
274 : () ->
275 1 : new IllegalStateException(
276 1 : String.format(
277 : "Change %s not found and hence can't be used as parent.", changeId)));
278 : }
279 :
280 : private static RevCommit resolveCommitFromSha1(RevWalk revWalk, ObjectId commitSha1) {
281 : try {
282 5 : return revWalk.parseCommit(commitSha1);
283 1 : } catch (Exception e) {
284 1 : throw new IllegalStateException(
285 1 : String.format("Commit %s not found and hence can't be used as parent/base.", commitSha1),
286 : e);
287 : }
288 : }
289 :
290 : private ObjectId resolvePatchset(PatchSet.Id patchsetId) {
291 3 : Optional<ChangeNotes> changeNotes = changeFinder.findOne(patchsetId.changeId());
292 3 : return changeNotes
293 3 : .map(ChangeNotes::getPatchSets)
294 3 : .map(patchsets -> patchsets.get(patchsetId))
295 3 : .map(PatchSet::commitId)
296 3 : .orElseThrow(
297 : () ->
298 1 : new IllegalStateException(
299 1 : String.format(
300 : "Patchset %s not found and hence can't be used as parent.", patchsetId)));
301 : }
302 :
303 : private static <T> ImmutableList<T> asImmutableList(Optional<T> value) {
304 14 : return Streams.stream(value).collect(toImmutableList());
305 : }
306 :
307 : private static TreeCreator getTreeCreator(
308 : RevWalk revWalk, ObjectId customBaseCommit, ImmutableList<ObjectId> parentCommits) {
309 5 : RevCommit commit = resolveCommitFromSha1(revWalk, customBaseCommit);
310 : // Use actual parents; relevant for example when a file is restored (->
311 : // RestoreFileModification).
312 5 : return TreeCreator.basedOnTree(commit.getTree(), parentCommits);
313 : }
314 :
315 : private static TreeCreator getTreeCreator(
316 : ObjectInserter objectInserter,
317 : ImmutableList<ObjectId> parentCommits,
318 : MergeStrategy mergeStrategy) {
319 14 : if (parentCommits.isEmpty()) {
320 2 : return TreeCreator.basedOnEmptyTree();
321 : }
322 14 : ObjectId baseTreeId = merge(objectInserter, parentCommits, mergeStrategy);
323 14 : return TreeCreator.basedOnTree(baseTreeId, parentCommits);
324 : }
325 :
326 : private static ObjectId merge(
327 : ObjectInserter objectInserter,
328 : ImmutableList<ObjectId> parentCommits,
329 : MergeStrategy mergeStrategy) {
330 : try {
331 14 : Merger merger = mergeStrategy.newMerger(objectInserter, new Config());
332 14 : boolean mergeSuccessful = merger.merge(parentCommits.toArray(new AnyObjectId[0]));
333 14 : if (!mergeSuccessful) {
334 1 : throw new IllegalStateException(
335 : "Conflicts encountered while merging the specified parents. Use"
336 : + " mergeOfButBaseOnFirst() instead to avoid these conflicts and define any"
337 : + " other desired file contents with file().content().");
338 : }
339 14 : return merger.getResultTreeId();
340 0 : } catch (IOException e) {
341 0 : throw new IllegalStateException(
342 : "Creating the merge commits of the specified parents failed for an unknown reason.", e);
343 : }
344 : }
345 :
346 : private static ObjectId createNewTree(
347 : Repository repository,
348 : TreeCreator treeCreator,
349 : ImmutableList<TreeModification> treeModifications)
350 : throws IOException {
351 14 : treeCreator.addTreeModifications(treeModifications);
352 14 : return treeCreator.createNewTreeAndGetId(repository);
353 : }
354 :
355 : private String correctCommitMessage(String desiredCommitMessage) throws BadRequestException {
356 14 : String commitMessage = CommitMessageUtil.checkAndSanitizeCommitMessage(desiredCommitMessage);
357 :
358 14 : if (ChangeIdUtil.indexOfChangeId(commitMessage, "\n") == -1) {
359 14 : ObjectId id = CommitMessageUtil.generateChangeId();
360 14 : commitMessage = ChangeIdUtil.insertId(commitMessage, id);
361 : }
362 :
363 14 : return commitMessage;
364 : }
365 :
366 : private ObjectId createCommit(
367 : ObjectInserter objectInserter,
368 : ObjectId tree,
369 : ImmutableList<ObjectId> parentCommitIds,
370 : PersonIdent author,
371 : PersonIdent committer,
372 : String commitMessage)
373 : throws IOException {
374 14 : CommitBuilder builder = new CommitBuilder();
375 14 : builder.setTreeId(tree);
376 14 : builder.setParentIds(parentCommitIds);
377 14 : builder.setAuthor(author);
378 14 : builder.setCommitter(committer);
379 14 : builder.setMessage(commitMessage);
380 14 : ObjectId newCommitId = objectInserter.insert(builder);
381 14 : objectInserter.flush();
382 14 : return newCommitId;
383 : }
384 :
385 : private ChangeInserter getChangeInserter(Change.Id changeId, String refName, ObjectId commitId) {
386 14 : ChangeInserter inserter = changeInserterFactory.create(changeId, commitId, refName);
387 14 : inserter.setMessage(String.format("Uploaded patchset %d.", inserter.getPatchSetId().get()));
388 14 : return inserter;
389 : }
390 :
391 : private class PerChangeOperationsImpl implements PerChangeOperations {
392 :
393 : private final Change.Id changeId;
394 :
395 9 : public PerChangeOperationsImpl(Change.Id changeId) {
396 9 : this.changeId = changeId;
397 9 : }
398 :
399 : @Override
400 : public boolean exists() {
401 1 : return changeFinder.findOne(changeId).isPresent();
402 : }
403 :
404 : @Override
405 : public TestChange get() {
406 2 : return toTestChange(getChangeNotes().getChange());
407 : }
408 :
409 : private ChangeNotes getChangeNotes() {
410 9 : Optional<ChangeNotes> changeNotes = changeFinder.findOne(changeId);
411 9 : checkState(changeNotes.isPresent(), "Tried to get non-existing test change.");
412 9 : return changeNotes.get();
413 : }
414 :
415 : private TestChange toTestChange(Change change) {
416 2 : return TestChange.builder()
417 2 : .numericChangeId(change.getId())
418 2 : .changeId(change.getKey().get())
419 2 : .build();
420 : }
421 :
422 : @Override
423 : public TestPatchsetCreation.Builder newPatchset() {
424 5 : return TestPatchsetCreation.builder(this::createPatchset);
425 : }
426 :
427 : private PatchSet.Id createPatchset(TestPatchsetCreation patchsetCreation)
428 : throws IOException, RestApiException, UpdateException {
429 5 : ChangeNotes changeNotes = getChangeNotes();
430 5 : Project.NameKey project = changeNotes.getProjectName();
431 5 : try (Repository repository = repositoryManager.openRepository(project);
432 5 : ObjectInserter objectInserter = repository.newObjectInserter();
433 5 : RevWalk revWalk = new RevWalk(objectInserter.newReader())) {
434 5 : Instant now = TimeUtil.now();
435 5 : ObjectId newPatchsetCommit =
436 5 : createPatchsetCommit(
437 : repository, revWalk, objectInserter, changeNotes, patchsetCreation, now);
438 :
439 5 : PatchSet.Id patchsetId =
440 5 : ChangeUtil.nextPatchSetId(repository, changeNotes.getCurrentPatchSet().id());
441 5 : PatchSetInserter patchSetInserter =
442 5 : getPatchSetInserter(changeNotes, newPatchsetCommit, patchsetId);
443 :
444 5 : IdentifiedUser changeOwner = userFactory.create(changeNotes.getChange().getOwner());
445 5 : try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, changeOwner, now)) {
446 5 : batchUpdate.setRepository(repository, revWalk, objectInserter);
447 5 : batchUpdate.addOp(changeId, patchSetInserter);
448 5 : batchUpdate.execute();
449 : }
450 5 : return patchsetId;
451 : }
452 : }
453 :
454 : private ObjectId createPatchsetCommit(
455 : Repository repository,
456 : RevWalk revWalk,
457 : ObjectInserter objectInserter,
458 : ChangeNotes changeNotes,
459 : TestPatchsetCreation patchsetCreation,
460 : Instant now)
461 : throws IOException, BadRequestException {
462 5 : ObjectId oldPatchsetCommitId = changeNotes.getCurrentPatchSet().commitId();
463 5 : RevCommit oldPatchsetCommit = repository.parseCommit(oldPatchsetCommitId);
464 :
465 5 : ImmutableList<ObjectId> parentCommitIds =
466 5 : getParents(repository, revWalk, patchsetCreation, oldPatchsetCommit);
467 5 : TreeCreator treeCreator = getTreeCreator(revWalk, oldPatchsetCommit, parentCommitIds);
468 5 : ObjectId tree = createNewTree(repository, treeCreator, patchsetCreation.treeModifications());
469 :
470 5 : String commitMessage =
471 5 : correctCommitMessage(
472 5 : changeNotes.getChange().getKey().get(),
473 5 : patchsetCreation.commitMessage().orElseGet(oldPatchsetCommit::getFullMessage));
474 :
475 5 : PersonIdent author = getAuthor(oldPatchsetCommit);
476 5 : PersonIdent committer = getCommitter(oldPatchsetCommit, now);
477 5 : return createCommit(objectInserter, tree, parentCommitIds, author, committer, commitMessage);
478 : }
479 :
480 : private String correctCommitMessage(String oldChangeId, String desiredCommitMessage)
481 : throws BadRequestException {
482 5 : String commitMessage = CommitMessageUtil.checkAndSanitizeCommitMessage(desiredCommitMessage);
483 :
484 : // Remove initial 'I' and treat the rest as ObjectId. This is not the cleanest approach but
485 : // unfortunately, we don't seem to have other utility code which takes the string-based
486 : // change-id and ensures that it is part of the commit message.
487 5 : ObjectId id = ObjectId.fromString(oldChangeId.substring(1));
488 5 : commitMessage = ChangeIdUtil.insertId(commitMessage, id, false);
489 :
490 5 : return commitMessage;
491 : }
492 :
493 : private PersonIdent getAuthor(RevCommit oldPatchsetCommit) {
494 5 : return Optional.ofNullable(oldPatchsetCommit.getAuthorIdent()).orElse(serverIdent);
495 : }
496 :
497 : private PersonIdent getCommitter(RevCommit oldPatchsetCommit, Instant now) {
498 5 : PersonIdent oldPatchsetCommitter =
499 5 : Optional.ofNullable(oldPatchsetCommit.getCommitterIdent()).orElse(serverIdent);
500 5 : if (asSeconds(now) == asSeconds(oldPatchsetCommitter.getWhenAsInstant())) {
501 : /* We need to ensure that the resulting commit SHA-1 is different from the old patchset.
502 : * In real situations, this automatically happens as two patchsets won't have exactly the
503 : * same commit timestamp even when the tree and commit message are the same. In tests,
504 : * we can easily end up with the same timestamp as Git uses second precision for timestamps.
505 : * We could of course require that tests must use TestTimeUtil#setClockStep but
506 : * that would be an unnecessary nuisance for test writers. Hence, go with a simple solution
507 : * here and simply add a second. */
508 5 : now = now.plusSeconds(1);
509 : }
510 5 : return new PersonIdent(oldPatchsetCommitter, now);
511 : }
512 :
513 : private long asSeconds(Instant date) {
514 5 : return date.getEpochSecond();
515 : }
516 :
517 : private ImmutableList<ObjectId> getParents(
518 : Repository repository,
519 : RevWalk revWalk,
520 : TestPatchsetCreation patchsetCreation,
521 : RevCommit oldPatchsetCommit) {
522 5 : return patchsetCreation
523 5 : .parents()
524 5 : .map(parents -> resolveParents(repository, revWalk, parents))
525 5 : .orElseGet(
526 5 : () -> Arrays.stream(oldPatchsetCommit.getParents()).collect(toImmutableList()));
527 : }
528 :
529 : private PatchSetInserter getPatchSetInserter(
530 : ChangeNotes changeNotes, ObjectId newPatchsetCommit, PatchSet.Id patchsetId) {
531 5 : PatchSetInserter patchSetInserter =
532 5 : patchsetInserterFactory.create(changeNotes, patchsetId, newPatchsetCommit);
533 5 : patchSetInserter.setCheckAddPatchSetPermission(false);
534 5 : patchSetInserter.setMessage(String.format("Uploaded patchset %d.", patchsetId.get()));
535 5 : return patchSetInserter;
536 : }
537 :
538 : @Override
539 : public PerPatchsetOperations patchset(PatchSet.Id patchsetId) {
540 3 : return perPatchsetOperationsFactory.create(getChangeNotes(), patchsetId);
541 : }
542 :
543 : @Override
544 : public PerPatchsetOperations currentPatchset() {
545 6 : ChangeNotes changeNotes = getChangeNotes();
546 6 : return perPatchsetOperationsFactory.create(
547 6 : changeNotes, changeNotes.getChange().currentPatchSetId());
548 : }
549 :
550 : @Override
551 : public PerCommentOperations comment(String commentUuid) {
552 1 : ChangeNotes changeNotes = getChangeNotes();
553 1 : return perCommentOperationsFactory.create(changeNotes, commentUuid);
554 : }
555 :
556 : @Override
557 : public PerDraftCommentOperations draftComment(String commentUuid) {
558 2 : ChangeNotes changeNotes = getChangeNotes();
559 2 : return perDraftCommentOperationsFactory.create(changeNotes, commentUuid);
560 : }
561 :
562 : @Override
563 : public PerRobotCommentOperations robotComment(String commentUuid) {
564 1 : ChangeNotes changeNotes = getChangeNotes();
565 1 : return perRobotCommentOperationsFactory.create(changeNotes, commentUuid);
566 : }
567 : }
568 : }
|