Несколько рабочих каталогов с Git?

243

Я не уверен, что это что-то поддерживается Git, но в теории мне кажется, что это должно работать.

Мой рабочий процесс часто включает в себя редактирование файлов в нескольких ветках одновременно. Другими словами, я часто хочу открыть несколько файлов в одной ветви, пока я редактирую содержимое другого файла в другой ветви.

Мое типичное решение для этого состоит в том, чтобы сделать две проверки, но позор, что я не могу разделить ветки и ссылки между ними. То, что я хотел бы, это просто иметь две рабочие директории, управляемые одной и той же папкой .git.

Я знаю о локальных решениях git clone (по умолчанию это жесткая ссылка на общие объекты и опция --shared, которая устанавливает альтернативное хранилище объектов с исходным репо), но эти решения только сокращают использование дискового пространства и особенно в случае --shared, похоже, чревато опасностью.

Есть ли способ использовать одну папку .git и иметь на ней две рабочие директории? Или Git жестко запрограммирован, чтобы в любой момент был проверен только один рабочий каталог?

jtolds
источник
1
git-new-workdirбудет заменен на git checkout --to=<path>Git 2.5. Смотрите мой ответ ниже
VonC
3
Собственно, команда будет git worktree add <path> [<branch>](Git 2.5 rc2). Смотрите мой отредактированный ответ ниже
VonC
Вы должны изменить принятый ответ. Ответ VonC, так как все изменилось с тех пор, как вы изначально задали вопрос.
xaxxon
спасибо за обновленный ответ!
jtolds

Ответы:

294

Git 2.5 предлагает с июля 2015 года замену contrib/workdir/git-new-workdir: git worktree

Смотрите коммит 68a2e6a от Junio ​​C Hamano ( gitster) .

В выпуске упоминается :

Замена contrib/workdir/git-new-workdirэтого не основывается на символических ссылках и делает совместное использование объектов и ссылок более безопасным, информируя заемщиков и заемщиков друг о друге.

Смотрите коммит 799767cc9 (Git 2.5rc2)

Это означает, что теперь вы можете сделатьgit worktree add <path> [<branch>]

Создать <path>и оформить заказ <branch>. Новый рабочий каталог связан с текущим репозиторием, разделяя все, кроме специфических для рабочего каталога файлов, таких как HEAD, index и т. Д. В этом git worktreeразделе добавлено:

Git-репозиторий может поддерживать несколько рабочих деревьев , что позволяет вам проверять более одной ветви одновременно.
С git worktree addновым рабочим деревом связано хранилище.

Это новое рабочее дерево называется «связанным рабочим деревом», а не «основным рабочим деревом», подготовленным « git init» или « git clone» .
Репозиторий имеет одно основное рабочее дерево (если это не пустой репозиторий) и ноль или более связанных рабочих деревьев.

подробности:

Каждое связанное рабочее дерево имеет частный подкаталог в каталоге репозитория $GIT_DIR/worktrees.
Имя частного подкаталога обычно является базовым именем пути связанного рабочего дерева, возможно, с добавлением номера, чтобы сделать его уникальным.
Например, когда $GIT_DIR=/path/main/.gitкоманда git worktree add /path/other/test-next nextсоздает:

  • связанное рабочее дерево в /path/other/test-nextи
  • также создает $GIT_DIR/worktrees/test-nextкаталог (или, $GIT_DIR/worktrees/test-next1если test-nextон уже занят).

Внутри связанного рабочего дерева:

  • $GIT_DIRустанавливается для указания на этот частный каталог (например, /path/main/.git/worktrees/test-nextв примере) и
  • $GIT_COMMON_DIRустановлен, чтобы указывать обратно на главное рабочее дерево $GIT_DIR(например /path/main/.git).

Эти настройки выполняются в .gitфайле, расположенном в верхней директории связанного рабочего дерева.

Когда вы закончите со связанным рабочим деревом, вы можете просто удалить его.
Административные файлы рабочего дерева в хранилище в конечном итоге будут удалены автоматически (см. gc.pruneworktreesexpireРис. git config), Или вы можете запустить git worktree pruneосновное или любое связанное рабочее дерево, чтобы очистить любые устаревшие административные файлы.


Предупреждение: еще есть раздел git worktree«ОШИБКИ», о котором нужно знать.

Поддержка подмодулей является неполной .
НЕ рекомендуется делать несколько проверок суперпроекта.


Примечание: с помощью git 2.7rc1 (ноябрь 2015 г.) вы можете составить список своих рабочих деревьев.
См совершать bb9c03b , совершать 92718b7 , совершают 5193490 , совершают 1ceb7f9 , совершают 1ceb7f9 , совершают 5193490 , совершают 1ceb7f9 , совершают 1ceb7f9 (08 окт 2015), совершать 92718b7 , совершать 5193490 , совершают 1ceb7f9 , совершают 1ceb7f9 (08 окт 2015), совершают 5193490 , зафиксировать 1ceb7f9 (8 октября 2015 г.), зафиксировать 1ceb7f9 (08 октября 2015 г.) и зафиксировать ac6c561(02 октября 2015) Майкл Раппаццо ( rappazzo) .
(Слиты Junio C Hamano - gitster- в фиксации a46dcfb , 26 октября 2015)

worktree: добавить listкоманду

' git worktree list' перебирает список рабочего дерева и выводит детали рабочего дерева, включая путь к рабочему дереву, текущую извлеченную ревизию и ветвь, а также, если рабочее дерево пустое.

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

Существует также опция формата фарфора.

Фарфоровый формат имеет строку для каждого атрибута.

  • Атрибуты перечислены с меткой и значением, разделенным одним пробелом.
  • Булевы атрибуты (такие как «bare» и «detached») перечислены только как метки и присутствуют только в том случае, если значение равно true.
  • Пустая строка указывает на конец рабочего дерева

Например:

$ 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

Примечание: если вы перемещаете папку рабочего дерева, вам необходимо вручную обновить gitdirфайл.

См. Коммит 618244e (22 января 2016 г.) и коммит d4cddd6 (18 января 2016 г.) от Nguyễn Thái Ngọc Duy ( pclouds) .
Помогает: Эрик Саншайн ( sunshineco) .
(Слиты Junio C Hamano - gitster- в фиксации d0a1cbc , 10 февраля 2016)

Новый документ в git 2.8 (март 2016 года) будет включать:

Если вы перемещаете связанное рабочее дерево, вам необходимо обновить gitdirфайл ' ' в каталоге записи.
Например, если связанное рабочее дерево перемещено /newpath/test-nextи его .gitфайл указывает на /path/main/.git/worktrees/test-next, то вместо этого обновите /path/main/.git/worktrees/test-next/gitdirссылку /newpath/test-next.


Будьте осторожны при удалении ветки: до git 2.9 (июнь 2016 года) вы могли удалить одну из используемых веток в другом рабочем дереве.

Когда git worktreeфункция « » используется, « git branch -d» разрешено удаление ветви, которая извлечена в другом рабочем дереве.

См. Коммит f292244 (29 марта 2016 г.) Казуки Ямагути ( rhenium) .
Помогает: Эрик Саншайн ( sunshineco) .
(Слиты Junio C Hamano - gitster- в фиксации 4fca4e3 , 13 Apr 2016)

branch -d: отказаться от удаления ветки, которая в данный момент извлечена

Когда ветка проверяется текущим рабочим деревом, удаление ветки запрещено.
Однако, когда ветвь проверяется только другими рабочими деревьями, удаление некорректно завершается успешно.
Используется find_shared_symref()для проверки, используется ли ветвь, а не просто для сравнения с HEAD текущего рабочего дерева.


Точно так же до git 2.9 (июнь 2016 г.) переименование ветви, отмеченной в другом рабочем дереве, не корректировало символический заголовок в другом рабочем дереве.

См. Коммит 18eb3a9 (8 апреля 2016 г.) и коммит 70999e9 , коммит 2233066 (27 марта 2016 г.) от Kazuki Yamaguchi ( rhenium) .
(Слиты Junio C Hamano - gitster- в фиксации 741a694 , 18 Apr 2016)

branch -m: обновить все заголовки для каждого рабочего дерева

При переименовании ветви в настоящее время обновляется только заголовок текущего рабочего дерева, но он должен обновлять заголовки всех рабочих деревьев, которые указывают на старую ветвь.

Это текущее поведение, заголовок / path / to / wt не обновляется:

  % 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]

Этот патч устраняет эту проблему, обновляя все соответствующие заголовки рабочего дерева при переименовании ветви.


Механизм блокировки официально поддерживается git 2.10 (3 квартал 2016 г.)

См. Коммит 080739b , коммит 6d30862 , коммит 58142c0 , коммит 346ef53 , коммит 346ef53 , коммит 58142c0 , коммит 346ef53 , коммит 346ef53 (13 июня 2016 г.) и коммит 984ad9e , коммит 6835314 (03 июня 2016 г.) от Nguyễn Thái Ngọc Duy ( pclouds) .
Предложил: Эрик Саншайн ( sunshineco) .
(Слиты Junio C Hamano - gitster- в фиксации 2c608e0 , 28 Jul 2016)

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

Если связанное рабочее дерево хранится на переносном устройстве или в общем сетевом ресурсе, который не всегда монтируется, вы можете предотвратить удаление его административных файлов, введя git worktree lockкоманду, дополнительно указав --reasonпричину, по которой рабочее дерево заблокировано.

<worktree>: Если последние компоненты пути в пути рабочего дерева уникальны среди рабочих деревьев, его можно использовать для идентификации рабочих деревьев.
Например, если у вас есть только рабочие деревья в « /abc/def/ghi» и « /abc/def/ggg», то « ghi» или « def/ghi» достаточно, чтобы указать на прежнее рабочее дерево.


В Git 2.13 (второй квартал 2017 года) добавлена lockопция в коммите 507e6e9 (12 апреля 2017 года) от Nguyễn Thái Ngọc Duy ( pclouds) .
Предложил: Дэвид Тейлор ( dt) .
Помогает: Джефф Кинг ( peff) .
(Объединено Junio ​​C Hamano - gitster- в коммите e311597 , 26 апреля 2017 г.)

Разрешить блокировать рабочее дерево сразу после его создания.
Это помогает предотвратить гонку между " git worktree add; git worktree lock" и " git worktree prune".

То git worktree add' --lock же самое, что и git worktree lockпосле git worktree add, но без условий гонки.


Git 2.17+ (Q2 2018) добавляет git worktree move/ git worktree remove: см. Этот ответ .


В Git 2.19 (Q3 2018) добавьте « --quiet», чтобы сделать « git worktree add» менее многословным.

См. Коммит 371979c (15 августа 2018 г.) Элии Пинто ( devzero2000) .
При поддержке: Мартин Агрен, Дуй Нгуен ( pclouds) и Эрик Саншайн ( sunshineco) .
(Слиты Junio C Hamano - gitster- в фиксации a988ce9 , 27 августа 2018)

worktree: добавить --quietопцию

Добавьте параметр ' --quiet' git worktree, как и для других gitкоманд.
' add' - это единственная команда, на которую она влияет, поскольку все остальные команды, кроме ' list', в настоящее время по умолчанию молчат.


Обратите внимание, что « git worktree add» используется для «поиска доступного имени с помощью stat и затем mkdir», что склонно к гонке.
Это было исправлено с помощью Git 2.22 (Q2 2019) с использованием mkdirи реакцией EEXISTв цикле.

Смотрите коммит 7af01f2 (20 февраля 2019 г.) Михала Суханека ( hramrach) .
(Слиты Junio C Hamano - gitster- в фиксации 20fe798 , 09 Apr 2019)

worktree: исправить worktree addгонку

Git запускает цикл статистики, чтобы найти имя рабочего дерева, которое доступно, а затем делает mkdirэто для найденного имени.
Включите его, чтобы mkdirизбежать повторного вызова рабочего дерева, добавьте поиск того же свободного имени и сначала создание каталога.


Git 2.22 (Q2 2019) исправляет логику, чтобы сказать, имеет ли репозиторий Git рабочее дерево, защищает " git branch -D" от удаления ветви, которая в настоящее время извлечена по ошибке.
Реализация этой логики была нарушена для репозиториев с необычным именем, что, к сожалению, является нормой для подмодулей в наши дни.

См. Коммит f3534c9 (19 апреля 2019 г.) Джонатана Тана ( jhowtan) .
(Объединено Junio ​​C Hamano - gitster- в коммите ec2642a , 8 мая 2019 г.)

Code Pull запрашивает 178 идей

worktree: обновить is_bareэвристику

Когда git branch -D <name>запускается "", Git обычно сначала проверяет, извлечена ли эта ветка в данный момент.
Но эта проверка не выполняется, если каталог Git этого репозитория не находится в « <repo>/.git», что имеет место, если этот репозиторий является подмодулем, в котором, например, его каталог Git хранится как « super/.git/modules/<repo>».
Это приводит к удалению ветви, даже если она извлечена.

Это связано с тем, что get_main_worktree()в worktree.cнаборах is_bareна рабочем дереве только с использованием эвристики репо является пустым, если путь рабочего дерева не заканчивается на " /.git", и не является голым в противном случае.
Этот is_bareкод был введен в 92718b7worktree: добавить детали в структуру рабочего дерева», 2015-10-08, Git v2.7.0-rc0), следуя pre-core.bareэвристике.

Этот патч делает 2 вещи:

  • Научить get_main_worktree()использовать is_bare_repository()вместо этого, введены в 7d1864c ( "Ввести is_bare_repository () и core.bare переменной конфигурации", 2007-01-07, Git v1.5.0-rc1) и обновляются в e90fdc3 ( "Очистка погрузочно - разгрузочные работы дерева", 2007 -08-01, Git v1.5.3-rc4).
    Это решает проблему " git branch -D <name>", описанную выше.

Однако ... Если в репозитории есть, core.bare=1но команда " git" запускается с одного из его вторичных рабочих деревьев, is_bare_repository()возвращает false (что нормально, поскольку доступно рабочее дерево).

И, рассматривая основное рабочее дерево как непокрытое, когда оно голое, возникает проблема:

Например, ошибка удаления ветви из вторичного рабочего дерева, на которую ссылается HEAD основного рабочего дерева, даже если это основное рабочее дерево пустое.

Во избежание этого также проверьте core.bareпри настройке is_bare.
Если core.bare=1доверяете этому, а иначе используйте is_bare_repository().

VonC
источник
1
Это самая крутая вещь, которую они создали, именно то, что я искал. Спасибо за это!
Как удалить только рабочее дерево и при этом сохранить ветку
Randeep Singh
@DotnetRocks, вы можете удалить любое рабочее дерево (локальную папку на вашем компьютере): это не будет иметь никакого влияния на ветку: основное репозиторий .git будет по-прежнему включать полную историю фиксации со всеми ее ветками, независимо от того, рабочее дерево было удалено.
VonC
Да, но если просто удалить рабочее дерево, перейдя в эту папку в моей системе и удалить, то git не разрешает мне возвращаться в эту ветку и говорит, что уже извлечено в <path> (путь <path> рабочего дерева). Но похоже, что если я сделаю следующее: rm -rf <path> git worktree prune, то это работает. Это правильно ?
Рандип Сингх
1
@Jayan Спасибо. Я пояснил, что сейчас вышли 2.5 и 2.7.
VonC
113

gitДистрибутив поставляется с более способствовали сценарию под названием git-new-workdir. Вы бы использовали его следующим образом:

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

где project-dir - это имя каталога, в котором находится ваш .gitрепозиторий. Этот сценарий создает другой .gitкаталог со многими символическими ссылками на исходный, за исключением файлов, которые не могут быть общими (например, текущая ветвь), что позволяет вам работать в двух разных ветвях.

Это звучит немного хрупко, но это вариант.

адль
источник
3
+1 Я исправлюсь, это круто. Похоже, что он мгновенно обменивается историей и ветвями между двумя разными извлеченными репозиториями без каких-либо нажатий / вытягиваний, только с символическими ссылками. Я совершенно не знал, что Git может справиться с этим. Увы, это не входит в мой дистрибутив.
Meagar
2
Для тех, кто использует msysgit (windows), вы можете использовать эту портированную версию скрипта: github.com/joero74/git-new-workdir
amos
9
Обычно это работает хорошо, но если вы случайно отредактировали одну и ту же ветку в разных местах, исправить это не составит труда.
grep
Для тех, кто застрял на Git <2.5 и у кого есть подмодули, попробуйте, git-new-workdir-recursiveкоторый является оберткой для git-new-workdir.
Уолф
13

Я наткнулся на этот вопрос в надежде найти решение, которого не нашел здесь. Так что теперь я сделал найти то , что мне было нужно, я решил разместить его здесь для других.

Предостережение: Это, вероятно, не очень хорошее решение, если вам нужно редактировать несколько веток одновременно, например, состояния OP. Это наличие нескольких ветвей Выданы одновременно , что вы не намерены редактировать. (Несколько рабочих каталогов, поддерживаемых одной папкой .git.)

С тех пор, как я впервые пришел к этому вопросу, я кое-чему научился:

  1. Что такое « голое хранилище ». По сути, это содержимое .gitкаталога, не находящееся в рабочем дереве.

  2. Тот факт, что вы можете указать местоположение репо, которое вы используете (местоположение вашего .gitкаталога) в командной строке с gitпараметром--git-dir=

  3. Тот факт, что вы можете указать местоположение вашей рабочей копии с --work-tree=

  4. Что такое «зеркальное репо».

Это последнее довольно важное различие. На самом деле я не хочу работать с репо, мне просто нужно, чтобы копии разных веток и / или тегов проверялись одновременно. На самом деле, я должен гарантировать, что ветви не будут отличаться от веток моего пульта. Так что зеркало идеально подходит для меня.

Так что для моего случая использования я получил то, что мне было нужно, выполнив:

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

Большое предостережение об этом заключается в том, что для этих двух копий не существует отдельной ГОЛОВКИ. Таким образом, после вышесказанного, запуск git --git-dir=<localgitdir> --work-tree=firstcopy statusпокажет все отличия от branch2 до branch1 как незафиксированные изменения - потому что HEAD указывает на branch2. (Вот почему я использую эту -fопцию checkout, потому что я вообще не планирую вносить какие-либо изменения локально. Я могу извлечь любой тег или ветвь для любого рабочего дерева, если я использую эту -fопцию.)

Для моего случая использования нескольких проверок, сосуществующих на одном компьютере без необходимости их редактирования , это прекрасно работает. Я не знаю, есть ли какой-нибудь способ иметь несколько HEAD для нескольких рабочих деревьев без сценария, такого как описано в других ответах, но я надеюсь, что это в любом случае полезно для кого-то еще.

Wildcard
источник
Это именно то, что я искал, но я не могу заставить его работать ... Я получаю "роковое: не git-репозиторий: '<localgitdir>'". Любые идеи?
Дан Р
Неважно, оказывается, я использовал «~» в своем имени каталога, а git это не понравилось. Когда я использовал полный путь, он работал нормально.
Дан Р
@DanR, рад, что это помогло. :) Вы также можете использовать $HOME. Есть еще одно предостережение о вышеописанном методе, который я обнаружил позже, который касается файлов, которые не существуют в той или иной ветке. Если вы извлекаете A в dir1, затем извлекаете B в dir2, а затем принудительно извлекаете C в dir1. Если существует файл, который существует в A, но не в B или C, файл не будет удален из dir1 даже при принудительной проверке , Поэтому вам может потребоваться поэкспериментировать git cleanв таком случае - или сделать то, что я сделал, и просто использовать этот метод только для заполнения только что созданного каталога.
Wildcard
Спасибо за чаевые. В любом случае я собираюсь обернуть это как часть CLI-инструмента в ruby, поэтому я буду следить за тем, чтобы он всегда начинался с нуля.
Дан Р
@DanR, вам может быть интересно посмотреть код, который я писал в то время . Большинство из них, как правило, применимо к любому сценарию git-сценария, за исключением проверок синтаксиса CFEngine. (Возможно, вы могли бы заменить это проверками синтаксиса Ruby.) :)
Wildcard
3

Единственное решение, которое я могу придумать, - это клонировать две директории и добавить их как удаленные репозитории друг друга. Затем вы можете продолжать вытаскивать вещи из измененного в другой, фактически не выталкивая что-либо в удаленный репозиторий.

Я предполагаю, что вы хотите иметь два рабочих каталога, а не два клона удаленного, потому что вы не хотите выдвигать некоторые ветви на удаленный. В противном случае два клона вашего пульта будут работать нормально - вам просто нужно сделать несколько нажатий и нажатий, чтобы синхронизировать все три.

vhallac
источник
Здравствуй. Парень наверху поделился классным решением, командой git worktree . Это работает лучше, чем клонировать несколько раз один и тот же репозиторий. Попробуйте, вам понравится эта новая функция.