前言:
大佬直接进入下面正题,前言可以略过!
我相信有很多童鞋在刚开始使用git或者使用了许久之后还是对git不怎么熟悉,尤其是出现了一大堆英文提示的时候手足无措,又或者代码"莫名其妙"的发生改动,导致团队无法继续正常工作的时候真恨不得不继续使用git了.
不要慌!这篇文章将带你完完全全掌握平时常见的场景以及出现的问题如何解决的本领!(同时我自己也可以再复习一遍~🤭)
那么本文章默认你已经学习过了git,但只是对git有的知识一知半解的情况下去阐述!(也许热爱git的你还不熟悉git基础知识建议可以先去其他地方学习git相关的基础知识,遇到不理解的也许这篇文章就会帮你解开谜团!)
最小配置
那么我们会发现在使用git之前需要配置一段这样的命令(如果不配置以后push之类的操作会有一大堆提示信息影响我们的后续操作):
$ git config --global user.name 'your_name'
$ git config --global user.email 'your_email@domain.com'
那么这段命令中的--global大家有没有想过是什么意思呢?究竟有什么作用?
config三个作用域
来讲下刚刚的--global(全局),那么既然有全局就肯定有局部作用域:在git中叫做--local,其实还有一个是针对系统的:--system
--global是对当前用户(根据系统用户名判定)所有仓库有效
--local只对当前工作的这个仓库有效
--system对系统所有登录的用户有效(不常用,对整个系统进行配置统一的配置项)
查看config的设置命令:
$ git config --list --local
$ git config --list --global
$ git config --list --system
建立git仓库
把已有的项目代码纳入Git管理(场景一):
$ cd 项目代码所在的文件夹
$ git init
新建的项目直接用Git管理(场景二):
$ cd 某个文件夹
$ git init your_project #会在当前路径下创建和项目名称同名的文件夹
$ cd your_project
文件重命名(方法一)
假设我们重命名readme.md为read.md
所以我们执行以下命令
$ mv readme.md read.md
这条命令会被git认为是 readme.md会删除掉了,然后新增了一个read.md文件
我们现在要做的是把新增的read.md文件添加到git管理里,且删除原来的readme.md文件
$ git add read.md
$ git rm readme.md
然后使用git status查看管理状态
结果会发现现在变成了
Changes to be commited:
(use "git reset HEAD <file>..." to unstage)
renamed: readme.md -> read.md
至此,说明git已经知道了我们在做重命名操作
假设现在我们发现自己误操作了,不想要刚刚的操作了,怎么办呢?
$ git reset --hard
其实这条命令属于git中比较危险的命令,他的意思是把暂存区工作空间里内容都清理掉(还原到刚刚操作重命名之前的状态 log 还是会保留的)
文件重命名(方法二)
细心的童鞋可能发现刚刚重命名的操作过于复杂,那有没有简单的方法重命名呢?答案是:当然有!(有木有很激动)
$ git mv readme.md read.md
然后使用git status查看管理状态
结果会发现现在也变成了
Changes to be commited:
(use "git reset HEAD <file>..." to unstage)
renamed: readme.md -> read.md
这样一句命令就完成了方法一完成的事情,是不是很简单呢~ 那么我们想提交本次操作该怎么做呢? 我们可以直接使用
$ git commit -m '重命名readme.md为read.md'
我们发现现在已经成功的新生成了一个commit记录
那么我们突然发现这次commit没什么作用想撤销本次commit提交该怎么做
$ git log #查看所有commitId
$ git revert <commit> #找到你想撤销的commitId复制并替换掉这里的<commit>
commitId是类似这样的:
删除暂存区文件(并不会直接删除工作区的文件)
$ git rm 文件名
暂存区相关
$ git rm --cached . #清理暂存区所有文件
git log
通过git log查看版本演变历史
其实git log后面还可以携带参数
$ git log --oneline --all #可以查看简洁的所有commit信息
$ git log -n4 #代表查看最近四次的commit信息
$ git log --all --graph #查看基于图形的commit历史记录
$ git log --oneline <branch> #查看某个分支的commit记录
分支操作
$ git branch -av #查看本地共有多少分支
$ git checkout -b temp #创建一个新的temp分支
$ git checkout -b temp <commitId> #基于某一个历史版本新建一个分支
删除本地分支: git branch -d/-D 分支名
删除远程分支: git push origin --delete 分支名
查看关联关系: git branch -vv
删除分支
$ git branch -d 分支名
如果git有提示说明是在一些情况下git不允许删除,如果确认的确需要删除,那么可以把小写d改成大写D来强制删除
remote
常规关联本地 git init 到远程仓库的姿势
git remote add origin url
新增其他上游仓
git remote add github url
修改推送源
git remote set-url origin(或者其他上游域) url
图形界面(git自带)
gitk
打开后也许会是这样
当然咱们可以借助第三方的软件,肯定比git自带的使用更加方便快捷,因为这篇文章重点偏向命令式操作,所以这里就不过多介绍了
分离头指针(detached HEAD)
有时候我们会做出如下操作
$ git checkout 415c5c8086e16 #后面的随机字符串指的是某一次的commitId
这时git会给我们出现类似下面的提示
what?
这段话大概是在说:
你正在基于415c5c8086e16这个commit正在做一个checkout的操作。
你现在正在处于分离头指针的状态,你可以做一些变更,产生一些commit,或者你也可以把你自己生成的这个commit丢弃掉,或者你可以继续做开发,并不会影响其他分支!
那么说的明白点就是:
我们现在正工作在没有分支的情况下,那么就是说现在没有branch跟它挂钩对应,那么如果当我们在这种情况下做了一些功能的开发后又切换到了其他分支后,最后很可能会被git当做垃圾清理掉,所以这就是分离头指针危险的地方!
但话又说回来,事物都具有两面性,用的不好或者在不知情的情况下使用肯定会对我们产生危害,但假设我们知道的他会有什么影响后,说不定多想想,还可能会带给我们好处呢。
好处场景举例:有时候变更只是尝试性的变更,如果做出来感觉不好或者不想要了,那我们就可以随时把它扔掉(直接checkout到其他分支,不理会他,过段时间就会自动被git清理掉)!那假设我们坐下来感觉还不错,那么我们就可以使用
$ git commit -am '提交信息' #这条命令意思是直接生成commit不用add,但不推荐
我们提交后,准备使用git log看下commit记录,然后就会发现一件奇怪的事情发生:
我们仔细看下图片,右边只有一个HEAD,正常情况下,HEAD总会跟一个分支挂钩,但现在怎么没有分支啦?
说明现在我们还处于分离头指针状态!
我们现在尝试切换一下分支到master分支!
切换后git可能会出来如下提示
它说有一个commit在后面还没有加到master分支里面去,那么这个其实就是分离头指针导致的!
然后下面它还会再次提醒你,如果你想保存这次的commit记录,那么你可以新建一个分支和这个3d4731d这个commit相关联!
如果你看都不看一眼这个提示,继续任性的往下随心操作,那么你刚刚在分离头指针状态下做的操作会统统丢掉!所以git的提示要多看
比较两个commit之间的差异
$ git diff commitId1 commitId2
$ git diff HEAD HEAD~1
$ git diff HEAD HEAD^
**HEAD可以指代最新的commit记录
^是指最新的commit的上一个commit
^^就是指上上一个
~1也是指上一个 ~2就是上上一个
所以这三条命令效果是一样的**
commit相关操作
修改最新commit的message
有时候我们提交的commit了发现描述的信息不全面,想进行修改,那么我们可以使用下面这条命令
$ git commit --amend
然后git会弹出一个新界面,让我们调整之前的message,修改后我们可以使用:wq!来保存这个新修改的message并且退出这个窗口
修改前几次的commit的message
我们先来看张图
这里我们用到了
git中的变基指令
那么本次我们想对图中标2的commit的message进行变更,那么我们该怎么做?
这里强调下,参数i是指交互式变基就是指会弹出窗口让我们更方便操作的方式进行变基
还有就是被变基的commit的commitId会发生改变哦!
重点来喽
这里我们需要选择一个基,我们这次要变更第二个commit,那么这个基就要选择被变(第二个commit)的这个commit的父级(第三个commit),父级的commitId是(27d2f814开头的那个),自己的commitId是(429243060b开头的那个),所以要基于父级(27d2f814开头的那个commit)去做变基操作!
这块比较绕,大家自己先捋一捋
接下来看命令操作!
$ git rebase -i 27d2f814
敲下回车后我们发现弹出来了一个交互式的窗口 如图:
我们把4292430开头的那个commit的message修改成 Add a refering projects
所以我们把pick指令改成reword指令
所以最终交互式窗口里的内容就会被我们修改为
$ reword 4292430 Add refering projects #这里我们只把刚刚的pick修改为了reword
$ pick a7dc188 Move filename readme.md to readme #本句可以无视,本例中只修改了第一句
修改完毕后我们使用:wq!保存并退出交互式控制台
然后接着git又会弹出另一个新窗口
那么我们在这里把Add refering projects修改为Add a refering project
修改完毕后我们使用:wq!保存并退出交互式控制台
然后git会做出如下提示:
说明我们的变基操作顺利完成了
变基操作其实是用到了分离头指针去完成的,
其实git先分离头指针,然后在上面做了调整,调整完毕后git还把新的commit产生后用一个指针指向了它!
但这里需要注意:这只是本地的变基行为,如果有多人已经在线上操作正式项目,切不可盲目用该命令随意操作,不然后果很严重!
差异比较
暂存区和HEAD差异比较
说白了就是add后的文件和最新commit做比较
$ git diff --cached
工作区和暂存区的差异比较
说白了就是自己最新写的代码和add里的对比
$ git diff -- 文件名
$ git diff #比较所有工作区和暂存区的差异
对比不同分支的相同文件的差异
$ git diff 分支一 分支二 -- 文件名
恢复相关
暂存区恢复为和HEAD(最新的commit)的内容一样
有时候我们add后突然发现我们不要暂存区的东西了,所以就要使用到如下命令了
$ git reset HEAD 或 git reset HEAD -- 文件名 #(如果没有写文件名就是全部恢复)
暂存区工作区都恢复为和HEAD(最新的commit)的内容一样
$ git reset --hard HEAD 或 git reset --hard HEAD -- 文件名 #(如果没有写文件名就是全部恢复)
这里补充说明下 --hard代表的是工作区
工作区的文件恢复为和暂存区的一样
有些时候做了一些变更,我们已经把这部分的代码放到了暂存区,然后我们又继续在工作区做了一些变更,做完之后,思前想后,发现工作区的变更还不如暂存区的好,所以我们就希望把工作区的内容扔掉,变成和暂存区的一模一样。
$ git checkout . #丢掉工作区全部的变更
$ git checkout -- 文件名 #丢弃这个文件的变更
这条命令也要慎用!一旦误操作,工作区最新的代码也就丢失啦!
stash
有时候我们正在写代码,突然加派紧急任务,要修改其他地方的bug,但我们又不想提交正在修改的,所以我们就需要是使用一个命令把当前的状态临时保存起来
$ git stash #存储
$ git stash push -m "更改了 xx"
$ git stash list #查看存储好的stash
存储好之后我们会发现工作区变成了干净的了。 那么假设我们的bug已经修改好了,我们想回到刚刚存储前的状态继续做之前我们做的事情,该怎么办?
$ git stash apply #把stash里面的东西取出来放到工作区,且他不会删除stash里的记录
$ git stash pop #把stash里面的东西取出来放到工作区,但他会删除stash里的记录
以上两个命令随便挑选一个使用都可以恢复,看情况而定
.gitignore
我们只需要在项目中新建一个名字为:.gitignore的文件 我们可以在.gitignore里配置自己想exclude(排除)的文件或文件夹。
远程相关
这里默认大家都会建立远程仓库,还不会的童鞋可以先去学习学习!
git push origin 分支名 把本地的某个分支推送到远程仓库
git push origin -d 分支名 # 用 -d 参数把远程仓库的分支也删了
假如是某个你自己独立开发的 branch 出错了,出错的内容已经合并到 master
同事的工作都在 master 上,你永远不知道你的一次强制 push 会不会洗掉同事刚发上去的新提交。所以除非你是人员数量和行为都完全可控的超小团队,可以和同事做到无死角的完美沟通,不然一定别在 master 上强制 push。
在这种时候,你只能退一步,选用另一种策略:增加一个新的提交,把之前提交的内容抹掉。例如之前你增加了一行代码,你希望撤销它,那么你就做一个删掉这行代码的提交;如果你删掉了一行代码,你希望撤销它,那么你就做一个把这行代码还原回来的提交。这种事做起来也不算麻烦,因为 Git 有一个对应的指令:revert。
它的用法很简单,你希望撤销哪个 commit,就把它填在后面:
$ git revert HEAD^
上面这行代码就会增加一条新的 commit,它的内容和倒数第二个 commit 是相反的,从而和倒数第二个 commit 相互抵消,达到撤销的效果。
在 revert完成之后,把新的 commit 再 push 上去,这个 commit 的内容就被撤销了。它和前面所介绍的撤销方式相比,最主要的区别是,这次改动只是被「反转」了,并没有在历史中消失掉,你的历史中会存在两条 commit :一个原始 commit ,一个对它的反转 commit。
2019年10月29日补充:
git在本地创建一个项目的过程
// git init
// git add .
// git commit -m 'first commit'
// git remote add origin git@github.xx/xxx.git
// git push -u origin master
#git设置关闭自动换行
git config --global core.autocrlf false git config --global core.safecrlf true
#编辑Git配置文件
git config -e [--global]
#设置提交代码时的用户信息
git config [--global] user.name "[name]" git config [--global] user.email "[email address]"
#从远端获取分支
git fetch origin
#更新远程分支列表
git remote update origin --prune
#基于远程分支创建本地分支
git checkout -b newBrach origin/master 在origin/master的基础上,创建一个新分支
#master
master 为主分支,也是用于部署生产环境的分支,确保master分支稳定性 master 分支一般由develop以及hotfix分支合并,任何时间都不能直接修改代码
#develop
develop 为开发分支,始终保持最新完成以及bug修复后的代码 一般开发的新功能时,feature分支都是基于develop分支下创建的
#feature
开发新功能时,以develop为基础创建feature分支 feature/xx 开头的为特性分支, 命名规则: feature/login_module、 feature/user_module
#release
release 为预上线分支,发布提测阶段,会release分支代码为基准提测
// 当有一组feature开发完成,首先会合并到develop分支,进入提测时,会创建release分支。
// 如果测试过程中若存在bug需要修复,则直接由开发者在release分支修复并提交。
// 当测试完成之后,合并release分支到master和develop分支,此时master为最新代码,用作上线。
#hotfix
分支命名: hotfix/ 开头的为修复分支,它的命名规则与 feature 分支类似 线上出现紧急问题时,需要及时修复,以master分支为基线,创建hotfix分支,修复完成后,需要合并到master分支和develop分支
#增加新功能
### 新模块开发git操作步骤:
模块名>表示需要进行替换
操作时一定要注意当前分支,命令前的(dev)代表在dev分支下执行!!
#### 1. 从dev分支切出一个新分支进行新模块的开发(只需要第一次写新模块前执行):
(dev): git checkout -b feature/<模块名> (创建本地新模块分支)
eg: git checkout -b feature/test
(feature/<模块名>): git push --set-upstream origin feature/<模块名> (将本地的分支推送到远程并与本地分支做关联)
eg: git push --set-upstream origin feature/test
#### 2. 在本分支进行新功能模块代码编写(coding...)
#### 3. 提交时的步骤:
##### (情况1) 功能未开发完毕时的操作步骤:
> 确保在新模块分支(feature/<模块名>)下执行
(feature/<模块名>): git add -A
(feature/<模块名>): git commit -m '[功能]开发了xx模块的xx功能'
(feature/<模块名>): git push
##### (情况2) 新功能模块整体开发完毕后的操作步骤(web端完成,接口对接完成):
(feature/<模块名>): git add -A
(feature/<模块名>): git commit -m '[功能]完成xx模块'
(feature/<模块名>): git push
(dev): git merge feature/<模块名> --no-ff (把新开发好的模块合并到dev分支)
eg: git merge feature/test --no-ff
#### 冲突情况的解决:
###### 有冲突的情况下解决冲突,解决完毕后:
(dev): git add -A
(dev): git commit -m '[冲突]解决xxx冲突'
(dev): git push
###### 没有冲突的情况下:
(dev): git push
#修复紧急bug
// (master)$: git checkout -b hotfix/xxx # 从master建立hotfix分支
// (hotfix/xxx)$: blabla # 开发
// (hotfix/xxx)$: git add xxx
// (hotfix/xxx)$: git commit -m 'commit comment'
// (master)$: git merge hotfix/xxx --no-ff # hotfix => master,并上线到生产环境
// (dev)$: git merge hotfix/xxx --no-ff # hotfix => dev,同步代码
#测试环境
// (release)$: git merge dev --no-ff dev => release
#生产环境
// (master)$: git merge release --no-ff release => master
// (master)$: git tag -a v0.1.0 -m '部署包版本名'
--no-ff 会让 Git 生成一个新的提交对象。为什么要这样?通常我们把 master 作为主分支,上面存放的都是比较稳定的代码,提交频率也很低,而 feature 是用来开发特性的,上面会存在许多零碎的提交,快进式合并会把 feature 的提交历史混入到 master 中,搅乱 master 的提交历史。
--ff 是指fast-forward命令,当使用ff模式进行合并时,将不会创造一个新的commit节点。
--no-ff,保留合并分支的提交记录,一般主干用的比较多.
--ff-only 除非当前HEAD节点为最新节点或者能够用ff模式进行合并,否则拒绝合并并返回一个失败状态。
--squash 则类似 rebase squash,可以把合并多个 commit 变成一个
使用git前推荐做以下配置:
win git config --global core.autocrlf true
mac git config --global core.autocrlf input
git config core.ignorecase false
总结
有关rebase的问题可以继续阅读这篇文章: jartto.wang/2018/12/11/…
文中如果出现错误,请大家在评论区指正,我会及时修改!