Chapter 5. Teams of One

Although this book is aimed at teams of more than one, there are often times when we are working as a team of one—a solo developer. This might be a personal side project, or you might actually be the only developer on your team. Working solo with no team constraints can be intimidating because there’s no one available to walk you through what you should do, or help you if you get stuck. In this chapter, I’ll show you how I do my work when I’m working on my own projects. Of course there are places where I get tempted to cut corners as a solo developer (after all, no one is watching over my shoulder, so who would know if I took a little shortcut here or there?). Where I can, I will show you the implications of those shortcuts.

By the end of this chapter, you will be able to:

  • Create a local copy of a remote repository

  • Initialize version control for an existing set of files

  • Create a new repository from an empty project directory

  • Examine the history of a repository via its commit messages

  • Work with branches to isolate different streams of work

  • Make commits to a local repository

  • Use tags to highlight individual commits

  • Connect your project to a remote code hosting system

If you are a creator (as opposed to a reviewer or manager), the majority of your time will likely be spent using the commands outlined in this chapter. Being able to work effectively with all of the tools outlined here should be considered a prerequisite to the remaining chapters in this part.

Those who learn best by following along with video tutorials will benefit from Collaborating with Git (O’Reilly), the companion video series for this book.

Issue-Based Version Control

Someone once told me that the person who can best describe a problem is the most likely to solve it. In writing this book, I’ve found that to be entirely too true. When I write myself a TODO item that is vague, such as “finish chapter 4,” I rarely feel motivated to work on the book. But when I write the task as “write-up sample workflow for small teams like Mai’s,” I become way more motivated to dive into the writing. This isn’t unique to writing books, though. As a team of one, you might not feel entirely motivated to work on your code. If you’re like me, though, if the work is re-framed as a way to help a person, you’re more likely to get it done.

Tip

If you’ve never thought about what motivates you as a developer, you may enjoy Joe Shindelar’s presentation “A Developer’s Primer to Managing Developers”.

You might be asking yourself, “what does this have to do with Git?” Each time you sit down to work on a project in source control, you should have an idea about what you’re trying to do. It doesn’t matter if you’re developing a new feature, fixing a bug, refactoring old code, or just trying out a new idea; you should still have some kind of motivation for tinkering. There are a lot of different ways to write down what you want to work on, but the following works nicely and can be more rewarding than just working on tickets.

The ticket has three main parts:

Problem

A terse description of what you’re trying to do

Rationale

The reason why you’d want to do this (who will it help if this problem is solved?)

Quality assurance test

How will I know that this problem has been solved?

This format is quite similar to another that I’ve seen used for Agile projects:

Card

A terse description of the problem, written from the perspective of the user

Conversation

Details about the problem you’re trying to solve; where possible, it should avoid prescribing solutions

Confirmation

The steps a user (from the first part) will be able to take to verify the problem has been solved

In a team of one, you might feel that the overhead of a ticketing system is a bit much for you. Perhaps your paper notebook is sufficient. I often think this is true, but then as I get working on my project, I start to lose track of all the little ideas I had. Sometimes I start a new branch for each idea, but then end up getting buried under an avalanche of out-of-date branches. If this sounds like you, take a moment now to find the ticket tracking options in whatever code hosting system you use, and start to get into the habit of writing yourself love notes for what you plan to do with your software. At the very least, it will give you arbitrary numbers that you can use to create branches and help you keep track of your code.

Tip

If you don’t have a code hosting system yet, I recommend GitLab, or its free online offering, GitLab.com. It will allow you to create private repositories with unlimited collaborators for free, and it can be installed on a local network if you are learning Git behind a firewall. The advantage of a private repository is that you can hide your work while you learn. If your work is hidden, you won’t be able to take advantage of community support, but I understand if you’re a bit shy right now. It happens to the best of us.

Once you have a way of tracking your ideas, the process for doing work should follow these steps:

  1. Create a new ticket in your issue tracking system; note the number on the issue.

  2. In your local repository, create a new branch using the format issuenumber-description.

  3. Do the work described in the ticket (and only the work described in the ticket).

  4. Test your work and make sure it is complete and correct. Ensure it passes your QA test from the ticket you wrote in the development environment.

  5. You now have a “dirty” working directory that contains new and/or modified files. Add your changes to the staging area of your local repository.

  6. Commit your staged changes to the repository.

  7. Push your changes to a backup server. In many cases, this will also be where your tickets are being tracked, such as GitLab, Bitbucket, or GitHub. Depending on your ticketing system, the ticket may now be marked as resolved but not necessarily closed.

  8. When you are completely satisfied with your work, merge your ticket branch into your main branch (usually master) and push the revised branches to the code hosting system.

  9. Test your work again to ensure there are no follow-up issues.

  10. Update your ticket as appropriate to close it out.

Depending on the type of code you’re writing, these steps may vary slightly. Rewrite this list, in full, and include any steps that are different for the way you work. For example, you may practice test-driven development, or have build scripts that you use to deploy your code. Commit to following your own process. If you’re not really motivated by words, draw out your process instead (Figure 5-1).

However you choose to do it, make sure you capture your process. You may choose to tuck it into the repository as a README file, or print it out and paste it to your Kanban board. By practicing consistency now, it will become infinitely easier to work with your coworkers to establish a process that everyone can follow.

In the remainder of the chapter, you will learn the commands needed to use the process I described. We’ll start by creating a new repository where you can store your work.

Creating Local Repositories

When you create a new repository in Git, you generally begin from one of three starting points:

  • From a clone of an existing repository

  • From an existing folder of untracked files

  • From an empty directory

In this section, you will learn how to create a new repository using each of these three methods.

Begin by creating a folder that will store all of your sample repositories (Example 5-1). You may choose to put this folder on your desktop, or in your home directory, or somewhere else. Git won’t care, so long as you remember where the folder is.

Sketched workflow diagram outlining the steps used to do new work.
Figure 5-1. Sketch a diagram of your workflow
Example 5-1. Create a project directory in your home directory
$ mkdir learning-git-for-teams
$ cd learning-git-for-teams

Unless otherwise stated, each of the exercises in this book will assume you have navigated to a sample repository within this folder. If it matters which repository you use, I will specify it in the instructions. Generally, however, it will not matter.

Cloning an Existing Project

On code hosting systems, such as GitLab or BitHub, when you navigate to a project page, you are typically given the option to download a .zip package of all the files or create a clone of the repository. Often these options are close together, but not always. Figure 5-2 shows the location of the repository URL in GitLab.

Screen shot of a GitLab project page; arrow pointing to the URL you will need to clone a project page.
Figure 5-2. Locating the URL to clone a repository

Practice What You Will Do Most Often

By starting with a repository, you will also have an easier time of learning the commands without having to invent problems to fix as you learn Git.

To download a copy of a project, you will use the command clone, as shown in Example 5-2. Unlike downloading a zipped set of files, creating a clone of a project will download a copy of all the files in the repository—along with the commit history—and it will remember where you downloaded the code from by setting up the remote code hosting server as a tracked repository. Don’t worry, it doesn’t keep a persistent connection, but rather it bookmarks the location in case you want to check for updates and download them to your local repository at a later date.

You will only clone a project once. Once the project is downloaded, you will use a different set of commands to keep it up to date. In Chapter 7, you will learn different ways to work with the command clone; in this chapter, we’re just going to use it to grab a snapshot of a project so that you have something to work with.

Example 5-2. Create a clone of the Git for Teams repository
$ git clone https://gitlab.com/gitforteams/gitforteams.git

The following should appear in the output of your terminal window:

Cloning into 'gitforteams'...
remote: Counting objects: 1040, done.
remote: Compressing objects: 100% (449/449), done.
remote: Total 1040 (delta 603), reused 915 (delta 538)
Receiving objects: 100% (1040/1040), 9.49 MiB | 1.68 MiB/s, done.
Resolving deltas: 100% (603/603), done.
Checking connectivity... done.

Congratulations! You have just cloned your first Git repository. You can muck about in this directory as much as you like. If you mess things up beyond recognition, delete the folder and run the clone command again.

Now that you have this directory, you also have all of the support material for this book. You can explore the supporting files, look for hidden Easter eggs, and generally have something to start with as you learn the more advanced commands without needing to worry about inventing weird scenarios, or destroying your own work.

Converting an Existing Project to Git

If I am working with software for the very first time, I tend to download a zipped package of files and begin versioning with an initial import of the software at that specific point. I’ll rip things out, move things around, and generally give myself a trial-by-fire introduction of how (and why) I might want to keep things exactly the way the original developers intended things to be.

In order to compare the effect of the commands you’re running, download a second instance of the Git for Teams repository, but this time grab a zipped package of the same repository you just cloned:

  1. Navigate to https://gitlab.com/gitforteams/gitforteams.

  2. Locate and download the zipped package for the project.

  3. Unpack the project, and place it into your project directory for this book. Because there is already a cloned copy of the files in this directory, you should name this new folder gitforteams-zip.

You can start with any folder of files and create a Git repository from it using the initialization command, init, as shown in Example 5-3. Git will be aware of all files in this directory, including subfolders, so make sure you run the command init from the root folder for your project.

Example 5-3. Initialize a directory for version control
$ git init

You will see a message similar to the following:

Initialized empty Git repository in /Users/emmajane/gitforteams/gitforteams-zip/.git/

Files are not immediately added to the repository. This is a feature because Git allows you to ignore files as well, and so it is waiting for you to tell it exactly which files you’d like to track. If there is a logical next step, Git will almost always have a useful suggestion in its status message. You should get into the habit of using the command status as frequently as you would use Save in a word processing program. This command does not save your work, but rather it lets you know what’s happening at this moment in your repository—and knowing what’s happening is key to understanding Git. Go ahead and check the status of your repository now (Example 5-4).

Example 5-4. Check the status of your repository
$ git status

Git lets you know the next step is to add the files you would like to track because you have just initialized the repository:

On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	[ lots of files listed here ... ]

nothing added to commit but untracked files present (use "git add" to track)

Getting your files into Git is a two-step process. Although it feels a little tedious when you’re first getting started, this is a feature because it allows you to make multiple unrelated changes at once in your working directory. Changes can be staged into groups of commits in the index—each group getting a different commit message. We want to add everything that is in our working directory because this is the initial import of files (Example 5-5).

Example 5-5. Add all files to the staging area of your repository
$ git add --all

Once again use the command status to check the status. The output will let you know the files have been staged and are ready to be committed:

On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   [ lots of files listed ... ]

Now that your files are added, you can save their current state into the repository with the command commit (Example 5-6).

Example 5-6. Commit all staged files to your repository
$ git commit -m "Initial import of all project files."

A lengthy commit confirmation message will be printed to the screen, notifying you that the files have been added to your repository. Your project files are now under version control.

Initializing an Empty Project

When we teach Git, it’s generally easiest to start with a completely empty directory, void of any files. This is because it’s easiest for the instructor and the student to begin from the same point. This exercise allows you to introduce yourself to Git without worry:

  1. Create a new, empty folder:

    $ mkdir empty-repository
  2. Change into your new folder:

    $ cd empty-repository
  3. Run the Git initialization command:

    $ git init
  4. Verify the hidden repository folder was added:

    $ ls -al

    On Windows:

    dir

If you see a new hidden folder, .git, your repository has been created. This folder will contain the record of all the changes to your repository. There’s nothing scary contained in this folder, but if you remove it, your project will no longer be tracked. This means you will not be able to recover previous versions of any of the files in your repository, you will lose all commit messages for your repository, and whatever state the files are currently in will be immutable.

At this point, you can follow the additional steps from the previous section to add files (Example 5-5), and commit them to your repository (Example 5-6).

Reviewing History

Once you have made your first commit into a repository, you are ready to start reviewing history. Of course, the history of your project is a combination of the work you have done, as well as the work done by others you have collaborated with. It may not feel like collaboration if you’ve merely downloaded an open source project, but it is. Collaboration can be as simple as adding your changes to someone else’s work.

To review the changes that have been made in a repository, use the command log (Example 5-7). By default, this command allows you to review the commit message and author information for every commit in the branch that is currently checked out of your local repository.

Example 5-7. Reviewing a repository’s history with log
$ git log

The command log will output a full history of your repository’s commit messages in reverse chronological order.

Ensure Your Details are Configured

If your name and email address aren’t displayed, refer to Appendix C for tips on how to configure Git.

If you’ve only made one commit message, the initial import, there will only be one message displayed:

commit fa04c309e3bb8de33f77c54c1f6cc46dc520c2ca
Author: emmajane <[email protected]>
Date:   Sat Oct 25 12:44:39 2014 +0100

    Initial import of all project files.

If, however, you are working with a more established code base, there will be a lot of messages. This can be quite overwhelming and difficult to scan. You can shorten the messages to just the first line of the message by adding the parameter --oneline as shown in Example 5-8. To exit, press q.

Example 5-8. Viewing a condensed history of your project
$ git log --oneline

To get a sense of how the same files can have a different history, run the commands from Examples 5-7 and 5-8 from both the cloned repository and from the repository you created from a downloaded .zip package. Even though the files are identical, their history is different (this will come up again when we talk about rebasing in Chapter 6).

Note

Other branches will have different commits, and different copies of the repository will have commits made by different developers. It’s basically anarchy, but limited to each little repository. The conventions we establish as software teams are what bring order to the chaos and allow us to share our work in a sane manner. (Remember the branching strategies we learned in Chapter 3? They’ll keep the work sorted into logical thought streams. Remember the permission strategies from Chapter 2? They’ll keep people locked into the right place, and unable to make changes to the “blessed” repository without the community gatekeeper’s consent.)

If you have completed all of the steps in this section, you will now have three separate repositories to work from for the remaining activities. For the section on branching, I recommend you work with the cloned repository because it has more to look at. For the other sections, you may choose any of the three.

Working with Branches

In version control, branches are a way of separating different ideas. They are used in a lot of different ways. You can use branches to denote different versions of software. You might use very short-term branches to work on a bug fix, or you might use a longer-term branch to test out a new idea.

Listing Branches

To get a list of all branches (Example 5-9), you can use either the branch command on its own, or add the parameter --list. At the beginning of this chapter, you cloned a repository; use that repository for this section because it already has branches for you to look at.

Example 5-9. Listing local branches
$ git branch --list

A list of the local branches will be printed:

master

By default, the master branch is copied into your local repository and you can begin working directly on it. In addition to this branch, you have also downloaded all other branches that were available in the remote repository. They are available for reference purposes, but they are not available to be worked on until you have set up a working copy of the remote branch. To list all branches in your repository, use the parameter --all (Example 5-10).

Example 5-10. List all branches
$ git branch --all

If you use this command in your local copy of the cloned repository, you should see both your local branches and a list of remote branches. The * denotes which branch you are currently viewing (or have “checked out”). The remainder of these lines all begin with remotes/origin: remotes just means “not here,” and origin is the default convention used for “my copy is cloned from here.” The final piece is the name of the branch (master, sandbox, and video-lessons are all branches):

* master
  remotes/origin/master
  remotes/origin/sandbox
  remotes/origin/video-lessons

The list can be a bit misleading, though. The remote branch names do not actually include the word remotes. This is just a piece of information about what type of branch it is. To get a usable list of the names of the remote branches, use the parameter --remotes (or -r for short) instead (Example 5-11).

Example 5-11. List remote branches
$ git branch --remotes

This will give a list of only the remote branches (using their real names):

origin/master
origin/sandbox
origin/video-lessons

These branches are all accessible to you, although you’ll need to make your own copy before committing changes to them.

Updating the List of Remote Branches

The list of remote branches does not stay up to date automatically, so the list will become out of date over time. To update the list, use the command fetch (Example 5-12).

Example 5-12. Fetch a revised list and the contents of all remote branches
$ git fetch

You will learn more about working with remotes in Chapter 7.

Using a Different Branch

When you check out a branch, you are updating the visible files on your system (the working tree) to match the version stored in the repository. This switch is completed with the command checkout (Example 5-13). The checkout process is a little different from a centralized version control system (VCS), such as Subversion. In a centralized VCS, you would need an Internet connection to use the checkout command because the branches are not stored locally, and must be downloaded in order to use.

Example 5-13. Switching branches with the command checkout
$ git checkout --track origin/video-lessons

Branch video-lessons set up to track remote branch video-lessons from origin.
Switched to a new branch 'video-lessons'

This command works differently in older versions of Git. If the previous command gave you an error, you may choose to upgrade (see Appendix B), or run the following variant:

$ git checkout --track -b video-lessons origin/video-lessons

This command (checkout -b) creates a new branch named video-lessons with tracking enabled (--track) from the branch video-lessons stored on the remote repository, origin. The local copy of the remote branch is available at origin/video-lessons, and your copy of the branch is available at video-lessons.

You should now have a local copy of the remote branch video-lessons (Figure 5-3).

Remote repository is cloned to local repository. Local repository now has a copy of all the branches that were present in the remote repository by checking out a reference to a remote branch, you create a local branch whose files can be edited in the working directory.
Figure 5-3. A local copy of a remote branch has been created

In your list of branches, it will look like the branch exists twice, except one includes the reference information for the remote repository:

$ git branch -a

  master
* video-lessons
  remotes/origin/master
  remotes/origin/sandbox
  remotes/origin/video-lessons

From this new branch, you can review history using Example 5-22 or Example 5-23. Note the commit history is not the same between the two branches.

Creating New Branches

For very tiny projects, I happily putter along in the master branch with each commit acting as a resolution to a problem; however, the bigger the team gets, the more it will benefit from having some structure in how people collaborate on the work. Chapter 3 covered the strategies you may want to adopt with your team for branching strategies. As a solo developer it can be more difficult to know if you should be working on a different branch. To help you decide, ask yourself a few questions:

  • Is it possible I will want to completely abandon this idea if things don’t work out?

  • Am I creating something that is a significant deviation from the current published version of the software?

  • Does my work need to undergo a review before it’s published or accepted into the published version of the software?

  • Is it possible I will need to switch tasks before I’ve completed this work?

If you answered “yes” to any of these questions, you should consider creating a new branch for your work. Teaching yourself good habits now is like buying insurance. You hope you never have to use it, but you buy it just in case.

The best way to decide what goes into a branch is to start with the issue tracker. By creating a written description of what you’re about to do, you will have a clear sense of when to start and finish with your branch. Yes, this will often feel like overhead, but it is a really good habit to get into, especially when you’re working in larger teams.

When you start a new branch, it will contain the identical history as the place you are branching from at the moment you create it (Figure 5-4). When you review the history of a new branch with the command log, it will also show the commits from its ancestor branch.

A line of dots representing commits on a branch. A second line breaks away from the main one, representing a branch.
Figure 5-4. New branches contain the same commits as their ancestor

Seeing as you are working on issue-based version control, your branch name should reflect the ticket you are working on. For example, if the issue was “1: Add process notes to README,” then the branch would be named 1-process_notes. The history for the new branch will include all of the commits up to the point of departure, so make sure you begin your new branch from the correct starting point. You can do this by either using the command checkout to situate yourself in the correct branch first (Example 5-14), or you can add the desired parent branch to your command (Example 5-15).

Example 5-14. Creating a new development branch

First checkout the branch you want to use as the starting point:

$ git checkout master

Switched to branch 'master'

Next, create a new branch:

$ git branch 1-process_notes
[no message displayed]

Finally, check out the new branch:

$ git checkout 1-process_notes

Switched to branch '1-process_notes'

Although it’s a little more to remember, Example 5-15 does have the advantage of creating a branch explicitly from the right base branch, meaning you don’t need to remember the extra checkout step from the previous instructions.

Example 5-15. Creating a new development branch from the master branch
$ git checkout -b 1-process_notes master

Switched to a new branch '1-process_notes'

Once you are in your new branch, you can go ahead and do your work. As an exercise, I encourage you to try adding your notes on how your process works to one of the three repositories you’ve created in this chapter. Once you’ve made all of your edits, it’s time to commit the changes to your local repository.

Adding Changes to a Repository

Each time you make a change to your working directory, you will need to explicitly save the changes to your Git repository. This is a two-part process. Figure 5-5 shows how changes must be explicitly staged in the index, and then saved to your repository.

Changes are captured through a progression of working directory (visible files) to staging area (ready for commit) to repository (changes recorded in Git).
Figure 5-5. Changes in Git must be staged, and then saved to the repository

When you previously created a new repository, you imported a series of files all at once (Example 5-5). You don’t have to add all the files at once, though. This can be especially helpful if you have been working on unrelated edits that should be captured in separate commits. If you do want to separate the changes into multiple commits, you need to change the parameter --all that you used previously for the filename you want to stage (Example 5-16). You can add one or more filenames at a time; the filenames do not need to be the same type.

Example 5-16. Add selected changed files to your Git repository
$ git add README.md process-diagram.png
$ git add branch-naming-rules.png

For the most part, I add files to the staging area one at a time. I find this prevents me from accidentally adding more than I meant to. At the command line, I can type the first few letters of the filename and then press the Tab key, and the remainder of the filename will be automatically typed out (this is known as tab completion and it’s one of my favorite things to use). If, however, you have a lot of files you need to add, and they’re all contained in the same directory, you may want to use a wildcard to match files with a subdirectory (Example 5-17), or that all have a similar name (Example 5-18).

Example 5-17. Add all files, recursively, from a given path
$ git add <directory_name>/*
Example 5-18. Add all files with the file extension .svg
$ git add *.svg

You can also completely omit the filenames, and instead stage files according to whether or not they are known to Git. By using the parameter --update you can stage all files that are known to Git, and that have been edited (or updated) since the last commit:

$ git add --update

If you want to be even more outrageous, you can stage all changed files in the working directory by adding the parameter --all. This will restage any files that have been modified since they were first staged (ensuring all new edits are captured in the commit); stage any files that are known to Git, but not already staged; and stage any files that are not currently being tracked by Git. It is a very greedy command! Before using it, you should check the list of files that will be added:

$ git status
$ git add --all

Once a change has been added to the staging area, it must be committed. If you continue to work in any of the files you’ve added to the index, only the previously staged changes will be added when you next run the command commit (Figure 5-6). If you keep working on the file, and want to include these changes in your commit, you will need to repeat the previous command where you added your files to the staging area.

Any file that is changed after it is added to the index will only be partially committed.
Figure 5-6. A commit will only save the work that has been added to the index

You can commit your staged changes to the repository by running commit (Example 5-6).

If this feels frustrating at first, you’re not alone! It took me a while to get used to this behavior and I felt it was broken when it didn’t automatically notice I’d changed the file and stage the new changes. It wasn’t until I started playing around with partial staging of files that I realized how powerful it was to not have my changes automatically staged.

Adding Partial File Changes to a Repository

If you want even more granularity over your commits, you can choose to add partial changes within a saved file by using the parameter --patch. One of my favorite reasons for committing files in this way is to record several unrelated edits into multiple smaller commits.

Adding files via the --patch process is a multistep approach (Example 5-19). You will first initialize the procedure, and then choose from a list of options on how you want to create your patches. You will be prompted to add the change to the staging area (y), or leave this hunk unchanged (n). Changed lines will begin either with a - (line removed) or a + (line added). If a line has been changed, it will display as both removed and added.

To separate the hunks into smaller units, you can use the option s to split the hunk. This will only work if there is at least one line of unchanged work between the two hunks. If you want to separate two adjacent lines for staging separately, you can edit (e) the hunk.

Example 5-19. Add selected changes to your Git repository interactively
$ git add --patch filename

By adding the optional filename, you will not need to cycle through each file. If you know exactly which file you need to split up, and you have a lot of files that need staging, it can save you time to work with specific files. After running the command, you will begin the process of walking through the files, looking for changes to stage:

diff --git a/ch05.asciidoc b/ch05.asciidoc
index 8f82732..e7be9ce 100644
--- a/ch05.asciidoc
+++ b/ch05.asciidoc
@@ -6,7 +6,6 @@ changed significantly in the last few years; however, a few of
the commands we'l easier to remember. Chances are very good that you have Git
installed if you are using Linux or OSX. If you are using Windows, however,
the changes are very good that Git is not installed unless you've explicitly
installed it already.

-. Open a terminal window.
 . Enter the command: +git --version+

 The version of Git you are running should be printed to the screen.

Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?

In the output displayed, we can see that Git is asking if we want to stage this one line change (. Open a terminal window), which is a proposed deletion as indicated by the -. Additional options for what to do with this hunk are available by pressing ?.

Committing Partial Changes

Assuming you’ve only added some changes from a given file to the staging area, when you check the status of your repository, you will see that a file is both ready to be committed, and has unstaged changes:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   ch05.asciidoc

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:   ch05.asciidoc

This is the same message that is displayed if you add a file to the staging area and then continue to edit the file before committing it to the repository, or if you only choose to stage some hunks while while adding files interactively to the index with the parameter patch.

Removing a File from the Stage

If you accidentally add too many files from the staging area, and want to break your changes into smaller commits, you can unstage your proposed changes (Example 5-20). Removing a file from the stage doesn’t mean you’ll be undoing the edits you’ve made; it notifies Git that you’re not ready for these changes to be committed to the repository yet.

Example 5-20. Remove proposed file changes from the staging area
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   ch05.asciidoc


$ git reset HEAD ch05.asciidoc
Unstaged changes after reset:
M	ch05.asciidoc

Optionally, you can also use the --patch parameter with the command reset if you only want to unstage some of the changes you’ve made to a file.

Undoing your work will be covered in greater detail in Chapter 6.

Writing Extended Commit Messages

Up to this point it’s possible you have been writing terse, one-line commit messages. While this is fine if you’re just practicing version control commands, it’s not going to make your future self very happy if you need to figure out what the commit message “Oops. Trying again.” means.

It took me quite a while to get out of the habit of thinking of Git as a place where I saved my work and instead as a place where I recorded my results. When I first started working with version control the commits I was making were granular to take advantage of the advanced tools available to me (you’ll learn more about these in Chapter 6). This is because I was coming the mindset of saving work and undoing mistakes. When in the save mindset, I would think of clicking the save button, or using control-Z to undo the last few things I typed. When the commits are this small, the commit messages tend to be nearly useless (“stopped for lunch”; “tried something”; “didn’t work”; “oops”; “testing”). If I wanted to roll back history, how the heck would I use those commit messages to find the spot where the code was working after something broke? It make take you a while to find your stride as well.

Your commit messages should always include the rationale for why you made a change, as well as a quick summary of the changes you made. In order to write a detailed commit message, you will need more than one line the one line short message style you have been using up to now. I typically write my commit messages with a two-step procedure (Example 5-21):

  1. Use a terse, one-line message to commit the changes to the repository.

  2. Amend the commit to include a full description of what I was thinking when I made the change.

Example 5-21. Writing a detailed commit message
$ git add --all
$ git commit -m "CH05: Adding technical edits."
$ git commit --amend

You don’t need to do this two-step process; you can jump straight into the message editor by omitting the parameter -m when first making your commit:

$ git commit

Your default editor will open, and you will be prompted to add a new commit message. The first line of the message will be used for the --oneline display, and all lines beginning with # will be removed from the final message. Once you’ve crafted your commit message, you will need to save it and then quit the editor to complete the commit.

In Chapter 6 you will learn how to squash granular commits into whole ideas with interactive rebasing.

Ignoring Files

Eventually, you may run into a situation where Git keeps adding files to the repository that you actually never want to add. If you’re on a Mac, this might include the pesky .DS_Store files. If you’re on Linux, maybe it’s your text editor’s .swp files. If you’re working on a web project, this may include the compiled CSS files created from Sass.

If you know that your favorite text editor, or IDE, creates temporary files, which are not project specific, you should create a global setting to ignore these files.

First, run the following command to let Git know which file you would like to store your list of “ignored” files in:

$ git config --global core.excludesfile ~/.gitignore

You can now update this file using one filename per line. You can use exact filenames, or wildcards (for example: *.swp will match any file ending in .swp). For a useful starting point of files to ignore, check out gitignore.io.

Additionally, you may want specific repositories to ignore specific files or file extensions. In this case, your best option is to add an extra .gitignore file to the repository. This has the added benefit of ensuring your teammates don’t accidentally sneak in their build files.

Complete the following steps to customize which files should be ignored for a specific repository:

  1. Create a file in the root level of your project named .gitignore.

  2. Using one filename per line, add all of the files you never want Git to add to the repository. You can use exact filenames, or wildcards (for example, *.swp).

  3. Add the file .gitignore to your repository by using the commands add and commit.

Files with these extensions will no longer be added to your repository, even if you are using the parameter --all.

Working with Tags

Tags are used to pinpoint specific commits. You can think of them like a bookmark. I don’t use tags nearly as much as I should. As a result, I rely on my commit messages to find specific points in the repository. You may find working with tags is a good habit to get into because they will allow you to easily reference points in your time line.

Tags for Teams of More Than One

In this chapter, we are referring to private repositories with no branches that are shared with other teammates. When your branches aren’t shared, there are no reasons to limit how and when you use tags. Use them as often as you’d like! The tags you use on shared branches, however, are typically used for deployment purposes and should follow a convention that is useful to the whole team.

Tags can only be added to specific commits. To know which commit you want to add your tag to, you’ll probably want to use a combination of log and show. The command log will give you a list of all commits in your repository (Example 5-22), and the command show will display the detailed information for any single commit.

Example 5-22. Quick list of recent commits
$ git log --oneline

fa04c30 Initial import

Once you think you have found a commit that you would like to investigate a little further, you can get the detailed commit message beginning at that commit by adding the commit ID (Example 5-23). To limit the output to only that commit, add the optional parameter --max-depth= along with the number of log entries you would like to show.

Example 5-23. Log details for a single commit
$ git log fa04c30 --max-depth=1

commit fa04c309e3bb8de33f77c54c1f6cc46dc520c2ca
Author: emmajane <[email protected]>
Date:   Sat Oct 25 12:44:39 2014 +0100

    Initial import

If you want even more details about the commit object, you can use the command show (Example 5-24) to list the changes that happened in that commit as text (of course, this will be less useful for binary files, such as images).

Example 5-24. Use show to display the log message and textual diff for a single commit
$ git show fa04c30

commit fa04c309e3bb8de33f77c54c1f6cc46dc520c2ca
Author: emmajane <[email protected]>
Date:   Sat Oct 25 12:44:39 2014 +0100

    Initial import

diff --git a/ch05.asciidoc b/ch05.asciidoc
new file mode 100644
index 0000000..8f82732
--- /dev/null
+++ b/ch05.asciidoc
@@ -0,0 +1,867 @@
+
+=== Verifying Git
+
+Before we dive into using Git, you'll want to check and see which version is
installed. For our purposes, Gi

[etc]

Once you have identified a commit that you want to bookmark, you can do so by using the command tag. In Example 5-25, a new tag, import, is created for the commit hash fa04c30.

Example 5-25. Adding a new tag, import, to a commit object
$ git tag import fa04c30

You can now list the available tags by using the command tag without any parameters (Example 5-26).

Example 5-26. Listing all tags
$ git tag

A list of tags will be printed to the screen. At this point, only one tag has been added, so the list is very short:

import

Once a tag is made, you can investigate the commit where the tag was added (Example 5-27).

Example 5-27. Reviewing a tagged commit
$ git show import

As you have seen previously, the command show will display the log message and textual diff for that commit.

Connecting to Remote Repositories

In a centralized version control system, like Subversion, there is one master copy of the repository and all work is written into that copy. When you commit, the information is immediately uploaded to that central repository and available to others. In a decentralized version control system, like Git, there is no single repository that everyone works with. It is merely a convention that declares one copy of the repository to be privileged (and considered to be the official source for the code).

When you’re a team of one, a remote repository is really more of a backup to your local repository because there won’t be any changes happening on the remote unless you put them there. This remote repository can also be used to transfer code between your different local development environments. For example, you may use both a laptop and a desktop for your projects. The remote repository can be an effective way to bounce your work from one place to the next so that you can continue working even when switching machines.

If you’ve been following along in this chapter, you should now have three local repositories: one created from a clone of a repository on GitLab, one created from a downloaded .zip package, and a third repository created from an empty folder. They are all local, however, and you don’t have the option to share your work with others because they either don’t have a remote associated with them (repository from the zipped package, and the repository that was initialized locally) or you don’t have write access to the remote repository (repository that you cloned).

In order to upload your work, you will need to create a new project on GitLab and associate it with one of your existing repositories.

Creating a New Project

If you haven’t already, you will need to create an account on GitLab.com (it’s free) and sign into your account. You can also sign in via GitHub, Twitter, or Google. Although you can also complete these steps on another code hosting system, such as GitHub, GitLab is an open source product that you can host yourself for free if you need to practice source control from behind a firewall:

  1. Log in to your GitLab account and navigate to your dashboard.

  2. From the project summary tab, click the button New project.

  3. Enter a Project path, such as gitforteams. All remaining fields can be left as their defaults.

  4. Click Create project. You will be redirected to the instruction page on how to upload your repository.

Adding a Second Remote Connection

GitLab gives you the copy/paste instructions you need to upload your repository to its platform; however, you don’t necessarily want to complete all of the steps. From your new project page, take a look at the second section, Create a new repository (Example 5-28).

Example 5-28. Create a new repository on GitLab
mkdir my-git-for-teams
cd my-git-for-teams
git init
touch README.md
git add README.md
git commit -m "first commit"
git remote add origin [email protected]:emmajane/my-git-for-teams.git
git push -u origin master

Can you see where your starting point would be if you had already created a repository locally? (Hint: compare it with the section labeled Push an existing Git repository.) You’ve already done all of the steps up to the line git remote add origin. If you want to create a new repository from scratch, you would follow all of these instructions, but you already have three local repositories! So instead of creating a new one (again), you are going to add the remote connection so that you can upload one of the three repositories to this new project on GitLab. It doesn’t matter which of the three you choose, but you can only choose one because each project presents a single repository.

When you add a remote to your repository, you must also assign it a nickname (Example 5-29). By default, the nickname is origin. You could name it anything you like, though--pickles, peanutbutter, kittens--Git wouldn’t care. The advantage of using origin is that more tutorials online will be as easy as copy and paste; the disadvantage is that origin doesn’t really explain very much, especially if your repository actually started locally. In addition to this, origin is already in use if you created your repository by cloning it from a remote repository. To connect the project you created to any of the three repositories you have locally, use the nickname my_gitlab.

Example 5-29. Adding a remote to a local repository with a custom name
$ git remote add my_gitlab [email protected]:emmajane/my-git-for-teams.git

It wasn’t until I finally started taking control over the names of things in Git that I really started to understand how all the pieces fit together. For example, I will often nickname my remote according to the name of the code hosting system. My local copy of the Git for Teams repository has the following remotes: github, gitlab, and bitbucket (Example 5-30).

Confirm the remote was correctly added with the command remote, as shown in Example 5-30.

Example 5-30. List remote repositories connected to your current repository
$ git remote --verbose

If you have assigned the remote to the repository you cloned, you will see two pairs of remotes listed:

my_gitlab	[email protected]:emmajane/my-git-for-teams.git (fetch)
my_gitlab	[email protected]:emmajane/my-git-for-teams.git (push)
origin	[email protected]:emmajane/gitforteams.git (fetch)
origin	[email protected]:emmajane/gitforteams.git (push)

You are now ready to push your work from any branch to your remote repository.

Pushing Your Changes

To upload your changes, you need to have a connection to the remote repository, permission to publish to the repository, and the name of the branch to which you want to upload your changes. The first time you push your branch, you will need to explicitly tell Git where to put things. If you start by using the command push, it will tell you what to do next.

Avoid the Hassle of Typing Your Password

If you haven’t added your SSH keys to the code hosting system (see Appendix D), you will need to enter your username and password each time you want to push your changes.

For example, if you’re currently using the branch 1-process_notes, and you try to push it to the remote repository (Example 5-31), you will get an error message (Example 5-32).

Example 5-31. Upload a branch using the command push
$ git push
Example 5-32. Without an upstream branch, you will get an error message
fatal: The current branch 1-process_notes has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin 1-process_notes

This error message provides us with very useful information, but it’s not quite right. Instead of uploading the branch to the remote origin, we actually want to use our new remote, my_gitlab (Example 5-33).

Example 5-33. Set the upstream branch while uploading your local branch
$ git push --set-upstream my_gitlab 1-process_notes

This will upload your branch and set it up for future use. Now whenever you are using this branch, you can issue the much shorter command git push to upload your work. By setting the upstream connection, you are building a relationship between your local copy of the branch and the remote repository. This has the same effect as when you used --track to check out a remote branch, except in that case you were starting with the remote copy and adding a tracked local copy.

Branch Maintenance

Once the code has been fully tested, you will want to merge the ticket branch into the master branch (Example 5-34), and delete the local (Example 5-35) and remote copies of the ticket branch (Example 5-36). As a team of one, it’s unlikely you’ll need to deal with merge conflicts. Merge conflicts will be covered in Chapter 7.

Example 5-34. Merging a ticket branch into your main branch
$ git checkout master
$ git merge 1-process_notes

If a true merge needs to be performed, as opposed to just a fast-forwarding of history, you may be presented with the editor for a commit message. Generally I leave the default message in place. Once the work has been merged into the master branch, you should push the master branch to the remote repository as well:

$ git push --set-upstream my_gitlab master

Now that the changes have been merged into the master branch, there’s not a lot of reason to keep the ticket branch open. To keep your repository tidy, you can go ahead and delete the ticket branch now (Example 5-35).

Example 5-35. Delete your local copy of the branch
$ git branch --delete 1-process_notes

Git will complain wildly if there are changes that haven’t been merged into another branch, so you don’t need to worry (too much) about losing unsaved work.

Finally, you need to do a bit of housekeeping for the remote repository as well. You should also delete remote branches whose changes have been merged into master (Example 5-36).

Example 5-36. Delete remote branches that are no longer needed
$ git push --delete my_gitlab 1-process_notes

With your housekeeping finished, it’s time to repeat this process for your next new idea.

Command Reference

Table 5-1 lists the commands used in this chapter. These commands are shell commands and should be used as written.

Table 5-1. Basic shell commands
Command Use

cd ~

Change to your home directory

mkdir

Make a new directory

cd directory_name

Change to a specified directory

ls -a

List hidden files for OS X and Linux-based systems

dir

List files on Windows

touch file_name

Create a new, empty file with the specified name

Table 5-2 lists the subcommands for the Git application. They will always be preceeded by the command git when used at the command line.

Table 5-2. Basic Git commands
Command Use

git clone URL

Download a copy of a remote repository

git init

Convert the current directory into a new Git repository

git status

Get a status report for your repository

git add --all

Add all changed and new files to the staging area of your repository

git commit -m "message"

Commit all staged files to your repository

git log

Review a repository’s history

git log --oneline

View a condensed history of your project

git branch --list

List all local branches

git branch --all

List local and remote branches

git branch --remotes

List all remote branches

git checkout --track remote_name/branch

Create a copy of a remote branch for local use

git checkout branch

Switch to a different local branch

git checkout -b branch branch_parent

Create a new branch from a specified branch

git add filename(s)

Stage only the specified file so that it is ready to be committed

git add --patch filename

Stage only portions of a file so that they are ready to be committed

git reset HEAD filename

Remove proposed file changes from the staging area

git commit --amend

Update the previous commit with changes currently staged, and supply a new commit message

git show commit

Log details for a single commit

git tag tag commit

Add a tag to a commit object

git tag

List all tags

git show tag

Log details for the commit where the tag was applied

git remote add remote_name URL

Create a new reference to a new remote repository

git push

Upload changes for the current branch to a remote repository

git remote --verbose

List the fetch and push URLs for all available remotes

git push --set-upstream remote_name branch_local branch_remote

Push a copy of your local branch to the remote server

git merge branch

Incorporate the commits currently stored in another branch into the current one

git push --delete remote_name branch_remote

Remove named branch from the remote server

Summary

Throughout this chapter you have learned how to work with Git as a team of one. The following is a guide to the best practices outlined in this chapter:

  • Always begin your work by defining the problem you want to work on. This definition will help you determine the name of the branch, and which piece of work you want to branch away from to start your work.

  • As you are making changes in your branch, you can choose to add some or all of the changes you’ve made through the staging area. This will help you to craft commits with related work.

  • Regardless of whether you start your repository locally or via a clone, you can always start a new project on a code hosting system and upload your work by adding a new remote to your local repository.

  • Housekeeping tasks should be performed as you wrap up each line of work. You can do this by merging your ticket branches into your main branch, and then deleting the local and remote copies of your branch.

In the next chapter, you will learn how to go back in time in the Git time machine to undo your work and change your commit history.

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

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