TFS Breaking Free of TFS with Git - The Case for Git

laofo · 2014年03月13日 · 2 次阅读

Breaking Free of TFS with Git (Part I) - The Case for Git Chadwell.Jeffrey

What is Git? "Git" is a version control system, similar to TFS. It shares several capabilities with TFS, such as branching, merging and labeling. It was originally written by Linus Torvalds in 2005 to handle the Linux source code. Since then, its use has spread and it is now one of the major version control systems used by open-source projects. What makes Git different? Even though Git shares a lot of functionality with TFS, it has one very important difference. TFS is a centralized version control system, meaning that there is only one copy of the "official repository." All of the file versions, the check in notes, the branches, etc. only exist in one place. Git, on the other hand, is a distributed version control system. In other words, every developer working with a Git repository has an entire copy of the repository, including all of the file version, all of the check in notes, all of the branches, etc. (In practice, there is usually one copy that's considered the "official repository," but it's not necessary to set it up that way. And each of the developers still has his or her own complete copy of the repository.) Okay, so TFS is centralized and Git is distributed. What advantages are there to a distributed model? Here are three: It offers redundancy. Since every developer has a complete copy of the repository, it's a lot easier to reconstruct the "official repository" should it somehow become corrupted. It makes distributed development a lot easier. It's no big deal if you have a team distributed over multiple locales, since each team member has his or her own copy of the repository, and since it's fairly easy to keep all of the copies in sync. It scales up nicely. With a centralized repository, each new developer means another person accessing the central repository for version control-related operation. This is minimized with a distributed repository. The only time a developer has to interact with a remote repository is whenever either copy of the repository needs to be updated. Otherwise, the developer can work independently. But my project uses TFS. Why would I even try to use Git? Have you ever worked on a feature that was so large or complex, it took several days to complete? And during those several days, did you check your code in regularly, or did you wait until it was all complete so you didn't break the build? During that time that the code wasn't under TFS control, did you ever worry that you might lose your work and have to start from scratch? If you're like most people, you didn't check your code in until it was done, and you probably worried about losing your work and having to start from scratch. So how would Git help with this? Before you started your work, you could create a Git repository to hold your "work in progress" until it was ready to check into TFS. That way, you'd have multiple checkpoints you could go back to in case you messed something up along the way. And once you were finished, you could check the final results into TFS. Now, let's change that scenario a little bit. Instead of working on one large feature, you're working on several features. How does Git help with this? With Git, it's very easy to create, use and destroy branches. In fact, that is a major part of the Git way of doing things. So you could create a branch for each of the features you're working on (and possibly share those branches with others who are working on the same feature), work on the features independently, merge them back into your master branch when finished, then delete the branches. And since you control the branches, you always know what feature each one corresponds to. I'm sold. Where do I find Git? That depends on the operating system you're using: Mac OSX: The easiest way to get it for OSX is to install homebrew, then use it to get Git. Linux: This is probably the easiest one. Just use the package manager for whichever Linux distro you're running to get Git. Microsoft Windows: You have a couple of choices here: Download and install msysgit (Git for Windows). For the most part, you should accept the default options when you install it. However, on the "Select Components" screen, you should make sure both "Git Bash Here" and "Git GUI Here" are selected. On the "Configuring the line ending conversions" screen, you should make sure that "Checkout Windows-style, commit Unix-style line endings" button is selected. (This is the one I use.) Download cygwin and use Git from there. Cygwin doesn't install Git by default, so you'll need to make sure the Git package is selected during installation. Git is command-line oriented, and it's better if you learn to use Git this way, but there are a few GUI front ends you may want to check out if you're not ready to give up your GUI access: Github (a public Git repository for open-source projects) has some GUI front ends for Mac and for Windows. Atlassian also has front end called SourceTree for both Mac and Windows. There's also one other product called Git Extensions that's just for Windows. If you install this one, it'll add a "Git" menu item to Visual Studio so that you can access Git Extensions from there. What's next? In my next posts, I'll go over some of the more important Git commands and try to give you a feel for the Git way of doing things.

Breaking Free of TFS with Git (Part II) - Getting Started

Introduction In my first post, I explained what Git is, how it differs from TFS and how you can get a copy to run on your computer. If you're still with me, it's now time to dig in and start using Git. Additional Notes for Windows Users Git is a command-line tool and it's Linux-based. That's why you had to install either msysgit or Cygwin -- so you would have a Unix-like shell (Bash) to run it in. If you're not used to using a command-line interface, or if you've only had experience with the MS-DOS command-line, some of the commands may look a little different than you're used to. However, it's close enough to MS-DOS that you should be able to follow it. And if you're familiar with PowerShell, you should have no trouble at all. By default, Git uses "Vim" (a Unix/Linux-based text editor) to handle processing commit messages when committing code changes to your repository. If you've never used Vim before, you have two choices: Learn enough about Vim to be able to add/edit commit messages. I believe this is actually the easier option, since you don't actually have to know too much about Vim to be able to add/edit commit messages. You can find everything you need to know in the first couple of chapters in this Vim reference. Change the editor to something else (like Notepad or Notepad++). This requires some changes to Git's configuration, and it may require you to write some batch files, so it's a little more difficult. If you want to go this route, you can find some information about setting it all up here. If you kept the default settings when you installed Git, you should be able to open up a Bash shell in the correct directory by using it from Windows Explorer. All you have to do is right-click on the directory you're interested in and select "Git Bash here" from the context menu. Post Installation Set-up Once you've downloaded and installed Git, there is an additional step you should perform before diving into Git. You need to configure your user name and email address in Git. To do this, open up a Bash shell and type in the following Git commands: $ git config --global "Doe.John" $ git config --global "" If you don't do this, you'll have to enter this information manually every time you commit changes to a repository. A Quick Introduction to Git The best way to see Git in action is to create a repository, add some content and make some revisions. There are two ways to make a Git repository. The first way is to create a new repository from scratch using your own files, and the second way is to copy, or clone, and existing repository. Since you're just getting started, it's easier to just create one from scratch, so that's what we'll do. Creating a Repository The first step is to create a directory somewhere and add some initial content to the directory. So go ahead and create a directory named git-repo and make a text file in that directory named testfile.txt containing the line "This is a test." To turn git-repo into a git repository, navigate to the directory, open a Bash shell there and type git init: $ git init Initialized empty git repository in .git/ This also works if your top-level directory is empty. Git will still create a repository for it. When you create a new repository, Git creates a new directory called .git (files that begin with a '.' are hidden in Linux and Unix) in the top-level of your project directory to store the repository and related information. Git considers everything that's not in the .git directory to be working files, so they remain untouched. Adding a File to Your Repository When you create a new Git repository, it starts off empty. You have to explicitly deposit your content files in the repository. This ensures that the only files that go into the repository are the ones you want (i.e. no scratch files). To add a file to the repository, use the git add command: $ git add testfile.txt NOTE: If you have a bunch of files in the directory, and if you want all of them to be added to the repository, you can type git add . to add all the files at once. This lets Git know that you want to add testfile.txt to the repository. At this point, Git hasn't added the file to the repository yet -- it's only been staged. Git won't actually add the file to the repository until you commit it. This allows you to batch up multiple steps (like file adds, file removals and file changes) and commit them all at once. You can see this if you run the git status command: $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached ..." to unstage) new file: testfile.txt Here, it shows that the new file testfile.txt will be added to the repository during the next commit. So let's go ahead and commit the current repository changes: $ git commit -m "Initial contents of testfile.txt" [master (root-commit) 9b2913d] Initial contents of testfile.txt 1 file changed, 1 insertion(+) create mode 100644 testfile.txt In this case, we provided the commit message on the command line (the -m option). In practice, though, it's more common to leave that option off and create the commit message during an interactive edit session. This allows you to provide more information about the commit. You can find information about best practices for commit messages here. Now, if you enter the git status command, you'll see that there are no outstanding changes to be committed: $ git status On branch master nothing to commit, working directory clean Adding Another Commit Now that we have a file in our repository, let's make a change. Open testfile.txt in your favorite editor and add the line "This is another test." to the file. Now commit the new changes: $ git commit testfile.txt This time, Git will start up an interactive editor for you enter a new commit message. Enter a new commit log entry, like "Add a new line," save the message and exit the editor. You now have a second version of the file in your repository. Viewing Your Commit Log Now that you have a couple of commits in your repository, you can use several Git commands to examine them in different ways. To just look at the sequential history of the individual commits, use git log: $ git log commit 5d7a5f4227c833867b8b794434cd1ea02352ddad Author: Chadwell.Jeffrey <[email][/email]> Date: Fri Jan 10 15:43:24 2014 -0800

Add a new line

commit 9b2913d957ff4260acfad3964293771132a4b9e4 Author: Chadwell.Jeffrey <[email][/email]> Date: Fri Jan 10 15:31:08 2014 -0800

Initial contents of testfile.txt This shows you each of the commits, newest to oldest. Each entry shows the commit ID, the commit author's name and email address, the date of the commit and the commit message. If you want extra details about a particular commit, use the git show command with a commit number: $ git show 5d7a5f4227c833867b8b794434cd1ea02352ddad commit 5d7a5f4227c833867b8b794434cd1ea02352ddad Author: Chadwell.Jeffrey <[email][/email]> Date: Fri Jan 10 15:43:24 2014 -0800

Add a new line

diff --git a/testfile.txt b/testfile.txt index 273c1a9..f0511bb 100644 --- a/testfile.txt +++ b/testfile.txt @@ -1 +1,2 @@ -This is a test. \ No newline at end of file +This is a test. +This is another test. If you don't feel like typing in the whole commit ID, you can type in the first six characters and get the same information (this works any place a commit ID is required): $ git show 5d7a5f commit 5d7a5f4227c833867b8b794434cd1ea02352ddad Author: Chadwell.Jeffrey [email][/email] Date: Fri Jan 10 15:43:24 2014 -0800

Add a new line

diff --git a/testfile.txt b/testfile.txt index 273c1a9..f0511bb 100644 --- a/testfile.txt +++ b/testfile.txt @@ -1 +1,2 @@ -This is a test. \ No newline at end of file +This is a test. +This is another test. Running git show without a commit ID shows the details of the most recent commit. Finally, you can get one-line summaries for the current development branch using git show-branch: $ git show-branch --more=10 [master] Add a new line [master^] Initial contents of testfile.txt The --more=10 argument tells git to show you the 10 most recent revisions. If you leave that argument off, it'll only show you the last revision. Master is the default branch name. Commit Differences To see the difference between any two commits, use the git diff command: $ git diff 9b2913d957ff4260acfad3964293771132a4b9e4 \ 5d7a5f4227c833867b8b794434cd1ea02352ddad diff --git a/testfile.txt b/testfile.txt index 273c1a9..f0511bb 100644 --- a/testfile.txt +++ b/testfile.txt @@ -1 +1,2 @@ -This is a test. \ No newline at the end of file +This is a test. +This is another test. This output is similar to what the Linux/Unix diff command produces. As is the convention, the older commit (9b2913d957ff4260acfad3964293771132a4b9e4) precedes the newer one (5d7a5f4227c833867b8b794434cd1ea02352ddad) in the command's argument list. You can see this at the bottom of the output where the plus sign (+) precedes each line of new content. Removing and Renaming Repository Files The command to remove a file from your repository is git rm. For example, suppose you have another file in your repository named redshirt.txt. This is how you remove it: $ git rm redshirt.txt rm 'redshirt.txt'

$ git commit -m "Kill red shirt" [master f034248] Kill red shirt 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 redshirt.txt Here again, the process requires two steps: git rm stages the file removal, and git commit actually removes the file from the repository. To rename a file, you have two options. The indirect way is to use a combination of git rm and git add, and the direct way is to use the git mv command (mv is the Linux/Unix command to rename a file). Here is what the indirect way looks like: $ mv foo.txt bar.txt $ git rm foo.txt rm 'foo.txt' $ git add bar.txt And here is the direct way: $ git mv foo.txt bar.txt As always, once you've renamed the file, you need to commit the change: $ git commit -m "Move foo.txt to bar.txt" [master 46cdfa9] Move foo.txt to bar.txt 1 file changed, 0 insertions(+), 0 deletions(-) rename foo.txt => bar.txt (100%) Cloning Your Repository At the beginning of this post, I mentioned that you can also create a new repository by cloning an existing one. Let's do that now. For simplicity, we'll make the clone in the same directory that contains your git-repo project directory. Clone your repository using git clone: $ git clone git-repo git-repo-clone Cloning into 'git-repo-clone'... done. And just like that, you have a new copy of your repository. There are some subtle differences between the two repositories, but for all intents and purposes, they are identical. Both repositories have the same files, the same commits, the same history. If you're interested in seeing the differences, you can enter the following two Linux/Unix commands and look at the output: $ ls -lsa git-repo git-repo-clone $ diff -r git-repo git-repo-clone Git has many more commands, but this should be enough to get you started. In my next post, I'll show you how to configure your Git repository to ignore certain files.

Breaking Free of TFS with Git (Part III) - Excluding Files from Your Repository

Introduction In my last post, I showed you how to create a repository (either from scratch or by cloning an existing repository), how to add/rename/delete files from the repository, how to check your changes into the repository, and how to examine your repository's history. It's almost time to learn about branching and merging, but before you do that, you should know how to exclude certain files from your repository, so that's what we'll cover today. The .gitignore File If you're used to working with TFS, you know that it excludes certain files and directories by default (like .o files, .exe files, bin directories, obj directories, etc.) unless you specifically tell it to include one of those files or directories. Git does this through the use of the .gitignore file. This file contains a list of filename patterns specifying which files to ignore. The format of the file is: Blank lines are ignored, and lines starting with a hash mark (#) are considered to be comments (the hash mark must be the first character on the line, though). A literal filename, like main.exe, matches a file in any directory with that name. Directory names, such as bin/ are denoted by a trailing slash character (/). This matches a directory and its subdirectories, but does not match a filename. If the pattern contains Unix shell globbing characters, such as an asterisk (*), it's expanded as a shell glob pattern. For example, to exclude all .exe files, you could use the pattern *.exe. If the first characters is an exclamation point (!), the sense of the pattern on the rest of the line is inverted. Also, any file that's been excluded by a previous pattern can be included by using an inversion rule. Git allows you to have a .gitignore file in any directory within your repository. Each .gitignore file affects its own directory and any subdirectories. This means that .gitignore rules cascade, i.e. you can overrule rules from a higher directory by creating a .gitignore file in one of the subdirectories and adding inversion (!) rules to it. In fact, there is a precedence to the order in which these patterns are applied: Patterns specified on the command line. Patterns read from the .gitignore file that's in the same directory. Patterns in parent directories, starting with the one directly above the current directory. The further up you go from the current directory, the less precedence a .gitignore file has. Patterns in the .git/info/exclude file. These are patterns that only apply to your copy of the repository instead of all copies of the repository. Patterns from the file specified by the configuration variable core.excludefile. We haven't really talked about the configuration file(s) yet, so you can ignore this one for now. For More Information If you want a more in-depth explanation of the .gitignore file and how to use it, take a look at this page. Also, there's a project on GitHub containing a sample .gitignore file for just about every conceivable language or environment. If you have an account on GitHub, you can make a clone of the project for yourself. If you don't have a GitHub account, do yourself a favor and get one. What's Next Now that we've got some basics out of the way, we'll veer away from common TFS usage into some areas where Git really shines. In the next post, I'll show you how Git does branching and how it differs from the TFS branching style.

Breaking Free of TFS with Git (Part IV) - Basic Branching

Introduction In my last post, I showed you how to make Git ignore certain files so they never show up in your repository. Now it's time to learn about Git branching. In a VCS (Version Control System), a branch is the means by which an alternate path of development happens in a software project. Reasons for creating branches include: Crating a separate line of development for a customer release. For instance, our most current sbX release is sbx 4.4. But all of our customers are still on earlier versions of sbX, so we have branches for each of those version in case we need to make fixes. Encapsulating a development phase. This allows potentially unstable code to be stored in the VCS without it affecting any of the release branches. For instance, when we work on a new release of sbX, that work happens in a development branch like the sbX 4.4 rel_devA branch. Once the development is complete, the code gets promoted into a release branch. This ensures that the release branch contains stable code. Isolating work on a particular bug or feature. This doesn't normally happen in a centralized VCS like TFS, but it's quite common in Git. Isolating work from an individual contributor. This post explains how Git branches are different from TFS branches and shows how to work with Git branches. Git Branches vs. TFS Branches Since TFS is a centralized VCS, every branch is available to every developer, i.e. a developer can't just create a branch that's private to him or her. Instead, repository branches are usually created and maintained by a single person or a small group of people. And developers end up doing all of their work inside a single branch. Git, on the other hand, is a distributed VCS, which allows it to have both branches that exist in every copy of the repository (tracking branches) and branches that exist only in a single copy of the repository (topic branches). A developer can create a topic branch in his or her repository to track a development on a new feature or a bug fix without affecting any other developer working in the same repository. And when they're finished, they can merge their changes back into the master branch (we'll cover merging in a later post) and push the change out to everyone else working on the same project. This is one of the things that makes Git branches so powerful. Developers are encouraged to create topic branches for feature development, for bug fixes and for just experimenting with new techniques, because no one else is going to ever see these branches. They exist as long as you want them to and, when you're done with them, they can be deleted. Branch Naming For the most part, any ASCII character is allowed in a branch name with these exceptions: The forward slash (/) is valid, as long as it's not the last character of the branch name. Slashes are generally used to make hierarchical names such as feature/24519 or bug/25822. Slash-separated components cannot begin with a dot (.). For example, feature/.24519 is not valid because the first character after the slash is a dot. The name may not contain two consecutive dots (..). The name may not contain any whitespace characters. The name may not contain any characters special to Git, such as tilde (~), caret (^), colon (:), question-mark (?), asterisk () and open bracket ([). The name cannot contain ASCII control characters. One good reason to use hierarchical names is that Git allows wildcards in searches. So if you wanted list all of the feature branches, you could enter this command: git show-branch 'feature/' Creating Branches Whenever you create a new Git repository, Git automatically creates a master branch and makes it the default active branch. While there is nothing to stop you from doing all of your work in the master branch, it's really not the best way to utilize Git. The commits in master branch should always represent a stable, releasable version of your application. If you do all of your work in that branch, you will have commits that represent transitory work, i.e. work you would never consider stable or releasable. So, if this isn't the best way to work in Git, what is the best way? Instead of doing all of your work in the master branch, you should create a separate branch for each feature or bug you work on, do all of your work there, merge the branch(es) back into master when you're satisfied with your changes, and delete the branch(es) when you're finished. You create new branches by executing the git branch command: $ git branch feature/24519 This creates a new branch called feature/24519 from the latest commit in the current active branch. To create a branch from a previous commit, pass the commit ID to git branch command: $ git branch bug/25822 b0a2eeb0c3dc9cb63181cf82cdccdc084dea7505 or $ git branch bug/25822 b0a2ee Note that all you've done here is create a new branch. You aren't actually working in the branch until you switch to it via the git checkout command, which we'll cover later. Viewing Branches If you just want to see a list of branch names, you can execute the git branch command without any arguments: $ git branch bug/25822 feature/24519 * master This shows you a list of all of the branches defined for your repository. The current active branch (master) is marked with an asterisk (). If you want more detailed output, use the git show-branch command: $ git show-branch ! [bug/25822] Initial commit ! [feature/24519] Initial commit of baz.txt * [master] Move foo.txt to bar.txt --- + [feature/24519] Initial commit of baz.txt + [feature/24519^] Add additional feature/24519 content to bar.txt + [feature/24519~2] Add feature/24519 content + [master] Move foo.txt to bar.txt ++* [bug/25822] Initial commit The output of the git show-branch command consists of two sections separated by a line of dashes. The section above the dashes lists the names of the branches enclosed in square brackets, one per line. Each line is also associated with one column of output, indicated by the corresponding exclamation point (!) or, in the case of the active branch, the asterisk (). The most recent commit message for each branch is also listed. The section below the dashes is a matrix showing which commits are present in each branch. As in the top section, each commit is listed with the first log message line from that commit. The presence of a plus (+), a minus (-) or an asterisk () in the corresponding branch's column means the commit is present in that branch. The plus sign means the commit is present in that branch, the minus sign means it's a merge commit and the asterisk shows the commit is present on the current branch. For example, the following commits commits are only found in the feature/24519 branch: + [feature/24519] Initial commit of baz.txt + [feature/24519^] Add additional feature/24519 content to bar.txt + [feature/24519~2] Add feature/24519 content They are listed in reverse chronological order. In other words, the first commit listed is the head commit for that branch, and is denoted by the branch name enclosed in brackets. The commit immediately before the current one is listed next, and is denoted by the branch name and a caret (^) enclosed in brackets. Commits previous to these are denoted by the branch name followed by a tilde (~) followed by the distance away from the head, all enclosed in brackets. In this example, the third commit listed (feature/24519~2) is two commits away from the head commit for the feature/24519 branch. If you want to see the commit history for some of the branches, you can list them on the command line after the git show-branch command: $ git show-branch feature/24519 bug/25822 ! [feature/24519] Initial commit of baz.txt ! [bug/25822] Initial commit -- + [feature/24519] Initial commit of baz.txt + [feature/24519^] Add additional feature/24519 content to bar.txt + [feature/24519~2] Add feature/24519 content + [feature/24519~3] Move foo.txt to bar.txt ++ [bug/25822] Initial commit If you're using hierarchical branch names, you can also use wildcard characters on the command line. For example, to get all of the features in this repository, you can execute the command like this: $ git show-branch feature/* and get all of the feature branches in the repository. Checking Out a Branch To do work in one of your branches, you need to check it out first using the git checkout command: $ git checkout feature/24519 Switched to branch 'feature/24519' If you intend to create a branch and immediately work in that branch, you can create it and check it out at the same time by executing the git checkout command with the -b option: $ git branch bug/25822 feature/24519 * master

$ git checkout -b feature/99999 Switched to branch 'feature/99999'

$ git branch bug/25822 feature/24519 * feature/99999 master So what actually happened when you checked out a different branch? Git actually modified the contents of your working directory to match the contents of the branch you checked out. Specifically, Git performed the following changes in your working directory: Any files and directories present in the branch being checked out but not in the current branch are checkout out of the repository and placed in your working directory. Any files and directories present in the current branch but not in the branch being checked out are removed from your working directory. Any files present in both the the current branch and the one being checked out are updated to reflect the contents of the version in the branch being checked out. Deleting Branches If you are completely done with a branch, you can delete it by executing the git branch command with the -d option: $ git branch -d feature/99999 However, you may not delete the current branch. If you try this, Git will display an error message: $ git checkout feature/99999 Switched to branch 'feature/99999'

$ git branch -d feature/99999 error: Cannot delete the branch 'feature/99999' which you are currently on. If you tried to delete the current branch, Git would have no idea of how the working directory should work. Git will also refuse to delete a branch if it contains commits that are also not present on the current branch: $ git checkout master Switched to branch 'master'

$ git branch -d feature/99999 error: The branch 'feature/99999' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature/99999' Since master is the current branch, and since the changes in feature/99999 aren't present in master, Git errors out. If you are truly sure that you want to delete the branch, you can do as Git suggests and execute git branch with the -D option. However, if you're not sure, you have a couple of ways to fix this. If the changes in the branch to be deleted exist in another branch, you can make that other branch the current branch before you attempt the deletion. For example, if you had a branch named feature/88888 that contained the same changes as feature/99999, you could check out the feature/88888 branch before you attempted to delete feature/999999. Your other option is to merge your changes into the current branch before attempting the deletion. For example, using the above example, you could merge your changes into master before deleting feature/99999 (I will be covering merges in more detail in the next post): $ git checkout master Switched to branch 'master'

$ git merge feature/99999 Updating 46cdfa9..d2447a9 Fast-forward baz.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 baz.txt Now you can safely delete feature/99999: $ git branch -d feature/99999 Deleted branch feature/99999 (was d2447a9)

需要 登录 后方可回复。