版本控制系统起源于软件代码管理,在编写代码的过程中,同一个文件可能存在多次修改,后续修改可能对之前的文件造成不好的影响,但是想恢复又十分麻烦。也有可能某一代码文件存在多个版本,更糟糕的是多个人共同修改一个或多个文件,该怎么办呢?
最早的时候是通过保存多个文档,通过不同的文件名区分,然后用diff命令查看比较各个文件的不同。但是随着项目文件越来越多,人工管理方法越来越难,尤其是多人的开源项目,根本没有办法管理,于是版本控制系统诞生了。
版本控制系统分为版本库和工作区两个部分,用户在工作区修改文件,修改一部分内容后,可以提交到版本库,版本库记录文件的修改,然后用户可以对比工作区与版本库文件、恢复版本库内的文件到工作区、版本库退回到更早的提交、多个人共同修改一个文件时合并各自修改到同一文件等等,事实上,版本控制系统更加复杂,功能更多。(ps:虽然版本控制系统起源于代码管理,但是用于个人文档管理也很方便,我就用svn在自己计算机上存储一些图片、文档资料,当然,版本控制系统管理文本文档更加有优势。)
早期的集中式版本控制通常把版本库放在服务器上,用户在本地工作区工作,修改完后提交到远程库;对比、恢复等工作都需要和服务器交换信息。这样会导致服务器负荷比较大,每个人每次提交都要和服务器交互,工作过程中有时提交比较频繁如果网速较慢十分影响效率,同时,如果一个人的失误的将中间过程都提交到了版本库主分支上,也将影响其他人工作。
分布式版本控制,提供了更好的功能,总体来讲分布式系统和集中式系统功能差不多,只是分布式系统的版本库并不在服务器上,而是在本地,也就是和工作区存储在供一个地方,用户在自己计算机上修改文件,向自己计算机上的版本库提交。分布式版本控制系统每个人计算机上都有一个完整的版本库,那多人共同开发一个项目时,怎么办呢?其实每个人只需要把各自的修改推送给其他人就可以知道其他人的修改了,并且可以以补丁的方式合并到自己的版本库。当然,很少有人相互推送,一般分布式版本库管理系统也会建立一个服务器,这个服务器上也有完整的版本库,但是和集中式版本控制系统的服务器不同,分布式系统的服务器仅仅是用于共享代码和交换大家修改的(大名鼎鼎的github网站的官方git程序就用同步/sync代替所有和远程服务器的交互操作)。
git的内部原理就不说了,反正git是当前速度最快,用户最多的系统,一切以实用为目的版本管理系统。
本文从应用的角度归类总结了git用的最多的操作和命令。
分布式版本控制系统,一般都有工作区、本地库、远程库3个空间。
git功能更强一些,有4个空间
用户在工作区修改文档,然后可以添加到暂存区暂存,工作达到一个小阶段可以把暂存区的文档提交到本地库;如果用户在工作区文件修改失误了,还可以从暂存区、本地库恢复文档;用户也可以随时把本地库推送/同步到远程库。
当然版本库还有分支操作,即在版本库里创建不同的分支,在不同的分支内创建代码的不同版本。
因此,我们把git的操作分为本地操作、远程操作以及环境设置几部分。
本地操作:
远程操作:
环境设置操作:
本文件是我个人作为git功能备忘而做的小结,不是一个系统的教程。如果没有一点基础,觉得本文比较难懂,可以到这位前辈的网上学习史上最浅显易懂的Git教程 。如果了解svn或git,本文将是一个很好的使用参考。
在安装好git后,必须要设置用户名和email才能使用其他命令,因为git是多人协作的仓库,需要用user和email来区分使用者。
git config --global user.name "Your Name"
$ git config --global user.email "[email protected]"
注意git config命令的–global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
然后我们需要有一个版本库,才能开始工作。可以在本地新建一个空的版本库,再添加文件到版本库,并根据需要推送到远程库上;也可以从远程clone一个版本库到本地,然后进行修改、查看或管理工作。
在project1目录下创建本地库:
cd project1
git init
即使project1目录下已经有一些代码文件,使用git init也没有问题,不会删除已有文件。
project1目录就是该库的工作目录,而本地库和暂存区则是在project1/.git隐藏目录下,因此,请不要删除.git文件夹。
将远程版本库repository克隆到directory目录下:
git clone <repository>
git clone <repository> <directory>
repository是远程库的地址,上述命令目录directory相当于克隆版本库的工作区。省略directory的话,则将远程库的名字作为directory name。
克隆裸版本库:
git clone --bare <repository> <directory.git>
git clone --mirror <repository> <directory.git>
上面的两种方法克隆的版本库,都直接将.git内的文件放在directory.git文件夹内,这样的版本库称为裸版本库。git init --bare
可以创建裸版本库。裸版本库不包含工作区,不能直接修改文件。通常我们不需要创建或者克隆裸版本库,裸版本库主要用于备份或者服务器端共享。
在拥有本地库后可以做很多工作,比如:
等等,具体包括:
把工作区文件添加到暂存区。
git add file1 file2
文件添加到暂存区后,再修改工作区内文件内容,工作区内文件内容将和暂存区内的文件内容不同,用git diff file1
可以查看file1文件在工作区和暂存区内的不同。
文件添加到暂存区一次后,git会进行跟踪,跟踪这个文件是否做了修改等。可以通过git status命令查看。对于工作区新建的文件,如果从没有被添加到暂存区过,git就不会跟踪该文件。
文件添加到暂存区后,再修改文件,暂存区的内容并未被修改,如果直接提交,提交的内容将是添加到暂存区时的内容,而不是修改后的内容。所以每次修改文件后都要重新add一次。
git add .
: Git会递归地将你执行命令时所在的目录中的所有文件添加上去,所以如果你将当前的工作目录作为参数,它就会追踪那儿的所有文件。
git add -u
: 使用-u参数调用了git add命令,会将本地有改动(包括删除和修改)的已经追踪的文件标记到暂存区中。忽略没有跟踪的问题件。
git add -A
: 使用-A参数会将添加所有改动的、已跟踪文件和未跟踪文件到暂存区,包括工作区删除文件操作也会被跟踪。
git add -i
: 交互式的方式进行添加。
git库的操作速度很快,因为每次操作都只针对修改部分进行。本地库实际上只保存了一份完整的文件内容,文本以修改片段的形式存储,使用类似指针的形式标记修改内容。所以本地库的大小也不会比实际存储文件大多少。
每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”到本地库,这个快照在Git中被称为commit。一旦你把文件改乱了,或者误 删了文件,还可以从最近的一个commit恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
把当前暂存区文件提交到本地库的命令:
git commit -m "add readme file"
-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
git commit -am "message"
,对本地所有变更的文件执行提交操作,包括对本地修改的文件和删除的文件,但是不包括未被版本库跟踪的文件。该命令是git commit -a -m "message"
的简写。等同于连续执行git add -A
和git commit -m "message"
。
commit之后,如果发现说明字符串不是很合适,还可以修改。用下面命令修改前一次提交的说明文本:
git commit --amend
运行该命令后会显示可修改内容,直接修改,然后保存即可。只能修改前一次提交-m后说明内容。
git commit -–amend –author=
,只修改作者信息。
要随时掌握工作区的状态,使用git status命令。
git status
git status将列出所有可暂存或commit的文件,并分为三类
如果一个文件暂存了一次,然后又进行了修改,则会在2、3两个列表内均出现一次。
git status -s
,简略的方式显示改动,staged Changes to be committed将用一个字母M标识,同样Changes not staged for commit也用一个字母M标识,同时属于这2种,显示则用2个M表示,可以通过和git status
命令对比学习。在文件比较多时,这个命令更容易阅读。
git status -uno
不列出未被跟踪的文件。等同于git status --untracked-files=no
git diff readme.txt
比较readme.txt文件在工作区和暂存区的不同,输出为标准的diff文档格式,即+开头代表增加的内容,-号开头代表删除的内容,在内容之前有比较文件的说明及修改行号信息。
git diff --cached
,显示工作区所有被跟踪文件的修改。
git diff branch1 branch2
比较branch1分支与branch2分支
git diff local_branch origin/remote_branch
比较本地分支local_branch与远程分支remote_branch
git difftool
:可视化的diff工具
git diff head test.txt
比较工作区test.txt文件和版本库里同一文件的不同。
该命令是git diff head – test.txt命令的简写。命令中–用来隔开版本和文件名,–可以省略。
用暂存区中file文件内容覆盖工作区内的同一个文件:
git checkout -- file
执行该命令,暂存区文件内容不变。执行完成后,工作区内file文件和暂存区中文件内容相同。
git checkout -- file
中–不能省略,因为git checkout name命令的功能是切换到name分支。
git checkout -- .
: 用暂存区的所有文件直接覆盖本地文件,取消所有的本地的修改,是一条危险的操作。
git reset file
仅将文件file撤销暂存,但仍然跟踪,工作区文件不改变。相当于命令git add file 的反向操作,该操作后git status将显示file文件为未暂存状态。
git reset file实际上就是用本地库head版的file文件覆盖暂存区。其实就是下边4个命令的简写:
git reset --mixed head -- file git reset head -- file git reset head file git reset -- file
撤销暂存还可以用git rm --cached
命令
git rm --cached file
从暂存区删除file文件。相当于撤销暂存file文件,并取消跟踪。该操作后git status将显示file文件为未跟踪状态。从未提交过的文件只能用这个命令,而不能用git reset file。
git reset
命令等同于git reset HEAD
。将撤销所有暂存区内容,相当于用head重置暂存区,当前工作区内容不变。
git reset --hard与git reset --hard head相同,用本地库head版本覆盖暂存区和工作区。该操作比较危险,谨慎使用。
git reset <commit-id> file
上述命令执行后,用提交的file覆盖暂存区的file。
用git log等命令可以查看每个提交的commit-id。通常我们只输入commit-id的前7位数。
git rm file
git rm命令会将删除暂存区及工作区中的file,本地库中文件不受影响。
相当于先执行工作区rm然后再git add。
git mv filefrom fileto
在暂存区及工作区中移动文件。
和如下命令效果相同:
mv filefrom fileto git rm filefrom git add fileto
或者
mv filefrom fileto git add filefrom git add fileto
如果想放弃工作区和暂存区的修改,比如说:误删了工作区和暂存区的文件或者做了错误的修改,想恢复文件,那么可以用下面命令从本地库恢复文件到暂存区和工作区(相当于用本地库的head版本文件覆盖工作区和暂存区文件)。
git checkout head file
当然也可以分两步做,运用之前的知识,先从版本库恢复文件到暂存区,再从暂存区恢复文件到工作区,即:
git reset file #用本地库的file文件覆盖暂存区的file文件
git checkout -- file #用暂存区的file文件覆盖工作区的file文件
也可以用指定某次commit中的文件覆盖暂存区和工作区中对应的文件。方法为:
git checkout <commit-id> file
比如可以这样:git checkout head^^ file
如果最新提交的文件有问题,不想要了,要撤销,回到上一次提交的状况,可以用下面命令。
git reset --hard HEAD^
上述命令表示回退到上一次提交,版本库、暂存区和工作区文件都将变成上一次提交时的状态。当前提交、当前暂存区、当前工作区内容都会丢失。
在Git中,用HEAD(小写head也可以)表示当前提交,也就是最新的提交,上一个提交用HEAD表示,上上一个版本就是HEAD^,当然往上100 个版本写100个^比较容易数不过来,所以写成HEAD~100。
事实上该命令执行了三个步骤操作:
git reset --mixed HEAD^
, 只执行1,2操作,覆盖暂存区,但不覆盖工作区,当前工作区不变。–mixed是默认参数,即该命令等同于git reset head^
git reset --soft HEAD^
, 只执行1操作,不进行暂存区和工作区的覆盖,当前暂存区和工作区不变
git reset --hard
,回退到某一commit-id的提交,git内部用commit-id来表示每一次提交,HEAD事实上就是一个指向某个commit-id的指针。
做了很多修改,提交了很多次后,很可能就已经忘记了之前的操作了,这时可以用git log查看曾经做的commit的操作。
git log
查看所有提交的历史,显示每次提交的commit-id, 作者, 时间, 说明。git log在GUI里就是一条时间线
git log --pretty=oneline
, 单行显示结果,只显示每次提交的commit-id和说明。
git log --graph
git log file.txt
, 显示file.txt的修改历史。
如果某些提交已经被回退掉了,那么git log就不能看到这个提交了。那么是不是该提交的记录都被删除了呢,实际上没有,还在本地库里。git会记录你的每一次操作,包括回退操作。可以用下面命令查看所有操作
git reflog
上述命令会列出之前你的每一次操作(包括回退等)的信息,包括commit-id, head指针, 操作命令,说明等信息,参考输出示例如下:
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
既然回退掉的提交还在本地库里,那么如果后悔了,能不能恢复呢,答案是能。
git reset --hard commit-id
用上述命令可以恢复到被回退了的某个提交。commit已经被回退了,用git log就看不见了,可以用git reflog
命令查看。
git中每次提交修改,git都只是记录修改的内容,并用commit-id进行标记,即使被回退的提交也并没有被删除。
当你修改了一些文件,修改到一半,还没有提交时,有个bug需要修复。如果这时提交修改的文件,因为文件修改了一部分,提交后代码可能根本不能编译;如果不提交,bug怎么修改,即使bug在其他分支上修复了也没法合并过来。怎么办?Git还提供了一个stash功能,可以临时保存工作现场,以后恢复现场后再继续工作。
git stash
把当前的工作区保存在stash空间内,并用本地库文件覆盖当前暂存区和工作区。这时用git status会发现工作区是干净的。
用git stash list
命令可以查看stash空间内临时保存的内容。git stash可以多次使用,因为stash空间里可以保存多个内容。git stash list显示的结果也是个列表,每个内容都有一个编号和id。
git stash pop命令可以恢复最近一次放到stash空间的内容到当前工作区,恢复后,stash空间内对应的内容则删除
git stash apply:恢复最近一次放到stash空间的内容到当前工作区,但stash中对应的内容并不删除
git stash drop:删除最近一次放到stash内的内容
git stash apply和git stash drop:都可以指定编号或id。比如:git stash apply stash@{0}。编号或id通过git stash list查看。
git stash apply --index:不仅恢复工作区,也会恢复暂存区
git branch
: 显示当前所在的分支git branch -a
: 列出远程跟踪及本地分支git branch
: 创建新的分支branchnamegit checkout
: 切换当前分支到branchnamegit checkout -b branchname
: 创建一个新的分支,并且切换到创建的新的分支上git merge
:把branchname分支合并到当前分支
如果有冲突会提示冲突,需要解决冲突后才能合并。git status也可以告诉我们冲突的文件是哪些。需要逐个手动修改冲突的文件,解决冲突后再重新合并。
git log --graph可以看到分支合并图。
git merge --no-ff -m "merge with no-ff"
:–no-ff表示不使用fastforward方式合并,即合并时dev分支的指针不动,创建一个新的commit,-m即commit的说明文本。
加上–no-ff参数合并后的历史有分支,能看出来曾经做过合并。
而fast forward合并过程中只是移动head指针,看不出来曾经做过合并。
git mergetool
是一个可视化的merge工具
合并远程分支实际上就是先拉取远程分支到本地,然后再合并,可以先git fetch,然后git merge。
git branch -m
: 移动/重命名一个分支git branch -d
: 删除名称为branchname的分支,只能删除已经合并到了的分支git branch -D
: 删除分支,即使没有合并。如果没有合并,则未合并的提交也都会被删除。加上-f参数时,表示创建、移动或删除分支
假如你想合并"mywork"分支到master分支,如果你想让”mywork“分支历史看起来像没有经过任何合并一样,也可以用 git rebase,如下所示:
git checkout mywork
git rebase origin
这些命令会把你的”mywork“分支里的每个提交(commit)取消掉,并且把它们临时 保存为补丁(patch)(这些补丁放到”.git/rebase“目录中),然后把”mywork“分支更新 到最新的master分支,最后把保存的这些补丁应用到”mywork“分支上。当’mywork‘分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。 如果运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除. 如果想更深入了解可以看本文参考文章链接。
前面的分支操作命令都只作用于本地库,远程库的分支也是可以操作(创建、删除、移动)的,只需要加上-r
参数就可以了。
比如:git branch -r
在远程库上创建分支
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像,但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
git tag v1.0
创建一个名称为v1.0的标签git tag v1.0 1094adb
使用指定的commit创建一个名称为v1.0的标签git tag -a v0.1 -m "version 0.1 released" 1094adb
创建一个名称为v0.1的标签,并添加文本说明git tag
查看所有标签git show v1.0
查看标签详细信息git tag -d v0.1
删除标签git push origin v1.0
推送标签到远程库git push origin --tags
一次性推送全部尚未推送到远程的本地标签git tag -d v0.9
git push origin :refs/tags/v0.9
如果标签已经推送到远程,要删除远程标签,先从本地删除然后推送标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。
git archive head
: 对当前提交对应的目录树建立归档。git archive -o latest.zip HEAD
: 基于最新提交建立归档文件latest.zipgit archive -o partial.tar HEAD src doc
: 只将src和doc目录建立到归档文件partial.tar中git archive -o v1.zip --prefix=1.0/ v1.0
: 将tag1.0每个文件路径添加前缀1.0后建立归档latestgit archive --format zip -o /path/f.zip master
将 master 以zip格式打包到指定文件git archive --format=tar --prefix=1.0/ v1.0 | gzip > foo-1.0.tar.gz
: 基于里程碑v1.0建立归档,并且为归档中的文件添加目录前缀1.0git archive -o f.zip head $( git diff v1.1.8_beta13..v1.1.8_beta14 --name-only)
: 增量打包. 比较两个版本之间的差异文件,生成一个差异文件压缩包git archive -o f.zip head $( git diff --name-only head^)
: 增量打包–format通常可以省略
-o在shell下通常可以用 | gzip 代替
比如:
git archive master -o f.zip
git archive master | gzip > f.tgz
还有一个不常用的命令git bundle。bundle 命令会将 git push 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch file' --prune-empty --tag-name-filter cat -- --all
如果你要删除的目标不是文件,而是文件夹,那么请在 git rm --cached
命令后面添加 -r 命令,表示递归的删除(子)文件夹和文件夹下的文件,类似于 rm -rf
命令。
如果你看到类似下面这样的, 就说明删除成功了:
Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (266/266)
# Ref 'refs/heads/master' was rewritten
推送修改后的库到远程需要以强制覆盖的方式推送你的repo, 命令如下:
git push origin master --force --all
为了能从打了 tag 的版本中也删除你所指定的文件或文件夹,您可以使用这样的命令来强制推送您的 Git tags:
git push origin master --force --tags
虽然上面我们已经删除了文件, 但是我们的repo里面仍然保留了这些objects, 等待垃圾回收(GC), 所以我们要用命令彻底清除它, 并收回空间。命令如下:
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
Counting objects: 2437, done.
# Delta compression using up to 4 threads.
# Compressing objects: 100% (1378/1378), done.
# Writing objects: 100% (2437/2437), done.
# Total 2437 (delta 1461), reused 1802 (delta 1048)
$ git gc --aggressive --prune=now
Counting objects: 2437, done.
# Delta compression using up to 4 threads.
# Compressing objects: 100% (2426/2426), done.
# Writing objects: 100% (2437/2437), done.
# Total 2437 (delta 1483), reused 0 (delta 0)
现在你再看看你的.git目录文件大小是不是变小了.
git本地仓库和远程仓库之间的传输是通过ssh加密的,所以必须要设置ssh key。没有sshkey是不能推送文件到远程仓库的。
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),用下面命令创建SSH Key:
ssh-keygen -t rsa -C "[email protected]"
[email protected]是邮件地址用于注释的,不设也可以。
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容。点“Add Key”就设置成功了
如果是git clone的库,自动与远程库关联。如果本地新建的库,则需要关联远程库,才能传输数据。
git remote add origin <远程库地址>
: 添加远程库,通常用origin作为远程库的别名,也可以是其他名字。
git remote -v
: 查看所有添加的远程库
git remote show origin
:查看某一个远程仓库的更多信息
git remote rename origin newname
:远程仓库的移除与重命名
git remote rm origin
:移除关联的远程仓库
在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。
Git也允许手动建立追踪关系。
git branch --set-upstream master origin/next
或者
git branch -u master origin/next
上面命令指定master分支追踪origin/next分支。
git push origin master
: 把本地当前分支的最新修改推送至远程库origin
的master分支
git push -u origin master
把本地当前分支的最新修改推送至远程库origin,并将本地当前分支与远程origin的master分支关联。第一次推送时通常本地库的分支没有和远程库分支关联,所以需要用-u参数。
如果当前分支已经与远程分支存在追踪关系,git pull就可以省略远程分支名。
git push一次只能推送一个分支,
git fetch origin
: 从origin拉取仓库数据到本地仓库,git fetch并不会自动合并或修改你当前的工作,也不会覆盖暂存区和工作区。
git pull <远程主机名> <远程分支名>:<本地分支名>
git pull origin next:master
:取回origin主机的next分支,并自动尝试合并到本地master分支。
git pull
:如果当前分支已经与远程分支存在追踪关系,则该命令取回与当前分支关联的远程分支。
在默认模式下,git pull相当于git fetch后跟git merge FETCH_HEAD的缩写。更准确地说,git pull使用给定的参数运行git fetch,并调用git merge将检索到的分支头合并到当前分支中。 如果使用–rebase参数,则它运行git rebase而不是git merge。
生成patch
git diff commit1 commit2 > 1.patch
应用patch
git apply 1.patch
可以看到patch已经打上去了,但是修改信息啥的还得自己来写一下,不是很完美。
git format-patch -1 commitid是提取单个commitid对应的patch)
2.使用git am 应用补丁
(一般建议git am应用补丁前使用git am --abort)
可以发现这种打补丁方式会将提交信息直接都打进去,非常方便。
有冲突的话解决冲突,然后git add对应文件,git am --resolve。不能解决想后面再说就git am --skip,具体使用方法参考git am --help
PS:
1 使用git format-patch生成所需要的patch:
git format-patch生成的补丁文件默认从1开始顺序编号,并使用对应提交信息中的第一行作为文件名。如果使用了-- numbered-files选项,则文件名只有编号,不包含提交信息;如果指定了–stdout选项,可指定输出位置,如当所有patch输出到一个文件;可指定-o
指定patch的存放目录;
2应用patch:
https://www.yiibai.com/git