How can I have multiple working directories with Git?
Asked Answered
R

4

293

I'm not sure if this is something supported by Git, but in theory it seems like it should work to me.

My workflow often involves my editing of files in multiple branches simultaneously. In other words, I often want to open a few files in one branch is while I edit the contents of another file in another branch.

My typical solution to this is to make two checkouts, but it's a shame I can't share branches and refs between them. What I would like is to just have two working directories managed by the same .git folder.

I'm aware of local git clone solutions (the default, which is to hardlink shared objects, and the --shared option, which sets up an alternate object store with the original repo), but these solutions only cut down on disk space usage, and especially in the case of --shared, seem fraught with peril.

Is there a way to use one .git folder, and have two working directories backed by it? Or is Git hardcoded to have just one working directory checked out at any time?

Restrictive answered 7/6, 2011 at 18:53 Comment(4)
git-new-workdir will be replaced by git checkout --to=<path> in Git 2.5. See my answer belowSidran
Actually, the command will be git worktree add <path> [<branch>] (Git 2.5 rc2). See my edited answer belowSidran
you should change the accepted answer VonC's answer, since things have changed since your originally asked the question.Oloughlin
IMO you could quote this question of yours as an answer to this question. It motivates the use-case very nicely.Acrobatic
S
354

Git 2.5 proposes since July 2015 a replacement for contrib/workdir/git-new-workdir: git worktree

See commit 68a2e6a by Junio C Hamano (gitster).

The release note mentions:

A replacement for contrib/workdir/git-new-workdir that does not rely on symbolic links and make sharing of objects and refs safer by making the borrowee and borrowers aware of each other.

See commit 799767cc9 (Git 2.5rc2)

That means you now can do a git worktree add <path> [<branch>]

Create <path> and checkout <branch> into it. The new working directory is linked to the current repository, sharing everything except working directory specific files such as HEAD, index, etc. The git worktree section adds:

A git repository can support multiple working trees, allowing you to check out more than one branch at a time.
With git worktree add, a new working tree is associated with the repository.

This new working tree is called a "linked working tree" as opposed to the "main working tree" prepared by "git init" or "git clone".
A repository has one main working tree (if it's not a bare repository) and zero or more linked working trees.

details:

Each linked working tree has a private sub-directory in the repository's $GIT_DIR/worktrees directory.
The private sub-directory's name is usually the base name of the linked working tree's path, possibly appended with a number to make it unique.
For example, when $GIT_DIR=/path/main/.git the command git worktree add /path/other/test-next next creates:

  • the linked working tree in /path/other/test-next and
  • also creates a $GIT_DIR/worktrees/test-next directory (or $GIT_DIR/worktrees/test-next1 if test-next is already taken).

Within a linked working tree:

  • $GIT_DIR is set to point to this private directory (e.g. /path/main/.git/worktrees/test-next in the example) and
  • $GIT_COMMON_DIR is set to point back to the main working tree's $GIT_DIR (e.g. /path/main/.git).

These settings are made in a .git file located at the top directory of the linked working tree.

When you are done with a linked working tree you can simply delete it.
The working tree's administrative files in the repository will eventually be removed automatically (see gc.pruneworktreesexpire in git config), or you can run git worktree prune in the main or any linked working tree to clean up any stale administrative files.


Warning: there is still a git worktree "BUGS" section to be aware of.

The support for submodules is incomplete.
It is NOT recommended to make multiple checkouts of a superproject.


Note: with git 2.7rc1 (Nov 2015) you are able to list your worktrees.
See commit bb9c03b, commit 92718b7, commit 5193490, commit 1ceb7f9, commit 1ceb7f9, commit 5193490, commit 1ceb7f9, commit 1ceb7f9 (08 Oct 2015), commit 92718b7, commit 5193490, commit 1ceb7f9, commit 1ceb7f9 (08 Oct 2015), commit 5193490, commit 1ceb7f9 (08 Oct 2015), commit 1ceb7f9 (08 Oct 2015), and commit ac6c561 (02 Oct 2015) by Michael Rappazzo (rappazzo).
(Merged by Junio C Hamano -- gitster -- in commit a46dcfb, 26 Oct 2015)

worktree: add 'list' command

'git worktree list' iterates through the worktree list, and outputs details of the worktree including the path to the worktree, the currently checked out revision and branch, and if the work tree is bare.

$ git worktree list /path/to/bare-source (bare) /path/to/linked-worktree abcd1234 [master] /path/to/other-linked-worktree 1234abc (detached HEAD)

There is also porcelain format option available.

The porcelain format has a line per attribute.

  • Attributes are listed with a label and value separated by a single space.
  • Boolean attributes (like 'bare' and 'detached') are listed as a label only, and are only present if and only if the value is true.
  • An empty line indicates the end of a worktree

For instance:

$ git worktree list --porcelain

worktree /path/to/bare-source
bare

worktree /path/to/linked-worktree
HEAD abcd1234abcd1234abcd1234abcd1234abcd1234
branch refs/heads/master

worktree /path/to/other-linked-worktree
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
detached

Note: if you MOVE a worktree folder, you need to manually update the gitdir file.

See commit 618244e (22 Jan 2016), and commit d4cddd6 (18 Jan 2016) by Nguyễn Thái Ngọc Duy (pclouds).
Helped-by: Eric Sunshine (sunshineco).
(Merged by Junio C Hamano -- gitster -- in commit d0a1cbc, 10 Feb 2016)

The new doc in git 2.8 (March 2016) will include:

If you move a linked working tree, you need to update the 'gitdir' file in the entry's directory.
For example, if a linked working tree is moved to /newpath/test-next and its .git file points to /path/main/.git/worktrees/test-next, then update /path/main/.git/worktrees/test-next/gitdir to reference /newpath/test-next instead.


Be careful when deleting a branch: before git 2.9 (June 2016), you could delete one in use in another working tree.

When "git worktree" feature is in use, "git branch -d" allowed deletion of a branch that is checked out in another worktree.

See commit f292244 (29 Mar 2016) by Kazuki Yamaguchi (rhenium).
Helped-by: Eric Sunshine (sunshineco).
(Merged by Junio C Hamano -- gitster -- in commit 4fca4e3, 13 Apr 2016)

branch -d: refuse deleting a branch which is currently checked out

When a branch is checked out by current working tree, deleting the branch is forbidden.
However when the branch is checked out only by other working trees, deleting incorrectly succeeds.
Use find_shared_symref() to check if the branch is in use, not just comparing with the current working tree's HEAD.


Similarly, before git 2.9 (June 2016), renaming a branch checked out in another worktree did not adjust the symbolic HEAD in said other worktree.

See commit 18eb3a9 (08 Apr 2016), and commit 70999e9, commit 2233066 (27 Mar 2016) by Kazuki Yamaguchi (rhenium).
(Merged by Junio C Hamano -- gitster -- in commit 741a694, 18 Apr 2016)

branch -m: update all per-worktree HEADs

When renaming a branch, currently only the HEAD of current working tree is updated, but it must update HEADs of all working trees which point at the old branch.

This is the current behavior, /path/to/wt's HEAD is not updated:

 % git worktree list
 /path/to     2c3c5f2 [master]
 /path/to/wt  2c3c5f2 [oldname]
 % git branch -m master master2
 % git worktree list
 /path/to     2c3c5f2 [master2]
 /path/to/wt  2c3c5f2 [oldname]
 % git branch -m oldname newname
 % git worktree list
 /path/to     2c3c5f2 [master2]
 /path/to/wt  0000000 [oldname]

This patch fixes this issue by updating all relevant worktree HEADs when renaming a branch.


The locking mechanism is officially supported with git 2.10 (Q3 2016)

See commit 080739b, commit 6d30862, commit 58142c0, commit 346ef53, commit 346ef53, commit 58142c0, commit 346ef53, commit 346ef53 (13 Jun 2016), and commit 984ad9e, commit 6835314 (03 Jun 2016) by Nguyễn Thái Ngọc Duy (pclouds).
Suggested-by: Eric Sunshine (sunshineco).
(Merged by Junio C Hamano -- gitster -- in commit 2c608e0, 28 Jul 2016)

git worktree lock [--reason <string>] <worktree>
git worktree unlock <worktree>

If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from being pruned by issuing the git worktree lock command, optionally specifying --reason to explain why the working tree is locked.

<worktree>: If the last path components in the working tree's path is unique among working trees, it can be used to identify worktrees.
For example if you only have to working trees at "/abc/def/ghi" and "/abc/def/ggg", then "ghi" or "def/ghi" is enough to point to the former working tree.


Git 2.13 (Q2 2017) add a lock option in commit 507e6e9 (12 Apr 2017) by Nguyễn Thái Ngọc Duy (pclouds).
Suggested-by: David Taylor (dt).
Helped-by: Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit e311597, 26 Apr 2017)

Allow to lock a worktree immediately after it's created.
This helps prevent a race between "git worktree add; git worktree lock" and "git worktree prune".

So git worktree add' --lock is the equivalent of git worktree lock after git worktree add, but without race condition.


Git 2.17+ (Q2 2018) adds git worktree move / git worktree remove: see this answer.


Git 2.19 (Q3 2018) add a "--quiet" option to make "git worktree add" less verbose.

See commit 371979c (15 Aug 2018) by Elia Pinto (devzero2000).
Helped-by: Martin Ågren [email protected], Duy Nguyen (pclouds), and Eric Sunshine (sunshineco).
(Merged by Junio C Hamano -- gitster -- in commit a988ce9, 27 Aug 2018)

worktree: add --quiet option

Add the '--quiet' option to git worktree, as for the other git commands.
'add' is the only command affected by it since all other commands, except 'list', are currently silent by default.


Note that "git worktree add" used to do a "find an available name with stat and then mkdir", which is race-prone.
This has been fixed with Git 2.22 (Q2 2019) by using mkdir and reacting to EEXIST in a loop.

See commit 7af01f2 (20 Feb 2019) by Michal Suchanek (hramrach).
(Merged by Junio C Hamano -- gitster -- in commit 20fe798, 09 Apr 2019)

worktree: fix worktree add race

Git runs a stat loop to find a worktree name that's available and then does mkdir on the found name.
Turn it to mkdir loop to avoid another invocation of worktree add finding the same free name and creating the directory first.


Git 2.22 (Q2 2019) fixes the logic to tell if a Git repository has a working tree protects "git branch -D" from removing the branch that is currently checked out by mistake.
The implementation of this logic was broken for repositories with unusual name, which unfortunately is the norm for submodules these days.

See commit f3534c9 (19 Apr 2019) by Jonathan Tan (jhowtan).
(Merged by Junio C Hamano -- gitster -- in commit ec2642a, 08 May 2019)

worktree: update is_bare heuristics

When "git branch -D <name>" is run, Git usually first checks if that branch is currently checked out.
But this check is not performed if the Git directory of that repository is not at "<repo>/.git", which is the case if that repository is a submodule that has its Git directory stored as "super/.git/modules/<repo>", for example.
This results in the branch being deleted even though it is checked out.

This is because get_main_worktree() in worktree.c sets is_bare on a worktree only using the heuristic that a repo is bare if the worktree's path does not end in "/.git", and not bare otherwise.
This is_bare code was introduced in 92718b7 ("worktree: add details to the worktree struct", 2015-10-08, Git v2.7.0-rc0), following a pre-core.bare heuristic.

This patch does 2 things:

  • Teach get_main_worktree() to use is_bare_repository() instead, introduced in 7d1864c ("Introduce is_bare_repository() and core.bare configuration variable", 2007-01-07, Git v1.5.0-rc1) and updated in e90fdc3 ("Clean up work-tree handling", 2007-08-01, Git v1.5.3-rc4).
    This solves the "git branch -D <name>" problem described above.
    However...
  • If a repository has core.bare=1 but the "git" command is being run from one of its secondary worktrees, is_bare_repository() returns false (which is fine, since there is a worktree available).
    And, treating the main worktree as non-bare when it is bare causes issues: for example, failure to delete a branch from a secondary worktree that is referred to by a main worktree's HEAD, even if that main worktree is bare.

In order to avoid that, also check core.bare when setting is_bare.
If core.bare=1, trust it, and otherwise, use is_bare_repository().


With Git 2.29 (Q4 2020), the "worktree" API offers a better determination of a worktree path.

See commit 918d8ff, commit 1c4854e, commit 246756f, commit 62573a5 (31 Jul 2020) by Eric Sunshine (sunshineco).
(Merged by Junio C Hamano -- gitster -- in commit 197253e, 10 Aug 2020)

worktree: drop bogus and unnecessary path munging

Signed-off-by: Eric Sunshine

The content of .git/worktrees/<id>/gitdir must be a path of the form "/path/to/worktree/.git".
Any other content would be indicative of a corrupt "gitdir" file.

To determine the path of the worktree itself one merely strips the "/.git" suffix, and this is indeed how the worktree path was determined from inception.

However, 5193490442 ("worktree: add a function to get worktree details", 2015-10-08, Git v2.7.0-rc0 -- merge listed in batch #7) extended the path manipulation in a mysterious way.
If it is unable to strip "/.git" from the path, then it instead reports the current working directory as the linked worktree's path:

if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
    strbuf_reset(&worktree_path);
    strbuf_add_absolute_path(&worktree_path, ".");
    strbuf_strip_suffix(&worktree_path, "/.");
}  

This logic is clearly bogus; it can never be generally correct behavior. It materialized out of thin air in 5193490442 with neither explanation nor tests to illustrate a case in which it would be desirable.

It's possible that this logic was introduced to somehow deal with a corrupt "gitdir" file, so that it returns some sort of meaningful value, but returning the current working directory is not helpful. In fact, it is quite misleading (except in the one specific case when the current directory is the worktree whose "gitdir" entry is corrupt).
Moreover, reporting the corrupt value to the user, rather than fibbing about it and hiding it outright, is more helpful since it may aid in diagnosing the problem.

Therefore, drop this bogus path munging and restore the logic to the original behavior of merely stripping "/.git".

Sidran answered 12/5, 2015 at 8:28 Comment(11)
This is the coolest thing they've created, just what I was looking for. Thanks for that!Caddie
How to delete the working tree only and still keeping the branchCholine
@DotnetRocks you can delete any working tree (a local folder on your computer): that won't have any influence on the branch: the main .git repo will still include the full committed history, with all its branches, whether or not the working tree has been deleted.Sidran
Yea, but if simply delete the working tree by going to that folder on my system and delete then git doesn't let me checkout to that branch and says that already checkout at <path> (<path> worktree path). But looks like if i do following : rm -rf <path> git worktree prune then it works . Is that right ?Choline
@DotnetRocks Yes, you are coorrect: that is what the man page git-scm.com/docs/git-worktree says: "When you are done with a linked working tree you can simply delete it. The working tree’s administrative files in the repository (see "DETAILS" below) will eventually be removed automatically (see gc.worktreePruneExpire in git-config[1]), or you can run git worktree prune in the main or any linked working tree to clean up any stale administrative files."Sidran
Oh I see, so even if didn't run git worktree prune, it would eventually remove the links right ! ThanksCholine
@VonC: the feature is now in mainline. You may cleanup up the answer a bit :)Pickaxe
@Pickaxe Thank you. I have made it clearer that 2.5 and 2.7 are now out.Sidran
@VonC: users may need some warning: See specific bugs listed in git-scm.com/docs/git-worktree. Specifically lack of support for "modules" etc. I am not using modules yet. so probably fine.Pickaxe
It seems submodules are now ok (although not optimal), see https://mcmap.net/q/11512/-what-goes-wrong-when-using-git-worktree-with-git-submodules. Unfortunately worktree is not supported yet by Tortoise (which is a real problem for an addicted like me), they are waiting libgit2 to be ready do that (see github.com/libgit2/libgit2/pull/3436), but it doesn't seem it will be integrated soon.Unkennel
@Unkennel I agree. That "waiting for libgit2 to be ready is what might have been part of the decision for another GUI (Visual Studio) to switch to an embedded git.exe instead of relying to this wrapper (libgit2): https://mcmap.net/q/11429/-visual-studio-git-source-control-without-git-for-windows-or-github-extensionSidran
C
113

The git distribution comes with a contributed script called git-new-workdir. You would use it as follows:

git-new-workdir project-dir new-workdir branch

where project-dir is the name of the directory containing your .git repository. This scripts creates another .git directory with many symlinks to the original one except for files that cannot be shared (like the current branch), allowing you to work in two different branches.

It sounds a bit fragile, but it's an option.

Coverley answered 7/6, 2011 at 19:44 Comment(4)
+1 I stand corrected, this is pretty awesome. It appears to instantly share history and branches between two different checked out repositories with no pushing/pulling, only symlinking. I was completely unaware that git could handle this. Alas, it isn't included in my distribution.Ultranationalism
For those using msysgit (windows) you can use this ported version of the script: github.com/joero74/git-new-workdirVarnish
Usually it works nice, but if you accidentally edited the same branch in different locations, it is nontrivial to fix the things back.Toper
For those stuck on Git < 2.5 and who have submodules, try git-new-workdir-recursive which is a wrapper for git-new-workdir.Mishnah
B
14

I came across this question hoping for a solution I didn't find here. So now that I did find what I needed, I decided to post it here for others.

Caveat: This is probably not a good solution if you need to edit multiple branches simultaneously, like OP states. It is for having multiple branches checked out simultaneously that you don't intend to edit. (Multiple working directories backed by one .git folder.)

There were a few things I've learned since I came to this question the first time:

  1. What a "bare repository" is. It is essentially the contents of the .git directory, without being located in a working tree.

  2. The fact that you can specify the location of the repo you are using (the location of your .git dir) on the command line with the git option --git-dir=

  3. The fact that you can specify the location of your working copy with --work-tree=

  4. What a "mirror repo" is.

This last is a pretty important distinction. I don't actually want to work on the repo, I just need to have copies of different branches and/or tags checked out simultaneously. In actual fact, I need to guarantee that the branches don't end up different from my remote's branches. So a mirror is perfect for me.

So for my use case, I got what I needed by doing:

git clone --mirror <remoteurl> <localgitdir> # Where localgitdir doesn't exist yet
mkdir firstcopy
mkdir secondcopy
git --git-dir=<localgitdir> --work-tree=firstcopy checkout -f branch1
git --git-dir=<localgitdir> --work-tree=secondcopy checkout -f branch2

The big caveat about this is that there isn't a separate HEAD for the two copies. So after the above, running git --git-dir=<localgitdir> --work-tree=firstcopy status will show all the differences from branch2 to branch1 as uncommitted changes - because HEAD is pointing at branch2. (That's why I use the -f option to checkout, because I'm not actually planning to make any changes locally at all. I can checkout any tag or branch for any work-tree, as long as I use the -f option.)

For my use case of having multiple checkouts co-existing on the same computer without needing to edit them, this works perfectly. I don't know if there is any way to have multiple HEADs for the multiple work trees without a script such as is covered in the other answers, but I hope this is helpful to someone else anyway.

Bullfrog answered 7/12, 2015 at 20:57 Comment(5)
This is exactly what I was looking for, but I can't get it to work... I'm getting "fatal: Not a git repository: '<localgitdir>'". Any ideas?Tc
Nevermind, turns out I was using "~" in my directory name, and git didn't like that. When I used the full path, it worked fine.Tc
@DanR, glad it helped. :) You might also use $HOME. There is one other caveat about the above method that I discovered later, which has to do with files that don't exist in one or another branch. If you checkout A into dir1, then checkout B into dir2, then force checkout C into dir1, if there is a file that exists in A but not in B or C, the file won't be removed from dir1 even by the force checkout. So you may need to experiment with git clean in such a case—or do what I did, and simply only use this method to populate a freshly created directory.Bullfrog
Thanks for the tip. I'm going to be wrapping this as part of a CLI tool in ruby anyway, so I'll make sure it always starts from scratch.Tc
@DanR, it might interest you to view the code I was writing at the time. Most of it is generally applicable to any git staging script, with the exception of the CFEngine syntax checks. (Perhaps you might replace that with Ruby syntax checks.) :)Bullfrog
H
3

The only solution I can think of is to clone two directories and add them as remote repositories of each other. You can then keep pulling stuff from the changed one into the other without actually pushing anything to the remote repository.

I am assuming you want to have two working directories and not two clones of the remote because you don't want to push some branches to the remote. Otherwise, two clones of your remote would work just fine - you just need to do some pushes and pulls to keep all three in sync.

Hammering answered 7/6, 2011 at 19:5 Comment(1)
Hi. The guy above shared a cool solution, the git worktree command. It works more nicely than clone several times the same repository. Try it out, you will like this new feature.Caddie

© 2022 - 2024 — McMap. All rights reserved.