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?
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
).
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. Thegit worktree
section adds:A git repository can support multiple working trees, allowing you to check out more than one branch at a time.
Withgit 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 commandgit 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
iftest-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 (seegc.pruneworktreesexpire
ingit config
), or you can rungit 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.
Usefind_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 martin.agren@gmail.com, 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 togit worktree
, as for the othergit
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
: fixworktree 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 tomkdir
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
: updateis_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()
inworktree.c
setsis_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.
Thisis_bare
code was introduced in 92718b7 ("worktree
: add details to the worktree struct", 2015-10-08, Git v2.7.0-rc0), following apre-core.bare
heuristic.This patch does 2 things:
- Teach
get_main_worktree()
to useis_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 settingis_bare
.
Ifcore.bare=1
, trust it, and otherwise, useis_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 mungingSigned-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
".