Git基本配置
Git安装好之后,需要通过如下两行命令做一个基本配置,配置的信息将展示在我们每一次提交的后面,所以不要使用不方便公开的信息,如果不配置以后每次提交的时候都会让你输入用户名和密码。
git config --global user.name "java" git config --global user.email "123456@qq.com"
这个配置会保存在当前用户目录下的.gitconfig文件中。
Git基本操作
工作区和暂存区
Git中引入了暂存区/缓存区(Stage/Index)的概念
- 工作区很好理解,就是本地的文件夹。
- 这些本地的文件我们要通过git add命令先将他们添加到暂存区中。
- git commit命令则可以将暂存区中的文件提交到本地仓库中去。
初始化仓库
仓库的初始化有两种方式:一种是直接从远程仓库克隆,另一种则是直接从当前目录初始化。
当前目录初始化git init
执行完成后当前目录下会多出一个.git的隐藏文件夹,所有git需要的数据和资源都存放在该目录中。
查看仓库状态
通过git status命令来查看仓库中文件的状态。
git status
添加文件到暂存区
git add命令可以将一个文件添加到暂存区。
git add 01.txt
提交到本地仓库
当文件提交到暂存区之后,通过git commit命令将当前暂存区的文件提交到本地仓库。
注意,执行commit命令时,需要加上提交备注,即-m参数。git commit -m "第一次提交代码"
提交成功之后,我们可以通过如下命令修改提交备注。
git commit --amend
运行该命令,会自动打开vi编辑器,此时我们可以重新编辑上次提交的备注信息。
查看提交日志
通过git log命令我们可以查看以往仓库中提交的日志,比如提交的版本号、提交者、提交者邮箱、提交时间、提交备注等信息。
git log
有的时候我们要查看的命令并不用这么详细,可以在git log后面加上–pretty=short,这样显示出来的就只是简略信息了。
git log --pretty=short
如果只想查看某一个文件的提交日志,在git log后面加上文件名即可。
git log 01.txt
如果我还想查看提交时文件的变化,加上-p参数即可。
git log -p 01.txt
绿色的+表示新增的行,红色的-表示删除的行。
git log有一个局限性,就是不能查看已经删除的commit的日志。
举个例子:下班了,我发现今天下午提交的代码全都是有问题的,于是做了一个版本回退,回退到今天早上的状态,然后关机回家,第二天来了后我发现搞错了,其实那些代码都是OK的,于是我又想让仓库版本前进到昨天下午的状态,却发现git log命令查看不到昨天下午提交的版本号。
此时,我们可以使用git reflog命令来实现这一个请求,git reflog命令可以显示整个本地仓库的commit, 包括所有branch的commit, 甚至包括已经撤销的commit, 只要HEAD发生了变化, 就会在reflog里面看得到,而git log只显示当前分支的commit,并且不显示删除掉的commit。git reflog
查看更改前后的差异
使用git diff命令我们可以查看工作区和暂存区的区别。
git diff
使用git diff HEAD命令我们可以查看工作区和本地仓库的差别。
git diff HEAD
Git中的各种后悔药
Git强大的撤销、版本回退功能,让我们在开发的过程中能够随意的回到任何一个时间点的状态。
工作区的代码想撤销
可以通过
git checkout -- <file>
命令来撤销工作区的代码修改。git checkout 01.txt
add到暂存区的代码想撤销
- 将暂存区的代码撤销到工作区
- 将工作区的代码撤销(具体操作和’工作区的代码想撤销’一致)
将暂存区的代码撤销,可以使用git reset HEAD命令来实现。
git reset HEAD git checkout 01.txt
提交到本地仓库的代码想撤销
提交到本地仓库的代码一样也可以撤销,可以利用git reset --hard <版本号>命令来实现版本回退,该命令中的版本号有几种不同的写法:
- 可以使用HEAD^ 来描述版本,一个^ 表示前一个版本,两个^^表示前两个版本,以此类推。
- 也可以使用数字来代替^,比如说前100个版本可以写作HEAD~100。
- 也可以直接写版本号,表示跳转到某一个版本处。我们每次提交成功后,都会生成一个哈希码作为版本号,所以这里我们也可以直接填版本号,哈希码很长,但是我们不用全部输入,只需要输入前面几个字符即可,就能识别出来。
1.通过git log查看当前提交日志
git log
2.通过git reset HEAD^^向前回退两个版本
git reset HEAD^^
3.查看日志,发现最后一次提交的版本号是695ce1fe,利用git reset —hard 695ce1fe命令回到回退之前的状态
git reset —hard 695ce1fe
4.通过git reset —hard HEAD~1回到上一个版本
git reset —hard HEAD~1
Git分支管理
查看分支
可以通过git branch命令来查看当前仓库有哪些分支,而我们处于哪一个分支中。
git branch
分支创建和切换
可以利用git branch <分支名>命令来创建一个分支,然后利用git checkout <分支名>来切换分支
git branch fa git checkout fa git branch
也可以通过git checkout -b <分支名>来一步到位,创建并切换分支
git checkout -b fb git branch
也可以通过git checkout -命令来切换回上一个分支
git checkout -
分支合并
想要合并分支,我们先切换到master分支上,然后执行git merge --no-ff fa命令即可完成分支合并
git merge --no-ff fa
以图表方式查看分支
可以通过git log --graph命令来直观的查看分支的创建和合并等操作
git log --graph
分支衍合
所谓的分支衍合其实也是分支合并的一种方式,下面我们就来看看这个分支衍合到底是什么样的。现在我的master分支的内容和fa分支的内容是保持一致的,fa是从master中创建出来的,如下图
现在我向fa和master中各自做一次提交,如下图
此时我们执行如下两条命令将两个分支合并git checkout fa git rebase master
rebase命令在执行的过程中会首先把fa中的每个commit取消,并且将之保存为临时patch,再将fa分支更新为最新的master分支,然后再把那些临时的patch应用到fa上,此时fa分支将指向新创建的commit上,那些老的commit将会被丢弃,这些被丢弃的commit在执行git gc命令时会被删除。合并后的分支如下图
上面的git rebase master命令在执行的过程中有可能会发生冲突,发生冲突时我们有两种方案,一种直接退回到之前的状态,另一种就是解决冲突继续提交。
- 退回到之前的状态
git rebase --abort
- 解决冲突
不过大多数情况下我们都是要解决冲突的,解决之后继续提交。此时我们用编辑器打开冲突的文件,看到的内容可能是这样的
======上面的是HEAD中的内容,下面的是要合并的内容,根据自己的需求编辑文件,编辑完成之后,通过如下两条命令继续完成合并:git add 01.txt git rebase --continue
冲突解决
前面提到了在分支衍合时出现冲突的解决方案,其实普通的合并也有可能出冲突,出现冲突很正常,解决就是了,git merge合并分支时如果出现冲突还是先重新编辑冲突文件,编辑完成之后,再执行git add 和git commit即可。
Git关联远程仓库
配置SSH KEY
SSH KEY的配置不是必须的,不配置的话我们就只能使用HTTPS协议,这样每次提交时要输入用户名密码,略麻烦,所以还是配置一下。配置SSH KEY的原理很简单,采用非对称加密方式生成公钥和私钥,公钥告诉GitHub,私钥留在自己电脑上(私钥不可泄露),当我们向GitHub上提交数据时,GitHub会用我们留给它的公钥加密一段消息返回给我们的电脑,如果我们能够用私钥解密成功,说明是合法的用户,这样就避免我们输入用户名密码了。大致的原理就是这样,现在很多免登录的系统都采用了这种方式,比如Hadoop免登录配置也是这样。
- 查看本地是否已有SSHKEY
查看当前用户目录下是否有.ssh文件,如下ls la ~/.ssh
如果查看之后有结果,则直接跳转到第四步,什么都没有就继续生成。
- 生成SSH指纹
生成SSH指纹的命令很简单,如下:ssh-keygen -t rsa -b 4096 -C "你的邮箱地址"
注意邮箱地址要替换。
- 添加ssh到ssh-agent中
执行如下命令即可:eval "$(ssh-agent -s)"
OK,做好这一切之后,我们当前用户目录下已经有了一个名为.ssh的隐藏文件夹了,打开这个目录,会发现有一个名为id_rsa.pub的文件,这就是我们一会要使用的公钥文件。
- 将公钥告诉GitHub
登录GitHub,点击右上角的向下的箭头,选择Settings,在新打开的页面中左边侧栏选择SSH and GPG keys,如下
完了之后点击最下面的Add SSH key按钮即可,如此之后,我们的SSH KEY就配置成功了。
创建远程仓库
接下来我们在GitHub上创建一个仓库,登录成功之后,直接点击右上角绿色的New repository按钮,如下
其实这里我们只需要填一个版本仓库的名字,我填了test,填好之后,点击Create repository就OK了。
关联远程仓库
创建成功之后,我们会看到仓库的地址,如下:git@github.com:lenve/test.git,然后我需要将我们之前的本地仓库和这个远程仓库进行关联,使用git remote add命令,如下:
$ git remote add origin git@github.com:lenve/test.git
在这条命令中,git会自动将远程仓库的名字设置为origin,方便我们的后续操作。
推送到远程仓库
推送到master分支
假设我想将本地master分支上的内容推送到远程master分支上,方式如下:
$ git push -u origin master
-u参数可以在推送的同时,将origin 仓库的master 分支设置为本地仓库当前分支的upstream(上游)。添加了这个参数,将来运行git pull命令从远程仓库获取内容时,本地仓库的这个分支就可以直接从origin 的master 分支获取内容,省去了另外添加参数的麻烦。这个参数也只用在第一次push时加上,以后直接运行git push命令即可。
推送到其他分支
如果想推送到其他分支,还是这条命令,修改一下分支的名字即可,比如我也想把我的fa分支推送到远程仓库中,执行如下命令:
git checkout fa git push -u origin fa
先切换到fa分支,然后执行git push命令,参数含义和之前的一样,这里我们创建的远程仓库的分支名也为fa(当然我们可以取任何名字,但是为了不混淆,最好取一致的名字)。这两条命令执行成功之后,此时在网页中我们就可以看到已经有多个分支了,如下:
从远程仓库获取
首次获取
刚刚是我们向远程仓库提交数据,有提交当然就有获取,我们可以通过git clone命令克隆一个远程仓库到本地,方式也简单,在本地创建一个空文件夹,执行如下命令:
git clone git@github.com:lenve/test.git
表示克隆文件到本地仓库。此时克隆的远程仓库的master分支到本地仓库,我们可以通过git branch -a来查看本地仓库和远程仓库的信息,-a参数可以同时显示本地仓库和远程仓库的信息,如下:
我们看到远程仓库中已经有了fa分支了,如果我们想把fa分支也克隆下来,执行如下命令:git checkout -b fa origin/fa
表示根据远程仓库的fa分支创建一个本地仓库的fa分支
从远程仓库更新
此时我们回到第一次最早的那个test本地仓库中,那个test仓库的fa分支现在和远程仓库不一致了,我们可以通过git pull命令来更新,如下:
Git工作区储藏
现在有一个master分支,master分支中有一个文件叫01.txt,该文件中只有一行数据,然后对01.txt执行add和commit,然后再从master分支中创建出一个新的分支fa,切换到fa分支上,然后向01.txt中再添加一行数据,添加成功之后,不做任何事情,再切换回master分支,此时用cat命令查看01.txt文件,发现竟然有两行数据,按理说master中的01.txt只有一行数据,而fa中的01.txt有两行数据,整个过程如下图:
- 方案一
第一种解决方案就是在某一个分支修改文件之后,先add并且commit之后再去切换分支,这个操作就比较简单了,这里就不再演示了。
- 方案二(储藏)
第二种解决方案就是储藏(Stashing),储藏适用在如下场景中:
当我在一个分支fa中修改了文件,但是还没有完全改好,此时我并不想add/commit,但是这个时候有一个更急迫的事情在另外一个分支fb上需要我去做,我必须要切换分支。
在这样一个场景中,如果我直接切换分支,会出现如下两个问题:1.从fa切换到fb之后,工作区的代码还是fa的代码,不符合我的工作要求。
2.假设我不在乎问题1,在fb中直接修改工作区的代码,等我在fb中修改完后提交后再回到fa,会发现我之前的代码丢失了。
为了解决这个问题,Git给我们提供了储藏(Stashing)。
现在假设一开始master和fa分支中的文件内容都是一致的,而且两个分支的工作区都是干净的,即没有东西需要add/commit,此时,我在master中修改了文件,修改完成之后,执行git status命令我们看到master中有东西需要add/commit,此时我想切换到fa分支中去,但是并不想对master分支执行add/commit,这个时候我们可以执行如下命令,先将当前分支中的文件储藏起来:git stash
OK,执行完git stash命令之后,再执行git status,我们发现此时master分支已经是干净的了,此时我们可以愉快的切换到fa分支中去了,切换到fa分支之后,我们发现master中的修改并没有干扰到fa分支,当我们完成了fa分支中的工作之后,再回到master分支,此时执行如下命令可以恢复刚刚储藏的数据:
git stash apply
上面这个命令执行完之后,master分支中的工作区中的文件就恢复了,此时执行git status就可以看到又有数据需要add/commit了。
我们也可将工作区储藏多次,这个时候我们可以执行如下命令来查看储藏:git stash list
执行效果如下:
git stash apply表示恢复最近一次储藏,如果我们想恢复到之前的某一次储藏,可以加上储藏的名字,如下:git stash apply stash@{1}
恢复储藏并出栈
git stash pop
执行效果和git stash apply一样,不同的是,这里执行完之后,会将栈顶的储藏移除。
删除某一个储藏
git stash drop stash@{4}
最后一个参数是指储藏的名字。
Git标签管理
我们可以针对某一次的提交打上一个标签,有点类似于给某次提交取个别名,比如1.0版本发布时打个标签叫v1.0,2.0版本发布时打个标签叫v2.0,因为每次版本提交的结果都是一连串的哈希码,不容易记忆,打上v1.0,v2.0这些具有某种含义的标签后,可以方便我们进行版本管理。
轻量级标签
轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。
首先我们可以通过如下命令来查看当前仓库中的所有标签:git tag
打标签的方式很简单,直接通过git tag 来完成即可,如下命令:
git tag v1
表示创建了一个名为v1的tag,这个tag默认是创建在最新一次的commit上的,如下:
我们可以利用git show 来查看标签对应的版本信息,如下:
我们可以通过git tag -d 命令删除一个标签:git tag -d v1
如果我想给历史上的某次commit打一个标签呢?我们可以通过如下命令git tag ,如下:git tag v0.0 7d519
表示给commit的哈希码为7d519的那一次commit打上一个标签,如下图:
含附注的标签
而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。
打一个含附注的标签很简单,使用git tag -a -m 命令,如下:git tag -a v0.0 -m "文件初次建立" 7d519
如果不加最后的版本号参数,表示给最新的一次commit打标签。
签署标签
说到签署标签我们得先介绍一下GPG:
GPG是加密软件,可以使用GPG生成的公钥在网上安全的传播你的文件、代码。为什么说安全的?以Google所开发的repo为例,repo即采用GPG验证的方式,每个里程碑tag都带有GPG加密验证,假如在里程碑v1.12.3处你想要做修改,修改完后将这个tag删除,然后又创建同名tag指向你的修改点,这必然是可以的。但是,在你再次clone你修改后的项目时,你会发现,你对此里程碑tag的改变不被认可,验证失败,导致你的修改在这里无法正常实现。这就是GPG验证的作用,这样就能够保证项目作者(私钥持有者)所制定的里程碑别人将无法修改。那么,就可以说,作者的代码是安全传播的。为什么会有这种需求?一个项目从开发到发布,再到后期的更新迭代,一定会存在若干的稳定版本与开发版本(存在不稳定因素)。作为项目发起者、持有者,有权定义他(们)所认可的稳定版本,这个稳定版本,将不允许其他开发者进行改动。还以Google的repo项目为例,项目所有者定义项目开发过程中的点A为稳定版v1.12.3,那么用户在下载v1.12.3版本后,使用的肯定是A点所生成的项目、产品,就算其他开发者能够在本地对v1.12.3进行重新指定,指定到他们修改后的B点,但是最终修改后的版本给用户用的时候,会出现GPG签名验证不通过的问题,也就是说这样的修改是不生效的。
——摘自<[带GPG签名的Git tag]>一文使用签署标签我们先要生成GPG Key,生成命令如下:
gpg --gen-key
能默认的就直接按回车默认,不能默认的就根据提示输入相应的值,这里的都很简单,不再赘述。完了之后,就可以通过如下命令来打标签了:
git tag -s v0.0 -u "laowang" -m "文件初次建立" 7d519
就把上面的-a换成-s,然后添加-u参数,-u参数的值是我们在生成GPG Key的时候配置的name属性的值,注意-u参数不可以写错,否则标签会创建失败,如下:
标签推送到远程仓库
git push命令并不会把tag提交到远程仓库中去,需要我们手动提交,如下:
git push origin v0.0
表示将v0.0标签提交到远程仓库,也可以通过git push origin --tags提交所有的tag到远程仓库,如下:
此时别人调用git pull更新代码之后,就能看到我们的tag。如下
Git Subtree
Git Subtree 是 Git 官方给出的一个管理子项目的解决方案,在 Git Subtree 之前,官方给出的方案是 Git Submodule,但是从 Git1.5.2 开始,Git 新增并推荐使用这个功能来管理子项目,只要大家本地安装的 Git 版本大于等于 1.5.2,都可以直接使用 Git Subtree。
Git Subtree 虽然不具备依赖管理的功能,但是在处理快速代替的公共代码库时,却显得非常得心应手,而且它能够做到双向同步!
首先,假设我现在有一个项目叫做 vmall,vmall 是一个微服务项目,里边包含了很多微服务,同时也包含了一个在快速迭代的公共代码块 vmall-common(注意,vmall 和 vmall-common 分属两个不同的仓库)。
vmall-common 我已经提交在 GitHub 上了,地址是 https://github.com/lenve/vmall-common。
现在我想在 vmall 项目中引用 vmall-common,怎么做呢?在 vmall 仓库中执行如下代码即可:git subtree add --prefix=vmall-common https://github.com/lenve/vmall-common.git master --squash
最后的 --squash 参数表示不拉取历史信息,而只生成一条 commit 信息,这是一个可选参数,可以不加。这行命令执行完成后,在 vmall 项目中,就可以看到 vmall-common 了,而且 vmall-common 将作为一个普通的文件夹存在,该怎么样还是怎么样。
假如说,我们在开发的过程中,修改了 vmall-common,这个时候,在 vmall 仓库中,我们可以通过如下命令将 vmall-common 提交到它自己的仓库中去(先将 vmall 中的变化提交到远程仓库,再执行如下代码):git subtree push --prefix=vmall-common https://github.com/lenve/vmall-common.git master
当 vmall-common 中的代码发生变化了,其他微服务通过如下指令可以更新代码:
git subtree pull --prefix=vmall-common https://github.com/lenve/vmall-common.git master --squash
这三个指令基本上就能应付日常的大部分操作了,不过每次都要输入一个长长的地址很不方便,我们可以给地址取一个别名:
git remote add -f vmall-common https://github.com/lenve/vmall-common.git
这样,最上面介绍的三个命令就可以简化了:
git subtree add --prefix=vmall-common vmall-common master --squash git subtree pull --prefix=vmall-common vmall-common master --squash git subtree push --prefix=vmall-common vmall-common master