View Javadoc
1   /*
2    * Copyright (C) 2010, Google Inc.
3    * Copyright (C) 2009, Robin Rosenberg
4    * and other copyright owners as documented in the project's IP log.
5    *
6    * This program and the accompanying materials are made available
7    * under the terms of the Eclipse Distribution License v1.0 which
8    * accompanies this distribution, is reproduced below, and is
9    * available at http://www.eclipse.org/org/documents/edl-v10.php
10   *
11   * All rights reserved.
12   *
13   * Redistribution and use in source and binary forms, with or
14   * without modification, are permitted provided that the following
15   * conditions are met:
16   *
17   * - Redistributions of source code must retain the above copyright
18   *   notice, this list of conditions and the following disclaimer.
19   *
20   * - Redistributions in binary form must reproduce the above
21   *   copyright notice, this list of conditions and the following
22   *   disclaimer in the documentation and/or other materials provided
23   *   with the distribution.
24   *
25   * - Neither the name of the Eclipse Foundation, Inc. nor the
26   *   names of its contributors may be used to endorse or promote
27   *   products derived from this software without specific prior
28   *   written permission.
29   *
30   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43   */
44  
45  package org.eclipse.jgit.internal.storage.file;
46  
47  import java.io.File;
48  import java.io.IOException;
49  import java.nio.file.AtomicMoveNotSupportedException;
50  import java.nio.file.StandardCopyOption;
51  
52  import org.eclipse.jgit.lib.Constants;
53  import org.eclipse.jgit.lib.ObjectId;
54  import org.eclipse.jgit.lib.RefRename;
55  import org.eclipse.jgit.lib.RefUpdate;
56  import org.eclipse.jgit.lib.RefUpdate.Result;
57  import org.eclipse.jgit.revwalk.RevWalk;
58  import org.eclipse.jgit.util.FileUtils;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  /**
63   * Rename any reference stored by {@link RefDirectory}.
64   * <p>
65   * This class works by first renaming the source reference to a temporary name,
66   * then renaming the temporary name to the final destination reference.
67   * <p>
68   * This strategy permits switching a reference like {@code refs/heads/foo},
69   * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a
70   * directory that happens to match the source name.
71   */
72  class RefDirectoryRename extends RefRename {
73  	private static final Logger LOG = LoggerFactory
74  			.getLogger(RefDirectoryRename.class);
75  
76  	private final RefDirectory refdb;
77  
78  	/**
79  	 * The value of the source reference at the start of the rename.
80  	 * <p>
81  	 * At the end of the rename the destination reference must have this same
82  	 * value, otherwise we have a concurrent update and the rename must fail
83  	 * without making any changes.
84  	 */
85  	private ObjectId objId;
86  
87  	/** True if HEAD must be moved to the destination reference. */
88  	private boolean updateHEAD;
89  
90  	/** A reference we backup {@link #objId} into during the rename. */
91  	private RefDirectoryUpdate tmp;
92  
93  	RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) {
94  		super(src, dst);
95  		refdb = src.getRefDatabase();
96  	}
97  
98  	/** {@inheritDoc} */
99  	@Override
100 	protected Result doRename() throws IOException {
101 		if (source.getRef().isSymbolic())
102 			return Result.IO_FAILURE; // not supported
103 
104 		objId = source.getOldObjectId();
105 		updateHEAD = needToUpdateHEAD();
106 		tmp = refdb.newTemporaryUpdate();
107 		try (final RevWalk rw = new RevWalk(refdb.getRepository())) {
108 			// First backup the source so its never unreachable.
109 			tmp.setNewObjectId(objId);
110 			tmp.setForceUpdate(true);
111 			tmp.disableRefLog();
112 			switch (tmp.update(rw)) {
113 			case NEW:
114 			case FORCED:
115 			case NO_CHANGE:
116 				break;
117 			default:
118 				return tmp.getResult();
119 			}
120 
121 			// Save the source's log under the temporary name, we must do
122 			// this before we delete the source, otherwise we lose the log.
123 			if (!renameLog(source, tmp))
124 				return Result.IO_FAILURE;
125 
126 			// If HEAD has to be updated, link it now to destination.
127 			// We have to link before we delete, otherwise the delete
128 			// fails because its the current branch.
129 			RefUpdate dst = destination;
130 			if (updateHEAD) {
131 				if (!linkHEAD(destination)) {
132 					renameLog(tmp, source);
133 					return Result.LOCK_FAILURE;
134 				}
135 
136 				// Replace the update operation so HEAD will log the rename.
137 				dst = refdb.newUpdate(Constants.HEAD, false);
138 				dst.setRefLogIdent(destination.getRefLogIdent());
139 				dst.setRefLogMessage(destination.getRefLogMessage(), false);
140 			}
141 
142 			// Delete the source name so its path is free for replacement.
143 			source.setExpectedOldObjectId(objId);
144 			source.setForceUpdate(true);
145 			source.disableRefLog();
146 			if (source.delete(rw) != Result.FORCED) {
147 				renameLog(tmp, source);
148 				if (updateHEAD)
149 					linkHEAD(source);
150 				return source.getResult();
151 			}
152 
153 			// Move the log to the destination.
154 			if (!renameLog(tmp, destination)) {
155 				renameLog(tmp, source);
156 				source.setExpectedOldObjectId(ObjectId.zeroId());
157 				source.setNewObjectId(objId);
158 				source.update(rw);
159 				if (updateHEAD)
160 					linkHEAD(source);
161 				return Result.IO_FAILURE;
162 			}
163 
164 			// Create the destination, logging the rename during the creation.
165 			dst.setExpectedOldObjectId(ObjectId.zeroId());
166 			dst.setNewObjectId(objId);
167 			if (dst.update(rw) != Result.NEW) {
168 				// If we didn't create the destination we have to undo
169 				// our work. Put the log back and restore source.
170 				if (renameLog(destination, tmp))
171 					renameLog(tmp, source);
172 				source.setExpectedOldObjectId(ObjectId.zeroId());
173 				source.setNewObjectId(objId);
174 				source.update(rw);
175 				if (updateHEAD)
176 					linkHEAD(source);
177 				return dst.getResult();
178 			}
179 
180 			return Result.RENAMED;
181 		} finally {
182 			// Always try to free the temporary name.
183 			try {
184 				refdb.delete(tmp);
185 			} catch (IOException err) {
186 				FileUtils.delete(refdb.fileFor(tmp.getName()));
187 			}
188 		}
189 	}
190 
191 	private boolean renameLog(RefUpdate src, RefUpdate dst) {
192 		File srcLog = refdb.logFor(src.getName());
193 		File dstLog = refdb.logFor(dst.getName());
194 
195 		if (!srcLog.exists())
196 			return true;
197 
198 		if (!rename(srcLog, dstLog))
199 			return false;
200 
201 		try {
202 			final int levels = RefDirectory.levelsIn(src.getName()) - 2;
203 			RefDirectory.delete(srcLog, levels);
204 			return true;
205 		} catch (IOException e) {
206 			rename(dstLog, srcLog);
207 			return false;
208 		}
209 	}
210 
211 	private static boolean rename(File src, File dst) {
212 		try {
213 			FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
214 			return true;
215 		} catch (AtomicMoveNotSupportedException e) {
216 			LOG.error(e.getMessage(), e);
217 		} catch (IOException e) {
218 			// ignore
219 		}
220 
221 		File dir = dst.getParentFile();
222 		if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
223 			return false;
224 		try {
225 			FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
226 			return true;
227 		} catch (IOException e) {
228 			LOG.error(e.getMessage(), e);
229 			return false;
230 		}
231 	}
232 
233 	private boolean linkHEAD(RefUpdate target) {
234 		try {
235 			RefUpdate u = refdb.newUpdate(Constants.HEAD, false);
236 			u.disableRefLog();
237 			switch (u.link(target.getName())) {
238 			case NEW:
239 			case FORCED:
240 			case NO_CHANGE:
241 				return true;
242 			default:
243 				return false;
244 			}
245 		} catch (IOException e) {
246 			return false;
247 		}
248 	}
249 }