View Javadoc
1   /*
2    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3    * Copyright (C) 2009-2010, Google Inc.
4    * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
6    * and other copyright owners as documented in the project's IP log.
7    *
8    * This program and the accompanying materials are made available
9    * under the terms of the Eclipse Distribution License v1.0 which
10   * accompanies this distribution, is reproduced below, and is
11   * available at http://www.eclipse.org/org/documents/edl-v10.php
12   *
13   * All rights reserved.
14   *
15   * Redistribution and use in source and binary forms, with or
16   * without modification, are permitted provided that the following
17   * conditions are met:
18   *
19   * - Redistributions of source code must retain the above copyright
20   *   notice, this list of conditions and the following disclaimer.
21   *
22   * - Redistributions in binary form must reproduce the above
23   *   copyright notice, this list of conditions and the following
24   *   disclaimer in the documentation and/or other materials provided
25   *   with the distribution.
26   *
27   * - Neither the name of the Eclipse Foundation, Inc. nor the
28   *   names of its contributors may be used to endorse or promote
29   *   products derived from this software without specific prior
30   *   written permission.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
33   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
34   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
39   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
43   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45   */
46  package org.eclipse.jgit.internal.storage.file;
47  
48  import static org.eclipse.jgit.lib.Constants.HEAD;
49  import static org.eclipse.jgit.lib.Constants.LOGS;
50  import static org.eclipse.jgit.lib.Constants.R_HEADS;
51  import static org.eclipse.jgit.lib.Constants.R_REFS;
52  import static org.eclipse.jgit.lib.Constants.R_REMOTES;
53  import static org.eclipse.jgit.lib.Constants.R_STASH;
54  
55  import java.io.File;
56  import java.io.FileNotFoundException;
57  import java.io.FileOutputStream;
58  import java.io.IOException;
59  import java.nio.ByteBuffer;
60  import java.nio.channels.FileChannel;
61  import java.text.MessageFormat;
62  
63  import org.eclipse.jgit.internal.JGitText;
64  import org.eclipse.jgit.lib.Constants;
65  import org.eclipse.jgit.lib.CoreConfig;
66  import org.eclipse.jgit.lib.ObjectId;
67  import org.eclipse.jgit.lib.PersonIdent;
68  import org.eclipse.jgit.lib.Ref;
69  import org.eclipse.jgit.lib.RefUpdate;
70  import org.eclipse.jgit.lib.ReflogEntry;
71  import org.eclipse.jgit.lib.Repository;
72  import org.eclipse.jgit.util.FS;
73  import org.eclipse.jgit.util.FileUtils;
74  
75  /**
76   * Utility for writing reflog entries
77   */
78  public class ReflogWriter {
79  
80  	/**
81  	 * Get the ref name to be used for when locking a ref's log for rewriting
82  	 *
83  	 * @param name
84  	 *            name of the ref, relative to the Git repository top level
85  	 *            directory (so typically starts with refs/).
86  	 * @return the name of the ref's lock ref
87  	 */
88  	public static String refLockFor(final String name) {
89  		return name + LockFile.SUFFIX;
90  	}
91  
92  	private final Repository parent;
93  
94  	private final File logsDir;
95  
96  	private final File logsRefsDir;
97  
98  	private final boolean forceWrite;
99  
100 	/**
101 	 * Create write for repository
102 	 *
103 	 * @param repository
104 	 */
105 	public ReflogWriter(final Repository repository) {
106 		this(repository, false);
107 	}
108 
109 	/**
110 	 * Create write for repository
111 	 *
112 	 * @param repository
113 	 * @param forceWrite
114 	 *            true to write to disk all entries logged, false to respect the
115 	 *            repository's config and current log file status
116 	 */
117 	public ReflogWriter(final Repository repository, final boolean forceWrite) {
118 		final FS fs = repository.getFS();
119 		parent = repository;
120 		File gitDir = repository.getDirectory();
121 		logsDir = fs.resolve(gitDir, LOGS);
122 		logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
123 		this.forceWrite = forceWrite;
124 	}
125 
126 	/**
127 	 * Get repository that reflog is being written for
128 	 *
129 	 * @return file repository
130 	 */
131 	public Repository getRepository() {
132 		return parent;
133 	}
134 
135 	/**
136 	 * Create the log directories
137 	 *
138 	 * @throws IOException
139 	 * @return this writer
140 	 */
141 	public ReflogWriter create() throws IOException {
142 		FileUtils.mkdir(logsDir);
143 		FileUtils.mkdir(logsRefsDir);
144 		FileUtils.mkdir(new File(logsRefsDir,
145 				R_HEADS.substring(R_REFS.length())));
146 		return this;
147 	}
148 
149 	/**
150 	 * Locate the log file on disk for a single reference name.
151 	 *
152 	 * @param name
153 	 *            name of the ref, relative to the Git repository top level
154 	 *            directory (so typically starts with refs/).
155 	 * @return the log file location.
156 	 */
157 	public File logFor(String name) {
158 		if (name.startsWith(R_REFS)) {
159 			name = name.substring(R_REFS.length());
160 			return new File(logsRefsDir, name);
161 		}
162 		return new File(logsDir, name);
163 	}
164 
165 	/**
166 	 * Write the given {@link ReflogEntry} entry to the ref's log
167 	 *
168 	 * @param refName
169 	 *
170 	 * @param entry
171 	 * @return this writer
172 	 * @throws IOException
173 	 */
174 	public ReflogWriter log(final String refName, final ReflogEntry entry)
175 			throws IOException {
176 		return log(refName, entry.getOldId(), entry.getNewId(), entry.getWho(),
177 				entry.getComment());
178 	}
179 
180 	/**
181 	 * Write the given entry information to the ref's log
182 	 *
183 	 * @param refName
184 	 * @param oldId
185 	 * @param newId
186 	 * @param ident
187 	 * @param message
188 	 * @return this writer
189 	 * @throws IOException
190 	 */
191 	public ReflogWriter log(final String refName, final ObjectId oldId,
192 			final ObjectId newId, final PersonIdent ident, final String message)
193 			throws IOException {
194 		byte[] encoded = encode(oldId, newId, ident, message);
195 		return log(refName, encoded);
196 	}
197 
198 	/**
199 	 * Write the given ref update to the ref's log
200 	 *
201 	 * @param update
202 	 * @param msg
203 	 * @param deref
204 	 * @return this writer
205 	 * @throws IOException
206 	 */
207 	public ReflogWriter log(final RefUpdate update, final String msg,
208 			final boolean deref) throws IOException {
209 		final ObjectId oldId = update.getOldObjectId();
210 		final ObjectId newId = update.getNewObjectId();
211 		final Ref ref = update.getRef();
212 
213 		PersonIdent ident = update.getRefLogIdent();
214 		if (ident == null)
215 			ident = new PersonIdent(parent);
216 		else
217 			ident = new PersonIdent(ident);
218 
219 		final byte[] rec = encode(oldId, newId, ident, msg);
220 		if (deref && ref.isSymbolic()) {
221 			log(ref.getName(), rec);
222 			log(ref.getLeaf().getName(), rec);
223 		} else
224 			log(ref.getName(), rec);
225 
226 		return this;
227 	}
228 
229 	private byte[] encode(ObjectId oldId, ObjectId newId, PersonIdent ident,
230 			String message) {
231 		final StringBuilder r = new StringBuilder();
232 		r.append(ObjectId.toString(oldId));
233 		r.append(' ');
234 		r.append(ObjectId.toString(newId));
235 		r.append(' ');
236 		r.append(ident.toExternalString());
237 		r.append('\t');
238 		r.append(message.replace("\r\n", " ").replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
239 		r.append('\n');
240 		return Constants.encode(r.toString());
241 	}
242 
243 	private ReflogWriter log(final String refName, final byte[] rec)
244 			throws IOException {
245 		final File log = logFor(refName);
246 		final boolean write = forceWrite
247 				|| (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
248 				|| log.isFile();
249 		if (!write)
250 			return this;
251 
252 		WriteConfig wc = getRepository().getConfig().get(WriteConfig.KEY);
253 		FileOutputStream out;
254 		try {
255 			out = new FileOutputStream(log, true);
256 		} catch (FileNotFoundException err) {
257 			final File dir = log.getParentFile();
258 			if (dir.exists())
259 				throw err;
260 			if (!dir.mkdirs() && !dir.isDirectory())
261 				throw new IOException(MessageFormat.format(
262 						JGitText.get().cannotCreateDirectory, dir));
263 			out = new FileOutputStream(log, true);
264 		}
265 		try {
266 			if (wc.getFSyncRefFiles()) {
267 				FileChannel fc = out.getChannel();
268 				ByteBuffer buf = ByteBuffer.wrap(rec);
269 				while (0 < buf.remaining())
270 					fc.write(buf);
271 				fc.force(true);
272 			} else
273 				out.write(rec);
274 		} finally {
275 			out.close();
276 		}
277 		return this;
278 	}
279 
280 	private boolean isLogAllRefUpdates() {
281 		return parent.getConfig().get(CoreConfig.KEY).isLogAllRefUpdates();
282 	}
283 
284 	private boolean shouldAutoCreateLog(final String refName) {
285 		return refName.equals(HEAD) //
286 				|| refName.startsWith(R_HEADS) //
287 				|| refName.startsWith(R_REMOTES) //
288 				|| refName.equals(R_STASH);
289 	}
290 }