Git is a distributed version control system supporting non linear workflows, that focuses on speed and data integrity. Version control systems are essential for any form of distributed, collaborative development. Git is developed and maintained by Linus Torval the creator of the Linux Kernel. For more information, the page Git Basics explains all the internal workings of git.
Starting with git
To work git needs a .git
repository, where it will store objects,
references and other metadata. To get that .git
repository you can
either cd into your project and call the command git init
or clone a
new project you have created on a public repository such as
github
$ mkdir new-project
$ cd new-project
$ git init
Initialized empty Git repository in /home/fred/new-project/.git/
$ ls -a
. .. .git
You can create a new project on github and then use the command
git clone
to create the git repository on your machine. Public
repositories such as github will also help you create a .gitignore
file containing the working files that you don't want to be managed by
git as well as a LICENCE
and README
file.
$ git clone https://github.com/0x9900/new-project.git
Cloning into 'new-project'...
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
Checking connectivity... done.
$ cd new-project
$ ls -a
. .. .git .gitignore LICENSE README.md
If you have not cloned an existing repository and want to connect your
local repository to a remote server, you need to add the remote URL
with git remote
$ git remote add origin https://github.com/0x9900/new-project.git
If the remote server has already been set, you will get an error.
$ git remote add origin [email protected]:0x9900/new-project.git
fatal: remote origin already exists.
$ git remote -v
origin https://github.com/0x9900/new-project.git (fetch)
origin https://github.com/0x9900/new-project.git (push)
You can change the remote server using the option set-url
$ git remote set-url origin [email protected]:0x9900/new-project.git
$ git remote -v
origin [email protected]:0x9900/new-project.git (fetch)
origin [email protected]:0x9900/new-project.git (push)
Now you are able to push your changes to the selected remote server
Note: To use a url such as [email protected]:0x9900/new-project.git you need to setup your ssh key on github. On your github profile, click on the Add SSH key button in the SSH Keys section.
Working with git
Your local repository consists of three "branches" maintained by git:
- The Working Directory holds the actual files.
- The Index acts as a staging area.
- The HEAD points to the last commit.
Git is about snapshots. A series of changes (snapshots) is saved into the Index.
The first step in the git workflow it tracking changes and staging
changes to files. Using the command git add
will add your changes
into the Index. Example: git add <directory>
, git add
<filename>
, git add *
To actually commit these changes to HEAD use git commit
$ git add hello.go
$ git status --short
A hello.go
$ git commit -m "Saying hello to the world"
[master 901d2d0] Saying hello to the world
1 file changed, 7 insertions(+)
create mode 100644 hello.go
The changes are committed from Index to the HEAD in your local working copy of the repository. These changes have not been committed to the remote repository yet.
To send those changes to the remote repository, the command is git push
.
$ git push origin master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 353 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To [email protected]:0x9900/new-project.git
7d19615..901d2d0 master -> master
Change master to whatever branch you want to push your changes to.
Branching
Branches are used to develop features isolated from each other. Once these features are ready, they can then be merged to the master branch.
The master branch is the "default" branch when you create a repository.
Create a new branch named "myfeature" and switch to it using
$ git checkout -b myfeature
Switched to a new branch 'myfeature'
To switch back to the master branch git checkout master
.
To delete a branch `git branch -d myfeature
The new branch is not available to others until you push the branch to
your remote repository with: git push origin myfeature
When you are done developing your new feature you can see your changes
with the command git diff
$ git diff
diff --git a/hello.go b/hello.go
index 11f7c72..7bfe586 100644
--- a/hello.go
+++ b/hello.go
@@ -4,4 +4,5 @@ import "fmt"
func main() {
fmt.Printf("hello, hello, hello, world\n")
+ fmt.Printf("hello and to the people of the moon\n")
}
Once you are satisfied with your new feature you can commit them to your branch.
$ git commit -am 'do not forget the people of the moon'
[myfeature d42fdce] do not forget the people of the moon
1 file changed, 1 insertion(+)
Update & Merge
Anytime you want to update your local repository with the changes that
other developers have commited to the master branch, execute git
pull
To merge another branch into your active branch, use git merge
. git
will try to automatically merge the changes. Sometimes, when the same
part of your code has been modified in two branches you will get a
merge conflict. You are responsible for resolving the conflicts
manually by editing the files shown by git.
After resolving the conflicts, you need to mark them as merged with
git add
Before merging changes, you can preview them by using
git diff
In the following example, we show the differences between the branches myfeature and master. Then we checkout master, merge the changes from myfeature into master, and finally push these changes to the origin (github)
$ git branch
master
* myfeature
$ git diff master myfeature
diff --git a/hello.go b/hello.go
index 11f7c72..7bfe586 100644
--- a/hello.go
+++ b/hello.go
@@ -4,4 +4,5 @@ import "fmt"
func main() {
fmt.Printf("hello, hello, hello, world\n")
+ fmt.Printf("hello and to the people of the moon\n")
}
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
fred[844](master)$ git merge myfeature
Updating 901d2d0..d42fdce
Fast-forward
hello.go | 1 +
1 file changed, 1 insertion(+)
$ git push origin
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 367 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To [email protected]:0x9900/new-project.git
901d2d0..d42fdce master -> master
logs
You can always look at the repository history using: git log
Git log has a lot of options, use git log --help
to configure the
output the way you like it.
$ git log
commit d42fdcef527c792cf5b088a0395c4fae8de366de
Author: Fred C <[email protected]>
Date: Sun Aug 23 15:45:52 2015 -0700
do not forget the people of the moon
commit 901d2d018586976fac3228356d48e28408883198
Author: Fred C <[email protected]>
Date: Sun Aug 23 12:03:29 2015 -0700
Saying hello to the world
commit 7d1961565e19c9c06bf3218e1825f093b3d2fdd5
Author: Fred Cirera <[email protected]>
Date: Sat Aug 22 10:34:58 2015 -0700
Initial commit
$ git log --graph --pretty=oneline --abbrev-commit --decorate --all
* d42fdce (HEAD -> master, myfeature) do not forget the people of the moon
* 901d2d0 (origin/master, origin/HEAD) Saying hello to the world
* 7d19615 Initial commit
It is possible to create an alias for long and hard to remember
commands such as git log --graph --pretty=oneline --abbrev-commit
--decorate --all
. The aliases should be stored in the .gitconfig
file in your home directory.
$ cat ~/.gitconfig
[user]
name = Fred C
email = fred@myamail.com
[color]
ui = auto
[alias]
co = checkout
st = status
ci = commit
log = log --topo-order --first-parent --graph --decorate
lgstat = log --topo-order --stat --abbrev-commit
lg = log --graph --decorate --pretty=oneline --abbrev-commit
lgall = log --graph --decorate --pretty=oneline --abbrev-commit --all
hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
wstat = log --author=$USER --format="- %B" --since=-7days --reverse
Tagging
When you want to release a new version of your software you can create a tag name.
$ git tag -a v1.0.0 -m 'The first release of our software'
$ git describe --tags
v1.0.0
$ git show v1.0.0
tag v1.0.0
Tagger: Fred C <[email protected]>
Date: Sun Aug 23 16:22:51 2015 -0700
The first release of our software
. . .
Note: You can crypto-sign your tag with the option
-u <keyid>
for security reasons
Rollback changes
You have started to develop a new feature, edited some files, and
after a few hours of work you realize that was wrong. You can undo the
changes you've made in a file using the command git checkout --
<filename>
, or git checkout -- <dirname>
for undoing the changes at
the directory level.
$ vi hello.go
$ git diff
diff --git a/hello.go b/hello.go
index 7bfe586..fc4f54d 100644
--- a/hello.go
+++ b/hello.go
@@ -5,4 +5,5 @@ import "fmt"
func main() {
fmt.Printf("hello, hello, hello, world\n")
fmt.Printf("hello and to the people of the moon\n")
+ fmt.Printf("and bye, bye\n")
}
$ git checkout -- hello.go
$ git diff
This will only undo the changes on files that haven't been committed
yet. If you have already committed your changes you need to reset your
branch to a previous state with git reset --hard
.
$ vi hello.go
$ git commit -am 'say bye'
[master a1c8631] say bye
1 file changed, 1 insertion(+)
$ git log --graph --pretty=oneline --abbrev-commit --decorate --all
* a1c8631 (HEAD -> master) say bye
* d42fdce (tag: v1.0.0, origin/master, origin/HEAD, myfeature) do not forget the people of the moon
* 901d2d0 Saying hello to the world
* 7d19615 Initial commit
$ git fetch origin
$ git reset --hard origin/master
HEAD is now at d42fdce do not forget the people of the moon
$ git log --graph --pretty=oneline --abbrev-commit --decorate --all
* d42fdce (HEAD -> master, tag: v1.0.0, origin/master, origin/HEAD, myfeature) do not forget the people of the moon
* 901d2d0 Saying hello to the world
* 7d19615 Initial commit
The option reset --hard
will drop any changes you have made and
reset your branch to the state of origin/master
. Without the
--hard
the command will have the same effect, your branch history is
back the the state of origin/master
, but all the changes in the
files are still here.
# the new feature
$ vi hello.go
$ git commit -am 'say bye'
[master fcc9e67] say bye
1 file changed, 1 insertion(+)
# the feature is committed
$ git log --graph --pretty=oneline --abbrev-commit --decorate --all
* fcc9e67 (HEAD -> master) say bye
* d42fdce (tag: v1.0.0, origin/master, origin/HEAD, myfeature) do not forget the people of the moon
* 901d2d0 Saying hello to the world
* 7d19615 Initial commit
# Ooops that feature was a mistake (roll it back)
$ git fetch origin
$ git reset origin/master
Unstaged changes after reset:
M hello.go
# The change has been rolled back. The branch is in the state it
# was before the commit.
$ git diff
diff --git a/hello.go b/hello.go
index 7bfe586..fc4f54d 100644
--- a/hello.go
+++ b/hello.go
@@ -5,4 +5,5 @@ import "fmt"
func main() {
fmt.Printf("hello, hello, hello, world\n")
fmt.Printf("hello and to the people of the moon\n")
+ fmt.Printf("and bye, bye\n")
}
$ git log --graph --pretty=oneline --abbrev-commit --decorate --all
* d42fdce (HEAD -> master, tag: v1.0.0, origin/master, origin/HEAD, myfeature) do not forget the people of the moon
* 901d2d0 Saying hello to the world
* 7d19615 Initial commit
Git Performance
