2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.jgit
.lib
;
19 import java
.io
.BufferedOutputStream
;
21 import java
.io
.FileInputStream
;
22 import java
.io
.FileNotFoundException
;
23 import java
.io
.FileOutputStream
;
24 import java
.io
.IOException
;
25 import java
.io
.OutputStream
;
26 import java
.nio
.channels
.FileLock
;
27 import java
.nio
.channels
.OverlappingFileLockException
;
30 * Git style file locking and replacement.
32 * To modify a ref file Git tries to use an atomic update approach: we write the
33 * new data into a brand new file, then rename it in place over the old name.
34 * This way we can just delete the temporary file if anything goes wrong, and
35 * nothing has been damaged. To coordinate access from multiple processes at
36 * once Git tries to atomically create the new temporary file under a well-known
39 public class LockFile
{
40 private final File ref
;
42 private final File lck
;
44 private FileLock fLck
;
46 private boolean haveLck
;
48 private FileOutputStream os
;
50 private boolean needStatInformation
;
52 private long commitLastModified
;
55 * Create a new lock for any file.
58 * the file that will be locked.
60 public LockFile(final File f
) {
62 lck
= new File(ref
.getParentFile(), ref
.getName() + ".lock");
66 * Try to establish the lock.
68 * @return true if the lock is now held by the caller; false if it is held
71 * the temporary output file could not be created. The caller
72 * does not hold the lock.
74 public boolean lock() throws IOException
{
75 lck
.getParentFile().mkdirs();
76 if (lck
.createNewFile()) {
79 os
= new FileOutputStream(lck
);
81 fLck
= os
.getChannel().tryLock();
83 throw new OverlappingFileLockException();
84 } catch (OverlappingFileLockException ofle
) {
85 // We cannot use unlock() here as this file is not
86 // held by us, but we thought we created it. We must
87 // not delete it, as it belongs to some other process.
92 } catch (IOException ioe
) {
93 // Fail by returning haveLck = false.
97 } catch (IOException ioe
) {
106 * Try to establish the lock for appending.
108 * @return true if the lock is now held by the caller; false if it is held
110 * @throws IOException
111 * the temporary output file could not be created. The caller
112 * does not hold the lock.
114 public boolean lockForAppend() throws IOException
{
117 copyCurrentContent();
122 * Copy the current file content into the temporary file.
124 * This method saves the current file content by inserting it into the
125 * temporary file, so that the caller can safely append rather than replace
128 * This method does nothing if the current file does not exist, or exists
131 * @throws IOException
132 * the temporary file could not be written, or a read error
133 * occurred while reading from the current file. The lock is
134 * released before throwing the underlying IO exception to the
136 * @throws RuntimeException
137 * the temporary file could not be written. The lock is released
138 * before throwing the underlying exception to the caller.
140 public void copyCurrentContent() throws IOException
{
143 final FileInputStream fis
= new FileInputStream(ref
);
145 final byte[] buf
= new byte[2048];
147 while ((r
= fis
.read(buf
)) >= 0)
152 } catch (FileNotFoundException fnfe
) {
153 // Don't worry about a file that doesn't exist yet, it
154 // conceptually has no current content to copy.
156 } catch (IOException ioe
) {
159 } catch (RuntimeException ioe
) {
162 } catch (Error ioe
) {
169 * Write an ObjectId and LF to the temporary file.
172 * the id to store in the file. The id will be written in hex,
173 * followed by a sole LF.
174 * @throws IOException
175 * the temporary file could not be written. The lock is released
176 * before throwing the underlying IO exception to the caller.
177 * @throws RuntimeException
178 * the temporary file could not be written. The lock is released
179 * before throwing the underlying exception to the caller.
181 public void write(final ObjectId id
) throws IOException
{
184 final BufferedOutputStream b
;
185 b
= new BufferedOutputStream(os
, Constants
.OBJECT_ID_LENGTH
* 2 + 1);
192 } catch (IOException ioe
) {
195 } catch (RuntimeException ioe
) {
198 } catch (Error ioe
) {
205 * Write arbitrary data to the temporary file.
208 * the bytes to store in the temporary file. No additional bytes
209 * are added, so if the file must end with an LF it must appear
210 * at the end of the byte array.
211 * @throws IOException
212 * the temporary file could not be written. The lock is released
213 * before throwing the underlying IO exception to the caller.
214 * @throws RuntimeException
215 * the temporary file could not be written. The lock is released
216 * before throwing the underlying exception to the caller.
218 public void write(final byte[] content
) throws IOException
{
226 } catch (IOException ioe
) {
229 } catch (RuntimeException ioe
) {
232 } catch (Error ioe
) {
239 * Obtain the direct output stream for this lock.
241 * The stream may only be accessed once, and only after {@link #lock()} has
242 * been successfully invoked and returned true. Callers must close the
243 * stream prior to calling {@link #commit()} to commit the change.
245 * @return a stream to write to the new file. The stream is unbuffered.
247 public OutputStream
getOutputStream() {
249 return new OutputStream() {
251 public void write(final byte[] b
, final int o
, final int n
)
257 public void write(final byte[] b
) throws IOException
{
262 public void write(final int b
) throws IOException
{
267 public void flush() throws IOException
{
272 public void close() throws IOException
{
278 } catch (IOException ioe
) {
281 } catch (RuntimeException ioe
) {
284 } catch (Error ioe
) {
292 private void requireLock() {
295 throw new IllegalStateException("Lock on " + ref
+ " not held.");
300 * Request that {@link #commit()} remember modification time.
303 * true if the commit method must remember the modification time.
305 public void setNeedStatInformation(final boolean on
) {
306 needStatInformation
= on
;
310 * Commit this change and release the lock.
312 * If this method fails (returns false) the lock is still released.
314 * @return true if the commit was successful and the file contains the new
315 * data; false if the commit failed and the file remains with the
317 * @throws IllegalStateException
318 * the lock is not held.
320 public boolean commit() {
323 throw new IllegalStateException("Lock on " + ref
+ " not closed.");
326 saveStatInformation();
327 if (lck
.renameTo(ref
))
329 if (!ref
.exists() || ref
.delete())
330 if (lck
.renameTo(ref
))
336 private void saveStatInformation() {
337 if (needStatInformation
)
338 commitLastModified
= lck
.lastModified();
342 * Get the modification time of the output file when it was committed.
344 * @return modification time of the lock file right before we committed it.
346 public long getCommitLastModified() {
347 return commitLastModified
;
351 * Unlock this file and abort this change.
353 * The temporary file (if created) is deleted before returning.
355 public void unlock() {
360 } catch (IOException ioe
) {
367 } catch (IOException ioe
) {