Skip to content

Instantly share code, notes, and snippets.

@fpl9000
Last active May 10, 2023 17:41
Show Gist options
  • Save fpl9000/1a67aebdb0237a13fa15 to your computer and use it in GitHub Desktop.
Save fpl9000/1a67aebdb0237a13fa15 to your computer and use it in GitHub Desktop.
Git Cheat Sheet
Git Cheat Sheet
Author: Fran Litterio <flitterio -at- gmail.com>
Things to do:
- Show Git commands throughout the 'Basic Concepts' and 'Advanced Concepts' sections.
- Stashing: https://git-scm.com/book/en/v2/Git-Tools-Stashing-and-Cleaning
- Revision specification syntax: https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection
- man gitrevisions
- Reflog syntax: HEAD@{n}, HEAD@{yesterday}, HEAD@{2.months.ago}, ...
- Ancestry References: foo^, foo^2, foo~, foo~2, foo^2~3, ...
- Commit ranges: foo..bar, foo...bar
- Reverting changes with 'git restore ...'
- Reverting commits
- Rebasing
- Squashing
- Refspecs & reference namespaces: https://git-scm.com/book/en/v2/Git-Internals-The-Refspec
- How to create a remote repo
- Porcelain vs plumbing
- Detached HEAD
================================================================================
Contents
================================================================================
This document has the following top-level sections:
- Git Documentation
- Basic Concepts
- Advanced Concepts
- Git Command Quick Reference
================================================================================
Git Documentation
================================================================================
The Git documentation includes a reference manual, a tutorial, and videos. You can find it at:
- https://git-scm.com/doc
If you're new to Git, start with the book "Pro Git", which can be read online at:
- https://git-scm.com/book
If you're already familiar with Git, the Git reference manual is at:
- https://git-scm.com/docs.
The Git reference manual is available interactively using command 'git help COMMAND'. Type
'git help' for a usage summary.
On UNIX/Linux systems, every Git command has a man page named "git-COMMAND", where COMMAND is
the Git command name.
================================================================================
Basic Concepts
================================================================================
A Git repository (called a "repo") is a directory tree containing a mix of revision controlled
files and other files.
- The revision controlled files are said to be "tracked" by Git. All other files in a repo
are "untracked". Tracked and untracked files exist alongside each other in the repo.
- Git metadata is stored in directory .git in the root of the repo. Rarely, you might look
at the .git directory, but generally you should ignore it.
- A repo contains one or more branches, each containing a different history of the files in
the repo.
- The main branch is named "master" by default, but you can choose any name for a branch.
Branches can be renamed at any time. People refer to the "master branch" even if its
name isn't "master".
- Unlike most revision control systems, Git branches are not represented by separate
directories. Instead, you can only see the tracked files from one branch at a time.
- To work on files in a different branch, you have to switch to that branch. This
switching operation is called a "checkout" of the branch. This use of the term
"checkout" differs from what most revision control systems mean by that word.
For each branch, tracked files can exist in the following places:
WORKING FILES <-> INDEX <-> LOCAL REPO <-> REMOTE REPO
- Above, the bidirectional arrows show how Git workflow copies tracked files between these
places. It's important to understand this workflow when using Git. Let's look at how
each of these places is used and when tracked files are copied between them.
- WORKING FILES are tracked files that you can work on. You create, modify, and delete
working files.
+ Only one branch's tracked files are visible at a time. Those are the working files.
+ Use command 'git checkout BRANCHNAME' to switch to another branch. This changes the
working files to the other branch's tracked files -- except for any modified tracked
files, so be careful not to have any, otherwise you'll be left with a mixture of
tracked files from different branches. Git warns you if this happens.
+ Depending on how different the two branches are, a checkout can cause existing
working files to disappear, new working files to appear, and the contents of working
files to change. Directories that contain working files can appear and disappear
too.
+ Other revision control systems use the term "checkout" to mean "obtain permission to
modify a tracked file". Git has no such concept, because Git is decentralized.
There is no central authority to grant that permission. You are free to edit, add,
and delete any working file in your repo.
+ When you checkout another branch, your untracked files don't change, because Git
generally ignores untracked files.
* This can be confusing if your tracked files contain source code, because
compiler-generated binaries are untracked files. After you switch branches, your
source code will likely change, but the untracked binaries will not change,
causing them to be out-of-sync with your source code.
* One way to avoid switching between branches in a local repo is to use a separate
local repo for each branch. All branches still exist in all local repos, but
each local repo has a different branch checked out.
* Using separate local repos also avoids the problem where modified tracked files
are not switched when you switch branches, because you don't need to switch
branches.
- The INDEX is a compressed copy of all tracked files in the current branch. When you
checkout another branch, the index changes just as the working files do.
+ The index seems redundant. Why have two copies of the tracked files: the working
files and the index? Because the working files are for you to modify and delete.
The index is Git's copy of the current branch's tracked files. In a centralized
revision control system, the role of the index is filled by the central database, but
there is no such thing with Git.
+ As you make changes to the working files, you can "stage" the modified, new, and
deleted working files into the index. This is how you give your changes to Git. Use
command 'git add FILE ...' to stage new or modified files, and command 'git rm FILE
...' to stage deleted files.
+ Staged changes wait in the index to be part of a future "commit" into the current
branch in the local repo. Typical workflow is to stage a series of related changes
into the index, then later commit all of them at once to the local repo.
+ A file can be staged repeatedly. A more recently staged file replaces the previously
staged copy in the index. After a file is staged, the copy in the working files and
the copy the index are identical, until you make more changes to the working file.
+ For convenience, files can be staged and committed in one operation. However,
staging many related changes and then making a single commit keeps the repo's commit
history uncluttered and simplifies reverting changes.
+ The contents of the index are often called the "staged files", but don't take this to
mean that the index contains only files you have staged. The index contains all of
the tracked files in the current branch, including the changes you have staged.
+ The index is stored in the .git directory, along with the rest of the metadata for
your local repo.
- The LOCAL REPO is a compressed copy of all commits in all branches along with their
historical time-ordering.
+ The local repo contains the history of ALL commits in ALL branches.
+ The local repo exists on the computer where you do your work. Git is a distributed
revision control system, so each collaborator has a copy of every commit in every
branch in their local repo. There is no central database.
+ The local repo (and the index) are accessed through the filesystem, which lets you do
the majority of operations offline.
+ The local repo is stored in the .git directory, but people often use "local repo" to
mean the directory containing all of the working files plus the .git directory.
- The REMOTE REPO, often called a "remote", is for sharing work with other people.
+ The working files, the index, and the local repo are all located on your computer
under the same directory, but the remote repo usually exists on a server, so it can
be accessed by multiple people.
+ Collaborators on a project each have their own local repo (and its associated index
and working files), but they all share a common remote repo. A single-user project
doesn't need a remote repo, because there are no collaborators.
+ People usually don't work in a remote repo, but it is possible.
+ Git workflow copies tracked files between the working files, the index, the local
repo, and the remote repo (if present). So it's possible for a tracked file to be
different in each of these places. Also, the file may be different in your
collaborators' local repos.
To use Git you need a local repo.
- There are three ways to create a local repo:
+ Initialize a local directory. Do this if you already have the files you want to put
under revision control.
+ Clone a remote repo. Do this if you need to work on files that are already under
Git's revision control.
+ Copy an existing local repo. More on this below.
- You initialize a local directory with command 'git init'.
+ Command 'git init' creates the .git directory, creates some Git metadata in the .git
directory, and leaves you with no tracked files, no working files, an empty index,
and an empty local repo.
+ After you initialize a local repo, you are ready to stage and commit new files.
+ If the directory was not empty when you initialize it, the files in it remain as
untracked files.
+ A newly initialized local repo has no associated remote repo, but it can be given one
at any time (more on this later).
- You clone a remote repo with command 'git clone REMOTEURL', where REMOTEURL identifies a
Git remote repo on a server. For instance, https://github.com/torvalds/linux.git
identifies the Linux kernel's remote repo.
+ After you clone a remote repo, the new local repo contains a copy of every branch in
the remote, with the working files and index matching the remote's current branch
(usually the master branch).
+ Cloning connects the local and remote repos. This enables you to share work with
collaborators who have cloned the same remote repo.
+ After you clone a remote repo, the remote is called the "origin" of the local repo.
The local repo remembers which remote is its origin.
- A less common way to create a local repo is simply to copy someone else's local repo.
+ This works best if the other person's local repo has no staged files in the index and
no modified working files, otherwise your copy will have them too, which is probably
not what you want.
+ This works because there is no per-user information in a local repo. Your personal
Git configuration data is stored in file .gitconfig in your home directory (on UNIX)
or in an OS-designated personal folder (on Windows and MacOS).
+ You can make copies of your own local repo. This allows you do any of the following:
* You can have different branches checked out at the same time, so you don't have
to do 'git checkout BRANCHNAME' to switch one local repo between branches.
* You can do work on multiple computers. You can have a copy of a local repo on
both your personal laptop and your work computer. If you change the same file in
both repos, you will eventually have to merge those changes (see below for info
on merging).
* You can easily migrate a local repo to another computer: just copy it to the
other computer, delete the original, and pick up where you left off.
A repo contains branches. A branch contains commits. A commit contains a snapshot of a
branch's files at one point in time.
- You create a commit with command 'git commit', which commits all staged files in the
index. This copies the staged files into the local repo, along with some metadata.
+ The commit command spawns an editor for you to enter a commit message. If you prefer
to give the commit message on the command line, use 'git commit -m "MESSAGE"'.
+ You can stage and commit working files in one operation using command 'git commit
FILE ...', where the specified FILEs are modified tracked files.
+ You can stage and commit all modified tracked files in one operation using command
'git commit -a'.
+ Staging and committing in one operation is not advised. The preferred workflow is to
stage multiple changes over time, then commit once for a set of related staged
changes.
- Each commit contains the following data:
+ A unique 160-bit ID (a SHA-1 hash). The hash is displayed as 40 hex digits, but is
sometimes abbreviated to the leftmost 7 hex digits.
+ The SHA-1 hash of its "parent" commit, which is the commit chronologically preceding
it in the branch. This is how commits are chained in historical order. The one
exception is that the very first commit in a repo has no parent.
+ A short message from the author describing the commit.
+ All tracked files in the branch at the time of the commit. Unlike other revision
control systems, Git does not store deltas. This doesn't waste space, because Git
implements a reference-counted, content-addressable, compressed file store (see
https://tinyurl.com/gitobj).
+ An optional GPG digital signature by the author.
+ Miscellaneous metadata: commit time, author's name and email, etc.
People collaborate on a project by copying commits between their local repos and a common
remote repo.
- Copying commits from a local repo to a remote is a "push".
+ Use command 'git push' to push all commits in all branches to the remote repo. The
only commits pushed are those that do not yet exist in the remote. You can use
command-line options and arguments to restrict a push to just one branch.
+ A push is how you actually share your work. A commit to your local repo does not
share anything, because only you use your local repo.
- Copying commits from a remote to a local repo is a "pull" or "fetch". A pull is a
superset of a fetch (more on pull and fetch later).
- Here, four users (Joe, Sue, Pat, and Bob) each have their own local repos. They
collaborate by sharing a common remote. This is a hub-and-spoke topology, where the
remote is the hub and local repos are on the spokes:
Joe: WORKING FILES <-> INDEX <-> LOCAL REPO <---\
Sue: WORKING FILES <-> INDEX <-> LOCAL REPO <----\
+--> REMOTE REPO
Pat: WORKING FILES <-> INDEX <-> LOCAL REPO <----/
Bob: WORKING FILES <-> INDEX <-> LOCAL REPO <---/
- A remote is identified by an URL and a short name.
+ The hostname in the URL is the server that hosts the remote. Here are the URLs for
some interesting remote repos that you can clone:
https://github.com/torvalds/linux.git - The Linux kernel
https://github.com/dotnet/core.git - Microsoft's .NET Core
https://github.com/dotnet/roslyn.git - Microsoft's Visual Studio compiler
https://github.com/apple/swift.git - Apple's Swift programming language
https://github.com/golang/go.git - Google's Go programming language
https://github.com/chrislgarry/Apollo-11.git - Apollo 11 guidance computer source
+ The short name of a remote is a convenient name chosen by the owner of the local
repo. The scope of a short name is a local repo, so different people can use
the same short names in their repos without problem.
+ When you clone a remote repo, the default short name of the remote is "origin", but
this can be changed at any time.
A branch is a named time-ordered sequence of commits. Every commit except the first points to
one (or sometimes two) "parent" commits, forming a history tree of all branches.
- A branch name is simply a "reference" to the latest commit in the branch (more on
references later). This makes Git branches very lightweight. Here, branch "master"
references commit C7. It is the current branch, and the branch contains eight commits:
C0 <- C1 <- C2 <- C3 <- C4 <- C7 <- "master" (current branch)
- In these diagrams, arrows point to each commit's parent, forming the chronological
history of commits. C0 is the only commit with no parent, because it is the first
commit.
- You create a branch with command 'git branch BRANCHNAME'. This creates the named branch
so that it references the latest commit in the current branch, but it does not checkout
the new branch, so the current branch does not change.
- Example: If the current branch is "master", and the most recent commit was C7, then
command 'git branch mybranch' would result in this, where "mybranch" and "master" both
reference commit C7:
+- "mybranch"
V
C0 <- C1 <- C2 <- C3 <- C4 <- C7 <- "master" (current branch)
- If you then do 'git checkout mybranch' and make a new commit (C8), the new commit is part
of "mybranch" instead of "master":
+- C8 <- "mybranch" (current branch)
V
C0 <- C1 <- C2 <- C3 <- C4 <- C7 <- "master"
- If you then checkout the "master" branch and make a new commit (C9), the new commit goes
in the "master" branch:
+- C8 <- "mybranch"
V
C0 <- C1 <- C2 <- C3 <- C4 <- C7 <- C9 <- "master" (current branch)
- Above, branch "mybranch" and branch "master" diverge at commit C7, the common ancestor.
All branches share a common ancestor with some other branch. This means the commits in a
repo form a directed acyclic graph (DAG) rooted at the very first commit in the repo.
- A branch in your local repo becomes visible to other users when you push it (and the
commits it contains) to a remote. A push normally copies all local branches to the
remote, but you can restrict it to just one or some branches.
- A branch can be deleted with command 'git branch -d BRANCH', but that only deletes the
branch's name to reduce clutter. A commit contiues to exist as long as it is referenced
by a child commit, a branch name, or a "tag" (a very lightweight label that points to a
commit).
- Git garbage collects commits that have no references. In the below diagram, if you were
to delete branch "newfeature", the only reference to commit C52 would be removed, causing
C52 to be garbage collected. But then C51 is unreferenced, so it would also be garbage
collected. Beware: deleting a branch destroys commits!
+- C51 <- C52 <- "newfeature"
V
... <- C47 <- C48 <- C49 <- C50 <- "master"
- For safety, Git never garbage collects an object that's less than two weeks old. This
gives you time to change your mind and recover unreferenced commits.
A branch can be "merged" into any other branch.
- The result is a "merge commit" in the target branch. Here, branch "bugfix" has been
merged into branch "master", creating merge commit C8:
+- C5 <- C6 <-----+ <- "bugfix"
V |
C0 <- C1 <- C2 <- C3 <- C4 <- C7 <- C8 <- "master"
- A merge commit always has _two_ parent commits, one from each of the branches that were
merged. Above, the parents of merge commit C8 are commits C6 and C7.
- After a merge, both branches can continue to accumulate commits. Later, they can be
merged again. Here, a second merge has created merge commit C13, then commit C14 was
created on branch "master":
+- C5 <- C6 <-----+ <- C10 <- C11 <--+ <- "bugfix"
V | |
C0 <- C1 <- C2 <- C3 <- C4 <- C7 <- C8 <- C9 <- C12 <- C13 <- C14 <- "master"
- A merge may cause "merge conflicts" in files, which are places where the same lines were
changed in both branches.
+ Merge conflicts are marked with a special syntax around the conflicting lines:
Here are lines that are either unchanged from the common
ancestor, or cleanly resolved because only one side changed.
<<<<<<< yours:sample.txt
Conflict resolution is hard.
Let's go shopping.
=======
Git makes conflict resolution easy.
>>>>>>> theirs:sample.txt
And here is another line that is cleanly resolved or unmodified.
+ The area where a pair of conflicting changes happened is marked with '<<<<<<<',
'=======', and '>>>>>>>'. The part before the '=======' is typically your change,
and the part afterwards is typically someone else's change.
+ You can change the conflict marker style to show a 3-way diff (see section "HOW
CONFLICTS ARE PRESENTED" in the help displayed by 'git help merge'). In that case, a
conflict appears like this (note the new marker '|||||||'):
Here are lines that are either unchanged from the common
ancestor, or cleanly resolved because only one side changed.
<<<<<<< yours:sample.txt
Conflict resolution is hard.
Let's go shopping.
|||||||
This is the orignal text from the common ancestor of the conflicting changes.
=======
Git makes conflict resolution easy.
>>>>>>> theirs:sample.txt
And here is another line that is cleanly resolved or unmodified.
+ After fixing merge conflicts in a file (including removing those marker lines), you
"resolve" that file's conflicts by staging the file with 'git add FILE'.
+ You complete the merge by committing all of the staged merged files in a single
commit. This creates the merge commit. This is a use case where you MUST accumulate
changes (the resolved merge conflicts) in the index before making a single commit to
finish the merge.
- If you pull other people's commits from a remote branch, and you've made commits to your
local branch since your previous pull, then a "merge" will happen automatically, because
the history of your local branch has diverged from the history of the remote branch.
This is normal and unavoidable, due to Git's distributed nature.
- Pushes never cause merges, because you aren't allowed to push to a remote unless you were
the most recent person to pull from it. Thus, all merges happen in someone's local repo
and never in a remote.
The special reference "HEAD".
- The reference "HEAD" always points to the name of the current branch, so it's actually a
reference to a reference. This extra indirection means HEAD always references
(indirectly) the latest commit in the current branch, even as the branch name changes to
reference new commits in the branch.
- When you do a commit, the new commit is always added to the branch referenced by HEAD.
Here, commit C3 is added to branch "master", which is the current branch. Reference
"master" changes to point to C3, but reference "HEAD" continues to point to "master",
so it now (indirectly) references C3 instead of C2:
Before: C1 <- C2 <- master <- HEAD
After: C1 <- C2 <- C3 <- master <- HEAD
- Here is how HEAD changes when you switch from branch "master" to branch "newfeature"
using command 'git checkout newfeature':
Before: ... <- C10 <- C11 <- master <- HEAD
... <- C26 <- C27 <- newfeature
After: ... <- C10 <- C11 <- master
... <- C26 <- C27 <- newfeature <- HEAD
- To see which branch is current, use 'git status'. In this output, the current branch is
"master":
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
- Above, note the mention to 'origin/master'. This notation connects the short name of a
remote ("origin") to the name of a branch ("master") with a '/' between them. This is
called a "remote tracking branch". More on these in the Advanced Concepts section.
Stashing.
- UNDER CONSTRUCTION
There are several kinds of branches.
- A "local branch" is a branch in your local repo.
- A "remote branch" is a branch in a remote repo. It exists for sharing commits with
others. People don't do day-to-day work in remote branches, so remote repos typically
have no working files. A repo without working files is called a "bare" repo.
- A "tracking branch" is a local branch that has an associated remote branch, called the
"upstream branch" or simply the "upstream".
+ When you push and pull, you synchronize tracking branches with their upstream
branches. Pushes send commits and references from a tracking branch to its upstream
branch. Pulls do the opposite.
+ You can set, clear, and change the upstream branch of a local branch at any time.
Don't go crazy. If you have a local branch containing the .NET source, and you set
its upstream to be a remote branch containing the Linux source, and then you do a
pull, Git will happily merge Linux into .NET. Oh, the humanity!
+ A local branch does not need an upstream branch. For instance, a "topic branch" is a
short-lived local branch used to isolate work (e.g., branch "bugfix" in the example
above). It needs no upstream branch, because eventually it will be merged into
another local branch.
- A "remote-tracking branch" is a read-only branch in your local repo that exactly matches
the state of a remote branch. This may seem like unnecessary duplication of state, but
remote tracking branches exist for a reason. More on these kinds of branches later in
the 'Advanced Concepts' section.
References. QUESTION: Should this section be moved up?
- References allow you to use human-readable names instead of SHA-1 hashes to refer to
commits. And since a branch is a chain of commits, references are also a way to refer to
branches.
- A "reference" is a symbolic name that points to a commit. A branch name is simply a
reference that points to the most recent commit in that branch. A "tag" is a reference
to an arbitrary commit to mark historic milestones.
- The command 'git show-ref' lists all references in your local repo:
$ git show-ref
fee6f1a6ae348935181ee6bfec59d1682bf6a22a refs/heads/master
fee6f1a6ae348935181ee6bfec59d1682bf6a22a refs/remotes/origin/HEAD
fee6f1a6ae348935181ee6bfec59d1682bf6a22a refs/remotes/origin/master
b4f7a8752c312e934bd122b2a3b5d1a81af93764 refs/tags/qa-release-1
- Above, hex values are SHA-1 hashes of commits pointed to by the references on the right.
- Reference names resemble pathnames and have a hierarchical structure.
+ Local branches are under "refs/heads".
+ Tags are under "refs/tags".
+ Remote-tracking branches are under "refs/remotes". Here, "origin" is the short name
of a remote, and under it are references to remote-tracking branches "master" and
"HEAD". Read more about remote-tracking branches in the 'Advanced Concepts' section.
This is a typical Git workflow for collaborating with others:
- In your local repo, make a short-lived topic branch (e.g., "fixvan12345"), switch to it
using 'git checkout fixvan12345', and stage/commit some work leaving no modified working
files.
- Checkout tracking branch "master". Pull from the upstream branch to merge recent commits
from collaborators into your local repo. If there are merge conflicts, resolve and
commit them.
- Merge topic branch "fixvan12345" into tracking branch "master". If there are merge
conflicts, resolve and commit them. Delete topic branch "fixvan12345".
- Push tracking branch "master" to upstream branch "master" in the remote. If others have
pushed since your last pull, you will first need to pull again (and potetially resolve
merge conflicts).
- Your collaborators pull from the remote to merge your commits into their local "master"
branches.
- Rinse, repeat.
================================================================================
Advanced Concepts
================================================================================
Fetching and pulling.
- After your collaborators push their commits to a branch in the remote repo, you will want
to copy their commits into your local repo. You do this by performing a "fetch" from the
remote branch.
- The command 'git fetch' will fetch all commits that you do not yet have from all remote
branches. If new branches have been created in the remote, it fetches them too.
+ As a distributed revision control system, Git's goal is for your local repo to have
everything that's in the remote repo.
+ To fetch just one branch, use 'git fetch REMOTE BRANCH', where REMOTE is the short
name of the remote, and BRANCH is the name of the of the local and remote branch
(assuming they have the same name).
+ If the local and remote branch names differ, use 'git fetch REMOTE LBRANCH:RBRANCH',
where LBRANCH is the name of the local branch, and RBRANCH is the name of the remote
branch.
- A fetch retrieves new commits from the remote repo, including new branches and the
commits in them, but it does _NOT_ alter your local branches, index, or working files.
- You must do a "merge" before you can see the changes from fetched commits in your local
branches, index, and working files. Details about how this works are in the following
sections.
- The pair of operations fetch-then-merge is so common that the "pull" command does both in
one operation for convenience. The command 'git pull' is literally identical to 'git
fetch' followed immediately by 'git merge'.
The role of remote-tracking branches in fetching and merging.
- It's tempting to think that a pull merges upstream branches _directly_ into the
corresponding local branches, but the reality is more subtle, even if the effect is the
same.
+ Recall that a pull is a fetch followed by a merge.
+ Fetches are networked, because they communicate with a remote Git server. Merges are
always local and non-networked. You can do 'git fetch', disconnect from the network,
then do 'git merge', and it works. So where does the fetch put the fetched commits
so they are available to be merged later when there is no network?
+ A fetch makes a read-only copy in your local repo of each remote branch that contains
the fetched commits. This local read-only copy of a remote branch is called a
"remote-tracking branch".
+ A remote-tracking branch exists in your local repo. It is a local cache of the state
of a remote branch at the time of the last fetch.
+ So the answer to the above question is: Fetched commits are put into remote-tracking
branches, where they wait to be merged into your local branches.
- A remote-tracking branch has a name of the form REMOTE/BRANCH, such as "origin/master" or
"doc/update1".
+ REMOTE is the short name of a remote repo, and BRANCH is the name of a remote branch.
+ If your local branch names are the same as their upstream branches, then BRANCH will
also (coincidentally) be the name of the local tracking branch for the remote branch.
+ Be careful! It's easy to see the branch name "origin/bugfix42" and think "That's a
branch named 'bugfix42' in the remote repo named 'origin'", but not so. It's a
remote-tracking branch in your local repo named "origin/bugfix42".
- The purpose of a remote-tracking branch is to give you local read-only access to the
state of a remote branch, so you can examine it and merge commits from it. All you can
do with it is diff against it, merge from it, and update it by fetching the latest state
from the remote.
- A remote-tracking branch can be out-of-date if time has passed since the last fetch from
the remote. The next fetch makes it up-to-date again.
Example of fetching and merging:
- If your local branch "master" has an upstream branch in remote "origin", then you have a
local tracking branch named "master" and a remote-tracking branch named "origin/master":
+- "origin/master"
V
... <- C21 <- C22 <- C23 <- C24 <- C25 <- C26 <- "master"
Above, you've made two commits, C25 and C26, in local branch "master" that did not exist
in the upstream branch at the time of the most recent pull or fetch.
- Then you do a 'git fetch', and things change:
+- C27 <- C28 <- "origin/master"
V
... <- C21 <- C22 <- C23 <- C24 <- C25 <- C26 <- "master"
Now, commits C27 and C28 have been fetched from the remote, because one of your
collaborators pushed them to the remote's "master" branch. Remote-tracking branch
"origin/master" has been updated to reference commit C28.
- Then you merge with 'git merge', and you now have a new merge commit (C29):
+- C27 <- C28 <------+ <- "origin/master"
V |
... <- C21 <- C22 <- C23 <- C24 <- C25 <- C26 <- C29 <- "master"
- Now, a 'git push' will push commits C25, C26, and C29 to the remote, for others to fetch
for themselves. Other collaborators will need to merge if they have their own local
commits parented to C28, just as you needed to merge above.
- It seems coincidental that every remote-tracking branch connects to your local repo's
commit graph at some common ancestor commit in one of your branches. Why is this always
the case?
+ Every collaborator's local repo contains a directed acyclic graph (DAG) of commits
rooted at the same initial commit: C0, the first commit in the first local repo where
the project started.
+ Git maintains a shared consensus among all contributors about the contents and shape
of the commit graph. Some contributors may not see the most recent commits if they
haven't fetched in a while, but if all contributors were to pull, they would all have
the same commit graph (i.e., every commit in every branch).
GitHub adds another level of indirection. It connects remote repos to each other.
- GitHub users can "fork" someone else's remote repo (hosted on GitHub) to create their own
personal copy of the remote. This copy is also a remote repo. Here, the four remotes on
the left are "forked" from the remote on the right:
LOCAL REPO(s) <-> REMOTE REPO <---\
LOCAL REPO(s) <-> REMOTE REPO <----\
+-- REMOTE REPO <-> LOCAL REPO(s)
LOCAL REPO(s) <-> REMOTE REPO <----/
LOCAL REPO(s) <-> REMOTE REPO <---/
- This is how the Linux kernel is developed and how Microsoft accepts contributions to
.NET. The remote on the right is the official repo and is writable only by its
maintainers. The remotes on the left belong to contributors.
- At the time this document was written, Linux has over 11,000 forks, and .NET has almost
2000 forks. The forked remote repos are also hosted by GitHub, so the contributors don't
need to run their own Git servers.
- When a contributor produces a commit of value, he pushes it to his personal remote and
submits a GitHub "pull request" to the maintainers asking them to evaluate the commit.
The maintainers can choose to pull the commit into the official repo.
- Keep in mind that "forking" and "pull requests" are not part of Git. GitHub invented
these things as part of their added layer of functionality.
- GitHub is owned by Microsoft. So Microsoft operates the source control system for the
Linux kernel.
Undoing changes to working files.
- WARNING: The Git commands to undo changes can delete data permanently. The deleted data
is lost unless you have it backed up.
- Suppose you accidentally edit the wrong working file or you modify a working file and
later decide you didn't want to change it. To undo changes to a modified working file do
this:
git checkout FILE
- The above command reverts a working file so it's identical to the copy in the index.
This will destroy your changes, so be careful!
- Note that 'git checkout FILE' overwrites your modifications to FILE, but if you use 'git
checkout BRANCH' to switch to a different branch, your modified working files are NOT
overwritten.
- In other revision control systems a "checkout" operation is typically non-destructive.
In Git, a checkout sometimes overwrites your changes to a working file! For Perforce
users, 'git checkout FILE' is the same as reverting a checked out file, or if the file
was modified but not checked out, it's the same as a "forced get latest".
Undoing changes to the index and local repo.
- Suppose you've done 'git add FILE' or 'git commit FILE', which change the index and local
repo (respectively). Those operations can be undone using Git's 'reset' command. See
https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified for nice graphics to help
understand this section.
- There are three types of reset: soft, mixed, and hard. Soft makes the fewest changes.
Mixed makes more changes than soft. Hard makes the most changes.
+ Recall where files reside in a local repo:
WORKING FILES <-> INDEX <-> LOCAL REPO
+ A SOFT reset changes only the local repo.
+ A MIXED reset changes the local repo and the index, but not the working files.
+ A HARD reset changes the local repo, the index, and the working files. This kind of
reset is dangerous, because it can delete and overwrite working files.
+ Graphically:
+------------+-----------+------ HARD reset changes all three.
| | |
| +-----------+------ MIXED reset changes these two.
| | |
| | +------ SOFT reset changes just this.
| | |
V V V
WORKING FILES <-> INDEX <-> LOCAL REPO
- The "git reset" command takes an existing COMMIT as an argument, and changes the current
branch to reference that commit. It moves a branch reference so that it points to a
different commit. Let's see what that looks like.
+ The specified COMMIT is usually an ancestor of the most recent commit in the branch,
so the effect is to undo all commits in the branch that follow COMMIT.
+ The COMMIT can be identified by a SHA-1 hash (or its first few digits), a branch name
that references the commit, a tag name that references the commit, or one of the
ancestry reference syntaxes supported by Git.
+ Suppose branch "master" has these commits:
... <- C21 <- C22 <- C23 <- C24 <- C25 <- C26 <- "master"
+ Suppose the SHA-1 hash of C24 is f13a7d9ac039e5f6c6eedbb5dad9faf42c82e659. The
command 'git reset --soft f13da7d' (note the '--soft' switch) will change the commit
graph to look like this:
+- C25 <- C26
V
... <- C21 <- C22 <- C23 <- C24 <- "master"
+ Commits C25 and C26 are now deleted from branch "master", which is now a reference to
C24. The deleted commits will continue to dangle off the commit graph for a couple
of weeks, and then they'll be garbage collected.
+ If you decide you want to un-delete commit C25 before it's garbage collected, just do
'git reset --soft C25HASH', where C25HASH is the SHA-1 hash of commit C25. The
commit graph will change to this:
+- C26
V
... <- C21 <- C22 <- C23 <- C24 <- C25 <- "master"
+ Thus, the reset command can both undo commits and restore deleted commits (as long as
they haven't been garbage collected).
- The example above uses the --soft switch, so the change only affects the local repo not
the index or working files.
+ After you delete commits with a soft reset, the changes in those commits remain in
the index and in your working files. Your index and working files are "out-of-date"
with respect to the local repo, because they have data that has since been "undone"
in the local repo.
+ Why do a soft reset? A common reason is that you committed some code that doesn't
work correctly or just isn't ready to be shared with your collaborators yet. After
doing the soft reset, you can do a push to share the reset with collaborators. If
the undone commits where never pushed, then you don't need to push anything.
+ UNDER CONSTRUCTION: Warn about using reset to delete commits that have already been
pushed.
- If you want to delete commits from the local repo _and_ unstage the changed files from
the index, do 'git reset --mixed COMMITHASH'. Mixed resets are the default, so you can
omit the --mixed switch.
+ After a mixed reset, the changes to the files in the deleted commits exist only in
your working files (and for a couple of weeks in the local repo, until garbage
collected). The files in the index exactly match the commit at the tip of the
branch.
+ A mixed reset is the default, because when most people undo a commit they typically
also want to un-stage the associated files, leaving them with only the modified
working files. This is like travelling back in time to a point before the changes
were staged.
- Soft and mixed resets are useful when you change your mind about making a commit.
However, a hard reset is all that and more. It's the most dangerous form of reset.
+ UNDER CONSTRUCTION
- Here's a quick summary soft, mixed, and hard resets:
Type of reset Effect of command 'git reset --TYPE COMMIT'
------------- ---------------------------------------------------------------------
soft 1. Changes the current branch in the local repo to reference COMMIT.
RESULT: This deletes commits after or restores commits before COMMIT,
depending on the direction of the reset.
mixed 1. Changes the current branch in the local repo to reference COMMIT.
2. Changes the files from COMMIT in the index to match the current
branch in the local repo.
RESULT: Same as soft but also unstages related files from the deleted
commits.
hard 1. Changes the current branch in the local repo to reference COMMIT.
2. Changes the files from COMMIT in the index to match the current
branch in the local repo.
3. Changes the files from COMMIT in the working files to match the
index and local repo. WARNING: This overwrites working files!
RESULT: Same as mixed, but also overwrites related working files.
- The reset command works differently if a file is specified.
+ When a file is given, the "soft" step is skipped, because you can't move a branch
reference for just one file. So only the "mixed" and "hard" steps are available.
+ To revert a modified working file _and_ the staged copy of the file to be identical
to the copy in the local repo, do:
git reset --hard FILE
This is more dangerous than 'git checkout FILE', because you may have days or weeks
of work on that file staged in your index, and it is all lost.
- It's important to remember that a reset can only affect the local repo, the index, and
working files, depending on which kind of reset you do. After you push your commits to a
remote repo, a reset cannot help you, because a reset cannot un-push commits!
- So what do you do if you have pushed some commits that you want to undo? You need to
revert your commits.
Reverting commits.
- UNDER CONSTRUCTION
- git revert ...
- Git HowTo: revert a commit already pushed to a remote repository:
http://christoph.ruegg.name/blog/git-howto-revert-a-commit-already-pushed-to-a-remote-reposit.html
Squashing.
- See https://git-scm.com/book/en/v2/Git-Tools-Reset-Demystified#Squashing for how to use
"git reset --soft" for squashing commits.
- UNDER CONSTRUCTION
Rebasing.
- UNDER CONSTRUCTION
Plumbing and porcelain.
- UNDER CONSTRUCTION
================================================================================
Git Command Quick Reference
================================================================================
These entries are ordered roughly in the order you need to use the commands when setting up Git
and starting work on a new repository.
Configuration commands:
git config [ --local | --global | --system ] OPTION VALUE
Sets configuration OPTION to VALUE. With --local, the option is written to the local
repo's .git/config file, and they only apply to that repo. With --global, the option
is written to the user's personal ~/.gitconfig file. With --system, the option is
written to the system-wide Git config file (e.g., /etc/gitconfig). System options
apply to all users and all repos on the machine. Global options apply to all repos
used by a given user, and they override system options. Local options apply to just
one repo, and they override global and system options. The default is --local.
Type "git help config" for a list of configuration options. It is recommended to at
least set these global options before using Git:
git config --global user.name "Joe Smith"
git config --global user.email "[email protected]"
git config --global core.editor vim
git config --list [ --local | --global | --system ]
Shows the current configuration options that apply to the current repo and user. With
--local, --global, or --system, this show only the specified options.
git config [ --local | --global | --system ] --unset OPTION
Removes the given option.
Command aliases:
git config [ --local | --global | --system ] alias.last "log -1 HEAD"
git config [ --local | --global | --system ] alias.logbranches "log --oneline --decorate --graph --all"
git config [ --local | --global | --system ] alias.unstage "reset HEAD"
Defines command aliases.
References:
HEAD always points to the currently checked out branch.
HEAD^ references the previous commit on the current branch.
HEAD~N references the Nth most recent commit on the current branch.
Thus, HEAD~1 is the same as HEAD^.
Create a new empty local repo:
- Make a new directory, cd into it, then type 'git init'.
git init
After this, use 'git add ...' to add files or 'git remote add ...' to add a remote repo
from which you can pull commits. This is not needed if you clone a remote repo.
Clone a remote repository:
git clone REPO-URL
Creates a local repo in the current directory cloned from the remote repo identified by
REPO-URL. Give an HTTPS-style URL if you need read-only access to the remote repo.
Give a GIT-style or SSH-style URL if you plan to push to the remote repo. See
https://tinyurl.com/githubssh for how to configure SSH for access to a remote repo.
This automatically creates a remote-tracking brach for origin/master.
TODO: Show HTTPS and SSH URL syntax.
Status of the local repo:
git status [ -s ]
Shows status of modified working files and staged files. Also shows if the local repo
is ahead or behind the remote repo in terms of commits. With -s, shows short status
where column 1 represents staged files, and column 2 represents working files.
Add files to the local repo:
git add FILE-OR-DIR ...
Begins tracking the given files, stages modified working files, or marks a merge
conflict as resolved in the given files. If a directory is given, all the files in it
are added (recursively including all subdirectories). If FILE-OR-DIR is a symbolic
link, the symlink itself is added not what it points to.
Remove files from the local repo:
git rm [ --cached ] FILE ...
Stages deletion of FILEs. After this, you must commit to remove it from the repo.
This removes FILE from the working and staged files. Option --cached leaves the files
in the working files.
Rename a file:
QUESTION: How is a directory renamed?
git mv OLDFILE NEWFILE
Syntactic sugar for: git rm --cached OLDFILE; mv OLDFILE NEWFILE; git add NEWFILE
Stage/unstage modified working files:
git add FILE ...
Stages modified tracked FILEs. Must later commit to get the changes into the local
repo.
QUESTION: Do these reset commands affect the local repo or just staged files? Maybe move
these to the 'Undoing changes' section below.
git reset HEAD [ FILE ... ]
git reset --mixed HEAD [ FILE ... ]
Unstages FILEs (or all files if FILE is omitted) by making the index match the local
repo for this file. Does not modify or delete any working files.
Differences:
NOTE: See the gitrevisions man page (or type "git help revisions") for the syntax for
specifying revisions and ranges.
TODO: Maybe integrate info from https://raw.githubusercontent.com/Osse/git-stuff/master/dots.txt
here.
git show COMMIT
Show the diff for COMMIT.
git diff
Compares working files with staged files.
git diff HEAD
Compares working files with the latest commit (HEAD) in the local repo.
git diff --staged
Compares the staged files with the latest commit.
git diff --stat
Show a summary of changes instead of the modified lines.
git diff <options> -- FILE
Same as above commands but limit output to FILE.
git diff COMMIT
Compares working files with COMMIT in the local repo.
git diff COMMIT1 COMMIT2
Compares COMMIT1 with COMMIT2 in the local repo.
git diff COMMIT1..COMMIT2
Same as previous. If <commit> on one side is omitted, it defaults to HEAD.
git diff HEAD^ HEAD
Compares the commit before the most recent commit with with the most recent commit in
the local repo.
git diff BRANCH1..BRANCH2
Compares the tips of BRANCH1 and BRANCH2. Not very useful. Typically, you want the
X...Y form (below).
git diff BRANCH..REMOTE/BRANCH
Compares local BRANCH with REMOTE/BRANCH.
git diff BRANCH1...BRANCH2
Compares BRANCH2 to the common ancestor of BRANCH2 and BRANCH1.
Commit changes to the local repo:
git commit [ -m MSG ]
Commits all staged files. Does not stage or commit any working files. Launches an
editor if -m not given.
git commit [ -m MSG ] FILE ...
Stages and commits the given working files. Launches an editor if -m not given. This
can leave the index out-of-sync with the local repo and working files.
git commit -a [ -m MSG ]
Stages and commits all modified working files. No files can be specified with -a.
Launches an editor if -m not given.
git commit --amend
Undoes the last commit, puts the working files back as they were with the changes
staged and the commit message ready to edit next time you commit. IMPORTANT: Do NOT
ammend the last commit if it has already been pushed to a remote repo, because this
changes the SHA-1 hash of the last commit.
View the commit log:
git log
Show all commits to the local repo.
git log -p
Show all commits to the local repo with diffs for each change.
git log BRANCH
Show all commits in BRANCH.
git log BRANCH1 ^BRANCH2
Show all commits in BRANCH1 that are not in BRANCH2.
git log REMOTE/BRANCH..HEAD
Show all commits to the local repo that have not yet been pushed to REMOTE/BRANCH.
git log --author=Andy
Show commits where the author name contains "Andy".
git log --grep=REGEX
Show commits where the commit message matches REGEX.
git log -G REGEX
Show commits where the diff contains text that matches REGEX.
git log --since=2.months.ago --until=1.day.ago
Show commits by date range.
git log --since=2.months.ago --until=1.day.ago --author=Andy -G "something" --all-match
Mix and match. Use --all-match to AND the options (default is OR).
git blame FILE
Show who modified each line of FILE at what time along with the commit hash.
Revert changes:
git reset --soft COMMIT
COMMIT defaults to HEAD. Omitting COMMIT does nothing, because the branch name is
already what HEAD references. It's the same as "git reset --soft HEAD", which is a
no-op.
git reset COMMIT
git reset --mixed COMMIT
COMMIT defaults to HEAD. Also, --mixed is the default operation.
git reset --hard COMMIT
COMMIT defaults to HEAD. This unstages all staged bug not-yet-committed files and
overwrites all working file to match the index and the branch in the local repo.
git reset --mixed COMMIT FILE ...
Unstages FILE, leaving the copy in the index identical to the branch in the local repo.
git reset --hard COMMIT FILE ...
Unstages FILE and makes the working copy match the index and local repo, destroying
your local edits!
Delete untracked and/or ignored files:
git clean [ -d ]
Remove untracked files that are not ignored. With option -d, remove any directories
that become empty as a result of deleting files.
git clean -x
Also remove ignored files in addition to untracked files. This is useful for removing
build products (executables, libraries, etc.).
git clean -n ...
Show what would be removed without removing anything.
Stash working/staged files:
git stash [ --include-untracked | --all ]
Stashes all modified working files and staged files. Do this before switching between
branches to avoid losing work. Option --include-untracked means also stash untracked
files. Option --all means also stash untracked and ignored files.
git stash --keep-index
Stashes all modified working files but NOT staged files.
git stash list
Lists stashes, showing which commit was current for each.
git stash apply
Restores working files from the most recent stash (merging with working files if
necessary). This does NOT restore staged files. This does NOT pop the most recent
stash off the stack.
git stash apply --index
Restores working and staged files from the most recent stash (merging with working
files if necessary. This restores staged files too. This does NOT pop the most recent
stash off the stack.
git stash pop
Restores working files from the most recent stash (merging with working files if
necessary). This does NOT restore staged files. This pops the most recent stash off
the stack.
git stash pop --index
Restores working and staged files from the most recent stash (merging with working
files if necessary. This restores staged files too. This pops the most recent stash
off the stack.
git stash drop stash@{0}
Drops stash@{0} from the stash.
git stash branch NEWBRANCH
Creates a branch, checks out the commit where the most recent stash was made, applies
the stash to the branch, and drops the stash (if the stash was applied to the branch
cleanly).
View remote repo(s):
git remote [ -v ]
Show currently configured remote repos for this repo. Option -v shows the remote repo
URL.
git remote show REMOTE
Show event more info about REMOTE, including the remote's HEAD branch, fetch/push URLs
and which local branches track remote branches.
Add/remove a remote repo to/from the local repo:
- Having multiple remote repos associated with one local repo simply means your local repo
can have tracking branches for remote branches in different remote repos. In each of
these tracking branches, you can push/pull from any of the remote branches. Example: One
remote repo might be the main project repo on Github or a corporate server. Other remote
repos might be coworkers' clones of the main project.
- QUESTION: How is the default remote chosen for commands? How is the default switched?
git remote add [ SHORTNAME ] CLONEURL
Link local repo to remote repo. QUESTION: Is this necessary after "git clone ..."? It
doesn't seem to be. Maybe only after "git init"?
git remote rm REMOTE
Removes the remote repo. All remote-tracking branches and configuration settings for
the remote are removed.
git remote rename OLDREMOTE NEWREMOTE
Renames the remote repo. QUESTION: What exactly does this do and why would you do it?
Is this purely a local change?
Pull commits from a remote repo:
git fetch [ --dry-run ] [ REMOTE ] [ BRANCH ]
Fetch the current (or specified) branch from the current (or specified) remote repo
into the local repo. Working files are not changed. You need to do 'git merge ...'
to update working files after fetching, then 'git commit ...' to commit the results of
the merge. With --dry-run, show what would be done without doing it. QUESTION: Does
this update staged files?
git pull [ REMOTE ] [ BRANCH ]
Fetch, merge, and commit the current (or specified) branch from the current (or
specified) remote repo. If BRANCH is given, pull from that BRANCH instead of the
current HEAD. QUESTION: What happens if the specified BRANCH is not the current
branch?
Push commits to a remote repo:
git push [ REMOTE ] [ BRANCH ]
Pushes the current (or specified) branch to the current (or specified) remote repo from
the local repo. If another user has pushed since your last push, you are told to first
pull from the current remote repo and resolve any merge conflicts.
git push REMOTE BRANCH
Pushes BRANCH to repo REMOTE.
Create a branch:
git branch BRANCH
Creates BRANCH in the local repo with BRANCH referencing the commit referenced by HEAD.
git checkout -b BRANCH
Creates BRANCH in the local repo and checks it out.
git checkout -b BRANCH REMOTE/BRANCH
git checkout --track REMOTE/BRANCH
Creates BRANCH in the local repo based on the current local state of remote-tracking
branch REMOTE/BRANCH. The local branch is called a "tracking branch", because it
tracks a remote branch. You push and pull commits between the local branch and the
remote branch.
View branches:
git branch
Show branches in the local repo. Only fetched branches are shown.
git branch -r
Show all remote branches.
git branch -a
Show all branches (local and remote).
git branch -v
Show branches in the local repo with latest commit in each branch.
git branch -vv
Same as previous but also shows name of upstream branch (if there is one).
Switch working files between branches:
git checkout BRANCH
Check out local repo's BRANCH (e.g. "master"). This is how you switch working files
between branches. You may be told to "stash" your uncommitted modified working and
staged files before doing this.
Rename a local branch:
git branch -m OLDBRANCH NEWBRANCH
Renames a branch in the local repo. This is equivalent to creating a new branch that
is a copy of the old branch and deleting the old branch, like this:
git checkout -b NEWBRANCH OLDBRANCH // Copy old branch to new branch.
git branch -d OLDBRANCH // Delete old branch.
Delete a branch:
git branch -d BRANCH
Deletes BRANCH from the local repo.
git push REMOTE --delete BRANCH
git push REMOTE :BRANCH
Deletes BRANCH from the remote repo.
git remote prune
Deletes local branches that no longer exist on the remote repo. QUESTION: What is one
of the deleted branches is the current branch?
Merge a branch into another branch:
o Merging is completely local. If your remote-tracking branches are out-of-date, a merge
will not fetch up-to-date state. You need to do fetch from the remote branch to stay
up-to-data. A pull will do a fetch-and-merge.
- To do a merge, first checkout the destination branch using 'git checkout DESTBRANCH'.
Then use Git's 'merge' command to merge from the source branch (see below).
- If there are merge conflicts, resolve them manually, then do 'git add FILE ...' to mark
the conficted files as resolved. Then commit the resolved files eventually.
git merge [ --no-commit ] SRCBRANCH
Merge SRCBRANCH into HEAD. Unless --no-commit is given, this commits the result of the
merge to the local repo if there were no conflicts.
git merge --squash SRCBRANCH
Produce the working and staged file state as if a real merge happened (except for the
merge information), but do not actually make a commit. This allows you to create a
single commit to the current branch whose effect is the same as merging another branch.
git add FILE
Marks a merge conflict in FILE as resolved. You do this after manually resolving the
conflict by editting FILE. You need to commit the conflict fix after this.
Emacs hacks -- please ignore ...
Local variables:
fill-column: 95
indent-tabs-mode: nil
paragraph-separate: "[ ]*$\\| git .*$"
paragraph-start: "[ ]*$\\| git .*$\\| +[-+*] $"
eval: (my-n-column-tabs 4)
eval: (auto-fill-mode 0)
End:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment