Git & GitHub
============

This guide is a practical, end-to-end reference for using ``Git`` and ``GitHub`` in a
research and machine-learning setting. It is written for researchers, students, and
engineers who produce code, configurations, notebooks, and papers, and who need to
collaborate without losing work, overwriting one another, or corrupting a shared history.

The emphasis throughout is on building an accurate *mental model* first and then layering
commands on top of it. Most confusion with Git comes not from the commands but from not
knowing what state the repository is in; once you have a clear mental model, the commands
become much more intuitive and easier to recall.

.. tip::

   You do not have to read this top to bottom. Many people already have a working
   knowledge of Git — use the section headers (and the "On this page" list below) to jump
   straight to the topics you want to level up on, and skip anything you already know.

**Scope.** This document covers version control with Git and collaboration on GitHub. It
deliberately stops at the point where automation begins: *automated testing* and
*continuous integration and deployment* (CI/CD with GitHub Actions) are substantial topics
in their own right and are out of scope here.

.. note::

   Hands-on practices are marked throughout with a **Practice** callout, and a dedicated
   :ref:`git-exercises` section at the end ties them together into short labs. You only
   need a terminal and a free GitHub account to follow along.

.. contents:: On this page
   :local:
   :depth: 2


Why Version Control
-------------------

Research and machine-learning work is, at bottom, a sequence of experiments whose results
must be *trustworthy* and *reproducible*. Without disciplined version control this breaks
down quickly: code is emailed around, directories named ``final``, ``final_v2``, and
``final_REALLY_final`` accumulate, and six months later nobody can say which version of the
code produced which figure in the paper.

``Git`` is a **distributed version-control system**. It records snapshots of your project
over time, lets many people work in parallel, and lets you move backward and forward
through that history with confidence. Because it is *distributed*, every clone is a
complete copy of the entire history — you can commit, branch, inspect, and diff completely
offline.

``GitHub`` is a **hosting platform** built on top of Git. It stores a shared copy of a
repository in the cloud and adds the collaboration layer that Git itself lacks: pull
requests, code review, issue tracking, access control, and automation.

For a research group specifically, version control delivers four things that are otherwise
very hard to guarantee:

* **Provenance** — every line of code is traceable to *who* wrote it, *when*, and *why*.
* **Reproducibility** — any past result can be regenerated by checking out the exact commit
  (and, with discipline, the exact environment) that produced it.
* **Parallelism** — several people can develop features and experiments simultaneously
  without stepping on each other.
* **Safety** — committed *and pushed* work is extraordinarily hard to lose, which makes bold
  refactoring and experimentation low-risk.

.. warning::

   That safety only applies to work you have **pushed** to a remote. A branch that lives
   only on your laptop is one hard-drive failure — or one bad rebase — away from being gone
   for good. If you have made substantial local changes, push them (even to a
   work-in-progress branch) so a copy exists on the server.

.. important::

   Git and GitHub are not the same thing. **Git** is the tool that versions your files and
   runs entirely on your machine. **GitHub** is one of several websites (others include
   GitLab and Bitbucket) that host Git repositories and add collaboration features. Git is
   fully usable with no GitHub account at all.


Installation and First-Time Setup
----------------------------------

Install Git
^^^^^^^^^^^

.. tab-set::

   .. tab-item:: Linux

      Use your distribution's package manager:

      .. code-block:: bash

         sudo apt update && sudo apt install git    # Debian / Ubuntu
         sudo dnf install git                        # Fedora / RHEL

   .. tab-item:: macOS

      Install via `Homebrew <https://brew.sh>`_ (recommended) or the Xcode command-line
      tools:

      .. code-block:: bash

         brew install git
         # or trigger Apple's installer:
         xcode-select --install

   .. tab-item:: Windows

      Download and install `Git for Windows <https://git-scm.com/download/win>`_, which
      bundles **Git Bash** — a terminal that provides the same commands shown throughout
      this guide.

Verify the installation:

.. code-block:: bash

   git --version

Configure Your Identity
^^^^^^^^^^^^^^^^^^^^^^^^

Every commit is permanently stamped with an author name and email. Set these once so your
contributions are attributed correctly, both locally and on GitHub.

.. code-block:: bash

   git config --global user.name "Your Name"
   git config --global user.email "you@example.com"

.. important::

   Use the **same email that is registered on your GitHub account**. If the two differ,
   your commits will not be linked to your profile and will not appear in contribution
   graphs — a common and confusing source of "missing" work.

A few defaults are worth setting immediately; they prevent whole classes of beginner
friction:

.. code-block:: bash

   git config --global init.defaultBranch main   # name the first branch "main", not "master"
   git config --global pull.ff only              # refuse surprise merge commits on pull
   git config --global push.autoSetupRemote true # first push auto-creates the upstream branch
   git config --global core.editor "nano"        # editor used for commit messages

Inspect your configuration at any time:

.. code-block:: bash

   git config --list --show-origin   # every setting and which file it came from
   git config user.email             # show a single value

.. note::

   Configuration lives at three levels, each overriding the one above it: **system**
   (``--system``, the whole machine), **global** (``--global``, your user account), and
   **local** (``--local``, a single repository). Use a local override when one project needs
   a different identity — for example, a personal email on a work machine.


How Git Works (a Brief Look Inside)
-----------------------------------

You do not need to know Git's internals to use it, but a small, accurate mental model
prevents the majority of confusion. This section is deliberately compact.

The Three Areas
^^^^^^^^^^^^^^^^

Every Git project has three places where a version of a file can live. Almost every Git
command is, in effect, moving content between them:

.. list-table::
   :widths: 28 72
   :header-rows: 1

   * - Area
     - What it is
   * - **Working tree**
     - The actual files on disk that you edit.
   * - **Staging area** (the *index*)
     - A holding zone where you assemble exactly what your next commit will contain.
   * - **Repository** (the ``.git`` directory)
     - The permanent, committed history of the project.

The everyday loop moves changes from left to right: you **edit** files in the working tree,
**stage** the ones you want with ``git add``, and **commit** the staged snapshot into the
repository.

.. code-block:: text

   working tree  --( git add )-->  staging area  --( git commit )-->  repository
        ^                                                                  |
        +-----------------------( git restore / checkout )-----------------+

The staging area is the feature that distinguishes Git from simpler tools: it lets you
craft a commit out of *some* of your changes while leaving the rest for a later, separate
commit — the basis of clean, atomic history.

The Object Model, Commits, and Branches
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Under the hood, Git is a content-addressed store of four object types. You rarely touch
them directly, but knowing they exist demystifies almost everything:

* A **blob** stores the contents of a file; a **tree** stores a directory listing (names
  plus the blobs and sub-trees it contains); a **commit** points to one tree (a full
  snapshot of the project) plus metadata and its parent commit(s); a **tag** is a named
  pointer to a commit.

From this a few important properties follow:

* A **commit** is a *complete snapshot*, not a diff. It records the whole project state, the
  author and date, a message, and a link to its **parent**. Chaining parents forms the
  history — more precisely a **Directed Acyclic Graph (DAG)** once branches and merges exist.
* Every object is named by a **SHA-1 hash** of its content (e.g. ``a04dbce…``). Because the
  hash covers the content *and* the parent, history is tamper-evident: change anything and
  every downstream hash changes. (This is also why *rewriting* history — see
  `Merge vs. Rebase`_ — is possible: it produces new commits with new hashes.)
* A **branch** is nothing more than a lightweight, movable *pointer* to a commit. Creating
  one writes a single small file; this is why branching is cheap and encouraged. Committing
  advances the current branch pointer to the new commit.
* **HEAD** is a pointer to *where you are now* — normally the branch you currently have
  checked out. A "detached HEAD" (discussed later) simply means HEAD points directly at a
  commit rather than at a branch.

.. tip::

   Hold on to one idea above all: branching is cheap and local. A large share of Git's
   power comes from creating short-lived branches freely and integrating them back.


Working with GitHub
-------------------

Everything so far has been entirely local. GitHub introduces a **remote**: a shared copy of
the repository that you and your collaborators push to and pull from.

Authenticating: SSH vs. HTTPS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

GitHub no longer accepts an account password on the command line; you must use one of two
mechanisms:

.. list-table::
   :widths: 20 40 40
   :header-rows: 1

   * - Method
     - How it works
     - Notes
   * - **SSH**
     - Add your machine's public SSH key to GitHub, then clone with ``git@github.com:…``
       URLs.
     - Best for a machine you control; no prompts after setup. The same key concept used to
       access the clusters.
   * - **HTTPS + token**
     - Clone with ``https://`` URLs and authenticate with a **Personal Access Token (PAT)**
       in place of a password.
     - Useful where SSH is blocked by a firewall. Store the token with a credential helper
       to avoid re-entering it.

To set up an SSH key (the same procedure used for cluster access):

.. code-block:: bash

   ssh-keygen -t ed25519 -C "you@example.com"   # generate a key pair
   cat ~/.ssh/id_ed25519.pub                     # copy this PUBLIC key into GitHub → Settings → SSH keys
   ssh -T git@github.com                         # test the connection

.. important::

   Only ever upload your **public** key (the ``.pub`` file). Your private key must never be
   shared, copied to a shared machine, or committed to a repository.

Remotes: Connecting Local to GitHub
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A **remote** is a named URL pointing at a hosted repository. By convention the primary
remote is called ``origin``.

.. code-block:: bash

   git remote -v                                       # list configured remotes
   git remote add origin git@github.com:you/repo.git   # link a local repo to GitHub
   git push -u origin main                             # first push; -u sets the upstream

Once the upstream is set, plain ``git push`` and ``git pull`` operate on the correct branch
automatically.

Push, Fetch, and Pull
^^^^^^^^^^^^^^^^^^^^^^

.. list-table::
   :widths: 22 78
   :header-rows: 1

   * - Command
     - What it does
   * - ``git push``
     - Upload your local commits to the remote.
   * - ``git fetch``
     - Download remote changes **without** touching your working files — for inspection
       first.
   * - ``git pull``
     - ``git fetch`` **plus** integrate the changes into your current branch, in one step.

.. tip::

   When collaborating, prefer ``git fetch`` followed by a review of ``git log origin/main``
   before integrating. A reflexive ``git pull`` can trigger a surprise merge or conflict at
   an inconvenient moment.

.. _git-visualizing-history:

Visualising Branch History
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Reading the commit graph is a skill, and good visual tools make it effortless:

* **The GitHub network graph.** Every repository has an interactive branch/commit graph at
  ``…/network`` — for example, the public
  `github.com/Amii-Engineering/amii-tools/network <https://github.com/Amii-Engineering/amii-tools/network>`_.
  It is the quickest way to *see* how branches diverged and merged.
* **The GitHub Desktop app** and the repository's **"Insights → Network"** view show the
  same history graphically.
* **In your editor (VS Code, Cursor, …)**, the
  `GitLens <https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens>`_ extension
  is invaluable: it draws the branch graph, and — most useful day to day — when you hover
  over any line of code it shows the last commit that changed it, with a click-through link
  to view that commit on GitHub. It surfaces a great deal of "who changed this and why"
  without leaving the file.

.. admonition:: Practice — publish a repository
   :class: tip

   #. Create a new, empty repository on GitHub (no README).
   #. In a local project, run ``git remote add origin <url>`` and ``git push -u origin main``.
   #. Edit a file through GitHub's web UI and commit it there, then run ``git pull`` locally
      and watch the change arrive. Finally, open the repo's ``/network`` graph to see it.


The Solo Workflow
-----------------

This is the core loop you will use every day, long before any collaboration enters the
picture.

Starting a Repository
^^^^^^^^^^^^^^^^^^^^^^

You either create a new repository or copy an existing one:

.. code-block:: bash

   # Option A: start a brand-new repository in the current folder
   git init

   # Option B: copy an existing repository (SSH URL preferred — see "Working with GitHub")
   git clone git@github.com:Amii-Engineering/amii-docs.git

Checking Status and Staging
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``git status`` is the single most useful command in Git: it reports what has changed, what
is staged, and which branch you are on. Run it liberally — before and after every other
command — until the repository's state is always clear to you.

.. code-block:: bash

   git status                  # what changed? what is staged? which branch?
   git add experiment.py       # stage one specific file
   git add -u                  # stage changes to tracked files only (no new files)
   git add -A                  # stage everything: modifications, new files, and deletions
   git restore --staged file   # unstage (keep the edits, just remove from the next commit)

.. tip::

   Use ``git add -p`` to stage changes *hunk by hunk*. This is invaluable for splitting a
   messy working tree into several small, coherent commits instead of one sprawling one.

Committing
^^^^^^^^^^

A commit records the staged snapshot together with a message.

.. code-block:: bash

   git commit -m "Add learning-rate sweep to training script"

   # stage all tracked, modified files AND commit in one step:
   git commit -am "Fix off-by-one error in data loader"

.. note::

   The commit summary is where you — and every future developer, including yourself — come
   to understand *why* a change was made. A conventional style keeps that history readable:
   write the summary in the imperative mood ("Add", "Fix", "Refactor"), keep it short, and
   add a body when the reasoning is not obvious. Many teams adopt
   `Conventional Commits <https://www.conventionalcommits.org>`_ (``feat:``, ``fix:``,
   ``docs:`` …); see :ref:`git-commit-conventions`.

Inspecting History and Differences
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: bash

   git log --oneline --graph --decorate --all   # compact, visual history of all branches
   git show <commit>                            # exactly what one commit changed
   git diff                                     # unstaged changes (working tree vs index)
   git diff --staged                            # staged changes (index vs last commit)
   git blame <file>                             # who last changed each line, and in which commit

.. _git-ignore:

Ignoring Files with ``.gitignore``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Not everything belongs in version control. A ``.gitignore`` file lists patterns that Git
should never track. Getting this right from the first commit is **critical** for research
repositories, which otherwise fill up with large data files, model checkpoints, and — most
dangerously — secrets.

.. code-block:: text

   # Python
   __pycache__/
   *.pyc
   .venv/

   # Data & model artifacts (keep these out of Git — see "Large Files and Datasets")
   data/
   *.csv
   *.ckpt
   *.pt

   # Secrets & environment
   .env
   *.key

   # Editor / OS noise
   .vscode/
   .DS_Store

.. important::

   ``.gitignore`` only affects files Git is **not already tracking**. If a file was already
   committed, adding it to ``.gitignore`` does nothing until you stop tracking it with
   ``git rm --cached <file>`` (this keeps the file on disk but removes it from the index).

.. admonition:: Practice — your first repository
   :class: tip

   #. Create a folder, run ``git init`` inside it, and add a ``README.md``.
   #. Stage and commit it, then run ``git log --oneline`` to see your commit.
   #. Create a ``.env`` file, add ``.env`` to ``.gitignore``, and confirm with
      ``git status`` that Git now ignores it.


Branching and Merging
---------------------

Branches let you develop a feature, experiment, or fix in isolation without disturbing a
known-good branch. They are the foundation of both safe solo experimentation and team
collaboration.

Creating and Switching Branches
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: bash

   git branch                      # list local branches (current one marked with *)
   git switch -c feature/new-loss  # create AND switch to a new branch
   git switch main                 # switch back to main
   git branch -d feature/new-loss  # delete a branch once it is merged

.. note::

   ``git switch`` (change branches) and ``git restore`` (discard file changes) are the
   modern, purpose-built commands. The older ``git checkout`` still does both jobs —
   ``git checkout -b feature/new-loss`` is equivalent to ``git switch -c feature/new-loss``
   — and you will see it throughout older tutorials and answers.

.. tip::

   Adopt a **branch naming convention** and use it consistently, e.g. ``feature/…``,
   ``fix/…``, ``experiment/…``, ``docs/…``. Prefixes make the branch list self-documenting
   and easy to filter.

Merging
^^^^^^^

When the work on one branch is ready, you integrate it into another with ``git merge``. You
switch to the branch that should *receive* the work, then merge the other branch into it. For
example, to fold a small fix branch into a larger feature branch:

.. code-block:: bash

   git switch feature/binary-classification   # the branch that will receive the work
   git merge fix/new-loss                      # merge the fix branch into it

There are two possible outcomes:

* **Fast-forward:** if the receiving branch has not moved since you branched, Git simply
  advances its pointer to your commits. No new commit is created, and the fact that a
  separate feature branch ever existed disappears from the history.
* **Merge commit:** if both branches have new commits, Git combines them and records a new
  *merge commit* with two parents, preserving the fact that the work happened in parallel.

.. code-block:: bash

   git merge --no-ff fix/new-loss

The ``--no-ff`` flag forces Git to always create a merge commit, even when a fast-forward
would be possible. This deliberately preserves the historical existence of the feature
branch and groups its commits together under one merge — which makes the DAG far easier to
read and a feature far easier to revert as a unit. Many teams standardise on ``--no-ff``
merges into shared branches for exactly this reason (see the
`git-flow model <https://nvie.com/posts/a-successful-git-branching-model/>`_).

.. note::

   **Branches can branch off branches.** Nothing forces every branch to start from ``main``
   or ``staging``. It is common to branch a small ``fix/…`` off a larger ``feature/…``,
   merge it back in as above, and let several such sub-branches accumulate on the feature
   until it is a coherent unit of work. That whole unit is then rolled up into a **release**
   and pull-requested into a shared branch (``staging``/``main``) in one go.

.. admonition:: ``git merge`` vs. a Pull Request — when to use which
   :class: important

   A ``git merge`` on your own machine and a **Pull Request** on GitHub both integrate one
   branch into another, but they serve different purposes:

   * A **local merge** is the right tool for integrating *your own* branches together —
     folding a fix into your feature, or keeping a private branch current. It is immediate
     and leaves no record on GitHub.
   * A **Pull Request** is the right tool for landing work in a **shared** branch
     (``main``, ``staging``, a QA/production environment). It exists to get the change
     *reviewed* before it lands, and it leaves a durable, searchable record on GitHub:
     every closed PR is a self-contained "here is a chunk of work that shipped, and why."
     Reviewing the list of merged PRs is far easier than archaeology through the raw commit
     graph hunting for where individual branches were merged in.

   A good habit — even when you work alone — is to always land work on ``main``/``staging``
   through a PR rather than a direct local merge. It keeps the same flow familiar, and it
   gives you the same reviewable history you would want on a team. (In practice most teams
   also enable **branch protection** on these branches, which *requires* a PR anyway.)

Merge vs. Rebase
^^^^^^^^^^^^^^^^^

Both integrate one branch's work into another, but they produce very different histories.
This distinction is one of the biggest "level-ups" in day-to-day Git, so it is worth
studying the pictures below.

.. list-table::
   :widths: 15 45 40
   :header-rows: 1

   * - Approach
     - What it does
     - When to use
   * - **Merge**
     - Joins histories with a merge commit, preserving the true, branching shape of events.
     - Integrating a finished branch into a shared branch; anything already public.
   * - **Rebase**
     - Replays your commits on top of another branch, producing a clean, linear history.
     - Updating and tidying *your own* feature branch before you open or update a PR.

**The problem rebase solves.** Imagine your ``feature`` branch falls behind ``staging``
while you work. If you keep *merging* ``staging`` into your feature to stay current, the
history fills with back-and-forth merge commits:

.. code-block:: text

   *   Merge staging into feature (again)
   |\
   | * work on staging
   * | your commit
   |/
   *   Merge staging into feature
   |\
   | * work on staging
   * | your commit
   |/
   *   branch point

When this branch is finally merged back, the DAG is a tangle of "in and out" merges that is
hard to follow. **Rebasing** instead lifts your commits and replays them on top of the
latest ``staging``:

.. code-block:: bash

   git fetch origin
   git rebase -i origin/staging   # replay MY commits on top of the latest staging

.. code-block:: text

   Before rebase (feature was branched off E):

         A---B---C  feature
        /
   D---E---F---G  staging

   After rebase (A B C are replayed on top of G, the tip of staging):

                 A'--B'--C'  feature
                /
   D---E---F---G  staging

Now your work sits in a single straight line on top of ``staging``. When the PR is merged,
the DAG shows one clean branch-off and one clean merge-back instead of a jumble — the
history becomes genuinely easy to read. (The interactive ``-i`` flag additionally lets you
*reword*, *squash*, or reorder your own commits along the way, e.g. collapsing five
"work in progress" commits into one.)

.. warning::

   **Rebasing rewrites history.** Because ``A'``, ``B'``, ``C'`` are *new* commits with new
   hashes, they are no longer the commits anyone else may have based work on. If a teammate
   has already checked out your branch, pulled ``A B C``, and added their own commit, then
   after you rebase and force-push they will end up with **both** the old and the new copies
   of your commits, plus conflicts, when they next pull:

   .. code-block:: text

      Teammate's local branch after pulling your rebased branch:

      ...--A---B---C---(their work)      <- the original commits, still here
            \
             A'--B'--C'                  <- your rebased copies, now also here

   This is confusing and painful to untangle. **Only rebase commits that are yours and
   unshared.** If in doubt, don't.

**Pushing after a rebase.** Because you have rewritten the branch, a normal ``git push`` is
rejected — your local history no longer matches the remote. You must *force* the push:

.. code-block:: bash

   git push --force-with-lease    # preferred: refuses if someone else updated the branch
   git push -f                    # blunt force: overwrites the remote unconditionally

Prefer ``--force-with-lease``: it still overwrites *your* feature branch, but aborts if the
remote has commits you have not seen (a sign someone else is working on it).

.. important::

   Rebasing is powerful but unforgiving, so build muscle memory on a throwaway branch first,
   *then* use it for real. And remember the escape hatch: at any point during a rebase you
   can run ``git rebase --abort`` to return to exactly where you started. If you get lost or
   the state stops making sense, abort — nothing is lost.


A Branching Model for Team & Production Work
--------------------------------------------

The commands above are neutral about *how* a team organises its branches. Adopting a shared
model turns Git from a personal tool into a release process — and it is what separates
hobby repositories from ones that ship reliably to clients. The model below is a lightly
simplified `git-flow <https://nvie.com/posts/a-successful-git-branching-model/>`_ and is a
good default for product and client work.

.. list-table::
   :widths: 20 80
   :header-rows: 1

   * - Branch
     - Role
   * - ``main`` (or ``master``)
     - **Production.** This is what is served to customers — the clients your work ships to.
       It must always be the latest *stable* release, with forward-facing documentation.
       Nothing lands here until it is ready to go live.
   * - ``staging``
     - **The next release.** An integration branch that accumulates finished features and is
       stabilised until it is ready, at which point it is merged into ``main``.
   * - ``feature/…``
     - **Work in progress.** Each feature or fix branches off ``staging``, and is merged
       back into ``staging`` (via a pull request), never directly into ``main``.

The everyday flow is therefore:

#. Branch ``feature/x`` off ``staging``.
#. Do the work; keep the branch current by **rebasing onto** ``origin/staging`` (see above).
#. Open a PR from ``feature/x`` into ``staging``. In practice the merge itself happens on
   GitHub: once the review comments are addressed and checks pass, you click the green
   **Merge** button (the merge-commit option corresponds to ``--no-ff``) and you are done.
#. When ``staging`` is ready to release, merge ``staging`` into ``main`` and tag the release.

.. tip::

   Treating ``main`` as sacred, always-deployable production is the single habit that most
   improves stability for the client. It also prepares staff for how the wider software
   industry works. Read the original
   `"A successful Git branching model" <https://nvie.com/posts/a-successful-git-branching-model/>`_
   for the full picture, including hotfix and release branches.


Undoing Things
--------------

Almost nothing that has been *committed* is truly lost, but the correct command depends
entirely on the situation. Choosing the wrong one — especially ``reset --hard`` — is the
most common way beginners lose work, so it is worth internalising this table.

.. list-table::
   :widths: 42 58
   :header-rows: 1

   * - You want to…
     - Command
   * - Discard unstaged changes to a file
     - ``git restore <file>``
   * - Unstage a file (but keep the edits)
     - ``git restore --staged <file>``
   * - Amend the **last** commit's message or contents
     - ``git commit --amend``
   * - Undo the last commit but **keep** the changes staged
     - ``git reset --soft HEAD~1``
   * - Undo the last commit and unstage the changes (keep the files)
     - ``git reset HEAD~1`` *(mixed — the default)*
   * - Undo the last commit and **discard** the changes entirely
     - ``git reset --hard HEAD~1`` ⚠️
   * - Undo a commit that is **already pushed / shared**
     - ``git revert <commit>``

.. note::

   ``HEAD~1`` means "one commit before HEAD"; ``HEAD~2`` is two before, and so on. So
   ``git reset HEAD~3`` rewinds the branch pointer back by three commits. You can also name
   a commit directly by its hash.

.. important::

   ``git reset`` moves your branch pointer and **rewrites history** — it is only safe on
   commits you have not shared. ``git revert`` instead creates a *new* commit that cancels
   out an old one, leaving history intact; this is the correct choice for anything already
   pushed.

.. note::

   In day-to-day team work, much of this "undo a shared change" happens through the **GitHub
   web UI** rather than the command line. Once work is merged you are usually in review mode
   rather than code-writing mode, and a merged PR has a one-click **Revert** button that
   opens a ready-made PR backing the change out — the frontend equivalent of ``git revert``.
   See :ref:`git-team-problems`.

.. warning::

   ``git reset --hard`` permanently discards uncommitted changes in your working tree. There
   is no undo for work that was never committed. When in doubt, ``git stash`` first (below)
   to set changes aside safely.

Stashing
^^^^^^^^

``git stash`` parks your uncommitted changes so you can switch context (for example, to a
quick fix on another branch) with a clean working tree, then restores them afterward.

.. code-block:: bash

   git stash            # set aside all uncommitted (tracked) changes
   git stash -u         # include untracked files as well
   git stash list       # see stashed entries
   git stash pop        # reapply the most recent stash and drop it from the list

.. _git-reflog:

Recovering "Lost" Commits with the Reflog
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Git records every movement of ``HEAD`` in the **reflog**. Even after a mistaken ``reset``
or a deleted branch, the commit almost always still exists and can be recovered.

.. code-block:: bash

   git reflog                       # list recent HEAD positions with their hashes
   git switch -c recovered <hash>   # bring a "lost" commit back on a new branch

.. tip::

   The reflog is your safety net. Before assuming work is gone, run ``git reflog`` — the
   commit is typically still reachable for at least 30 days.


Collaborating in a Team
-----------------------

This is where Git delivers the most value — and where most problems occur. The workflow
below, the **feature-branch + pull-request** model, is what this documentation repository
itself uses, and it scales from two people to hundreds.

The Pull-Request Workflow
^^^^^^^^^^^^^^^^^^^^^^^^^^

#. **Sync** your local base branch (``staging`` in the model above, or ``main`` in simpler
   repos) with the remote:

   .. code-block:: bash

      git switch staging
      git pull

#. **Branch** for your task, using a descriptive, prefixed name:

   .. code-block:: bash

      git switch -c feature/add-fsdp-example

#. **Work**, making focused commits as you go.

#. **Push** the branch to GitHub:

   .. code-block:: bash

      git push -u origin feature/add-fsdp-example

#. **Open a Pull Request (PR)** from your branch into the base branch. Describe *what*
   changed and *why*, and link any related issue. Open it as a **draft** if it is not yet
   ready for review.

#. **Review**: teammates comment, request changes, or approve. Push additional commits to
   the same branch to address feedback — the PR updates automatically.

#. **Merge** the PR once it is approved and any required checks pass.

#. **Clean up**: delete the merged branch and update your local base branch:

   .. code-block:: bash

      git switch staging
      git pull
      git branch -d feature/add-fsdp-example

Fork vs. Branch
^^^^^^^^^^^^^^^^

There are two collaboration models; choose based on whether you have write access to the
repository:

.. list-table::
   :widths: 22 78
   :header-rows: 1

   * - Model
     - When to use
   * - **Shared repository** (branches)
     - You are a member of the team or organisation with write access. Everyone pushes
       branches to the *same* repository and opens PRs internally. This is the typical
       internal-team setup.
   * - **Fork & pull**
     - You are an outside contributor without write access. You **fork** (create your own
       server-side copy), push branches to your fork, and open a PR from your fork back to
       the original ("upstream") repository. **This is the standard way to contribute to
       open-source projects.**

When you fork, keep your copy current by adding an ``upstream`` remote:

.. code-block:: bash

   git remote add upstream git@github.com:Amii-Engineering/amii-docs.git
   git fetch upstream
   git switch main
   git merge upstream/main      # bring your fork's main up to date

Keeping a Branch Up to Date
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Long-lived branches drift from their base. Integrate regularly to keep each reconciliation
small. For a personal feature branch, rebasing onto the latest base keeps history linear
(see `Merge vs. Rebase`_):

.. code-block:: bash

   git switch feature/my-work
   git fetch origin
   git rebase origin/staging    # linear history; or `git merge origin/staging` to preserve merges

.. tip::

   Integrate from the base branch **frequently** and in **small steps**. Ten small conflicts
   spread across a week are far easier to handle than one enormous conflict at merge time.

Reviewing Code Well
^^^^^^^^^^^^^^^^^^^

Code review is where a team's quality and shared understanding are built. A few habits make
it effective:

* **Keep PRs small and focused.** A reviewer can reason carefully about 200 lines; a
  1,000-line PR gets a rubber-stamp. Small PRs merge faster and hide fewer bugs.
* **Review the *why*, not just the *what*.** The PR description should let a reviewer judge
  whether the approach is sound, not only whether the code compiles.
* **Be specific and kind.** Comment on the code, not the author; prefer questions and
  suggestions over demands.

.. admonition:: Reviewing in the age of generative coding tools
   :class: note

   A growing share of code now originates from AI assistants and agents (Claude, Copilot,
   Gemini, Codex, …), and some teams even have agents perform a first pass of PR review. The
   industry is still working out the norms here, but the centre of gravity of review has
   shifted:

   #. **Did it build the right thing?** Modern agents are very good at producing code that
      runs and is locally correct, so review is less about hunting for subtle syntax bugs and
      more about verifying that what was built actually matches what was *desired* — the
      right feature, the right output, the intended behaviour and edge cases.
   #. **Does the change fit the whole?** Earlier agents lacked a picture of the broader
      codebase and would duplicate logic or bolt on approaches that did not fit; that gap has
      largely closed, but it is still worth confirming the change belongs where it landed and
      is consistent with the surrounding architecture.

.. _git-commit-conventions:

Commit Message Conventions
^^^^^^^^^^^^^^^^^^^^^^^^^^^

A consistent commit-message format makes history readable and machine-parseable. A widely
used standard is `Conventional Commits <https://www.conventionalcommits.org>`_, in which
each message begins with a *type*:

.. code-block:: text

   <type>: <short summary in the imperative mood>

   [optional body explaining the what and why]

Common types:

.. list-table::
   :widths: 22 78
   :header-rows: 1

   * - Type
     - Meaning
   * - ``feat``
     - A new feature or section.
   * - ``fix``
     - A bug fix.
   * - ``docs``, ``refactor``, ``style``, ``test``, ``chore``, ``ci``
     - Documentation, restructuring, formatting, tests, and other supporting changes.

.. note::

   The convention can be **enforced automatically** so it does not rely on everyone
   remembering it. A `commitlint <https://commitlint.js.org>`_ hook (wired in through
   `pre-commit <https://pre-commit.com>`_ or Husky, and run in CI) checks each commit message
   as it is created and rejects anything that does not match the ``<type>: <summary>`` shape.

.. admonition:: Practice — a realistic pull request
   :class: tip

   #. Fork or clone a sandbox repository and create ``feature/add-greeting``.
   #. Add a line to a file, commit it as ``feat: add greeting to readme``, and push.
   #. Open a PR on GitHub, request a review (from yourself or a teammate), then merge it.
   #. Delete the branch and run ``git pull`` on the base branch to retrieve the merge.


Resolving Merge Conflicts
-------------------------

A **conflict** arises when two branches change the *same lines* of a file in different ways.
Git cannot know which version is correct, so it pauses and asks you to decide. Conflicts are
a normal part of collaboration, not an error, and resolving them calmly is an essential
skill.

When a merge or pull stops with a conflict, Git marks the affected region inside the file:

.. code-block:: text

   <<<<<<< HEAD
   learning_rate = 0.001          # your version (current branch)
   =======
   learning_rate = 0.0005         # their version (incoming branch)
   >>>>>>> feature/tune-lr

To resolve it:

#. List the conflicted files with ``git status`` and open each one.
#. Edit the region into the desired final result, deleting the ``<<<<<<<``, ``=======``,
   and ``>>>>>>>`` marker lines entirely.
#. Stage the resolved file and complete the merge:

   .. code-block:: bash

      git add config.py
      git commit              # completes the merge (the message is pre-filled)

To abandon the merge and return to the pre-merge state:

.. code-block:: bash

   git merge --abort

.. important::

   Editor buttons like VS Code's "Accept Current / Incoming / Both" are convenient for
   trivial conflicts, but for complex projects prefer resolving conflicts **by hand**. Real
   conflicts are often too nuanced to settle by picking one side wholesale — the correct
   result is frequently a careful blend of both changes, and only reading the code closely
   will get it right.


Common Problems and How to Fix Them
-----------------------------------

This section collects the situations researchers and collaborators hit most often, working
both alone and in a team.

Working Alone
^^^^^^^^^^^^^

**"Detached HEAD" state.**
   You ran ``git checkout <commit-hash>`` and Git warns about a detached HEAD. You are
   viewing an old commit rather than a branch, and new commits made here can be lost. To
   keep any work, create a branch: ``git switch -c my-branch``. To simply leave,
   ``git switch main``.

**Committed to the wrong branch.**
   Which fix you need depends on the situation:

   *If you intended to start a new feature branch anyway* — you simply committed on ``main``
   before creating it — branch off, then rewind ``main``:

   .. code-block:: bash

      git switch -c feature/intended      # the new branch includes your commit
      git switch main
      git reset --hard origin/main        # rewind main (only if main is not yet pushed!)

   *If the feature branch already exists* and you committed to ``main`` by accident, copy the
   commit over with ``cherry-pick`` and then roll ``main`` back:

   .. code-block:: bash

      git switch feature/existing
      git cherry-pick <the-stray-commit>   # copy it onto the right branch
      git switch main
      git reset --hard origin/main         # remove it from main (only if main is not yet pushed!)

**Accidentally committed a large data file or a secret.**
   Removing it in a *new* commit is not enough — it remains in history. For the most recent
   commit, ``git rm --cached bigfile && git commit --amend``. For deeper history, use
   `git filter-repo <https://github.com/newren/git-filter-repo>`_ or the BFG tool. If a
   **secret** was committed, **rotate it immediately** — assume it is compromised the moment
   it was pushed.

**Your branch and origin/main have diverged.**
   Both you and the remote have new commits. Integrate them with ``git pull`` (which creates
   a merge) or ``git pull --rebase`` (which replays your commits on top for a linear
   history), then push.

**Orphan / disconnected branches.**
   Occasionally someone starts a "clean slate" — often to try a radically different approach
   — with ``git checkout --orphan`` or by importing an unrelated history. This creates a
   **second, disconnected DAG** inside the same repository: a branch that shares no common
   ancestor with ``main`` and can never be cleanly merged back. Avoid this. It makes the
   project's history impossible to follow, breaks CI/CD assumptions, and leaves it unclear
   what is actually delivered to the customer. To explore a very different approach, branch
   off the existing history like anything else (or use a separate repository) — an orphan
   branch is almost never what you want.

.. _git-team-problems:

Working in a Team
^^^^^^^^^^^^^^^^^

**Push rejected (``! [rejected] … fetch first``).**
   Someone pushed before you, so your local branch is behind. Run ``git pull`` (resolving
   any conflicts), then ``git push`` again. **Do not** force-push a shared branch to
   sidestep this.

**The temptation to force-push.**
   ``git push --force`` overwrites the remote branch and can erase teammates' commits. If
   you genuinely must (for example, after rebasing *your own* feature branch), use the safer
   ``git push --force-with-lease``, which refuses to push if someone else has updated the
   branch since your last fetch. Never force-push shared branches such as ``main`` or
   ``staging`` — and in practice most repositories enable **branch protection**, which
   explicitly forbids force-pushing (and often direct pushing) to them at all.

**A merged PR introduced a bug.**
   Use ``git revert`` to add a new commit that undoes the change — safe for shared history —
   rather than rewriting the branch. Many people find this easiest from the **GitHub web
   UI**: open the merged PR and click the **"Revert"** button, which opens a ready-made PR
   that backs the change out.

**Line-ending noise (Windows vs. Linux/macOS).**
   Whole files appear "changed" with no real edits. Normalise line endings with a
   ``.gitattributes`` file containing ``* text=auto``, and set
   ``git config --global core.autocrlf input`` (Linux/macOS) or ``true`` (Windows).


.. _git-large-files:

Large Files and Datasets
------------------------

For AI/ML work this deserves special attention: datasets and model checkpoints are large,
binary, and constantly changing — exactly what plain Git handles worst.

**Why not just commit them?** Git stores the full history of every file forever. Commit a
2 GB checkpoint, change it ten times, and every single clone now carries ~20 GB that can
never be removed without rewriting history. Binaries also cannot be diffed or merged, so
they bloat the repository while providing none of Git's benefits. **Keep data and
checkpoints out of Git** (via :ref:`git-ignore`) and store the payload on appropriate
storage — an object store, a shared filesystem, or the cluster's project space.

**When large files genuinely must be versioned**, use `Git LFS <https://git-lfs.com>`_
(Large File Storage). LFS keeps a tiny text *pointer* in the Git history while the real
bytes live on a separate LFS server, so clones stay small and history stays clean:

.. code-block:: bash

   git lfs install                    # one-time setup per machine
   git lfs track "*.pt" "*.ckpt"      # choose which patterns LFS should handle
   git add .gitattributes             # LFS records the tracked patterns here — commit it
   git add model.pt
   git commit -m "Add trained checkpoint via LFS"
   git push                           # the pointer goes to Git; the bytes go to LFS storage

Cloning and pulling then work as usual — ``git clone`` / ``git pull`` fetch the pointers and
transparently download the real files from LFS. For heavier, pipeline-style data versioning
(datasets tied to experiments), consider a dedicated tool such as
`DVC <https://dvc.org>`_.


Using Git with Generative Coding Tools
--------------------------------------

AI coding assistants and agents (Claude Code, the Gemini CLI, Codex, and others) now write,
edit, and even commit code directly. Git is what keeps that productive rather than
dangerous — it is the safety layer that lets you accept the good and discard the rest.

* **Keep agent work on a branch.** Point assistants at a ``feature/…`` branch, never
  straight at ``main`` or ``staging``. A branch is a sandbox you can throw away.
* **Commit in small, reviewable steps.** Frequent commits let you inspect each change with
  ``git diff`` and cleanly ``git revert`` or ``git reset`` anything the agent got wrong,
  instead of untangling one giant change.
* **Always review the diff before you push.** Treat generated code exactly like a teammate's
  PR: the code is usually locally correct, so focus on whether it *built the right thing* and
  *fits the surrounding codebase* (see `Reviewing Code Well`_).
* **Guard secrets and large files.** Agents can accidentally stage a ``.env`` file or a
  checkpoint; a solid ``.gitignore`` and a quick ``git status`` before committing are your
  guardrails.
* **Let them help with Git itself.** These tools are genuinely useful for drafting commit
  messages, explaining a confusing diff, or talking you through a merge conflict — but you
  remain responsible for what actually gets committed.


Best Practices for Researchers
------------------------------

* **Commit early, commit often, in logical units.** A commit should capture one coherent
  change, not a day's worth of unrelated edits. Atomic commits are easier to review, revert,
  and understand later.
* **Write meaningful messages.** "Fix bug" is useless in six months; state *what* changed
  and *why*. Follow :ref:`git-commit-conventions`.
* **Never commit secrets or data.** API keys, credentials, and datasets do not belong in
  Git. Use ``.gitignore`` and environment variables, and if a secret leaks, rotate it.
* **Branch per task.** Keep production branches always working; do experiments and features
  on branches.
* **Integrate frequently.** Pull before you push, and rebase/merge from the base branch
  often to keep conflicts small.
* **Push work you care about.** A local-only branch is not backed up — see the warning under
  `Why Version Control`_.
* **Version the environment, not just the code.** Commit your ``pyproject.toml`` / lockfile
  / ``requirements.txt`` alongside the code so the exact dependencies behind a result can be
  reproduced.
* **Keep notebooks reviewable.** Notebook outputs and execution counts create large, noisy
  diffs and frequent conflicts. Strip outputs before committing (e.g. with ``nbstripout``)
  or pair each notebook with a plain-text script (e.g. with ``jupytext``).

Tag Releases and Paper Submissions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A **tag** marks a permanent, named point in history — ideal for "the exact code behind this
result." Annotated tags are best because they carry a message and author:

.. code-block:: bash

   git tag -a v1.0 -m "NeurIPS 2026 submission"
   git push origin v1.0        # tags are not pushed by default; push them explicitly

Once pushed, the tag appears on the repository's main page under **Releases/Tags**. From
there — or by turning the tag into a GitHub **Release** — anyone can view that snapshot and
download a ``.zip`` or ``.tar.gz`` of the repository *exactly as it was at that commit*.
This is a wonderful habit for reproducibility: link the release (or its commit hash) in your
paper, and reviewers or future readers can retrieve precisely the code that produced your
numbers.


.. _git-tips:

Tips & Tricks
-------------

A grab-bag of high-leverage habits, especially useful once the fundamentals are second
nature:

* **Make an alias for the graph.** You will use it constantly. A richly formatted version
  colours the hash, relative date, subject, and author, which makes the history far easier
  to scan:

  .. code-block:: bash

     git config --global alias.graph "log --graph --oneline --decorate --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)'"
     # then just:  git graph

* **Stage interactively** with ``git add -p`` to build small, coherent commits.
* **Fix an earlier commit cleanly** with ``git commit --fixup <sha>``. This makes a normal
  commit tagged to say "this belongs to ``<sha>``". Later, running
  ``git rebase -i --autosquash <sha>~1`` reorders the fixup commit next to its target and
  marks it to be squashed in, so the correction folds silently into the original commit with
  no new entry in the history. It is the clean way to amend a commit that is *not* the most
  recent one (where a plain ``git commit --amend`` would do). Set
  ``git config --global rebase.autosquash true`` to make ``--autosquash`` the default.
* **Grab a single commit** from another branch with ``git cherry-pick <sha>``.
* **Find the commit that introduced a bug** with ``git bisect`` — a guided binary search
  across history that pinpoints the culprit in a handful of steps. For a quick hunt, many
  people instead reach for **blame on the GitHub frontend**: open the file, click *Blame*,
  and step back through the revisions of a line to find when a change was introduced.
  ``git bisect`` shines when the bug is not tied to one obvious line and you need to
  binary-search *behaviour* across the history instead.
* **Work on two branches at once** with ``git worktree add ../other-branch other-branch``.
  A worktree checks a second branch out into its own folder that shares the same repository,
  so you can have two branches "live" on disk at the same time — no stashing or committing
  half-done work to switch. It is handy when, say, a build or long training run is going on
  one branch and you want to start a fix on another without disturbing it, or to compare two
  branches side by side in two editor windows.
* **Always** prefer ``git push --force-with-lease`` over ``git push --force``.
* **Let Git remember conflict resolutions** with ``git config --global rerere.enabled true``
  so you do not re-solve the same conflict twice during a long rebase.
* **Use GitLens** (or your editor's Git integration) to see per-line commit history on hover
  — see :ref:`git-visualizing-history`.
* **Do not commit large files** — use Git LFS instead (see :ref:`git-large-files`).


.. _git-exercises:

Hands-On Exercises
------------------

Work through these in order; together they exercise the full workflow.

.. admonition:: Lab 1 — Local fundamentals
   :class: note

   Initialise a repository, make three commits to a ``README.md``, then use
   ``git log --oneline``, ``git diff``, and ``git show`` to inspect them. Finally, use
   ``git restore`` to discard an uncommitted edit.

.. admonition:: Lab 2 — Branching, merging, and rebasing
   :class: note

   From ``main``, create ``feature/x`` and ``feature/y``. Add a different line to the *same*
   file on each branch. Merge ``feature/x``, then merge ``feature/y`` and **resolve the
   resulting conflict** by hand. Bonus: on a fresh branch, add two commits, then
   ``git rebase -i`` onto ``main`` and squash them into one — compare the graph before and
   after with ``git graph``.

.. admonition:: Lab 3 — Undo and recover
   :class: note

   Make a commit, then ``git reset --hard HEAD~1`` to "lose" it. Recover it using
   ``git reflog`` and ``git switch -c recovered <hash>``. Separately, practise
   ``git revert`` on a pushed commit.

.. admonition:: Lab 4 — End-to-end collaboration
   :class: note

   Push a repository to GitHub, open a pull request from a feature branch, request a review,
   merge it, delete the branch, and sync your local base branch. Then open the repo's
   ``/network`` graph and find your merge. Bonus: fork a public repository, add an
   ``upstream`` remote, and keep your fork up to date.


Quick Reference
---------------

.. list-table::
   :widths: 40 60
   :header-rows: 1

   * - Command
     - Purpose
   * - ``git status``
     - Show changed and staged files, and the current branch.
   * - ``git add <file>`` / ``git add -p``
     - Stage a file / stage changes interactively, hunk by hunk.
   * - ``git add -u`` / ``git add -A``
     - Stage tracked changes only / stage everything including new files.
   * - ``git commit -m "msg"``
     - Record the staged snapshot.
   * - ``git log --oneline --graph``
     - View compact, visual history.
   * - ``git switch -c <branch>``
     - Create and switch to a new branch.
   * - ``git merge --no-ff <branch>``
     - Merge a branch, always creating a merge commit.
   * - ``git rebase -i origin/staging``
     - Replay your commits on top of the base branch for a linear history.
   * - ``git fetch`` / ``git pull`` / ``git push``
     - Download / download+integrate / upload commits.
   * - ``git push --force-with-lease``
     - Safely push a rebased branch (aborts if others pushed).
   * - ``git restore <file>``
     - Discard unstaged changes to a file.
   * - ``git reset --soft HEAD~1``
     - Undo the last commit, keeping the changes staged.
   * - ``git revert <commit>``
     - Safely undo a pushed commit with a new commit.
   * - ``git cherry-pick <commit>``
     - Copy a single commit onto the current branch.
   * - ``git stash`` / ``git stash pop``
     - Park / restore uncommitted changes.
   * - ``git reflog``
     - Recover "lost" commits and branch tips.
   * - ``git tag -a <name> -m "…"``
     - Mark a reproducible point in history.


Further Resources
-----------------

* `Pro Git (free book) <https://git-scm.com/book>`_ — the canonical, thorough reference.
* `A successful Git branching model <https://nvie.com/posts/a-successful-git-branching-model/>`_
  — the git-flow ``main`` / ``staging`` / feature model referenced above.
* `GitHub Docs <https://docs.github.com>`_ — pull requests, forks, and platform features.
* `GitHub Skills <https://skills.github.com/>`_ — interactive, hands-on learning modules.
* `Conventional Commits <https://www.conventionalcommits.org>`_ — the commit-message spec.
* `Oh Sh*t, Git!?! <https://ohshitgit.com>`_ — concise recipes for getting out of trouble.
* `Learn Git Branching <https://learngitbranching.js.org>`_ — an interactive, visual
  tutorial for branching and merging.
