20200228のGitに関する記事は5件です。

gitコマンドよく使うやつコピペ用

git checkout <ブランチ名>

ブランチ移動

git checkout -b <ブランチ名>

新規ブランチ作成

git pull

リモートにある最新のコードをローカルに反映

git add

git管理の対象に追加

git commit -m "コメント"

変更を記録する

git push

変更内容をリモートに送る

git merge <任意のブランチ>

作業ディレクトリにリモートの<任意のブランチ>をマージさせる。コンフリクトが発生する可能性もあるので随時解決

git log

commitとmergeの履歴を見れる

git reflog

HEAD やブランチ先端の動きの履歴。各個人のローカルリポジトリに存在。git reflog <ブランチ名>で任意のブランチを見れる。

git status

ワーキング・ツリーの状態を表示する(追加されたとかコミットされたとか)

git fetch

みんなの更新内容を、自分の開発環境に取り入れる機能。みんなの作ったコミットやブランチ・タグなどを、自分の環境に取り入れる。pullと違ってマージしない

git reset <--hard/--soft> HEAD

--hard:ファイルの変更、add、commitを取り消す
--soft:commitを取り消す
git reflogでHEADを確認する

git cherry-pick <コミットID>

git logでコミットIDを確認できる
任意のコミットのみを取り込む

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リモート上で削除されたブランチをローカルからも削除するワンライナー

git for-each-ref --format '%(refname:short) %(upstream:track)' |
  awk '$2 == "[gone]" {print $1}' |
  xargs git branch -D

.gitconfigにエイリアスとして登録するとなお良しです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

過去を共有するプロジェクトを monorepo に統合するまで (tomono)

tomono を使ってmonorepoに移行した話を書きます。

(※ monorepo化 = ソフトウェアプロジェクトを複数のgitリポジトリに分けずに、1つのgitリポジトリにおさめること)

monorepo に移行したかった

業務上、あるプロジェクト内でシステムコンポーネントごと(各所で動くAPIやバッチプログラム)に、gitプロジェクトを切っていたのですが、下記の理由により、だんだんmonorepoにしたくなってきました。

  • そもそもメンテナが1-2人しかいないのに、リポジトリが10弱存在していて管理が煩雑。
  • 依存パッケージやランタイムのバージョンアップについて、それぞれのリポジトリにプルリクだしてマージするのが超煩雑。
  • 共通ライブラリもリポジトリが分かれているけれども、半分自動化されているとはいえ、共通ライブラリのアップデートのたびに下記を繰り返すのが苦痛。
    • 共通ライブラリの更新とバージョン付け
    • 付与したバージョンでリポジトリ(e.g. Sonatype Nexus)へのpublish
    • そのバージョンを利用するように各プロジェクトの依存性ファイルを更新する
    • (以後、共通ライブラリにミスを発見すると最初のステップからやり直し…)
  • それぞれのプロジェクトで共通コードがある場合、共通プロジェクトにくくりだすのが超面倒 (リポジトリ作ってCI置いてpublishして参照して…)
  • それぞれのリポジトリごとに開発環境のセットアップ手順があり(docker-compose up -dするだけですが)、無駄。
  • エディタ切り替える時間が無駄。

リポジトリを用途ごとに細かく分けているのはそれはそれで多人数だと回る面はあるのですが、少人数だと苦痛しか生んでいない状況はおかしいと思いました。

要するにリポジトリの凝集度がおかしかったんです。

monorepo なら俺に任せろー!(バリバリバリバリ) → 失敗

そういうことで、monorepoに下記のように移行しようと思ったのですが、

repo1 (git://example.com/git/repo1.git)
repo2 (git://example.com/git/repo2.git)
:
repoN (git://example.com/git/repoN.git)

↓↓上記を下記に変換↓↓

monorepo/ (git://example.com/git/monorepo.git)
  repo1/
  repo2/
  :
  repoN/

「gitのことならくわしいんだ!」と思う私は、下記のように「各プロジェクトをマージしていけばいいんじゃね?」と思い、移行を試みます。

$ git init

$ git remote add repo1 git://example.com/git/repo1.git
$ git fetch repo1
$ git merge --allow-unrelated-histories repo1/master
$ # いそいそとrepo1の全ファイルを子ディレクトリに移動させる

$ git remote add repo2 git://example.com/git/repo2.git
$ git fetch repo2
$ git merge --allow-unrelated-histories repo2/master
$ # いそいそとrepo2の全ファイルを子ディレクトリに移動させる

git merge--allow-unrelated-histories オプションにより、全く関係のないリポジトリのコミットグラフをマージすることができます。普通、gitの履歴をたどると、かならず最初のコミットにたどり着くわけですが、これを行ったリポジトリの場合は2つ以上のコミットにたどり着くことになります。

これまでプロジェクトを統合した時の経験に従って、上記のようにmonorepo統合化を始めたのですが、今回は途中でうまくいかなくなりました。

$ git remote add repo2 git://example.com/git/repo3.git
$ git fetch repo3

# ここで conflict!
$ git merge --allow-unrelated-histories repo3/master
? You have unmerged paths. (以下略)

git 「マージしようと思ったけどな、repo2の中身の一部がコンフリクトしてんねん。あ、一部はマージできとるからな! 」 (そして一部が無駄に改変されたrepo2の内容が…!)

ここで裏設定が発覚! なんとrepo3はrepo2からはるか昔に暖簾分けされたリポジトリだったのです!  …まあ、だからこそmonorepoに戻したいと思ったわけですが…

「そこは別にマージしなくていいんだよぉ!」と思ったのですが、gitさんからしたらマージせざるをえないですよね。どう見ても、マージですし。

マージさせながら、させない(謎)オプションあるかな?ないよな… うーんどうしよう… と思っていました。

tomono

そういうことで tomono の紹介です。同僚の人が見つけてくれました。

上記でやったような作業を、コンフリクトなしで全て自動的にやってくれます。

使い方は簡単で、下記のようにマージ指示のテキストを用意して、リポジトリにある tomono.sh をダウンロードして標準入力に与えてあげればいいです。

$ bash tomono.sh <<EOF
git://example.com/git/repo1.git repo1
git://example.com/git/repo2.git repo2
git://example.com/git/repo3.git repo3
:
git://example.com/git/repoN.git repoN
EOF

# 上記完了後、 core/ ディレクトリの中に全てがおさまったリポジトリができあがっている
$ ls -a core
.git
repo1 repo2 repo3 ... repoN

履歴はもちろん、手作業で諦めていた各リポジトリのタグも、すべてきちんと移行できました。特に魔法を使っているわけではなく、gitコマンドで移行してくれます。

俺たちのmonorepo化は始まったばかりだ

ここでmonorepo化が終わればいいのですが、どちらかというとリポジトリ統合の後からがmonorepo化の本番で、開発環境定義を修正したり、モジュール間の依存性を書き直したり、きちんとCI/CDの設定をしたり、READMEを修正したりなどなど、いろいろ実施する必要がありました。

特にCI/CDは、そもそも分かれていたリポジトリについて、一斉にビルド・テスト・パブリッシュをするようになるので、ビルド時間増大の懸念などあったのですが、特にそこまで問題にならずに統合することができました。(もちろんいくつかトリックは入れたのですが)

みなさんも、リポジトリ多すぎと思ったら、monorepo化してみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitLab CIの基本的な流れ

GitLab CI

GitLabから他のツールに連携して、ビルド、テスト、デプロイなどのCI連携ができる機能です。
使ってみると意外と色んなことが出来て面白そうなので、作り方をメモしておきます。

今回の構成と動作の流れ

バージョンなど

OS...Debian 9
GitLab...12.6
GitLab Runner...12.6
Ansible...2.10

流れ

  • Debian_1にDockerがインストールされていて、GitLabがコンテナで動作している。ここにレポジトリがあり、ソースが保存される。
  • 別のDebian_2にGitLab Runner(後述)とAnsibleがインストールされている。
  • Ansibleはさらに別のDebian_3を構築する。

gitlab.png

  1. pushやmergeなどのタイミングでGitLab本体がGitLab runnerに通知を送る
  2. Runnerは実行条件に合致していたら動作を開始する
  3. Runnerはまずgit cloneコマンドでレポジトリの内容をローカルにコピーして、そのレポジトリにcdする
  4. Runnerはそのノード上でansible-playbookコマンドを実行する
  5. Ansibleは普通に実行した時と同じようにdebian_3を構築する
  • 2のところの実行条件と、4で実行するコマンドはジョブの定義として作ってあげる必要があります。
  • また、5のところでAnsibleがちゃんとDebian_3を構築できるようにinventryを設定してあげる必要があります。 環境変数とかを使ってうまくやってください。

GitLab Runnerのセットアップ

上の図でDebian_2にあるGitLab Runnerをインストールします。

Runnerの種類

GitLab Runnerには大きくShared RunnerとSpecific Runnerがあります。
自分もよくわかってないので、詳しくはここを見てください。

Shared Runner

複数のプロジェクトで共通して利用できるRunner。
いつどれが使われるかはRunnerとCIジョブに付けられたタグで決まります。

Specific Runner

特定のプロジェクトでのみ利用されるRunner

Runnnerのインストール

GitLabの公式サイトから.debのバイナリを落としてくることができます。
Debian以外にもRHEL, Windows, Mac, FreeBSDなど複数に対応します。

これだけでインストールが完了します。

root@debian:~# dpkg -i gitlab-runner_amd64.deb

Runnerの初期設定

Runner側でコマンドをいくつか打つことで、GitLabにRunnerとして登録することができます。

root@debian:~# gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=4496 revision=1b659122 version=12.8.0
Running in system-mode.

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://10.91.77.8/ #GitLabサーバーのURLを入力
Please enter the gitlab-ci token for this runner:
dx5bFN4PJQ9XNUj_Vxbf #GitLabのadmin画面から取得できるトークンを入力
Please enter the gitlab-ci description for this runner:
[debian]: Ansible runner #このRunnerの説明を入力
Please enter the gitlab-ci tags for this runner (comma separated):
ansible #コンマ区切りでタグを入力
Registering runner... succeeded                     runner=dx5bFN4P
Please enter the executor: docker+machine, docker-ssh+machine, kubernetes, docker, docker-ssh, shell, virtualbox, custom, parallels, ssh:
shell #ジョブの実行方法を入力
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

ジョブの実行方法はローカルでシェルを実行する、SSHでリモートを操作する、Dockerコンテナを立ち上げるなど複数から選べます。

.gitlab-ci.ymlの作成

環境が完成したら実際にCIを実行する定義.gitlab-ci.ymlを作ります。
このファイルはレポジトリのルートに置いておき、他のソースと同じようにバージョン管理されます。

今回はレポジトリに登録されたAnsible playbookを自動実行するように以下のようなYAMLを書きました。
詳細な文法などはGitLabの公式サイトを見てください。

.gitlab-ci.yml
# ステージ定義
stages:
  - test
  - deploy
  - result

# Pushするたびに文法チェックをする
sanity_check:
  stage: test
  script:
    - "ansible-playbook site.yml --syntax-check"
  only:
    - push
  tags:
    - ansible

# MRを作成したらDRY RUNで実行し、エラーにならないかを確認
run_playbook_dry:
  stage: test
  script:
    - ansible-playbook -i ansible_hosts.ini site.yml --check
  only:
    - merge_requests
  tags:
    - ansible

# develブランチにマージされたタイミングで自動デプロイ
run_playbook:
  stage: deploy
  script:
    - ansible-playbook -i ansible_hosts.ini site.yml
  only:
    - devel
  tags:
    - ansible

# ジョブが失敗したらMRにコメントを残す
send_failure_message:
  stage: result
  script:
    - 'MR_IID=$(curl --request GET --header "Private-Token: $API_PRIVATE_TOKEN" "http://10.91.77.8/api/v4/projects/$CI_PROJECT_ID/repository/commits/$CI_COMMIT_SHA/merge_requests" --insecure | jq --raw-output ".[0].iid")'
    - 'PROJECT_URL=$(curl --request GET --header "Private-Token: $API_PRIVATE_TOKEN" "http://10.91.77.8/api/v4/projects/$CI_PROJECT_ID" --insecure | jq --raw-output ".web_url")'
    - 'curl --request POST --header "Private-Token: $API_PRIVATE_TOKEN" --data "body=CIジョブに失敗しました。<br><a href=$PROJECT_URL/pipelines>パイプラインの詳細</a>から内容を確認してください。" http://10.91.77.8/api/v4/projects/$CI_PROJECT_ID/merge_requests/$MR_IID/notes --insecure'
  when: on_failure
  only:
    - merge_requests
  tags:
    - ansible

まずGitLabから対象のサーバーにレポジトリがgit cloneされます。
前述の通りクローンした後はそのディレクトリにcdされるので、scriptブロックに記載するコマンドはすべてレポジトリのルートから見える相対パスで書きます。

ジョブ

YAMLの一番上の階層に書いてあるもの(予約語以外のもの)がジョブで、この単位でスクリプトや実行条件を指定します。
上のスクリプトでいうとsanity_testrun_playbook_dryとかsend_failure_messageですね。

stages

順序立ててジョブを実行する必要がある場合、stagesを定義してジョブを所属させると順番を決めて実行できます。
同じstageで複数のジョブが定義されている場合はパラレルで実行されます。
上のでいうと、まずtestに所属するジョブが実行され、次にdeploy、終わったらresultが動きます。

only,except

ジョブごとに実行条件を指定できます。(exceptは除外条件)
pushはpushした時、merge_requestはMRを作成した時、ブランチ名を書くとそのブランチが更新された時です。
今回は実際にplaybookを動かすところをonly: develにしているので、develブランチの中身が更新された時だけPlaybookが適用されます。

tag

Runnerのタグを指定します。
ここに指定したRunnerでジョブが実行されます。

APIを使ってコメント投稿

ジョブsend_failure_messageではジョブに失敗した時という条件でMR画面にコメントを書き込んでいます。

scriptに書かれてる内容は普通にRunnerがインストールされているOS上でコマンドを打ってるのと同じなので、GitLab APIを使ってコメント残したり色々できます。
$API_PRIVATE_TOKENというのはgitlab-runnerのconfig.toml内で定義した環境変数的なやつです。変数の中身はGitLabのWeb画面から取ってきたbot用アカウントのプライベートトークンです。

実際に試す

Push

ローカルからpushすると、レポジトリの一番上に緑のチェックがつきます。
YAMLの文法に問題なかったということがわかります。

citest2.png

MR作成

MRを作成するとAnsibleのDRY RUNが走り、不具合が起きないかをチェックできます。
成功したらさっきと同じように緑のチェックが付きます。

citest.png

クリックしないと具体的に何のジョブが実行されたのかわからないのは難点ですね。

マージ

マージされると実際にPlaybookが実行され、サーバーに構成が反映されます。
上の画像の下にもう一つチェックが付きます。

参考:詰まったところ

よくわからんエラーで詰まったのでメモしておきます。
エラーはコピーしてなかったので後からネットで拾ってきました。

HTTP 403

Running with gitlab-ci-multi-runner 9.4.2 (6d06f2ec)
on host-vm (a126d8fa)
Using SSH executor...
Running on host-vm via host-vm...
Cloning repository...
Cloning into 'builds/a126d8fa/0/project/appName'...
fatal: unable to access 'https://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@gitlab.my-company.com/project/appName.git/': The requested URL returned error: 403
ERROR: Job failed: Process exited with: 1. Reason was:  ()

→ユーザー権限の問題でした。権限振り直したら直りました。

git fetch-packのエラー

Running with gitlab-runner 11.9.2 (fa86510e)
  on stage-test yMEso5rx
Using SSH executor...
Running on d1.XXXXXX.com via d1.XXXXXX.com...
warning: templates not found builds/yMEso5rx/0/kudja/postel-deluxe.tmp/git-template
Reinitialized existing Git repository in /home/qapd/builds/yMEso5rx/0/XXXX/XXXXXX/.git/
Clean repository
Fetching changes with git depth set to 10...
fatal: remote origin already exists.
fatal: git fetch-pack: expected shallow list
ERROR: Job failed: Process exited with: 1. Reason was:  ()

→Runnerに入ってるGitのバージョンを上げたら直りました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git pull origin masterしたら「error: Your local changes to the following files would be overwritten by merge」となる

事象

masterブランチを親とするfeatureブランチをチェックアウトした状態でgit pull origin masterをしたら、「error: Your local changes to the following files would be overwritten by merge」となった。

$ git branch
  master
* feature
$ git pull origin master
From github.com:Example-Git/api-repo
 * branch            master     -> FETCH_HEAD
Updating 5966be1..1c8aaff
error: Your local changes to the following files would be overwritten by merge:
        Makefile
Please commit your changes or stash them before you merge.
Aborting

原因

masterブランチの変更により上書きされるファイルMakefileが、ローカルのfeatureブランチで変更されたまま未コミットであったためだった。

$ git status
On branch feature
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   Makefile

no changes added to commit (use "git add" and/or "git commit -a")

解消

Makefileの変更はコミット不要だったので、git checkoutにより変更を破棄したら正常にgit pull origin masterできた。

$ git checkout Makefile
$ git status
On branch feature
nothing to commit, working tree clean
$ git pull origin master
From github.com:Example-Git/api-repo
 * branch            master     -> FETCH_HEAD
Updating 5966be1..1c8aaff
Fast-forward
 Makefile                                                     |    3 +-
 sam.yml                                                      |   26 +
 2 files changed, 28 insertions(+), 1 deletions(-)
 create mode 100644 src/handlers/__init__.py

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む