waqas bhatti / notes / some notes on git

Setting up git and git repositories

Set up some local variables:

$ git config --global user.name 'User Name on [machine]'
$ git config --global user.email 'user-email@example.com'
$ git config --global color.ui true

To make a git repo of the current directory:

$ git init

To add the entire current directory to git source control:

$ git add .

To add a specific file in the current directory to git source control:

$ git add some.file 

Committing changes to git

Once files are being tracked by git, you need to commit any changes you've made into the git repo. This can be done in two ways. The first is to write a one-line commit message indicating what changed:

$ git commit -am 'initial commit message'

The second way involves launching an editor to explain your changes in detail (using as many lines as you want):

$ git commit -a

If you want to revert changes to a file that you've saved, but not yet commited to git, you can use:

$ git checkout -- [file-to-revert]

This effectively returns the file to the last committed version and discards any changes made since then.

If you want to reset the current state of the repository to an earlier state (this might happen when a change you made results in a catastrophe), use the following:

$ git reset [commit name] --hard

This requires that the current state of the repository have no uncommitted changes. This also completely wipes out any history of the commits between the current state and the state you reset to ([commit name]). The commit id [commit name] is usually the short id shown by git log --oneline.

Git branches and merging

Create a git branch to work on some feature in isolation from the master branch:

$ git branch [new-branch-name]

To switch to a branch, use git checkout:

$ git checkout [branch]

To look at the changelog:

$ git log

To merge changes from another branch to the current branch:

$ git merge [other-branch-name]

If an automatic merge fails, you will have to resolve the changes manually. One way to do this is to launch a diff viewer such as vimdiff, or meld:

$ git pull staging master
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From remote-repo
 * branch            master     -> FETCH_HEAD
Auto-merging problematic-file.txt
CONFLICT (content): Merge conflict in problematic-file.txt
Automatic merge failed; fix conflicts and then commit the result.

$ git mergetool
merge tool candidates: meld [...blah, blah, tool names...] vimdiff
Merging:
problematic-file.txt

Normal merge conflict for 'problematic-file.txt':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (vimdiff):

[fix merge conflicts using the merge resolution tool]

If you'd rather replace your local copy with the remote copy of the problematic file:

$ git checkout --theirs problematic-file.txt

Once you've resolved the merge conflicts, add the changed file back to the git tracking repository:

$ git add problematic-file.txt
$ git commit -am 'fixed merge conflict for problematic-file.txt'

See this Stack Overflow question for more hints on how to resolve git merge conflicts manually.

Storing changes to the current branch temporarily

You can temporarily store your uncommitted changes to the current branch using git stash. An example is shown below:

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 17 commits.
#
# Changes not staged for commit:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#	modified:   lc_actions.py
#	modified:   search_utils.py
#	modified:   var_search.py
#
no changes added to commit (use "git add" and/or "git commit -a")

$ git stash
Saved working directory and index state WIP on master: 550a557 more work to get UID direct info working
HEAD is now at 550a557 more work to get UID direct info working

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 17 commits.
#
nothing to commit (working directory clean)

You can then recall these changes later and apply them to the current branch using git stash apply, like in the following example:

$ git checkout cat-search-newdb
Switched to branch 'cat-search-newdb'

$ git stash list
stash@{0}: WIP on master: 550a557 more work to get UID direct info working

$ git stash apply
# On branch cat-search-newdb
# Changes not staged for commit:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#	modified:   lc_actions.py
#	modified:   search_utils.py
#	modified:   var_search.py
#
no changes added to commit (use "git add" and/or "git commit -a")

Dealing with other git repositories

To clone a git repo in full:

$ git clone user@remote.example.com:/path/to/repo  -- remote repo over ssh
$ git clone path/to/repo  -- local repo

A bare git repository can be useful in some situations. This will act as a central or staging code repository with code being checked in and out. Make an empty directory to serve as this staging repo, and then inside this directory, do:

$ git init --bare

To pull from a remote repo:

$ git pull ssh://user@remote.example.com:/path/to/repo master

This pulls the master branch from the remote repo.

To pull from a local repo:

$ git pull /path/to/repo master

This pulls the master branch from the other repo.

To set up repo aliases:

$ git remote add repo-alias ssh://user@remote.example.com:/path/to/remote/repo

or

$ git remote add repo-alias /path/to/local/repo

To deal with nonstandard SSH ports for remote repos, use the following:

$ git remote add repo-alias ssh://user@remote.example.com:port/path/to/remote/repo

To push local changes in the master branch to a remote BARE repo (set up with git init --bare):

$ git push remote-alias master

To list remote aliases:

$ git remote -v

To remove remote aliases:

$ git remote rm remote-alias

Deploying websites/webapps with git

  1. set up a bare git repo to serve as the staging repo
  2. git init in the local project directory
  3. git commit the local project directory
  4. git push the local repo to the remote staging repo
  5. git pull from the remote staging repo to the deployment directory

Now all changes made in the local repo can be pushed to staging, and then pulled from there to the deployment repo. I use this method to deploy this website.

Automating parts of the git workflow

Git comes with launchable shell scripts that fire before/after certain events in the commit process take place. Git places examples of these scripts in the local directory in the .git/hooks subdirectory. You can use these to automate any part of the workflow you wish.

One particular case I've found useful is to automatically backup local git repositories to a couple of remote repositories outside of normal work hours when code is committed. This ensures that at the end of the day, your code is safe in multiple locations. For this, I use the post-commit git hook. An example listing is below:

#!/bin/sh
#
# An example hook script that is called after a successful
# commit is made.
#
# To enable this hook, rename this file to 'post-commit'.

CURR_TIME=`date +%H%M`

if [ $CURR_TIME -gt 1700 ]; then
    echo 'OUTSIDE WORKING HOURS: auto-pushing master branch to backup repos'
    git push backup-repo-one master
    git push backup-repo-two master
fi

if [ $CURR_TIME -lt 0800 ]; then
    echo 'OUTSIDE WORKING HOURS: auto-pushing master branch to backup repos'
    git push backup-repo-one master
    git push backup-repo-two master
fi

In the case outlined above, backup-repo-one and backup-repo-two are bare git repositories.

Emacs and git

Emacs is git aware and will allow commits with C-x v v. This pulls up a buffer asking for the commit message. C-c C-c will close the buffer and commit the changes to the local git repo.

References