05/06/2017 · Разработка

Выделение подпроекта в отдельный репозиторий на github

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

Итак, что дано:

Что необходимо сделать:

В теории можно было бы просто скопировать весь репозиторий со всем содержимым в новое место, а потом просто удалить те папки, которые не нужны. Но такой способ довольно неоптимален и не особо мне понравился, так что я решил поступить иначе.

Я использовал стандартный гитовый filter-branch. За основу я взял следующие статьи:

В этом посте я хочу немного адаптировать процесс для лучшего восприятия (пост содержит UPDATE, рекомендую не торопиться пробовать и сначала дочитать до конца).

Предположим для примера, что наш репозиторий называется movement-example, а та единственная папка, которую мы хотим перенести в отдельный репозиторий – folder-to-move. Тогда шаги, которые необходимо выполнить для подготовки переноса, выглядят следующим образом:

  1. git clone git@github.com:<user_or_organization>/movement-example.git
    Лучше сделать новый клон репозитория, даже если он у вас уже скачан. А еще лучше делать клон из локального репозитория: git clone <path-to-movement-example> – это гораздо быстрее.
  2. cd movement-example
  3. git remote rm origin
    Это именно тот момент, ради которого мы делали новый клон – теперь мы не боимся поломать оригинальный репозиторий.
  4. git filter-branch --subdirectory-filter folder-to-move -- --all
    После выполнения этого шага в вашем локальном репозитории останется только контент папки folder-to-move, причем самой папки больше нет – все содержащиеся в ней файлы теперь лежат в текущей директории (в movement_example).
  5. mkdir folder-to-move
    mv * folder-to-move
    Это опциональный шаг – если вы хотите иметь все файлы внутри той же папки, что и раньше, а не в корне нового репозитория.
  6. git add .
  7. git commit -m “Preparing to extract folder”

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

  1. git clone git@github.com:<user_or_organization>/new-repo.git
    Клонируем себе новый репозиторий, если, конечно, все еще не сделали этого.
  2. cd new-repo
  3. git remote add old-repo-branch <path-to-movement-example-folder>
    Добавляем новый remote. Если ваши папки new-repo и movement-example лежат на одном уровне в файловой системе, то path-to-movement-example-folder выглядел бы как ../movement-example
  4. git pull old-repo-branch master
    После выполнения этого шага цель будет практически достигнута – у вас уже будет весь контент folder-to-move в локальном репозитории new-repo, останется только сделать push. Но сначала нужно сделать кое-что еще.
  5. git remote rm old-repo-branch
    Вам же больше не нужен второй remote, верно?
  6. git push origin master

Готово! Теперь в вашем новом репозитории есть только интересующая вас папка вместе со всей историей коммитов. Например, сразу после вышеописанных шагов я увидел следующее в своем новом репозитории:

810 коммитов!
Кстати, вы увидите только 1 бранч – master. Процедура переносит только один бранч за раз. Если вы хотите перенести dev, то вам нужно просто сделать git checkout dev и git pull origin dev после второго шага на обоих этапах.

Если вам нужно перенести все 50 (60? 100?) бранчей, то данное решение не будет удачным из-за слишком большого количества рутинной работы. Но я считаю, что для переноса достаточно лишь сохранить master и dev бранчи, потому что все feature branches уже должны быть в dev, а новые бранчи вы будете ветвить уже в новом репозитории.

UPDATE

Можно просто воспользоваться другой гитовой утилитой – subtree. Для этого нужно всего ничего:

  1. git clone <path-to-movement-example>
  2. git remote rm origin
    Обратите внимание – эти 2 шага опциональны, вы можете выполнять следующие шаги напрямую из вашего "боевого" локального репозитория. Иметь клон с удаленным origin – мое личное предпочтение, чтобы даже не иметь шанса как-то сломать оригинальный репозиторий.
  3. git subtree split --prefix folder-to-move master
    Или любой другой бранч, кроме master. Эта команда будет долго вычислять значения, но в итоге вернет вам что-то вроде 253f8a5edd9a4dbbb1d72e5837243e93c92ebfcd
  4. git push git@github.com:<user_or_organization>/new-repo.git 253f8a5edd9a4dbbb1d72e5837243e93c92ebfcd:master --force
    Здесь мы пушим с флагом --force, потому что new-repo наверняка будет иметь как минимум readme файл, который вы (вероятно) не захотите пуллить.

Ну, а если new-repo является совсем свежим и не содержит даже readme файла – другими словами, вообще не содержит файлов, то все еще проще:

git subtree push --prefix folder-to-move git@github.com:<user_or_organization>/new-repo.git master

comments powered by Disqus