Git同生活中的许多伟大事件一样,它诞生于一个极富纷争大举创新的年代。
很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件。当然在嵌入式,移动端,机器人,仪器仪表,人工智能等方面也无处不在。因此Linus是非常牛的,但是再牛也不可能完全一个人实现一个性能可以和微软对抗的操作系统,据说世界上最难的三个工程:一个是从一个硅片开始做一个CPU,一个从第一行代码开始做一个操作系统,一个是六七十年代的阿波罗登月计划。
所以一个完备的操作系统需要多人合作来完成,Linux的壮大是靠全世界热心的志愿者参与的,这么多人在世界各地为Linux编写代码,那Linux的代码是如何管理的呢?
事实是,在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!
你也许会想,为什么Linus不把Linux代码放到版本控制系统里呢?不是有CVS、SVN这些免费的版本控制系统吗?因为Linus坚定地反对CVS和SVN,这些集中式的版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比CVS、SVN好用,但那是付费的,和Linux的开源精神不符。
不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。
Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。
开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds )不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。他们对新的系统制订了若干目标:
~速度
~简单的设计
~对非线性开发模式的强力支持(允许上千个并行开发的分支)
~完全分布式
~有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
最后实际情况是这样的:Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以感受一下。
工作区(Working Directory):就是你在电脑里能看到的目录,比如我的zhaozw文件夹就是一个工作区。
版本库(Repository):工作区有一个隐藏目录“.git”,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
对于任何一个文件,在 Git 内都只有三种状态:
- 已提交(committed)已提交表示该文件已经被安全地保存在本地数据库中了;
- 已修改(modified)已修改表示修改了某个文件,但还没有提交保存;
- 已暂存(staged)。已暂存表示把已修改的文件放在下次提交时要保存。
什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。
怎么分布呢?
最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
CVS及SVN都是集中式的版本控制系统,而Git是分布式版本控制系统,集中式和分布式版本控制系统有什么区别呢?
集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。
那分布式版本控制系统与集中式版本控制系统有何不同呢?
首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改 了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。
sudo apt-get install git
wpr@wpr:~$ git config --global user.name
wpr@wpr:~$ git config --global user.name "wangpeirong"
用于在查询历史的时候文件显示由谁修改过。
查看信息可以采用如下操作:
wpr@wpr:~$ git config --global user.email
1005361108@qq.com
wpr@wpr:~$ git config --global user.name
wangpeirong
wpr@wpr:~$ git init
初始化空的 Git 仓库于 /home/wpr/.git/
创建成功后可以输入
ls -a
查看隐藏的文件.git
wpr@wpr:~$ git add test.py
wpr@wpr:~$ git commit -m "write test.py"
[master (根提交) 1dada7f] write test.py
1 file changed, 1 insertion(+)
create mode 100644 test.py
-m后面是注释内容,说明我们具体修改了什么。
用命令git diff可以用来查看修改了什么内容。
wpr@wpr:~$ git diff
diff --git a/test.py b/test.py
index 2d385da..89d888f 100644
--- a/test.py
+++ b/test.py
@@ -1,4 +1,4 @@
a = 100
-print(a)
+print(a+1)
print('hello world')
git status
wpr@wpr:~$ git status
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
修改: test.py
运行git status,提示我们已经被修改过了,但是没有提交。
再次提交后运行查看结果:
wpr@wpr:~$ git commit -m "V3"
[master 0848706] V3
1 file changed, 1 insertion(+), 1 deletion(-)
wpr@wpr:~$ git status
位于分支 master
提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
运行git log
wpr@wpr:~$ git log
commit 08487069f2067d7b2f99d45086c4b024be47a65e
Author: wangpeirong <1005361108@qq.com>
Date: Wed Aug 1 20:55:24 2018 +0800
V3
commit a2282b4f9ce1b9ce4b0e125220eeac6979145fae
Author: wangpeirong <1005361108@qq.com>
Date: Wed Aug 1 20:49:24 2018 +0800
v2
commit 1dada7f2f6472100970a93a65259c635758d3c97
Author: wangpeirong <1005361108@qq.com>
Date: Wed Aug 1 20:46:17 2018 +0800
write test.py
前面的字符是用16进制的数表示版本号。
这样看清来有点乱,我们用命令git log –pretty=oneline可以减少输出信息。
wpr@wpr:~$ git log --pretty=oneline
08487069f2067d7b2f99d45086c4b024be47a65e V3
a2282b4f9ce1b9ce4b0e125220eeac6979145fae v2
1dada7f2f6472100970a93a65259c635758d3c97 write test.py
为了防止关机后丢失或者要退回很久以前的版本,我们需要通过git reflog更新日志
wpr@wpr:~$ git reflog
0848706 HEAD@{0}: reset: moving to 0848706
1dada7f HEAD@{1}: reset: moving to HEAD^^
0848706 HEAD@{2}: commit: V3
a2282b4 HEAD@{3}: commit: v2
1dada7f HEAD@{4}: commit (initial): write test.py
从上到下显示了我们过去使用过的版本。
第一列的数字是版本序号,第二列是我们使用过的版本顺序号,reset代表我们只是重新选择了版本,commit代表我们修改并提交了一个新的版本。最后一列则是代表了我们对应的操作。
查看当前版本:
wpr@wpr:~$ git reset --hard HEAD
HEAD 现在位于 0848706 V3
查看上一个版本:
wpr@wpr:~$ git reset --hard HEAD^^
HEAD 现在位于 1dada7f write test.py
HEAD表示当前版本,HEAD^表示上一个版本,同理HEAD^^表示上上个版本。。。。
不过这样太麻烦。所以如果要往上回退100个版本,则写成 HEAD~100
也可以通过git log查看id直接指定回到哪:(id号可只取前两位,为了避免相同,可以多取几位)
wpr@wpr:~$ git reset --hard 0848706
HEAD 现在位于 0848706 V3
git reset --hard id号
另外我们还可以直接指定回到哪里。
git reset –hard id号
wpr@wpr:~$ git reset --hard a228
HEAD 现在位于 a2282b4 v2
git add是把文件的某次修改放入暂存区,所以修改以后必须先add再提交才生效。
git diff HEAD – xxx
index 2d385da..24728ff 100644
--- a/test.py
+++ b/test.py
@@ -1,3 +1,4 @@
+#这是一个软件版本测试
a = 100
print(a)
print('hello world')
撤销修改:
只是修改,并没有添加:
所以可用git checkout – file来恢复修改之前的模样 (–后面有个空格) ,其实是用版本库里的版本替换工作区的版本(一键还原)
wpr@wpr:~$ git checkout test.py
wpr@wpr:~$ cat test.py
a = 100
print(a)
print('hello world')
直接rm
通过git status 会看到git告诉你删除了哪个文件。
wpr@wpr:~$ rm test.py
wpr@wpr:~$ git status
位于分支 master
尚未暂存以备提交的变更:
(使用 "git add/rm <文件>..." 更新要提交的内容)
(使用 "git checkout -- <文件>..." 丢弃工作区的改动)
删除: test.py
用rm来删除文件,仅仅是删除了物理文件,没有将其从git的记录中剔除。
用命令git checkout – file找回
wpr@wpr:~$ git checkout -- test.py
wpr@wpr:~$ ls
catkin2 catkin_ws Desktop Download examples.desktop test.py
但是git commit -m ” “提交的时候,并不会将删除操作提交上去。
wpr@wpr:~$ git reflog
e8300a0 HEAD@{0}: commit: 修改
a2282b4 HEAD@{1}: reset: moving to a228
0848706 HEAD@{2}: reset: moving to 0848706
1dada7f HEAD@{3}: reset: moving to HEAD^^
0848706 HEAD@{4}: commit: V3
a2282b4 HEAD@{5}: commit: v2
1dada7f HEAD@{6}: commit (initial): write test.py
所以如果想要提交删除操作,让文件在版本中删除,必须要用-am提交
wpr@wpr:~$ rm test.py
wpr@wpr:~$ git commit -am "rm test.py"
[master 45e9f10] rm test.py
1 file changed, 5 deletions(-)
delete mode 100644 test.py
提交之后我们就不能使用git checkout来恢复了,因为当前head为空。需要使用git reset+id序号来恢复。
wpr@wpr:~$ git checkout -- test.py
error: pathspec 'test.py' did not match any file(s) known to git.
wpr@wpr:~$ git reset --hard 0848
HEAD 现在位于 0848706 V3
sudo adduser git
后面的就默认设置即可
如果需要让此用户有root权限,执行命令:
root@ubuntu:~# sudo vim /etc/sudoers
修改文件如下:
# User privilege specification
root ALL=(ALL) ALL
username ALL=(ALL) ALL
保存退出,username用户就拥有了root权限。
切换用户
切换用户的命令
su git
在git用户下,建立存储代码的仓库目录。
sudo mkdir /home/git/repositories
更改目录归属者,设置归属者为git用户
sudo chown git:git /home/git/repositories
设置该目录的权限。
sudo chmod 755 /home/git/repositories
cd /home/git/repositories
初始化一个仓库名称为TestGit
git@wpr:~/repositories$ git init --bare TestGit.git
初始化空的 Git 仓库于 /home/git/repositories/TestGit.git/
pi@raspberrypi:~/Desktop/LD_current $ git clone git@192.168.1.104:/home/git/repositories/TestGit.git
Cloning into 'TestGit'...
The authenticity of host '192.168.1.104 (192.168.1.104)' can't be established.
ECDSA key fingerprint is 62:80:d5:5c:74:f4:de:14:54:5d:7a:d1:37:f4:b0:e3.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.1.104' (ECDSA) to the list of known hosts.
[email protected]'s password:
warning: You appear to have cloned an empty repository.
Checking connectivity... done.
pi@raspberrypi:~/Desktop/LD_current $ ls
bb.bmp happycat.ppm SSD1306.pyc THU.bmp
constant_current.py SSD1306.py TestGit
前面搭建好了远程git服务器,那么就需要我们管理远程仓库。
git远程仓库并不是必须的,如果只是一个人作为自由工作者,这个时候完全可以建立本地仓库进行代码的管理、但是一般建立一个方便彼此之间沟通,如果是要做某个开源项目,这个时候git是最好的选择。
远程仓库,不得不提的是github,github是一个web版本的版本仓库平台,主要是做开源项目的代码托管。【也可以建立私有项目,不开源、内部使用,需要花钱】
github是互联网上一个免费的git仓库,可以在上面搭建开源的项目,但是这个网站的服务器是在国外的,不是很稳定。有的时候由于被墙我们可能登录不上。
【github网址】 https://github.com/
注册一个GitHub账号,就可以免费获得Git远程仓库。
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以需要设置:
- 第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接 跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C "[email protected]"
这里邮箱改为自己的邮箱,然后一路回车。
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa和id_rsa.pub两个文件,这两个就是SSH Key的秘钥对。id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
wpr@wpr:~$ cd .ssh/
wpr@wpr:~/.ssh$ ls
id_rsa id_rsa.pub
首先我们在Github上建立一个空的仓库
然后在自己的电脑上添加这个仓库,输入的指令格式为 git remote add[shortname] [url]。
git remote add origin git@github.com:MRwangmaomao/LinuxTest.git
git remote 可以查看当前配置有哪些远程仓库,也可以加上-v选项,显示对应的克隆地址。
wpr@wpr:~$ git remote -v
origin git@github.com:MRwangmaomao/LinuxTest.git (fetch)
origin git@github.com:MRwangmaomao/LinuxTest.git (push)
再强调一次,分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝提交代码的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
从远程库克隆用git clone命令,现在,我们从GitHub上克隆一个本地库:
git clone git@github.com:vickyi/resume.git
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了,你也许还注意到,GitHub给出的地址不止一个,还可以用https://github.com/michaelliao/gitskills.git这样的地址。实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
git fetch remote-name
OR
git pull remote-name
命令从远程仓库抓取数据到本地。两者不同:
- git pull,相当于git fetch+git merge。
- git fetch命令会到远程仓库中拉取所有你本地仓库中还没有的数据。
运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟。有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。git pull命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。
git push origin master
例如:
wpr@wpr:~$ git push origin master
The authenticity of host 'github.com (13.250.177.223)' can't be established.
RSA key fingerprint is SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,13.250.177.223' (RSA) to the list of known hosts.
对象计数中: 12, 完成.
Delta compression using up to 4 threads.
压缩对象中: 100% (5/5), 完成.
写入对象中: 100% (12/12), 948 bytes | 0 bytes/s, 完成.
Total 12 (delta 0), reused 0 (delta 0)
To [email protected]:MRwangmaomao/LinuxTest.git
* [new branch] master -> master
git remote show [remote-name]
wpr@wpr:~$ git remote show origin
* 远程 origin
获取地址:git@github.com:MRwangmaomao/LinuxTest.git
推送地址:git@github.com:MRwangmaomao/LinuxTest.git
HEAD 分支:master
远程分支:
master 已跟踪
为 'git push' 配置的本地引用:
master 推送至 master (最新)
git remote rename
碰到远端仓库服务器迁移,或者原来的克隆镜像不再使用,又或者某个参与者不再贡献代码,那么需要移除对应的远端仓库,可以运行命令:
git remote rm
分支在实际中有什么用呢?
假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
创建于合并分支
查看分支:git branch
创建分支:git branch name
切换分支:git checkout name
创建+切换分支:git checkout -b name
合并某分支到当前分支:git merge name
删除分支:git branch -d name
添加分支并且查看,左边带有星号说明目前是使用的feature-A分支
wpr@wpr:~$ git checkout -b feature-A
切换到一个新分支 'feature-A'
wpr@wpr:~$ git branch
* feature-A
master
我们现在修改文件test1.py,然后提交。
再切换到master下,查看该文件发现没有被修改。
wpr@wpr:~$ nano test1
wpr@wpr:~$ nano test1.py
wpr@wpr:~$ git commit test1.py -m "feature 分支测试"
[feature-A da56fa0] feature 分支测试
1 file changed, 2 insertions(+)
wpr@wpr:~$ git checkout master
切换到分支 'master'
wpr@wpr:~$ cat test1.py
hello world
接下来我们假设feature-A已经实现完毕,想要将他合并到主干分支master中
使用git merge –no-ff feature-A指令
wpr@wpr:~$ git merge --no-ff feature-A
Merge made by the 'recursive' strategy.
test1.py | 2 ++
test2.py | 1 +
2 files changed, 3 insertions(+)
create mode 100644 test2.py
这样一来,feature-A分支的内容就合并到master中了。