This works fine for me using pygit2:
- Clone master branch of a repo
- Create a branch
- Make some changes
- Push branch to origin
This is failing for me:
- Clone master branch of a repo
- Checkout an existing branch
I always end up with detached HEAD. Is there some trick to doing the checkout without getting a detached HEAD? I've spent well over a day on this now and I know there must be a way of doing this properly but every example I see online does what I do below ...
Here is my stripped down test case which fails every time for every branch I tried:
repo = pygit2.clone_repository(url,dir,bare=False,checkout_branch="master",callbacks=RemoteCallbacks())
checkout_branch = repo.branches["origin/{0}".format(branch))
ref = repo.lookup_reference(checkout_branch.name)
repo.checkout(ref)
While pygit2 may throw its own extra wrinkles into the mix, start with this fact: When you clone a repository, you first get all of the other Git's commits and none of its branches. Then, just before the command-line
git clonecommand returns a prompt to you, it creates one branch for you, based on the-bargument you supplied. If you didn't supply a-bargument, it—your own Git—asks the other Git what branch name they recommend, and creates a branch from that name.The result is that if they, in their Git, have branches
branch1,branch2,branch3,main, andxyzzy, and you specify-b mainor don't specify anything and they recommend theirmain, your Git now has exactly one branch in your repository—your clone of their repository—and that's the branch namedmain. Your Git created this as yourmain; it's not theirmain. Their branch names are theirs.If you want to have branches named
fred,barney,betty, andwilmainstead of their names, you can do that. To prevent yourgit clonefrom creating any branch at all you can add the-noption to yourgit clonecommand: now you get all their commits, and no branch at all and now you can choose a name that doesn't match any of their names.But: what happens to their branch names? The answer is: your Git takes their branch names, such as
branch1andbranch2andmainand so on, and changes those into remote-tracking names. The remote-tracking names your Git uses here areorigin/branch1,origin/branch2,origin/main, and so on.These remote-tracking names match their branch names, with the obvious substitution. But they're not branch names. They're remote-tracking names. What's the difference? It's that detached HEAD that you're seeing. Using a remote-tracking name means I don't intend to make any new commits. Using a branch name means I do intend to make new commits. If you intend to make new commits, you should use a branch name, not a remote-tracking name.
That, in general, may mean that you have to ask your Git to create a new branch name in your own local repository. How do you do that? Well, it all gets a bit complicated in complicated setups, but we start with this: A request to check out branch name X, for any X, can't proceed if there is no branch named X ... so if your Git does not have an X yet, your Git first checks to see if your have
origin/X, and if so, your Git will create your X from yourorigin/X. Git calls this "DWIM mode", for "Do What I Mean (not what I say)". Thegit checkoutcommand has a new flag,--no-guess, to disable this kind of guessing about what you meant. Using:won't look to see if you have an
origin/branch1before complaining that there is nobranch1to check out. The default is to check first: there's nobranch1? Check fororigin/branch1, if so, createbranch1usingorigin/branch1, the same waygit clonecreates yourmainfrom yourorigin/mainthat you got from their Git'smain.This is all a bit roundabout, but it tends to work out pretty well for most users, most of the time. As long as they don't run
git checkout origin/branch1, that is.