| /* |
| * Copyright (C) 2010, Google Inc. |
| * Copyright (C) 2009, Robin Rosenberg |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.internal.storage.file; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.AtomicMoveNotSupportedException; |
| import java.nio.file.StandardCopyOption; |
| |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.RefRename; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.RefUpdate.Result; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.util.FileUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Rename any reference stored by {@link RefDirectory}. |
| * <p> |
| * This class works by first renaming the source reference to a temporary name, |
| * then renaming the temporary name to the final destination reference. |
| * <p> |
| * This strategy permits switching a reference like {@code refs/heads/foo}, |
| * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a |
| * directory that happens to match the source name. |
| */ |
| class RefDirectoryRename extends RefRename { |
| private static final Logger LOG = LoggerFactory |
| .getLogger(RefDirectoryRename.class); |
| |
| private final RefDirectory refdb; |
| |
| /** |
| * The value of the source reference at the start of the rename. |
| * <p> |
| * At the end of the rename the destination reference must have this same |
| * value, otherwise we have a concurrent update and the rename must fail |
| * without making any changes. |
| */ |
| private ObjectId objId; |
| |
| /** True if HEAD must be moved to the destination reference. */ |
| private boolean updateHEAD; |
| |
| /** A reference we backup {@link #objId} into during the rename. */ |
| private RefDirectoryUpdate tmp; |
| |
| RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) { |
| super(src, dst); |
| refdb = src.getRefDatabase(); |
| } |
| |
| @Override |
| protected Result doRename() throws IOException { |
| if (source.getRef().isSymbolic()) |
| return Result.IO_FAILURE; // not supported |
| |
| objId = source.getOldObjectId(); |
| updateHEAD = needToUpdateHEAD(); |
| tmp = refdb.newTemporaryUpdate(); |
| try (final RevWalk rw = new RevWalk(refdb.getRepository())) { |
| // First backup the source so its never unreachable. |
| tmp.setNewObjectId(objId); |
| tmp.setForceUpdate(true); |
| tmp.disableRefLog(); |
| switch (tmp.update(rw)) { |
| case NEW: |
| case FORCED: |
| case NO_CHANGE: |
| break; |
| default: |
| return tmp.getResult(); |
| } |
| |
| // Save the source's log under the temporary name, we must do |
| // this before we delete the source, otherwise we lose the log. |
| if (!renameLog(source, tmp)) |
| return Result.IO_FAILURE; |
| |
| // If HEAD has to be updated, link it now to destination. |
| // We have to link before we delete, otherwise the delete |
| // fails because its the current branch. |
| RefUpdate dst = destination; |
| if (updateHEAD) { |
| if (!linkHEAD(destination)) { |
| renameLog(tmp, source); |
| return Result.LOCK_FAILURE; |
| } |
| |
| // Replace the update operation so HEAD will log the rename. |
| dst = refdb.newUpdate(Constants.HEAD, false); |
| dst.setRefLogIdent(destination.getRefLogIdent()); |
| dst.setRefLogMessage(destination.getRefLogMessage(), false); |
| } |
| |
| // Delete the source name so its path is free for replacement. |
| source.setExpectedOldObjectId(objId); |
| source.setForceUpdate(true); |
| source.disableRefLog(); |
| if (source.delete(rw) != Result.FORCED) { |
| renameLog(tmp, source); |
| if (updateHEAD) |
| linkHEAD(source); |
| return source.getResult(); |
| } |
| |
| // Move the log to the destination. |
| if (!renameLog(tmp, destination)) { |
| renameLog(tmp, source); |
| source.setExpectedOldObjectId(ObjectId.zeroId()); |
| source.setNewObjectId(objId); |
| source.update(rw); |
| if (updateHEAD) |
| linkHEAD(source); |
| return Result.IO_FAILURE; |
| } |
| |
| // Create the destination, logging the rename during the creation. |
| dst.setExpectedOldObjectId(ObjectId.zeroId()); |
| dst.setNewObjectId(objId); |
| if (dst.update(rw) != Result.NEW) { |
| // If we didn't create the destination we have to undo |
| // our work. Put the log back and restore source. |
| if (renameLog(destination, tmp)) |
| renameLog(tmp, source); |
| source.setExpectedOldObjectId(ObjectId.zeroId()); |
| source.setNewObjectId(objId); |
| source.update(rw); |
| if (updateHEAD) |
| linkHEAD(source); |
| return dst.getResult(); |
| } |
| |
| return Result.RENAMED; |
| } finally { |
| // Always try to free the temporary name. |
| try { |
| refdb.delete(tmp); |
| } catch (IOException err) { |
| FileUtils.delete(refdb.fileFor(tmp.getName())); |
| } |
| } |
| } |
| |
| private boolean renameLog(RefUpdate src, RefUpdate dst) { |
| File srcLog = refdb.getLogWriter().logFor(src.getName()); |
| File dstLog = refdb.getLogWriter().logFor(dst.getName()); |
| |
| if (!srcLog.exists()) |
| return true; |
| |
| if (!rename(srcLog, dstLog)) |
| return false; |
| |
| try { |
| final int levels = RefDirectory.levelsIn(src.getName()) - 2; |
| RefDirectory.delete(srcLog, levels); |
| return true; |
| } catch (IOException e) { |
| rename(dstLog, srcLog); |
| return false; |
| } |
| } |
| |
| private static boolean rename(File src, File dst) { |
| try { |
| FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); |
| return true; |
| } catch (AtomicMoveNotSupportedException e) { |
| LOG.error(e.getMessage(), e); |
| } catch (IOException e) { |
| // ignore |
| } |
| |
| File dir = dst.getParentFile(); |
| if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) |
| return false; |
| try { |
| FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); |
| return true; |
| } catch (IOException e) { |
| LOG.error(e.getMessage(), e); |
| return false; |
| } |
| } |
| |
| private boolean linkHEAD(RefUpdate target) { |
| try { |
| RefUpdate u = refdb.newUpdate(Constants.HEAD, false); |
| u.disableRefLog(); |
| switch (u.link(target.getName())) { |
| case NEW: |
| case FORCED: |
| case NO_CHANGE: |
| return true; |
| default: |
| return false; |
| } |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| } |