Git-版本控制-创建远程仓库
版本控制介绍
版本控制系统之前如何维护文档的版本
集中式和分布式
集中式版本控制
- 集中式版本控制,版本库是集中存放在中央服务器的,用的都是自己的电脑干活,所以要先从中央服务器取得最新的版本,然后再开始干活,干完活了,再把自己的活推送给中央服务器。需要联网。
- 有代表性的软件:cvs、 svn
分布式版本控制
- 分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。为了多人协同开发时,同事之间交换版的方便,可以定一个中央服务器,只是用于多人之间的版本交换和比对。不需要联网
- 有代表性的软件: git
1 Git 使用
1.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了。
1.2 安装 Git
linux 安装
# yum install git
假如多人协作开发,应该在每个使用者的机器上安装 git
1.3 初始化版本库
什么是版本库呢?版本库又名仓库,英文名repository,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
1.3.1 创建版本库:
- 首先,选择一个合适的地方,创建一个空目录
$ git init # 初始化,把当前目录变为由 git 管理的版本库
Initialized empty Git repository in /Users/yanshunjun/Desktop/mygithub/.git/$ ls -a
. .. .git
可以看到在当前目录下会创建一个隐藏的文件夹 .git
轻易不用动它里面的任何文件,因为这个目录是 Git 来跟踪和管理版本库用的,假如你搞坏了,它就会给你点儿颜色看(Git 仓库就会被破坏)。
- 非空目录其实也是可以创建仓库的,不过不建议这么干。有必要吗!
1.3.2 版本库(Repository)
.git 目录就是 Git 的版本库
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支 master(通常称为主分支),以及指向 master 的一个指针叫HEAD
- 分支和指针后面再讲
1.4 工作区、暂存区和 master 分支
Git 和 SVN 不同之一,就是有 工作区、暂存区的概念
* 工作区: 用来平时的开发、编辑文件之用,在你创建的仓库目录下,就是工作区
* 暂存区: 用来暂时存放准备提交到仓库的文档的地方,在 .git 目录下。
* master 分支: 真正用来存放和发布已经完成的代码文件的地方,在 .git 目录下。
1.4.1 三者的关系位置见下图
-
最初文件在工作区
-
git add readme.txt 后,文件被添加到暂存区,此时工作区的文件和暂存区的文件一致。
-
git commit -m “new file readme.txt” 后,在暂存区的所有文件和目录都将后被提交(移动)到分支 master。
而此时,工作区的文件会和当前分支 master 的文件一致,而暂存区没有任何文件,是清洁的。
总结: 一个文件被提交到版本库的基本流程是:
1. 在你的工作区创建编写你的代码文件 readme.txt (当然也包括目录)
2. 用命令 git add readme.txt 将文件 readme.txt 放到暂存区,这个可以多次执行添加
3. 用命令 git commint -m "new file readme.txt" 将暂存区的所有文件和目录一起提交到 master
可以多次添加,一次提交
1.4.2 实操:
$ pwd
/Users/yanshunjun/Desktop/mygithub
$ mkdir study
$ cd study
$ vi readme.txt
$ cd ..
$ git add study # 我这里是把目录一起提交了
$ git commit -m "crete a readme file"
[master (root-commit) 63e4ecd] crete a readme file1 file changed, 2 insertions(+)create mode 100644 study/readme.txt
- 强调一下, git commit 的 -m 参数后面跟的是关于这次提交版本的描述信息。 这个参数不是必须的,但是强烈要求这么干,这样便于以后自己对版本回滚操作,协同开发时,也便于其他人员可以获取到每次提交改动的基本信息
暂存区是Git非常重要的概念,弄明白了暂存区,就弄明白了Git的很多操作到底干了什么。
1.5 时空穿越
git 支持版本的回滚操作,并且,你可以在之前任何一个版本和当前最新有效提交后的版本之间来回切换。
1.5.1 提交修改
之前,我们知道了如何创建一个新的文件 readmi.txt ,并提交到仓库;现在让我们来修改这个文件,内容变为这样:
Git is a distributed version control system.
Git is free software.
并且再创建一个新的空文件 test.txt 和空文件夹 newdir
bash-3.2$ pwd
/Users/yanshunjun/Desktop/mygithub
bash-3.2$ vi study/readme.txt
bash-3.2$ touch test.txt;mkidr newdir
- 观察一下仓库的最新变化和状态
bash-3.2$ git status
On branch master
Changes not staged for commit: # 未提交的更改(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txt # 已经修改的文件Untracked files: # 没有被添加过的文件(use "git add <file>..." to include in what will be committed)test.txtno changes added to commit (use "git add" and/or "git commit -a")
空的目录是不会被 Git 跟踪的,也没这个必要!
- 可以看看被修改过的文件,到底修改了哪些内容
bash-3.2$ git diff study/readme.txt
diff --git a/study/readme.txt b/study/readme.txt
index 46d49bf..9247db6 100644
--- a/study/readme.txt
+++ b/study/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system. # 修改前的内容
+Git is a distributed version control system. # 修改后的内容Git is free software.
(END)
- 之后一起放到暂存区
bash-3.2$ git add .
- . 会把当前目录下需要提交的所有文件和目录放到暂存区
提交前可以再次观察下仓库的状态
bash-3.2$ git status
On branch master
Changes to be committed: # 下面的文件将会被提交(use "git reset HEAD <file>..." to unstage)modified: study/readme.txtnew file: test.txt # 第一次被添加后的状态为 new file
- 提交到仓库
bash-3.2$ git commit -m "add distributed"
[master a34d237] add distributed2 files changed, 1 insertion(+), 1 deletion(-)create mode 100644 test.txt
- 再次观察仓库的状态
bash-3.2$ git status
On branch master
nothing to commit, working tree clean # 不需要提交,工作区是清洁的
1.5.2 回到过去(版本的回滚)
想象一种场景,你把你代码写好,提交到版本库,准备发布 v1.0。但此时,你的老板说要添加一个新的功能。于是你就在原来的代码文件的基础上修改添加,又再次提交到版本库。可老板这时候,又反悔了,说这个功能不能现在添加。那你是不是又要,再次修改呢?有了git 当然不用,此时你就需要版本回滚,滚到原来最初的版本。
让我们来模拟一下
- 首先把 readme.txt 文件的内容修改成下面的样子
Git is a distributed version control system.
Git is free software distributed under the GPL.
- 然后提交修改后的文件
bash-3.2$ vi study/readme.txt
bash-3.2$ git status
On branch master
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txtno changes added to commit (use "git add" and/or "git commit -a")
bash-3.2$ git add .
bash-3.2$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)modified: study/readme.txtbash-3.2$ git commit -m "add GLP for readme.txt"
[master da197f4] add GLP for readme.txt1 file changed, 1 insertion(+), 1 deletion(-)
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
- 验证最新提交后的版本
bash-3.2$ pwd
/Users/yanshunjun/Desktop/mygithub
bash-3.2$ cat study/readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
- 准备版本回退
在回退版本之前,我们先来回想一下 readme.txt 文件共修改了几次, 3 次对吧。但实际的开发中,有很多文件,一个文件里会有上千行代码,修改过的次数远远超出了你的智商能够承受的范围。怎么办? Git 早就为你想好了。
git log 命令可以查看提交的版本历史
commit da197f447342e65bbf37f5b2e609c71d52db7955 (HEAD -> master)
Author: sharkgit1976 <dockerhub@163.com>
Date: Sun Oct 15 10:04:23 2017 +0800add GLP for readme.txtcommit a34d2370138d520d1fabc1aa2acc2d234047a39a
Author: sharkgit1976 <dockerhub@163.com>
Date: Sat Oct 14 17:31:19 2017 +0800add distributedcommit 63e4ecd9409ff1679b8367c116a1c68f045af88d
Author: sharkgit1976 <dockerhub@163.com>
Date: Sat Oct 14 16:16:22 2017 +0800crete a readme file
git log 常用参数**
某一个人的提交记录:
git log --author=name一个压缩后的每一条提交记录只占一行的输出:
git log --pretty=oneline或者你想通过 ASCII 艺术的树形结构来展示所有的分支, 每个分支都标示了他的名字和标签:
git log --graph --oneline --decorate --all看看哪些文件改变了:
git log --name-status更多的信息,参考:
git log --help
所以加上参数 --pretty=oneline 会显示的更简洁
da197f447342e65bbf37f5b2e609c71d52db7955 (HEAD -> master) add GLP for readme.txt
a34d2370138d520d1fabc1aa2acc2d234047a39a add distributed
63e4ecd9409ff1679b8367c116a1c68f045af88d crete a readme file
(END)
最前面的一串字符 da197f…7955 是commit id(版本号),是一个SHA1计算出来的一个非常大的数字,用十六进制表示。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。
- 开始版本回退
开回退之前,起码需要知道当前的版本。 在Git中,用HEAD表示当前版本和分支,从上面的信息中我们现在处于分支 master 的 da197f4…7955 版本。下面我用命令 git reset 回退版本到上次修改前的版本,也就是 a34d2370138…a39a 。
命令用法:
git reset --hard 版本号
版本号的表示形式:
1. 可以是十六进制的形式
2. 也可以是 Git 内部变量的形式。 上一个版本就是HEAD^,上上一个版本就是HEAD^^,100 个版本写成 HEAD~100
十六进制的版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
bash-3.2$ git reset --hard HEAD^
HEAD is now at a34d237 add distributed
- 验证版本
bash-3.2$ cat study/readme.txt
Git is a distributed version control system.
Git is free software.
- 回到未来
再看看版本库日志
a34d2370138d520d1fabc1aa2acc2d234047a39a (HEAD -> master) add distributed
63e4ecd9409ff1679b8367c116a1c68f045af88d crete a readme file
(END)
可以看到最近一次修改后提交的版本 add GLP for readme.txt 不见了,此时,我们是不是回不到未来了呢?
要想回到未来(最后一次提交的版本),就必须知道那个版本 ID,但是现在没有了,怎么办?
放心 Git 又给想好了, 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)
这样我们现在又可以回到未来了
da197f4 就是我们要回去的版本 ID
bash-3.2$ git reset --hard da197f4
HEAD is now at da197f4 add GLP for readme.txt
bash-3.2$ cat study/readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
bash-3.2$ git log --pretty=oneline
da197f447342e65bbf37f5b2e609c71d52db7955 (HEAD -> master) add GLP for readme.txt
a34d2370138d520d1fabc1aa2acc2d234047a39a add distributed
63e4ecd9409ff1679b8367c116a1c68f045af88d crete a readme file
就这样,在 Git 的帮助下,你滚来滚去…
1.5.3 HEAD 指针
用过其他版本控制的软件的同学,可能会感觉 Git 的版本切换很快,就是因为有指针在起作用。
HEAD 指向哪个版本,当前就是哪个版本;当你来回切换版本的时候,Git 只是把 HEAD 指向你要切换的版本,顺便把工作区的文件更新一下,见下图:
-
处于最新提交后的指针指向:
-
版本回退后的指针指向:
1.5.4 深入理解 add 和 commit
为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
- Git 认为的修改是什么?比如:
1. 你新增了一行,这就是一个修改;
2. 删除了一行,也是一个修改;
3. 更改了某些字符,也是一个修改;
4. 删了一些又加了一些,也是一个修改;
5. 甚至创建一个新文件,也算一个修改。
下面的实验操作可以很好的说明 Git 管理是修改而不是文件。
- 首先在 readme.txt 新增一行内容 “Git 管理的是修改”
bash-3.2$ echo "Git 管理的是修改" >> study/readme.txt
- 添加到暂存区
bash-3.2$ git add study/readme.txt
- 再次修改 readme.txt 文件,再添加一行新的内容 “Git 管理的不是文件”
bash-3.2$ echo "Git 管理的不是文件" >> study/readme.txt
- 执行 commit 提交
bash-3.2$ git commit -m "Append two lines of content"
- 查看版本库状态
bash-3.2$ git status
On branch master
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txtno changes added to commit (use "git add" and/or "git commit -a")
我的天呐!怎么第二次的修改没有被提交?
来回顾一下刚才的操作
* 第一次修改 -> git add -> 第二次修改 -> git commit
这恰恰体现了 Git 管理的是修改,当你用 git add 命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
其实当你提交后,可以使用 git diff HEAD readme.txt 命令来查看工作区和版本库里面最新版本的区别
diff --git a/study/readme.txt b/study/readme.txt
index ea4f525..062fcc9 100644
--- a/study/readme.txt
+++ b/study/readme.txt
@@ -1,3 +1,4 @@Git is a distributed version control system.Git is free software distributed under the GPL.Git 管理的是修改
+Git 管理的不是文件
(END)
- 输出中+号绿色显示的就是没有被提交到版本库的修改或新增的内容
正确的方式是:
* 第一次修改 -> git add -> 第二次修改 -> git add -> git commit
正确操作后,再比对 readme.txt 文件,看是否不同,结果返回是空的,证明工作区的文件和版本库里面最新版本的文件是一致的。
总结:再次强调一下, commit 提交的是暂存区的所有文件,而不是工作区的被修改的文件;每次修改,如果不add到暂存区,那就不会加入到commit中。
1.5.5 改变未来(撤销修改)
想像一种场景,你在工作区,对 readme.txt 文件添加了一行新的内容 : “这一行内容我不能添加”,当你想要撤销掉这行内容时,你有两种方法:
-
一种是你记得,在这之前你记得你的所有修改。这里我是记得的,因为我只添加了一行内容而已,这样的情况下,你重新编辑这个文件,删除你不要的这一行即可。
-
另一种是,修改的太多了,一片混乱,使你无法继续进行下去。你只想会到没有修改之前,就是恢复到最近一次提交到 master 之后的状态。那就需要用 git checkout – readme.txt
命令 git checkout – readme.txt意思就是,readme.txt文件在被添加到暂存区之前,对在工作区对此文件的修改全部撤销
这里有两种情况:
- 一种是,在工作区对 readme.txt 进行修改之后,还没有被放到暂存区,暂存区为空;现在,撤销修改就回到和版本库一模一样的状态;
bash-3.2$ echo "这一行内容我不能添加" >> study/readme.txt
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加
bash-3.2$ git status
On branch master
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txtno changes added to commit (use "git add" and/or "git commit -a")
bash-3.2$ git checkout -- study/readme.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
bash-3.2$ tail -2 study/readme.txt
Git 管理的是修改
Git 管理的不是文件
-
一种是,在工作区对 readme.txt 进行修改之后,并且已经添加到暂存区了,接着又在工作区对文件作了修改。此时,现在 readme.txt 的状态是:
- 在工作区是一种最新修改后的状态
- 在暂存区是另一种 add 后的状态
- 在 master 是版本库最新的状态
此时,撤销修改,工作区的文件就会和暂存区文件的状态保持一致,master 的文件状态不变。
bash-3.2$ echo "这一行内容我不能添加,但是我把这个文件添加到了暂存区" >>study/readme.txt
bash-3.2$ tail -2 study/readme.txt # 第一次修改后的文件内容
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
bash-3.2$ git status
On branch master
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txtno changes added to commit (use "git add" and/or "git commit -a")
bash-3.2$ git add study/readme.txt # 添加到暂存区
bash-3.2$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)modified: study/readme.txtbash-3.2$ echo "这一行内这个文件添加到了暂存区,我又添加了此行" >> study/readme.txt
bash-3.2$ tail -2 study/readme.txt # 第二次修改文件后的内容
这一行内容我不能添加,但是我把这个文件添加到了暂存区
这一行内这个文件添加到了暂存区,我又添加了此行
bash-3.2$ git diff study/readme.txt # diff 一下,可以看到工作区和暂存区的文件的不一致
diff --git a/study/readme.txt b/study/readme.txt
index d348259..f303166 100644
--- a/study/readme.txt
+++ b/study/readme.txt
@@ -3,3 +3,4 @@ Git is free software distributed under the GPL.Git 管理的是修改Git 管理的不是文件这一行内容我不能添加,但是我把这个文件添加到了暂存区
+这一行内这个文件添加到了暂存区,我又添加了此行
(END)
- 观察版本库的状态
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)modified: study/readme.txtChanges not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txt
- 撤销修改,使工作区和暂存区文件的一致
bash-3.2$ git checkout -- study/readme.txt
- 对比一下工作区和暂存区的文件,结果是一致的
bash-3.2$ git diff study/readme.txt
(END)
- 再次观察版本库的状态
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)modified: study/readme.txt
- 验证文件内容
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
注意:git checkout – file命令中的–很重要,没有–,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
- 此时,你可能会想再执行一次 git checkout – study/readme.txt,让其回到和版本库一致。
bash-3.2$ git checkout -- study/readme.txt
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)modified: study/readme.txt
那你就错了,说明你还没理解上面黄色的话,再复习一遍:
命令 git checkout – readme.txt 意思就是,readme.txt文件在被添加到暂存区之前,对 在工作区对此文件的修改全部撤销
意思就是说只能对在工作区的修改进行全部撤销,上面的例子是,第一次修改后,你已经把修改添加到了暂存区, Git 就认为目前工作区的和暂存区的一致,工作区的状态被相对的看做为起始状态,没有什么要撤销的,只剩 commit 了。那么真的没有办法了吗?答案是:当然有办法,请继续看…
此时,想继续恢复到和版本库的一致可以用下面发方法:
你还记得怎么滚吗?
- 先看一下目前版本库的状态
bash-3.2$ git status study/readme.txt
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage) # 这里 Git 已经有了提示,清除暂存区的状态modified: study/readme.txt
- 回到最新的状态
bash-3.2$ git reset HEAD study/readme.txt
Unstaged changes after reset:
M study/readme.txt
- 此时再看一下状态,暂存区是清洁的,工作区有做了修改
bash-3.2$ git status study/readme.txt
On branch master
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: study/readme.txtno changes added to commit (use "git add" and/or "git commit -a")
- 此在清除工作区的修改,就有效了
bash-3.2$ tail -2 study/readme.txt
Git 管理的不是文件
这一行内容我不能添加,但是我把这个文件添加到了暂存区
bash-3.2$ git checkout -- study/readme.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
bash-3.2$
此时整个世界都清净了!
- 客官请留步
现在,假设你不但改错了,还从暂存区提交到了版本库,怎么办呢?还记得版本回退吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把错误修改的代码提交推送到远程版本库,你就真的惨了……
思路梳理
-
当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout – file。
-
当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了 1,第二步按 1 操作。
-
已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
1.5.6 删除操作
在Git中,删除也是一个修改操作
分两种情况,一种是,在工作区删除的文件,已经被添加到了暂存区,但是没有提交。
当你在工作区删除一个你认为没用的文件时,但是这个文件被已经添加了暂存区,这样 Git 会知道你删除了这个文件,因为此时,工作区和版本库就不一致了,git status命令会立刻告诉你哪些文件被删除了
bash-3.2$ touch useless.txt
bash-3.2$ git add useless.txt
bash-3.2$ rm useless.txt
remove useless.txt? y
bash-3.2$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: useless.txtChanges not staged for commit:(use "git add/rm <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)deleted: useless.txt
- 此时,你有两种选择:
- 真的要删除这个文件
可以用 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
- 删错了,需要把文件恢复到工作区
还记 git checkout – file 吗?
bash-3.2$ git checkout -- useless.txt
bash-3.2$ ls useless.txt
useless.txt
bash-3.2$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: useless.txt
git checkout – file 不单可以撤销工作区的修改,也可以撤销工作区的删除,因为之前提到过,删除在 Git 看了也是修改。
另一种是,在工作区删除的文件,添加到了暂存区,并且别提交了。
此时,你也有两种选择:
- 真的要删除这个文件
- 执行删除文件操作
bash-3.2$ rm useless.txt
remove useless.txt? y
bash-3.2$ git status
On branch master
Changes not staged for commit:(use "git add/rm <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)deleted: useless.txtno changes added to commit (use "git add" and/or "git commit -a")
- 从版本库删除这个文件
bash-3.2$ git rm useless.txt
rm 'useless.txt'
- 并且提交
bash-3.2$ git commit -m "del file useless.txt"
[master 6b0e1ca] del file useless.txt1 file changed, 0 insertions(+), 0 deletions(-)delete mode 100644 useless.txt
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
此时,文件就从版本库中被删除了,一般情况下它再也会不到你身边了
- 删错了,需要把文件恢复到工作区
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其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
版本库: 包括 暂存区 和 分支
1.6 我的兄弟们(分支管理)
分支就像科幻电影里的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
分支在实际中有什么用呢?在实际的开发过程中,不是你一个人在可发,都是一个团队,假设你负责一个数据库操作模块的开发,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,在这中间,代码会保存在你自己电脑上的工作区,存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
- Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
1.6.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 分支:
实战
- 创建分支 bac
bash-3.2$ git branch bac
- 切换到分支 bac
bash-3.2$ git checkout bac
Switched to branch 'bac'
- 创建并切换分支
- 上面的两条命令可以合并为一条
bash-3.2$ git checkout -b bac
- 查看分支
bash-3.2$ git branch
* bacmaster
- 星号代表当前所在的分支
- 在分支 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.txt2 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 分支的
- 切换到 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 分支对文件做的任何修改
- 把分支 bac 合并到 master分支
bash-3.2$ git branch # 确定一下你现在所在的分支是 msterbac
* master
bash-3.2$ git merge bac # 把 bac 分支合并到 master
Updating 6b0e1ca..096a515
Fast-forwardbac_new.txt | 0study/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 分支后的文件变化:
- 合并完成后,删除分支 bac,并查看分支
bash-3.2$ git branch -d bac
Deleted branch bac (was 096a515).
bash-3.2$ git branch
* master
bash-3.2$
- 删除分支 bac 就变成下图的样子:
Git鼓励大量使用分支:
- 因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
* 查看分支:git branch* 创建分支:git branch <name>* 切换分支:git checkout <name>* 创建+切换分支:git checkout -b <name>* 合并某分支到当前分支:git merge <name>* 删除分支:git branch -d <name>
1.6.2 解决内部矛盾
在实际开发过程中,合并分支不会像之前我们操作的这么简单,因为是多人协同开发。假如你正在你自己的分支 bac 开发,完事了要合并到 master,但是,就在你提交之前,你的同事小王,已经向 master 分支提交了他自己的版本;这个时候 master 分支与你之前从 master 分支得到的版本已经出现了不同。这个时候冲突就出现了, Git 是不允许你合并你的分支到 master的。
来个小测试,我现在新建并切换到一个分支 bac 。
bash-3.2$ git checkout -b bac
M wangyinerqi/.idea/workspace.xml
M wangyinerqi/version/views.py
Switched to a new branch 'bac'
并在这个分支上开始开发工作,向 bac_new.txt 文件里,写一些内容。
bash-3.2$ cat bac_new.txt
bash-3.2$ echo "bac branch" >> bac_new.txt
然后添加到暂存区,并提交到版本库(自己的分支 bac)
bash-3.2$ git add bac_new.txt
bash-3.2$ git commit -m "from branch bac"
[bac 270fa9a] from branch bac1 file changed, 1 insertion(+)
bash-3.2$
现在模拟一下另一分支 yan 提交并且合并了自己的分支到 master
这里分支 yan 我之前创建好了,这里就不演示了。
bash-3.2$ git checkout yan
M wangyinerqi/.idea/workspace.xml
M wangyinerqi/version/views.py
Switched to branch 'yan'
bash-3.2$ cat bac_new.txt
bash-3.2$ echo "yan add a line to bac_new.txt" >> bac_new.txt
bash-3.2$ cat bac_new.txt
yan add a line to bac_new.txt
bash-3.2$ git add bac_new.txt
bash-3.2$ git commit -m "yan add a line to bac_new.txt"
[yan 9b69e73] yan add a line to bac_new.txt1 file changed, 1 insertion(+)
bash-3.2$ git checkout master # 切换到 master 分支
M wangyinerqi/.idea/workspace.xml
M wangyinerqi/version/views.py
Switched to branch 'master'
bash-3.2$ git merge yan # 合并分支 yan 到 master 分支
Updating 6c8e3fb..9b69e73
Fast-forwardbac_new.txt | 1 +1 file changed, 1 insertion(+)
bash-3.2$ cat bac_new.txt
yan add a line to bac_new.txt
bash-3.2$
下面我们再合并分支 bac 到 master,相等于你并不知道你的同事,已经合并了他自己的分支到 master。
bash-3.2$ git checkout master # 切换到 master,每次合并时,都确认一下目前所在的分支是正确的
M wangyinerqi/.idea/workspace.xml
M wangyinerqi/version/views.py
Switched to branch 'master'
bash-3.2$ git merge bac # 合并分支
Auto-merging bac_new.txt
CONFLICT (content): Merge conflict in bac_new.txt # 在 bac_new.txt 合并冲突
Automatic merge failed; fix conflicts and then commit the result. # 解决冲突,然后提交
bash-3.2$
- 这里 Git 告诉我们,需要先解决冲突,让后才能继续提交进行合并。
此时,版本库的状态就像下图:
- git status 也会给出用冲突文件
bash-3.2$ git status
On branch master
You have unmerged paths.(fix conflicts and run "git commit")(use "git merge --abort" to abort the merge)Unmerged paths:(use "git add <file>..." to mark resolution)both modified: bac_new.txt
怎么解决冲突?
现在你是处在 master 分支,之间查看冲突的文件,就会看到下面的情况
bash-3.2$ cat bac_new.txt
<<<<<<< HEAD # 表示下面的是当前分支的内容
yan add a line to bac_new.txt
======= # 不同分支的分割线
bac branch
>>>>>>> bac # 表示上面是分支 bac 的内容
- Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容
然后,你尅之间用 vi 编辑器,编辑这个文件,改为你想要的样子,但是一般不删除别人的代码,只删除 Git 的标记。
让我们修改后保存
bash-3.2$ vi bac_new.txt
bash-3.2$ cat bac_new.txt
yan add a line to bac_new.txt
bac branch
bash-3.2$
冲突解决了,添加到暂存区后提交
bash-3.2$ git add bac_new.txt
bash-3.2$ git commit -m "conflicts fixed"
[master f66b051] conflicts fixed
看看状态
bash-3.2$ git status
On branch master
nothing to commit, working tree clean
也可以用 git log 查看分支的合并情况
bash-3.2$ git branch bac
* master # 是在 master 上看yan
bash-3.2$ git log --graph --pretty=oneline --abbrev-commit
* f66b051 conflicts fixed
|\
| * 270fa9a (bac) from branch bac
* | 9b69e73 (yan) yan add a line to bac_new.txt
|/
*
...
最后版本库的状态会像下图一样
小结
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
- 用git log --graph命令可以看到分支合并图。
- 只能看到和当前分支有关系的合并情况
1.6.3 分支管理的策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下–no-ff方式的git merge:
首先,仍然创建并切换dev分支:
$ git checkout -b dev
Switched to a new branch 'dev'
修改readme.txt文件,并提交一个新的commit:
$ git add readme.txt
$ git commit -m "add merge"
[dev 6224937] add merge1 file changed, 1 insertion(+)
现在,我们切换回master:
$ git checkout master
Switched to branch 'master'
准备合并dev分支,请注意–no-ff参数,表示禁用Fast forward:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.readme.txt | 1 +1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去。
合并后,我们用git log看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* 7825a50 merge with no-ff
|\
| * 6224937 add merge
|/
* 59bc1cb conflict fixed
...
可以看到,不使用Fast forward模式,merge后就像这样:
分支策略
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
小结
Git分支十分强大,在团队开发中应该充分应用。
合并分支时,加上 --no-ff 参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward 合并就看不出来曾经做过合并。
1.6.4 Bug 分支
Bug 分支相当于一个临时分支,出现的场景是这样的:
当你正在你自己的分支 shark 上开发,但是进度已经超过了 50%,离开发完成到一定的阶段,还需要到下班的时间。但是此时,老板说,此系统 bac 分支上有一个重要的 Bug 需要紧急修改,你听完情况后,简单估算了一下,修复此 Bug 需要 2 个小时。此时你有没有完成手头的工作,不能提交当前的代码。怎么办?
放心, Git 早就为你想好了, git stash 命令支持把当前分支的工作状态暂时冻结并保存,保存后就可以区其他分支修复 Bug,等修复 Bug 之后,回到你自己的分支,再把冻结的工作状态解冻,恢复到你离开时的状态,继续开发。
实操
准备测试文件
- 为了测试,我们需要在自己的分支 shark 上新建一个文件 frozen.txt,写点内容,之后添加、提交。
- 这样版本库就有了一个测试文件,被 Git 管理了,之后再对其修改,Git 就会监控到。
bash-3.2$ git branchbacmaster
* sharkyan
bash-3.2$ touch frozen.txt
bash-3.2$ echo "this is a frozen file" >> frozen.txt
bash-3.2$ git add frozen.txt
bash-3.2$ git commit -m "first commit "
- 好,现在测试文件被提交到版本库里了,下面我们可以对其进行修改,增加一行内容进行测试。
bash-3.2$ echo "change frozen.txt v1" >> frozen.txt
- 冻结之前,看看状态
bash-3.2$ git status
On branch shark
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: frozen.txtno changes added to commit (use "git add" and/or "git commit -a")
冻结当前分支的工作状态
- 现在冻结
bash-3.2$ git stash
Saved working directory and index state WIP on shark: 51d9025 first commit
- 冻结后,再观察状态
bash-3.2$ git status
On branch shark
nothing to commit, working tree clean
bash-3.2$
- 冻结后的分支工作区是清洁的。
去 bac 分支修复 Bug
-
我们在 bac 分支修改一个已经存在文件,对这个文件添加一行内容
在哪个分支需要修复 Bug 就在哪个分支上创建临时 Bug 分支。
这里我们是在 bac 分支修复 Bug切换到 bac 分支,并且创建临时分支 repair_1
bash-3.2$ git checkout bac
Switched to branch 'bac'
bash-3.2$ git checkout -b repair_1
Switched to a new branch 'repair_1'
- 添加、提交
对文件 bac_new.txt 的内容进行修改,之后添加、提交
bash-3.2$ ls bac_new.txt
bac_new.txt
bash-3.2$ echo "Problem one has been repaired" >> bac_new.txt
bash-3.2$ git add bac_new.txt
bash-3.2$ git commit -m "Problem one has been repaired"
[repair_1 fa6419a] Problem one has been repaired1 file changed, 1 insertion(+)
bash-3.2$ git status # 状态没问题
On branch repair_1
nothing to commit, working tree clean
- 合并分支,完成 Bug 修复
现在,在临时分支 repair_1 上完成了 Bug 的修复,我们把 repair_1 分支合并到 bac 分支,以完成 bac 分支 Bug 的修复
bash-3.2$ git branchbacmaster
* repair_1sharkyan
bash-3.2$ git checkout bac
Switched to branch 'bac'
bash-3.2$ git merge repair_1
Updating 6c8e3fb..fa6419a
Fast-forwardbac_new.txt | 1 +1 file changed, 1 insertion(+)
bash-3.2$ git status
On branch bac
nothing to commit, working tree clean
- 最后删除临时分支 repair_1
bash-3.2$ git branch -d repair_1
Deleted branch repair_1 (was fa6419a).
解冻
- 切换到自己的分支,并查看目前的状态
bash-3.2$ git checkout shark
Switched to branch 'shark'
bash-3.2$ git status
On branch shark
nothing to commit, working tree clean
- 查看冻结列表
git stash list
bash-3.2$ git stash list
stash@{0}: WIP on shark: 51d9025 first commit
(END) # 按 q 退出
- 开始解冻工作状态
要恢复工作状态,有两种方式:
-
一是用git stash apply恢复,继续开发工作,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
-
另一种方式是用git stash pop,恢复的同时把stash内容也删了
第一种方式:
# git stash apply 恢复
bash-3.2$ git stash apply
On branch shark
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: frozen.txtno changes added to commit (use "git add" and/or "git commit -a")
bash-3.2$ git stash list # 被冻结的工作状态还是冻结列表里
stash@{0}: WIP on shark: 51d9025 first commit
(END)# git stash drop 删除冻结bash-3.2$ git stash list
stash@{0}: WIP on shark: 51d9025 first commit
bash-3.2$ git stash drop stash@{0} # 可以指定删除某一个
Dropped stash@{0} (2193f7bc4ce37055883661a1869f891e7dd4f74a)
注意: 假如你冻结了一个工作状态,在你没有恢复这个被冻结的工作状态的情况下,你直接从冻结列表里删除(drop)了这个工作状态,那么这个工作将会丢失,当时修改的内容也丢失了。
第二种方式:
bash-3.2$ git stash pop
On branch shark
Changes not staged for commit:(use "git add <file>..." to update what will be committed)(use "git checkout -- <file>..." to discard changes in working directory)modified: frozen.txtno changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (a1179204ee9b4fe437170b6b7e6ee7cd6284ed8d) # 冻结的工作状态同时被删除了。
恢复同样可以指定恢复到某一个工作状态。
你可以多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash,用命令:
bash-3.2$ git stash apply stash@{0}
- 继续你自己分支的开发工作
看下刚才的修改还在,继续搬砖吧。
bash-3.2$ cat frozen.txt
this is a frozen file.
1.6.5 Feature 分支
在一个项目开发过程中,总会有产品经理或者甲方,会时不时的要求添加新功能。
但是,你在添加一个新功能时,肯定不希望因为一些实验性质的代码,把主分支搞乱了。
所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
现在假设我接收到一个开发 Time machine 的新功能,真的很想回到过去,青春!
说干就干
bash-3.2$ git branch
* bacmastersharkyan
bash-3.2$ git checkout -b featrue-time_machine
Switched to a new branch 'featrue-time_machine'
bash-3.2$ touch time_machine.py
bash-3.2$ git add time_machine.py
bash-3.2$ git commit -m "added a file time_machine.py"
[featrue-time_machine cdff25c] added a file time_machine.py1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 time_machine.py
干完了,切到 bac 分支,准备合并
bash-3.2$ git checkout bac
Switched to branch 'bac'
就在此时,你接到高层领导和专家的紧急通知,目前这个功能核心技术有个致命错误,这个功能不能合并到 bac 分支,并且需要马上销毁,不然会发生大爆炸。
于是你现在开始就地销毁这个分支
bash-3.2$ git branch -d featrue-time_machine
error: The branch 'featrue-time_machine' is not fully merged.
If you are sure you want to delete it, run 'git branch -D featrue-time_machine'.
此时,Git 提示: 分支还没有被合并,假如你确定要删除,执行
git branch -D featrue-time_machine
强行删除
bash-3.2$ git branch -D featrue-time_machine
Deleted branch featrue-time_machine (was cdff25c).
bash-3.2$ git branch
* bacmastersharkyan
现在删除成功,在分支 featrue-time_machine 上的所有修改也将丢失。
Git 命令速查表
Git 流程图
1.7 标签管理
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
Git有commit,为什么还要引入tag?
“请把上周一的那个版本打包发布,commit号是6a5819e…”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。
1.7.1 创建标签
- 在Git中打标签非常简单,首先,切换到需要打标签的分支上
bash-3.2$ git checkout bac
Already on 'bac'
bash-3.2$ git branch
* bacmastersharkyan
- 敲命令git tag 就可以打一个新标签
默认是把标签打到最新一次的 commit 上
bash-3.2$ git tag v1.0
v1.0 就是标签名
- 可以用命令git tag查看所有标签
bash-3.2$ git tag
v1.0
- 给历史 commit 打标签
先查看历史版本
bash-3.2$ git log --pretty=oneline --abbrev-commit
fa6419a (HEAD -> bac, tag: v1.0) Problem one has been repaired
096a515 added a new line in readme.txt,create a file bac_new.txt
6b0e1ca del file useless.txt
3dc63a6 added a file useless.txt
比方说要对 del file useless.txt 这次提交打标签,它对应的 commit id 是 6b0e1ca,敲入命令
bash-3.2$ git tag v0.8 6b0e1ca
bash-3.2$ git tag
v0.8
v1.0
注意,标签不是按时间顺序列出,而是按字母排序的
- 查看标签信息: git show
bash-3.2$ git show v0.8
commit 6b0e1cafc5a72ef8367e4c83e23558f8f00084c8 (tag: v0.8)
Author: sharkgit1976 <dockerhub@163.com>
Date: Mon Oct 16 14:32:44 2017 +0800del file useless.txtdiff --git a/useless.txt b/useless.txt
deleted file mode 100644
index e69de29..0000000
(END)
- 通过-s用私钥签名一个标签
bash-3.2$ git tag -s v0.1 -m "signed version 0.1 released" 63e4ecd
fatal: cannot run gpg: No such file or directory
error: gpg failed to sign the data
error: unable to sign the tag
签名采用PGP签名,因此,必须首先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报上面的错误信息。如果报错,请参考GnuPG帮助文档配置Key。
正常的话不会输出任何信息,你是知道的,Linux 就是这样。
- 查看 PGP 签名信息
git show
用PGP签名的标签是不可伪造的,因为可以验证PGP签名。验证签名的方法比较复杂,这里就不介绍了。
1.7.2 操作标签
- 删除本地标签
bash-3.2$ git tag -d v0.8
Deleted tag 'v0.8' (was 6b0e1ca)
默认标签只会存储在本地,不会被自动推送到远程。
关于远程仓库,我们会在下一小节介绍。
小结时刻
-
git tag 用于新建一个标签,默认为 HEAD,也可以指定一个 commit id;
-
git tag -a -m “blablabla…” 可以指定标签信息;
-
git tag -s -m “blablabla…” 可以用 PGP 签名标签;
-
git tag 可以查看标签信息及 PGP 签名信息(假如有)
-
git tag 可以查看所有标签。
-
git tag -d 删除标签
1.8 和远程仓库的那些事
如果只是在一个仓库里管理文件历史,Git和SVN真没啥区别。
远程仓库是 Git 的杀手级功能之一(注意是之一,也就是后面还有之二,之三……)。
生产环境中的实际情况往往是这样,找一台电脑充当 Git 服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
那现在是不是需要搭建一台 Git 服务器,来作为远程仓库呢?如何搭建 Git 服务器,稍后我们再谈。
目前互联网上已经存在了这样的网站,可以提供 Git 服务器,并且大部分情况下是免费的。
GitHub 网站你知道吧,这是一个神奇的网站,它就可以充当我们的远程仓库。
要想使用 GitHub 作为我们的远程仓库,只需要注册一个账号即可,当然这是免费的。
GitHub 虽然是免费的,但是指针对于你创建的公共仓库;什么是公共仓库?就是放在仓库里的代码互联网的任何用户都可看到,并且可以拉取等操作。要想创建私有仓库,需要花钱。
所以目前大部分公司使用另一个网址的服务 GitLab ,这个网站和 GitHub 唯一的区别是,可以为普通用户提供创建私有仓库,并且为这个私有仓库提供权限服务,免费。
所以我们接下来主要介绍 GitLab 的使用。
1.81 注册 gitlab 账号
网址 https://about.gitlab.com/
填写信息
见到下方图片内容后,就可以去邮箱进行确认了
邮箱确认
确认后,重新打开 GitLab 网站进行登录
1.8.2 创建一个仓库
创建一个私有仓库
填好注册的相关信息后点击绿色的 Create project
按钮进行创建
1.8.3 和远程仓库建立SSH
信任关系
由于 GitLab 和 GitHub 都是用 SSH 协议对你的本地Git仓库和GitHub仓库之间的传输进行加密传输的。
为什么需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送,所以,需要一点设置
我们这里选择是 SSH
的通信方式,所以执行相关操作前,需要先解决本地机器和远程仓库的服务器之间的 SSH 信任关系,需要让远程仓库的服务器信任我们本地的机器。
只需要把本地的公钥传给远程仓库的服务器即可。
- 首先在本地机器上创建自己的密钥对
[shark@git ~]$ ssh-keygen # 一路敲回车键即可
-
复制自己的公钥内容
用 vim 打开公钥文件,之后赋值文件的整个内容
[shark@git ~]$ vim ~/.ssh/id_rsa.pub
-
添加公钥
在刚才的
GitLab
创建仓库的页面上,点击头像,再点击Settings
在弹出的网页左侧边栏,点击 SSH Keys
把自己的公钥内容添加到远程仓库 GitLab
网站上
-
回到自己新建仓库的页面
-
继续按照提示执行相关命令
设置本地仓库和 GitLab 仓库的通信方式为 SSH
在本地 Linux 机器上按照不同的情况执行如下命令
应该在每台需要给这个 GitLab 仓库建立联系的 Linux 机器上执行这些操作
我这里选择第一种,就是本地没有仓库,比如我要在我的家目录下建立仓库,做如下操作即可:
[shark@git ~]$ pwd
/home/shark
[shark@git ~]$ git config --global user.name "sharkyun"
[shark@git ~]$ git config --global user.email \
"sharkyunops@126.com"
[shark@git ~]$ git clone \
git@gitlab.com:sharkyun/my_project_one.git
克隆成功后,在当前目录下会有一个和你远程仓库同名的一个目录。
这个目录就是本地的仓库了。
- 测试本地仓库
接下来继续按照 GitLab 网站上的提示,在这个本地仓库里创建测试文件并提交到本地仓库,最后同步到远程仓库(即: GitLab)
[shark@git ~]$ cd my_project_one/
[shark@git my_project_one]$ touch README.md
[shark@git my_project_one]$ git add README.md
[shark@git my_project_one]$ git commit -m "add README"
[master(根提交) 5e2b547] add README1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 README.md
[shark@git my_project_one]$ git push -u origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 211 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@gitlab.com:sharkyun/my_project_one.git* [new branch] master -> master
分支 master 设置为跟踪来自 origin 的远程分支 master。
[shark@git my_project_one]$
- 验证远程仓库
1.8.4 总结
# 克隆一个远程仓库到本地
git clone git@gitlab.com:sharkyun/my_project_one.git# 推送本地仓库的内容到远程仓库, 只有在第一次是使用 -u 之后不必再用 -u 参数
git push -u origin master# 以后再此同步使用如下命令即可
git push origin master# origin 默认的远程仓库名
# master 本地仓库名
最后有彩蛋赠送 git 和 github 的合作
1.9 再谈标签管理
1.9.1 推送某个标签到远程
- 使用命令git push origin
为了测试先创建一个本地标签
[shark@git my_project_one]$ git log --pretty=oneline --abbrev-commit
5e2b547 add README
[shark@git my_project_one]$ git tag tag_init 5e2b547
[shark@git my_project_one]$ git tag
tag_init
推送标签
[shark@git my_project_one]$ git push origin tag_init
Total 0 (delta 0), reused 0 (delta 0)
To git@gitlab.com:sharkyun/my_project_one.git* [new tag] tag_init -> tag_init
[shark@git my_project_one]$
可以登陆 GitLab 验证
- 一次性推送全部尚未推送到远程的本地标签
[shark@git my_project_one]$ git tag v1.0 5e2b547
[shark@git my_project_one]$ git tag v1.1 5e2b547
[shark@git my_project_one]$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To git@gitlab.com:sharkyun/my_project_one.git* [new tag] v1.0 -> v1.0* [new tag] v1.1 -> v1.1
当你推送标签时,Git 会同时把这个便签的内容一起推送,可以在 GitHub 上点击 tag 查看到这个标签的内容。版本是不会被推送的,需要显式推送
1.9.2 删除已经推送到远程的标签
- 先从本地删除
[shark@git my_project_one]$ git tag -d v1.1
已删除 tag 'v1.1'(曾为 5e2b547)
- 然后,从远程删除。
- 删除命令也是push,但是格式如下:
[shark@git my_project_one]$ git push origin :refs/tags/v1.1
To git@gitlab.com:sharkyun/my_project_one.git- [deleted] v1.1
要验证删除远程标签,可以登陆GitHub查看。
1.10 和兄弟们协同作战
从上面的操作中不难看出,当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin。
1.10.1 查看远程库的信息
执行如下命令需要在本地的 git
仓库目录下
- 用
git remote
查看远程仓库名
[shark@git my_project_one]$ git remote
origin
- 用 git remote -v 显示更详细的信息
[shark@git my_project_one]$ git remote -v
origin git@gitlab.com:sharkyun/my_project_one.git (fetch)
origin git@gitlab.com:sharkyun/my_project_one.git (push)
上面显示了可以抓取和推送的 origin 的地址。如果没有推送权限,就看不到push的地址。
1.10.2 推送分支
推送分支,就是把该分支上 的所有本地提交推送到远程库。
- 推送时,要指定本地分支名,这样,Git就会把该分支推送到远程库对应的远程分支上:
git push origin master
- 如果要推送其他分支,比如 bac
为了测试,先创建一个分支 bac
[shark@git my_project_one]$ git branch bac
开始推送本地分支 bac 到远程仓库
[shark@git my_project_one]$ git push origin bac
- 查看远程仓库的分支
刷新刚才的仓库页面
在这里就会看到所有的分支,√
对号表示 GitHub 仓库当前所在的分支
但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢?
* master 分支是主分支,因此要时刻与远程同步;* dev 分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;* bug 分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;* feature分支是否推到远程,取决于你是否和你的小伙伴合作,在上面开发。
总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定
1.10.3 拉取分支
多人协作的工作模式通常是这样:
首先,可以试图用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
这就是多人协作的工作模式,一旦熟悉了,就非常简单。
1.10.4 小结
查看远程库信息,使用 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,如果有冲突,要先处理冲突。
1.11 自定义 Git
1.11.1 配置别名
有没有经常敲错命令?比如git status?status这个单词真心不好记。
如果敲git st就表示git status那就简单多了,当然这种偷懒的办法我们是极力赞成的。
我们只需要敲一行命令,告诉Git,以后st就表示status:
$ git config --global alias.st status
好了,现在敲git st看看效果。
当然还有别的命令可以简写,很多人都用co表示checkout,ci表示commit,br表示branch:
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
以后提交就可以简写成:
$ git ci -m "bala bala bala..."
--global参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用。
在撤销修改一节中,我们知道,命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区。既然是一个 unstage 操作,就可以配置一个unstage 别名:
$ git config --global alias.unstage 'reset HEAD'
当你敲入命令:
$ git unstage test.py
实际上Git执行的是:
$ git reset HEAD test.py
配置一个git last,让其显示最后一次提交信息:
$ git config --global alias.last 'log -1'
这样,用git last就能显示最近一次的提交:
$ git last
commit adca45d317e6d8a4b23f9811c3d7b7f0f180bfe2
Merge: bd6ae48 291bea8
Author: Michael Liao <askxuefeng@gmail.com>
Date: Thu Aug 22 22:49:22 2013 +0800merge & fix hello.py
甚至还有人丧心病狂地把lg配置成了:
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
可以试试 git lg 的效果
1.11.2 配置文件
配置Git的时候,加上–global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。
配置文件放哪了?每个仓库的Git配置文件都放在.git/config文件中:
[shark@git my_project_one]$ cat .git/config
[core]repositoryformatversion = 0filemode = truebare = falselogallrefupdates = true
[remote "origin"]url = git@gitlab.com:sharkyun/my_project_one.gitfetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]remote = originmerge = refs/heads/master
[alias]last = log -1
别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。
而当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中:
$ cat .gitconfig
[alias]co = checkoutci = commitbr = branchst = status
[user]name = Your Nameemail = your@email.com
配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。
1.11.3 忽略特殊文件
有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次git status都会显示Untracked files …,有强迫症的童鞋心里肯定不爽。
这个问题解决起来也很简单,在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览:
忽略文件的原则是:
-
忽略操作系统自动生成的文件,比如缩略图等;
-
忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
-
忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
举个例子:
假设你在Windows下进行Python开发,Windows会自动在有图片的目录下生成隐藏的缩略图文件,如果有自定义目录,目录下就会有Desktop.ini文件
- 因此你需要忽略Windows自动生成的垃圾文件:
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini
- 然后,继续忽略Python编译产生的.pyc、.pyo、dist等文件或目录:
# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build
- 加上你自己定义的文件,最终得到一个完整的.gitignore文件,内容如下:
# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build# My configurations:
db.ini
deploy_key_rsa
- 最后一步就是把.gitignore也提交到Git,就完成了!
当然检验 .gitignore 的标准是git status命令是不是说 working directory clean。
使用Windows的童鞋注意了,如果你在资源管理器里新建一个 .gitignore 文件,它会非常弱智地提示你必须输入文件名,但是在文本编辑器里“保存”或者“另存为”就可以把文件保存为.gitignore了。
有些时候,你想添加一个文件到 Git,但发现添加不了,原因是这个文件被.gitignore忽略了:
$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.
- 如果你确实想添加该文件,可以用-f强制添加到Git:
$ git add -f App.class
或者你发现,可能是.gitignore写得有问题,需要找出来到底哪个规则写错了,可以用git check-ignore
命令检查:
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
Git会告诉我们,.gitignore的第3行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。
小结
-
忽略某些文件时,需要编写.gitignore;
-
.gitignore文件本身要放到版本库里,并且可以对.gitignore做版本管理!
1.12 搭建 Git 服务器
1.12.1 安装 Git
[shark@git ~]$ sudo yum install git
1.12.2 创建 git 用户
创建用户,并为git用户指定的git-shell,每次一登录就自动退出。
[shark@git ~]$ sudo useradd git -s /usr/bin/git-shell
[shark@git ~]$ grep git /etc/passwd
git:x:1001:1001::/home/git:/usr/bin/git-shell
设置密码
[shark@git ~]$ sudo passwd git
1.12.3 创建证书登录
收集所有需要登录的用户的公钥,就是他们自己的id_rsa.pub文件
把所有公钥导入到 Git
服务器的 git
用户的
/home/git/.ssh/authorized_keys
文件里,一行一个。
1.12.4 初始化Git仓库
先选定一个目录作为Git仓库,假定是/srv/sample.git,在 /srv 目录下输入命令:
[shark@git ~]$ sudo mkdir /srv
[shark@git ~]$ cd /srv/
[shark@git srv]$ sudo git init --bare sample.git
初始化空的 Git 版本库于 /srv/sample.git/
Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾。
然后,把 owner 改为git:
[shark@git srv]$ sudo chown git.git sample.git
[shark@git srv]$ ll
总用量 0
drwxr-xr-x. 7 git git 119 6月 26 17:37 sample.git
1.12.5克隆远程仓库
现在,可以通过git clone命令克隆远程仓库了,在各自的电脑上运行
bash-3.2$ git clone git@172.16.153.129:/srv/sample.git
Cloning into 'sample'...
git@172.16.153.129's password: # 输入密码
warning: You appear to have cloned an empty repository.
1.13 Gitosis 管理公钥
如果团队很小,把每个人的公钥收集起来放到服务器的
/home/git/.ssh/authorized_keys
文件里就是可行的。
如果团队有几百号人,就没法这么玩了,这时,可以用Gitosis来管理公钥。
简单地说,Gitosis 就是一套用来管理 authorized_keys
文件和实现简单连接限制的脚本。有趣的是,用来添加用户和设定权限的并非通过网页程序,而只是管理一个特殊的 Git 仓库。你只需要在这个特殊仓库内做好相应的设定,然后推送到服务器上,Gitosis 就会随之改变运行策略,听起来就很酷,对吧?
1.13.1 安装
[shark@git ~]$ sudo yum install python-setuptools
[shark@git ~]$ git clone https://github.com/tv42/gitosis.git
[shark@git ~]$ cd gitosis/
[shark@git gitosis]$ sudo python setup.py install
管理权限
有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为Git是为Linux源代码托管而开发的,所以Git也继承了开源社区的精神,不支持权限控制。不过,因为Git支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。Gitolite就是这个工具。
安装和初始化
首先,在你的服务器上创建一个名为git
的用户,然后以这个用户登录。
从你的工作站拷贝工作站用户的SSH公钥(也就是你用ssh-keygen
默认生成的~/.ssh/id_dsa.pub
文件),重命名为<工作站用户>.pub
(我们这里使用shark.pub
作为例子),把这个 shark.pub
文件放到 git 服务器的 git
用户的家目录下。
然后用 git 用户在 git 服务器上执行下面的命令:
[git@git ~]$ cd ~
[git@git ~]$ git clone git://github.com/sitaramc/gitolite
[git@git ~]$ mkdir bin
[git@git ~]$ gitolite setup -pk shark.pub
最后一个命令的作用是在服务器上创建了一个名为gitolite-admin
的Git仓库。
最后,回到你的工作站,执行
git clone git@gitolite-server-ip:gitolite-admin
然后你就完成了!
shark@SharkAir ~/test_data git clone git@172.16.153.143:gitolite-admin
Cloning into 'gitolite-admin'...
warning: templates not found /Users/yanshunjun/.git_template
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (6/6), done.
在执行上面命令的目录下一个会看到如下目录
shark@SharkAir ~/test_data ls
gitolite-admin
到目前为止,Gitolite现在已经安装在了服务器上。
在你的工作站上,你也有一个名为gitolite-admin
的新仓库。
你可用在工作站通过更改这个仓库以及推送到服务器上来管理你的Gitolite配置。
配置文件和访问规则
安装结束后,你切换到gitolite-admin
仓库,然后看看都有啥:
$ cd gitolite-admin/
$ ls
conf/ keydir/
$ find conf keydir -type f
conf/gitolite.conf
keydir/scott.pub
$ cat conf/gitolite.confrepo gitolite-adminRW+ = sharkrepo testingRW+ = @all
上面的意思是 shark 用户对于仓库 gitolite-admin
具有读写权限
conf/gitolite.conf
文件是权限配置文件
keydir/
目录下存放了用户的公钥文件,文件名的命名规则是: username.pub
添加用户
为了添加一个名为alice
的用户,获取她的公钥,命名为alice.pub
,然后放到在你工作站上的gitolite-admin
克隆的keydir
目录。添加,提交,然后推送更改。这样用户就被添加了。
$ ls gitolite-admin/keydir/
alice.pub shark.pub
$ cd gitolite-admin
$ git add .
$ git commit -m "add user pub key for alice"
$ git push origin master
创建 Git 项目并赋予不同的用户权限
这里以创建 sharkyun
项目为例,并添加用户 alice 的权限为读, shark 用户的权限为读写。
在工作站主机上操作
- 配置权限信息
$ cat conf/gitolite.conf
repo gitolite-adminRW+ = sharkrepo testingRW+ = @all
repo sharkyunRW+ = sharkR = alice
- 提交并同步到远程服务器,使之生效
$ git add conf/gitolite.conf$ git commit -m "add project sharkyun and configure add shark/alice user"
[master 24702de] add project sharkyun and configure add shark/alice user1 file changed, 3 insertions(+)$ git push origin master
- 测试用户
shark
和用户alice
的权限
shark 用户可以 clone/pull
和 push
$ git clone git@172.16.153.143:sharkyun
Cloning into 'sharkyun'...
warning: templates not found /Users/yanshunjun/.git_template
warning: You appear to have cloned an empty repository.$ cd sharkyun
$ touch shark.txt
$ touch shark.txt$ git add .
$ git commit -m "add a file"
[master (root-commit) ba72c1b] add a file1 file changed, 0 insertions(+), 0 deletions(-)create mode 100644 shark.txt$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 221 bytes | 221.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To 172.16.153.143:sharkyun* [new branch] master -> master$ git pull git@172.16.153.143:sharkyun
From 172.16.153.143:sharkyun* branch HEAD -> FETCH_HEAD
Already up-to-date.
alice 用户 只可以 clone/pull
不可以 push
5e84deafdb47:~/sharkyun$ git config --global user.email alice@example.com
5e84deafdb47:~/sharkyun$ git config --global user.name alice
5e84deafdb47:~$ git clone git@172.16.153.143:sharkyun
Cloning into 'sharkyun'...
warning: You appear to have cloned an empty repository.
5e84deafdb47:~$ ls
sharkyun
5e84deafdb47:~$ cd sharkyun/
5e84deafdb47:~/sharkyun$ touch alice.txt
5e84deafdb47:~/sharkyun$ ls
alice.txt shark.txt
5e84deafdb47:~/sharkyun$ git add .
5e84deafdb47:~/sharkyun$ git commit -m "add a file: alice.txt"5e84deafdb47:~/sharkyun$ git pull git@172.16.153.143:sharkyun
From 172.16.153.143:sharkyun* branch HEAD -> FETCH_HEAD
Already up to date.
# 以上是在本地仓库的操作# 下面是同步到远程仓库,就会失败
5e84deafdb47:~/sharkyun$ git push origin master
FATAL: W any sharkyun alice DENIED by fallthru
(or you mis-spelled the reponame)
fatal: Could not read from remote repository.Please make sure you have the correct access rights
and the repository exists.
权限控制管理
/* 实例1 */
repo projectRW+ = admin // admin 有读,写,强制写的权限R = alice // alice 仅有读权限,如果尝试提交会报错/* 实例2 */
repo projectRW+ = admin // admin 有读,写,强制写的权限// alice 对所有以dev开始的分支都有读写权限。即可以新建dev3,修改dev2分支 RW dev = alice /* 实例3 */
repo projectRW+ = admin// alice 仅对dev分支有写权限,$表示精确匹配RW dev$ = alice/* 实例4 */
repo project... // 此处表示省略的需要配置的必要权限信息// alice 用户仅能创建除了以v加上数字开头之外的其他tag- refs/tags/v[0-9] = alice/* 实例5 */
repo project...// 除了根目录的Makefile文件外,alice 对其他文件都具有写权限- NAME/Makefile = aliceRW Name/ = alice/* 推荐用法 */
repo project// 有效防止误操作:// 指定admin对branch的读写权限,防止误操作将本地的临时性branch推送到服务器端// 如果确定需要新增一个branch,则在下面新增一行,例如新增dev分支// admin 有读,写,强制写master分支的权限RW+ master$ = admin// admin 有读,写,强制写dev分支的权限RW+ dev$ = admin// 普通developer仅能读写dev分支,且不能强制写RW dev$ = devuser1 devuser2// 限制普通用户不能创建以v加上数字开头的release tag- refs/tags/v[0-9] = devuser1 devuser2// 如果不同的用户(组)分别负责完全独立的两个子系统,则可通过类似以下这种方式排除互相干扰// devuser1 不能修改net子系统下的文件- NAME/net/ = devuser1// devuser2 不能修改arch子系统下的文件- NAME/arch/ = devuser2
配置 GitWeb
// 安装gitweb 和用于代码高亮的highlight
$ sudo apt-get install -y gitweb highlight// 修改文件权限,用于gitweb读取
$ chmod 0027 /home/git.gitolite.rc
$ sudo usermod -a -G git www-data // www-data 是运行nginx服务的用户
$ sudo chmod g+r /home/git/projects.list
$ sudo chmod -R g+rx /home/git/repositories// 将要显示的repo写入projects.list文件
$ cat projects.listtesting.git// 修改/etc/gitweb.conf,修改以下几个关键值
$ cat /etc/gitweb.conf# path to git projects (<project>.git)$projectroot = "/home/git/repositories/";# directory to use for temp files$git_temp = "/tmp";# target of the home link on top of all pages#$home_link = $my_uri || "/";# html text to include at home page#$home_text = "indextext.html";# file with project list; by default, simply scan the projectroot dir.$projects_list = "/home/git/projects.list";$strict_export = 1;# stylesheet to use#@stylesheets = ("static/gitweb.css");# javascript code for gitweb$javascript = "static/gitweb.js";# logo to use$logo = "static/git-logo.png";# the 'favicon'#$favicon = "static/git-favicon.png";# git-diff-tree(1) options to use for generated patches#@diff_opts = ("-M");@diff_opts = ();$feature {'blame'}{'default'} = [1];$feature {'blame'}{'override'} = 1;$feature {'snapshot'}{'default'} = ['zip', 'tgz'];$feature {'snapshot'}{'override'} = 1;$feature{'highlight'}{'default'} = [1];// 配置nginx
$ sudo apt-get install spawn-fcgi fcgiwrap
// 修改/etc/init/d/fcgi.fcgiwrap,将FCGI_USER FCGI_GROUP FCGI_SOCKET_OWNER FCGI_SOCKET_GROUP 都修改为运行web服务的用户
// 在nginx的配置文件中添加一个新的server段server {listen 80;server_name gitweb.example.com;access_log /home/wwwlogs/access.log main;location / {root /usr/share/gitweb;index index.cgi;include fastcgi_params;gzip off;fastcgi_param GITWEB_CONFIG /etc/gitweb.conf;if ($uri ~ "/index.cgi") {fastcgi_pass unix:/var/run/fcgiwrap.socket;}}}// 重启fcgiwrap和nginx
$ sudo service fcgiwrap restart
$ sudo nginx -s reload
小结
搭建Git服务器非常简单,通常10分钟即可完成;
要方便管理公钥,用Gitosis;
要像SVN那样变态地控制权限,用Gitolite。
彩蛋
GitHub 和 Git 的合作
1.8.1 设置本地和远程仓库的互信
由于 GitHub 是用 SSH 协议对你的本地Git仓库和GitHub仓库之间的传输进行加密传输的。
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送,所以,需要设置
创建自己的 key
首先看看你的家目录里有没有你自己的私钥和公钥位置就在自己的家目录下的 .ssh 隐藏文件夹里
bash-3.2$ ls -l ~/.ssh
total 24
-rw------- 1 yanshunjun staff 1679 10 18 11:53 id_rsa # 自己的私钥
-rw-r--r-- 1 yanshunjun staff 402 10 18 11:53 id_rsa.pub # 自己的公钥
-rw-r--r-- 1 yanshunjun staff 3343 10 17 09:00 known_hosts # 里面有自己信任的主机
假如没有,用下面的命令创建即可,一路回车
bash-3.2$ ssh-keygen -t rsa -C "bengtiaomami@126.com" # 邮箱地址自定义
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/yanshunjun/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/yanshunjun/.ssh/id_rsa.
Your public key has been saved in /Users/yanshunjun/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:LMI6zxlGTEyJlOL0iMlAKSKN5klNGBh3OPNWiJKGDbU bengtiaomami@126.com
The key's randomart image is:
+---[RSA 2048]----+
|*%X*oo |
|%OX*o . |
|#+E+o. |
|o* =o . |
| .= . S |
| o . . |
| o o |
| = o |
| + |
+----[SHA256]-----+
添加你的公钥到 GitHub 上
- 登录后点击自己的头像,再点击 Settings
–此图片截取时,是 2019-10-18,GitHub 在以后有可能会更新页面布局。
-
点击 SSH and GPG keys
-
点击后,再跳转的页面再点击 New SSH key
- 添加公钥
点击后,会跳转到添加 key 页面。
第一步: 给这个 key 起一个名字
第二步: 把你的公钥 id_rsa.pub 的内容粘贴到这里
需要注意的是要保证此文件里的内容被粘贴到这里时 是一行,本身就是一行
第三步: 点击 Add ssh key 完成 key 的添加
成功添加后,应该会看到自己添加好的 key
当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。
如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。
1.8.2 创建远程仓库
假如你从一开始看这个教程,一直到现在。目前的情景是,你已经在本地创建了一个 Git 仓库,现在又想在GitHub 创建一个 Git 仓库,并且让这两个仓库进行远程同步,这样,GitHub 上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。
首先,登陆 GitHub
- 点击 GIt 图标即可回到自己账户的首页
- 点击 New repository 来创建一个新的仓库
填写新仓库的相关信息
- 给仓库起个名字 本地仓库名和远程仓库名可以不一样
- 写上一些关于这个仓库的描述信息
- 默认是公共的仓库
- 最后点击 Create repository
创建完成
创建完成后,会跳转到这个新创建的仓库页面
- 这个是你使用本地仓库链接 GitHub 上这个仓库的地址,下一小节会介绍怎么用它。
- 点击这里可以进行复制
新创建好的仓库支持三种方式和本地仓库同步链接
- 支持在本地创建一个新的仓库和 Git 仓库同步
- 支持从本地现有的仓库和 Git 仓库同步
- 支持从其他的 版本控制库里和 Git 仓库同步,比如 SVN
接下来我会介绍前两种方式的同步,接下来先介绍把本地已有的仓库和 Git 仓库同步
1.8.3 在一起
按照 Git 的提示,把本地已有的仓库 mygithub 和 刚才我们新建的 GitHub 上的仓库关联在一起
git remote add origin git@github.com:sharkgit1976/study.git
说明一下: * git remote add 命令用于添加远程仓库到本地仓库
* origin 是 GitHub 远程仓库映射到本地的仓库名,这是默认的,也没必要去修改它
* sharkgit1976 是我的 GitHub 的账户名,你不要复制这个教程中的这条信息,复制你自己的
* study.git 同样是我刚才创建的仓库名,你用你自己的
实操
bash-3.2$ git remote add origin git@github.com:sharkgit1976/study.git
正常不会出现任何信息
信息会保存在 .git/config 文件中
bash-3.2$ cat .git/config
[core]repositoryformatversion = 0filemode = truebare = falselogallrefupdates = trueignorecase = trueprecomposeunicode = true
[remote "origin"]url = git@github.com:sharkgit1976/study.git # 这里是连接 GitHub 的方式,当前的方式是 SSHfetch = +refs/heads/*:refs/remotes/origin/*
推送本地仓库的内容到远程仓库
接下来我们就可以把本地仓库的代码全部推送到远程仓库了。
- 把本地库的内容推送到远程,用git push命令,实际上是把当前分支master推送到远程。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
bash-3.2$ git push -u origin master
此后,每次在本地仓库提交后,只要有必要,就可以使用命令 git push origin master 推送最新修改。
- 第一次连接远程仓库,会出现一个警告信息,这个是提示你验证远程 Git 的 key 的真实身份,我们这里输出 yes ,表示信任此来源的 key。
The authenticity of host 'github.com (192.30.255.113)' 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,192.30.255.113' (RSA) to the list of known hosts.
Counting objects: 906, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (865/865), done.
- 之后就开始同步了
Writing objects: 100% (906/906), 4.86 MiB | 146.00 KiB/s, done.
Total 906 (delta 217), reused 0 (delta 0)
remote: Resolving deltas: 100% (217/217), done.
To github.com:sharkgit1976/study.git* [new branch] master -> master
Branch master set up to track remote branch master from origin.
- 同步完成, 可以看看远程仓库,应该和本地的一样
从远程仓库克隆到本地仓库
现实的情况是:
假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
- 登陆GitHub,创建一个新的仓库,名字叫 sharkgit
和之前在 GitHub 上创建一个新仓库的步骤基本一样,不一样的地方看下图
这里我们填好仓库名和描述信息,我们在下面勾选 Initialize this repository with a README
看此选项下方的说明:这将让您立即将存储库克隆到计算机上。如果您正在导入现有的存储库,请跳过此步骤。
意思是:勾选此选项,GitHub 就会初始化这个仓库,并且创建一个名字为 README.md 的文件。就像是你再把本地执行 git init 命令一样。
这里我没创建成功。原因是创建的仓库名在这个账号中已经存在了
重新修改仓库名为 xiguatian-repository,创建成功
- 克隆到本地
创建成功后,会跳转到这个新仓库的页面
- 点击 Clone or download
- 再在弹出的框里点击 复制 图标,复制克隆的地址
这里可以看到默认是用 ssh 进行克隆的,协议是 git。
当然,也支持 HTTPS 不过会很慢,并且每次连接需要输入用户名和密码。
- 执行命令: git clone
这个命令会把远程仓库克隆到本地,会在你执行命令的目录下,创建一个和远程仓库名字一样的文件夹,也就是以后本地的仓库了
bash-3.2$ git clone git@github.com:sharkgit1976/xiguatian-repository.git
Cloning into 'xiguatian-repository'...
Warning: Permanently added the RSA host key for IP address '192.30.255.112' to the list of known hosts.
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
bash-3.2$ ls
xiguatian-repository
在这个目录下你会看到 README.md 文件和 .git 目录
bash-3.2$ cd xiguatian-repository/
bash-3.2$ ls
README.md
bash-3.2$ ls -a
. .. .git README.md
现在克隆完成,你可以在这个本地仓库进行开发,提交到本地仓库。需要的话,把本地仓库的内容推送到远程的仓库。
bash-3.2$ git status
On branch master
Your branch is up-to-date with 'origin/master'.nothing to commit, working tree clean
bash-3.2$ git branch
* master
bash-3.2$ git log
commit c032b977c57cce865b42041a975a32888e4a965f (HEAD -> master, origin/master, origin/HEAD)
Author: sharkgit1976 <dockerhub@163.com>
Date: Wed Oct 18 02:15:55 2017 -0500Initial commit
(END)