Skip to main content
  1. 归档/

OS Lab0 实验报告

·806 words·4 mins·
心得 学习 BUAA 操作系统
Table of Contents

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 将文件分为四种状态:untrackedunmodifiedmodifiedstaged#1 的文件处于 untracked 的状态。#2 的文件在 add 之后变成了 staged 的状态,并在 #3 commit 之后变成了 unmodified 的状态。#4 的文件在修改之后,变成了 modified 的状态。

这是一种有限状态机的设计,用图示可以表示为如下:

stateDiagram [*] --> untracked untracked --> staged : git add file staged --> unmodified : git commit unmodified --> modified : edit file modified --> staged : git add file staged --> modified : edit modified --> unmodified : git checkout -- file staged --> untracked : git rm --cached file modified --> untracked : git rm file

VS Code 的自动暂存
#

平时我们在使用 VS Code 的时候,在一个 Git 仓库中,VS Code 会自动把新建的文件(包括修改的内容)提交到暂存区,自动变成 staged 状态。我们输入提交消息并 commit 之后,文件就变成了 unmodified 的状态。也就是说,VS Code 将这四种状态化简为了两种状态,而自动帮我们处理了其他两种状态与这两种状态之间的转换:

stateDiagram [*] --> staged staged --> unmodified : commit unmodified --> staged : edit

我觉得这非常方便,从多种状态转换的路径中抽离出最常用的两条路径供用户手动处理,而自动处理其他部分的内容,是一种非常自然的设计。

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 如何恢复文件
#

  1. 以往我的理解是从 Git 的三个区域入手的,但现在我发现也许从文件的四个状态去理解恢复机制会更好。
  2. untracked 的文件被误删,因为它和 Git 还没产生关系,就没法通过 Git 恢复了。
  3. 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 状态而已。
  4. 如果我们删除了一个文件,并已经通过 git commit 提交了(这还能算是误删吗?),我们也可以恢复其历史版本(如果删除之前我们提交过的话)。可以使用 git log 查看提交的历史记录,确定某一提交有我们所需的文件后,记住其哈希值的前几位,使用 git restore --source=<hashcode> <file> 来恢复该文件。恢复后,该文件会处于 modified 的状态。

事实上在 Git 中我们很难彻底删除一个文件,即便是我们毁灭了某几条提交记录,兴许还有办法把这几条记录给找回。这种高级操作大概也叫 Git 魔法(

从 Git 的三个区域入手大概也能理清楚 git restore 的逻辑,但我想应该比上面这种方法要复杂。

Git 如何删除文件
#

这个问题从 Git 的三个区域分别讲更好。Git 复杂的原因之一就是有很多个角度去看待它的操作,而且不同操作的最佳视角还不一样。

  1. 从工作区删除一个文件。直接使用系统的 rm 就好。
  2. 从暂存区删除一个文件。我们可以使用 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> 是同义词。更糟糕了。
  3. 从版本库删除一个文件。这件事情就难办了, 但是使用 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 fetchgit 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不会,完完蛋蛋。

tsxb
Author
tsxb
Evaluate. Focus. Moderate.