| // Copyright (C) 2021 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.googlesource.gerrit.plugins.eventseiffel.parsing; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCC; |
| import static com.googlesource.gerrit.plugins.eventseiffel.eiffel.dto.EiffelEventType.SCS; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.entities.Project; |
| import com.google.gerrit.extensions.common.AccountInfo; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| import com.googlesource.gerrit.plugins.eventseiffel.EiffelEventHub; |
| import com.googlesource.gerrit.plugins.eventseiffel.cache.EiffelEventIdLookupException; |
| import com.googlesource.gerrit.plugins.eventseiffel.eiffel.SourceChangeEventKey; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.UUID; |
| import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.errors.RepositoryNotFoundException; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.revwalk.RevFlag; |
| import org.eclipse.jgit.revwalk.RevSort; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| |
| public abstract class CommitsWalker implements AutoCloseable { |
| @Singleton |
| public static class Factory { |
| private final EiffelEventHub eventHub; |
| private final GitRepositoryManager repoManager; |
| |
| @Inject |
| public Factory(EiffelEventHub eventHub, GitRepositoryManager repoManager) { |
| this.eventHub = eventHub; |
| this.repoManager = repoManager; |
| } |
| |
| public UnprocessedCommitsWalker sccWalker(SourceChangeEventKey eventKey) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| return new SccWalker(eventKey, eventHub, repoManager); |
| } |
| |
| public CommitsWalker childWalker(SourceChangeEventKey eventKey) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| return new ChildWalker(eventKey, eventHub, repoManager, Optional.empty()); |
| } |
| |
| public CommitsWalker childWalker(SourceChangeEventKey eventKey, SourceChangeEventKey parentKey) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| return new ChildWalker(eventKey, eventHub, repoManager, Optional.of(parentKey)); |
| } |
| |
| public ScsWalker scsWalker( |
| SourceChangeEventKey eventKey, |
| String commitSha1TransactionEnd, |
| AccountInfo submitter, |
| Long submittedAt) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| return new ScsWalker( |
| eventKey, eventHub, repoManager, commitSha1TransactionEnd, submitter, submittedAt); |
| } |
| } |
| |
| private static void flagWithParents(RevCommit commit, RevFlag flag) { |
| commit.add(flag); |
| for (RevCommit parent : commit.getParents()) { |
| parent.add(flag); |
| } |
| } |
| |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| protected final SourceChangeEventKey eventKey; |
| protected final EiffelEventHub eventHub; |
| protected final Repository repo; |
| protected final RevWalk rw; |
| protected final List<AutoCloseable> toClose = Lists.newArrayList(); |
| protected EventCreate next; |
| |
| CommitsWalker( |
| SourceChangeEventKey eventKey, EiffelEventHub eventHub, GitRepositoryManager repoManager) |
| throws RepositoryNotFoundException, IOException { |
| this(eventKey, eventHub, repoManager.openRepository(Project.nameKey(eventKey.repo()))); |
| this.toClose.add(this.repo); |
| } |
| |
| CommitsWalker(SourceChangeEventKey eventKey, EiffelEventHub eventHub, Repository repo) { |
| this.eventKey = eventKey; |
| this.eventHub = eventHub; |
| this.repo = repo; |
| this.rw = new RevWalk(repo); |
| this.toClose.add(rw); |
| rw.setRetainBody(false); |
| } |
| |
| @Override |
| public void close() { |
| try { |
| for (AutoCloseable resource : toClose) { |
| resource.close(); |
| } |
| } catch (Exception e) { |
| logger.atSevere().withCause(e).log("Exception when closing resources."); |
| } |
| } |
| |
| boolean hasNext() { |
| return next != null; |
| } |
| |
| EventCreate next() throws MissingObjectException, EiffelEventIdLookupException, IOException { |
| EventCreate current = next; |
| setNext(); |
| return current; |
| } |
| |
| protected abstract void setNext() |
| throws MissingObjectException, EiffelEventIdLookupException, IOException; |
| |
| protected SourceChangeEventKey toKey(RevCommit commit) { |
| return eventKey.copy(commit.getName()); |
| } |
| |
| public class EventCreate { |
| SourceChangeEventKey key; |
| RevCommit commit; |
| AccountInfo submitter; |
| Long submittedAt; |
| |
| public EventCreate(RevCommit commit) throws MissingObjectException, IOException { |
| this(commit, null, null); |
| } |
| |
| public EventCreate(RevCommit commit, AccountInfo submitter, Long submittedAt) |
| throws MissingObjectException, IOException { |
| rw.parseBody(commit); |
| this.key = toKey(commit); |
| this.commit = commit; |
| this.submitter = submitter; |
| this.submittedAt = submittedAt; |
| } |
| } |
| |
| public abstract static class UnprocessedCommitsWalker extends CommitsWalker { |
| protected final boolean hasEvents; |
| |
| UnprocessedCommitsWalker( |
| SourceChangeEventKey eventKey, EiffelEventHub eventHub, GitRepositoryManager repoManager) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| this(eventKey, eventHub, repoManager.openRepository(Project.nameKey(eventKey.repo()))); |
| this.toClose.add(this.repo); |
| } |
| |
| UnprocessedCommitsWalker( |
| SourceChangeEventKey eventKey, EiffelEventHub eventHub, Repository repo) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| super(eventKey, eventHub, repo); |
| this.hasEvents = hasEvents(); |
| } |
| |
| protected boolean isAlreadyHandled( |
| RevFlag hasEventFlag, RevCommit commit, SourceChangeEventKey key) |
| throws EiffelEventIdLookupException { |
| logger.atFine().log("%s has event flag: %b", key.copy(commit), commit.has(hasEventFlag)); |
| return commit.has(hasEventFlag) || eventHub.getExistingId(key).isPresent(); |
| } |
| |
| /* Returns true if an event has been created for key.branch from any commits reachable from |
| * key.commit (i.e. initial commit) */ |
| private boolean hasEvents() |
| throws MissingObjectException, IncorrectObjectTypeException, IOException, |
| EiffelEventIdLookupException { |
| rw.sort(RevSort.REVERSE); |
| rw.markStart(rw.parseCommit(ObjectId.fromString(eventKey.commit()))); |
| RevCommit initialCommit = rw.next(); |
| if (initialCommit == null) { |
| logger.atWarning().log("Unable to find initial commit for: %s", eventKey); |
| return false; |
| } |
| logger.atFine().log("Found initial commit: \"%s\" of %s.", initialCommit.name(), eventKey); |
| |
| /* Reset RevWalk. */ |
| rw.reset(); |
| |
| Optional<UUID> eventId = eventHub.getExistingId(toKey(initialCommit)); |
| eventId.ifPresent(id -> logger.atFine().log("%s has events", eventKey)); |
| return eventId.isPresent(); |
| } |
| } |
| |
| static class SccWalker extends UnprocessedCommitsWalker { |
| private RevFlag hasSccEventFlag; |
| |
| SccWalker( |
| SourceChangeEventKey eventKey, EiffelEventHub eventHub, GitRepositoryManager repoManager) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| super(eventKey, eventHub, repoManager); |
| checkState(eventKey.type().equals(SCC), "EventKey must have type SCC for SccWalker."); |
| this.hasSccEventFlag = rw.newFlag("HAS_SCC_EVENT"); |
| parse(); |
| setNext(); |
| } |
| |
| SccWalker(SourceChangeEventKey eventKey, EiffelEventHub eventHub, Repository repo) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| super(eventKey, eventHub, repo); |
| this.hasSccEventFlag = rw.newFlag("HAS_SCC_EVENT"); |
| parse(); |
| setNext(); |
| } |
| |
| protected void parse() |
| throws MissingObjectException, EiffelEventIdLookupException, IOException { |
| rw.markStart(rw.parseCommit(ObjectId.fromString(eventKey.commit()))); |
| rw.sort(RevSort.TOPO); |
| RevCommit commit; |
| while ((commit = rw.next()) != null) { |
| SourceChangeEventKey current = toKey(commit); |
| /* If branch is previously unhandled there's no point in asking eventHub/Event Repository |
| * since no events will be found. */ |
| if (hasEvents && isAlreadyHandled(hasSccEventFlag, commit, current)) { |
| logger.atFine().log("Event already created for %s", current); |
| flagWithParents(commit, hasSccEventFlag); |
| rw.markUninteresting(commit); |
| } |
| } |
| rw.resetRetain(RevFlag.UNINTERESTING, hasSccEventFlag); |
| rw.sort(RevSort.TOPO, true); |
| rw.sort(RevSort.REVERSE, true); |
| rw.markStart(rw.parseCommit(ObjectId.fromString(eventKey.commit()))); |
| } |
| |
| @Override |
| protected void setNext() |
| throws MissingObjectException, EiffelEventIdLookupException, IOException { |
| next = null; |
| RevCommit commit = rw.next(); |
| if (commit == null) { |
| return; |
| } |
| next = new EventCreate(commit); |
| } |
| } |
| |
| static class ScsWalker extends UnprocessedCommitsWalker { |
| |
| private final RevCommit transactionEnd; |
| private final AccountInfo submitter; |
| private final Long submittedAt; |
| private RevFlag hasScsEventFlag; |
| private RevFlag outsideTransaction; |
| |
| ScsWalker( |
| SourceChangeEventKey eventKey, |
| EiffelEventHub eventHub, |
| GitRepositoryManager repoManager, |
| String commitSha1TransactionEnd, |
| AccountInfo submitter, |
| Long submittedAt) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| super(eventKey, eventHub, repoManager); |
| checkState(eventKey.type().equals(SCS), "EventKey must have type SCS for ScsWalker."); |
| this.transactionEnd = |
| (commitSha1TransactionEnd == null |
| || ObjectId.fromString(commitSha1TransactionEnd).equals(ObjectId.zeroId())) |
| ? null |
| : rw.parseCommit(ObjectId.fromString(commitSha1TransactionEnd)); |
| this.submitter = submitter; |
| this.submittedAt = submittedAt; |
| this.hasScsEventFlag = rw.newFlag("HAS_SCS_EVENT"); |
| this.outsideTransaction = rw.newFlag("OUTSIDE_TRANSACTION"); |
| parse(); |
| setNext(); |
| } |
| |
| public SccWalker sccWalker() |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| return new SccWalker(this.eventKey.copy(SCC), this.eventHub, this.repo); |
| } |
| |
| protected void parse() |
| throws MissingObjectException, EiffelEventIdLookupException, IOException { |
| rw.markStart(rw.parseCommit(ObjectId.fromString(eventKey.commit()))); |
| rw.sort(RevSort.TOPO); |
| if (transactionEnd != null && !transactionEnd.equals(ObjectId.zeroId())) { |
| rw.markUninteresting(transactionEnd); |
| } |
| RevCommit commit; |
| while ((commit = rw.next()) != null) { |
| SourceChangeEventKey current = toKey(commit); |
| /* If branch is previously unhandled there's no point in asking eventHub/Event Repository |
| * since no events will be found. */ |
| if (hasEvents && isAlreadyHandled(hasScsEventFlag, commit, current)) { |
| flagWithParents(commit, hasScsEventFlag); |
| rw.markUninteresting(commit); |
| } |
| } |
| if (transactionEnd != null |
| && eventHub.getExistingId(eventKey.copy(transactionEnd)).isEmpty()) { |
| rw.resetRetain(hasScsEventFlag); |
| transactionEnd.remove(RevFlag.UNINTERESTING); |
| rw.markStart(transactionEnd); |
| rw.sort(RevSort.TOPO); |
| while ((commit = rw.next()) != null) { |
| SourceChangeEventKey current = eventKey.copy(commit); |
| /* If branch is previously unhandled there's no point in asking eventHub/Event Repository |
| * since no events will be found. */ |
| if (hasEvents && isAlreadyHandled(hasScsEventFlag, commit, current)) { |
| flagWithParents(commit, hasScsEventFlag); |
| rw.markUninteresting(commit); |
| } else { |
| commit.add(outsideTransaction); |
| } |
| } |
| } |
| rw.resetRetain(RevFlag.UNINTERESTING, hasScsEventFlag, outsideTransaction); |
| rw.sort(RevSort.TOPO, true); |
| rw.sort(RevSort.REVERSE, true); |
| rw.markStart(rw.parseCommit(ObjectId.fromString(eventKey.commit()))); |
| } |
| |
| @Override |
| protected void setNext() |
| throws MissingObjectException, EiffelEventIdLookupException, IOException { |
| next = null; |
| RevCommit commit = rw.next(); |
| if (commit == null) { |
| return; |
| } |
| if (commit.has(outsideTransaction)) { |
| next = new EventCreate(commit); |
| } else { |
| next = new EventCreate(commit, submitter, submittedAt); |
| } |
| } |
| } |
| |
| static class ChildWalker extends CommitsWalker { |
| private Optional<SourceChangeEventKey> parentKey; |
| |
| ChildWalker( |
| SourceChangeEventKey eventKey, |
| EiffelEventHub eventHub, |
| GitRepositoryManager repoManager, |
| Optional<SourceChangeEventKey> parentKey) |
| throws RepositoryNotFoundException, IOException, EiffelEventIdLookupException { |
| super(eventKey, eventHub, repoManager); |
| this.parentKey = parentKey; |
| checkState( |
| eventKey.type().equals(SCC) || eventKey.type().equals(SCS), |
| "EventKey must have type SCC or SCS for ChildWalker."); |
| rw.markStart(rw.parseCommit(ObjectId.fromString(eventKey.commit()))); |
| rw.sort(RevSort.TOPO); |
| rw.sort(RevSort.REVERSE, true); |
| setNext(); |
| } |
| |
| @Override |
| protected void setNext() |
| throws MissingObjectException, EiffelEventIdLookupException, IOException { |
| next = null; |
| RevCommit commit = rw.next(); |
| |
| if (parentKey.isPresent()) { |
| while (commit != null) { |
| if (isDirectChild(commit)) { |
| rw.markUninteresting(commit); |
| break; |
| } |
| commit = rw.next(); |
| } |
| } |
| if (commit == null) { |
| return; |
| } |
| next = new EventCreate(commit); |
| } |
| |
| private boolean isDirectChild(RevCommit commit) { |
| return Arrays.asList(commit.getParents()).stream() |
| .map(RevCommit::getName) |
| .anyMatch(parent -> parent.equals(parentKey.get().commit())); |
| } |
| } |
| } |