Как переустанавливать репозиторий git в родительскую папку при сохранении истории?
У меня есть git-репо в /foo/bar
с большой историей коммитов и несколькими ветками.
Теперь я хочу, чтобы /foo/baz
находился в том же репо, что и /foo/bar
, что (я думаю) означает, что мне нужно создать новый репо в /foo
. Тем не менее, я хочу сохранить историю изменений, которые я внес в /foo/bar
.
Сначала я подумал о git format-patch, а затем о apply, но сообщения коммитов не сохранились.
Редактировать: мой вопрос не является обманом этого вопроса, потому что мой специально о сохранении истории, и ответ здесь включает в себя дополнительные сведения о том, как сохранить историю, и дополнительные сведения о том, как выполнить более сложную работу по воспитанию детей на произвольно более высоком уровне, а не чем один раз.
Ответы
Ответ 1
Вы хотите git filter-branch
, который может перемещать весь репозиторий в поддерево, сохраняя историю, заставляя его выглядеть так, как будто это всегда было так. Создайте резервную копию своего хранилища, прежде чем использовать это!
Здесь волшебство. В /foo/bar
запустите:
git filter-branch --commit-filter '
TREE="$1";
shift;
SUBTREE=`echo -e 040000 tree $TREE"\tbar" | git mktree`
git commit-tree $SUBTREE "[email protected]"' -- --all
Это сделает репозиторий /foo/bar
еще одним поддиректорией "bar" со всем содержимым на протяжении всей своей истории. Затем вы можете переместить все репо до уровня foo
и добавить к нему код baz
.
Update:
Хорошо, вот что происходит. Конец - это ссылка на "дерево" (подумайте об этом как о SHA, представляющем содержимое подкаталога всей файловой системы) плюс некоторые "родительские" SHA и некоторые авторы/сообщения ссылки метаданных/и т.д. Команда git commit-tree
- это бит низкого уровня, который объединяет все это вместе. Параметр --commit-filter
обрабатывается как функция оболочки и запускается вместо git commit-tree
во время процесса фильтрации и должен действовать как он.
Что я делаю, это взять первый параметр, исходное дерево для фиксации и создать новый "древовидный объект", который говорит об этом во вложенной папке с помощью git mktree
, другой низкоуровневой команды git. Для этого мне нужно вложить в него что-то похожее на дерево git, то есть набор (тип SP типа SP SHA TAB filename) строк; таким образом, команда эха. Выходной сигнал mktree
затем заменяется первым параметром, когда I-цепь до вещественной commit-tree
; "[email protected]"
- способ передать все остальные параметры без изменений, разделив первый на shift
. Для информации см. git help mktree
и git help commit-tree
.
Итак, если вам нужно несколько уровней, вам нужно вложить несколько дополнительных уровней древовидных объектов (это не проверено, а общая идея):
git filter-branch --commit-filter '
TREE="$1"
shift
SUBTREE1=`echo -e 040000 tree $TREE"\tbar" | git mktree`
SUBTREE2=`echo -e 040000 tree $SUBTREE1"\tb" | git mktree`
SUBTREE3=`echo -e 040000 tree $SUBTREE2"\ta" | git mktree`
git commit-tree $SUBTREE3 "[email protected]"' -- --all
Это должно переместить реальное содержимое вниз на a/b/bar
(обратите внимание на обратный порядок).
Обновление. Интегрированные улучшения. Матвей Алперт ниже. Без -- --all
это работает только в текущей проверенной ветке, но поскольку вопрос задает вопрос об общем репо, имеет смысл делать это так, как разветвление.
Ответ 2
Вместо создания нового репозитория переместите то, что в вашем текущем репозитории, в нужное место: создайте новый каталог bar
в вашем текущем каталоге и переместите текущий контент (так что ваш код находится в /foo/bar/bar
). Затем создайте каталог baz
рядом с вашим новым каталогом bar
(/foo/bar/baz
). mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2
и вы закончили:).
Git Отслеживание переименования означает, что ваша история все равно будет работать, а Git хеширование содержимого означает, что даже если вы перемещаете вещи, вы все равно ссылаетесь на одни и те же объекты в репозитории.
Ответ 3
У меня было решение, о котором никто пока не сказал:
Мне особенно нужно было включить файлы из родительского каталога в мой репозиторий (эффективно перемещая репо на один каталог).
Я достиг этого:
- переместить все файлы (кроме .git) в новый подкаталог с тем же именем. И сообщите git об этом (с
git mv
)
- переместите все файлы из родительского каталога в пустую (за исключением .git/) текущую директорию и сообщите git об этом (с помощью
git add
)
- передать все это в репо, которое не переместило (
git commit
).
- переместить текущий каталог на один уровень в иерархии каталогов. (с командной строки jiggery-pokery)
Я надеюсь, что это поможет следующему парню прийти - у меня, наверное, просто безмозглый день, но я нашел ответы выше чрезмерно сложными и страшными (для чего нужен I). Я знаю, что это похоже на ответ Эндрю Айлетта выше, но моя ситуация казалась немного иной, и мне хотелось получить более общий вид.
Ответ 4
Это добавляет принятый ответ Уолтера Мундта. Я бы скорее прокомментировал его ответ, но у меня нет репутации.
Итак, метод Уолтера Мундта отлично работает, но он работает только для одной ветки за раз. И после первой ветки могут быть предупреждения, требующие -f для принудительного действия. Чтобы сделать это для всех ветвей сразу, просто добавьте "- --all" в конец:
git filter-branch --commit-filter '
tree="$1";
shift;
subtree=`echo -e 040000 tree $tree"\tsrc" | git mktree`
git commit-tree $subtree "[email protected]"' -- --all
И чтобы сделать это для определенных ветвей, вместо этого добавьте их имена в конец, хотя я не могу себе представить, почему вы изменили структуру каталогов только некоторых ветвей.
Подробнее об этом читайте на странице руководства для ветки фильтра git. Однако, пожалуйста, обратите внимание на предупреждение о возможных трудностях при нажатии такой команды. Просто убедитесь, что вы знаете, что делаете.
Я хотел бы получить больше информации о любых потенциальных проблемах с этим методом.
Ответ 5
Наиболее распространенное решение
В большинстве нормальных ситуаций git просматривает все файлы относительно своего местоположения (что означает каталог .git), в отличие от использования абсолютных путей к файлам.
Таким образом, если вы не против иметь фиксацию в своей истории, которая показывает, что вы все переместили, существует очень простое решение, которое заключается в перемещении каталога git. Единственное немного сложная задача - убедиться, что git понимает, что файлы одинаковы и что они только перемещаются относительно него:
# Create sub-directory with the same name in /foo/bar
mkdir bar
# Move everything down, notifying git :
git mv file1 file2 file3 bar/
# Then move everything up one level :
mv .git ../.git
mv bar/* .
mv .gitignore ../
# Here, take care to move untracked files
# Then delete unused directory
rmdir bar
# and commit
cd ../
git commit
Единственное, что нужно быть осторожным, - это правильно обновить .gitignore при переходе в новый каталог, чтобы избежать размещения нежелательных файлов или забыть некоторые.
Бонусное решение
В некоторых настройках git удается самостоятельно выяснить, что файлы были перемещены, когда он видит новые файлы, которые точно такие же, как и удаленные. В этом случае решение еще проще:
mv .git ../.git
mv .gitignore ../.gitignore
cd ../
git commit
Снова, будьте осторожны с вашим .gitignore
Ответ 6
1 дополнение к принятому ответу, который помог мне заставить его работать:
когда я помещаю указанный текст в оболочку script, по какой-то причине -e сохраняется.
(вполне вероятно, потому что я слишком толстый, чтобы работать со сценариями оболочки)
когда я удалил -e и переместил кавычки, чтобы охватить все, он сработал.
SUBTREE2 = echo "040000 tree $SUBTREE1 modules" | git mktree
Обратите внимание, что есть вкладка между $SUBTREE1 и модулями, которая является той же \t, что -e должна интерпретировать.
Ответ 7
Вы можете создать репозиторий git в foo
и указать как baz
, так и bar
через git submodules
.
Тогда оба bar
и baz
сосуществуют с сохранением их полной истории.
Если вам действительно нужно только одно репо (foo), в нем есть как история бара, так и история baz, тогда метод трансплантатов или стратегия слияния подделок находятся в порядке.