Git の使い方



1 つのブランチまたはタグを、履歴なしで、サブモジュールも履歴なしでクローンする(シャロークローン)

git clone -b v1.2.3 --recurse-submodules --shallow-submodules --depth 1 [repo] 


Git LFS(Large File Storage)

Git LFS をインストールし、ユーザーアカウントに対して一度だけ初期化します。

# macOS
brew install git-lfs

# Ubuntu/Debian
sudo apt install git-lfs

git lfs install

リポジトリで Git LFS を有効にし、LFS で保存するファイルの種類を選択します。

cd path/to/repository
git lfs track "*.zip"       # use a pattern or a specific file path
git add .gitattributes      # commit the tracking rules
git add path/to/archive.zip
git commit -m "Track ZIP files with Git LFS"
git push

git lfs ls-files を実行すると、追跡対象のファイルが一覧表示されます。パターンによる追跡は、新しく追加されるファイルにのみ適用されます。 すでに通常の Git にコミット済みのファイルを LFS に移すには git lfs migrate が必要で、これはリポジトリの履歴を書き換えます。


不変のリリースを公開し、メジャーバージョンタグを移動する

バージョン付きの GitHub Action やその他のプロジェクトでは、2 つのタグを使用します。

GitHub Release は v1.0.1 に紐づいたままにしておく必要があります。移動するのは v1 タグのみで、移動する GitHub Release を別途作成することはありません。

1) GitHub Release を公開する

リポジトリの GitHub ページで次を行います。

  1. Releases を開き、Draft a new release を選択します。
  2. v1.0.1 タグを作成(または選択)します。
  3. リリースのタイトル、ノート、各種アセットを追加します。

2) メジャーバージョンタグを作成または更新する

不変のバージョンと移動するメジャーバージョンを設定します。

version=v1.0.1
major=v1

v1.0.1 の GitHub Release を公開してから、v1 をそのリリースに向けます。最初のリリースではこれで v1 が作成され、以降のリリース(v1.0.2、v1.1.2、…)では同じコマンドで v1 が最新の不変タグに更新されます。 GitHub が作成したばかりのタグをフェッチし、ローカルの v1 タグを $version が参照するコミットに向け、 そのタグだけを GitHub 上で強制更新します。

git fetch origin tag "$version"
git tag -f "$major" "$version^{}"
git push --force origin "refs/tags/$major"

^{} サフィックスはバージョンタグを参照先まで解決するため、v1 はリリースコミットを直接指します。タグの移動は non-fast-forward な更新になるため強制更新が必要です(v1 がまだ存在しない初回でも問題ありません)。v*.*.* のような不変タグは GitHub のタグルールセットで保護し、移動を想定しているメジャーバージョンタグの更新は許可してください。


タグ/リリースを更新する(既存のタグを書き換える)

次の変数を使用します

例: tag=v1.2.3

btag="b$tag"
rtag="refs/tags/$tag"

1) 既存のタグから一時ブランチを作成して切り替える

git fetch origin --tags
git switch -c "$btag" "$tag"

2) 変更を加え、コミットし、一時ブランチをプッシュする

git commit -am "Fixes for $tag"
git push -u origin "$btag"

3) 古いコミットを指す古いローカルタグを削除し、新しい HEAD でローカルにタグを再作成する

(すでに $btag 上にいるため、追加の git switch "$btag" は冗長です。)

git tag -d "$tag"
git tag "$tag"

4) リモートタグを新しいコミットを指すように置き換える

まず古いリモートタグを削除してから、新しいものをプッシュします。 タグは慣例上不変であり、多くのサーバーは強制しない限り non-fast-forward なタグ更新を拒否するため、リモートタグを明示的に強制更新します。

git push origin ":$rtag"
git push --force origin "$rtag"

5) 必要に応じて GitHub Release(およびアセット)を更新する

6) 後片付け: 一時ブランチを削除する(リモート+ローカル)

git push origin --delete "$btag"
git switch main
git branch -D "$btag"


ブランチ

新しいブランチを作成して切り替える:

git checkout -b branchname

既存のブランチに切り替える:

git checkout branchname

branch1 の変更を branch2 にマージする(どちらか一方が main でも構いません)。きれいな履歴にするには rebase を使います。 あるいは merge を使って両方のブランチの履歴を残すこともできます。

git checkout branch2
git merge/rebase branch1 

ブランチをリモートにプッシュする:

git push -u origin branchname

リモートブランチを削除する:

git push origin --delete branchname

すべてのリモートブランチを一覧表示する:


既存のプルリクエストまたはマージリクエストを修正する

フォークからの GitHub プルリクエスト(PR)または GitLab マージリクエスト(MR)は、そのフォーク内のソースブランチを 追跡し、それを元のリポジトリのターゲットブランチ(多くの場合 main)にマージすることを提案します。フォークは 別個のリポジトリであり、その中にもブランチがあります。同じフォーク内の同じブランチにコミットを追加すれば、 ブラウザからでもローカルクローンからのプッシュでも、開いているリクエストは自動的に更新されます。 別のリクエストを作成する必要はありません。

ブラウザでコードを更新する

フォークをクローンする必要はありません。ただし、そのソースブランチを更新する権限は必要です。作成者は自分のフォークを 編集でき、メンテナは作成者が GitHub で Allow edits from maintainers を、GitLab で Allow contributions from members を 有効にしている場合に変更を加えられます。

  1. 既存の PR または MR を開き、そのソースフォークとソースブランチを特定します。
  2. ブラウザでそのフォークを開き、ソースブランチ(例: my-feature)を選択します。
  3. ファイルを開いて Web エディタを使用します。複数ファイルにまたがる変更には、GitHub の Web エディタまたは GitLab の Web IDE を使用します。
  4. 変更をレビューし、既存のソースブランチに直接コミットします。選択肢が提示された場合は、新しいブランチや リクエストを作成するのではなく、そのブランチを選びます。

既存の PR または MR にそれらのコミットが自動的に含まれます。リクエストのページで更新された差分とチェック結果を 確認してください。

ソースブランチを更新する権限がある場合は、レビューコメントの変更提案を PR または MR 上で直接適用することもできます。 提案を適用すると、そのブランチにコミットが作成されます。権限を持たないレビュアーは、作成者または権限を持つメンテナが 適用できるように変更を提案できます。

ローカルクローンからコードを更新する

1) フォークとそのソースブランチを特定する

リクエストのページでソースフォークとブランチ(例: your-user/repository と my-feature)を確認します。 そのフォークのソースブランチへの書き込み権限が必要です。別のフォークを作成したり別のブランチにプッシュしたりしても、 既存のリクエストは更新されません。

ローカルクローンで、各リモートがどのリポジトリを指しているか確認します。

git remote -v

以下のコマンドでは、リクエストのソースフォークを指す fork という名前のリモートを使用します。そのフォークがすでに origin の場合は、全体を通して fork を origin に置き換えてください。それを指すリモートがない場合は、GitHub または GitLab に表示される、そのフォークの SSH または HTTPS クローン URL を使って追加します。

git remote add fork <fork-clone-url>

2) フォークのソースブランチに切り替える

作業ツリーをクリーンな状態にしてから始めます。ブランチを切り替える前に、関係のない変更はコミットするか、一時退避しておきます。

git status
git fetch fork
git switch my-feature
# If the branch does not exist locally, use this instead:
# git switch --track fork/my-feature
git pull --ff-only fork my-feature

プル時にブランチの分岐が報告された場合は、続行する前にローカルとリモートのコミットの分岐を解消してください。

3) ファイルを編集し、変更をレビューし、コミットして、フォークにプッシュする

要求された変更を加えて関連するチェックを実行し、その後、次を実行します。

git diff
git add path/to/file path/to/another-file
git diff --cached
git commit -m "Address review feedback"
git push fork HEAD:my-feature

HEAD:my-feature は現在のコミットをフォークの既存のソースブランチにプッシュします。元のリポジトリの PR または MR に新しいコミットが含まれます。そのページで更新された差分とチェック結果を確認してください。

4) 必要に応じてタイトルや説明を更新する

GitHub または GitLab で既存のリクエストを開き、タイトルや説明を編集します。これらの編集に Git のコミットは 必要ありません。変更の範囲が変わった場合は、レビュアーが理解できるように最終的な変更内容を説明してください。

コミットを修正したりソースブランチをリベースしたりした場合

すでにプッシュしたコミットを修正したりリベースしたりすると、その履歴が書き換えられます。ブランチを共有している人と 調整したうえで、git push --force-with-lease fork HEAD:my-feature を使って同じリクエストを更新します。lease のチェックで プッシュが拒否された場合は、再試行する前にリモートの変更をフェッチしてレビューしてください。ブランチ保護により 強制プッシュが禁止されている場合があります。新しいコミットを追加すれば履歴の書き換えを回避できます。

リクエストがマージされた後は、新しいブランチと新しい PR または MR を通じて追加の変更を提出してください。


別のブランチから特定のファイルだけを取り込む(ブランチ全体はマージしない)

別のブランチ(またはコミット/タグ)から 1 つまたは数個のファイルだけが必要な場合は、ブランチ全体をマージせず、 それらのパスだけを現在の作業ツリーに取り込みます。

⚠️ これらのコマンドは、指定されたパスのローカルコピーを上書きします。それらに対する未コミットの変更は 失われます。不安な場合はまず git status を実行してください。

まず差分をプレビューする

git diff main..otherbranch -- path/to/file        # what would change in that file
git show otherbranch:path/to/file                 # print the file as it is on otherbranch

ファイルをコピーする(ローカル版を置き換える)

# Modern (recommended). --source can be a branch, tag, or commit SHA.
git restore --source=otherbranch --staged --worktree -- path/to/file [more/files...]

# Classic equivalent (also stages the result):
git checkout otherbranch -- path/to/file [more/files...]

git checkout <branch> -- <path> は作業ツリーとインデックスの両方を更新します(変更はステージング済みになります)。 git restore --source=<ref> -- <path> はデフォルトでは作業ツリーのみを更新します。ステージングもするには(上記のように) --staged を追加します。その後は通常どおりコミットします。

git add path/to/file        # only needed if the file is not already staged
git commit -m "Bring path/to/file from otherbranch"

ファイル内の一部の変更だけを取り込む(対話的、ハンク単位)

これは 1 つのファイルを「マージする」のに最も近い方法です。どのハンクを取り込むかを選べます。

git restore -p --source=otherbranch -- path/to/file   # or: git checkout -p otherbranch -- path/to/file

1 つのファイルの真の 3 ウェイマージ

checkout/restore はファイルを置き換えるのであって、その内容を自分のファイルの内容とマージするわけではありません。 2 つのバージョン間で 1 つのファイルを実際に行単位でマージするには、git merge-file を使用します。

git show otherbranch:path/to/file > /tmp/theirs       # their version
git show $(git merge-base HEAD otherbranch):path/to/file > /tmp/base   # common ancestor
git merge-file path/to/file /tmp/base /tmp/theirs     # merges into path/to/file, marks conflicts


コンフリクト

1. コンフリクトを解決する

git status
git add path
git checkout --ours  path   # keep our version (rebase source)
git checkout --theirs path  # keep their version (rebase target)
git add path
git rm -- path
# if the file is already gone locally:
git rm --cached -- path
git add -u

2. リベースまたはマージを完了する

git rebase --continue   # repeat until it finishes
git push origin branch2

または

git commit -m "merge branch1 into branch2"
git push origin branch2


未マージのステータスコード


履歴

特定のファイルのコミットと差分を表示する

git log -p -- path/to/file

コンパクトな 1 行履歴

git log --follow --date=short --pretty=format:"%h %ad %an %s" -- path/to/file

リポジトリからすべての履歴を削除するには:

	git checkout --orphan new-main
	git add -A
	git commit -m 'new files'
	git branch -D main
	git branch -m main
	git push -f origin main
	git branch --set-upstream-to=origin/main main


直近のコミットをリバートする/取り消す

直近のコミットを「取り消す」には、まったく異なる 2 つの方法があります。コミットがすでにプッシュされているか どうかで選びます。

すでにプッシュ済み → git revert(安全、履歴を保持)

git revert はコミットを削除しません。その変更を打ち消す新しいコミットを作成します。履歴は保持され、 強制プッシュも不要なので、共有ブランチやプッシュ済みのブランチに適した選択です。

git revert HEAD          # opens an editor for the revert message
git revert --no-edit HEAD   # reuse the default message, no editor
git push

リバート自体を取り消すには、そのリバートをさらにリバートする(git revert HEAD)か、まだローカルにしかないうちに、 以下のリセットコマンドで破棄します。

ローカルのみ → git reset(履歴を書き換える)

コミットがまだプッシュされていない場合は、ブランチポインタを 1 コミット戻します。変更内容がどうなるかは フラグによって異なります。

git reset --soft  HEAD~1   # undo the commit, keep changes staged
git reset --mixed HEAD~1   # undo the commit, keep changes in the working tree (default)
git reset --hard  HEAD~1   # undo the commit AND discard the changes

⚠️ --hard はそのコミットの作業ツリー上の変更を永久に破棄します。まず git status(不安なら git stash)を実行してください。

よくあるケース: 作業は残しつつコミットをやり直す — git reset --soft HEAD~1 の後、必要に応じて再ステージング/コミットします。

リセットせずに、メッセージだけを修正したり、忘れていたファイルを直近のコミットに追加したりする:

git commit --amend            # edit message and/or fold in staged changes
git commit --amend --no-edit  # keep the message, just add the staged changes

すでにプッシュ済みだった場合

プッシュ済みのコミットをリセットすると履歴が書き換えられるため、プッシュは non-fast-forward となり、強制する必要があります。 他の誰かがその間にプッシュしていた場合に拒否する --force-with-lease を優先してください。

git reset --hard HEAD~1
git push --force-with-lease

⚠️ これは自分が管理するブランチでのみ行ってください。共有履歴を書き換えると他のクローンとの整合性が崩れます。 共有ブランチでは代わりに git revert を使用してください。

誤って取り除いたコミットを復元する

reset はコミットを即座に削除するわけではありません。以前の先端コミットはしばらく reflog に残ります。

git reflog                 # find the SHA of the lost commit
git reset --hard <sha>     # or: git cherry-pick <sha>


トークン(個人アクセストークン)

個人アクセストークン(PAT)は、リポジトリではなくあなたの個人アカウントに紐づく認証情報です。 常に次の場所で作成します。

Personal Settings > Developer Settings > Personal access tokens

間違えやすい重要な 2 点:

既存のトークンを regenerate(新しい値を生成)するか、クラシックトークンのスコープをその場で編集(値は変わらない)できます。 どちらも同じページで行います。常にカスタムの有効期限を設定してください。

どのスコープ/権限が必要か?

トークンが存在するリポジトリではなく、トークンが行うべきことに対応する行を選びます。

目的 クラシックのスコープ きめ細かなトークンの権限
別のリポジトリのワークフローをトリガーする(API) repo / public_repo Actions → Read and write
.github/workflows/* ファイルを追加/編集する workflow Workflows → Read and write
プライベートコンテナイメージをプルする read:packages Packages → Read

⚠️ よくある間違い: workflow スコープはワークフローファイルをコミットするためだけのものです。実行を トリガーすることはできません。workflow_dispatch / repository_dispatch を発火させるには、クラシックトークンでは repo (パブリックリポジトリでは public_repo)、きめ細かなトークンでは Actions: Read and write が 必要です。

直接リンク

(ワークフローではなく)ローカルツールが使用するトークンは、そのツールが読み込むファイルに保存してください。通常はホームディレクトリ内の ファイルで、たとえば .bob です。


シークレット

GitHub Actions のシークレットは、リポジトリの設定に保存される暗号化された変数です。そのリポジトリのワークフローは 実行時に ${{ secrets.SECRET_NAME }} 経由でそれを読み取ります。値がログに出力されたり、 リポジトリを閲覧する人に表示されたりすることはありません。

2 つの要素は別物として扱ってください。これらは別の場所に存在します。

作成して保存する方法

  1. あなたの個人アカウント(Developer settings)でトークンを生成し、ワークフローに操作させたい ターゲットリポジトリに対応するスコープ/権限を選びます(上記の表を参照)。そのアカウントはターゲットリポジトリへの書き込み権限を持っている必要があります。

  2. 画面に表示されたトークンの値をコピーします(表示されるのは一度だけ)。

  3. それを使用するワークフローが存在するリポジトリにシークレットを追加する

    • そのリポジトリ → Settings → Secrets and variables → Actions に移動します (例: https://github.com/gemc/pygemc/settings/secrets/actions)
    • New repository secret をクリックします
    • 名前: 例 GEMC_SRC_PAT(ワークフロー YAML で参照される名前と一致させる必要があります)
    • 値: トークンを貼り付けます
    • Add secret をクリックします


別のリポジトリのワークフローをトリガーする

組み込みの GITHUB_TOKEN は別のリポジトリのワークフローを開始できないため、リポジトリ間のトリガーには シークレットとして保存した PAT を使用します。リポジトリ A のワークフローが GitHub API を呼び出して リポジトリ B のワークフローをディスパッチする、という形です。

必要なものは 3 つです。

  1. リポジトリ B への書き込み権限を持つアカウントが所有し、Actions をトリガーするスコープを持つ PAT (クラシックの repo、または B に対するきめ細かなトークンの Actions: Read and write)。上記のトークンの表を参照してください。
  2. その PAT を保持するシークレット。リポジトリ A(トリガー元)に保存します。
  3. リポジトリ B のワークフローにディスパッチ可能なトリガー。ターゲットのワークフローの on: ブロックに workflow_dispatch:(任意で inputs:)を追加します。そうしないと API 呼び出しが 404/422 を返します。

ディスパッチ呼び出し(リポジトリ A のワークフロー内)

      - name: Trigger workflow_dispatch on repo B
        run: |
          curl --fail-with-body -X POST \
            -H "Accept: application/vnd.github+json" \
            -H "Authorization: Bearer ${{ secrets.B_PAT }}" \
            -H "X-GitHub-Api-Version: 2022-11-28" \
            https://api.github.com/repos/<owner>/<repoB>/actions/workflows/test.yml/dispatches \
            -d '{"ref":"main","inputs":{"triggered_by":"repo A"}}'

inputs のキーは、リポジトリ B のワークフローの workflow_dispatch: の下で宣言された inputs: と 一致させる必要があります。

セキュリティ: workflow_run で連鎖するトリガーを保護する

on: workflow_run を持つワークフローは特権付きで実行され(シークレット/PAT を参照できる)、フォークからの PR イベントでも起動するため、ジョブの実行条件を設けてください。そうしないと、フォークからの PR が特権付きステップに到達し、 workflow_run.head_sha 経由でチェックアウトされたコードがその特権で実行されるおそれがあります。これが CodeQL が 「特権コンテキストでの信頼できないコードのチェックアウト」を指摘する理由でもあります。

どのフィールドをチェックするかは連鎖上の位置によって異なります — 間違えやすい点です。

このプロジェクトでの例

トリガー元(リポジトリ A) シークレット名(A 内) ターゲット(リポジトリ B) トークンに必要な権限(B 上)
gemc/pygemc GEMC_SRC_PAT gemc/src src 上で Actions をトリガーする
gemc/src CLAS12_SYSTEMS_PAT gemc/clas12-systems clas12-systems 上で Actions をトリガーする