Bitbucket Server(オンプレ)からbitbucket.org(クラウド)への移行

この記事は、Infocom Advent Calendar 2020 15日目の記事です。

qiita.com

2020年の9月くらいからBitbucket Serverの移行をしたときの話です。

まえがき

自分が所属している部署では2014年に自分が導入したBitbucket Serverを使っています。(その前はSubversion)
COVID-19以降は基本在宅勤務になり、制約はあまりないが接続が面倒なSSL-VPNから接続を意識しないブラウザベースのPre App VPN主体へと切り替わり、CloneやPush/Pull出来ないオンプレのGitサーバが使いにくいと思う事が増えてきてました。
また、20年以上その時々に所属する部署の開発環境を整備してきたのだけど、いつまでも自分がやれないこともあるし、残してきたシステムが古くなるさまを見ると、マネージドなサービスに移行すべきなんだろうなと考えていたりして、移行に踏み切れたのかもしれないですね。

移行直後に、AtlassianからオンプレミスのServer製品EOLが発表されたのでタイミングとしては良かったのかなと思ってます。

事前調査

中身はGitなのでリポジトリが移行出来るというのはわかってました。これはbitbucket.orgじゃなくて別サービス、例えばgithub.comでも一緒です。
Pull Requestは移行できないというのが公式サイトにも書いてあったのでそこは考えた末に、過去分はオンプレで見れるからいいやということにしました。
やってみて、気がついたのがフォーク(fork)でした。今までフォークしたリポジトリ間でPull Requestを出すようなワークフローでした。
が、普通にリポジトリを移行しただけではフォーク関係が切れてしまいました。
これでは今のワークフローが回らなくなるので、フォークを維持しながら移行できるか調べたら、なんとかなりました。

支払いはドル建てのクレジットカード払いになるということはわかっていたので、そこは社内で精算方法を確認しておきました。

移行

大まかな手順はこんな感じです。

  1. git clone --mirror で社内リポジトリのミラーを作成
  2. APIクラウドリポジトリ作成
  3. 社内のミラーに、クラウドリポジトリをremoteとして登録
  4. 社内ミラーをクラウドリポジトリへpush

フォークがあるともうちょっと複雑になります。

  1. git clone --mirror で社内リポジトリのミラーを作成
  2. APIクラウドリポジトリ作成
  3. 社内のミラーに、クラウドリポジトリをremoteとして登録
  4. 1のforkリポジトリのミラーを作成
  5. APIクラウドに2のforkリポジトリ作成
  6. 社内のforkミラーに、クラウドforkをremoteとして登録
  7. 社内forkミラーをクラウドforkへpush
  8. 社内ミラーをクラウドリポジトリへpush

という感じです。

リポジトリ

Bitbucket Serverとbitbucket.orgのリポジトリはURLが違います。
Bitbucket Serverは、 https://git.intra.example.com/scm/$PROJECT/$REPO という構成になっています。
bitbucket.orgは、 https://bitbucket.org/$WORKSPACE/$REPO です。

概念としてはクラウドにもプロジェクトはありますが、URLで識別するものではなくなっています。

単純なリポジトリの移行ならこんなスクリプトでやってます。
Macでやってますが、デフォルトのbashでは使えない機能を使ってしまったので、homebrewで入れたbashを使っています。

#!/usr/local/bin/bash

リポジトリ--mirror オプションを付けてクローンしてます。

git clone --mirror https://userid@git.intra.example.com/scm/$ORG_PROJECT/$REPO

クラウドリポジトリを作るAPIは、割と単純ですが、プロジェクトを指定するために指定するJSONは結構試行錯誤しました。
消してから作るようにしているので、移行後に流すのは厳禁です。
(※実際にはコマンドは改行なしの1行です)

curl -v -X DELETE -u userid:passwd https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}
curl -v -X POST -u userid:passwd -H "Content-type: application/json"
    https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}
    -d {
        "scm": "git",
        "is_private": true,
        "fork_policy": "no_public_forks",
        "name": "'$R'",
        "project": {
            "type": "project",
            "name": "'$PROJECT'",
            "key": "'${PROJECT^^}'"
        }
    }

Git-LFSは使っているところが少ないのですが、もれないようにスクリプトの中で発行してしまってます。

git lfs fetch --all
...
git lfs push --all new

remoteにpushするのは --mirror オプションを付けていますが、 git push --all new && git push --tags の方がいいのかもしれない?

git push --mirror new

次に移行するリポジトリがあったら調べてみようかな。

以下スクリプトの全文です。

#!/usr/local/bin/bash

BASE_DIR="/Users/gozu/projects/migration"

WORKSPACE="example_ws"
ORG_PROJECT="Project1"
PROJECT="Project1"

REPOS="repo1 repo2"

rm -rf $PROJECT
mkdir $PROJECT
cd $PROJECT

for R in $REPOS ; do
        REPO=$R.git
        rm -rf $REPO
        git clone --mirror https://userid@git.intra.example.com/scm/$ORG_PROJECT/$REPO
        curl -v -X DELETE -u userid:passwd https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}
        curl -v -X POST -u userid:passwd -H "Content-type: application/json" https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,} -d '{"scm":"git", "is_private":true, "fork_policy":"no_public_forks", "name":"'$R'", "project": {"type":"project", "name":"'$PROJECT'", "key":"'${PROJECT^^}'"} }'
        cd $REPO
        git lfs fetch --all
        git remote add --mirror=push new https://userid@bitbucket.org/$WORKSPACE/$REPO
        git push --mirror new
        git lfs push --all new
        cd ..
done

cd $BASE_DIR
  • 参考

developer.atlassian.com

qiita.com

www.seeds-std.co.jp

フォーク

bitbucket.orgのリポジトリはURLにプロジェクトが入らなくなったので、forkして同じリポジトリ名を別プロジェクトに作ることが出来ません。
なので、forkを移行するときはリポジトリ名の後ろに -fork を付加して移行しました。
https://git.intra.example.com/scm/$PROJECT/$REPOhttps://bitbucket.org/$WORKSPACE/$REPO-fork
という感じですね。

以下のスクリプトは、repo1, repo2にそれぞれ別プロジェクトでフォークがあった場合です。
forkを含めて空のリポジトリを作ってからpushしてます。

フォークはリポジトリ名に -fork を付加してAPIで作ってます。
(※実際にはコマンドは改行なしの1行です)

curl -v -X DELETE -u userid:passwd https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${FORK_R,,}-fork
curl -v -X POST -u userid:passwd
    https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${FORK_R,,}/forks
    -H 'Content-Type: application/json'
    -d {
        "name": "'$FORK_R'-fork",
        "workspace": {
            "slug": "$WORKSPACE"
        },
        "project": {
            "type": "project",
            "name": "'$FORK_PROJECT'",
            "key": "'${FORK_PROJECT^^}'"
        }
    }

以下スクリプトの全文です。

#!/usr/local/bin/bash

BASE_DIR="/Users/gozu/projects/migration"

WORKSPACE="example_ws"
ORG_PROJECT="Project1"
PROJECT="Project1"

REPOS="repo1 repo2"

rm -rf $PROJECT
mkdir $PROJECT
cd $PROJECT

for R in $REPOS ; do
        REPO=$R.git
        rm -rf $REPO
        git clone --mirror https://userid@git.intra.example.com/scm/$ORG_PROJECT/$REPO
        curl -v -X DELETE -u userid:passwd https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}
        curl -v -X POST -u userid:passwd -H "Content-type: application/json" https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,} -d '{"scm":"git", "is_private":true, "fork_policy":"no_public_forks", "name":"'$R'", "project": {"type":"project", "name":"'$PROJECT'", "key":"'${PROJECT^^}'"} }'
        cd $REPO
        git lfs fetch --all
        git remote add --mirror=push new https://userid@bitbucket.org/$WORKSPACE/$REPO
        #git push --mirror new  ← fork作ってからPushする
        #git lfs push --all new  ← fork作ってからPushする
        cd ..
done

cd $BASE_DIR

FORK_PROJECT="ForkProject"
FORK_ORG_PROJECT="fp"

FORK_REPOS="repo1 repo2"

rm -rf $FORK_PROJECT
mkdir $FORK_PROJECT
cd $FORK_PROJECT

for FORK_R in $FORK_REPOS ; do
        FORK_REPO=$FORK_R.git
        rm -rf $FORK_REPO
        git clone --mirror https://userid@git.intra.example.com/scm/$FORK_ORG_PROJECT/$FORK_REPO

        curl -v -X DELETE -u userid:passwd https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${FORK_R,,}-fork
        curl -v -X POST -u userid:passwd https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${FORK_R,,}/forks -H 'Content-Type: application/json' -d '{ "name": "'$FORK_R'-fork", "workspace": { "slug": "$WORKSPACE" }, "project": {"type":"project", "name":"'$FORK_PROJECT'", "key":"'${FORK_PROJECT^^}'"} }'

        cd $FORK_REPO
        git lfs fetch --all
        git remote add --mirror=push new https://userid@bitbucket.org/$WORKSPACE/$FORK_R-fork.git
        git push --mirror new
        git lfs push --all new
        cd ..
done

cd $BASE_DIR

cd $PROJECT

for R in $REPOS ; do
        REPO=$R.git
        cd $REPO
        git push --mirror new
        git lfs push --all new
        cd ..
done
  • 参考

developer.atlassian.com

機能差

移行後に気が付きましたが、細かい機能差があります。
どうやらクラウド版とオンプレ版では別製品のようですね。(クラウド版はPython実装で、オンプレ版はJava実装みたい)
パッと気がついたところで以下のような差異があります。だいたいクラウドでは出来ないという感じ・・・。今後に期待。

  • Shift-JISが文字化けする
    • Pull Requestで旧表示(See the old experience)にすると化けないこともある
    • .gitattribute でdiffは直るようだが、ファイル表示では化けてる
  • 要作業がない
    • 昨日(2020/12/14)、 Request changes がリリースされた!
  • Pull Request作成後、添付ファイルを追加できない
    • Pull Requestを作るときは添付できる
  • プロジェクト設定がない
    • URLでもわかるようにリポジトリがフラットに管理されているようなので仕方がないか
    • APIでまとめて設定した→※後述
  • git lfsの容量で課金額が変わる
    • むやみに使わないようにするしか無いかな
  • Pull Requestの削除がない
    • 却下のみ
  • クラウド版のJIRAとの連携は強化されている
    • JIRAも同じ時期にCloudへ移行したのでこれは嬉しかった
  • SourceTreeがうまく使えない
    • 特に社内のProxy環境下で・・・
    • なんか意味不明な挙動をするので、早々に諦めて私はForkというアプリに移行しました。VS Codeに移行したメンバーも居るよう
  • CI(pipeline)がついた
    • まだ使ってない(既存のCIはJenkins)
  • オンラインエディターがついてた
    • 以前はAddonを買っていたので節約できた
  • Markdownレンダリング結果が違う
    • 修正するしか無い
  • 自動マージングがなくなった
    • これはすごい不便。git-flowでやってるので、releaseブランチをmasterとdevelopの両方に自動でマージしたいのに・・・
  • Addonだけど、Auto Unapprove for Bitbucket Serverに相当するものがない

APIでまとめて設定

プロジェクト設定がなくなったので、リポジトリ毎に設定をしないといけないが1つづやってられない・・・
APIでやりたいことは一応出来た。
とりあえずやったことは、ブランチのアクセス許可の設定です。
masterとdevelopの削除禁止と履歴の書き換え禁止、プルリクエストでマージ(全員)です。(他もやればできそう)

f:id:gozuk16:20201215131345p:plain

(※実際にはコマンドは改行なしの1行です)

curl -v -X POST -u userid:passwd -H "Content-type: application/json"
    https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}/branch-restrictions
    -d '{
        "kind": "force",
        "pattern": "master",
        "branch_match_kind": "glob"
    }'

curl -v -X POST -u userid:passwd -H "Content-type: application/json"
    https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}/branch-restrictions
    -d '{
        "kind": "delete",
        "pattern": "master",
        "branch_match_kind": "glob"
    }'

curl -v -X POST -u userid:passwd -H "Content-type: application/json"
    https://api.bitbucket.org/2.0/repositories/$WORKSPACE/${R,,}/branch-restrictions
    -d '{
        "kind": "push", "user": [],
        "pattern": "master",
        "branch_match_kind": "glob", "groups": []
    }'

リポジトリに強制的に付与したので、リポジトリの一覧を取得してます。
APIリポジトリの一覧を取得しているのですが、一回のリクエストに付き最大100個しか取れないので以下のようにしてます。
2ページ取ればよかったのでループにはしてないですね。。。

以下スクリプトの概要です。

#!/usr/local/bin/bash

#--- page 1 ---
REPOS=`curl -v -X GET -u userid:passwd -H "Content-type: application/json" https://api.bitbucket.org/2.0/repositories/$WORKSPACE\?pagelen\=100\&page\=1 | jq -r '.values[].name'`
for R in $REPOS ; do
    ...
done

#--- page 2 ---
REPOS=`curl -v -X GET -u userid:passwd -H "Content-type: application/json" https://api.bitbucket.org/2.0/repositories/$WORKSPACE\?pagelen\=100\&page\=2 | jq -r '.values[].name'`
for R in $REPOS ; do
    ...
done
  • 参考

developer.atlassian.com

残課題

bitbucket.orgのミラーを社内に残ったBitbucket Serverに作って、1日1回くらい同期させておきたい。
バックアップにもなるし、メンテ落ちのときにソースも見られるし。