Git & GitHub 详细教程 - Go语言中文社区

Git & GitHub 详细教程


Git & GitHub 详细教程

一、Git 简介

Git(读音为/gɪt/。)是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git 经典开发过程

Git 功能特性

一般开发者角度

  1. 从服务器上克隆完整的Git仓库(包括代码和版本信息)到单机上。
  2. 在自己的机器上根据不同的开发目的,创建分支,修改代码。
  3. 在单机上自己创建的分支上提交代码。
  4. 在单机上合并分支。
  5. 把服务器上最新版的代码fetch下来,然后跟自己的主分支合并。
  6. 生成补丁(patch),把补丁发送给主开发者。
  7. 看主开发者的反馈,如果主开发者发现两个一般开发者之间有冲突(他们之间可以合作解决的冲突),就会要求他们先解决冲突,然后再由其中一个人提交。如果主开发者可以自己解决,或者没有冲突,就通过。
  8. 一般开发者之间解决冲突的方法,开发者之间可以使用pull 命令解决冲突,解决完冲突之后再向主开发者提交补丁。

主开发者角度

  1. 查看邮件或者通过其它方式查看一般开发者的提交状态。
  2. 打上补丁,解决冲突(可以自己解决,也可以要求开发者之间解决以后再重新提交,如果是开源项目,还要决定哪些补丁有用,哪些不用)。
  3. 向公共服务器提交结果,然后通知所有开发人员。

Git 的优势

  1. 适合分布式开发,强调个体。
  2. 公共服务器压力和数据量都不会太大。
  3. 任意两个开发者之间可以很容易的解决冲突。
  4. 本地的版本库,大部分操作在本地完成,不需要联网即可完成版本控制。
  5. 完整性保证,不同的版本有不同的哈希值保存。
  6. 版本库中会尽可能添加数据而不是删除或修改数据。
  7. 分支操作快捷流畅,分支是对不同快照的不同指针,速度快、灵活。
  8. Git 与 Linux 命令全面兼容

Git 工作流程

一般工作流程如下:

  1. 从远程仓库中克隆 Git 资源作为本地仓库。
  2. 从本地仓库中checkout代码然后进行代码修改。
  3. 在提交前先将代码提交到暂存区。
  4. 提交修改。提交到本地仓库。本地仓库中保存修改的各个历史版本。
  5. 在修改完成后,需要和团队成员共享代码时,可以将代码push到远程仓库。

二、Git 安装

Linux 上安装 Git

yum -y install git

Mac 上安装 Git

一是使用homebrew安装git。

brew install git

二是去git官网下载文件手动安装。

Windows 上安装 Git

Git 安装步骤
安装在非中文目录下,多数步骤使用默认。
调整 path 环境变量的三个选项,建议选择 use Git from Git Bash only。完全不修改 path 环境变量,仅在 Git Bash 中使用 Git。
library 本地库和远程库的连接方式,使用默认值。
行末换行符转换方式,继续使用默认值。
使用 Git 命令的默认终端,继续使用默认值。

测试git是否安装成功

$ git --version
git version 2.22.0.windows.1

附1: Git 结构

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就可以一次性把暂存区的所有修改提交到分支。

三、Git 基本操作

1. 创建版本库

先创建一个空目录:

$ mkdir gitest
$ cd gitest/
$ pwd
D:/WorkSpaces/gittest/.git/

通过git init命令把这个目录变成可以管理的仓库:

$ git init
Initialized empty Git repository in D:/WorkSpaces/gittest/.git/

2. 设置签名

作用:区分不同开发人员的身份。
形式:用户名,邮箱。
注:登录远程库的账号、密码和这个没有任何关系。

# 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文件内

3. 添加文件到本地库

编写一个readme.md文件,内容如下:

Git is a version control system.
Git is free software.

注:文件必须放在gitest目录(或其子目录)下,因为这是一个Git仓库,在仓库对应的目录下才能找到这个文件。

添加到暂存区:git add [file name]

用法:git add file_name1 file_name2 …

git add readme.md 

状态查看:git status

显示当前所在分支,本地库提交历史,当前可提交文件。功能类似于查看警告,查看当前本地库状态,会有提示信息。查看工作区、暂存区状态

$ 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 “commit message” [file name]

用法: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

4. 修改文件

我们之前成功地添加并提交了一个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被修改过了,但还没有准备提交的修改。

5. 比较文件差异

**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.

6. 查看历史版本

当然了,在实际工作中,我们脑子里怎么可能记得一个几千行的文件每次都改了什么内容,不然要版本控制系统干什么。版本控制系统肯定有某个命令可以告诉我们历史记录,在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 GPLcommit id369ebbd...,于是就可以指定回到未来的某个版本:

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 reflog

在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 addgit 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 commitgit 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_rsaid_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_rsaid_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

第2步:登陆GitHub,打开“Account settings”,“SSH Keys”页面:

然后,点“Add SSH Key”,填上任意Title,在Key文本框里粘贴id_rsa.pub文件的内容:

img

mage-20180319113227

点“Add Key”,你就应该看到已经添加的Key:

img

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”按钮,创建一个新的仓库:

img

mage-20180319113715

在Repository name填入gitest,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库:

img

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页面中看到远程库的内容已经和本地一模一样:

img

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

img

mage-20180319142804

我们勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。创建完毕后,可以看到README.md文件:

img

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!

img

0

分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。

但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。

创建与合并分支

版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向mastermaster才是指向提交的,所以,HEAD指向的就是当前分支。

一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:

img

-

每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长:

img

it-

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:

img

-

你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!

不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:

img

-

假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:

img

-

所以Git合并分支也很快!就改改指针,工作区内容也不变!

合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:

img

-

真是太神奇了,你看得出来有些提交是通过分支完成的吗?

img

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

img

-

现在,我们把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分支各自都分别有新的提交,变成了这样:

img

-

这种情况下,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分支变成了下图所示:

img

-

用带参数的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
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢