動かざることバグの如し

近づきたいよ 君の理想に

Gitで別ブランチの特定ファイルをサルベージしたい

やりたいこと

別ブランチにある特定ファイルだけを、いまの作業ツリーへ引っ張ってきたいことがある。

git restore --source <branch> でも近いことはできるが、雑に叩くとインデックスや手元の変更まで意識する必要があって地味に気を使う。欲しいのは「指定ブランチから指定ファイルだけ抜き出して、必要なら既存ファイルを退避してから上書きする」くらいの単機能なやつである。

その場で毎回 git show branch:path > file を組み立ててもよいが、絶対パスと相対パスの差分吸収やバックアップ作成まで含めるとシェルスクリプトにしておいた方が楽だった。

コード

以下を ~/.local/bin/git-salvage.sh に作成する。chmod +x ~/.local/bin/git-salvage.sh も実行する。

#!/usr/bin/env bash
set -euo pipefail

usage() {
  echo "Usage: $(basename "$0") <branch> <file-path>" >&2
  echo "  branch     : サルベージ元のブランチ名" >&2
  echo "  file-path  : リポジトリルートからの相対パス、または作業ツリー内の絶対パス" >&2
  exit 1
}

[[ $# -ne 2 ]] && usage

BRANCH="$1"
FILE_ARG="$2"

# Git リポジトリのルートを特定
GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || {
  echo "エラー: Git リポジトリ内で実行してください" >&2
  exit 1
}

# 絶対パスが渡された場合はリポジトリルートからの相対パスに変換
if [[ "$FILE_ARG" = /* ]]; then
  REL_PATH="${FILE_ARG#"$GIT_ROOT/"}"
else
  REL_PATH="$FILE_ARG"
fi

# ブランチの存在確認
git rev-parse --verify "refs/heads/$BRANCH" > /dev/null 2>&1 || \
git rev-parse --verify "refs/remotes/$BRANCH" > /dev/null 2>&1 || {
  echo "エラー: ブランチ '$BRANCH' が見つかりません" >&2
  exit 1
}

# 対象ブランチにファイルが存在するか確認
git cat-file -e "$BRANCH:$REL_PATH" 2>/dev/null || {
  echo "エラー: ブランチ '$BRANCH' にファイル '$REL_PATH' が存在しません" >&2
  exit 1
}

# 書き込み先(作業ツリー上の絶対パス)
DEST="$GIT_ROOT/$REL_PATH"

# 書き込み先ディレクトリを作成
mkdir -p "$(dirname "$DEST")"

# 既存ファイルがあればバックアップ
if [[ -f "$DEST" ]]; then
  BACKUP="${DEST}.bak.$(date +%Y%m%d%H%M%S)"
  cp "$DEST" "$BACKUP"
  echo "バックアップ作成: $BACKUP"
fi

# ブランチのファイルを展開(インデックスは変更しない)
git show "$BRANCH:$REL_PATH" > "$DEST"

echo "完了: '$BRANCH:$REL_PATH' -> $DEST"

実行例

❯ git-salvage.sh 
Usage: git-salvage.sh <branch> <file-path>
  branch     : サルベージ元のブランチ名
  file-path  : リポジトリルートからの相対パス、または作業ツリー内の絶対パス
❯ git-salvage.sh origin/main src/server/app.ts
バックアップ作成: /home/thr3a/work/example/src/server/app.ts.bak.20260521003214
完了: 'origin/main:src/server/app.ts' -> /home/thr3a/work/example/src/server/app.ts

origin/main 上の src/server/app.ts を現在の作業ツリーへ復元している例である。対象ファイルがすでに存在していれば、タイムスタンプ付きで .bak を切ってから上書きする。