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/delete has not been attempted by the caller. */
59 * The ref could not be locked for update/delete.
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
72 * necessary for an update. For delete the branch is removed.
77 * The ref was created locally for an update, but ignored for delete.
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/deleted.
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/deleted 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/delete to take place, so ref still contains the old value. No
108 * previous history was lost.
113 * Rejected because trying to delete the current branch.
115 * Has no meaning for update.
117 REJECTED_CURRENT_BRANCH
,
120 * The ref was probably not updated/deleted because of I/O error.
122 * Unexpected I/O error occurred when writing new ref. Such error may
123 * result in uncertain state, but most probably ref was not updated.
125 * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a
131 /** Repository the ref is stored in. */
132 private final RefDatabase db
;
134 /** Name of the ref. */
135 private final String name
;
137 /** Location of the loose file holding the value of this ref. */
138 private final File looseFile
;
140 /** New value the caller wants this ref to have. */
141 private ObjectId newValue
;
143 /** Does this specification ask for forced updated (rewind/reset)? */
144 private boolean force
;
146 /** Message the caller wants included in the reflog. */
147 private String refLogMessage
;
149 /** Should the Result value be appended to {@link #refLogMessage}. */
150 private boolean refLogIncludeResult
;
152 /** Old value of the ref, obtained after we lock it. */
153 private ObjectId oldValue
;
155 /** Result of the update operation. */
156 private Result result
= Result
.NOT_ATTEMPTED
;
158 private final Ref ref
;
160 RefUpdate(final RefDatabase r
, final Ref ref
, final File f
) {
163 name
= ref
.getName();
164 oldValue
= ref
.getObjectId();
169 * Get the name of the ref this update will operate on.
171 * @return name of this ref.
173 public String
getName() {
178 * Get the new value the ref will be (or was) updated to.
180 * @return new value. Null if the caller has not configured it.
182 public ObjectId
getNewObjectId() {
187 * Set the new value the ref will update to.
192 public void setNewObjectId(final AnyObjectId id
) {
193 newValue
= id
.toObjectId();
197 * Check if this update wants to forcefully change the ref.
199 * @return true if this update should ignore merge tests.
201 public boolean isForceUpdate() {
206 * Set if this update wants to forcefully change the ref.
209 * true if this update should ignore merge tests.
211 public void setForceUpdate(final boolean b
) {
216 * Get the message to include in the reflog.
218 * @return message the caller wants to include in the reflog.
220 public String
getRefLogMessage() {
221 return refLogMessage
;
225 * Set the message to include in the reflog.
228 * the message to describe this change.
229 * @param appendStatus
230 * true if the status of the ref change (fast-forward or
231 * forced-update) should be appended to the user supplied
234 public void setRefLogMessage(final String msg
, final boolean appendStatus
) {
236 refLogIncludeResult
= appendStatus
;
240 * The old value of the ref, prior to the update being attempted.
242 * This value may differ before and after the update method. Initially it is
243 * populated with the value of the ref before the lock is taken, but the old
244 * value may change if someone else modified the ref between the time we
245 * last read it and when the ref was locked for update.
247 * @return the value of the ref prior to the update being attempted; null if
248 * the updated has not been attempted yet.
250 public ObjectId
getOldObjectId() {
255 * Get the status of this update.
257 * The same value that was previously returned from an update method.
259 * @return the status of the update.
261 public Result
getResult() {
265 private void requireCanDoUpdate() {
266 if (newValue
== null)
267 throw new IllegalStateException("A NewObjectId is required.");
271 * Force the ref to take the new value.
273 * This is just a convenient helper for setting the force flag, and as such
274 * the merge test is performed.
276 * @return the result status of the update.
277 * @throws IOException
278 * an unexpected IO error occurred while writing changes.
280 public Result
forceUpdate() throws IOException
{
286 * Gracefully update the ref to the new value.
288 * Merge test will be performed according to {@link #isForceUpdate()}.
290 * This is the same as:
293 * return update(new RevWalk(repository));
296 * @return the result status of the update.
297 * @throws IOException
298 * an unexpected IO error occurred while writing changes.
300 public Result
update() throws IOException
{
301 return update(new RevWalk(db
.getRepository()));
305 * Gracefully update the ref to the new value.
307 * Merge test will be performed according to {@link #isForceUpdate()}.
310 * a RevWalk instance this update command can borrow to perform
311 * the merge test. The walk will be reset to perform the test.
312 * @return the result status of the update.
313 * @throws IOException
314 * an unexpected IO error occurred while writing changes.
316 public Result
update(final RevWalk walk
) throws IOException
{
317 requireCanDoUpdate();
319 return result
= updateImpl(walk
, new UpdateStore());
320 } catch (IOException x
) {
321 result
= Result
.IO_FAILURE
;
329 * @return the result status of the delete.
330 * @throws IOException
332 public Result
delete() throws IOException
{
333 if (name
.startsWith(Constants
.R_HEADS
)) {
334 final Ref head
= db
.readRef(Constants
.HEAD
);
335 if (head
!= null && name
.equals(head
.getName()))
336 return Result
.REJECTED_CURRENT_BRANCH
;
340 return updateImpl(new RevWalk(db
.getRepository()),
342 } catch (IOException x
) {
343 result
= Result
.IO_FAILURE
;
348 private Result
updateImpl(final RevWalk walk
, final Store store
)
354 lock
= new LockFile(looseFile
);
356 return Result
.LOCK_FAILURE
;
358 oldValue
= db
.idOf(name
);
359 if (oldValue
== null)
360 return store
.store(lock
, Result
.NEW
);
362 newObj
= safeParse(walk
, newValue
);
363 oldObj
= safeParse(walk
, oldValue
);
364 if (newObj
== oldObj
)
365 return store
.store(lock
, Result
.NO_CHANGE
);
367 if (newObj
instanceof RevCommit
&& oldObj
instanceof RevCommit
) {
368 if (walk
.isMergedInto((RevCommit
) oldObj
, (RevCommit
) newObj
))
369 return store
.store(lock
, Result
.FAST_FORWARD
);
373 return store
.store(lock
, Result
.FORCED
);
374 return Result
.REJECTED
;
380 private static RevObject
safeParse(final RevWalk rw
, final AnyObjectId id
)
383 return rw
.parseAny(id
);
384 } catch (MissingObjectException e
) {
385 // We can expect some objects to be missing, like if we are
386 // trying to force a deletion of a branch and the object it
387 // points to has been pruned from the database due to freak
388 // corruption accidents (it happens with 'git new-work-dir').
394 private Result
updateStore(final LockFile lock
, final Result status
)
396 if (status
== Result
.NO_CHANGE
)
398 lock
.setNeedStatInformation(true);
399 lock
.write(newValue
);
400 String msg
= getRefLogMessage();
401 if (msg
!= null && refLogIncludeResult
) {
402 if (status
== Result
.FORCED
)
403 msg
+= ": forced-update";
404 else if (status
== Result
.FAST_FORWARD
)
405 msg
+= ": fast forward";
406 else if (status
== Result
.NEW
)
409 RefLogWriter
.writeReflog(db
.getRepository(), oldValue
, newValue
, msg
,
412 return Result
.LOCK_FAILURE
;
413 db
.stored(name
, newValue
, lock
.getCommitLastModified());
418 * Handle the abstraction of storing a ref update. This is because both
419 * updating and deleting of a ref have merge testing in common.
421 private abstract class Store
{
422 abstract Result
store(final LockFile lock
, final Result status
)
426 private class UpdateStore
extends Store
{
429 Result
store(final LockFile lock
, final Result status
)
431 return updateStore(lock
, status
);
435 private class DeleteStore
extends Store
{
438 Result
store(LockFile lock
, Result status
) throws IOException
{
439 Storage storage
= ref
.getStorage();
440 if (storage
== Storage
.NEW
)
442 if (storage
.isPacked())
443 db
.removePackedRef(ref
.getName());
444 if (storage
.isLoose())
445 if (!looseFile
.delete())
446 throw new IOException("File cannot be deleted: "
448 new File(db
.getRepository().getDirectory(), Constants
.LOGS
+ "/"
449 + ref
.getName()).delete();