这是一篇拖了很久的 Blog,偶然间翻到,发现正好可以用来梳理一些忘记了的点。欢迎拍砖。
一、git 删除文件及恢复
1)git rm <file>
做了两件事:
1.删除文件;
2.将被删除的文件纳入到暂存区(stage、index)
【补充】git 的三个区域:工作区、暂存区、仓库
若想恢复被删除的文件,需进行两个操作:
1.git reset HEAD <filename>
,将待删除的文件从暂存区恢复到工作区
2.git checkout -- <filename>
,将工作区中的修改丢弃掉
2)rm file
做了一件事,将 file 删除,这时,被删除掉的文件并未纳入暂存区中 ,还在工作区中
所以要恢复的话,直接丢弃工作区的操作就行:git checkout -- file
有个需要注意的就是 Changes to be committed:
表示文件被放在了暂存区
而Changes not staged for commit
表示文件还在工作区
二、git log 的一些操作
1.commit 信息错误,想修正?
——git commit --amend -m '再次修正'
: 就可以把最近的一条 log 中的消息修改为 ‘再次修正’
2.显示最近的 n 条日志
—— git log -n
3.定制 log 显示格式
——git log --prettt=oneline
: 单行显示
——git log --pretty=format:"%h - %an, % ar:%s"
:不同参数有不同格式
三、.gitignore 与分支
1.首先我们需要知道一个开发习惯:项目本身的 ide 的项目管理文件是不应该提交到版本控制中的。(比如 .idea 这种)因为项目管理文件中往往标识了项目引用的路径,或者是 IDE 用来识别项目等信息,不同人之间合作时,这些信息是不同的,不应该被 pull。
jar 包是不允许也不应该丢到版本控制中的,应该放的是 build.gradle/pom.xml 这样的描述文件
然而,我们的修改总会导致工作区不干净,同时又不能 git add .
全部添加,这种情况该如何处理?——此时,.gitignore 就发挥它的作用了
假设我们的项目管理文件叫 setting.properties ,那么我们应该如此操作:
vi .gitignore
## 然后在 .gitignore 中写 setting.properties ,保存退出
git add .gitignore
git commit -m "添加了.gitignore"
注:.gitignore 支持通配符、正则表达式整来通配文件名
2.忽略文件的写法
一个 * 表示一层目录,** 表示所有层次
*.a # 忽略所有 .a 结尾的文件
!lib.a # lib.a 除外
/TODO # 忽略根目录下的 TODO 文件,不包括子目录下的TODO
build/ #忽略 build/ 下的所有文件
doc/*.txt #忽略 doc/下一级目录的所有 .txt ,但是下下级别不管
/**/a.txt. # 忽略所有的 a.txt 文件
3.分支
SVN 的分支是重量级的,会复制一份完整的分支信息,而 git 分支是轻量级的,仅仅创建了一个指针
a.查看当前分支: git branch
, * 所在的分支表示当前分支
b.创建新分支: git branch new _branch
c.切换分支:git checkout new_branch
,刚刚切换到新分支的一刹那,两个分支的内容是相同的。后续的开发则是独立的
d.切换到前一个分支:git checkout -
。 ‘-’ 表示前一个,cd - 也会切换到之前的目录
四、分支重要操作
1.删除分支: git branch -d new_branch
-d 表示 delete
不能删除当前正在使用的分支;不能删除有文件改动而且未合并的分支
git branch -D new_branch
:强制删除 new_branch (不推荐)
2.创建并切换到新分支
git branch new _branch
+ git checkout new_branch
<=> git checkout -b new_branch
3.当前在 matser 上,希望将 new_branch 上的修改合并到 master 上
git merge new_branch
会输出 Fast-forwad(FF,快进)。现在就可以 -d 删除 new_branch 了
即:将 git merge 后跟的分支,合并到当前所在的分支上
PS:Fast-forwad:快进,指的是没有任何冲突,直接移动指针的操作
4.查看当前分支上最近一条的提交消息: git branch -v
所以,什么是分支呢?
——其实,分支是一个 commit 对象链
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yabLpmYP-1592959258243)(E:\goodgoodstudy\课外书\git learn.assets\git4-1 分支.png)]
而 HEAD 和 master 其实也是两个指针:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gs2CKuqT-1592959258258)(E:\goodgoodstudy\课外书\git learn.assets\git4-2 head master.png)]
如上图, master 指向第三次提交,HEAD 指向当前所在的分支。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1K2Vvy45-1592959258263)(E:\goodgoodstudy\课外书\git learn.assets\git4-3 dev.png)]
而这个图,相当于创建了 master 和 dev 分支,当前处在 dev 分支上。
所以 HEAD 保存在哪里呢?
cd .git
ls
cat HEAD # 会随着切换不同分支而改变
5.冲突:如果不同分支修改的是同个文件,则合并时就会发生冲突,需要手动解决;如果修改的不是同个文件,直接合并即可
1)如果 dev 分支和 master 分支都对 test.txt 修改了一行,此时在 master 分支上尝试合并,会报错:
Auto-merging text.txt
CONFLICT (content):Merge confict in test.txt
Automatic merge failed:fix conflicts and commit the result.
即,自动合并失败,产生了冲突,需要修复冲突后重新提交。
此时,text.txt 的内容会成为标准的冲突形式:
xxxxx(没冲突的)
<<<<<< HEAD
hello cloei
======
hello swift
>>>>>> dev
#即:
#发生冲突的内容在当前分支是 <<< HEAD 和 ==== 之间的,
#另外的分支的冲突是 ==== 和 >>>> dev 的
#此时直接编辑即可,保留想保留的就行
修改完之后的 status 是:You have unmerged paths
,有未合并的文件;
要 git add text.txt
来标识 “冲突解决了”,此时的 status 是 “冲突解决了,但是还处于 merge 状态”;
使用 git commit
来 conclude merge (结束冲突),此时的工作环境才是 clean 的。
2)如果再切换回 dev 上进行合并,此时不会再冲突了(因为已经被解决了)
仅仅发生快进(FF)动作,而且对应的文件内容变成了冲突合并后的文件内容
3)git -am “add a new line” <=> git add . + git commit -m "add a new line"
6.分支改名
git branch -m master master2
:将 master 分支改名为 master2
五、分支进阶与版本回退
1.快进 fast-forwad ff
-
如果可能,合并分支时 git 会使用 ff 模式
-
在这种模式下,删除分支时会丢掉分支信息
-
可加上 --no-ff 来禁用,会多出一个 commit id:
git merge --no-ff dev
- 此时的 merge 需要再输入 commit message ,同时会保留下分支信息
-
用图形化方式查看 log :
git log --graph
2.版本回退
用 git 来作为版本控制工具,任何时候都有后悔药可吃。
git reset -- hard HEAD^
: 回退几个版本,就有几个 ^git reset -- hard HEAD~2
: 回退到相对于当前版本前的第 2 个版本git reset -- hard commit_id
:直接指明回到该 id 的版本
3.如果回到了前面的版本,又想回到后面的版本呢?
由于git log
仅仅记录当前版本及其以前的日志,无法看到后面的 commit_id ,所以 git log 失效了。需要使用 git reflog
,它记录下了操作日志,记录下了所有的操作信息,包括 commit_id
所以正确的方式是:
git reflog # 找到想回去的版本id (保留五位即可)
git reset hard -- id
六、checkout 进阶与 stash
git checkout -- test.txt
:丢弃相对于暂存区最后一次添加的最新内容的工作区修改。简单说就是丢弃工作区的修改
git reset HEAD test.txt
:将之前添加到暂存区(stage、index)的内容,从暂存区移除到工作区
关于 checkout 的真相:前面有个图已经说过,git 的管理其实是一个 commit 链。不同的分支指向了不同的节点,checkout 分支 其实也就是在修改 分支指针 所指向的节点。
如果在切换了分支以后,在子分支 dev1 上进行了修改,但是代码的 bug 还没解决完全,此时又来了一个新的需求需要开发;而如果我们直接 chekout 到新的分支 dev2 ,是无法切换的,因为 dev1 相对于 master 分支已经快了一步,此时切换不再是简单的 fast-forward。
通过 commit 来保存修改是可行的,但是按照惯例, commit 的代码应该是没有问题的代码,而此时 dev1 是还未修复完 bug 的,不应该进行提交。此时,stash 就登场了。
stash 主要是用来保存现场和恢复现场的,它将所有需要暂存的修改的信息保存在一个 list 中 ,等需要恢复时使用。
-
保存现场
git stash
git stash list
-
恢复现场
-
git stash apply stash@{0}
:将 0 号修改恢复,但是不删除 stash 中的内容 -
git stash pop
:恢复并且删除内容
-
七、标签与 diff
(一)标签
1.何时使用标签?
——当我们的开发进行到一定程度,想对当前版本进行发布时,想要一个一个里程碑式的分界,就可以使用标签。
2.标签有两种:轻量级标签和带有附注的标签。标签就是一个指针,而且不依赖于某个特点分支而存在,所有的分支上都能看到当前存在的标签。
3.标签操作:
-
创建一个轻量级标签:
git tag v1.0.1
-
创建一个带有附注的标签:
git tag -a v1.0.2 -m 'release version'
-
删除标签:
git tag -d tag_name
4.一个很好用的命令 git blame file_name
,能清晰地看出 file_name 文件的修改情况以及修改的作者是谁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MsqMBRdK-1592959258270)(E:\goodgoodstudy\课外书\git learn.assets\image-20200503204812905.png)]
(二)diff——差异性
1.本身 diff 是 Linux 系统自带的内建命令,使用方法:diff file_a file_b
,更清楚的看到差别:diff -u file_a file_b
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nw5Iz3Cu-1592959258276)(E:\goodgoodstudy\课外书\git learn.assets\image-20200503205349041.png)]
含义:
- ‘-’ 表示源文件,‘+’ 表示目标文件
- 1,3 :从第一行开始,连续三行
- 后续为内容行:如果没有符号,表示二者相同;’-‘ 表示把这行删掉能得到目标文件,’+‘ 表示加上这行能得到目标文件
2、git diff 有四种情况
-
比较暂存区(索引区)和工作区的差别:
git diff
,此时暂存区的作为原始文件(-),工作区的作为目标文件(+) -
比较工作区与 commit_id 对应的提交的差别:
git diff commit_id
;如果想比较工作区与最新一次提交的差别:git diff HEAD
,因为 HEAD 指向当前分支。【原始文件是版本库的,目标文件是工作区的】 -
比较版本库与暂存区的差别:
git diff -cached <commit_id>
,如果缺省了 id ,比较的是最新提交和暂存区的差别 -
比较两个提交的差别:
git diff commit_id1 commit_id2
,如果是要比较两个版本下的 src 文件夹的差别:git diff commit_id1 commit_id2 src
默认的原始文件优先级:版本库 > 暂存区
默认的目标文件优先级:工作区 > 暂存区
八、远程与 Github
(一)远程的工作流程
1.借用一个场景来叙述:
张三和李四都在开发一个项目,通过远程来进行合作。
- 在推送代码时,如果张三首先把代码 push 推送到了远程是可以的,因为远程还是空的;但之后李四想直接 push 是不可以的,因为不可能一直让后来的覆盖前面的
- 所以此时李四应该先 pull 拉取,同时执行合并 merge,实现了两个人代码的交换。merge 可能会成功:修改的是不同文件,或者是一个文件的不同地方;失败:修改了同个文件的同一行,就需要李四手动进行合并。执行完成之后,李四才可以进行 push 到远程
pull == fetch + merge 【原因之后会说】
2.远程版本库和本地有什么区别吗?
——严格来说,没有。远程的存在只是因为它一直处于开机状态,便于代码交换
3.GitLab 是用于公司内网、局域网开发使用的,从 Github 模仿而来
4.git config 的不同级别:system(整个电脑)、global(用户级别 )、local(仓库级别)。如果配置的是 local ,每次新建仓库都需要重新配置 git config
(二)远程和本地进行关联
1.添加远程地址:git remote add origin https://xxxxx.git
,此处的 origin 以后将代表后面的 url
2.进行第一次关联:git push -u origin master
,-u 表示当前本地分支和远程的 master 分支关联,以后再推送直接 git push
即可
九、Git 远程操作
git remote show
:展示本地与之对应的远程仓库(可能不止一个),默认情况下别名都叫 origin ,如果取名 test1、test2 ,也会展示出来
git remote show origin
:会列出来远程仓库的详细信息,本地分支和远程分支的 push、pull 关系也会罗列出来
git push
会根据 git config 中的配置来显示不同信息,推送到相应远程分支
使用 git 开发一般是鼓励创建分支的,也有多种开发流程:
- Gitflow:配置较为复杂,适合于某些场景,具体可见 https://www.cnblogs.com/jeffery-zou/p/10280167.html ,有正在被抛弃的趋势
- 基于 Git 分支的开发模型:
- develop 分支:频繁变化的分支,供开发人员 push、merge代码
- test 分支:供测试、产品等人员使用的一个分支,变化不是特别频繁,merge 自 develop 分支
- master 分支:生产发布分支,变化非常不频繁的一个分支,merge 自 test 分支,然后直接 push 到远程服务器,进行生产、部署
- bugfix(hotfix) 分支:生产系统当中出现了紧急 bug,用于紧急修复的分支。从 master 上拉取分支,在这个分支上修复玩再合并到 master
使用 github 来进行远程操作的流程:
- 在本地配置个人信息,
git config --local user.xxx='xxx'
git remote add origin https://xxxxx.git
关联远程仓库,可以加 https 或者 ssh 的地址,这一步的结果是:将远程仓库的地址写入本地 known_hosts 文件- 如果加的是 ssh 地址,要求在本地生成密钥,将公钥放到 github 上,后续在建立连接时会进行公私钥的匹配。
- 如果在放置公钥时,没有勾选添加写权限的勾,则只能从远程 pull 代码(读),但是不能从本地 push 到远程(写)
密钥有针对仓库级别的和账号级别的,后者是同个账户的所有仓库都有效
十、Git 协作
git push 发生了什么?
——当远程分支和本地分支关联后,会在本地存在两个分支:master 和 origin/master ,后者是和远程分支关联的分支。当 push 发生时,首先将本地修改推送到远程,再者修改 origin/master 指向的提交点。(否则,会说 master 比 origin/master 快)
origin/master 是系统自动维护的,我们不能直接操作它。分支指向当前修改,HEAD 指向分支