libgit2 / libgit2/pygit2

Cloning with remote callback results in HEAD pointing to user's `init.defaultBranch`

Open
#1,073 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.7k
Forks
408
Avg merge
2d 57m
Merged PRs (30d)
7

Description

Cloning with a remote callback results in HEAD pointing to the user's init.defaultBranch instead of the default branch of the cloned repository. See repro below.

Looks like an upstream bug to me, just confirming with you... thanks :)

I'll also note that HEAD already contains the wrong reference before the callback is executed. (Reproduce this e.g. by placing a breakpoint() in remote_cb below, and inspecting HEAD from the debugger prompt.) Still, the issue does not reproduce without the callback.

Repro

❯ docker run --rm -ti python bash
root@e747d803b7f3:/# pip install pygit2
Collecting pygit2
  Downloading pygit2-1.5.0-cp39-cp39-manylinux2014_x86_64.whl (3.1 MB)
     |████████████████████████████████| 3.1 MB 2.4 MB/s 
Collecting cffi>=1.4.0
  Downloading cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl (406 kB)
     |████████████████████████████████| 406 kB 15.7 MB/s 
Collecting cached-property
  Downloading cached_property-1.5.2-py2.py3-none-any.whl (7.6 kB)
Collecting pycparser
  Downloading pycparser-2.20-py2.py3-none-any.whl (112 kB)
     |████████████████████████████████| 112 kB 17.0 MB/s 
Installing collected packages: pycparser, cffi, cached-property, pygit2
Successfully installed cached-property-1.5.2 cffi-1.14.5 pycparser-2.20 pygit2-1.5.0
root@e747d803b7f3:/# cat >> ~/.gitconfig
[init]
    defaultBranch = teapot
root@e747d803b7f3:/# python
Python 3.9.4 (default, Apr 10 2021, 15:31:19) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygit2
>>> def remote_cb(repo, name, url):
...     n = name.decode()
...     repo.config[f"remote.{n}.mirror"] = True
...     return repo.remotes.create(name, url, "+refs/*:refs/*")
... 
>>> repo = pygit2.clone_repository("https://github.com/libgit2/pygit2", "/tmp/pygit2", remote=remote_cb)
>>> repo.head
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pygit2.GitError: reference 'refs/heads/teapot' not found
>>> print(open("/tmp/pygit2/.git/HEAD").read())
ref: refs/heads/teapot

Workaround

import contextlib
import pygit2

def _fix_repository_head(repository: pygit2.Repository) -> pygit2.Reference:
    """Work around a bug in libgit2 resulting in a bogus HEAD reference.

    Cloning with a remote callback results in HEAD pointing to the user's
    `init.defaultBranch` instead of the default branch of the cloned repository.
    """
    head = repository.references["HEAD"]

    with contextlib.suppress(KeyError):
        return head.resolve()

    for branch in ["main", "master"]:
        ref = f"refs/heads/{branch}"
        if head.target != ref and ref in repository.references:
            head.set_target(ref, message="repair broken HEAD after clone")

    return head.resolve()

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the provided pygit2.clone_repository call and its remote_cb callback, reproducing the behavior with and without the callback. Inspect /tmp/pygit2/.git/HEAD before the callback runs and compare it with the cloned repository's default branch. Done means cloning with the callback leaves HEAD pointing to the cloned repository's default branch rather than init.defaultBranch, with coverage added wherever clone behavior is tested.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, python
Domain
api, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.