Using Git



Clone one branch or tag, w/o history, with submodules w/o their history (shallow)

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


Update a tag / release (rewrite an existing tag)

Use these Variables

Example: tag=v1.2.3

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

1) Create and switch to a temporary branch from the existing tag

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

2) Make changes, commit, and push the temporary branch

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

3) Delete old local tag pointing to the old commit, recreate the tag locally at the new HEAD

(You’re already on $btag, so the extra git switch "$btag" is redundant.)

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

4) Replace the remote tag pointing to the new commit

First delete the old remote tag, then push the new one. Force-update the remote tag explicitly because tags are immutable by convention and many servers reject non-fast-forward tag updates unless forced.

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

5) Update the GitHub Release (and assets) if necessary

6) Cleanup: delete the temporary branch (remote + local)

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


Branches

Create new branch and switch to it:

git checkout -b branchname

Switch to an existing branch:

git checkout branchname

Merge changes from branch1 into branch2 (one of them can be main). Use rebase for a clean history. Alternatively, you can use merge to keep the history of both branches.

git checkout branch2
git merge/rebase branch1 

Push a branch to remote:

git push -u origin branchname

Delete a remote branch:

git push origin --delete branchname

List all remote branches:


Take specific files from another branch (not a full merge)

When you only want one or a few files from another branch (or commit/tag), don’t merge the whole branch — pull just those paths into your current working tree.

⚠️ These commands overwrite the local copy of the listed paths. Any uncommitted changes to them are lost. Run git status first if unsure.

Preview the difference first

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

Copy the files over (replace local version)

# 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> updates both the working tree and the index (already staged). git restore --source=<ref> -- <path> updates only the working tree by default — add --staged (as above) to also stage it. Then commit normally:

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

Take only some changes within a file (interactive, hunk by hunk)

This is the closest thing to “merging” a single file: pick which hunks to bring in.

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

True 3-way merge of a single file

checkout/restore replace the file; they do not merge its content with yours. For an actual line-by-line merge of one file between two versions, use 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


Conflicts

1. Resolve Conflicts

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. Finalize a rebase or a merge

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

or

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


Unmerged status codes


History

Show commit + diff for a specific file

git log -p -- path/to/file

Compact one-line history

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

To remove all history from a repo:

	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


Tokens (Personal Access Tokens)

A Personal Access Token (PAT) is a credential tied to your personal account, not to a repository. You always create it under:

Personal Settings > Developer Settings > Personal access tokens

Two key facts that are easy to get wrong:

You can regenerate an existing token (new value) or edit a classic token’s scopes in place (value unchanged) — both under the same page. Always set a custom expiration date.

Which scope / permission do I need?

Pick the row for what the token must do, not for the repo it lives in:

Goal Classic scope Fine-grained permission
Trigger another repo’s workflow (API) repo / public_repo Actions → Read and write
Add / edit .github/workflows/* files workflow Workflows → Read and write
Pull private container images read:packages Packages → Read

⚠️ Common mistake: the workflow scope is only for committing workflow files. It does not let you trigger a run. To fire a workflow_dispatch / repository_dispatch you need repo (public_repo for public repos) on a classic token, or Actions: Read and write on a fine-grained token.

A token used by a local tool (not a workflow) should go in a file read by that tool, typically in the home dir, for example .bob.


Secrets

A GitHub Actions secret is an encrypted variable stored in a repository’s settings. Workflows in that repository read it at runtime via ${{ secrets.SECRET_NAME }}; the value is never printed in logs or shown to anyone browsing the repo.

Keep the two pieces separate — they live in different places:

How to create and store it

  1. Generate the token in your personal account (Developer settings), choosing the scope/permission for the target repo you want the workflow to act on (see the table above). The account must have write access to that target repo.

  2. Copy the token value shown on screen (visible only once).

  3. Add the secret to the repository whose workflow uses it

    • Go to that repo → Settings → Secrets and variables → Actions (e.g. https://github.com/gemc/pygemc/settings/secrets/actions)
    • Click New repository secret
    • Name: e.g. GEMC_SRC_PAT (must match the name referenced in the workflow YAML)
    • Value: paste the token
    • Click Add secret


Triggering a workflow in another repository

The built-in GITHUB_TOKEN cannot start workflows in a different repository, so cross-repo triggers use a PAT stored as a secret. The pattern: repo A’s workflow calls the GitHub API to dispatch repo B’s workflow.

You need three things:

  1. A PAT owned by an account with write access to repo B, scoped to trigger Actions (classic repo, or fine-grained Actions: Read and write on B). See the Tokens table above.
  2. The secret holding that PAT, stored in repo A (the trigger source).
  3. A dispatchable trigger on repo B’s workflow — add workflow_dispatch: (with optional inputs:) to the on: block of the target workflow, otherwise the API call returns 404/422.

The dispatch call (in repo A’s workflow)

      - 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"}}'

The inputs keys must match the inputs: declared under workflow_dispatch: in repo B’s workflow.

Security: guard workflow_run-chained triggers

A workflow with on: workflow_run runs privileged (it can see secrets/PATs) and fires for fork-PR events too, so gate the job — otherwise a fork PR could reach the privileged step and any code it checks out via workflow_run.head_sha. This is also why CodeQL flags “checkout of untrusted code in a privileged context”.

Which field to check depends on the position in the chain — this trips people up:

Examples in this project

Trigger source (repo A) Secret name (in A) Target (repo B) Token needs (on B)
gemc/pygemc GEMC_SRC_PAT gemc/src trigger Actions on src
gemc/src CLAS12_SYSTEMS_PAT gemc/clas12-systems trigger Actions on clas12-systems