Open7

【備忘録】Git操作

koukou

git checkoutで特定のファイルの内容を取得して上書きする方法

実務で教えてもらって便利そうだったので備忘録として残しておく。

使う場面としては、「自分の作業ブランチ内で作成したファイル」が他の人の作業でも使いたいファイルだったときに、そのファイルの分だけ切り出して先にPRを作るみたいな場合。つまり、別ブランチから一部のファイルのみを取得したいようなケース。共通で使うモデルクラスとかでよく起きそうなイメージ。

やり方

# 特定のコミットID内の特定のファイルを今いるブランチのファイルに上書きしてステージング(git add)された状態にする(コミットはされていない状態)
git checkout {コミットID} {ファイル名}
git checkout {コミットID} -- {ファイル名} # 参考記事では -- のハイフンが区切り文字として付いていた
git checkout {コミットID} {ディレクトリ名}  # ディレクトリごと取り込んで上書きすることもできる

# コミットIDの箇所はブランチ名を指定することもできる。別ブランチ内の特定のファイルを今いるブランチのファイルに上書きしてステージング(git add)された状態にする
git checkout {ブランチ名} {ファイル名}
git checkout {ブランチ名} -- {ファイル名} 
git checkout {ブランチ名} {ディレクトリ名}

実際の例

前提:Aさんが開発しているfeature/aブランチで作成したHogeModel.phpを、Bさんが開発しているfeature/bブランチの方でも使う必要が出てきた。

対応策としては、今回のgit checkoutで取り込む方法以外にも、Bさん側が🍒git cherry-pickで取り込む方法が考えられるが、cherry-pickは取り込む対象のコミットにHogeModel.php以外の変更も含まれている時には少し使いづらい。
こういった場合においては、特定の対象ファイルだけを取り込むことのできるgit checkoutの方が有用でありそう。

# 共通で使うブランチへ取り込みたいため、まずはdevelopブランチに移動しておく(ブランチ移動のcheckout)
git checkout develop

# feature/aから取り込みたい対象ファイルを指定して、今いるブランチに取り込む(ファイル取り込みのcheckout)
git checkout feature/a HogeModel.php

# 取り込んだファイルはステージング状態(git add)なのでコミットする
git commit -m "HogeModelの作成"

# プッシュする(developブランチ内にHogeModel作成の変更が入る)
git push origin HEAD

# feature/bの作業をしているBさんが最新のdevelopを取り込んだら作業終了

余談

最近ではこのgit checkoutの役割と同等なものとしてgit restoreというコマンドが新設されているので、こちらを使うのも可。
https://blog.inductor.me/entry/git-checkout-deprecation

koukou

ローカルブランチをリモートブランチの状態に強制的に合わせる方法

やり方

# リモート追跡ブランチの更新
git fetch --prune

# ローカルの内容をリモートブランチの内容に強制的に合わせる
git reset --hard origin/hoge

補足

git reset --hardは、現在のブランチの状態を強制的に対象に合わせるコマンド

git reset --hard {コミットへの参照}

今回やっていることは、現在いるブランチの内容を、強制的にリモート追跡ブランチの内容に書き換えたということ。リモート追跡ブランチ(=リモートブランチ(ほぼ))なので、リモートブランチの内容に強制的に合わせるということが実現できる。

koukou

CLI上でローカルブランチとリモートブランチ間の差分を表示する

以下の記事参照。

よく使いそうなのは、リモートのdevelopブランチとローカルの作業ブランチの差分を表示させる以下の書き方(GitHubのPRの差分で出る内容と同じ)。

git fetch --prune # リモートの最新状態のフェッチ
git diff origin/develop {ローカル作業ブランチ} --stat # 差分の表示

実行結果。

koukou

git stashコマンドで任意のファイルを指定して退避させる方法

# 「--」はファイル指定との区切りを明確にするためのものだが、なくても可
# 「-u」オプションは、一度もコミットに含めたことのないファイルまで含めて退避する
git stash push -u -m '退避メッセージ' -- filepath/path/... filepath2/path/...

ちなみに、ファイルを指定せずに全て退避する場合は普通に以下のようにする

git stash push -u -m '退避メッセージ'
koukou

同じブランチに対して、別の人がコミットしてプッシュしていて、自分が後からプッシュしようとしたときに、エラーになってしまうのをいい感じに処理する方法

前提

  • feature/exampleというブランチがある(Aというコミットが最新)
  • 自分がAコミットに続けて、Bというコミットを作る
  • 他人がAコミットに続けて、Cというコミットを作る。そしてそのままプッシュする
    • この時点では、origin/feature/example は「AC」でコミットが続いている
  • 自分が「AB」となっている状態のローカルブランチをリモートにプッシュする
  • 以下のエラーが発生する
git push origin HEAD
To https://github.com/example.git
 ! [rejected]          HEAD -> feature/example (non-fast-forward)
error: failed to push some refs to 'https://github.com/example.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

いい感じに処理する方法

上記状態をいい感じに解消する方法。
よりよい方法があれば、求む。

# 最新のリモート状態を取得する
git fetch --prune

# 現在のブランチ名を適当なブランチ名に変更する(ここでは仮に「feature/example-OLD」に改名)
git branch -M feature/example-OLD

# 現在の最新のリモートブランチを元にローカルブランチを作成し直す
git checkout -b feature/example origin/feature/example

# 元のブランチでプッシュが叶わなかったコミットたちを cherry-pick で取得する(ここでコンフリクトした場合はわからない)
git cherry-pick {コミットID}

# リモートにプッシュする
git push origin HEAD

これで多分いい感じ🐨

koukou

一時的に過去のコミットの状態に戻る方法

あくまで一時的に戻る方法。

# 指定したコミットIDの場所に戻る(切り替える)
git checkout {コミットID}

# 元の最新状態に戻る場合
git checkout {ブランチ名}


checkoutの代替コマンドであるswitchコマンドで同じことをしたい場合は以下の記事参照。
https://tmytokai.github.io/open-ed/activity/git/text02/page09.html

https://blog.inductor.me/entry/git-checkout-deprecation

# 指定したコミットIDの場所に戻る(切り替える)
# ※ -d オプションは移動後の HEAD を「detached HEAD」という使い捨ての HEAD にするという意味
git switch -d {コミットID}

# 元の最新状態に戻る場合
git switch -