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

  1. Re-read the current ref value, rebase/apply your change on the new tip, and retry the update
  2. Catch ObsoleteCommitException explicitly and refresh the expected old object id before retrying
  3. Serialize conflicting updates (e.g. per-project locking) if the operation cannot be merged
  4. 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

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


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/ce3ccec3c05a8eab. Report an issue: GitHub.