Як переміщувати файли з одного git repo в інший (не клон), зберігаючи історію


483

Наші сховища 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 repo в інший) вручну, використовуючи скрипти оболонки. В основному я відтворював історію (відрізняється, фіксує повідомлення журналів) з певних файлів / dirs у друге сховище.
Адам Монсен

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
Чи є якийсь рецепт про те, як я можу слідувати за перейменами?
Night Warrier

Я думаю, що підтримка та курінгова історія є одним із головних моментів git.
artburkart

287

Якщо ваша історія є розумною, ви можете вийняти комісії як виправлення та застосувати їх у новому сховищі:

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)

(Взято з документів Егербо )


21
Для трьох або 4 файлів, які мені потрібно було перемістити, це було набагато простіше рішення, ніж прийнята відповідь. Я закінчив обрізки шляхів у патч-файлі з пошуку-заміни, щоб він міг вписатися в нову структуру каталогу мого репо.
Ріан Сандерсон

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 вище, тоді це працювало для мене.
Gábor Lipták

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. Створіть віддалене підключення до сховища А як гілка в сховищі В

    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", цей варіант працював для мене ідеально! Дякую!
AlejandroVD

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

Цей скрипт не вносить жодних змін до оригінального репо-репо. Якщо destpopo, вказаний у файлі карти, не існує, цей сценарій спробує створити його.
Четабахана

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 пульт та натиснути його на це

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

додати -fдля перезапису


ПОПЕРЕДЖЕННЯ: у гіт-фільтр-відділення є безліч готчей, що генерують переплутані історію перезапису. Натисніть 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

Я хотів чогось надійного та багаторазового використання (функція one-command-and-go + unndo), тому я написав наступний скрипт bash. Я працював для мене кілька разів, тому я думав, що поділюсь цим.

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

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

Оскільки для цього потрібно створити осиротілу гілку зі всією історією репо, а потім об'єднати її в HEAD, це навіть спрацює у випадку зіткнення імен файлів (тоді вам доведеться вирішити злиття наприкінці курсу) .

Якщо зіткнень з іменем файлу немає, вам потрібно просто git commitв кінці завершити злиття.

Мінус полягає в тому, що він, ймовірно, не буде перейменовувати файли (за межами REWRITE_FROMпапки) у вихідному репо-репортажі - запросити запрошення на GitHub для їх влаштування.

Посилання GitHub: git-move-folder-between-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 до параметрів вишеньки вишні, якщо вам потрібно змінити початкову комісію, наприклад, скинути файли з того комітету, який ви не хочете вносити.

Після цього ви можете продовжувати 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>

Вище сказане я виконав, коли ми перемістили наш код зі сховища в GitLab, і він працював дуже добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.