前言
最近实习中上手了一个简单的TMC项目,实际开发中使用SVN,但是突然有了想对比下Git和SVN的想法,于是就上网看了下教程。GitHub一直在用,但是基本都是clone别人的项目,所以对Git的使用一直没有跟进,所以现在算是亡羊补牢。
就实际工作而言,只需要掌握常见的Git提交文件以及管理分支的几个指令,学会怎样进行版本回退等(Git的命令清单)就可以满足日常团队写作需求。为了进一步了解常用指令,本文对Git做一个详尽的介绍,主要内容是对廖雪峰的官方网站内容的总结,尊重版权,从我做起。
1 版本控制系统:集中式or分布式
集中式版本控制系统(SVN):版本库存放在服务器中,工作的时候需要先从服务器拷贝最新版本到本地,修改完成后推送给服务器。注意:单个非服务器的电脑之间是无法相互通信的。集中式版本控制的最大问题在于需要时刻联网,对局域网来说,带宽大,速度快,但是对于互联网,速度就会很慢。
分布式版本控制系统(Git):每个用户的电脑上都有一个完整的版本库,工作的时候不需要联网,多用户之间交换修改只需要将修改互相推送给对方。虽然也有一个形式上的中央服务器,但是该服务器作用是用来方便交换用户之间的修改。优点是安全性较好,一方的电脑故障,在另一个电脑上依旧保存有完整的版本库。而且,减少了对服务器的依赖。
1.1 在windows上安装Git
Windows上安装linux/Unix需要Cygwin工具,配置较为复杂。msysgit是Windows上的Git,从https://git-for-windows.githu...下载,然后按默认选项安装即可。安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功!
安装完成后,还需要最后一步设置,在命令行输入:
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。
注意git config命令的--global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
1.2 工作区和暂存区
工作区(working directory)就是在电脑里能够看到的目录,工作区中有一个隐藏目录.git,这是Git的版本库,里面存储了名叫stage的暂存区,还有Git为用户自动创建的一个分支master,以及指向master的一个指针叫HEAD。
在将修改过的文件提交到Git版本库的时候,需要git add将文件添加进去,实际上就是添加到暂存区,然后通过git commit提交文件,实质上就是将暂存区的所有文件一次性提交。一旦提交后,再使用git status命令,显示工作区是干净的。
1.3 创建版本库
版本库即一个仓库(repository),这个目录里的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便于任何时刻都可以追踪历史。创建版本库的代码如下:
pwd用于显示当前目录,如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
然后通过git init将版本库变成Git可以管理的仓库:
在learngit目录下创建文件index.js,然后将其添加到Git仓库,需要两步,用git add <fileName>添加文件,用git commit提交到仓库:
-m后的字符串是提交说明,提交后输出版本库的改动信息。add和commit的区别在于,add可以多次添加不同的文件,commit可以一次提交多个文件。
1.4 查询文件状态
对index.js文件做修改,然后通过git status 查询状态:
Git指出那些文件被修改了,并给出建议提交更改的命令。“no changes added to commit”表明改变的部分并未被提交。甚至可以通过git diff <filename>查询更改发生的具体内容。
更改完之后就是提交文件,这和在版本库中增加文件操作相同,先add再commit。
提交完成后再观察版本库的目前状态,可以发现,没有需要提交的更改,工作树是干净的。
总之:git status 查看是否有需要提交的文件;git diff <fileName>输出更改的具体部分。
1.5 版本回退:git reset –hard <fileVersion>
事实上我对index.js做了不止一次修改和提交,因此可以通过 git log 查看最近多次修改的内容,这时可以看出为什么在git commit命令后加-m “修改说明”了,这些修改说明会在log时显示出来,依次显式:版本号、修改日期、提交的修改说明。
回退到先前版本:Git用HEAD表示当前版本,HEAD^表示上一版本,HEAD^^表示再上一个版本。如果回溯的版本比价多,可以用HEAD~100表示回溯到100版本。回溯命令是git reset --hard <回溯版本>,例如想回溯到modified index.js,使用命令:
git reset --hard HEAD^
Git显示已经恢复到上一版本,通过cat <fileName>命令查看文件内容,可以看到,文件已经恢复。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从当前版本指向旧版本,然后顺便把工作区的文件更新了。
一种更直接的版本回退方式是通过 git reset --hard <version-id>,而且可以通过 git reflog显式所有先前输入的命令来帮助获得版本号。
总结:
HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。
2 管理修改
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。假设有这样一个操作过程:第一次修改 -> git add -> 第二次修改 -> git commit,此时通过git status显示第二次修改并没有被提交,这是因为只有通过git add才能将文件的修改提交到暂存区,然后git commit提交的是这部分修改,不在暂存区的修改是没办法提交的。总之,如果不add到暂存区,就不能commit到版本库中。
2.1 撤销修改
假设工作区有一份index.js文件,将它add到暂存区中,
然后,在文件中加入 and tall,此时cat index.js显示如下,更改已经生效,但是没有add。
此时通过git checkout撤销更改,此处的撤销是返回到上一次add之后的文件:
命令git checkout -- file意思就是,把index.js文件在工作区的修改全部撤销,这里有两种情况:
一种是index.js自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是index.js已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit或git add时的状态。git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令。
如果已经将修改add到暂存区,怎么撤回呢?
此时通过git reset HEAD <fileName>将暂存区的修改撤销,重新回到工作区:
此时内容修改依旧存在,但是git status表明changes not staged for commit,即修改没有add到暂存区,这时就可以使用上述git checkout撤销工作区的更改:
cat输出原来内容,上传到暂存区的更改被撤销。
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。但是这仅仅返回到上一次add或commit时的版本。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD fileName,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
2.2 文件删除
如果需要删除文件,使用rm <fileName>即可,删除之后,工作区和版本库中的文件不一致,如果是确认删除,再commit一下即可,文件就会从版本库删除;如果是误删,版本库里依旧有文件,因此可以使用git checkout用版本库中的文件恢复到工作区,git checkout 其实就是用版本库中版本替换工作区的版本。
2.3 远程仓库
上述关于Git管理仓库文件的功能和集中式版本管理系统SVN没有区别,以下介绍Git的分布式特性:远程仓库。GitHub网站提供了远程仓库的简易实现,但是由于因为本地Git仓库和GitHub之间是通过SSH加密的,所以需要进行设置SSH。
创建ssh密匙
命令: ssh-keygen -t rsa -C “emailAdress”
登录GitHub,打开SSH Keys页面,点击 add SSH Key,粘贴id-rsa.pub的内容。然后在GitHub网站创建自己的仓库learngit,建立本地库和远程库的连接:
然后将本地版本库push到远程github,本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样。从现在起,只要本地作了提交,就可以通过命令:
$ git push origin master
把本地master分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!
总之,要记住以下几点:
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
2.4 克隆仓库
要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone命令克隆。Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快。
3 分支管理
如果在Git中仅有一条时间线,也就意味着仅有一条分支:master分支,HEAD指针指向master分支,master指向最新的提交,所以HEAD指向的就是当前分支。
每次提交,master就会向前移动一步,这样随着不断提交,master分支的线也越来越长。当我们创建新的分支dev时,Git新建了一个指针叫做dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上。
Git创建一个分支很快,因为除了增加一个dev指针,改变HEAD的指向,工作区的文件没有变化。指向dev后,所有的修改和提交就是针对dev了。
当分支上的工作完成之后,就可以把dev合并到master之上,合并的方式就是把master指向dev的当前提交。
合并分支后,就可以把dev分支删除,删除分支就是删除dev指针。
3.1 分支转换
创建dev分支,然后切换到dev分支,git checkout 加上-b相当于创建并且换分支,其中git branch <branchName>是创建分支、git checkout <branchName>是转换分支;git branch命令列出所有分支,当前分支前会有*号。
随后对index.js进行修改并add和commit到分支,在dev分支上正常提交,随后切换到master分支:git checkout master,此时查看index.js发现,内容没有修改,这很正常,因为主分支没有修改。
现在将dev分支合并到master分支上,git merge命令用于合并指定分支到当前分支,合并后查看index.js的内容,发现修改已经反应到master分支上。
合并后就可以删除dev分支,删除之后分支就只有master了。
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
注意,以上合并基于fast-forward模式,这种模式下,删除分之后,会丢掉分支信息,对此可以使用—no-ff方式进行合并:
git merge --no-ff -m "merge with no-ff" dev
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
3.2 分支策略
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
3.3 Bug分支
如果工作过程中需要解决一个bug,此时最好能够将当前工作现场保存起来,然后创建bug分支,并转换到需要修复bug的分支进行修复。在bug分支修复bug之后,将其合并到目标分支上,然后删除bug分支。
Git提供了一个stash功能,可以将当前的工作现场保存起来,等以后恢复现场后继续工作:
git stash
bug修复之后,切换回到原来分支,通过 git stash list查看工作现场列表,然后进行恢复:采用git stash apply恢复,恢复之后,stash内容并不删除,用git stash drop删除即可;采用git stash pop恢复,同时stash的内容也被删除,这是针对只有一个stash的情况。当然,如果进行了多次stash,恢复的时候需要先用git stash list查看需要回复的stash的名称,通过git stash apply <stashName>恢复:
git stash apply stash@{0}
3.4 Feature分支
软件开发过程中,会有新功能不断加进来,添加新功能时,为了保证主分支不被弄乱,最好是新建一个feature分支,在上面开发,完成后再进行合并,并删除feature分支。也可能最后设计好的新功能不需要了,也就是需要删除未合并的分支,这时候删除分支就需要强制删除,也就是 git branch -D feature命令。
3.5 多人协作的分支操作
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。要查看远程库的信息,用git remote:
$ git remote
或者,用git remote -v显示更详细的信息:
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
3.6 推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上:
$ git push origin master
并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
master分支是主分支,因此要时刻与远程同步;
dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;
feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。
3.7.gitignore:
该文件指出哪些文件不应该加到版本库中,已经添加过的文件不受影响,所以最好在项目一开始就配置.gitignore文件,否则就没啥作用了。.gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!文件中的规则包含两类:过滤规则和添加规则,区别在于添加规则多了开头的感叹号。
过滤规则:/表示.gitignore所在的目录,一般是主目录。
以星号“*”通配多个字符;
以问号“?”通配单个字符
以方括号“[]”包含单个字符的匹配列表;
以叹号“!”表示不忽略(跟踪)匹配到的文件或目录;
git 对于 .ignore 配置文件是按行从上到下进行规则匹配的,意味着如果前面的规则匹配的范围更大,则后面的规则将不会生效。
(1)规则:fd1/*
说明:忽略目录 fd1 下的全部内容;注意,不管是根目录下的 /fd1/ 目录,还是某个子目录 /child/fd1/ 目录,都会被忽略;
(2)规则:/fd1/*
说明:忽略根目录下的 /fd1/ 目录的全部内容;
(3)规则:
/*
!.gitignore
!/fw/bin/
!/fw/sf/
说明:忽略全部内容,但是不忽略 .gitignore 文件、根目录下的 /fw/bin/ 和 /fw/sf/ 目录;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。