Git 基本操作② — fetch, merge, pull

2023-06-13 牧野研 技術輪読会 git

今日やること

  1. 前回の復習

  2. git statusgit log に慣れる

  3. リモートリポジトリの変更を手元に反映させる

  4. あえて競合を起こしてそれを解決してみる(時間があれば)

参考
過去の牧野研での git 講習資料
git 公式リファレンス
kaito256さん: Github演習

前回やったこと

  1. git をインストールする。

  2. Github に個人アカウントをつくる。

  3. Gitの初期設定をする: ~/.gitconfig

  4. SSHの設定をする: ~/.ssh/

前回の復習: 手元のプロジェクトをGitで管理する

  1. 適当なディレクトリを作ってテキストファイル README.md を新規作成する:

    mkdir new_project && cd new_project
    echo Hello, world! > README.md
  2. ローカルリポジトリをつくる:

    git init
  3. ローカルリポジトリに README.md をコミットする。

    最初は git statusgit log で頻繁に確認すると安心。

    git status
    git add README.md  # README.mdをindexに登録
    git status
    git commit -m "Create README.md" # コミットメッセージを添えてコミット
    git status
    git log

前回の復習: 手元のプロジェクトをGithubで管理する

  1. GitHubアカウントページの右上の “+” から “New repository” を選択する。

  2. 適当なリポジトリ名(基本は手元と同じ)をつけて “Create repository” を押す。

  3. 手順が表示されるので基本的にそれに従う:

    git remote add origin https://github.com/USER_NAME/new_project.git  # リモートリポジトリを紐づける
    git remote -v               # ちゃんと紐づいたか確認
    # git branch -M main        # ブランチの名前をmainに
    git push -u origin main     # リモートにpush
    git status

    “Private” リポジトリの場合、SSHで紐付けしないと下り( fetch, pull )でもパスワードを聞かれる。

  4. リポジトリのページを更新して README.md が見えるか確認する。

前回の復習: 既存のリポジトリを手元に落としてくる

  1. GitHub上の適当なリポジトリをひとつ選ぶ。
    (e.g., https://github.com/ymat2/practice_git)

  2. 右の方の <>Code▼ ボタンを押す。

  3. SSHではなくHTTPSを選択し、URLをコピー。

  4. git clone https://github.com/ymat2/practice-git.git

  5. 中身を眺めてみる:

    cd practice-git
    ls -al
    git log

clone はどんな時に使う?
他人の作ったソフトウェアをインストールして使うとき
新しいPCで最初に作業を始めるとき
etc.

準備運動: git statusgit log に慣れる

まずは何もしていない状態で git status & git log

git status
# On branch main
# Your branch is up to date with 'origin/main'.
#
# nothing to commit, working tree clean

git log --oneline --graph   # 1コミット1行で, グラフィカルに
# * 36d0617 (HEAD -> main, origin/main) Create README.md
origin
リモートリポジトリのこと。
origin/main はリモートリポジトリのmainブランチ。
HEAD
いま見ているブランチ/commitを指す目印。
基本的には「手元の最新のcommit」を表す。

準備運動: git statusgit log に慣れる

README.md をさらに編集してみる:

# Hello, world!
This is a practice of `git`.


git status すると:

git status
# On branch main
# Your branch is up to date with 'origin/main'.
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git restore <file>..." to discard changes in working directory)
#   modified:   README.md
#
# no changes added to commit (use "git add" and/or "git commit -a")

準備運動: git statusgit log に慣れる

README.md をindexに加える:

git add README.md


ここでも git status :

git status
# On branch main
# Your branch is up to date with 'origin/main'.
#
# Changes to be committed:
#   (use "git restore --staged <file>..." to unstage)
#   modified:   README.md

↑ 「間違えて add しちゃった」って時は git restore --staged README.md すればいい。

準備運動: git statusgit log に慣れる

README.md の変更をコミットする:

git commit -m "Update README.md"
# [main 0f1a686] Update README.md
#  1 file changed, 2 insertions(+), 1 deletion(-)


ここで git status & git log:

git status
# On branch main
# Your branch is ahead of 'origin/main' by 1 commit.
#   (use "git push" to publish your local commits)
#
# nothing to commit, working tree clean

git log --oneline --graph
# * 0f1a686 (HEAD -> main) Update README.md     <- HEAD(ローカル)はここに移動
# * 36d0617 (origin/main) Create README.md      <- origin(リモート)はまだここ

準備運動: git statusgit log に慣れる

最後に git push:

git push


git log で確認:

git log --oneline --graph
# * 0f1a686 (HEAD -> main, origin/main) Update README.md        <- originも追いついた
# * 36d0617 Create README.md


「あれ、いまどういう状態だっけ?」

常に git status, git log を確認する癖をつける。

休憩 & 質問タイム

今日やること

  1. 前回の復習

  2. git statusgit log に慣れる

  3. リモートリポジトリの変更を手元に反映させる

  4. あえて競合を起こしてそれを解決してみる(時間があれば)

リモートリポジトリの変更を手元に反映させる

複数人で同じリポジトリを使う場合や、 個人で複数のマシンを使って開発する場合など、 別のひと/マシンが push した変更を手元に取り寄せるという操作が必要になる。

git fetch + git mergegit pull といったコマンドで、 リモートリポジトリの変更を手元に反映させる。

リモートリポジトリの変更を手元に反映させる

git fetch
リモートリポジトリの変更をローカルリポジトリに取り込む。
この時点では .git/ 内だけが変更されているため、手元のファイルはそのまま。
git merge
ローカルリポジトリの内容を、手元のファイルに反映する。

git pullgit fetchgit merge を一気にやるコマンド。

実際にやってみる

  1. リモートでの変更を再現するために、Githubページ上で README.md を編集する。

    1. README.md をクリック -> 右上のペンマーク 🖊 から編集画面に入る。

    2. “This line is edited online.” など適当に編集して、右上の Commit changes を押す。

    3. 表示されるウィンドウはとりあえずそのままで Commit changes

    4. 変更されていることを確認する。


  1. その変更を fetch でローカルリポジトリに取り寄せる:

    git fetch
    
    git log --oneline --graph --all    # コミット全部
    # * 47d354f (origin/main) Update README.md      <- origin(リモート)の変更が.git/に反映された
    # * 0f1a686 (HEAD -> main) Update README.md     <- HEAD(ローカル)はまだここ
    # * 36d0617 Create README.md

実際にやってみる

  1. merge で手元のファイルに反映する:

    git merge
    # Fast-forward
    #  README.md | 2 ++
    #  1 file changed, 2 insertions(+)
    
    git log --oneline --graph
    # * 47d354f (HEAD -> main, origin/main) Update README.md    <- HEADがoriginに追いついた
    # * 0f1a686 Update README.md
    # * 36d0617 Create README.md


🔰 練習: もう一度リモートで編集して git pull で一気に反映する。

HEADorigin に追いつかせるマージ

手元のファイルに変更がない場合、fetch してきた origin に追いつくだけでいい。 このようなマージをfast-forward(早送り) マージという。

(このあとFast-Forwardじゃないマージも出てきます。)

ここから先は時間があれば進む

Fast-Forwardじゃないマージ

手元のファイルも変更していた場合、fetch してきた origin に追いつくのではなく、 分岐した両者を再び1つにするマージが必要。

このようなマージをnon-fast forward マージという。

手元でもファイルを変更していたらどうなるの??

「別のファイルの変更」や「同じファイルの別の箇所の変更」である場合、non-fast forward マージで両方の変更を取り入れる。

手元の変更:

## 第1章
私はネコである。

## 第2章
あなたもネコである。

## 第1章
私はイヌである。

## 第2章
あなたもネコである。

リモートの変更:

## 第1章
私はネコである。

## 第2章
あなたもネコである。

## 第1章
私はネコである。

## 第2章
あなたもイヌである。

git fetch + git merge

## 第1章
私はイヌである。

## 第2章
あなたもイヌである。

手元でもファイルを変更していたらどうなるの??

「同じファイルの同じ箇所の変更」である場合、conflict が発生する。

手元の変更:

## 第1章
私はネコである。

## 第2章
あなたもネコである。

## 第1章
私はイヌである。

## 第2章
あなたもネコである。

リモートの変更:

## 第1章
私はネコである。

## 第2章
あなたもネコである。

## 第1章
私はサルである。

## 第2章
あなたもネコである。

git fetch + git merge

git merge
# Auto-merging README.md
# CONFLICT (content): Merge conflict in README.md
# Automatic merge failed; fix conflicts and then commit the result.

conflictを解消する

conflict が生じたファイル( README.md )を開いてみるとこんな風になっている。

## 第1章
<<<<<<< HEAD
私はイヌである。
=======
私はサルである。
>>>>>>> refs/remotes/origin/main

## 第2章
あなたもネコである。


======= を挟んで、

  • <<<<<<< HEAD は手元での変更

  • >>>>>>> refs/remotes/origin/main はリモートからの変更

を示している。

conflictを解消する

ファイルを編集して conflict を解消する。例えば:

## 第1章
私はイヌであるし、サルでもある。

## 第2章
あなたもネコである。


この変更をコミットしてリモートにも反映する:

git add README.md
git commit -m "Solve a conflict"
git push

とにかく使ってみる

🔰 練習1: 先ほどつくった README.md を編集して複数行の内容にする。できたらコミットしてプッシュ。


🔰 練習2: 手元とリモートで異なる行を編集する。git fetch してから git merge してみる。

  • 手元: 編集したらコミット
  • リモート: 編集したらCommit changes


🔰 練習3: 手元とリモートで同じ行に異なる編集をする。

  • まずは手元ではコミットせずに git fetch してから git merge してみる。どんなメッセージが出る?

余談: Fast-forward onlyの設定

git merge でリモートとローカルの両方の変更を取り込んだ場合、“merge commit”が自動的につくられる。

git log --oneline --graph
# *   03899a3 Merge remote-tracking branch 'refs/remotes/origin/main'
# |\                                                    ↑ マージコミット
# | * 798b869 Edit line.5
# * | 16117cc Edit line.2
# |/
# * 335b76a Some commit

余談: Fast-forward onlyの設定

git pull をした時は、「どういう方法でマージするか」を設定していないと自動的なmergeも起こらない。

hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge (the default strategy)
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.


そこで、「fast-forwardでのmergeのみを試みる。」という設定をしておく。

余談: Fast-forward onlyの設定

方法1

--ff-only オプション付きで git pull する。

git pull --ff-only
方法2

~/.gitconfig--ff-only の設定をする。

git config --global pull.ff only

もしくは

~/.gitconfig
[pull]
    ff = only

余談2: その他の git 便利機能

「あのファイルとこのファイル、どこが変わったんだっけ」
git diff
「間違えて〇〇しちゃった、取り消したい」
git reset
「ソースコードは管理したいけど、データや画像は外に出したくないな」
.gitignore


see more
git 公式リファレンス