创建版本库
什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
首先,选择一个合适的地方,创建一个空目录:
mkdir test
cd test
如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
第二步,通过git init
命令把这个目录变成Git可以管理的仓库:
git init
命令执行很快,到文件夹下查看会多出一个 .git
文件夹,默认为隐藏可使用ls -ah
命令查看,不要动这个文件夹下的东西,改乱了会把仓库破坏掉。
不一定非得在空目录下创建git仓库,有文件的目录也是可以创建的。
工作区和暂存区
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
工作区
工作区可以简单的理解为是git仓库目录下内容.-
暂存区
回顾一下, 在将内容提交到仓库时候, 需要先使用git add一下, 此时这个add其实是吧内容提交到stage暂存区, 在执行commit的使用才会将stage暂存区中的内容提交到工作区.
git add命令将文件提交到缓存区
git commit命令将文件提交到工作区 -
先在本目录创建一个文件
touch readme.txt 或 vim readme.txt //编写文件内容 111111
-
把文件添加到暂存区
$ git add `file_name`
-
把文件提交到工作区
$ git commit -m "m1" [master (root-commit) cb926e7] m1 1 file changed, 2 insertions(+) create mode 100644 readme.txt
简单解释一下git commit
命令,-m
后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
提示:每次修改,如果不add到暂存区,那就不会加入到commit中
为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
可以通过命令
git status
查看是否还有文件未被提交,他是用来查看状态的。
$ git status-
如果文件修改,输入命令
git diff
可以查看文件内容做了什么修改
$ git diff 'file_name'
小结:初始化一个Git仓库,使用
git init
命令。添加文件到Git仓库,分两步:
第一步,使用命令
git add <file_name>
,注意,可反复多次使用,添加多个文件;第二步,使用命令
git commit
,完成。查看仓库状态
git status
查看文件修改了那些内容
git diff file_name
版本回退
先把文件内容改一下,增加一行内容:222222,执行以下命令
$ git add readme.txt
$ git commit -m "添加内容11111"
我们已经对文件更改了两次,想要查看一下历史使用命令
$ git log
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上
--pretty=oneline
参数:
提示:像
db41fe81cf9b5
这样一长串的是版本号(commit id
)。
如果想要回退到哪个版本需要命令:
$ git reset --hard 版本号
提示: 版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
如果已经回退到以前的某个版本、但现在想要回到最新版、但用git log
查看版本号已经没有最新版的版本号, 可以用命令:
$ git reflog
这条命令用来记录你的每一次命令的。
小结:
HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。
撤消修改
当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令
$ git checkout --`file_name`
当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,命令:
$ git reset HEAD `file_name`
$ git checkout --`file_name`
删除文件
一般情况下 ,删除文件用命令:
$ rm readme.txt
这个时候git知道你删除了文件工作区个版本库不一致了,用 git status
命令查看,git会告诉你哪些文件被删除了
现在就有两个选择了,一种是用命令把文件删除:
$ git rm `file_name`
$ git commit -m "注释" //提交一下
另一种是删错了,因为版本库里还在,使用命令把误删的文件恢复到最新版本:
$ git checkout --`file_name`
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
远程仓库
Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。最初,只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。并且一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下(不过很少有这种需求)。实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。(可以自己用一台电脑搭建git环境作为远程仓库, 也可以使用GitHub)
GitHub(神奇的网站 - 远程仓库)
首先,创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "youremail@example.com"
(你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。)
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
- 登陆GitHub,打开“Account settings”,“SSH Keys”页面:
点“Add Key”,你就应该看到已经添加的Key:
说明:
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
添加远程库
-
要关联一个远程库,使用命令git remote add origin
git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步!
从远程库克隆
从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
先在GitHub创建一个仓库(创建默认的readme.md)
使用命令git clone克隆一个本地库(多人开发就每个人都clone一份):
$ git clone git@github.com:michaelliao/gitskills.git
说明:
GitHub给出的地址不止一个,还可以用
https:// github.com/michaelliao/gitskills.git这样的地址。
实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。
分支管理
-
分支是什么
分支就类似于一个平行的空间, 其相互之间是独立的, 在各个分支上工作是各不影响的
-
分支的作用
假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
有了分支,你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
创建与合并分支
在Git里只有一个主分支master
分支。HEAD
严格来说不是指向提交,而是指master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
再新创建一个dev
分支后,并且切换到这个分支,则新建的dev
指向提交,将HEAD
指向的是dev
分支,此后,提交内容,dev指针向前挪动,master
不变,将分支合并到主分支则是master
指正指向分支现在的位置,删除分支则是去掉dev
分支即可。
创建一个分支 dev:
$ git checkout -b dev
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$git checkout dev
然后,用git branch
命令查看当前分支:
$ git branch
* dev
master
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
然后,我们就可以在dev
分支上正常提交,比如对readme.txt做个修改,加上一行:
222222
然后提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev fec145a] branch test
1 file changed, 1 insertion(+)
现在,dev
分支的工作完成,我们就可以切换回master
分支:
$ git checkout master
Switched to branch 'master
切换回master
分支后,再查看一个readme.txt
文件,刚才添加的内容不见了!因为那个提交是在dev
分支上,而master分支此刻的提交点并没有变,现在,我们把dev
分支的工作成果合并到master
分支上:
$ git merge dev
Updating d17efd8..fec145a
Fast-forward readme.txt | 1 +
1 file changed, 1 insertion(+)
这时在去看readme.md文件, 会发现刚才修改的内容有出现了, 因为命令是master分支指向了dev的位置.
删除分支:
$ git branch -d dev
$ git branch
* master
提示:
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
解决冲突
如果在分支上做了修改, 并且在master
分支上也做了修改, 这时候合并就会造成冲突, 无法’快速合并’, 需要手动合并
以下代码是在两个分支上进行修改:
合并两个分支:
此时, 再去test.md文件看下内容, 会有以下内容
将以上内容手动修改成你需要的内容后再add commit, 并输入命令git log –graph来查看合并记录:
提示:
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
用git log --graph
命令可以看到分支合并图。
分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
合并分支时,加上–no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并。而fast forward
合并就看不出来曾经做过合并。
$ git merge --no-ff -m 'merge with no-ff' dev
Bug分支
在执行某些特殊任务时, 就像处理必须立即解决的Bug, 这时就行中断请求一样, 需要先保护一下现场, 处理结束后再恢复现场, git的Bug分支就类似如此, 在处理突发Bug时, 如果当前分支上的任务完成一半, 又不可以提交, 这时可以通过git stash命令把当前工作现场“储藏”起来.
处理Bug在需要的地方建立临时分支, 处理完Bug后合并到对应的分支, 再删掉临时分支即可.
处理完Bug后, 可以通过命令git stash list
来查看之前’储存’的现场, 工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法: - 一是用git stash apply
恢复,但是恢复后,stash内容并不删除,你需要用git stash drop
来删除 - 另一种方式是用git stash pop
,恢复的同时把stash内容也删了
Feature分支
在软件开发时, 会有新需求加入, 这时明智的做法是新建一个feature
分支, 在分支上开发这个功能进行测试, 这样才不会影响到master分支, 开发成功后再删除这个测试分支即可.
如果新功能需求突然取消, 此时这个分支还没有合并, 但必须销毁这个分支, 此时使用命令git branch -d xxxx
, 分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令git branch -D xxxx
。这时根据提示即可删除了.
多人协作
多人协作, 往往要用到远程仓库, 那么要如何解决与远程仓库之间的同步以及和伙伴代码合并的问题呢?
和远程仓库同步
当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
可是使用git remote查看远程仓库的信息
$ git remote
origin
或者,使用git remote -v查看详细的信息
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)
上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址。
推送分支
推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上, 比如下面这句就是吧本地的master分支推送到远程的master分支上:
$ git push origin master
但是并不是所有分支都需要推送, 像一些大家都需要用到的诸如: master(主分支), dev(开发分支)等这些需要推送, 像一些只是自己完成任务的独立分支, 类似:bug(修改bug的分支)就不用推送, 除非你的老板想知道你都修改了哪些bug.
抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改。
当你的协作伙伴从远程clone时, 他只能看到master分支, 如果想看到dev等其他分支, ,就必须创建远程origin的dev分支到本地,可以使用这个命令创建本地dev分支:
$ git checkout -b dev origin/dev
此后, 你的伙伴就可以在分支上做修改了, 并且会提交修改, 如果他提交后(此时有个问题: 你手里的版本已经不是最新版本了), 你同样做好了修改需要提交, 这时当你pull时, 你会发现报错, 因为你和你的伙伴可能修改了同样的文件, 这就和前面分支合并一样, 出现了冲突. 解决办法也很简单,Git已经提示我们,就是先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送(这时可能会git pull失败,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接).
创建分支链接
$ git branch --set-upstream dev origin/dev
然后pull到本地
$ git pull
这时, 在本地对伙伴修改的内容和自己修改的内容进行合并, 会有合并冲突, 需要手动解决, 解决后在推送.
注意:
多人协作的工作模式通常是这样:
- 可以试图用git push origin branch-name推送自己的修改;
- 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;
- 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用git push origin branch-name推送就能成功!
- 如果git pull提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch –set-upstream branch-name origin/branch-name。
说明:
- 查看远程库信息,使用
git remote -v
; - 本地新建的分支如果不推送到远程,对其他人就是不可见的;
- 从本地推送分支,使用
git push origin branch-name
,如果推送失败,先用git pull抓取远程的新提交; - 在本地创建和远程分支对应的分支,使用
git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致; - 建立本地分支和远程分支的关联,使用
git branch –set-upstream branch-name origin/branch-name
; - 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。
标签管理
标签(tag), 一个简单的理解就是它记录了一次commit id, tag一般建立在主分支上, 方便用于版本发布. 标签和分支类似, 一个指针指向某次提交, 只不过分支可以继续先前移动, 而标签不可移动.
创建标签
在创建标签前需要先切换到需要打标签的分支上, 然后使用命令 git tag v1.0
来创建标签. 默认标签是打在HEAD指向的地方(即最新提交), 如果想把标签创建在先前的提交上, 需要先知道commit id
(用命令git log –pretty oneline –abbrev-commit
), 然后用命令 git tag v0.9 6224937
在对应的提交上打上标签, 可以使用 git tag
查看所有标签, 使用命令git show v0.9
来查看标签详细信息.
还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字:
$ git tag -a v0.1 -m "version 0.1 released" 3628164
还可以通过-s用私钥签名一个标签:
$ git tag -s v0.2 -m "signed version 0.2 released" fec145a
注意:
签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错
操作标签
如果标签打错了,也可以删除:
$ git tag -d v0.1
如果要推送某个标签到远程,使用命令:
$ git push origin v1.0
或者,一次性推送全部尚未推送到远程的本地标签:
$ git push origin --tags
如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
$ git tag -d v0.9
然后,从远程删除。删除命令也是push,但是格式如下:
$ git push origin :refs/tags/v0.9
使用GitHub
在GitHub上,可以任意Fork开源仓库;
自己拥有Fork后的仓库的读写权限;
可以推送pull request给官方仓库来贡献代码
自定义Git
让Git显示颜色,会让命令输出看起来更醒目:
$ git config --global color.ui true
忽略特殊文件
在工作目录下创建.gitignore文件, 在这个文件中填写需要忽略的文件内容, 然后git add进去. 此后这个.gitignore就开始起作用了.
忽略文件的原则是:
- 忽略操作系统自动生成的文件,比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
- 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
如果有些时候,你想添加一个文件到Git,但发现添加不了,原因是这个文件被.gitignore忽略了.
如果你确实想添加该文件,可以用-f强制添加到Git:
$ git add -f App.class
或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore命令检查:
$ git check-ignore -v App.class
配置别名
通过以下命令将status 缩写成st
$ git config --global alias.st status
搭建Git服务器
搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简单的apt命令就可以完成安装。
假设你已经有sudo权限的用户账号,下面,正式开始安装。
第一步,安装git
$ sudo apt-get install git
第二步,创建一个git用户,用来运行git服务
$ sudo adduser git
第三步,创建证书登录
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub
文件,把所有公钥导入到/home/git/.ssh/authorized_keys
文件里,一行一个。
第四步,初始化Git仓库:
先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令:
$ sudo git init --bare sample.git
第五步,禁用shell登录:
出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd
文件完成。找到类似下面的一行:
git:x:1001:1001:,,,:/home/git:/bin/bash
改为:
git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell
这样,git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell
每次一登录就自动退出。
第六步,克隆远程仓库:
现在,可以通过git clone
命令克隆远程仓库了,在各自的电脑上运行:
$ git clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.
剩下的推送就简单了。
管理公钥
如果团队很小,把每个人的公钥收集起来放到服务器的/home/git/.ssh/authorized_keys
文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis
来管理公钥。
这里我们不介绍怎么玩Gitosis
了,几百号人的团队基本都在500强了,相信找个高水平的Linux管理员问题不大。
管理权限
有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。