CHAPTER 17

image

Version Control with Git

All disasters have their tipping point, the moment at which order finally breaks down and events simply spiral out of control. Do you ever find yourself in projects like that? Are you able to spot that crucial moment? Perhaps it’s when you make “just a couple of changes” and find that you have brought everything crashing down around you (and, even worse, you’re not quite sure how to get back to the point of stability you have just destroyed). It could be when you realize that three members of your team have been working on the same set of classes and merrily saving over each other’s work. Or perhaps it’s when you discover that a bug fix that you have implemented twice has somehow disappeared from the codebase yet again. Wouldn’t it be nice if there was a tool to help you manage collaborative working, allowing you to take snapshots of your projects and roll them back if necessary, and to merge multiple strands of development? In this chapter, we look at Git, a tool that does all that and more.

This chapter will cover:

  • Basic configuration: Some tips for setting up Git
  • Importing: Starting a new project
  • Committing changes: Saving your work to the repository
  • Updating: Merging other people’s work with your own
  • Branching: Maintaining parallel strands of development

Why Use Version Control?

If it hasn’t already, version control will change your life (if only your life as a developer). How many times have you reached a stable moment in a project, drawn a breath, and plunged onward into development chaos once again? How easy was it to revert to the stable version when it came time to demonstrate your work in progress? Of course, you may have saved a snapshot of your project when it reached a stable moment, probably by duplicating your development directory. Now, imagine that your colleague is working on the same codebase. Perhaps he has saved a stable copy of the code as you have. The difference is that his copy is a snapshot of his work, not yours. Of course, he has a messy development directory, too. So you have four versions of your project to coordinate. Now imagine a project with four programmers and a web UI developer. You’re looking pale. Perhaps you would like to lie down?

Git exists exclusively to address this problem. Using Git, all of your developers can clone their own copies of the codebase from a central repository. Whenever they reach a stable point in their code, they can pull the latest code from the server and merge it with their own recent work. When they are ready, and after they have fixed any conflicts, they can push their new stable synthesis back into the shared repository.

Git is a distributed version control system. This means that, once they have acquired a branch, users commit to their own local repository without the need for a network connection. There are a number of benefits to this. It means that day-to-day operations are faster, and that you can work easily on planes and trains and in automobiles. Ultimately, however, you can share an authoritative repository with your teammates.

The fact that each developer can merge her work into a central repository means that you no longer have to worry about reconciling multiple strands of development by hand. Even better, you can check out versions of your codebase based on a date or a label. So when your code reaches a stable point, suitable for showing to a client as work in progress, for example, you can tag that with an arbitrary label. You can then use that tag to check out the correct codebase when your client swoops into your office looking to impress an investor.

Wait! There’s more! You can also manage multiple strands of development at the same time. If this sounds needlessly complicated, imagine a mature project. You have already shipped the first version, and you’re well into development of version 2. Does version 1.n go away in the meantime? Of course not. Your users are spotting bugs and requesting enhancements all the time. You may be months away from shipping version 2, so where do you make and test the changes? Git lets you maintain distinct branches of the codebase. So you might create a bug-fix branch of your version 1.n for development on the current production code. At key points, this branch can be merged back into the version 2 code (the trunk), so that your new release can benefit from improvements to version 1.n.

image Note  Git is not the only version control system available. You might also like to look into Subversion (http://subversion.apache.org/) or Mercurial (http://mercurial.selenic.com/).

This chapter is necessarily A brief introduction to a large topic. Luckily, however, Pro Git (Apress 2009) covers the topic with depth and clarity. Not only that, but a web version is available online at http://git-scm.com/book.

Let’s get on and look at some of these features in practice.

Getting Git

If you are working with a Unix-like operating system (such as Linux or FreeBSD), you may already have  Git installed and ready to use.

Try typing:

$ git help

from the command line. You should see some usage information that will confirm that you are ready to get started. If you do not already have Git, you should consult your distribution’s documentation. You will almost certainly have access to a simple installation mechanism such as Yum or Apt or you can  acquire Git directly from http://git-scm.com/downloads.

image Note  Throughout this chapter, I denote command line input by displaying it in bold text. A dollar sign ($) represents the command prompt.

Configuring a Git Server

Git is different from traditional version control systems in two key ways. First, under the hood, it stores snapshots of files, rather than the changes made to files between commits. Second, and more obviously to the user, it operates locally to your system until you choose to push to or pull from a remote repository. This means that you are not dependent on an Internet connection to get on with your work.

You do not need a single remote repository in order to work with Git, but in practice it almost always make sense to have a shared source of authority if you are working with a team.

In this section, I look at the steps needed to get a remote Git server up and running. I assume root access to a Linux machine.

Creating the Remote Repository

In order to create a Git repository I must first create a containing directory. I log into a freshly provisioned remote server via SSH. I am going to create my repository under /var/git. Generally speaking, only the root user can create and modify directories there so I run the following command as root:

$ mkdir -p /var/git/megaquiz
$ cd /var/git/megaquiz/

I create /var/git, a parent directory for my repositories, and a subdirectory for a sample project called megaquiz. Now, I can prepare the directory itself:

$ sudo git init --bare
Initialized empty Git repository in /var/git/megaquiz/

The --bare flag tells Git to initialize a repository without a working directory. Git will complain if you try to push to a repository that has not been created in this way.

At the moment only the root user can mess around under /var/git. I can change this by creating a user and a group named git and making it the directory's owner:

$ adduser git
$ chown -R git:git /var/git

Preparing the Repository for Local Users

Although this is a designated remote server, I should also ensure that local users can commit to the repository. If you’re not careful, this can cause ownership and permissions issues (especially if users with sudo privileges push code).

$ chmod -R g+rws /var/git

This gives members of the git group write access to /var/git and causes all files and directories created here to take on the git group. Now, as long as I ensure that they are members of the git group, local users will be able to write to the repository. Not only that, any files created will be writeable by other members of the group.

You can add a local user to the git group like this:

$ usermod -aG git bob

Now user bob is a member of the git group.

Providing Access to Users

The owner of the bob user mentioned in the previous section can log in to the server and interact with the repository from his shell. Generally, though, you won’t want to provide shell access to all your users. In any case, most users will prefer to take advantage of Git’s distributed nature and to work locally with their cloned data.

One way to grant a user SSH access is via public key authentication. To do this, you first need to acquire the user’s public SSH key. The user may already have this—on a Linux machine he or she will probably find the key in found in the configuration directory, .ssh, in a file named id_rsa.pub. Otherwise, he or she can easily generate a new key. On a Unix-like machine, this is a matter of running the ssh-keygen command and copying the value that it generates:

$ ssh-keygen
$ cat .ssh/id_rsa.pub

As the repository administrator, I will have asked you for a copy of this key. Once I have it, I must add it to the git user’s SSH set up on the repository server. This is merely a matter of pasting the public key into the .ssh/authorized_keys file. I may need to create the .ssh configuration directory for the first key I set up (I am running these commands from the git user’s home directory):

$ mkdir .ssh
$ chmod 0700 .ssh
$ # create authorized_keys file and paste in the user's key:
$ vi .ssh/authorized_keys

image Note  A common cause of SSH access failure is the creation of configuration files with overliberal permissions. The SSH configuration environment should be readable and writable to the account’s owner only.

Pro OpenSSH by Michael Stahnke (Apress, 2005) covers SSH comprehensively.

Closing Down Shell Access for the git User

No server should be any more open than it needs to be. You may want to enable your user to access Git commands, but probably not much more.

You can see the shell associated with a user on a Linux server by looking at the file /etc/passwd. Here is the relevant line for the git account on my remote server:

git:x:1001:1001::/home/git:/bin/bash

Git provides a special shell, named git-shell, which restricts the user to to selected commands only. I can enable this program for logins by editing /etc/passwd:

git:x:1001:1001::/home/git:/usr/bin/git-shell

To run without error, git-shell requires that you create a directory named git-shell-commands in the relevant home directory. I intend to close down all interactive functionality, and to write a message explaining this to anyone who attempts to log in. I can do this by creating a simple executable file named no-interactive-login in the git-shell-commands directory:

$ su -s /bin/bash – git
$ mkdir git-shell-commands
$ echo 'echo "Sorry. No interactive access.";' > git-shell-commands/no-interactive-login
$ chmod 755 git-shell-commands/no-interactive-login

I switch to the git user. Note that I have to specify the bash shell at this point, because I’ve already reset the default shell for git. I create the git-shell-commands directory and generate the simple message script. Finally, I make it executable.

Now, if I attempt to log in from an authorized account, I’m told the score and logged out:

$ ssh git@my-git-server

 
Enter passphrase for key '/home/mattz/.ssh/id_rsa':
Last login: Mon Oct 21 19:03:58 2013 from some-remote-server
Sorry. No interactive access.
Connection to my-git-server closed.
../AppData/Local/Microsoft/Windows/Temporary Internet
Files/Content.Outlook/NN5V35B7/spiina1003z/var/local/svn/blah

../AppData/Local/Microsoft/Windows/Temporary Internet
Files/Content.Outlook/NN5V35B7/spiina1003z/var/local/svn/blah

Beginning a Project

Now that I have a remote Git server and access to it from my local account, it’s time that I added my work in progress to the repository at /var/git/megaquiz.

Before I start, I take a good look at my files and directories, and remove any temporary items I might find. Failure to do this is a common annoyance. Temporary items to watch for include automatically generated files such as phpDocumentor output, build directories, installer logs, and so on.

image Note  You can specify files and patterns to ignore by placing a file named .gitignore in your repository. On a Linux system, the man gitignore command should provide examples of filename wildcarding that you can amend to exclude the various lock files and temporary directories created by your build processes, editors, and IDEs. This text is also available online at http://git-scm.com/docs/gitignore.

Before I go any further, I should register my identity with Git—this makes it easier to track who did what in the repository:

$ git config --global user.name "matt z"
$ git config --global user.email "[email protected]"

Now I have established my personal details and ensured that my project is clean I can set it up and push its code to the server:

$ cd megaquiz
$ git init
Initialized empty Git repository in /home/mattz/work/megaquiz/.git/

Now to add my files:

$ git add .

Git is now tracking all the files and directories under megaquiz. Tracked files can be in three states: unmodified, modified or staged. You can check on this by running the command git status:

$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#     new file:   command/Command.php
#     new file:   command/CommandContext.php
#     new file:   command/FeedbackCommand.php
#     new file:   command/LoginCommand.php
#     new file:   main.php
#     new file:   quizobjects/User.php
#     new file:   quiztools/AccessManager.php
#

Thanks to my previous git add command, all my files are staged for commit. I can go ahead now and execute the commit command:

$ git commit -m 'my first commit'
[master (root-commit) f4454af] my first commit
 7 files changed, 214 insertions(+), 0 deletions(-)
 create mode 100755 command/Command.php
 create mode 100755 command/CommandContext.php
 create mode 100755 command/FeedbackCommand.php
 create mode 100755 command/LoginCommand.php
 create mode 100755 main.php
 create mode 100755 quizobjects/User.php
 create mode 100755 quiztools/AccessManager.php

I add a message via the -m flag. If I omitted this, then Git would launch an editor that I can use to add my check in message.

If you are accustomed to version control systems such as CVS and Subversion, you might think that we’re done. Actually, although I could happily continue editing, adding, committing, and branching from here, there is an additional stage I need to consider if I want to share this code using a central repository. As we will see later on in the chapter, Git allows us to manage multiple project branches. Thanks to this feature, I can maintain a branch for each release, and keep my bleeding-edge risky development safely out of my production code. When we start out, Git sets up a single branch named master. I can confirm the state of my branches with the command git branch:

$ git branch -a
* master

The -a flag specifies that Git should show us all branches (the default is to omit the remote ones). And the output shows the master branch.

In fact, I have done nothing yet to associate my local repository with the remote server. Time to put that right:

$ git remote add --track master origin  git@mygitserver:/var/git/megaquiz

This command is disappointingly quiet, given the work that it has done. In fact, it is the equivalent of telling Git to “associate the nickname origin with the given server location. Furthermore, set up a tracking relationship between the local branch master and a remote equivalent.”

To confirm all of this, I check with Git that the remote handle origin has been set up:

$ git remote -v
origin     git@mygitserver:/var/git/megaquiz (fetch)
origin     git@mygitserver:/var/git/megaquiz (push)

I still haven’t sent any actual files to my Git server, however. So that’s my next step:

$ git push origin master
Enter passphrase for key '/home/mattz/.ssh/id_rsa':
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (12/12), 2.65 KiB, done.
Total 12 (delta 1), reused 0 (delta 0)
To git@mygitserver:/var/git/megaquiz
 * [new branch]      master -> master

Now I can run the git branch command again to confirm that the remote version of the master branch has appeared:

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

image Note  I worked quite hard here to ensure that I established what is called a tracking branch. This is a local branch that is associated with a remote twin. Thanks to this, Git knows that when I push changes from the master branch I intend them to end up on the remote origin/master branch. When you clone a repository, Git automatically establishes this tracking relationship for you.

Cloning the Repository

For the purposes of this chapter, I have invented a team member named Bob. Bob is working with me on the MegaQuiz project. Naturally, he wants his own version of the code. I have already added his public key to the Git server, so he is good to go.

Bob can acquire the repository using the command git clone:

$ git clone git@mygitserver:/var/git/megaquiz
Cloning into 'megaquiz'...
Enter passphrase for key '/home/bob/.ssh/id_rsa':
remote: Counting objects: 12, done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 12 (delta 1), reused 0 (delta 0)
Receiving objects: 100% (12/12), done.
Resolving deltas: 100% (1/1), done.

Now both Bob and I can develop locally and, when we’re ready, share our code with one another.

Updating and Committing

Bob is, of course, a fine and talented fellow. Except, that is, for one common and highly annoying trait: he cannot leave other people’s code alone.

Bob is smart and inquisitive, easily excited by shiny new avenues of development, and keen to help optimize new code. As a result, everywhere I turn, I seem to see the hand of Bob. Bob has added to my documentation; Bob has implemented an idea I mentioned over coffee. I may have to kill Bob. In the meantime, however, I must handle the fact that the code on which I am working needs to be merged with Bob’s input.

Here’s a file called quizobjects/User.php. At the moment, it contains nothing but the barest of bones:

<?php
class User {}
?>

I have decided to add some documentation. As you know from the last chapter, I should add file and class comments. I begin by adding the file comment to my version of the file:

<?php
/**
 * @license   http://www.example.com Borsetshire Open License
 * @package  quizobjects
 */
 
class User {}
?>

Remember that a file can have three states: unmodified, modified and staged. The User.php file has now moved from unmodified to modified. I can see this with the git status command:

$ git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#     modified:   quizobjects/User.php
#
no changes added to commit (use "git add" and/or "git commit -a")

Now User.php is modified but not staged for commit. I can change this state using the command git add.

$ git add quizobjects/User.php
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#     modified:   quizobjects/User.php
#

Now I am ready to commit:

$ git commit -m'added documentation' quizobjects/User.php
[master 2302dd8] added documentation
 1 files changed, 5 insertions(+), 0 deletions(-)

A Git commit only affects my local repository. If I am sure that the world is ready for my change, I must push my code to the remote repository:

$ git push
Enter passphrase for key '/home/mattz/.ssh/id_rsa':
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 409 bytes, done.
Total 4 (delta 1), reused 0 (delta 0)
To git@mygitserver:/var/git/megaquiz
   f4454af..2302dd8  master -> master

Meanwhile, working in his own sandbox, Bob is keen as ever, and he has created a class comment:

<?php
 
/**
 * @package  quizobjects
 */
class User {}
?>

Now it’s Bob’s turn to add, commit and push. Because the adding and committing parts of this are so commonly run together, Git allows you to combine them into a single command:

$ git commit -a -m'my great documentation'
[master 2d880e6] my great documentation
 1 files changed, 3 insertions(+), 0 deletions(-)

So we now have two distinct versions of User.php. There’s the version I just pushed to the remote repository, and there is Bob’s version, committed but not yet pushed. What will happen when Bob tries to push his local version to the remote repository?

$ git push
Enter passphrase for key '/home/bob/.ssh/id_rsa':
To git@mygitserver:/var/git/megaquiz
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@mygitserver:/var/git/megaquiz'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again.  See the
'Note about fast-forwards' section of 'git push --help' for details.

As you can see, Git won’t let you push if there’s a nontrivial update to apply. Bob must first pull down my version of the User.php file:

$ git pull
Enter passphrase for key '/home/bob/.ssh/id_rsa':
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 1), reused 0 (delta 0)

Unpacking objects: 100% (4/4), done.
From mygitserver:/var/git/megaquiz
   f4454af..2302dd8  master     -> origin/master
Auto-merging quizobjects/User.php
CONFLICT (content): Merge conflict in quizobjects/User.php
Automatic merge failed; fix conflicts and then commit the result.

Git will happily merge data from two sources into to the same file so long as the changes don’t overlap. Git has no means of handling changes that affect the same lines. How can it decide what is to have priority? Should the repository overwrite Bob’s changes, or the other way around? Should both changes coexist? Which should go first? Git has no choice but to report a conflict and let Bob sort out the problem.

Here’s what Bob sees when he opens the file:

<?php
/**
<<<<<<< HEAD
 * @package  quizobjects
 */
=======
 * @license   http://www.example.com Borsetshire Open License
 * @package  quizobjects
 */
>>>>>>> 2302dd80ad0da8699971bd70e49f43b6fd431518
class User {}
?>

Git includes both Bob’s comment and the conflicting changes, together with metadata that tells him which part originates where. The conflicting information is separated by a line of equals signs. Bob’s input is signaled by a line of less-than symbols followed by “HEAD”. The remote changes are included on the other side of the divide.

Now that Bob has identified the conflict, he can edit the file to fix the  collision:

<?php
/**
 * @license   http://www.example.com Borsetshire Open License
 * @package  quizobjects
 */
 
/**
 * @package  quizobjects
 */
class User {}
?>

Next, Bob resolves the conflict by staging the file:

$ git add quizobjects/User.php
$ git commit -m'documentation merged'
[master a407692] documentation merged

And now, finally he can push to the remote repository:

$ git push

Adding and Removing Files and Directories

Projects change shape as they develop. Version control software must take account of this, allowing users to add new files and remove deadwood that would otherwise get in the way.

Adding a File

You have seen the add subcommand many times already. I used it during my project set up to add my code to the empty megaquiz repository, and subsequently to stage files for commit. By running git add on an untracked file or directory, you ask Git to track it—and stage it for commit. Here I add a document called Question.php to the project:

$ touch quizobjects/Question.php
 
$ git add quizobjects/Question.php

In a real-world situation, I would probably start out by adding some content to Question.php. Here, I confine myself to creating an empty file using the standard touch command. Once I have added a document, I must still invoke the commit subcommand to complete the addition:

$ git commit -m'initial checkin'
[master 96da108] initial checkin
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 quizobjects/Question.php

Question.php is now in the local repository.

Removing a File

Should I discover that I have been too hasty and need to remove the document, it should come as no surprise to learn that you can use a subcommand called rm.

$ git rm quizobjects/Question.php
rm 'quizobjects/Question.php'

Once again, a commit is required to finish the job. As usual, I can confirm this by running git status:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#     deleted:    quizobjects/Question.php
#
$ git commit -m'removed Question'
[master 0d571b2] removed Question
 0 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 quizobjects/Question.php

Adding a Directory

You can also add and remove directories with add and rm. Let’s say Bob wants to make a new directory available:

$ mkdir resources
$ touch resources/blah.gif
$ git add resources/
 
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#     new file:   resources/blah.gif
#

Notice how the contents of resources are added automatically to the repository. Now Bob can commit and then push the whole lot to the remote repository in the usual way.

Removing Directories

As you might expect, you can remove directories with the rm subcommand. In this situation, however, I must tell Git that I wish it to remove the directory’s contents by passing an -r flag to the subcommand. Here, I profoundly disagree with Bob’s decision to add a resources directory:

$ git rm -r resources/
rm 'resources/blah.gif'

Tagging a Release

All being well, a project will eventually reach a state of readiness, and you will want to ship it or deploy it. Whenever you make a release, you should leave a bookmark in your repository so that you can always revisit the code at that point. As you might expect, you can create a tag in your code with the git tag command:

$ git tag -a 'version1.0' -m 'release 1.0'

You can see the tags associated with your repository by running git tag with no arguments:

$ git tag
version1.0

We have been working locally up until this point. In order to get your tag onto the remote repository, you must use the --tags flag with the git push subcommand:

$ git push --tags
Enter passphrase for key '/home/mattz/.ssh/id_rsa':
Counting objects: 1, done.
Writing objects: 100% (1/1), 160 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@mygitserver:/var/git/megaquiz
 * [new tag]         version1.0 -> version1.0

Once you can bookmark your code with a tag, it makes sense to wonder how you might go about revisiting old releases. For this, however, we should first spend some time looking at branching—something at which Git is particularly good.

Branching a Project

Once my project has been released, I can pack it away and wander off to do something new, right? After all, it was so elegantly written that bugs are an impossibility and so thoroughly specified that no user could possibly require any new features!

Meanwhile, back in the real world, I must continue to work with the codebase on at least two levels. Bug reports should be trickling in right about now, and the wish list for version 1.2.0 swelling with demands for fantastic new features. How do I reconcile these forces? I need to fix the bugs as they are reported, and I need to push on with primary development. I could fix the bugs as part of development and release in one go when the next version is stable. But then users may have a long wait before they see any problems addressed. This is plainly unacceptable. On the other hand, I could release as I go. Here, I risk shipping broken code. Clearly, I need two strands to my development. I will continue to add new and risky features to the project’s main branch (often called the  trunk), but I should now create a branch for my new release on which I can add only bug fixes.

I can both create and switch to a new branch using the git checkout command. First, let’s take a quick look at the state of my branches:

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

As you can see I have a single branch: master and its remote equivalent. Now, I will create and switch to a new branch:

$ git checkout -b megaquiz-branch1.0
Switched to a new branch 'megaquiz-branch1.0'

To track my use of branches I will use a particular file, command/FeedbackCommand.php, as an example. It seems that I created my bugfix branch just in time. Users have started to report that they are unable to use the feedback mechanism in the system. I locate the bug:

        //...
        $result = $msgSystem->dispatch( $email, $msg, $topic );
        if ( ! $user ) {
            $this->context->setError( $msgSystem->getError() );
        //...

I should, in fact, be testing $result, and not $user. Here is my edit:

        //...
        $result = $msgSystem->dispatch( $email, $msg, $topic );
        if ( ! $result ) {
            $this->context->setError( $msgSystem->getError() );
        //...

Because I am working on the branch megaquiz-branch1.0, I can commit this change:

$ git add command/FeedbackCommand.php
$ git commit -m'bugfix'
[megaquiz-branch1.0 5c963b6] bugfix
 1 files changed, 1 insertions(+), 1 deletions(-)

Of course, this commit is local. I need to use the git push command to get the branch on to the remote repository:

$ git push -u origin megaquiz-branch1.0
Enter passphrase for key '/home/mattz/.ssh/id_rsa':
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 431 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
To git@mygitserver:/var/git/megaquiz
 * [new branch]      megaquiz-branch1.0 -> megaquiz-branch1.0
Branch megaquiz-branch1.0 set up to track remote branch megaquiz-branch1.0 from origin.

Remember that origin is the nickname for our remote repository. The -u flag ensures that the branch on the local repository tracks that on the remote one. If you omit the flag when you push the branch, you can set the tracking relationship after the fact like this:

$ git branch --set-upstream megaquiz-branch1.0 origin/megaquiz-branch1.0

Now, what about Bob? He will inevitably want to pitch in and fix some bugs. First, he invokes git pull, which helpfully tells him about the new branch:

$ git pull
Enter passphrase for key '/home/bob/.ssh/id_rsa':
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From mygitserver:/var/git/megaquiz
 * [new branch]      megaquiz-branch1.0 -> origin/megaquiz-branch1.0
Already up-to-date.

Now Bob must create a local branch that tracks the remote one:

$ git checkout -b megaquiz-branch1.0 origin/megaquiz-branch1.0
Branch megaquiz-branch1.0 set up to track remote branch megaquiz-branch1.0 from origin.
Switched to a new branch 'megaquiz-branch1.0'

Bob is good to go now. He can add and commit his own fixes, and when he pushes they will end up on the remote branch.

Meanwhile, I would like to add some bleeding-edge enhancements on the trunk—that is, my master branch. Let’s look again at the state of my branches from the perspective of my local repository:

$ git branch -a
  master
* megaquiz-branch1.0
  remotes/origin/master

I can switch to an existing branch by invoking git checkout, but this time without the -b flag:

$ git checkout master
Switched to branch 'master'

When I look now at command/FeedbackCommand.php, I see that my bug fix has magically disappeared. Of course, it’s still stored under megaquiz-branch1.0—and later I can merge the fix into the master branch, so there’s no need to worry. Instead, I can focus on adding new code:

class FeedbackCommand extends Command {
    function execute( CommandContext $context ) {
        // new and risky development
        // goes here
        $msgSystem = ReceiverFactory::getMessageSystem();
        $email = $context->get( 'email' );
        $msg = $context->get( 'pass' );
        $topic = $context->get( 'topic' );
        $result = $msgSystem->dispatch( $email, $msg, $topic );
        if ( ! $user ) {
            $this->context->setError( $msgSystem->getError() );
            return false;
        }
        $context->addParam( "user", $user );
        return true;
    }
}

All I have done here is to add a comment to simulate an addition to the code.

So I now have parallel branches. Of course, sooner or later, I will want my master branch to benefit from the bug fixes that I have committed on megaquiz-branch1.0. This is pretty easy. Git provides a subcommand named merge:

$ git checkout master
Switched to branch 'master'
$ git merge megaquiz-branch1.0
Updating 102966e..5c963b6
Fast-forward
 command/FeedbackCommand.php |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

image Note  To merge or not to merge? The choice is not always as straightforward as it might seem. In some cases, for example, your bug fix may be the kind of temporary work that is supplanted by a more thorough refactoring on the trunk, or it may no longer apply due to a change in specification. This is necessarily a judgment call. Most teams I have worked in, however, tend to merge to the trunk where possible while keeping work on the branch to the bare minimum. New features for us generally appear on the trunk and find their way quickly to users through a “release early and often” policy.

Now, when I look at the version of FeedbackCommand in the trunk, I confirm that all changes have been merged:

    function execute( CommandContext $context ) {
        // new and risky development
        // goes here
        $msgSystem = ReceiverFactory::getMessageSystem();
        $email = $context->get( 'email' );
        $msg = $context->get( 'pass' );
        $topic = $context->get( 'topic' );
        $result = $msgSystem->dispatch( $email, $msg, $topic );
        if ( ! $result ) {
            $this->context->setError( $msgSystem->getError() );
            return false;
        }
        $context->addParam( "user", $user );
        return true;
    }

The execute() method now includes both my simulated trunk development and the bug fix.

I created a branch when I first “released” MegaQuiz version 1.0 and that’s what we have been working with. Remember, however, that I also created a tag at that stage. I promised at the time that I would show you how to access the tag. In fact, you’ve already seen how. You can create a local branch based on the tag in just the same way that Bob set up his local version of our bugfix branch. The difference is that this new branch is entirely fresh. It does not track an existing remote branch:

$ git checkout -b new-version1.0-branch version1.0
Switched to a new branch 'new-version1.0-branch'

However, now that I have this new branch, I can push it and share it just as you have seen.

Summary

Git comprises an enormous number of tools, each with a daunting range of options and capabilities. I can only hope to provide a brief introduction in the space available. Nonetheless, if you only use the features that I have covered in this chapter, you should see the benefit in your own work, whether through protection against data loss or improvements in collaborative working.

In this chapter, we took a tour through the basics of Git. I looked briefly at configuration before importing a project. I checked out, committed, and updated code, finally tagging and exporting a release. I ended the chapter with a brief look at branches, demonstrating their usefulness in maintaining concurrent development and bug-fix strands in a project.

There is one issue that I have glossed over here to some extent. We established the principle that developers should check out their own versions of a project. On the whole, however, projects will not run in place. In order to test their changes, developers need to deploy code locally. Sometimes, this is as simple as copying over a few directories. More often, however, deployment must address a whole range of configuration issues. In the next chapter, we will look at some techniques for automating this process.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.205.169