Hello OS!
Thinking 0.1 Git 的使用 1#
操作流程#
# init
mkdir ~/learnGit && cd ~/learnGit
git init .
# 1
touch README.txt
git status > Untracked.txt
# 2
echo "hello world!" > README.txt
git add .
git status > Staged.txt
# 3
git commit -m "23371263"
cat Untracked.txt
cat Stage.txt
# 4
echo "hello git!" > README.txt
git status > Modified.txt
cat Modified.txt
流程分析#
Git 将文件分为四种状态:untracked,unmodified,modified,staged。#1 的文件处于 untracked 的状态。#2 的文件在 add 之后变成了 staged 的状态,并在 #3 commit 之后变成了 unmodified 的状态。#4 的文件在修改之后,变成了 modified 的状态。
这是一种有限状态机的设计,用图示可以表示为如下:
VS Code 的自动暂存#
平时我们在使用 VS Code 的时候,在一个 Git 仓库中,VS Code 会自动把新建的文件(包括修改的内容)提交到暂存区,自动变成 staged 状态。我们输入提交消息并 commit 之后,文件就变成了 unmodified 的状态。也就是说,VS Code 将这四种状态化简为了两种状态,而自动帮我们处理了其他两种状态与这两种状态之间的转换:
我觉得这非常方便,从多种状态转换的路径中抽离出最常用的两条路径供用户手动处理,而自动处理其他部分的内容,是一种非常自然的设计。
IDEA 的文件状态转换#
反观 IntelliJ IDEA,没有像 VS Code 这样流畅的设计,它的文件状态的转换逻辑和 Git 的设计相同,没有进行简化。所以我们在 IDEA 里新建一个文件,默认是 untracked 状态的。需要在版本管理页面手动为文件打勾,表示将其暂存,才能提交这些文件。多一种操作当然多一些自由度,但是也增加了操作的复杂性。
这样的文件状态转换的方式,与 .gitignore 文件相结合时,也会遇到一些问题。.gitignore 忽略的是 untracked 状态的文件。如果你打开了 IDEA 的自动暂存功能,它就真的把新建的文件提交到暂存区,然后不管不顾了——也就是说,如果你在将某一文件(或者文件类型)加入.gitignore 之前就创建了这一文件(或者这一类型的文件),那么这个文件仍然会被 Git 追踪。VS Code 对待的方式则不同——如果你在创建之后把文件加入了 .gitignore,那么即使它被暂存了,它依然会被 VS Code 认为是需要忽略的文件,于是它会被清出暂存区,并设置成被忽略的状态。只有被提交过的文件,VS Code 才不会去将它忽略。
总的来说,虽然 IDEA 的逻辑设计与 Git 如出一辙,但我并不认为这是一个好的设计——也许对于 Git 这样的命令行工具来说这种完备性是需要的,但作为一个现代的、智能的 IDE,VS Code(姑且认为它是个 IDE)这种更简洁的方式更加符合直觉。
Thinking 0.2 箭头与命令#
- “add the file”: 
git add - “stage the file”: 
git add - “commit”: 
git commit 
git add 命令对应的操作是两种:一种是将 untracked 的文件放入暂存区,一种是将 modified 的文件放入暂存区。其实本质上,都是将文件的变化放入暂存区中以记录。
Thinking 0.3 Git 的一些场景#
Git 如何恢复文件#
- 以往我的理解是从 Git 的三个区域入手的,但现在我发现也许从文件的四个状态去理解恢复机制会更好。
 untracked的文件被误删,因为它和 Git 还没产生关系,就没法通过 Git 恢复了。unmodified的文件被误删,此时它的状态就变成了modified。我们可以通过git restore <file>来将其恢复到unmodified的状态。modified的文件被误删,状态还是modified。我们可以把删除也理解为一种修改,而这次的修改(即删除)与上次的修改合并了。所以无论如何,我们也只能通过git restore <file>把它恢复到unmodified的状态。- 另一种理解是,我们上一次的修改尚未被 Git 记录,所以没法通过 Git 恢复上一次修改的部分。
 
staged的文件被误删。此时文件的状态变为modified。此时我们也可以通过git restore <file>的方法将其恢复到staged的状态。- 注意这里是恢复到 
staged状态而不是更早的unmodified的状态。如果我们想要(将一个staged状态下的文件)恢复到更早的unmodified的状态,我们首先需要通过git restore --staged <file>来将staged状态的文件恢复到modified的状态,再通过git restore <file>来把它恢复到先前的unmodified的状态。 
- 注意这里是恢复到 
 - 如果 
staged的文件被误删,此时又git commit提交了暂存区。已经staged的“文件”变成了unmodified的状态,但由于删除的操作并未暂存,所以该文件仍处于modified的状态。我们还是可以通过git restore <file>来把它恢复到unmodified的状态。 - 可以看到,
git restore <file>可以将一个处于modified的状态的文件恢复到其上一个状态(unmodified或者staged),而git restore --staged <file>可以将一个处于staged的状态的文件恢复到其上一个状态(modified或者untracked)。所谓的误删操作,在 Git 看来就是将文件从其当前状态变为了modified状态而已。 
- 如果我们删除了一个文件,并已经通过 
git commit提交了(这还能算是误删吗?),我们也可以恢复其历史版本(如果删除之前我们提交过的话)。可以使用git log查看提交的历史记录,确定某一提交有我们所需的文件后,记住其哈希值的前几位,使用git restore --source=<hashcode> <file>来恢复该文件。恢复后,该文件会处于modified的状态。 
事实上在 Git 中我们很难彻底删除一个文件,即便是我们毁灭了某几条提交记录,兴许还有办法把这几条记录给找回。这种高级操作大概也叫 Git 魔法(
从 Git 的三个区域入手大概也能理清楚 git restore 的逻辑,但我想应该比上面这种方法要复杂。
Git 如何删除文件#
这个问题从 Git 的三个区域分别讲更好。Git 复杂的原因之一就是有很多个角度去看待它的操作,而且不同操作的最佳视角还不一样。
- 从工作区删除一个文件。直接使用系统的 
rm就好。 - 从暂存区删除一个文件。我们可以使用 
git rm <file>命令。这个命令就是 Git 版rm,它会同时将工作区和暂存区的这个文件全部删除——前提是这两份版本相同。也就是说,如果你暂存了这个文件之后又修改了它,使用这个命令时 Git 就会发出警告并终止操作。要想强制执行,加-f即可。- 如果想保留工作区的文件,而删除暂存区的文件,可以使用 
git rm --cached <file>。也就是说,git add <file>的反义词其实是git rm --cached <file>——非常糟糕,对吧?- 如果真要这么说,
git rm --cached <file>和git restore --staged <file>是同义词。更糟糕了。 
 - 如果真要这么说,
 
 - 如果想保留工作区的文件,而删除暂存区的文件,可以使用 
 - 从版本库删除一个文件。这件事情就难办了, 但是使用 Git 的新手又经常遇到这样的问题——经常不检查即将 
commit的代码,而把自己的个人信息或者隐私给提交了。我之前就遇到过这样的问题。下面是我的解决方法,当然可能清除得不彻底。- 使用 pip 安装 git-filter-repo
 - 确定泄露信息的格式,比如学号"2337xxxx",则可以使用 
git filter-repo --replace-text <(echo "2337xxxx==>")将所有历史提交中的"2337xxxx"替换为空串。如果要删除某个文件,可以使用git filter-repo --path file --invert-paths上述的方法也许不够灵活,下面再介绍一种方法: - 如果能够确定是最近几次提交中泄露的信息,可以使用 
git rebase -i HEAD~<x>来修改最近 x 次提交。 - 终端会打开文本编辑器,显示这几次提交的 hash 信息和 commit 消息,将需要修改的提交前的 
pick改为edit,然后保存退出。 - 此时工作区会恢复到第一条转为 
edit的提交被提交之前的状态,可以在此时清除信息。 - 使用 
git commit --amend修改提交消息。 - 使用 
git rebase --continue继续修改下一条提交。 -这些操作之后,由于你修改了历史提交记录,推送到远程仓库时必须使用git push --force来强制覆盖远程仓库的记录。注意 OS 和 OO 的 gitlab 是不支持强制推送的,只有在你自己的 Github 仓库中你才有权利这么做,而如果有别的协作者,他们需要使用git fetch和git reset --hard origin/main来避免与远程仓库的冲突。 
 
Thinking 0.4 Git 的使用 2#
操作流程#
cd ~/learnGit
#1
touch README.txt
echo "Testing 1" > README.txt
git add .
git commit -m "1"
# repeat with commit message "2" and "3"
git log
#2
git reset --hard HEAD^
git log
#3
git reset --hard <HASHCODE_1> # hashcode of commit with message "1"
git log
#4
git reset --hard <HASHCODE_3> # hashcode of commit with message "3"
分析#
使用 git reset --hard 命令可以进行版本回退或者切换到任何一个版本。
- 使用 
HEAD^切换到上一个版本 - 使用 
HEAD~<x>切换到前第 x 个版本 - 使用 
hash值切换到任意版本 
这里可以看到,我们在 #2 中回退到上一个版本,此时执行 git log 会找不到提交消息为"3"的提交记录。如果不是事先记录下它的 hash 值,我们可能无法找到这条记录了。
事实上并非找不到。可以使用 git reflog 来查看 HEAD 指针移动的历史记录。刚才我们 commit 了三次,随后 reset 一次,这四次指针移动的情况被 git reflog 完整的记录下来,我们就可以通过前三次记录找到提交消息为"3"的提交的哈希值。
Thinking 0.5 echo 的使用#
操作流程#
echo first
# first
echo second > output.txt
# output.txt: second
echo third > output.txt
# output.txt: third
echo forth >> output.txt
# output.txt:
# third
# forth
分析#
> 用于将命令的输出覆盖到文件中,>> 用于将命令的输出追加到文件中。
Thinking 0.6 文件的操作#
操作流程#
# command
#!/bin/bash
touch test
echo '#!/bin/bash' >> test
echo 'echo Shell Start...' >> test
echo 'echo set a = 1' >> test
echo 'a=1' >> test
echo 'echo set b = 2' >> test
echo 'b=2' >> test
echo 'echo set c = a+b' >> test
echo 'c=$[$a+$b]' >> test
echo 'echo c = $c' >> test
echo 'echo save c to ./file1' >> test
echo 'echo $c>file1' >> test
echo 'echo save b to ./file2' >> test
echo 'echo $b>file2' >> test
echo 'echo save a to ./file3' >> test
echo 'echo $c>file3' >> test
echo 'echo save file1 file2 file3 to file4' >> test
echo 'cat file1>file4' >> test
echo 'cat file2>>file4' >> test
echo 'cat file3>>file4' >> test
echo 'echo save file4 to ./result' >> test
echo 'cat file4>>result' >> test
bash command > test
bash test > result
分析#
- 单引号用来引用完全的“字面值”的字符串。所有在单引号中的内容都会被原样保留,不会进行替换或者转义。
 - 双引号用来引用字符串,但允许变量和命令的替换或转义。
 - 反引号用于命令替换,即先执行反引号包含的命令,再将该命令的输出替换到当前位置。等价于
$()。 
上机#
题量很大,勉强做完。知道思路,不懂实现。bash不会,完完蛋蛋。
