{"record":{"id":"ce3ccec3c05a8eab","repo":"theonedev/onedev","slug":"obsoletecommitexception","errorCode":null,"errorMessage":"ObsoleteCommitException","messagePattern":"ObsoleteCommitException","errorType":"exception","errorClass":"ObsoleteCommitException","httpStatus":null,"severity":"warning","filePath":"server-core/src/main/java/io/onedev/server/git/GitUtils.java","lineNumber":684,"sourceCode":"\n\tpublic static boolean isValid(File gitDir) {\n\t\treturn new File(gitDir, \"objects\").exists();\n\t}\n\n\tpublic static RefUpdate getRefUpdate(Repository repository, String refName) {\n\t\ttry {\n\t\t\treturn repository.updateRef(refName);\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\tpublic static void updateRef(RefUpdate refUpdate) {\n\t\ttry {\n\t\t\tRefUpdate.Result result = refUpdate.forceUpdate();\n\t\t\tif (result == RefUpdate.Result.LOCK_FAILURE && refUpdate.getExpectedOldObjectId() != null\n\t\t\t\t\t&& !refUpdate.getExpectedOldObjectId().equals(refUpdate.getOldObjectId())) {\n\t\t\t\tthrow new ObsoleteCommitException(refUpdate.getOldObjectId());\n\t\t\t} else if (result != RefUpdate.Result.FAST_FORWARD && result != RefUpdate.Result.FORCED\n\t\t\t\t\t&& result != RefUpdate.Result.NEW && result != RefUpdate.Result.NO_CHANGE) {\n\t\t\t\tthrow new RefUpdateException(result);\n\t\t\t}\n\t\t} catch (IOException e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t}\n\n\tpublic static void deleteRef(RefUpdate refUpdate) {\n\t\ttry {\n\t\t\trefUpdate.setForceUpdate(true);\n\t\t\tRefUpdate.Result result = refUpdate.delete();\n\t\t\tif (result == RefUpdate.Result.LOCK_FAILURE && refUpdate.getExpectedOldObjectId() != null\n\t\t\t\t\t&& !refUpdate.getExpectedOldObjectId().equals(refUpdate.getOldObjectId())) {\n\t\t\t\tthrow new ObsoleteCommitException(refUpdate.getOldObjectId());\n\t\t\t} else if (result != RefUpdate.Result.FAST_FORWARD && result != RefUpdate.Result.FORCED\n\t\t\t\t\t&& result != RefUpdate.Result.NEW && result != RefUpdate.Result.NO_CHANGE) {","sourceCodeStart":666,"sourceCodeEnd":702,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-core/src/main/java/io/onedev/server/git/GitUtils.java#L666-L702","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nGitUtils.updateRef(refUpdate); // may throw ObsoleteCommitException\n\n// after\ntry {\n    GitUtils.updateRef(refUpdate);\n} catch (ObsoleteCommitException e) {\n    ObjectId current = e.getOldObjectId(); // ref moved; rebase change onto 'current' and retry\n    refUpdate.setExpectedOldObjectId(current);\n    GitUtils.updateRef(refUpdate);\n}","handlingStrategy":"retry","validationCode":"// Before updateRef, verify the ref still points at the expected commit\nObjectId current = repository.exactRef(refUpdate.getName()).getObjectId();\nboolean safe = current.equals(refUpdate.getExpectedOldObjectId());","typeGuard":null,"tryCatchPattern":"try {\n  GitUtils.updateRef(refUpdate);\n} catch (ObsoleteCommitException e) {\n  refUpdate.setExpectedOldObjectId(e.getOldObjectId());\n  // rebase change onto the new tip and retry\n  GitUtils.updateRef(refUpdate);\n}","preventionTips":["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"],"tags":["git","ref-update","concurrency","onedev"],"backgroundTag":"invalid-state-transition","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}