Will King (will@youainti.com)
| Operating System | Windows | MacOS | Linux |
|---|---|---|---|
| Git Source | git for windows | homebrew or Xcode | sudo apt/yum/pkg/zypper install git |
| Text Editor | Nano (comes with git for windows) | Nano (already installed) | Nano/Vim/Emacs |
Why are we using the command line?
Pay attention to spelling, spaces, and capitalization.
pwd: Print Working Directory.ls: LiSt.cd: Change Directory.nano: The text editor we will be using.Prep: Download and extract the zip file I’ve provided.
How is this similar to using your file browser?
When you run into issues on the command line, you need to know how to get help:
Let’s start a new repository.
git init .The git init command turned the current directory
(.) into a git repository.
If you were to type git init repo_name it would create a
new directory named repo_name/ and then turn it into a git
repo.
Now let’s set some defaults that will make your life easier:
Ok, time to make some changes
Now let’s see what git has to say:
Now let’s stage these files (mark that we want to track these files)
Now let’s see what git has to say:
Now let’s take a snapshot. In git this is called a commit.
Let’s make personal wiki’s to keep track of what we are doing.
git init wikinano basic_git_workflow.txtLets take a look at what you just did.
Please take notes in your wiki; saving, staging, and committing as you go. Put each topic into it’s own file; we will use them later.
If you run into issues, please let me know and I’ll pause to help.
I owe this approach to explaining git to Tom Preston-Werner’s Git Parable
When working with a project, you might want to know the history of that project. A simple way to do this is to make copies or snapshots at regular or important points.
Imagine I am writing a novel. I start by writing “It was a dark and stormy knight.” In order to avoid loosing my work, I save my working version, and then make a copy to google drive called
novel-(date1).docx. Later I decide to fix my error and change it to “It was a dark and stormy night.” I then save my working version and make a second copynovel-(date2).docx.
This is the basic way git works. Each time a file is committed, a
copy is saved to a hidden directory in the .git folder.
Staging the file marks that you want to save the current version of the
file.
There is a computer science tool called a hash function. It allows us to give each version of a file a (nearly) unique name that depends on the file’s contents.
bash_exploration/ directory.sha1sum c.txtcf44e4a24958c62790979deaad545d23c8fbe98e?c.txt by removing the ‘(txt)’ part of the first
line.This is called a Content Addressible System, because we can address each unique version of our content.
Imagine if I had 100 different files, and I was taking full snapshots each time I changed one or two of them. I would end up with a lot of copies of the same content.
If I wanted to save on storage space, I could instead copy just the versions that I changed.
This is what Git does. Each snapshot contains just a list of the staged files, i.e. the files that we have said have changed in an important way.
This is done by saving the staged objects with SHA1SUM names, and then writing a small file that contains the filenames and SHA1SUM names of the change. This is what committing a change does.
Now notice one problem with Incremental Snapshots. If I miss a snapshot somewhere, I am left without those changes. So, I need a way to know which previous snapshots are required to get to a specific state.
The solution is to allow commits to include information on ‘parent’ commits. Now we can draw a graph of how we get to a specific state.
git log --graph describes the commit dag.
Imagine the following commit DAG
How can we know what state we should have?
In CS, a pointer is something that records an address to something else.
Three common types of pointers: Branches, Tags, and HEAD
A branch is a flexible marker that simplifies isolating work from different parts of the codebase. It is used to track areas of work. For example, if I:
When you create a commit, a branch will change to point to the new commit.
Branches are cheap, use them.
A tag points to a specific commit. They are useful for:
Points to the commit that your current working copy is based on.
You may see a detached HEAD error. No the revolution hasn’t started yet, you just got your HEAD pointing to a specific commit instead of a branch pointer.
Take a look at commit 10. Notice how it has to handle
the cases where
Commit 5 has removed b.txt but
commit 9 hasn’tCommit 5 and Commit 4 have conflicting
edits of e.txtNotice how it has to handle the cases where
Commit 5has removedb.txtbutcommit 9hasn’tCommit 5andCommit 4have conflicting edits ofe.txt
This is called a conflict, where the same file has had different changes happen in different branches.
To handle this, Git asks you to resolve it, choosing what should be kept or removed. Resolving a conflict is called a merge.
Merging is the main skill we want to develop.
Because the DAG is just a bunch of records pointing to other records, you can rewrite it, BUT if you rewrite a copy of the DAG and your coworkers don’t, then you’re in for a world of hurt.
This is what advanced GIT consists of: rewriting the commit DAG so that it clearly, cleanly, and consisely represents how the codebase grew.
We will only take the briefest of looks at one way to do that.
Remember!
You can inspect what has been going on using:
If you ever forget what options are available for git:
For example
To create a new branch, either of the following work:
In our wiki repo, let’s create a branch named spellcheck
git checkout -b spellcheck
Now check which branch we are on
There are two ways to change between branches
git switch: only used to change branchesgit checkout: does so much moreOk, let’s do the following:
git switch spellcheck
echo "hello world" > n.txt
git switch main
git merge spellcheck
git log --graph --onelineThis is called a fast-forward merge.
Conflicts are not bad, they are inconvenient and necessary.
A conflict occurs when two commits have different versions of the same file(s).
Let’s create a conflict in our wiki repo.
git checkout spellcheck
nemo favorites.txt #Talk about your favorite color
git add favorites.txt
git commit -m "write an actual description"
git switch main
nemo favorites.txt #Talk about your favorite food"
git add favorites.txt
git commit -m "write an actual description"
git log --graph --oneline
git merge spellcheckIn this case we have a warning message: TODO
We have two conflicting changes to the favorites.txt
file.
We need to choose between them.
Note the symbols “<<<<<” “======” “>>>>>”. These tell us what the differences are between the commits
To resolve the commit:
git merge spellcheck)Usually, you have branches that represent “states” and branches that represent areas you are working on.
Consider the following branches
main: This is the branch that you are using to present
work that you consider somewhat complete, i.e. when you have a first
draft of your data processing code, or the output data.data_processing: This is where you write your data
processing code, e.g. a web scraper and data munging tools. It might
include a copy of a .csv file or .rdata file
that you will use in the analysis later.regression_analysis: This is where you develop the
analysis that you will apply to the data you have. This will require
pulling the most recent data to analyze.What this might look like.
data_processing branchdata_processing to a working state
(committing along the way).data_processing into main.regression_analysis
from main.data_processingdata_analysis and merge the
fixes from main.report and begin
writing your report.To someone looking at your main branch, they would see.
Sometimes when you have a bunch of small rough changes, you might want to turn them into a single (nice looking) commit.
This is called squashing
For example, if we had 3 commits in spellcheck, we could squash merge them into main by:
This is one way to rewrite the DAG. It depends on the fact that branches are disposable. There is no need to keep a branch around after it is squashed.
We have focused on the Version Control System portions of Git. Now it is time to look at how to use it as a “distributed” VCS and how to collaborate together.
Git originated as a tool to develop the Linux Kernel. It is now the most popular VCS in the world. This is - in part - because people can work on the same thing without getting in each other’s way.
Because Git is flexible, it supports many different workflows. When you work with an established team, learn their workflow.
A remote is somewhere that git will fetch commits from. A repository can have more than one remote.
A local remote is a remote that is on the same computer, e.g. a separate HDD or USB drive.
How would we add a remote?
git remote --helpgit remote add usb_drive /path/to/usb/drive/repogit clone --helpgit clone /path/to/onedrive/folder/with/repo# Begin by fetching chagnes
git fetch remote/branch
# Merge changes from remote branch into your current branch
git merge remote/branch
# Work like normal
# Give your updates back
git push remote/branchThere is a command that combines the fetch and merge steps:
When people think of git, they usually think of github.
Git is to Github as video is to YouTube. Quoted in Hari-up
A Git Forge provides
I have a git forge that we are going to practice using
We are going to - add it as a remote - continue the experiment using the remote.
[!NOTE] I will be removing your access to this git remote sometime soon. You will still have a local copy of the wiki though!
.gitignore file tell git to not to stage them.
For example, if you are doing an analysis in python, you might get a
__pycache__/ directory. If you put a line that says
*/__pycache__/ in your .gitignore, it will not
be suggested that you stage anything in that directory..pdf, .jpg,
.png,.xlsx, .docx, or
.zip files. Any change will cause the whole thing to be
resaved, and this can quickly add up to lots of storage being used. Git
LFS does a couple of things to reduce how much storage will be
used.