у меня есть Git- репозиторий, который содержит несколько подкаталогов. Теперь я обнаружил, что одно из подкаталогов не связано с другим и должно быть отсоединено от отдельного хранилища.
Как я могу сделать это, сохраняя историю файлов в подкаталоге?
Я думаю, я мог бы сделать клон и удалить ненужные части каждого клона, но я полагаю, что это даст мне полное дерево при проверке более ранней ревизии и т. Д. Это может быть приемлемо, но я предпочел бы сделать вид, что два репозитория не имеют общей истории.
Просто чтобы прояснить, у меня есть следующая структура:
XYZ/
.git/
XY1/
ABC/
XY2/
Но я бы хотел этого:
XYZ/
.git/
XY1/
XY2/
ABC/
.git/
ABC/
git filter-branch
см. Мой ответ ниже.Ответы:
Обновление : этот процесс настолько распространен, что команда git значительно упростила его с помощью нового инструмента
git subtree
. Смотрите здесь: Отсоединить (переместить) подкаталог в отдельный Git-репозиторийВы хотите клонировать свой репозиторий, а затем использовать,
git filter-branch
чтобы пометить все, кроме подкаталога, который вы хотите в своем новом репо для сбора мусора.Чтобы клонировать ваш локальный репозиторий:
(Примечание: репозиторий будет клонирован с использованием жестких ссылок, но это не проблема, поскольку файлы с жесткими ссылками не будут изменены сами по себе - будут созданы новые.)
Теперь давайте сохраним интересные ветки, которые мы также хотим переписать, а затем удалим источник, чтобы избежать его появления и убедиться, что источник не будет ссылаться на старые коммиты:
или для всех удаленных филиалов:
Теперь вы можете также удалить теги, которые не имеют отношения к подпроекту; вы также можете сделать это позже, но вам может понадобиться снова обрезать репо. Я не сделал этого и получил
WARNING: Ref 'refs/tags/v0.1' is unchanged
для всех тегов (так как все они не были связаны с подпроектом); Кроме того, после удаления таких тегов будет больше места. Видимо,git filter-branch
должны быть в состоянии переписать другие теги, но я не мог проверить это. Если вы хотите удалить все теги, используйтеgit tag -l | xargs git tag -d
.Затем используйте filter-branch и reset, чтобы исключить другие файлы, чтобы их можно было удалить. Давайте также добавим,
--tag-name-filter cat --prune-empty
чтобы удалить пустые коммиты и переписать теги (обратите внимание, что для этого придется удалить их подпись):или, альтернативно, переписать только ветку HEAD и игнорировать теги и другие ветки:
Затем удалите резервные копии, чтобы освободить место (хотя теперь операция разрушительна)
и теперь у вас есть локальный git-репозиторий подкаталога ABC со всей его историей.
Примечание: для большинства применений
git filter-branch
действительно должен быть добавлен параметр-- --all
. Да, это действительно так --space--all
. Это должны быть последние параметры для команды. Как обнаружил Матли, это сохраняет ветки проекта и теги, включенные в новый репозиторий.Изменить: различные предложения из комментариев ниже были включены, чтобы убедиться, например, в том, что хранилище действительно сокращено (что не всегда было раньше).
источник
--no-hardlinks
? Удаление одной жесткой ссылки не повлияет на другой файл. Git объекты тоже неизменны. Только если вы измените права доступа владельца / файла, которые вам нужны--no-hardlinks
.filter-branch
---prune-empty
удалить пустые коммиты.-- --all
. Я тоже бегалgit remote rm origin
иgit tag -l | xargs git tag -d
доgit filter-branch
команды. Это сократило мой.git
каталог с 60М до ~ 300К. Обратите внимание, что мне нужно было выполнить обе эти команды, чтобы уменьшить размер.Легкий путь ™
Оказывается, это настолько распространенная и полезная практика, что повелители Git сделали это действительно легко, но вам нужно иметь более новую версию Git (> = 1.7.11, май 2012). Смотрите приложение о том, как установить последнюю версию Git. Кроме того, есть реальный пример в пошаговом руководстве ниже.
Подготовьте старый репо
Примечание:
<name-of-folder>
НЕ должно содержать начальных или конечных символов. Например, папка с именемsubproject
ДОЛЖНА быть передана какsubproject
, НЕ./subproject/
Примечание для пользователей Windows: если глубина вашей папки> 1,
<name-of-folder>
должен иметь разделитель папок в стиле * nix (/). Например, папка с именемpath1\path2\subproject
ДОЛЖНА быть передана какpath1/path2/subproject
Создать новый репо
Свяжите новый репо с GitHub или где угодно
Очистка внутри
<big-repo>
, при желанииПримечание . Это оставляет все исторические ссылки в хранилище. См. Приложение ниже, если вы действительно обеспокоены тем, что зафиксировали пароль, или вам необходимо уменьшить размер файла вашей
.git
папки....
Прохождение
Это те же шаги, что и выше , но следуйте моим точным шагам для моего репозитория вместо использования
<meta-named-things>
.Вот мой проект по реализации модулей браузера JavaScript в узле:
Я хочу разделить одну папку
btoa
, в отдельный репозиторий GitТеперь у меня есть новая ветка, для
btoa-only
которой есть только коммиты,btoa
и я хочу создать новый репозиторий.Затем я создаю новый репозиторий на GitHub или Bitbucket, или как угодно, и добавляю его как
origin
Счастливый день!
Примечание: Если вы создали репо с микросхемой
README.md
,.gitignore
иLICENSE
, вам нужно будет тянуть первым:Наконец, я хочу удалить папку из большего репо
...
аппендикс
Последний Git на macOS
Чтобы получить последнюю версию Git с помощью Homebrew :
Последний Git на Ubuntu
Если это не работает (у вас очень старая версия Ubuntu), попробуйте
Если это все еще не работает, попробуйте
Спасибо rui.araujo из комментариев.
Очистка вашей истории
По умолчанию удаление файлов из Git фактически не удаляет их, а просто подтверждает, что их больше нет. Если вы хотите на самом деле удалить исторические ссылки (т.е. у вас есть подтвержденный пароль), вам нужно сделать это:
После этого вы можете проверить, что ваш файл или папка больше не отображаются в истории Git
Тем не менее, вы не можете «нажать» удаления на GitHub и тому подобное. Если вы попытаетесь, вы получите ошибку, и вам придется,
git pull
прежде чем вы сможетеgit push
- и тогда вы вернетесь к тому, чтобы иметь все в своей истории.Поэтому, если вы хотите удалить историю из «источника» - то есть удалить ее из GitHub, Bitbucket и т. Д. - вам необходимо удалить репо и повторно нажать на сокращенную копию репо. Но подождите - это еще не все ! - Если вы действительно хотите избавиться от пароля или чего-то в этом роде, вам потребуется удалить резервную копию (см. Ниже).
Делая
.git
меньшеВышеупомянутая команда удаления истории все еще оставляет кучу файлов резервных копий - потому что Git слишком любезен, чтобы помочь вам не испортить репо случайно. В конечном итоге он удалит потерянные файлы в течение нескольких дней и месяцев, но на некоторое время оставит их там, если вы поймете, что случайно удалили то, что не хотели.
Поэтому, если вы действительно хотите очистить корзину, чтобы сразу же уменьшить размер клона репо, вы должны сделать все эти действительно странные вещи:
Тем не менее, я бы порекомендовал не выполнять эти шаги, если вы не знаете, что вам нужно - просто на тот случай, если вы удалили неправильный подкаталог, понимаете? Файлы резервных копий не должны быть клонированы, когда вы нажимаете репо, они просто будут в вашей локальной копии.
кредит
источник
git subtree
все еще является частью папки contrib и по умолчанию не устанавливается во всех дистрибутивах. github.com/git/git/blob/master/contrib/subtreepopd
andpushd
делает это довольно неявным и труднее понять, что она собирается делать ...Ответ Пола создает новый репозиторий, содержащий / ABC, но не удаляет / ABC из / XYZ. Следующая команда удалит / ABC из / XYZ:
Конечно, сначала протестируйте его в репозитории 'clone --no-hardlinks' и следуйте за ним с помощью команд reset, gc и prune, которые перечисляет Paul.
источник
git filter-branch --index-filter "git rm -r -f --cached --ignore-unmatch ABC" --prune-empty HEAD
и это будет гораздо быстрее. index-filter работает с индексом, в то время как tree-filter должен извлекать и устанавливать все для каждого коммита .--index-filter
метод, вы также можете захотеть сделать этоgit rm -q -r -f
, чтобы при каждом вызове не печаталась строка для каждого удаляемого файла.Я обнаружил, что для того, чтобы правильно удалить старую историю из нового репозитория, вам нужно проделать еще немного работы после этого
filter-branch
шага.Делаем клон и фильтр
Удалите все ссылки на старую историю. «Origin» отслеживал ваш клон, а «original» - то, где фильтр-ветвь сохраняет старые данные:
Даже сейчас ваша история может застрять в пакете, который fsck не коснется. Разорвите его на части, создав новый файл пакета и удалив неиспользуемые объекты:
Существует объяснение этого в руководстве для фильтра-ветви .
источник
git gc --aggressive --prune=now
все еще отсутствует, не так ли?git gc --aggressive --prune=now
большая часть нового репо сократиласьИзменить: Bash скрипт добавлен.
Ответы, данные здесь, работали только частично для меня; В кеше осталось много больших файлов. Что в итоге сработало (после нескольких часов в #git на freenode):
В предыдущих решениях размер хранилища составлял около 100 МБ. Этот уменьшил его до 1,7 МБ. Может быть, это кому-нибудь поможет :)
Следующий скрипт bash автоматизирует задачу:
источник
Это уже не так сложно, вы можете просто использовать команду git filter-branch для клона вашего репозитория, чтобы отбросить ненужные вам подкаталоги, а затем отправить их на новый пульт.
источник
The result will contain that directory (and only that) as its project root.
это действительно то, что вы получите, то есть исходная структура проекта не сохранилась.Обновление : модуль git-subtree оказался настолько полезным, что команда git втянула его в ядро и сделала его
git subtree
. Смотрите здесь: Отсоединить (переместить) подкаталог в отдельный Git-репозиторийGit-поддерево может быть полезно для этого
http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (устарело)
http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/
источник
Вот небольшая модификация CoolAJ86 «s „The Easy Way ™“ответ для того , чтобы разделить несколько папок суб (скажем ,
sub1
иsub2
) в новый репозиторий.Easy Way ™ (несколько подпапок)
Подготовьте старый репо
Примечание:
<name-of-folder>
НЕ должно содержать начальных или конечных символов. Например, папка с именемsubproject
ДОЛЖНА быть передана какsubproject
, НЕ./subproject/
Примечание для пользователей Windows: если глубина вашей папки> 1,
<name-of-folder>
должен иметь разделитель папок в стиле * nix (/). Например, папка с именемpath1\path2\subproject
ДОЛЖНА быть передана какpath1/path2/subproject
. Кроме того, не используйтеmv
команду, ноmove
.Конечное примечание: уникальная и большая разница с базовым ответом - вторая строка сценария "
git filter-branch...
"Создать новый репо
Свяжите новый репо с Github или где угодно
Очистка при желании
Примечание . Это оставляет все исторические ссылки в репозитории. См. Приложение в исходном ответе, если вы действительно обеспокоены тем, что зафиксировали пароль, или вам необходимо уменьшить размер файла вашей
.git
папки.источник
sub1
иsub2
папки не существуют с первоначальной версией, я должен был изменить свой--tree-filter
сценарий следующим образом :"mkdir <name-of-folder>; if [ -d sub1 ]; then mv <sub1> <name-of-folder>/; fi"
. Для второйfilter-branch
команды я заменил <sub1> на <sub2>, исключил создание <name-of-folder> и включил-f
после,filter-branch
чтобы переопределить предупреждение о существующей резервной копии.Исходный вопрос требует, чтобы XYZ / ABC / (* файлы) стали ABC / ABC / (* файлами). После реализации принятого ответа для моего собственного кода, я заметил, что он на самом деле изменяет XYZ / ABC / (* файлы) на ABC / (* файлы). Страница руководства ветки фильтра даже говорит:
Другими словами, он продвигает папку верхнего уровня «вверх» на один уровень. Это важное различие, потому что, например, в моей истории я переименовал папку верхнего уровня. Продвигая папки «вверх» на один уровень, git теряет непрерывность при коммите, где я сделал переименование.
Мой ответ на этот вопрос состоит в том, чтобы сделать 2 копии репозитория и вручную удалить папки, которые вы хотите сохранить в каждой. Страница man поддерживает меня с этим:
источник
targetdir
была переименована в какой-то момент иgit filter-branch
просто назвала это днем, удалив все коммиты, сделанные до переименования! Шокирующе, учитывая, как адепт Git отслеживает подобные вещи и даже переносит отдельные фрагменты контента!git rm
требуется несколько аргументов, поэтому нет причин запускать его для каждого файла / папки:BYEBYE="dir/subdir2 dir2 file1 dir/file2"; git filter-branch -f --index-filter "git rm -q -r -f --cached --ignore-unmatch $BYEBYE" --prune-empty -- --all
Добавить к ответу Павла , я обнаружил, что для окончательного восстановления пространства мне нужно поместить HEAD в чистый репозиторий, и это сокращает размер каталога .git / objects / pack.
т.е.
После gc prune также выполните:
Тогда вы можете сделать
и размер ABC / .git уменьшается
На самом деле, некоторые из трудоемких шагов (например, git gc) не требуются для очистки репозитория, например:
источник
Правильный путь сейчас заключается в следующем:
git filter-branch --prune-empty --subdirectory-filter FOLDER_NAME [first_branch] [another_branch]
GitHub теперь даже имеет небольшую статью о таких случаях.
Но не забудьте сначала клонировать исходное хранилище в отдельный каталог (так как это приведет к удалению всех файлов и других каталогов, и вам, вероятно, придется работать с ними).
Итак, ваш алгоритм должен быть:
git filter-branch
только оставленные файлы в некотором подкаталоге, нажмите на новый пультисточник
Похоже, что большинство (все?) Ответов здесь полагаются на некоторую форму
git filter-branch --subdirectory-filter
и тому подобное. Однако в большинстве случаев это может работать «в большинстве случаев», например, когда вы переименовали папку, например:Если вы используете обычный стиль фильтра git для извлечения «move_me_renamed», вы потеряете историю изменений файла, которая произошла с того времени, когда она изначально была move_this_dir ( ref ).
Таким образом, кажется, что единственный способ действительно сохранить всю историю изменений (если у вас такой случай), это, по сути, скопировать хранилище (создать новый репозиторий, установить его в качестве источника), а затем уничтожить все остальное. и переименуйте подкаталог в родительский файл следующим образом:
git branch -a
git checkout --track origin/branchABC
cp -r oldmultimod simple
cd simple
git rm otherModule1 other2 other3
git mv moduleSubdir1/* .
rmdir moduleSubdir1
git status
git remote set-url origin http://mygithost:8080/git/our-splitted-module-repo
git remote -v
git push
git checkout branch2
Это следует за github doc "Разделение подпапки в новый репозиторий" шаги 6-11, чтобы подтолкнуть модуль к новому репо.
Это не сэкономит вам места в папке .git, но сохранит всю историю изменений этих файлов даже при переименовании. И это может не стоить того, если не будет потеряно «много» истории и т. Д. Но по крайней мере вы гарантированно не потеряете старые коммиты!
источник
Я рекомендую руководство GitHub по разделению подпапок в новый репозиторий . Шаги аналогичны ответу Пола , но я понял, что их инструкции легче понять.
Я изменил инструкции, чтобы они применялись для локального репозитория, а не для размещения на GitHub.
источник
If you create a new clone of the repository, you won't lose any of your Git history or changes when you split a folder into a separate repository.
Однако в соответствии с комментариями ко всем ответам здесьfilter-branch
иsubtree
сценарий приводит к потере истории, когда подкаталог был переименован. Можно ли что-нибудь сделать для решения этой проблемы?При запуске
git filter-branch
с использованием более новой версииgit
(2.22+
может быть?), Он говорит, чтобы использовать этот новый инструмент git-filter-repo . Этот инструмент, безусловно, упростил для меня вещи.Фильтрация с помощью фильтра репо
Команды для создания
XYZ
репо из исходного вопроса:предположения: * удаленный репозиторий XYZ был новым и пустым до пуша
Фильтрация и перемещение
В моем случае я также хотел переместить пару каталогов для более согласованной структуры. Сначала я
filter-repo
выполнил эту простую командуgit mv dir-to-rename
, а затем обнаружил, что с помощью этой--path-rename
опции я могу получить немного «лучшую» историю . Вместо того, чтобы видеть последние измененные5 hours ago
перемещенные файлы в новом репо, которое я сейчас вижуlast year
(в пользовательском интерфейсе GitHub), что соответствует измененному времени в оригинальном репо.Вместо...
Я в конечном итоге побежал ...
Ноты:git filter-repo --subdirectory-filter dir-matching-new-repo-name
). Эта команда правильно преобразовала этот подкаталог в корень скопированного локального репозитория, но она также привела к истории только трех коммитов, которые потребовались для создания подкаталога. (Я не осознавал, что это--path
можно было бы указать несколько раз; тем самым я избавился от необходимости создавать подкаталог в репозитории с исходным кодом.) Поскольку к моменту, когда я заметил, что кто-то перешел на репозиторий с исходным кодом, я не смог перенести историю, которую я только использовалgit reset commit-before-subdir-move --hard
послеclone
команды, и добавил--force
кfilter-repo
команде, чтобы заставить ее работать со слегка измененным локальным клоном.git
, но в конечном итоге я клонировал git-filter-repo и сделал ссылку на него$(git --exec-path)
:источник
filter-repo
инструмента (который я представил в прошлом месяце на stackoverflow.com/a/58251653/6309 )git-filter-repo
должно определенно быть предпочтительным подходом в этой точке. Это намного, намного быстрее и безопаснееgit-filter-branch
, и защищает от многих ошибок, с которыми можно столкнуться при переписывании своей истории мерзавцев. Надеюсь, что этот ответ получит больше внимания, так как это тот, который нужно рассмотретьgit-filter-repo
.У меня была именно эта проблема, но все стандартные решения, основанные на git filter-branch, были чрезвычайно медленными. Если у вас небольшой репозиторий, то это может не быть проблемой, это было для меня. Я написал другую программу фильтрации git, основанную на libgit2, которая в качестве первого шага создает ветки для каждой фильтрации основного хранилища, а затем отправляет их для очистки хранилищ в качестве следующего шага. В моем репозитории (500Mb 100000 коммитов) стандартные методы git filter-branch заняли несколько дней. Моя программа занимает минуты, чтобы выполнить ту же фильтрацию.
Он имеет невероятное имя git_filter и живет здесь:
https://github.com/slobobaby/git_filter
на GitHub.
Надеюсь это кому-нибудь пригодится.
источник
Используйте эту команду фильтра для удаления подкаталога, сохраняя при этом ваши теги и ветви:
источник
Для чего это стоит, вот как использовать GitHub на компьютере с Windows. Допустим, у вас есть клонированный репо, в котором вы проживаете
C:\dir1
. Структура каталогов выглядит следующим образом :C:\dir1\dir2\dir3
.dir3
Каталог является один я хочу быть новым отдельным репо.Github:
MyTeam/mynewrepo
Bash Prompt:
$ cd c:/Dir1
$ git filter-branch --prune-empty --subdirectory-filter dir2/dir3 HEAD
Возвращено:
Ref 'refs/heads/master' was rewritten
(fyi: dir2 / dir3 чувствителен к регистру.)$ git remote add some_name git@github.com:MyTeam/mynewrepo.git
git remote add origin etc
, не работал, вернулся "remote origin already exists
"$ git push --progress some_name master
источник
Как я упоминал выше , мне пришлось использовать обратное решение (удаление всех коммитов, не касаясь моего
dir/subdir/targetdir
), которое, казалось, работало довольно хорошо, удаляя около 95% коммитов (по желанию). Однако остаются две небольшие проблемы.ПЕРВЫЙ ,
filter-branch
сделал огромную работу по удалению коммитов, которые вводят или модифицируют код, но, очевидно, коммиты слияния находятся ниже его станции в Gitiverse.Это косметическая проблема, с которой я, вероятно, могу смириться (говорит он ... медленно отступая, не отводя глаз) .
ВТОРОЙ немногие коммиты , которые остаются в значительной степени все дублируются! Кажется, я приобрел второй, избыточный график времени, охватывающий почти всю историю проекта. Интересная вещь (которую вы можете видеть из рисунка ниже) состоит в том, что мои три локальные ветви не все находятся на одной и той же временной шкале (что, разумеется, почему оно существует, а не просто сбор мусора).
Единственное, что я могу себе представить, это то, что одна из удаленных фиксаций была, возможно, единственной фиксацией слияния, которая
filter-branch
действительно удаляла, и которая создала параллельную временную шкалу, поскольку каждая теперь не слитая цепочка получила свою собственную копию фиксаций. ( пожимает плечами Где мои ТАРДИ?) Я почти уверен, что смогу решить эту проблему, хотя мне бы очень хотелось понять, как это произошло.В случае сумасшедшего mergefest-O-RAMA, я, вероятно, оставлю этот в покое, так как он так прочно укоренился в моей истории коммитов - угрожая мне всякий раз, когда я подхожу - он, кажется, на самом деле не вызывает любые не косметические проблемы и потому что это довольно симпатично в Tower.app.
источник
Более легкий путь
git splits
. Я создал его как расширение git, основанное на решении jkeating .Разделите каталоги в местный филиал
#change into your repo's directory cd /path/to/repo #checkout the branch git checkout XYZ
#split multiple directories into new branch XYZ git splits -b XYZ XY1 XY2
Создайте пустой репо где-нибудь. Предположим, что мы создали пустой репозиторий
xyz
на GitHub с указанием пути:git@github.com:simpliwp/xyz.git
Нажмите на новый репо.
#add a new remote origin for the empty repo so we can push to the empty repo on GitHub git remote add origin_xyz git@github.com:simpliwp/xyz.git #push the branch to the empty repo's master branch git push origin_xyz XYZ:master
Клонировать только что созданный удаленный репо в новый локальный каталог
#change current directory out of the old repo cd /path/to/where/you/want/the/new/local/repo #clone the remote repo you just pushed to git clone git@github.com:simpliwp/xyz.git
источник
git splits
)Вам может понадобиться что-то вроде «git reflog expire --expire = now --all» перед сборкой мусора, чтобы фактически удалить файлы. git filter-branch просто удаляет ссылки в истории, но не удаляет записи reflog, которые содержат данные. Конечно, сначала проверьте это.
Использование моего диска резко упало при этом, хотя мои начальные условия были несколько иными. Возможно --subdirectory-filter отменяет эту необходимость, но я сомневаюсь в этом.
источник
Проверьте проект git_split на https://github.com/vangorra/git_split
Превратите каталоги git в свои собственные репозитории на своем месте. Не поддельное смешное дело. Этот скрипт возьмет существующий каталог в вашем git-репозитории и превратит этот каталог в независимый собственный репозиторий. Попутно он скопирует всю историю изменений для предоставленного вами каталога.
источник
Поместите это в ваш gitconfig:
источник
Я уверен, что с поддеревом git все в порядке и замечательно, но мои подкаталоги управляемого кода git, которые я хотел переместить, были в затмении. Так что если вы используете egit, это больно легко. Возьмите проект, который вы хотите переместить, и объедините его в команду> отключите его, а затем объедините команду> поделиться им с новым местоположением. По умолчанию будет пытаться использовать старое место репо, но вы можете снять отметку с уже существующего выбора и выбрать новое место для его перемещения. Приветствую вас.
источник
Вы можете легко попробовать https://help.github.com/enterprise/2.15/user/articles/splitting-a-subfolder-out-into-a-new-repository/
Это сработало для меня. Проблемы, с которыми я столкнулся на приведенных выше этапах:
в этой команде является мастер
git filter-branch --prune-empty --subdirectory-filter FOLDER-NAME BRANCH-NAME
BRANCH-NAME
если последний шаг завершается неудачно при фиксации из-за проблем защиты, следуйте - https://docs.gitlab.com/ee/user/project/protected_branches.html
источник
Я нашел довольно простое решение, идея состоит в том, чтобы скопировать хранилище, а затем просто удалить ненужную часть. Вот как это работает:
1) Клонируйте репозиторий, который вы хотите разбить
2) Переместить в папку git
2) Удалите ненужные папки и зафиксируйте его
3) Удалить ненужные папки (ы) истории формы с BFG
4) Убедитесь, что история не содержит файлов / папок, которые вы только что удалили
5) Теперь у вас есть чистый репозиторий без ABC, так что просто вставьте его в новый источник
Вот и все. Вы можете повторить шаги, чтобы получить другой репозиторий,
просто удалите XY1, XY2 и переименуйте XYZ -> ABC на шаге 3
источник