Define a more powerful RefUpdate utlity
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / RefUpdate.java
blob4e37a00155e7cf2fddb327df1d010cc0a409c4d3
1 /*
2 * Copyright (C) 2008 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.File;
20 import java.io.IOException;
22 import org.spearce.jgit.revwalk.RevCommit;
23 import org.spearce.jgit.revwalk.RevObject;
24 import org.spearce.jgit.revwalk.RevWalk;
26 /**
27 * Updates any locally stored ref.
29 public class RefUpdate {
30 /** Status of an update request. */
31 public static enum Result {
32 /** The ref update has not been attempted by the caller. */
33 NOT_ATTEMPTED,
35 /**
36 * The ref could not be locked for update.
37 * <p>
38 * This is generally a transient failure and is usually caused by
39 * another process trying to access the ref at the same time as this
40 * process was trying to update it. It is possible a future operation
41 * will be successful.
43 LOCK_FAILURE,
45 /**
46 * Same value already stored.
47 * <p>
48 * Both the old value and the new value are identical. No change was
49 * necessary.
51 NO_CHANGE,
53 /**
54 * The ref was created locally.
55 * <p>
56 * The ref did not exist when the update started, but it was created
57 * successfully with the new value.
59 NEW,
61 /**
62 * The ref had to be forcefully updated.
63 * <p>
64 * The ref already existed but its old value was not fully merged into
65 * the new value. The configuration permitted a forced update to take
66 * place, so ref now contains the new value. History associated with the
67 * objects not merged may no longer be reachable.
69 FORCED,
71 /**
72 * The ref was updated in a fast-forward way.
73 * <p>
74 * The tracking ref already existed and its old value was fully merged
75 * into the new value. No history was made unreachable.
77 FAST_FORWARD,
79 /**
80 * Not a fast-forward and not stored.
81 * <p>
82 * The tracking ref already existed but its old value was not fully
83 * merged into the new value. The configuration did not allow a forced
84 * update to take place, so ref still contains the old value. No
85 * previous history was lost.
87 REJECTED
90 /** Repository the ref is stored in. */
91 private final Repository db;
93 /** Name of the ref. */
94 private final String name;
96 /** Location of the loose file holding the value of this ref. */
97 private final File looseFile;
99 /** New value the caller wants this ref to have. */
100 private ObjectId newValue;
102 /** Does this specification ask for forced updated (rewind/reset)? */
103 private boolean force;
105 /** Message the caller wants included in the reflog. */
106 private String refLogMessage;
108 /** Should the Result value be appended to {@link #refLogMessage}. */
109 private boolean refLogIncludeResult;
111 /** Old value of the ref, obtained after we lock it. */
112 private ObjectId oldValue;
114 /** Result of the update operation. */
115 private Result result = Result.NOT_ATTEMPTED;
117 RefUpdate(final Repository r, final Ref ref, final File f) {
118 db = r;
119 name = ref.getName();
120 oldValue = ref.getObjectId();
121 looseFile = f;
125 * Get the name of the ref this update will operate on.
127 * @return name of this ref.
129 public String getName() {
130 return name;
134 * Get the new value the ref will be (or was) updated to.
136 * @return new value. Null if the caller has not configured it.
138 public ObjectId getNewObjectId() {
139 return newValue;
143 * Set the new value the ref will update to.
145 * @param id
146 * the new value.
148 public void setNewObjectId(final AnyObjectId id) {
149 newValue = id.toObjectId();
153 * Check if this update wants to forcefully change the ref.
155 * @return true if this update should ignore merge tests.
157 public boolean isForceUpdate() {
158 return force;
162 * Set if this update wants to forcefully change the ref.
164 * @param b
165 * true if this update should ignore merge tests.
167 public void setForceUpdate(final boolean b) {
168 force = b;
172 * Get the message to include in the reflog.
174 * @return message the caller wants to include in the reflog.
176 public String getRefLogMessage() {
177 return refLogMessage;
181 * Set the message to include in the reflog.
183 * @param msg
184 * the message to describe this change.
185 * @param appendStatus
186 * true if the status of the ref change (fast-forward or
187 * forced-update) should be appended to the user supplied
188 * message.
190 public void setRefLogMessage(final String msg, final boolean appendStatus) {
191 refLogMessage = msg;
192 refLogIncludeResult = appendStatus;
196 * The old value of the ref, prior to the update being attempted.
197 * <p>
198 * This value may differ before and after the update method. Initially it is
199 * populated with the value of the ref before the lock is taken, but the old
200 * value may change if someone else modified the ref between the time we
201 * last read it and when the ref was locked for update.
203 * @return the value of the ref prior to the update being attempted; null if
204 * the updated has not been attempted yet.
206 public ObjectId getOldObjectId() {
207 return oldValue;
211 * Get the status of this update.
212 * <p>
213 * The same value that was previously returned from an update method.
215 * @return the status of the update.
217 public Result getResult() {
218 return result;
221 private void requireCanDoUpdate() {
222 if (newValue == null)
223 throw new IllegalStateException("A NewObjectId is required.");
227 * Force the ref to take the new value.
228 * <p>
229 * No merge tests are performed, so the value of {@link #isForceUpdate()}
230 * will not be honored.
232 * @return the result status of the update.
233 * @throws IOException
234 * an unexpected IO error occurred while writing changes.
236 public Result forceUpdate() throws IOException {
237 requireCanDoUpdate();
238 return result = forceUpdateImpl();
241 private Result forceUpdateImpl() throws IOException {
242 final LockFile lock;
244 lock = new LockFile(looseFile);
245 if (!lock.lock())
246 return Result.LOCK_FAILURE;
247 try {
248 oldValue = lock.readCurrentObjectId();
249 if (oldValue == null)
250 return store(lock, Result.NEW);
251 if (oldValue.equals(newValue))
252 return Result.NO_CHANGE;
253 return store(lock, Result.FORCED);
254 } finally {
255 lock.unlock();
260 * Gracefully update the ref to the new value.
261 * <p>
262 * Merge test will be performed according to {@link #isForceUpdate()}.
263 * <p>
264 * This is the same as:
266 * <pre>
267 * return update(new RevWalk(repository));
268 * </pre>
270 * @return the result status of the update.
271 * @throws IOException
272 * an unexpected IO error occurred while writing changes.
274 public Result update() throws IOException {
275 return update(new RevWalk(db));
279 * Gracefully update the ref to the new value.
280 * <p>
281 * Merge test will be performed according to {@link #isForceUpdate()}.
283 * @param walk
284 * a RevWalk instance this update command can borrow to perform
285 * the merge test. The walk will be reset to perform the test.
286 * @return the result status of the update.
287 * @throws IOException
288 * an unexpected IO error occurred while writing changes.
290 public Result update(final RevWalk walk) throws IOException {
291 requireCanDoUpdate();
292 return result = updateImpl(walk);
295 private Result updateImpl(final RevWalk walk) throws IOException {
296 final LockFile lock;
297 RevObject newObj;
298 RevObject oldObj;
300 lock = new LockFile(looseFile);
301 if (!lock.lock())
302 return Result.LOCK_FAILURE;
303 try {
304 oldValue = lock.readCurrentObjectId();
305 if (oldValue == null)
306 return store(lock, Result.NEW);
308 newObj = walk.parseAny(newValue);
309 oldObj = walk.parseAny(oldValue);
310 if (newObj == oldObj)
311 return Result.NO_CHANGE;
313 if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
314 if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
315 return store(lock, Result.FAST_FORWARD);
316 if (isForceUpdate())
317 return store(lock, Result.FORCED);
318 return Result.REJECTED;
321 if (isForceUpdate())
322 return store(lock, Result.FORCED);
323 return Result.REJECTED;
324 } finally {
325 lock.unlock();
329 private Result store(final LockFile lock, final Result status)
330 throws IOException {
331 lock.write(newValue);
332 String msg = getRefLogMessage();
333 if (msg != null && refLogIncludeResult) {
334 if (status == Result.FORCED)
335 msg += ": forced-update";
336 else if (status == Result.FAST_FORWARD)
337 msg += ": fast forward";
338 else if (status == Result.NEW)
339 msg += ": created";
341 RefLogWriter.writeReflog(db, oldValue, newValue, msg, getName());
342 if (!lock.commit())
343 return Result.LOCK_FAILURE;
344 return status;