Git 与Github
1、简单介绍
很多人都知道,Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。
Linus虽然创建了Linux,但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公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的。实际情况是这样的:
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。
Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。
历史就是这么偶然,如果不是当年BitMover公司威胁Linux社区,可能现在我们就没有免费而超级好用的Git了。
2、基本概念
安装git
yum install git #centos系统默认安装好的
初始化版本库
git init #会生成一个.git的隐藏目录
- 重点理解:工作区、暂存区和master分支
工作区:用于平时的开发,编辑之用,在你创建的仓库目录下,就是工作区
暂存区:用来暂时存放准备提交到仓库的文档的地方,在.git目录下
master分区:真正用来存放和发布已经完成代码的地方
关系图
-
最初文件在工作区
-
git add readme.txt 后,文件被添加到暂存区,此时工作区的文件和暂存区的文件一致。
-
git commit -m "new file readme.txt" 后,在暂存区的所有文件和目录都将后被提交(移动)到分支 master。
3、一个文件被提交到版本库的基本流程
- 初始化一个仓库
mkdir my_project #建立一个目录
cd my_project #进入这个目录
git init #在当前目录初始化一个仓库
- 提交版本
touch/mkdir… #在工作区创建/修改文件
git add file #将文件添加到暂存区
git commit -m "描述" #将暂存区的内容提交到master分支
可以多次添加,一次提交
- 暂存区是Git非常重要的概念,弄明白了暂存区,就弄明白了Git的很多操作到底干了什么。
4、更多git操作
时空穿越
git 支持版本的回滚操作,并且,你可以在之前任何一个版本和当前最新有效提交后的版本之间来回切换。
将代码提交之后,突然有不要提交的代码了,这种情况的话,就要用到回滚了
-
git log
命令可以查看提交的版本历史
git log 常用参数
某一个人的提交记录:
git log --author=name
一个压缩后的每一条提交记录只占一行的输出:
git log --pretty=oneline
或者你想通过 ASCII 艺术的树形结构来展示所有的分支, 每个分支都标示了他的名字和标签:
git log --graph --oneline --decorate --all
看看哪些文件改变了:
git log --name-status
更多的信息,参考:
git log --help
最前面的一串字符,例如
da197f...7955
是commit id(版本号),是一个SHA1计算出来的一个非常大的数字,用十六进制表示。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线
-
开始版本回退
命令用法:
git reset --hard 版本号
版本号的表示形式:
1. 可以是十六进制的形式
2. 也可以是 Git 内部变量的形式。 上一个版本就是HEAD^
上上一个版本就是HEAD^^
100 个版本写成 HEAD~100
-
回到未来
git reflog
命令会记录每一次导致版本变化的命令以及涉及到的版本号
bash-3.2$ git reflog
a34d237 (HEAD -> master) HEAD@{0}: reset: moving to HEAD^
da197f4 HEAD@{1}: commit: add GLP for readme.txt
a34d237 (HEAD -> master) HEAD@{2}: commit: add distributed
63e4ecd HEAD@{3}: commit (initial): crete a readme file
(END)
利用命令进行版本切换
bash-3.2$ git reset --hard da197f4
-
深入理解 add 和 commit
再次强调一下,commit 提交的是暂存区的所有文件
,而不是工作区的被修改的文件;每次修改,如果不add到暂存区,那就不会加入到commit中。
-
改变未来(撤销修改)
你在工作区,对 readme.txt 文件添加了一行新的内容 : "这一行内容我不能添加",当你想要撤销掉这行内容时有两种方法:
1、一种是你记得,在这之前你记得你的所有修改。这里我是记得的,因为我只添加了一行内容而已,这样的情况下,你重新编辑这个文件,删除你不要的这一行即可。
2、另一种是,修改的太多了,一片混乱,使你无法继续进行下去。你只想会到没有修改之前,就是恢复到最近一次提交到 master 之后的状态。那就需要用git checkout -- readme.txt
命令
git checkout -- readme.txt
意思就是,readme.txt
文件在被添加到暂存区之前,对在工作区对此文件的修改全部撤销
(2)这里有两种情况:
1、一种是,在工作区对 readme.txt 进行修改之后,还没有被放到暂存区,暂存区为空;现在,撤销修改就回到和版本库一模一样的状态;
2、一种是,在工作区对 readme.txt 进行修改之后,并且已经添加到暂存区了,接着又在工作区对文件作了修改。此时,现在 readme.txt 的状态是:在工作区是一种最新修改后的状态
在暂存区是另一种 add 后的状态
在 master 是版本库最新的状态
此时,撤销修改,工作区的文件就会和暂存区文件的状态保持一致,master 的文件状态不变。
bash-3.2$ git checkout -- study/readme.txt
- 注意:
git checkout -- readme.txt
只能使工作区和缓存区内容保持一致
想要把缓存区的内容也去掉,只能回退版本再统一工作区和缓存区。
这样工作区就回到了master的状态
撤销总结
1、当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file。
2、当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了 1,第二步按 1 操作。
3、已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
-
自毁前程(删除)
在Git中,删除也是一个修改操作,分两种情况:
1、一种是,在工作区删除的文件,已经被添加到了暂存区,但是没有提交。
当你在工作区删除一个你认为没用的文件时,但是这个文件被已经添加了暂存区,这样 Git 会知道你删除了这个文件,因为此时,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了
- 此时,你有两种选择:
1.真的要删除这个文件
可以用git rm
删除在暂存区的文件
bash-3.2$ git rm useless.txt
rm 'useless.txt'
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
2.删错了,需要把文件恢复到工作区
利用git checkout -- file
把工作区和缓存区统一
2、另一种是,在工作区删除的文件,添加到了暂存区,并且提交了。
- 此时,你也有两种选择:
1.真的要删除这个文件
rm useless.txt //删除这个文件
git rm useless.txt //从版本库删除这个文件
git commit -m "del file useless.txt" //并且提交
此时,文件就从版本库中被删除了,一般情况下它再也会不到你身边了
2.删错了,需要把文件恢复到工作区
bash-3.2$ ls useless.txt
ls: useless.txt: No such file or directory
bash-3.2$ git checkout -- useless.txt
bash-3.2$ ls useless.txt
useless.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
总结
git checkout其实是用<mark style="box-sizing: border-box;">版本库</mark>里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
版本库: 包括 暂存区 和 分支
5、分支管理
分支就像科幻电影里的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
-
创建与合并分支
在版本回退里,你已经知道,每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在 Git 里,这个分支叫主分支,即master 分支。HEAD 严格来说不是指向提交,而是指向 master, master 才是指向提交的,所以HEAD 指向的就是当前分支。
一开始的时候,master 分支是一条线,Git 用 master 指向最新的提交点,再用HEAD 指向 master,就能确定当前分支,以及当前分支的提交点:
每次提交,master 分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长
当我们创建新的分支,例如 bac 时,Git 会新建一个指针叫 bac,指向 master 相同的提交点,再把HEAD指向 bac,就表示当前分支在 bac 上:
- Git创建一个分支很快,因为此时,只是增加一个 bac 指针,然后改改 HEAD 的指向即可,工作区的文件都没有任何变化!
从现在开始,对工作区的修改和提交就是针对 bac 分支了,比如新提交一次后,bac 指针往前移动一步,而master 指针不变,HEAD 指针同样不变:
假如我们在dev上的工作完成了,就可以把 bac 合并到 master 上。Git 怎么合并呢?很简单,先切换到 master 分支,此时 HEAD 指针就会指向 master 指针,之后就是直接把master 指向 bac 的当前提交点,就完成了合并:
- 所以Git合并分支也很快!就改改指针,工作区内容也不需要变!
合并完分支后,你觉得 bac 分支没什么用了,甚至可以删除 bac 分支。删除 bac 分支就是把 bac 指针给删掉,删掉后,我们就剩下了一条 master 分支:
实战
1、创建分支 bac
bash-3.2$ git branch bac
2、切换到分支 bac
bash-3.2$ git checkout bac
Switched to branch 'bac'
3、创建并切换分支
上面的两条命令可以合并为一条
bash-3.2$ git checkout -b bac
4、查看分支
bash-3.2$ git branch
* bac
master
// 星号代表当前所在的分支
5、在分支 bac 上修改文件,并创建一个新文件 bac_new.txt,最后正常添加、提交。
bash-3.2$ echo "changes on the branch of bac" >> study/readme.txt
bash-3.2$ touch bac_new.txt
bash-3.2$ git add .
bash-3.2$ git commit -m "added a new line in readme.txt,create a file bac_new.txt"
[bac 096a515] added a new line in readme.txt,create a file bac_new.txt
2 files changed, 1 insertion(+)
create mode 100644 bac_new.txt
bash-3.2$ tail -3 study/readme.txt
Git 管理的是修改
Git 管理的不是文件
changes on the branch of bac
bash-3.2$ ls
bac_new.txt newdir study
// 此时会被提交到 bac 分支,工作区当然也是属于 bac 分支的
6、切换到 master 分支,并观察文件的变化
bash-3.2$ git checkout master
Switched to branch 'master'
bash-3.2$ tail -3 study/readme.txt
Git is free software distributed under the GPL.
Git 管理的是修改
Git 管理的不是文件
bash-3.2$ ls
newdir study
- 切换到 master 分支后, HEAD 指针也就会指向 master 所指向的提交点,工作区也就属于 master,自然,你看不到在 bac 分支对文件做的任何修改
7、把分支 bac 合并到 master分支
bash-3.2$ git branch # 确定一下你现在所在的分支是 mster
bac
* master
bash-3.2$ git merge bac # 把 bac 分支合并到 master
Updating 6b0e1ca..096a515
Fast-forward
bac_new.txt | 0
study/readme.txt | 1 +
2 files changed, 1 insertion(+)
create mode 100644 bac_new.txt
bash-3.2$ ls # 确认工作区的文件
bac_new.txt newdir study
-
把 bac 分支合并到 master 分支后的文件变化:
8、合并完成后,删除分支 bac,并查看分支
bash-3.2$ git branch -d bac
Deleted branch bac (was 096a515).
bash-3.2$ git branch
* master
bash-3.2$
- 删除分支 bac 就变成下图的样子:
总结
* 查看分支:git branch
* 创建分支:git branch <name>
* 切换分支:git checkout <name>
* 创建+切换分支:git checkout -b <name>
* 合并某分支到当前分支:git merge <name>
* 删除分支:git branch -d <name>
6、HEAD 指针
HEAD 指向哪个版本,当前就是哪个版本;当你来回切换版本的时候,Git 只是把 HEAD 指向你要切换的版本,顺便把工作区的文件更新一下,见下图:
-
处于最新提交后的指针指向:
-
版本回退后的指针指向:
7、标签管理
标签是来为某个版本取个标签名
1、创建标签
git tag tagname #为最新版本打标签
注意: 标签名是为最近提交的一次打的,代表了最近提交的那个版本
git tag #查看所有标签
git show tagname #查看标签信息
2、删除标签
git tag -d tagname #删除某个标签
3、小结
git tag -a tagname -m 'comment' #添加标签并且指定描述信息
8、远程仓库
设置全局用户名和邮箱
git config --global user.name "anan"
git config --global user.email "283728@qq.com"
克隆远程仓库到本地
本地没有仓库的时候,创建一个本地目录,并于远程的GitHub仓库建立联系
git clone git@gitlab.com:alice/test.git #将远程仓库克隆到本地
cd test
touch file
git add .
git commit -m "new file"
git push -u origin master #将这个仓库的master分支上传到远程仓库
注意: 只有第一次上传到远程需要-u
在本地目录创建仓库,建立联系,上传远程
cd floder
git init #将目录变成仓库
git remote add git@gitlab.com:alice/test.git #与远程仓库建立联系
git add .
git commit -m ''
git push -u origin master
将本地已有的仓库上传到远程
cd floder
git remote rename origin old-origin
git remote add origin git@gitlab.com:alice/test.git
git push -u origin --all
git push -u origin --tags