gitgit-worktree

How do you push git worktrees to a remote branch


I don't know if I have a fundamental misundertanding about what a worktree is or what, but, I have tried fixing some bugs on a worktree. Used the git worktree add command to make it. I navigated to the folder it created, made my changes, made a commit, and now I am ready to push to the commit to a remote branch so I can create a Pull Request.

This is my workflow when doing good old fashioned git branches. I will:

  1. create a local branch
  2. make changes
  3. stage changes
  4. commit staged changes
  5. Push and set upstream branch
  6. Create a PR

With my worktree I am stuck on step 5.

  1. create worktree git worktree add <path>
  2. make changes
  3. stage changes
  4. commit staged changes
  5. Push and set upstream branch
  6. Refspec error

When I try to push with --set-upstream I get the src refspec <branch> does not match any error. I then looked for the .git folder to find that there wasnt one, just a .git file with the path to that worktree.

There are a lot of tutorials out there on worktrees, and every one I have tried to read/watch has skipped this crucial detail. They tend to just cover the add/remove commands.

Is a worktree even considered a branch? I didnt think so at first but git branch now shows me as being on a branch with the name of my worktree, interstingly, and unexpectedly it also shows that branch as being under another branch I was working on as if it were a sub branch of some kind

$ git branch
+ 371601_TLS_Certificates
* 395275_396239_MVPbugs
  aspire-dev-shell-baseurl
  ls
+ main
  users/rrhughes/US/371601_TLS_Certificates
  users/rrhughes/US/383751_resolve_URL
  users/rrhughes/US/383751_settings_NXupdate
  users/rrhughes/US/383751_settingsui
  users/rrhughes/US/settings_devops

Later via brute force attempting to find a solution I tried a bare git push and git told me it was not tracking a remote branch (as expected). It then provided me with this command

git push --set-upstream origin <my branch name>

I copy pasted it and it worked. However, this is nearly identical to the command I was trying before, and the command that I use to set upstream from regular git branches. the only difference was the name of my branch. It is a standardization on my dev team to make remote branches under a file structure for easy navigation and organization, so my command usually looks like this.

git push --set-upstream origin users/username/bugs/<my branch name>

Updated Question(s): So I am still confused why this did not work. Do I need to name my local worktree exactly what I will name it in the remote? If I want to name my local branch something simple, how do I set it to track a remote with a different name that meets my teams remote branch naming standard?

-Original Question: how do I get these changes on this worktree into my remote repo so I can create a PR?-


Solution

  • You need to --set-upstream with a refspec:

    git checkout -b branch-name
    # Assuming remote is `origin`
    git push --set-upstream origin branch-name:users/rrhughes/US/branch-name
    

    Or using this shortcut as LeGEC suggested for pushing the currently checked out branch:

    git push -u origin $(git branch --show-current):users/rrhughes/US/$(git branch --show-current)
    

    Now the easiest thing to do if you’re using git-pull(1) is to use git pull origin while you are checked out on that branch:

    $ git pull origin
    Already up to date.
    

    Because note that the following will not work as you probably intend/expect:

    $ git pull origin branch-name
    fatal: couldn't find remote ref branch-name
    

    Elaboration on why git-worktrees(1) is irrelevant

    Switch to a new worktree and create branch users/kristoffer/demo/update-readme (note the git worktree command):

    $ git worktree add ../demo -b users/kristoffer/demo/update-readme
    Preparing worktree (new branch 'users/kristoffer/demo/update-readme')
    HEAD is now at a1e8606 init
    $ cd ../demo
    $ # Assumes `origin` exists already
    $ git push -u origin users/kristoffer/demo/update-readme
    Password for 'https://LemmingAvalanche@github.com':
    Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
    remote:
    remote: Create a pull request for 'users/kristoffer/demo/update-readme' on GitHub by visiting:
    remote:      https://github.com/LemmingAvalanche/test-worktree/pull/new/users/kristoffer/demo/update-readme
    remote:
    To https://github.com/LemmingAvalanche/test-worktree.git
     * [new branch]      users/kristoffer/demo/update-readme -> users/kristoffer/demo/update-readme
    branch 'users/kristoffer/demo/update-readme' set up to track 'origin/users/kristoffer/demo/update-readme'.
    

    This worked.

    (See section “Annex: git-worktrees/remote demonstration” for a full script that you can try on your machine.)

    What branch am I on?

    $ # We’re in the `demo` worktree
    $ pwd
    /home/kristoffer/programming/demo
    $ git branch
    + main
    * users/kristoffer/demo/update-readme
    

    * is the currently checked out branch. What’s the + in front of main?

    According to man git-branch:[1]

    Any branches checked out in linked worktrees will be highlighted in cyan and marked with a plus sign.

    So what happens if I change back to the directory I was in before I changed directory into the worktree?

    $ cd -
    /home/kristoffer/programming/test-worktree
    $ git branch
    * main
    + users/kristoffer/demo/update-readme
    

    Makes sense:

    What is a worktree?

    First you need to understand what a “working tree” is:[2]

    The tree of actual checked out files.

    A “worktree” is a working tree plus repository metadata, with the repository metadata mostly “shared among other worktrees of a single repository”[3] [4].

    Also:

    A repository can have zero (i.e. bare repository) or one or more worktrees attached to it.

    When you did git init, you made a repository with one worktree. When you did git worktree add you created the second worktree (more on this in the section “Back to basics (init)”).

    Is a worktree even considered a branch?

    No, and these things have nothing to do with each other.

    A branch was created for you with the base name of the worktree since you gave:

    $ git worktree add ../some-name
    Preparing worktree (new branch 'some-name')
    HEAD is now at a1e8606 init
    

    I.e. you didn’t provide the optional commit-ish after the branch:[5]

    $ # Assuming that `branch-name` exists
    $ git worktree add ../some-name branch-name
    

    Unpacking “push git worktrees to a remote branch”

    When you push a branch to a remote repository, you are pushing a ref. A ref doesn’t care what your working tree looks like (e.g it could be “dirty” but that doesn’t affect the ref). In fact a remote repository is likely to be “bare”, which means that it doesn’t have a working tree.

    So the sentence by itself does not make sense.

    But what was clearly meant was:

    Push the branch that is checked out in this work tree

    Which does make sense if you are using something like git push without explicitly giving the branch name, since then you want whatever branch is checked out to be pushed.

    Then the standard troubleshooting applies:

    Back to basics (init)

    Given this blank slate:

    mkdir a-new-start
    cd a-new-start
    git init
    

    What’s the worktree and branch in this case?

    The exact same principles apply since a non-bare repository like this one has one worktree, i.e. the one where you are right now; this is the “main worktree”.[6] If you started using git worktree add then you would be creating “linked worktrees”.[6]

    So then we can ask ourselves again:

    And these questions turn out to be much more elementary since the worktree is in fact mostly a distraction from the real problem.

    Notes

    1. On git version 2.40.0

    2. man gitglossary, entry “working tree”

    3. man gitglossary, entry “worktree”

    4. The non-shared metadata “are maintained separately per worktree (e.g. the index, HEAD […]” (ibid)

    5. From man git worktree, under subcommand add:

      If <commit-ish> is omitted and neither -b nor -B nor --detach used, then, as a convenience, the new worktree is associated with a branch (call it <branch>) named after $(basename <path>).

    6. man git worktree

    Annex: branches/worktrees/remote demonstration

    This shell script demonstrates how branches, worktrees, and remotes can interact:

    # Make a `temp` directory in your home directory (but change to
    # e.g. `/tmp` if you prefer)
    cd
    mkdir -p temp
    cd ~/temp/
    mkdir demo
    cd demo
    mkdir local-repo
    mkdir remote-repo
    cd remote-repo
    git init --bare
    cd ../local-repo
    git init
    # Make an initial commit so that Git won’t freak out when
    # we try to make the next branch for the worktree
    git commit --allow-empty --message=Init
    git remote add origin ../remote-repo
    # Make the branch `worktree-branch` and check it out
    # in `worktree`
    git worktree add ../worktree -b worktree-branch
    git push --set-upstream origin worktree-branch:users/rrhughes/US/worktree-branch
    git worktree list
    cd ..
    mkdir local-repo-2
    cd local-repo-2
    git init
    git remote add origin ../remote-repo
    # This will fetch the branch as `users/rrhughes/US/worktree-branch`;
    # we didn’t give an explicit refspec and this is the name that we gave it
    # on the remote
    git fetch origin
    # Now you should get a message that the branch is set
    # up to track the remote branch of that name
    git checkout users/rrhughes/US/worktree-branch
    # Make a commit for the remote
    git commit --allow-empty --message='Second commit'
    git push origin users/rrhughes/US/worktree-branch
    # Back to the worktree where `worktree-branch` is set up
    cd ../worktree
    # This should update the branch in this worktree with the
    # second commit
    git pull --ff-only origin