Gitでローカルブランチでの作業フローまとめ

# はじめに

「Gitの使い方わかったけど、やっぱり `merge` や `rebase` 怖すぎるでしょ」
という方向けに、
「ローカルブランチで `merge` や `rebase` したら良いんだよ。普段から使えば怖くない」
「いいからバンバン、まとめてけ、まとめてけぇ！」
という提案めいた、筆者自身の作業フローのまとめです。

# 大まかな手順

1. [とりあえずmasterからブランチを切る](#とりあえずmasterからブランチを切る)
2. [なんか書く（テストでも実装でもドキュメントでも）](なんか書く（#テストでも実装でもドキュメントでも）)
3. [昔のRPGのノリで `Commit`](#昔のRPGのノリで `Commit`)
4. [2. と 3. を繰り返し](#2. と 3. を繰り返し)
5. [ここで `rebase` ですよ（自分のコミットをある程度まとめる）](#ここで `rebase` ですよ（自分のコミットをある程度まとめる）)
6. [そして `merge` ですよ（ 事前に `pull` を行ったmasterを `merge` ）](#そして `merge` ですよ（ 事前に `pull` したmasterを `merge` ）)
7. [決めろ！　`push origin`](#決めろ！　`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 ではなく、スカッシュマージしてコミットを一つにまとめる](https://qiita.com/ucan-lab/items/9877dbc3273eda7633b2)

最近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](https://qiita.com/fukajun/items/41288806e4733cb9c342)

その変更が一度もステージングしていないファイルであれば、そのまま `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個の奥義 - やらかし７：大きすぎるコミットをスマートに分割したい時 - Qiita](https://qiita.com/muran001/items/dea2bbbaea1260098051#%E3%82%84%E3%82%89%E3%81%8B%E3%81%97%EF%BC%97%E5%A4%A7%E3%81%8D%E3%81%99%E3%81%8E%E3%82%8B%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%82%92%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%81%AB%E5%88%86%E5%89%B2%E3%81%97%E3%81%9F%E3%81%84%E6%99%82)

勢いに乗ってると、やりますね。

実はGitは「ファイル単位」ではなく「ファイルの中の各場所の変更単位」でコミットできます。hunkと書きます。

筆者はこの機能を使ったことがなく、今まで `git stash` と変更内容を別ファイルに待避という方法でやっちゃっていましたが、対話モードが起動するんですね。

[git add コマンドの使い方と、主要オプション | WWWクリエイターズ](http://www-creators.com/archives/4939)

とりあえず覚えておけばいいと思うのは、

* y: hunkをコミットする
* n: hunkを捨てる
* ?: コマンド一覧を表示

でしょうか。

なお、別の目的の変更だけどファイルも別の場合は `stash --keep-index` を上手に使うといいです。最近（2019-08-01 辺りから）よくやってます。

参考資料：[git stash についてまとめてみた(file単位でstashしたい)](https://qiita.com/KTakata/items/4a25b6e47475b73b43b6)

## rebaseするコミット間違えちゃったままrebase完了しちゃった。テヘペロ

あー、君もやっちゃったかぁ。大丈夫、筆者もよくやる。

そんなときは「Git自体の操作の履歴」を確認し、「その操作までリセット」しましょう。

ということで、とりあえず *一応やり直せるが慎重に* `git reflog` コマンドで操作の履歴を確認。
そして、 `git reset HEAD@{{ここにreflogで確認したHEAD番号を入れる}}` を実行しましょう。

ふぅ、ヒヤッとしたぜ。

# まとめ

という感じで僕はローカルブランチで作業しています。
皆様のGitによる精神的安定のできるプログラミングライフの参考になれば幸いです。

Gitは変更履歴を重んじるプログラマや物書きの頼れる仲間だよ！

# [Add][2021-10-06] `master` → `main` の時勢対応
そこまでギッチギチに神経を張り詰めずに、

``` shell-session
$ git switch master
$ git branch -m main
$ git config init.defaultbranch main
$ git reset --hard origin/main
```

とでも叩いても良いかもしれないですが(むしろ `reset` という単語そのものの意図としては正しい？)以下もあったり；

``` shell-session
$ 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 はどんな状態になるか](https://qiita.com/nirasan/items/27cdd75e8117bf41d2be)を見に行った方が早いです。

釈然としない方がうっかり居たとしても、おそらく筆者のテキトーな解釈を記すと余計混乱させてしまいそうなので、この辺で。 ~~とは言え、「誰(何・どのコミット)にとっての `theirs` や `ours` なのかな？」という意識は抱くと良いのかもしれないとつい思って書いてしまってる。~~ 

# 参考資料

* [Git - 歴史の書き換え](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E6%AD%B4%E5%8F%B2%E3%81%AE%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88)

# P.S.

「もっと簡略化できるだろ」
というご提案は、例を交えてコメントを頂けると幸いです。

「このコマンド分からない……」
調べていただけると幸いです。この記事はあくまでgitへの恐怖心を、理屈や理論の説明は省いて使ってもらって拭うことと、筆者が自分の作業フローのまとめる為に書いたものです。

# Appendix: たまに筆者もGUIのGitクライアントを使ってます

普段の作業は大抵CLIで事足りてしまう（むしろCLIの方がマウスやトラックボールなどのデバイスに持ち替えなくて済む）のではありますが、少しコミット履歴を俯瞰したいときにはGUIのクライアントのほうが筆者個人としては操作が速いことが多いです。ということで2つ紹介。なお、個人所有のマシンはすべてUbuntuのため、Linuxでも動作するのが前提のチョイスとなっております。

## マルチプラットフォーム対応のCLI支援がコンセプト「Guitar」
[Guitar -- the-graphical-git-client](https://soramimi.github.io/Guitar/)

上記リンク先のページでも、

> 日常で頻繁に使うGit操作を快適に行いたいとか、コミットグラフを綺麗に見たいとか、そのような用途だけでもお使いいただけます。使いたがらない人にGUIを強要する考えはないので、ブランチの流れをビジュアルに見たいだけの理由でこのアプリを使っていただくのでも嬉しいです。なので、高機能なクライアントアプリとしてではなく、GUI支援アプリと思っていただいても結構です。

と書かれておりますので、僕みたいな「Eternal noob（永遠の雑魚）」も使わせてもらっています。なおライセンスはGPLv2です。

Raspberry PiやHaiku OSでの動作の様子の写真も[掲載](https://qiita.com/soramimi_jp/items/8425196ddc28bf283333)されているので、
「お金の都合でRaspberry Piしか開発端末がない……」
という少年少女も安心してGitが使えますね！

## パッケージ管理システムでお手軽インストール「gitg」
[Gitg | Gnome/Apps](https://wiki.gnome.org/Apps/Gitg)

こちらも「[Flatpak](https://flatpak.org/)」でのお手軽インストールが可能なのでちょこちょこ使ってます。比較的フリーソフトウェアのGitクライアントの中では見た目と軽快さのバランスが良いです。こちらもライセンスはGPLv2です。