-
Notifications
You must be signed in to change notification settings - Fork 4
Git
Traditional Version Control Systems (VCS), like SVN, have a very simple semantics where information changes recorded are linear. In contrast, Git provides non-linear semantics and an user interface (UI) that is not very intuitive, which can create confusion when we try to apply the same model as a linear VCS. Therefore, it is important to understand some basic concepts about Git in order to understand better what the different operations are doing to the repository.
Git is a non-linear VCS, information stored within the a Git repository is representable as a graph where each node is product of performing some operation on the graph. The database that stores the graph is immutable and append-only.
In the image above we see an example repository, displaying each commit alongside it's corresponding node in the commit tree and and it's unique hash which give a way to address each node.
References (or "refs") exists to make interaction with the commit tree easier. A reference is just a human readable label that contains a pointer to right commit hash. Branches, tags, remotes are all forms of refrences.
💡 References, do not hold the information in Git database; it is held within the commit tree, and as we mentioned previously the commit tree is immutable. Sometimes, we might operate on the Git repo and leave it in undesirable place from which we will want to back track. It is important to remember that old state is still inside the tree and all we have to do is change the references to right commit address. Later we will show a few techniques to achieve this.
Git provides a special reference named HEAD
for the current address that refers to the state that is checked out inside the working directory (See below)
A Git repo is composed by three different states that are independent but related:
- Working Directory: when a new Git repository is cloned what the user sees is within a directory all the files that Git repository contains.
- Staging Index: provides an intermediate space where the user can add changes from the working directory, without adding them to the commit tree.
- Commit Tree: when changes in the staging index are ready, they are added to the commit tree and given a hash address.
Create local copy of a repo. It is important to highlight that the local copy is complete and independent from its source. Git supports various protocols and can perform copies from local filesystems to remote servers via the network:
git clone [<options>] <repo> [<dir>]
If [<dir>]
is missing, Git will create a new directory in the current working directory with the same name as the repo. The repo name depends on the protocol being used.
Useful examples:
# Local filesystem clone
git clone /Path/To/Git/Repo/Dir
# Remote HTTPS clone from GitHub
git clone https://github.com/radiasoft/devops.git
# Remote SSH clone from GitHub
git clone ssh://git@github.com:radiasoft/devops.git
git-clone
man page.
Branches provide a way to track different set of changes on the same repo, without having to deal with possible conflicts arising from concurren modifications to the same areas in the repo. As we explained previously, a branch is only a reference within the repo, that points at the latest commit that has occurrent within a branch of the commit tree.
In our example repo, in the image above, we start with two branches, my_branch
and master
, both initially pointing to the same address (2d52a68
). After changes in each branch occur separately we see they diverged into addresses 243742d
and 04d25ed
respectively.
The git branch
command allows to create a new branch or list the available branches.
Useful examples:
# Create new branch challed 'branch_name' pointing
# to the same address
# that points to HEAD
git branch branch_name
# List local branches
git branch
# List remote branches
git branch -r
# Delete branch named 'branch_name'
git branch -d branch_name
# Rename branch 'branch_name' to 'new_branch_name'
git branch -m branch_name new_branch_name
# Make the current branch track 'branch_name' branch
# within the 'radiasoft' remote
git branch -u radiasoft branch_name
git-branch
man page.
Change the HEAD
reference to point to a different address, affecting the working directory accordingly.
💡 Checkout can also be used to undo changes in the working directoy.
git checkout [<options>] <branch>
Useful examples:
# Checkout the latest commit within the master branch
git checkout master
# Checkout an address within the git repo, and label it
# as a branch called 'new_branch_name'
git checkout -b new_branch_name 2d52a68
# Force a checkout, throwing away local modifications
git checkout -f master
# Revert changes in file README.md
git checkout path/to/README.md
# Revert README.md to the sate in branch `my_branch`
git checkout my_branch -- path/to/README.md
git-checkout
man page.
Adds changes from the working directory to staging index. The git add
provides the interface do staging.
Useful examples:
# Add a new file to be tracked, or add new changes to
# an already tracked file to the staging index
git add path/to/file
# Add to the staging index all the changes of already
#tracked files
git add -u
git-add
man page.
Store changes within the commit tree.
💡 Changes con come either from the was is in the staging index or directly from the working directory, depending on how the command is invoked.
💡 Each commit requires a commit message to document the changes being recorded. If a commit message is not provided on the command line, Git will provide an editor where the commit message can be written.
Useful examples:
# Commit the staging index
git commit
# Commit the staging index and provide a commit message
git commit -m 'this is my commit message'
# Commit all changes in tracked files
git commit -a
# Commit changes within a specific file
git commit /path/to/file
git-commit
man page.
Provide alias for an external repository to track.
💡 When a repo is cloned, Git will assign by default the name origin to the repo
Useful examples:
# Add a remote repo in the same filesystem as 'fs_remote'
git remote add fs_remote /path/to/repo
# Add a github repo as 'radiasoft'
git remote add radiasoft ssh://git@github.com:radiasoft/devops.git
# Rename remote from 'old_name' to 'new_name'
git remote rename old_name new_name
# Remove remote 'to_remove'
git remote remove to_remove
# List registered remotes with their URIs
git-remote
man page.
Synchronize locate commit tree state with a remote repository.
💡 Git is a Distributed VCS (DVCS), which means each copy is complete and indepent from one antoher. Fetching will retrieve any new changes contained in the remote repo and make them available within the local commit tree.
💡 When a new remote is fetched, all the branches of the remote will be available locally using the following naming schema remote_name/remote_branch_name
.
Useful examples:
# Fetch remote named 'radiasoft'
git fetch radiasoft
# Fetch all remotes
git fetch --all
# Fetch tags from remote
git fetch --tags
git-fetch
man page.
Creates a new commit in local tree that is product of joining the state of HEAD
and the state pointed by another address or reference in the tree.
Useful examples:
# Merge local branch named 'my_branch' to `HEAD`
git merge my_branch
# Merge branch named 'their_branch' from remote
# `radiasoft` to `HEAD`
git merge radiasoft/their_branch
git-merge
man page.
Performs a fetch and merge in one step.
Useful examples:
# Pull the remote tracking branch into the current
git pull
# Pull 'branch_name' branch into from 'radiasoft'
# remote into the current
git pull radiasoft branch_name
git-pull
man page.
Send changes from the local branch to a remote repository.
Useful examples:
# Push to the remote tracking branch
git push
# Push and set as tracking branch
git push -u radiasoft destination_branch
# Git push to 'radiasoft' remote the contents from
# local branch 'my_branch' into the remote branch
# 'their_branch'
git push radiasoft my_branch:their_branch
# Delete remote branch named 'their_branch'
git push radiasoft :their_branch
git-push
man page.
See the following links for some example operations:
Make sure you checkin a "subject" which is up to 50 characters followed by a newline if you need more space to describe the checkin. Don't just write a long line. You can hit return if you are on the command line, e.g.
git commit -am 'sometimes amazon screws up contents
Had to check numberInner to see if it exists'
This will show up as this in GitHub:
sometimes amazon screws up contents …
robnagler committed 4 minutes ago
rpm -U http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm
perl -pi.bak -e '!$first && s/(?<=enabled = )0/1/ && $first++' /etc/yum.repos.d/rpmforge.repo
yum install -y git
Branch xyz
needs to be merged into the master
, and there are no conflicts, that is, xyz
is ahead of master
and not "behind":
$ git checkout xyz
$ git checkout master
$ git merge xyz master
$ git push
$ git branch -d xyz
$ git push origin --delete fix
If there are conflicts, you'll need to do more work:
$ git checkout xyz
$ git checkout master
$ git merge xyz master
Automated merge did not work.
$ git checkout xyz
# fix the conflicts so a merge will succeed
$ git commit -am 'fix merge conflicts from xyz to master'
$ git push
$ git merge xyz master
Trying simple merge with fix
Already up-to-date with master
Merge made by octopus.
$ git push
$ git branch -d fix
$ git push origin --delete fix
When you’ve prepared the index you want, use git commit to store it as a new commit. Use git status first to check the files involved, and git diff --cached to check the actual changes you’re applying. git diff alone shows any remaining unstaged changes (the difference between your working tree and the index); adding --cached (or the synonym --staged) shows the difference between the index and the last commit instead (i.e., the changes you’re about to make with this commit).
git reset --patch
With git reset --patch you can be even more specific, interactively selecting portions of your staged changes to unstage; it is the reverse of git add -p. See “Discarding Any Number of Commits” for other options.
- Use git add (with various options) to stage a subset of your changes.
- Run git stash --keep-index. This saves and undoes your outstanding, unstaged changes while leaving your staged changes in the index alone, resetting your working tree to match the index.
- Examine this working tree state to make sure your selection of changes makes sense; build and test your software, for example.
- Run git commit.
- Now, use git stash pop to restore your remaining unstaged changes, and go back to step 1. Continue this process until you’ve committed all your changes, as confirmed by git status reporting “nothing to commit, working directory clean.”
git log --graph --oneline
* f4dcb5b Merge remote branch 'origin/xyz' into xyz
|\
| * 2bc662f Merge remote branch 'origin/xyz' into xyz
| |\
| | * dfdac18 a new file
* | | f9d4689 a new file
|/ /
* | 7d482cf remove
* | 158f30e merge conflict
* | 3ddd434 cause conflict with xyz
|/
* 30f4b62 cleanup
* de74c31 fixed merge xyz to master
* 8224c26 fixed merge xyz to master
git config --global branch.autosetuprebase always
http://documentup.com/skwp/git-workflows-book http://chimera.labs.oreilly.com/books/1230000000561/index.html http://scottchacon.com/2011/08/31/github-flow.html http://git-scm.com/book/en/v2
git checkout path/to/modified/file
will drop all the outstanding changes in a file and revert it to the state pointed by HEAD
or the value in the staging index.
If you want to remove previously staged changes while still keeping the changed file intact, you can do git reset HEAD path/to/file
.
git merge --abort
allows you to drop an incomplete merge and return to the previous HEAD
value before the merge started.
git revert <reference or sha>
will generate a new commit on top of the current HEAD
.
Changing a single file to different commit requires the following steps:
git reset <reference or sha> -- path/to/file
git checkout path/to/file
When performing history rewriting, typically git reset
, we overwrite the reference pointers to a new Git commit address. When going back in history, git will change HEAD
to new address and discard, from the point of view of references, all the future changes.
For example, given a repository with the following structure, for branch master
:
* 1d7261c new
* 2512b58 Merge branch 'branch'
|\
| * 8ecb709 Change a and c
* | f3b84b3 Change b and c
* | 44dfdec Merge branch 'branch'
|\ \
| |/
| * 153740e add c
* | 7d7d0a2 add b
|/
* 46de418 add a
If we perform a reset
to return the repo to a previous commit:
git commit reset --hard HEAD~3
HEAD is now at 44dfdec Merge branch 'branch'
The repo will show the following tree for master
:
* 44dfdec Merge branch 'branch'
|\
| * 153740e add c
* | 7d7d0a2 add b
|/
* 46de418 add a
Comparing with the previous graph, we can see that there is a lot of state missing. We have made HEAD
for master
point to 44dfdec
.
💡 As we have mentioned previously, the Git commit tree is immutable, so while we cannot see the future state after we effectively traveled back in commit-time with git reset
, the state is still stored within the repo.
If we need to recover some of that state, we can use git reflog
, which will return a list of the previous commands issued within the repo and the HEAD
value for that command:
git reflog
44dfdec HEAD@{0}: reset: moving to HEAD~3
1d7261c HEAD@{1}: reset: moving to 1d7261c
2512b58 HEAD@{2}: checkout: moving from f3b84b38591c2b51f79cf0dc354d024dbfb5d7d4 to master
f3b84b3 HEAD@{3}: checkout: moving from master to HEAD~1
2512b58 HEAD@{4}: reset: moving to HEAD~1
1d7261c HEAD@{5}: commit: new
2512b58 HEAD@{6}: commit (merge): Merge branch 'branch'
f3b84b3 HEAD@{7}: commit: Change b and c
44dfdec HEAD@{8}: checkout: moving from branch to master
8ecb709 HEAD@{9}: commit: Change a and c
153740e HEAD@{10}: checkout: moving from master to branch
44dfdec HEAD@{11}: checkout: moving from branch to master
153740e HEAD@{12}: reset: moving to HEAD~1
6d94462 HEAD@{13}: checkout: moving from master to branch
44dfdec HEAD@{14}: reset: moving to HEAD~1
dd78fce HEAD@{15}: checkout: moving from branch to master
6d94462 HEAD@{16}: commit: update c
153740e HEAD@{17}: checkout: moving from master to branch
dd78fce HEAD@{18}: commit: update c
44dfdec HEAD@{19}: checkout: moving from 7d7d0a244a58a95c4017c497199305011010a335 to master
7d7d0a2 HEAD@{20}: checkout: moving from master to HEAD~1
44dfdec HEAD@{21}: merge branch: Merge made by the 'recursive' strategy.
7d7d0a2 HEAD@{22}: checkout: moving from branch to master
153740e HEAD@{23}: commit: add c
46de418 HEAD@{24}: checkout: moving from master to branch
7d7d0a2 HEAD@{25}: commit: add b
46de418 HEAD@{26}: commit (initial): add a
We can see that the HEAD@{0}
in the reflog points to our current HEAD
. The previous entry, HEAD@{1}
is HEAD
before issuing the last command; we can match the SHA addresss, 1d7261c
to the first graph in this section.
If we wanted to return that point in commit-time, we can just issue git reset --hard 1d7261c
and our current HEAD
will go back to where we were initially.
git-reflog
man page.
If you are running Vagrant with Linux (guest) on Windows (host) and you want to share your git folder, you will need to turn off filemode checking. If you don't, Git will see all files as modified, because shared files get permissions 777, always.
To fix this, you'll need to:
git config --global core.filemode false
You'll need to run this on each of the shared repos:
cd <some-repo>
git config --local --unset core.filemode
You could potentially just set filemode
to false on each of the repos,
but it's easier to set it globally in Vagrant.
Git only tracks the execute bit so if you need to set that, you can do so explicitly:
git update-index --chmod=+x <some-file>