Git голые репозитории, рабочие деревья и отслеживание веток - PullRequest
0 голосов
/ 25 января 2019

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

git clone --bare ssh://git@git.example.com/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.

Какая магия, чтобы заставить это работать?

1 Ответ

0 голосов
/ 28 января 2019

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

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

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

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

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

    git checkout -b branch-1 ssh://git@git.example.com/project/repo branch-1
    

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

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

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

    git clone ssh://git@git.example.com/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 проверяет это какОТДЕЛЬНАЯ ГОЛОВА.В результате мы освобождаем наш индекс и рабочее дерево, так что единственное, что есть в рабочем дереве хранилища - это каталог .git.Пустой коммит, который мы сделали, не принадлежит ни одной ветке и не имеет родительского коммита (это однокорневой коммит), который мы никогда нигде не протолкнем.В Git 2.5, где впервые появился git worktree, есть ошибка, которую я считаю очень плохой: git gc никогда не сканирует добавленные рабочие деревья HEAD и их индексные файлы.Если добавленные рабочие деревья всегда находятся в некоторой ветке и никогда не имеют git add редактируемой, но незафиксированной работы, это никогда не вызывает никаких проблем.Если незавершенная работа не выполняется в течение по крайней мере 14 дней, для защиты от удаления из нее достаточно времени защиты по умолчанию.Но если вы git add выполняете какую-то работу или делаете коммит на отдельном HEAD, уходите в отпуск на месяц или иным образом оставляете это добавленное рабочее дерево без изменений, а затем возвращаетесь к нему, и a git gc --auto побежалипо истечении этого двухнедельного льготного периода сохраненные вами файлы были уничтожены!

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


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

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

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

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

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

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

git clone --bare ssh://git@git.example.com/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/{} {}).

...