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  	@Override
99  	protected Result doRename() throws IOException {
100 		if (source.getRef().isSymbolic())
101 			return Result.IO_FAILURE; // not supported
102 
103 		objId = source.getOldObjectId();
104 		updateHEAD = needToUpdateHEAD();
105 		tmp = refdb.newTemporaryUpdate();
106 		try (final RevWalk rw = new RevWalk(refdb.getRepository())) {
107 			// First backup the source so its never unreachable.
108 			tmp.setNewObjectId(objId);
109 			tmp.setForceUpdate(true);
110 			tmp.disableRefLog();
111 			switch (tmp.update(rw)) {
112 			case NEW:
113 			case FORCED:
114 			case NO_CHANGE:
115 				break;
116 			default:
117 				return tmp.getResult();
118 			}
119 
120 			// Save the source's log under the temporary name, we must do
121 			// this before we delete the source, otherwise we lose the log.
122 			if (!renameLog(source, tmp))
123 				return Result.IO_FAILURE;
124 
125 			// If HEAD has to be updated, link it now to destination.
126 			// We have to link before we delete, otherwise the delete
127 			// fails because its the current branch.
128 			RefUpdate dst = destination;
129 			if (updateHEAD) {
130 				if (!linkHEAD(destination)) {
131 					renameLog(tmp, source);
132 					return Result.LOCK_FAILURE;
133 				}
134 
135 				// Replace the update operation so HEAD will log the rename.
136 				dst = refdb.newUpdate(Constants.HEAD, false);
137 				dst.setRefLogIdent(destination.getRefLogIdent());
138 				dst.setRefLogMessage(destination.getRefLogMessage(), false);
139 			}
140 
141 			// Delete the source name so its path is free for replacement.
142 			source.setExpectedOldObjectId(objId);
143 			source.setForceUpdate(true);
144 			source.disableRefLog();
145 			if (source.delete(rw) != Result.FORCED) {
146 				renameLog(tmp, source);
147 				if (updateHEAD)
148 					linkHEAD(source);
149 				return source.getResult();
150 			}
151 
152 			// Move the log to the destination.
153 			if (!renameLog(tmp, destination)) {
154 				renameLog(tmp, source);
155 				source.setExpectedOldObjectId(ObjectId.zeroId());
156 				source.setNewObjectId(objId);
157 				source.update(rw);
158 				if (updateHEAD)
159 					linkHEAD(source);
160 				return Result.IO_FAILURE;
161 			}
162 
163 			// Create the destination, logging the rename during the creation.
164 			dst.setExpectedOldObjectId(ObjectId.zeroId());
165 			dst.setNewObjectId(objId);
166 			if (dst.update(rw) != Result.NEW) {
167 				// If we didn't create the destination we have to undo
168 				// our work. Put the log back and restore source.
169 				if (renameLog(destination, tmp))
170 					renameLog(tmp, source);
171 				source.setExpectedOldObjectId(ObjectId.zeroId());
172 				source.setNewObjectId(objId);
173 				source.update(rw);
174 				if (updateHEAD)
175 					linkHEAD(source);
176 				return dst.getResult();
177 			}
178 
179 			return Result.RENAMED;
180 		} finally {
181 			// Always try to free the temporary name.
182 			try {
183 				refdb.delete(tmp);
184 			} catch (IOException err) {
185 				FileUtils.delete(refdb.fileFor(tmp.getName()));
186 			}
187 		}
188 	}
189 
190 	private boolean renameLog(RefUpdate src, RefUpdate dst) {
191 		File srcLog = refdb.getLogWriter().logFor(src.getName());
192 		File dstLog = refdb.getLogWriter().logFor(dst.getName());
193 
194 		if (!srcLog.exists())
195 			return true;
196 
197 		if (!rename(srcLog, dstLog))
198 			return false;
199 
200 		try {
201 			final int levels = RefDirectory.levelsIn(src.getName()) - 2;
202 			RefDirectory.delete(srcLog, levels);
203 			return true;
204 		} catch (IOException e) {
205 			rename(dstLog, srcLog);
206 			return false;
207 		}
208 	}
209 
210 	private static boolean rename(File src, File dst) {
211 		try {
212 			FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
213 			return true;
214 		} catch (AtomicMoveNotSupportedException e) {
215 			LOG.error(e.getMessage(), e);
216 		} catch (IOException e) {
217 			// ignore
218 		}
219 
220 		File dir = dst.getParentFile();
221 		if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
222 			return false;
223 		try {
224 			FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
225 			return true;
226 		} catch (IOException e) {
227 			LOG.error(e.getMessage(), e);
228 			return false;
229 		}
230 	}
231 
232 	private boolean linkHEAD(RefUpdate target) {
233 		try {
234 			RefUpdate u = refdb.newUpdate(Constants.HEAD, false);
235 			u.disableRefLog();
236 			switch (u.link(target.getName())) {
237 			case NEW:
238 			case FORCED:
239 			case NO_CHANGE:
240 				return true;
241 			default:
242 				return false;
243 			}
244 		} catch (IOException e) {
245 			return false;
246 		}
247 	}
248 }