ReftableBatchRefUpdate.java
- /*
- * Copyright (C) 2019, Google Inc. and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.internal.storage.reftable;
- import org.eclipse.jgit.annotations.Nullable;
- import org.eclipse.jgit.errors.MissingObjectException;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.lib.AnyObjectId;
- import org.eclipse.jgit.lib.BatchRefUpdate;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectIdRef;
- import org.eclipse.jgit.lib.PersonIdent;
- import org.eclipse.jgit.lib.ProgressMonitor;
- import org.eclipse.jgit.lib.Ref;
- import org.eclipse.jgit.lib.RefDatabase;
- import org.eclipse.jgit.lib.ReflogEntry;
- import org.eclipse.jgit.lib.Repository;
- import org.eclipse.jgit.lib.SymbolicRef;
- import org.eclipse.jgit.revwalk.RevObject;
- import org.eclipse.jgit.revwalk.RevTag;
- import org.eclipse.jgit.revwalk.RevWalk;
- import org.eclipse.jgit.transport.ReceiveCommand;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.TreeSet;
- import java.util.concurrent.locks.Lock;
- import java.util.stream.Collectors;
- import static org.eclipse.jgit.lib.Ref.Storage.NEW;
- import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
- import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
- import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
- import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
- import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
- import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
- import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
- import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
- /**
- * {@link org.eclipse.jgit.lib.BatchRefUpdate} for Reftable based RefDatabase.
- */
- public abstract class ReftableBatchRefUpdate extends BatchRefUpdate {
- private final Lock lock;
- private final ReftableDatabase refDb;
- private final Repository repository;
- /**
- * Initialize.
- *
- * @param refdb
- * The RefDatabase
- * @param reftableDb
- * The ReftableDatabase
- * @param lock
- * A lock protecting the refdatabase's state
- * @param repository
- * The repository on which this update will run
- */
- protected ReftableBatchRefUpdate(RefDatabase refdb, ReftableDatabase reftableDb, Lock lock,
- Repository repository) {
- super(refdb);
- this.refDb = reftableDb;
- this.lock = lock;
- this.repository = repository;
- }
- /** {@inheritDoc} */
- @Override
- public void execute(RevWalk rw, ProgressMonitor pm, List<String> options) {
- List<ReceiveCommand> pending = getPending();
- if (pending.isEmpty()) {
- return;
- }
- if (options != null) {
- setPushOptions(options);
- }
- try {
- if (!checkObjectExistence(rw, pending)) {
- return;
- }
- // if we are here, checkObjectExistence might have flagged some problems
- // but the transaction is not atomic, so we should proceed with the other
- // pending commands.
- pending = getPending();
- if (!checkNonFastForwards(rw, pending)) {
- return;
- }
- pending = getPending();
- lock.lock();
- try {
- if (!checkExpected(pending)) {
- return;
- }
- pending = getPending();
- if (!checkConflicting(pending)) {
- return;
- }
- pending = getPending();
- if (!blockUntilTimestamps(MAX_WAIT)) {
- return;
- }
- List<Ref> newRefs = toNewRefs(rw, pending);
- applyUpdates(newRefs, pending);
- for (ReceiveCommand cmd : pending) {
- if (cmd.getResult() == NOT_ATTEMPTED) {
- // XXX this is a bug in DFS ?
- cmd.setResult(OK);
- }
- }
- } finally {
- lock.unlock();
- }
- } catch (IOException e) {
- pending.get(0).setResult(LOCK_FAILURE, "io error"); //$NON-NLS-1$
- ReceiveCommand.abort(pending);
- }
- }
- /**
- * Implements the storage-specific part of the update.
- *
- * @param newRefs
- * the new refs to create
- * @param pending
- * the pending receive commands to be executed
- * @throws IOException
- * if any of the writes fail.
- */
- protected abstract void applyUpdates(List<Ref> newRefs,
- List<ReceiveCommand> pending) throws IOException;
- private List<ReceiveCommand> getPending() {
- return ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
- }
- private boolean checkObjectExistence(RevWalk rw,
- List<ReceiveCommand> pending) throws IOException {
- for (ReceiveCommand cmd : pending) {
- try {
- if (!cmd.getNewId().equals(ObjectId.zeroId())) {
- rw.parseAny(cmd.getNewId());
- }
- } catch (MissingObjectException e) {
- // ReceiveCommand#setResult(Result) converts REJECTED to
- // REJECTED_NONFASTFORWARD, even though that result is also
- // used for a missing object. Eagerly handle this case so we
- // can set the right result.
- cmd.setResult(REJECTED_MISSING_OBJECT);
- if (isAtomic()) {
- ReceiveCommand.abort(pending);
- return false;
- }
- }
- }
- return true;
- }
- private boolean checkNonFastForwards(RevWalk rw,
- List<ReceiveCommand> pending) throws IOException {
- if (isAllowNonFastForwards()) {
- return true;
- }
- for (ReceiveCommand cmd : pending) {
- cmd.updateType(rw);
- if (cmd.getType() == UPDATE_NONFASTFORWARD) {
- cmd.setResult(REJECTED_NONFASTFORWARD);
- if (isAtomic()) {
- ReceiveCommand.abort(pending);
- return false;
- }
- }
- }
- return true;
- }
- private boolean checkConflicting(List<ReceiveCommand> pending)
- throws IOException {
- TreeSet<String> added = new TreeSet<>();
- Set<String> deleted =
- pending.stream()
- .filter(cmd -> cmd.getType() == DELETE)
- .map(c -> c.getRefName())
- .collect(Collectors.toSet());
- boolean ok = true;
- for (ReceiveCommand cmd : pending) {
- if (cmd.getType() == DELETE) {
- continue;
- }
- String name = cmd.getRefName();
- if (refDb.isNameConflicting(name, added, deleted)) {
- if (isAtomic()) {
- cmd.setResult(
- ReceiveCommand.Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
- } else {
- cmd.setResult(LOCK_FAILURE);
- }
- ok = false;
- }
- added.add(name);
- }
- if (isAtomic()) {
- if (!ok) {
- pending.stream()
- .filter(cmd -> cmd.getResult() == NOT_ATTEMPTED)
- .forEach(cmd -> cmd.setResult(LOCK_FAILURE));
- }
- return ok;
- }
- for (ReceiveCommand cmd : pending) {
- if (cmd.getResult() == NOT_ATTEMPTED) {
- return true;
- }
- }
- return false;
- }
- private boolean checkExpected(List<ReceiveCommand> pending)
- throws IOException {
- for (ReceiveCommand cmd : pending) {
- if (!matchOld(cmd, refDb.exactRef(cmd.getRefName()))) {
- cmd.setResult(LOCK_FAILURE);
- if (isAtomic()) {
- ReceiveCommand.abort(pending);
- return false;
- }
- }
- }
- return true;
- }
- private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) {
- if (ref == null) {
- return AnyObjectId.isEqual(ObjectId.zeroId(), cmd.getOldId())
- && cmd.getOldSymref() == null;
- } else if (ref.isSymbolic()) {
- return ref.getTarget().getName().equals(cmd.getOldSymref());
- }
- ObjectId id = ref.getObjectId();
- if (id == null) {
- id = ObjectId.zeroId();
- }
- return cmd.getOldId().equals(id);
- }
- /**
- * Writes the refs to the writer, and calls finish.
- *
- * @param writer
- * the writer on which we should write.
- * @param newRefs
- * the ref data to write..
- * @param pending
- * the log data to write.
- * @throws IOException
- * in case of problems.
- */
- protected void write(ReftableWriter writer, List<Ref> newRefs,
- List<ReceiveCommand> pending) throws IOException {
- long updateIndex = refDb.nextUpdateIndex();
- writer.setMinUpdateIndex(updateIndex).setMaxUpdateIndex(updateIndex)
- .begin().sortAndWriteRefs(newRefs);
- if (!isRefLogDisabled()) {
- writeLog(writer, updateIndex, pending);
- }
- }
- private void writeLog(ReftableWriter writer, long updateIndex,
- List<ReceiveCommand> pending) throws IOException {
- Map<String, ReceiveCommand> cmds = new HashMap<>();
- List<String> byName = new ArrayList<>(pending.size());
- for (ReceiveCommand cmd : pending) {
- cmds.put(cmd.getRefName(), cmd);
- byName.add(cmd.getRefName());
- }
- Collections.sort(byName);
- PersonIdent ident = getRefLogIdent();
- if (ident == null) {
- ident = new PersonIdent(repository);
- }
- for (String name : byName) {
- ReceiveCommand cmd = cmds.get(name);
- if (isRefLogDisabled(cmd)) {
- continue;
- }
- String msg = getRefLogMessage(cmd);
- if (isRefLogIncludingResult(cmd)) {
- String strResult = toResultString(cmd);
- if (strResult != null) {
- msg = msg.isEmpty() ? strResult : msg + ": " + strResult; //$NON-NLS-1$
- }
- }
- writer.writeLog(name, updateIndex, ident, cmd.getOldId(),
- cmd.getNewId(), msg);
- }
- }
- private String toResultString(ReceiveCommand cmd) {
- switch (cmd.getType()) {
- case CREATE:
- return ReflogEntry.PREFIX_CREATED;
- case UPDATE:
- // Match the behavior of a single RefUpdate. In that case, setting
- // the force bit completely bypasses the potentially expensive
- // isMergedInto check, by design, so the reflog message may be
- // inaccurate.
- //
- // Similarly, this class bypasses the isMergedInto checks when the
- // force bit is set, meaning we can't actually distinguish between
- // UPDATE and UPDATE_NONFASTFORWARD when isAllowNonFastForwards()
- // returns true.
- return isAllowNonFastForwards() ? ReflogEntry.PREFIX_FORCED_UPDATE
- : ReflogEntry.PREFIX_FAST_FORWARD;
- case UPDATE_NONFASTFORWARD:
- return ReflogEntry.PREFIX_FORCED_UPDATE;
- default:
- return null;
- }
- }
- // Extracts and peels the refs out of the ReceiveCommands
- private static List<Ref> toNewRefs(RevWalk rw, List<ReceiveCommand> pending)
- throws IOException {
- List<Ref> refs = new ArrayList<>(pending.size());
- for (ReceiveCommand cmd : pending) {
- if (cmd.getResult() != NOT_ATTEMPTED) {
- continue;
- }
- String name = cmd.getRefName();
- ObjectId newId = cmd.getNewId();
- String newSymref = cmd.getNewSymref();
- if (AnyObjectId.isEqual(ObjectId.zeroId(), newId)
- && newSymref == null) {
- refs.add(new ObjectIdRef.Unpeeled(NEW, name, null));
- continue;
- } else if (newSymref != null) {
- refs.add(new SymbolicRef(name,
- new ObjectIdRef.Unpeeled(NEW, newSymref, null)));
- continue;
- }
- RevObject obj = rw.parseAny(newId);
- RevObject peel = null;
- if (obj instanceof RevTag) {
- peel = rw.peel(obj);
- }
- if (peel != null) {
- refs.add(new ObjectIdRef.PeeledTag(PACKED, name, newId,
- peel.copy()));
- } else {
- refs.add(new ObjectIdRef.PeeledNonTag(PACKED, name, newId));
- }
- }
- return refs;
- }
- }