作为一枚测试,一直贪方便使用SourceTree工具进行Git操作,以致无法深刻理解Git的精髓,上周重新学习了廖雪峰老师的Git教程,瞬间豁然开朗。
Git安装
从官网下载Git安装包,安装完成后,打开Git Bash设置本机的用户名和邮箱。
git config --global user.name "用户名"
git config --global user.email "邮箱地址"
创建版本库及添加文件
使用git init命令可以把本地的目录变为可管理的git仓库。
执行完命令后,可发现目录下多了一个.git的目录。该目录是隐藏的,可使用以下命令查看。
ls -ah
使用以下命令添加文件到版本库,第一步添加仓库,第二步提交仓库。
git add learn.txt
git commit -m "create learn.txt"
再次修改本地的learn.txt文件,使用git status查看仓库状态,可以看到提示文件有改动。
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: learngit.txt
no changes added to commit (use "git add" and/or "git commit -a")
使用git diff命令对比文件改动的内容,可以看到文件后面加了一行“add by tomandy ---update1” 。
$ git diff learngit.txt
WARNING: terminal is not fully functional
diff --git a/learngit.txt b/learngit.txt
index 97f3542..12b8c54 100644
--- a/learngit.txt
+++ b/learngit.txt
@@ -1,2 +1,3 @@
i am tomandy.
who are you?tomandy
+add by tomandy ---update1
\ No newline at end of file
git diff #是工作区(work dict)和暂存区(stage)的比较。
git diff --cached #是暂存区(stage)和分支(master)的比较
git diff HEAD -- learngit.txt命令可以查看工作区和版本库里面最新版本的区别。
版本回退
git log或git log --pretty=oneline可以查看版本日志。
$ git log
WARNING: terminal is not fully functional
commit 90cfb6d8576f26f46159521e6e1a3f17662e769b
Author: linrongbiao
Date: Tue Mar 13 14:32:31 2018 +0800
add something
commit ad0d3bb95e93da1bf22be6d7f689e25a33c90562
Author: linrongbiao
Date: Tue Mar 13 11:41:22 2018 +0800
create learngit.txt
$ git log --pretty=oneline
WARNING: terminal is not fully functional
90cfb6d8576f26f46159521e6e1a3f17662e769b add something
ad0d3bb95e93da1bf22be6d7f689e25a33c90562 create learngit.txt
在git中,HEAD表示当前版本,也就是上述的90cfb6d8576f26f46159521e6e1a3f17662e769b版本id,上一个版本是HEAD^, 上上个版本是HEAD^^,往上100个版本写成HEAD~100。可以使用git reset命令进行版本回退。
$ cat learngit.txt
i am tomandy.
who are you?tomandy
add by tomandy ---update1
$ git reset --hard head^
HEAD is now at ad0d3bb create learngit.txt
$ cat learngit.txt
i am tomandy.
who are you?tomandy
可以发现,learngit.txt回到了最初的版本,即去掉了“add by tomandy ---update1”这行。
git reflog命令记录每次执行的操作日志。
$ git reflog
WARNING: terminal is not fully functional
ad0d3bb HEAD@{0}: reset: moving to head^
90cfb6d HEAD@{1}: commit: add something
ad0d3bb HEAD@{2}: commit (initial): create learngit.txt
git reset命令也可以根据版本id进行版本回退。
$ git reset --hard 90cfb6d
HEAD is now at 90cfb6d add something
$ cat learngit.txt
i am tomandy.
who are you?tomandy
add by tomandy ---update1
可以发现,“add by tomandy ---update1”又回来了。
工作区和缓存区
工作区有个隐藏的.git目录,是Git的版本库。Git版本库提供了成为stage的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master分支的一个HEAD指针。
第一步的git add相当于把文件添加到暂缓区。第二部的git commit实际上是把暂缓区的所有内容提交到当前分支。
撤销修改
git checkout -- learngit.txt用于把文件在工作区的修改全部撤销,有以下两种情况。
- 一种是learngit.txt修改后还没放到暂存区,撤销修改后就回到和版本库一模一样的状态。
- 一种是learngit.txt修改后已放到暂存区,工作区又做了修改,撤销修改后回到添加暂缓区一模一样的状态。
总之就是让文件内容回到最近一次git commit或git add的状态。
$ cat learngit.txt
i am tomandy.
who are you?tomandy
add by tomandy ---update1
add by tomandy ---update2
add by tomandy ---update3
$ git checkout -- learngit.txt
$ cat learngit.txt
i am tomandy.
who are you?tomandy
add by tomandy ---update1
add by tomandy ---update2
通过以上命令,发现learngit.txt恢复为与版本库一致。
git reset head leargit.txt可以把暂存区的修改撤销掉,重新放回工作区,然后再使用git checkout -- learngit.txt撤销工作区的修改。git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
$ git add learngit.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: learngit.txt
$ git reset head learngit.txt
Unstaged changes after reset:
M learngit.txt
$
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: learngit.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout -- learngit.txt
$ cat learngit.txt
i am tomandy.
who are you?tomandy
add by tomandy ---update1
add by tomandy ---update2
add by tomandy ---update3
删除文件
git rm命令可以删除版本库的文件。比如删除了工作区的test.txt文件后,git status展示文件与版本库不一致,此时可以使用git rm删除版本库文件,然后再git commit。
$ rm test.txt
$ ls
learngit.txt
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git rm test.txt
rm 'test.txt'
$ git commit -m "delete test.txt"
[master 4a2a120] delete test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
远程仓库
本地Git仓库和远程Github仓库之间的传输是通过SSH加密的,所以需要先创建SSH key,创建成功的话,最终在用户目录C:\Users\lenovo.ssh下可以看到id_rsa和id_rsa.pub两个文件,把公钥的内容添加到Github。
$ ssh-keygen -t rsa -C "[email protected]"
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
Github上建立远程仓库后,可以克隆本地,也可以把本地的Git版本库关联远程仓库,使用以下命令:
git remote add origin [email protected]:Tomandy1988/TestProject.git
或者克隆远程仓库到本地:
git clone [email protected]:Tomandy1988/TestProject.git
下一步就可以把本地库的内容全部推送到远程仓库:
$ git push -u origin master
The authenticity of host 'github.com (192.30.253.113)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,192.30.253.113' (RSA) to the list of known hosts.
Counting objects: 16, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (16/16), 1.27 KiB | 0 bytes/s, done.
Total 16 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), done.
To [email protected]:Tomandy1988/TestProject.git
* [new branch] master -> master
Branch master set up to track remote branch master from origin.
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
后续本地作了提交,就可以通过以下命令把本地master分支的最新修改推送至Github远程仓库:
git push origin master
创建与合并分支
master分支是一条线,Git用master指向最新的提交,再用Head指向master,就能确定当前分支,以及当前分支提交点,每次提交,master分支都会向前移动一步。
当我们创建新的分支dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上。
后续对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变;
dev分支的工作完成后,就可以把dev合并到master上了,接着再删除dev分支。
以下两组命令效果是一样的,创建并切换到dev分支:
$ git checkout -b dev
等同于
$ git branch dev
$ git checkout dev
git branch命令列出所有分支,当前分支前面标注*号。
$ git branch
* dev
master
如果在dev分支进行工作,后可以通过以下命令将dev分支合并到master分支,git merge属于快进模式合并,也就是直接把master指向dev当前提交,所以合并速度非常快。
$ git merge dev
Already up-to-date.
使用git branch -d dev删除分支。
解决冲突
分支合并往往不是一帆风顺。比如创建并切换到分支feature,然后修改learngit.txt提交(增加一行“add by tomandy ---update4”)。
$ git add learngit.txt
$ git commit -m "AND update4"
切换到master分支,增加一行“add by tomandy ---update5”到learngit.txt,此时master分支和feature分支各自都分别有新的提交,如下图所示。
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
$ git merge feature1
Auto-merging learngit.txt
CONFLICT (content): Merge conflict in learngit.txt
Automatic merge failed; fix conflicts and then commit the result.
查看发现learngit.txt文件标记了不同分支的差异:
$ cat learngit.txt
i am tomandy.
who are you?tomandy
add by tomandy ---update1
add by tomandy ---update2
add by tomandy ---update3
<<<<<<< HEAD
add by tomandy ---update5
=======
add by tomandy ---update4
>>>>>>> dev
修改learngit.txt后,再提交即可解决冲突,最终master和feature分支变成如下图所示:
使用以下命令可以查看分支的合并情况 。
$ git log --graph --pretty=oneline --abbrev-commit
WARNING: terminal is not fully functional
* 34fced9 add update4 not update5
|\
| * 7383c70 add update4
* | 0db4d8c add update5
|/
* 3f682d7 conflict fixed
|\
| * cd1f79b add and to test.txt
* | 211f418 add & to test.txt
|/
* daa2f86 add test.txt
* 4a2a120 delete test.txt
* 407a04b add test.txt
* 5529036 add update3
* 73f4ef3 add update2
* 90cfb6d add something
* ad0d3bb create learngit.txt
分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
readme.txt | 1 +
1 file changed, 1 insertion(+)
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
多人协作
以下命令查看远程仓库信息。
$ git remote
origin
$ git remote -v
origin [email protected]:Tomandy1988/TestProject.git (fetch)
origin [email protected]:Tomandy1988/TestProject.git (push)
当从远程库clone时,默认情况下,只能看到本地的master分支。如果要在dev分支上开发,就必须创建远程origin的dev分支到本地,可以使用以下命令创建本地dev分支。
$ git checkout -b dev origin/dev
使用以下命令指定本地dev分支与远程origin/dev分支的链接。
$ git branch --set-upstream dev origin/dev
Branch dev set up to track remote branch dev from origin.