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
.FileOutputStream
;
22 import java
.io
.IOException
;
23 import java
.io
.OutputStream
;
24 import java
.nio
.channels
.FileLock
;
25 import java
.nio
.channels
.OverlappingFileLockException
;
28 * Git style file locking and replacement.
30 * To modify a ref file Git tries to use an atomic update approach: we write the
31 * new data into a brand new file, then rename it in place over the old name.
32 * This way we can just delete the temporary file if anything goes wrong, and
33 * nothing has been damaged. To coordinate access from multiple processes at
34 * once Git tries to atomically create the new temporary file under a well-known
37 public class LockFile
{
38 private final File ref
;
40 private final File lck
;
42 private FileLock fLck
;
44 private boolean haveLck
;
46 private FileOutputStream os
;
48 private boolean needStatInformation
;
50 private long commitLastModified
;
53 * Create a new lock for any file.
56 * the file that will be locked.
58 public LockFile(final File f
) {
60 lck
= new File(ref
.getParentFile(), ref
.getName() + ".lock");
64 * Try to establish the lock.
66 * @return true if the lock is now held by the caller; false if it is held
69 * the temporary output file could not be created. The caller
70 * does not hold the lock.
72 public boolean lock() throws IOException
{
73 lck
.getParentFile().mkdirs();
74 if (lck
.createNewFile()) {
77 os
= new FileOutputStream(lck
);
79 fLck
= os
.getChannel().tryLock();
81 throw new OverlappingFileLockException();
82 } catch (OverlappingFileLockException ofle
) {
83 // We cannot use unlock() here as this file is not
84 // held by us, but we thought we created it. We must
85 // not delete it, as it belongs to some other process.
90 } catch (IOException ioe
) {
91 // Fail by returning haveLck = false.
95 } catch (IOException ioe
) {
104 * Write an ObjectId and LF to the temporary file.
107 * the id to store in the file. The id will be written in hex,
108 * followed by a sole LF.
109 * @throws IOException
110 * the temporary file could not be written. The lock is released
111 * before throwing the underlying IO exception to the caller.
112 * @throws RuntimeException
113 * the temporary file could not be written. The lock is released
114 * before throwing the underlying exception to the caller.
116 public void write(final ObjectId id
) throws IOException
{
119 final BufferedOutputStream b
;
120 b
= new BufferedOutputStream(os
, Constants
.OBJECT_ID_LENGTH
* 2 + 1);
127 } catch (IOException ioe
) {
130 } catch (RuntimeException ioe
) {
133 } catch (Error ioe
) {
140 * Write arbitrary data to the temporary file.
143 * the bytes to store in the temporary file. No additional bytes
144 * are added, so if the file must end with an LF it must appear
145 * at the end of the byte array.
146 * @throws IOException
147 * the temporary file could not be written. The lock is released
148 * before throwing the underlying IO exception to the caller.
149 * @throws RuntimeException
150 * the temporary file could not be written. The lock is released
151 * before throwing the underlying exception to the caller.
153 public void write(final byte[] content
) throws IOException
{
161 } catch (IOException ioe
) {
164 } catch (RuntimeException ioe
) {
167 } catch (Error ioe
) {
174 * Obtain the direct output stream for this lock.
176 * The stream may only be accessed once, and only after {@link #lock()} has
177 * been successfully invoked and returned true. Callers must close the
178 * stream prior to calling {@link #commit()} to commit the change.
180 * @return a stream to write to the new file. The stream is unbuffered.
182 public OutputStream
getOutputStream() {
184 return new OutputStream() {
186 public void write(final byte[] b
, final int o
, final int n
)
192 public void write(final byte[] b
) throws IOException
{
197 public void write(final int b
) throws IOException
{
202 public void flush() throws IOException
{
207 public void close() throws IOException
{
213 } catch (IOException ioe
) {
216 } catch (RuntimeException ioe
) {
219 } catch (Error ioe
) {
227 private void requireLock() {
230 throw new IllegalStateException("Lock on " + ref
+ " not held.");
235 * Request that {@link #commit()} remember modification time.
238 * true if the commit method must remember the modification time.
240 public void setNeedStatInformation(final boolean on
) {
241 needStatInformation
= on
;
245 * Commit this change and release the lock.
247 * If this method fails (returns false) the lock is still released.
249 * @return true if the commit was successful and the file contains the new
250 * data; false if the commit failed and the file remains with the
252 * @throws IllegalStateException
253 * the lock is not held.
255 public boolean commit() {
258 throw new IllegalStateException("Lock on " + ref
+ " not closed.");
261 saveStatInformation();
262 if (lck
.renameTo(ref
))
264 if (!ref
.exists() || ref
.delete())
265 if (lck
.renameTo(ref
))
271 private void saveStatInformation() {
272 if (needStatInformation
)
273 commitLastModified
= lck
.lastModified();
277 * Get the modification time of the output file when it was committed.
279 * @return modification time of the lock file right before we committed it.
281 public long getCommitLastModified() {
282 return commitLastModified
;
286 * Unlock this file and abort this change.
288 * The temporary file (if created) is deleted before returning.
290 public void unlock() {
295 } catch (IOException ioe
) {
302 } catch (IOException ioe
) {