Linus 大佬团队俩星期写出来的 Git
# 开源发展史
提到 Linus,就不得不提到这段历史,这部分了解的时候真的让人激动,可以说,Linux 的发展,极大推动(开创)了开源。
强烈推荐这个视频:你不知道的「开源」60 年秘史。
视频讲得非常清楚,所以我这里就简单的总结一点点:
- 贝尔实验室的 Ken Thompson 和 Dennis Ritchie 开发了 UNICS,后来被神秘地改成了 UNIX。
- Unix 源码分享出去,K 神在休假时还和加州大学伯克利分校的研究小组共同改进 Unix。
- 研究小组的 Bill Joy 开发 BSD,成为 Unix 最大分支。毕业后创建 Sun 公司,进而创建 SunOs 系统(Unix 逐步商业化)。
- AT&T 注册 Unix 商标,开启了商业化 Unix 之路,Unix 第 7 版开始,禁止大学使用 Unix 源代码。
- 阿姆斯特丹自由大学计算机科学系教授 Andrew Tanenbaum 为了在课上更好演示操作系统细节,自己开发了 Minix,即【迷你 Unix】,并作为《操作系统设计与实现》艺术参考案例。
- Richard Stallman 作为 MIT 人工智能实验室研究员,推崇黑客文化,1984 年辞去 MIT 工作,随后开发类 Unix 系统 ——GNU(全称为:GNU IS NOT UNIX),次年成立自由软件基金会。
- 《操作系统设计与实现》这本书为 Linux 之父 ——Linus Torvalds 推向人生巅峰,之后开发出 Linux。
开源宇宙中子星,商业软件面壁人的 Linus・你的上帝・Torvalds。美国时代周刊评价:“有些人生来就具有统率百万人的领袖风范;另一些人则是为写出颠覆世界的软件而生。唯一一个能同时做到这两者的人,就是 Linus Torvalds”。Linus 自己评价自己:“My name is Linus , and I am your God.”
- Linux 代码完全开源,早年参与开发的黑客水平较高,直到今天,Linux 社区内核的开发才被程序员们认为是【真正的编程】。
# 基本使用
安装这么简单的事就不讲了。一些介绍就是:分布式版本控制系统,远程仓库 巴拉巴拉的。
# 基本模块
这部分一定要仔细,git 的命令与不同模块息息相关。
工作目录:存放我们正在写的代码(当我们新版本开发完成之后,就可以进行新版本的提交)
暂存区:暂时保存待提交的内容(新版本提交后会存放到本地仓库)
本地仓库:位于我们电脑上的一个版本控制仓库(存放的就是当前项目各个版本代码的增删信息),可以是随便一个文件夹作为本地仓库,
git init
生成.git
目录,仓库目录就是工作目录。远程仓库:位于服务器上的版本控制仓库(服务器上的版本信息可以由本地仓库推送上去,也可以从服务器抓取到本地仓库)
# 创建仓库
github
和gitee
都可以,看自己能力创建捏,这里建议仓库私有,你拿来练手git
的仓库就没必要公开了。最近做课设,团队作业,我在Gitee
上还搜到了好几个认识的同学的仓库,还有几个老哥是实名上网,笑😥。
创建好后,拿到仓库地址:https://gitee.com/xxx/teach-learning.git (这个仓库我之后会删了。。),本地随便创建一个文件夹 Teach-learning
。进入文件夹,右键 git bash here
初始化仓库 git init
。
你应该还看到了有一个 Git GUI Here
, GUI
就是图形化, Bash
是命令行。
如果从来没有配置过用户名和邮箱,先配置一下:
git config --global user.name "Your Name" | |
git config --global user.email "email@example.com" |
现在远程仓库和本地仓库都创建好了,但是先不需要管远程仓库,我们这里啥都没配置,本地仓库也不知道远程仓库地址。其实,你如果觉得 git
提供的 UI 不好看,也可以使用 cmd 终端执行 git
命令。
# 本地操作
现在的都做都是在和本地仓库进行交互,与远程仓库没有关系。
# 添加与提交
我们先看一下工作目录的文件与本地仓库:
执行 git status
查看本地仓库状态,结果是:No commits yet。表示我们还没有向仓库提交任何内容。
创建一个文本文件 test.txt
,随便写内容: hello world
,再查看状态:
On branch master | |
No commits yet | |
Untracked files: | |
(use "git add <file>..." to include in what will be committed) | |
test.txt | |
nothing added to commit but untracked files present (use "git add" to track) |
Untracked files 是未追踪文件的意思, git
不会记录它的变化,始终将其当做一个新创建的文件。加入到暂存区,该文件就会自动变为追踪状态:
git add test.txt #也可以 add . 一次性添加目录下所有的 |
再次 git status
发现文件颜色变成绿色了。其实在 IDEA 里面也是这样,项目中文件未追踪,颜色是红色的(前提是项目已经使用了 Git):
1.txt
是我新建的,此时是红色的,然后 IDEA 提醒我叫我添加该文件到 Git,也就是将文件交给 Git 管理。选择 add 后就变绿色了。
此时文件就在暂存区了,使用 commit
命令就可以提交到本地仓库中了:
git commit -m '提交test.txt文件' # -m 就是添加提交消息,meassage | |
# 提交后暂存区就没东西了,之后要提交,就必须先 commit | |
# -a 选项可以将没有加入到暂存区的文件放入暂存区再提交 |
现在先不演示在 IDEA 中的样子,先把基本学明白。
查看提交记录:
C:\Users\Cyan\Desktop\Teach-learning>git log | |
commit ed549112c66212a24d659583ac2fe472eec9b378 (HEAD -> master) | |
Author: cyan <我的邮箱> | |
Date: Tue Nov 1 17:15:28 2022 +0800 | |
'提交test.txt文件' |
第一行是提交的 commitID
,此时再查看状态,就会提示工作目录没有需要提交的。
git log --all --graph --oneline
挺好用的
尝试修改一下 test.txt
(删除 hello world 吧),由于当前文件已经是被追踪状态,那么 git 会去跟踪它的变化,如果说文件发生了修改,那么我们再次查看状态会得到下面的结果:
C:\Users\Cyan\Desktop\Teach-learning>git status | |
On branch master | |
Changes not staged for commit: | |
(use "git add <file>..." to update what will be committed) | |
(use "git restore <file>..." to discard changes in working directory) | |
modified: test.txt # 红色的 | |
no changes added to commit (use "git add" and/or "git commit -a") |
此时修改的文件在工作目录,需要再次提交到暂存区( git add .
),然后提交 git commit
。
可以创建一个 .gitignore
文件来确定一个文件忽略列表,如果忽略列表中的文件存在且不是被追踪状态,那么 git 不会对其进行任何检查:
# 这样就会匹配所有以 txt 结尾的文件 | |
*.txt | |
# 虽然上面排除了所有 txt 结尾的文件,但是这个不排除 | |
!666.txt | |
# 也可以直接指定一个文件夹,文件夹下的所有文件将全部忽略 | |
test/ | |
# 目录中所有以 txt 结尾的文件,但不包括子目录 | |
xxx/*.txt | |
# 目录中所有以 txt 结尾的文件,包括子目录 | |
xxx/**/*.txt |
修改当前 commit 的注解:
# vim 修改第一行即可 | |
git commit --amend |
修改之前的注解:n 标识最近 n 次 commit
。
git rebase -i HEAD~n |
自动进入 vim
之后,选择需要修改的 commit
,将首部的 pick
改为 edit
。
pick 0583cc1 提交注释1 | |
edit 4ddae1a 提交注释2 # 需要修改 |
然后保存退出,再次执行 git commit --amend
修改即可。
想要删除某一次 commit,也是进入 rebase
将 pick
改为 drop
即可。
删除非常容易出现冲突,注意解决
# 回滚
每提交一次,就是一个版本,通过 commitID
就可以回滚到指定版本。(ID 通过 git log
查看)
git reset --hard commitID |
执行后,会直接重置为那个时候的状态。再次查看提交日志,我们发现之后的日志全部消失了。
要是现在我又想回去呢?我们可以通过查看所有分支的所有操作记录:
git reflog | |
# 就能找到之前的 commitID,再次重置即可 |
# 分支
为什么要有分支:
- 基于当前项目的基础功能,我们想做成不同方向的需求,开发不同方向的功能,就需要分支。
- 像一些开源项目就有很多分支,一方面是各个分支功能实现略有不同,另一方面也方便不同人开发,也就是一部分人开发某个分支,另一部分人开发另一个分支啥的。
像有些项目一条路走到黑的,就没必要使用分支;如果是类似前后端分离,MVVM 之类的,就可以开多个分支。
我的看法就是有需求就开分支,不要吃饱了撑的。
查看当前分支: git branch
。默认分支是 master
,以前 github
的默认分支我记得是 main
,后来就改了。 master
一般是正式版本更新。
相关命令:
# 基于当前分支创建一个新的分支 | |
git branch test | |
# 删除分支 | |
git branch -d test | |
# 切换分支 | |
git checkout test | |
# 切出一个新分支 | |
git checkout -b newBranch |
不同分支的文件内容是相互隔离的
# 分支合并
这部分是重点难点,
两个分支想要合并,就不能存在冲突。假设有一个文件,在 A 分支修改提交了,在 B 分支也修改提交了,A 和 B 分支合并时,该文件哪个版本是最新的呢?哪个该被覆盖呢?
我们重新建一个本地仓库演示一下:
目前是,有 A.txt 和 B.txt 两个文件,A,B 两个分支,两个文件写入内容后提交到 master 分支上。
我们合并 AB 两个分支来演示分支合并:
- 切入到 A 分支,对 A.txt 文件新增一行(就是修改):A branch add a row,
commit
提交。 - 切入到 B 分支,此时查看 A.txt 文件,并没有 A 分支修改的内容(分支之间相互隔离)。
- 在 B 分支,对 B.txt 文件新增一行:B branch add a row,
commit
提交。 - 再回到 A 分支,使用
git log --all --graph --oneline
查看一下。
分支情况一目了然。
- 在 A 分支查看 A.txt,B.txt 两个分支的内容,并没有 B 分支修改的内容。
- 将 B 分支合并到 A 分支:
git merge B
,再看一下log
。
检查 B.txt 内容,合并成功。
分支合并的时候,A,B 分支的文件修改上没有冲突 ——A 分支 A.txt 修改了,而 B 分支的 A.txt 没有修改,所以 A.txt 合并可以成功(B.txt 同理),如果 A,B 分支都对 A 文件进行修改,就有可能发生冲突。
# 分支冲突
现在我们再来演示一下分支冲突:
- A 分支对 A.txt 再新增一行:A branch add a row again,提交。
- 切换到 B 分支,同样在 A.txt 新增一行:B branch add a row again,提交。
现在两个分支在 A.txt 的修改上发生冲突,回到 A 分支,执行 git merge B
,就会出现冲突提示:
我们调用 git diff
查看一下(必须合并失败之后才有用):
解决办法就是,在当前分支下,要么回到没有冲突的版本,要么在 A.txt 文件中选定冲突的部分:
- A 分支下,回滚到修改 A.txt 之前的版本。
- 或者是,你现在去看一下 A.txt 的内容,里面就会加入
<<HEAD
之类的东西,你可以手动选择需要那部分,或者两部分都要(其实你可以随便改),然后再 commit 一下,然后在合并,就可以了。
# 远程仓库
之前我们的操作都是在本地操作的,最后我们可以将一个分支的结果上传到远程仓库:
- 先添加本地仓库:
git remote add 仓库名 远程仓库地址
。 - 如果你是空文件夹,是想单纯拉取那些开源项目:
git pull 仓库名 远程分支
。 - 然后自己修改代码后,就可以将本地的文件传过去了:
git push 远程仓库名称 本地分支名称[:远端分支名称]
。git clone
也可以
第一次 push 一般需要用户名和密码。每次都需要指定分支映射,我们可以绑定分支: git push --set-upstream 仓库名 本地分支:远程仓库
。
如果只是一个人玩 git
,那么以上命令没有任何问题,可是如果是多人开发,我们假设一个场景:
AB 两人开发项目,他们在写代码时,有自己的本地仓库,然后有一天他俩都克隆了同一远程仓库,A1h 后将自己的代码交到远程仓库,再过 1h 后,B 执行 push 的话就会出错: error: failed to push some refs to 'https://github.com/xx/xxx.git' hint: Updates were rejected because the remote contains work that you do
。
因为如果让他 push 上去,就会覆盖 A 的代码, git push --force
是强制 push,那么就会真的覆盖 A 的代码。
一旦一个本地仓库推送了代码,那么另一个本地仓库的推送会被拒绝,原因是当前文件已经被其他的推送给修改了,我们这边相当于是另一个版本,和之前两个分支合并一样,产生了冲突,因此我们只能去解决冲突问题。
一般就是通过 fetch 拉取下来,但是没有合并,然后自己再合并(其实就是 pull),至于他人写的会不会覆盖你的代码,如果没有修改同一个文件就不会,然后合并就会冲突。
一些问题:
git push
提示你需要先git pull
,但是拉取后把你本地的代码全部覆盖了解决:首先要回滚到未覆盖版本,先git reflog
查看之前提交的版本号,再git reset --hard 版本号
。如果你是自己开发或者远程仓库的代码可以覆盖,建议直接git push -f
强制推送,其他办法我也还没找到,因为这个情况我是使用 VS 遇到的,玄学。