ズボラな僕らだからこそ「正しそうで正しくない少し正しいgitの使い方」

  • 26
    いいね
  • 0
    コメント

本日のいまさら記事はこちらです。

この記事の対象読者

  • commit messageは wip fix が9割を超える
  • 「宇宙人」と書いて「マメな人」と読む
  • 夏休みの宿題を9/1までに終える人は未来人である
  • 「毎日1分頑張るだけ」などと言える超能力者がいるらしい

ただの人間にしか興味ありません。
この中に宇宙人、未来人、異世界人、超能力者がいたら、そっと記事を「いいね」して閉じなさい。以上!

コミットの正しい運用って?

色んな世界線の人が参考になる記事を書いています。

http://qiita.com/kozyty@github/items/87fa95a236b6142f7c10
http://qiita.com/itosho/items/9565c6ad2ffc24c09364
http://qiita.com/risacan/items/f4cbabc62b684ab9296d
http://qiita.com/crifff/items/1abf08bca4ce51db4775

などなど。

正しい世界の人はこれらの記事を参考にしましょう。

正しいコミットを常に意識しろ、なんて面倒くさい。ならどうすればいい?

ズボラなコミットメッセージ、適当なプルリクエスト、File Changes(243) は嫌われます。
チームメンバーのMPを削っても、良いことはありません。

adv2016-12-02-01.png

ですから、次の事だけは気を付けましょう。

みんなで作業するブランチは汚さない

  • 自分だけのブランチで作業して、できあがったらPR!PR!!PR!!!
  • マージするときはGitHubのSquash and Mergeを使おう

人の目に触れるもの(≒プルリクエスト)はキレイにする

  • プルリクエストの description で何を目的とした変更なのか明確に説明する
    • ポエムにしない。
    • せめて新機能の追加なのか、バグフィクスなのか、リファクタリングなのかだけでも書く。
  • どこを重点的に見てほしいのか、小さな単位で説明する
    • もちろん「小さい変更・小さいPR」が良いけど、面倒くさい諸事情により大きくなることもある。
    • 「●●ってファイルの✕✕行目、△△のためにこうしたけど、□□かもしれんので見て欲しい」

「●●ファイルの✕✕行目」って・・・もっとなんか良いのあるだろ!

はい。そこまで来て初めて、「コミットは小さくしよう、メッセージは適切に」という言葉が身にしみ得るんです。最近実感しました。

とはいえ、「あれやってる最中にここが気になってこっちも直した」は普通の人ならよくあることです。
実装中には面倒くさくて、「あ、コレ違う、別のブランチ切ろう、あの時点に戻ってここからブランチ切って・・・」なんて、面倒くさくてやってられません。面倒くさくて

例えばSlackに投稿する前に、間違いがないか厳密に推敲するでしょうか?しませんよね。
とりあえずEnterキーをターン!で、後からEditすりゃいいですよね。あとで見えなきゃいいんです。

「あとでキレイにすればいいや」。

そもそもVersion管理をとても乱暴に言ってしまえば、「あとで何があったか見返すためのもの」「あとで過去の編集を改めるためのもの」です。
ですから、コミットなんて最初は適当でいいんです。
実装中は直したいところを直して、やりたい放題コミットしまくりましょう。

ただし、自分用の作業ブランチで。他人様に迷惑をかけることだけはやめましょう。

こんなふうに出来たらいいな

●●の機能を追加するため、…

  • ●●の実装: 9xd4941
  • ●●のテスト: 0979xd2
  • ついでにやった✕✕のリファクタリング: 337xab7
  • 自動生成コード部分: 6exd364

見やすいですね。もっとキレイにすることはできるでしょうが…

http://qiita.com/umanoda/items/93aec41213f8e3ce14c8

「あとでキレイにする」ための色々

ユースケースごとに便利な git コマンド

では実際、一度コミットしてしまった内容をキレイにするために、ユースケース別に使えるgitコマンドを少しばかり。

過去のコミットをまとめたい・並び替えたい

さて、貴方の目前には作業中に作った大量のwipコミットがあります。

$ git branch

  master
* wip-branch

$ git log

commit 035a324a7e9e03822be202536a89d3bd27451c95
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 08:15:42 2016 +0900

    wip まだゴミあった

commit d387cf8dd21b297d9b8c427db8b7007fe9dee108
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 07:57:33 2016 +0900

    wip バグ直し

commit 101f652a9dd51a29269fa818b91d2fac8a2e3d0c
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 07:51:02 2016 +0900

    wip できた?

commit 59be7f5930f6d3fe7d3cda89ddb1dd252d170b77
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 06:53:42 2016 +0900

    wop ごみ掃除

commit df735e2e2b31df94d73ad134fa02692996e3ad9d
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 05:09:27 2016 +0900

    functions 再整理 (#42)

df735e2 (functions 再整理 (#42)) より後のコミットがすべてwipのようです。
さほど複雑な状況にはなっていません。ただ、「ゴミ掃除」コミットが2個あったり、「出来た?」と「バグ直し」のように作業の途中経過がコミットされていたりします。
本来は「ゴミ掃除」で1コミット、「◯◯機能の実装」で1コミット、というのが望ましいところです。

そこで、git rebase -i でコミットを並び替えて、いくつか一つにまとめてしまいます。

$ git rebase -i df735e2e2b31df94d73ad134fa02692996e3ad9d # ← "functions 再整理 (#42)" のコミットハッシュ

pick 59be7f5 wop ごみ掃除
pick 101f652 wip できた?
pick d387cf8 wip バグ直し
pick 035a324 wip まだゴミあった

# Rebase 326fc9f..0d4a808 onto d286baa
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

エディタ(デフォルトではvim)が開いて、説明通りに編集すれば、

  • 並び替える
  • 1つにまとめる
  • コミットメッセージを変える
  • コミット内容を修正する

といったコミットの操作を行えます。
ここでは、以下のようにいじってみます。

reword 59be7f5 wop ごみ掃除
squash 035a324 wip まだゴミあった
reword 101f652 wip できた?
squash d387cf8 wip バグ直し

結果、git logは次のようになりました。

commit 4641774bde2c85eaa3ce905e1e6d9b4e1cebf60a
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 07:51:02 2016 +0900

    ◯◯の実装

commit 398acba7019ca7f7d915b55711c9ac53d7b22d3e
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 06:53:42 2016 +0900

    リファクタリング:使用していない✕✕パッケージを削除

commit df735e2e2b31df94d73ad134fa02692996e3ad9d
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 05:09:27 2016 +0900

    functions 再整理 (#42)

めでたしめでたし。

git rebase -i による編集の詳細は、別の記事でまとめている人がたくさんいらっしゃいます。

http://qiita.com/takke/items/3400b55becfd72769214

過去のコミットをやりなおしたい

さて、貴方の目前にはまたしても大量の wip コミットがあります。

$ git log

commit eb8b87a08098fc2fd7df9805970dd197ab850447
Author: Kyoichiro Yamada <yamada@wacul.co.jp>
Date:   Tue Sep 27 21:52:57 2016 +0900

    fix

commit 61a476ba015556e167a46c3e29fcada60c510e9b
Author: Kyoichiro Yamada <yamada@wacul.co.jp>
Date:   Tue Sep 27 21:48:03 2016 +0900

    fix

commit c2df90e8bc50446b736249ecaaac89f2c4c4dc9e
Author: Kyoichiro Yamada <yamada@wacul.co.jp>
Date:   Tue Sep 27 21:44:01 2016 +0900

    fix

commit 37d070c710f4df384686a27a99b9a2e9dcf84610
Author: Kyoichiro Yamada <yamada@wacul.co.jp>
Date:   Tue Sep 27 21:41:06 2016 +0900

    fix

commit b3293ccd4420938941843aa70d4b85d5ac4f3acc
Author: Kyoichiro Yamada <yamada@wacul.co.jp>
Date:   Tue Sep 27 21:40:45 2016 +0900

    fix

commit 490e1c0c46b67594aee31e38fcb28076ed6c1fdb
Author: Kyoichiro Yamada <yamada@wacul.co.jp>
Date:   Sun Sep 25 16:35:56 2016 +0900

    fix

commit df735e2e2b31df94d73ad134fa02692996e3ad9d
Author: kyoh86 <yamada@wacul.co.jp>
Date:   Tue Nov 22 05:09:27 2016 +0900

    ◯◯機能の実装 (#43)

さて、rebaseしますか・・と思ったものの、今度はどのコミットで何をしたか、メッセージは何も語りかけてはくれません
過去の自分をぶん殴りたいですか?ぶん殴りましょう。

adv2016-12-02-02.png

git reset で、巻き戻りたい過去のコミットを指定します。
この目的では間違っても --hard は使わないように気を付けましょう。

$ git reset df735e2e2b31df94d73ad134fa02692996e3ad9d # ← "◯◯機能の実装 (#43)" のコミットハッシュ

これで、過去の自分の愚かなコミットたちはなかったことになりました。
ただし、--hard オプションは使用していないので、ファイルの変更は生き残ったままです
あとは正しい commit を生産するだけです。

$ git add foo.go
$ git add foo_test.go
$ git commit -m '△△機能の実装'
$
$ git rm bar.go
$ git rm -r baz/
$ git commit -m '□□機能の廃止'

同じファイルの中に、違う目的の変更が混じってしまった!

前述の git reset が要るようなケースではママあることですが、
単一のファイル内に違う目的の変更が混じってしまった場合、どうすれば良いんでしょう。

git add には、 -p というオプションがあります。partialp でしょうね。
実行すると、対象ファイルのdiffが表示され、必要な変更だけを選択して add することができます。

詳細はこちらを参考にしましょう。

http://qiita.com/crifff/items/1abf08bca4ce51db4775

ブランチ名にwipって入ってるんだよね…

誰かが自分の作業ブランチから新しくブランチを切ってしまう という悲劇を少しでも避けるため、
作業ブランチ等にはwip-xxxxxのようなそれと分かる名前を付けることもあります。(というか付けたほうが良い)

ですが、いざPRを出す段になって、ブランチ名がwip-xxxxx では締まりません。
「もうコレは特定の新しい要件を満たしたブランチだよ」ということを示すためにも、名前を変えてしまいましょう。

$ git branch -M feature-xxx
$ git push -u origin feature-xxx

# 作業ブランチを既にリモートにプッシュしていた場合、リモートのゴミも残さずキレイにしましょう。
$ git push origin :wip-xxxxx 

これらのコマンドを使いこなせると、作業中は気持ちよくコミットし、
終わった後の「キレイにする」際もミス少なく、気楽に挑むことが出来ます。

もっと気持ちよく作業するために

tig

git log や、 git add -p は便利ですが、随所で痒いところに手が届きません。
tigを使うことで、そのあたりで楽をすることができます。

詳細はこちらの記事を参考にして下さい。

http://qiita.com/suino/items/b0dae7e00bd7165f79ea

また、この記事では取り上げられていませんが、「tigの画面で選択したコミットのハッシュをコピーする」keybindを設定すると、
特定のコミットに遡ったり、rebaseしたりがとても楽になります。

~/.tigrc
# main viewの左端にコミットIDを表示する
set main-view = id:width=12 date author commit-title:graph=yes,refs=yes
# デフォルト
# set main-view = date author commit-title:graph=yes,refs=yes

# 水平分割したウィンドウの下画面サイズを % で指定(行数指定も可)
set split-view-height = 80%

# Shift-Cで、選択行のコミットのハッシュをクリップボードにコピー(macOS用)
bind main C !@git pbcopy %(commit)

作業用ブランチを猛烈に切り替える

自分用の作業用ブランチは、用途ごとに作りまくります。
ですから、どれが何のためのブランチか、忘れがちです。

fzfpecoを使いこなしましょう。https://github.com/junegunn/fzf/wiki/examples#git を参考にしてもいいですし、
コレのためだけの拙作、ブランチリスト出す君git-branches を使ってもいいかもしれません。

function/checkout-git-branch.zsh
function checkout-git-branch() {
  git-branches --color --exclude-current \
    | fzf \
    | awk '{print $2}' \
    | xargs -r git checkout
}
autoload -Uz checkout-git-branch
bindkey '^xgb' checkout-git-branch
bindkey '^xg^b' checkout-git-branch
bindkey '^x^gb' checkout-git-branch
bindkey '^x^g^b' checkout-git-branch

終わりに

いかがだったでしょうか。
几帳面な人や、真面目な人が読んだら卒倒するか、罵倒の声が挙がりそうな記事ですね。
マメにコミットしたり、丁寧にメッセージ書いたりを継続的にできない・・・僕のような人たちの、お役に立てれば幸いです。

間違いなく言えることは、「正しくコミット運用できるに越したことはありません」。
チームメンバーに厳しい人がいたり、マメな人がいるのであれば、あえて道を踏み外すようなことはせず、みんなに歩調を合わせて「正しい運用」ができるよう、心がけましょう(自戒)。

この投稿は WACUL Advent Calendar 20162日目の記事です。