Skip to content
  • Jeff King's avatar
    lock_ref_sha1_basic: handle REF_NODEREF with invalid refs · 2859dcd4
    Jeff King authored and Junio C Hamano's avatar Junio C Hamano committed
    
    
    We sometimes call lock_ref_sha1_basic with REF_NODEREF
    to operate directly on a symbolic ref. This is used, for
    example, to move to a detached HEAD, or when updating
    the contents of HEAD via checkout or symbolic-ref.
    
    However, the first step of the function is to resolve the
    refname to get the "old" sha1, and we do so without telling
    resolve_ref_unsafe() that we are only interested in the
    symref. As a result, we may detect a problem there not with
    the symref itself, but with something it points to.
    
    The real-world example I found (and what is used in the test
    suite) is a HEAD pointing to a ref that cannot exist,
    because it would cause a directory/file conflict with other
    existing refs.  This situation is somewhat broken, of
    course, as trying to _commit_ on that HEAD would fail. But
    it's not explicitly forbidden, and we should be able to move
    away from it. However, neither "git checkout" nor "git
    symbolic-ref" can do so. We try to take the lock on HEAD,
    which is pointing to a non-existent ref. We bail from
    resolve_ref_unsafe() with errno set to EISDIR, and the lock
    code thinks we are attempting to create a d/f conflict.
    
    Of course we're not. The problem is that the lock code has
    no idea what level we were at when we got EISDIR, so trying
    to diagnose or remove empty directories for HEAD is not
    useful.
    
    To make things even more complicated, we only get EISDIR in
    the loose-ref case. If the refs are packed, the resolution
    may "succeed", giving us the pointed-to ref in "refname",
    but a null oid. Later, we say "ah, the null oid means we are
    creating; let's make sure there is room for it", but
    mistakenly check against the _resolved_ refname, not the
    original.
    
    We can fix this by making two tweaks:
    
      1. Call resolve_ref_unsafe() with RESOLVE_REF_NO_RECURSE
         when REF_NODEREF is set. This means any errors
         we get will be from the orig_refname, and we can act
         accordingly.
    
         We already do this in the REF_DELETING case, but we
         should do it for update, too.
    
      2. If we do get a "refname" return from
         resolve_ref_unsafe(), even with RESOLVE_REF_NO_RECURSE
         it may be the name of the ref pointed-to by a symref.
         We already normalize this back to orig_refname before
         taking the lockfile, but we need to do so before the
         null_oid check.
    
    While we're rearranging the REF_NODEREF handling, we can
    also bump the initialization of lflags to the top of the
    function, where we are setting up other flags. This saves us
    from having yet another conditional block on REF_NODEREF
    just to set it later.
    
    Signed-off-by: default avatarJeff King <peff@peff.net>
    Signed-off-by: default avatarJunio C Hamano <gitster@pobox.com>
    2859dcd4