Overview

traditional save patterns without git

The traditional save pattern is to store a file in-place or to a new file.

Saving in place is destructive: it does not preserve the old version.

Saving to a new file is safe, but leads to another challenge: managing multiple versions, their names, the storage and the retrieval, which is cumbersome and error-prone.

software source code specifics

Software source code usually consists of a tree of files that, together, make a whole unit. Compiling the software usually requires to have the whole tree available as-is on the filesystem.

As such, an actionable snapshot is one that comes as a whole.

The naive way to store snapshots is to store the whole file tree for any given file change. This method has a heavy and inefficient storage footprint, due to spreading identical files across snapshots.

what we want from a versioning software

We want an approach that is more efficient in terms of storage: the snapshots should not be stored as-is, but instead be reconstructed on-demand, with files being fetched from a central storage. The software should keep track of the files that a given snapshot relies on.

We want to conveniently browse and load old snapshots or snapshots from alternative development branches.

We also want snapshots to come with metadata: their date, their unique ID, and the description of changes they bring (commit message).

lightweight snapshot: a declarative list of files

We have the snapshot as a declarative list of files it is comprised of, like a shopping list.

In this pattern, the files may live anywhere. This allows files to be referred to by any snapshot, and to be stored only once.

The software may reconstruct the snapshot based on the list: the software fetches the files from the storage and builds a snapshot mirror.

editing area

The local directory where we make changes is called the working directory or working tree.

Most of the time, it should start as a mirror of the latest snapshot, allowing to work on changes and submit a new snapshot for commit.

Sometimes, it will instead mirror an old snapshot, so that we may run the snapshot as it was, or to fork away from that snapshot onwards.

other terminology

to commit means to intentionally save a snapshot. By extension, we may refer to a snapshot itself as a commit. In git internals, a commit is the file that stores all the file dependencies and the metadata related to a given snapshot.

The staging area, also called the index, is a tentative snapshot, on that is ready to be committed. See Editing and staging.

Editing and staging

separation of concern: working directory and staging area

We keep the working directory and the staging area (index) separate to acknowledge their different role.

the role of the working directory

The working directory is a playground where changes are not saved, good for exploration and iteration. Its content has no influence on what is saved when we perform a commit.

terminology

This allows us to have files that we don't commit yet or ever: untracked files. If the file is a modified version of one that was committed before, and that it is not staged yet, we call it modified.

When files are added to the staging area, they cease to be untracked or modified: they become staged.

staging area: granularity and storage

The staging area determines what is saved on commit. Having some granularity instead of saving the whole working tree has benefits:

  • We may select related changes and commit them as a group.
  • We may technically save different portions of a file at a time.

Besides, we may use the index as a temporary storage, in a manner similar of saving checkpoints: This allows to store one version and keep iterating on it, with the ability to revert to the checkpoint if needed.

This is possible because the index is similar to a commit: it has its own list of files, which live in the storage area, and that are decoupled from the working tree.

Common commands

the standard workflow is as follows:

  • make some changes to one or more files in the working directory
  • select one or more changes, put them in the staging area
  • save the staging area as a commit.

standard flow

stage files: working-directory to index

git add adds one or more files to the staging area. It affects both untracked files and modified files, turning them into staged files.

git add .
git add -u

We may exclude untracked files from staging with -u. A more permanent solution is to add them to .gitignore.

commit: index to commit

Save the staging area as a new commit. It has no effect over the working directory:

git commit -m "..."

We may edit the last commit message with --amend. This does not create a new commit.

git commit --amend -m "an updated commit message"

Note: the -am flag is not a shortcut for both git add . and git commit -m '', since it does not add untracked files.

git commit -am "adding and committing modified files"

reverting to the last commit

The last commit is commonly represented by HEAD.

commit to working-directory: reset the working-directory.

we use restore and set the --source to HEAD.

git restore --source=HEAD --worktree  .
git restore --source=HEAD .

old syntax

git checkout HEAD   -- . 				# the last commit
git checkout v1.2.3 -- .      # tag v1.2.3
git checkout stable -- file   # stable branch
git checkout origin/master -- file  # upstream master
git checkout HEAD^ -- file          # the version before the last commit

commit to working-directory AND index.

We aim to revert both the index and the working directory to the commit version.

It does not affect or remove untracked files, since they are ignored by git.

git restore --source=HEAD --staged --worktree .

git reset --hard

commit to index: reset the staging area

We may revert the staging area to the clean state of the previous commit. The working tree is unaffected.

We use the restore command, with the staged flag.

git restore --staged . # tested
git reset .

If we are before the first commit, we may remove content from the staging area:

git rm --cached file
git reset

reverting to the index version

index to working-directory: go back to the index checkpoint

We may restore the worktree from the index. This may restore a checkpoint or the last commit, depending on the content of the staging area.

The restore and checkout command implicitly restore the worktree from the staged area. We may make it more explicit by setting flags.

git restore .
git restore --worktree . #same
git restore file
git checkout .
git checkout main.js

reverting to older commits

revert the current branch to a previous commit

Restaurer la version de travail, la version staged et la version sauvegardée depuis la sauvegarde précédente nommée explicitement ou relativement par le nombre de commit depuis cette sauvegarde.

revenir au commit spécifié explicitement.

git reset --hard 2bje9dlhash

revenir au commit à une distance de N par rapport au dernier commit, ici l'avant dernier commit.

git reset --hard HEAD^1

other commands

remove untracked files

delete untracked files

git clean -fd

delete untracked files and files present but ignored by git

git clean -fdx

dry run

git clean -fdn
git clean -fdxn

Branches

a line of commits and the last commit on a line

A list of commits can be seen as a line in the sense of a continuous list of snapshots that follow each other. Technically, each commit keeps a reference to its parent (the former commit), so that we may reconstruct a whole line from a given commit.

We may conceptualize a branch as a line of a commits that lead to a tip commit.

There are as many distinct theoretical branches as there are commits, and we may have several actual branches pointing to the same commit.

All it takes to create a branch is to pick a name and decide what commit it points to. This commit becomes the current tip of the branch.

Branches are independent from each others: they allow to work in isolation. When we add a commit, the active branch is updated to point to the new commit, while others are untouched.

branches that live on the same line: fast-forward compatible

We may say that two branches live on the same line when the longer branch is the continuation of the shorter branch. The tip of the longer branch is a descendant of the tip of the short branch.

To fast forward a branch is to bring it closer to, or at the same point than a longer branch that lives on the same line. Technically, we make it point to a more recent commit on the line.

while the git command we use is called merge, there is no graph merge during a fast-forward.

branches that have forked: eligible for a merge commit

One branch has forked from the other one. The last commit they have in common is the common ancestor. The merge commit is one that aims to merge the paths coming from two parents. As such, it is a special commit that points to two parents instead of one.

Git compares files across the two parents and, when they are different, checks if one of them is stalled, aka has not changed since the last common ancestor. In that case, it favors the one that has received work, as it assumes the merge intends to make use of such work.

If both paths have worked on the ancestor version to change it, the merge cannot pick one version on its own. It asks the developer to resolve the difference between the two parents.

add the merge commit to the receiving branch

Once the merge commit has been created, the merge process doesn't merge the two branches into one, neither does it make both branches point to it.

Instead, the merge commit is only added to the receiving branch. The receiving branch is now "aware" of the two paths that led to the merge commit.

The other branch stays as-is. It is eligible for fast forward toward the receiving branch.

merge one branch (ex:dev) to current (ex:main) (repetition)

We first compare the two branches: the receiving branch (current), and the giving branch.

If the receiving branch is a parent of the giving branch, that is, it has not drifted away because it didn't do any commit on its own, then we can simply fast forward the receiving branch to the state of the giving branch, making them point to the same commit.

If the receiving branch has done at least one distinct commit, it means there was an actual fork, and a merge commit is required, to integrate the branch that has forked, and to preserve the fact that there was a fork. The merge commit only occurs on the receiving branch.

For example, we merge dev into main. dev is unaffected. main has a merge commit.

commands

list branches

## list branches
git branch
## includes remote-tracking branches
git branch -a

create, rename, delete branch commands

creating a branch does not switch to it.

git branch dev
git branch -m master main
git branch -d dev

branch switching

switching to a branch means to have HEAD pointing to the branch, which points to a commit. The idea of switching to a commit is to get a clean working directory and index that match this commit, so we may start working from this snapshot.

git switch dev

switching to a branch that comes from a remote

if the remote branch such as origin/dev does not have a matching local branch, the git switch command may create the dev local branch and switch into it.

Remote

instances and the canonical repository.

A remote repository is an instance of the repository that lives at another location.

There is no built-in hierarchy between instances. When there is a conflict, it's up to users to resolve it.

We use a pattern that sets one instance as the source-of-truth, with some flexibility when it needs to be corrected. We name it the canonical repository.

In practice, we put the canonical repository on a server known by all team members. Each member pushes changes to it and pulls changes from it.

For example, a member sets up a repository and publishes it to a host such as Github. The hosted repository becomes the canonical repository. Then, members clone the repository. Cloning downloads the repository and pre-configures it to be linked with the canonical repository.

remote-tracking branch

the remote-tracking branch mirrors a branch that lives on a remote repository, usually the canonical repository. As a faithful mirror, it is read-only, and only changes through a network call that signals changes.

we may poll the remote repository with git fetch. If updates are found, Git downloads the related commits and add them to the remote-tracking branches. We may inspect them like any other branch: we may log the commits and check the differences.

git log origin/main
git diff main origin/main

We may then merge the remote-tracking branch into the local-only one:

git merge origin/main

git pull is a shortcut for git fetch and git merge.

remote

As a Git technical term, a remote is a configured reference to a remote repository. We setup a remote to be able to push and pull from it.

We add a remote through its URL, and give it an alias name for convenience. By convention, we use origin for the main repository remote.

git remote add origin https://github.com/user333/helloworld.git

We may list remotes and show information for a given one. It's usually only origin.

git remote show
git remote show origin

## remote origin
##   Fetch URL: https://github.com/user33/abc.git
##   Push  URL: https://github.com/user33/abc.git
##   HEAD branch: main
##   Remote branch:
##     main tracked
##   Local branch configured for 'git pull':
##     main merges with remote main
##   Local ref configured for 'git push':
##     main pushes to main (up to date)

mutate a remote's URL

example: change the URL that origin refers to

git remote set-url origin git@github.com:user33/abc.git

merge the changes coming from remote

this is what happens when we do git pull (fetch + merge): the fetch updates origin/main, then the merge applies a fast forward so that main and origin/main point to the same commit.

git merge origin/main

merge a branch into main

git merge <branch-name>
git merge dev

clone: download and expand the .git directory

download

a clone downloads the .git directory.

https://github.com/mantinedev/mantine.git

we may clone a stripped-out version of the .git directory, one that contains fewer files.

For example, we ask the files needed by the last commit, and not the others. This is called a shallow clone.

We specify the depth, to target only the n-last commits.

git clone --depth 1 https://github.com/mantinedev/mantine.git
git clone --depth 1 [--branch master] https://github.com/mantinedev/mantine.git

expand

the clone automatically expands the latest commit files into the working directory.

branches and remote

push a branch that does not exist yet on the remote repository

Given that the remote repository doesn't have our local branch, we also don't have a remote-tracking branch that is supposed to track this branch on the remote. As such, we cannot set up an upstream just yet, because doing so requires binding a local-only branch to a remote-tracking branch.

Instead, we must first push the branch to the remote repository. If it is successful, we get the remote-tracking branch, and Git may now bind the branch to the remote-tracking branch, configuring it as the upstream.

We do it with one command: the push command, where we specify which remote we are pushing to, which branch we are pushing, and that we want Git to make the upstream binding if the push was a success

git push -u origin 2024

delete a branch on the remote repository

push is because it's an update to the remote repo.

git push origin --delete 2024

indicate match between current branch and remote-tracking branch

git branch --set-upstream-to origin/my_branch
git branch -u origin/my_branch

prune remote-tracking branches

We may have deleted some branches on the remote, and we want our repository to stop mirroring them:

git fetch --prune

Stashing unsaved work

dirty working tree

The working tree is dirty when it has modified files. They may represent unsaved work. Untracked files live outside of Git, but may also represent unsaved work.

the modified files that we added to the index became staged files. They also represent unsaved work.

There are a few scenario where Git has to clear up both the working tree and the index, to replace them with the content from another commit. This may occur when switching to a branch that points to another commit, or when merging some remote branch into our current one. (this does not occur when creating a branch because it keeps pointing to the current commit, keeping the current changes relevant)

The straightforward solution is to save the pending work into a new commit to make the working directory and index clean, and then perform the operation mentioned above.

stashing and popping

In the case where we don't want to create a commit, but we want to preserve the work that lives in the working tree and the index, we may ask Git to stash -u the changes. Under the hood, Git creates a hidden commit for the working tree and one for the index.

Later, we may reverse the operation, bringing back the unsaved work to the working tree and the index, with pop --index.

git stash
git stash -u # include the untracked files

Restore the working directory and staging area, assuming we are back on the correct branch. the --index flag restores the staging area as well.

git stash pop --index # if it worked without conflict

git stash pop --index
git stash drop # if conflict happened and has been solved

git stash apply --index # apply never drops the stash
git stash drop

conflict resolution on pop

When popping the unsaved files back to the working tree, there may be a conflict. The conflict resolution is the same as the one for merge conflicts. If it cannot solve the conflict itself, it will expose the incompatible changes in the files with visible marks to let the developer pick one. Similar for merge conflicts, the developer edits the file and signals it as resolved by performing a git add on the file.

As Git is conservative, it does not clear the stash if a conflict was detected. After we have resolved the conflict, we may have to clear the stash manually.

advanced: If we attempt to pop back a file from the index into an index that has a distinct version that also received work, it will fail completely, since the manual resolution is not possible for files that are in the index.

Other

edit the files without editing the message

git commit --amend --no-edit

example

git init
git add .
git commit -m "initial commit"
git remote add origin https://github.com/s3/aw33.git
git push -u origin master

diff

git diff
git diff --staged

The default diff is the one between the working tree and the staging area.

Once changes are staged, we may check the diff between the staging area and the last commit instead.

information about latest commit

we may show information about a commit, including the diff, with the git show command. It shows information about the last commit by default.

git show

prune .git folder, prevent some safeguards

clean : supprimer aussi les untracked files et folders

git clean -df

supprime les untracked files et directories. En effet, git checkout . ne permet pas de supprimer les fichiers et directories qui sont untracked, ramene a leur ancienne version seulement les fichiers tracked.

log commits, forks and merges

we log the commits of the current branch

git log

we may display the forks and merge commits

git log --graph --oneline

head

Head is which commit the working tree is coming from.

We change the head by checking out a specific commit.

When we check-in a commit that is not tracked by any branch, we say the head is detached, probably meaning detached from any branch.

Internals

the .git directory serves as the git repository

In Git terminology, the git repository is the .git directory managed by git. It is a hidden directory that lives at the root of the project. It stores all versioning data.

Because the directory stores all the project's data, we sometimes conflate it with the project directory itself, the one that lives locally and that contains both the working tree and the git repository proper.

enable git versioning in a project

The git init command creates the .git repository and enables git commands.

git init

.git directory content

files and directories

COMMIT_EDITMSG
FETCH_HEAD
HEAD
ORIG_HEAD
config
description
index
packed-refs

hooks/
info/
logs/
objects/
refs/

project specific configuration

the configuration file lives at .git/config

open .git/config

per user configuration

We may configure git at the user-level on a given computer. The configuration file is .gitconfig and lives in the home directory

code ~/.gitconfig
code ~/.gitattributes

git config user.name
git config user.email

git config user.name "John"
git config user.email "a@b.co"
earlymorning logo

© Antoine Weber 2025 - All rights reserved

Overview

traditional save patterns without git

The traditional save pattern is to store a file in-place or to a new file.

Saving in place is destructive: it does not preserve the old version.

Saving to a new file is safe, but leads to another challenge: managing multiple versions, their names, the storage and the retrieval, which is cumbersome and error-prone.

software source code specifics

Software source code usually consists of a tree of files that, together, make a whole unit. Compiling the software usually requires to have the whole tree available as-is on the filesystem.

As such, an actionable snapshot is one that comes as a whole.

The naive way to store snapshots is to store the whole file tree for any given file change. This method has a heavy and inefficient storage footprint, due to spreading identical files across snapshots.

what we want from a versioning software

We want an approach that is more efficient in terms of storage: the snapshots should not be stored as-is, but instead be reconstructed on-demand, with files being fetched from a central storage. The software should keep track of the files that a given snapshot relies on.

We want to conveniently browse and load old snapshots or snapshots from alternative development branches.

We also want snapshots to come with metadata: their date, their unique ID, and the description of changes they bring (commit message).

lightweight snapshot: a declarative list of files

We have the snapshot as a declarative list of files it is comprised of, like a shopping list.

In this pattern, the files may live anywhere. This allows files to be referred to by any snapshot, and to be stored only once.

The software may reconstruct the snapshot based on the list: the software fetches the files from the storage and builds a snapshot mirror.

editing area

The local directory where we make changes is called the working directory or working tree.

Most of the time, it should start as a mirror of the latest snapshot, allowing to work on changes and submit a new snapshot for commit.

Sometimes, it will instead mirror an old snapshot, so that we may run the snapshot as it was, or to fork away from that snapshot onwards.

other terminology

to commit means to intentionally save a snapshot. By extension, we may refer to a snapshot itself as a commit. In git internals, a commit is the file that stores all the file dependencies and the metadata related to a given snapshot.

The staging area, also called the index, is a tentative snapshot, on that is ready to be committed. See Editing and staging.

Editing and staging

separation of concern: working directory and staging area

We keep the working directory and the staging area (index) separate to acknowledge their different role.

the role of the working directory

The working directory is a playground where changes are not saved, good for exploration and iteration. Its content has no influence on what is saved when we perform a commit.

terminology

This allows us to have files that we don't commit yet or ever: untracked files. If the file is a modified version of one that was committed before, and that it is not staged yet, we call it modified.

When files are added to the staging area, they cease to be untracked or modified: they become staged.

staging area: granularity and storage

The staging area determines what is saved on commit. Having some granularity instead of saving the whole working tree has benefits:

  • We may select related changes and commit them as a group.
  • We may technically save different portions of a file at a time.

Besides, we may use the index as a temporary storage, in a manner similar of saving checkpoints: This allows to store one version and keep iterating on it, with the ability to revert to the checkpoint if needed.

This is possible because the index is similar to a commit: it has its own list of files, which live in the storage area, and that are decoupled from the working tree.

Common commands

the standard workflow is as follows:

  • make some changes to one or more files in the working directory
  • select one or more changes, put them in the staging area
  • save the staging area as a commit.

standard flow

stage files: working-directory to index

git add adds one or more files to the staging area. It affects both untracked files and modified files, turning them into staged files.

git add .
git add -u

We may exclude untracked files from staging with -u. A more permanent solution is to add them to .gitignore.

commit: index to commit

Save the staging area as a new commit. It has no effect over the working directory:

git commit -m "..."

We may edit the last commit message with --amend. This does not create a new commit.

git commit --amend -m "an updated commit message"

Note: the -am flag is not a shortcut for both git add . and git commit -m '', since it does not add untracked files.

git commit -am "adding and committing modified files"

reverting to the last commit

The last commit is commonly represented by HEAD.

commit to working-directory: reset the working-directory.

we use restore and set the --source to HEAD.

git restore --source=HEAD --worktree  .
git restore --source=HEAD .

old syntax

git checkout HEAD   -- . 				# the last commit
git checkout v1.2.3 -- .      # tag v1.2.3
git checkout stable -- file   # stable branch
git checkout origin/master -- file  # upstream master
git checkout HEAD^ -- file          # the version before the last commit

commit to working-directory AND index.

We aim to revert both the index and the working directory to the commit version.

It does not affect or remove untracked files, since they are ignored by git.

git restore --source=HEAD --staged --worktree .

git reset --hard

commit to index: reset the staging area

We may revert the staging area to the clean state of the previous commit. The working tree is unaffected.

We use the restore command, with the staged flag.

git restore --staged . # tested
git reset .

If we are before the first commit, we may remove content from the staging area:

git rm --cached file
git reset

reverting to the index version

index to working-directory: go back to the index checkpoint

We may restore the worktree from the index. This may restore a checkpoint or the last commit, depending on the content of the staging area.

The restore and checkout command implicitly restore the worktree from the staged area. We may make it more explicit by setting flags.

git restore .
git restore --worktree . #same
git restore file
git checkout .
git checkout main.js

reverting to older commits

revert the current branch to a previous commit

Restaurer la version de travail, la version staged et la version sauvegardée depuis la sauvegarde précédente nommée explicitement ou relativement par le nombre de commit depuis cette sauvegarde.

revenir au commit spécifié explicitement.

git reset --hard 2bje9dlhash

revenir au commit à une distance de N par rapport au dernier commit, ici l'avant dernier commit.

git reset --hard HEAD^1

other commands

remove untracked files

delete untracked files

git clean -fd

delete untracked files and files present but ignored by git

git clean -fdx

dry run

git clean -fdn
git clean -fdxn

Branches

a line of commits and the last commit on a line

A list of commits can be seen as a line in the sense of a continuous list of snapshots that follow each other. Technically, each commit keeps a reference to its parent (the former commit), so that we may reconstruct a whole line from a given commit.

We may conceptualize a branch as a line of a commits that lead to a tip commit.

There are as many distinct theoretical branches as there are commits, and we may have several actual branches pointing to the same commit.

All it takes to create a branch is to pick a name and decide what commit it points to. This commit becomes the current tip of the branch.

Branches are independent from each others: they allow to work in isolation. When we add a commit, the active branch is updated to point to the new commit, while others are untouched.

branches that live on the same line: fast-forward compatible

We may say that two branches live on the same line when the longer branch is the continuation of the shorter branch. The tip of the longer branch is a descendant of the tip of the short branch.

To fast forward a branch is to bring it closer to, or at the same point than a longer branch that lives on the same line. Technically, we make it point to a more recent commit on the line.

while the git command we use is called merge, there is no graph merge during a fast-forward.

branches that have forked: eligible for a merge commit

One branch has forked from the other one. The last commit they have in common is the common ancestor. The merge commit is one that aims to merge the paths coming from two parents. As such, it is a special commit that points to two parents instead of one.

Git compares files across the two parents and, when they are different, checks if one of them is stalled, aka has not changed since the last common ancestor. In that case, it favors the one that has received work, as it assumes the merge intends to make use of such work.

If both paths have worked on the ancestor version to change it, the merge cannot pick one version on its own. It asks the developer to resolve the difference between the two parents.

add the merge commit to the receiving branch

Once the merge commit has been created, the merge process doesn't merge the two branches into one, neither does it make both branches point to it.

Instead, the merge commit is only added to the receiving branch. The receiving branch is now "aware" of the two paths that led to the merge commit.

The other branch stays as-is. It is eligible for fast forward toward the receiving branch.

merge one branch (ex:dev) to current (ex:main) (repetition)

We first compare the two branches: the receiving branch (current), and the giving branch.

If the receiving branch is a parent of the giving branch, that is, it has not drifted away because it didn't do any commit on its own, then we can simply fast forward the receiving branch to the state of the giving branch, making them point to the same commit.

If the receiving branch has done at least one distinct commit, it means there was an actual fork, and a merge commit is required, to integrate the branch that has forked, and to preserve the fact that there was a fork. The merge commit only occurs on the receiving branch.

For example, we merge dev into main. dev is unaffected. main has a merge commit.

commands

list branches

## list branches
git branch
## includes remote-tracking branches
git branch -a

create, rename, delete branch commands

creating a branch does not switch to it.

git branch dev
git branch -m master main
git branch -d dev

branch switching

switching to a branch means to have HEAD pointing to the branch, which points to a commit. The idea of switching to a commit is to get a clean working directory and index that match this commit, so we may start working from this snapshot.

git switch dev

switching to a branch that comes from a remote

if the remote branch such as origin/dev does not have a matching local branch, the git switch command may create the dev local branch and switch into it.

Remote

instances and the canonical repository.

A remote repository is an instance of the repository that lives at another location.

There is no built-in hierarchy between instances. When there is a conflict, it's up to users to resolve it.

We use a pattern that sets one instance as the source-of-truth, with some flexibility when it needs to be corrected. We name it the canonical repository.

In practice, we put the canonical repository on a server known by all team members. Each member pushes changes to it and pulls changes from it.

For example, a member sets up a repository and publishes it to a host such as Github. The hosted repository becomes the canonical repository. Then, members clone the repository. Cloning downloads the repository and pre-configures it to be linked with the canonical repository.

remote-tracking branch

the remote-tracking branch mirrors a branch that lives on a remote repository, usually the canonical repository. As a faithful mirror, it is read-only, and only changes through a network call that signals changes.

we may poll the remote repository with git fetch. If updates are found, Git downloads the related commits and add them to the remote-tracking branches. We may inspect them like any other branch: we may log the commits and check the differences.

git log origin/main
git diff main origin/main

We may then merge the remote-tracking branch into the local-only one:

git merge origin/main

git pull is a shortcut for git fetch and git merge.

remote

As a Git technical term, a remote is a configured reference to a remote repository. We setup a remote to be able to push and pull from it.

We add a remote through its URL, and give it an alias name for convenience. By convention, we use origin for the main repository remote.

git remote add origin https://github.com/user333/helloworld.git

We may list remotes and show information for a given one. It's usually only origin.

git remote show
git remote show origin

## remote origin
##   Fetch URL: https://github.com/user33/abc.git
##   Push  URL: https://github.com/user33/abc.git
##   HEAD branch: main
##   Remote branch:
##     main tracked
##   Local branch configured for 'git pull':
##     main merges with remote main
##   Local ref configured for 'git push':
##     main pushes to main (up to date)

mutate a remote's URL

example: change the URL that origin refers to

git remote set-url origin git@github.com:user33/abc.git

merge the changes coming from remote

this is what happens when we do git pull (fetch + merge): the fetch updates origin/main, then the merge applies a fast forward so that main and origin/main point to the same commit.

git merge origin/main

merge a branch into main

git merge <branch-name>
git merge dev

clone: download and expand the .git directory

download

a clone downloads the .git directory.

https://github.com/mantinedev/mantine.git

we may clone a stripped-out version of the .git directory, one that contains fewer files.

For example, we ask the files needed by the last commit, and not the others. This is called a shallow clone.

We specify the depth, to target only the n-last commits.

git clone --depth 1 https://github.com/mantinedev/mantine.git
git clone --depth 1 [--branch master] https://github.com/mantinedev/mantine.git

expand

the clone automatically expands the latest commit files into the working directory.

branches and remote

push a branch that does not exist yet on the remote repository

Given that the remote repository doesn't have our local branch, we also don't have a remote-tracking branch that is supposed to track this branch on the remote. As such, we cannot set up an upstream just yet, because doing so requires binding a local-only branch to a remote-tracking branch.

Instead, we must first push the branch to the remote repository. If it is successful, we get the remote-tracking branch, and Git may now bind the branch to the remote-tracking branch, configuring it as the upstream.

We do it with one command: the push command, where we specify which remote we are pushing to, which branch we are pushing, and that we want Git to make the upstream binding if the push was a success

git push -u origin 2024

delete a branch on the remote repository

push is because it's an update to the remote repo.

git push origin --delete 2024

indicate match between current branch and remote-tracking branch

git branch --set-upstream-to origin/my_branch
git branch -u origin/my_branch

prune remote-tracking branches

We may have deleted some branches on the remote, and we want our repository to stop mirroring them:

git fetch --prune

Stashing unsaved work

dirty working tree

The working tree is dirty when it has modified files. They may represent unsaved work. Untracked files live outside of Git, but may also represent unsaved work.

the modified files that we added to the index became staged files. They also represent unsaved work.

There are a few scenario where Git has to clear up both the working tree and the index, to replace them with the content from another commit. This may occur when switching to a branch that points to another commit, or when merging some remote branch into our current one. (this does not occur when creating a branch because it keeps pointing to the current commit, keeping the current changes relevant)

The straightforward solution is to save the pending work into a new commit to make the working directory and index clean, and then perform the operation mentioned above.

stashing and popping

In the case where we don't want to create a commit, but we want to preserve the work that lives in the working tree and the index, we may ask Git to stash -u the changes. Under the hood, Git creates a hidden commit for the working tree and one for the index.

Later, we may reverse the operation, bringing back the unsaved work to the working tree and the index, with pop --index.

git stash
git stash -u # include the untracked files

Restore the working directory and staging area, assuming we are back on the correct branch. the --index flag restores the staging area as well.

git stash pop --index # if it worked without conflict

git stash pop --index
git stash drop # if conflict happened and has been solved

git stash apply --index # apply never drops the stash
git stash drop

conflict resolution on pop

When popping the unsaved files back to the working tree, there may be a conflict. The conflict resolution is the same as the one for merge conflicts. If it cannot solve the conflict itself, it will expose the incompatible changes in the files with visible marks to let the developer pick one. Similar for merge conflicts, the developer edits the file and signals it as resolved by performing a git add on the file.

As Git is conservative, it does not clear the stash if a conflict was detected. After we have resolved the conflict, we may have to clear the stash manually.

advanced: If we attempt to pop back a file from the index into an index that has a distinct version that also received work, it will fail completely, since the manual resolution is not possible for files that are in the index.

Other

edit the files without editing the message

git commit --amend --no-edit

example

git init
git add .
git commit -m "initial commit"
git remote add origin https://github.com/s3/aw33.git
git push -u origin master

diff

git diff
git diff --staged

The default diff is the one between the working tree and the staging area.

Once changes are staged, we may check the diff between the staging area and the last commit instead.

information about latest commit

we may show information about a commit, including the diff, with the git show command. It shows information about the last commit by default.

git show

prune .git folder, prevent some safeguards

clean : supprimer aussi les untracked files et folders

git clean -df

supprime les untracked files et directories. En effet, git checkout . ne permet pas de supprimer les fichiers et directories qui sont untracked, ramene a leur ancienne version seulement les fichiers tracked.

log commits, forks and merges

we log the commits of the current branch

git log

we may display the forks and merge commits

git log --graph --oneline

head

Head is which commit the working tree is coming from.

We change the head by checking out a specific commit.

When we check-in a commit that is not tracked by any branch, we say the head is detached, probably meaning detached from any branch.

Internals

the .git directory serves as the git repository

In Git terminology, the git repository is the .git directory managed by git. It is a hidden directory that lives at the root of the project. It stores all versioning data.

Because the directory stores all the project's data, we sometimes conflate it with the project directory itself, the one that lives locally and that contains both the working tree and the git repository proper.

enable git versioning in a project

The git init command creates the .git repository and enables git commands.

git init

.git directory content

files and directories

COMMIT_EDITMSG
FETCH_HEAD
HEAD
ORIG_HEAD
config
description
index
packed-refs

hooks/
info/
logs/
objects/
refs/

project specific configuration

the configuration file lives at .git/config

open .git/config

per user configuration

We may configure git at the user-level on a given computer. The configuration file is .gitconfig and lives in the home directory

code ~/.gitconfig
code ~/.gitattributes

git config user.name
git config user.email

git config user.name "John"
git config user.email "a@b.co"