使用git很久了,期间踩了一些坑,通常来说对git理解和使用不熟的话踩得坑都是致命的(哭死),所以花了点时间总结了一些用法,加深对git的一些理解
远程主机remote
相当于远程仓库的别名(如ip与域名),git上默认主机名为:origin
查看主机
1 | $ git remote |
主机操作
1 | $ git remote add <主机名> <网址> |
通常情况下只需要一个远程主机,有时候我们需要本地代码同事push到两个仓库(如:OSChina和github),这个时候可以配置主机的Url
添加主机的Url,可以添加多个网址
1 | $ git remote set-url --add <主机名> <网址> |
当push到主机的时候会自动同步到添加的网址
拉去的时候会直接则取第一个url,修改可以在config(.git/config
)文件进行修改
克隆仓库clone
clone是远程操作的第一步,把远程仓库取回到本地
1 | $ git clone <版本库的网址> <本地目录名> |
指定主机名
1 | $ git clone -o <主机名> <版本库的网址> <本地目录名> |
拉取更新fetch
当远程仓库有更新的时候,可以通过fetch拉取
1 | $ git fetch <远程主机名> <远程分支名>:<本地分支名> |
远程分支名缺省,则拉去与本地分支有track关系远程分支
拉去后不会merge到当前分支,而是放到一个临时分支上
拉取并合并更新pull
1 | $ git pull <远程主机名> <本地分支名>:<远程分支名> |
push
推送更新
1 | $ git push <远程主机名> <本地分支名>:<远程分支名> |
如果分支不存在,则创建
如果本地分支名为空,表示删除远程分支
删除远程分支
1 | $ git push <远程主机名> --delete <远程分支名>:<本地分支名> |
只要没有push的改变都只是在本地,包括branch,commit,reset,都可以撤销和删除
比较pull和fetch
pull相当于fetch+merge
1 | $ git pull origin Development |
分支 Branck
1 | # 查看分支(所有) |
提交 Commit
修改/添加文件
test.txt
此时文件没有被跟踪,状态为Unchecked Files
丢弃对文件的添加修改,不带文件则撤销全部工作区的修改(__不可恢复__,慎用)1
$ git checkout -- text.txt
把
test.txt
文件add到当前分支的暂存区,文件状态为Changed to be commited
1
$ git add test.txt
添加文件到暂存区后可以取消暂存(此命令不会影响工作区,如果不带文件则撤销全部)
1
$ git reset HEAD test.txt
继续修改文件
test.txt
,此时暂存区的文件和工作区的文件不一样了,此时在工作区的副本文件的状态为Changes not staged for commit
提交
test.txt
文件1
$ git commit 添加了一个test.txt文件
撤销提交,撤销commit比较麻烦,需要拿到上一次提交的commitId
1
$ git reset --hard commit_id
如果是撤销上一次commit,等价于用下面命令
1
$ git reset --hard HEAD^
只有添加到暂存区的文件才能提交,所以我们只提交了第一次修改,第二次修改没有被提交
暂存区的文件都是只读的,不能修改,只有工作区的文件才能修改
问题
发现刚刚commit漏提交了一个文件(或者提交说明写错了),需要修改上一次commit
解决一:此时为push到远程仓库,修正上一个commit,把相关需要补充的文件或修改add到HEAD暂存区(保持commitId不变)
1
$ git add test.txt
执行1
$ git commit --amend
上面命令会追加暂存区的提交到上一次commit,并重新编辑提交说明解决二:撤销上一个commit,然后重新commit(新的commitId)
撤销上一次提交1
$ git reset --hard HEAD^
回复到指定的commitid1
$ git reset --hard commit_id
上面操作不会影响工作区如果commit已经push到远程
有时候我们在本地修改后并且push到远程,这个时候发现,需要撤销,如果我们使用git reset
撤销后push到远程主机1
2$ git reset --HARD HEAD^ # 撤销上一次commit
$ git push origin master # 这时候push会报错,因为本地的分支比远程的分支落后上面push会报错,有两种方式解决
使用
fast-forward
方式推送,直接覆盖远程分支,这种方式有风险,有可能在这之前有其他人的push都会一并被覆盖,慎用1
$ git push -f origin master
reset
换成revert
命令,这种方式会新增一个commit,而不是回退,这个时候当前的分支就不会比远程分支落后了,两者区别见后面1
2
3
4$ git revert head^ # 撤销上一次commit,恢复到AAAA,并生成新的commitId
# 这个时候可能会产生冲突,解决冲突,commit
$ git push origin master
推荐使用这种方式解决远程恢复的问题
文件操作(添加,修改,删除,重命名)
本地对文件的所以修改,都存放在工作区,需要下面命令将修改放到暂存区,才能提交
1 | $ git add test.txt |
通常这些操作都借助GUI工具完成
撤销提交 reset/revert
保留原来的commit,会退到历史的点,创建新commit并commit
1 | $ git reset --hard |
重置后的修改会被放到暂存区,需要自己commit
1 | $ git reset --soft |
mixed为默认参数,重置后的所有修改会被放到工作区
1 | $ git reset --mixed |
注意
1 | $ git reset --hard 会清空工作区 |
在使用之前,尽量保证工作区和暂存区没有文件,避免多重冲突
历史是不可以修改的Reflog
git跟踪过的所有的操作都会成为历史,所有的操作都是添加,所以远程的所有操作理论上都是可以恢复的
git能跟踪所有commit, checkout, reset命令,所有这些命令都可以恢复,可以通过reflog查看所有这些操作
1
$ git reflog
恢复到指定的SHA
1
$ git reset --hard <SHA>
暂存管理Stash
有时候我们对本地修改到一半时,这时候要去拉去远程更新,为了防止冲突,可以把本地的工作现场保存到另一个暂存区,拉去完成后,在恢复当前工作现场。
Stash可以当前工作区和暂存区的所有修改保存起来,暂存区是全局的,不同分支也共享一个Stash,Stash可以存放多个工作现场
保存当前工作现场(保存暂存区的文件,不保存工作区的文件)
1 | $ git stash |
保存当前工作现场(保存暂存区和工作区的文件)
1 | $ git stash --include-untracked |
注意:暂存工作区文件有风险,当工作区的文件存在冲突的时候,工作区的文件无法恢复,并且无法自动解决冲突,暂存区的文件可以
恢复工作现场
1 | #恢复上一个保存的工作现场 |
查看文件状态(暂存区和工作区)
通常我们通过GUI工具查看
1 | $ git status |
取消git版本控制
如果一个目录取消git的版本控制,恢复成正常的目录,可以直接把.git
目录删了,包含目录下所有文件和该目录本身
1 | $ rm -rf .git |
分歧与Fast-Forward
1 | #1 #2 #3 #4 |
A和B都从master分支的的#2
checkout代码,假设A对文件README.md
做了修改,并push到master,B也对文件README.md
做了修改,然后push到master,在默认情况下这个时候就会发生冲突,必须先fetch
同步远程最新的代码并merge
解决冲突后再提交才能push成功,这是为了防止B的修改直接覆盖A的修改
在默认情况下系统为non-fast-forward
,即非快进模式,必须以时间顺序提交,也就是B必须基于#3
的代码修改才能提交
解决方法:
1、fetch远程代码后merge解决冲突,然后commmit,再push
2、忽略冲突,直接覆盖远程代码,可以开启fast-forward
模式强制覆盖远程的操作
1 | $ git push -f |
此操作还会覆盖远程的commit记录,尽量少用,fast-forward
模式,特别是在多人开发的时候,会覆盖别人的代码,还会覆盖别人的提交,不利于回滚代码
rebase与merge
rebase和merge都有合并代码的功能,二者的区别在于
- merge合并后会保留两个分支的所有commit
- rebase合并后会丢弃合并分支的commit
如下两个分支
- master:A <- B <- C <- D
- test: A <- B <- C <- E
__Merge__:合并test到master
合并之后为:A <- B <- C <- D <- E <- M
__Rebase__:合并test到master
合并之后为:A <- B <- C <- D <- R
使用rebase可以减少一些多余的commit,让分支历史基本在一条直线上,有利有弊,看使用场景选择merge或rebase
使用git pull
默认使用merge,可以加参数--rebase
让其使用rebase合并
1 | $ git pull --rebase |
合并后,当git无法自动解决冲突的时候,查看分支会出现(no branch, rebasing master)
,表示不再任何分支上工作
1 | ACA80164:Test bomo$ git branch |
这个时候手动解决冲突,然后执行add
1 | $ git add . |
总结
- 所有commmit和reset操作都是可以恢复的(reflog/reset/revert)
- 工作区的文件通常是不可恢复的
- checkout是撤销工作区修改,不可恢复,慎用checkout
- pull之前防止冲突,通常把所有文件放到暂存区(为了防止冲突),然后stash,解决冲突,在stash pop
- 只有放到暂存区的文件的冲突才能被识别,如果是工作去的冲突,会直接被覆盖
- 从工作区
add
文件到暂存区不会识别冲突 - 所有的commit都可以恢复,建议多commit(提高代码粒度)
- 所有的操作都是本地的,对远程仓库的操作只有同步操作,如果要修改远程仓库,在本地改完push到服务器