Motivation
I'd like to advance a reference atomically from in-process Rugged, the way
git update-ref <ref> <new-oid> <old-oid> does on the command line: update the
ref to a new OID only if it still points at the OID I expect. This is the one
operation that currently forces a service doing everything else in-process
(index build, Rugged::Commit.create, reads) to shell out to git.
libgit2 has the primitive -- git_reference_create_matching, which takes a
current_id and returns GIT_EMODIFIED if the ref has moved -- but Rugged
doesn't currently surface it. ReferenceCollection#create calls plain
git_reference_create, and #update uses git_reference_set_target; neither
lets the caller pass an expected current value.
Proposed API
Add a :current_id option to ReferenceCollection#create, dispatching to
git_reference_create_matching when present:
# advance refs/heads/main to new_oid only if it still points at base_oid
repo.references.create("refs/heads/main", new_oid,
force: true, current_id: base_oid)
Outcomes:
- ref points at
current_id -> updated, returns the new Rugged::Reference
- ref exists but points elsewhere -> no change, returns
nil (this mirrors how
merge_trees returns nil on GIT_EMERGECONFLICT and rebase.commit on
GIT_EAPPLIED -- a CAS miss is an expected outcome under concurrency, not an
error)
- ref does not exist -> raises
Rugged::ReferenceError (libgit2's not-found)
:current_id with a symbolic target -> ArgumentError (CAS is OID-only)
Open question
Is create the right home for this, or would you prefer it on #update, or a
dedicated method? I put it on create because that mirrors libgit2's own
naming (..._create_matching) and handles both create-if-absent and
conditional-update in one call, but I'm happy to reshape it to whatever fits
Rugged's conventions best.
Status
I'm opening a PR alongside this issue (thin C wrapper + tests covering all the
outcomes above + CHANGELOG/rdoc). Happy to rework the API shape before it's
merged if you'd prefer a different form.
Motivation
I'd like to advance a reference atomically from in-process Rugged, the way
git update-ref <ref> <new-oid> <old-oid>does on the command line: update theref to a new OID only if it still points at the OID I expect. This is the one
operation that currently forces a service doing everything else in-process
(index build,
Rugged::Commit.create, reads) to shell out togit.libgit2 has the primitive --
git_reference_create_matching, which takes acurrent_idand returnsGIT_EMODIFIEDif the ref has moved -- but Ruggeddoesn't currently surface it.
ReferenceCollection#createcalls plaingit_reference_create, and#updateusesgit_reference_set_target; neitherlets the caller pass an expected current value.
Proposed API
Add a
:current_idoption toReferenceCollection#create, dispatching togit_reference_create_matchingwhen present:Outcomes:
current_id-> updated, returns the newRugged::Referencenil(this mirrors howmerge_treesreturnsnilonGIT_EMERGECONFLICTandrebase.commitonGIT_EAPPLIED-- a CAS miss is an expected outcome under concurrency, not anerror)
Rugged::ReferenceError(libgit2's not-found):current_idwith a symbolic target ->ArgumentError(CAS is OID-only)Open question
Is
createthe right home for this, or would you prefer it on#update, or adedicated method? I put it on
createbecause that mirrors libgit2's ownnaming (
..._create_matching) and handles both create-if-absent andconditional-update in one call, but I'm happy to reshape it to whatever fits
Rugged's conventions best.
Status
I'm opening a PR alongside this issue (thin C wrapper + tests covering all the
outcomes above + CHANGELOG/rdoc). Happy to rework the API shape before it's
merged if you'd prefer a different form.