Linus 大佬团队俩星期写出来的 Git

# 开源发展史

提到 Linus,就不得不提到这段历史,这部分了解的时候真的让人激动,可以说,Linux 的发展,极大推动(开创)了开源。

强烈推荐这个视频:你不知道的「开源」60 年秘史

视频讲得非常清楚,所以我这里就简单的总结一点点:

  • 贝尔实验室的 Ken ThompsonDennis 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 目录,仓库目录就是工作目录。

  • 远程仓库:位于服务器上的版本控制仓库(服务器上的版本信息可以由本地仓库推送上去,也可以从服务器抓取到本地仓库)

# 创建仓库

githubgitee 都可以,看自己能力创建捏,这里建议仓库私有,你拿来练手 git 的仓库就没必要公开了。最近做课设,团队作业,我在 Gitee 上还搜到了好几个认识的同学的仓库,还有几个老哥是实名上网,笑😥。

创建好后,拿到仓库地址:https://gitee.com/xxx/teach-learning.git (这个仓库我之后会删了。。),本地随便创建一个文件夹 Teach-learning 。进入文件夹,右键 git bash here 初始化仓库 git init

你应该还看到了有一个 Git GUI HereGUI 就是图形化, 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,也是进入 rebasepick 改为 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 一下,然后在合并,就可以了。

# 远程仓库

之前我们的操作都是在本地操作的,最后我们可以将一个分支的结果上传到远程仓库:

  1. 先添加本地仓库: git remote add 仓库名 远程仓库地址
  2. 如果你是空文件夹,是想单纯拉取那些开源项目: git pull 仓库名 远程分支
  3. 然后自己修改代码后,就可以将本地的文件传过去了: 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 遇到的,玄学。