git详细
# Git 简介
Git 是 Linux 之父 Linus 的第二个伟大的作品,它最早是在 Linux 上开发的,被用来管理 Linux 核心的源代码。后来慢慢地有人将其移植到了 Unix、Windows、Max OS 等操作系统中。
Git 是一个分布式的版本控制系统,与集中式的版本控制系统不同的是,每个人都工作在通过克隆建立的本地版本库中。也就是说每个人都拥有一个完整的版本库,查看提交日志、提交、创建里程碑和分支、合并分支、回退等所有操作都直接在本地完成而不需要网络连接。
对于 Git 仓库来说,每个人都有一个独立完整的仓库,所谓的远程仓库或是服务器仓库其实也是一个仓库,只不过这台主机 24 小时运行,它是一个稳定的仓库,供他人克隆、推送,也从服务器仓库中拉取别人的提交。
Git 是目前世界上最先进的分布式版本控制系统,没有之一,对,没有之一!
# 三个区

工作区 (working diretory) 用于修改文件
缓存区 (stage) 是用来暂时存放工作区中修改的内容
提交历史(commit history) 提交代码的历史记录
# 主要的几个命令
1 | git add # 将工作区的修改提交到暂存区 |
# git commit 用法
git commit –m “本次提交描述”
该命令会将 git add . 存入暂存区修改内容提交至本地仓库中,若文件未添加至暂存区,则提交时不会提交任何修改。
git commit -a
相当于运行 git add -u 把所有当前目录下的文件加入缓存区域再运行 git commit.
注意!对于新增的文件,并没有被 commit
git commit –am “本次提交描述”
或者 git commit –a –m “本次提交描述”
等同于上面的 - a 和 - m
git commit --amend
修改最近一次提交。有时候如果提交注释书写有误或者漏提文件,可以使用此命令。对于漏提交的文件,需要 git add 到缓存区之后,git commit --amend 才能将修改追加到最近的一次提交上。
# git stash 用法
$ git stash
所有未提交的修改都保存起来,用于后续恢复当前工作目录
$ git stash save “stash_name”
给每个 stash 加一个 message,用于记录版本
$ git stash pop / git stash apply
恢复最新缓存的工作目录(第一个),并删除缓存堆栈中的那一个 stash 删除 (pop), apply 则只恢复不删除
$ git stash list
查看现有所有 stash
在使用 git stash pop (apply) 命令时可以通过名字指定使用哪个 stash,默认使用最近的 stash(即 stash@{0})
$ git stash drop
移除最新的 stash,后面也可以跟指定 stash 的名字
# git reset 用法
git reset 根据–soft –mixed –hard,会对 working tree 和 index 和 HEAD 进行重置
$ git reset HEAD^
回退版本,一个 ^ 表示一个版本,可以多个,另外也可以使用 git reset HEAD~n 这种形式。
也可以回退到指定版本:
$ git reset commit-id
soft 参数:git reset --soft HEAD~1 意为将版本库软回退 1 个版本,所谓软回退表示将本地版本库的头指针全部重置到指定版本,且将这次提交之后的所有变更都移动到暂存区
默认的 mixed 参数:git reset HEAD~1 意为将版本库回退 1 个版本,将本地版本库的头指针全部重置到指定版本,且会重置暂存区,即这次提交之后的所有变更都移动到工作区
hard 参数:git reset --hard HEAD~1 意为将版本库回退 1 个版本,但是不仅仅是将本地版本库的头指针全部重置到指定版本,也会重置暂存区,并且会将工作区代码清空(工作区是 clean 状态)
注意,soft 参数与默认参数都不会修改工作区代码,只有 hard 参数才会修改工作区代码。
另外,git reset HEAD filename
回退文件,将文件从暂存区回退到工作区(unstage),此时不能带 hard,soft 参数
# git reflog
如果在回退以后又想再次回到之前的版本,git reflog 可以查看所有分支的所有操作记录(包括 commit 和 reset 的操作),包括已经被删除的 commit 记录,git log 则不能察看已经删除了的 commit 记录
1 | 615ce06 HEAD@{44}: rebase -i (finish): returning to refs/heads/my_test_branch |
比如说,回退到 commit: zancun3,只需要:
git reset --hard f3ef592 (或者 HEAD@{49}) 即可
这个命令对于找回丢失的代码非常有用。
# git add
删除文件后需要 git add -A, 光 git add. 不行,区别如下:
git add -A 保存所有的修改
git add . 保存新的添加和修改,但是不包括删除
git add -u 保存修改和删除,但是不包括新建文件。
所以默认使用 git add -A 就行
# git checkout
git checkout 既可以操作分支,也可以操作文件
# git checkout 切换分支
$ git checkout -b newBranchName
Switched to a new branch ‘newBranchName’
这相当于执行下面这两条命令:
git branch newBranchName
git checkout newBranchName (工作区一定要是 clean 的)
$ git checkout -b newBranchName remote_branch_name
拉取远程分支 remote_branch_name 创建一个本地分支 newBranchName,并切到本地分支 newBranchName,采用此种方法建立的本地分支会和远程分支建立映射关系。
# git checkout 回退修改
git checkout – fileName
这条命令把 fileName 从当前 HEAD 中检出,也就是回退当前工作区的这个文件的修改
–可以省略不写
如果需要回退工作区的全部文件修改,可以使用:
git checkout --hard HEAD
而不需要对每个文件进行 checkout,这样太累
# git revert
git revert, 反转提交,撤销一个提交的同时会创建一个新的提交,也就是用一个新提交来消除一个历史提交所做的任何修改.
git revert commit-id revert 指定的一个 commit
git revert HEAD~3 revert 指定倒数第四个 commit
revert 过程有可能遇到冲突,要么 git revert --abort 终止此次 revert 操作,代码还原至 revert 命令前。要么手动消除冲突 (同普通的冲突解决),然后 add commit
# reset,checkout,revert 总结
<<<<<<< HEAD
下面这个表格总结了这些命令最常用的使用场景。记得经常对照这个表格,因为你使用 Git 时一定会经常用到。
| 命令 | 作用域 | 常用情景 |
|---|---|---|
| git reset | 提交层面 | 在私有分支上舍弃一些没有提交的更改 |
| git reset | 文件层面 | 将文件从缓存区中移除 |
| git checkout | 提交层面 | 切换分支或查看旧版本 |
| git checkout | 文件层面 | 舍弃工作目录中的更改 |
| git revert | 提交层面 | 在公共分支上回滚更改 |
| git revert | 文件层面 | (然而并没有) |
=======
下面这个表格总结了这些命令最常用的使用场景。记得经常对照这个表格,因为你使用 Git 时一定会经常用到。
| 命令 | 作用域 | 常用情景 |
|---|---|---|
| git reset | 提交层面 | 在私有分支上舍弃一些没有提交的更改 |
| git reset | 文件层面 | 将文件从缓存区中移除 |
| git checkout | 提交层面 | 切换分支或查看旧版本 |
| git checkout | 文件层面 | 舍弃工作目录中的更改 |
| git revert | 提交层面 | 在公共分支上回滚更改 |
| git revert | 文件层面 | (然而并没有) |
1e6acdcbc1bb278f2ad0c275b0a6360e6149c936
# 删除分支
删除分支: $ git branch -d branchName
或者, git branch -D branchName 删除分支(不管它有没有 merge)
前提是先要切换到其他分支
$ git branch -d branch1
error: The branch ‘branch1’ is not fully merged.
If you are sure you want to delete it, run ‘git branch -D branch1’.
# git push
git push 命令用于将本地分支的更新,推送到远程主机。
1 | $ git push <远程主机名> <本地分支名>:<远程分支名> |
上面命令表示,将本地的 master 分支推送到 origin 主机的 master 分支。如果 master 不存在,则会被新建。
如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。
1 | $ git push origin :master |
上面命令表示删除 origin 主机的 master 分支。如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。
1 | $ git push origin |
上面命令表示,将当前分支推送到 origin 主机的对应分支。如果当前分支只有一个追踪分支,那么主机名都可以省略。
1 | $ git push |
如果当前分支与多个主机存在追踪关系,则可以使用 - u 选项指定一个默认主机,这样后面就可以不加任何参数使用 git push
1 | $ git push -u origin master |
上面命令将本地的 master 分支推送到 origin 主机,同时指定 origin 为默认主机,后面就可以不加任何参数使用 git push 了。
将当前分支推送到远程的同名的简单方法,如下:
1 | $ git push origin HEAD |
将当前分支推送到源存储库中的远程引用匹配主机。 这种形式方便推送当前分支,而不考虑其本地名称。如下:
1 | $ git push origin HEAD:master |
单独使用 git push 时,没有指定 push 的 remote 分支名,假如当前本地分支名称与其对应的 remote 分支名称不一样,则会有一下提示:
1 | fatal: The upstream branch of your current branch does not match |
当执行 git push origin test 时,会在远程重新创建一个新的分支,名称就是 test,然后把修改同步到 test 分支。
# git pull
git pull 命令用于从另一个存储库或本地分支获取并集成 (整合)。git pull 命令的作用是:取回远程主机某个分支的更新,再与本地的指定分支合并,
1 | $ git pull <远程主机名> <远程分支名>:<本地分支名> |
比如,要取回 origin 主机的 master 分支,与本地的 test 分支合并,需要写成下面这样
1 | $ git pull origin master:test |
如果远程分支 (master) 要与当前分支合并,则冒号后面的部分可以省略。上面命令可以简写:
1 | $ git pull origin master |
将远程存储库中的更改合并到当前分支中。在默认模式下,git pull 是 git fetch 后跟 git merge FETCH_HEAD 的缩写。
更准确地说,git pull 使用给定的参数运行 git fetch,并调用 git merge 将检索到的分支头合并到当前分支中。 使用–rebase,它运行 git rebase 而不是 git merge。也就是说
1 | git pull = git fetch + git merge |
git 中都 fetch 命令是将远程分支的最新内容拉到了本地,但是 fetch 后是看不到变化的,此时本地多了一个 FETCH_HEAD 的指针,checkout 到该指针后可以查看远程分支的最新内容。然后 checkout 到 master 分支,执行 metch, 选中 FETCH_HEAD 指针,合并后如果出现冲突则解决冲突,最后 commit。
pull 的作用就相当于 fetch 和 merge,自动合并
git fetch origin master
git merge FETCH_HEAD
git fetch origin isoda-android_1.3.0_feature :branch1
使用远程 isoda-android_1.3.0_feature 分支在本地创建 branch1 分支(但不会切换到该分支)
# 1. git merge
将 origin 分支合并到 mywork 分支最简单的办法就是用下面这些命令
git checkout mywork
git merge origin
或者,你也可以把它们压缩在一行里:
git merge origin mywork
假设远程分支上有 3 次提交 A,B,C:
在远程分支 origin 的基础上创建一个名为 "mywork" 的本地分支并提交了修改 E,同时有其他人在 "origin" 上做了一些修改并提交了修改 D。
用 git merge 命令把 "origin" 分支与本地提交合并(merge)成版本 M,mywork 分支中新的合并提交(merge-commit)将两个分支的历史连在了一起,但这样会形成图中的菱形,让人很困惑。
Merge 好在它是一个安全的操作,比较安全,现有的分支不会被更改,避免了 rebase 潜在的缺点(后面会说)。另一方面,这同样意味着每次合并上游更改时 feature 分支都会引入一个外来的合并提交。如果 master 非常活跃的话,这或多或少会污染你的分支历史。虽然高级的 git log 选项可以减轻这个问题,但对于开发者来说,还是会增加理解项目历史的难度。
# 2. git rebase
作为 merge 的替代选择,你可以像下面这样将 mywork 分支并入 origin 分支:
git checkout mywork
git rebase origin
它会把整个 mywork 分支移动到 origin 分支的后面,有效地把所有 master 分支上新的提交并入过来。但是,rebase 为原分支上每一个提交创建一个新的提交,重写了项目历史,并且不会带来合并提交。rebase 的好处是避免了菱形的产生,保持提交曲线为直线,让大家易于理解。
rebase 最大的好处是你的项目历史会非常整洁。首先,它不像 git merge 那样引入不必要的合并提交。其次,如上图所示,rebase 导致最后的项目历史呈现出完美的线性 —— 你可以从项目终点到起点浏览而不需要任何的 fork。这让你更容易使用 git log、git reset 和 gitk 来查看项目历史。
不过,这种简单的提交历史会带来两个后果:安全性和可跟踪性。如果你违反了 rebase 黄金法则,重写项目历史可能会给你的协作工作流带来灾难性的影响。此外,rebase 不会有合并提交中附带的信息 —— 你看不到 mywork 分支中并入了上游的哪些更改。
在 rebase 的过程中,有时也会有 conflict,这时 Git 会停止 rebase 并让用户去解决冲突,解决完冲突后,用 git add 命令去更新这些内容,然后不用执行 git commit, 直接执行 git rebase --continue, 这样 git 会继续 apply 余下的补丁。
在任何时候,都可以用 git rebase --abort 参数来终止 rebase 的行动,并且 mywork 分支会回到 rebase 开始前的状态。
官方的两张 merge 和 rebase 对比图:
merge 示例图:
rebase 示例图:
# 3. rebase 的高级操作–交互式 rebase
交互式的 rebase 允许你更改并入新分支的提交。这比自动的 rebase 更加强大,因为它提供了对分支上提交历史完整的控制。一般来说,这被用于将 feature 分支并入 master 分支之前,清理混乱的历史。
把 -i 传入 git rebase 选项来开始一个交互式的 rebase 过程:
git checkout feature
git rebase -i master
它会打开一个文本编辑器,显示所有将被移动的提交:
1 | pick e900fa0 zancun |
这个列表定义了 rebase 将被执行后分支会是什么样的。更改 pick 命令或者重新排序,这个分支的历史就能如你所愿了。比如说,如果第二个和第三个提交只是修复了第一个提交中的小问题,你可以用 fixup 命令把它们合到第一个提交中,并修改第一个的日志:
1 | r e900fa0 zancun |
这样三个提交合并成了一个提交,并可以重新修改提交日志,非常实用。
忽略不重要的提交会让你的 feature 分支的历史更清晰易读。这是 git merge 做不到的。
# 4. Rebase 的黄金法则
当你理解 rebase 是什么的时候,最重要的就是什么时候不能用 rebase。git rebase 的黄金法则便是,绝不要在公共的分支上使用它。
比如说,如果你把 master 分支 rebase 到你的 feature 分支上会发生什么:
这次 rebase 将 master 分支上的所有提交都移到了 feature 分支后面。问题是它只发生在你的代码仓库中,其他所有的开发者还在原来的 master 上工作。因为 rebase 引起了新的提交,Git 会认为你的 master 分支和其他人的 master 已经分叉了。
同步两个 master 分支的唯一办法是把它们 merge 到一起,导致一个额外的合并提交和两堆包含同样更改的提交。不用说,这会让人非常困惑。
所以,在你运行 git rebase 之前,一定要问问你自己「有没有别人正在这个分支上工作?」。如果答案是肯定的,那么把你的爪子放回去,重新找到一个无害的方式(如 git merge)来提交你的更改。不然的话,你可以随心所欲地重写历史。
# 5. rebae 的本地清理功能
在你工作流中使用 rebase 最好的用法之一就是清理本地正在开发的分支。隔一段时间执行一次交互式 rebase,你可以保证你 feature 分支中的每一个提交都是专注和有意义的。
调用 git rebase 的时候,你有两个基(base)可以选择:上游分支(比如 master)或者你 feature 分支中早先的一个提交。我们在「交互式 rebase」一节看到了第一种的例子。后一种在当你只需要修改最新几次提交时也很有用。比如说,下面的命令对最新的 3 次提交进行了交互式 rebase:
1 | git checkout feature |
这样,就可以对本地提交历史中最新的三个提交进行重新整理了,包括提交合并,提交日志修改等等。
通过指定 HEAD~3 作为新的基提交,你实际上没有移动分支 —— 你只是将之后的 3 次提交重写了。注意它不会把上游分支(master)的更改并入到 feature 分支中。
交互式 rebase 是在你工作流中引入 git rebase 的的好办法,因为它只影响本地分支。其他开发者只能看到你已经完成的结果,那就是一个非常整洁、易于追踪的分支历史。
# 追踪关系
建立 test 仓库 并建立追踪关系
1 | $ git branck --track test origin/master |
修改追踪关系
先切换到 test
1 | $ git checkout test |
修改追踪仓库(一定要先切换)
1 | $ git branch --set-upstream-to origin/master |
建立追踪关系之后,本地分支名称和远程一样时,使用 git push 时不用带上远程名称,git pull 也不用带上远程分支名
# git 冲突的修复
# 1. 直接编辑冲突文件
使用 git pull --rebase 经常会出现冲突
冲突产生后,文件系统中冲突了的文件里面的内容会显示为类似下面这样:
1 | <<<<<<< HEAD |
其中:<<<<<<<(7 个 <)HEAD 与 = 之间的内容是 remote 上的修改,冲突标记 = 与>>>>>>> 之间的内容是我的修改内容。
在这两者之间选择任何你需要的内容保留下来,并删除所有的 ===,<<<,>>> 即可解决冲突,解决完成之后,git add -A, git rebase --continue 就提交了代码
# 2. 利用图形界面工具解决冲突
当然我们也可以利用图形工具解决冲突
如果要解决的冲突很多,且比较复杂,图形界面的冲突解决工具就显得很重要了。
执行 git mergetool 用预先配置的 Meld (Beyond Compare) 解决冲突:
上面左右两个窗口依次是 “LOCAL”、“REMOTE”,它们只是提供解决冲突需要的信息,是无法编辑的。中间的窗口是合并后的结果,可以手动修改,也可以点击相应颜色的箭头选择 “LOCAL” 或者 “REMOTE”。
如果不向解决冲突,回到之前状态,可执行:
1 | $ git rebase --abort |
# 3. 代码提交完整流程
步骤如下:
git add -A
git commit -m “message”
git pull --rebase (或者 git fetch + git rebase)
解决冲突
git add 冲突文件
git rebase –continue
git push
其中,3、4、5 点,如果没遇到冲突就不用进行,直接 push 上去。
当遇到冲突时,git 会提示 patch failed,并要我们解决问题了再执行 git rebase --continue
# 常用命令速查表







