はじめに
「Gitの使い方わかったけど、やっぱり merge
や rebase
怖すぎるでしょ」
という方向けに、
「ローカルブランチで merge
や rebase
したら良いんだよ。普段から使えば怖くない」
「いいからバンバン、まとめてけ、まとめてけぇ!」
という提案めいた、筆者自身の作業フローのまとめです。
大まかな手順
- とりあえずmasterからブランチを切る
- なんか書く(テストでも実装でもドキュメントでも)
- 昔のRPGのノリで
Commit
- 2. と 3. を繰り返し
- ここで
rebase
ですよ(自分のコミットをある程度まとめる) - そして
merge
ですよ( 事前にpull
を行ったmasterをmerge
) - 決めろ!
push origin
とりあえずmasterからブランチを切る
暗黙の了承ではありますが、とりあえず自分のこれからやる作業1個につき一つブランチを切りましょう。
なんか書く(テストでも実装でもドキュメントでも)
ノリノリで実装していきましょう。
実装が思いついていない場合はテストでも書きましょう。
テスト要件もはっきりしていない個人案件の場合は、とりあえずドキュメントでもMarkdownやAsciidoc、reStructuredTextなどで作りましょう。
昔のRPGのノリで Commit
昔のバイオハザードシリーズの様にセーブ回数が限られているということは無いですし、
コミットは上書き保存されることも無いです。
ですので、
「1行苦労して書き終えたぜ」
と思ったらそこで即座に git add
and git commit
しましょう。
でも、動作するか分からないコード書いちゃったよ?
ご安心を。後で rebase
するから良いんだよ。
しかし後で、
「どれがいらないコミットだっけ……」
とならないように、プレフィックスをつけておくと幸せになれます。
[Add][alpha] APIのモデルの仮実装
頭に思いついたコードを書いただけなので、動く自信はあまりない。
や、
[beta] APIのモデルをモジュール単体で動作確認した
おおむね良好。ただしテストを考えていないので整合性が取れるかは自信ない。
という感じで。
2. と 3. を繰り返し
以上の、「なんか書く(テストでも実装でもドキュメントでも)」、「昔のRPGのノリで Commit
」の手順を、とりあえず1タスク終わるまでやります。
ここで rebase
ですよ(自分のコミットをある程度まとめる)
ここまでであなたのブランチには相当のコミットがあると思います。これらをまとめていきます。
初心者殺しの rebase
機能で。
rebase
は本当に、
「用法用量を守って正しくお使いください」
サブコマンドですが、 -i
オプションを付けて行うと、とても友好的なコマンドになります(個人的な感想です)。
log
で自分のコミットを確認
まずはこれです。自分のブランチで一番初めに行ったコミットを確認しておきましょう。
git log
でログを出力して、
「どれが今回の変更を始めたコミットだっけー」
と、 最新コミットから何個目か を覚えておきましょう。
覚えたら q
を入力してログ表示を終了します。
rebase -i HEAD~
を。
最新コミットから何個目か は覚えてますね?
といっても、忘れたらまた git log
で数え直せば良いんですよ。
ここで忘れちゃいけないコマンドが -i
オプションです。
今回は「自分の作業だけで17コミットあった」と仮定します。
そんな場合は git rebase -i HEAD~18
を実行します。
一つ多く指定するのがコツ。
さて、いつもコミットメッセージを編集するときに使うエディタが起動すると思います。
そのメッセージの下の方にこんな説明が書いていると思います。
# 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
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
とりあえず今回覚えておけば良いと思うのは、
- p, pick
- r, reword
- s, squash
- d, drop
でしょうか。
こんな感じでコミットログが並んでいれば:
pick 443857b [Add][alpha] APIのモデルの仮実装
pick 2482b74 [Mod][beta] APIのモデルの仮実装。テストはまだ
pick 33df862 [Add][alpha] テストの仮実装
pick 4b6725f [Mod][beta]テスト大体できた
pick 68f7b34 [Mod]テストコードの実行結果を踏まえて変更
こういう風に書き直すといいかもしれません:
drop 443857b [Add][alpha] APIのモデルの仮実装
drop 2482b74 [Mod][beta] APIのモデルの仮実装。テストはまだ
reword 33df862 [Add][alpha] テストの仮実装
squash 4b6725f [Mod][beta]テスト大体できた
pick 68f7b34 [Mod]テストコードの実行結果を踏まえて変更
最初の2つ、443857b
, 2482b74
を drop
したのは、
「テストコードもなく、思いついた限りでの実装を先にしてしまったので、動作はおろかビルドできるかも保証できるものではない」
為です。
その次の 33df862
のコミットは、仮ではあるものの、
「要求する動作をテストコードを書いてはっきりさせた」
コミットなので、次のコミット 4b6725f
の変更とまとめて、コミットメッセージを編集して記録することにします。
さらに、 68f7b34
のコミットは、
「テストコードの実行結果を踏まえて変更(改修)」
したものなので、しっかり記録しておきます。
ここで、 rebase
計画(と勝手に言ってみる)を保存してエディタを終了すると、あとはgitがよろしくやってくれます。
Conflictが発生したときは……ちゃんと解決して git rebase --continue
しましょう。
「やっぱりなんかおかしい」
と思ったら、 git rebase --abort
で rebase
操作を無かったことにできます。
Appendix: 潰して重ねる「スクァッシュマージ」
と書いてきましたが、慣れてくるとこの、
「rebaseをいちいち指定するのが面倒くさい」
となってくるかもしれません。そこで最近(2020-07-16)見つけた記事を紹介いたします。
git rebase ではなく、スカッシュマージしてコミットを一つにまとめる
最近rebase計画の建て方を高頻度でしくじっている筆者はこちらのほうがいいのかもしれない……。
そして merge
ですよ( 事前に pull
を行ったmasterブランチを merge
)
masterブランチは毎朝 origin master ブランチを pull
しているものと仮定します。
そうであったとしても、この工程を行う前には念の為他の人の最新の変更を取り込める様に、masterブランチで pull
することをおすすめします。
では、作業ブランチで git merge master
を決めちゃってください。
merge
という言葉の響きだと、
「masterブランチそのものが消えるのでは?」
と思われるかもしれませんが、それはないです。あくまでmasterブランチはそのままに、masterブランチの変更を作業ブランチに取り込むだけです。
これで安心して push
してプルリクエストを発行できますね!
ゑ、Conflictした……しかも他人のコミットと……。
編集者同士で要相談。
決めろ! push origin
さあ、これでプルリクエストまであともう少しです。後は、
git push origin {作業ブランチ名}
しちゃってください!
こういう時どうするの?
この項は後で内容を充実させますねー。
別のブランチで作業しちゃった……(コミットはしていない)
変更を一時的に退避!キメろgit stash - Qiita
その変更が一度もステージングしていないファイルであれば、そのまま git checkout
で適切なブランチに移動して、そのまま作業しましょう。
ステージングしてるやつだったり、変更がGitに認識されている場合は、まず git stash save
コマンドで保管。
その後、適切なブランチに git checkout
しましょう。
そうしたら、 git stash pop
を実行です。
そうすると素晴らしいことに、自分のさっきまで変更していた内容が、移動先のブランチに反映されます。
別のブランチで作業しちゃった……(コミットもした)
とりあえずいいとこ取り。 cherry-pick
コミットしたなら、まずは git log
で コミットハッシュをコピペしておきます。最初の7桁くらいで大丈夫だった記憶。
そして、適切なブランチに git checkout
で移動します。
ここでGitのいいとこ取り機能、 cherry-pick
を使います。 `git cherry-pick {コピペしたコミットハッシュ}
あとはコンフリクトしていなければ、熟したさくらんぼをつまんで取ったように変更が反映されます。もちろんコミットログも。
ついでにお片付けで、間違えたブランチで git reset --hard HEAD^
として、変更を一つ前に戻せば完璧ですね!
ついうっかり、同じブランチで別の目的の変更をしてしまってな……
Gitでやらかした時に使える19個の奥義 - やらかし7:大きすぎるコミットをスマートに分割したい時 - Qiita
勢いに乗ってると、やりますね。
実はGitは「ファイル単位」ではなく「ファイルの中の各場所の変更単位」でコミットできます。hunkと書きます。
筆者はこの機能を使ったことがなく、今まで git stash
と変更内容を別ファイルに待避という方法でやっちゃっていましたが、対話モードが起動するんですね。
git add コマンドの使い方と、主要オプション | WWWクリエイターズ
とりあえず覚えておけばいいと思うのは、
- y: hunkをコミットする
- n: hunkを捨てる
- ?: コマンド一覧を表示
でしょうか。
なお、別の目的の変更だけどファイルも別の場合は stash --keep-index
を上手に使うといいです。最近(2019-08-01 辺りから)よくやってます。
参考資料:git stash についてまとめてみた(file単位でstashしたい)
rebaseするコミット間違えちゃったままrebase完了しちゃった。テヘペロ
あー、君もやっちゃったかぁ。大丈夫、筆者もよくやる。
そんなときは「Git自体の操作の履歴」を確認し、「その操作までリセット」しましょう。
ということで、とりあえず 一応やり直せるが慎重に git reflog
コマンドで操作の履歴を確認。
そして、 git reset HEAD@{{ここにreflogで確認したHEAD番号を入れる}}
を実行しましょう。
ふぅ、ヒヤッとしたぜ。
まとめ
という感じで僕はローカルブランチで作業しています。
皆様のGitによる精神的安定のできるプログラミングライフの参考になれば幸いです。
Gitは変更履歴を重んじるプログラマや物書きの頼れる仲間だよ!
[Add][2021-10-06] master
→ main
の時勢対応
そこまでギッチギチに神経を張り詰めずに、
$ git switch master
$ git branch -m main
$ git config init.defaultbranch main
$ git reset --hard origin/main
とでも叩いても良いかもしれないですが(むしろ reset
という単語そのものの意図としては正しい?)以下もあったり;
$ git switch master
$ git branch -m main
$ git config init.defaultbranch main # ここまでは同じ
$ git pull origin main --rebase -s ours
とやっても良いのかもしれない……。
解説
git pull origin main --rebase -s ours
の詳しい動作については、
「まずGitのドキュメントを読んで、ソースコードで詳細を」
としか書けない程度の知識・理解です。
ただ、全く判らずにコマンドをコピペした訳では無いので、主にオプションについて解説します。
pull
のオプションとして
「ローカルリポジトリのコミットを、リモートリポジトリに rebase
でよろしく」
と指示する --rebase
オプションがあったりします(期待して man 1 git-pull
を検索したら見つけた)。
また大変有り難い事に、
「コンフリクト時は こちら側 (ours
) か、 あちら側 (theirs
) の変更内容にして」
という指示を追加できる --strategy=
オプションなんてのもあり、 このオプションの省略が -s
です(rebase
単体でできるなら pull
のオプションのオプションでもできると思ってやったら、できた)。
さて今回の --strategy
(-s
)の値になぜ ours
が使えたかに関して早く知りたい方は、
git の merge, rebase, revert で衝突した際の ours, theirs はどんな状態になるかを見に行った方が早いです。
釈然としない方がうっかり居たとしても、おそらく筆者のテキトーな解釈を記すと余計混乱させてしまいそうなので、この辺で。 とは言え、「誰(何・どのコミット)にとっての theirs
や ours
なのかな?」という意識は抱くと良いのかもしれないとつい思って書いてしまってる。
参考資料
P.S.
「もっと簡略化できるだろ」
というご提案は、例を交えてコメントを頂けると幸いです。
「このコマンド分からない……」
調べていただけると幸いです。この記事はあくまでgitへの恐怖心を、理屈や理論の説明は省いて使ってもらって拭うことと、筆者が自分の作業フローのまとめる為に書いたものです。
Appendix: たまに筆者もGUIのGitクライアントを使ってます
普段の作業は大抵CLIで事足りてしまう(むしろCLIの方がマウスやトラックボールなどのデバイスに持ち替えなくて済む)のではありますが、少しコミット履歴を俯瞰したいときにはGUIのクライアントのほうが筆者個人としては操作が速いことが多いです。ということで2つ紹介。なお、個人所有のマシンはすべてUbuntuのため、Linuxでも動作するのが前提のチョイスとなっております。
マルチプラットフォーム対応のCLI支援がコンセプト「Guitar」
Guitar -- the-graphical-git-client
上記リンク先のページでも、
日常で頻繁に使うGit操作を快適に行いたいとか、コミットグラフを綺麗に見たいとか、そのような用途だけでもお使いいただけます。使いたがらない人にGUIを強要する考えはないので、ブランチの流れをビジュアルに見たいだけの理由でこのアプリを使っていただくのでも嬉しいです。なので、高機能なクライアントアプリとしてではなく、GUI支援アプリと思っていただいても結構です。
と書かれておりますので、僕みたいな「Eternal noob(永遠の雑魚)」も使わせてもらっています。なおライセンスはGPLv2です。
Raspberry PiやHaiku OSでの動作の様子の写真も掲載されているので、
「お金の都合でRaspberry Piしか開発端末がない……」
という少年少女も安心してGitが使えますね!
パッケージ管理システムでお手軽インストール「gitg」
こちらも「Flatpak」でのお手軽インストールが可能なのでちょこちょこ使ってます。比較的フリーソフトウェアのGitクライアントの中では見た目と軽快さのバランスが良いです。こちらもライセンスはGPLv2です。