Git的使用
全局配置
设置环境信息git config
,这个就可以理解为git命令的上下文环境,尤其是在和远程仓库交互的时候。
# 加 --system 参数配置信息存放在/etc/gitconfig文件中,对系统中所有用户适用
git config --system
# 加 --global 选项配置存放在 ~/.gitconfig文件中 对当前的所有仓库有效
git config --global
# 就在当前工作目录的.git目录下存放,仅适用于当前仓库,这个优先级最高。--local 是默认值
git config --local # 等效 git config
修改配置git config [--system|--global] -e
,它会打开一个类似VI编辑器的东西,这样就可以进行修改操作。
常用的设置包括:
# 下面是设置一个用户信息
git config [--global] user.name [USER_NAME] 不加用户名就是查看当前项目中配置的用户
# 设置邮箱,如果不加邮箱就是查看
git config [--global] user.email ""
# 设置密码,如果不加密码就是查看
git config [--global] user.password ""
# 禁用SSL验证
git config [--global] http.sslverify false
# 查看配置信息
git config --list
设置Giblab SSH key
我们在clone代码的时候可以选择http协议,当然我们亦可以选择ssh协议来拉取代码,一般在公司内部都会使用ssh协议。
正常情况下我们通过ssh连接是需要密码的
首先生成你的公钥和私钥ssh-keygen -t rsa
,然后复制~/.ssh/id_rsa.pub
里面的内容,然后使用你自己的账号登录Gitlab:
这里的名字会根据公钥自动设置,你可以修改。
保存即可。
首次使用常见场景
本地代码推送到远程现有仓库
这种方式要求你本地已经有代码目录,但是没有使用Git托管,且目录名称和远程仓库的名称一致。
创建归属到人的Gitlab账号
这个账号是真正的开发人员来使用,但是它不能创建任何项目,他也看不到任何私有项目,除非对他进行授权。
这个账号登录进来没有任何权限,也无法创建项目
创建项目账号
使用项目的Gitlab账号创建项目
,创建一个项目。项目名称也是账号,所以使用这个账号来登录并创建项目,但是这个账号并不是给开发用的,而是会单独授权给其他人。
授权,Maintainer
这个角色可以发起MR以及批准MR,给其他人授权、设置HOOKS,但是不能删除这个项目,作为普通Owner来说权限完全足够。
我们为什么要采取这种方式呢?看起来是绕了一个圈子,但是我们希望一个APP具有一个仓库,且该APP是这个仓库的最终管理者,其他人员都是具有某些权限的用户,这样我们把用户和仓库剥离,也就是说项目不属于某个用户,将来用户离职或者项目调整可以方便的进行设置。这种方式在阿里内部也是这么用的。
开发人员开始使用Git仓库
在GitLab上已经创建好了一个空项目,现在需要把本地代码推送上去放到Git上托管。
其实这里面有使用方法我们跟着步骤来做就好了
mkdir mex-ops-robots # 如果已经有这样一个代码目录这一步可以省略
cd mex-ops-robots
git init
git config http.sslverify false # 不是必须的
git config user.name "cy189999"
git config user.email "cy140987@contoso.com"
git remote add origin https://gitlab.example.com/mex-ops-robots/mex-ops-robots.git
touch README.MD
git add .
git commit -m "Initial commit"
git push -u origin master
上面的origin是什么意思呢?其实就是给后面的URL起的一个别名,之后再对这个仓库操作的时候就使用别名,就好像
git push -u origin master
,就是推送到仓库的master分支。这个别名记录在目录的.git
目录的config
里。
接下来就可以新建分支然后进行后续代码开发。
全新项目且没有已存在代码
这种场景是因为某种事项,需要创建一个新的APP,我们在Gitlab上创建新的项目和仓库然后使用。这种场景在公司里是比较多见的。
创建归属到人的Gitlab账号
这个账号是真正的开发人员来使用,但是它不能创建任何项目,他也看不到任何私有项目,除非对他进行授权。
这个账号登录进来没有任何权限,也无法创建项目
创建项目账号
使用项目的Gitlab账号创建项目
,创建一个项目。项目名称也是账号,所以使用这个账号来登录并创建项目,但是这个账号并不是给开发用的,而是会单独授权给其他人。
授权,Maintainer
这个角色可以发起MR以及批准MR,给其他人授权、设置HOOKS,但是不能删除这个项目,作为普通Owner来说权限完全足够。
开发人员开始使用Git仓库
在GitLab上已经创建好了一个空项目,现在要进行项目的开发。或者别人已经在做该项目,后期你加入该组一起开发。
全局设置,如果在公司里这一步是不需要的,因为这是公司范围的账号且你之前也有可能设置过。
git config --global user.name "cy189999"
git config --global user.email "cy140987@contoso.com"
git config --global http.sslverify false # 非必须,我测试环境无法验证证书
克隆项目到本地
git clone https://gitlab.example.com/mex-ops-robots/mex-ops-robots.git
cd mex-ops-robots
到这里就结束了,如果是全新项目,那么你可以通过IDE创建项目到这个目录,然后提交,如果是已存在的项目那么克隆下来是有代码的,你通过IDE直接打开就可以,然后创建自己的分支git checkout -b 分支名称
并继续开发。
常用操作
把新文件添加到本地仓库和移除
在仓库中创建一个新的文件,然后执行git status
,会有下面的显示,说明有新的内容没有被追踪。
然后我们运行git add .
或者git add 具体文件
来把未追踪的文件添加到暂存区。
如果我们要想把文件移除暂存区则执行git reset HEAD file1.txt
,如下:
如果把所有add的文件都撤回到工作区则执行git reset HEAD .
。
HEAD到底什么意思呢?在Git中,用
HEAD
表示当前版本。HEAD^
表示上一个版本,HEAD^^
表示上2个版本,写一大堆^
也不合适,所以HEAD~N
就表示上N个版本。但实际上HEAD是一个游标,它指向不同分支的某一个版本,所以你也不能简单认为它就是表示当前版本。
现在重新添加到暂存区git add .
, 然后进行提交,git commit -m "添加一个新文件"
,如果注释写错了可以使用git commit --amend
进行修改。
每次commit之后暂存区的内容会被提交到版本库中,然后会生成一个新的版本号。另外为commit添加说明的时候要尽可能的详细,不要简单几个词。不要怕字数多,原则是在描述清楚的同时使用精简的文字。
然后我们使用git log
命令可以查看提交历史,
如果要想撤回本次的commit怎么办?这里又分成2种情况,仅仅撤销commit和撤销commit和add操作。我们分开来说:
在当前版本中撤销commit
直接运行git reset --soft HEAD^
,表示撤销上一个版本,且仅仅是撤销上一次的commit操作,数据看起来就像又回到暂存区,其实不是回来了,而是切换了版本所导致的。严格来说它是回退到上一个版本的暂存区。
HEAD^
表示上一个版本,也可以写成HEAD~1
,表示撤销上一次commit。如果要撤销前2次的commit则输入git reset --soft HEAD~2
。需要注意的是这里使用的参数是--soft
,表示不删除代码改动,仅仅撤销commit而不撤销add操作。如果改成git reset --hard HEAD^
的话,其实它就是直接跳转到上一个版本,也就是修改了HEAD的指向,虽然看起来数据都没了,但是其实是存在的,只是在你当前的版本里没有。
在当前版本中同时撤销commit和add
最笨的办法是进行两部操作首先执行git reset --soft HEAD^
,然后输入git reset HEAD file1.txt
。但是其实有更好的办法来同时撤销,输入命令git reset HEAD^
或者git reset --mixed HEAD^
就可以实现同时撤销,不过--mixed
是默认参数所以直接使用git reset HEAD^
就行。
说明:git reset --soft
你不能单纯的理解它就是撤销commit,其实也对,但是严格来说是回退到上一个版本,因为每次commit都会产生新的版本,所以你撤销上一次commit就等于回退到上一个版本。不过--soft
仅仅是回退版本,不会把暂存区的内容移除。而--hard
则是直接回退到某个版本的工作区,你在工作区看到的内容也会变化。而--mixed
是撤销commit并把内容从暂存区移动到工作区,回退版本的同时把内容从暂存区移动到你的工作区恢复未被追踪的状态。
--soft
回退到指定版本的暂存区;--hard
回退到指定版本的工作区。
在多个版本间跳转(过去和现在自由切换)
通过2次提交,添加了2个新文件
查看log
运行下面的命令进行跳转git reset --hard 602f4e853d1b43f2e7cdc5cb54117b3794ee4585
,后面就是指定的提交ID。这里相当于把HEAD指向了之前的一次提交。
如何再跳转回去呢?你可能想到执行git reset HEAD^
这个命令,但是你不能执行它,因为它是对当前版本也就是HEAD的做一次操作上的回退,你目前的HEAD指向的是之前的一个版本,所以这个回退是在之前版本上做的,并不会让你回到上一次的版本。
我们应该使用git reflog
来查看一下我们执行跳转版本操作之前在哪个commit id上,然后跳过去。
从上图看,我们是从commit id为b7bbae3上跳转过去的。所以我们执行git reset --hard b7bbae3
这种操作对于误删除文件且进行了提交比较有帮助,我们可以切换到之前的版本上找到文件内容进行恢复后再切换回来。其实这个功能常用于版本回滚。
如何获取当前HEAD指向的版本是
获取完整IDgit rev-parse HEAD
,获取短IDgit rev-parse --short HEAD
。
通过这个命令我们可以知道当前HEAD指向的是哪个ID,这样在版本跳转时方便我们先记录一下当前的ID,然后再回到过去,操作完成再跳转回来,虽然可以通过git reflog
查看,但是也不太好找。
如何撤销所有工作区内的修改
这里我只修改了一个文件,但是如果我们修改了大量文件,而且突然想全部撤销所有修改那该怎么办呢?运行git checkout .
或者git checkout 文件名称
来进行操作。
这种方法只适用于把工作区修改过的内容还原。如果是针对已经提交的则需要使用git rest HEAD^
来把所有内容回退到工作区然后再执行该命令。
它到底是怎么还原回来的呢?这里就用到了暂存区和版本库,它先从暂存区中尝试恢复,如果暂存区中没有这个文件那么再从版本库中进行恢复。所谓恢复就是用暂存区或者版本库中的内容替换工作区。
运行git checkout .
或者git checkout 文件名
,它只影响工作区和暂存区中同时存在的文件且会把暂存区中该文件的内容覆盖到工作区的该文件中,而不影响只存在于工作区而暂存区中没有的文件。
推送代码到远程分支
从Gitlab上查看
stash的用法
这个命令就是把当前工作区所有modified的文件保存到一个其他地方,让你工作区变成一个没有被修改的状态。
这时候使用git stash
来进行保存。
这时候的工作区状态就是修改之前的状态。这就相当于保存之前所有的修改内容,回到一个干净的状态来做一些其他工作,比如在你开发新功能的时候突然要修复一个之前的BUG,这时候我们可以保存现在的内容。
这里模拟修BUG,我们修改了2个文件的内容
之后进行提交
现在我们可以把之前保存的内容拿回来继续开发,我们运行git stash pop
命令,
这里产生了冲突,因为我们上次stash之前修改了内容,而我们把这些修改通过stash保存了,这就导致stash之后没有那些内容,工作区是干净的,我们是基于未修改的工作区做的修复BUG工作,而且其中涉及了file1.txt和file2.txt,然后提交和发布。当这个修改工作完成后我们需要把之前stash的内容拿回来继续之前的开发工作,因为file2.txt之前我们没有修改所以没有冲突会被自动合并,但是file1.txt在我们stash前后都修改了所以就出现冲突,我们这时候就只能手动解决冲突,其实就是决定文件中应该保留哪些内容。解决完之后就可以继续开发。
# 查看stash了哪些内容
git stash list
# 删除指定编号的stash
git stash drop <NUM>
# 清空所有stash的内容
git stash clear
虽然我们平时基于分支开发,但是这个
git stash
也会用到,因为可能会出现临时切换其他分支情况,这时候如果当前分支没有commit则不允许你切换,可是此时你的开发工作并没有完成不应该进行提交,这时候你就应该使用git stash
命令保存现场然后切换分支,完成其他工作后再切换回来并执行git stash pop
来恢复现场。git stash pop
与git stash apply
都可以恢复现场,区别是前者恢复现场后会删除保存的现场后者不会删除。
branch的用法
其实针对上面的场景我们更加推荐使用创建分支的用法,master分支保留当前线上正在运行的代码,如果要开发新的功能或者修复BUG我们会基于master单独创建一个分支来做。
运行git checkout -b BRANCH_NAME
来创建分支并切换到分支上。这一条命令等于 git branch BRANCH_NAME
和git checkout BRANCH_NAME
。
其实创建分支后git会保证master分支和当前新的分支内容一样,然后我们在新分支开发,从此新分支和master分支就分开了。master分支是之前的内容,新分支包含了master分支及其以后修改的内容,等到新功能开发完毕之后可以把新分支合并到master上。
如果在新分支开发新功能的时候,如果要修复之前一个的一个BUG,那么我们会切换到master分支,然后再创建一个分支来做修复工作,修复完成并测试通过后合并到master上,然后我们再切换到之前的新功能分支上合并master分支到当前分支如果有冲突就解决,解决完成后继续之前的开发。
对于没有用的分钟我们可以通过git branch -d 分支名称
来删除,如果是强制删除则可以使用git branch -D 分支名称
。在使用-d
来删除分支的时候需要保证保留的分支和被删除分支一样新,或者保留的分支比被删除的分支更新。否则就会报错,当然这时你确信没有问题的话可以使用-D
来强制删除。
# 查看本地和远程分支
git branch -av
# 基于远程分支创建一个本地分支并切换,远程分支名称是 remotes/ 后面的内容
git branch -b 本地新分支名称 远程分支名称
revert和reset的区别
对于已经提交的内容上面我们使用的reset,但是其实还有另外一种方式,不过它俩的原理并不一样。
reset是修改HEAD指向,让其指向之前的版本的最后状态,这样我们看到的不是那个reset之前的那个提交状态。
而revert的原理是做一次反向操作然后提交用来抵消上一次修改,但是这会产生一个新的提交。所以它不是回退,而是提交一个反向操作。
下面使用revert来操作git revert HEAD --no-edit
,
可以看到file4.txt的内容以及不存在了,但是它产生了一个新的提交,通过反向操作并提交的方式实现撤销修改的效果。所以它并没有撤销之前的提交,只是通过反向操作抵消了之前提交所带来的变化。
如果不想产生新的提交怎么办?那就需要有一些手工的动作:
首先运行it revert HEAD --no-edit --no-commit
,这个--no-commit
就是抵消暂存区和工作区的变化,但是不产生新的提交。
然后就是删除之前的修改,运行git reset HEAD file4.txt
命令把修改从暂存区撤回到工作区,这个从提示中也可以看出来。
如果你想保留整个过程保存提交、撤销等操作就使用revert。如果想恢复到之前的版本且后续修改都不要了就可以使用reset。
pull和fetch的区别
我们从远程获取最新代码通常使用pull,其实pull里面是两步操作,它先fetch然后merge到当前工作区,如果有冲突我们需要解决冲突然后合并, 但是没有冲突的话就直接合并了。所以这里就有个隐患,这些东西我真的需要么或者我是不是在合并之前先检查一下。所以这就用到了fetch。
如果使用fetch的话,它会把远程仓库代码拉取到本地的远程仓库副本,注意这里并不是本地仓库而是存放在本地的远程仓库副本,所以fetch之后看不到工作区有什么变化。我在页面上添加了一个文件:
在本地工作区查看
执行git fetch origin master
操作
现在需要检查一下fetch下来的版本和我当前的有啥区别,git diff FETCH_HEAD
或者git diff origin/分支名称
,来进行比较查看。然后再进行合并git merge FETCH_HEAD
或者git merge origin/master
,合并后会进行一次提交。注意这里的合并一定是origin/分支名称
这个表示远程仓库的本地副本。最简单的办法就是在fetch前后使用git branch -av
来比较一下,其中remotes/origin/master
本地远程分支副本的版本发生变化而本地分支master
不会。
git diff FETCH_HEAD
就等于git diff FETCH_HEAD master
。我这里是在master分支操作的,请在实际中输入你的分支名称。
FETCH_HEAD,是一个版本链接,记录在本地的一个文件中,指向着目前已经从远程仓库取下来的分支的末端版本。在实际工作中推荐使用
git fetch
而不是直接使用git pull
。
merge和rebase的区别
一般我们会切换到特定分支然后执行git merge SOURCE_BRANCH
来把SOURCE_BRANCH
合并到当前分支,因为它会产生一个commit所以会导致曲线不规则,图形化显示提交过程git log --graph --oneline
。
上图会出现很多分叉。具体区别你真的懂git rebase吗?,写的很清楚,我就不弄了,因为用merge和rebase都行。
git checkout master
# 更新存储在本地的远程分支副本版本
git fetch origin master
git diff origin/master FETCH_HEAD # 比较
# 把存储在本地的远程分支副本更新到本地当前分支
git rebase
# 如果有冲突解决冲突,解决完
git add .
git rebase --continue
#
git checkout 其他分支
git rebase master # 把最新master分支合并到当前分支
HEAD是什么
我们之前说过HEAD指向当前最后一次提交的版本或者就说是当前版本。这个只是它最普通的含义,真正的含义是HEAD是一个游标,它可以动,它指向的是某一个分支而并不指向某个版本,它和版本的关联是间接的。
所以最直接的找到HEAD指向哪里我们就直接使用git log
就可以。
命令总结
# 克隆分支
git clone 地址
# 克隆指定分支
git clone -b [Branch] 地址
# 添加指定文件到暂存区
git add 文件名
# 递归添加当前目录文件及其子目录和文件
git add .
# 提交暂存区到本地版本库
git commit -m ""
# 把工作区和暂存区一起提交到本地版本库,它是把add和commit合并一起使用
git commit -a -m ""
# 提交到远程仓库
git push origin 仓库名
# 撤销工作区对文件的修改(恢复暂存区的内容到工作区,改变工作区。不建议使用,建议使用新版git的restore命令来恢复)
git checkout FILE_NAME
# 撤销工作区对文件的修改(修改了文件,在add之前想反悔,使用暂存区指定文件恢复到工作区,改变工作区)
git restore FILE_NAME
# 移除添加到暂存区的文件或文件内容它不改变工作区(git add 之后想反悔就用这个命令,它不改变工作区)
git restore --staged FILE_NAME
# 恢复暂存区指定文件和HEAD一致(使用上一次提交来覆盖暂存区指定文件,不改变工作区)
git reset HEAD FILE_NAME1 FILE_NAME2 ..
# 恢复整个暂存区和HEAD一致(使用上次提交来覆盖整个暂存区,不改变工作区)
git reset HEAD
# 比较工作区和当前版本的区别(比较当前工作区与本地版本库最近一次commit的内容)
git diff HEAD
# 比较工作区和暂存区的区别
git diff
# 比较暂存区和当前版本的区别
git diff --cached
# 重置当前HEAD为指定版本,使用上一个版本重置暂存区,但工作区不变。
git reset 版本
# 把版本库回退到指定版本,当前本地工作区和暂存区不变,版本库改变
git reset --soft 版本
# 回退到指定版本,工作区和暂存区都会发生变化(谨慎使用)
git reset --hard 版本
# 创建并切换到新分支
git checkout -b 分支名称
# 修改目录名称
git mv -f OLD_FOLDER NEW_FOLDER
git add -u NEW_FOLDER # -u 更新已经追踪的文件和目录
git commit -m "XXXX"
# 拉取特定远程分支方式1
git clone -b 远程分支名称 GIT地址
# 拉取特定远程分支方式1
git branch -r # 查看远程分支
git checkout -b 本地分支 origin/远程分支
# 拉取指定tag的
git clone --branch TAG_NAME 仓库地址