写在前面
自己原来一直在用IDEA提供的Git管理,但是有时候会遇到解决不了和理解不了的问题,所以觉得还是需要系统学习一下Git。
1. 安装Git
安装命令
$ sudo apt-get install git
安装完成后,需要配置全局用户名和邮箱
$ git config --global user.name "my name"
$ git config --global user.email "my email"
2. 创建仓库
创建仓库
$ git init
这样会创建一个空仓库,生成.git目录,.git目录是仓库管理目录
提交
$ git add [filename]
将文件添加进暂存区,接受Git管理
$ git commit -m "这次的提交说明"
将暂存区的修改提交到版本库中的分支
$ git commit -am "提交说明"
将工作区和暂存区的所有修改都提交到分支,这种方式不能指定具体文件
注意,每次对工作区有了新的修改,都要先执行git add [filename]将修改存入暂存区,Git才可以管理这次的修改
3. 版本控制
查看当前Git的状态
$ git status
对于还未提交的修改,可以对比当前的修改和最新的版本库
$ git diff [filename]
3.1 版本回退
查看当前分支
$ git log
reset
重置HEAD指针:
$ git reset --hard HEAD^
HEAD^代表回退一个版本,如果想回退两个版本,需要HEAD^^,依次类推,或者可以HEAD~100指定回退100个版本。这种方式是以当前HEAD指针为参考,指定要回退几个版本,也可以不参考HEAD指针,直接重置HEAD指针到某个版本:
$ git reset --hard [版本号]
这种直接指定具体版本重置HEAD指针的方式非常好用,不仅可以向前回退,也可以向后回退。但是向后回退需要知道版本号,git log命令无法知道后面的版本号。但是既然当前版本存在更后的版本,那么肯定是执行过重置HEAD指针的操作的,我们可以先查看当前版本库执行过的所有操作:
$ git reflog
这样可以得到版本库的操作链,这条操作链不仅包括提交等记录,还包括每一次重置HEAD指针的操作,会比较复杂。如果需要理清版本库操作,可以先重置HEAD到最新的commit版本,然后用git log就可以得到版本库的所有版本,再结合git reflog就可以理清所有操作。
soft hard mixed merge keep
git reset后面可以跟四种参数,分别表示这次reset操作对工作区和暂存区的影响
--soft: 不改变工作区和暂存区
--hard: 改变工作区和暂存区,意味着工作内容丢失
--mixed: 改变暂存区,不改变工作区,这是默认参数
--merge: 基本和--hard,但会保留一些修改,不常用
--keep: 基本和--hard类似,不同的地方是,如果reset过程中和当前工作区有冲突,则停止reset,不常用
revert
用法:
$ git revert HEAD^
$ git revert [commit_id]
基本和reset一样,但是revert操作会新生成一个commit,用来抵消之前的某个commit
3.2 工作区,暂存区和版本库
下图显示了Git的基本空间划分,主要分为工作区和版本库。工作区就是当前修改在的地方,体现在仓库中就是.git目录之外的地方。.git目录就是Git版本库,而版本库又包含暂存区和具体分支。暂存区就是.git目录下的index文件,所以暂存区称为index或者stage。凡是没有进入暂存区的修改,都不会被Git跟踪和管理。
IDEA自带的Git管理总给人感觉工作区和暂存区合二为一了,我们只需要做出修改后执行git commit提交到分支上,但是其实标准Git流程是需要先git add把修改移入暂存区,然后才可以git commit提交到分支。暂存区的存在更有利于版本控制,但是增加了我们使用Git的复杂度。IDEA简化了这样的操作,但是有时候出了问题还是需要知道Git的原理并用命令行解决。如果需要查看内容和解决冲突这样的操作,还是IDEA好用,毕竟提供了很好的界面,但是如果是Git管理流程出了问题,最好还是使用Git命令行解决。
图中的各种命令其实就是在工作区,暂存区和分支之间转移和同步修改。

git add命令会把工作区的修改移入暂存区,如果是删除文件,需要git rm把删除文件这个修改移入暂存区
git commit: 会把暂存区的修改提交到分支上
git commit -a: 把工作区和暂存区的所有修改一起提交到分支上
git checkout -- [filename]: 把工作区的内容同步成暂存区的内容
git reset HEAD [filename]: 把暂存区同步成和分支的内容一样,工作区不受影响
git rm --cached [filename]: 删除暂存区文件,不影响工作区
git checkout -- [filename]: 把工作区的内容同步成暂存区的内容
git checkout HEAD [filename]: 把HEAD指向的分支内容同步到工作区和暂存区
以上的同步操作都会把修改丢失,比较危险,而且三个区域之间操作很复杂,比较难记。
我个人的总结方案是只要记住基本的git add和git commit,另外还有git rm --cached [filename]比较实用。
git rm --cached [filename]主要是在.gitignore中添加新的文件,但是这个文件又已经被跟踪的时候,就可以用这个方法把文件从暂存区删除,也就是取消跟踪文件。
如果有修改不想提交又不想丢失,最好使用栈先保存下来。
3.3 栈
凡是想要暂时保存的修改,都可以使用Git栈暂时保存。
保存栈
$ git stash save "这次保存的说明"
这样会把工作区和暂存区的修改都保存到栈中,这不是复制,这是剪切,工作区和暂存区都被还原了
查看栈
$ git stash list
栈还原
$ git stash pop
栈顶节点还原到工作区
$ git stash pop --index
把栈顶节点还原到工作区和暂存区
$ git stash pop stash@{1}
还原指定节点到工作区,这个节点号可以通过git stash list得到
如果两个栈节点有冲突,想先后被还原这两个节点并解决冲突,需要把第一个节点还原到暂存区,再还原第二个节点,否则还原第二个节点会失败。其实这很好理解,在暂存区才能接受Git管理,冲突解决也是管理的一种。
上面的命令都会删除节点,把pop改成apply就不会删除节点
删除栈
$ git stash drop stash@{1}
删除stash@{1}这个节点,如果不指定节点,则删除栈顶节点
$ git stash clear
删除栈内所有节点
删除栈节点都要慎重,因为不会还原到工作区
4. 远程仓库
添加和查看远程仓库
$ git remote add origin [git_address]
添加一个名为origin的远程仓库关联,一个本地仓库可以和多个远程仓库关联
$ git remote -v
查看本仓库关联的所有远程仓库
克隆仓库
$ git clone [git_address]
拉取
$ git fetch origin/matser
更新本地的远程origin仓库的master分支副本,不指定分支则更新整个仓库
$ git pull origin master:test
把远程origin仓库的master分支更新并合并到本地test分支上
合并
$ git merge origin/master
把本地的origin/master副本合并到当前分支
提交
$ git push -u origin master
将本地仓库当前分支推送到origin的master分支上 -u参数的意思是把关联origin地址的master分支,以后可以简化拉取和推送命令
$ git push origin test:master
把本地的test分支推送到远程origin仓库的master分支上
这张图可以帮助理解仓库之间的关系和操作

5. 分支管理
Git的分支管理速度非常快,因为一般只涉及到指针的创建和移动。
创建分支
$ git branch dev
创建dev分支
切换分支
$ git checkout dev
切换到dev分支,如果dev分支不存在,需要创建并切换,则如下
$ git checkout -b dev
git checkout本身还有把暂存区内容向工作区同步的作用,为了避免混淆,高版本的Git可以这样切换分支
$ git switch dev
$ git switch -c dev
意思分别是切换到dev分支,创建并切换到dev分支
查看分支
$ git branch
合并分支
$ git checkout master
$ git merge dev
把dev分支合并到master分支,需要先切换到master分支,再执行git merge dev
这样执行的是Fast forward模式,这个模式在合并分支的时候不会创建新节点,最好禁用Fast forward模式,如下
$ git merge --no-ff -m "合并分支说明" dev
这样会创建一个新节点,从日志上方便查看
合并分支可能会有冲突要解决,涉及到冲突解决最好使用IDEA的Git管理,所以合并分支最好也用IDEA
删除分支
$ git branch -d dev
删除dev分支
如果dev分支从来没被合并过,上面的命令会报错,要使用下面的命令强制删除
$ git branch -D dev
这样会强制删除dev分支
解决冲突
解决冲突最好使用IDEA的冲突解决,比较方便
Rebase
用法:
$ git rebase
rebase操作会把不同人的commit尽量合并到一个主干上,让提交记录比较好看,有冲突的commit需要merge,不能变成一条线
合并commit
$ git rebase -i [start_commint_id] [end_commit_id]
从start_commint_id到end_commit_id进行commit合并,end_commit_id必须写,start_commint_id如果不写,则默认从HEAD开始,这是个左闭右开的区间
$ git rebase -i HEAD~3
把HEAD,HEAD~1,HEAD~2进行合并
$ git rebase -i 36224db
把36224db之前的所有commit合并,不包括36224db
cherry-pick
假如现在有master和dev分支,我们不想把dev分支合并过来,只是想把其中的某个commit合并到master上,就需要用到cherry-pick,用法:
$ git checkout master
$ git cherry-pick [commit_id]
6. 标签管理
标签(tag)其实就是commit的一个别名,只不过commit的唯一ID是一串难记的哈希值,所以用标签来简化记忆,并且默认带标签的都是有版本意义的,所以标签也不是可以随便加的
如果一个加了tag的commit出现在多个分支上,那么所有分支都可以看见这个tag
创建标签
$ git tag v1.0
给当前分支的当前commit加上v1.0的tag
$ git tag v1.0 f52c633
给f52c633这个commit加上v1.0的tag
$ git tag -a v1.0 -m "标签的说明"
给v1.0标签加上说明
删除标签
要想修改标签的内容,只能先删除,再重新打标签
$ git tag -d v1.0
删除v1.0这个tag
$ git push origin :refs/tags/v1.0
删除远程origin的v1.0标签,其实就是推送一个空的东西代替refs/tags/v1.0
推送标签到远程仓库
$ git push origin v1.0
把v1.0推送到origin
$ git push --tags
一次性把所有标签推送到origin
查看标签
$ git tag
查看当前分支的所有tag
$ git show v1.0
查看v1.0这个tag的具体信息