On Mon, Nov 14, 2022 at 05:57:27PM +0100, Uwe Brauer wrote: > I am sorry for such an elementary question, but using mainly hg, I found > the following very confusing.
No, the question is not elementary, and yes, the behavior is confusing. > I cloned a repository for which > > git branch -a > > Returns > > git branch -a > * main > remotes/origin/HEAD -> origin/main > remotes/origin/feature > remotes/origin/main > > This is a github repository where the default branch is now main not master. > > Now I want to commit to the feature branch that has its origin in > github. > > So > > git checkout -b feature > is not the right thing since it seems to create a local branch feature. Yes, it exactly what it did, see below. > So > > git checkout -b remotes/origin/feature > > Seems to be correct but No, it isn't, but bear with me. > git branch -a gives me > > main > * remotes/origin/feature > remotes/origin/HEAD -> origin/main > remotes/origin/feature > remotes/origin/main > > So it seems the branch is now duplicated? Confusing. Can I > edit+commit+push without problems? I am afraid to scew up things No, the branch is not duplicated but it's better to get rid of it anyway, hold on. OK, so a couple bits of theory first. Git has two kinds of branches: - "Local" branches is what displayed by unadorned `git branch`. They are yours, and in most cases are created by you with may be the only obvious exclusion being the `git clone` being run in its default mode, which creates a single local branch for you. - "Remote" branches are sort of "bookmark" to the state of the branches in some named remote repository Git has contacted with - specifically, the last time it did so. The chief difference between these two kinds of branches is that local branches are for doing development while remote branches are there for reference purposes only. Let's now consider naming of the branches. Git calls all symbolic handles for commits "references". Tags are references, and so are all kinds of branches. HEAD is also a reference, and there exists a couple other special references which are like HEAD but which are less visible, so let's not digress. All refs do actually have full unambiguous names, but this is generally not seen - in particular high-level commands such as `git branch` do not expose this. Still, the thing is, all refs have their "namespaces". In particular, - Local branches really have names refs/heads/<branch_name>, so "main" is really refs/heads/main. - Tags are, perhaps, less surprisingly are refs/tags/<tag_name>. - Remote branches are refs/remotes/<remote_name>/<branch_name>. - Special refs such as HEAD live at the top level, so HEAD is just HEAD. Since full names are flexible but are unwieldy for frequent use, Git implements a simple rule of "resolving" short names designating references to full names. This is detailed in the "gitrevisions" manual page (you can run `git help revisions`) - right there in the section "SPECIFYING REVISIONS". Long story short, when you run `git checkout master`, Git sees you used "master" in a place where a reference name is expected, sees it does not look like one of the special refs such as HEAD, and also does not start with "refs/" and is hence not a full reference name, so it uses its resolving algorithm and ends up with refs/heads/master, which is then uses to actually manipulate the repository. Let's now consider the output of `git branch -a`. The "-a" option told Git to output all the branches - both local and remote, - wich it did. While doing so it dropped the special prefixes so that local branches are listed with their short names and remote branches start with "remotes/". Let's now move to the git checkout -b remotes/origin/feature command. The behaviour of this command with the "-b" option is documented as follows: | git checkout -b|-B <new_branch> [<start point>] | Specifying -b causes a new branch to be created as if git-branch(1) were | called and then checked out. <...> Hence basically that command did the following: git branch remotes/origin/feature git checkout remotes/origin/feature as you probably know, the `git branch <branch_name>` creates the named branch pointing to the same commit HEAD points at. So the command has created a branch with the full name refs/heads/remotes/origin/feature pointing at the same commit HEAD did at the time that command run. The name of the branch is perfectly valid albeit confusing: the prefix-trimming rules of `git branch` makes your new branch and the existing remote branch look 100% the same (well, if your terminal support colors you should see the colored output - with the remote branches being rendered in reddish color by default). You can verify they are actually different branches by using a low-level command: git for-each-ref --format='%(refname)' will list all the refs with their no-frills full names. Note that `git branch -r` shows only remote branches, and `git branch` without any options shows only local branches. You can delete this banch using simple git branch -d remotes/origin/feature Let's now consider how local and remote branches interact. Git's model of working with remote repositories is asymmetric: any given repository can fetch from and push to any other Git repository it is technically able to communicate with. To highlight the point, nothing prevents you from fetching everything from, say, github.com/torvalds/linux, into your local repository containing your weekend toy project web application. >From this follows, that the fact some branch is named "master" in a given repository means nothing for a branch named "master" in your local repository; continuing my example, the branch "master" in your weekend project repo has nothing in common with the same-named branch you have fetched from the repository tracking the development of Linux. This is where the asymmetry is rooted. Still, it's convenient to have certain local branches "linked" to certain branches in other Git repositories. For instance, in now-popular workflows involving a single centralized rendez-vouz repository everyone fetches from and pushes to, its sensible to have, say, your local branch "master" be somehow tied to the branch "master" in that remote repository. Enter branch tracking ;-) Any local branch in a Git repository can be "set to track" a single remote branch fetched from a named remote repository. This is precisely what happens when you `git clone` without any special options: Git picks the branch designated as "main" (this is not a name but a concept), creates a local branch with the same name that main branch has and sets it to track that remote branch. Tracking is a purely configuration thing. Tracking a remote branch is useful because it trivializes questions like "what commits my branch has compared to the recorded state of this branch in the remote repository?", and allows to easily synchronize your local state with the updated remote state. Unfortunately, Git has confusing terminology here: when you make a local branch track a remote branch, the former is named "remote-tracking branch" because it tracks a remote branch. Let's now add the final piece to this picture. You can read in any book or guide that you never "work on" remote branches, but only on local branches. This is not the full truth but it's a good sane default mindset to maintain. When you check out a local branch using `git checkout <branch_name>`, Git makes the HEAD ref point at the (full) name of the checked out branch, and because of this recording a new commit makes that named branch be promoted to point to the new commit. When you check a remote branch, Git makes the HEAD ref point directly at the commit which that branch points at - a situation known as "detached HEAD". Recording new commits will update just the HEAD, and not any branch. This is a pretty normal situation for seasoned Git uses (I, for one, mostly work in this state) but it's not recommended for inexperienced users: I have described what happens to have you prepared for the case which would occur if you were to run git checkout origin/feature as you would end in that detached HEAD state. OK, so what you do if you want to do work based on a non-default branch in a remote repository? If we return to your example with the branch named "feature", the simplest approach would be to just run git checkout feature Git would notice there is no local branch named "feature" but there exist a remote branch named "feature", and would create a local branch "feature" pointing at the same commit the remote one points at, and tracking that remote branch. Alternatively, you could do that explicitly without involving any magic of the short-circuit command invocation: git branch feature origin/feature # create local branch off a remote one git checkout feature # check local branch out git branch -u origin/feature # make current branch track origin/feature After recording a couple of commits, you run git push origin feature With default configuration settings, it will try to update the branch named "feature" in the remote repository known locally as "origin" with the commits from your local branch "feature". -- You received this message because you are subscribed to the Google Groups "Git for human beings" group. To unsubscribe from this group and stop receiving emails from it, send an email to git-users+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/git-users/20221115153626.zbaxz7ysd2fao6ty%40carbon.