実戦用Gitまとめ

# 何はともあれ
* [Git超絶まとめ](http://qiita.com/masashi127/items/2e103c3fba9d1b058961)

# ブランチ戦略
* git flowやgithub flowを応用しながらブランチをたくさん切る。
* 大手アパレルなどでgitブランチ戦略を担当したりして来たが
  fetureブランチに個人名を入れたブランチを切るのを強くオススメしている。
  (feature/aaa/aaa_bob、feature/aaa/aaa_john、といった具合)
  気軽にpushできるし、featureブランチはrebaseすることが多いためオリジナルを残して事故に備えやすい。
  ただし後で消します。恥ずかしいし。

# コミットIDの別名
* `origin` : `git clone http://HOST/PROJECT`としたときのURLの別名。
* `origin/BRANCH1` : リモートのBRANCH1の先頭コミットIDを指す別名、とは言え最新化しないと古くなる。追跡ブランチ。`git branch -a`で一覧したときは「`remotes/origin/BRANCH1`」と表記される。
* `HEAD` : 現在チェックアウトしているブランチの先頭コミットを指す、コミットIDの別名。`git log` の先頭であり `git reset` で移動させられる。
* コミットID `ab1Cef2H` に対しての前回コミットの別名は `ab1Cef2H^`で、**この `^` はよく使う**。 `diff HEAD^ HEAD` も頻繁に使う。

# ユーザ設定の方法
* `git config user.name USERNAME` (普段は`git config`で設定する)
* `git config user.email MAILADDRESS`
* `git commit --amend --author="USERNAME <MAILADDRESS>"`

# 設定ファイルの在り処
* リポジトリ単位 : `.git/config` (`git config -e`で開く)
* ユーザ単位 : `~/.gitconfig` (`git config --global -e`で開く)
* システム全体 : `(app-path)/etc/gitconfig` (`git config --system -e`で開く)

# 各スペースの状態の確認 (手癖で使う)
* `git status` : ワークツリー(赤色)とインデックス(緑色)が見れる。コミットされたもの(ローカルリポジトリ)は見れない。
* `git log` : コミットされているものを見る。最も打つコマンドが `status` と `log`。
  何かするたびに打ってその時その時のコミットIDをコンソールに残しておく。**それらのコミットIDが様々な事故から救ってくれる。`git log -3`などで上手くコンソールに残して行こう。**
* `git diff` : ワークツリーとインデックスとの比較。何をaddしようとしているかを知るときに使う。
* `git diff --cached` : インデックスとHEADの比較。何をコミットしようとしているか知るときに使う。

# ログの確認
* ~~`git log --decorate --graph --branches --tags --remotes` (おすすめ)~~
* ~~`git log --oneline --decorate --graph --branches --tags --remotes`~~
* ~~`git log --graph --date=short --decorate=short --pretty=format:'%Cgreen%h %Creset%cd %Cblue%cn %Cred%d %Creset%s'`~~
* `git log -3`
* `git log -5`
* 以前はエイリアス必須だったが、現在のGitBashは--decorateがなくても色が付くため、エイリアスなしでも運用できる。

# インデックスへの追加と削除
* `git add .` : カレントディレクトリ配下 (普段はgitディレクトリの頂上にいるのでこれを使う)
* `git add -A` : gitディレクトリ全て (ほとんど使わない)
* `git reset FILEPATH1`
* `git reset FILEPATH1*.txt`
* `git reset /DIRNAME/` (「/DIRNAME/*」では無反応)

# リモートブランチからの取り込み方
* `git clone http://HOST/PROJECT -b BRANCH1`
* `git fetch origin` : ローカルの追跡ブランチであるorigin/BRANCH1, origin/BRANCH2...を全て最新化する。 (便利だがダウンロード容量に注意)
* `git fetch origin BRANCH2` : origin/BRANCH2を最新化し、FETCH_HEADに入れる
* `git fetch origin BRANCH3:BRANCH3` : ローカルにorigin/BRANCH3とBRANCH3の2つを作る。新しくブランチを落とすときはこれ。「`git fetch origin BRANCH3 && git branch BRANCH3 origin/BRANCH3`」と同等。
* `git pull origin BRANCH1`
  有名なコマンドだが**使う必要がない**。`pull` を最新化だと思っている人も多いが、最新化は `fetch`。
  ブランチ名を指定せず実行するとローカルの全ブランチに対して行うため**危険**。
  FETCH_HEADの状態を確認せずにいきなり `merge` (設定によっては `rebase`) するため予期しない変更を取り入れたりconflictを起こす恐れがある。そのため `fetch` してから、`log FETCH_HEAD` で状況を確認しつつ、 `merge FETCH_HEAD` や `rebase FETCH_HEAD`、`reset --hard FETCH_HEAD` などの選択をすればいい。
* 手元の状態を残しながらローカルブランチを最新化したい場合(`origin/BRANCH1` としてではなく `BRANCH1` として手に入れたい場合)、一度バックアップブランチを作って手元の状態をコミットとして退避させるといい。
  1. `git fetch origin BRANCH1`
  1. `git checkout -b BK`
  1. `git add .`
  1. `git commit -m "MESSAGE"`
  1. `git checkout BRANCH1`
  1. `git reset --hard FETCH_HEAD`

# コミットはしたくないが手元の状態を一旦残したいと感じたとき
  まずはコミットして一呼吸置く。コミットは `reset` でアンステージしてすぐ戻せる。
  コミットしてコミットIDを控えておけば、その後アレコレした後でも**一旦残したいと感じたとき**の状態に戻せる。

# リモートへの反映
* `git push origin BRANCH1` または `git push origin BRANCH1:BRANCH1`
(ブランチ名を指定せず実行するとローカルの全ブランチがリモートに飛ぶため**非常に危険**)
* リモートブランチを消すときは `git push origin :BRANCH1`。思っている以上にお手軽。

# 別のブランチからマージを避けて特定のファイルだけ持ってきたい
* `git checkout BRANCH1 -- FILEPATH1 FILEPATH2`
  ややこしいが `checkout` はブランチ切り替え以外に、**別ブランチからファイルを持ってくる機能**がある。

# コミットを別のブランチに付け替えたいがマージはしたくはないとき
* 一度 `merge` してから `reset` してコミットし直す。以下はBRANCH1からBRANCH2への付け替え。
  1. `git checkout BRANCH2`
  1. `git merge BRANCH1`
  1. `git reset HEAD^`
  1. `git commit -m "MESSAGE"`
* HEADではなくHEAD^^辺りのコミットを別のブランチに付け替えたい場合は `cherry-pick`。使いすぎ注意。
  

# ローカルの変更の戻し方
* `git reset --hard HEAD && git clean -df` : 何もかもごっそり戻したいとき
  `clean -df` は空ディレクトリを消すため、残したいときは `.gitkeep` を置く。
* HEADを色々と移動させていてリモートブランチのHEADに戻したい場合は
  `git reset --hard origin/BRANCH1`
* ファイル単位でリモート追跡ブランチから戻したいときは
  `git checkout origin/BRANCH1 -- FILEPATH1 FILEPATH2 FILEPATH3`。
* `git branch -f BRANCH1 COMMITID` : `BRANCH1`を`COMMITID`で上書き。今いるブランチ以外で使えて非常に強力。

# コミットコメントやauthorやemailを直したい場合
* コミットコメントを直す場合は
  `git commit --amend -m "MESSAGE"`
* authorやemailも直すなら
  `git commit --amend -m "MESSAGE" --author="user.name <user.email>"`
* 直近の4つに対して行う場合
  1. `git rebase -i HEAD~4` でvimを開いて、先頭を全て`e(edit)`にして`:wq`するとrebaseモードになる。
  1. `git commit --amend -m "MESSAGE" --author="user.name <user.email>" && git rebase --continue`を4回やるとrebaseモード終了。

# リモートの戻し方
* `git reset COMMITID && git push -f origin BRANCH1`
  個人用のリモートブランチならバンバン使っていい。origin/BRANCH1をfetchしていなければ`--force-with-lease`で他の作業者のコミットを消してしまう危険性を下げられるらしい。全く関係のないブランチで上書きしてしまったりなどのミスが怖い。
  万が一に備えて必ず**元のリモートヘッドのコミットIDをどこかにバックアップしておく**。コミットIDは、`push`前に`git log`で出力してもいいし、force-push時の` + 1234abc...5678def main -> main (forced update)`というログでもいい。(左側がオリジナルのコミットIDになる)
  JenkinsがGitHUB上でforce-pushをして元に戻せなくなったことがある。GitHUB上では`reflog`ができない(元のコミットIDを見失うと元に戻せなくなる)。著名なプロジェクトだったためか、最終的にはGitHUB側が対応をして難を逃れた。
  参考 : [本の虫: Jenkinsの開発者、間違えて一ヶ月前のローカルレポジトリをgit push --forceしてしまう](https://cpplover.blogspot.com/2013/11/jenkinsgit-push-force.html)
  それを受けて(?)、GitHUBではブランチごとへのforce-pushを禁止できるようになった。(masterのみ禁止、など)

# マージと競合の解消
* `git merge BRANCH2 --no-ff`
    `--no-ff`を付ける。`--no-ff`を付けないとコミットログにマージの痕跡が残らない。痕跡を残したくないと思っても結局conflictが起こったものはマージ跡が残り、残ったり残らなかったりカオスな状態になる。
    `--no-ff`を付けるというルールを採用するプロジェクトもある。そのルールで良い。
* `git merge --abort` でマージをやめる (mergeモード中はブランチ名が`(master|MERGING)`になる、それを抜ける)
* `git checkout --ours -- FILEPATH1` (`git checkout HEAD -- FILEPATH1` でいい)
* `git checkout --theirs -- FILEPATH1` (`git checkout BRANCH1 -- FILEPATH1` でいい)
一旦theirsで `checkout` して、他人のコミットをベースにして自分の修正を入れ直すことが頻繁にある。diffは基本的には `git diff HEAD` で行う。
* `git merge --continue`でマージの内容を決定する (エディタ等でconflictを解消した後にやる)

# 部分操作
* `git add -p FILE` + `e`モード : エディタでファイルの変更の一部だけを選んでインデックスに追加する。複数の機能変更を同じファイルにしてしまった場合に利用する。意味のあるコミット単位にするための意識高い系コマンド。
* `git cherry-pick` : 他ブランチを`merge`したくはないが、そのブランチの一部コミットは`merge`したい場合に利用する。`merge`の痕跡が残らず、ツリーがカオスになるためおすすめできない。必要になった時点で何かがおかしい。以下が参考になる。

  > [cherry-pick 運用の地獄から這い上がった話をしよう](https://zenn.dev/noraworld/articles/cherry-pick-operation)
  > 筆者もプロジェクトの最初からいたわけではない上に、初期メンバーも今は一人もいないので詳細はわからないが、develop ブランチを、いわゆる feature ブランチのように使ってしまったことが発端らしい。

# タグをつける
1. `git tag -a TAGNAME -m "MESSAGE"`
1. `git log -1 --decorate`
1. `git push origin BRANCH1 TAGNAME`

# タグを消す
1. `git tag -d TAGNAME`
1. `git push origin :TAGNAME`

# リポジトリのプロジェクトをcloneして、それを別のリポジトリにpushする
* `git clone git@github.com:XXX.git - b BRANCH1`
* `git remote remove origin`
* `git remote add origin git@github.com:YYY.git`
* `git push origin BRANCH1`

# FETCH_HEADからブランチを作成する
* 意外とよくやる操作。
  `git branch BRANCH1 FETCH_HEAD` または `git branch BRANCH1 origin/BRANCH1`。

# 別リポジトリからブランチを取り込む
* `git fetch 別リポジトリURL BRANCH1:BRANCH1`

# 複数行のコミットコメントでコミットする
* `-m`を付けずに`git commit`。viが開き、`wq`すればコミットされ、`q!`すればコミットされない。`git log`で他のメッセージを参照しつつコミットコメントを書けない上に、oneline指定の`git log`に出てこなかったりするため使い勝手が非常に悪い。gitはちょくちょくコミットするものなので、複数行書く必要がある時点でコミット単位が粗いとも言える。長文コメントが必要なコミットになった場合は、別ブランチを切って `--no-ff` でマージしてそれを知らせたりタグを切ったりして、大きな分岐点になったことを残しておくと良いかも知れない。

# rebaseの注意点
* 有名なコマンドだが、Git Bash上に残したコミットIDや`reflog`でしか戻せずforce-pushと併用されることが多い、**ちょっと危ないコマンド**。ただしブランチを上手く運用するためには `rebase` が必要。**`rebase` を活用できる人はgitを上手く使えている**と思って大丈夫。

* 戻すとき
  * `git log`などで残しておいたコミットIDで`reset --hard`。一応`reflog`で上から3, 4番目辺りに出てくる「reset: checkout BRANCH1」と書かれているコミットIDに`reset --hard`しても戻せるが、`rebase`の後に色々操作したりすると行方不明になる。

* 使うとき
  1. ローカルで作成した自分の作業ツリーを作り直したりコミットコメントを修正したりするとき。(`rebase -i`)
  1. ローカルで作成した自分の作業ツリーが分岐した場合にそれを一本化してリモートにpushしたいとき。
  1. リモートから`fetch`してきたものを一番下にして自分のツリーをそこから始めたいとき。
  1. リモートツリーを一本化する場合。force-pushと**歴史改変**が行われる。以下が参考になる。

      > [3.6 Git のブランチ機能 - リベース](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81%E6%A9%9F%E8%83%BD-%E3%83%AA%E3%83%99%E3%83%BC%E3%82%B9)
      > マージとリベースではどちらがいいのか。<br>お察しのとおり、単純にどちらがよいとは言い切れません。<br>Git は強力なツールで、歴史に対していろんな操作をすることができます。しかし、チームやプロジェクトによって、事情はそれぞれ異なります。<br>あなたは既に、両者の特徴を理解しています。あなたが今いる状況ではどちらがより適切なのか、それを判断するのはあなたです。<br><br>一般論として、両者のいいとこどりをしたければ、まだプッシュしていないローカルの変更だけをリベースするようにして、 歴史をきれいに保っておきましょう。プッシュ済みの変更は決してリベースしないようにすれば、問題はおきません。

# ローカルでプロジェクトを作ったので、それを元にGitHUBのリポジトリを作りたい
  * 普通この順番。まずはGitHUBにリポジトリを切ってcloneしてから、ローカルで新規プロジェクトを作成するという順番もあるにはあるが。
  1. まずはGitHUBに中身が空のリポジトリを作成する。(そのとき.gitignoreやLICENSEを用意してもOK)
  1. ローカルに作ったプロジェクトのトップディレクトリで`git init`してgitプロジェクトにする。
  1. そこで`git remote add origin git@github.com:xxx/yyy.git`してGitHUBで作成したリポジトリのURLを登録する。
  1. `git fetch origin main:main`でGitHUBリポジトリを落として来る。 (今のGitHUBのデフォルトのブランチはmainになっている) (Git Bashもmainになっている場合は別名でアレコレすれば良し)
      * 「Please make sure you have the correct access rights」が出た場合 : ローカルで`ssh-keygen -t rsa`してとりあえず全部Enterを押した後、「~/.ssh/id_rsa.pub」の内容をコピーしてGitHUBの[Settings -> SSH keys](https://github.com/settings/keys)に登録
      * 「push declined due to email privacy restrictions」が出た場合 : GitHUBの[Settings->Email](https://github.com/settings/emails)からprivate設定を公開に変更 (迷惑メールがわんさか来るため**危険**)
  1. `git checkout main`で`fetch`したブランチに移動する。 (GitHUBで作成した初期ブランチはmainブランチを想定する)
  1. 念のため`git log`でGitHUBで行ったinitial commitが存在するか確認する。
  1. 念のため`git status`でローカルで作成したプロジェクトが赤くなっている(ワークツリーにいる)ことを確認する。
  1. コミット前に`git config user.name USERNAME`と`git config user.email MAILADDRESS`でAuthorを設定しておく。
  1. `git add .`して`git commit -m "MESSAGE"`してコミットログを作成する。
  1. 最後に`git push origin main:main`でGitHUBに`push`する。 (GitHUBで作成した初期ブランチはmainブランチを想定する) (`-u`オプションがあるとoriginを付けなくても良くなるため初回の`push`で推奨されているが、個人的には必要性を感じない)
  1. GitHUB上で無事に`push`されていることを確認して、めでたしめでたし。

# 実戦で使用しているGitBash用エイリアス (オリジナル)
 ```
 vim ~/.bash_profile
 alias f='git fetch origin'
 alias a='git add'
 alias b='git branch'
 alias com='git commit'
 alias coma='git commit --amend'
 alias ch='git checkout'
 alias st='git status'
 alias d='git diff'
 alias dc='git diff --cached'
 alias l='git log --decorate --graph'
 alias push='git push'
 source ~/.bash_profile
 ```