git是版本控制和团队协作的一个高效工具。今天通过链接https://learngitbranching.js.org/?demo中的git闯关小游戏来学习并了解git,并熟悉有关git的各种命令。*本文中的一些重要概念都来自于链接中的小游戏说明*。

1.git commit
Git 希望提交记录尽可能地轻量,因此在你每次进行提交时,它并不会盲目地***整个目录。条件允许的情况下,它会将当前版本与仓库中的上一个版本进行对比,并把所有的差异打包到一起作为一个提交记录。

2.git branch
分支是git中的一个重要的概念,git 的分支也非常轻量。它们只是简单地指向某个提交纪录
早建分支!多用分支!因为即使创建再多分支也不会造成存储或者内存的开销。
使用分支其实就相当于在说:“我想基于这个提交以及它所有的父提交进行新的工作。”

使用格式:git branch [your-branchname]

3.git checkout
检出分支,其实就是从当前分支跳到某个分支上面。

使用格式:git checkout [your-branchname]

4.git merge
合并分支,意思是“我要把这两个父节点本身及它们所有的祖先都包含进来。”
可以先跳到某个分支,如master,再合并目标分支

使用格式:git checkout master; git merge [dest-branch]

5.git rebase
另一种更高级的合并方法:实际上就是取出一系列的提交记录,“*”它们,然后在另外一个地方逐个的放下去。它可以创建更加线性**的提交历史。
线性的含义:及让两个并行开发的分支合并的时候看起来就像顺序开发出来的那样,如下图:
图片说明
注意:当前正在bugFix分支上,执行命令git rebase master将会得到如下的提交历史:
图片说明
注意:此时master分支还没有更新,可以跳到master分支下面(git checkout master),然后使用git rebase bugFix来更新maseter.

6.在提交树上移动:
HEAD:一个对当前检出记录的符号引用 —— 也就是指向你正在其基础上进行工作的提交记录。
HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。

如果想看 HEAD 指向,可以通过 cat .git/HEAD 查看, 如果 HEAD 指向的是一个引用,还可以用 git symbolic-ref HEAD 查看它的指向

分离的HEAD:分离的 HEAD 就是让其指向了某个具体的提交记录而不是分支名。前提是知道该提交记录的索引值(哈希值) --- git checkout [commit-hash]
注:可以通过git log命令来查看提交记录的哈希值。

7.相对引用
通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用

使用相对引用的话,你就可以从一个易于记忆的地方(比如 bugFix 分支或 HEAD)开始计算。

相对引用非常给力,这里介绍两个简单的用法:
使用 ^ 向上移动 1 个提交记录
使用 ~[num] 向上移动多个提交记录,如 ~3

直接相对某个分支使用git checkout 命令,如:git checkout master~; 这条命令将使HEAD指向master的父分支。也可以相对于HEAD使用

9.强制修改分支位置
git branch -f master HEAD~3
上面的命令会将 master 分支强制指向 HEAD 的第 3 级父提交。

10.撤销变更
在 Git 里撤销变更的方法很多。和提交一样,撤销变更由底层部分(暂存区的独立文件或者片段)和上层部分(变更到底是通过哪种方式被撤销的)组成。我们主要关注后者。
主要有两种方法用来撤销变更 —— 一是 git reset,还有就是 git revert
git reset主要用在撤销本地代码库的变更:git reset HEAD~,该命令从当前提交记录返回到了当前提交记录的父级提交记录。
git revert则用在远程代码库的变更,使用方法与git reset一样。

11.整理提交记录
git cherry-pick [commit-number] ... 可以把一系列的提交记录放到当前分支下面。

git rebase -i HEAD~[num] 交互式的rebase

12.提交的技巧
在我们做了几次提交之后,突然想对以前的某个提交做修改,这个时候可以通过git rebase -i 命令先把想要修改的那个提交换到最新的位置来,修改后再通过git rebase -i 命令将提交的顺序调回去。(但这有可能造成rebase的冲突)

13.另一个提交的技巧
cherry-pick 可以将提交树上任何地方的提交记录取过来追加到 HEAD 上(只要不是 HEAD 上游的提交就没问题),使用git cherry-pick便可以把非HEAD上游的分支取过来。
方法:
先使用git checkout 让HEAD指向上面的分支,使目标分支在HEAD下游
然后使用git cherry-pick将目标分支***的当前分支下,修改后,再将之后的分支用git cherry-pick拿到放过来。

14.tag
tag,它们可以(在某种程度上 —— 因为标签可以被删除后重新在另外一个位置创建同名的标签)永久地将某个特定的提交命名为里程碑,然后就可以像分支一样引用了。

更难得的是,它们并不会随着新的提交而移动。你也不能检出到某个标签上面进行修改提交,它就像是提交树上的一个锚点,标识了某个特定的位置。

命令格式:将某个提交记录赋予v1标签
git tag [commit-name]
不指定名字则默认为HEAD

15.git describe
git describe [reference-name] 可以是分支名

输出结果:[tag] [numCommits] g[hash]
tag 表示的是离 ref 最近的标签, numCommits 是表示这个 ref 与 tag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。

16.多次git rebase
命令格式:git rebase [branch-name1] [branch-name2]
将branch-name2指向的提交记录拿到branch-name1指向的提交记录这边来,并且检出到了最新的提交记录,branch-name1不移动

17.多个父记录的选择
^[num] 该操作符后面的数字与 ~ 后面的不同,并不是用来指定向上返回几代,而是指定合并提交记录的某个父提交。默认指向第一个父提交。

18.远程仓库
特性:
a.首先也是最重要的点, 远程仓库是一个强大的备份。本地仓库也有恢复文件到指定版本的能力, 但所有的信息都是保存在本地的。有了远程仓库以后,即使丢失了本地所有数据, 你仍可以通过远程仓库拿回你丢失的数据。

b.还有就是, 远程让代码社交化了! 既然你的项目被托管到别的地方了, 你的朋友可以更容易地为你的项目做贡献(或者拉取最新的变更)

19.远程分支
远程分支反映了远程仓库(在你上次和它通信时)的状态。这会有助于你理解本地的工作与公共工作的差别
特性:
检出时自动进入分离 HEAD 状态。Git 这么做是出于不能直接在这些分支上进行操作的原因, 你必须在别的地方完成你的工作, (更新了远程分支之后)再用远程分享你的工作成果。
远程分支命名规范:[remote-name]/[branch-name] [远程仓库名]/[远程分支名]
真正使用git clone时,远程仓库名默认为origin
远程分支只有在远程仓库中相应的分支更新了以后才会更新。检出远程分支会自动进入分离状态

20.git fetch
git fetch 完成了仅有的但是很重要的两步:
a.从远程仓库下载本地仓库中缺失的提交记录
b.更新远程分支指针(如 o/master)

git fetch不会做的事:
不会改变你本地仓库的状态。它不会更新你的 master 分支,也不会修改你磁盘上的文件。

21.git pull
git pull == git fetch ; git merge

22.git push
将你的变更上传到指定的远程仓库中,并在你的远程仓库中合并你的新提交记录

23.一个现实情境
假设你周一克隆了一个仓库,然后开始研发某个新功能。到周五时,你新功能开发测试完毕,可以发布了。但是 —— 天啊!你的同事这周写了一堆代码,还改了许多你的功能中使用的 API,这些变动会导致你新开发的功能变得不可用。但是他们已经将那些提交推送到远程仓库了,因此你的工作就变成了基于项目旧版的代码,与远程仓库最新的代码不匹配了。

这种情况下, git push 就不知道该如何操作了。如果你执行 git push,Git 应该让远程仓库回到星期一那天的状态吗?还是直接在新代码的基础上添加你的代码,异或由于你的提交已经过时而直接忽略你的提交?

因为这情况(历史偏离)有许多的不确定性,Git 是不会允许你 push 变更的。实际上它会强制你先合并远程最新的代码,然后才能分享你的工作。

git pull --rebase; git push

24.rebase的优缺点
优点:Rebase 使你的提交树变得很干净, 所有的提交都在一条线上(即提交历史更加线性)
缺点:Rebase 修改了提交树的历史

25.远程跟踪
当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/master)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 master。
git checkout -b totallyNotMaster o/master
创建名为totallyNotMaster的分支,跟踪远程分支o/master

git branch -u o/master foo
让foo跟踪远程分支o/master
如果当前就在 foo 分支上, 还可以省略 foo:
git branch -u o/master

26.git push的参数
git push [remote] [place]
例子:
git push origin master
切到本地仓库中的“master”分支,获取所有的提交,再到远程仓库“origin”中找到“master”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。

git push [remote] [source]:[destination]
当本地分支名和远程分支名不同时可以这样用

同样git fetch也可以使用这样的参数。如果不使用参数,则git fetch会将远程仓库的所有分支都下载

其中source值可以为空,当git fetch source值为空时,将在本地仓库创建一个新的分支,当git push的source值为空时,将删除远程仓库中的目的分支。

27.git pull的参数
git pull origin foo 相当于:

git fetch origin foo; git merge o/foo (与当前检出位置合并)

git pull origin bar~1:bugFix 相当于:

git fetch origin bar~1:bugFix; git merge bugFix