Git教程
这篇博客是在学习廖雪峰git教程所做的一个笔记。
这里我总结了一些常用的命令,需要看教程的同学可以往下看。
| git命令 | 说明 |
|---|---|
git init |
将文件夹变成一个版本库 |
git status |
查看当前状态 |
git add+fileName |
将文件添加到暂存区 |
git checkout --+fileName |
git add的反向命令,撤销工作区修改,即把暂存区最新版本转移到工作区 |
git commit -m+提交说明 |
将暂存区文件推入版本库 |
git reset+HEAD^..或HEAD~100 |
git commit的反向命令,把仓库最新版本转移到暂存区。HEAD上有几个^表示回退几个版本HEAD~n表示会回退n个版本 |
git diff |
查看工作区和暂存区差异 |
git diff --cached |
查看暂存区和仓库差异 |
git diff+HEAD^..或HEAD~100 |
查看工作区和仓库的差异 |
git rm+fileName |
删除版本库中的文件 |
git branch+branchName |
创建分支,其后加orgin/branchName可以远程库的分支创建到本地 |
git branch+-d或-D+branchName |
删除分支(-d删除,-D强制删除) |
git checkout+brachName 或者git switch+brachName |
切换分支 |
git merge+brachName |
合并某分支到当前分支 |
git log |
查看提交记录;参数说明:--pretty=oneline --abbrev-commit:显示简洁信息;--graph:绘图 |
git stash |
将当前工作区文件保存在stash中 |
git stash apply |
将stash文件恢复到工作区 |
git stash drop |
删除stash中的内容 |
git stash pop |
将stash文件恢复到工作区并且同时删除stash中的内容 |
git remote |
查看远程库的信息,加-v参数可以显示更加详细的信息 |
git push orgin+branchName |
将分支上传到远程仓库(需要当前分支与远程仓库关联好) |
git pull+origin/branchName |
将远程仓库pull到本地(可能需要解决冲突) |
git tag tagName |
可以给当前分支下的最近一次commit打上标签 |
git tag+tagName CommitID |
然也可以指定commit记录打标签,可加-m参数增加说明 |
git tag -d+tagName |
删除本地标签 |
git push origin+tagName |
将某个标签推到远程仓库 |
git push origin+--tags |
将所有标签推到远程仓库 |
git push origin:refs/tags/tagName |
删除远程标签(需要先从本地删除) |
ssh-keygen -t rsa -C "youremail@example.com" |
创建公私钥对,用于上传远程库 |
git remote add origin git@github.com:+YourGithubName/RepositoryName.git |
添加远程仓库 |
git push -u origin+branchName |
首次向远程仓库提交 |
git push origin+brachName |
非首次向远程仓库提交 |
git clone+克隆地址 |
从远程库克隆 |
Git是目前世界上最先进的分布式版本控制系统。有时我们会经常有这样的困扰,一个经过不断的修改,但是可能又需要备份之前的版本,然后出现下面的情况(一堆乱七八糟的备份,自己都忘记备份的什么)

创建版本库
首先创建一个版本库,然后进入该文件夹下,将该文件夹变成git可以管理的仓库,当然也不一定要在空目录下创建git仓库:初始化后会新建一个.git的隐藏文件夹,会在后文介绍。
1 | mkdir +文件夹名 //创建文件夹 |

将文件添加到版本库
新建一个readme.md文件,在文件中写入如下内容:
1 | Git is a version control system. |
第一步,用命令git add告诉git,把文件添加到仓库
1 | $ git add readme.md |

第二步,用git commit告诉git,将文件提交到仓库,-m "xx"参数是对本次提交的说明
1 | git commit -m "wrote a readme file" |
这里使用的是github,所以需要添加一下用户的邮箱和用户名。

执行完命令后,会有一些响应信息,1 file changed:一个文件被改动,2 insertions:插入了两行内容。
为什么Git添加文件需要add,commit一共两步?
因为commit可以一次提交很多文件,所有可以多次add不同文件后再commit
1 | $ git add file1.txt |
时光机穿梭
修改版本库
修改readme.md,改成如下内容:
1 | Git is a distributed version control system. |
运行git status命令查看结果:

git status可以让我们掌握仓库当前的状态,由上面的输出结果可以知道,readme.md被修改过了,但是还没有准备提交的修改。
git status只能告诉我们什么被修改了,但是想要查看具体修改了什么内容,可以使用git diff命令查看:

git diff就是查看difference,是Unix通用的diff格式,想要详细的理解diff文件,可以点击这里
知道了对readme.md做了什么修改之后,就可以放心的提交了,git add readme.md,git status

git status告诉我们,将要被提交的修改包括readme.md,然后就可以git commit提交了。

版本退回
我们再修改一个版本的readme.md,修改为如下版本:
1 | Git is a distributed version control system. |

就这样不断的对文件进行修改,然后不断的提交修改到版本库中,类似于我们玩游戏,在关键点存档,不至于在阵亡后重新开始游戏。这个存档在Git中被称为commit,一旦给文件改乱了,可以从最近的一个commit恢复,而不需要重新开始。
以下是我们修改过的三个版本:
版本1:wrote a readme file
1 | Git is a version control system. |
版本2:add distributed
1 | Git is a distributed version control system. |
版本3:append GPL
1 | Git is a distributed version control system. |
在git中,我们可以使用git log命令查看。可以看到最近到最久远的一些提交记录。

如果嫌输出的信息过于繁多,可以在其后添加--pretty=oneline参数。前面的一大串(红色框内)是commit id版本号,是使用SHA-1算法计算的一个摘要值的十六进制表示。

在git中,用HEAD表示当前版本,上一个版本就是HEAD^,上两个版本的话就是HEAD^^,往上100个版本的话写100个^比较麻烦,可以写成HEAD~100。
我们可以使用git reset命令实现版本的退回。(这里有一处疑问,--hard参数有什么意义?)

我们可以看到上图中readme.md的版本已经退回到了版本2了,并且使用git log可以看到之前的版本3已经不见了,此时如果想要回到版本3,你需要知道该版本的commit id(如果git窗口没有关的话,可以在上面找到,commit id不需要写全,只需要能够和其他版本区分开来即可)。

Git的版本退回速度非常块,因为git在内部有个指向当前版本的HEAD指针,版本的切换只是指针的移动而已。
当然,如果忘记了commit id怎么办,还有有后悔药的,可以使用git reflog查看自己的每一次命令版本库的变化。

工作区和暂存区
Git和其他版本控制系统SVN的一个不同之处就是有暂存区的概念。
工作区
就是该文件夹下能看到的目录(比如上文的learngit文件夹),就是工作区。

版本库
我们在创建版本库时会生成的隐藏目录.git就是Git的版本库
我们在修改版本库时,分为两步:
第一步是先git add将文件添加进去,实际上就是将工作区的文件添加到暂存区。
第二步再使用git commit提交更改,实际上就是将暂存区的所有内容提交到当前分支。
分支和HEAD的一些概念会在后面提到。

我们修改以下readme.md的内容(内容如下),并且新增一个LICENSE文本文件(内容无要求)
1 | Git is a distributed version control system. |
然后再使用git status查看一下状态。可以看到readme.md被修改了,而LINENSE还没有被添加,所以其状态时Untracked。

然后使用git add将两个文件添加到暂存区,使用git status查看状态;再使用git commit提交修改,使用git status查看状态(执行后如下图)。

总的来说,Git管理的文件分为:工作区,版本库,版本库又分为暂存区stage和暂存区分支master(仓库)
管理修改
我们再给readme.md添加一行(内容如下),然后使用git add命令将文件移动到暂存区。
1 | Git is a distributed version control system. |
然后再次修改readme.md,修改内容如下:
1 | Git is a distributed version control system. |
然后使用git commit提交,使用git diff HEAD -- readme.md查看工作区和版本库中的文件的区别(如下图),可以看到版本库和工作区的readme.md的版本不同之处。

回顾一下整个的操作过程:
第一次修改->git add->第二次修改->git commit,所以这就说明了git add命令只将第一次的修改放入了暂存区,然后的git commit命令再将暂存区的修改提交到了仓库。
撤销修改
需要撤销修改分为两种情况:
第一种是已经添加暂存区,然后再进行的修改,现在想要回到暂存区的状态。即回到最近一次
git add时的状态。第二种是修改后还没有放到暂存区,现在需要撤销到和版本库一摸一样的状态。即回到最近一次
git commit时的状态
第一种
git checkout -- filename可以丢弃当前工作区的修改,回到暂存区的状态。可以从下面的操作过程可以理会这一点。

第二种
git reset HEAD filename可以将暂存区的修改撤销掉(将仓库里的文件搬到了暂存区),然后再使用git checkout -- filename就能将暂存区的内容重新放回到工作区。下面的操作过程就很好的体现了这一点。

删除文件
git rm删除版本库中的文件

远程仓库
添加远程库
第一步:创建SSH Key(如果已创建可以直接跳过),输入以下命令(可以一路enter不设置密码),然后就会在用户主目录下产生一个.ssh文件夹,里面包含一对公私钥对(私钥是私人持有,公钥可以公开)
1 | $ ssh-keygen -t rsa -C "youremail@example.com" |

第二步:在Github中添加密钥,如下图所示(密钥名称任意,内容填写公钥)


第三步,创建一个远程仓库(略,但是最好不要添加readme.md和LINENSE),然后将该仓库与已有的本地仓库关联,然后将本地仓库的内容推送到Github仓库。
关联仓库,远程库的名字是origin,是git的默认叫法:
1 | $ git remote add origin git@github.com:YourGithubName/learngit.git |
将本地库的内容推送到远程,用git push命令,将当前分支master推送到远端,第一次推送需要加上-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
1 | git push -u origin master //首次提交 |

从远程库克隆
使用git clone命令,这个操作使用是比较多的,在我们远程库已经有内容时需要克隆到本地时,或是下载其他用户的源码时都是常用的。
1 | git clone git@github.com:AgentGuo/learngit.git |
当然,Github给出了两个地址,https和ssh,使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。
分支管理
创建和合并分支
在Git中,有一个主分支master,HEAD指针指向master,每一次新的提交,master就指向一个新的结点。这里直接用廖雪峰git教程的示意图

如果需要创建分支,就可以新建一个和master类似的dev指针,更改HEAD指向即可,然后我们就能在dev分支上愉快的开展我们的工作了(不断产生新节点)

假如我们在dev上完成了工作,就可以将dev合并到master主分支,即修改master指针,此时甚至能删除dev,因为删除的只是一个指针。

下面是实践过程,对一些命令做一些说明。
创建分支:git branch brachName
切换分支:git checkout brachName 或者git switch brachName
合并某分支到当前分支:git merge brachName
删除分支:git brach -d brachName

解决冲突
我们首先创建切换到一个新的feature分支,在readme.md中添加and simple,然后git add、git commit提交。

切换会master分支,在readme.md中添加& simple(图中我修改错了,但是无伤大雅),然后git add、git commit提交。

现在尝试合并将feature分支合并到master分支,会提示我们出现冲突,需要我们手动解决后再次合并:

那我们就修改readme.md的内容,可以看到Git在readme.md文档中用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,然后我们修改到和feature1内容一样,并git add,git commit提交,这下就能合并feature1分支了。

最后可以用带参数的git log命令查看分支的合并情况。

分支管理策略
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果添加--no-f参数强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
首先创建并切换到dev分支,然后简单的修改readme.md然后提交修改,回到master分支,使用git merge --no-ff命令合并分支。

所以,不使用Fast forward模式,merge后就像这样(图片来自廖雪峰教程):

在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样

BUG分支
假设此时你正在dev分支上开发,然而老板让你现在去master分支上改bug,此时你的工作还没有完成所以还不能提交到dev,这是就可以用stash功能。
首先在dev分支下编写一个简单的hello.py,然后使用git stash功能,将当前的工作现场保存起来,用于将来恢复。

在master分支上创建修复bug的临时分支issue_101,修改完成后回到master分支进行merge操作,完成bug的修复。

重新回到dev分支,发现之前的hello.py不见了,可以使用git stash apply恢复,但是恢复后保存的内容并不删除,需要使用git stash drop来删除,当然可以采用git stash pop直接弹出并删除,这样就成功恢复现场了,可以使用git stash list查看stash的内容(图中内容已经删掉了)。

但是不要忘了,成功恢复后的dev分支也是有之前master分支相同的bug,可以使用cherry-pick命令将特定的提交复制到当前分支。

Feature分支
有时我们为公司软件开发一些新特性,但是可能出于各种原因无法上线,所以不得不舍弃。
我们在dev分支下新建了一个feature分支,用于开发新特性。

然后回到dev分支,需要舍弃该新特性。但是直接用git branch -d会报错,因为需要删除的分支和当前分支内容不同(还没经过merge),此时可以使用git branch -D强制删除。

多人协作
git remote查看远程库的信息,加-v参数可以显示更加详细的信息。(可以用于抓取和推送的origin的地址)

然后我们开始推送分支,比如主分支master和dev分支(一般来说master作为主分支要时刻与远程通过,dev分支作为开发分支,团队成员需要在其上工作,所以也需要和远程同步,其他的一些个人的分支一般保存在本地即可)

这时候小伙伴就能够从另一台电脑上克隆该项目了(上传的话需要设置ssh密钥)

默认克隆下来后只有master分支,但是该小伙伴需要在dev分支上开发

该小伙伴开发完成后就可以将dev推送到远程库

在小伙伴已经对远程库进行修改的情况下,此时你尝试推送到远程库,会报错,因为远程库的内容与本地版本库内容不同。

这时就需要先git pull将origin/dev抓取下来,这时候可能也会报错,因为没有指定本地dev和远程origin/dev分支的链接,然后根据提示连接即可。

之后如果两人修改了同一文件的话就和解决冲突的方法一样了,此处就不再赘述了。
标签管理
标签的作用可以简单的理解为和commit id一样的作用,由于commit id复杂不便于记忆,所以引入了标签tag
创建标签
切换到某一分支下,使用git tag tagName命令就可以当前分支下的最近一次commit打上标签。使用git tag查看标签

当然也可以指定commit记录打标签,使用git tag tagName CommitID命令即可。可以使用git show tagName查看详细信息。

打标签时也可以附带说明文字,使用-m参数即可。

删除标签
删除本地标签使用git tag -d tagName即可。

还有一些可能选需要用到的命令在这就不一一演示了,如下:
将某个标签推到远程仓库:git push origin tagName
将所有标签推到远程仓库:git push origin --tags
删除远程标签:git push origin:refs/tags/tagName(需要先从本地删除)