Gitでやらかした時に使える19個の奥義


タイトルは大目に見てください><。
本内容は危険な操作を伴うのでくれぐれも自己責任でお願いします。

間違いもあったら指摘ください。



# ローカル編

自分のローカル環境だけで閉じていて、他の人への影響がない場合に有効です。
リモートにプッシュしちゃってる時は、他人への影響が発生するので危険です。

## やらかし１：コミットメッセージに禁止ワード入ってて人生やめたい時

コミットメッセージを修正するのは簡単です。
ファイルの追加なんかもできちゃいます

```
$ git commit --amend
# あとは自由にコメント書き換えしましょう
```


## やらかし２：違うユーザー名でコミットしちゃった時

自宅と仕事場で作業していてgit configを使い分けてたりすると使い分け間違う時がありますね

これはAuthor変更のお話です。
Commiterは.gitconfigか.git/configで変更してください。


### HEADのユーザー名orメールアドレスを変えたい場合

```
# user.nameとuser.emailの部分を適宜修正してください
$ git commit --amend -m "コミットメッセージ" --author="user.name <user.email>"
```

### 結構前のコミットを変更したい場合


```
$ git rebase -i <コミット>

====
# エディタが開くので以下のように変更して保存

# (変更前)上から順に古いコミットが並ぶ
pick aa11bbc コミットメッセージ１
pick b2c3c4d コミットメッセージ２
pick 4e56fgh コミットメッセージ３
・・・

# (変更後)対象のコミットをeditに変更
edit aa11bbc コミットメッセージ１
pick b2c3c4d コミットメッセージ２
pick 4e56fgh コミットメッセージ３
・・・
====

# あとは同じように修正
$ git commit --amend -m "コミットメッセージ" --author="user.name <user.email>"

# 元に戻る
$ git rebase --continue
```

### (危険)歴史を完全に書き換える

あるcommitter名が特定の値の時に、いろいろまとめて書き換える
歴史が完全にかわります

```
git filter-branch --commit-filter '
        if [ "$GIT_COMMITTER_NAME" = "<Old Name>" ];
        then
                GIT_COMMITTER_NAME="<New Name>";
                GIT_AUTHOR_NAME="<New Name>";
                GIT_COMMITTER_EMAIL="<New Email>";
                GIT_AUTHOR_EMAIL="<New Email>";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD
```

## やらかし３：余計なファイルをコミットしたけどやっぱignoreしたい時

### 削除コミットする場合

余計なものが入ってたら削除してignore追記

```
# まずはコミット済みのファイルをリポジトリから消す
$ git rm --cached <ファイル名>

# 続いてgitignoreに追記
$ echo '<ファイル名>' >> .gitignore
```


### そもそもファイルなんてなかったことにする場合

え、そんなファイルなかったよ？という状態にする

```
# 履歴からのみ削除する（ワーキングツリーには残る）
$ git filter-branch -f --index-filter 'git rm --cached -rf --ignore-unmatch <ファイル名>' HEAD

# 履歴からもワーキングツリーからも削除する
$ git filter-branch -f --index-filter 'git rm -rf --ignore-unmatch <ファイル名>' HEAD
```



## やらかし４：ブランチ名がダサすぎてやっぱないな・・・な時

誰にも見られてないならこっそりブランチ名を変えましょう


```
$ git branch -m <変更後のブランチ名>
```

## やらかし５：血迷ってたのでコミット自体なしにしたい時

### 誰にもバレてない（＝取り込まれていない）場合

直前の歴史を改変しましょう

```
# 用途に応じて３つのどれかで歴史を巻き戻す

# 1.HEADだけを元に戻す
$ git reset --soft HEAD~

# 2.HEADとインデックスを元に戻す
$ git reset HEAD~

# 3.１つまえのコミットまでインデックス、ワーキングツリーも含めて元に戻す
$ git reset --hard HEAD~
```


### 誰かにバレてる（＝取り込まれてる）場合

歴史改変するとカオスなので打ち消しコミットでしのぎます

```
$ git revert <コミット>
```


## やらかし６：細かすぎるので過去の数コミットをまとめたい時

rebase + squash(or fixup)で過去の数コミットをまとめちゃいます。

```
# HEADを含む過去のnコミット分をrebase
# 例) git rebase -i HEAD~3
$ git rebase -i HEAD~<数字>

====
# エディタが開くので以下のように変更して保存

# (変更前)上から順に古いコミットが並ぶ
pick aa11bbc コミットメッセージ１
pick b2c3c4d コミットメッセージ２
pick 4e56fgh コミットメッセージ３
・・・

# (変更後)squashと書いたコミットは１つ前のコミットにまとめられる
pick aa11bbc コミットメッセージ１
squash b2c3c4d コミットメッセージ２
squash 4e56fgh コミットメッセージ３
・・・
====
```

squashの代わりにfixupでもいいが、その場合にはfixupと書いたコミットのコミットメッセージは削除されます


## やらかし７：大きすぎるコミットをスマートに分割したい時

やらかし６でまとめたはいいけどまとめすぎるのも問題です。
機能追加とバグフィックスが一緒になっちゃったときなどに便利な方法


```
# リモートにプッシュしたあとにはやらないこと

# まずはHEADとインデックスを１つまえに戻す
# つまりでかいコミットがなかったことになってワーキングツリーにのみある状況にする
$ git reset HEAD~

# 下のコマンドで細切れ(=hunk)ごとにインデックス追加(y)or保留(n)を選ぶ
# 細切れでも大きいなら(s)でさらに分割（git add -pの詳細はググってね）
$ git add -p

# 必要な変更のみが入った状態でコミット
$ git commit -m "コミットメッセージ"

# あとは適宜上記を繰り返しコミットを繰り返すだけ

```



## やらかし８：間違って違うブランチにcommitしちゃった時


うっかりmasterにcommitしちゃったけどホントは別ブランチにcommitしたくなる時があります

```
# まずはコミットが残った状態で別ブランチを作る
$ git branch other-branch

# masterのHEAD、インデックス、ワーキングツリー全てを１つまえに戻す
$ git reset --hard HEAD~

# コミットが残っている別ブランチをチェックアウト
$ git checkout other-branch
```


## やらかし９：いらないファイルを全部消し去りたい時


自動生成ファイルとか自動バックアップファイルとかいらないファイル一瞬で消し去りたい時ありますよね
git管理対象もそうでない場合もコミットしてない変更をサクッと削除できます
やらかしではないか・・・

```
# まずは必要な物はすべてadd＆commit
# これしないと必要な物まで消えちゃうよ
$ git add <必要なファイル>
$ git commit -m "コミットメッセージ"

# git stashでuntrackedなファイルも含めて退避
$ git stash -u

# stashしたものをdropして消去
$ git stash drop
```

ちなみにリポジトリ管理されていないファイルだけなら以下でOK

```
# 対象ファイルを念のため確認
$ git clean -n

# 削除実行
$ git clean -f
```


## やらかし１０：一時的に作業内容を退避して別ブランチで作業したい時

自分は悪くないけど緊急対応入っちゃうと一旦作業止めないと行けませんね
これもやらかしではないか。

```
# 作業中の内容を根こそぎ退避
$ git stash -u

# 別のブランチを切って別の作業をこなす
$ git checkout -b other-branch
~作業~
$ git add <必要なファイル>
$ git commit -m "コミットメッセージ"

# 元のブランチに戻る
$ git checkout origin-branch

# 退避していた作業内容を取り出す
$ git stash pop
```




## やらかし１１：大事な大事なコミットをうっかり消しちゃった時

やらかしたときの焦り度No1。git resetで--hardしちゃった時とか起こりがち。
でもどんなコミットでも戻せるんです

```
# まずは過去のコミット一覧を見る
$ git reflog 

# そこからコミットを選択して復元する　
# 例）git reset --hard HEAD@{2}
$ git reset --hard <コミット>
```


## やらかし１２：うっかりブランチを削除したけど元に戻したい時


reflogがあれば大丈夫。ブランチは復元できます。
無名ブランチでコミットしたあと、別ブランチチェックアウトして消えちゃった場合も同様です。

やらかし１３とほぼ一緒です

```
# まずは過去のコミット一覧を見る
$ git reflog 

# そこからコミットを選択してブランチを作成　
# 例）git branch new-branch HEAD@{2}
$ git branch <ブランチ名> <コミット>
```

## やらかし１３：コミット内容を一部変更したい時

タイポとかちょっとした修正したいときにわざわざ打ち消すのもな～という時にはコミット自体を編集しましょう

```
# 対象のコミットを指定
$ git rebase -i <コミット>

====
# エディタが開くので以下のように変更して保存

# (変更前)上から順に古いコミットが並ぶ
pick aa11bbc コミットメッセージ１
pick b2c3c4d コミットメッセージ２
pick 4e56fgh コミットメッセージ３
・・・

# (変更後)対象のコミットをeditに変更
edit aa11bbc コミットメッセージ１
pick b2c3c4d コミットメッセージ２
pick 4e56fgh コミットメッセージ３
・・・
====

# 対象のコミットがワーキングツリーに展開されているので編集しadd
$ git add <ファイル>

# 対象コミットを変更
$ git commit --amend

# rebaseを完了して元にもどる
$ git rebase --continue

# rebaseを取り消したい場合
$ git rebase --abort
```

## やらかし１４：ブランチ融合（rebase)でコンフリクトが起こった時

mergeじゃなくrebaseでブランチを融合させる場合、コンフリクトしたら焦ります。

```
# branch1にいる状態でbranch2に融合
$ git rebase branch2

~~ コンフリクト発生 ~~

# 無名ブランチになっているので要注意
$ git branch

* (no branch, rebasing branch2)
  branch1
  branch2
  master

# コンフリクトを頑張って解消してください

# 終わったらaddしてrebaseを続行
$ git add <ファイル>

# 対象コミットを変更
$ git commit --amend

$ git rebase --continue
```


### やらかし１５：マージしたけどやっぱり元に戻したい時

あくまで他者に取り込まれていない場合です！

```
# マージする。
$ git checkout <マージ先ブランチ>
$ git merge <マージ元ブランチ>

# マージしたあと、やっぱやめよって思ったらこれをやる
# ORIG_HEADを指定すればマージ前に戻る
$ git reset --hard ORIG_HEAD

```



# リモート編

## やらかし１６：リモートからpullしたらコンフリクトしまくったので一旦元に戻したい時

更新取り込んで山ほどコンフリクトしてると、そっ閉じ・・・というか戻したくなります
やらかし１５とほぼ同じです。

```
# リモートから更新を取得
$ git pull origin master

# コンフリクト発生！！

# やっぱりpull(fetch + merge)のうちmergeをやめたい
$ git reset --hard ORIG_HEAD
```

## やらかし１７：リモートのmasterに強制pushしてボコられた時

origin/masterにpush -fは禁物です。
がたまにやらかす人もいるので、目には目を・・・

イケナイコミットを消し去って強制pushし直しましょう

### １つ前のコミットpushすれば済む場合

```
# ダメよ～ダメダメ(強制プッシュ
$ git commit -m "コミットメッセージ"
$ git push -f origin master

# まっとうな会社ならここで怒られます

# １つ前のコミットに元に戻したくなったら
$ git push -f origin HEAD~:master

```

ローカルにも反映させたいならresetでもいいかも

```
$ git reset HEAD~
$ git push -f origin master
```


### 何個か前のコミットを消したい場合

```
# ダメよ～ダメダメ(強制プッシュ
$ git commit -m "コミットメッセージ"
$ git push -f origin master

# 怒られて元に戻したくなったら
$ git rebase -i <対象コミット>

====
# エディタが開くので以下のように変更して保存

# (変更前)上から順に古いコミットが並ぶ
pick aa11bbc コミットメッセージ１
pick b2c3c4d コミットメッセージ２

・・・

# (変更後)いらないコミット行を削除
pick b2c3c4d コミットメッセージ２

・・・
====

# 再度強制プッシュ
$ git push -f origin master
```



## やらかし１８：リリースしてバグがあったので爆速で切り戻したい時

障害発生などで、masterへのマージを一旦元に切り戻したいことはよくあります。
でもコミット数が多いと１つ１つrevertするわけにもいきません。


```
# コミットログ確認
$ git log --oneline --graph

====
*   1x3y5z7 - Merge pull request #4 from develop
|\
| * a2b45c7 - (develop) コミット３
* | dbc65f4 - コミット２
* | f0b0a91 - コミット１
# 戻り先を指定してrevert(基本は1でOK)
====

# masterにdevelopをマージしたとして、masterなら１、developなら２を指定してrevert
# 例）git revert -m 1 1x3y5z7
$ git revert -m 1 <マージコミット>

```

誰も取り込みしてない前提なら以下も行けます（危険です）

```
# マージ前に戻す
$ git reset ORIG_HEAD

# 強制PUSH
$ git push -f origin master
```


## やらかし１９：切り戻していくつか追加コミットしたけど再マージできない時

やらかし１８などでマージを打ち消しした場合、修正して再マージしようとするとハマります。
「打ち消し後に追加したコミットだけが反映されている」という罠に。

打ち消しはマージがなかったことになるわけではなく、マージしたコミットとは逆の変更をコミットする行為なのです。

ということで再マージする場合は、打ち消しコミットをさらに打ち消しましょう。

```
# やらかし１８の続き

# developブランチで追加コミット
$ git checkout develop
$ git commit -m "コミットメッセージ"

# masterをチェックアウト
$ git checkout master

# 打ち消しコミットを打ち消し
$ git revert <打ち消したコミット>

# developをマージ
$ git merge develop
```

# 上記を安全な環境で試してみたい時は・・・

やっぱり慣れていないといきなり実践するのは大変です。

すでにプロダクトがある人は、テスト用に別途git cloneしてきてその環境で試しましょう。

そうすれば失敗しようと何しようとメインの開発環境には影響がありません。


プロダクトなんてない人は、Githubから適当に拝借してきましょう。
有名ドコロのリポジトリをgit cloneしてきて、それで試してみればいいかと思います。


ただ気をつけるべきは、「ローカル編」しか試せないよってことです。

「リモート編」も試したい場合には、Githubにテスト用の環境を用意して、git cloneしてきたものの向き先をそちらに変えてやる必要があります。

だいたい以下の様なイメージ

```
# git cloneしてくる
$ git clone <リポジトリURL>

# ローカル編を色々お試しする

# リモートリポジトリを確認
$ git remote -v

# リモートリポジトリ登録を削除
$ git remote rm origin

# Githubにテスト用の環境を用意する
# & 向き先を変える
$ git remote add origin <新しいリポジトリURL>

# あとはリモート編をお試しする

```