The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Architecture and operations
Git commits and branches
What is a commit, and what does a branch point to?
In Git, a commit stores the whole state of the files at one moment, and it points to the commit right before it. A branch is not a separate copy of commits. It is a label that points to one commit. When you commit on a branch, only that label moves to the new commit, and the other branches stay as they are. When you move to another branch with git switch, the files in the folder change to the state of the commit that branch points to. This lab shows the result of running the commands in the code box one line at a time in a new empty folder with real Git. You remove the second commit line and the line that goes back to main, one at a time, and you see how the commit each label points to and the memo.txt in the folder change. A commit number is a long string that is different on every run, so I wrote them as commit 1 and commit 2 in the order they were made. In the code box, a line that starts with # is a comment, echo is a command that writes text to a file, and cat is a command that shows the contents of a file.
Checked against real execution · Ran four combinations line by line in new empty folders with git 2.55.0 and checked the state against the model
Concepts in this lab
- Git commit
- A Git commit is one save that you leave in the repository. Git does not store the differences of the changed parts. It stores the state of the files at that moment (a snapshot), and the commit points to that state. A commit also points to the commit right before it (its parent). The very first commit has no parent, and a normal commit has one parent. So commits are linked by lines that point to the previous commit (Pro Git section 3.1). git commit makes a new commit from what you staged (with git add), and that commit becomes a child of the commit the branch you are on pointed to, and the branch is updated to point to the new commit (git-commit documentation). A change that you only edited and did not commit is in no commit.
- Git branch
- A Git branch is a lightweight movable pointer (a label) to one commit. It is not a copy of commits. A branch is a simple file that holds the 40-character SHA-1 checksum of the commit it points to, so it costs little to create and delete (Pro Git section 3.1). When you commit on a branch, only the label of that branch moves to the new commit, and other branches that pointed to the same commit stay as they are. If you only create a branch and do not commit, the commits do not increase.
- git switch
- git switch is a command that changes the branch you are on to another branch (since Git 2.23). Written like git switch main, it moves you to main, and the files in the folder change to the state of the commit that branch points to (Pro Git section 3.1). git switch -c feature creates a new branch named feature and then moves to it (the -c option in the git-switch documentation). You can move even when the folder is not clean (when there are uncommitted changes), but it aborts if moving would make you lose those changes (DESCRIPTION in the git-switch documentation). On git 2.55.0 on the PC used to write this lab, when the file was the same in the two branches you move between, the edited content came along unchanged, and when the file was different in the two branches, the move was blocked.
- HEAD (Git)
- HEAD is a special pointer with which Git remembers which branch you are on, and it usually points to the branch you are on, though it can also point to a commit directly (Pro Git section 3.1). So git commit moves the label of the branch HEAD points to onto the new commit, and git switch changes HEAD to another branch. On git 2.55.0 on the PC used to write this lab, the .git/HEAD file of a new repository held the name of the branch you are on in the form ref: refs/heads/main.
Try these
- Default settings — When you commit on feature, only the feature label moves to the new commit, and when you return to main, the folder changes to the state of the commit main points to (v1).
- Not going back to main — You stay on feature, so memo.txt in the folder is the state of the second commit (v2). main stays at commit 1.
- Moving to main without a second commit — The edited memo.txt comes along to main without being in any commit. Both branches point to the same commit 1.
- Removing both — There is only 1 commit, and both branches point to the same commit 1. The edited v2 is only in the folder.
Running with the default settings
- git init -b main: this makes the folder a Git repository. A folder named .git is created inside the folder, and commits will pile up there from now on. -b main means to name the first branch main, and you will see what a branch is later. There is not a single commit yet, so there is no commit for the name main to point to.
- echo v1 > memo.txt: this creates the file memo.txt in the folder and writes v1 in it. This line is not a Git command. It is a command that writes a file in the folder. Git only reports this file as a new file (??) that has not yet been put in the repository.
- git add memo.txt: this stages the current state of memo.txt (v1) to go into the next commit. It is not a commit yet. Git reports that this file is staged for the commit (A).
- git commit -m "First commit": this saves what you staged as a commit. Commit 1 was made, and it holds the state of memo.txt at this moment (v1). It is the very first commit, with no commit before it. A label that points to one commit like this is called a branch, and the main branch now points to commit 1. The file in the folder and the state in the commit are the same, so there is no unsaved change.
- git switch -c feature: this creates a new label named feature that points to commit 1, where you are now, and moves the branch you are on to feature. You made a branch, but the commits did not increase and there is still 1. A branch is not a copy of commits. It only points to one commit, so main and feature both point to the same commit 1.
- echo v2 > memo.txt: this changes the inside of memo.txt in the folder to v2. Only the file in the folder changed, and memo.txt inside commit 1 is still v1. Git reports it as a change that was edited but not committed (M). This v2 is not in any commit yet.
- git commit -am "Second commit": this stages the changed memo.txt (-a) and saves it as commit 2. Commit 2 points to the commit right before it, commit 1, and it holds the state of memo.txt at this moment (v2). The label of the branch you are on, feature, moved to commit 2. The main label did not move, so it still points to commit 1, and memo.txt inside commit 1 is still v1.
- git switch main: this moves the branch you are on to main and changes the files in the folder to the state of commit 1, which main points to. So memo.txt in the folder became v1. Commit 2 did not disappear. The feature label still points to it.
- cat memo.txt: this shows memo.txt in the folder on the screen. It is v1. You edited it to v2 on feature and committed it, but that state is inside commit 2, and the folder now shows the state of commit 1, which main points to.
Result of this run
- A commit stores the whole state of the files at one moment, and it points to the commit right before it. A branch (main, feature) is not a copy of commits but a label that points to one commit. When you commit on feature, only the feature label moves to commit 2 and main stays at commit 1, so when you return to main, memo.txt is v1.
- git switch main only moved the label you are on and changed the files in the folder to the state of the commit main points to. Commit 2 was not deleted. The feature label points to it.
Code
# Make this folder a Git repository (the first branch is named main)
git init -b main
# Create the file memo.txt and write v1 in it
echo v1 > memo.txt
# Stage memo.txt to go into the next commit
git add memo.txt
# Commit (save) what you staged
git commit -m "First commit"
# Create a new branch feature and switch to it
git switch -c feature
# Change the contents of memo.txt to v2
echo v2 > memo.txt
# Stage the changed file (-a) and commit it right away
git commit -am "Second commit"
# Switch to the main branch
git switch main
# Show the contents of memo.txt on the screen
cat memo.txt
Assumptions of this model
I checked against real runs of git 2.55.0 (Windows). For all four combinations, in a new empty folder for each combination, I ran the code on the screen (the # comment lines were not run) one line at a time exactly as it is. The git commands were given to git.exe with their arguments as they are (the user name and email were given with -c only to that command, and the global settings were not read), and echo and cat were run by Git Bash. Right after each line ran, I measured the current branch, the commits that the main and feature branches point to, the number of commits made, memo.txt in the folder, git status --short, and memo.txt inside the commit of each branch, and compared them with the state of the step on the screen, and I also checked for each combination that the code on the screen is the same text as what was run. A commit number (hash) differs on every run, so I wrote them as commit 1 and commit 2 in the order they were made. I used git init -b main to make the first branch main. In the combination of this lab without the second commit line, main and feature point to the same commit 1, so the uncommitted change was not blocked and came along to main. This is because memo.txt is the same in the two branches. In a separate check outside the combinations on the screen, when memo.txt differed between the two branches (when, with main at v1 and feature at v2, you edit memo.txt to v3 on main and try to move to feature), Git blocked the move and gave the error "Your local changes to the following files would be overwritten by checkout". I did not put this blocking in the combinations on the screen, and it is behavior seen with this Git version. I confirmed that the .git/HEAD file of a new repository holds the name of the branch you are on (in the form "ref: refs/heads/main"), and that a branch file (such as .git/refs/heads/main) is a 41-byte file holding only one commit number. What I checked against documentation is Pro Git 2nd edition section 3.1 Branches in a Nutshell (a commit points to a snapshot and to its parent, a branch is a lightweight movable pointer to a commit and a simple file holding 40 characters of SHA-1, HEAD is a pointer to the branch you are on, and when you change branches the files in the working directory change, and if it cannot be done cleanly you cannot switch), the git-switch documentation (you can switch even with an unclean working directory, and it aborts if you would lose changes; -c creates a new branch and switches to it), the git-commit documentation (-a automatically stages changes to files it already knows about, -m is the message, a new commit is a child of HEAD, and the branch is updated to point to that commit), and the git-init documentation (-b is the name of the first branch). The explanation that one commit is "the whole state of the files at one moment" is an easy rewording of the snapshot description in the documentation, and I directly checked the contents of memo.txt in commit 1 and commit 2 with git show (the repository has only one file, so the whole state and the one file are the same). Merging (merge, rebase), remote repositories, conflicts, and in what form files are stored inside a commit (objects) were not covered. The in-depth version of the same topic is covered in the in-depth lab "Merge and rebase".
Previous lab: Deployment