这里需要辨析一下概念。Github是代码托管平台,是协作的工具;而Git是版本控制工具。Git不需要联网,在本机就可以使用
集中式版本控制系统与分布式版本控制系统
SVN是集中式的版本控制系统,而Git是分布式版本控制系统,集中式和分布式版本控制系统有什么区别呢?
集中式版本控制系统
版本库是集中存放在*服务器的,干活的时候,用的都是自己的电脑,要先从*服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给*服务器,集中式版本控制系统必须联网才能工作
分布式版本控制系统
没有"*服务器",每个人的电脑上都是一个完整的版本库,这样工作的时候就不需要联网了,因为版本库就在自己的电脑上,即便他们下线或者在外,他们依然可以随时查看代码库的修改历史。
既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
优劣:
分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的*服务器要是出了问题,所有人都没法干活了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当"*服务器"的电脑,作用仅仅是用来方便"交换"大家的修改,没有它大家也一样干活,只是交换修改不方便而已。所有的开发者都拥有完整的代码库拷贝,所以他们的工作不会被*服务器的性能甚至有无左右。
Git 在决定代码修改历史以及保存形式的时候不会被文件名的变化所愚弄,Git 关注的是文件的内容本身。
切换到任意分支修改,这些分支不互相影响:
比如说,现有一名开发成员 Alice 对代码进行了一些改动,添加了一些在2.0版本中准备公开的功能,然后将这些修改以及一份简单的说明进行了提交。随后她又增加了一些另外的新功能,并又作了一次新提交。显然这两次修改在版本历史中被分开各自进行了保存。在这之后 Alice 把代码切换到了 1.3 版本,修复了一些旧版本中的 Bug(这和她新添加的功能没有关系)。这次修复的目的是为了让团队可以在公开 2.0 版本之前,释放一个 1.3.1 版本来解决 1.3 版本中的 Bug 问题。Alice 马上又可以回到她之前进行的 2.0 版本功能开发之中(通过切换代码分支),这些操作由于 Git 的分散属性,都不需要通过网络来连接到*服务器进行,她甚至可以在飞机中完成这一切。当她完成所有工作,只需要向远程的代码库推送(Push)自己的修改即可。
Git 同时享有极好的社区支持和庞大的用户群体。你可以找到各种内容深入浅出的学习资料,包括书籍,教程,以及专题网站。甚至还有广播以及视频教程存在。
Git操作概览
提交修改,创建分支,融合分支,以及求取差分。
所有的文件内容,文件相互关系,以及文件目录结构,版本,标签以及修改,都经过加密哈希校验算法(SHA1)的保护
所有对分支和标签的操作也都会被保存到修改历史中
Git安装
Windows 用户: 官网下载
对于 Windows 用户,安装后如果希望在全局的 cmd 中使用 git,需要把 git.exe 加入 PATH 环境变量中,或在 Git Bash 中使用 Git。
安装完成后,在开始菜单里找到"Git"->"Git Bash",蹦出一个类似命令行窗口的东西,就说明Git安装成功!
安装完成后,还需要最后一步设置,在命令行输入:
$ git config --global user.name "xxx"
$ git config --global user.email "xxx@qq.com"
因为Git是分布式版本控制系统,所以,每个机器都必须自报家门:你的名字和Email地址。
注意:git config --global 参数,有了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置
Git操作
对本地仓库进行操作:
创建仓库
cd 进要创建仓库的目录(cd+拖拽目录)——》mkdir 仓库名——cd 库名——pwd 查看当前路径。路径用/,不是win的\。
将该仓库变成git可以管理的仓库,初始化仓库:git init 仓库里会多处一个.git目录,这个目录是Git来跟踪管理版本的
git status 来查看你的 git 仓库状态
仓库还没有提交过文件。
提交新文件到仓库
所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词"Linux",在第8行删了一个单词"Windows"。
而图片、视频这些二进制文件,没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道。
因为文本是有编码的,比如中文有常用的GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。不要使用记事本,保存的utf-8格式有问题。使用Notepad++,把默认编码设置为UTF-8 without BOM即可。
- 在仓库任意目录下创建一个文本
-
通知git:
1、将文本添加到仓库(实际是把文件修改添加到暂存区)——git add readme.txt。//要在文件所在的目录执行add,不然会报没有符合的文件
2、将文本提交到仓库(实际上就是把暂存区的所有内容提交到当前分支)——git commit -m "wrote a readme file"
git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
git commit命令执行成功后会告诉你,1 file changed:1个文件被改动(我们新添加的readme.txt文件);2 insertions:插入了两行内容(readme.txt有两行内容)。
你可以多次add不同的文件,commit可以一次提交很多文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
对文件进行操作:
修改文件后保存,在文件目录查看修改:
Git status 显示modified了one.html文件,并且仓库的当前状态是修改部分还没有准备提交。
提交修改:
提交前查看具体被修改了什么,git diff 文件名:
通知git准备提交修改:执行Git add 被修改的文件名。
Git status 查看此时仓库状态,将要被提交的修改包括one.html:
提交修改:git commit,提交后查看仓库状态,没有要提交的,目前工作目录是干净的
Ps:
要随时掌握工作区的状态,使用git status命令。
如果git status告诉你有文件被修改过,用git diff可以查看修改内容。
撤销修改:
git checkout -- readme.txt
情况一:撤销工作区的修改(还没有使用add命令添加到暂存区)
这个时候,工作区的文件和版本库一模一样的状态。git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以"一键还原"。
情况二:已经添加到暂存区后,又作了修改
撤销修改就回到添加到暂存区时的状态。
撤销暂存区的修改:
git reset HEAD <file>可以把暂存区的修改撤销掉(unstage),使暂存区清空
删除文件:
删除工作区的文件:rm 文件名
删除版本库里的文件:
现在有两个选择:
一是确实要从版本库中删除该文件,那就用命令git rm 文件名删掉,并且git commit。
另一种情况是删错了,因为版本库里还有,所以可以把误删的文件恢复到最新版本,git checkout -- test.txt
git checkout其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以"一键还原"。
查看提交日志:
Git log 查看提交日志(显示的是commit -m时的说明),显示从最近到最远的提交日志,有提交人,日期和备注
如果嫌输出信息太多,使用git log --pretty=oneline
日志前面黄色的部分为commit id(版本号)。使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线。在工作目录输入 gitk命令
定制要显示的记录格式:
git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 11 months ago : changed the version number
085bb3b - Scott Chacon, 11 months ago : removed unnecessary test code
a11bef0 - Scott Chacon, 11 months ago : first commit
常用的格式占位符写法及其代表的意义。
选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 -date= 选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s 提交说明
你一定奇怪_作者(author)_和_提交者(committer)_之间究竟有何差别,其实作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。所以,当你为某个项目发布补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者
Git log 根据条件筛选输出:
git log -p -2:-p 选项展开显示每次提交的内容差异,用 -2 则仅显示最近的两次更新:
另外还有按照时间作限制的选项,比如 --since 和 --until。下面的命令列出所有最近两周内的提交:git log --since=2.weeks
还可以给出若干搜索条件,列出符合的提交。用 --author 选项显示指定作者的提交,用 --grep 选项搜索提交说明中的关键字。(请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用--all-match 选项。)
如果只关心某些文件或者目录的历史提交,可以在 git log 选项的最后指定它们的路径。因为是放在最后位置上的选项,所以用两个短划线(--)隔开之前的选项和后面限定的路径名。
表 2-3 还列出了其他常用的类似选项。
选项 说明
-(n) 仅显示最近的 n 条提交
--since, --after 仅显示指定时间之后的提交。
--until, --before 仅显示指定时间之前的提交。
--author 仅显示指定作者相关的提交。
--committer 仅显示指定提交者相关的提交。
回退到指定版本:
在Git中,用HEAD表示当前版本,也就是最新的提交,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
git reset --hard HEAD^ 回退到上一个版本,执行这个命令后,工作目录中的该文件就实时还原成上一个版本的内容了。此时使用git log查看,就看不到最后一次提交的那个日志了。
要是想变成其中某一个版本,就使用git reset --hard commit id。版本号没必要写全,前几位就可以了,Git会自动去找
要是关闭了bash,想要再到某个版本时,找不到版本号,可以使用git reflog,可以查看每次head变动时的命令
远程仓库
实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个"服务器"仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
好在这个世界上有个叫GitHub的神奇的网站,从名字就可以看出,这个网站就是提供Git仓库托管服务的,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。
由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,需要SSH Key识别出你推送的提交确实是你推送的,而不是别人冒充的, GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
两步走:
在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C "xxx@qq.com"
$ cd ~/.ssh
$ pwd
查看用户主目录的路径了
第2步:登陆GitHub,打开"Account settings","SSH Keys"页面:
然后,点"Add SSH Key",填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容
在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。
如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。
GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作。
创建远程仓库:
在右上角找到"Create a new repo"按钮,创建一个新的仓库
在Repository name填入本地仓库名,其他保持默认设置,点击"Create repository"按钮,就成功地创建了一个新的Git仓库
在GitHub上的仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
关联与推送本地库内容:
关联:创建了一个远程库之后,会提示你几种操作:
与远程库关联,在本地的仓库下运行命令:git remote add origin https://github.com/xxx/xxx.git
远程库的名字就是origin,这是Git默认的叫法,也可以改成别的,但是origin这个名字一看就知道是远程库
下一步,就可以把本地库的所有内容推送到远程库上:
$ git push -u origin master
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和已经被提交到本地库里的内容一模一样,从现在起,只要本地作了提交,再次打开bash,然后就可以通过命令,可以看见"to https://......:
$ git push origin master。
克隆远程库:
$ git clone git@github.com:michaelliao/gitskills.git
GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
分支:
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
新创建的版本库,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说是指向master,master才是指向提交点的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
创建、切换和删除分支详细的工作过程参考:
创建并切换到分支:
git checkout -b dev
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
用git branch命令查看当前分支:
$ git branch
* dev
master
git branch命令会列出所有分支,当前分支前面会标一个*号。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev(分支指针),指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上
,从这个提交点开始,有了分支(图1),对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变(图2)
图1:
图2:
对dev分支的内容做出修改并提交后,切回到master分支,此时查看工作区被修改的文件,发现修改的内容不见了,再切回到dev分支,查看工作区被修改的文件,修改的内容又有了,所以随着分支的切换,工作区的内容变化的。文件还是只有一份。
合并分支:
git merge dev 用于合并指定分支到当前分支
把dev分支的工作成果合并到master分支上,先设定当前分支为master分支,再执行命令合并命令
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
注意到上面的Fast-forward信息,这次合并是"快进模式",也就是直接把master指向dev的当前提交。
当然,也不是每次合并都能Fast-forward,后面会讲其他方式的合并。
合并原理:我们在dev上的工作完成了,就可以把dev合并到master上。Git直接把master指向dev的当前提交,就完成了合并:
删除分支:
git branch -d dev
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
解决冲突:
把Git合并失败的文件手动编辑为我们希望的内容,再提交。
Your branch is ahead of 'origin/master' by 1 commit. 当前master分支比远程的master分支要超前1个提交。
合并时,提醒有冲突,打开文件可以看见以=====为分界,上面时master上的内容,下面的分支的内容。手动编辑后,再提交
工作区,版本库,暂存区
工作区:.git文件所在目录
版本库:.git文件
暂存区:Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。
可以简单理解为,需要提交的文件和修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
每次修改,如果不用git add到暂存区,那就不会加入到commit中。
几个重要的提示:
Changes to be committed: 表示暂存区有东西
Changes not staged for commit::表示工作区有修改,暂存区干净的
Git diff 默认比较的是比较工作区与暂存区