Using git

Contents

  1. Theory
    1. See Also
  2. Configuring
    1. Configuring Basic git Options
    2. Configuring git to sign commits with gnupg
  3. Using Git on the CLI
    1. Initialize a Project
    2. Add extra remote hosts
    3. Clone A Project
    4. Commit to a Project
      1. Amending a Commit
      2. Stashing Changes
        1. Partially Stash Changes
      3. Resetting changes
      4. Ignoring files
    5. Check Status
    6. Pull (Sync) A Repository
    7. Fetch A Repository
    8. Merge
    9. Other Commands To Know
    10. Branches
      1. Verify the Current Branch
      2. Verify upstream/tracking for Current Branch
      3. Checkout/Switch Branches
      4. Create a Branch
      5. Diff a Branch
      6. Merge a Branch
      7. Rebase
      8. Delete a Branch
        1. Delete a Remote Branch
    11. Inspect Commit Logs
    12. Revert a Commit
      1. Skipping back many Commits
    13. Deleting a commit
    14. Finding commits relative to a particular file
    15. Finding a string in a repository (grep)
    16. Finding a string in a commit/log
    17. Example CLI Workflow
      1. Normal Development Flow
  4. GUIs
  5. Errors
    1. fatal: remote origin already exists.
    2. Deleting a commit
    3. (pre-receive hook declined)
    4. CONFLICT (content): Merge conflict
      1. Anatomy of a merge conflict
      2. Resolving the merge conflict
    5. Windows
      1. Case Insensitivity
      2. Bad files paths
    6. Linux
      1. gnome SSH_ASKPASS
  6. One-off scenarios
    1. Remove a whole bunch of wasteful branches
  7. See Also
    1. Documentation
    2. Tutorials
    3. Videos

Theory

  1. The first time you work on a project, you will download that project's repository code to your machine by doing a clone.
  2. To update the code on your machine from the repository, you perform a pull.
    1. A checkout is how you switch from one branch of a project to another.
  3. Changes are commited on your machine.
  4. To submit your changes to the repository, you push them back.
  5. You merge data from one branch to another - to combine/diff them into one.

See Also

  1. http://nvie.com/posts/a-successful-git-branching-model/

Configuring

Configuring Basic git Options

Most IDEs or git GUIs will handle this step

Configure user:

git config --global user.name "David Pocock"
git config --global user.email "dpocock@dayid.org"
git config --global core.editor vim
git config --global merge.tool vimdiff
git config --global merge.conflictstyle diff3
git config --global mergetool.prompt false
git config --global diff.tool vimdiff
git config --global color.interactive auto
git config --global color.diff auto
git config --global color.status auto
git config --global color.branch auto
git config --global color.ui true
  

Perhaps cache our password for an hour: (although preferable would be to use ssh-keys)

git config credential.helper cache
git config credential.helper 'cache --timeout=3600'
  

Alias 'lol' to give slightly better logs:

git config --global alias.lol "log --pretty=oneline --abbrev-commit --graph --decorate"

Or just create ~/.gitconfig

[user]
name = David Pocock
email = dpocock@dayid.org
[core]
editor = vim
[merge]
tool = vimdiff
[color]
    status = auto
    branch = auto
    diff = auto
    interactive = auto
      

Verify config:

git config --list

Configuring git to sign commits with gnupg

Add to .gitconfig:

[commit]
  gpgsign = true
[user]
  signingkey = ______________________
[log]
  showSignature = true
      

You may need to also specify [gpg] program = if your gpg program is not in your user's $PATH
It is also wise to then modify your ~/.gnupg/gpg.conf to point "default-key" to the key you'd like to use for signing and enable "use-agent".
You may also need to modify your login files to set/export GPG_TTY.

Once those are all working you will be prompted to unlock your GPG private key when you do a commit so the commit can be signed.


Using Git on the CLI

Initialize a Project

mkdir project
cd project
git init
echo "This is my new project" > README
git add README
git commit -m 'Initialized project'
      

Add extra remote hosts

Add another remote host to a project

cd /home/git/myproject.git
git remote add hostname user@host:/remote/path/
      

e.g.,

cd /home/dpocock/git/nagios-plugins
git remote add bedrock dpocock@bedrock:~/git/nagios-plugins.git
      

Clone A Project

Cloning a project to your own machine:

git clone youruser@remote:/path/to/project.git

Commit to a Project

To update a file in a project:

cd /path/to/myproject/
vim file
git add file
git commit
git push
      
  1. If you modified many files, see also "git add ." or "git add -A"
  2. If you only want to add some-line-changes, see also "git add -p"
  3. You may also specify the commit message using:
git commit -m 'This is my commit message'

Again; this time using shortcuts:

git commit -a
git push
      

Again; this time using long formatting:

git commit -am 'ISSUE-31 I fixed this'
git push origin MYBRANCH
      

Amending a Commit

If you did your commit wrong the first time, you may need to amend it.

Simply:

git commit --amend

Stashing Changes

If you've been working on some changes, but aren't ready to commit them yet, then you may stash them for now (saving your current state on a branch without committing it), and apply those stashed changes later.

git stash
Saved working directory and index state \
  "WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")
      

See available stashes

git stash list
stash@{0}: WIP on master: 049d078 added the index file
stash@{1}: WIP on master: c264051 Revert "added file_size"
stash@{2}: WIP on master: 21d80a5 added number to log
      

Apply a stashed-state

git stash apply

For a particular older state:

git stash apply stash@{2}

You may also stash into a new branch if you've already been doing work (say you've been working on master accidentally, but would like your changes to be in the branch testchanges):

git stash branch testchanges

Drop (remove) a stash by id:

git stash drop stash@{0}

When dropping, the index number {#} of the available stashes (shown from 'stash list') will re-index to 0. Thus, be careful as repeat 'git stash drop stash@{0}' will continue dropping each next-stash that fills index-place 0.

Partially Stash Changes

You can also choose to stash only some files/portions of files you've been working on:

git stash -p

Resetting changes

Say you've checked out master and made changes, then realized you wanted to make those changes under a different branch:

  1. Revert changes to modified files.
git reset --hard
  1. Remove all untracked files and directories.
git clean -fd

Ignoring files

The best way is to create a .gitignore file within the project.

Some examples: (Note, **/ matches a dir, not just */)

comment
filename
filepattern*
partial*pattern
/filename
dir/filename
      

See also: http://git-scm.com/docs/gitignore

Check Status

See the status of what you have changed:

git status -s

I'm personally fond of using the --porcelain option to git status to involve in hooks/scripting:

git status --porcelain

Also - to see what has already changed from past logs:

git whatchanged
git whatchanged -p
      

Pull (Sync) A Repository

This essentially does a fetch with an automatic merge

git pull

Fetch A Repository

git fetch

Merge

Switch to the branch you want to merge to:

git checkout test

Merge the other code/branch

git merge dev

Other Commands To Know

Other:

git commit
git rm file
git mv file newfile
git log
git commit -am 'Comment for update here'
git push origin master
      

Branches

Verify the Current Branch

You will know which branch you are commiting to if you do a regular git commit because it will show in the comments: "# On branch branchname".

To verify which branch you are on use git branch. If your terminal supports colors it will highlight which you are currently on: either way it will place an asterisk to show which you are on.

[dpocock@hummus test]$ git branch
  feature
* master
      

Verify upstream/tracking for Current Branch

Your local branch may be tracking a different name upstream - to verify use git branch -vv. This will report your local branch name as well as the remote/remote-branch it is tracking:

git branch -vv
* local_master e2e6424 [origin/production] Merge branch 'master' into production
      

Checkout/Switch Branches

Doing a code checkout is how you'll switch between different branches of the same code:

git checkout master
Switched to branch 'master'
git checkout dev
Switched to branch 'dev'
      

Create a Branch

Branching a project presumes you already have a project created (whether it is a new project, a clone'd project, etc). First, make sure to do a checkout of the branch you'd like to branch.

To create a new branch based on master, then switch to (checkout) it:

git checkout master
git branch newbranch
git checkout newbranch
      

Shorthand for this is available with checkout -b, which creates a branch and does a checkout of it at the same time:

git checkout master
git checkout -b newbranch
      

Diff a Branch

To see changes to a file:

git diff file

To do a diff between two different branches:

git diff master otherbranch

To do a diff between the branch you are on and another branch:

git diff otherbranch

With a specific file in mind:

git diff branch1 branch2 filename

Also useful: 'R'everse the diff using -r to show the +/- opposite

git diff -r filename

Merge a Branch

See also the git-merge manpage.

Checkout the branch you want to merge to:

git checkout master
Switched to branch 'master'
      

Do a merge with the other branch:

git merge dev
Updating c8aa593..07dec67
Fast-forward
   test.txt |    1 +
   1 files changed, 1 insertions(+), 0 deletions(-)
      

Rebase

See also the git-rebase manpage.

git rebase -i upstream branch

So, I've done 20 commits of "Trying to fix X" - and now I finally - fixed X on my current branch. I want to merge it back into another branch; but do not want 20 backmessages of nothingness.

git rebase -i HEAD~X # where X is the amount of back-commits I want to work from
  1. This launches my editor, where I'll change the commits I don't want anymore to say "squash" instead of "pick" and write-out
  2. This will re-base and open a commit screen with the X number of commits that I am combining (squashing).
Rebase to condense commits
git init
echo '1' > 1
git add 1
git commit -m '1'
echo '2' > 2
git add 2
git commit -m '2'
git echo '3' >> 2
git add 2
git commit -m '3'
git echo '4' >> 2
git add 2
git commit -m '4'
git log # verify how-many commits back you want to rebase/condense
git rebase -i HEAD~3
      

Opens in your editor:

  pick 105a640 2
  pick f05e996 3
  pick 7a2f1e4 4
      

See the useful tooltips git provide:

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
      

Here, I want to keep all of the commits, but make it just one big commit rather than all my sequentials. So change the lines to:

  pick 105a640 2
  squash f05e996 3
  squash 7a2f1e4 4
      

This gives the opportunity to clarify the commit messages:

# This is a combination of 3 commits.
# The first commit's message is:
2

# This is the 2nd commit message:

3

# This is the 3rd commit message:

4
      

Change this

Added file b and now have a useful commit message summarizing what all of my
smaller commits accomplished.
      

Inspect the 'git log' to see that your commits have been condensed.

Reset an in-progress rebase

If you accidentally choose the wrong amount of HEAD~# to go back or otherwise choose to not continue a rebase, quit out and rm -rf .git/rebase-merge

Delete a Branch

Delete a branch by being in a different branch and doing: git branch -d branch

You cannot delete a branch if (a) You're currently on it (b) It has not been merged

If that branch hasn't been merged

git branch -d bug415fix
error: The branch 'bug415fix' is not fully merged.
If you are sure you want to delete it, run 'git branch -D bug415fix'.
      

If you're on that branch:

git branch -d bug415fix
error: Cannot delete the branch 'bug415fix' which you are currently on.
      

If you want to delete a branch which has not been merged, use -D

Delete a Remote Branch
git push origin :branch

or - the style I find clearer:

git push origin --delete branch

See also: http://www.gitguys.com/topics/adding-and-removing-remote-branches/

Inspect Commit Logs

Short version:

git log --oneline

More verbose:

git log

With stats:

git log --stat

From a particular user:

git log --author=Author

With diffs:

git log -p

Only if they affected a particular path/file:

git log -- path

Search for a string in commit message/diff::

git log -Sstring # yes, the lack of space here is correct

Do the same, but create diffs for each:

git log -p -Sstring # again, lack of space is correct

Do the same, but only for a particular file:

git log -p -Sstring file

Search for a regular expression in commit message/diff:

git log -G'regex'

Do the same, but only for particular lines of a particular file:

git log -L 1,1:file

Similar: Finding who last-modified a line of a file and in what commit:

git blame

Revert a Commit

For any various reasons you may wish to revert back to an older commit-point. Say you hired a new developer who wrote a bunch of commits to a project all willy-nilly like, and you've now sacked them and would like to get back to your old, usable code.

First, you'll want to Inspect Commit Logs, ultimately to find the commit-hash of the last/prior good-commit (one not done by A. Fictitious Hire):

commit c8aa593f35b48c3837247992431f4cb4764d80a2
Author: A. Fictitious Hire <ahire@dayid.org>
Date:   Fri Apr 25 12:25:28 2014 -0400

    Rewriting all this code in csh because csh has C

commit 6b7e945c78c6e72084423b3a8d750c39a1a89e63
Author: A. Fictitious Hire <ahire@dayid.org>
Date:   Fri Apr 25 12:24:18 2014 -0400

     Removed all comments from code

commit 07dec67a9c784534f8af9ab3f66864acf8b72a58
Author: David Pocock <dpocock@dayid.org>
Date:   Fri Apr 20 14:01:04 2014 -0400

    Wrote massive application to do all of support's work for them automatically
      

Do a "revert" for those last two bad-commits:

git revert c8aa593f35b48c3837247992431f4cb4764d80a2
git revert 6b7e945c78c6e72084423b3a8d750c39a1a89e63
      

Each of these actually does a commit that reverts the commit-hash that you provide.

Skipping back many Commits

What if A. Fictitious Hire did more than just 2 commits? In that case, we'd like to "go back" to a prior-point in time when things were still good - perhaps without having to do 20 "git reverts" to get there:

For this example, we'll want to checkout David's last commit:

user@server:~/git/thisproject/ $ git checkout 07dec67a9c784534f8af9ab3f66864acf8b72a58

You'll get a nice warning screen about being in 'detached HEAD' state. If you try to add/commit right now you'll receive an error that you are "Not currently on any branch".

If you want to just pull all the files from that commit in to your current branch:

git checkout master
git checkout 38f1b9d2e651b9498299c7dafaffd5cd84c683b6 *
      

Deleting a commit

Say you did something silly like putting the wrong Jira Issue-ID into your commit-message. Thus, associating a change to the nagios-configs branch with another project's issue. Whoops!

If you *have* made changes, copy those out somewhere safe for the moment:

cp ~/git/project/myfile ~/myfile.safe

Check git log for the commit that you want to be on:

git log

Get that commit's hash and reset to it:

git reset --hard 55bc0dce9d67f1bb65bb91a336d0758117ac6b4c

Force send that (if you already pushed):

git push origin HEAD --force

Put your changes back and do the commit right this time:

cp ~/myfile.safe ~/git/project/myfile
git add myfile
git commit -m 'ISSUE-#### added this feature'
git push
      

Finding commits relative to a particular file

git log filename
git log --all -- filename

e.g.,

git log --all -- databugverifonecardsummary.php

This will show all commits relevant to a particular filename. This is useful for seeing the history of a single file rather than commits for the project as a whole.

Finding a string in a repository (grep)

This is very similar to using the system's grep - I have not found it to be any faster nor slower when doing quick tests on small repositories. This returns any file (and the contents of the matching-line) with your string.

git grep 'string'

Finding a string in a commit/log

Using git log with -G allows you to specify a regex to search within commit-contents/diffs

git log -G'hostname.*10.1.1.5$'

Example CLI Workflow

Normal Development Flow

For the most part, projects for developers will already exist everywhere that they need to. Their workflow will mostly use checkout, add, and commit; with the occasional branch and merge.

  1. Go to where that project's parent is:
    cd /var/www/htdocs/base/bin/incab
  2. Make sure you're on the branch you want to branch from:
    git checkout master
  3. Verify that branch has the newest code from the origin:
    git pull
  4. Branch off your new branch to develop on:
    git checkout -b INC-32dev
  5. Edit whatever is being fixed/improved/created:
    vim dispatch/index.php
  6. Add that file's changes to git:
    git add dispatch/index.php
  7. Commit the changes with comment
    git commit -m 'INC-32 Fixed issue with prepending mileage'
  8. Push that branch to origin:
    git push origin INC-32dev

GUIs

There are many out there, see this.

  1. gitk ships with git, so it's what I use when I need a GUI.
  2. SourceTree is what most people I know currently use. It is from Atlassian and nicely ties in with Stash and Jira, and it is free (even for business use).

Errors

fatal: remote origin already exists.

You can't remote add origin if one already exists - this one is pretty simple.

Here, a project already had a remote defined, but I needed to change it. So rm the one that I currently have defined, and add the new one.

[dpocock@hummus nagios-plugins]$ git remote add origin dpocock@git:/home/git/nagios-plugins.git
fatal: remote origin already exists.
[dpocock@hummus nagios-plugins]$ git remote -v
origin  git@hummus:~/nagios-plugins.git (fetch)
origin  git@hummus:~/nagios-plugins.git (push)
[dpocock@hummus nagios-plugins]$ git remote rm origin
[dpocock@hummus nagios-plugins]$ git remote add origin dpocock@git:/home/git/nagios-plugins.git
[dpocock@hummus nagios-plugins]$ git remote -v
origin  dpocock@git:/home/git/nagios-plugins.git (fetch)
origin  dpocock@git:/home/git/nagios-plugins.git (push)
      

Deleting a commit

Typically will not be done, but in the event of erroneous commits, refer to this post from StackOverflow.

(pre-receive hook declined)

A pre-receive hook on the destination is refusing to allow your commit.

Check pre-receive hooks on the destination.

You will most likely need to amend your commit.

CONFLICT (content): Merge conflict

A good recommendation to avoid merge conflicts is to continuously pull (or fetch and merge) against your parent branch - particularly before pushing up your own branch!

I created a conflict with a file named file. I did this by branch master to dev, and also branching master to test. I then edited file in both branches before doing a commit. Then, while on the test branch, I did:

git merge dev
Auto-merging file
CONFLICT (content): Merge conflict in file
Automatic merge failed; fix conflicts and then commit the result.
      

Anatomy of a merge conflict

I change the content of file on the branch "dev" to be "2" (and commit my changes to the "dev" branch)
You can think of this change being processed as (very basically):
In file file, change "1" to "2" on line 1

I change the content of file on the branch "test" to be "7" (and commit my changes to the "test" branch)
You can think of this change being processed as (very basically):
In file file, change "1" to "7" on line 1

In file file, change "1" to "7" on line 1
However, on the branch "dev", the file file doesn't have a "1" on line 1 to be changed!
Since what is being looked for does not exist, git cannot change it: Now you have a merge conflict.

Resolving the merge conflict

To resolve a conflict (I used to prefer 'meld' but now mostly just use vim):

git mergetool

-OR-

edit the file in conflict manually: Normally, search for <<< and >>> and choose which section should be correct and delete/remove the other

Once the conflict is resolved: commit

git commit -m 'Resolved conflict'
  1. http://stackoverflow.com/questions/161813/fix-merge-conflicts-in-git/7589612#7589612
  2. http://www.rosipov.com/blog/use-vimdiff-as-git-mergetool/
  3. https://help.github.com/articles/resolving-a-merge-conflict-from-the-command-line

Windows

Case Insensitivity

Not git's fault, but Windows filesystems are normally not case sensitive. It will not recognize that "THISFILE" and "thisfile" are unique files. This may mean that particular Windows clients may tell you that you have uncommitted changes from the initial time that you checkout a project. Check on the server side to see if there are any files with a similar (case-INsensitive) name. This should not happen, but has already been found "in the wild" here.

Bad files paths

If when cloning/fetching a project you have errors that the file may not be created, check the name of the file. In a project I worked on before, someone had named a file "IF" (including the quotes): Windows could not handle this.
Windows also could not handle a file named: c:\test.xls (as in: ~/git/project/c:\test.xls as a filename on a Linux system). Please use sane names for things.

Linux

gnome SSH_ASKPASS

You're trying to do a git push/pull/clone/whatever and get an error with X.

I'd bet if you do the following you'll get back some gnome BS:

echo $SSH_ASKPASS

Use unset to remove this crap

unset $SSH_ASKPASS

One-off scenarios

Remove a whole bunch of wasteful branches

git fetch --all
git pull --all
git branch -a > FILE
      

edit FILE to show only branches you want to delete

while read BRANCH; do git push origin --delete $BRANCH; done < FILE

See Also

Documentation

  1. http://git-scm.com/book/en/Git-Basics-Recording-Changes-to-the-Repository
  2. http://kovshenin.com/2011/howto-remote-shared-git-repository/
  3. http://git-scm.com/book/en/Git-Branching-Basic-Branching-and-Merging
  4. http://krisjordan.com/essays/setting-up-push-to-deploy-with-git
  5. Atlassian: Basic git commands
  6. http://git-scm.com/book/
  7. http://gitref.org/inspect/
  8. http://stackoverflow.com/questions/15736564/why-after-merge-does-git-say-already-upto-date-but-differences-between-branch
  9. https://jwiegley.github.io/git-from-the-bottom-up/
  10. http://www.rosipov.com/blog/use-vimdiff-as-git-mergetool/
  11. http://www.wei-wang.com/ExplainGitWithD3/
  12. http://pcottle.github.io/learnGitBranching/

Tutorials

  1. https://www.git-tower.com/learn/ebook
  2. https://www.codecademy.com/learn/learn-git

Videos

  1. Introduction to Git with Scott Chacon of GitHub
  2. Please. Stop Using Git. - Matthew McCullough
  3. Advanced Git: Graphs, Hashes, and Compression, Oh My!
  4. Linus Torvalds & git
  5. From the Bits Up