Basics: repository, commits and branches

Basics: repository, commits and branches #

Repository #

A git repository is a folder that stores a project. It is identical to a regular folder, except for a folder named .git. This folder stores (among others) the whole history of the project.

Commit #

A commit is a snapshot of a repository.

Each commit has medatada associated to it. In particular:

  • an ID,
  • the name of the commit’s author,
  • a timestamp,
  • a message that describes the commit.

For instance, Alice may create a fresh git repository, add some files to it, and commit these files. After this step, Alice’s repository may be pictured as follows.

Next, Alice may modify some of these files (and/or add new ones), and commit these changes:

Notation. In the figures below, for readability, we will omit the commits’ metadata. So the above repository will be represented as:

Main branch #

By default, Alice’s commits belong the main branch of her repository (often called main or master). You can think of a branch as a timeline.

Git keeps tack of the latest commit on this branch with a so-called “pointer” (you can think of it as a variable) to that commit:

Time travel #

Alice can load any previous snapshot of her repository. The history of the repository will not be affected.

To achieve this, Git uses another “pointer” called HEAD, which intuitively keeps track of the current position of Alice in the commit’s history. By default, HEAD points to the main pointer:

If Alice decides to load her previous snapshot (thanks to the git checkout command), then HEAD will instead point to the corresponding commit:

In this case (i.e. when it does not point to a branch pointer, but directly to a commit), the HEAD pointer is said to be in a detached state.

Branching time #

Alice may want to work on an experimental feature of her project. She can develop this feature in an “alternative timeline”, a.k.a. another branch.

Before creating this new branch, Alice most likely wants to travel back to her latest commit. This will move the HEAD pointer back to where it was:

Now Alice may create a new branch, and name it myFeature. This will create a new branch pointer:

Alice can also specify that she want to work on this new branch (using the git checkout command still). This will affect the HEAD pointer:

Next, Alice may implement (part of) her new feature, and commit these changes. This will move the myfeature pointer forward:

Now Alice may need to fix an urgent bug on her main branch. If she switches to the main branch (again, with git checkout), then HEAD will point to it:

Alice may fix the bug and commit her changes. This will create a commit and move the main pointer as expected. The two branches now diverge:

Then Alice may switch back to the myFeature branch to resume her work:

And add a commit to this branch:

History #

The history of a branch consists of all commits on a path from the branch’s pointer to the original commit. For instance, this is the history of the myFeature branch:

And this is the history of main branch:

Merging branches #

We left Alice’s repository in this situation:

Alice is now satisfied with her new feature, and she wants to incorporate it into her main branch.

To do so, she first switches to the receiving branch (in this case, the main branch):

Then she can merge the branch myFeature into the branch main. Because the two branches have diverged, this will generate a new commit, called a merge commit:

Before she can create a merge commit, Alice may need to fix potential conflicts between the diverging branches.

In this happens, then git will provide Alice a list of files that contain so-called “merge conflicts”. In each of theses files, each conflict will be identified as follows:

<<<<<<< HEAD

  <Conflicting content from the receiving branch>

=======

  <Conflicting content from the merged branch>

>>>>>>> <mergedBranchName>

After a merge #

Observe that a merge commit (usually) has two parent commits. More generally:

  • a repository with merge commits is not a tree.
  • in such a repository, the history of a “branch” may consists of commits from different paths. For instance, here is the history of the main branch:

Note that after this merge, all commits of the myFeature branch are part of the history of the branch main.

Alice may keep the myFeature branch for some future usage, or she may decide to delete it. In the latter case, the myFeature pointer will simply be deleted:

Fast-forward merge #

If two branches have not diverged, then there is a simpler way to merge them.

For instance, consider Carol’s repository:

The branches main and experimental have not diverged. So experimental can be merged into main by simply moving the main pointer:

In this case, no merge commit is needed. This is called a fast-forward merge.