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.     /** A reference we backup {@link #objId} into during the rename. */
  48.     private RefDirectoryUpdate tmp;

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

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

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

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

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

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

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

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

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

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

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

  140.         if (!srcLog.exists())
  141.             return true;

  142.         if (!rename(srcLog, dstLog))
  143.             return false;

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

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

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

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