Updated:

Gitをやっていく

モチベーション

  • 個人的に普段よく使うGitコマンドについて自分なりにまとめておきたい
  • Pro Gitだとカロリー過多

Repositoryを用意する

ローカルリポジトリから始める

  • git init <directory>
    • <directory>をローカルリポジトリとする.
    • <directory>内に.gitという隠しフォルダが作成される.Gitの諸々は.gitで管理されることになる.

リモートリポジトリから始める

  1. GitHubにSSH公開鍵を登録しておく
    1. cd ~/.ssh

    2. ssh-keygen -t rsa -b 4096で鍵作成

    3. chmod 600 <秘密鍵へのパス>

    4. パスフレーズを設定したなら

      1. ssh-add <秘密鍵へのパス>で秘密鍵をSSHエージェントに登録
      2. ssh-add -lでSSHエージェントに秘密鍵が登録されたことを確認
    5. pbcopy < <公開鍵へのパス>で公開鍵をクリップボードにコピー

    6. SSH and GPG keysNew SSH keyに公開鍵をコピペ

    7. ~/.ssh/configに接続先を登録

      Host <nickname>
      HostName github.com
      IdentityFile <秘密鍵へのパス>
      User git
    8. ssh -T <nickname>で接続確認

      • Hi XXX! You've successfully authenticated, but GitHub does not provide shell access.と言われれば優勝
    9. rm <公開鍵へのパス>で公開鍵を削除しておく

  2. git clone git@github.com:<repository>.git <directory>
    • <directory> : ローカルリポジトリとしたいディレクトリへのパス
    • <directory>内に.gitという隠しフォルダが作成される.Gitの諸々は.gitで管理されることになる.

Repositoryの初期設定をする

ユーザ情報を設定する

  1. git config --global user.name "<username>"
    • ローカルマシン内のすべてのlocal repositoryに触れるユーザを<username>に設定する.
    • この設定情報は~/.gitconfigに保存される.
  2. Local repositroy内でgit config --local user.name "<username>"
    • Local repositoryでの作業は<username>によって行われたものとしてログされていく.
    • git config --localにより設定したものは.git/configに保存される.
      • globalとlocalをどっちも設定している場合は,localの設定が優先される.
  3. 同じ要領で,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 cogit 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 mastergit 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 reflogHEAD@{1}ORIG_HEADは同じコミットを指すが,git stashはコミットオブジェクトをつくるので,git reflogHEAD@{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/configremote.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 fetchgit 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 -irewordを指定する.

あるコミット時点のファイルをみる

  • 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
  • git log <branch A>...<branch B>

    • 以下のC,D,E,F,Gを表示する.git diff X...Yとスコープが異なるので注意.
          E---F---G branchB
         /
    A---B---C---D branchA
  • 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を一括で行うためのオプション.
  • git submodule deinit
    • submoduleとの依存を切ることができる.git 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 addgit commitで何をやっているか

  1. echo hoge > hoge.txt & echo hoge > hoge.txt & git add hoge.txt
  2. git ls-files --stage
    • インデックスされたファイルのblob一覧が表示される
  3. git write-tree
    • blobをまとめたtreeが作られ,そのtreeのハッシュが表示される.
  4. echo "first commit" | git commit-tree <git write-treeのハッシュ>
    • git write-treeで作られたtreeをまとめたcommitをつくる
  5. echo <git commit-treeのハッシュ> > .git/refs/heads/masterまたはgit update-ref
    • masterブランチがgit commit-treeのハッシュを指すようにする
  6. git symbolic-ref HEAD refs/heads/master
    • HEADがmasterブランチを指すようにする.
  7. コミット完了

通常のコミットとマージコミットの違い

  • 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
B!