ksaitoの日記

日々試したことの覚え書き

gitのrebaseでコミットを書き換える

git rebaseでコミットをきれいにする方法です。

使うタイミング

自分専用のトピックブランチで作業が完了して 元のブランチに戻す前 に行います。

練習用のリポジトリ作成

sampleというファイルに機能が3つインプリされているという想定のテスト用のリポジトリを作成します。

$ git init test
$ cd test
$ for i in `seq 1 3`; do printf "func%d\n\n" $i; done > sample
$ cat sample 
func1

func2

func3

$ git add sample; git commit -m "init"
[master (root-commit) c509fc6] init
 1 file changed, 6 insertions(+)
 create mode 100644 sample
$ 

トピックブランチを作って、3つの機能の全てに修正を加えてコミットしたと想定します。

$ git checkout -b topic
Switched to a new branch 'topic'
$ sed -ie 's/\(func.\)/\1+fix/' sample 
$ cat sample
func1+fix

func2+fix

func3+fix

$ git commit -a -m "fix"
[topic 77adf00] fix
 1 file changed, 3 insertions(+), 3 deletions(-)
$ 

この時点で、気がつけば、git reset HEADで戻すことも可能ですが、気が付いたときには、もう一つ機能が追加されていたとします。

$ echo func4 >> sample
$ git commit -am "これは、正しいコミットで修正したくない"
[topic e836500] これは、正しいコミットで修正したくない
 1 file changed, 1 insertion(+)
$ 

テスト用のリポジトリは、こんな感じになります。

$ git --no-pager log --pretty=oneline
e836500e25943c80e67fcbfdcfef10e0d8fc0040 これは、正しいコミットで修正したくない
77adf00234c23ceda93be668eef460a218944f5a fix
c509fc62f1a14a5d374c20203fecec670cf82a6f init
$ 

コミットを分割する

3つの機能の修正は、独立しているので、修正毎に3つのコミットにするべきだったと想定して、コミットを分割します。

対象のコミットを確認する。

git show-branchコマンドで、現在のtopicブランチに2つのコミットがありtopic^に修正したいコミットがあることがわかります。

$ git show-branch
! [master] init
 * [topic] これは、正しいコミットで修正したくない
--
 * [topic] これは、正しいコミットで修正したくない
 * [topic^] fix
+* [master] init
$ 

対象のコミットを修正

git rebase -iを使って対象のコミットを修正します。

対象は、topic^なので、その一つ前のmasterを指定します。

$ git rebase -i master

エディタが開いて対象のコミットが下記のように表示されます。

pickとなっている部分で[e]を選択してエディタを押します。

pick 77adf00 fix                                                                                                                                                                     
pick e836500 これは、正しいコミットで修正したくない                                                                                                                                  
                                                                                                                                                                                     
# Rebase c509fc6..e836500 onto c509fc6 (2 command(s))     

修正したいコミットのpickeditに変わったのを確認してエディタを修正します。

edit 77adf00 fix                                                                                                                                                                     
pick e836500 これは、正しいコミットで修正したくない                                                                                                                                  
                                                                                                                                                                                     
# Rebase c509fc6..e836500 onto c509fc6 (2 command(s))   

エディタが終了すると、こんなメッセージが表示されます。

Stopped at 77adf00234c23ceda93be668eef460a218944f5a... fix
You can amend the commit now, with

        git commit --amend 

Once you are satisfied with your changes, run

        git rebase --continue

$ 

git resetで修正を戻します。

$ git reset HEAD^
Unstaged changes after reset:
M       sample
$ 

修正内容を分割

git add -pを使って修正を分割します。

下記のように表示されるので、必要な修正まで[s]で分割していきます。

$ git add -p
diff --git a/sample b/sample
index bd073e0..b9dc832 100644
--- a/sample
+++ b/sample
@@ -1,6 +1,6 @@
-func1
+func1+fix
 
-func2
+func2+fix
 
-func3
+func3+fix
 
Stage this hunk [y,n,q,a,d,/,s,e,?]? 

[s]を押すと次のように、コミットが分割されます。

Stage this hunk [y,n,q,a,d,/,s,e,?]? s
Split into 3 hunks.
@@ -1,2 +1,2 @@
-func1
+func1+fix
 
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? 

[y]を押して、分割されたコミットがステージされた状態にします。

[q]を押して一旦、終了します。

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
@@ -2,3 +2,3 @@
 
-func2
+func2+fix
 
Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]? q

$ 

git statusで確認すると下記のようになります。

$ git status
interactive rebase in progress; onto c509fc6
Last command done (1 command done):
   edit 77adf00 fix
Next command to do (1 remaining command):
   pick e836500 これは、正しいコミットで修正したくない
  (use "git rebase --edit-todo" to view and edit)
You are currently splitting a commit while rebasing branch 'topic' on 'c509fc6'.
  (Once your working directory is clean, run "git rebase --continue")

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   sample

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:   sample

$ 

下記のように分割したコミットだけがステージされるので、これを、あと2回繰り返して、3つのコミットに分割します。

$ git --no-pager diff --staged
diff --git a/sample b/sample
index bd073e0..604342d 100644
--- a/sample
+++ b/sample
@@ -1,4 +1,4 @@
-func1
+func1+fix
 
 func2
 
$ git --no-pager diff
diff --git a/sample b/sample
index 604342d..b9dc832 100644
--- a/sample
+++ b/sample
@@ -1,6 +1,6 @@
 func1+fix
 
-func2
+func2+fix
 
-func3
+func3+fix
 
$ 

ログが分割されたことを確認します。

$ git --no-pager log --pretty=oneline
04d8da4ce4057583d6b917f0b1e481211fdb207c func3
e780e2cbd878d6d7bc2c9dd99cb1e0cefc9131dc func2
8263453e933119f5397b96267425c357731120e0 func1
c509fc62f1a14a5d374c20203fecec670cf82a6f init
$ 

git rebase --continueでログの修正を終了します。

$ git rebase --continue
Successfully rebased and updated refs/heads/topic.
$ 

これで、コミットが分割されました。

$ git show-branch
! [master] init
 * [topic] これは、正しいコミットで修正したくない
--
 * [topic] これは、正しいコミットで修正したくない
 * [topic^] func3
 * [topic~2] func2
 * [topic~3] func1
+* [master] init
$ 

以上

systemdで設定を上書きする方法

systemdで設定を上書きするには、/etc/systemd/systemに設定します。

systemctl edit <サービス>コマンドがあります。

$ sudo systemctl edit <サービス名>.service

エディタが起動されて追加設定を書き込めます。 設定先は、/etc/systemd/system/<サービス名>.service.d/override.confです。

$ ls -l /etc/systemd/system/<サービス名>.service.d/
合計 4
-rw-r--r-- 1 root root 27 11月  6 08:35 override.conf
$

今まで、手動でやってました。

既存のgitリポジトリを短時間でコピーする。

既存のgitリポジトリを短時間でコピーする方法です。

ローカルにクローン

ブランチを指定して最新コミットに限定してクローンします。

$ git clone --depth 1 -b <ブランチ名> <URL>

リモートにプッシュ

自分用のリポジトリにプッシュするとrejectされます。

$ git remote add origin <URL>
$ git push origin <ブランチ名>
...
 ! [remote rejected] <ブランチ名> -> <ブランチ名> (shallow update not allowed)
error: failed to push some refs to <URL>
$

部分的にクローンしたgitリポジトリ--unshallowオプションでpush可能なリポジトリに変換します。

再度、pushします。

$ git fetch --unshallow
$ git push origin <ブランチ名>
Counting objects: 227, done.
Compressing objects: 100% (97/97), done.
Writing objects: 100% (227/227), 66.44 KiB | 0 bytes/s, done.
Total 227 (delta 132), reused 214 (delta 129)  
To <URL>
 * [new branch]      <ブランチ名> -> <ブランチ名>
$

pythonの開発環境

pythonでちょっとしたテストをする際に独立した環境を準備する方法です。

virtualenvコマンドで独立したpython環境を作成して、direnvcdするだけで環境の切り替えをすることが出来ます。

環境

初期設定

下記のパッケージをインストールします。

$ sudo apt install direnv python-virtualenv

.bashrcに下記の2行を追加します。(direnvの設定)

$ tail -2 ~/.bashrc
export EDITOR=emacs
eval "$(direnv hook bash)"

開発環境の作成

開発環境は、ディレクトリを作成して.envrcファイルを準備します。

$ mkdir sample
$ cd sample
$ direnv edit .

EDITORに指定したエディタが開くので必要な設定を追加します。 pythonのテスト環境をvirtualenvで準備する場合は、以下のように設定します。

$ direnv edit .
direnv: loading .envrc
Running virtualenv with interpreter /usr/bin/python2
New python executable in /home/ksaito/tmp/sample/plib/bin/python2
Also creating executable in /home/ksaito/tmp/sample/plib/bin/python
Installing setuptools, pkg_resources, pip, wheel...done.
direnv: export +VIRTUAL_ENV ~PATH
$ cat .envrc
PLIB=plib
ACTIVE=${PLIB}/bin/activate
if [ ! -e ${ACTIVE} ]
then
    virtualenv --no-site-packages ${PLIB}
fi
. ${ACTIVE}
$ 

使い方

使うときには、ディレクトリに移動するだけです。

direnvVIRTUAL_ENVPAHTが追加され、pipでパッケージ一覧を表示すると4つしかないことが分かります。

$ cd sample
direnv: loading .envrc
direnv: export +VIRTUAL_ENV ~PATH
$ pip list --format=legacy
pip (9.0.0)
pkg-resources (0.0.0)
setuptools (28.7.1)
wheel (0.30.0a0)
$ pip list --format=legacy | wc -l
4
$ 

ディレクトリを移動すると自動的に設定はもとに戻ります。

パッケージは、32個あります。

$ cd ..
direnv: unloading
$ pip list | wc -l
32
$ 

direnvは、pythonに限らず利用できるのでとても便利です。

プロンプトを変えたかったのですが、PS1は設定しても無視されてしまう...

複数のディレクトリの差分と同期

複数の環境に共通のシェルやプログラムを配置することがあります。「全て同じはず」という言葉を信じる以外に確認方法がない場合、gitに移行する方法です。

bareリポジトリの作成

bareリポジトリを作ります。GitHubやbitbucketを使ってもokです。

$ git init --bare repo

対象の複数のディレクトリにリポジトリを作ります。

$ cd serv1
$ git init
$ git add .
$ git commit -m "init"
$ cd ../srv2
$ git init
$ git add .
$ git commit -m "init"

bareリポジトリにpushします。 同じことをserv2にも行います。

$ cd serv1
$ git remote add origin user@localhost:/tmp/bare
$ git push -u origin master:serv1
$ 

bareリポジトリをclone

bareリポジトリをcloneして、serv1serv2を、それぞれcheckoutします。

$ git clone user@localhost:/tmp/repo work
$ cd work
$ git checkout -b serv1 origin/serv1
$ git checkout -b serv2 origin/serv2
$

差分

serv1ブランチとserv2ブランチの差分を確認することで、違いが分かります。

$ git diff --name-status serv1 serv2
M       readme.txt
D       src/scr2
$

serv1をチェックアウトして修正/テストする

テスト環境で特定のブランチをcloneして修正/テストします。

$ git clone -b serv1 user@localhost:/tmp/repo work
$ cd work
$ git branch -av
* serv1                0640614 update2
  remotes/origin/serv1 0640614 update2
  remotes/origin/serv2 b7ab37e update
$

テストが完了したらadd/commitしてpushします。

$ git add .
$ git status
On branch serv1
Your branch is up-to-date with 'origin/serv1'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   readme.txt

$ git commit -m "fix"
[serv1 554a8cb] fix
 1 file changed, 1 insertion(+)
$ git push
$ 

リリース

serv1環境でpullしてタグ付けします。

$ cd serv1
$ git pull
   0640614..554a8cb  serv1      -> origin/serv1
 * [new branch]      serv2      -> origin/serv2
Updating 0640614..554a8cb
Fast-forward
 readme.txt | 1 +
 1 file changed, 1 insertion(+)
$ git tag v1.1
$ 

ブランチをサーバ名、タグをリリースを特定する番号にすることで台帳+手作業をなくすことが出来ます。

以上

git mergetoolを使ったコンフリクトの解消

gitのmergetoolでemacsのemergeを選択する設定です。

git config --global merge.tool emerge

コンフリクトが発生した場合に、mergetoolを起動するとemacsのemergeが起動します。

git mergetool

abで、どちらの変更を採用するか、pnで前後の変更へ移動、qでマージ完了です。 マージ処理をキャンセルするには、Ctrl-]です。

Ubuntu 16.04のDockerをaufsからdevicemapperに切り替える。

Ubuntu 16.04のDockerをaufsからdevicemapperに切り替えました。 aufsで作成したコンテナは、全て削除されます。

RedHat系のコンテナでswapを追加しようと試みました。 LVMなのでサイズ変更できるかと思いましたが、下記の通りmdsetupがエラーとなりました。 aufsをdevicemapperに変えることで改善するか試しました。(結果改善しませんでした。涙)

$ docker pull oraclelinux:7.2
$ docker run -it --rm oraclelinux bash
[root@a453b4f54f10 /]# dmsetup ls
/dev/mapper/control: open failed: Operation not permitted
Failure to communicate with kernel device-mapper driver.
Check that device-mapper is available in the kernel.
Incompatible libdevmapper (unknown version) and kernel driver (unknown version).
Command failed
[root@a453b4f54f10 /]# 

環境

使った環境は、下記の通りです。

$ grep DESC /etc/lsb-release 
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
$ docker --version
Docker version 1.12.1, build 23cf638
$ docker info | grep Storage
Storage Driver: aufs
$ 

変更

/etc/default/dockerにdevicemapperを使うようにオプションを設定します。

dockerサービスを止めてaufsで作成されたコンテナを削除します。

$ sudo systemctl stop docker
$ sudo rm -rf /var/lib/docker
$

Ubuntu 16.04のdockerは、/etc/default/dockerのオプションを読み込まないのでsystemdのお作法に従ってオプションを読み込ませるように変更します。

$ grep devicemapper /etc/default/docker 
DOCKER_OPTS="--storage-driver=devicemapper"
$ sudo cp -p /lib/systemd/system/docker.service /etc/systemd/system
$ egrep "Envi|DOCKER_OPT" /etc/systemd/system/docker.service
EnvironmentFile=/etc/default/docker
ExecStart=/usr/bin/dockerd -H fd:// $DOCKER_OPTS
$ 

systemdの設定を反映してdockerを起動します。

$ sudo systemctl daemon-reload
$ sudo systemctl start docker
$

devicemapperに切り替わりました。

$ docker info | grep Storage
 WARNING: Usage of loopback devices is strongly discouraged for production use. Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.
Storage Driver: devicemapper
WARNING: No swap limit support
$ 

結果は変わらず...

$ docker pull oraclelinux:7.2
7.2: Pulling from library/oraclelinux
10ec637c060c: Pull complete
Digest: sha256:583f9e880f9228894555775c720d32eb22bf09cd13009c036d8f19b3257ccb41
Status: Downloaded newer image for oraclelinux:7.2
$ docker run -it --rm oraclelinux bash
Unable to find image 'oraclelinux:latest' locally
latest: Pulling from library/oraclelinux
Digest: sha256:583f9e880f9228894555775c720d32eb22bf09cd13009c036d8f19b3257ccb41
Status: Downloaded newer image for oraclelinux:latest
[root@f7d5ae7a0107 /]# dmsetup ls
/dev/mapper/control: open failed: Operation not permitted
Failure to communicate with kernel device-mapper driver.
Check that device-mapper is available in the kernel.
Incompatible libdevmapper (unknown version) and kernel driver (unknown version).
Command failed
[root@f7d5ae7a0107 /]#