Here’s the fourth post in a series of Git posts intended to help programmers understand Git. These posts are primarily targeted towards Windows using CVS for programmers to help them move to Git for versioning. The previous two posts covered the following:
- Setting up Git on a Windows machine and creating the first local repository.
- Creating a bare repository and sharing files with users.
- Creating branches, merging branches and resolving the conflicts that may occur.
As promised in the earlier post we are going to discuss about identifying a deviation, fast-forward merges and a little tip on speeding up typing the commands in this post.
I am quite fed up of the repetitive typing that I am going to make you wait for the other answers :). The tip first: Bash, the original shell after which the git bash is created on windows, is a unix shell. A shell is a command line user interface. Bash allows for setting some command aliases. Aliases are typically shorter commands that are expanded internally to full commands. They save the typing time.
The aliases are configured from a simple text file in your home directory. Now ‘home’ in windows is the “Document’s and settings” of the logged in user. (Not same as my documents folder, generally its parent). You should be able to see a .gitconfig file there too. Now in this folder, we add a new file “.bashrc”, if not already present. It is here we configure our bash.
To begin with, we would be adding some of the most commonly used commands, but as you advance, feel free to add more.
alias gs='git status '
alias ga='git add '
alias gc='git commit'
alias gb='git branch '
alias go='git checkout '
Of course, you can change the aliases to your liking.
There is another way of adding aliases, from within Git. Let’s check that one out too. In Git bash:
git config --global alias.st status
git config --global alias.a add
git config --global alias.ci commit
git config --global alias.br branch
git config --global alias.co checkout
Why both? There are benefits and drawbacks of each method. The aliases from within Git are not understood by bash and so when typing you need to type ‘git’ anyway. So an aliased command would look like ‘git st’ or ‘git br’ and opposed to shorter command alias set in .bashrc: ‘gs’ or ‘gb’.
The .bashrc method has a drawback of autocompletion not being fully supported from git. As these aliases are handled at bash level, Git does not suggest command completion. For example, typing ‘git add ’ and hitting tab intelligently cycles you only through files/folders that can be added to the repo and not all the folders. Typing ‘ga’ and hitting tab does nothing!
Personally, I tend to use both, where a support for file/branch suggestion is required; git aliases come handy, for all other uses .bashrc method comes handy. Now that we are acquainted with the shorter commands, I am finding it difficult to resist using them.
Now to the fast forward merge: a fast forward merge is possible when the parent branch is not modified after the branch being merged diverged from it. Normally when two branches merge, a commit has to be done marking the merge, but in case of a fast-forward merge, this commit is not needed. Instead the HEAD can just be shifted to the latest commit. This also causes the history to look ‘common’. non-fast-forward merges allow for ‘abstracting’ the history from the parent branch.
In the era of CVS and manual merging of branches, it was very important to identify the ‘common ancestor’. Git handles it all automatically when a merge is called. But while resolving conflicts in a multi-developer environment it comes handy knowing where a conflicting change has come in from. Git identifies only three things for us: changes in current branch, changes in branch being merged and rest of the file (common) as it is in common ancestor. Having the knowledge of common ancestor can help resolve conflicts.
Let’s take a look at this: a common ancestor is the latest commit that is common to both branches being merged.
Fig. 1: a sample Git repository, letters denoting branches.
Consider Fig.1. Let us assume that the branches do not have any commits after new branches are created from them. The common ancestor in following scenarios will be:
- merge A & B: master.
- merge B & C: master.
- merge D & C: A.
- merge F & D: A.
- mmerge E & F: C
- merge B & D: master!
When a merge between E & F is being done, the content of the file shown as ‘common’ would be from the commit in branch C with changes done in branch E and F marked. When merging D and C, it would be from A. Simple? One question, what will happen if we merged F and A?