Table of Contents
モチベーション
- 個人的に普段よく使うGitコマンドについて自分なりにまとめておきたい
- Pro Gitだとカロリー過多
Repositoryを用意する
ローカルリポジトリから始める
git init <directory>
<directory>
をローカルリポジトリとする.<directory>
内に.git
という隠しフォルダが作成される.Gitの諸々は.git
で管理されることになる.
リモートリポジトリから始める
GitHubにSSH公開鍵を登録しておく
cd ~/.ssh
ssh-keygen -t rsa -b 4096
で鍵作成chmod 600 <秘密鍵へのパス>
- パスフレーズを設定したなら
ssh-add <秘密鍵へのパス>
で秘密鍵をSSHエージェントに登録ssh-add -l
でSSHエージェントに秘密鍵が登録されたことを確認
pbcopy < <公開鍵へのパス>
で公開鍵をクリップボードにコピー- SSH and GPG keysの
New SSH key
に公開鍵をコピペ ~/.ssh/config
に接続先を登録Host <nickname> HostName github.com IdentityFile <秘密鍵へのパス> User git
ssh -T <nickname>
で接続確認Hi XXX! You've successfully authenticated, but GitHub does not provide shell access.
と言われれば優勝
rm <公開鍵へのパス>
で公開鍵を削除しておく
git clone git@github.com:<repository>.git <directory>
<directory>
: ローカルリポジトリとしたいディレクトリへのパス<directory>
内に.git
という隠しフォルダが作成される.Gitの諸々は.git
で管理されることになる.
Repositoryの初期設定をする
ユーザ情報を設定する
git config --global user.name "<username>"
- ローカルマシン内のすべてのlocal repositoryに触れるユーザを
<username>
に設定する. - この設定情報は
~/.gitconfig
に保存される.
- ローカルマシン内のすべてのlocal repositoryに触れるユーザを
- Local repositroy内で
git config --local user.name "<username>"
- Local repositoryでの作業は
<username>
によって行われたものとしてログされていく. git config --local
により設定したものは.git/config
に保存される.- globalとlocalをどっちも設定している場合は,localの設定が優先される.
- Local repositoryでの作業は
- 同じ要領で,
git config --global/local user.email
でメールアドレスを登録する.
デフォルトのエディタやMerge tool, Diff toolを設定する
~/.gitconfig
または.git/config
に以下を追加する.- デフォルトエディタ,Marge tool, Diff toolにVSCodeを用いる場合は以下のようにする.
[core] editor = code --wait [merge] tool = vscode [mergetool "vscode"] cmd = code --wait $MERGED [diff] tool = vscode [difftool "vscode"] cmd = code --wait --diff $LOCAL $REMOTE
オリジナルのサブコマンドを設定する
- 例えば,
git config --global alias.co "checkout"
git co
でgit checkout
を呼ぶことができる
~/.gitconfig
に直接設定してもよい.例えば,以下はgit history
で変更履歴を可視化するサブコマンド.
[alias]
history = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
設定値の確認
git config --local -l
またはgit config --global -l
で設定を確認できる~/.gitconfig
または<local repository>/.git/config
を直接見ても良い.
Gitで管理したくないファイルを指定する
例えば,Macだと,
.DS_Store
という隠しファイルが勝手に作られてしまうので,.DS_Store
をGitの管理対象から除くためには以下を実行すると良い.git config --global core.excludesfile ~/.gitignore_global echo ".DS_Store" >> ~/.gitignore_global
git ls-files -io --exclude-standard
で,excludesfileにて無視されているファイル一覧を確認できる.
リモートリポジトリにはニックネームが付いている
リモートリポジトリには,ニックネームがついている.例えば,cloneしてきたローカルリポジトリにて,git config -l
またはgit remote -v
を実行すると,以下のように,リモートリポジトリがorigin
と名付けられていることが確認できる.
# git config -l でconfigを直接見る
remote.origin.url=git@github.com:<ユーザ名>/<リポジトリ名>.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
# git remote -v でリモートリポジトリ一覧をみる
origin git@github.com:<ユーザ名>/<リポジトリ名>.git (fetch)
origin git@github.com:<ユーザ名>/<リポジトリ名>.git (push)
一方,git init
で作成したローカルリポジトリに,任意のリモートリポジトリのニックネームを設定するには,git remote add <nickname> git@github.com:<user>/<repository>.git
を実行する.
ここで設定された/されているリモートリポジトリのニックネーム(たとえばorigin
など)は,git push origin master
やgit pull origin master
などのコマンドで利用される.
GitHubのレポジトリにHTTPSではなくSSHを使ってアクセスするように変更する
GitHubは2021年8月13日以降,パスワード(HTTPS)を使ったレポジトリへのアクセスを禁止する.
GitHubにpushしたときに,GitHubから”You recently used a password to access the repository at <repository>
へのアクセス方法がSSHではなくHTTPSになっている.
git remote -v
すると,以下のようにhttps
から始まるリモートリポジトリが登録されていることが確認できる.
origin https://github.com/<user>/<repository>.git (fetch)
origin https://github.com/<user>/<repository>.git (push)
SSHを使ったアクセスに変更するには,git remote set-url origin git@github.com:<user>/<repository>.git
を実行し(git@github.com
の部分は自分のsshの設定による.sshの設定についてはリモートリポジトリから始めるを参照),git remote -v
すると,以下のように設定され,以後はSSHによるアクセスとなる.
origin git@github.com:<user>/<repository>.git (fetch)
origin git@github.com:<user>/<repository>.git (push)
コミットのいろいろな呼ばれ方
- ブランチ:枝の先端のコミットを指す.「枝全体のコミット群をまとめたもの」をイメージしてしまうが,そうではない.
- HEAD:あるコミットを指すポインタのようなもの.
- HEAD^ と HEAD~ は,HEADの一つ前を指す.
- HEAD^^^ と HEAD~~~ と HEAD~3 は全部同じ意味.
- あるブランチをチェックアウトしたなら,HEADはそのブランチを指している.
- 無名ブランチとしてチェックアウトしているのなら,HEADはそのブランチを指しており,これをdetached HEADという.
- 無名ブランチ:ブランチ名でなくあるコミットを直接チェックアウトした場合,チェックアウト先のブランチを無名ブランチと呼ぶらしい.
- ORIG_HEAD:コミットオブジェクトをつくるコマンドの実行前にHEADだったコミットを指す
- 基本的に,
git reflog
のHEAD@{1}
とORIG_HEAD
は同じコミットを指すが,git stash
はコミットオブジェクトをつくるので,git reflog
のHEAD@{1}
とORIG_HEAD
がずれることに注意.
- 基本的に,
- FETCH_HEAD:最後に
git fetch
によって更新されたorigin/<branch>
を指す. - MERGE_HEAD:マージ元のブランチを指す.
- CHERRY_PICK_HEAD:チェリーピック元のコミットを指す.
ブランチのいろいろな呼ばれ方
origin
が以下のようなブランチを持っているとすると,
master
branch1
branch2
git clone
により,以下のようなブランチがローカルリポジトリにつくられる.
master
origin/master
origin/branch1
origin/branch2
master
は,origin/master
を追跡するブランチであり,「作業ブランチ」と呼ぶ.origin/master
は,作業ブランチmaster
の「上流ブランチ」であり,リモートリポジトリのmaster
を追跡する「リモート追跡ブランチ」でもある.git branch -vv
にて,作業ブランチと上流ブランチの対応を確認することができる.
- いまいるブランチを「カレントブランチ」と呼ぶ.
git status
で確認できる
リモートブランチをリモート追跡ブランチに取り込む
git fetch <remote> <branch>
すると,リモートの<branck>
が<remote>/<branch>
に取り込まれる.- 例:
git fetch origin master
により,originのmasterがorigin/masterに取り込まれる.
- 例:
git fetch <remote>
すると,リモートの全ブランチがorigin/*
に取り込まれる.<branch>
を省略すると,.git/config
のremote.origin.fetch
の設定が用いられる.
[remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* <=== +<source>:<target>
- 例:
git fetch origin
を実行すると,originの全ブランチがorigin/*
に取り込まれる.
git fetch
を実行すると<remote>
として,.git/config
の作業ブランチごとに設定されているremote
パラメータが適用される.- 例:以下のような
.git/config
の場合,master
上で<remote>
を省略すると,origin
が適用される.
[branch "master"] remote = origin
- 例:以下のような
カレントブランチに上流ブランチがある場合は
git fetch <remote> <branch>
が実行される.カレントブランチに上流ブランチがない場合は
git fetch <remote>
が実行される.
特定のコミットをカレントブランチに取り込む
- カレントブランチ上で
git merge <branch>
すると,<branch>
がカレントブランチに取り込まれ,マージコミットが作られる.
E---F---G dev
/
A---B---C---D master
git checkout master & git merge dev
E---F---G dev
/ \
A---B---C---D---H master
git merge
のように<branch>
を省略すると,カレントブランチの上流ブランチがカレントブランチにマージされる.- 以下のようなマージを,fast-forward(ff,早送り)マージという.
C---D---E dev
/
A---B master
git checkout master & git merge dev
C---D---E dev,master
/
A---B
git merge --no-commit <branch>
:マージ後にコミットしない
リモートブランチをカレントブランチに取り込む
- カレントブランチで
git pull <remote> <remote branch>:<local branch>
すると,<origin>
の<remote branch>
が<local branch>
に取り込まれる.git fetch <remote> <branch> & git merge <remote>/<branch>
をまとめたコマンド.- リモートブランチ一覧は
git branch -r
で確認できる.
git pull
のように<remote>
と<branch>
を省略して実行すると- カレントブランチに上流ブランチがある場合は,
git fetch
とgit merge
のルールが適用される. - カレントブランチに上流ブランチが無い場合は,
git fetch
のみ適用される.
- カレントブランチに上流ブランチがある場合は,
特定のコミットに移動する
- コミットに移動する
git checkout <commit>
で,<commit>
に移動する.<commit>
にブランチ名を指定しないでチェックアウトした先を「無名ブランチ」と呼ぶ.一旦,名無しブランチにrebaseさせたりして様子を見て,大丈夫そうならmasterを名無しブランチにresetさせるというような使い方をする.
git chekout -b <branch名>
で,<branch名>
を作成して,<branck名>
に移動する.- チェックアウトすると,内部的には,オブジェクトからインデックスへのコピーが起こり,そこからワーキングツリーへ書き出すという作業が行われている.
- ブランチをつくる
git branch <branch名>
で,<branch名>
を作成する.git branch <branch名> <commit>
で,<commit>
を親にした新たなブランチを作成する.
git branch -a
で,ブランチ一覧を確認できる.- ブランチを削除する
git branch -d <branch名>
- 上流ブランチを変更する
git branch -u <remote>/<branch> <branch>
- あるコミット時のファイルを参照する
git checkout <commit> <filename>
カレントブランチの状態を確認する
git status
- カレントブランチの状態を確認できる.
- ワーキングツリーで変更があったもの,ステージングされているもの,トラッキングされていないものなど
- カレントブランチの状態を確認できる.
変更したものをとりあえずひとまとめにする
git add
を実行することで,インデックス(変更されたファイルのスナップショットのようなもの)が内部的につくられる.この動作をステージングという.git add .
- ワーキングツリーにて新規作成または変更されたファイルをステージング
git add -u
- ワーキングツリーにて
git rm
または変更されたファイルをステージング - つかいどころ:
git rm
したときにgit add -u
する
- ワーキングツリーにて
git add -A
git add .
とgit add -u
を合体させたもの
変更を確定する
git commit -m <message>
<message>
というコミットメッセージをつけてコミットする
git commit --amend -m 'hogehoge'
- 直前のコミットメッセージを変更する
- いくつか前のコミットメッセージを変更したい場合は,
git rebase -i
でreword
を指定する.
あるコミット時点のファイルをみる
git show <commit>:<file>
- あるコミット時点のあるファイルの中を見る
変更履歴(コミットログ)をみる
git log <branch>
で<branch>
に属したコミットログをみる--oneline
を指定すると,一行1コミットで表示される.--graph
を指定すると,枝が可視化される.
git log <branch A> <branch B>
- 以下のA,B,C,D,E,F,Gを表示する.
E---F---G branchB / A---B---C---D branchA
git log <branch A>..<branch B>
- 以下のE,F,Gを表示する.
git diff X..Y
とスコープが異なるので注意.
E---F---G branchB / A---B---C---D branchA
- 以下のE,F,Gを表示する.
git log <branch A>...<branch B>
- 以下のC,D,E,F,Gを表示する.
git diff X...Y
とスコープが異なるので注意.
E---F---G branchB / A---B---C---D branchA
- 以下のC,D,E,F,Gを表示する.
git show-branch <branch A> <branch B> ..
<branch A>
と<branch B>
...が交わる根本までのブランチとコミットの対応が表示される.一番下の行が根本.---
より上にあるのが作業ブランチ一覧,ブランチごとにインデントがずれている.---
より下にあるのがブランチとコミット一覧.*
はカレントブランチに属するコミット.+
は,作業ブランチ(---
より上に表示されたインデントのズレ)に対応するブランチに属していることを表す.
差分をみる
git diff
- ワークツリーとインデックスの差分
git diff <target>
- ワークツリーと
<target>
の差分.<target>
にはHEADやHEAD^(HEADの一つ前),コミットのハッシュ値などを指定する.
- ワークツリーと
git diff <source> <target>
またはgit diff <source>..<target>
<source>
と<target>
の差分.HEADやコミット,ブランチ,タグを指定する.
git diff <commit A>...<commit B>
<commit A>
と<commit B>
の共通の親(マージベース)と,<commit B>
との差分が得られる.- 例えば,以下の状態で,
git diff G...D
を実行すると,BとDの差分が得られる.
E---F---G dev / A---B---C---D master
git diff `git merge-base <commitA> <commit B>` <commit B>
でも良い.
git diff --cached(--staged)
git add
したインデックスとHEADとの差分
git diff <option>
--name-only
:変更されたファイル一覧-- <file>
:特定のファイルに関してdiffをとる-w
:改行コードや空白を無視する--ignore-blank-lines
:空行を無視する
特定のコミットをカレントブランチに反映させる
git cherry-pick <commit>
:<commit>
をカレントブランチに反映させる.-n
を指定することで,カレントブランチのワークツリーとインデックスに<commit>
を持ってくることができる.
枝を付け替える
git rebase
は既存のブランチをガッツリ変更してしまうのでgit push
するときは-f
オプションをつけて強制的にpushする必要がある.なので,git rebase
を好まないプロジェクトもある.
git rebase <start> <end>
<start>
の次のコミットから<end>
までのコミットを<start>
に移す.<start>
がブランチ名なら,<start>
の枝と<end>
の枝の共通のコミットの次のコミットから<end>
までのコミットが<start>
に移される.
<end>
は指定しなくても良い.指定しない場合はカレントブランチが選択される.- rebase途中でコンフリクトが起きる場合がある.
- コンフリクトを解消して続ける場合は
git rebase --continue
- rebaseをやめるには
git rebase --abort
- コンフリクトを解消して続ける場合は
以下はrebaseの内部動作を図示したもの.元のE,F,Gが,Dからの新しいコミットとして順番にcherry-pickされる.親が変わるので,E,F,GのハッシュとE’,F’,G’のハッシュは違うものになる.
E---F---G dev / A---B---C---D master # git checkout dev & git rebase master # または # git rebase master dev E---F---G A---B---C---D master \ E' dev A---B---C---D master \ E'---F' dev A---B---C---D master \ E'---F'---G' dev # git chekout master & git merge dev A---B---C---D \ E'---F'---G' dev,master
git rebase -i <start> <end>
対話モードでrebaseできる.cherry-pick中のコミットについて,細かい操作が行える.
E---F---G dev / A---B---C---D master # git rebase -i E dev # # pick F <=== Fは採用 # squash G <=== GはFとまとめてしまう # # p, pick:コミットを採用 # r, reword:コミットを採用するが、コミットメッセージを変更 # e, edit:コミットを採用するが、ファイルを修正する # s, squash:一個前のコミットと合体させる # f, fixup:コミットメッセージを変更しない点以外、squashと同じ # x, exec:shellでコマンドを実行する E---F' dev / A---B---C---D master
git rebase --onto <target> <start> <end>
<start>
の次のコミットから<end>
までのコミットを<target>
に持っていく.git checkout <end> & git rebase --onto <target> <start>
でも良い.H--I--J dev2 / E---F---G dev / A---B---C---D master # git rebase --onto master F dev2 # または # git checkout dev2 & git rebase --onto master F E---F---G dev / A---B---C---D master \ H'--I'--J' dev2
特定のブランチをリモート追跡ブランチとリモートブランチに反映する
git push <remote> <branch>
- 作業ブランチ
<branch>
を,同名のリモート追跡ブランチとリモートブランチに反映する - コンフリクトが起きたら失敗する.
- 作業ブランチ
git push <remote>
<branch>
を省略した場合は,カレントブランチと同名のリモート追跡ブランチとリモートブランチに反映される.- 具体的な設定は
push.default
に依存する.
git push <remote> <local branch>:<remote branch>
<local branch>
の最新の状態を<remote branch>
に反映する.<local branch>
を空にして実行すると,<remote branch>
が削除される.
変更を戻す
git reset
git reset
には3つのオプションがある--soft
: HEADの位置を変更する--mixed
または指定しない : HEADとインデックスの位置を変更する--hard
: HEADとインデックス,ワーキングツリーの位置を変更する
git reset <target>
<target>
(ファイルやディレクトリ)に対してgit reset
を実行する<target>
に.
を指定すると,今いるディレクトリ配下のファイルに対してgit reset
が実行される.
# 初期状態
B <- HEAD,インデックス,ワーキングツリー
|
A
# ファイルを修正するとワーキングツリーが進む
<- ワーキングツリー
|
B <- HEAD,インデックス
|
A
# git add により,インデックスが進む
<- ワーキングツリー,インデックス
|
B <- HEAD
|
A
# ファイルを修正するとワーキングツリーが進む
<- ワーキングツリー
|
<- インデックス
|
B <- HEAD
|
A
# git reset --soft HEAD
# HEADがHEADに移動する(この場合は何も起きない)
<- ワーキングツリー
|
<- インデックス
|
B <- HEAD
|
A
# git reset --soft HEAD^
# HEADがHEAD^に移動する
<- ワーキングツリー
|
<- インデックス
|
B
|
A <- HEAD
# git reset HEAD
# HEADとインデックスがHEADに移動する
<- ワーキングツリー
|
B <- HEAD,インデックス
|
A
# git reset HEAD^
# HEADとインデックスがHEAD^に移動する
<- ワーキングツリー
|
B
|
A <- HEAD,インデックス
# git reset --hard HEAD
# HEAD,インデックス,ワーキングツリーがすべてHEADに移動する
B <- HEAD,インデックス,ワーキングツリー
|
A
# git reset --hard HEAD^
# HEAD,インデックス,ワーキングツリーがすべてHEAD^に移動する
B
|
A <- HEAD,インデックス,ワーキングツリー
git reflog
git reflog
- HEADの移動履歴をみる
git reset --hard <commit>
でそのコミットまで戻る
git reflog <branch>
* ブランチが指していたコミットの一覧をみる
コミットにタグを付ける
git tag <name> <commit>
<commit>
に<name>
というタグをつける.<commit>
を省略した場合はカレントブランチが選択される.- タグを作成したユーザの情報などは保存されない.
git tag -a <name> <commit> -m <comment>
<commit>
に<name>
という注釈付きタグをつける.<commit>
を省略した場合はカレントブランチが選択される.- タグを作成したユーザの情報なども保存される.
git tag -s <name> <commit> -m <comment>
<commit>
に<name>
という署名付きタグをつける.OSSなどにcontributeする際に使うことが多い.
git tag -d <tag>
<tag>
を削除する
git show <tag>
<tag>
がつけられたコミットをみる
git push origin <tag>
<tag>
をpushする.git push origin --tags
で,ローカルの全tagをpushできる.
- タグをチェックアウトするのはおすすめしない.
git checkout <tag>
しても,tagに紐付いたcommitをチェックアウトするだけ.git tag -d
で元のタグを消してgit tag -a
で新しいタグを付けるか,git tag -f -a
で強制的に変更する.
ファイルを削除する
git rm/clean
を使わずにファイルを直接消しても問題ない.git rm <target>
- インデックスとワークツリーから
<target>
(ファイルまたはディレクトリ)を削除する -r
オプションで再帰
- インデックスとワークツリーから
git rm --cached <file>
- インデックスから
<file>
を削除する
- インデックスから
git clean -n
- トラックしていないファイル一覧
git clean -f
- トラックしていないファイルを削除する
-d
オプションでディレクトリも削除
ワークツリーの内容をまるっと取り置きする
git stash
またはgit stash save
git stash clear
:全stashを削除git stash list
:stashの一覧を表示git stash list -p
:差分を確認git stash show stash@{X}
で,スタッシュされたファイル一覧をみる.git stash apply stash@{X}
でワーキングツリーに戻す.ただしstashには残っている.git stash drop stash@{X}
で特定のstashを削除.git stash pop stash@{X}
でpopする.
リポジトリに依存関係を持たす
あるリポジトリをsubmoduleとして追加する
git submodule add <user>@github.com:<repository>.git <directory>
<directory>/
に<repository>
へのリンクが追加される.- これにより,
.gitmodule
が作成され,どのリポジトリに依存しているかなどの情報が保持される.
git submodule
- submoduleを確認することができる.
submoduleを持っているrepositoryをcloneする
.gitmodule
が埋め込まれたrepositoryをcloneしても,submoduleは自動でcloneされない.git submodule update -i
- submoduleをclone/updateしてくれる.
-i
は,initとupdateを一括で行うためのオプション.
- submoduleをclone/updateしてくれる.
git submodule deinit
- submoduleとの依存を切ることができる.
git submodule
で”-“がついているものは依存が切れている.
- submoduleとの依存を切ることができる.
git rm <submodule名>
.gitmodule
からsubmodule情報が消え,submoduleも消える.
そもそもGitはどのように差分を管理しているか
blob/tree/commitオブジェクトを関連付けて差分管理している
- blobオブジェクト
- treeの子.ファイルの中身を保持している.
git hash-object <filename>
:<filename>
からハッシュ値を生成できる- ファイルサイズとファイル内容からSHA-1でハッシュ値を計算している.
- あるローカルリポジトリ内で,
git cat-file -t <hash>
:<hash>
で指定したものがblobなのかtreeなのかcommitなのかを確認できる.git cat-file blob <hash>
:<hash>
に対応したファイルの内容を見ることができる.
- treeオブジェクト
- blobの親.ファイル名や作成日時などのメタデータを保持している.
git cat-file -p <hash of tree>
:ツリーにぶら下がっていblob/tree一覧を確認できる.git cat-file -p <branch>^{tree}
:<branch>
が参照しているツリーオブジェクトを見る.
- commitオブジェクト
git cat-file commit <hash of commit>
:treeやparentを確認できる.
git ls-tree <commit>
:<commit>
でリポジトリに追加されたblob/tree一覧を確認できるgit rev-parse <commit>
:<commit>
自体のtreeのhashを確認できる.find .git/objects -type f
:blob/tree/commitの実体.git show
で見れるのはこれ.
git add
とgit commit
で何をやっているか
echo hoge > hoge.txt & echo hoge > hoge.txt & git add hoge.txt
git ls-files --stage
- インデックスされたファイルのblob一覧が表示される
git write-tree
- blobをまとめたtreeが作られ,そのtreeのハッシュが表示される.
echo "first commit" | git commit-tree <git write-treeのハッシュ>
git write-tree
で作られたtreeをまとめたcommitをつくる
echo <git commit-treeのハッシュ> > .git/refs/heads/master
またはgit update-ref
- masterブランチが
git commit-tree
のハッシュを指すようにする
- masterブランチが
git symbolic-ref HEAD refs/heads/master
- HEADがmasterブランチを指すようにする.
- コミット完了
通常のコミットとマージコミットの違い
git cat-file -p <commit>
で通常のコミットを見ると,parentを一つ持っていることがわかる.一方,マージコミットでは,parentを2つ持っている.1つ目のparentはマージ先のコミットを指し,2つ目のparentはマージ元のコミットを指している.- マージを元に戻すときは
git reset --hard ORIG_HEAD
をおすすめする.git reset --hard HEAD^
だと,以下のような現象が起こる.
E---F---G dev
/
A---B---C---D master
git checkout dev & git merge master
E---F---G---I dev
/ /
A---B---C---D---H master
git checkout master & git merge dev
E---F---G---I dev,master <=== fast-forward merge...
/ /
A---B---C---D---H
git reset --hard HEAD^
E---F---G dev
/
A---B---C---D master
- fast-forward mergeにより,HEADがdevとmasterを指してしまっていることが原因.
git reset --hard ORIG_HEAD
で,チェックアウトしているブランチの直前のHEAD(ORIG_HEAD)にリセットすることで,期待通りの動作が行える.
E---F---G---I dev,master
/ /
A---B---C---D---H
git reset --hard ORIG_HEAD
E---F---G---I dev
/ /
A---B---C---D---H master