RefDirectoryRename.java

  1. /*
  2.  * Copyright (C) 2010, Google Inc.
  3.  * Copyright (C) 2009, Robin Rosenberg and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.internal.storage.file;

  12. import java.io.File;
  13. import java.io.IOException;
  14. import java.nio.file.AtomicMoveNotSupportedException;
  15. import java.nio.file.StandardCopyOption;

  16. import org.eclipse.jgit.lib.Constants;
  17. import org.eclipse.jgit.lib.ObjectId;
  18. import org.eclipse.jgit.lib.RefRename;
  19. import org.eclipse.jgit.lib.RefUpdate;
  20. import org.eclipse.jgit.lib.RefUpdate.Result;
  21. import org.eclipse.jgit.revwalk.RevWalk;
  22. import org.eclipse.jgit.util.FileUtils;
  23. import org.slf4j.Logger;
  24. import org.slf4j.LoggerFactory;

  25. /**
  26.  * Rename any reference stored by {@link RefDirectory}.
  27.  * <p>
  28.  * This class works by first renaming the source reference to a temporary name,
  29.  * then renaming the temporary name to the final destination reference.
  30.  * <p>
  31.  * This strategy permits switching a reference like {@code refs/heads/foo},
  32.  * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a
  33.  * directory that happens to match the source name.
  34.  */
  35. class RefDirectoryRename extends RefRename {
  36.     private static final Logger LOG = LoggerFactory
  37.             .getLogger(RefDirectoryRename.class);

  38.     private final RefDirectory refdb;

  39.     /**
  40.      * The value of the source reference at the start of the rename.
  41.      * <p>
  42.      * At the end of the rename the destination reference must have this same
  43.      * value, otherwise we have a concurrent update and the rename must fail
  44.      * without making any changes.
  45.      */
  46.     private ObjectId objId;

  47.     /** True if HEAD must be moved to the destination reference. */
  48.     private boolean updateHEAD;

  49.     /** A reference we backup {@link #objId} into during the rename. */
  50.     private RefDirectoryUpdate tmp;

  51.     RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) {
  52.         super(src, dst);
  53.         refdb = src.getRefDatabase();
  54.     }

  55.     /** {@inheritDoc} */
  56.     @Override
  57.     protected Result doRename() throws IOException {
  58.         if (source.getRef().isSymbolic())
  59.             return Result.IO_FAILURE; // not supported

  60.         objId = source.getOldObjectId();
  61.         updateHEAD = needToUpdateHEAD();
  62.         tmp = refdb.newTemporaryUpdate();
  63.         try (RevWalk rw = new RevWalk(refdb.getRepository())) {
  64.             // First backup the source so its never unreachable.
  65.             tmp.setNewObjectId(objId);
  66.             tmp.setForceUpdate(true);
  67.             tmp.disableRefLog();
  68.             switch (tmp.update(rw)) {
  69.             case NEW:
  70.             case FORCED:
  71.             case NO_CHANGE:
  72.                 break;
  73.             default:
  74.                 return tmp.getResult();
  75.             }

  76.             // Save the source's log under the temporary name, we must do
  77.             // this before we delete the source, otherwise we lose the log.
  78.             if (!renameLog(source, tmp))
  79.                 return Result.IO_FAILURE;

  80.             // If HEAD has to be updated, link it now to destination.
  81.             // We have to link before we delete, otherwise the delete
  82.             // fails because its the current branch.
  83.             RefUpdate dst = destination;
  84.             if (updateHEAD) {
  85.                 if (!linkHEAD(destination)) {
  86.                     renameLog(tmp, source);
  87.                     return Result.LOCK_FAILURE;
  88.                 }

  89.                 // Replace the update operation so HEAD will log the rename.
  90.                 dst = refdb.newUpdate(Constants.HEAD, false);
  91.                 dst.setRefLogIdent(destination.getRefLogIdent());
  92.                 dst.setRefLogMessage(destination.getRefLogMessage(), false);
  93.             }

  94.             // Delete the source name so its path is free for replacement.
  95.             source.setExpectedOldObjectId(objId);
  96.             source.setForceUpdate(true);
  97.             source.disableRefLog();
  98.             if (source.delete(rw) != Result.FORCED) {
  99.                 renameLog(tmp, source);
  100.                 if (updateHEAD)
  101.                     linkHEAD(source);
  102.                 return source.getResult();
  103.             }

  104.             // Move the log to the destination.
  105.             if (!renameLog(tmp, destination)) {
  106.                 renameLog(tmp, source);
  107.                 source.setExpectedOldObjectId(ObjectId.zeroId());
  108.                 source.setNewObjectId(objId);
  109.                 source.update(rw);
  110.                 if (updateHEAD)
  111.                     linkHEAD(source);
  112.                 return Result.IO_FAILURE;
  113.             }

  114.             // Create the destination, logging the rename during the creation.
  115.             dst.setExpectedOldObjectId(ObjectId.zeroId());
  116.             dst.setNewObjectId(objId);
  117.             if (dst.update(rw) != Result.NEW) {
  118.                 // If we didn't create the destination we have to undo
  119.                 // our work. Put the log back and restore source.
  120.                 if (renameLog(destination, tmp))
  121.                     renameLog(tmp, source);
  122.                 source.setExpectedOldObjectId(ObjectId.zeroId());
  123.                 source.setNewObjectId(objId);
  124.                 source.update(rw);
  125.                 if (updateHEAD)
  126.                     linkHEAD(source);
  127.                 return dst.getResult();
  128.             }

  129.             return Result.RENAMED;
  130.         } finally {
  131.             // Always try to free the temporary name.
  132.             try {
  133.                 refdb.delete(tmp);
  134.             } catch (IOException err) {
  135.                 FileUtils.delete(refdb.fileFor(tmp.getName()));
  136.             }
  137.         }
  138.     }

  139.     private boolean renameLog(RefUpdate src, RefUpdate dst) {
  140.         File srcLog = refdb.logFor(src.getName());
  141.         File dstLog = refdb.logFor(dst.getName());

  142.         if (!srcLog.exists())
  143.             return true;

  144.         if (!rename(srcLog, dstLog))
  145.             return false;

  146.         try {
  147.             final int levels = RefDirectory.levelsIn(src.getName()) - 2;
  148.             RefDirectory.delete(srcLog, levels);
  149.             return true;
  150.         } catch (IOException e) {
  151.             rename(dstLog, srcLog);
  152.             return false;
  153.         }
  154.     }

  155.     private static boolean rename(File src, File dst) {
  156.         try {
  157.             FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
  158.             return true;
  159.         } catch (AtomicMoveNotSupportedException e) {
  160.             LOG.error(e.getMessage(), e);
  161.         } catch (IOException e) {
  162.             // ignore
  163.         }

  164.         File dir = dst.getParentFile();
  165.         if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
  166.             return false;
  167.         try {
  168.             FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
  169.             return true;
  170.         } catch (IOException e) {
  171.             LOG.error(e.getMessage(), e);
  172.             return false;
  173.         }
  174.     }

  175.     private boolean linkHEAD(RefUpdate target) {
  176.         try {
  177.             RefUpdate u = refdb.newUpdate(Constants.HEAD, false);
  178.             u.disableRefLog();
  179.             switch (u.link(target.getName())) {
  180.             case NEW:
  181.             case FORCED:
  182.             case NO_CHANGE:
  183.                 return true;
  184.             default:
  185.                 return false;
  186.             }
  187.         } catch (IOException e) {
  188.             return false;
  189.         }
  190.     }
  191. }