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.LOCK_SUFFIX;
50  import static org.eclipse.jgit.lib.Constants.R_HEADS;
51  import static org.eclipse.jgit.lib.Constants.R_NOTES;
52  import static org.eclipse.jgit.lib.Constants.R_REFS;
53  import static org.eclipse.jgit.lib.Constants.R_REMOTES;
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.util.FileUtils;
72  
73  /**
74   * Utility for writing reflog entries using the traditional one-file-per-log
75   * format.
76   */
77  public class ReflogWriter {
78  
79  	/**
80  	 * Get the ref name to be used for when locking a ref's log for rewriting.
81  	 *
82  	 * @param name
83  	 *            name of the ref, relative to the Git repository top level
84  	 *            directory (so typically starts with refs/).
85  	 * @return the name of the ref's lock ref.
86  	 */
87  	public static String refLockFor(String name) {
88  		return name + LOCK_SUFFIX;
89  	}
90  
91  	private final RefDirectory refdb;
92  
93  	private final boolean forceWrite;
94  
95  	/**
96  	 * Create writer for ref directory.
97  	 *
98  	 * @param refdb
99  	 *            a {@link org.eclipse.jgit.internal.storage.file.RefDirectory}
100 	 *            object.
101 	 */
102 	public ReflogWriter(RefDirectory refdb) {
103 		this(refdb, false);
104 	}
105 
106 	/**
107 	 * Create writer for ref directory.
108 	 *
109 	 * @param refdb
110 	 *            a {@link org.eclipse.jgit.internal.storage.file.RefDirectory}
111 	 *            object.
112 	 * @param forceWrite
113 	 *            true to write to disk all entries logged, false to respect the
114 	 *            repository's config and current log file status.
115 	 */
116 	public ReflogWriter(RefDirectory refdb, boolean forceWrite) {
117 		this.refdb = refdb;
118 		this.forceWrite = forceWrite;
119 	}
120 
121 	/**
122 	 * Create the log directories.
123 	 *
124 	 * @throws java.io.IOException
125 	 * @return this writer.
126 	 */
127 	public ReflogWriter create() throws IOException {
128 		FileUtils.mkdir(refdb.logsDir);
129 		FileUtils.mkdir(refdb.logsRefsDir);
130 		FileUtils.mkdir(
131 				new File(refdb.logsRefsDir, R_HEADS.substring(R_REFS.length())));
132 		return this;
133 	}
134 
135 	/**
136 	 * Write the given entry to the ref's log.
137 	 *
138 	 * @param refName
139 	 *            a {@link java.lang.String} object.
140 	 * @param entry
141 	 *            a {@link org.eclipse.jgit.lib.ReflogEntry} object.
142 	 * @return this writer
143 	 * @throws java.io.IOException
144 	 */
145 	public ReflogWriter log(String refName, ReflogEntry entry)
146 			throws IOException {
147 		return log(refName, entry.getOldId(), entry.getNewId(), entry.getWho(),
148 				entry.getComment());
149 	}
150 
151 	/**
152 	 * Write the given entry information to the ref's log
153 	 *
154 	 * @param refName
155 	 *            ref name
156 	 * @param oldId
157 	 *            old object id
158 	 * @param newId
159 	 *            new object id
160 	 * @param ident
161 	 *            a {@link org.eclipse.jgit.lib.PersonIdent}
162 	 * @param message
163 	 *            reflog message
164 	 * @return this writer
165 	 * @throws java.io.IOException
166 	 */
167 	public ReflogWriter log(String refName, ObjectId oldId,
168 			ObjectId newId, PersonIdent ident, String message) throws IOException {
169 		byte[] encoded = encode(oldId, newId, ident, message);
170 		return log(refName, encoded);
171 	}
172 
173 	/**
174 	 * Write the given ref update to the ref's log.
175 	 *
176 	 * @param update
177 	 *            a {@link org.eclipse.jgit.lib.RefUpdate}
178 	 * @param msg
179 	 *            reflog message
180 	 * @param deref
181 	 *            whether to dereference symbolic refs
182 	 * @return this writer
183 	 * @throws java.io.IOException
184 	 */
185 	public ReflogWriter log(RefUpdate update, String msg,
186 			boolean deref) throws IOException {
187 		ObjectId oldId = update.getOldObjectId();
188 		ObjectId newId = update.getNewObjectId();
189 		Ref ref = update.getRef();
190 
191 		PersonIdent ident = update.getRefLogIdent();
192 		if (ident == null)
193 			ident = new PersonIdent(refdb.getRepository());
194 		else
195 			ident = new PersonIdent(ident);
196 
197 		byte[] rec = encode(oldId, newId, ident, msg);
198 		if (deref && ref.isSymbolic()) {
199 			log(ref.getName(), rec);
200 			log(ref.getLeaf().getName(), rec);
201 		} else
202 			log(ref.getName(), rec);
203 
204 		return this;
205 	}
206 
207 	private byte[] encode(ObjectId"../../../../../../org/eclipse/jgit/lib/ObjectId.html#ObjectId">ObjectId oldId, ObjectId newId, PersonIdent ident,
208 			String message) {
209 		StringBuilder r = new StringBuilder();
210 		r.append(ObjectId.toString(oldId));
211 		r.append(' ');
212 		r.append(ObjectId.toString(newId));
213 		r.append(' ');
214 		r.append(ident.toExternalString());
215 		r.append('\t');
216 		r.append(
217 				message.replace("\r\n", " ") //$NON-NLS-1$ //$NON-NLS-2$
218 						.replace("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$
219 		r.append('\n');
220 		return Constants.encode(r.toString());
221 	}
222 
223 	private FileOutputStream getFileOutputStream(File log) throws IOException {
224 		try {
225 			return new FileOutputStream(log, true);
226 		} catch (FileNotFoundException err) {
227 			File dir = log.getParentFile();
228 			if (dir.exists()) {
229 				throw err;
230 			}
231 			if (!dir.mkdirs() && !dir.isDirectory()) {
232 				throw new IOException(MessageFormat
233 						.format(JGitText.get().cannotCreateDirectory, dir));
234 			}
235 			return new FileOutputStream(log, true);
236 		}
237 	}
238 
239 	private ReflogWriter log(String refName, byte[] rec) throws IOException {
240 		File log = refdb.logFor(refName);
241 		boolean write = forceWrite
242 				|| (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
243 				|| log.isFile();
244 		if (!write)
245 			return this;
246 
247 		WriteConfig wc = refdb.getRepository().getConfig().get(WriteConfig.KEY);
248 		try (FileOutputStream out = getFileOutputStream(log)) {
249 			if (wc.getFSyncRefFiles()) {
250 				FileChannel fc = out.getChannel();
251 				ByteBuffer buf = ByteBuffer.wrap(rec);
252 				while (0 < buf.remaining()) {
253 					fc.write(buf);
254 				}
255 				fc.force(true);
256 			} else {
257 				out.write(rec);
258 			}
259 		}
260 		return this;
261 	}
262 
263 	private boolean isLogAllRefUpdates() {
264 		return refdb.getRepository().getConfig().get(CoreConfig.KEY)
265 				.isLogAllRefUpdates();
266 	}
267 
268 	private boolean shouldAutoCreateLog(String refName) {
269 		return refName.equals(HEAD)
270 				|| refName.startsWith(R_HEADS)
271 				|| refName.startsWith(R_REMOTES)
272 				|| refName.startsWith(R_NOTES);
273 	}
274 }