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  
50  import org.eclipse.jgit.lib.Constants;
51  import org.eclipse.jgit.lib.ObjectId;
52  import org.eclipse.jgit.lib.RefRename;
53  import org.eclipse.jgit.lib.RefUpdate;
54  import org.eclipse.jgit.lib.RefUpdate.Result;
55  import org.eclipse.jgit.revwalk.RevWalk;
56  import org.eclipse.jgit.util.FileUtils;
57  
58  /**
59   * Rename any reference stored by {@link RefDirectory}.
60   * <p>
61   * This class works by first renaming the source reference to a temporary name,
62   * then renaming the temporary name to the final destination reference.
63   * <p>
64   * This strategy permits switching a reference like {@code refs/heads/foo},
65   * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a
66   * directory that happens to match the source name.
67   */
68  class RefDirectoryRename extends RefRename {
69  	private final RefDirectory refdb;
70  
71  	/**
72  	 * The value of the source reference at the start of the rename.
73  	 * <p>
74  	 * At the end of the rename the destination reference must have this same
75  	 * value, otherwise we have a concurrent update and the rename must fail
76  	 * without making any changes.
77  	 */
78  	private ObjectId objId;
79  
80  	/** True if HEAD must be moved to the destination reference. */
81  	private boolean updateHEAD;
82  
83  	/** A reference we backup {@link #objId} into during the rename. */
84  	private RefDirectoryUpdate tmp;
85  
86  	RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) {
87  		super(src, dst);
88  		refdb = src.getRefDatabase();
89  	}
90  
91  	@Override
92  	protected Result doRename() throws IOException {
93  		if (source.getRef().isSymbolic())
94  			return Result.IO_FAILURE; // not supported
95  
96  		objId = source.getOldObjectId();
97  		updateHEAD = needToUpdateHEAD();
98  		tmp = refdb.newTemporaryUpdate();
99  		try (final RevWalk rw = new RevWalk(refdb.getRepository())) {
100 			// First backup the source so its never unreachable.
101 			tmp.setNewObjectId(objId);
102 			tmp.setForceUpdate(true);
103 			tmp.disableRefLog();
104 			switch (tmp.update(rw)) {
105 			case NEW:
106 			case FORCED:
107 			case NO_CHANGE:
108 				break;
109 			default:
110 				return tmp.getResult();
111 			}
112 
113 			// Save the source's log under the temporary name, we must do
114 			// this before we delete the source, otherwise we lose the log.
115 			if (!renameLog(source, tmp))
116 				return Result.IO_FAILURE;
117 
118 			// If HEAD has to be updated, link it now to destination.
119 			// We have to link before we delete, otherwise the delete
120 			// fails because its the current branch.
121 			RefUpdate dst = destination;
122 			if (updateHEAD) {
123 				if (!linkHEAD(destination)) {
124 					renameLog(tmp, source);
125 					return Result.LOCK_FAILURE;
126 				}
127 
128 				// Replace the update operation so HEAD will log the rename.
129 				dst = refdb.newUpdate(Constants.HEAD, false);
130 				dst.setRefLogIdent(destination.getRefLogIdent());
131 				dst.setRefLogMessage(destination.getRefLogMessage(), false);
132 			}
133 
134 			// Delete the source name so its path is free for replacement.
135 			source.setExpectedOldObjectId(objId);
136 			source.setForceUpdate(true);
137 			source.disableRefLog();
138 			if (source.delete(rw) != Result.FORCED) {
139 				renameLog(tmp, source);
140 				if (updateHEAD)
141 					linkHEAD(source);
142 				return source.getResult();
143 			}
144 
145 			// Move the log to the destination.
146 			if (!renameLog(tmp, destination)) {
147 				renameLog(tmp, source);
148 				source.setExpectedOldObjectId(ObjectId.zeroId());
149 				source.setNewObjectId(objId);
150 				source.update(rw);
151 				if (updateHEAD)
152 					linkHEAD(source);
153 				return Result.IO_FAILURE;
154 			}
155 
156 			// Create the destination, logging the rename during the creation.
157 			dst.setExpectedOldObjectId(ObjectId.zeroId());
158 			dst.setNewObjectId(objId);
159 			if (dst.update(rw) != Result.NEW) {
160 				// If we didn't create the destination we have to undo
161 				// our work. Put the log back and restore source.
162 				if (renameLog(destination, tmp))
163 					renameLog(tmp, source);
164 				source.setExpectedOldObjectId(ObjectId.zeroId());
165 				source.setNewObjectId(objId);
166 				source.update(rw);
167 				if (updateHEAD)
168 					linkHEAD(source);
169 				return dst.getResult();
170 			}
171 
172 			return Result.RENAMED;
173 		} finally {
174 			// Always try to free the temporary name.
175 			try {
176 				refdb.delete(tmp);
177 			} catch (IOException err) {
178 				FileUtils.delete(refdb.fileFor(tmp.getName()));
179 			}
180 		}
181 	}
182 
183 	private boolean renameLog(RefUpdate src, RefUpdate dst) {
184 		File srcLog = refdb.getLogWriter().logFor(src.getName());
185 		File dstLog = refdb.getLogWriter().logFor(dst.getName());
186 
187 		if (!srcLog.exists())
188 			return true;
189 
190 		if (!rename(srcLog, dstLog))
191 			return false;
192 
193 		try {
194 			final int levels = RefDirectory.levelsIn(src.getName()) - 2;
195 			RefDirectory.delete(srcLog, levels);
196 			return true;
197 		} catch (IOException e) {
198 			rename(dstLog, srcLog);
199 			return false;
200 		}
201 	}
202 
203 	private static boolean rename(File src, File dst) {
204 		if (src.renameTo(dst))
205 			return true;
206 
207 		File dir = dst.getParentFile();
208 		if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
209 			return false;
210 		return src.renameTo(dst);
211 	}
212 
213 	private boolean linkHEAD(RefUpdate target) {
214 		try {
215 			RefUpdate u = refdb.newUpdate(Constants.HEAD, false);
216 			u.disableRefLog();
217 			switch (u.link(target.getName())) {
218 			case NEW:
219 			case FORCED:
220 			case NO_CHANGE:
221 				return true;
222 			default:
223 				return false;
224 			}
225 		} catch (IOException e) {
226 			return false;
227 		}
228 	}
229 }