2 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org
.spearce
.jgit
.lib
;
41 import java
.io
.BufferedOutputStream
;
43 import java
.io
.FileInputStream
;
44 import java
.io
.FileNotFoundException
;
45 import java
.io
.FileOutputStream
;
46 import java
.io
.IOException
;
47 import java
.io
.OutputStream
;
48 import java
.nio
.channels
.FileLock
;
49 import java
.nio
.channels
.OverlappingFileLockException
;
52 * Git style file locking and replacement.
54 * To modify a ref file Git tries to use an atomic update approach: we write the
55 * new data into a brand new file, then rename it in place over the old name.
56 * This way we can just delete the temporary file if anything goes wrong, and
57 * nothing has been damaged. To coordinate access from multiple processes at
58 * once Git tries to atomically create the new temporary file under a well-known
61 public class LockFile
{
62 private final File ref
;
64 private final File lck
;
66 private FileLock fLck
;
68 private boolean haveLck
;
70 private FileOutputStream os
;
72 private boolean needStatInformation
;
74 private long commitLastModified
;
77 * Create a new lock for any file.
80 * the file that will be locked.
82 public LockFile(final File f
) {
84 lck
= new File(ref
.getParentFile(), ref
.getName() + ".lock");
88 * Try to establish the lock.
90 * @return true if the lock is now held by the caller; false if it is held
93 * the temporary output file could not be created. The caller
94 * does not hold the lock.
96 public boolean lock() throws IOException
{
97 lck
.getParentFile().mkdirs();
98 if (lck
.createNewFile()) {
101 os
= new FileOutputStream(lck
);
103 fLck
= os
.getChannel().tryLock();
105 throw new OverlappingFileLockException();
106 } catch (OverlappingFileLockException ofle
) {
107 // We cannot use unlock() here as this file is not
108 // held by us, but we thought we created it. We must
109 // not delete it, as it belongs to some other process.
114 } catch (IOException ioe
) {
115 // Fail by returning haveLck = false.
119 } catch (IOException ioe
) {
128 * Try to establish the lock for appending.
130 * @return true if the lock is now held by the caller; false if it is held
132 * @throws IOException
133 * the temporary output file could not be created. The caller
134 * does not hold the lock.
136 public boolean lockForAppend() throws IOException
{
139 copyCurrentContent();
144 * Copy the current file content into the temporary file.
146 * This method saves the current file content by inserting it into the
147 * temporary file, so that the caller can safely append rather than replace
150 * This method does nothing if the current file does not exist, or exists
153 * @throws IOException
154 * the temporary file could not be written, or a read error
155 * occurred while reading from the current file. The lock is
156 * released before throwing the underlying IO exception to the
158 * @throws RuntimeException
159 * the temporary file could not be written. The lock is released
160 * before throwing the underlying exception to the caller.
162 public void copyCurrentContent() throws IOException
{
165 final FileInputStream fis
= new FileInputStream(ref
);
167 final byte[] buf
= new byte[2048];
169 while ((r
= fis
.read(buf
)) >= 0)
174 } catch (FileNotFoundException fnfe
) {
175 // Don't worry about a file that doesn't exist yet, it
176 // conceptually has no current content to copy.
178 } catch (IOException ioe
) {
181 } catch (RuntimeException ioe
) {
184 } catch (Error ioe
) {
191 * Write an ObjectId and LF to the temporary file.
194 * the id to store in the file. The id will be written in hex,
195 * followed by a sole LF.
196 * @throws IOException
197 * the temporary file could not be written. The lock is released
198 * before throwing the underlying IO exception to the caller.
199 * @throws RuntimeException
200 * the temporary file could not be written. The lock is released
201 * before throwing the underlying exception to the caller.
203 public void write(final ObjectId id
) throws IOException
{
206 final BufferedOutputStream b
;
207 b
= new BufferedOutputStream(os
, Constants
.OBJECT_ID_LENGTH
* 2 + 1);
214 } catch (IOException ioe
) {
217 } catch (RuntimeException ioe
) {
220 } catch (Error ioe
) {
227 * Write arbitrary data to the temporary file.
230 * the bytes to store in the temporary file. No additional bytes
231 * are added, so if the file must end with an LF it must appear
232 * at the end of the byte array.
233 * @throws IOException
234 * the temporary file could not be written. The lock is released
235 * before throwing the underlying IO exception to the caller.
236 * @throws RuntimeException
237 * the temporary file could not be written. The lock is released
238 * before throwing the underlying exception to the caller.
240 public void write(final byte[] content
) throws IOException
{
248 } catch (IOException ioe
) {
251 } catch (RuntimeException ioe
) {
254 } catch (Error ioe
) {
261 * Obtain the direct output stream for this lock.
263 * The stream may only be accessed once, and only after {@link #lock()} has
264 * been successfully invoked and returned true. Callers must close the
265 * stream prior to calling {@link #commit()} to commit the change.
267 * @return a stream to write to the new file. The stream is unbuffered.
269 public OutputStream
getOutputStream() {
271 return new OutputStream() {
273 public void write(final byte[] b
, final int o
, final int n
)
279 public void write(final byte[] b
) throws IOException
{
284 public void write(final int b
) throws IOException
{
289 public void flush() throws IOException
{
294 public void close() throws IOException
{
300 } catch (IOException ioe
) {
303 } catch (RuntimeException ioe
) {
306 } catch (Error ioe
) {
314 private void requireLock() {
317 throw new IllegalStateException("Lock on " + ref
+ " not held.");
322 * Request that {@link #commit()} remember modification time.
325 * true if the commit method must remember the modification time.
327 public void setNeedStatInformation(final boolean on
) {
328 needStatInformation
= on
;
332 * Commit this change and release the lock.
334 * If this method fails (returns false) the lock is still released.
336 * @return true if the commit was successful and the file contains the new
337 * data; false if the commit failed and the file remains with the
339 * @throws IllegalStateException
340 * the lock is not held.
342 public boolean commit() {
345 throw new IllegalStateException("Lock on " + ref
+ " not closed.");
348 saveStatInformation();
349 if (lck
.renameTo(ref
))
351 if (!ref
.exists() || ref
.delete())
352 if (lck
.renameTo(ref
))
358 private void saveStatInformation() {
359 if (needStatInformation
)
360 commitLastModified
= lck
.lastModified();
364 * Get the modification time of the output file when it was committed.
366 * @return modification time of the lock file right before we committed it.
368 public long getCommitLastModified() {
369 return commitLastModified
;
373 * Unlock this file and abort this change.
375 * The temporary file (if created) is deleted before returning.
377 public void unlock() {
382 } catch (IOException ioe
) {
389 } catch (IOException ioe
) {