2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.lib
;
41 import java
.io
.IOException
;
43 import org
.spearce
.jgit
.errors
.MissingObjectException
;
44 import org
.spearce
.jgit
.lib
.Ref
.Storage
;
45 import org
.spearce
.jgit
.revwalk
.RevCommit
;
46 import org
.spearce
.jgit
.revwalk
.RevObject
;
47 import org
.spearce
.jgit
.revwalk
.RevWalk
;
50 * Updates any locally stored ref.
52 public class RefUpdate
{
53 /** Status of an update request. */
54 public static enum Result
{
55 /** The ref update has not been attempted by the caller. */
59 * The ref could not be locked for update.
61 * This is generally a transient failure and is usually caused by
62 * another process trying to access the ref at the same time as this
63 * process was trying to update it. It is possible a future operation
69 * Same value already stored.
71 * Both the old value and the new value are identical. No change was
77 * The ref was created locally.
79 * The ref did not exist when the update started, but it was created
80 * successfully with the new value.
85 * The ref had to be forcefully updated.
87 * The ref already existed but its old value was not fully merged into
88 * the new value. The configuration permitted a forced update to take
89 * place, so ref now contains the new value. History associated with the
90 * objects not merged may no longer be reachable.
95 * The ref was updated in a fast-forward way.
97 * The tracking ref already existed and its old value was fully merged
98 * into the new value. No history was made unreachable.
103 * Not a fast-forward and not stored.
105 * The tracking ref already existed but its old value was not fully
106 * merged into the new value. The configuration did not allow a forced
107 * update to take place, so ref still contains the old value. No
108 * previous history was lost.
113 * The ref was probably not updated because of I/O error.
115 * Unexpected I/O error occurred when writing new ref. Such error may
116 * result in uncertain state, but most probably ref was not updated.
118 * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a
124 /** Repository the ref is stored in. */
125 private final RefDatabase db
;
127 /** Name of the ref. */
128 private final String name
;
130 /** Location of the loose file holding the value of this ref. */
131 private final File looseFile
;
133 /** New value the caller wants this ref to have. */
134 private ObjectId newValue
;
136 /** Does this specification ask for forced updated (rewind/reset)? */
137 private boolean force
;
139 /** Message the caller wants included in the reflog. */
140 private String refLogMessage
;
142 /** Should the Result value be appended to {@link #refLogMessage}. */
143 private boolean refLogIncludeResult
;
145 /** Old value of the ref, obtained after we lock it. */
146 private ObjectId oldValue
;
148 /** Result of the update operation. */
149 private Result result
= Result
.NOT_ATTEMPTED
;
151 private final Ref ref
;
153 RefUpdate(final RefDatabase r
, final Ref ref
, final File f
) {
156 name
= ref
.getName();
157 oldValue
= ref
.getObjectId();
162 * Get the name of the ref this update will operate on.
164 * @return name of this ref.
166 public String
getName() {
171 * Get the new value the ref will be (or was) updated to.
173 * @return new value. Null if the caller has not configured it.
175 public ObjectId
getNewObjectId() {
180 * Set the new value the ref will update to.
185 public void setNewObjectId(final AnyObjectId id
) {
186 newValue
= id
.toObjectId();
190 * Check if this update wants to forcefully change the ref.
192 * @return true if this update should ignore merge tests.
194 public boolean isForceUpdate() {
199 * Set if this update wants to forcefully change the ref.
202 * true if this update should ignore merge tests.
204 public void setForceUpdate(final boolean b
) {
209 * Get the message to include in the reflog.
211 * @return message the caller wants to include in the reflog.
213 public String
getRefLogMessage() {
214 return refLogMessage
;
218 * Set the message to include in the reflog.
221 * the message to describe this change.
222 * @param appendStatus
223 * true if the status of the ref change (fast-forward or
224 * forced-update) should be appended to the user supplied
227 public void setRefLogMessage(final String msg
, final boolean appendStatus
) {
229 refLogIncludeResult
= appendStatus
;
233 * The old value of the ref, prior to the update being attempted.
235 * This value may differ before and after the update method. Initially it is
236 * populated with the value of the ref before the lock is taken, but the old
237 * value may change if someone else modified the ref between the time we
238 * last read it and when the ref was locked for update.
240 * @return the value of the ref prior to the update being attempted; null if
241 * the updated has not been attempted yet.
243 public ObjectId
getOldObjectId() {
248 * Get the status of this update.
250 * The same value that was previously returned from an update method.
252 * @return the status of the update.
254 public Result
getResult() {
258 private void requireCanDoUpdate() {
259 if (newValue
== null)
260 throw new IllegalStateException("A NewObjectId is required.");
264 * Force the ref to take the new value.
266 * This is just a convenient helper for setting the force flag, and as such
267 * the merge test is performed.
269 * @return the result status of the update.
270 * @throws IOException
271 * an unexpected IO error occurred while writing changes.
273 public Result
forceUpdate() throws IOException
{
279 * Gracefully update the ref to the new value.
281 * Merge test will be performed according to {@link #isForceUpdate()}.
283 * This is the same as:
286 * return update(new RevWalk(repository));
289 * @return the result status of the update.
290 * @throws IOException
291 * an unexpected IO error occurred while writing changes.
293 public Result
update() throws IOException
{
294 return update(new RevWalk(db
.getRepository()));
298 * Gracefully update the ref to the new value.
300 * Merge test will be performed according to {@link #isForceUpdate()}.
303 * a RevWalk instance this update command can borrow to perform
304 * the merge test. The walk will be reset to perform the test.
305 * @return the result status of the update.
306 * @throws IOException
307 * an unexpected IO error occurred while writing changes.
309 public Result
update(final RevWalk walk
) throws IOException
{
310 requireCanDoUpdate();
312 return result
= updateImpl(walk
, new UpdateStore());
313 } catch (IOException x
) {
314 result
= Result
.IO_FAILURE
;
322 * @return the result status of the delete.
323 * @throws IOException
325 public Result
delete() throws IOException
{
327 return updateImpl(new RevWalk(db
.getRepository()),
329 } catch (IOException x
) {
330 result
= Result
.IO_FAILURE
;
335 private Result
updateImpl(final RevWalk walk
, final Store store
)
341 lock
= new LockFile(looseFile
);
343 return Result
.LOCK_FAILURE
;
345 oldValue
= db
.idOf(name
);
346 if (oldValue
== null)
347 return store
.store(lock
, Result
.NEW
);
349 newObj
= safeParse(walk
, newValue
);
350 oldObj
= safeParse(walk
, oldValue
);
351 if (newObj
== oldObj
)
352 return Result
.NO_CHANGE
;
354 if (newObj
instanceof RevCommit
&& oldObj
instanceof RevCommit
) {
355 if (walk
.isMergedInto((RevCommit
) oldObj
, (RevCommit
) newObj
))
356 return store
.store(lock
, Result
.FAST_FORWARD
);
360 return store
.store(lock
, Result
.FORCED
);
361 return Result
.REJECTED
;
367 private static RevObject
safeParse(final RevWalk rw
, final AnyObjectId id
)
370 return rw
.parseAny(id
);
371 } catch (MissingObjectException e
) {
372 // We can expect some objects to be missing, like if we are
373 // trying to force a deletion of a branch and the object it
374 // points to has been pruned from the database due to freak
375 // corruption accidents (it happens with 'git new-work-dir').
381 private Result
updateStore(final LockFile lock
, final Result status
)
383 lock
.setNeedStatInformation(true);
384 lock
.write(newValue
);
385 String msg
= getRefLogMessage();
386 if (msg
!= null && refLogIncludeResult
) {
387 if (status
== Result
.FORCED
)
388 msg
+= ": forced-update";
389 else if (status
== Result
.FAST_FORWARD
)
390 msg
+= ": fast forward";
391 else if (status
== Result
.NEW
)
394 RefLogWriter
.writeReflog(db
.getRepository(), oldValue
, newValue
, msg
,
397 return Result
.LOCK_FAILURE
;
398 db
.stored(name
, newValue
, lock
.getCommitLastModified());
403 * Handle the abstraction of storing a ref update. This is because both
404 * updating and deleting of a ref have merge testing in common.
406 private abstract class Store
{
407 abstract Result
store(final LockFile lock
, final Result status
)
411 private class UpdateStore
extends Store
{
414 Result
store(final LockFile lock
, final Result status
)
416 return updateStore(lock
, status
);
420 private class DeleteStore
extends Store
{
423 Result
store(LockFile lock
, Result status
) throws IOException
{
424 Storage storage
= ref
.getStorage();
425 if (storage
== Storage
.NEW
)
427 if (storage
.isPacked())
428 db
.removePackedRef(ref
.getName());
429 if (storage
.isLoose())
430 if (!looseFile
.delete())
431 throw new IOException("File cannot be deleted: "
433 new File(db
.getRepository().getDirectory(), Constants
.LOGS
+ "/"
434 + ref
.getName()).delete();