git 使用简明手册
代码在workspace、本地仓库、远端仓库之间的数据同步流程图如下:
从上面的流程图可以看出,本地git仓库(Repository)是由三棵“树”组成:
1、工作目录(working space)
,它持有实际文件;
2、暂存区(Index/Stage)
,它像个缓存区域,临时保存你的改动;
3、当前版本(HEAD)
,HEAD^ 指上一版本,HEAD^^ 指上上一个版本,HEAD~100往上100个版本。
可见,working space与本地仓库Repository之间的代码流动,跟SVN的workspace和服务器中央仓库之间的流动类似,通过checkout、add、commit命令控制。
与SVN不一样的是,git是分布式的,不存在一个托管所有代码的“中央服务器”。
或者从某种意义上来说,git有很多“中央服务器”,这些“中央服务器”可以是本地仓库,也可以是远端仓库,仓库之间也可以进行代码同步。
一、git 常见的应用场景
代码提交(Merge Request)流程
- git checkout -b ${branch_name},创建并切换分支
- git checkout ${branch_name},只切换分支
- git add ${file_name},将工作目录中修改过的文件添加到暂存区(working space -> Index)
- git commit -m "${msg}",将暂存区的文件提交到本地仓库(Index -> HEAD)
- git remote add origin ${server},关联到远程仓库(这是先有本地仓库,后有远程仓库的情况,反之就用git clone命令将远程仓库复制到本地)
- git push origin ${branch_name},将本地某个分支的修改推送到远端服务器(远程仓库已存在)
撤销修改(未提交到版本库)
- git checkout -- ${file_name},丢弃在working space的修改;
- git reset HEAD ${file_name},撤销已经保存到index(但没有commit)的修改,修改会回滚到working space中;
- git reset --hard origin/${branch_name},用远程仓库的文件覆盖本地的修改(包括working space和Index);
版本回退(已提交到版本库)
- git reset --hard HEAD^,回退到上一个版本;
- git reset --hard ${commit_id},滚动到指定版本;
更新本地仓库
- git pull,将远程仓库的修改拉取到本地仓库;
- git merge ${branch_name},将指定分支合并到当前分支;
- git fetch origin,
二、git 分支管理
git 会把每次提交串成一条时间线,这条时间线就是一个分支,例如下面的master分支:
每次提交,master就向前一步,master指向最近一次提交,而HEAD指向master,所以HEAD就指向了当前分支的提交点。
当创建一个分支dev时,Git新建一个指针dev,指向与master相同的提交点,再把HEAD指向dev,就表示当前分支在dev上。
如上图,可见Git创建一个分支很快,因为除了增加一个dev
指针,再修改HEAD
的指向,working space的文件都没有任何变化!
不过,从现在开始,对working space的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,working space内容也不变!
上面这种merge方式是快进模式(Fast-forward ),直接把master指向dev的当前提交,所以合并速度非常快。但这种模式下,删除分支后,会丢掉分支信息。当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
Git鼓励大量使用分支:
- 查看分支:git branch,-a 列出所有分支, -r 远程分支;
- 创建分支:git branch ${branch_name}
- 切换分支:git checkout ${branch_name}
- 创建并切换分支:git checkout -b ${branch_name}
- 合并某分支到当前分支:git merge ${branch_name},--no-ff 选项可以关闭Fast-forward合并模式;
- 删除分支:git branch -d ${branch_name},如果分支还未合并到主分支会报错;
- 强制删除:git branch -D ${branch_name},丢弃一个没有被合并过的分支,删除后无法恢复;
冲突
考虑一种更复杂的情况,master分支和dev分支各自都有新的提交,变成了这样:
此时git merge命令可能会产生冲突(如果修改了通过一个文件),
此时可以用git status查看冲突文件,并手动修改冲突文件然后保存,再利用git add和git commit命令提交。
上面这种merge方式不能用Fast-forward模式,而是像这样:
stash
有时候我们需要在当前开发分支(dev)中,停下来去处理bug,而当前dev分支没有完成,又不能提交到master,这个时候可以使用stash功能。
- git stash, 保存现场(包括working space和Index)到stash;
- git stash pop,恢复现场,并把stash内容也删除;
- git stash list,查看stash内容;
- git stash apply,只恢复现场,不删除stash内容;
- git stash drop,删除stash内容;
三、Git命令
创建新仓库(init)
创建新文件夹,打开,然后执行
git init
以创建新的 git 仓库。
克隆仓库(clone)
执行如下命令以创建一个本地仓库的克隆版本:
git clone /path/to/repository
如果是远端服务器上的仓库,你的命令会是这个样子:
git clone username@host:/path/to/repository
该命令会在本地主机生成一个目录,与远程主机的版本库同名。如果要指定不同的目录名,可以将目录名作为git clone命令的第二个参数:
git clone <版本库的网址> <本地目录名>
另外,git clone支持多种协议,除了HTTP(s)以外,还支持SSH、Git、本地文件协议等,下面是一些例子:
git clone http[s]://example.com/path/to/repo.git/ git clone ssh://example.com/path/to/repo.git/ git clone git://example.com/path/to/repo.git/ git clone /opt/git/project.git git clone file:///opt/git/project.git git clone ftp[s]://example.com/path/to/repo.git/ git clone rsync://example.com/path/to/repo.git/
远程仓库(remote)
列出所有的远程主机名:
git remote
加上选项-v可以查看远程主机的地址
对远程主机的操作:
git remote show <主机名> git remote add <主机名> <网址> git remote rm <主机名> git remote rename <原主机名> <新主机名>
查看(diff/status/log)
查看版本修改的历史日志
git log --pretty=oneline
git的版本号与SVN的纯数字不一样,它是一个SHA1计算出来的一个非常大的数字,用十六进制表示。
查看working space的具体修改
git diff
git diff filename
查看working space的修改状态
git status
推送改动(push)
你的改动现在已经在本地仓库的 HEAD 中了。执行如下命令以将这些改动提交到远端仓库:
git push <远程主机名> <本地分支名>:<远程分支名>
如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。
分支(checkout)
分支是用来将特性开发绝缘开来的。在你创建仓库的时候,master 是“默认的”分支。在其他分支上进行开发,完成后再将它们合并到主分支上。
创建一个叫做“feature_x”的分支,并切换过去:
git checkout -b feature_x
切换回主分支:
git checkout master
除非你将分支推送到远端仓库,不然该分支就是不为他人所见的:
git push origin <branch>
在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。
比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动"追踪"origin/master分支。
Git也允许手动建立追踪关系:
git branch --set-upstream master origin/next
上面命令指定master分支追踪origin/next分支。
获取分支(fetch)
fetch命令可以将远端主机的更新取回本地:
git fetch <远程主机名> <分支名>
若省略<分支名>,则更新远程主机的全部分支。
所取回的更新,在本地主机上要用"远程主机名/分支名"的形式读取,比如origin主机的next分支,就要用origin/next读取。
取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支
git checkout -b newBrach origin/next
此外,也可以使用git merge命令或者git rebase命令,在本地分支上合并远程分支
git merge origin/next
或者
git rebase origin/next
更新与合并(pull)
取回远程主机某个分支的更新,再与本地的指定分支合并:
git pull <远程主机名> <远程分支名>:<本地分支名>
如果本地分支名是当前分支,本地分支名可以省略不写;
如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。
git pull origin
上面命令表示,本地的当前分支自动与对应的origin主机"追踪分支"(remote-tracking branch)进行合并。
如果当前分支只有一个追踪分支,连远程主机名都可以省略:
git pull
上面命令表示,当前分支自动与唯一一个追踪分支进行合并。
例如,取回远程主机origin的next分支,与本地的master分支合并,可以执行:
git pull origin next:master
这等同于先做git fetch,再做git merge:
git fetch origin next
git merge origin/next
在这两种情况下,git 都会尝试去自动合并改动。遗憾的是,这可能并非每次都成功,并可能出现冲突(conflicts)。 这时候就需要你修改这些文件来手动合并这些冲突(conflicts)。改完之后,你需要执行如下命令以将它们标记为合并成功:
git add <filename>
在合并改动之前,你可以使用如下命令预览差异:
git diff <source_branch> <target_branch>
标签(tag)
TAG就是指向某个commit的指针。你可以执行如下命令创建一个叫做 1.0.0 的标签:
git tag 1.0.0 1b2e1d63ff
1b2e1d63ff 是你想要标记的提交 ID 的前 10 位字符。可以使用下列命令获取提交 ID:
git log
你也可以使用少一点的提交 ID 前几位,只要它的指向具有唯一性。
如果不写commit ID,则默认取最近的提交(即默认HEAD)。
tag的作用主要是比commit id更方便记忆 。
git tag -d ${tag_name}, 删除本地标签 git push origin :refs/tags/${tag_name},删除远程仓库的标签 git push origin ${tag_name},将指定标签push到远程仓库 git push origin --tags,将所有标签push到远程仓库
替换本地改动
假如你操作失误(当然,这最好永远不要发生),你可以使用如下命令替换掉本地改动:
git checkout -- <filename>
此命令会使用 HEAD 中的最新内容替换掉你的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响。
假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:
git fetch origin
git reset --hard origin/master