Git基本概念
Git是什么
Git是一个分布式代码管理工具,而SVN则是集中式代码管理工具。
- 集中式: 所有的代码都保存在中央服务器,所以提交必须依赖网络,协同工作的人们都通过客户端连接到这台服务器,取出最新的文件或者提交更新
- 分布式: 每个终端都是一个仓库,客户端并不只提取最新版本的文件快照,而是把原始的代码仓库完整的镜像下来,每一次的提取操作,实际上都是一次对代码仓库的完整备份。因此可以在本地进行提交,并不需要依赖网络。
工作区域
- 工作区: 实际的工作目录
- 暂存区: 代码add以后暂时存储的区域
- 仓库区: 分为本地仓库和远程仓库。 本地仓库是commit以后存储的区域, 远程仓库是push以后存储的区域
commit节点
每次Git提交以后都会生成一个节点,每个节点都会有一个哈希值作为一个唯一标示,多次提交就会形成一个线性节点链, 如下图所示,C2是最后一个提交节点,那么C2会包含前面C1和C0的提交内容。右边则是每次提交对应的哈希值
HEAD
HEAD相当于一个指针,它可以指向任意一个节点,它指向的那个节点就是当前工作节点。如上图所示,当HEAD指向C2,那么当前工作目录就是C2。
分支
分支可以让开发分多条主线同时进行,每条主线互不影响
假如将整个项目比作一个单机游戏,节点就是玩家保存的进度,HEAD指的就是玩家当前选择的那个进度,而分支可以认为是不同的玩家在玩这个游戏的历程。
Git命令详解
1. 初始化
1.1 初始化本地仓库
1.2 关联远程仓库
git remote add <自定义仓库名> + 仓库ssh地址
git init初始化仓库以后需要进行git remote操作,实现本地和远程仓库绑定,这样才算是完整创建本地仓库
1.3 克隆远程仓库到本地
可以通过git clone的方式直接将远程仓库克隆到本地,此时远程仓库名默认为origin。
2. 查看文件状态
2.1 查看文件状态
git status
2.2 查看文件状态简览
git status -s
相对于git status来说显示的内容更简单直观一点
- 新添加的未跟踪的文件前面有??, 即test5.txt
- 新添加的文件已经放入暂存区的,文件前面会显示A, 即test4.txt
- 文件被修改但是没有放入暂存区,右边会出现M, 即 test2.txt和test3.txt
- 文件被修改并且已经被放入暂存区,左边会出现M, 即test.txt
- 文件被删除但是没有放入暂存区,右边会出现D
- 文件被删除并且已经被放入暂存区,左边会出现D
3. 查看修改
3.1 查看工作区所有文件的修改
git diff
diff --git a/git_test_demo/README.md b/git_test_demo/README.md
这一行显示diff下两个文件的对比。 a版本指的是修改前的README.md, b版本指的是修改后的README.mddeleted file mode 100644
删除了文件 100指的是普通文件,644代表文件权限
Index e69de29..000000
index后面的两个数字表示两个文件的hash值,因为是删除,所以后面的数字hash值为000000。diff--git a/git_test_demo/test.txt b/git_test_demo/test.txt
这一行显示diff下两个文件的对比。 a版本指的是修改前的test.txt, b版本指的是修改后的text.txtIndex 5d58fcd..f316c8d 100644
index后面的两个数字表示两个文件的hash值(index区域的5d58fcd与工作区的f316c8d对象对比)--- a/git_test_demo/test.txt
+++ b/git_test_demo/test.txt
---表示修改前, +++表示修改后@@ -2,3 +2,4 @@
该行表示接下来下面显示的内容所在的位置。
-表示修改前, +表示修改后
-2,3 表示修改前test.txt文件从第2行显示,一直到第4行(2位起始行, 3位向后偏移的行数。即显示修改内容从第2行到第4行)
+2,4 表示修改后test.txt文件从第2行显示,一直到第5行(2位起始行, 3位向后偏移的行数。即显示修改内容>从第2行到第5行)
3.2 查看暂存区所有文件的修改
git diff --staged
3.3 查看两次提交之间的差异
git diff <commit id1> <commit id2>
3.4 查看两个分支之间的差异
git diff <branch1> <branch2>
3.5 查看指定提交的修改内容
git show <commit id>
3.6 查看指定文件的修改历史
git blame <file>
3.7 查看最近N次提交的修改内容
git log -p -N
4. 查看提交记录
4.1 查看当前分支的历史提交记录
git log
4.2 简洁的方式查看提交历史
git log --pretty=oneline
4.3 查看分支合并图
git log --graph
4.4 查看所有的操作记录
git reflog
任何的操作记录都会显示在reflog上面
显示内容为Commit id + 执行的命令 + 提交描述
4.5 查看指定提交记录是在哪个分支提交的
git branch --contains <commit id>
5. 添加文件
当前目录下修改了三个文件test.txt, test2.txt, test3.txt, 新增了test4.txt, 移除了README.md
5.1 暂存工作区所有的文件
git add . / git add -A / git add --all
5.2 暂存工作区指定的文件
git add <file1> <file2>
5.3 暂存工作区指定目录的所有文件
git add {directory}
5.4 暂存工作区所有文件,不包含删除的文件
git add *
5.5 暂存工作区所有文件,不包含新增的文件
git add -u
6. 分支
6.1 查看本地分支列表
git branch (带星号指的是当前的分支)
6.2 查看远程分支列表
git branch -r
6.3 查看所有的分支列表(包括本地和远程)
git branch -a
6.4 查看本地每个分支最后一次提交
git branch -v
6.5 查看本地每个分支最后一次提交,并且与远程分支进行比较
git branch -vv
6.6 创建分支
git branch <branch>
6.7 删除指定分支
git branch -d <branch>
6.8 强制删除指定分支
git branch -D <branch>
6.9 删除远端分支
git push origin --delete <branch>
6.10 切换到上一个分支
git checkout -
6.11 切换分支
git checkout <branch>
6.12 创建分支并且切换到该分支
git checkout -b <branch>
等价于git branch <branch> + git checkout <branch>
7. 提交
7.1 提交暂存区的文件到本地仓库
git commit -m "message"
7.2 跳过暂存区直接执行提交
git commit -a -m "message" / git commit -am "message"
这种方式可以直接提交工作区修改后的文件,但是新添加的文件是不会提交的,仍然需要先add到暂存区。
7.3 修改之前已经提交的结果
git commit --amend
第二次提交将 代替 上一次提交的结果。适用于提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了的情况。
7.4 将本地提交推送至远端
git push origin <branch>
7.5 强制提交到远端
git push origin <branch> --force
以下图为例, 将上面那次commit给撤销以后,然后执行git push origin cdf2发现无法推到远端仓库。
所以执行了git push origin cdf2 --force强制将远端仓库的那次提交也给撤销掉
7.6 将修改提交到远端指定的分支
git push origin <commit id>:<branch>
以下图为例
- 在cdf2的分支下面提交了一个test4.txt文件
- 将test4.txt文件推到了远端cdf4的分支上
8. 拉取
8.1 拉取远端分支的最新代码
git fetch
git fetch --depth=N
只拉取最近N次的commit, 这样可以加快拉取速度。 但是由于只拉取最近N次的commit,所以相应的本地也只能看到最近的N次commit。
git fetch 会先将远端的代码拉取下来保存到FETCH_HEAD中,然后通过git merge的方式将FETCH_HEAD的代码merge到当前本地分支
8.2 拉取远端分支的最新代码, 并且合并到本地分支
git pull
git pull则是git fetch + git merge的操作, 直接更新本地分支的代码
8.3 拉取远端分支的最新代码,并且合并到本地分支
git pull --rebase
当本地没有任何提交记录,那么git pull = git pull --rebase。
当本地提交了一个节点,远端同一个分支也更新了节点
通过git pull的方式会产生一个merge的提交记录
如右下图所示
通过git pull --rebase的方式,则会让整个提交记录看起来更线性。
9. 回退
9.1 清空暂存区
git reset HEAD/ git reset --mixed
9.2 将HEAD回退N个节点
git reset HEAD~N
例如N=2,那么HEAD将往前推2个节点,会指向倒数第三个节点。 回退以后保留修改文件
9.3 重置所有文件到未修改的状态
git reset --hard
9.4 将HEAD回退一个节点
git reset --hard HEAD^ / git reset --hard HEAD~1
9.5 将HEAD回退到指定的节点
git reset --hard <commit id>
9.6 撤销工作区指定文件的改动
git checkout -- <file> / git restore <file>
9.7 将HEAD指向指定节点
git checkout <commit id>
9.8 将HEAD指向前一个节点
git checkout HEAD^
9.9 将HEAD往前移动N个节点
git checkout HEAD~N
例如N=2,那么HEAD将往前推2个节点,会指向倒数第三个节点
9.10 还原指定节点的修改
git revert <commit id>
revert以后会将原来的修改回退掉,并且新增一条提交记录
9.11 还原上次的提交
git revert HEAD
10. 保存
10.1 保存当前修改的内容
git stash
10.2 查看保存的记录列表
git stash list
10.3 将上一次保存的内容恢复到工作区
git stash apply
10.4 删除stash里面上一次保存的记录
10.5 上一次保存的内容恢复到工作区,并且删除对应的记录
git stash pop
10.6 清空stash里面所有保存的记录
git stash clear
10.7 删除stash里面指定index的那一条记录
git stash drop <index>
11. 标签
11.1 在指定节点上创建标签
git tag <tag name> <commit id>
11.2 查看所有标签
git tag
11.3 删除本地标签
git tag -d <tag name>
11.4 将本地指定标签推送到远端
git push origin <tag name>
11.5 将本地所有标签推送到远端
git push origin --tags
12. 合并
合并整体的链路比较复杂,并且在命令行中不好直观呈现,下一节做比较的时候再详细给出
12.1 将指定的提交节点复制到当前最新的节点上
git cherry-pick <commit id>
执行git cherry-pick <commitid>将其他分支的指定commit记录复制过来提交到当前分支
12.2 将指定分支的提交记录复制到当前分支
git merge <branch>
- 执行git merge cdf将cdf分支的代码合并过来
- 产生冲突以后,将文件冲突解决掉
- 执行git add .命令
- 执行git merge --continue。
- 然后就会产生一条merge的commit记录
12.3 将指定分支的提交记录复制到当前分支,并且保持线性的提交记录
git rebase
- 执行git rebase cdf将cdf分支的代码合并过来
- 产生冲突以后,将文件冲突解决掉
- 执行git add .命令
- 执行git rebase --continue。
- rebase以后的提交记录更具线性
git rebase之前的commit记录
git rebase 之后的commit记录
12.4 将多次的提交合并为一次提交
12.4.1 git rebase -i HEAD~N
将最近的N个commit合并为同一个commit。
12.4.1.1 执行前:
12.4.1.2 执行后:
12.4.2 git rebase -i <commit id1> <commit id2>
将<commitid1>到<commitid2>之间的提交合并成一个提交,左开右闭。所以如果要合并最近的三个提交,那么只需要<commitid1>填倒数第四个提交的commitid, <commitid2>填最近一个提交的commitid即可
12.4.2.1 执行前:
12.4.2.2 执行后:
Git相似命令比较
1. 「git reset HEAD^」 VS 「git checkout HEAD^」
1.1 git reset HEAD^
git reset HEAD^在当前分支将HEAD指向了前一个节点,这个时候可以直接执行push origin <branch> --force就可以更新远端分支
1.2 git checkout HEAD^
git checkout HEAD^则是脱离了当前分支,然后将HEAD指向了前一个节点。但是main分支最新的commit依然是C4, 这个时候执行push origin <branch> --force会提示Everything up-to-date。
2. 「git reset HEAD^」VS 「git revert HEAD」
2.1 git reset HEAD^
git reset HEAD^将本地分支最新的commit记录指向了前一个节点,最新的commit已经变成了C3,这个时候可以直接执行push origin <branch> --force就可以更新远端分支
2.2 git revert HEAD^
git revert HEAD则只是将上一次的提交的记录回滚,并且重新提交了一遍,虽然最后的结果跟reset一样,但是在分支上面会新增一个节点
3. 「git cherry-pick」 VS 「git merge」 VS 「git rebase」
3.1 git cherry-pick
下图HEAD指向了main, 我们想将cdf分支的C3复制到当前所在位置的话,就可以使用cherry-pick的方式
如图所示,将cdf分支上C3的提交cp到main分支变成了C3',C3'跟C3就只有提交的内容一样,属于不同的两次提交
3.2 git merge
下图HEAD 是指向了 main,我们想将 cdf 分支的代码合并到 main 分支上来,那么执行 git merge cdf
合并之后,多了一个C6节点,这个节点的父节点有两个,分别是C4和C5。 main和HEAD指向了合并后的新节点
3.3 git rebase
下图HEAD指向了main, 我们想将cdf 分支的代码合并到main分支,但是又想让提交的记录比较整洁,这个时候就可以使用git rebase的方式
rebase,顾名思义就是重新以XX为父节点。重新创建一个"分支",将需要rebase的分支提交节点给复制过来,然后再将当前分支的节点放到新"分支"最新的节点上。并且HEAD指向了新"分支"(注:这里的分支打个双引号是因为它其实并不是一个真正意义上的分支,它没有分支名,只有HEAD),而且原来分支节点依然还存在,如果还想用到之前的节点,可以通过checkout 对应的commit id即可
Q&A
1. 应该如何回退自己的git操作
1.1 发现自己提交的代码有点问题, 想要撤回上一次的提交,但是又想保留原先的修改,那么执行git reset --mixed或者git reset --soft 。 git reset默认就是mixed
1.2 发现自己的提交就是有问题的,想要撤回上一次的提交,并且不想保留原先的修改,那么可以执行git reset --hard
1.3 如果我们想要撤回远端分支的提交,但是远端分支又是一个protected的分支,那么我们只能选择git revert的方式将修改还原,但是会在分支上新增一次提交记录
2. 应该怎么保存自己的代码?
2.1 一个feature开发完成或者一个bug修改完成,这个时候需要切换分支去做其他事情,那么可以commit到本地仓库,然后推到远端自己建的分支上面。 但是后续如果有些修改但是又不想再增加commit了,那么可以通过git commit -amend的方式来覆盖上一次commit的记录
2.2 有时候工作区文件改了一半,但是又不想增加一个commit,但是需要把远端的最新代码拉下来又发现有冲突拉代码失败。 这个时候就可以先通过git add的方式放到暂存区,然后git stash将文件保存起来。
3. 如何快速查到一段代码的修改记录?
3.1 如果只是想看这段代码是什么时候提交的,可以通过git blame -L N, +M --<file>查看
其中N代表是从第N行开始, M代表一共要看M行代码, file就是对应的文件名
3.2 如果需要看到某一行代码的修改记录,那么可以通过git log -L N, +M:<file>查看
其中N代表是从第N行开始, M代表一共要看M行代码, file就是对应的文件名
3.3 AndroidStudio自带的git功能也非常好用,选中一段代码,右键Git->Show History for Selection就可以查看所有的修改记录
4. 如何快速查找某个人的提交记录?
git log --author=<email>
5. 如何快速查找某个commit?
- 通过git log --grep <key> 关键字搜索的方式搜索出所有相关的commit
- 找到对应的commitid
-
通过git show <commitid> 就可以找到对应的修改记录
6. 如何知道自己的某个commit 都合入了哪些分支 以及哪些版本(tag)?
如果知道对应的commitid,那么通过git branch --contains <commitid>
如果并不知道commitid,那么需要先通过git log --all --grep <key>的方式找到对应的提交记录,然后通过git branch --contains <commitid>
7. git reset --hard之后代码丢了,怎么恢复
7.1 如果你的代码现在在工作区或者是暂存区,你不小心执行了git reset --hard命令,那么真的是神仙难救,所以如果工作区有代码,切忌不要执行git reset --hard方法
7.2 如果你的代码已经push到了远程仓库,你不小心执行了git reset --hard命令,那么只需要git pull就可以恢复。如下图所示
7.3 如果你的代码已经push到了远程仓库,你不小心执行了git reset --hard命令,并且还强制推送到了远端,那么可以执行下面的步骤,如下图所示
- 通过git reflog查看所有的操作记录
- 找到撤销之前的那条commit
- 通过git reset --hard <commitid>
- 通过git push origin <branch> 的方式恢复远端分支
通过git reflog查看所有的操作记录,找到撤销之前的那条commit
通过git reset --hard <commitid>,git push origin <branch> 的方式恢复远端分支
8. 新增的文件刚好命中了.gitignore的规则,怎么提交
当你新增的文件或者修改的文件刚好命中了gitignore的规则,那么你执行git add命令的时候会有上面这段提示。通过git add -f <file>的方式就可以添加到暂存区。
当然如果觉得gitignore的规则不合理,修改.gitignore文件即可
9. 一个文件里面有多个bug单对应的修改或者是有些修改是测试代码,该怎么提交指定的内容
9.1 通过git add -p <file>的方式筛选出要提交的代码,如下图所示。
-
在test10.txt文件中新增四行,然后保存。
-
执行git add -p test10.txt的命令
- 这里有几个命令需要执行
a. y: 同意这段修改放入暂存区
b. n: 不同意这段修改放入暂存区
c. q: 退出
d. a: 同意这段修改和这个文件中所有的其他修改都放入暂存区
e. d: 不同意这段修改以及这个文件中所有的其他修改放入暂存区
f. e: 自定义选择放入暂存区
g. ?: 打印帮助 - 选择e进入编辑页面
a. 新增行,不想提交(但是变更仍然在工作区)。删除整行
b. 删除行,不想提交。用空格符" "代替"-"字符。
c. 变更行,不想提交。上面两个结合——对于"+"所在行整行删除,对于"-"所在行,用空格符" "代替"-"字符
-
删除「add new line 2」和「add new line3」然后保存。然后执行git commit将test10提交到本地仓库
-
根据commitid查看修改记录
9.2 使用sourcetree提交
执行命令行来提交指定的内容多少是有点不直观,并且不易上手,采用sourcetree的方式就非常直观,选中指定的提交的内容然后点击暂存行即可
10. git push origin <branch> --force以后,如何回退
git push origin <branch> --force以后,远端的分支节点已经发生了改变,但是本地分支依然是有以前的commit记录,所以先使用git reflog找到之前的操作记录,通过git reset -hard <commitid>的方式回退到当时的版本, 然后git push origin <branch>来更新远端分支