Как добавить прошлое в репозиторий git?
Я получил некоторый исходный код и решил использовать для него git, так как мой коллега использовал mkdir $VERSION
и т. д. подход. Хотя прошлое кода в настоящее время кажется неважным, я все же хотел бы поставить его под контроль git, чтобы лучше понять процесс разработки. Так:
Какой удобный способ поместить эти прошлые версии в мое уже существующее git-репо? В настоящее время удаленного репо нет, поэтому я не возражаю переписывать историю, но решение, которое учитывает удаленные репозитории, будет, конечно, предпочтительным, если только тогда оно не будет намного более скопированным. Бонусные баллы за сценарий, который больше не нуждается в взаимодействии на основе либо каталога, либо истории архива.
4 ответа
Для импорта старых снимков вы найдете некоторые инструменты из каталога contrib / fast-import в Git. Или, если у вас уже есть каждый старый снимок в каталоге, вы можете сделать что-то вроде этого:
# Assumes the v* glob will sort in the right order
# (i.e. zero padded, fixed width numeric fields)
# For v1, v2, v10, v11, ... you might try:
# v{1..23} (1 through 23)
# v?{,?} (v+one character, then v+two characters)
# v?{,?{,?}} (v+{one,two,three} characters)
# $(ls -v v*) (GNU ls has "version sorting")
# Or, just list them directly: ``for d in foo bar baz quux; do''
(git init import)
for d in v*; do
if mv import/.git "$d/"; then
(cd "$d" && git add --all && git commit -m"pre-Git snapshot $d")
mv "$d/.git" import/
fi
done
(cd import && git checkout HEAD -- .)
Затем загрузите старую историю в ваш рабочий репозиторий:
cd work && git fetch ../import master:old-history
Если у вас есть и старая история, и ваша история на основе Git в одном и том же репозитории, у вас есть несколько вариантов операции prepend: пересадки и замены.
Трансплантаты являются механизмом репозитория для (возможно, временного) редактирования происхождения различных существующих коммитов. Трансплантаты контролируются $GIT_DIR/info/grafts
файл (описан в разделе "информация / трансплантаты" справочной страницы макета gitrepository).
INITIAL_SHA1=$(git rev-list --reverse master | head -1)
TIP_OF_OLD_HISTORY_SHA1=$(git rev-parse old-history)
echo $INITIAL_SHA1 $TIP_OF_OLD_HISTORY_SHA1 >> .git/info/grafts
С трансплантатом на месте (у исходного начального коммита не было родителей, у него был один родитель), вы можете использовать все обычные инструменты Git для поиска и просмотра расширенной истории (например, git log
теперь должен показать вам старую историю после ваших коммитов).
Основная проблема с трансплантатами заключается в том, что они ограничены вашим хранилищем. Но, если вы решите, что они должны быть постоянной частью истории, вы можете использовать git filter-branch для их создания (сделайте резервную копию tar/zip вашего .git
режиссёр первый; git filter-branch сохранит оригинальные ссылки, но иногда проще использовать простую резервную копию).
git filter-branch --tag-name-filter cat -- --all
rm .git/info/grafts
Механизм замены более новый (Git 1.6.5 +), но их можно отключить для каждой команды (git --no-replace-objects …
) и они могут подтолкнуть для облегчения обмена. Замена работает с отдельными объектами (BLOB-объектами, деревьями, коммитами или аннотированными тегами), поэтому механизм также является более общим. Механизм замены описан на странице руководства git replace. Из-за общности, "предварительная" настройка немного сложнее (нам нужно создать новый коммит, а не просто назвать нового родителя):
# the last commit of old history branch
oldhead=$(git rev-parse --verify old-history)
# the initial commit of current branch
newinit=$(git rev-list master | tail -n 1)
# create a fake commit based on $newinit, but with a parent
# (note: at this point, $oldhead must be a full commit ID)
newfake=$(git cat-file commit "$newinit" \
| sed "/^tree [0-9a-f]\+\$/aparent $oldhead" \
| git hash-object -t commit -w --stdin)
# replace the initial commit with the fake one
git replace -f "$newinit" "$newfake"
Разделение этой замены не является автоматическим. Вы должны выдвинуть часть (или все) refs/replace
поделиться заменой.
git push some-remote 'refs/replace/*'
Если вы решили сделать замену постоянной, используйте git filter-branch (как и в случае с трансплантатами; сделайте резервную копию tar/zip вашего .git
каталог первый):
git filter-branch --tag-name-filter cat -- --all
git replace -d $INITIAL_SHA1
Если вы не хотите изменять коммиты в своем хранилище, вы можете использовать трансплантаты для переопределения родительской информации для коммита. Это то, что делает репозиторий Linux Kernel, чтобы получить историю до того, как они начали использовать Git.
Это сообщение: http://marc.info/?l=git&m=119636089519572 похоже, содержит лучшую документацию, которую я могу найти.
Вы должны создать последовательность коммитов, относящуюся к вашей истории pre-git, а затем использовать .git/info/grafts
файл, чтобы Git использовал последний коммит в этой последовательности в качестве родителя первого коммита, который вы сгенерировали с помощью Git.
Самым простым подходом, конечно же, является создание нового git-репо, коммит истории, чтобы предварительно добавить его, а затем повторное применение патчей старого репо. Но я бы предпочел решение, которое отнимает меньше времени за счет автоматизации.
Если вы просто хотите навсегда объединить 2 репозитория, лучшее решение - экспортировать все коммиты из второго репозитория (кроме первоначального коммита, который создал репозиторий как продолжение другого).
Я думаю, что это лучше, потому что когда вы делаете шаги, как объяснено Chris Johnsen, он преобразует ваш первоначальный коммит во второй репозиторий как коммит удаления, который удаляет несколько файлов. И если вы попытаетесь пропустить первоначальный коммит, он преобразует ваш второй коммит в коммит, который удаляет все файлы (конечно, мне пришлось попробовать его). Я не уверен, как это влияет на способность Git отслеживать историю файлов в команде, как git log --follow -- file/name.txt
Вы можете экспортировать всю историю (за исключением первого коммита, который уже присутствует в первом репозитории) вашего второго репозитория и импортировать его в первый репозиторий, выполнив следующие команды:
- Откройте командную строку Linux в вашем втором хранилище (чтобы экспортировать последние коммиты)
commit_count=$(git rev-list HEAD --count)
git format-patch --full-index -$(($commit_count - 1))
- Переместить все патчи git
.patch
файлы, созданные в корне вашего второго хранилища, в новый каталог с именемpatches
на стороне вашего первого корневого каталога хранилища - Теперь откройте командную строку Linux в вашем первом хранилище (чтобы импортировать последние коммиты)
git am ../patches/*.patch
- Если у вас возникли проблемы при применении git-патчей, запустите
git am --abort
затем посмотрите git: patch не применяется и попробуйте что-то вродеgit am ../patches/*.patch --ignore-space-change --ignore-whitespace
как предложено в связанном вопросе Stackru.
В качестве альтернативы использованию git
из командной строки вы можете использовать интерфейс git, такой как SmartGit или GitExtensions
Рекомендации:
- https://www.ivankristianto.com/create-patch-files-from-multiple-commits-in-git/
- Git: Как создать патчи для слияния?
- https://www.ivankristianto.com/create-patch-files-from-multiple-commits-in-git/
- как применить несколько патчей мерзавца в один выстрел
- https://davidwalsh.name/git-export-patch
Для полноты здесь я представляю автоматический сценарий оболочки, который следует за действиями Chris Johnsen, чтобы окончательно объединить 2 репозитория. Вам нужно запустить это в первом репозитории, где вы хотели бы интегрировать историю из второго репозитория, которая продолжает историю из первого репозитория. После нескольких часов экспериментов я обнаружил, что это лучший подход. Если вы знаете, как что-то улучшить, исправьте / поделитесь / прокомментируйте.
Пожалуйста, полностью сделайте резервную копию вашего первого и второго репозиториев в .zip
файл до запуска этого.
old_history=master
new_history=master-temp
old_remote_name=deathaxe
old_remote_url=second_remote_url
git remote add $old_remote_name $old_remote_url
git fetch $old_remote_name
git branch --no-track $new_history refs/remotes/$old_remote_name/$old_history
git branch --set-upstream-to=origin/$old_history $new_history
# the last commit of old history branch
oldhead=$(git rev-parse --verify $old_history)
# the initial commit of current branch
# newinit=$(git rev-list $new_history | tail -n 2 | head -n -1)
newinit=$(git rev-list $new_history | tail -n 1)
# create a fake commit based on $newinit, but with a parent
# (note: at this point, $oldhead must be a full commit ID)
newfake=$(git cat-file commit "$newinit" \
| sed "/^tree [0-9a-f]\+\$/aparent $oldhead" \
| git hash-object -t commit -w --stdin)
# replace the initial commit with the fake one
# git replace <last commit> <first commit>
# git replace <object> <replacement>
git replace -f "$newinit" "$newfake"
# If you decide to make the replacement permanent, use git filter-branch
# (make a tar/zip backup of your .git directory first)
git filter-branch --tag-name-filter cat -- --all
git replace -d $newinit
git push -f --tags
git push -f origin $new_history
git checkout $old_history
git branch -d $new_history
git pull --rebase
Рекомендации: