theonedev/onedev · warning · ObsoleteCommitException
ObsoleteCommitException
Error message
ObsoleteCommitException
What it means
GitUtils.updateRef wraps a RefUpdate.forceUpdate() call. If the result is LOCK_FAILURE and the ref's current old object id differs from the expected old object id, the caller attempted a compare-and-swap update against a ref that has already moved — the commit being updated on is obsolete. It throws ObsoleteCommitException carrying the actual current old object id so callers can rebase/retry.
Source
Thrown at server-core/src/main/java/io/onedev/server/git/GitUtils.java:684
public static boolean isValid(File gitDir) {
return new File(gitDir, "objects").exists();
}
public static RefUpdate getRefUpdate(Repository repository, String refName) {
try {
return repository.updateRef(refName);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void updateRef(RefUpdate refUpdate) {
try {
RefUpdate.Result result = refUpdate.forceUpdate();
if (result == RefUpdate.Result.LOCK_FAILURE && refUpdate.getExpectedOldObjectId() != null
&& !refUpdate.getExpectedOldObjectId().equals(refUpdate.getOldObjectId())) {
throw new ObsoleteCommitException(refUpdate.getOldObjectId());
} else if (result != RefUpdate.Result.FAST_FORWARD && result != RefUpdate.Result.FORCED
&& result != RefUpdate.Result.NEW && result != RefUpdate.Result.NO_CHANGE) {
throw new RefUpdateException(result);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void deleteRef(RefUpdate refUpdate) {
try {
refUpdate.setForceUpdate(true);
RefUpdate.Result result = refUpdate.delete();
if (result == RefUpdate.Result.LOCK_FAILURE && refUpdate.getExpectedOldObjectId() != null
&& !refUpdate.getExpectedOldObjectId().equals(refUpdate.getOldObjectId())) {
throw new ObsoleteCommitException(refUpdate.getOldObjectId());
} else if (result != RefUpdate.Result.FAST_FORWARD && result != RefUpdate.Result.FORCED
&& result != RefUpdate.Result.NEW && result != RefUpdate.Result.NO_CHANGE) {View on GitHub (pinned to d44925c47c)
Solutions
- Re-read the current ref value, rebase/apply your change on the new tip, and retry the update
- Catch ObsoleteCommitException explicitly and refresh the expected old object id before retrying
- Serialize conflicting updates (e.g. per-project locking) if the operation cannot be merged
- Verify no concurrent automation (webhooks, jobs) is pushing to the same ref during the update
Example fix
// before
GitUtils.updateRef(refUpdate); // may throw ObsoleteCommitException
// after
try {
GitUtils.updateRef(refUpdate);
} catch (ObsoleteCommitException e) {
ObjectId current = e.getOldObjectId(); // ref moved; rebase change onto 'current' and retry
refUpdate.setExpectedOldObjectId(current);
GitUtils.updateRef(refUpdate);
} Defensive patterns
Strategy: retry
Validate before calling
// Before updateRef, verify the ref still points at the expected commit ObjectId current = repository.exactRef(refUpdate.getName()).getObjectId(); boolean safe = current.equals(refUpdate.getExpectedOldObjectId());
Try / catch
try {
GitUtils.updateRef(refUpdate);
} catch (ObsoleteCommitException e) {
refUpdate.setExpectedOldObjectId(e.getOldObjectId());
// rebase change onto the new tip and retry
GitUtils.updateRef(refUpdate);
} Prevention
- Re-read the ref's current value immediately before optimistic updates
- Expect concurrent pushes on shared refs and design idempotent retries
- Minimize the window between reading the expected tip and updating the ref
When it happens
Trigger: updateRef called on a RefUpdate with an expected old object id (e.g. updating a comment/branch from a specific commit) while another push/update concurrently advanced the ref, so refUpdate.getOldObjectId() != expected.
Common situations: Two users or CI jobs updating the same ref simultaneously (race on note/comment commits or branch updates); optimistic-concurrency UI operations where the underlying branch moved between page load and save; retried updates after an earlier successful push.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- RefUpdateException
- No default branch in project:
- Ref not found (project:
- Project not specified
- Project not found or inaccessible: <projectPath>
AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06).
Data as JSON: /api/errors/ce3ccec3c05a8eab.
Report an issue: GitHub.