NeogitOrg / neogit

An interactive and powerful Git interface for Neovim, inspired by Magit
MIT License
3.98k stars 233 forks source link

Git checkout remote branch results in a detached head #127

Open dkirchhof opened 3 years ago

dkirchhof commented 3 years ago

Hi,

while using the checkout branch/revision command and cycle through the branches via tab, it will autocomplete all branches. Unfortunately remote branches have the following structure remotes/origin/BRANCH_NAME. After checking them out i will have a detached head.

Note: switching to 'remotes/origin/staging'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If a just use git checkout staging (i have just one remote) in the terminal everything is fine.

TimUntersberger commented 3 years ago

You should have every branch in the form of the short version staging and the long version remotes/origin/staging.

If you select the short version you won't detach your head.

dkirchhof commented 3 years ago

Hey,
sorry for my late reply. Actually if i haven't checked out a specific remote branch before, the auto completion wouldn't show me the short version of it. After checking it out via native git command (which will suggest me both versions), i can also see both versions in neogit.

TimUntersberger commented 3 years ago

Actually if i haven't checked out a specific remote branch before, the auto completion wouldn't show me the short version of it

Really? That is very weird. Can you try creating a new repo locally and try to change to a new branch.

I haven't encountered this problem yet.

dkirchhof commented 3 years ago

So i tried to recreate the issue by following steps:

mkdir neogit-bug
cd neogit-bug

# create bare repo
mkdir repo
cd repo
git init --bare

cd ..

# clone repo
git clone repo clone
cd clone

# push some stuff
touch README.md
git add --all
git commit -m "init"
git push

# create new branch
git checkout -b test-branch
git push --set-upstream origin test-branch

# open vim, open neogit, press b b tab => following branches appear
# - test-branch
# - remotes/origin/master
# - remotes/origin/test-branch
#
# everything is fine :)

cd ..

# clone again
git clone repo clone2
cd clone2

# open vim, open neogit, press b b tab => following branches appear
# - remotes/origin/test-branch
#
# well, it shows only the "long version" remote branches :(
# if i remove "remotes/origin/" and press enter, it tries to create a new branch

# try to check the branch out outside of vim 
git checkout test-branch

# this works as usual
TimUntersberger commented 3 years ago

if i remove "remotes/origin/" and press enter, it tries to create a new branch

Are you sure that the branches exist in this version of the repo? We only autocomplete local branches.

dkirchhof commented 3 years ago

Locally it exists only the default (master) branch. But the native git checkout test-branch would fetch the remote one. As it autocompletes all remote branches with the unwanted "prefix", i hoped that i can also check them out. I'm a bit confused. What command will be triggered by neogit?

TimUntersberger commented 3 years ago

The b b command checks whether the given branch exists locally and calls either git checkout ... or git checkout -b ... depending on whether the branch exists locally.

I think we should rethink the way we display/use the branch list @RianFuro

dkirchhof commented 3 years ago

Okay, this makes totally sense.

svenXY commented 1 year ago

Hi, I stumbled upon the same issue - trying to checkout a remote branch locally that has never been checked out before locally seems not currently possible.

I have seen a similar discussion here: https://github.com/nvim-telescope/telescope.nvim/issues/1932

Basically, what I (and most probably others) are after is to provide git switch somehow

shivanthzen commented 1 year ago

+1

jZhangTk commented 1 year ago

I found b c works just fine, but I don't like that I need to type the local branch name every time I checkout a remote branch.

Can we make the local branch name populated by default and allow editing if needed?

CKolkey commented 1 year ago

There will be some changes soon that improve this experience - b l will also list remote branches -not- present locally, and if selected will checkout a local branch by that name while setting the upstream automatically.

DiaanEngelbrecht commented 1 year ago

Hi @CKolkey do you have a update on this? Not sure if this would classify as a good first issue, but if you need some help on this I wouldn't mind giving it a go.

ten3roberts commented 1 year ago

For me bl (not bb) will result in a new local branch being created if you select a origin/ branch

CKolkey commented 1 year ago

Seems like a great first issue! Like @ten3roberts said, b l does this (creates a local branch tracking a remote) so extending that behaviour should be straightforward :) lmk if you have any questions

levouh commented 1 year ago

I don't quite understand how this is still an issue as it feels like 99% of the time I use any branch operation from Neogit I end up in a detached state even when git checkout <branch> from the CLI does not.

@CKolkey in an effort to not just complain and actually help with the problem, how do you recommend going about submitting a fix for this or determining where the issue is? I am happy to help, but would need some notion of direction.

Is it a bug with what branches are shown in the different views? Even if I have a branch locally, Neogit shows it as origin/<branch> in b l and b b views. Is it just because origin/<branch> is ahead of the local copy? How do I confirm if this is a bug?

If it is not a bug, is this fixed by adding a new git.cli.switch implementation and just replacing the default git.cli.checkout calls in the popup.branch.actions section? Is this a new flag that the user would need to toggle (to switch rather than checkout) in the branch view? Do we care to check that the user has a git version > 2.23.0 (when git switch) was added?

Happy to help, but do not want to spend time working on a PR just to be told the solution should be something else, or that it is a bug in the first place.

CKolkey commented 1 year ago

Thats... odd, actually. I use b b and b l, and b r all the time without winding up in a detached head state.

Something important to note: origin/<branch> is a different refspec than <branch>. You absolutely should have entries in the b l and b b lists that are not on a remote (origin, in this case).

There is a subtle difference between b b and b l in this regard: b b will not create a local branch tracking the remote, while b l will.

Check it out here: https://github.com/NeogitOrg/neogit/blob/master/lua/neogit/popups/branch/actions.lua#L62-L71

The logic is pretty straightforward:

Compare that to b l https://github.com/NeogitOrg/neogit/blob/master/lua/neogit/popups/branch/actions.lua#L73-L94

The only way (as far as I know) that you would wind up with a detached head is if you are using b b and pointing to a remote branch. If you are doing that, then... it's expected that you'd have a detached head.

If you're getting a detached head when you shouldn't be, then I'm definitely keen to help get that sorted.

levouh commented 1 year ago

I'll try to reproduce the issue, but the TLDR is:

  1. --bare repo with sub-directories for working trees
  2. On a branch, say trunk, locally that tracks origin/trunk (omitting some branch names as this repo. is for work):

image

  1. Neogit shows this in the BranchPopup as well (omitting some branch names as this repo. is for work):

image

  1. Switch to some other local branch just to allow me to demonstrate switching back
  2. Open BranchPopup, then press b l to try to switch back (note trunky is a distinct branch from trunk + origin/trunk):

image

In the above I am using a custom telescope handler to override vim.ui.select, but I validated that the return value is as expected - just the string origin/trunk.

  1. When trying to select origin/trunk from the list, it denotes:

image

as well which is expected - I already have the branch locally and am tracking the correct upstream for it.


I'm not sure where things fall apart, but based on what you noted above it seems that "branch determination" phase is incorrect, as origin/trunk should not show up in that list in the first place as far as I understand what b l does.

Based on what you noted above, it looks like the general flow for b l is:

  1. Get local branches -> here
  2. Call git.cli.branch.list which is "resolved" -> here
  3. Is resolved to a "literal command string" -> here
    • If I print this value when running, I get 2 things:
      • [user] -- DBGSTR [cli.lua:777-2] cmd={ "git", "--no-pager", "-c", "color.ui=always", "--no-optional-locks", "branch", "--sort=-committerdate" }
      • [user] -- DBGSTR [cli.lua:777-2] cmd={ "git", "--no-pager", "-c", "color.ui=always", "--no-optional-locks", "branch", "-r", "--sort=-committerdate" }
    • If I run these locally and look for trunk (omitting some irrelevant results):
      • image
      • image
  4. Then we are quite literally comparing the two lists to see if the remote branch stripped of the leading origin/ is present in the local branch list, which it is -> here

I have not looked into the utils.filter_map or utils.merge functions yet, but all from above seems to align with how you've noted that things should work so far. Anything stand out to you @CKolkey?

616b2f commented 1 year ago

Thats... odd, actually. I use b b and b l, and b r all the time without winding up in a detached head state.

Something important to note: origin/<branch> is a different refspec than <branch>. You absolutely should have entries in the b l and b b lists that are not on a remote (origin, in this case).

There is a subtle difference between b b and b l in this regard: b b will not create a local branch tracking the remote, while b l will.

Check it out here: https://github.com/NeogitOrg/neogit/blob/master/lua/neogit/popups/branch/actions.lua#L62-L71

The logic is pretty straightforward:

  • Get a list of all branches, plus any refspecs that were selected by the user
  • Pick one
  • run git checkout <branch>

Compare that to b l https://github.com/NeogitOrg/neogit/blob/master/lua/neogit/popups/branch/actions.lua#L73-L94

  • Get a list of local branches
  • Get a list of remote branches, and filter it down to remote branches that don't have a local counterpart
  • Pick one
  • If the branch was a remote branch, use git checkout --track <remote>/<branch>
  • Otherwise, git checkout <branch>

The only way (as far as I know) that you would wind up with a detached head is if you are using b b and pointing to a remote branch. If you are doing that, then... it's expected that you'd have a detached head.

If you're getting a detached head when you shouldn't be, then I'm definitely keen to help get that sorted.

It works for me exactly, like you described, still I also had that issue, because I didn't understood the difference and it is not obvious if you read the short description:

image

So for me this is an UX issue not a technical issue.

I thought that "Checkout local branch" means that you can only checkout/switch to branches that are local only. So I always use the more generic description "Checkout branch/revision" and end up with the issue described by so many here.

Would it make sense just to merge the functionality b b and b l to only b b and depending on what you select handle it differently:

Is there any real use case where you really want to checkout remote branch and don't create a local branch for it?

The other solution could be to expend the short description, so that it's more clear what they do.

CKolkey commented 1 year ago

Spot on analysis @levouh. It looks like the issue is in creating the list for step 5 above: You're entirely right, trunk should be listed, and origin/trunk should not be.

It's expected behaviour that checking out a remote branch results in a detached head (see: magit docs, neogit docs)

That all said... the fact that the correct option isn't available is (to me) the real issue here. The failure message Fatal: a branch named trunk already exists is also expected... because it does. Maybe the pattern matching isn't as good as it should be in the filter_map function... I'm not sure. But thats where to look, as far as I can tell.

@616b2f while I'm sympathetic to the issue, the docs are pretty clear (I think...) as to whats going on. They are different actions, with different results, and merging them would pretty much mean removing one or the other. Your description of how b b should work is pretty much exactly how b l already works. Or.. how it should work, not accounting for this filtering issue. I can't say I know every use case for checking out a remote without a local copy, but our source material (magit) offers this, so in my view we should as well. The thing is, it's entirely valid to check out a remote refspec without making a local copy 🤷🏼

0x0013 commented 7 months ago

@CKolkey I think the behavior @levouh reproduced is related to the pattern matching which matches if the branch starts with 2 spaces, or * in case of including the current branch (which I think it isn't in this case).

I understand this means that the branches prefixed with + are skipped. The + signifies that this branch is already linked in a different Worktree. It makes sense to filter those out, since git will not by default allow checking out a branch already linked in another worktree. However, it looks like the default branch (trunk in your case) is also linked to the bare repository, even if it doesn't actually have its own worktree, and thus gets skipped, even though git would allow it to be checked out in an actual worktree.

You can test this by manually editing the HEAD file in the bare repo (not a worktree), to point to a different branch, after which the default branch will show up in the list.