Как перемещать файлы из одного репозитория git в другую историю сохранения, используя `git format-patch` и` git am`

Проблема

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

Я нашел один подход к SE: Как перенести файлы из одного репозитория git в другой (не клон), сохраняя историю. И другая идея на blog.neutrino.es. Это последнее, что я хотел бы обсудить здесь.

Попытка решения

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

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

Ошибка

Я получаю это сообщение об ошибке при выполнении git am /tmp/mergepatchs/*.patch:

Applying: Initial commit
error: .gitignore: already exists in index
error: README.md: already exists in index
Patch failed at 0001 Initial commit
The copy of the patch that failed is found in:
   /Users/myuser/repo/org/.git/rebase-apply/patch
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".

Чтобы лучше понять процесс, я сначала попробовал один файл (а не весь каталог). Однако "ничего" не произошло после git am (т.е. Никакого нового файла, git status не сообщает о каких-либо изменениях). Почему это?

Затем я попытался:

INITCOMMIT=$(git rev-list --parents HEAD | egrep "^[a-f0-9]{40}$")
git format-patch -1 -o /tmp/mergepatchs ${INITCOMMIT}

Но затем получилось такое же сообщение об ошибке, что и раньше.

Почему патчи не работают?


Изменить 1

Я пробовал что-то связанное, вдохновленное Как создать и применить патч с помощью Git.

В ~/repo/org:

$ git format-patch --root HEAD --stdout myfile.c > /tmp/mergepaths/01.patch

В ~/depo/dest:

$ git apply --stat /tmp/mergepaths/01.patch 
 0 files changed
$ git apply --check /tmp/mergepaths/01.patch 
$ git am < /tmp/mergepaths/01.patch 

Оба stat и check говорят мне, что ничего не будет сделано. Объект патча далеко не пуст. Кстати, я не знаю, насколько это актуально, но создание и применение патчей выполняется в ветких.

Ответ 1

Там есть инструмент для операции, git filter-branch.

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

# make a throwaway sandbox to play in:
git clone -s . /tmp/deleteme
cd !$
git checkout -b sliced

# do it
git filter-branch --subdirectory-filter your/subdir

# and if the result looks good:
git push origin sliced

документы с фильтрами

и вы можете нажать на любое репо, у вас есть URL-адрес или путь, просто используйте url напрямую, а не создавайте удаленное имя для двух-двух работ. Чтобы нажать и переименовать: git push u://r/l sliced:branchnameinthatrepo


Из комментариев вы хотите выбрать и переместить подкаталог. Это довольно простая работа git read-tree. Read-tree работает с индексом, поэтому для него требуется индексный фильтр. Для этого это:

git filter-branch --index-filter '
        git read-tree --prefix=des/ti/nation/    $GIT_COMMIT:source/subdir
        git read-tree -m                         $GIT_COMMIT    `git mktree </dev/null`
'

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

В качестве напоминания или повторения или ввода в зависимости от обстоятельств:

  • Собственный репозиторий - это хранилище объектов: запрашивайте что-либо по типу и уникальному имени (aka SHA1), репо обязывает его срывать; попросите репо запомнить какой-либо контент, вы его пишете и байты, и он (a) сохраняет это и (b) передает вам уникальное имя.

  • Индекс представляет собой список, сопоставляющий имена путей с содержимым репозитория.

  • git read-tree является основной операцией для проверки и слияния и reset - на самом деле она не работает на репо, все объекты, с которыми она работает, уже существуют. Вы кормите его существующими деревьями, он объединяет их с тем, что в индексе (и, возможно, обновляет рабочую строку, хотя это и не имеет значения здесь) для создания нужного вам индекса или, по крайней мере, приближает вас к нему.

Первое дерево чтения выше

    git read-tree --prefix=destination/subdir/ $GIT_COMMIT:source/subdir

и вы можете определить фундаментальный характер того, что будет делать git read-tree, посчитав, сколько деревьев вы дали. Это однообразное чтение, используемое здесь для добавления к индексу (edit: btw без параметров, 1-tree git read-tree заменяет индекс). Дерево, добавляемое здесь, находится в source/subdir в фильтре фиксации, а read-tree добавляет все это к индексу с destination/subdir/, добавленным к началу путей.

Следующее read-tree - это двухъядерное чтение, оно делает индекс (и worktree, если он был там, который вы хотели бы сделать здесь) работает для git checkout - он применяет различия между исходным деревом и целевое дерево к индексу. Здесь исходное дерево $GIT_COMMIT, а целевое дерево git mktree </dev/null - пустое дерево. Таким образом, операция "найти все в исходном дереве в индексе и сделать все эти записи похожими на целевое дерево" ака здесь "заставляет всех уйти". Добавленный выше subdir назначения не задействован, он не был в исходном дереве, поэтому read-tree не заставляет это уходить.

Сделанный фильтр, ветвь фильтра фиксирует то, что в новом индексе (содержимого уже в репо, помните), и это время для следующего фиксации.

документы для чтения-дерева. Эта ссылка пропускает описание, которое преднамеренно. Не читайте.

Ответ 2

Похоже, проблема связана с тем, что определенные файлы существуют в обоих хранилищах, которые вы пытаетесь создать из патча. В вашем примере это README.md и .gitignore

При применении патча с помощью git -apply вы можете игнорировать эти файлы с помощью --exclude=<path-pattern>. См. http://git-scm.com/docs/git-apply