Git-SSH 複数計算機同期・運用マニュアル

GitHubを使わず、Laptop A / A'、Desktop B、Desktop C、 SuperComp D / E の間でコードを同期・配布するための一人研究向けGit運用。

1. 全体構成

                    +----------------------+
                    |      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への通信を必ず中継するわけではない。

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には影響しない。

2. 初期設定

B / C / D / E

mkdir -p ~/project_dir
cd ~/project_dir

git init -b main
git config receive.denyCurrentBranch updateInstead

Bのみ

cd ~/project_dir
git config receive.denyNonFastForwards true

Bはcanonical hubなので、履歴の巻き戻しを防ぐ。 Bでは ~/project_dir をそのままテスト計算にも使用してよい。

D / E の実行領域

RUN_ROOT=/path/to/work-or-scratch/project_runs
mkdir -p "$RUN_ROOT"

実際には各スパコンのwork/scratch領域を使う。

3. A / A' のリモート設定

# 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 に一本化する。

4. Bからの配布設定

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

5. .gitignore

gpush は git add . を使うため、 生成物をGit管理から除外する。

__pycache__/
.ipynb_checkpoints/
*.pyc
*.log

output/
outputs/
results/
checkpoint/
checkpoints/
tmp/

B/Cでテスト計算を直接行う場合も、出力類を確実に .gitignore する。

6. gpush

A / A' / B に配置する。

#!/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 に合わせる。

7. 日常運用

用途 操作
Bだけに保存 / Bでテスト gpush -b
Cでテスト gpush -c
D/Eへ本計算コードを送る gpush -d -e
通常の全体配布 gpush -a
A/A'を同期 BへPush後、もう一方で git pull origin main

Bでのテスト

Bはcanonical hubであると同時に計算機なので、 短いテスト計算は ~/project_dir から直接実行してよい。

cd ~/project_dir
python main.py

長いテストや大量出力を伴う場合は、必要に応じてBでも別run directoryを使ってよいが、 必須ルールとはしない。

8. ブランチ運用

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のみを送る。

9. D/Eでrunを作る

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には影響しない。

10. 論文版・再現

論文投稿時

git tag -a paper-v1-submission \
    -m "Code used for paper v1 submission"

git push origin paper-v1-submission

過去runの再現

cat /path/to/run/git_commit.txt

git switch --detach <commit-hash>

11. 運用ルール

  1. A/A'で開発し、Bを同期の基準点にする。 Bはテスト計算にも普通に使う。
  2. B/Cはテスト用、D/Eは本計算用。 D/Eへ送るbranchはmainのみ。
  3. D/Eではコード管理用repoと実行用run directoryを分ける。 各runにはcommit hashを残す。
  4. gpush -a はB成功後にC/D/Eへ配布する。 B障害時だけ個別Pushで迂回する。
  5. gpush の自動add/commitは維持する。 その代わり生成物は .gitignore で確実に除外する。

論文投稿版を固定したい場合はannotated tagを付ける。 それ以外は上の5ルールだけ覚えればよい。


Private Technical Guide: Multi-Machine Code Synchronization Environment