Как переместить файлы из одного git-репо в другой (не клон), сохранив историю


484

Наши Git-репозитории начинались как части одного монстра SVN-репозитория, где у каждого проекта было свое дерево, например:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Очевидно, что было довольно легко перемещать файлы с одного на другой svn mv. Но в Git каждый проект находится в своем собственном репозитории, и сегодня меня попросили переместить подкаталог из project2в project1. Я сделал что-то вроде этого:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

Но это кажется довольно запутанным. Есть ли лучший способ сделать такое вообще? Или я принял правильный подход?

Обратите внимание, что это включает в себя объединение истории в существующее хранилище, а не просто создание нового автономного хранилища из части другого ( как в предыдущем вопросе ).


1
Это звучит как разумный подход ко мне; Я не могу придумать какой-либо очевидный способ значительно улучшить ваш метод. Приятно, что Git действительно делает это легко (я не хотел бы пытаться перемещать каталог файлов между различными репозиториями в Subversion, например).
Грег Хьюгилл,

1
@ebneter - я сделал это (перенес историю из одного репозитория SVN в другое) вручную, используя сценарии оболочки. В основном я воспроизводил историю (diffs, commit logs messages) из определенных файлов / каталогов во второй репозиторий.
Адам Монсен

1
Интересно, почему ты не делаешь git fetch p2 && git merge p2вместо git fetch p2 && git branch .. && git merge p2? Изменить: хорошо, похоже, вы хотите получить изменения в новой ветви с именем p2, а не текущей ветви.
Лекенштейн

1
Нет ли способа предотвратить разрушение структуры каталогов --filter-branch? Этот шаг "git mv" приводит к массивному коммиту, полному удалений и созданий файлов.
Эдвард Фальк,

1
Обратите внимание, что с git 2.9 объединение несвязанных историй по умолчанию запрещено. Чтобы заставить это работать, добавьте --allow-unrelated-historiesк последнему, git mergeчтобы это работало.
Скотт Берревоц

Ответы:


55

Да, попав на --subdirectory-filterиз filter-branchбыло ключевым. Тот факт, что вы использовали его, по существу доказывает, что нет более простого способа - у вас не было выбора, кроме как переписать историю, поскольку вы хотели получить только (переименованное) подмножество файлов, и это по определению меняет хэши. Поскольку ни одна из стандартных команд (например pull) не переписывает историю, вы не можете использовать их для этого.

Конечно, вы могли бы уточнить детали - некоторые из ваших клонов и ветвлений не были строго необходимы - но общий подход хорош! Обидно, что это сложно, но, конечно, смысл Git не в том, чтобы упростить переписывание истории.


1
Что делать, если ваш файл прошел через несколько каталогов, а теперь находится в одном - будет ли работать фильтр подкаталогов? (т.е. я предполагаю, что если я просто хочу переместить один файл, я могу переместить его в свой собственный подкаталог, и это будет работать?)
rogerdpack

1
@rogerdpack: Нет, это не будет следовать за файлом через переименования. Я полагаю, что он будет создан в тот момент, когда он был перемещен в выбранный подкаталог. Если вы хотите выбрать только один файл, загляните на страницу --index-filterруководства filter-branch.
Каскабель

8
Есть ли какой-нибудь рецепт того, как я могу следовать переименованиям?
Ночной воин

Я думаю, что поддержание и выздоровление истории - один из главных моментов мерзавца.
artburkart

288

Если ваша история нормальна, вы можете взять коммиты как патч и применить их в новом хранилище:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Или в одну строку

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(Взято из документов Exherbo )


21
Для трех или четырех файлов, которые мне нужно было переместить, это было гораздо более простым решением, чем принятый ответ. В итоге я обрезал пути в файле патча с помощью find-replace, чтобы он вписался в структуру каталогов моего нового репо.
Риан Сандерсон

8
Я добавил варианты , так что двоичные файлы (например , изображения), также правильно мигрировали: git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Работает без проблем AFAICT.
Эммануэль Тузери

35
На этапе применения я использовал --committer-date-is-author-dateопцию, чтобы сохранить исходную дату фиксации вместо даты, когда файлы были перемещены.
darrenmc

6
Коммит слияния в истории нарушает команду «am». Вы можете добавить "-m --first-parent" к команде git log выше, тогда она сработала для меня.
Габор Липтак

6
@Daniel Golden Мне удалось исправить проблему с файлами, которые были перемещены (что является следствием ошибки git log, так что она не работает с обоими --followи --reverseправильно). Я использовал этот ответ , и вот полный сценарий, который я использую сейчас для перемещения файлов
tsayen

75

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

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

Первый этап

  1. Сделайте копию хранилища A, так как следующие шаги вносят существенные изменения в эту копию, которые вам не следует нажимать!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. CD в ​​это

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Удалите ссылку на исходный репозиторий, чтобы избежать случайного внесения каких-либо удаленных изменений (например, путем нажатия)

    git remote rm origin
    
  4. Просматривайте свою историю и файлы, удаляя все, что не находится в каталоге 1. В результате содержимое каталога 1 выливается в базу хранилища А.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Только для перемещения одного файла: пройдите через то, что осталось, и удалите все, кроме нужного файла. (Вам может понадобиться удалить файлы, которые вы не хотите, с тем же именем и зафиксировать.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

Второй этап

  1. Шаг очистки

    git reset --hard
    
  2. Шаг очистки

    git gc --aggressive
    
  3. Шаг очистки

    git prune
    

Вы можете импортировать эти файлы в хранилище B в каталоге, а не в корне:

  1. Сделать этот каталог

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Переместить файлы в этот каталог

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Добавить файлы в этот каталог

    git add .
    
  4. Передайте ваши изменения, и мы готовы объединить эти файлы в новый репозиторий

    git commit
    

Этап третий

  1. Сделайте копию хранилища B, если у вас ее еще нет

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (при условии, что FOLDER_TO_KEEP - это имя нового репозитория, в который вы копируете)

  2. CD в ​​это

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Создайте удаленное соединение с хранилищем A как ветвь в хранилище B

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. Извлеките эту ветку (содержащую только каталог, который вы хотите переместить) в хранилище B.

    git pull repo-A-branch master --allow-unrelated-histories
    

    Тяга копирует как файлы, так и историю. Примечание: вы можете использовать слияние вместо вытягивания, но вытягивание работает лучше.

  5. Наконец, вы, вероятно, хотите немного очиститься, удалив удаленное соединение с хранилищем A

    git remote rm repo-A-branch
    
  6. Нажмите и все готово.

    git push
    

Я прошел через большинство шагов, описанных здесь, однако он, кажется, копирует только историю фиксации файла или директории с мастера (а не из каких-либо других веток). Это правильно?
Бао-Лонг Нгуен-Тронг

Я думаю, что это правильно, и что вам придется пройти через аналогичные шаги для любых веток, из которых вы хотите переместить файлы или папки, т.е. переключиться на ветку например MyBranch в репозитории A, фильтр-ветвь и т. Д. Затем вы должны были «git pull repo-A-branch MyBranch» в репозитории B.
mcarans

Спасибо за ответ. Вы знаете, будут ли перенесены теги на ветках?
Бао-Лонг Нгуен-Тронг

Боюсь, я не знаю, но думаю, что они будут.
mcarans

1
@mcarans К сожалению, это НЕ надежный способ, хотя, похоже, так и есть. Он страдает от той же проблемы, что и все другие решения - он не сохраняет историю после переименования. В моем случае самый первый коммит - это когда я переименовал каталог / файл. Все, что за этим, потеряно.
xZero

20

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

Он содержит только три шага (скопировано из блога):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

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

git am --3way <patch-directory>/*.patch

Под Windows я получил ошибку InvalidArgument. Поэтому мне пришлось применять все патчи один за другим.


У меня не получилось, так как в какой-то момент пропали шахаши. Это помогло мне: stackoverflow.com/questions/17371150/…
dr0i

В отличие от подхода «git log», эта опция отлично сработала для меня! Спасибо!
АлехандроВД

1
Пробовал разные подходы к переносу проектов в новый репо. Это единственный, который работал на меня. Не могу поверить, что такая общая задача должна быть такой сложной.
Chris_D_Turk

Спасибо за то, что поделились блогом Росса Хендриксона . Этот подход работал для меня.
Каушик Ачарья

1
Это очень элегантное решение, однако, опять же, оно страдает от той же проблемы, что и все другие решения - оно НЕ сохранит историю после переименования.
xZero

6

СОХРАНЯЯ НАИМЕНОВАНИЕ КАТАЛОГА

Фильтр подкаталога (или более короткое командное поддерево git) работает хорошо, но у меня не работает, так как они удаляют имя каталога из информации о коммите. В моем сценарии я просто хочу объединить части одного репозитория в другой и сохранить историю с полным именем пути.

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

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

Этот скрипт не будет вносить никаких изменений в ваш исходный репо. Если репозиторий dest, указанный в файле карты, не существует, этот сценарий попытается его создать.
Четабахана

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

6

Тот, который я всегда использую, находится здесь http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ . Просто и быстро.

Для соответствия стандартам stackoverflow, вот процедура:

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

5

В этом ответе представлены интересные команды, основанные git amи представленные с помощью примеров, шаг за шагом.

Задача

  • Вы хотите переместить некоторые или все файлы из одного хранилища в другое.
  • Вы хотите сохранить свою историю.
  • Но вы не заботитесь о сохранении тегов и веток.
  • Вы принимаете ограниченную историю переименованных файлов (и файлов в переименованных каталогах).

Процедура

  1. Извлечь историю в формате электронной почты, используя
    git log --pretty=email -p --reverse --full-index --binary
  2. Реорганизовать дерево файлов и обновить изменение имени файла в истории [необязательно]
  3. Применить новую историю, используя git am

1. Извлечь историю в формате электронной почты

Пример: история Экстракт file3, file4иfile5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Очистить временный каталог назначения

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

Очистите свой источник репо

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

Извлечь историю каждого файла в формате электронной почты

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

К сожалению вариант --followили --find-copies-harderнельзя сочетать с --reverse. Вот почему история обрезается при переименовании файла (или при переименовании родительского каталога).

После: Временная история в формате электронной почты

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2. Реорганизовать дерево файлов и обновить изменение имени файла в истории [необязательно]

Предположим, вы хотите переместить эти три файла в этом другом репо (это может быть тот же репо).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

Поэтому реорганизуйте ваши файлы:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

Ваша временная история сейчас:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Измените также имена файлов в истории:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

Примечание: это переписывает историю, чтобы отразить изменение пути и имени файла.
      (т.е. изменение нового местоположения / имени в новом репо)


3. Применить новую историю

Ваш другой репо:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Применить коммиты из временных файлов истории:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

Ваш другой репо сейчас:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

использование git status чтобы увидеть количество коммитов, готовых к отправке :-)

Примечание. Поскольку история была переписана, чтобы отразить изменение пути и имени файла:
      (т.е. по сравнению с местоположением / именем в предыдущем репо)

  • Не нужно git mv менять местоположение / имя файла.
  • Нет необходимости для git log --followдоступа к полной истории.

Дополнительный трюк: обнаружение переименованных / перемещенных файлов в вашем репо

Для просмотра списка файлов, которые были переименованы:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Дополнительные настройки: вы можете выполнить команду, git logиспользуя параметры --find-copies-harderили --reverse. Вы также можете удалить первые два столбца, используя cut -f3-полный шаблон '{. * =>. *}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

Имея подобный царапин (хотя только для некоторых файлов данного репозитория), этот скрипт оказался действительно полезным: git-import

Короткая версия заключается в том, что он создает файлы исправлений для данного файла или каталога ( $object) из существующего хранилища:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

которые затем применяются к новому хранилищу:

cd new_repo
git am "$temp"/*.patch 

Для деталей, пожалуйста, посмотрите вверх:


2

Попробуй это

cd repo1

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

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

Теперь вы можете добавить свой новый репозиторий в свой git remote и добавить его к этому

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

добавить -fперезаписать


ПРЕДУПРЕЖДЕНИЕ: git-filter-branch имеет избыток ошибок, генерирующих искаженные переписывания истории. Нажмите Ctrl-C, прежде чем продолжить, затем используйте альтернативный инструмент фильтрации, такой как 'git filter-repo' ( github.com/newren/git-filter-repo ). См. Страницу руководства ветки фильтра для получения дополнительной информации; чтобы подавить это предупреждение, установите FILTER_BRANCH_SQUELCH_WARNING = 1.
Колин

1

Используя вдохновение из http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/ , я создал эту функцию Powershell для того же, которая имеет до сих пор отлично работал для меня:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

Использование для этого примера:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

После того, как вы это сделали, вы можете реорганизовать файлы в migrate-from-project2ветке, прежде чем объединять ее.


1

Я хотел что-нибудь надежное и многократно используемое (функция «одна команда и иди + отменить»), поэтому я написал следующий скрипт bash. Несколько раз работал на меня, поэтому я решил поделиться этим здесь.

Он может перемещать произвольную папку /path/to/fooиз repo1в /some/other/folder/barв repo2(пути к папкам могут быть одинаковыми или разными, расстояние от корневой папки может быть разным).

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

Поскольку для этого нужно создать потерянную ветку со всей историей старого репо, а затем объединить ее с ГОЛОВКОЙ, она будет работать даже в случае столкновения имен файлов (тогда вам, конечно, придется разрешить объединение в конце) ,

Если нет конфликтов имен файлов, вам просто нужно git commitв конце завершить объединение.

Недостатком является то, что он, скорее всего, не будет следовать переименованиям файлов (вне REWRITE_FROMпапки) в исходных репо-запросах, которые GitHub приветствует для этого.

Ссылка на GitHub: git-move-folder -ween-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

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

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

На этих двух шагах я смог заставить филиал другого репо появляться в том же репо.

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

git br --set-upstream-to=origin/mainline

Теперь он вел себя так, как будто это была просто еще одна ветка, которую я толкнул против того же репо.


0

Если пути для указанных файлов одинаковы в двух репозиториях, и вы хотите перенести только один файл или небольшой набор связанных файлов, один из простых способов сделать это - использовать git cherry-pick .

Первый шаг - перенести коммиты из другого репо в ваше локальное репо с помощью git fetch <remote-url>. Это оставит FETCH_HEADуказание на главный коммит из другого репо; если вы хотите сохранить ссылку на этот коммит после выполнения других выборок, вы можете пометить его тегом git tag other-head FETCH_HEAD.

Затем вам нужно будет создать первоначальный коммит для этого файла (если он не существует) или коммит, чтобы привести файл в состояние, которое может быть исправлено первым коммитом из другого репо, который вы хотите ввести. Вы можете быть в состоянии сделать это с помощью, git cherry-pick <commit-0>если commit-0вы представили нужные файлы, или вам может понадобиться создать коммит «вручную». Добавить-n к параметрам cherry-pick, если вам нужно изменить первоначальный коммит, например, удалить файлы из того коммита, который вы не хотите вносить.

После этого вы можете перейти к git cherry-pickпоследующим коммитам, снова используя -nпри необходимости. В простейшем случае (все коммиты именно то , что вы хотите и применять чисто) , вы можете дать полный список коммитов в командной строке вишневой выбрать: git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

Это становится проще благодаря использованию git-filter-repo.

Для того, чтобы перейти project2/sub/dirк project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

Чтобы установить инструмент просто: pip3 install git-filter-repo ( более подробную информацию и опции в README )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

Приведенный ниже метод переноса моего GIT Stash в GitLab с сохранением всех веток и сохранением истории.

Клонировать старый репозиторий в локальный.

git clone --bare <STASH-URL>

Создайте пустой репозиторий в GitLab.

git push --mirror <GitLab-URL>

Выше я выполнил, когда мы перенесли наш код из stash в GitLab, и он работал очень хорошо.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.