Skip to content

Instantly share code, notes, and snippets.

@githubhjs
Created November 20, 2025 05:44
Show Gist options
  • Select an option

  • Save githubhjs/36814052eb4d43a55e8ffe8bd75309b1 to your computer and use it in GitHub Desktop.

Select an option

Save githubhjs/36814052eb4d43a55e8ffe8bd75309b1 to your computer and use it in GitHub Desktop.
git-teleporter.sh:讓你在任何目錄下,用「檔案路徑」當參數執行 git 指令。
#!/bin/bash
# -----------------------------------------------------------------------------
# git-teleporter
#
# 功能:
# 讓你在任何目錄下,用「檔案路徑」當參數執行 git 指令。
# 腳本會自動偵測參數裡哪一個是實際存在的路徑,cd 到該檔案所在的目錄,
# 然後在那個目錄裡執行指定的 git 指令。
#
# 典型用法:
# git-teleporter blame repos/ip/isp/units/cfc/v1/l_rtl/cfc.sv
# git-teleporter log -n 5 repos/ip/isp/units/cfc/v1/l_rtl/cfc.sv
#
# 限制與假設:
# - 主要設計給「單一檔案/目錄」使用,會使用「第一個」找到的存在路徑來 teleport。
# - 其他在該路徑「之前」出現的引數會被視為 git 的一般參數(flags、數值等)。
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Step 1: 參數檢查
# 需要至少一個參數:git 指令本身(如 log、blame、diff...)。
# $#: 目前輸入參數的數量。
# -----------------------------------------------------------------------------
if [ $# -lt 1 ]; then
echo "Usage: $0 <git-command> [args...] <file-path>"
echo "Example: $0 blame repos/ip/isp/units/cfc/v1/l_rtl/cfc.sv"
echo "Example: $0 log -n 5 repos/ip/isp/units/cfc/v1/l_rtl/cfc.sv"
exit 1
fi
# -----------------------------------------------------------------------------
# Step 2: 基本變數設定
# 把第一個參數當成 git 指令 (GIT_CMD),例如 "log"、"blame"、"diff"。
# 然後用 shift 把它從參數列表移除,之後 $@ 就是不含指令本身的其餘參數。
# -----------------------------------------------------------------------------
GIT_CMD="$1"
shift
# FILE_PATH 會用來記錄「找到的第一個存在路徑(檔案或目錄)」的完整路徑
FILE_PATH=""
# ARGS 是一個陣列,用來存放在「遇到檔案/目錄之前」的 git 參數(例如 -n, 5 等)
ARGS=()
# -----------------------------------------------------------------------------
# Step 3: 逐一解析剩下的參數,嘗試找出實際存在的路徑
# 迴圈走過目前所有的 "$@"。
# 對每個 arg:
# 1. 若是絕對路徑 (以 / 開頭),直接當成測試路徑。
# 2. 若是相對路徑,則以目前工作目錄 (pwd) 為基準組成完整路徑。
# 3. 用 [ -e ] 測試這個路徑是否存在(可視需要改成 -f 只接受檔案)。
# 4. 第一個存在的路徑就視為目標 FILE_PATH,並結束迴圈。
# 5. 若不存在,就把它視為一般的 git 參數,存進 ARGS。
# -----------------------------------------------------------------------------
for arg in "$@"; do
# 判斷是否為絕對路徑:以 "/" 開頭
if [[ "$arg" == /* ]]; then
TEST_PATH="$arg"
else
# 否則視為相對路徑:用目前工作目錄組成完整路徑
TEST_PATH="$(pwd)/$arg"
fi
# 測試該路徑是否存在:
# -e:檔案或目錄存在即為 true;若只想接受檔案,可改成 -f。
if [ -e "$TEST_PATH" ]; then
# 找到第一個存在的路徑,記錄下來並立即跳出迴圈
FILE_PATH="$TEST_PATH"
break
else
# 若該參數不是一個存在的路徑,
# 便假定它是 git 的一般參數(例如 "-n"、"5"、"--since" 等),
# 收集到 ARGS 陣列,之後會一併傳給 git。
ARGS+=("$arg")
fi
done
# -----------------------------------------------------------------------------
# Step 4: 如果完全沒有找到任何存在的路徑
#
# 情境說明:
# - 使用者可能只是想要在目前目錄直接跑:
# git log -n 10
# - 這種情況底下,$@ 其實全都是 flags 或一般參數,沒有實際的檔案/目錄。
#
# 行為:
# - 直接在目前目錄執行:git <GIT_CMD> <全部原參數>
# - 這裡只用 "$@",不再用 ARGS,避免重複帶入同一批參數。
# -----------------------------------------------------------------------------
if [ -z "$FILE_PATH" ]; then
git "$GIT_CMD" "$@"
exit $?
fi
# -----------------------------------------------------------------------------
# Step 5: Teleportation 核心邏輯
#
# 1. 從 FILE_PATH 抽出「目錄路徑」與「檔名 (或最後一層目錄名)」
# - dirname:給 /a/b/c.sv → 傳回 /a/b
# - basename:給 /a/b/c.sv → 傳回 c.sv
#
# 2. cd 到該目錄底下,等同於手動走到該檔案背後的 git repo 目錄結構。
#
# 3. 在該目錄中執行 git 指令:
# git <GIT_CMD> <前面收集的 ARGS...> <FILE_NAME>
#
# 這樣一來,在執行時 git 看到的檔案路徑就只是單純的檔名,
# 而不會是從頂層開始的長長路徑,體感就像你本來就 cd 到那裡一樣。
# -----------------------------------------------------------------------------
DIR_PATH="$(dirname "$FILE_PATH")" # 檔案/目錄所在的父層目錄
FILE_NAME="$(basename "$FILE_PATH")" # 檔案名或最後一層目錄名
# 先切換目錄到 DIR_PATH;若 cd 成功,再執行 git 指令。
# "${ARGS[@]}" 會展開成先前收集的所有 flags/參數(不含檔案路徑)。
cd "$DIR_PATH" && git "$GIT_CMD" "${ARGS[@]}" "$FILE_NAME"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment