blob: 0ad822364c2bca192c6ac2d49c5083eec3ca0c72 [file] [log] [blame]
// 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()));
}
}
}