Git 学习
以往的 version control 是记录补丁(RCS),记录新文件和上一个文件的变化(比如新增了一行aaa、删除了一行bbbb),从而保证版本控制,可以回退,但是这样的 version control 只能管理文本,不能管理多媒体等其他文件
Git 不一样,每一次提交的文件,都是保存完整的文件,Git 版本库拥有每个版本文件的快照(SNAP),切换速度非常快,除了管理代码外,还可以管理多媒体等文件
集中化的版本控制
- 让不同系统上的开发者协同工作
- 有一个单一的集中管理的服务器,保存所有文件的修订版本,大家从这台服务器拉取文件,或者提交更新
- 好处:
- 大家都能在一定程度上看到别人在做什么
- 管理一个CVCS比每个客户端都维护一个本地数据库来得容易
- 坏处:
- 单点故障
分布式版本控制
- 客户端不止提取最新版本的文件快照,而是把代码仓库完整地镜像下来,包括完整的历史记录
- 每一次的克隆,其实都是对代码仓库的完整备份
- 避免了单点故障,也不需要维护一个中央服务器
- 可以在同一项目中和不同的工作小组的人协作
Git 是一个分布式的版本管理系统
Git 特点
-
直接记录快照,而非差异比较(每当提交更新或者保存项目状态时,就会对当时的全部文件创建一个快照,并保存这个快照的索引。为了效率,没有修改的文件不会重新存储,而是保留一个链接指向之前存储的文件)
-
近乎所有操作都是本地执行(快!)没有网络延迟
-
保证完整性,类似区块链,所有数据保存时都计算哈希,并且以哈希来引用!(SHA-1)
-
几乎只有添加数据,(删除其实也是提交一个快照,所以我之前的阿里云密钥现在还在给我发段信!!!)
-
三种状态:committed、modified、staged
- committed:表示数据已经保存在本地数据库中
- modified:表示数据已经修改、但还没有保存到数据库中
- staged:表示对一个已修改的文件的当前版本作了标记,使之包含在下次提交的快照中(类似暂存吧)
-
三个阶段:工作区、暂存区、Git 目录
-
工作区是项目的某个版本的内容,提取出来放在磁盘上供你修改
-
暂存区是一个文件,保存下次将要提交的文件列表信息,一般在.git里面
-
Git 仓库目录:.git 文件夹,保存Git 的元数据和对象数据库,克隆就是克隆这个
-
Git 基本工作流程
1、在工作区中修改文件
2、将想下次提交的更改选择性地暂存(别把配置文件提交!!!)
3、提交更新,将快照永久性地存储到 Git 目录
建立Git 仓库
- git init
- git clone ( git clone 会复制所有的版本,然后将最新版本的文件放在工作区)
Git add
没有被 add 的文件,Git 会忽略它
所有被 Git add 的文件都会有副本
git add 是一个多功能命令
- 没有被追踪的文件,使用 git add 会让 git 将这个文件加入管理范畴
- 已经追踪的文件,使用 git add 会将该文件放到暂存区
- 合并时,使用 git add 可以把有冲突的文件标记为已解决
可以理解为将内容添加到下一次提交中
当你修改了文件并且 git add 后,保存在暂存区,之后又修改了这个文件
再次查看 git status
可以看到这个文件同时出现在 to be committed 和 not staged
所以,git add 并不是添加这个文件,而是添加的 SNAP (当时的文件)
git add 之后的修改并没有添加到暂存区
Git ignore
我们想要忽略一些编译文件、日志文件等,又不想让他总提示我没追踪,就要git忽略它
编写一个文件 .gitignore
文件 .gitignore 的格式规范如下:
所有空行或者以 # 开头的行都会被 Git 忽略。
可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
匹配模式可以以(/)开头防止递归。
匹配模式可以以(/)结尾指定目录。
要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。
星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。
使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。
一个仓库可能只根目录下有一个 .gitignore 文件。 然而,子目录下也可以有额外的 .gitignore 文件。子目录中的 .gitignore 文件中的规则只作用于它所在的目录中。
Git diff
git status 只能看到文件当前的状态
使用 git diff 查看:
- 当前做的哪些更新尚未暂存?
- 有哪些更新已暂存并准备好下次提交?
只是使用文件补丁的格式给你展示,并不是底层使用文件补丁实现
git diff 不加参数,比较的是工作目录中当前文件和暂存区快照的差异
git diff --staged ,比较的是暂存区快照和最后一次提交的文件的差异
经常 git add 之后 git diff 没有信息就是这个原因
Git commit
只会提交已经 add 的文件作为快照
git commit -a :将所有已经跟踪的文件一并提交(跳过 add 过程)
这很方便,但是很有可能会提交一些你不想提交的文件(比如说配置文件)
git commit --amend : 上次提交漏掉了几个文件(马上git add)、或者提交信息写错了,用这个命令
git commit -m "asd"
git add forgotten_file
git commit --amend
最终只会有一个提交
移除文件
从 Git 中移除某个文件,就需要将它移出跟踪文件清单(也是从暂存区移出)
git rm : 连带着在工作区也删除了(因为工作区只是 .git 的一个版本快照)
如果只是在工作目录删除文件,那只会作为一次操作记录,会出现 Changes not staged for commit
如果要删除之前修改过、已经放到暂存区的文件,需要使用 git rm -f , 这样的数据不能被 Git 恢复
删除 git 暂存区的文件 而保留在磁盘: git rm --cached README
移动文件 (不重要)
git mv file_from file_to
git mv 相当于下面三个命令:
- mv file1 file2
- git rm file1
- git add file2
查看提交历史(重要)
git log :
-
-n 显示最近 n 次提交
-
-p 按照补丁格式显示统计信息
-
–stat 显示简略统计信息
-
–pretty(很有用),比如 git log --pretty=oneline , 将信息展示为一行
-
–pretty=format 还有很多种参数
撤销操作(重要)
一、取消暂存的文件:
不同的版本不一样,git status 会提示你,怎么取消暂存,我这边是 git restore --staged
二、撤销对文件对修改:
不同的版本不一样,git status 会提示你,怎么取消修改,我这边是 git restore file
“这是一个危险的命令。 你对那个文件在本地的任何修改都会消失
Git 会用最近提交的版本覆盖掉它。 除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令。”
Git 中任何 已提交 的东西几乎总是可以恢复
远程仓库使用(重要)
并不一定是 github 或者 gittee 才是远程仓库,任何不在本地的 git 仓库都是远程仓库
git remote // 查看已经配置的远程仓库
git remote -v // 查看简写以及URL,会全部列出
查看远程仓库
git remote show <remote> (常用)
可以看到远程有两个分支、一个main一个master
添加远程仓库
git remote add "shortname" "url"
fetch 和 pull(重要)
git fetch remote
会访问远程仓库,拉取所有你还没有的数据。拉取完后,会拥有所有分支的引用
如果 clone 了一个仓库,会自动添加远程仓库,默认为 origin
git fetch origin 会抓上一次抓取后新的推送的所有工作
fetch 只会将数据下载到本地仓库,而不会自动合并
pull 在抓取数据后会尝试直接合并分支 ,git pull 会从最初克隆的服务器上抓取数据并自动尝试合并
git pull 相当于三条命令
git pull/fetch <远程主机名> <远程分支名>:<本地分支名> (常用)
git fetch origin master:tmp // 从远程拉一个分支到tmp分支
git merge tmp // 尝试合并当前分支和tmp分支
git branch -d tmp // 删除tmp分支
pull 自带的merge 不是很好用(有时候会发生错误),建议还是 git fetch origin master:tmp
push
git push <远程主机名> <本地分支名>:<远程分支名>
如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。
git push origin master
push 前一定要先抓取、消除冲突,再 push。开始工作前也 fetch 一下,在最新的版本上开发
git tag
git tag 命令查看所有标签 也可以 git tag -l “v1.8*” 列出所有版本号为1.8开头的版本
git 有两种标签:
-
轻量标签:只是某个特定 commit 的引用
- git tag v1.4
-
附注标签:是Git 数据库中的一个完整对象,是可以被校验的,包含打标签的人的名字、邮箱、日期、还可以签名验证 、 创建附注标签很简单
- git tag -a v1.4 -m ”my version 1.4“
标签要另外push才会到远程
git push origin v1.1
注意⚠️:tag 是和某个 commit 直接绑定的,而非 branch
所以你切换分支了,还是可以看到这个 tag( git show v1.2 )
所以你推到远程去, 不需要指定分支 而是直接 push origin v1.2
版本回滚(重要)
首先通过 git log 或者 git tag 查看 xxx
git reset --hard xxx ( xxx为某个 commit 的hash 或者某个 tag )
回滚后看不到 git log 了怎么办?( tag还是能看见 ) git reflog
git 分支(重要)
将每个commit 理解为链表的节点、就很容易理解分支了
HEAD 指针,指向当前所在的本地分支,HEAD指向master,你就在master分支上
其实所有的 HEAD、master、dev、都是一个指向commit 的指针,只是不同的分支向不同的方向延续
git switch 切换分支、会改变你的工作目录
这种情况如果修改了同一个文件,merge会冲突,这个时候需要手动解决冲突,再 add 、 commit
git branch 查看(新建)分支(基本命令)
git branch -d 删除分支
稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。
删除远程分支:
git push origin --delete serverfix
To https://github.com/schacon/simplegit
变基
整合分支最容易的就是 merge 命令,会将两个分支的最新快照、以及二者的最近共同祖先,三者合并,生成新的 commit
还有一种合并的方法:
变基: git switch c4 \ git rebase c5 将 C4 的修改 按顺序应用到 C5
提取C4的补丁和修改、在C3的基础上再应用一次
结果都是一样,但是变基使得提交历史更加整洁
变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起
变基的风险:
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基
大厂实际工作中 Git 流程
master 永远是正常稳定可用的主干分支
拉出一个 dev 分支,check out from master (master镜像)、类似防火墙
dev 是所有开发小组的一个共用分支
但是如果开发人员都向dev 提交,会频繁发生冲突
所以开发人员各再拉分支
开发人员要提交到dev之前,小组内先合一下
测试人员一般从 dev 拉代码测试、没问题了之后,运维把 dev 合并到 master
从 master 拉一些 Tag 、 Release 发布小更新 、 feature(加一些新的东西、不想影响原来的东西)
git 5、6成的时间都在解决冲突
大厂还有专门的 CMO :配置管理员:解决各种冲突