Showing posts with label Software Engineering. Show all posts
Showing posts with label Software Engineering. Show all posts

Wednesday, July 20, 2011

Git: Using SmartGit in Windows

Windows users was addicted to GUI interface.  Command line git may be hard for users to get started with.  A nice Java based GIT GUI is available to fill the space.  SmartGit is there to help manage your Git repository using nice GUI frontend.  You may choose the portable SmartGit too in this case.

Work with SmartGit

SmartGit GUI frontend is easy to use.  Most of the common Git command is available in top buttons bar:

a_thumb[1]

To make SmartGit works, you should tell SmartGit where your Git executable is.  Navigate to Edit | Preferences…

b_thumb[1]

Using external file comparator

Smart support external file comparator by navigate to Edit | Preference | Tools | File Comparators:

i

Git SSH client

SmartGit provides two SSH client access: System SSH client or Smart SSH client:

j

SmartGit as SSH Client does not possess all SSH client option.  Using System SSH client with ssh.exe or putty plink.exe may enjoy most from SSH.

Git: using msysgit in Windows

Git was originally develop to manage Linux kernel source.  It is widely use in Linux community.  It has porting to many platform too including Windows.

msysgit is windows porting of Git.  It is git command line utility running in command shell console.  I personally prefer using portable msysgit and set path in my home user’s environment variable.  By using portable, I may switch git version easily when new release is available.  No setup and installation is needed for portable release.

First configuration task for msysgit is set path variable in your local user account’s environment variable to include the <msysgit>/bin folder.  Git will then available whenever you start cmd shell.

ssh.exe: Password authentication

The most common ssh authentication is password authentication.  The following example shows a basic usage to access git repository via SSH:

C:\> git clone ssh://alice@git.example.com/srv/repos/git/project.git project
Cloning into project...
<user>@<git-server>'s password:
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.

You should supply a user name (e.g: alice) for ssh connection or else it may fail.

ssh.exe: Password authentication without user name

There are situation with Git that user name may not supply in git command clause.  For example, a git repository that consist of submodule have .gitmodules as follow:

[submodule "build"]
    path = build
    url = ssh://git.example.com/srv/repos/git/build.git
[submodule "core"]
    path = core
    url = ssh://git.example.com/srv/repos/git/core.git

As the repository is shared for team work, commit .gitmodules with user name is not practical.  The git ssh clause should not contain user name in origin repository.  Nor it should be modified to add user name in local repository.  In this case, a default user name should supply with ssh access.

To automatically supply user name to git-server, create a ~/.ssh/config file with the following entry:

Host git.example.com
  User alice

Subsequent git operation accessing ssh will use the user specify in ~/.ssh/config. This is useful when the local user name is different to ssh service user name.

ssh.exe: public key authentication

To specify the private key used for public key authentication, update ~/.ssh/config as follow:

Host git.example.com
  IdentityFile ~/.ssh/id_dsa.home
  User alice

ssh.exe: public key authentication with key agent

If a private key was protected by passphrase, each git operation accessing ssh service with the private key requires user to enter passphrase.  This is some how troublesome and will cause problem for other program like smartgit that do not prompt for passphrase.

A direct solution is using ssh key agent: ssh-agent to supply private key to ssh.  For more detail information, please refer to this article: http://chee-yang.blogspot.com/2011/07/ssh-public-key-authentication.html

msysgit: public key authentication with Putty

First, follow instructions in this article: http://chee-yang.blogspot.com/2011/07/ssh-public-key-authentication-with.html to configure putty to work with ssh.

To make msysgit work with putty as ssh client, you need a putty program: plink.exe.  Download plink.exe and keep in save location as putty.exe.

Next, create a new environment variable GIT_SSH that point to plink.exe:

h

You may start using git that use putty as ssh client.

There are 2 types of ssh connection with plink: using SSH server name or putty saved session.

Plink with SSH server name is limited and does not make use of extra options available in putty.  A saved putty session may consume options like pageant authentication and SSH user name for authentication.  To make git work with putty saved session, just name saved session same as  SSH server name.

Error – missing libconv-2.dll

In PortableGit-1.7.6-preview20110709.7z release, you may encounter the following errors when executing some Git commands::

The program can’t start because libiconv-2.dll is missing from your computer. Try installing the program to fix this problem.

git-error

To get rid of the error, locate <msysgit>\bin\libconv-2.dll and copy to <msysgit>\libexec\git-core.

Monday, July 11, 2011

Git: Migrating from CVS

There is a git-cvsimport utility that may migrate CVS repository to Git Repository.  However, this utility is buggy and doesn’t export the CVS properly especially there are branches in the repository.  A more mature tools to use is cvs2svn. Do not confuse with the name it implied that it is CVS to SVN tool.  In fact, it contain a cvs2git utility to convert CVS repository to Git.

git-cvsimport

Using git-cvsimport is not recommended.  If you insist to use, here are some simple usage.

Install git-cvsimport utility:

# apt-get install git-cvs
To import a  CVS repository to Git, try this
# git-cvsimport -v –d <CVSROOT> –C <git.repo> <cvs-repository>

The CVSROOT may be a CVS folder that has CVSROOT folder or a CVS protocol URL.

cvs2svn

Download and install cvs2svn.

Use this command create a blob file and a dump file for next stage:

# cvs2svn-2.3.0/cvs2git --blobfile=git-blob.dat --dumpfile=git-dump.dat --username=cvs2git <cvs-repository>

The cvs-repository should be direct filesystem CVS repository.  CVS protocol URL is not allow here.

Create a Git repository:

# git init --bared --shared import.git
# cd import.git
Import the blob and dump files to git repository:
# cat ../git-blob.dat ../git-dump.dat | git fast-import

Friday, July 08, 2011

CVS: Make Repository Read Only

There are few ways to make CVS repository read only.  The most easiest so far I know of is create an empty writers file in CVSROOT folder.

Here is the initial content of CVSROOT:

# ls
checkoutlist    config,v       Emptydir   modules,v  taginfo
checkoutlist,v  cvswrappers    history    notify     taginfo,v
commitinfo      cvswrappers,v  loginfo    notify,v   val-tags
commitinfo,v    editinfo       loginfo,v  rcsinfo    verifymsg
config          editinfo,v     modules    rcsinfo,v  verifymsg,v

Create an empty writers file:

# touch writers
# ls
checkoutlist    config,v       Emptydir   modules,v  taginfo      writers,v
checkoutlist,v  cvswrappers    history    notify     taginfo,v
commitinfo      cvswrappers,v  loginfo    notify,v   val-tags
commitinfo,v    editinfo       loginfo,v  rcsinfo    verifymsg
config          editinfo,v     modules    rcsinfo,v  verifymsg,v

An empty writers means nobody may write into repository, thus the repository is read only now.

Monday, July 04, 2011

Git: Submodule

Introduction

For those who familiar with CVS ampersand modules mechanism, git submodule is some how similar to it.

Prepare a submodule bare repository

Create 3 repositories that may use as submodules:

$ mkdir ~/project
$ cd ~/project
$ mkdir sub1.git sub2.git sub3.git
$ git init --bare sub1.git
Initialized empty Git repository in /tmp/test.sub/sub1.git/
$ git init --bare sub2.git
Initialized empty Git repository in /tmp/test.sub/sub2.git/
$ git init --bare sub3.git
Initialized empty Git repository in /tmp/test.sub/sub3.git/

Three bare repositories now exists in your home folder:

$ dir *.git
sub1.git:
HEAD  branches  config  description  hooks  info  objects  refs

sub2.git:
HEAD  branches  config  description  hooks  info  objects  refs

sub3.git:
HEAD  branches  config  description  hooks  info  objects  refs

Prepare super git repository

The super git repository is an example of a git repository that hold some submodules.

$ mkdir super
$ cd ~/super
$ git init
$ echo “This is super” > readme
$ git add readme
$ git commit –m “Super commit”

Add submodules

Next, we add 3 sub modules into the super git repository:

$ git submodule add /tmp/test.sub/sub1.git
Initialized empty Git repository in /tmp/test.sub/super/sub1/.git/
warning: You appear to have cloned an empty repository.
fatal: You are on a branch yet to be born
Unable to checkout submodule 'sub1'
$ git submodule add /tmp/test.sub/sub2.git
Initialized empty Git repository in /tmp/test.sub/super/sub2/.git/
warning: You appear to have cloned an empty repository.
fatal: You are on a branch yet to be born
Unable to checkout submodule 'sub2'
$ git submodule add /tmp/test.sub/sub3.git
Initialized empty Git repository in /tmp/test.sub/super/sub3/.git/
warning: You appear to have cloned an empty repository.
fatal: You are on a branch yet to be born
Unable to checkout submodule 'sub3'
$ ls -a
.  ..  .git  sub1  sub2  sub3

You may have noticed there are warning and error messages when execute “git submodule add” command.  This is due to the sub modules are empty in the example.  You won’t get the messages if submodule is not empty.

We should further re-execute “git submodule add” again to complete the task:

$ git submodule add /tmp/test.sub/sub1.git
Adding existing repo at 'sub1' to the index
$ git submodule add /tmp/test.sub/sub2.git
Adding existing repo at 'sub2' to the index
$ git submodule add /tmp/test.sub/sub3.git
Adding existing repo at 'sub3' to the index
$ ls -a
.  ..  .git  .gitmodules  sub1  sub2  sub3

A new file .gitmodules was created:

$ cat .gitmodules
[submodule "sub1"]
        path = sub1
        url = /home/user/sub1.git
[submodule "sub2"]
        path = sub2
        url = /home/user/sub2.git
[submodule "sub3"]
        path = sub3
        url = /home/user/sub3.git

We should now add some changes to all empty sub module directory:

$ cd sub1
$ touch README
$ git add README
$ git commit -m "commit #1"
[master (root-commit) 3a8d946] commit #1
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 201 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/test.sub/sub1.git
* [new branch]      master -> master
$ cd ../sub2
$ touch README
$ git add README
$ git commit -m "commit #1"
[master (root-commit) 63b5d68] commit #1
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 200 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/test.sub/sub2.git
* [new branch]      master -> master
$ cd ../sub3
$ touch README
$ git add README
$ git commit -m "commit #1"
[master (root-commit) 46b2a53] commit #1
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 README
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 201 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/test.sub/sub3.git
* [new branch]      master –> master
$ cd ..

The git repository status now become

$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   .gitmodules
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       sub1/
#       sub2/
#       sub3/

Add and commit submodule folder

$ git add sub1 sub2 sub3
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   .gitmodules
#       new file:   sub1
#       new file:   sub2
#       new file:   sub3
#

Commit changes:

$ git commit -m "super commit"
[master (root-commit) 7029640] super commit
4 files changed, 12 insertions(+), 0 deletions(-)
create mode 100644 .gitmodules
create mode 160000 sub1
create mode 160000 sub2
create mode 160000 sub3
$ git status
# On branch master
nothing to commit (working directory clean)

To further make a super git repository as bare repository, run

$ git clone –bare ~/super ~/super.git
$ git remote add origin ~/super.git
$ git config branch.master.remote origin
$ git config branch.master.merge refs/heads/master

Clone a git repository with submodules

This example shows how to work with clone of a super git repository that contain submodules:

$ cd ~/project
$ git clone super.git super.clone
$ cd ~/super.clone
$ ls –a *

readme

sub1:
.  ..

sub2:
.  ..

sub3:
.  ..

All sub module’s directory is empty at this moment.  Run this to get updated commits

$ git submodule init
$ git submodule update
Initialized empty Git repository in /home/user/super.clone2/sub1/.git/
Submodule path 'sub1': checked out '8acdbdd86119c4a9777bbefe13bd8f80c96a8b7a'
Initialized empty Git repository in /home/user/super.clone2/sub2/.git/
Submodule path 'sub2': checked out 'e390c3e3114fde7643a3544c0a262eff5a52e09c'
Initialized empty Git repository in /home/user/super.clone2/sub3/.git/
Submodule path 'sub3': checked out '001355362c4ed758f17206a3fbbb2f7637983434'
$ ls -a *
readme

sub1:
.  ..  .git  readme

sub2:
.  ..  .git  readme

sub3:
.  ..  .git  readme

An alternate way to clone repository is run everything in a single command:

$ git clone --recursive ~/super.git ~/super.clone
Initialized empty Git repository in /tmp/submodules/super.clone2/.git/
Submodule 'sub1' (/home/user/sub1.git) registered for path 'sub1'
Submodule 'sub2' (/home/user/sub2.git) registered for path 'sub2'
Submodule 'sub3' (/home/user/sub3.git) registered for path 'sub3'
Initialized empty Git repository in /home/user/super.clone2/sub1/.git/
Submodule path 'sub1': checked out '8acdbdd86119c4a9777bbefe13bd8f80c96a8b7a'
Initialized empty Git repository in /home/user/super.clone2/sub2/.git/
Submodule path 'sub2': checked out 'e390c3e3114fde7643a3544c0a262eff5a52e09c'
Initialized empty Git repository in /home/user/super.clone2/sub3/.git/
Submodule path 'sub3': checked out '001355362c4ed758f17206a3fbbb2f7637983434'
$ ls -a *
sub1:
.  ..  .git  README

sub2:
.  ..  .git  README

sub3:
.  ..  .git  README

Add changes to submodule

The following example shows a submodule workflow to add, commit, push new changes:

Check out a branch first

$ cd ~/project/super/sub1
super/sub1$ git branch
* (no branch)
  master
super/sub1$ git checkout master
super/sub1$ git branch
* master

Make a changes

$ cat readme
This is submodule
super/sub1$ echo "New changes" >> README
super/sub1$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   readme
#
no changes added to commit (use "git add" and/or "git commit -a")

Commit and push changes

$ git commit -a -m "New changes"
[master 6a26854] New changes
1 files changed, 1 insertions(+), 0 deletions(-)
$ git push
Counting objects: 5, done.
Writing objects: 100% (3/3), 246 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/test.sub/sub1.git
   3a8d946..6a26854  master –> master

Check status of submodule directory

$ cd ..
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   sub1
#
no changes added to commit (use "git add" and/or "git commit -a")

The submodule “sub1” status is modified now.

Commit and push changes of submodule directory

$ git commit -a -m "commit sub1"
[master 6039eee] commit sub1
1 files changed, 1 insertions(+), 1 deletions(-)
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
nothing to commit (working directory clean)
$ git push
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 309 bytes, done.
Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
To /tmp/test.sub/super.git
   7029640..6039eee  master –> master

Update a submodule

Try this to pull changes from origin repository to update local repository:

Pull changes

$ cd ../super.clone
super.clone$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (2/2), done.
Updating 7029640..6039eee
Fast-forward
sub1 |    2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

$ cat sub1/README
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   sub1
#
no changes added to commit (use "git add" and/or "git commit -a")

Up to this stage, the file has not updated to latest HEAD version yet.  After pull from origin, the status of sub1 show modified.  Continue to next topic to complete submodule update task.

Update submodule

To pull latest commit snapshot from origin, run “git submodule update” to checkout latest committed snapshot:

$ git submodule update
Submodule path 'sub1': checked out '6a26854c7985d6ba516dcca8fc8cb42908bcb639'
$ git status
# On branch master
nothing to commit (working directory clean)
$ cat sub1/README
New Changes

The repository folder is in clean state now.  sub1 folder also updated to latest committed snapshot.  Let’s continue to check the status of sub1 by continue to next topic.

Detached HEAD in submodule

Let’s check the status of sub1 now:

$ cd sub1
$ cat README
New Changes
$ git branch
* (no branch)
  master

The current position of sub1 point to a commit stage without any label.  This is known as detached HEAD commit or headless commit.  Commit detached HEAD changes is not encounrage.  It may be problems for other collaborators to retrieve the changes.

Instead, we should checkout the master branch, work, commit changes and merge commits to master branch:

$ git checkout master
Previous HEAD position was 6a26854... New changes
Switched to branch 'master'
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
$ cat README
$

The master branch’s README file is in last committed snapshot.  Let’s check the status of super git repository after checkout:

$ cd ..
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   sub1
#
no changes added to commit (use "git add" and/or "git commit -a")

It’s parent directory has changed to modified.  That simply means sub1 doesn’t match with latest committed snapshot.

$ git merge origin
Updating 3a8d946..6a26854
Fast-forward
README |    1 +
1 files changed, 1 insertions(+), 0 deletions(-)
$ cat README
New Changes

You may also use “git pull” to replace the “git merge origin” .  Let’s check parent status again:

$ cd ..
$ git status
# On branch master
nothing to commit (working directory clean)

It is in clean state now.

Git: Undoing

Introduction

Git is a distributed version control system.  Each git working directory has a local copy of repository.  A common Git workflow includes the following:

  1. Make changes in working directory
  2. Add changes to staging area
  3. Commit to local repository
  4. Push to origin repository on remote

In real practice, we may regret of what we have done at any stages above.  We may want to revert or undo our works.

Revert untracked files

Untracked files are simply new files that have not added to staging area nor committed before.  The most easiest way to remove untracked file is delete it from working directory.  However, if there are bunch of untracked files exist in working directory, you may remove them using “git clean” command:

$ echo "This is new file" > newfile 
... // do some work and revert the work fianlly 
$ git status –s 
?? newfile 
$ git clean -f 
Removing newfile 
$ git status 
# On branch master 
nothing to commit (working directory clean) 
$ ls -a 
.  ..  .git  install  newfile  readme

Revert modified tracked files

Assume there are 2 files in a clean git repository:

$ ls -a 
.  ..  .git  install  readme 
$ git status 
# On branch master 
nothing to commit (working directory clean)

Make some changes to the working tree:

$ echo "modified" >> install 
$ echo "modified" >> readme

And the working tree is now become

$ ls -a 
.  ..  .git  install  newfile  readme 
$ git status –s 
M install 
M readme

Both readme and install file has modified status.

Revert a single file

To revert only a readme file, run

$ cat readme 
this is a readme 
modified 
$ git checkout readme 
$ cat readme 
this is a readme 
$ git status –s 
M install 
?? newfile

Revert a working directory

To revert whole working directory, run

$ git status –s 
M install 
M readme 
?? newfile 
$ git reset --hard 
HEAD is now at 5543e79 second commit 
$ git status –s 
?? newfile

Un-staging files

By using “git add” on new or modified files will move them into staging area for commit in later stage.  To un-stage files, use “git reset HEAD <file>...":

Add 3 new files and make changes to readme file:

$ ls -a 
.  ..  .git  install  readme 
$ touch newfile1 newfile2 newfile3 
$ ls -a 
.  ..  .git  install  newfile1  newfile2  newfile3  readme 
$ cat readme 
this is a readme 
$ echo "modified" >> readme 
$ git status -s 
M readme 
?? newfile1 
?? newfile2 
?? newfile3

Add files to staging area:

$ git add * 
$ git status 
# On branch master 
# Changes to be committed: 
#   (use "git reset HEAD <file>..." to unstage) 
# 
#       new file:   newfile1 
#       new file:   newfile2 
#       new file:   newfile3 
#       modified:   readme 
#

Un-stage files:

$ git reset HEAD 
Unstaged changes after reset: 
M       readme 
$ git status 
# On branch master 
# Changed but not updated: 
#   (use "git add <file>..." to update what will be committed) 
#   (use "git checkout -- <file>..." to discard changes in working directory) 
# 
#       modified:   readme 
# 
# Untracked files: 
#   (use "git add <file>..." to include in what will be committed) 
# 
#       newfile1 
#       newfile2 
#       newfile3 
no changes added to commit (use "git add" and/or "git commit -a")

Run this to un-stage only a single file, newfile3:

$ git reset HEAD newfile3

Revert committed changes in working directory

Assume 3 commits has been done on a working directory:

$ ls -a 
.  ..  .git  install  readme 
$ git status 
# On branch master 
nothing to commit (working directory clean) 
$ echo "commit 1" >> readme 
$ git commit -a -m "commit 1" 
[master fdaa7d3] commit 1 
1 files changed, 1 insertions(+), 0 deletions(-) 
$ echo "commit 2" >> readme 
$ git commit -a -m "commit 2" 
[master 7ad2d0a] commit 2 
1 files changed, 1 insertions(+), 0 deletions(-) 
$ echo "commit 3" >> readme 
$ git commit -a -m "commit 3" 
[master 8a56cd9] commit 3 
1 files changed, 1 insertions(+), 0 deletions(-) 
$ git log --oneline 
8a56cd9 commit 3 
7ad2d0a commit 2 
fdaa7d3 commit 1 
5543e79 second commit 
f9d7ae7 this is first commit

Revert to last commit

$ git reset --hard HEAD^ 
HEAD is now at 7ad2d0a commit 2 
$ cat readme 
this is a readme 
commit 1 
commit 2 
$ git log --oneline 
7ad2d0a commit 2 
fdaa7d3 commit 1 
5543e79 second commit 
f9d7ae7 this is first commit

Revert to specific commit

We may revert to specific commit by specify sha1 hash of the commit in “git reset

$ git log --oneline 
07187ef commit 3 
ca79a77 commit 2 
37194a2 commit 1 
5543e79 second commit 
f9d7ae7 this is first commit 
$ git reset --hard 37194a2 
HEAD is now at 37194a2 commit 1 
$ git log --oneline 
37194a2 commit 1 
5543e79 second commit 
f9d7ae7 this is first commit

Revert to last few commit

Using “HEAD~n” with “git reset” allow us to revert to last n committed:

$ git log --oneline 
fdbde67 commit 3 
54d5cef commit 2 
37194a2 commit 1 
5543e79 second commit 
f9d7ae7 this is first commit 
$ git reset --hard HEAD~3 
HEAD is now at 37194a2 commit 1 
$ git log --oneline 
5543e79 second commit 
f9d7ae7 this is first commit

You may also use something like “HEAD^” to revert a file to last commit:

$ git reset –hard HEAD^

Add more caret (^) symbol to indicate revert for last few commits.  One caret represent one backward commit.  For example, to revert to last 5 commits:

$ git reset –hard HEAD^^^^^

Revert committed pushed to remote

It is not encourage to revert pushed commit to origin repository if other has pulled what you have pushed.  However, human makes mistake.  An unwanted push may be reverted too if you are aware that nobody has pulled before.

There are few ways to revert pushed commit.  This may be the most simple example:

$ git push 
Everything up-to-date 
$ git log --oneline 
f6abcfa commit 3 
0660af9 commit 2 
37194a2 commit 1 
5543e79 second commit 
f9d7ae7 this is first commit 
$ git reset --hard HEAD^^^ 
HEAD is now at 5543e79 second commit 
$ git push origin +master 
Total 0 (delta 0), reused 0 (delta 0) 
To /tmp/test.git 
+ f6abcfa...5543e79 master -> master (forced update) 
$ git pull 
Already up-to-date. 
$ git push 
Everything up-to-date 
$ git log --oneline 
5543e79 second commit 
f9d7ae7 this is first commit

You may encounter error while revert pushed commit:

$ git push origin +master
Total 0 (delta 0), reused 0 (delta 0)
remote: error: denying non-fast-forward refs/heads/master (you should pull first)
! [remote rejected] e3fce6 -> master (non-fast-forward)

This is due to the origin repository has disable non fast forward (revert) commit.  Edit .git/config in origin repository to allow non fast forward commit:

$ cat .git/config

[receive]
        denyNonFastforwards = true false

Revert again and it should work:

$ git push origin +master

Prune loose committed objects

Even if committed works has been undone, it still exists in the repository as loose object.  Run reflog command shows loose objects:

# git reflog
704b63a HEAD@{0}: checkout: moving from 05fec4a9b062d7d9883969283a3ba18e5e06aad0
05fec4a HEAD@{1}: HEAD^ --: updating HEAD
9fa2a61 HEAD@{2}: checkout: moving from 806a6fd6ae4198a20af0f301d27c13d0dd052931
806a6fd HEAD@{3}: checkout: moving from master to 806a6fd6ae4198a20af0f301d27c13
704b63a HEAD@{4}: 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e8: updating HEAD
9fa2a61 HEAD@{5}: merge origin/master: Merge made by recursive.
05fec4a HEAD@{6}: HEAD^ --: updating HEAD
5f3b0a1 HEAD@{7}: rebase finished: returning to refs/heads/master
5f3b0a1 HEAD@{8}: checkout: moving from master to 5f3b0a1e2167652b32e765b9431727
704b63a HEAD@{9}: checkout: moving from test to master
5f3b0a1 HEAD@{10}: checkout: moving from 806a6fd6ae4198a20af0f301d27c13d0dd05293
806a6fd HEAD@{11}: checkout: moving from master to 806a6fd6ae4198a20af0f301d27c1
704b63a HEAD@{12}: merge 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e8: Fast-forward
806a6fd HEAD@{13}: checkout: moving from 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e
704b63a HEAD@{14}: checkout: moving from master to 704b63ab353cf32b5d6a9a54b8eb2
806a6fd HEAD@{15}: 806a6fd6ae4198a20af0f301d27c13d0dd052931: updating HEAD
5f3b0a1 HEAD@{16}: 5f3b0a1e2167652b32e765b94317279868faa82b: updating HEAD
4441dc5 HEAD@{17}: HEAD^ --: updating HEAD
05fec4a HEAD@{18}: HEAD^ --: updating HEAD
5f3b0a1 HEAD@{19}: checkout: moving from 5f3b0a1e2167652b32e765b94317279868faa82
5f3b0a1 HEAD@{20}: checkout: moving from master to 5f3b0a1e2167652b32e765b943172
5f3b0a1 HEAD@{21}: checkout: moving from 5f3b0a1e2167652b32e765b94317279868faa82
5f3b0a1 HEAD@{22}: checkout: moving from master to 5f3b0a1e2167652b32e765b943172
5f3b0a1 HEAD@{23}: checkout: moving from 704b63ab353cf32b5d6a9a54b8eb2d4d52b824e
704b63a HEAD@{24}: checkout: moving from master to 704b63ab353cf32b5d6a9a54b8eb2
5f3b0a1 HEAD@{25}: merge 5f3b0a1e2167652b32e765b94317279868faa82b: Fast-forward
806a6fd HEAD@{26}: merge origin/master: Fast-forward

These objects may expire and will be pruned by “git gc” after 30 days (default).  It has no harm exist in local repository except occupy some hard drive space.

It is possible to prune loose objects immediately by executing

# git reflog expire --expire=now --all
# git gc
Counting objects: 35395, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (7225/7225), done.
Writing objects: 100% (35395/35395), done.
Total 35395 (delta 26533), reused 35393 (delta 26533)
# git reflog
#

Git: Branching and Merging

Git allows us to maintain multiple branches of development.  This is common and consider a must to develop with git.  You may switch between branches to enjoy isolate development workflow.

To ease the explanation, we create a bare repository as origin and 2 cloned repository from origin.

$ mkdir project.git 
$ cd project.git/ 
$ git init --bare 
Initialized empty Git repository in /tmp/test.branch/project.git/ 
$ cd .. 
$ git clone project.git clone1 
Initialized empty Git repository in /tmp/test.branch/clone1/.git/ 
warning: You appear to have cloned an empty repository. 
$ cd clone1 
$ echo "This is readm" > readme 
$ git add readme 
$ git commit -m "first commit" 
[master (root-commit) 8b60032] first commit 
1 files changed, 1 insertions(+), 0 deletions(-) 
create mode 100644 readme 
$ git push 
No refs in common and none specified; doing nothing. 
Perhaps you should specify a branch such as 'master'. 
fatal: The remote end hung up unexpectedly 
error: failed to push some refs to '/tmp/test.branch/project.git' 
$ git push origin master 
Counting objects: 3, done. 
Writing objects: 100% (3/3), 215 bytes, done. 
Total 3 (delta 0), reused 0 (delta 0) 
Unpacking objects: 100% (3/3), done. 
To /tmp/test.branch/project.git 
* [new branch]      master –> master 
$ cd .. 
$ git clone project.git clone2 
Initialized empty Git repository in /tmp/test.branch/clone2/.git/ 
$ ls -a clone2 
.  ..  .git  readme

Create git branch

Let’s create a new branch “newfeature1” in clone1:

$ cd clone1 
$ git branch 
* master 
$ git branch newfeature1
$ git branch 
* master 
  newfeature1 
$ git checkout newfeature1 
Switched to branch 'newfeature1' 
$ git branch 
  master 
* newfeature1

The repository is now point to branch newfeature1.  Add a new file to the repository and commit changes:

$ echo "This is new feature" > feature1 
$ git add feature1 
$ git commit -m "Commit new feature" 
[newfeature 399cffe] Commit new feature 
1 files changed, 1 insertions(+), 0 deletions(-) 
create mode 100644 feature1

Switch branch

Continue from above example.  Let’s switch between branches and see what happen to the working directory:

$ git branch 
  master 
* newfeature1 
$ ls -a 
.  ..  .git  feature1  readme 
$ git checkout master 
Switched to branch 'master' 
$ ls -a 
.  ..  .git  readme

When switch branch from “newfeature1” to “master”, file “feature1” that was committed in “newfeature1” won’t appear in branch “master”.

List branches in repository

To list existing local branches:

$ git branch 
* master 
  newfeature1

Active branch is indicated by * in front.

To list local and remote tracked branch

$ git branch -a 
* master 
  newfeature1 
  remotes/origin/master

To list remote tracked branch only:

$ git branch -r 
  origin/master

Merge with Fast Forward

Assume we perform few commits into branch “newfeature1”:

$ echo "changes #1" >> feature1 
$ git commit -a -m "commit changes #1" 
[newfeature1 fc6a399] commit changes #1 
1 files changed, 1 insertions(+), 0 deletions(-) 
$ echo "changes #2" >> feature1 
$ git commit -a -m "commit changes #2" 
[newfeature1 1ea0fda] commit changes #2 
1 files changed, 1 insertions(+), 0 deletions(-) 
$ git log --oneline --graph 
* 1ea0fda commit changes #2 
* fc6a399 commit changes #1 
* 048ce34 Commit new feature 1 
* 8b60032 first commit

Perform a fast forward merge into master:

$ git checkout master 
Switched to branch 'master' 
$ git merge newfeature1 
Updating 8b60032..1ea0fda 
Fast-forward 
feature1 |    3 +++ 
1 files changed, 3 insertions(+), 0 deletions(-) 
create mode 100644 feature1 
$ git log --oneline --graph 
* 1ea0fda commit changes #2 
* fc6a399 commit changes #1 
* 048ce34 Commit new feature 1 
* 8b60032 first commit

All commit changes from newfeature1 is now merge into master.

Merge without Fast Forward

An advantage with no fast forward merge preserve the branch commit history:

$ git log --oneline --graph 
* 8b60032 first commit 
$ git merge --no-ff newfeature1 
Merge made by recursive. 
feature1 |    3 +++ 
1 files changed, 3 insertions(+), 0 deletions(-) 
create mode 100644 feature1 
$ git log --oneline --graph 
*   4725f13 Merge branch 'newfeature1' 
|\ 
| * 1ea0fda commit changes #2 
| * fc6a399 commit changes #1 
| * 048ce34 Commit new feature 1 
|/ 
* 8b60032 first commit

Push Branch to remote

So far all branches are stored in local repository only.  You may share the branch with others by pushing it to origin:

$ git branch -a 
* master 
  newfeature1 
  remotes/origin/master 
$ git checkout newfeature1 
Switched to branch 'newfeature1' 
$ git push origin HEAD 
Total 0 (delta 0), reused 0 (delta 0) 
To /tmp/test.branch/project.git 
* [new branch]      HEAD -> newfeature1 
$ git branch -r 
  origin/master 
  origin/newfeature1

Now we create more branch and push to origin:

$ git branch newfeature2 master 
$ git checkout newfeature2 
Switched to branch 'newfeature2' 
$ echo "Feature 2" > feature2 
$ git branch 
  feature3 
  master 
  newfeature1 
* newfeature2 
$ git add feature2 
$ git commit -m "commit feature 2" 
[newfeature2 ef39e45] commit feature 2 
1 files changed, 1 insertions(+), 0 deletions(-) 
create mode 100644 feature2 
$ git push origin HEAD 
Total 0 (delta 0), reused 0 (delta 0) 
To /tmp/test.branch/project.git 
* [new branch]      HEAD –> newfeature2

$ git branch newfeature3 master 
$ git checkout newfeature3 
Switched to branch 'newfeat 
$ echo "Feature 3" > feature3 
$ git add feature3 
$ git commit -m "commit feature 3" 
[newfeature3 ec1da8d] commit feature 3 
1 files changed, 1 insertions(+), 0 deletions(-) 
create mode 100644 feature3 
$ git push origin HEAD 
Counting objects: 7, done. 
Compressing objects: 100% (4/4), done. 
Writing objects: 100% (6/6), 512 bytes, done. 
Total 6 (delta 1), reused 0 (delta 0) 
Unpacking objects: 100% (6/6), done. 
To /tmp/test.branch/project.git 
* [new branch]      HEAD –> newfeature3

We have these branches now both in local and origin repositories:

$ git branch -a 
  master 
  newfeature1 
* newfeature2 
  newfeature3 
  remotes/origin/master 
  remotes/origin/newfeature1 
  remotes/origin/newfeature2 
  remotes/origin/newfeature3

Fetch Remote Branch

Let’s switch to clone2:

$ cd clone2 
$ git branch -a 
* master 
  remotes/origin/HEAD -> origin/master 
  remotes/origin/master 
$ git remote show origin 
* remote origin 
  Fetch URL: /tmp/test.branch/project.git 
  Push  URL: /tmp/test.branch/project.git 
  HEAD branch: master 
  Remote branches: 
    master      tracked 
    newfeature1 new (next fetch will store in remotes/origin) 
    newfeature2 new (next fetch will store in remotes/origin) 
    newfeature3 new (next fetch will store in remotes/origin) 
  Local branch configured for 'git pull': 
    master merges with remote master 
  Local ref configured for 'git push': 
    master pushes to master (up to date)

Notice that there are 3 new branches: newfeature1, newfeature2 and newfeature3 in origin.

Now fetch from origin,

$ git fetch 
From /tmp/test.branch/project 
* [new branch]      newfeature1 -> origin/newfeature1 
* [new branch]      newfeature2 -> origin/newfeature2 
* [new branch]      newfeature3 -> origin/newfeature3 
$ git branch -a 
* master 
  remotes/origin/HEAD -> origin/master 
  remotes/origin/master 
  remotes/origin/newfeature1 
  remotes/origin/newfeature2 
  remotes/origin/newfeature3 
$ git remote show origin 
* remote origin 
  Fetch URL: /tmp/test.branch/project.git 
  Push  URL: /tmp/test.branch/project.git 
  HEAD branch: master 
  Remote branches: 
    master      tracked 
    newfeature1 tracked 
    newfeature2 tracked 
    newfeature3 tracked 
  Local branch configured for 'git pull': 
    master merges with remote master 
  Local ref configured for 'git push': 
    master pushes to master (up to date)

To have a look on the remote branch newfeature2, use git checkout:

$ git checkout origin/newfeature2 
Note: checking out 'origin/newfeature2'.

You are in 'detached HEAD' state. You can look around, make experimental 
changes and commit them, and you can discard any commits you make in this 
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may 
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at c47ec36... This is new feature 2 
$ ls -a 
.  ..  .git  feature2  readme

The checkout origin/newfeature2 will switch local repository to “detached HEAD”.  A detached HEAD doesn’t belongs to any branch:

$ git branch -a 
* (no branch) 
  master 
  remotes/origin/master 
  remotes/origin/newfeature1 
  remotes/origin/newfeature2 
  remotes/origin/newfeature3

You shouldn’t commit any changes that doesn’t belong to a named branch or else others will have problems merging with the changes.  Instead you should name a branch or checkout local branch before commit any changes:

$ git checkout newfeature2
Branch newfeature2 set up to track remote branch newfeature2 from origin.
Switched to a new branch 'newfeature2'
$ git branch -a
  master
* newfeature2
  remotes/origin/master
  remotes/origin/newfeature1
  remotes/origin/newfeature2
  remotes/origin/newfeature3

Delete local branch

For branch that you never push to origin, you may delete it as follow:

$ git branch
  master
* newfeature1
$ git branch -d newfeature1
error: Cannot delete the branch 'newfeature1' which you are currently on.

Active branch is not allow to delete.  You may switch to other branch first

$ git checkout master
Switched to branch 'master'
$ git branch -d newfeature1
error: The branch 'newfeature1' is not fully merged.
If you are sure you want to delete it, run 'git branch -D newfeature1'.
$ git branch -D newfeature1
Deleted branch newfeature1 (was 201bb22).
$ git branch
* master

You can’t delete branch using “-d” if it is not merged with others.  To force delete, use “-D” option.

Delete remote tracked branch

Assume the repository has the following branches:

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/newfeature1
  remotes/origin/newfeature2
  remotes/origin/newfeature3
$ git remote show origin
* remote origin
  Fetch URL: /tmp/test.branch/project.git
  Push  URL: /tmp/test.branch/project.git
  HEAD branch: master
  Remote branches:
    master      tracked
    newfeature1 tracked
    newfeature2 tracked
    newfeature3 tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)

Delete a remote tracked branch:

$ git branch -d -r origin/newfeature3
Deleted remote branch origin/newfeature3 (was ec1da8d).
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/newfeature1
  remotes/origin/newfeature2
$ git remote show origin
* remote origin
  Fetch URL: /tmp/test.branch/project.git
  Push  URL: /tmp/test.branch/project.git
  HEAD branch: master
  Remote branches:
    master      tracked
    newfeature1 tracked
    newfeature2 tracked
    newfeature3 new (next fetch will store in remotes/origin)
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (up to date)