notes blog about

2014-01-06

Although I’m more of a sysadmin than a developer I often write scripts (in Perl or Bash). I don’t work within a big group of developers, so I try to keep things simple. I tend to use Git for tracking my programs. Git is a free & open source, distributed version (revision) control system created by Linus Torvalds. Every Git repository contains complete history of revisions and is not dependent on a central server or network access. Git uses an intelligent compression system to reduce the cost of storing the entire history. Branching and merging are fast and (relatively :-) easy to do.

Configuration

First-time setup

Introduce yourself to git with your name and public email address before doing any operation:

git config --global user.name "Jeffrey Lebowski"
git config --global user.email "jlebowski@dude.org"
git config --global github.login=jlebowski
git config --global github.email=jlebowski@gmail.com

You only need to do this once. You might also like to add some aliases and other configuration settings.

Everyday configuration

You can see your configuration like this:

$ git config --list    # output depends on whether you're in a git repo directory or not
## OR
$ cat ~/.gitconfig

If you want to manage the current repository configuration just leave out the --global option or use the local config file .git/config.

Gir repository

To start using git, you can either get a project from the Internet or create a new one.

Getting a git repository

## the Linux kernel (approx. 150MB download):
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
## any server with ssh and git
$ git clone ssh://[user@]server.xy/path/to/repo.git/

Starting a git repository

$ mkdir project
$ cd project
$ git init

You’ve now initialized the working directory. You may notice a new directory created, named .git. Git stores all of its stuff in this hidden directory. If you want Git to stop tracking your files, just remove .git.

Next, tell git to take a snapshot [in svk: momentka] of the contents of all files under the current directory (note the .), with git-add:

$ git add .     # add files in working directory to a temporary storage - index

This snapshot is now stored in a temporary staging area which git calls the “staging index” or just “index”. You can permanently store the contents of the index in the repository with git-commit:

$ git commit    # add files in index to a permanent storage - repository (.git directory)

This will prompt you for a commit message. You’ve now stored the first version of your project in git.

Making changes

When dealing with git, it’s best to work in small bits. Rule of thumb: if you can’t summarise it in a sentence, you’ve gone too long without committing.

The typical working cycle is:

  1. Work on your project.

  2. Check which files you have changed:

    $ git status
    
  3. Check what the actual changes were:

    $ git diff
    
  4. Add any files/folders mentioned in step 2:

    $ git add file1 newfile2 newfolder3
    
  5. You are now ready to commit. You can see what is about to be committed using git-diff with the --cached option:

    $ git diff --cached
    

    (Without --cached, git-diff will show you any changes that you’ve made but not yet added to the index.) If you need to make any further adjustments, do so now, and then add any newly modified content to the index. Finally, “commit” your changes with:

    $ git commit
    

    This will again prompt you for a message describing the change, and then record a new version of the project.

Alternatively, instead of running git-add beforehand, you can use

$ git commit -am "commit message"

which will automatically notice any modified (but not new) files, add them to the index, and commit, all in one step.

A note on commit messages: Though not required, it’s a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. Tools that turn commits into email, for example, use the first line on the Subject: line and the rest of the commit in the body.

Excluding some files

To set certain files or patterns to be ignored by Git, you must either modify the $GIT_DIR/info/exclude file or create a file called .gitignore in your project’s root directory. The former is not shared between repositories (so you can set ignores for your specific environment), while the .gitignore is usually checked into Git and distributed along with everything else.

For example if you don’t want to track dot-files, setup .gitignore like this:

.*
!.gitignore

See more here.

If some of your files is already beeing tracked by git, you can untrack it like this:

git rm --cached <filename>

Undo/Redo

Use git-reset only when you haven’t pushed the changes to the remote. If you have pushed use git-revert.

See How to reset, revert, and return to previous states in Git and What’s the difference between Git Revert, Checkout and Reset? for more.

Reset

Go back and forget about every change past a certain point:

git reset --hard SHA1_HASH  # all newer commits get erased forever!!!

This invocation of git-reset makes changes to the existing repo history. Shouldn’t be used if you have pushed to remote.

Revert

Create a new commit that undoes one or more commits:

git revert HEAD                 # undo the last commit
git revert SHA1_HASH SHA1_HASH  # undo the last two commits

git-revert adds new history to the project it doesn’t modify existing history.

Checkout

Similar in effect to git-revert:

git checkout SHA1_HASH .    # note the dot

Travel back in time:

git checkout SHA1_HASH

… if you now edit and commit, you will be in an alternate reality (called a branch).

git-checkout doesn’t modify existing history.

Revert last merge containing multiple commits

$ git show :/^Merge
Merge: 805cf9c 11e94f9
<...snip...>

$ git checkout 805cf9c .
$ git commit -m "revert last merge of test into master"

Branching and merging

Branch is a separate line of development.

To stop Git complaining, always commit or reset your changes before running checkout.

Create and switch to a new branch

git checkout -b experimental_idea

Compare two branches

# from within e.g. experimental_idea branch
git diff ..master
git diff master..
git log ..master
git log master..

Push a new local branch to remote

git push -u origin experimental_idea

Merge two branches

git checkout master
git merge experimental_idea

Delete a branch

git branch -d experimental_idea # you might need -D if not merged

# delete a remote branch
git push origin --delete experimental_idea

My workflow

if [[ $CURRENT_BRANCH == "master" ]]; then
    run_cmd 'git pull'
    run_cmd 'git push'
else
    #run_cmd 'git pull'
    run_cmd 'git checkout master'
    run_cmd 'git pull'
    run_cmd "git checkout $CURRENT_BRANCH"
    run_cmd 'git merge master'
    run_cmd 'git push'
fi

See git-sync for my current setup.

Tags

You can specific points in a repo’s history as being important. Typically, people use this functionality to mark release points (v1.0, v2.0, …).

git fetch --all --tags  # fetch all tags from remote
git tag | sort -V       # list existing local tags sorting by version

git tag -a v0.0.2 -m "improve naming"  # add new tag locally
git push --tags                        # push local tags to remote

git tag -d v0.0.1                # delete local tag
git push --delete origin v0.0.1  # delete remote tag

Tips and Tricks

List tracked files

# currently tracked files under master branch
git ls-tree -r master --name-only

# files that ever existed (i.e. including deleted files)
git log --pretty=format: --name-only --diff-filter=A | sort - | sed '/^$/d'

Remove files not tracked by Git (like log files, zipped files, compiled files)

git clean -n  # dry-run ...
git clean -f  # files removed!

Apply changes generated via git diff [--binary]

git apply --ignore-space-change --ignore-whitespace
for f in `find -type f`; do
        git log -1 --date=iso -- $f |
        grep ^Author |
        perl -wnla -s -F: -e 'print "$file --" . $F[1]' -- -file=$f
done

Ignore changes of the files’ mode (for current repo)

git config core.fileMode false

Sync a GitHub fork

cd ~/git/hub/grokking-algorithms-golang
git remote add upstream https://github.com/jonatasbaldin/grokking-algorithms-golang
git remote -v
git fetch upstream
git merge upstream/master
git push

source

More