2014/08/19
git是最近几年程序员必须掌握的一套源代码管理工具,从最早的cvs到svn都没有git用起来更优雅。尤其是github.com的出现,让git更普及了。但git的命令体系的确入手有点难,我们今天就讲讲如何练习git的问题。
本文章不需要开任何账户,也不需要开任何线上测试项目,就可以模拟多分支进行练习。
提示:如果你想看懂下面的代码,你只需要记住每行前面有$
符号的是你输入的命令,其它行是系统返回的信息。
这不是一篇求全的文章,有一些基础的操作并没有在文章中出现,重点是多个分支下的那些操作和流程。
##所有内容概要
- 创建环境
- 单分支操作
- 提交文件
- 查看历史
- 当前状态
- 查看差异
- 还原文件状态
- 还原提交
- 合并提交
- 多分支流程
- 查看分支情况
- 关联远程仓库
- 创建分支(包含远程分支)
- 顺利交叉提交
- 有冲突的交叉提交
- 向production分支提交成熟代码
- 线上代码临时修改
- 打补丁包
- 打标签
创建环境
我们就不讲怎么安装git了,只假设git是存在的,先创建git项目,因为公司正式的git上都在跑正式的项目,有时候很难拿出来让你练手,如果迫不得已给你用了,很可能合并代码的时间比写代码的时间都漫长。
mkdir test_project
cd test_project/
git init
Initialized empty Git repository in /data/tests/test_project/.git/
这三个命令已经让我们创建了一个测试项目。
单分支操作
提交文件
$ echo "hello" > readme.txt
$ git add readme.txt
$ git commit -m "add readme."
[master (root-commit) 7734044] add readme.
1 file changed, 1 insertion(+)
create mode 100644 readme.txt
这是最标准的文件提交过程。
查看历史
$ git log
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
查看git历史,目前我们只有一个提交。
当前状态
$ echo "\nworld." >> readme.txt
$ echo "new file" > newfile.txt
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
newfile.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status
查看当前的状态,目前有一个新文件newfile.txt,还有一个已经改动的文件readme.txt,如果你决定要提交这两个文件,需要先通过git add
命令把这两个文件增加索引里,这样才能在git commit
的时候被提交。
查看差异
LangwandeMac-mini:test_project langwan$ git diff
diff --git a/readme.txt b/readme.txt
index ce01362..5b6191e 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,2 @@
hello
+\nworld.
如果查看状态不能满足你的需求,你想简单看一下差异,你可以用git diff
这个命令,当然更高级的用法是结合比较差异工具来一起使用,这个命令其实还有别的用途,例如打补丁包,我们后面讲。
还原文件状态
$ git add .
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: newfile.txt
modified: readme.txt
$ git reset .
Unstaged changes after reset:
M readme.txt
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
newfile.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/readme.txt b/readme.txt
index ce01362..5b6191e 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,2 @@
hello
+\nworld.
这是最常见的一种状态还原,当你使用git add
添加了若干文件以后,突然发现某些文件不想提交,这时候你使用git reset .
命令可以还原这些文件的索引状态,但并不会还原文件中的内容,所以我们再次使用git diff
可以看到和上次的差异是一样的。
还原提交
$ git add .
$ git commit -m "add newfile."
[master 0025a55] add newfile.
2 files changed, 2 insertions(+)
create mode 100644 newfile.txt
$ git log
commit 0025a55ecebfb1994ca0ec88997b5fca24546a83
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:59:44 2014 +0800
add newfile.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git reset HEAD^
Unstaged changes after reset:
M readme.txt
LangwandeMac-mini:test_project langwan$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
newfile.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/readme.txt b/readme.txt
index ce01362..5b6191e 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,2 @@
hello
+\nworld.
$ git log
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
我们首先提交了所有的改动,当我们查看log的时候多出来一次提交,但这时候我们后悔了,可以采用git reset HEAD^
命令还原我们这次提交,我们通过比较和再次执行git log
命令可以发现,完全退到了原来的状态上,这时候你可以继续修改代码,再次进行提交。
合并提交
如果你想合并多次提交,道理是一样的,使用git reset
命令,当然会稍微有点变化:
$ echo "newfile2" > newfile2.txt
$ git add .
$ git commit -m "add newfile2.txt"
[master 7850c0d] add newfile2.txt
1 file changed, 1 insertion(+)
create mode 100644 newfile2.txt
$ git log
commit 7850c0dfb00effef8881d6fa8df2eb9d4ec2b155
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:07:36 2014 +0800
add newfile2.txt
commit 94a79a472958a75c3fe769e611251dc92f47a0ee
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:07:04 2014 +0800
add newfile.txt
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git reset 773404467e68c297752c25b2942bb34681e70dd5
Unstaged changes after reset:
M readme.txt
$ git log
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
newfile.txt
newfile2.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git add .
$ git commit -m "add newfile newfile2."
[master 87c2903] add newfile newfile2.
3 files changed, 3 insertions(+)
create mode 100644 newfile.txt
create mode 100644 newfile2.txt
$ git log
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
我们添加了新的文件,继续提交,查看log会看到三个历史,我们现在想合并最后产生的两个提交,我们可以先使用git reset 773404467e68c297752c25b2942bb34681e70dd5
还原到第二个提交之前的状态,这时候我们查看状态会发现,有两个新文件和一个没修改过的文件,这时候我们再次提交就相当于合并了最后两次提交,这里的`773404467e68c297752c25b2942bb34681e70dd5`就是commit的提交id,可以在log里找到。
多分支流程
查看分支情况
$ git branch -a
* master
目前系统有一个master分支。
关联远程仓库
为了让流程顺利跑起来,我们需要模拟一个远程仓库,然后和本地仓库关联起来,这样才能支持pull、rebase、push等命令。
$ mkdir test_project_remote
$ cd test_project_remote
$ git init --bare
Reinitialized existing Git repository in /data/tests/test_project_remote/
创建一个模拟的远程仓库,这个仓库只能作为远程仓库使用,不会产生本地代码。
$ cd test_project
$ git remote add origin /data/tests/test_project_remote
$ git remote
origin
回到我们的本地仓库,添加一个远程仓库到本地上。
创建分支(包括远程分支)
$ git branch -a
* master
默认情况下我们只有这一个分支,我们为了模拟工作流程,现在需要创建一个模拟的远程分支
我们需要创建多个分支,包括两个远程分支和四个本地分支,三个本地分支分别模仿两个程序员小明和小红,另外一个是默认的master分支,其中master分支和远程的master分支默认关联,小明和小红开发的代码会向远程的master分支进行同步,当代码稳定以后master分支的代码向production分支同步,production分支的代码经过充分的测试以后才会部署到线上服务器。其中本地也会存在一个prodction分支,是远程production分支的本地代码。
$ git push origin master
Everything up-to-date
$git push origin master:production
Total 0 (delta 0), reused 0 (delta 0)
To /data/tests/test_project_remote
* [new branch] master -> production
$ git branch xiaoming
$ git branch xiaohong
$ git branch production
$ git branch -a
* master
production
xiaohong
xiaoming
remotes/origin/master
remotes/origin/production
现在我们可以看到本地的四个分支和远程的两个分支。有时候分支还会更复杂一些,会多出来一个test分支,这和你的上线流程有关系,我们这里简化了中间的那些流程。
顺利交叉提交
这是最常见的工作,小明和小红都在自己的分支上编写代码,然后向master上提交自己的成果,他(她)们之间最常出现的问题就是解决代码冲突,我们采用git pull --rebase
来解决这些问题,而不是采用git megre
来合并代码。
步骤:
- 小明添加了新的文件,向远程的master分支推送了新的提交。
- 小红添加了新的文件,计划向远程的master分支推送新的提交,两者之间没有产生冲突,两个人都顺利完成了提交。
小明没有遇到冲突,顺利提交。
$ git checkout xiaoming
Switched to branch 'xiaoming'
$ echo "xiaoming" > xiaoming.txt
$ git add .
$ git commit -m "add xiaoming."
[xiaoming 8507a8f] add xiaoming.
1 file changed, 1 insertion(+)
create mode 100644 xiaoming.txt
$ git pull --rebase origin master
From /data/tests/test_project_remote
* branch master -> FETCH_HEAD
Current branch xiaoming is up to date.
$ git push origin xiaoming:master
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 276 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To /data/tests/test_project_remote
87c2903..8507a8f xiaoming -> master
LangwandeMac-mini:test_project langwan$
正确的流程是小明先要rebase远程master上的改动,它这次没有发现任何改动,顺利提交。
小红没有遇到冲突,但是更新了小明的更新下来,顺利提交
$ git checkout xiaohong
Switched to branch 'xiaohong'
$ echo "xiaohong" > xiaohong.txt
$ git add .
$ git commit -m "add xiaohong."
[xiaohong f2cd9bf] add xiaohong.
1 file changed, 1 insertion(+)
create mode 100644 xiaohong.txt
$ git pull --rebase origin master
From /data/tests/test_project_remote
* branch master -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: add xiaohong.
$ git push origin xiaohong:master
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 276 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To /data/tests/test_project_remote
8507a8f..a03160d xiaohong -> master
小红提交自己的代码,小红先rebase远程master上的改动,顺利合并了小明的改动add xiaoming.
,接着顺利提交。
$ git log
commit a03160decd81244feda5ffc54c76482be0da0b9f
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:48:23 2014 +0800
add xiaohong.
commit 8507a8f5b343f9584b1bd98fc24f060e4429bf06
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:45:18 2014 +0800
add xiaoming.
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
我们查看小红的历史可以看到小明的提交在前,小红的提交在后。
有冲突的交叉提交
我们回到小明的分支,制造一个冲突,让小明来解决。
步骤:
- 切换到小明的分支,添加文件xiaohong.txt。
git pull --rebase
远程的master分支,产生了冲突,提示我们先解决冲突在提交git rebase --continue.
- 利用
git diff
简单了解一下冲突的内容,利用vim编辑冲突的文件。 - 解决完冲突使用
git add .
标记完成。 - 查看历史发现小红的提交在前,小明的新提交在后。
- 向远程master提交最新的代码。
$ git checkout xiaoming
Switched to branch 'xiaoming'
$ echo "newxiaohong" > xiaohong.txt
$ git add .
$ git commit -m "new xiaohong."
[xiaoming 453de53] new xiaohong.
1 file changed, 1 insertion(+)
create mode 100644 xiaohong.txt
$ git pu
pull push
$ git pull --rebase origin master
From /data/tests/test_project_remote
* branch master -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: new xiaohong.
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging xiaohong.txt
CONFLICT (add/add): Merge conflict in xiaohong.txt
Failed to merge in the changes.
Patch failed at 0001 new xiaohong.
The copy of the patch that failed is found in:
/data/tests/test_project/.git/rebase-apply/patch
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
$ git diff
diff --cc xiaohong.txt
index 760e8bd,95477bb..0000000
--- a/xiaohong.txt
+++ b/xiaohong.txt
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD
+xiaohong
++=======
+ newxiaohong
++>>>>>>> new xiaohong.
$ vim xiaohong.txt
$ git add .
$ git rebase --continue
Applying: new xiaohong.
$ git log
commit b22bf2ddf74e81cf867e79ec7a8e88f86d0662f4
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:54:24 2014 +0800
new xiaohong.
commit a03160decd81244feda5ffc54c76482be0da0b9f
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:48:23 2014 +0800
add xiaohong.
commit 8507a8f5b343f9584b1bd98fc24f060e4429bf06
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:45:18 2014 +0800
add xiaoming.
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git push origin xiaoming:master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 270 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To /data/tests/test_project_remote
a03160d..b22bf2d xiaoming -> master
向production分支提交成熟代码
当master上的代码成熟以后,就要向production上合并代码,并提交给测试部门测试。
步骤:
- 切换到production分支
- merge master分支上的代码
- 提交到production分支上
如果不特殊指定merge命令会合并所有master上的提交到production分支上,如果有冲突需要你去解决冲突,如果没有冲突就可以顺利提交,如果指定了commit id会合并所有这次commit之前的所有内容。
$ git checkout production
Switched to branch 'production'
$ git merge origin/master
$ git log
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git merge origin/master
Updating 87c2903..b22bf2d
Fast-forward
xiaohong.txt | 1 +
xiaoming.txt | 1 +
2 files changed, 2 insertions(+)
create mode 100644 xiaohong.txt
create mode 100644 xiaoming.txt
$ git log
commit b22bf2ddf74e81cf867e79ec7a8e88f86d0662f4
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:54:24 2014 +0800
new xiaohong.
commit a03160decd81244feda5ffc54c76482be0da0b9f
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:48:23 2014 +0800
add xiaohong.
commit 8507a8f5b343f9584b1bd98fc24f060e4429bf06
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:45:18 2014 +0800
add xiaoming.
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git push origin production
Total 0 (delta 0), reused 0 (delta 0)
To /data/tests/test_project_remote
87c2903..b22bf2d production -> production
LangwandeMac-mini:test_project langwan$
如果产生了冲突,请先解决冲突。
线上代码临时修改
如果需要你修改一些线上的文案或者一些非常小的修改,又希望你能尽快上线,那么你可以用这个流程和方法。
步骤:
- 切换到production分支
- 直接修改代码并提交到线上produciton分支,记录这次修改的commit id号。
- 切换到自己的分支,
cherry-pick
这次提交到你的代码里,下一次master向production同步代码的时候会带上这部分修改。这样就两不耽误了。
$ echo "production" > production.txt
$ git add .
$ git commit -m "add production."
[production 228f6e6] add production.
1 file changed, 1 insertion(+)
create mode 100644 production.txt
$ git push production.
$ git push origin production
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To /data/tests/test_project_remote
b22bf2d..228f6e6 production -> production
$ git log
commit 228f6e673ee64261df37ef21910c86a3c10a0962
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 14:28:45 2014 +0800
add production.
commit b22bf2ddf74e81cf867e79ec7a8e88f86d0662f4
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:54:24 2014 +0800
new xiaohong.
commit a03160decd81244feda5ffc54c76482be0da0b9f
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:48:23 2014 +0800
add xiaohong.
commit 8507a8f5b343f9584b1bd98fc24f060e4429bf06
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:45:18 2014 +0800
add xiaoming.
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
$ git checkout xiaoming
Switched to branch 'xiaoming'
$ git cherry-pick 228f6e673ee64261df37ef21910c86a3c10a0962
[xiaoming 31777d2] add production.
1 file changed, 1 insertion(+)
create mode 100644 production.txt
LangwandeMac-mini:test_project langwan$ git log
commit 31777d26d01343b9e4f5e9f21e3294cb5f4038c8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 14:28:45 2014 +0800
add production.
commit b22bf2ddf74e81cf867e79ec7a8e88f86d0662f4
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:54:24 2014 +0800
new xiaohong.
commit a03160decd81244feda5ffc54c76482be0da0b9f
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:48:23 2014 +0800
add xiaohong.
commit 8507a8f5b343f9584b1bd98fc24f060e4429bf06
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 13:45:18 2014 +0800
add xiaoming.
commit 87c2903b60e9ae97dc37e311e8c34c2b9cd10ea8
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 12:08:52 2014 +0800
add newfile newfile2.
commit 773404467e68c297752c25b2942bb34681e70dd5
Author: langwan <langwanluo@gmail.com>
Date: Tue Aug 19 11:42:21 2014 +0800
add readme.
打补丁包
刚才我们在production上做了一个很小的修改,一方面我们要把这个小的修改cherry-pick
回我们自己的工作分支,另外也需要制作一个标准的补丁包发给运维的同事,让他们在线上打一个小补丁来处理,而不是重新发布所有代码。
步骤:
- 切换到production分支
- 制作补丁包
- 邮件发送补丁包
- 在线上打补丁
生成补丁
$ git checkout production
Switched to branch 'production'
$ git diff HEAD^ --no-prefix > 2014.08.19.1.patch
应用补丁
$ patch -p0 < 2014.08.19.1.patch
patching file production.txt
打标签
标签是一个只读的版本标记,经常用来标示版本号,例如我们刚才做的修改我们可以命名为v1.0.0版本。
$ git checkout production
Switched to branch 'production'
$ git tag v1.0.0
$ git push --tags
Total 0 (delta 0), reused 0 (delta 0)
To /data/tests/test_project_remote
* [new tag] v1.0.0 -> v1.0.0
$ git tag
v1.0.0