git

git fetch and pull

How to use git command 4/N

公開日: 2021-01-03
更新日: 2023-05-29

  Table of Contents

What is git fetch?

git pullgit fetchはともにremote repositoryの変更をlocalへ伝えるコマンドですが, 以下のような違いがあります:

  • git fetch: remote repositoryの変更情報を.gitrepository経由でlocalへ通知するが, 変更自体をlocal branchへはmergeしない
  • git pull: remote repositoryの変更をlocal branchへmergeする
sequenceDiagram

  participant A as working directory<br>(working tree)
  participant B as staging area<br>(index)
  participant C as local repository<br>(local branch)
  participant D as local repository<br>(tracking branch)
  participant E as remote repository

  A->>B: git add
  B->>C: git commit
  C->>E: git push
  C->>A: git switch -c<br>(checkout)
  E->>D: git fetch
  D->>A: git merge
  E->>A: git pull (実質的には git fetch + git merge)

git fetch Use-case: Refresh the list of remote branches

Remote repository側ですでに消去されたBranchの履歴がLocal側でremote tracking branchとして残るケースは多々あります. すでに不必要なremote tracking branchを書経する場合の1つの手法として, git fetch --pruneがあります

1
2
3
4
5
6
% man git fetch|grep -A5 "^ *-p"  
-p, --prune
           Before fetching, remove any remote-tracking references that no longer exist on the remote. Tags are not subject to pruning if they are fetched only because of the default tag auto-following or due to a --tags option. However, if tags are fetched due to an explicit
           refspec (either on the command line or in the remote configuration, for example if the remote was cloned with the --mirror option), then they are also subject to pruning. Supplying --prune-tags is a shorthand for providing the tag refspec.

           See the PRUNING section below for more details.

実行コマンドは以下,

1
% git fetch -p 

What is git pull?

git pullの内部的挙動は, git fetch + git merge FETCH_HEADです. git fetchで取得したremote content refsとheadsをlocal branchへmerge commitするのが基本です.

    gitGraph
       commit id: "0-abcdef"
       commit id: "1-ghijkl"
       branch origin/main
       commit
       commit
       commit
       checkout main
       commit
       commit

この例の場合, localのmainはremoteのorigin/mainに対して, 2 ahead, 3 behindの 状態となっています. このときに, git pullを実行すると, B 以後に発生したdiffを解消するため のmergeが走り, H のmerge commit hashが作成されます.

    gitGraph
       commit id: "A"
       commit id: "B"
       branch origin/main
       commit id: "C"
       commit id: "D"
       commit id: "E"
       checkout main
       commit id: "F"
       commit id: "G"
       merge origin/main id: "H"

REMARKS: Should We use git pull?

remote repositoryに他の開発者がエラーを含んだコードを上げてしまった場合, そのバグもローカルへ取り込んでしまうリスクが git pullにはあります. 開発者の言葉として,

  • git pull is evil!
  • Avoid using git pull, unless you’re the sole owner and user of the repository

という人がいるくらいです. 実際にdevelop, feature branchをどのように運用していくかは チームごとの方針なので, ちゃんとチームメンバーとコミュニケーションしましょう.

Three Types of git pull

多くの開発者がgit pull のoptionとして, git pull –ff-only を設定することを推奨しています. .gitconfigへの設定は

1
% git config --global pull.ff only

で終わりますが, そもそもこの設定はどのような挙動なのか?の理解するには, git mergeの形式として

  • non-fast-forward(=merge commit)
  • fast-forward
  • rebase

の3つの違いがあり, この違いが git pullの3つの種類に関連していることを理解する必要があります.

non-fast-forward vs fast-forward

mainで2回commitしたあとにremoteへpush. その後, remote側で2回修正が入った状況を考えます.

    gitGraph
       commit id: "A"
       commit id: "B"
       branch origin/main
       commit id: "C"
       commit id: "D"

上記の状況を考えた時, non-fast-forwardでは

    gitGraph
       commit id: "A"
       commit id: "B"
       branch origin/main
       commit id: "C"
       commit id: "D"
       checkout main
       merge origin/main id:"merge commit"

以上のように, merge commitが入るかたちで変更がlocalへ反映されます. logを確かめると以下のようになります:

1
2
3
4
5
6
7
8
% git log --oneline --graph 
*   930abe8 (HEAD -> main) Merge branch 'main' of https://github.com/user/hogehoge
|\  
| * ae34bf4 (origin/main) DOC: Update README.md, fixing a doctoc
| * b9f6ba3 DOC: Update README.md, adding a header
|/  
* f35ecd7 DOC: Update README adding the second line
* 66b3811 DOC: Update README adding the first line

一方, fast-forwardの場合は merge commitを作成することなく, mainのHEADをorigin/mainの HEADまで移動させる形で2つのbranchを統合します. イメージとしては以下:

%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
              'git0': '#ff00ff',
              'git1': '#ff00ff',
              'git3': '#ff00ff'
       } } }%%
    gitGraph
       commit id: "A"
       commit id: "B"
       branch " "
       commit id: "C"
       commit id: "D"

git logコマンド確認してみると, commit logが以下のように一直線にきれいに見えることがわかります:

1
2
3
4
5
% git log --oneline --graph 
* ae34bf4 (HEAD -> main, origin/main) DOC: Update README.md, fixing a doctoc
* b9f6ba3 DOC: Update README.md, adding a header  
* f35ecd7 DOC: Update README adding the second line
* 66b3811 DOC: Update README adding the first line

REMARKS

  • git 2.41.0 時点では fast-forwardがdefaultの挙動となる
  • fast-forwardが出来ない時は, --ff-onlyがDefaultの挙動なのでエラーが返ってくる

Git公式Document git pull Description

Incorporates changes from a remote repository into the current branch. If the current branch is behind the remote, then by default it will fast-forward the current branch to match the remote.

–ff-only

1
Only update to the new history if there is no divergent local history. This is the default when no method for reconciling divergent histories is provided (via the --rebase=* flags).

When fast-forward impossible: git pull --rebase

次は, mainorigin/mainでそれぞれ別の作業をした結果, どちらも2 ahead/2 behindの状況が発生したとします.

    gitGraph
       commit id: "A"
       commit id: "B"
       branch origin/main
       commit id: "C"
       commit id: "D"
       checkout main
       commit id: "E"
       commit id: "F"

この局面の場合, conflictが存在しない場合でも, fast-forward pullは実行できません. git pullを実行すると以下のようなエラーメッセージが出力されます:

1
2
3
4
5
6
7
8
9
10
hint: Diverging branches can't be fast-forwarded, you need to either:
hint: 
hint:   git merge --no-ff
hint: 
hint: or:
hint: 
hint:   git rebase
hint: 
hint: Disable this message with "git config advice.diverging false"
fatal: Not possible to fast-forward, aborting.

このdivergening branchesの状態は, mergerebase で対応する必要があります. Mergeの場合はMerge commitが作成されるのは上で説明したとおりです. rebase で対応(=git pull --rebase)する場合は, 以下のような挙動になります:

    gitGraph
       commit id: "A"
       commit id: "B"
       commit id: "C [origin/main]"
       commit id: "D [origin/main]"
       commit id: "E [main]"
       commit id: "F [main]"

実際に挙動を手元で確認してみると, branchが分岐することなく一つの直線でlogが出力されます:

1
2
3
4
5
6
% git log --oneline --graph
* 04a33b9 (HEAD -> main) DOC: add a new line '5'
* b6bd960 DOC: add a new line '4'
* cb5c6c2 (origin/main) Update README.md add a line under header 2
* 0caae86 Update README.md, add a new header
* ef20373 CREATE README.md

When Conflict happens: git pull --rebase

git pull --rebaseを試みた際にConflictが発覚した場合, 結論から言うと, conflictを解消するためのcommit rebaseが該当commitの分だけ発生します:

    gitGraph
       commit id: "A"
       commit id: "B"
       commit id: "C [origin/main]"
       commit id: "D [origin/main]"
       commit id: "E' [main]"
       commit id: "F' [main]"

実際に試して見ると, 次のような指示がでます:

  • conflict該当箇所と解消後にgit add/rmでstaging areaに変更を上げろ
  • git rebase --continueを入力せよ
  • 作業を止めたい場合は, git rebase --abortを入力せよ
1
2
3
4
5
6
7
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply b11806c... DOC: add a new line '4'
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".

その後, EditorでConflictを解消し, git rebase --continueを実行すると, pullが完了します:

1
2
3
4
% git reabse --continue
[detached HEAD c873c3c] GPRSC: Solve the conflict between local and remote on README.md
 1 file changed, 2 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/main.

git pullのfast-forward optionの指定の仕方は以下の3つとなります:

option fast-forward 可能な場合 fast-forward できない場合
--ff fast-forwardでpull merge commit 生成
--no-ff merge commit生成 merge commit を生成
--ff-only fast-forwardでpull エラー終了

--ff-onlyを指定することで,

  • accidentalなcommit hashの生成を防ぐことができる
  • conflictを解消する際にmerge/rebaseどちらで対応するにあたっても, 人の意思がしっかり入る
  • 開発者が想定していない挙動を勝手にGitが実行してしまうことを防げる

というメリットを享受することができます.

References

関連記事

公式ドキュメント



Share Buttons
Share on:

Feature Tags
Leave a Comment
(注意:GitHub Accountが必要となります)