社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Git(读音为/gɪt/。)是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
一般工作流程如下:
yum -y install git
一是使用homebrew安装git。
brew install git
二是去git官网下载文件手动安装。
Git 安装步骤
安装在非中文目录下,多数步骤使用默认。
调整 path 环境变量的三个选项,建议选择 use Git from Git Bash only。完全不修改 path 环境变量,仅在 Git Bash 中使用 Git。
library 本地库和远程库的连接方式,使用默认值。
行末换行符转换方式,继续使用默认值。
使用 Git 命令的默认终端,继续使用默认值。
$ git --version
git version 2.22.0.windows.1
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
**工作区(Working Directory):**写代码,添加文件、修改文件的地方
在电脑里能看到的目录,比如gitest
文件夹就是一个工作区:
**本地库(Repository):**储存每次提交的历史版本
工作区有一个隐藏目录.git
,这个不算工作区,而是Git的本地库。
**暂存区(Stage):**临时存储打算提交尚未提交的文件
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
工作过程:新建文件 在工作区,使用 git add 添加到暂存区,使用 git commit 提交到本地库
把文件往Git版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库时,Git自动为我们创建了唯一一个master
分支,所以,现在,git commit
就是往master
分支上提交更改。
git add
命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit
就可以一次性把暂存区的所有修改提交到分支。
先创建一个空目录:
$ mkdir gitest
$ cd gitest/
$ pwd
D:/WorkSpaces/gittest/.git/
通过git init
命令把这个目录变成可以管理的仓库:
$ git init
Initialized empty Git repository in D:/WorkSpaces/gittest/.git/
作用:区分不同开发人员的身份。
形式:用户名,邮箱。
注:登录远程库的账号、密码和这个没有任何关系。
# git config
# 项目级别/仓库级别:仅在当前本地库范围内有效。
# 设置用户名和邮箱:
git config user.name evanstark
git config user.email evanstark@qq.com
# git config --global
# 系统用户级别:登录当前操作系统用户范围。
# 设置用户名和邮箱:
git config --global user.name evanstark
git config --global user.email evanstark@qq.com
级别优先级
就近原则:项目级别优先于系统用户级别,二者都有时采用项目级别签名
如果只有系统用户级别签名,就以系统用户级别签名为准
不允许二者都没有的情况
签名信息保存位置
项目级别:
.git文件夹下的config文件内
系统级别:
C:/Users/administrator文件夹下 的.gitconfig文件内
编写一个readme.md
文件,内容如下:
Git is a version control system.
Git is free software.
注:文件必须放在gitest
目录(或其子目录)下,因为这是一个Git仓库,在仓库对应的目录下才能找到这个文件。
用法:git add file_name1 file_name2 …
git add readme.md
显示当前所在分支,本地库提交历史,当前可提交文件。功能类似于查看警告,查看当前本地库状态,会有提示信息。查看工作区、暂存区状态
$ git status -s
A readme.md
$ git status
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
# git rm --cached <file>将提交到暂存区的文件删除,只剩下工作区文件
new file: readme.md
用法:git commit -m "文件描述"
$ git commit -m "new creating a readme file"
[master (root-commit) 05b80af] new creating a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.md
我们之前成功地添加并提交了一个readme.md文件,现在尝试修改一下文件内容:
Git is a distributed version control system.
Git is free software.
现在运行git status
命令查看结果:
$ 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: readme.md
no changes added to commit (use "git add" and/or "git commit -a")
git status
命令可以让我们时刻掌握仓库当前的状态,上面的命令告诉我们,readme.md被修改过了,但还没有准备提交的修改。
**git diff [文件名]:**将工作区中的文件和暂存区进行比较
**git diff [本地库中历史版本] [文件名]:**将工作区中的文件和本地库历史记录比较
不带文件名比较多个文件
虽然Git告诉我们readme.md被修改了,但是我们想看看具体修改了什么内容。这时候需要用git diff
命令查看:
$ git diff readme.md
diff --git a/readme.md b/readme.md
index 46d49bf..9247db6 100644
--- a/readme.md
+++ b/readme.md
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
git diff
就是查看差异之处,通过该命令我们可以清楚地看到修改了哪些内容,显示的格式是Unix通用的diff格式。
第二次提交readme.md文件:
$ git add readme.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.md
$ git commit -m "add distributed"
[master b99e891] add distributed
1 file changed, 1 insertion(+), 1 deletion(-)
$ git status
On branch master
nothing to commit, working tree clean
再次修改readme.md文件,内容如下:
Git is a distributed version control system.
Git is free software distributed under the GPL.
然后尝试提交:
$ git add readme.md
$ git commit -m "append GPL"
[master 49d0f9b] append GPL
1 file changed, 1 insertion(+), 1 deletion(-)
像这样,你不断对文件进行修改,然后不断提交修改到版本库里,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为commit
。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个commit
恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
现在,我们回顾一下readme.md文件一共有几个版本被提交到Git仓库里了:
版本1:new creating a readme file
Git is a version control system.
Git is free software.
版本2:add distributed
Git is a distributed version control system.
Git is free software.
版本3:append GPL
Git is a distributed version control system.
Git is free software distributed under the GPL.
当然了,在实际工作中,我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容,不然要版本控制系统干什么。版本控制系统肯定有某个命令可以告诉我们历史记录,在Git中,我们用git log
命令查看:
$ git log
commit 369ebbd20023d8a1257e2825a5f9521c04576cf6 (HEAD -> master)
Author: evan <evanstark@qq.com>
Date: Thu Oct 10 01:52:53 2019 +0800
append GPL
commit f298d924e1ba99e88ef544b8d9de1ef001d69a10
Author: evan <evanstark@qq.com>
Date: Thu Oct 10 00:59:43 2019 +0800
add distributed
commit 05b80af25d27130a16fc93c12f4cfa5cc1d3feb3
Author: evan <evanstark@qq.com>
Date: Wed Oct 9 16:45:51 2019 +0800
new creating a readme file
git log
命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是append GPL
,上一次是add distributed
,最早的一次是new creating a readme file
。
查看简洁历史版本
git log --pretty=oneline
$ git log --pretty=oneline
369ebbd20023d8a1257e2825a5f9521c04576cf6 (HEAD -> master) append GPL
f298d924e1ba99e88ef544b8d9de1ef001d69a10 add distributed
05b80af25d27130a16fc93c12f4cfa5cc1d3feb3 new creating a readme file
git log --oneline
$ git log --oneline
369ebbd (HEAD -> master) append GPL
f298d92 add distributed
05b80af new creating a readme file
基于索引值操作[推荐]
git reset --hard [局部索引值]
git reset --hard a6ace91
使用^符号:只能后退
git reset --hard HEAD^
# 注:一个^表示后退一步,n 个表示后退 n 步
使用~符号:只能后退
git reset --hard HEAD~n
# 注:表示后退 n 步
reset 命令的三个参数对比
–soft 参数
仅仅在本地库移动 HEAD 指针
–mixed 参数
在本地库移动 HEAD 指针
重置暂存区
–hard 参数
在本地库移动 HEAD 指针
重置暂存区
重置工作区
删除文件并找回
前提:删除前,文件存在时的状态提交到了本地库。
操作:git reset --hard [指针位置]
删除操作已经提交到本地库:指针位置指向历史记录
删除操作尚未提交到本地库:指针位置使用 HEAD
如果把readme.md回退到上一个版本,也就是“add distributed”的那个版本,怎么做呢?
首先,Git必须知道当前版本是哪个版本,在Git中,用HEAD
表示当前版本,也就是最新的提交49d0f9b9bcf30069a3b0ed91ecf5397738a940e6
(注意我的提交ID和你的肯定不一样),上一个版本就是HEAD^
,上上一个版本就是HEAD^^
,当然往上100个版本写100个^
比较容易数不过来,所以写成HEAD~100
。
现在,我们要把当前版本“append GPL”回退到上一个版本“add distributed”,就可以使用git reset
命令:
$ git reset --hard HEAD^
HEAD is now at b99e891 add distributed
看看readme.md的内容是不是版本add distributed
:
$ cat readme.md
Git is a distributed version control system.
Git is free software.
果然。
还可以继续回退到上一个版本wrote a readme file
,不过且慢,然我们用git log
再看看现在版本库的状态:
$ git log
commit b99e891f22fcf8cfcdd3aef737a6b7681b100976 (HEAD -> master)
Author: junxi <xinlei3166@126.com>
Date: Fri Mar 16 14:43:09 2018 +0800
add distributed
commit f2bc5b8abee58de182cce051b3e0e6d1c7431a22
Author: junxi <xinlei3166@126.com>
Date: Fri Mar 16 14:04:37 2018 +0800
new creating a readme file
最新的那个版本append GPL
已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办?
办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个append GPL
的commit id
是369ebbd...
,于是就可以指定回到未来的某个版本:
git reset --hard 369ebbd
HEAD is now at 369ebbd append GPL
版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
再小心翼翼地看看readme.md的内容:
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
果然,回到最新了。
Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD
指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL
:
然后顺便把工作区的文件更新了。所以你让HEAD
指向哪个版本号,你就把当前版本定位在哪。
现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的commit id
怎么办?
在Git中,总是有后悔药可以吃的。当你用$ git reset --hard HEAD^
回退到add distributed
版本时,再想恢复到append GPL
,就必须找到append GPL
的commit id。Git提供了一个命令git reflog
用来记录你的每一次命令:
$ git reflog
369ebbd (HEAD -> master) HEAD@{0}: reset: moving to 369ebbd
f298d92 HEAD@{1}: reset: moving to f298d92
369ebbd (HEAD -> master) HEAD@{2}: reset: moving to 369ebbd
f298d92 HEAD@{3}: reset: moving to HEAD^
369ebbd (HEAD -> master) HEAD@{4}: commit: append GPL
f298d92 HEAD@{5}: commit: add distributed
05b80af HEAD@{6}: commit (initial): new creating a readme file
# HEAD@{移动到当前版本需要多少步}
小结:
HEAD
指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id
。git log
可以查看提交历史,以便确定要回退到哪个版本。git reflog
查看命令历史,以便确定要回到未来的哪个版本。现在,假定你已经完全掌握了暂存区的概念。下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
为什么说Git管理的是修改,而不是文件呢?我们还是做实验。第一步,对readme.md做一个修改,比如加一行内容:
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
然后添加:
$ git add readme.md
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.md
然后,再修改readme.md:
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
提交:
$ git commit -m "git tracks changes"
[master fd42bc4] git tracks changes
1 file changed, 1 insertion(+)
提交后,再看看状态:
$ 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: readme.md
no 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.md
命令可以查看工作区和版本库里面最新版本的区别:
$ git diff HEAD -- readme.md
diff --git a/readme.md b/readme.md
index 76d770f..a9c5755 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,4 @@
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
-Git tracks changes.
+Git tracks changes of files.
可见,第二次修改确实没有被提交。
那怎么提交第二次修改呢?你可以继续git add
再git commit
,也可以别着急提交第一次修改,先git add
第二次修改,再git commit
,就相当于把两次修改合并后一块提交了:
第一次修改 -> git add
-> 第二次修改 -> git add
-> git commit
好,现在,把第二次修改提交了,然后开始小结。
小结:
现在,你又理解了Git是如何跟踪修改的,每次修改,如果不add
到暂存区,那就不会加入到commit
中。
自然,你是不会犯错的。不过现在是凌晨两点,你正在赶一份工作报告,你在readme.md
中添加了一行:
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN.
在你准备提交前,一杯咖啡起了作用,你猛然发现了“stupid boss”可能会让你丢掉这个月的奖金!
既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状态。如果用git status
查看一下:
$ 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: readme.md
no changes added to commit (use "git add" and/or "git commit -a")
你可以发现,Git会告诉你,git checkout -- file
可以丢弃工作区的修改:
$ git checkout -- readme.md
命令git checkout -- readme.md
意思就是,把readme.md
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.md
自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.md
已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
现在,看看readme.md
的文件内容:
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
文件内容果然复原了。
git checkout -- file
命令中的--
很重要,没有--
,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout
命令。
现在假定是凌晨3点,你不但写了一些胡话,还git add
到暂存区了:
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN.
$ git add readme.md
庆幸的是,在commit
之前,你发现了这个问题。用git status
查看一下,修改只是添加到了暂存区,还没有提交:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.md
Git同样告诉我们,用命令git reset HEAD file
可以把暂存区的修改撤销掉(unstage),重新放回工作区:
$ git reset HEAD readme.md
Unstaged changes after reset:
M readme.md
git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD
时,表示最新的版本。
再用git status
查看一下,现在暂存区是干净的,工作区有修改:
$ 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: readme.md
no changes added to commit (use "git add" and/or "git commit -a")
还记得如何丢弃工作区的修改吗?
$ git checkout -- readme.md
$ git status
On branch master
nothing to commit, working tree clean
$ cat readme.md
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks change
ok.
现在,假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得版本回退一节吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把“stupid boss”提交推送到远程版本库,你就真的惨了……
小结
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file
,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。
在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件test.txt到Git并且提交:
$ cat test.txt
new createing a test.txt file.
$ git add test.txt
$ git commit -m "add test.txt"
[master 6aade49] add test.txt
1 file changed, 2 insertions(+)
create mode 100644 test.txt
一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用rm
命令删了:
$ rm test.txt
这个时候,Git知道你删除了文件,因此,工作区和版本库就不一致了,git status
命令会立刻告诉你哪些文件被删除了:
$ 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: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm
删掉,并且git commit
:
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master 3f1febc] remove test.txt
1 file changed, 2 deletions(-)
delete mode 100644 test.txt
现在,文件就从版本库中被删除了。
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
$ git checkout -- test.txt
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
小结
命令git rm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的问题了。
可是有用过集中式版本控制系统SVN的童鞋会站出来说,这些功能在SVN里早就有了,没看出Git有什么特别的地方。
没错,如果只是在一个仓库里管理文件历史,Git和SVN真没啥区别。为了保证你现在所学的Git物超所值,将来绝对不会后悔,本章开始介绍Git的杀手级功能之一(注意是之一,也就是后面还有之二,之三……):远程仓库。
Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。
你肯定会想,至少需要两台机器才能玩远程库不是?但是我只有一台电脑,怎么玩?
其实一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下。不过,现实生活中是不会有人这么傻的在一台电脑上搞几个远程库玩,因为一台电脑上搞几个远程库完全没有意义,而且硬盘挂了会导致所有库都挂掉,所以我也不告诉你在一台电脑上怎么克隆多个仓库。
实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
完全可以自己搭建一台运行Git的服务器,不过现阶段,为了学Git先搭个服务器绝对是小题大作。好在这个世界上有个叫GitHub的神奇的网站,从名字就可以看出,这个网站就是提供Git仓库托管服务的,所以,只要注册一个GitHub账号,就可以免费获得Git远程仓库。
在继续阅读后续内容前,请自行注册GitHub账号。由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:
第1步:创建SSH Key。在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "xxx@126.com"
$ ll ~/.ssh/
total 56
-rw------- 1 junxi staff 1679 12 11 09:51 id_rsa
-rw-r--r-- 1 junxi staff 396 12 11 09:51 id_rsa.pub
如果一切顺利的话,可以在用户主目录里找到.ssh
目录,里面有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key的秘钥对,id_rsa
是私钥,不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。
第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:
然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容:
mage-20180319113227
点“Add Key”,你就应该看到已经添加的Key:
mage-20180319113324
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。
如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。
确保你拥有一个GitHub账号后,我们就即将开始远程仓库的学习。
小结
“有了远程仓库,妈妈再也不用担心我的硬盘了。”——Git点读机
现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。
首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库:
mage-20180319113715
在Repository name填入gitest
,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库:
mage-20180319113746
目前,在GitHub上的这个gitest
仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
现在,我们根据GitHub的提示,在本地的gitest
仓库下运行命令:
$ pwd
/Users/junxi/gitest
$ git remote add origin git@github.com:junxi3166/gitest.git
请千万注意,把上面的junxi3166
替换成你自己的GitHub账户名,否则,你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH Key公钥不在我的账户列表中。
添加后,远程库的名字就是origin
,这是Git默认的叫法,也可以改成别的,但是origin
这个名字一看就知道是远程库。
下一步,就可以把本地库的所有内容推送到远程库上:
$ git push -u origin master
Counting objects: 20, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (20/20), 1.63 KiB | 837.00 KiB/s, done.
Total 20 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:junxi3166/gitest.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
把本地库的内容推送到远程,用git push
命令,实际上是把当前分支master
推送到远程。
由于远程库是空的,我们第一次推送master
分支时,加上了-u
参数,Git不但会把本地的master
分支内容推送的远程新的master
分支,还会把本地的master
分支和远程的master
分支关联起来,在以后的推送或者拉取时就可以简化命令。
推送成功后,可以立刻在GitHub页面中看到远程库的内容已经和本地一模一样:
mage-20180319142156
从现在起,只要本地作了提交,就可以通过命令:
$ git push origin master
把本地master
分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库!
SSH警告
当你第一次使用Git的clone
或者push
命令连接GitHub时,会得到一个警告:
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)?
这是因为Git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时,需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes
回车即可。
Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:
Warning: Permanently added 'github.com,13.250.177.223' (RSA) to the list of known hosts.
这个警告只会出现一次,后面的操作就不会有任何警告了。
如果你实在担心有人冒充GitHub服务器,输入yes
前可以对照GitHub的RSA Key的指纹信息是否与SSH连接给出的一致。
小结
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git
;
关联后,使用命令git push -u origin master
第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master
推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
上次我们讲了先有本地库,后有远程库的时候,如何关联远程库。
现在,假设我们从零开发,那么最好的方式是先创建远程库,然后,从远程库克隆。
首先,登陆GitHub,创建一个新的仓库,名字叫gitskills
:
mage-20180319142804
我们勾选Initialize this repository with a README
,这样GitHub会自动为我们创建一个README.md
文件。创建完毕后,可以看到README.md
文件:
mage-20180319142831
现在,远程库已经准备好了,下一步是用命令git clone
克隆一个本地库:
$ git clone git@github.com:junxi3166/gitskills.git
Cloning into 'gitskills'...
Warning: Permanently added the RSA host key for IP address '13.229.188.59' 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.
$ cd gitskills/
$ ls
README.md
注意把Git库的地址换成你自己的,然后进入gitskills
目录看看,已经有README.md
文件了。
如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
你也许还注意到,GitHub给出的地址不止一个,还可以用https://github.com/junxi3166/gitskills.git
这样的地址。实际上,Git支持多种协议,默认的git://
使用ssh,但也可以使用https
等其他协议。
使用https
除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh
协议而只能用https
。
小结
要克隆一个仓库,首先必须知道仓库的地址,然后使用git clone
命令克隆。
Git支持多种协议,包括https
,但通过ssh
支持的原生git
协议速度最快。
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN!
0
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
一开始的时候,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
-
每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长:
it-
当我们创建新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
-
你看,Git创建一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
-
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
-
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
-
真是太神奇了,你看得出来有些提交是通过分支完成的吗?
it-
下面开始实战。
首先,我们创建dev
分支,然后切换到dev
分支:
$ pwd
/Users/junxi/gitskills
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
然后,用git branch
命令查看当前分支:
$ git branch
* dev
master
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
然后,我们就可以在dev
分支上正常提交,比如对README.md
做个修改,加上一行:
Creating a new branch is quick.
然后提交:
$ git add README.md
$ git commit -m "branch test"
[dev be2e7c9] branch test
1 file changed, 1 insertion(+)
现在,dev
分支的工作完成,我们就可以切换回master
分支:
$ git checkout master
Switched to branch 'master'
切换回master
分支后,再查看一下README.md文件,刚才添加的内容不见了!因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
$ cat README.md
# gitskills
gitskills
-
现在,我们把dev
分支的工作成果合并到master
分支上:
$ git merge dev
Updating 09f920d..be2e7c9
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)
git merge
命令用于合并指定分支到当前分支。合并后,再查看README.md
的内容,就可以看到,和dev
分支的最新提交是完全一样的。
$ cat README.md
# gitskills
gitskills
Creating a new branch is quick.
注意到上面的Fast-forward
信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
$ git branch -d dev
Deleted branch dev (was be2e7c9).
删除后,查看branch
,就只剩下master
分支了:
$ git branch
* master
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
小结
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
准备新的feature1
分支,继续我们的新分支开发:
$ git checkout -b feature1
Switched to a new branch 'feature1'
修改README.md
最后一行,改为:
Creating a new branch is quick AND simple.
在feature1
分支上提交:
$ git add README.md
$ git commit -m "AND simple"
[feature1 3a8b438] AND simple
1 file changed, 1 insertion(+), 1 deletion(-)
切换到master
分支:
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Git还会自动提示我们当前master
分支比远程的master
分支要超前1个提交。
在master
分支上把README.md
文件的最后一行改为:
Creating a new branch is quick & simple.
提交:
$ git add README.md
$ git commit -m "& simple"
[master 23307ae] & simple
1 file changed, 1 insertion(+), 1 deletion(-)
现在,master
分支和feature1
分支各自都分别有新的提交,变成了这样:
-
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
$ git merge feature1
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
果然冲突了!Git告诉我们,README.md
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
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: README.md
no changes added to commit (use "git add" and/or "git commit -a")
我们可以直接查看README.md的内容:
$ cat README.md
# gitskills
gitskills
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存:
Creating a new branch is quick and simple.
再提交:
$ git add README.md
$ git commit -m "conflict fixed"
[master 5f3dff6] conflict fixed
现在,master
分支和feature1
分支变成了下图所示:
-
用带参数的git log
也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit
* 5f3dff6 (HEAD -> master) conflict fixed
|
| * 3a8b438 (feature1) AND simple
* | 23307ae & simple
|/
* be2e7c9 branch test
* 09f920d (origin/master, origin/HEAD) Initial commit
最后,删除feature1
分支:
$ git branch -d feature1
Deleted branch feature1 (was 3a8b438).
完成。
小结
当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。
用git log --graph
命令可以看到分支合并图。
通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
下面我们实战一下--no-ff
方式的git merge
:
首先,仍然创建并切换dev
分支:
$ git checkout -b dev
修改README.md文件,并提交一个新的commit:
$ # vi README.md
$ git add README.md
$ git commit -m "fenzhi."
现在,我们切换回master
:
$ git checkout master
准备合并dev
分支,请注意--no-ff
参数,表示禁用Fast forward
:
$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
README.md | 1 +
1 file changed, 1 insertion(+)
因为本次合并要创建一个新的commit,所以加上-m
参数,把commit描述写进去。
合并后,我们用git log
看看分支历史:
$ git log --graph --pretty=oneline --abbrev-commit
* 535190d (HEAD -> master) merge with no-ff
|
| * c5e8566 (dev) fenzhi.
|/
* 5f3dff6 conflict fixed
|
| * 3a8b438 AND simple
* | 23307ae & simple
|/
* be2e7c9 branch test
* 09f920d (origin/master, origin/HEAD) Initial commit
可以看到,不使用Fast forward
模式,merge后就像这样:
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/wukong2877/article/details/102490653
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!