git голые репозитории, рабочие деревья и ветки отслеживания

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

git clone --bare ssh://[email protected]/project/repo repo.git
cd repo.git
git worktree add ../branch-1 branch-1
git worktree add ../branch-2 branch-2
... someone else creates branch-3 and pushes is ...
git fetch origin +refs/heads/*:refs/heads/* --prune
git worktree add ../branch-3 branch-3

Теперь рабочее дерево branch-3 не настроено на отслеживание удаленного дерева, и, пытаясь заставить его это делать, я попадаю в ужасный беспорядок.

$ cd ../branch-3
$ git branch -u origin/branch-3
error: the requested upstream branch 'origin/refs/heads/feature/SW-5884-move-database-container-to-alpine-base-2' does not exist
hint: ...<snip>
$ git fetch +refs/heads/*:refs/remotes/origin/* --prune
$ git branch -u origin/branch-3
fatal: Cannot setup tracking information; starting point 'origin/feature/SW-5884-move-database-container-to-alpine-base-2' is not a branch.

Что такое правильная магия, чтобы заставить это работать?


person Tom    schedule 25.01.2019    source источник
comment
Не рекомендуется добавлять рабочие деревья в --bare репозитории. Я бы порекомендовал вам оставить голый репозиторий голым и сделать не голый клон, в котором можно создавать рабочие деревья. (В частности, --bare изменяет refspec выборки таким образом, что любая выборка может серьезно испортить вашу незавершенную работу.)   -  person torek    schedule 25.01.2019
comment
Хорошо, спасибо за совет. Я предполагал, что именно так должны были использоваться рабочие деревья; если репозиторий не голый, вам в конечном итоге придется создать фиктивную ветку, чтобы разместить сам репозиторий, потому что невозможно одновременно иметь репозиторий и рабочее дерево в одной и той же ветке. Хотели бы вы поместить это в ответ, который я могу принять?   -  person Tom    schedule 28.01.2019


Ответы (2)


Во-первых, примечание: если вы собираетесь использовать git worktree add в течение нетривиальных периодов (более двух недель за раз), убедитесь, что у вас Git не ниже версии 2.151.

Для вашей конкретной цели я бы рекомендовал не использовать git clone --bare. Вместо этого используйте обычный клон, а затем git worktree add, которые вы собираетесь сделать. Вы заметили в комментарий, что:

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

Для этого есть несколько простых обходных путей:

  • Выберите одно из N рабочих деревьев, которые вы добавите, и используйте его как ветвь в основном рабочем дереве:

    git checkout -b branch-1 ssh://[email protected]/project/repo branch-1
    

    Недостаток в том, что теперь у вас есть специальная выделенная «основная» ветвь, которую вы не можете просто удалить в любое время — от нее зависят все остальные ветки.

  • Или после клона используйте git checkout --detach в рабочем дереве, чтобы получить отдельный HEAD на ветке по умолчанию:

    git clone ssh://[email protected]/project/repo repo.git
    cd repo.git
    git checkout --detach
    
  • Единственным недостатком этого второго подхода является то, что рабочее дерево заполнено файлами, что, вероятно, является пустой тратой места. Для этого также есть решение: создайте пустой коммит, используя пустое дерево, и проверьте его:

    git clone ssh://[email protected]/project/repo repo.git
    cd repo.git
    git checkout $(git commit-tree $(git hash-object -t tree /dev/null) < /dev/null)
    

Хорошо, последнее не совсем очевидно. Хотя на самом деле это довольно просто. git hash-object -t tree /dev/null создает хэш-идентификатор пустого дерева, которое уже существует в каждом репозитории. git commit-tree делает фиксацию, чтобы обернуть это пустое дерево — его нет, поэтому мы должны сделать его, чтобы проверить его — и распечатывает хэш-идентификатор этой новой фиксации, а git checkout проверяет это как отсоединенный HEAD. Эффект состоит в том, чтобы очистить наш индекс и рабочее дерево, так что единственной вещью в рабочем дереве репозитория является каталог .git. Пустой коммит, который мы сделали, не находится ни в какой ветке, и у него нет родительского коммита (это единственный корневой коммит), который мы никогда никуда не отправим.


1Причина этого в том, что в Git 2.5, где впервые появился git worktree, есть ошибка, которую я считаю очень серьезной: git gc никогда не сканирует файлы HEAD добавленных рабочих деревьев, а также их индексные файлы. Если добавленные рабочие деревья всегда находятся в какой-то ветке и никогда не имеют git added, но незафиксированной работы, это никогда не вызывает никаких проблем. Если незафиксированная работа не задерживается в течение как минимум 14 дней, времени защиты от сокращения по умолчанию достаточно, чтобы предотвратить ее уничтожение. Но если вы git add немного поработаете или зафиксируете на отдельном HEAD, отправитесь в отпуск на месяц или иным образом оставьте это добавленное рабочее дерево нетронутым, а затем вернетесь к нему, и git gc --auto побежал после этих двух - недельный льготный период истек, сохраненные вами файлы были уничтожены!

Эта ошибка была исправлена ​​в Git 2.15.


Почему --bare идет не так

Корень проблемы здесь в том, что git clone --bare делает две вещи:

  • он создает голый репозиторий (core.bare установлен на true), который не имеет рабочего дерева и не выполняет начального git checkout; и
  • он изменяет стандартную спецификацию fetch с +refs/heads/*:refs/remotes/origin/* на +refs/heads/*:refs/heads/*.

Этот второй пункт означает, что refs/remotes/origin/ имен нет, как вы обнаружили. Это нелегко исправить из-за (в основном скрытой/внутренней) концепции refmaps, которые очень кратко упоминаются в документации git fetch (см. ссылку).

Хуже того, это означает, что refs/heads/* будет обновляться каждые git fetch. Есть причина, по которой git worktree add отказывается создавать второе рабочее дерево, ссылающееся на ту же ветку, которая проверена в любом существующем рабочем дереве, и заключается в том, что Git принципиально предполагает, что никто не будет связываться с ссылка refs/heads/name, к которой прикреплен HEAD в этом рабочем дереве. Таким образом, даже если вы решили проблему с refmap, когда вы запускаете git fetch и он обновляет — или даже удаляет, из-за --prune и удаления того же имени в восходящем потоке — имя refs/heads/name, разрывы HEAD добавленного рабочего дерева и работу -дерево само по себе становится проблематичным. (См. раздел Почему Git разрешает отправку в проверенную ветку в добавленном рабочем дереве? Как восстановить?)

Есть еще одна вещь, которую вы могли бы попробовать, которую я вообще не проверял, а именно: сделать голое клонирование, как вы делаете, но затем изменить refspec и выполнить повторную выборку, а также удалить все существующие имена ветвей, так как они не имеют установленных восходящих потоков (или, что то же самое, устанавливают свои восходящие потоки):

git clone --bare ssh://[email protected]/project/repo repo.git
cd repo.git
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch
git for-each-ref --format='%(refname:short)' refs/heads | xargs git branch -d

(или замените xargs на xargs -n1 -I{} git branch --set-upstream-to=origin/{} {}).

person torek    schedule 28.01.2019
comment
Мне нравится ваш третий вариант, но знаете ли вы эквивалент этой фиксации в Windows (с использованием /dev/null)? Я запустил его с помощью git bash, но я разрабатываю среду сборки для разработчика Windows и автоматизирую эти команды с помощью пакетных файлов. - person rhaben; 01.10.2020
comment
Я не знаю, как вы имитируете /dev/null в Windows. Древний DOS имел NUL: как и CON: и тому подобное (я не уверен, правильно ли я пишу это), возможно, это работает; в любом случае, мне сказали, что в WIndows есть что-то, что действительно работает. - person torek; 02.10.2020
comment
Я пробовал НУЛ. Не работает. Я могу обойти это. Я просто удивился, когда не смог запустить команду, подменяющую NUL. - person rhaben; 03.10.2020

Я долго боролся с этим, никогда не вспоминая шаги, которые я предпринял для создания голого репо, которое не действовало как таковое. В конце концов я написал следующий скрипт под названием git-clone-bare-for-worktrees для создания чистых репозиториев для использования с рабочими деревьями. Кажется, он работает довольно хорошо, но имейте в виду, что он не выполняет много обработки ошибок.

#! /bin/env bash
set -e

url=$1
name=${url##*/}

git init --bare "${name}"
cd "${name}"
git config remote.origin.url "$url"
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
git fetch

firstCommit=$(git rev-list --all --max-parents=0 --date-order --reverse | head -n1)
git branch bare-dummy $firstCommit
git symbolic-ref HEAD refs/heads/bare-dummy

Он создает ветку с именем bare-dummy, указывающую на первую фиксацию в репозитории, и устанавливает для нее значение HEAD, гарантируя, что все реальные ветки доступны для безопасного извлечения в рабочих деревьях. Кроме этого, репозиторий не будет содержать никаких локальных веток, даже master, но удаленные ветки отслеживания будут созданы точно так же, как и в обычном, не голом клоне. Так что просто запустите быстрый git worktree add ../master-worktree master, и вы должны быть готовы к работе.

person Parker Coates    schedule 22.06.2020
comment
Это интересно и полезно. Лично я создал сценарий git-make-bare, который выполняет третий пункт @torek в принятом ответе. Мой рабочий процесс при клонировании тогда git clone <repo>; cd repo; git make-bare; git worktree add master master. - person Tom; 24.06.2020
comment
@tom, что ты делаешь с HEAD в голом репо? Мой метод создания фиктивной ветки из первого коммита довольно хромой, но это лучшее, что я нашел до сих пор, чтобы не дать голому репо захватить ветку. - person Parker Coates; 24.06.2020
comment
Мой git-make-bare в основном делает это: git checkout $(git commit-tree $(git hash-object -t tree /dev/null) < /dev/null) - person Tom; 25.06.2020
comment
@tom, я помню, как пытался использовать отдельный HEAD в голом репо, но, если я правильно помню, некоторые операции рабочего дерева жаловались, если это было так (хотя я не мог понять, зачем операции нужна ветвь). Это было бы с гораздо более ранней версией git worktree, поэтому мне, вероятно, следует попробовать еще раз. - person Parker Coates; 25.06.2020
comment
У меня работает, но тогда я использую только add и prune. - person Tom; 25.06.2020