diff --git a/_layouts/reference.html b/_layouts/reference.html index 0c91d1d..183dd2b 100755 --- a/_layouts/reference.html +++ b/_layouts/reference.html @@ -58,8 +58,8 @@
Branching in Git is one of my favorite features. If you have used other + version control systems, it's probably helpful to forget most of what you + think about branches - in fact, it may be more helpful to think of them + practically as contexts since that is how you will most often be + using them. When you checkout different branches, you change contexts + that you are working in and you can quickly context-switch back and forth + between several different branches. +
+ +
+ In a nutshell you can create a branch with
+ git branch (branchname)
, switch into that context with
+ git checkout (branchname)
, record commit snapshots while
+ in that context, then can switch back and forth easily. When you switch
+ branches, Git replaces your working directory with the snapshot of the
+ latest commit on that branch so you don't have to have multiple directories
+ for multiple branches. You merge branches together with
+ git merge
. You can easily merge multiple times from the same
+ branch over time, or alternately you can choose to delete a branch
+ immediately after merging it.
+
The git branch
command is a general branch management tool
+ for Git and can do several different things. We'll cover the basic ones
+ that you'll use most - listing branches, creating branches and deleting
+ branches. We will also cover basic git checkout
here which
+ switches you between your branches.
+
Without arguments, git branch
will list out the local
+ branches that you have. The branch that you are currently working on will
+ have a star next to it and if you have
+ coloring turned on,
+ will show the current branch in green.
+
+$ git branch
+* master
+
+
+ This means that we have a 'master' branch and we are currently on it.
+ When you run git init
it will automatically create a 'master'
+ branch for you by default, however there is nothing special about the name -
+ you don't actually have to have a 'master' branch but since it's the default
+ that is created, most projects do.
+
So let's start by creating a new branch and switching to it. You can do
+ that by running git branch (branchname)
.
+
+
+$ git branch testing
+$ git branch
+* master
+ testing
+
+
+ Now we can see that we have a new branch. When you create a branch this
+ way it creates the branch at your last commit so if you record some commits
+ at this point and then switch to 'testing', it will revert your working
+ directory context back to when you created the branch in the first place -
+ you can think of it like a bookmark for where you currently are. Let's see
+ this in action - we use git checkout (branch)
to switch the
+ branch we're currently on.
+
+$ ls +README hello.rb +$ echo 'test content' > test.txt +$ echo 'more content' > more.txt +$ git add *.txt +$ git commit -m 'added two files' +[master 8bd6d8b] added two files + 2 files changed, 2 insertions(+), 0 deletions(-) + create mode 100644 more.txt + create mode 100644 test.txt +$ ls +README hello.rb more.txt test.txt +$ git checkout testing +Switched to branch 'testing' +$ ls +README hello.rb ++ +
So now we can see that when we switch to the 'testing' branch, our new + files were removed. We could switch back to the 'master' branch and see + them re-appear.
+ ++$ ls +README hello.rb +$ git checkout master +Switched to branch 'master' +$ ls +README hello.rb more.txt test.txt ++ +
+ In most cases you will be wanting to switch to the branch immediately, so
+ you can do work in it and then merging into a branch that only contains
+ stable work (such as 'master') at a later point when the work in your new
+ context branch is stable. You can do this pretty easily with
+ git branch newbranch; git checkout newbranch
, but Git gives
+ you a shortcut for this: git checkout -b newbranch
.
+
+$ git branch +* master +$ ls +README hello.rb more.txt test.txt +$ git checkout -b removals +Switched to a new branch 'removals' +$ git rm more.txt +rm 'more.txt' +$ git rm test.txt +rm 'test.txt' +$ ls +README hello.rb +$ git commit -am 'removed useless files' +[removals 8f7c949] removed useless files + 2 files changed, 0 insertions(+), 2 deletions(-) + delete mode 100644 more.txt + delete mode 100644 test.txt +$ git checkout master +Switched to branch 'master' +$ ls +README hello.rb more.txt test.txt ++ +
You can see there how we created a branch, removed some of our files + while in the context of that branch, then switched back to our main branch + and we see the files return. Branching safely isolates work that we do into + contexts we can switch between.
+ ++ If you start on work it is very useful to + always start it in a branch (because it's fast and easy to do) and then + merge it in and delete the branch when you're done. That way if what you're + working on doesn't work out you can easily discard it and if you're forced + to switch back to a more stable context your work in progress is easy to put + aside and then come back to.
+ +If we want to delete a branch (such as the 'testing' branch in the
+ previous example, since there is no unique work on it),
+ we can run git branch -d (branch)
to remove it.
+
+
+$ git branch +* master + testing +$ git branch -d testing +Deleted branch testing (was 78b2670). +$ git branch +* master ++ +
+ In a nutshell you use git branch
to list your
+ current branches, create new branches and delete unnecessary or
+ already merged branches.
+
Once you have work isolated in a branch, you will eventually want to
+ incorporate it into your main branch. You can merge any branch into your
+ current branch with the git merge
command. Let's take as a
+ simple example the 'removals' branch from above. If we create a branch
+ and remove files in it and commit our removals to that branch, it is
+ isolated from our main ('master', in this case) branch. To include those
+ deletions in your 'master' branch, you can just merge in the 'removals'
+ branch.
+
+$ git branch +* master + removals +$ ls +README hello.rb more.txt test.txt +$ git merge removals +Updating 8bd6d8b..8f7c949 +Fast-forward + more.txt | 1 - + test.txt | 1 - + 2 files changed, 0 insertions(+), 2 deletions(-) + delete mode 100644 more.txt + delete mode 100644 test.txt +$ ls +README hello.rb ++ +
Of course, this doesn't just work for simple file additions and + deletions. Git will merge file modifications as well - in fact, it's very + good at it. For example, let's see what happens when we edit a file in + one branch and in another branch we rename it and then edit it and then + merge these branches together. Chaos, you say? Let's see. +
+ ++$ git branch +* master +$ cat hello.rb +class HelloWorld + def self.hello + puts "Hello World" + end +end + +HelloWorld.hello ++ +
So first we're going to create a new branch named 'change_class' and + switch to it so your class renaming changes are isolated. I'm going to + change each instance of 'HelloWorld' to 'HiWorld'.
+ ++$ git checkout -b change_class +M hello.rb +Switched to a new branch 'change_class' +$ vim hello.rb +$ head -1 hello.rb +class HiWorld +$ git commit -am 'changed the class name' +[change_class 3467b0a] changed the class name + 1 files changed, 2 insertions(+), 4 deletions(-) ++ +
So now I've committed the class renaming changes to the 'change_class'
+ branch. If I now switch back to the 'master' branch my class name will
+ revert to what it was before I switched branches. Here I can change
+ something different (in this case the printed output) and at the same
+ time rename the file from hello.rb
to ruby.rb
.
+
+
+
+$ git checkout master +Switched to branch 'master' +$ git mv hello.rb ruby.rb +$ vim ruby.rb +$ git diff +diff --git a/ruby.rb b/ruby.rb +index 2aabb6e..bf64b17 100644 +--- a/ruby.rb ++++ b/ruby.rb +@@ -1,7 +1,7 @@ + class HelloWorld + + def self.hello +- puts "Hello World" ++ puts "Hello World from Ruby" + end + + end +$ git commit -am 'added from ruby' +[master b7ae93b] added from ruby + 1 files changed, 1 insertions(+), 1 deletions(-) + rename hello.rb => ruby.rb (65%) ++ +
Now those changes are recorded in my 'master' branch. Notice that the + class name is back to 'HelloWorld', not 'HiWorld'. Now I want to + incorporate the 'HiWorld' change so I can just merge in my 'change_class' + branch. However, I've changed the name of the file since I branched, + what will Git do?
+ ++$ git branch + change_class +* master +$ git merge change_class +Renaming hello.rb => ruby.rb +Auto-merging ruby.rb +Merge made by recursive. + ruby.rb | 6 ++---- + 1 files changed, 2 insertions(+), 4 deletions(-) +$ cat ruby.rb +class HiWorld + def self.hello + puts "Hello World from Ruby" + end +end + +HiWorld.hello ++ +
Well, it will just figure it out. Notice that I had no merge conflicts + and the file that had been renamed now has the 'HiWorld' class name change + that was done in the other branch. Pretty cool.
+ +So, Git merges are magical, we never ever have to deal with merge + conflicts again, right? Not quite. In situations where the same block + of code is edited in different branches there is no way for a computer + to figure it out, so it's up to us. Let's see another example of changing + the same line in two branches. +
+ +
+$ git branch +* master +$ git checkout -b fix_readme +Switched to a new branch 'fix_readme' +$ vim README +$ git commit -am 'fixed readme title' +[fix_readme 3ac015d] fixed readme title + 1 files changed, 1 insertions(+), 1 deletions(-) ++ +
Now we have committed a change to one line in our README file in a + branch. Now let's change the same line in a different way back on + our 'master' branch.
+ ++$ git checkout master +Switched to branch 'master' +$ vim README +$ git commit -am 'fixed readme title differently' +[master 3cbb6aa] fixed readme title differently + 1 files changed, 1 insertions(+), 1 deletions(-) ++ +
Now is the fun part - we will merge the first branch into our master + branch, causing a merge conflict.
+ ++$ git merge fix_readme +Auto-merging README +CONFLICT (content): Merge conflict in README +Automatic merge failed; fix conflicts and then commit the result. +$ cat README +<<<<<<< HEAD +Many Hello World Examples +======= +Hello World Lang Examples +>>>>>>> fix_readme + +This project has examples of hello world in +nearly every programming language. ++ +
You can see that Git inserts standard merge conflict markers, much like + Subversion, into files when it gets a merge conflict. Now it's up to us + to resolve them. We will do it manually here, but check out + git mergetool + if you want Git to fire up a graphical mergetool + (like kdiff3, emerge, p4merge, etc) instead. +
+ ++$ vim README # here I'm fixing the conflict +$ git diff +diff --cc README +index 9103e27,69cad1a..0000000 +--- a/README ++++ b/README +@@@ -1,4 -1,4 +1,4 @@@ +- Many Hello World Examples + -Hello World Lang Examples +++Many Hello World Lang Examples + + This project has examples of hello world in ++ +
A cool tip in doing merge conflict resolution in Git is that if you
+ run git diff
, it will show you both sides of the conflict
+ and how you've resolved it as I've shown here. Now it's time to mark
+ the file as resolved. In Git we do that with git add
-
+ to tell Git the file has been resolved, you have to stage it.
+$ git status -s +UU README +$ git add README +$ git status -s +M README +$ git commit +[master 8d585ea] Merge branch 'fix_readme' ++ +
And now we've successfully resolved our merge conflict and committed + the result.
+ +