8. Advanced git concepts#

Stashing changes#

Before you can git pull, you need to have committed any changes you have made. If you find you want to pull, but you’re not ready to commit, you have to temporarily “put aside” your uncommitted changes. For this, you can use the git stash command, like in the following example:

import os

try:
    from google.colab import drive  # type: ignore

    drive.mount("/content/drive")
    drive_dir = "/content/drive/MyDrive"
except ImportError:
    print("Not running on colab")
    drive_dir = os.path.join(os.getcwd(), "drive", "MyDrive")
    os.makedirs(drive_dir, exist_ok=True)

print(f"Drive dir: {drive_dir}")

git_dir = os.path.join(drive_dir, "learning_git")
working_dir = os.path.join(git_dir, "git_example")

if os.path.exists(working_dir):
    print(f"Git example directory: {working_dir}")
    os.chdir(working_dir)
else:
    print("Start from the beginning")
Not running on colab
Drive dir: /mnt/nvme1n1p2/home/yj.lee/workspace/projects/lecture/book/lectures/softeng/vcs/drive/MyDrive
Git example directory: /mnt/nvme1n1p2/home/yj.lee/workspace/projects/lecture/book/lectures/softeng/vcs/drive/MyDrive/learning_git/git_example

Remind ourselves which branch we are using:

%%bash
git branch -vv
* main 4fb2ed8 [origin/main] Merge branch 'experiment'
%%writefile Wales.md
Mountains In Wales
==================

* Pen y Fan
* Tryfan
* Snowdon
* Glyder Fawr
* Fan y Big
* Cadair Idris
* Penygader
Overwriting Wales.md
%%bash
git stash
Saved working directory and index state WIP on main: 4fb2ed8 Merge branch 'experiment'
%%bash
git pull
Already up to date.

By stashing your work first, your repository becomes clean, allowing you to pull. To restore your changes, use git stash apply.

%%bash
git stash apply
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   Wales.md

no changes added to commit (use "git add" and/or "git commit -a")

The “Stash” is a way of temporarily saving your working area, and can help out in a pinch.

Tagging#

Tags are easy to read labels for revisions, and can be used anywhere we would name a commit.

Produce real results only with tagged revisions.

NB: we delete previous tags with the same name remotely and locally first, to avoid duplicates.

git tag -a v1.0 -m "Release 1.0"
git push --tags

You can also use tag names in the place of commmit hashes, such as to list the history between particular commits:

git log v1.0.. --graph --oneline

If … is used without a following commit name, HEAD is assumed.

Ignoring files#

We often end up with files that are generated by our program. It is bad practice to keep these in Git; just keep the sources.

Examples include .o and .x files for compiled languages, .pyc files in Python.

In our example, we might want to make our .md files into a PDF with rinohtype:

%pip install -U rinohtype
%%writefile Makefile

MDS=$(wildcard *.md)
PDFS=$(MDS:.md=.pdf)

default: $(PDFS)

%.pdf: %.md
	rinoh $< 2> /dev/null
	rm $(basename $@).rtc $(basename $@).stylelog
Writing Makefile
%%bash
make
rinoh Scotland.md 2> /dev/null
Using the CommonMark frontend [built-in]
rinohtype 0.5.4 (2022-06-17)  Copyright (c) Brecht Machiels and contributors
This program comes with ABSOLUTELY NO WARRANTY. Its use is subject
to the terms of the GNU Affero General Public License version 3.
100% [========================================] ETA 00:00 (00:00) page 3
Not yet converged, rendering again...
100% [========================================] ETA 00:00 (00:00) page 3
Writing output: Scotland.pdf
rm Scotland.rtc Scotland.stylelog
rinoh lakeland.md 2> /dev/null
Using the CommonMark frontend [built-in]
rinohtype 0.5.4 (2022-06-17)  Copyright (c) Brecht Machiels and contributors
This program comes with ABSOLUTELY NO WARRANTY. Its use is subject
to the terms of the GNU Affero General Public License version 3.
100% [========================================] ETA 00:00 (00:00) page 3
Not yet converged, rendering again...
100% [========================================] ETA 00:00 (00:00) page 3
Writing output: lakeland.pdf
rm lakeland.rtc lakeland.stylelog
rinoh test.md 2> /dev/null
Using the CommonMark frontend [built-in]
rinohtype 0.5.4 (2022-06-17)  Copyright (c) Brecht Machiels and contributors
This program comes with ABSOLUTELY NO WARRANTY. Its use is subject
to the terms of the GNU Affero General Public License version 3.
100% [========================================] ETA 00:00 (00:00) page 3
Not yet converged, rendering again...
100% [========================================] ETA 00:00 (00:00) page 3
Writing output: test.pdf
rm test.rtc test.stylelog
rinoh Wales.md 2> /dev/null
Using the CommonMark frontend [built-in]
rinohtype 0.5.4 (2022-06-17)  Copyright (c) Brecht Machiels and contributors
This program comes with ABSOLUTELY NO WARRANTY. Its use is subject
to the terms of the GNU Affero General Public License version 3.
100% [========================================] ETA 00:00 (00:00) page 3
Not yet converged, rendering again...
100% [========================================] ETA 00:00 (00:00) page 3
Writing output: Wales.pdf
rm Wales.rtc Wales.stylelog

We now have a bunch of output .pdf files corresponding to each Markdown file.

But we don’t want those to show up in git:

%%bash
git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   Wales.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	Makefile
	Scotland.pdf
	Wales.pdf
	lakeland.pdf
	test.pdf

no changes added to commit (use "git add" and/or "git commit -a")

Use .gitignore files to tell Git not to pay attention to files with certain paths:

%%writefile .gitignore
*.pdf
Writing .gitignore
%%bash
git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   Wales.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.gitignore
	Makefile

no changes added to commit (use "git add" and/or "git commit -a")
%%bash
git add Makefile
git add .gitignore
git commit -am "Add a makefile and ignore generated files"
git push
[main 7ffd228] Add a makefile and ignore generated files
 3 files changed, 11 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Makefile
To https://github.com/chu-aie/github-example.git
   4fb2ed8..7ffd228  main -> main

Cleaning your directory#

Sometimes you end up creating various files that you do not want to include in version control. An easy way of deleting them (if that is what you want) is the git clean command, which will remove the files that git is not tracking.

%%bash
git clean -fX
Removing Scotland.pdf
Removing Wales.pdf
Removing lakeland.pdf
Removing test.pdf
%%bash
ls
lakeland.md
Makefile
Scotland.md
test.md
Wales.md
  • With -f: don’t prompt

  • with -d: remove directories

  • with -x: Also remove .gitignored files

  • with -X: Only remove .gitignored files

Hunks#

Git hunks#

A “hunk” is one git change. This changeset has three hunks:

+import matplotlib
+import numpy as np

 from matplotlib import pylab
 from matplotlib.backends.backend_pdf import PdfPages

+def increment_or_add(key,hash,weight=1):
+       if key not in hash:
+               hash[key]=0
+       hash[key]+=weight
+
 data_path=os.path.join(os.path.dirname(
                        os.path.abspath(__file__)),
-regenerate=False
+regenerate=True

Interactive add#

git add and git reset can be used to stage/unstage a whole file, but you can use interactive mode to stage by hunk, choosing yes or no for each hunk.

git add -p myfile.py
+import matplotlib
+import numpy as np
#Stage this hunk [y,n,a,d,/,j,J,g,e,?]?