GitHubを使わず、Laptop A / A'、Desktop B、Desktop C、 SuperComp D / E の間でコードを同期・配布するための一人研究向けGit運用。
+----------------------+
| Desktop B |
| canonical sync hub |
| + test calculation |
+----------+-----------+
^
| sync
+-------------+-------------+
| |
+------+-----+ +-----+------+
| Laptop A | | Laptop A' |
| main dev | | travel dev |
+------+-----+ +-----+------+
\ /
\ /
\------ deploy ---------/
|
+---------------+----------------+
| | |
v v v
+-------------+ +-------------+ +-------------+
| Desktop C | | SuperComp D | | SuperComp E |
| test | | production | | production |
| any branch | | main only | | main only |
+-------------+ +-------------+ +-------------+
Bは通常の同期基準点(canonical hub)であると同時に、テスト計算にも使う。 A/A'間の同期は通常Bを介する。 Cは自由度の高いテスト機、D/Eは本計算機とする。
gpush -a はA/A'からまずBへPushし、
成功後にC/D/Eへ配布する。
Bは論理的な基準点だが、C/D/Eへの通信を必ず中継するわけではない。
SuperComp D / E
~/project_dir/
[ Git管理・Push受信 ]
|
| git archive HEAD
| + commit hash保存
v
RUN_ROOT/run_xxx/
[ 実行専用スナップショット ]
[ output / log / checkpoint ]
D/Eでは長時間計算を ~/project_dir から直接走らせない。
runごとにコードを実行ディレクトリへコピーする。
これにより、計算中に新しいコードをPushしても既存runには影響しない。
mkdir -p ~/project_dir
cd ~/project_dir
git init -b main
git config receive.denyCurrentBranch updateInstead
cd ~/project_dir
git config receive.denyNonFastForwards true
Bはcanonical hubなので、履歴の巻き戻しを防ぐ。
Bでは ~/project_dir をそのままテスト計算にも使用してよい。
RUN_ROOT=/path/to/work-or-scratch/project_runs
mkdir -p "$RUN_ROOT"
実際には各スパコンのwork/scratch領域を使う。
# Bを通常の同期先にする
git remote add origin univ-b:~/project_dir
# 個別送信用
git remote add b univ-b:~/project_dir
git remote add c univ-c:~/project_dir
git remote add d inst-d:~/project_dir
git remote add e inst-e:~/project_dir
# A/A'間で履歴が分岐した場合は自動mergeしない
git config pull.ff only
一括送信用の all remote は作らず、
一括配布は gpush -a に一本化する。
cd ~/project_dir
git remote add c univ-c:~/project_dir
git remote add d inst-d:~/project_dir
git remote add e inst-e:~/project_dir
gpush は git add . を使うため、
生成物をGit管理から除外する。
__pycache__/
.ipynb_checkpoints/
*.pyc
*.log
output/
outputs/
results/
checkpoint/
checkpoints/
tmp/
B/Cでテスト計算を直接行う場合も、出力類を確実に .gitignore する。
A / A' / B に配置する。
-b: BへPush-c: CへPushし、Cも同じbranchへ切り替える-d: DへPush(mainのみ)-e: EへPush(mainのみ)-a: Bへ先にPushし、成功後にC/D/Eへ配布-m: commit message指定。省略時はtimestamp#!/bin/bash
# Usage:
# gpush [-b] [-c] [-d] [-e] [-a] [-m message]
usage() {
echo "Usage: gpush [-b] [-c] [-d] [-e] [-a] [-m message]"
exit 1
}
if [ $# -eq 0 ]; then
usage
fi
targets=()
commit_message=""
push_all=false
while getopts "bcdeam:" opt; do
case "$opt" in
b) targets+=("b") ;;
c) targets+=("c") ;;
d) targets+=("d") ;;
e) targets+=("e") ;;
a) push_all=true ;;
m) commit_message="$OPTARG" ;;
*) usage ;;
esac
done
if [ -z "$commit_message" ]; then
commit_message=$(date "+%Y-%m-%d %H:%M:%S")
fi
echo ">> git add ."
git add .
if ! git diff-index --quiet HEAD --; then
echo ">> git commit -m \"$commit_message\""
git commit -m "$commit_message"
else
echo ">> Nothing to commit, working tree clean."
fi
current_branch=$(git symbolic-ref --short HEAD)
push_normal() {
local target="$1"
echo ">> Pushing to: $target ($current_branch)"
git push "$target" "$current_branch"
}
push_c() {
echo ">> Pushing to C: $current_branch"
git push c "$current_branch"
echo ">> Switching C to branch: $current_branch"
ssh univ-c "
cd ~/project_dir &&
git switch '$current_branch'
"
}
push_production() {
local target="$1"
if [ "$current_branch" != "main" ]; then
echo "ERROR: $target accepts main only."
return 1
fi
echo ">> Pushing main to: $target"
git push "$target" main
}
if $push_all; then
# B自身で実行している場合はBへのpushを省略
if [ "$HOSTNAME" != "DesktopB_Host" ]; then
echo ">> First push to canonical hub B"
if ! git push b "$current_branch"; then
echo "ERROR: Push to B failed."
echo " C/D/E deployment aborted."
echo " If necessary, explicitly use -c, -d, or -e."
exit 1
fi
fi
push_c || exit 1
if [ "$current_branch" == "main" ]; then
push_production d || exit 1
push_production e || exit 1
else
echo ">> D/E skipped: production machines accept main only."
fi
exit 0
fi
for target in "${targets[@]}"; do
case "$target" in
b)
if [ "$HOSTNAME" == "DesktopB_Host" ]; then
echo ">> Already on Desktop B; skipping B push."
else
push_normal b || exit 1
fi
;;
c)
push_c || exit 1
;;
d|e)
push_production "$target" || exit 1
;;
*)
echo "Unknown target: $target"
exit 1
;;
esac
done
DesktopB_Host は実際のBのhostnameに置き換える。
SSH aliasも自分の ~/.ssh/config に合わせる。
| 用途 | 操作 |
|---|---|
| Bだけに保存 / Bでテスト | gpush -b |
| Cでテスト | gpush -c |
| D/Eへ本計算コードを送る | gpush -d -e |
| 通常の全体配布 | gpush -a |
| A/A'を同期 | BへPush後、もう一方で git pull origin main |
Bはcanonical hubであると同時に計算機なので、
短いテスト計算は ~/project_dir から直接実行してよい。
cd ~/project_dir
python main.py
長いテストや大量出力を伴う場合は、必要に応じてBでも別run directoryを使ってよいが、 必須ルールとはしない。
git switch -c feat-new-algo
vim main.py
# Cで試す
gpush -c
Bでも同じbranchを使ってテストしたい場合は、 Bでそのbranchへ切り替えて実行してよい。
ssh univ-b
cd ~/project_dir
git switch feat-new-algo
python main.py
テスト後:
git switch main
git merge feat-new-algo
gpush -a
D/Eへはmainのみを送る。
D/Eでは ~/project_dir から直接長時間計算を走らせず、
runごとに現在のcommitを別ディレクトリへ展開する。
CODEDIR=~/project_dir
RUN_ROOT=/path/to/work-or-scratch/project_runs
RUN_ID=$(date "+%Y%m%d_%H%M%S")
RUNDIR="$RUN_ROOT/$RUN_ID"
mkdir -p "$RUNDIR"
cd "$CODEDIR"
COMMIT=$(git rev-parse HEAD)
git archive HEAD | tar -x -C "$RUNDIR"
printf '%s\n' "$COMMIT" > "$RUNDIR/git_commit.txt"
cd "$RUNDIR"
# 例:
# sbatch job.sh
# qsub job.sh
# python main.py
これでrunは固定されたコードスナップショットになる。
その後 ~/project_dir に新しいコードをPushしても、
既存runには影響しない。
git tag -a paper-v1-submission \
-m "Code used for paper v1 submission"
git push origin paper-v1-submission
cat /path/to/run/git_commit.txt
git switch --detach <commit-hash>
gpush -a はB成功後にC/D/Eへ配布する。 B障害時だけ個別Pushで迂回する。gpush の自動add/commitは維持する。 その代わり生成物は .gitignore で確実に除外する。論文投稿版を固定したい場合はannotated tagを付ける。 それ以外は上の5ルールだけ覚えればよい。
Private Technical Guide: Multi-Machine Code Synchronization Environment