Multiple working directories with Git?

后端 未结 4 638
礼貌的吻别
礼貌的吻别 2020-11-21 07:14

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 bra

相关标签:
4条回答
  • 2020-11-21 08:03

    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.

    0 讨论(0)
  • 2020-11-21 08:04

    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 d4cffffd6 (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 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 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".

    0 讨论(0)
  • 2020-11-21 08:08

    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.

    0 讨论(0)
  • 2020-11-21 08:15

    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.

    0 讨论(0)
提交回复
热议问题