diff --git a/.gitignore b/.gitignore index 9f11b75..f32e31a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea/ +.DS_Store diff --git a/Git教程1-Git初级主题和入门.md b/Git教程1-Git初级主题和入门.md new file mode 100644 index 0000000..97f1f6f --- /dev/null +++ b/Git教程1-Git初级主题和入门.md @@ -0,0 +1,1931 @@ +# git 教程1--- git 初级主题和入门 + +本教程面向初学者,没有接触过版本控制概念的读者也可以轻松入门。 + +本教程的特点是边学边练,一点也不觉得枯燥。而且,你所学的Git命令是“充分且必要”的,掌握了这些东西,你就可以通过Git轻松地完成你的工作。 + +同时,本教程的目标是快速教会你Git的基本操作,后续的高级操作和原理,会在后面的教程详细解释。 + +学习本教程的方法是,跟着做,一边做一边思考和理解。光看不练只会转头就忘。 + +另外说明:本篇教程演绎自廖雪峰的 git教程,我在此基础上增加了一些细节的东西,并且会进一步深入的探究 git 的一些原理,这个系列教程会比廖雪峰的更加深入一些,可能会关于一些更有趣的内容,并且注重讲透某些命令的本质原理。 + + + +## 目录和知识点 + +- ssh和https +- git的权限管理 + + + + + +## 为什么要用版本管理工具? + +在进入本教程之前,首先说明一下为什么需要使用版本管理工具。 + +如果只有一个文件,那么每次修改后保存一下就可以。 + +如果有不满足于单纯保存,想知道每次修改了哪些内容,可以每次修改都复制一份并做好命名,比如有一个文件初始名字叫 hello.txt, 第一次修改后就拷贝一份叫做 hello_v2.txt, 第二次修改就叫做 hello_v3.txt.... + +如果有两个文件,hello.txt 和 happy.txt, 不仅仅想知道每次修改了哪些内容,还需要知道每次修改后现在两个文件的内容,那么需要建立一个文件夹,把两个文件放进去,假设名字叫 my_file。第一次修改了 hello.txt, 那么把文件夹复制一份,改名为 my_file_v2, 第二次修改了 happy.txt, 又把文件夹复制一份,改名为 my_file_v3.... + +如果这两个文件夹几个协作的人都需要同时编辑,那么你还要给每个版本后面加上修改人的名字,再继续复制文件... + +想象一下,在多人协作场景下,为了控制更加精细,你需要知道每一次谁修改了哪里,那么拷贝文件夹的方式可能无法胜任了!! + +如果一个项目文件有成千个,那么每次拷贝文件就愈发不可能了,这个时候,版本管理工具就派上用场了! + +而当下最经典最流行的版本工具就非 Git 莫属了。 + + + +## 安装 Git + +Git可以在Linux、Unix、Mac和Windows这几大平台上跨平台运行。 + +要使用Git,第一步当然是安装Git了。可以访问 Git官网,选择自己的系统进行安装。 + +另外补充的是,MacOS 和 Linux 个人发行版应该是默认安装了 Git的,可以在终端使用 git --version 查看。 + +在这里说明一下,git 的命令风格和其他 shell 命令风格非常相似,使用 git 命令以 git SUB_COMMAND 开始,然后指定各种参数。 + + + +## 创建版本库 + +什么是版本库呢?版本库又名仓库,英文名**repository**,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。 + +所以,创建一个版本库非常简单,首先,选择一个合适的地方,创建一个空目录并且进入这个目录: + +``` +$ mkdir learngit && cd learngit +``` + +第二步,通过`git init`命令把这个目录变成Git可以管理的仓库: + +``` +$ git init +Initialized empty Git repository in /Users/liaoxuefeng/learngit/.git/ +``` + +瞬间Git就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个`.git`的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。 + +如果你没有看到`.git`目录,那是因为这个目录默认是隐藏的,用`ls -ah`命令就可以看见。(MacOS用户可以使用 `command + shift + . `在 finder 下查看隐藏文件) + +虽然选择一个已经有文件的目录也是可以创建Git仓库的,但是在学习阶段还是先从空目录开始进行练习。 + +当然,也可以在别人的仓库上修改,使用到 git clone 命令,我们后续会讲到。 + +## 把文件添加到版本库 + +首先这里再明确一下,所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词“Linux”,在第8行删了一个单词“Windows”。而图片、视频这些二进制文件,虽然也能由版本控制系统管理,但没法跟踪文件的变化,只能把二进制文件每次改动串起来,也就是只知道图片从100KB改成了120KB,但到底改了啥,版本控制系统不知道,也没法知道。 + +如果要真正使用版本控制系统,就要以纯文本方式编写文件。比如记事本文件 *.txt,或者markdown文本,或者各种代码文件,比如 *.c, *.java, *.py, *.html 。 + +现在我们编写一个`readme.txt`文件,内容如下: + +``` +Git is a version control system. +Git is free software. +``` + +一定要放到 Git 管理的目录下(子目录也行),否则 Git 无法管理其内容。 + +和把大象放到冰箱需要3步相比,把一个文件放到Git仓库只需要两步。 + +第一步,用命令`git add`告诉Git,把文件添加到仓库: + +``` +$ git add readme.txt +``` + +执行上面的命令,没有任何显示,这就对了,Unix的哲学是“没有消息就是好消息”,说明添加成功。 + +第二步,用命令`git commit`告诉Git,把文件提交到仓库: + +``` +$ git commit -m "wrote a readme file" +[master (root-commit) eaadf4e] wrote a readme file + 1 file changed, 2 insertions(+) + create mode 100644 readme.txt +``` + +简单解释一下`git commit`命令,`-m`后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。 + +`git commit`命令执行成功后会告诉你,`1 file changed`:1个文件被改动(我们新添加的readme.txt文件);`2 insertions`:插入了两行内容(readme.txt有两行内容)。 + +为什么Git添加文件需要`add`,`commit`一共两步呢?因为`commit`可以一次提交很多文件,所以你可以多次`add`不同的文件,比如: + +``` +$ git add file1.txt +$ git add file2.txt file3.txt +$ git commit -m "add 3 files." +``` + +我们已经成功地添加并提交了一个readme.txt文件,现在,是时候继续工作了,于是,我们**继续修改**readme.txt文件,改成如下内容: + +``` +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 ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt + +no changes added to commit (use "git add" and/or "git commit -a") +``` + +`git status`命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,`readme.txt`被修改过了,但还没有准备提交的修改。 + +虽然Git告诉我们`readme.txt`被修改了,但如果能看看具体修改了什么内容,自然是很好的。比如你玩了两天,再回来接着写代码,已经记不清上次怎么修改的`readme.txt`,所以,需要用`git diff`这个命令看看: + +``` +$ git diff readme.txt +diff --git a/readme.txt b/readme.txt +index 46d49bf..9247db6 100644 +--- a/readme.txt ++++ b/readme.txt +@@ -1,2 +1,2 @@ +-Git is a version control system. ++Git is a distributed version control system. + Git is free software. +``` + +`git diff`顾名思义就是查看difference,显示的格式正是Unix通用的diff格式,可以从上面的命令输出看到,我们在第一行添加了一个`distributed`单词。 + +知道了对`readme.txt`作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步,第一步是`git add`: + +``` +$ git add readme.txt +``` + +同样没有任何输出。在执行第二步`git commit`之前,我们再运行`git status`看看当前仓库的状态: + +``` +$ git status +On branch master +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + modified: readme.txt +``` + +`git status`告诉我们,将要被提交的修改包括`readme.txt`,下一步,就可以放心地提交了: + +``` +$ git commit -m "add distributed" +[master e475afc] add distributed + 1 file changed, 1 insertion(+), 1 deletion(-) +``` + +提交后,我们再用`git status`命令看看仓库的当前状态: + +``` +$ git status +On branch master +nothing to commit, working tree clean +``` + +Git告诉我们当前没有需要提交的修改,而且,工作目录是干净(working tree clean)的。 + + + +**总结一下:** + +1. 如果新创建了一个文件,使用 `git add NEW_FILE_NAME` 把文件加入版本控制中。如果修改了一个已经存在于版本库中的文件,也需要使用 `git add UPDATED_FILE_NAME` 把修改加入版本控制中。 +2. 使用 `git commit` 命令提交刚才的修改内容。 + + + +## 版本回退 + +现在,你已经学会了修改文件,然后把修改提交到Git版本库,现在,再练习一次,修改readme.txt文件如下: + +``` +Git is a distributed version control system. +Git is free software distributed under the GPL. +``` + +然后尝试提交: + +``` +$ git add readme.txt +$ git commit -m "append GPL" +[master 1094adb] append GPL + 1 file changed, 1 insertion(+), 1 deletion(-) +``` + +像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为`commit`。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个`commit`恢复,然后继续工作,而不是把几个月的工作成果全部丢失。 + +现在,我们回顾一下`readme.txt`文件一共有几个版本被提交到Git仓库里了: + +版本1:wrote 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`命令查看: + +> tip:git log 展示的提交历史默认顺序是从最新的到最早的,可以指定参数修改这一行为 + +``` +$ git log +commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) +Author: Michael Liao +Date: Fri May 18 21:06:15 2018 +0800 + + append GPL + +commit e475afc93c209a690c39c13a46716e8fa000c366 +Author: Michael Liao +Date: Fri May 18 21:03:36 2018 +0800 + + add distributed + +commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 +Author: Michael Liao +Date: Fri May 18 20:59:18 2018 +0800 + + wrote a readme file +``` + +`git log`命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是`append GPL`,上一次是`add distributed`,最早的一次是`wrote a readme file`。 + +如果嫌输出信息太多,看得眼花缭乱的,可以试试加上`--pretty=oneline`参数: + +``` +$ git log --pretty=oneline +1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL +e475afc93c209a690c39c13a46716e8fa000c366 add distributed +eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file +``` + +需要友情提示的是,你看到的一大串类似`1094adb...`的是`commit id`(版本号),和SVN不一样,Git的`commit id`不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的`commit id`和我的肯定不一样,以你自己的为准。为什么`commit id`需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。 + +每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线: + +![git-log-timeline](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0.jpeg) + +好了,现在我们启动时光穿梭机,准备把`readme.txt`回退到上一个版本,也就是`add distributed`的那个版本,怎么做呢? + +首先,Git必须知道当前版本是哪个版本,在Git中,用`HEAD`表示当前版本,也就是最新的提交`1094adb...`(注意我的提交ID和你的肯定不一样),上一个版本就是`HEAD^`,上上一个版本就是`HEAD^^`,当然往上100个版本写100个`^`比较容易数不过来,所以写成`HEAD~100`。也可以使用HEAD@{N}来表示当前指针的上N次历史记录(我更习惯的方式)。 + +现在,我们要把当前版本`append GPL`回退到上一个版本`add distributed`,就可以使用`git reset`命令: + +``` +$ git reset --hard HEAD^ +HEAD is now at e475afc add distributed +``` + +`--hard`参数有啥意义?这个后面再讲,现在你先放心使用。 + +> tip: --hard 中间不能有空格,否则命令无效 + +看看`readme.txt`的内容是不是版本`add distributed`: + +``` +$ cat readme.txt +Git is a distributed version control system. +Git is free software. +``` + +果然被还原了。 + +还可以继续回退到上一个版本`wrote a readme file`,不过且慢,然我们用`git log`再看看现在版本库的状态: + +``` +$ git log +commit e475afc93c209a690c39c13a46716e8fa000c366 (HEAD -> master) +Author: Michael Liao +Date: Fri May 18 21:03:36 2018 +0800 + + add distributed + +commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 +Author: Michael Liao +Date: Fri May 18 20:59:18 2018 +0800 + + wrote a readme file +``` + +最新的那个版本`append GPL`已经看不到了!好比你从21世纪坐时光穿梭机来到了19世纪,想再回去已经回不去了,肿么办? + +办法其实还是有的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个`append GPL`的`commit id`是`1094adb...`,于是就可以指定回到未来的某个版本: + +``` +$ git reset --hard 1094a +HEAD is now at 83b0afe append GPL +``` + +版本号没必要写全,前几位就可以了(一般至少写前4位就够了,如果前4位不能唯一确定一个提交,多谢几位),Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。 + +再小心翼翼地看看`readme.txt`的内容: + +``` +$ cat readme.txt +Git is a distributed version control system. +Git is free software distributed under the GPL. +``` + +果然,readme 上次提交的内容又回来了。 + +Git的版本回退速度非常快,因为Git在内部有个指向当前版本的`HEAD`指针,当你回退版本的时候,Git仅仅是把HEAD从指向`append GPL`: + +```ascii +┌────┐ +│HEAD│ +└────┘ + │ + └──> ○ append GPL + │ + ○ add distributed + │ + ○ wrote a readme file +``` + +改为指向`add distributed`: + +```ascii +┌────┐ +│HEAD│ +└────┘ + │ + │ ○ append GPL + │ │ + └──> ○ add distributed + │ + ○ wrote a readme file +``` + +然后顺便把工作区的文件更新了(`--hard`的缘故)。所以你让`HEAD`指向哪个版本号,你就把当前版本定位在哪。 + +现在,你回退到了某个版本,关掉了电脑,第二天早上就后悔了,想恢复到新版本怎么办?找不到新版本的`commit id`怎么办? + +在Git中,总是有后悔药可以吃的。当你用`$ git reset --hard HEAD^`回退到`add distributed`版本时,再想恢复到`append GPL`,就必须找到`append GPL`的commit id。Git提供了一个命令`git reflog`用来记录你的每一次命令: + +``` +$ git reflog +e475afc HEAD@{1}: reset: moving to HEAD^ +1094adb (HEAD -> master) HEAD@{2}: commit: append GPL +e475afc HEAD@{3}: commit: add distributed +eaadf4e HEAD@{4}: commit (initial): wrote a readme file +``` + +终于舒了口气,从输出可知,`append GPL`的commit id是`1094adb`,现在,你又可以乘坐时光机回到未来了。 + +> tip: 正如你看到的那样,如果我们想版本回退,那么每一次提交说明都必须尽可能的写清楚,否则就会在代码审查的时候遇到麻烦。 + +**总结一下**: + +- `HEAD`指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令`git reset --hard commit_id`。 +- 穿梭前,用`git log`可以查看提交历史,以便确定要回退到哪个版本。 +- 要重返未来,用`git reflog`查看命令历史,以便确定要回到未来的哪个版本。 + + + +## 工作区和暂存区 + +Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。 + +先来看名词解释。 + +#### 工作区(Working Directory) + +就是你在电脑里能看到的目录,比如我的`learngit`文件夹就是一个工作区: + +![working-dir](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0.png) + +#### 版本库(Repository) + +工作区有一个隐藏目录`.git`,这个不算工作区,而是Git的版本库。 + +Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支`master`,以及指向`master`的一个指针叫`HEAD`。需要说明的是,如果仓库里一次提交都没有的话,是没有 `master `分支的。 + +![git-repo](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231154944.jpeg) + +分支和`HEAD`的概念我们以后再讲。 + +前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的: + +第一步是用`git add`把文件添加进去,实际上就是把文件修改添加到暂存区; + +第二步是用`git commit`提交更改,实际上就是把暂存区的所有内容提交到当前分支。 + +因为我们创建Git版本库时,Git自动为我们创建了唯一一个`master`分支,所以,现在,`git commit`就是往`master`分支上提交更改。 + +你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。 + +俗话说,实践出真知。现在,我们再练习一遍,先对`readme.txt`做个修改,比如加上一行内容: + +``` +Git is a distributed version control system. +Git is free software distributed under the GPL. +Git has a mutable index called stage. +``` + +然后,在工作区新增一个`LICENSE`文本文件(内容随便写)。 + +先用`git status`查看一下状态: + +``` +$ git status +On branch master +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt + +Untracked files: + (use "git add ..." to include in what will be committed) + + LICENSE + +no changes added to commit (use "git add" and/or "git commit -a") +``` + +Git非常清楚地告诉我们,`readme.txt`被修改了,而`LICENSE`还从来没有被添加过,所以它的状态是`Untracked`。 + +现在,使用两次命令`git add`,把`readme.txt`和`LICENSE`都添加后,用`git status`再查看一下: + +``` +$ git status +On branch master +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + new file: LICENSE + modified: readme.txt +``` + +现在,暂存区的状态就变成这样了: + +![git-stage](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231155060.jpeg) + +所以,`git add`命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行`git commit`就可以一次性把暂存区的所有修改提交到分支。 + +``` +$ git commit -m "understand how stage works" +[master e43a48b] understand how stage works + 2 files changed, 2 insertions(+) + create mode 100644 LICENSE +``` + +一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的: + +``` +$ git status +On branch master +nothing to commit, working tree clean +``` + +现在版本库变成了这样,暂存区就没有任何内容了: + +![git-stage-after-commit](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231154958.jpeg) + + + +**总结一下:** + +1. 想要修改需要两步,先把修改放到暂存区,然后再从暂存区提交到版本库。 +2. 暂存区的好处是,修改的文件不一定需要立即提交,可以选择自己想提交的内容进行提交。 + + + + + +## 管理修改 + +现在,假定你已经完全掌握了暂存区的概念。下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。 + +你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。 + +为什么说Git管理的是修改,而不是文件呢?我们还是做实验。第一步,对readme.txt做一个修改,比如加一行内容: + +``` +$ cat readme.txt +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.txt +$ git status +# On branch master +# Changes to be committed: +# (use "git reset HEAD ..." to unstage) +# +# modified: readme.txt +# +``` + +然后,再修改readme.txt: + +``` +$ cat readme.txt +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 519219b] git tracks changes + 1 file changed, 1 insertion(+) +``` + +提交后,再看看状态: + +``` +$ git status +On branch master +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt + +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.txt`命令可以查看工作区和版本库里面最新版本的区别( `--表示后面是一个可选参数,和Linux的shell风格一致`, HEAD表示最后一次提交的指针): + +``` +$ git diff HEAD -- readme.txt +diff --git a/readme.txt b/readme.txt +index 76d770f..a9c5755 100644 +--- a/readme.txt ++++ b/readme.txt +@@ -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` + + + +**总结一下:** + +1. 如上一节所说,修改的步骤是,先加入暂存区,再提交暂存区的内容。如果修改了没有加入暂存区,那么修改的内容就不会被提交。 + + + +### 撤销修改 + +------ + +Git也有很高的容错率,比如,你不小心在`readme.txt`中添加了一行字: + +``` +$ cat readme.txt +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. +adfafadfadfaadadfadfadadfadfadfadadfafds +``` + +在你准备提交前,你发现最后一行字是错误的,这行字是你不小心手压倒键盘了,一串毫无意义的文字。 + +既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状态。如果用`git status`查看一下: + +``` +$ git status +On branch master +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt + +no changes added to commit (use "git add" and/or "git commit -a") +``` + +你可以发现,Git会告诉你,`git checkout -- file`可以丢弃工作区的修改: + +``` +$ git checkout -- readme.txt +``` + +命令`git checkout -- readme.txt`意思就是,把`readme.txt`文件在工作区的修改全部撤销,这里有两种情况: + +一种是`readme.txt`自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态; + +一种是`readme.txt`已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。 + +总之,就是让这个文件回到最近一次`git commit`或`git add`时的状态。 + +现在,看看`readme.txt`的文件内容: + +``` +$ cat readme.txt +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 checkout -- file`命令中的`--`很重要,没有`--`,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到`git checkout`命令。 + + + +现在假定是凌晨3点,你不但写了一些胡话,还`git add`到暂存区了: + +``` +$ cat readme.txt +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. +adfafadfadfaadadfadfadadfadfadfadadfafds. + +$ git add readme.txt +``` + +庆幸的是,在`commit`之前,你发现了这个问题。用`git status`查看一下,修改只是添加到了暂存区,还没有提交: + +``` +$ git status +On branch master +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + modified: readme.txt +``` + +Git同样告诉我们,用命令`git reset HEAD `可以把暂存区的修改撤销掉(unstage),重新放回工作区: + +``` +$ git reset HEAD readme.txt +Unstaged changes after reset: +M readme.txt +``` + +`git reset`命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用`HEAD`时,表示最新的版本。 + +再用`git status`查看一下,现在暂存区是干净的,工作区有修改: + +``` +$ git status +On branch master +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt +``` + +还记得如何丢弃工作区的修改吗? + +``` +$ git checkout -- readme.txt + +$ git status +On branch master +nothing to commit, working tree clean +``` + +整个世界终于清静了! + +> tip: 再复习一下,这个命令 `git checkout -- FILE_NAME` 的解释是:使用暂存区的内容覆盖工作区的内容,而暂存区的内容在使用 git add 命令之前是和版本区最新的内容保持一致的。 +> +> 如何恢复暂存区的内容呢?使用 git reset HEAD 即可取消所有暂存动作,恢复暂存区到最后一次提交内容。 + + + +现在,假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得[版本回退](https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192)一节吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把提交推送到远程版本库,你就真的惨了…… + + + +**小结一下:** + +场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令`git checkout -- file`。 + +场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令`git reset HEAD `,就回到了场景1,第二步按场景1操作。 + +场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考[版本回退](#版本回退)一节,不过前提是没有推送到远程库。 + + + +### 删除文件 + +------ + +在Git中,删除也是一个修改操作,我们实战一下,先添加一个新文件`test.txt`到Git并且提交: + +``` +$ git add test.txt + +$ git commit -m "add test.txt" +[master b84166e] add test.txt + 1 file changed, 1 insertion(+) + 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 ..." to update what will be committed) + (use "git checkout -- ..." 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 d46f35e] remove test.txt + 1 file changed, 1 deletion(-) + delete mode 100644 test.txt +``` + +现在,文件就从版本库中被删除了。 + + 小提示:先手动删除文件,然后使用git rm 和git add效果是一样的。 + +另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本: + +``` +$ git checkout -- test.txt +``` + +`git checkout`其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。 + + 注意:从来没有被添加到版本库就被删除的文件,是无法恢复的! + + + +**小结一下:** + +命令`git rm`用于删除一个文件。 + +如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最后提交的版本,你会丢失**最近一次提交后你修改的内容**。 + + + +## 远程仓库 + +到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的问题了。 + +可是有用过集中式版本控制系统SVN的同学会站出来说,这些功能在SVN里早就有了,没看出Git有什么特别的地方。 + +没错,如果只是在一个仓库里管理文件历史,Git和SVN真没啥区别。为了保证你现在所学的Git物超所值,将来绝对不会后悔,同时为了打击已经不幸学了SVN的同学,本章开始介绍Git的杀手级功能之一:远程仓库。 + +Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。 + +你肯定会想,至少需要两台机器才能玩远程库不是?但是我只有一台电脑,怎么玩? + +其实一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下。不过,现实生活中是不会有人这么傻的在一台电脑上搞几个远程库玩,因为一台电脑上搞几个远程库完全没有意义,而且硬盘挂了会导致所有库都挂掉,所以我也不告诉你在一台电脑上怎么克隆多个仓库。 + +实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。 + +完全可以自己搭建一台运行Git的服务器,不过现阶段,为了学Git先搭个服务器绝对是小题大作。好在这个世界上有个叫[GitHub](https://github.com/)的神奇的网站,从名字就可以看出,这个网站就是提供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 "youremail@example.com" +``` + + + +你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。 + +如果一切顺利的话,可以在用户主目录里找到`.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`文件的内容: + +![github-addkey-1](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231256374.png) + +点“Add Key”,你就应该看到已经添加的Key: + +![github-addkey-2](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231256486.png) + +为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。 + +当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。 + +最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。 + +如果你不想让别人看到Git库,有两个办法,一个是把GitHub公开的仓库变成私有的,这样别人就看不见了。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。 + +确保你拥有一个GitHub账号后,我们就即将开始远程仓库的学习。 + + + + + + + +### 添加远程库 + +------ + +现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,真是一举多得。 + +首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库: + +![github-create-repo-1](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231316252.png) + +在Repository name填入`learngit`,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库: + +![github-create-repo-2](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231316332.png) + +目前,在GitHub上的这个`learngit`仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。 + +现在,我们根据GitHub的提示,在本地的`learngit`仓库下运行命令: + +``` +$ git remote add origin git@github.com:michaelliao/learngit.git +``` + +请千万注意,把上面的`michaelliao`替换成你自己的GitHub账户名,否则,你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的SSH Key公钥不在我的账户列表中。 + +添加后,远程库的名字就是`origin`,这是Git默认的叫法,也可以改成别的,但是`origin`这个名字一看就知道是远程库。 + +下一步,就可以把本地库的所有内容推送到远程库上: + +``` +$ git push -u origin master +Counting objects: 20, done. +Delta compression using up to 4 threads. +Compressing objects: 100% (15/15), done. +Writing objects: 100% (20/20), 1.64 KiB | 560.00 KiB/s, done. +Total 20 (delta 5), reused 0 (delta 0) +remote: Resolving deltas: 100% (5/5), done. +To github.com:michaelliao/learngit.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页面中看到远程库的内容已经和本地一模一样: + +![github-repo](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231316382.png) + +从现在起,只要本地作了提交,就可以通过命令: + +``` +$ git push origin master +``` + +把本地`master`分支的最新修改推送至GitHub,现在,你就拥有了真正的分布式版本库! + +### SSH警告 + +当你第一次使用Git的`clone`或者`push`命令连接GitHub时,会得到一个警告: + +``` +The authenticity of host 'github.com (xx.xx.xx.xx)' can't be established. +RSA key fingerprint is xx.xx.xx.xx.xx. +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' (RSA) to the list of known hosts. +``` + +这个警告只会出现一次,后面的操作就不会有任何警告了。 + +如果你实在担心有人冒充GitHub服务器,输入`yes`前可以对照[GitHub的RSA Key的指纹信息](https://help.github.com/articles/what-are-github-s-ssh-key-fingerprints/)是否与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`: + +![github-init-repo](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231350313.png) + +我们勾选`Initialize this repository with a README`,这样GitHub会自动为我们创建一个`README.md`文件。创建完毕后,可以看到`README.md`文件: + +![github-init-repo-2](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231350313-4134830.png) + +现在,远程库已经准备好了,下一步是用命令`git clone`克隆一个本地库: + +``` +$ git clone git@github.com:michaelliao/gitskills.git +Cloning into 'gitskills'... +remote: Counting objects: 3, done. +remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 3 +Receiving objects: 100% (3/3), done. +``` + +注意把Git库的地址换成你自己的,然后进入`gitskills`目录看看,已经有`README.md`文件了: + +``` +$ cd gitskills +$ ls +README.md +``` + + + +如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。 + +你也许还注意到,GitHub给出的地址不止一个,还可以用`https://github.com/michaelliao/gitskills.git`这样的地址。实际上,Git支持多种协议,默认的`git://`使用ssh,但也可以使用`https`等其他协议。 + +使用`https`除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用`ssh`协议而只能用`https`。 + +> tip: 在下载Git项目上,很多时候都是直接使用https url克隆到本地,当然也可以使用SSH url克隆到本地。 +> +> 这两种方式的主要区别在于:使用https url 克隆对初学者来说会比较方便,但是每次fetch和push代码都需要输入账号和密码,而使用SSH url克隆仓库后,每次push代码都不需要输入账号和密码(push的前提是你拥有推送的权限),如果你想要每次都输入账号密码才能进行fetch和push也可以另外进行设置。 +> +> 使用ssh 协议的url全称,以上面的地址为例是 ssh://git@github.com/michaelliao/gitskills.git + + + +**小结一下:** + +要克隆一个仓库,首先必须知道仓库的地址,然后使用`git clone`命令克隆。 + +Git支持多种协议,包括`https`,但`ssh`协议速度最快。 + + + + + +## 分支管理 + +分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git的时候,另一个你正在另一个平行宇宙里努力学习SVN。 + +如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git又学会了SVN! + +![learn-branches](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231403492.png) + +分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。 + +现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。 + +其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。 + +但Git的分支是与众不同的,无论创建、切换和删除分支,Git在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。 + + + +### 创建与合并分支 + +------ + +在[版本回退](https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192)里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即`master`分支。`HEAD`严格来说不是指向提交,而是指向`master`,`master`才是指向提交的,所以,`HEAD`指向的就是当前分支。 + +一开始的时候,`master`分支是一条线,Git用`master`指向最新的提交,再用`HEAD`指向`master`,就能确定当前分支,以及当前分支的提交点: + +![git-br-initial](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231420863.png) + +每次提交,`master`分支都会向前移动一步,这样,随着你不断提交,`master`分支的线也越来越长。 + +当我们创建新的分支,例如`dev`时,Git新建了一个指针叫`dev`,指向`master`相同的提交,再把`HEAD`指向`dev`,就表示当前分支在`dev`上: + +![git-br-create](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/l.png) + +你看,Git创建一个分支很快,因为除了增加一个`dev`指针,改改`HEAD`的指向,工作区的文件都没有任何变化! + +不过,从现在开始,对工作区的修改和提交就是针对`dev`分支了,比如新提交一次后,`dev`指针往前移动一步,而`master`指针不变: + +![git-br-dev-fd](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/l-20200707231421066.png) + +假如我们在`dev`上的工作完成了,就可以把`dev`合并到`master`上。Git怎么合并呢?最简单的方法,就是直接把`master`指向`dev`的当前提交,就完成了合并: + +![git-br-ff-merge](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231421021.png) + +所以Git合并分支也很快!就改改指针,工作区内容也不变! + +合并完分支后,甚至可以删除`dev`分支。删除`dev`分支就是把`dev`指针给删掉,删掉后,我们就剩下了一条`master`分支: + +![git-br-rm](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231421031.png) + +真是太神奇了,你看得出来有些提交是通过分支完成的吗? + +下面开始实战。 + +首先,我们创建`dev`分支,然后切换到`dev`分支: + +``` +$ 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.txt`做个修改,加上一行: + +``` +Creating a new branch is quick. +``` + +然后提交: + +``` +$ git add readme.txt +$ git commit -m "branch test" +[dev b17d20e] branch test + 1 file changed, 1 insertion(+) +``` + +现在,`dev`分支的工作完成,我们就可以切换回`master`分支: + +``` +$ git checkout master +Switched to branch 'master' +``` + +切换回`master`分支后,再查看一个`readme.txt`文件,刚才添加的内容不见了!因为那个提交是在`dev`分支上,而`master`分支此刻的提交点并没有变: + +![git-br-on-master](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231421018.png) + +现在,我们把`dev`分支的工作成果合并到`master`分支上: + +``` +$ git merge dev +Updating d46f35e..b17d20e +Fast-forward + readme.txt | 1 + + 1 file changed, 1 insertion(+) +``` + +`git merge`命令用于合并指定分支到当前分支。合并后,再查看`readme.txt`的内容,就可以看到,和`dev`分支的最新提交是完全一样的。也就是说,被合并的分支中含有当前分支不具有的提交,合并的结果是,让当前分支拥有了被合并分支的内容,而被合并分支本身并没有发生变化,而且一直存在。 + +注意到上面的`Fast-forward`信息,Git告诉我们,这次合并是“快进模式”,也就是直接把`master`指向`dev`的当前提交,所以合并速度非常快。 + +当然,也不是每次合并都能`Fast-forward`,我们后面会讲其他方式的合并。 + +合并完成后,就可以放心地删除`dev`分支了: + +``` +$ git branch -d dev +Deleted branch dev (was b17d20e). +``` + +删除后,查看`branch`,就只剩下`master`分支了: + +``` +$ git branch +* master +``` + +因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在`master`分支上工作效果是一样的,但过程更安全。 + + + +**小结一下:** + +Git鼓励大量使用分支: + +查看分支:`git branch` + +创建分支:`git branch ` + +切换分支:`git checkout `或者`git switch ` + +创建+切换分支:`git checkout -b `或者`git switch -c ` + +合并某分支到当前分支:`git merge ` + +删除分支:`git branch -d ` + + + +### 解决冲突 + +------ + +人生不如意之事十之八九,合并分支往往也不是一帆风顺的。 + +准备新的`feature1`分支,继续我们的新分支开发: + +``` +$ git checkout -b feature1 +Switched to a new branch 'feature1' +``` + +修改`readme.txt`最后一行,改为: + +``` +Creating a new branch is quick AND simple. +``` + +在`feature1`分支上提交: + +``` +$ git add readme.txt + +$ git commit -m "AND simple" +[feature1 14096d0] 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.txt`文件的最后一行改为: + +``` +Creating a new branch is quick & simple. +``` + +提交: + +``` +$ git add readme.txt +$ git commit -m "& simple" +[master 5dc6824] & simple + 1 file changed, 1 insertion(+), 1 deletion(-) +``` + +现在,`master`分支和`feature1`分支各自都分别有新的提交,变成了这样: + +![git-br-feature1](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231442155.png) + +这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看: + +``` +$ git merge feature1 +Auto-merging readme.txt +CONFLICT (content): Merge conflict in readme.txt +Automatic merge failed; fix conflicts and then commit the result. +``` + +果然冲突了!Git告诉我们,`readme.txt`文件存在冲突,必须手动解决冲突后再提交。`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 ..." to mark resolution) + + both modified: readme.txt + +no changes added to commit (use "git add" and/or "git commit -a") +``` + +我们可以直接查看readme.txt的内容: + +``` +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. +<<<<<<< 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.txt +$ git commit -m "conflict fixed" +[master cf810e4] conflict fixed +``` + +现在,`master`分支和`feature1`分支变成了下图所示: + +![git-br-conflict-merged](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231442158.png) + +用带参数的`git log`也可以看到分支的合并情况: + +``` +$ git log --graph --pretty=oneline --abbrev-commit +* cf810e4 (HEAD -> master) conflict fixed +|\ +| * 14096d0 (feature1) AND simple +* | 5dc6824 & simple +|/ +* b17d20e branch test +* d46f35e (origin/master) remove test.txt +* b84166e add test.txt +* 519219b git tracks changes +* e43a48b understand how stage works +* 1094adb append GPL +* e475afc add distributed +* eaadf4e wrote a readme file +``` + +最后,删除`feature1`分支: + +``` +$ git branch -d feature1 +Deleted branch feature1 (was 14096d0). +``` + +工作完成。 + + + +**小结一下:** + +当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。 + +解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。 + +用`git log --graph`命令可以看到分支合并图。 + + + +### 分支管理策略 + +------ + +通常,合并分支时,如果可能,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 f52c633] add merge + 1 file changed, 1 insertion(+) +``` + +现在,我们切换回`master`: + +``` +$ git switch 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 +* e1e9c68 (HEAD -> master) merge with no-ff +|\ +| * f52c633 (dev) add merge +|/ +* cf810e4 conflict fixed +... +``` + +可以看到,不使用`Fast forward`模式,merge后就像这样: + +![git-no-ff-mode](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231457676-4134897.png) + + + +### 分支策略 + +在实际开发中,我们应该按照几个基本原则进行分支管理: + +首先,`master`分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活; + +那在哪干活呢?干活都在`dev`分支上,也就是说,`dev`分支是不稳定的,到某个时候,比如1.0版本发布时,再把`dev`分支合并到`master`上,在`master`分支发布1.0版本; + +你和你的小伙伴们每个人都在`dev`分支上干活,每个人都有自己的分支,时不时地往`dev`分支上合并就可以了。 + +所以,团队合作的分支看起来就像这样: + +![git-br-policy](https://typora-1256991781.cos.ap-beijing.myqcloud.com/uPic/0-20200707231457676.png) + +**小结一下:** + +Git分支十分强大,在团队开发中应该充分应用。 + +合并分支时,加上`--no-ff`参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而`fast forward`合并就看不出来曾经做过合并。 + + + +#### Bug分支 + +------ + +软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。 + +当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支`issue-101`来修复它,但是,等等,当前正在`dev`上进行的工作还没有提交: + +``` +$ git status +On branch dev +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + new file: hello.py + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt +``` + +并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办? + +幸好,Git还提供了一个`stash`功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作: + +``` +$ git stash +Saved working directory and index state WIP on dev: f52c633 add merge +``` + +现在,用`git status`查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。 + +首先确定要在哪个分支上修复bug,假定需要在`master`分支上修复,就从`master`创建临时分支: + +``` +$ git checkout master +Switched to branch 'master' +Your branch is ahead of 'origin/master' by 6 commits. + (use "git push" to publish your local commits) + +$ git checkout -b issue-101 +Switched to a new branch 'issue-101' +``` + +现在修复bug,需要把“Git is free software ...”改为“Git is a free software ...”,然后提交: + +``` +$ git add readme.txt +$ git commit -m "fix bug 101" +[issue-101 4c805e2] fix bug 101 + 1 file changed, 1 insertion(+), 1 deletion(-) +``` + +修复完成后,切换到`master`分支,并完成合并,最后删除`issue-101`分支: + +``` +$ git checkout master +Switched to branch 'master' +Your branch is ahead of 'origin/master' by 6 commits. + (use "git push" to publish your local commits) + +$ git merge --no-ff -m "merged bug fix 101" issue-101 +Merge made by the 'recursive' strategy. + readme.txt | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) +``` + +太棒了,原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到`dev`分支干活了! + +``` +$ git switch dev +Switched to branch 'dev' + +$ git status +On branch dev +nothing to commit, working tree clean +``` + +工作区是干净的,刚才的工作现场存到哪去了?用`git stash list`命令看看: + +``` +$ git stash list +stash@{0}: WIP on dev: f52c633 add merge +``` + +工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法: + +一是用`git stash apply`恢复,但是恢复后,stash内容并不删除,你需要用`git stash drop`来删除; + +另一种方式是用`git stash pop`,恢复的同时把stash内容也删了: + +``` +$ git stash pop +On branch dev +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + new file: hello.py + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: readme.txt + +Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a) +``` + +再用`git stash list`查看,就看不到任何stash内容了: + +``` +$ git stash list +``` + +你可以多次stash,恢复的时候,先用`git stash list`查看,然后恢复指定的stash,用命令: + +``` +$ git stash apply stash@{0} +``` + + + +在master分支上修复了bug后,我们要想一想,dev分支是早期从master分支分出来的,所以,这个bug其实在当前dev分支上也存在。 + +那怎么在dev分支上修复同样的bug?重复操作一次,提交不就行了? + +有木有更简单的方法? + +有! + +同样的bug,要在dev上修复,我们只需要把`4c805e2 fix bug 101`这个提交所做的修改“复制”到dev分支。注意:我们只想复制`4c805e2 fix bug 101`这个提交所做的修改,并不是把整个master分支merge过来。 + +为了方便操作,Git专门提供了一个`cherry-pick`命令,让我们能复制一个特定的提交到当前分支: + +``` +$ git branch +* dev + master +$ git cherry-pick 4c805e2 +[master 1d4b803] fix bug 101 + 1 file changed, 1 insertion(+), 1 deletion(-) +``` + +Git自动给dev分支做了一次提交,注意这次提交的commit是`1d4b803`,它并不同于master的`4c805e2`,因为这两个commit只是改动相同,但确实是两个不同的commit。用`git cherry-pick`,我们就不需要在dev分支上手动再把修bug的过程重复一遍。 + +有些聪明的同学会想了,既然可以在master分支上修复bug后,在dev分支上可以“重放”这个修复过程,那么直接在dev分支上修复bug,然后在master分支上“重放”行不行?当然可以,不过你仍然需要`git stash`命令保存现场,才能从dev分支切换到master分支。 + + + +**小结一下:** + +修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除; + +当手头工作没有完成时,先把工作现场`git stash`一下,然后去修复bug,修复后,再`git stash pop`,回到工作现场; + +在master分支上修复的bug,想要合并到当前dev分支,可以用`git cherry-pick `命令,把bug提交的修改“复制”到当前分支,避免重复劳动。 + + + +#### Feature分支 + +------ + +软件开发中,总有无穷无尽的新的功能要不断添加进来。 + +添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。 + +现在,你终于接到了一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船。 + +于是准备开发: + +``` +$ git checkout -b feature-vulcan +Switched to a new branch 'feature-vulcan' +``` + +5分钟后,开发完毕: + +``` +$ git add vulcan.py + +$ git status +On branch feature-vulcan +Changes to be committed: + (use "git reset HEAD ..." to unstage) + + new file: vulcan.py + +$ git commit -m "add feature vulcan" +[feature-vulcan 287773e] add feature vulcan + 1 file changed, 2 insertions(+) + create mode 100644 vulcan.py +``` + +切回`dev`,准备合并: + +``` +$ git checkout dev +``` + +一切顺利的话,feature分支和bug分支是类似的,合并,然后删除。 + +但是! + +就在此时,接到上级命令,因经费不足,新功能必须取消! + +虽然白干了,但是这个包含机密资料的分支还是必须就地销毁: + +``` +$ git branch -d feature-vulcan +error: The branch 'feature-vulcan' is not fully merged. +If you are sure you want to delete it, run 'git branch -D feature-vulcan'. +``` + +销毁失败。Git友情提醒,`feature-vulcan`分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的`-D`参数。。 + +现在我们强行删除: + +``` +$ git branch -D feature-vulcan +Deleted branch feature-vulcan (was 287773e). +``` + +终于删除成功! + + + +**小结一下:** + +开发一个新feature,最好新建一个分支; + +如果要丢弃一个没有被合并过的分支,可以通过`git branch -D `强行删除。 + + + +## 多人协作 + +当你从远程仓库克隆时,实际上Git自动把本地的`master`分支和远程的`master`分支对应起来了,并且,远程仓库的默认名称是`origin`。 + +要查看远程库的信息,用`git remote`: + +``` +$ git remote +origin +``` + +或者,用`git remote -v`显示更详细的信息: + +``` +$ git remote -v +origin git@github.com:michaelliao/learngit.git (fetch) +origin git@github.com:michaelliao/learngit.git (push) +``` + +上面显示了可以抓取和推送的`origin`的地址。如果没有推送权限,就看不到push的地址。 + +### 推送分支 + +推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上: + +``` +$ git push origin master +``` + +如果要推送其他分支,比如`dev`,就改成: + +``` +$ git push origin dev +``` + +但是,并不是一定要把本地分支往远程推送,那么,哪些分支需要推送,哪些不需要呢? + +- `master`分支是主分支,因此要时刻与远程同步; +- `dev`分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步; +- bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug; +- feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。 + +总之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,视你的心情而定! + + + +### 抓取分支 + +多人协作时,大家都会往`master`和`dev`分支上推送各自的修改。 + +现在,模拟一个你的小伙伴,可以在另一台电脑(注意要把SSH Key添加到GitHub)或者同一台电脑的另一个目录下克隆: + +``` +$ git clone git@github.com:michaelliao/learngit.git +Cloning into 'learngit'... +remote: Counting objects: 40, done. +remote: Compressing objects: 100% (21/21), done. +remote: Total 40 (delta 14), reused 40 (delta 14), pack-reused 0 +Receiving objects: 100% (40/40), done. +Resolving deltas: 100% (14/14), done. +``` + +当你的小伙伴从远程库clone时,默认情况下,你的小伙伴只能看到本地的`master`分支。不信可以用`git branch`命令看看: + +``` +$ git branch +* master +``` + +现在,你的小伙伴要在`dev`分支上开发,就必须创建远程`origin`的`dev`分支到本地,于是他用这个命令创建本地`dev`分支: + +``` +$ git checkout -b dev origin/dev +``` + +现在,他就可以在`dev`上继续修改,然后,时不时地把`dev`分支`push`到远程: + +``` +$ git add env.txt + +$ git commit -m "add env" +[dev 7a5e5dd] add env + 1 file changed, 1 insertion(+) + create mode 100644 env.txt + +$ git push origin dev +Counting objects: 3, done. +Delta compression using up to 4 threads. +Compressing objects: 100% (2/2), done. +Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done. +Total 3 (delta 0), reused 0 (delta 0) +To github.com:michaelliao/learngit.git + f52c633..7a5e5dd dev -> dev +``` + + + +你的小伙伴已经向`origin/dev`分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送: + +``` +$ cat env.txt +env + +$ git add env.txt + +$ git commit -m "add new env" +[dev 7bd91f1] add new env + 1 file changed, 1 insertion(+) + create mode 100644 env.txt + +$ git push origin dev +To github.com:michaelliao/learngit.git + ! [rejected] dev -> dev (non-fast-forward) +error: failed to push some refs to 'git@github.com:michaelliao/learngit.git' +hint: Updates were rejected because the tip of your current branch is behind +hint: its remote counterpart. Integrate the remote changes (e.g. +hint: 'git pull ...') before pushing again. +hint: See the 'Note about fast-forwards' in 'git push --help' for details. +``` + +推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用`git pull`把最新的提交从`origin/dev`抓下来,然后,在本地合并,解决冲突,再推送: + +``` +$ git pull +There is no tracking information for the current branch. +Please specify which branch you want to merge with. +See git-pull(1) for details. + + git pull + +If you wish to set tracking information for this branch you can do so with: + + git branch --set-upstream-to=origin/ dev +``` + +`git pull`也失败了,原因是没有指定本地`dev`分支与远程`origin/dev`分支的链接,根据提示,设置`dev`和`origin/dev`的链接: + +``` +$ git branch --set-upstream-to=origin/dev dev +Branch 'dev' set up to track remote branch 'dev' from 'origin'. +``` + +再pull: + +``` +$ git pull +Auto-merging env.txt +CONFLICT (add/add): Merge conflict in env.txt +Automatic merge failed; fix conflicts and then commit the result. +``` + +这回`git pull`成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的[解决冲突](http://www.liaoxuefeng.com/wiki/896043488029600/900004111093344)完全一样。解决后,提交,再push: + +``` +$ git commit -m "fix env conflict" +[dev 57c53ab] fix env conflict + +$ git push origin dev +Counting objects: 6, done. +Delta compression using up to 4 threads. +Compressing objects: 100% (4/4), done. +Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done. +Total 6 (delta 0), reused 0 (delta 0) +To github.com:michaelliao/learngit.git + 7a5e5dd..57c53ab dev -> dev +``` + + + +因此,多人协作的工作模式通常是这样: + +1. 首先,可以试图用`git push origin `推送自己的修改; +2. 如果推送失败,则因为远程分支比你的本地更新,需要先用`git pull`试图合并; +3. 如果合并有冲突,则解决冲突,并在本地提交; +4. 没有冲突或者解决掉冲突后,再用`git push origin `推送就能成功! + +如果`git pull`提示`no tracking information`,则说明本地分支和远程分支的链接关系没有创建,用命令`git branch --set-upstream-to origin/`。 + +这就是多人协作的工作模式,一旦熟悉了,就非常简单。 + + + +**小结一下** + +- 查看远程库信息,使用`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`,如果有冲突,要先处理冲突。 + + + + + +## 扩展阅读 + + + +### 集中式vs分布式 + +集中式和分布式版本控制系统有什么区别呢? + +先说集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。 + +集中式版本控制系统最大的毛病就是必须联网才能工作,每次修改的内容都要推送到中央仓库。如果在局域网内还好,可如果在互联网上,遇到网速慢的话,就会阻塞在向服务器提交的过程中,因为提交期间,你是不能对文件做其他的修改的。 + +那分布式版本控制系统与集中式版本控制系统有何不同呢?首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。 + +和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。 + +当然,分布式也有坏处。必须一次下载所有的文件,因为要保证一个完整的版本库。而集中式的好处则恰恰是分布式的坏处,集中式的版本控制可以只下载一部分文件,并且随时与系统保持同步。 + +集中式工具包括CVS、SVN。 + +而分布式的经典代表则是Git。最快、最简单也最流行!其优势包括单机运行,高效分支管理。 + +值得说明的是,现在有很多传统软件公司内部开发可能因为遗留因素还在使用集中式代码管理工具,并不能说不好,只能说向后兼容。另外,在一个大家都熟悉SVN的团队里强行推行Git也是一个隐患,应该考虑使用习惯、熟练度和学习成本。没有必要为了用Git而用Git,说到底,工具没有优劣之分,只有合适与否。 + + + +### Git的诞生 + +Linus在1991年创建了开源的Linux,从此,Linux系统不断发展,已经成为最大的服务器系统软件了。 + +Linus虽然创建了Linux,但Linux的壮大是靠社区志愿者协作努力的,代码管理相当的麻烦。在2002年以前,世界各地的志愿者把源代码文件通过diff的方式(Unix下的文件对比格式)发给Linus,然后由Linus本人通过手工方式合并。 + +为什么Linus不把Linux代码放到版本控制系统里呢? + +因为Linus坚定地反对CVS和SVN等等类似的集中式版本控制工具,这些集中式的版本控制系统面对全世界各地的开发者,速度相当的慢,而且必须联网才能使用。 + +不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区开发者也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。 + +在2005年,由于一些不愉快的事情,BitMover公司收回了Linux社区的对于其BitKeeper免费使用权。然后Linus自己用C写了一个分布式版本控制系统,这就是Git的初始版本,并且很快就可以正式使用了。 + +后来,Git迅速成为最流行的分布式版本控制系统,尤其是2008年,GitHub网站上线了,它为开源项目免费提供Git存储,无数开源项目开始迁移至GitHub,包括jQuery,PHP,Ruby等等。 + +现在,各大互联网公司应该都在使用Git作为其代码版本管理工具。git 非常的流行,作为开发人员和计算机专业人员,绝对值得学习。 + + + +## 下篇预告 + +这个教程大概说了一下 git 的入门使用手段,下个文章我们会详细讨论一下比较高级的主题,比如 rebase, cherry-pick 等等,如果你对此感兴趣,可以关注一下下次的更新。 + + + + + diff --git a/Git教程2-高级主题.md b/Git教程2-高级主题.md new file mode 100644 index 0000000..e628333 --- /dev/null +++ b/Git教程2-高级主题.md @@ -0,0 +1,397 @@ +# Git教程2-高级主题 + +## 标签管理 + +发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。 + +Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。 + +Git有commit,为什么还要引入tag? + +“请把上周一的那个版本打包发布,commit号是6a5819e...” + +“一串乱七八糟的数字不好找!” + +如果换一个办法: + +“请把上周一的那个版本打包发布,版本号是v1.2” + +“好的,按照tag v1.2查找commit就行!” + +所以,tag就是一个让人容易记住的有意义的名字,它跟某个commit绑在一起。 + + + +### 创建标签 + +------ + +在Git中打标签非常简单,首先,切换到需要打标签的分支上: + +``` +$ git branch +* dev + master +$ git checkout master +Switched to branch 'master' +``` + +然后,敲命令`git tag `就可以打一个新标签: + +``` +$ git tag v1.0 +``` + +可以用命令`git tag`查看所有标签: + +``` +$ git tag +v1.0 +``` + +默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办? + +方法是找到历史提交的commit id,然后打上就可以了: + +``` +$ git log --pretty=oneline --abbrev-commit +12a631b (HEAD -> master, tag: v1.0, origin/master) merged bug fix 101 +4c805e2 fix bug 101 +e1e9c68 merge with no-ff +f52c633 add merge +cf810e4 conflict fixed +5dc6824 & simple +14096d0 AND simple +b17d20e branch test +d46f35e remove test.txt +b84166e add test.txt +519219b git tracks changes +e43a48b understand how stage works +1094adb append GPL +e475afc add distributed +eaadf4e wrote a readme file +``` + +比方说要对`add merge`这次提交打标签,它对应的commit id是`f52c633`,敲入命令: + +``` +$ git tag v0.9 f52c633 +``` + +再用命令`git tag`查看标签: + +``` +$ git tag +v0.9 +v1.0 +``` + +注意,标签不是按时间顺序列出,而是按字母排序的。可以用`git show `查看标签信息: + +``` +$ git show v0.9 +commit f52c63349bc3c1593499807e5c8e972b82c8f286 (tag: v0.9) +Author: Michael Liao +Date: Fri May 18 21:56:54 2018 +0800 + + add merge + +diff --git a/readme.txt b/readme.txt +... +``` + +可以看到,`v0.9`确实打在`add merge`这次提交上。 + +还可以创建带有说明的标签,用`-a`指定标签名,`-m`指定说明文字: + +``` +$ git tag -a v0.1 -m "version 0.1 released" 1094adb +``` + +用命令`git show `可以看到说明文字: + +``` +$ git show v0.1 +tag v0.1 +Tagger: Michael Liao +Date: Fri May 18 22:48:43 2018 +0800 + +version 0.1 released + +commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (tag: v0.1) +Author: Michael Liao +Date: Fri May 18 21:06:15 2018 +0800 + + append GPL + +diff --git a/readme.txt b/readme.txt +... +``` + + 注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。 + + + +小结 + +- 命令`git tag `用于新建一个标签,默认为`HEAD`,也可以指定一个commit id; +- 命令`git tag -a -m "blablabla..."`可以指定标签信息; +- 命令`git tag`可以查看所有标签。 + + + +### 操作标签 + +------ + +如果标签打错了,也可以删除: + +``` +$ git tag -d v0.1 +Deleted tag 'v0.1' (was f15b0dd) +``` + +因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。 + +如果要推送某个标签到远程,使用命令`git push origin `: + +``` +$ git push origin v1.0 +Total 0 (delta 0), reused 0 (delta 0) +To github.com:michaelliao/learngit.git + * [new tag] v1.0 -> v1.0 +``` + +或者,一次性推送全部尚未推送到远程的本地标签: + +``` +$ git push origin --tags +Total 0 (delta 0), reused 0 (delta 0) +To github.com:michaelliao/learngit.git + * [new tag] v0.9 -> v0.9 +``` + +如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除: + +``` +$ git tag -d v0.9 +Deleted tag 'v0.9' (was f52c633) +``` + +然后,从远程删除。删除命令也是push,但是格式如下: + +``` +$ git push origin :refs/tags/v0.9 +To github.com:michaelliao/learngit.git + - [deleted] v0.9 +``` + +要看看是否真的从远程库删除了标签,可以登陆GitHub查看。 + + + +小结 + +- 命令`git push origin `可以推送一个本地标签; +- 命令`git push origin --tags`可以推送全部未推送过的本地标签; +- 命令`git tag -d `可以删除一个本地标签; +- 命令`git push origin :refs/tags/`可以删除一个远程标签。 + + + +## Rebase + +在上一个教程中,多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的人不得不先pull,在本地合并,然后才能push成功。 + +每次合并再push后,分支变成了这样: + +``` +$ git log --graph --pretty=oneline --abbrev-commit +* d1be385 (HEAD -> master, origin/master) init hello +* e5e69f1 Merge branch 'dev' +|\ +| * 57c53ab (origin/dev, dev) fix env conflict +| |\ +| | * 7a5e5dd add env +| * | 7bd91f1 add new env +| |/ +* | 12a631b merged bug fix 101 +|\ \ +| * | 4c805e2 fix bug 101 +|/ / +* | e1e9c68 merge with no-ff +|\ \ +| |/ +| * f52c633 add merge +|/ +* cf810e4 conflict fixed +``` + +总之看上去很乱,有强迫症的童鞋会问:为什么Git的提交历史不能是一条干净的直线? + +其实是可以做到的! + +我们还是从实际问题出发,看看怎么把分叉的提交变成直线。 + +在和远程分支同步后,我们对`hello.py`这个文件做了两次提交。用`git log`命令看看: + +``` +$ git log --graph --pretty=oneline --abbrev-commit +* 582d922 (HEAD -> master) add author +* 8875536 add comment +* d1be385 (origin/master) init hello +* e5e69f1 Merge branch 'dev' +|\ +| * 57c53ab (origin/dev, dev) fix env conflict +| |\ +| | * 7a5e5dd add env +| * | 7bd91f1 add new env +... +``` + +注意到Git用`(HEAD -> master)`和`(origin/master)`标识出当前分支的HEAD和远程origin的位置分别是`582d922 add author`和`d1be385 init hello`,本地分支比远程分支快两个提交。 + +现在我们尝试推送本地分支: + +``` +$ git push origin master +To github.com:michaelliao/learngit.git + ! [rejected] master -> master (fetch first) +error: failed to push some refs to 'git@github.com:michaelliao/learngit.git' +hint: Updates were rejected because the remote contains work that you do +hint: not have locally. This is usually caused by another repository pushing +hint: to the same ref. You may want to first integrate the remote changes +hint: (e.g., 'git pull ...') before pushing again. +hint: See the 'Note about fast-forwards' in 'git push --help' for details. +``` + +很不幸,失败了,这说明有人先于我们推送了远程分支。按照经验,先pull一下: + +``` +$ git pull +remote: Counting objects: 3, done. +remote: Compressing objects: 100% (1/1), done. +remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0 +Unpacking objects: 100% (3/3), done. +From github.com:michaelliao/learngit + d1be385..f005ed4 master -> origin/master + * [new tag] v1.0 -> v1.0 +Auto-merging hello.py +Merge made by the 'recursive' strategy. + hello.py | 1 + + 1 file changed, 1 insertion(+) +``` + +再用`git status`看看状态: + +``` +$ git status +On branch master +Your branch is ahead of 'origin/master' by 3 commits. + (use "git push" to publish your local commits) + +nothing to commit, working tree clean +``` + +加上刚才合并的提交,现在我们本地分支比远程分支超前3个提交。 + +用`git log`看看: + +``` +$ git log --graph --pretty=oneline --abbrev-commit +* e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit +|\ +| * f005ed4 (origin/master) set exit=1 +* | 582d922 add author +* | 8875536 add comment +|/ +* d1be385 init hello +... +``` + +对强迫症童鞋来说,现在事情有点不对头,提交历史分叉了。如果现在把本地分支push到远程,有没有问题? + +有! + +什么问题? + +不好看! + +有没有解决方法? + +有! + +这个时候,rebase就派上了用场。我们输入命令`git rebase`试试: + +``` +$ git rebase +First, rewinding head to replay your work on top of it... +Applying: add comment +Using index info to reconstruct a base tree... +M hello.py +Falling back to patching base and 3-way merge... +Auto-merging hello.py +Applying: add author +Using index info to reconstruct a base tree... +M hello.py +Falling back to patching base and 3-way merge... +Auto-merging hello.py +``` + +输出了一大堆操作,到底是啥效果?再用`git log`看看: + +``` +$ git log --graph --pretty=oneline --abbrev-commit +* 7e61ed4 (HEAD -> master) add author +* 3611cfe add comment +* f005ed4 (origin/master) set exit=1 +* d1be385 init hello +... +``` + +原本分叉的提交现在变成一条直线了!这种神奇的操作是怎么实现的?其实原理非常简单。我们注意观察,发现Git把我们本地的提交“挪动”了位置,放到了`f005ed4 (origin/master) set exit=1`之后,这样,整个提交历史就成了一条直线。rebase操作前后,最终的提交内容是一致的,但是,我们本地的commit修改内容已经变化了,它们的修改不再基于`d1be385 init hello`,而是基于`f005ed4 (origin/master) set exit=1`,但最后的提交`7e61ed4`内容是一致的。 + +这就是rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。 + +最后,通过push操作把本地分支推送到远程: + +``` +Mac:~/learngit michael$ git push origin master +Counting objects: 6, done. +Delta compression using up to 4 threads. +Compressing objects: 100% (5/5), done. +Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done. +Total 6 (delta 2), reused 0 (delta 0) +remote: Resolving deltas: 100% (2/2), completed with 1 local object. +To github.com:michaelliao/learngit.git + f005ed4..7e61ed4 master -> master +``` + +再用`git log`看看效果: + +``` +$ git log --graph --pretty=oneline --abbrev-commit +* 7e61ed4 (HEAD -> master, origin/master) add author +* 3611cfe add comment +* f005ed4 set exit=1 +* d1be385 init hello +... +``` + +远程分支的提交历史也是一条直线。 + + + +小结 + +- rebase操作可以把本地未push的分叉提交历史整理成直线; +- rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。 + + + +## 查看区别 + +http://marklodato.github.io/visual-git-guide/index-zh-cn.html#diff + +![image-20200722085320388](/Users/zxzx/typora_pics/image-20200722085320388.png) + diff --git a/README.md b/README.md index 484159d..28cfc1a 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ git的入门是真的难. [2] 高频使用的 Git 清单 -http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html +http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html 推荐理由:阮一峰出品,必属精品 @@ -116,7 +116,7 @@ http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html 网上的教程很多, 但是写的少的却不多. 就我个人而言, 如何形象的感受 Git 在工作中的应用, 我推荐去看廖雪峰的 Git 教程. -因为我在学习 Git 的时候, 刚开始看了一些别的网站的教程, 都是一些支离破碎的命令和比较浅的没有系统讲述 Git 使用的, 而且很多教程是互相抄袭的, 你抄我, 我抄你, 特别是 CSDN 上, 批评一下 CSDN 对于创作者的版权保护不够, 现在乌烟瘴气的, 导出是抄袭的文章. +因为我在学习 Git 的时候, 刚开始看了一些别的网站的教程, 都是一些支离破碎的命令和比较浅的没有系统讲述 Git 使用的, 而且很多教程是互相抄袭的, 你抄我, 我抄你, 特别是 CSDN 上, 批评一下 CSDN 对于创作者的版权保护不够, 现在乌烟瘴气的, 到处是抄袭的文章. **这个教程内容丰富, 生动形象, 白话为主, 学起来相对 Easy, 我觉得三天或者一周左右可以吃透这个教程**. @@ -128,9 +128,9 @@ http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html -[4] 我用四个命令,总结了 Git 的所有套路 +[4] 四个命令总结 Git 的套路 -传送门:https://mp.weixin.qq.com/s/VdeQpFCL3GGsfOKrIRW6Hw +传送门:https://mp.weixin.qq.com/s/VdeQpFCL3GGsfOKrIRW6Hw 推荐理由:labuladong的博客, 简单粗暴,魅力自成体系。 @@ -723,27 +723,62 @@ Small note: If editing the README, please conform to the [standard-readme](https ## 参考文献 -[1]git - 简明指南 +5@ [4]如果你觉得学习 Git 很枯燥,**那是因为你还没玩过这款游戏**! - GitHub Daily的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/134346782 + +5@ [6]工作流一目了然,看小姐姐用动图展示10大Git命令 - 机器之心的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/132573100 + +3@ [2] 高频使用的 Git 清单 +http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html + +3@ [2]给自己点时间再记记这200条Git命令 - 爱前端不爱恋爱的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/137194960 + +3@ [24]图解git git diff的用法 + +http://marklodato.github.io/visual-git-guide/index-zh-cn.html + +3@ [1]git - 简明指南 https://rogerdudler.github.io/git-guide/index.zh.html -[2]用动图展示10大Git命令 +3@ [2]用动图展示10大Git命令 https://mp.weixin.qq.com/s/PUUL913fig6cFfqy4OKcGA -https://mp.weixin.qq.com/s/PUUL913fig6cFfqy4OKcGA +2@ [4]Git 的 4 个阶段的撤销更改 https://mp.weixin.qq.com/s/S33W_L9-taAC-aEuHvZYPQ -[3]Git使用教程:最详细、最傻瓜、最浅显、真正手把手教 +2 @[6] -- 的理解 -https://mp.weixin.qq.com/s/g0jgzZZ0RG_-dNoBwEs16Q +https://stackoverflow.com/a/22750480/9561380 -[4]Git 的 4 个阶段的撤销更改 +1@ [5]Git 高级用法 https://mp.weixin.qq.com/s/LTHLKrle5mOczHKsK2k30A -https://mp.weixin.qq.com/s/S33W_L9-taAC-aEuHvZYPQ +1@ [3]Git使用教程:最详细、最傻瓜、最浅显、真正手把手教 https://mp.weixin.qq.com/s/g0jgzZZ0RG_-dNoBwEs16Q -[5]Git 高级用法 +3@[1] ebooks for git -https://mp.weixin.qq.com/s/LTHLKrle5mOczHKsK2k30A +https://github.com/EbookFoundation/free-programming-books/blob/master/free-programming-books.md#git +2@[5]这才是真正的Git——Git内部原理揭秘! - 腾讯技术工程的文章 - 知乎 +https://zhuanlan.zhihu.com/p/96631135 + +1@[21]我用四个命令,总结了 Git 的所有套路 + +https://mp.weixin.qq.com/s/VdeQpFCL3GGsfOKrIRW6Hw + +1@[11] cherry pick的用法 + +http://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html + +1@详解 Git 大文件存储(Git LFS) - 腾讯技术工程的文章 - 知乎 https://zhuanlan.zhihu.com/p/146683392 + +1@过来人告诉你,去工作前最好还是学学Git - Java3y的文章 - 知乎 https://zhuanlan.zhihu.com/p/149169595 + +1@给女朋友讲解什么是Git - Java3y的文章 - 知乎 https://zhuanlan.zhihu.com/p/147161045 diff --git a/[开源]Git与Github与开源软件.md b/[开源]Git与Github与开源软件.md new file mode 100644 index 0000000..db65388 --- /dev/null +++ b/[开源]Git与Github与开源软件.md @@ -0,0 +1,1192 @@ +# Git知识 + +59 + +场景:今天想到之前的一个仓库,想好好的整理一下. 比如清空过去的提交记录? + +哦.. 这个完全可以通过删库来解决的。 + +我想起来了一个常用的办法 + +git有很多的子命令,不懂的时候,输入 git 子命令 --help 就可以看手册了。 + +当然,目前也有很多问题,比如仅仅知道git的命令,却不知道其背后的原理,我很焦虑 + +想法:我想把一个刚刚push过的本地仓库合并几个commit, 然后看能不能推送上去? + +知识:rebase的用法啊,git rebase master, 当前在dev上面,那么意思就是把dev的commits应用到master上面去。当前的分支放到前面。 + +511 + +场景: 今天在一个项目里开始git管理,使用git add -A, 把一些不想加进去的东西添加到暂存区里去了。 想到了git reset 命令, 就恢复了,然后再在 .gitignore里加上 + +比如 *.pyc .idea + +一 + +我在想 git 对一个改名的文件该如何处理呢 ? + +如果为了简洁处理,那么绝对不会把文件作为唯一标识符,而是会起一个文件的ID,保存着当前的路径。 + + + +521 + +Git status -s + +git.diff [--cached] + +Git log -p -2。p表示patch + +git log --since --before + +525 + +在a上Merge b,意思是把b合并到a里面去 + +a的指针移动 + +$ git log --no-merges issue54..origin/master + +issue54..origin/master 语法是一个日志过滤器,要求 Git 只显示所有在后面分支(在本例中是origin/master)但不在前面分支(在本例中是 issue54)的提交的列表 + +你可能会想要使用 rebase -i 来将工作压缩成一个单独的提交,或者重排提交中的工作使补丁更容易被维护者审核 - 查看 重写历史 了解关于交互式变基的更多信息。 + +$ git log contrib --not master + +528 + +场景: + +如果某次提交忘了加上某个文件或者忘记漏了某一行,该怎么办? + +如果重新再补一个commit, 那么会非常的丑陋,两个commit只做了一件事,因为自己的疏忽多提交了一个commit。 + +解决: 使用git commit --ammend + +书签: + +如何写一个标准的 .gitignore文件呢? + +参见p25 + +场景:如何查看某次commit 做了哪些修改呢? + +解决: 使用 show命令 + commitID + +场景:每次使用的流程都是 git add xxx, git commit, 可以缩短步骤吗? + +解决: 使用 git commit -a -m 'commit'。 + +理解: git reset --hard 和 git reset 到底有什么区别? + +答: git reset --hard 用提交区(或者说待提交区)覆盖工作区,但是 git rest 不修改工作区。 + +理解: git reflog到底是干什么的? + +答: git reflog是用来记录每一次 HEAD指针变化的。可以用来方便的进行时空穿梭。 + +书籍补充: + +状态简览: + +AM表示加入待提交区,又修改了。 + +UU 双方都修改了文件 + +在网上看到一个消息, 双字母的,再无冲突的时候,第一个表示待提交区,第二个表示工作区。 + +有冲突的时候,XY 表示两个版本 + +530 + +git show CommitID, 可以查看某次提交的详细信息,修改了哪些东西。 + +git log -p -1 可以查看上次提交的详细信息, -2 可以查看两次信息。 + +git log --stat 可以查看统计信息。 + +git log是一个非常非常非常重要的命令,我今天,现在终于知道了。这个命令,简直是重要的不能重要了。但是,我反思了一下,其实我对这个命令了解的并不多。 + +问题:提交者和作者是什么区别?TODO + +git log的筛选 + +根据日期筛选 git log --before --after=2018-01-01, 如果跟的有时分秒,用引号引起来 + +根据作者筛选 + +git log显示日志的顺序, 是先显示最近的,再显示最早的, 如果想从头查询, 跟上 --reverse + +书签:p37页,有关log的自定义特殊用法。 + +重置暂存区, 用提交区去覆盖暂存区, git reset + +git checkout -- file ,会用提交区覆盖工作区。 + +rebase + +最难的环节 + +打标签 git tag + +标签的作用是别名,我觉得意义非常重要。 + +p47 + +推送标签 + +git push origin tagname + +git push origin --tags + +git tag -d + +62 + +章节:Git分支 + +查看各个分支指向的位置,使用 --decorate命令 + +--all 会显示所有分支的位置 + +git branch --merged 查看合并到当前分支的其他分支 + +git branch --no-merged则相反 + +分支的工作模式 + +p71 + +pu分支 proposed-updates + +dev分支 + +master分支 + +分支命名规范 issNUMBR, issNUMBERv2 + +远程分支默认只有一个不可移动的 remote/branchX, 不会自动新建一个本地的branchX和它关联,这需要手动建立。 + +git checkout -b branchX origin/branchX + +等同于 + +git checkout --track origin/branchX + +git branch -vv 只会显示绑定的本地分支和没联网的 origin/分支之间差几个,想要查看实时的,需要先 fetch, git fetch --all + +git pull == git fetch + git merge + +git rebase 最常用的作用是 向别人的仓库贡献代码的时候 + +打个比方, HEAD在branchX上面 + +git rebase branchY, 那么意思是,以branchY为基底,把branchX上面独有的提交在branchY上面重新应用一遍,同时把branchX的指针位置,移动到rebase过程中最后一次提交上面。 + +git rebase --onto branchX branchY branchZ + +把branchZ上有而branchY没有的应用到branchX上面 + +git rebase branchX branchY + +rebase变基之后,可能需要使用-f推送 + +问题: 对提交进行sha1校验和计算包含哪些东西? + +默认配置 pull 使用rebase : git config --global pull.rebase true + +书签:在p90页,因为rebase已经推送的提交, 导致和其他同事尴尬的事。 + +在p91页提出了解决办法 + +Git分支这一章节结束!! + +章节:分布式Git + +git工作模式: + +集中式工作流 + +集成管理者工作流 + +63 + +继续看分布式Git这一章 + +书签: p116, 提交的信息格式,不只是git commit -m 'XXX', 还可以写的更详细一点 + +\- + +问题:git fetch origin 会抓取 origin所有的新提交吗?包括所有的分支? + +知识:引用规格是什么? + +答:关于refs的,是关于remote某个仓库的配置,应用在fetch中。 用于指定每次 运行 + +git fetch origin时拉取哪些分支,默认拉取所有分支。 + +git remote add remoteX,会在 仓库里的 .git/config下面写入详细信息 + +远程引用的全称是 refs/remotes/origin/mater, 简写为 origin/master + +知识:--amend可以删除或加入在暂存区的文件,而git add和git rm都是修改暂存区的文件,但是这会修改commit的Sha1,如果推送了就不要这么做了,会造成麻烦!! + +知识:关于如何命名想贡献代码的仓库呢?可以起名为origin,但是你没有推送代码的权利,你可以在fork一个,并且命名为myfork. + +知识:关于两个分支如何关联的相关命令,有以下: + +\- git push -u + +\- git checkout -b branchX origin/branchX + +\- [branchX]: git branch -u origin/branchX + +\- git branch -u origin/branchX branchX + +知识:如果想向一个仓库remoteX贡献代码,fork了一个仓库MyFork, 并且拉取到本地,基于master的C1开创一个本地功能分支FeatureA, 如果remoteX/master往前移动到了C2,如何申请pr呢?答案是基于remoteX/master变基,那么就可以解决冲突问题了。 + +知识:向一个公开的项目做贡献,在自己的特性分支上提交。 + +知识:如果有两个已经分叉的分支branchX和branchY, 如何查看branchX有而branchY没有的分支?写出用到的命令 + +git log branchY..branchX +note: 两个点不是三个点。 + +等同于 git log branchX --not branchY + +知识: git diff master...iss1 比较iss1新加入的东西,找到和master分叉的祖先节开始计算。 + +比较两个commit之间的区别,使用 git diff commit1 commit10 + + + +维护项目 + +-在 特性分支中工作 + +分支命名规范 贡献者ID/分支名 + +其他:创建分支而不立即切换 git branch newbranch basedbranch(local or origin/branchX) + +-应用来自邮件的补丁,跳过 + +-检出远程分支 没东西 + +-确定引入了哪些东西 + +知识:git log branchx..branchY 很好理解,从branchx到branchY,字面理解是,branchx了落后,branchY靠前 + +替代语法, git log branchY --not branchX + +git merge-base branchX branchY 查看这两个分支的交点 + +替代语法 git diff branchX…branchY + +-将贡献的工作整合进来 + +--合并工作流:merge就完了 + +-大项目合并工作流:没东西 + +-变基与拣选工作流: git cherry-pick commitid + +-rerere:没看懂 + +-为发布打标签:没看懂 + +-生成一个构建号:无用 + +-准备一次发布:不看 + +-制作提交简报:有点意思,下来细看 + +-总结:本章完结 + + + +\#Github + +Tmp GITHUB学习 + +-:章节介绍:看完 + +-账号创建:看完 + +-SSH访问:学会 + +-头像:看完 + +-邮件地址: 看完 + +-两步验证:看完 + +-对项目做出贡献:正在看 + +-fork 项目:看完 + +-Github 流程:看完 + +-创建合并请求: 看完: 可以创建一个新的有含义名字的分支,然后在这个分支上创建 pr申请拉取, 基于github 的可视化网页是非常方便的. + +-利用合并请求:看完 + +-合并请求进阶:有子项 + +-将合并做成补丁:翻译的抠脚,没看懂. + +-与上游保持同步:没看懂 + +-markdown: + +\```Python + +\``` + +-维护项目: + +-创建新的版本库:没东西 + +-添加合作者:添加 collaborator + +-管理合并请求: + +-邮件通知: + +-在合并请求上通知: + +-合并请求引用:看不懂 + +-合并请求之上的合并请求:看不懂,翻译的狗屎一样 + +-提醒和通知:没东西 + +-通知页面: 没啥东西 + +notification-center + +-网页通知: 没东西 + +-邮件通知: 无聊 + +-特殊文件:无聊 + +-README: 有点提示 + +可以是任何格式,只要可读,就会渲染 + +-CONTRIBUTING: 有点帮助 + +在提 PR 的时候有用 + +-项目管理:没卵用 + +—改变默认分支:没卵用 + +—移交项目:没卵用 + +-组织管理:没卵用 + +—组织的基本知识: + +可以把项目挂在组织下 + +—团队 + +可以对不同仓库有不同的权限 + +—审计日志:没卵用 + +-脚本 Github:完全不感兴趣 + + + + + + + +Git 工具 + +*选择修订版本 + +**单个修订版本 + +**剪短的 sha1:没意思 + +**分支引用:无用 + +探测分支的 头 rev-pare branchX + +**引用日志: + +只在本地 + +**祖先引用: + +~和^ + +**提交区间 + +***三点: + +只存在一个分支的 + +*交互式暂存 + +**暂存于取消暂存: 垃圾玩意 + +**暂存补丁: -p + +*贮藏 + +**贮藏工作: + +贮藏可以在 branchX 上储藏,在 branchY 上应用..但是 stash list 上有,最好不要整这些幺蛾子 + +git stash 默认会把暂存区里的东西也主藏起来. + +而如果—keep-index的话, 会保留待提交区里的东西, 并且切换分支也会继续带到下一个分支. + +git checkout 是不管工作区干不干净的. + +git stash -u 会把未跟踪的也暂存起来. + +***从贮藏的创建一个分支. + +git stash branch branchx + +这会自动 pop,并且创建一个分支 + +***清理工作目录 + +git clean -n -d 会显示模拟删除的东西 + +-d 表示目录 + +默认会过滤符合 + +这没卵用 + +*签署工作:暂时搁置 + +*搜索: 作用不大,搁置 + +*git日志搜索: + +git log -S”你要找的东西” + +**行日志搜索:实用价值不大. + +*重写历史 + +**修改多个提交历史 + +git rebase -i HEAD~X + +*核武器即应用: git filter-branch:暂时不想看了. + + + +Git 暂时学到这里, 后面的工具不用了, 感觉暂时够用. + + + + 67 + +到此为止,我感觉已经基本够用了. + +我将暂停 Git 的学习. + + + +一. 在一台新电脑上用git应该干哪些事? + +答: 应该配置自己的user.name, user.email, + +配置一些命令缩写, 方便自己使用. 比如 git config --global alias.l "log --color --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" + +
+ +二. How to view the git user.name? + +
+ +三. git log 是做什么用的? + +答: 不知道, 不过当一个仓库没有提交的时候, 输入 git log 命令会提示如下: + +\``` + +$ git log + +fatal: your current branch 'master' does not have any commits yet + +\``` + +
+ +四. 问:git branch 命令是干嘛的? + +答: 查看当前版本的. 详细的解释参见 https://git-scm.com/docs/git-branch#_description + +
+ +五. 如何获取帮助? + +答: git 子命令 --help, 在windows下使用 git bash for windows, 会自动打开默认浏览器并且加载html文档, 可读性很高. + +还可以参看官方文档 https://git-scm.com/docs, 还可以看廖雪峰的git教程, https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 + +
+ +六. 如何把本地写了很久的仓库推送到github上面去? + +答:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/0013752340242354807e192f02a44359908df8a5643103a000 可以找到答案, 步骤是在github上新创建一个仓库, 关键是不要在远程初始化, 否则需要处理冲突. 然后在本地 git remote add origin XXXX, 然后git push -u origin master, 就会把本地仓库完全推送到远程去. + +
+ +七. git diff的格式是咋样的? + +答: 应该是和linux下的diff格式一致的. + +
+ +八. 版本回退是什么命令?(2)git log和git reflog有什么区别? + +答: git reset --hard 版本号或者 head∧ (2)todo + +
+ +九. 从暂存区撤销一个文件的命令是什么? + +答: git checkout -- filename + +\# git查看 + +看历史 + +\- git log pretty=oneline + +\- git reflog + +ref log 详细log + +\### 查看当前状态 + +git status + +\# 版本控制 + +\### 版本号 + +\- HEAD:当前版本, HEAD^: 前一个版本, HEAD^^ 前两个版本 + +\### 版本选择 + +git reset --hard 版本号 + +[从版本库删除] + +\# 针对已经加入版本库的文件, + +git rm filepath # 删除动作处于缓存区 + +git rm --cache filepath # 从缓存区里删除, 但是保存文件. + +\# 参考 https://blog.csdn.net/GW569453350game/article/details/50956059 + +[从stage区回退] + +\# git add 回退 + +git reset HEAD filepath # 保存源文件, 但是从暂存区删掉 + +[查看某一个文件的提交历史] + +git log -- filepath + + + + + + + + + + + +@git读物 + +https://gist.github.com/yisibl/8281454 + + + + + +[[ 廖雪峰的git教程 ]] + + + +[ 时光机穿梭 ] + + + +@难点 + +diff命令和git diff + +diff命令的格式和标记是什么意思? + + + +@提示 + +git log可以查看提交历史 + + + +@疑点 + +git log和git reflog有什么区别? + + + +@重点 + +git reset + +git reset HEAD^可以回退一个版本 + +HEAD必须大写 + + + +@提示 + +版本号最短需要4位 + + + +@? + +git checkout -- readme.txt + +git reset HEAD + +git rm + +git commit + + + + + + + +[ 远程仓库 ] + +ssh-keygen -t rsa -C “youremail@example.com" + +git remote add origin git@github.com:michaelliao/learngit.git + + git push -u origin master + +使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。 + +Git支持多种协议,包括https,但通过ssh支持的原生git协议速度最快。 + + + + + + + + + + + + + + + + + + + +Commit管理 + +\# 改 BUG 流程 + +cherry-pick 介绍 https://www.jianshu.com/p/08c3f1804b36 + +修复流程完整描述 https://blog.csdn.net/treesky/article/details/50126387 + + + +\# undo git pull + +git reset --keep HEAD@{1} + +https://stackoverflow.com/questions/5815448/how-to-undo-a-git-pull/5815626 + + + +\# 文件代码含义 + +https://blog.csdn.net/u011623532/article/details/46692937 + + + +git add filename ; + +git add -A 会把所有修改添加到缓存区 + +git add -p, git checkout -p 添加到缓存区与删除修改 + +-p 是--patch, 批,块的意思 + + + + + +git reset HEAD filename, + +git reset HEAD 会清空缓存区, + + + +git commit -m’注释内容’ + +git commit --amend 修改最后一次没有推送的提交的注释 + + + +git checkout -- filename 恢复到最近一次commit 或者add状态 + + + +git rm --cache .idea/workspace.xml 从缓存区里删除, 但是保存文件. + +如果git rm filname后,想反悔, git checkout -- filename 撤销删除 + +\### 撤销修改 + +!QUESTION 如果一个文件既加入了缓存区, 又修改了工作区?如何恢复到最初的版本? + +ANSWER: 先丢弃缓存区的东西,git reset HEAD filename, 再 git checkout -- filename, 顺序不能反。 + +git checkout(add) -p filename + +git log --pretty=oneline filename + + + + + +reset 是 commit 的反义词, + +commit 使指针向前移动一次, reset 则相反 + +reset --soft 只移动版本库的指针, 不移动待提交区和工作区 + +reset --mixed 移动版本库的指针和修改待提交区, 不改变工作区 + +reset --hard 则会修改所有. + + + +其他 + +\# 查看一个文件的作者 + +https://www.cnblogs.com/flyme/archive/2011/11/28/2265899.html + + + +git diff HEAD -- read.txt + + + +\# display unicode + +git config --global core.quotePath false + + + + + +Stash命令 + + + +git stash--> git stack push 把当前工作区压栈 + +git stash list--> 查看工作栈 + +git stash apply--> 把工作栈的内容和当前的分支合并,可能会起冲突,冲突的话手动修复。 + +git stash apply stash@{0} + +另一种方式是用git stash pop,恢复的同时把stash内容也删了: + + + + + + + +git 快捷键 + + + +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 config --global alias.l "log --color --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" + + + +理解提示:alias是config的自命令 + +git config alias.lg + + + +superlog = log --color --graph --all --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cd) %C(bold blue)<%an>%Creset %ae ' --abbrev-commit --date ='format:%Y-%m-%d %H:%M:%S' + + + + + +分支管理 + +git branch -va 远程查看 https://www.barretlee.com/blog/2014/04/30/switch-branch-in-git/ + + + +git checkout -b 本地分支名x origin/远程分支名x 创建本地的远程分支 https://blog.csdn.net/tterminator/article/details/52225720 + + + +\# 分支关联 + +git branch --set-upstream-to[简写 -u] remote/ local_branch + + + + 另外, 在推送的时候 加上 -u 也可以建立关联 + +git push remote localbranch:remotebranch 推送,如果远程分支不存在,创建 + + + + + +git checkout -b 分支名 某个版本号, 为某个历史版本建立一个分支 + +git branch -d dev + +git merge + + + +git branch -vva可以查看远程分支,本地分支, 远程分支和本地分支的关联,本地分支落后了远程分支几个版本等等。 + + + + + +rebase + + + +git pull --rebase origin dev + + + +都相差了几百个commit了还去做rebase的是傻逼!rebase只适合相差不大提交的合并。 + + + +有时候采用分支开发的模式,一个feature开一个分支,开发完毕之后合并了,这个branch就可以删了,这个时候用rebase还是不错的 + + + +刚维护完一个1年的分支 我的过程是 经常rebase 时刻保持你的代码是基于最新代码修改。解决冲突也是一个挑战,合并上线用merge 提交历史会很清晰。 + + + + + +推送管理 + +git push remote --delete remotebranch = git push remote :remotebranch 删除远程分支 + +命令详解 https://blog.csdn.net/sky1203850702/article/details/41344131 + + + +\# git push 回退 + +https://blog.csdn.net/jeffasd/article/details/51736569 + +远程关联 + +git remote -v + +删除 git remote rm remote_name + +添加远程 git remote add origin git@github.com:notfresh/wechat_miniapp_mytododemo + +\# git 从本地分支推送 + +现在本地创库, 开发,然后推送到远端 + +当出现这种错误: + +git无法pull仓库refusing to merge unrelated histories + +解决办法是: git pull origin master --allow-unrelated-histories + + + + + +\# 我在本地建了一个仓库 + + + +我在本地建立了一个仓库, 不停的提交代码, 写着写着我觉得有必要提交到线上去了. + + + +\1. 于是, 此时, 我在github上面建了一个仓库. abc + + + +\2. 我把github仓库和本地仓库关联, 第一步, 初始化远程仓库, 提交一次(必须的, 否则没有分支, 就没法建立关联) + + + +\3. 我在本地仓库使用命令 git remote add origin git@github.com:GITHUB_USERNAME/abc.git ( 这种地址表明使用git协议, 推送不必使用密码, http协议推送需要使用密码, 使用git协议需要使用ssh配置, 这里就不多讲了) + + + +\4. 我从本地拉取远程仓库 git pull --rebase origin master --allow-unrelated-histories + + + +\5. 我把本地的master分支和远程的master分支关联起来. 使用以下命令: + + + +git branch --set-upstream-to[简写 -u] remote_name/ local_branch , (提示, 远程仓库在前, 本地仓在后, 顺序是固定的) + + + +在这里, 我写 git branch -u origin/master master, 于是就建立了关联. + + + +\6. 我向远程推送代码. git push. + + + + + + + +打标签 + + + +git tag v1 创建标签 git tag -d v1 删除标签 + + + +git tag v1 commitid(5位或者7位) + +\## 查看标签 + + + +git show tagname + +git tag 查看所有标签 + +git show commitid + + + +# Github知识 + +## 如何提一个PR? + +进入pr,new pr + + + +# 为什么阅读软件是可行的? + +因为软件遵循一定的体系结构和设计模式,采用模块化思路,采用面向对象的思路,所以相对独立的功能是可以被理解的。 + +但是也不能着急,因为一个开源软件的创作是非常辛苦的,所以不要以为可以凭借自己的摸索在短期时间内搞懂全部知识。 + + + +# CJson学习笔记 + + + +# 给力仓库 + +github的给力仓库 + +https://mp.weixin.qq.com/s/OD0AxM1j41M04bOM3k20Og + + + + + +# 参考 + +[1]如何使用 GitHub? - 腾讯技术工程的回答 - 知乎 + +https://www.zhihu.com/question/20070065/answer/1174997617 + +[3]和逛知乎、刷微博一样高效使用 GitHub - 程序猿杂货铺的文章 - 知乎 + + https://zhuanlan.zhihu.com/p/61172248 + +[7]程序员宝库,开源社区GitHub到底该怎么玩 |如何玩转Github + +https://www.bilibili.com/video/BV1d4411L7Jk/ + +关键词:使用GitHub,compare、project、wiki、insight、 + +感受:0基础人的人看的 + +[8]视频演示如何玩转一个开源项目 |如何运行+如何读代码 |顺便讲讲IDE + +https://www.bilibili.com/video/BV1y4411p74E + +关键词:跑起来、idea、拆分、阅读源码调试、halo、修改自定义、springboot、gradle(json)、Import、auto-import、src-main-java-application、调试 + + + +**[9] HelloGithub** + +**https://hellogithub.com/** + + + +[10]如何轻松阅读 GitHub 上的项目源码? + +https://mp.weixin.qq.com/s/suBaNVG6S18mv06owccRPw + + + + + + + + + +[11]最值得学习阅读的10个C语言开源项目代码 + +https://blog.csdn.net/hackerwin7/article/details/40864551 + + + +[12]有哪些轻量级适合阅读的优秀 C++ 开源项目? - Flyer的回答 - 知乎 + +https://www.zhihu.com/question/40131963/answer/90966253 + + + +[13]有哪些轻量级适合阅读的优秀 C++ 开源项目? - 码云 Gitee的回答 - 知乎 +https://www.zhihu.com/question/40131963/answer/249330962 + + + +[14]写给初学者的C语言源代码阅读指南--书籍介绍 + +https://book.douban.com/review/6512809/ + + + +[15]如何高效有效的阅读源码? + +https://mp.weixin.qq.com/s/Tt-SQR6NCEtLAgzviopiLA + + + +[16] Tinyhttpd的中文文档 + +https://github.com/EZLippi/Tinyhttpd + + + +[17]**Tinyhttpd**官网 + +https://sourceforge.net/projects/tinyhttpd/ + + + +[18]sqlite 源码解读 + +https://huili.github.io/sqlite/architecture.html + + + +[19]Java开源项目推荐 + +https://www.bilibili.com/video/BV1rb41157Rk + + + +[20]Flask语言阅读 + +https://jiajunhuang.com/articles/2016_09_15-flask_source_code.rst.html + +[22]Git 同步fork 拉取远程仓库代码 + +https://blog.csdn.net/wzy4072/article/details/79628659 + +[23]使用 git upstream 从其他远程仓库同步分支 + +http://jalan.space/2019/05/28/2019/git-upstream/ + + + + + +[2]给自己点时间再记记这200条Git命令 - 爱前端不爱恋爱的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/137194960 + +[4]如果你觉得学习 Git 很枯燥,**那是因为你还没玩过这款游戏**! - GitHub Daily的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/134346782 + +[5]这才是真正的Git——Git内部原理揭秘! - 腾讯技术工程的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/96631135 + +[6]工作流一目了然,看小姐姐用动图展示10大Git命令 - 机器之心的文章 - 知乎 + +https://zhuanlan.zhihu.com/p/132573100 + +[11] cherry pick的用法 + +http://www.ruanyifeng.com/blog/2020/04/git-cherry-pick.html + +[21]我用四个命令,总结了 Git 的所有套路 + +https://mp.weixin.qq.com/s/VdeQpFCL3GGsfOKrIRW6Hw + +[24]图解git git diff的用法 + +http://marklodato.github.io/visual-git-guide/index-zh-cn.html +