Как добавить прошлое в репозиторий git?

Я получил некоторый исходный код и решил использовать git для него, так как мой сотрудник использовал подход mkdir $VERSION и т.д. Хотя прошлое кода в настоящее время кажется несущественным, я все же хотел бы поместить его под управлением git, чтобы лучше понять процесс разработки. Итак:

Каков удобный способ разместить эти прошлые версии в уже существующем репозитории git? В настоящее время нет удаленного репо, поэтому я не возражаю переписывать историю, но решение, которое учитывает удаленные репозитории, будет, конечно, предпочтительным, если только оно не будет намного более сложным. Бонусные баллы для script, которые не нуждаются в более активном взаимодействии на основе каталога или истории архивного файла.

Ответ 1

Для импорта старых снимков вы найдете полезные инструменты в Git contrib/fast-import directory. Или, если у вас уже есть каждый старый снимок в каталоге, вы можете сделать что-то вроде этого:

# 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 в том же репозитории, у вас есть пара опций для операции preend: трансплантаты и замены.

Графики являются механизмом per-repository для (возможно, временно) редактирования происхождения различных существующих коммитов. Графики управляются файлом $GIT_DIR/info/grafts (описанным в разделе "info/grafts" gitrepository-layout manpage).

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, чтобы сделать их такими (сначала создайте резервную копию tar/zip для вашего .git dir, Git filter-branch будет сохранять исходные ссылки, но иногда проще использовать простое резервное копирование).

git filter-branch --tag-name-filter cat -- --all
rm .git/info/grafts

Механизм замены более новый (Git 1.6.5 +), но их можно отключить для каждой команды (git --no-replace-objects …), и они могут продвигаться для более простого обмена. Работы по замене отдельных объектов (капли, деревья, коммиты или аннотированные теги), поэтому механизм также более общий. Механизм замены описан в Git заменить manpage. Из-за общности, настройка "prepending" немного более сложна (мы должны создать новую фиксацию вместо того, чтобы просто называть нового родителя):

# 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 (то же самое, что и с трансплантатами, сначала создайте резервную копию tar/zip вашего каталога .git):

git filter-branch --tag-name-filter cat -- --all
git replace -d $INITIAL_SHA1

Ответ 2

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

Это сообщение: http://marc.info/?l=git&m=119636089519572, похоже, имеет лучшую документацию, которую я могу найти.

Вы создадите последовательность фиксаций, относящихся к вашей предыстории git, затем используйте файл .git/info/grafts, чтобы сделать Git использование последнего коммита в этой последовательности в качестве родителя первого коммита, сгенерированного с использованием Git.

Ответ 3

Самый простой подход - это, конечно, создание нового репозитория git, связанного с предысторией истории, а затем повторным применением патчей старого репо. Но я бы предпочел решение, которое требует меньше времени на автоматизацию.

Ответ 4

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

Я думаю, что это лучше, потому что когда вы делаете шаги, как объяснено Крисом Джонсеном, он преобразует ваш первоначальный коммит во второй репозиторий как коммит удаления, который удаляет несколько файлов. И если вы попытаетесь пропустить первоначальный коммит, он преобразует ваш второй коммит в коммит, который удаляет все файлы (конечно, мне пришлось попробовать его). Я не уверен, как это влияет на способность git отслеживать историю файлов в команде как git log --follow -- file/name.txt

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

  1. Откройте командную строку Linux в вашем втором хранилище (для экспорта последних коммитов)
  2. commit_count=$(git rev-list HEAD --count)
  3. git format-patch --full-index -$(($commit_count - 1))
  4. Переместите все файлы .patch git patches, созданные в корне вашего второго репозитория, в новый каталог, называемый patches на стороне корневого каталога вашего первого репозитория.
  5. Теперь откройте командную строку Linux в вашем первом хранилище (чтобы импортировать последние коммиты)
  6. git am../patches/*.patch
  7. Если у вас возникли проблемы при применении патчей git, запустите git am --abort, затем посмотрите, что git: patch не применяется, и попробуйте что-то вроде git am../patches/*.patch --ignore-space-change --ignore-whitespace как предлагается в связанном вопросе StackOverflow.

В качестве альтернативы использованию git из командной строки, вы можете использовать интерфейс git, такой как SmartGit или GitExtensions

Рекомендации:

  1. https://www.ivankristianto.com/create-patch-files-from-multiple-commits-in-git/
  2. Git: Как создать патчи для слияния?
  3. https://www.ivankristianto.com/create-patch-files-from-multiple-commits-in-git/
  4. как применить несколько патчей мерзавца в один выстрел
  5. https://davidwalsh.name/git-export-patch

Для полноты здесь я представляю автоматический сценарий оболочки, который следует за действиями Криса Джонсена, чтобы окончательно объединить 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

Рекомендации:

  1. https://feeding.cloud.geek.nz/posts/combining-multiple-commits-into-one/
  2. https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-replace.html
  3. Удалить последнюю строку из файла в Bash
  4. Принудительно "git push" перезаписывать удаленные файлы
  5. Git force push tag, когда метка уже существует на пульте