Git学习

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
2
mkdir +文件夹名		//创建文件夹
git init //将文件夹变成可管理的仓库

将文件添加到版本库

新建一个readme.md文件,在文件中写入如下内容:

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

第一步,用命令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添加文件需要addcommit一共两步?

因为commit可以一次提交很多文件,所有可以多次add不同文件后再commit

1
2
3
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."

时光机穿梭

修改版本库

修改readme.md,改成如下内容:

1
2
Git is a distributed version control system.
Git is free software.

运行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
2
Git is a distributed version control system.
Git is free software distributed under the GPL.

Ui7h3n.png

就这样不断的对文件进行修改,然后不断的提交修改到版本库中,类似于我们玩游戏,在关键点存档,不至于在阵亡后重新开始游戏。这个存档在Git中被称为commit,一旦给文件改乱了,可以从最近的一个commit恢复,而不需要重新开始。

以下是我们修改过的三个版本:

版本1:wrote a readme file

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

版本2:add distributed

1
2
Git is a distributed version control system.
Git is free software.

版本3:append GPL

1
2
Git is a distributed version control system.
Git is free software distributed under the GPL.

在git中,我们可以使用git log命令查看。可以看到最近到最久远的一些提交记录。

UibJFe.png

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

Uiq9te.png

在git中,用HEAD表示当前版本,上一个版本就是HEAD^,上两个版本的话就是HEAD^^,往上100个版本的话写100个^比较麻烦,可以写成HEAD~100

我们可以使用git reset命令实现版本的退回。(这里有一处疑问,--hard参数有什么意义?)

UiLAC4.png

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

UijOZq.png

Git的版本退回速度非常块,因为git在内部有个指向当前版本的HEAD指针,版本的切换只是指针的移动而已。

当然,如果忘记了commit id怎么办,还有有后悔药的,可以使用git reflog查看自己的每一次命令版本库的变化。

UizlIP.png

工作区和暂存区

Git和其他版本控制系统SVN的一个不同之处就是有暂存区的概念。

工作区

就是该文件夹下能看到的目录(比如上文的learngit文件夹),就是工作区。

UFSPyQ.png

版本库

我们在创建版本库时会生成的隐藏目录.git就是Git的版本库

我们在修改版本库时,分为两步:

第一步是先git add将文件添加进去,实际上就是将工作区的文件添加到暂存区。

第二步再使用git commit提交更改,实际上就是将暂存区的所有内容提交到当前分支。

分支和HEAD的一些概念会在后面提到。

UF9lI1.png

我们修改以下readme.md的内容(内容如下),并且新增一个LICENSE文本文件(内容无要求)

1
2
3
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.

然后再使用git status查看一下状态。可以看到readme.md被修改了,而LINENSE还没有被添加,所以其状态时Untracked

UFC2p6.png

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

UFPuNR.png

总的来说,Git管理的文件分为:工作区,版本库,版本库又分为暂存区stage和暂存区分支master(仓库)

管理修改

我们再给readme.md添加一行(内容如下),然后使用git add命令将文件移动到暂存区。

1
2
3
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.

然后再次修改readme.md,修改内容如下:

1
2
3
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 of files.

然后使用git commit提交,使用git diff HEAD -- readme.md查看工作区和版本库中的文件的区别(如下图),可以看到版本库和工作区的readme.md的版本不同之处。

UFEiOf.png

回顾一下整个的操作过程:

第一次修改->git add->第二次修改->git commit,所以这就说明了git add命令只将第一次的修改放入了暂存区,然后的git commit命令再将暂存区的修改提交到了仓库。

撤销修改

需要撤销修改分为两种情况:

  • 第一种是已经添加暂存区,然后再进行的修改,现在想要回到暂存区的状态。即回到最近一次git add时的状态。

  • 第二种是修改后还没有放到暂存区,现在需要撤销到和版本库一摸一样的状态。即回到最近一次git commit时的状态

第一种

git checkout -- filename可以丢弃当前工作区的修改,回到暂存区的状态。可以从下面的操作过程可以理会这一点。

UFVl8A.png

第二种

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

UFZEGj.png

删除文件

git rm删除版本库中的文件

UFZhTS.png

远程仓库

添加远程库

第一步:创建SSH Key(如果已创建可以直接跳过),输入以下命令(可以一路enter不设置密码),然后就会在用户主目录下产生一个.ssh文件夹,里面包含一对公私钥对(私钥是私人持有,公钥可以公开)

1
$ ssh-keygen -t rsa -C "youremail@example.com"

UFYvWt.png

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

UFtf0g.png

UFN991.png

第三步,创建一个远程仓库(略,但是最好不要添加readme.mdLINENSE),然后将该仓库与已有的本地仓库关联,然后将本地仓库的内容推送到Github仓库。

关联仓库,远程库的名字是origin,是git的默认叫法:

1
$ git remote add origin git@github.com:YourGithubName/learngit.git

将本地库的内容推送到远程,用git push命令,将当前分支master推送到远端,第一次推送需要加上-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。

1
2
git push -u origin master	//首次提交
git push origin master //以后提交

UFUJsK.png

从远程库克隆

使用git clone命令,这个操作使用是比较多的,在我们远程库已经有内容时需要克隆到本地时,或是下载其他用户的源码时都是常用的。

1
git clone git@github.com:AgentGuo/learngit.git

当然,Github给出了两个地址,httpsssh,使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https

分支管理

创建和合并分支

在Git中,有一个主分支masterHEAD指针指向master,每一次新的提交,master就指向一个新的结点。这里直接用廖雪峰git教程的示意图

UF03kD.png

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

UF0dnP.png

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

UF0rtg.png

下面是实践过程,对一些命令做一些说明。

创建分支:git branch brachName

切换分支:git checkout brachName 或者git switch brachName

合并某分支到当前分支:git merge brachName

删除分支:git brach -d brachName

UFceU0.png

解决冲突

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

UF4Uw4.png

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

UF4zpq.png

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

UF5AAJ.png

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

UF53AH.png

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

UF5bgx.png

分支管理策略

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果添加--no-f参数强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

首先创建并切换到dev分支,然后简单的修改readme.md然后提交修改,回到master分支,使用git merge --no-ff命令合并分支。

UFO8UK.png

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

UFOIaV.png

在实际开发中,我们应该按照几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;

你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样

UFj1fO.png

BUG分支

假设此时你正在dev分支上开发,然而老板让你现在去master分支上改bug,此时你的工作还没有完成所以还不能提交到dev,这是就可以用stash功能。

首先在dev分支下编写一个简单的hello.py,然后使用git stash功能,将当前的工作现场保存起来,用于将来恢复。

UkJoTJ.png

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

UkYrjK.png

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

Uktk59.png

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

UkNwy6.png

Feature分支

有时我们为公司软件开发一些新特性,但是可能出于各种原因无法上线,所以不得不舍弃。

我们在dev分支下新建了一个feature分支,用于开发新特性。

Ukazi4.png

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

UkdEdO.png

多人协作

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

UkrBIf.png

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

UksctK.png

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

UkyLUx.png

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

Uk69rd.png

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

UkWUEj.png

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

UkWzxf.png

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

UkfaLD.png

之后如果两人修改了同一文件的话就和解决冲突的方法一样了,此处就不再赘述了。

标签管理

标签的作用可以简单的理解为和commit id一样的作用,由于commit id复杂不便于记忆,所以引入了标签tag

创建标签

切换到某一分支下,使用git tag tagName命令就可以当前分支下的最近一次commit打上标签。使用git tag查看标签

UkHRaT.png

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

Ukbezj.png

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

UkbROI.png

删除标签

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

UkqE0x.png

还有一些可能选需要用到的命令在这就不一一演示了,如下:

将某个标签推到远程仓库:git push origin tagName

将所有标签推到远程仓库:git push origin --tags

删除远程标签:git push origin:refs/tags/tagName(需要先从本地删除)