Skip to content

Expose libgit2's git_reference_create_matching (atomic compare-and-swap ref update) #1013

Description

@JonJagger

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions