ローカルリポジトリのコミットから、任意のファイルを抽出する

**もちろん、`mkdir ./export && git archive "commit id" "file path" | tar --extract --directory=./export` 推奨**
↓を使う理由は特にないですw 逆にデメリットがあるかもしれません。

**<font color="DeepPink">※注意※</font>**
　<font color="MediumVioletRed">現在の "`git add`" を取り消して、ステージ経由でファイルを復元します。</font>
　<font color="MediumVioletRed">また最後に "`git add`" を取り消した分をステージに戻します。</font>
　<font color="MediumVioletRed">その過程で何らかのファイルが削除される可能性があります。</font>

**実行環境**
・Bash
・Ubuntu Server 22.04

```bash:git-export_version.1
# 0.変数代入
Commit_ID="コミットID(sha1)"
Want_to_restore="抽出したいファイル"
Export_folder="抽出先にしたいディレクトリ"
Work_tree_path="$PWD"

# 1.変数 初期値設定
Commit_ID="${Commit_ID:-HEAD}"
Want_to_restore="${Want_to_restore:-:/}"
Export_folder="${Export_folder:-Export_folder}"
Work_tree_path="${Work_tree_path:-.}"
git_path="`git -C $Work_tree_path rev-parse --absolute-git-dir`"
work_tree_root="`git -C "$Work_tree_path" rev-parse --show-toplevel`"
stage_backup="${work_tree_root%/}/.stage_backup_checkin"
export_path=`cd "$Work_tree_path" && mkdir -p "$Export_folder" && readlink -f "$Export_folder"`
# 2.バックアップ・抽出
git -C "$work_tree_root" checkout-index --force --prefix="${stage_backup%/}/" --all
git -C "$work_tree_root" rm -r --force --ignore-unmatch --quiet --cached -- :/
git -C "$work_tree_root" restore --staged --source="$Commit_ID" -- $Want_to_restore
git -C "$work_tree_root" checkout-index --force --prefix="${export_path%/}/" --all
git -C "$work_tree_root" rm -r --force --ignore-unmatch --quiet --cached -- :/
# 3.ステージにあったファイルを復元
git --git-dir="$git_path" --work-tree="$stage_backup" add .
# 4.ステージ_バックアップの削除
git -C "$work_tree_root" rm -r --force --ignore-unmatch --quiet -- "$stage_backup" \
   ; rm -rf "$stage_backup"
# 5.抽出したファイルが git の管理除外対象になっていることを確認
# grep --quiet --line-regexp "`basename $export_path`" "$git_path/info/exclude" \
#    || echo `basename "$export_path"` >> "$git_path/info/exclude"
```
```bash:git-export_version.2
# 0.変数代入
Commit_ID="コミットID(sha1)"
Want_to_restore="抽出したいファイル"
Export_folder="抽出先にしたいディレクトリ"
Work_tree_path="$PWD"

# 1.変数 初期値設定
Commit_ID="${Commit_ID:-HEAD}"
Want_to_restore="${Want_to_restore:-:/}"
Export_folder="${Export_folder:-Export_folder}"
Work_tree_path="${Work_tree_path:-.}"
work_tree_root="`git -C "$Work_tree_path" rev-parse --show-toplevel`"
stage_backup="${work_tree_root%/}/.index_backup_tostage"
export_path=`cd "$Work_tree_path" && mkdir -p "$Export_folder" && readlink -f "$Export_folder"`
# 2.バックアップ・抽出
rm -f "$stage_backup"
while read LINE; do
  on_stage_A="`cut -f 1 <<< $LINE`"
  on_stage_B="`cut -f 2 <<< $LINE`"
  echo -e "$on_stage_A"'\t'"\"$on_stage_B\"" >> "$stage_backup"
done < <(git ls-files --stage)
git -C "$work_tree_root" rm -r --force --ignore-unmatch --quiet --cached -- :/
git -C "$work_tree_root" restore --staged --source="$Commit_ID" -- $Want_to_restore
git -C "$work_tree_root" checkout-index --force --prefix="${export_path%/}/" --all
git -C "$work_tree_root" rm -r --force --ignore-unmatch --quiet --cached -- :/
# 3.ステージにあったファイルを復元
git -C "$work_tree_root" update-index --index-info < "$stage_backup"
# 4.ステージ_バックアップの削除
git -C "$work_tree_root" rm --force --ignore-unmatch --quiet -- "$stage_backup" \
   ; rm -f "$stage_backup"
# 5.抽出したファイルが git の管理除外対象になっていることを確認
# grep --quiet --line-regexp "`basename $export_path`" "$git_path/info/exclude" \
#    || echo `basename "$export_path"` >> "$git_path/info/exclude"
```
### 詳細
**version&nbsp;1➝2 の変更点**
以下のやり方だと blob オブジェクトを新たに追加しない。
```git ls-files --stage > "$stage_backup"```
　mode &nbsp;&thinsp;SP &nbsp;&thinsp;type &nbsp;&thinsp;SP &nbsp;&thinsp;sha1 TAB &nbsp;path
（mode 空白 type 空白 sha1 タブ path）
の形式でインデックスをバックアップ。
```git update-index --index-info < "$stage_backup"```
バックアップからインデックスに直接記入。
***
`Commit_ID="コミットID(sha1)"`
　⌇⌇⌇
　⌇⌇⌇
``` export_path="`cd $Work_tree_path && mkdir -p $Export_folder && readlink -f $Export_folder`" ```
変数に代入、初期値を用意。最後のは、相対パスだった時に絶対パスに変換するもの。

``` git_path="`git -C $work_tree_root rev-parse --absolute-git-dir`" ```
$GIT_DIR 及び .git のフルパス（"--path-format=absolute --git-dir"）を取得する。
``` work_tree_root="`git -C "$Work_tree_path" rev-parse --show-toplevel`" ```
--show-toplevel は既定で絶対パスを返す。
***
`git -C "$work_tree_root"`
git コマンドを実行する場所を指定。
***
`git checkout-index --force --prefix="${stage_backup%/}/" --all`
stage 上のファイルを .stage_backup にコピー。
***
``` git rm -r --force --ignore-unmatch --quiet --cached -- `git ls-files` ```
対象を .git/index から削除し、stage から降ろす（ stage を空にする、work_tree からはファイルを削除しない）。
***
```git restore --stage --source="$Commit_ID" -- $Want_to_restore```
指定したコミットから、指定したファイルを stage 上に戻す。
***
```git checkout-index --force --prefix="${export_path%/}/" --all```
stage 上に戻したファイルを $Export_folder にコピー。
***
``` git rm -r --force --ignore-unmatch --quiet --cached -- `git ls-files` ```
.git/index から削除し、stage を空にする。
***
```git --git-dir="$git_path" --work-tree="$stage_backup" add .```
.stage_backup を work_tree に設定し、
ファイルを .stage_backup から stage に上げる。
***
`git rm -r --force --ignore-unmatch --quiet "$stage_backup" \ `
　 `; rm -rf "$stage_backup" `
念のため .git/index から .stage_backup 全体を削除し、work_tree からもファイルとディレクトリを削除する。さらに、git 管理されていない場合の削除も work_tree で実行する。
***
``` grep --quiet --line-regexp "`basename $export_path`" "$git_path/info/exclude" \ ```
　 ``` || echo "`basename $export_path`" >> "$git_path/info/exclude" ```
$Export_folder を git 管理対象から除外する。
***
### 備考
```
git -C "$Work_tree_path" checkout-index --force --prefix="${stage_backup%/}/" --all
git -C "$Work_tree_path" rm -r --force --ignore-unmatch --quiet --cached -- `git -C "$Work_tree_path" ls-files`
git --git-dir="$git_path" --work-tree="$stage_backup" add .
git -C "$Work_tree_path" rm -r --force --ignore-unmatch --quiet -- "$stage_backup" \
   ; rm -rf "$stage_backup"
```
これらは、以下で代用できるかもしれない（ただし、work_tree も消える可能性がある）。
　（※[-S --staged] は "Git 2.35" 以上）
・```git stash push --staged --all --message "stage_backup"```
・```git stash pop --index```