Pull to refresh

Корзинка с сюрпризами — cпасите наши push'и

Reading time 5 min
Views 2.7K
TLDR: Сделал набор скриптов автоматизации миграции Bitbucket репозиториев с Mercurial на Git.

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

image

На Bitbucket у меня много приватных репозиториев Mercurial, терять их не хотелось, так же, как и их историю коммитов. Переезжать на другой хостинг — также не вариант — к Bitbucket я привык. Сами Bitbucket, как ни странно, не сподобились сделать in-place конвертор. Они даже не написали никакой пошаговой инструкции по конвертированию, отсылая всех на форум своего коммьюнити — с довольно таки иезуитской формулировкой (We are happy to support your migration, and you can find a discussion about available options in our dedicated Community thread) — мол, мы рады переезду ваших репозиториев под Git, а как это сделать — обсудите сами на форуме. Впрочем, в том же посте они оставили ссылки на пару конвертеров hg-fast-export и hg-git.

Первый из них — отдельный скрипт на Python, второй — плагин, для работы с Git репозиториями непосредственно из Mercurial. В Google нашлось также некоторое количество примеров того, как другие решали задачу перетаскивания Mercurial под Git: вот, вот, и вот. В них также использовался либо hg-fast-export, либо hg-git. Второй был для меня уже проверенным решением — hg-git я раньше иногда пользовался, когда коммитил на Github из Mercurial — пока не понял, что в Git, как это ни странно, лучше все таки коммитить из Git же. Так что для своих целей я выбрал hg-git.

Тут приведен Powershell-скрипт для автоматизации миграции репозитория, который и стал отправной точкой моего решения, но для Linux мне пришлось переписать его на Bash. Кроме того, оригинальный скрипт только делает миграцию в локальный Git-репозиторий, и я добавил туда возможность запушивания содержимого сконвертированного Git-репозитория в репозиторий на Bitbucket. Вот как выглядит работа полученного скрипта:

$ ./convert_repo.sh lebedevsergey advertisements_parser
2a51eee7ade0
requesting all changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 5 changes to 5 files
updating to branch default
5 files updated, 0 files merged, 0 files removed, 0 files unresolved
Initialized empty Git repository in /home/serge/project/_probes/hg2git/src/advertisements_parser-git/
pushing to ../advertisements_parser-git
searching for changes
adding objects
added 1 commits with 2 trees and 5 blobs
error: Could not remove config section 'remote.origin'
Branch master set up to track 

Теперь у меня было готовое решение, но оно требовало ручных действий — перед его запуском надо было предварительно создать на Bitbucket Git-репозиторий, чтобы было куда пушить результат. Поглядев на свои 100500 подлежащих конверсии репозиториев, я понял, что так я долго буду их обрабатывать, хотелось бы чтобы скрипт сам создавал Git репозитории для сконвертированных, а еще лучше — сам получал список моих Mercurial репозиториев и работал по нему. Это явно выходило за рамки умения чистого Mercurial, тут требовался функционал самого Bitbucket, а точнее — Bitbucket API.

Для работы с Bitbucket API есть готовые библиотеки для разных языков программирования, в частности, для Python — по-видимому официальный SDK (не путать с лежащим на самом Bitbucket давно заброшенным, но когда-то не менее официальным SDK). Есть также Bitbucket API клиенты для Java, NodeJS и PHP. Последний я и выбрал.

Однако, при его детальном изучении оказалось, что там не реализовано получение списка репозиториев пользователя и создание нового репозитория. К счастью, создатели библиотеки предусмотрели возможность расширения своих классов, сделав закрытые методы protected, так что, унаследовав класс их API-клиента, можно было добавить свои методы, делающие то что мне нужно:

class ExtendedClient extends Client
{
    /**
     * @return \Bitbucket\Api\Repositories
     */
    public function repositories()
    {
        return new ExtendedRepositories($this->getHttpClient());
    }
}

class ExtendedRepositories extends Repositories
{    
    public function listWorkspace(string $workspaceName, array $params = [])
    {
        $path = $this->buildRepositoriesPath($workspaceName);
        return $this->get($path, $params);
    }

    public function create(string $workspaceName, string $repoName, array $params = [])
    {
        $path = $this->buildRepositoriesPath($workspaceName, $repoName);

        return $this->post($path, $params);
    }
}

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

Checking repository: mysett
Trying to create Git repository: git_mysett
Created Git repository: git_mysett
20abecfb36fe
applying clone bundle from https://api.media.atlassian.com/file/4d5980dc-148f-400c-97f7-8067506778a5/binary?client=403e8d2f-6661-452a-8307-5c68f82c1a13&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOnsidXJuOmZpbGVzdG9yZTpmaWxE2bUg7aRIzuKbjwOn5SF8IbGEVs33WHDVb-JYto
adding changesets
adding manifests
adding file changes
added 27 changesets with 56 changes to 41 files
finished applying clone bundle
searching for changes
no changes found
updating to branch default
35 files updated, 0 files merged, 0 files removed, 0 files unresolved
Initialized empty Git repository in /home/serge/project/hg2git/src/mysett-git/
pushing to ../mysett-git
searching for changes
adding objects
added 27 commits with 82 trees and 50 blobs
error: Could not remove config section 'remote.origin'
Counting objects: 159, done.
Delta compression using up to 12 threads.
Compressing objects: 100% (133/133), done.
Writing objects: 100% (159/159), 12.95 MiB | 7.95 MiB/s, done.
Total 159 (delta 0), reused 159 (delta 0)
To git@bitbucket.org:lebedevsergey/git_mysett.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.
Checking repository: jazzz
Trying to create Git repository: git_jazzz

В итоге, конверсия заняла около часа работы, после чего я сейчас в фоновом режиме просматриваю сконвертированные Git-репозитории, чтобы убедиться в том, что ничего не потерялось. И только после этого руками удаляю оригинальные Mercurial-репозитории. Конечно, ничто не мешает добавить в скрипт удаление с Bitbucket Mercurial-репозитория после конверсии, но на мой взгляд, тут как раз тот случай, когда излишняя автоматизация может повредить.

Готовый набор скриптов автоматизации миграции репозиториев находится тут

P.S.: Пользуясь случаем, подниму риторический вопрос — почему Git, работающий временами с довольно таки неочевидной логикой, настолько популярнее гораздо более логичного Mercurial, в котором просто коммитишь, создаешь ветки, и, если что-то пошло не так, — откатываешь коммиты, не думая о том, что находится под капотом системы контроля версий, и об указателе на Head, а просто делая то, что нужно? На мой взгляд, это несправедливо, ведь очевидное лучше неочевидного, и очень печально, что поддержку Mercurial прекращает Bitbucket — один из столпов сопротивления мейнстриму в системах контроля версий.
Tags:
Hubs:
+9
Comments 10
Comments Comments 10

Articles