Silently permit invalid ObjectIds during RefUpdate
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / RefUpdate.java
blobca77b757628ff1609a3a25d80b86c19fb4f8c462
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
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
21 * written permission.
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;
40 import java.io.File;
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;
49 /**
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. */
56 NOT_ATTEMPTED,
58 /**
59 * The ref could not be locked for update.
60 * <p>
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
64 * will be successful.
66 LOCK_FAILURE,
68 /**
69 * Same value already stored.
70 * <p>
71 * Both the old value and the new value are identical. No change was
72 * necessary.
74 NO_CHANGE,
76 /**
77 * The ref was created locally.
78 * <p>
79 * The ref did not exist when the update started, but it was created
80 * successfully with the new value.
82 NEW,
84 /**
85 * The ref had to be forcefully updated.
86 * <p>
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.
92 FORCED,
94 /**
95 * The ref was updated in a fast-forward way.
96 * <p>
97 * The tracking ref already existed and its old value was fully merged
98 * into the new value. No history was made unreachable.
100 FAST_FORWARD,
103 * Not a fast-forward and not stored.
104 * <p>
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.
110 REJECTED,
113 * The ref was probably not updated because of I/O error.
114 * <p>
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.
117 * <p>
118 * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a
119 * different case.
121 IO_FAILURE
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) {
154 db = r;
155 this.ref = ref;
156 name = ref.getName();
157 oldValue = ref.getObjectId();
158 looseFile = f;
162 * Get the name of the ref this update will operate on.
164 * @return name of this ref.
166 public String getName() {
167 return name;
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() {
176 return newValue;
180 * Set the new value the ref will update to.
182 * @param id
183 * the new value.
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() {
195 return force;
199 * Set if this update wants to forcefully change the ref.
201 * @param b
202 * true if this update should ignore merge tests.
204 public void setForceUpdate(final boolean b) {
205 force = 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.
220 * @param msg
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
225 * message.
227 public void setRefLogMessage(final String msg, final boolean appendStatus) {
228 refLogMessage = msg;
229 refLogIncludeResult = appendStatus;
233 * The old value of the ref, prior to the update being attempted.
234 * <p>
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() {
244 return oldValue;
248 * Get the status of this update.
249 * <p>
250 * The same value that was previously returned from an update method.
252 * @return the status of the update.
254 public Result getResult() {
255 return result;
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.
265 * <p>
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 {
274 force = true;
275 return update();
279 * Gracefully update the ref to the new value.
280 * <p>
281 * Merge test will be performed according to {@link #isForceUpdate()}.
282 * <p>
283 * This is the same as:
285 * <pre>
286 * return update(new RevWalk(repository));
287 * </pre>
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.
299 * <p>
300 * Merge test will be performed according to {@link #isForceUpdate()}.
302 * @param walk
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();
311 try {
312 return result = updateImpl(walk, new UpdateStore());
313 } catch (IOException x) {
314 result = Result.IO_FAILURE;
315 throw x;
320 * Delete the ref.
322 * @return the result status of the delete.
323 * @throws IOException
325 public Result delete() throws IOException {
326 try {
327 return updateImpl(new RevWalk(db.getRepository()),
328 new DeleteStore());
329 } catch (IOException x) {
330 result = Result.IO_FAILURE;
331 throw x;
335 private Result updateImpl(final RevWalk walk, final Store store)
336 throws IOException {
337 final LockFile lock;
338 RevObject newObj;
339 RevObject oldObj;
341 lock = new LockFile(looseFile);
342 if (!lock.lock())
343 return Result.LOCK_FAILURE;
344 try {
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);
359 if (isForceUpdate())
360 return store.store(lock, Result.FORCED);
361 return Result.REJECTED;
362 } finally {
363 lock.unlock();
367 private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
368 throws IOException {
369 try {
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').
377 return null;
381 private Result updateStore(final LockFile lock, final Result status)
382 throws IOException {
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)
392 msg += ": created";
394 RefLogWriter.writeReflog(db.getRepository(), oldValue, newValue, msg,
395 getName());
396 if (!lock.commit())
397 return Result.LOCK_FAILURE;
398 db.stored(name, newValue, lock.getCommitLastModified());
399 return status;
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)
408 throws IOException;
411 private class UpdateStore extends Store {
413 @Override
414 Result store(final LockFile lock, final Result status)
415 throws IOException {
416 return updateStore(lock, status);
420 private class DeleteStore extends Store {
422 @Override
423 Result store(LockFile lock, Result status) throws IOException {
424 Storage storage = ref.getStorage();
425 if (storage == Storage.NEW)
426 return status;
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: "
432 + looseFile);
433 new File(db.getRepository().getDirectory(), Constants.LOGS + "/"
434 + ref.getName()).delete();
435 return status;