【超干货】CS61B 2021sp全攻略(上)|一亩三分地公开课版 (1point3acres.com)的建议:

我做proj2花了大概10天代码大概一共800行推荐到springbreak的时候做这个项目写之前要把lab6复习一遍再把Lecture12看完然后推荐先把spec读一遍再看intro视频磨刀不误砍柴这个时候终于对要写几个对象、分别实现什么功能有点概念了然后再读一遍spec对着指令一条一条实现缺少一个整体设计概念的话不建议着急写代码否则你debug40多条指令的时间会远远高于这些思考的时间我感觉merge是最复杂的可以先看助教的视频他给你总结好了7种情况分类讨论下来有点心累但提交autograde看到1600满分的时候爽爆了

Lec12:

Lec12

命令行的介绍

image-20240323170733016

版本控制

Approach Number Information to use as file version number Downside
1, 2, and 3 Commit ID (that goes up by 1) that includes the file. No central server to decide which commit is “next” if people are working offline.
4 Date and time of file. Awkward to deal with simultaneous file changes. Not as elegant as SHA1-hash.
5 git-SHA1 hash of file. ???

Approach 1-3

image-20240323170924189

Approach 4

image-20240323170844113

Approach 5

image-20240323170954784

image-20240323171539681

commit后会出现3个文件夹:

  • 其中一个是以hash码前两位为名称,hash码文件的
    • image-20240323171721402
  • 其中一个是存储了commit消息的
    • image-20240323171749576
  • 另一个是读取文件的
    • image-20240323171712871

序列化和储存

image-20240323171447179

image-20240323171431511

分支合并

image-20240323171235620

image-20240323171136900

image-20240323171215906

这里没看懂…

Lab6

CS61B项目笔记(四)-Lab6-文件序列化 | bai的小窝 (bailog.top)

Capers 设计文档示例 |CS 61B 2021 年春季 — Capers Design Document Example | CS 61B Spring 2021 (datastructur.es)

Git Intro

视频主要介绍了git initgit add xgit commitgit checkout这四个命令

git init

image-20240324005322743

创造两个区域,一个存储区域和确认区域

git add

image-20240324005415911

image-20240324005427015

git commit

image-20240324005506920

image-20240324005518632

清空储存区,提交确认

git checkout

image-20240324005603569

image-20240324005613533

image-20240324005630537

image-20240324005637210

checkout的时候要保证status树是干净的

Gitlet Intro

Gitlet设计文档

类以及数据结构

Commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private String message;
private List<String> parentID = new ArrayList<>();
//merge后可能有两个父提交
private String timestamp;
private String shaName;
public HashMap<String, String> version;
//存储文件名以及对应的Blob的Sha1
public void updateVersion()
//更新文件版本
public String toString()
//输出log格式
public String getFlieVersion(String name)
//获得当前版本文件的Sha1
public void copyMapTo(Commit c)
//将其版本复制给c

CommitTree

1
2
3
4
5
6
7
8
9
10
11
12
public static Commit HEAD;
public static Commit Master;
public static String curBranchName;
static File indexFold = StagingArea.indexFold;//object的目录
static File refs = Utils.join(Repository.GITLET_DIR,"refs"); //folder
static File heads = Utils.join(refs, "heads"); //folder
static File master = Utils.join(heads, "master"); // file
static File head = Utils.join(Repository.GITLET_DIR, "HEAD"); //file
static File CURBranch = Utils.join(Repository.GITLET_DIR, "curBranch");//file
static File commitList = Utils.join(refs, "cList");
static List<String> cList = new ArrayList<>();
static StringBuilder logSB = new StringBuilder();

Blob

1
2
3
4
String fileName;
String sha1ID;
File file; //代表的文件
byte[] aByte; //存储文件内容

StagingArea

1
2
3
4
5
6
private static final File CWD = Repository.CWD;
public static File indexFold = Utils.join(Repository.GITLET_DIR, "object");//folder
public static HashMap<String, String> additionStage = new HashMap<>();
public static HashMap<String, String> removalStage = new HashMap<>();
public static File additionStageFile = Utils.join(Repository.GITLET_DIR, "additionStage");//file
public static File removalStageFile = Utils.join(Repository.GITLET_DIR, "removalStage"); //file

算法

对于复杂的任务,比如确定合并冲突,我们建议您将任务分解成几个部分。在单独的部分中描述每个部分的算法。从最简单的组件开始,一次构建设计的每一部分。例如,您的合并冲突的算法部分可以有以下部分:

  1. 检查是否需要合并。
  2. 确定哪些文件(如果有)存在冲突。
  3. 在文件中表示冲突。 尽量清晰地使用空格或其他符号标记类的标题或名称。

Gitlet编写思路

前序工作

可以把文件类比成Lab6里的Dog

整体逻辑框架

image-20240324234728616

image-20240324233547054

init做了什么?

init创建了.gitlet文件,我们可以参考下git的:

image-20240324233814127

image-20240324234219622

image-20240324234329272

如图所示:

  • HEAD指针指向的是一个文件,文件里存储了当前指向的文件码
  • objects文件夹存储的应该是blobs里的文件,而他使用的是Hash表

image-20240325171441116

整理下全部过程

首先add的时候文件序列化后存储在了我的index文件夹,然后commit的时候,commit节点是包含着Blobs节点的。这里就有个需要解决的地方:

  • commit如何指向?
    • 初步想法是commit的时候,在commitTree添加节点,然后每个节点有个列表,列表里包含了文件的sha1值
  • 如何通过sha1值找到指向的值?
    • 我的StagingArea应该会有一个方法,通过指定的sha1值找到其文件。

image-20240327215936968

Blob

Blob 是 Git 中用来存储文件内容的基本单位。它可以是文本文件、图像、音频或任何其他类型的文件,Git 对它们都一视同仁。每个 Blob 对象都会被分配一个唯一的 SHA-1 哈希值,该哈希值是根据 Blob 对象的内容计算得出的。

当你向 Git 添加文件时,Git 会将文件内容存储为 Blob 对象。在 Git 内部,Blob 对象被保存在 .git/objects 目录下,并以其哈希值命名的文件中。这样,Git 就能够非常高效地存储和检索文件内容。

SHA1值前两位为文件名,后38位为文件名

add

  • 首先找到CWD中的文件
  • 将文件存为Blob
  • 将Blob存入Hash表[SHA1-Blob]
  • 将Blob序列化存入.index文件中
  • 将文件对应的Blob存入additon中

commit

  • 根据传入的message new 一个 commit
  • commit的parent来自HEAD
  • 复制文件表中的Blob
  • 序列化?

rm

  • 如果文件已经被add, 那么从add区删除
  • 如果文件已经被commit, 则标记为删除
  • 从工作列表中删除文件

checkout

  • status得干净

做题思路

重要的一点

为了保证程序的持续性,我们执行命令前需要读取被存储的文件,如HEADAdditonStage

init

  • 描述:在当前目录中创建一个新的 Gitlet 版本控制系统。该系统将自动开始一个提交:一个不包含文件且具有提交消息 initial commit(就是这样,没有标点符号)。它将有一个单一分支:master,最初指向此初始提交,并且master将是当前分支。此初始提交的时间戳将为 00:00:00 UTC,Thursday, 1 January 1970,以您选择的日期格式(这被称为“Unix 纪元”,内部由时间 0 表示)。由于由 Gitlet 创建的所有存储库中的初始提交都具有完全相同的内容,因此所有存储库将自动共享此提交(它们将具有相同的 UID),并且所有存储库中的所有提交都将追溯到它。
  • 失败情况:如果当前目录中已经存在 Gitlet 版本控制系统,则应中止。不应使用新系统覆盖现有系统。应打印错误消息 A Gitlet version-control system already exists in the current directory.

明确一下我们要做什么:

  • 首先创建一个.gitlit的文件夹
  • 然后自动提交一个初始commit
  • 设置masterHEAD指针
  • 初始化StagingArea目录
  • 初始化CommitTree

初始化后目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
.gitlet
|--object //Folder
| |---存储commit和blob
|
|--refs //Folder
| |---heads //Folder
| | |--存储branch指针
| |--cList //List类, 存储commit的列表
|
|---additionStage //HashMap<String, String>, 存储添加的blob
|---removalStage //HashMap<String, String>, 存储删除的blob
|---HEAD //Commit类,存储当前分支的头指针
|---curBranch //String类,存储当前分支的名字

add

  • 描述:将文件的当前副本添加到暂存区(参见 commit 命令的描述)。因此,添加文件也称为为添加而暂存文件。将已经暂存的文件暂存会使用新内容覆盖暂存区中的先前条目。暂存区应该位于.gitlet的某个地方。如果文件的当前工作版本与当前提交中的版本相同,则不要将其暂存以添加,并且如果已经存在(当文件更改、添加,然后更改回其原始版本时可能会发生)的情况下,从暂存区删除它。文件将不再被暂存以删除(请参见 gitlet rm),如果在命令时处于该状态。

  • 失败情况:如果文件不存在,则打印错误消息 File does not exist. 并且退出而不更改任何内容。

  • 思路

    • 首先读取addtionStageremovalStageHEAD

    • 找到需要add的文件

    • 为该文件创建blob快照

    • 将blob存储到object文件夹

    • 上级文件夹名为Sha1的前两位,文件名为Sha1的后38位

    • 特殊情况判断

    • 将该blob添加到addtionStage

特殊情况判断:

  • 如果removalStage中有该版本,清空rS,不添加到aS
  • 如果Head指向的commit已经包含了该版本,忽略这次提交

commit

  • 描述: 在当前提交和暂存区保存已跟踪文件的快照,以便稍后可以恢复,创建一个新的提交。该提交被称为跟踪保存的文件。默认情况下,每个提交的文件快照将与其父提交的文件快照完全相同;它将保留文件的版本完全不变,不会更新它们。提交仅会更新在提交时已标记为要添加的文件的内容,在这种情况下,提交现在将包含已标记为要添加的文件的版本,而不是从其父提交中获取的版本。提交将保存并开始跟踪任何已标记为要添加但父提交未跟踪的文件。最后,由于被rm命令(下面)标记为要删除,当前提交中跟踪的文件可能会在新提交中被取消跟踪。

  • 失败情况: 如果没有文件被暂存,则中止。打印消息No changes added to the commit.。每个提交必须有一个非空消息。如果没有,则打印错误消息Please enter a commit message.。对于已跟踪文件在工作目录中缺失或更改的情况算是失败。完全忽略.gitlet目录外的所有内容。

  • 思路:分两种情况,第一种是init时提交的初始commit,另一种就是正常commit的

  • 读取HEADcurBranchrSaSCList

    • 首先新建commit
    • 将该commit提交到commitTree:
      • 检查是否为初始提交,若是:

        • 将该commit直接写入HEAD,curBranch(master)指针中去
        • 存储到object文件夹中,存储方式同blobs
        • 将该commit的ShaName添加到CList列表中保存文件CList
      • 若非:

        • 将该commit的Parent设置为HEAD指向的commit的ShaName
        • 拷贝父提交的version哈希表
        • 更新version哈希表
        • 清空所有Stage
        • 将该commit写入HEAD,curBranch指针中去
        • 存储到object文件夹中,存储方式同blobs
        • 将该commit的ShaName添加到CList列表中并保存文件Clist

记住,清空Stage的时候需要保存两个Stage

rm

  • 描述: 如果文件当前已暂存以添加,则取消暂存该文件。如果文件在当前提交中被跟踪,则将其标记为删除,并从工作目录中删除该文件(除非用户已经这样做,否则不要删除它)。

  • 失败情况: 如果文件既没有被暂存也没有被当前提交跟踪,则打印错误消息No reason to remove the file.

  • 思路

    • 首先读取需要用到的

    • 如果add了,那就取消add

    • 如果当前提交有追踪当前文件,将该文件添加到rA并删除CWD中该文件。

log

  • 描述: 从当前头提交开始,沿着提交树向后显示每个提交的信息,直到初始提交,跟随第一个父提交链接,忽略合并提交中找到的任何第二父提交(在常规Git中,这就是使用git log --first-parent得到的)。这组提交节点称为提交的历史记录。对于历史记录中的每个节点,应显示的信息是提交ID、提交时间和提交消息。以下是应遵循的确切格式示例:

  • 思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
===
commit a0da1ea5a15ab613bf9961fd86f010cf74c7ee48
Date: Thu Nov 9 20:00:05 2017 -0800
A commit message.

===
commit 3e8bf1d794ca2e9ef8a4007275acf3751c7170ff
Date: Thu Nov 9 17:01:33 2017 -0800
Another commit message.

===
commit e881c9575d180a215d1a636545b8fd9abfb1d2bb
Date: Wed Dec 31 16:00:00 1969 -0800
initial commit
  • 读取需要的东西
  • 根据实例,为commit覆写toString方法
  • 利用StringBulider来存储信息
  • 利用递归来添加commit的信息

global-log

  • 描述: 类似于日志,但显示有关已经进行的所有提交的信息。提交的顺序并不重要。提示:在gitlet.Utils中有一个有用的方法,可以帮助您遍历目录中的文件。

  • 思路

    • 读取需要的东西

    • 也是用StringBuilder来存储信息

    • 重要的一点是获得所有Commit的列表,我们可以直接读取我们的CList文件来获取

另一个想法: 可以创建一个log文件,每次addCommit的时候就append

find

  • 描述: 打印出具有给定提交消息的所有提交的ID,每行一个。如果有多个这样的提交,它会将ID分别打印在不同的行上。提交消息是一个单独的操作数;要指示多个单词的消息,请将操作数放在引号中,如下所示的commit命令。提示:此命令的提示与global-log的提示相同。
  • 失败情况: 如果不存在这样的提交,则打印错误消息Found no commit with that message.

类似golbal-log,复用其代码即可

status

  • 描述: 显示当前存在的分支,并用*标记当前分支。还显示哪些文件已经被标记为添加或删除。它应该遵循的确切格式示例如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    PLAINTEXT

    === 分支 ===
    *master
    other-branch

    === 暂存的文件 ===
    wug.txt
    wug2.txt

    === 已删除的文件 ===
    goodbye.txt

    === 未暂存的修改 ===
    junk.txt (deleted)
    wug3.txt (modified)

    === 未跟踪的文件 ===
    random.stuff

  • 思路

最后两个我没有实现

  • 这个重点考察了持续性的使用,只要正确读取就不难

checkout

检出是一种通用命令,可以根据其参数执行几种不同的操作。下面有3种可能的用法。在每个部分下面,您会看到3个编号的点。每个对应于相应的检出用法。

  • 用法
    1. java gitlet.Main checkout -- [文件名]
    2. java gitlet.Main checkout [提交ID] -- [文件名]
    3. java gitlet.Main checkout [分支名]
  • 描述
    1. 获取文件在当前分支的最新提交中的版本,并将其放入工作目录中,如果已经存在同名文件,则覆盖它。新版本的文件不会被暂存。
    2. 获取文件在具有给定ID的提交中的版本,并将其放入工作目录中,如果已经存在同名文件,则覆盖它。新版本的文件不会被暂存。
    3. 获取给定分支头部的提交中的所有文件,并将它们放入工作目录中,如果已经存在同名文件,则覆盖它们。此外,在执行此命令结束时,给定分支将被视为当前分支(HEAD)。任何在当前分支中被跟踪但在检出分支中不存在的文件将被删除。暂存区将被清空,除非检出的分支是当前分支(参见失败情况下面)。
  • 失败情况
    1. 如果文件在上一个提交中不存在,则中止操作,并打印错误消息File does not exist in that commit. 不要更改当前工作目录。
    2. 如果不存在具有给定ID的提交,则打印No commit with that id exists.。否则,如果文件在给定提交中不存在,则打印与失败情况1相同的消息。不要更改当前工作目录。
    3. 如果不存在具有该名称的分支,则打印No such branch exists. 如果该分支是当前分支,则打印No need to checkout the current branch. 如果当前分支中有一个工作文件在检出时将被覆盖,则打印There is an untracked file in the way; delete it, or add and commit it first. 并退出;在执行任何其他操作之前执行此检查。不要更改当前工作目录。

checkout(String branch)

前两个比较容易就不讨论了

这个切换分支分为三个情况

  • 文件名既被branch追踪的文件,也被head追踪,那么对于相同文件名但blobID不同(也就是内容不同),则用branch中的文件来替代原来的文件;相同文件名并且blobID相同,不进行任何操作。

  • 文件名不被branch追踪的文件,而仅被head追踪,那么直接删除这些文件。

  • 文件名仅被branch追踪的文件,而不被head追踪,那么直接将这些文件写入到工作目录。

  • 这里有个例外,即对于第三种情况,将要直接写入的时候如果有同名文件(例如1.txt)已经在工作目录中了,说明工作目录中在执行checkout前增加了新的1.txt文件而没有commit,这时候gitlet不知道是应该保存用户新添加进来的1.txt还是把branch中的1.txt拿过来overwrite掉,为了避免出现信息丢失,gitlet就会报错,输出There is an untracked file in the way; delete it, or add and commit it first.

我的做法:

  • 对于第一种情况

    • 首先得到一个包含在branchVersion和headVersion文件名的列表(遍历headVersion的keySet)
    • 遍历该列表
      • 删除CWD中包含在该列表的文件
      • 写入branchVersion版本的该文件到CWD中
  • 对于第二种情况

    • 首先得到一个不包含在branchVersion但包含在headVersion文件名的列表(遍历headVersion的keySet)
    • 遍历该列表
      • 删除CWD中包含在该列表的文件
  • 对于第三种情况

    • 首先得到一个包含在branchVersion但不包含在headVersion文件名的列表(遍历headVersion的keySet)
    • 遍历该列表
      • 检查特殊情况
      • 直接写入branchVersion版本的该文件到CWD中

    不要忘记保存curBranch

branch

  • 描述: 创建一个具有给定名称的新分支,并将其指向当前的头部提交。分支只是对提交节点的引用(一个SHA-1标识符)的名称。该命令不会立即切换到新创建的分支(就像真实的Git一样)。在调用分支之前,您的代码应该使用一个名为“master”的默认分支运行。

增加一个Branch,即在heads文件夹中添加一个新的名为branchname的文件,内容为当前的commitID。此操作不改变HEAD指向,只是单纯增加一个Branch。改变分支依然是通过checkout命令来改变。

失败的情况:无

rm-branch

  • 描述: 删除具有给定名称的分支。这只是删除与分支关联的指针;不意味着删除在该分支下创建的所有提交,或者类似的操作。
  • 失败情况: 如果具有给定名称的分支不存在,则中止。打印错误消息 A branch with that name does not exist. 如果尝试删除当前正在使用的分支,则中止,打印错误消息 Cannot remove the current branch.

merge

  • 用法: java gitlet.Main merge [分支名称]
  • 描述: 将给定分支的文件合并到当前分支。这个方法有点复杂,所以这里有一个更详细的描述:
    • 首先考虑当前分支和给定分支的 分割点。例如,如果 master 是当前分支,branch 是给定分支:分割点 分割点是当前分支和给定分支头部的最新共同祖先:- 共同祖先 是一个提交,从两个分支头部都有一条路径(0个或多个父指针)。- 最新 的共同祖先是一个不是其他任何共同祖先的共同祖先。例如,尽管上图中最左边的提交是 masterbranch 的共同祖先,但它也是其右边紧邻的提交的祖先,因此它不是最新的共同祖先。如果分割点 与给定分支相同的提交,则我们不执行任何操作;合并已完成,并以消息 Given branch is an ancestor of the current branch.。 结束操作。如果分割点是当前分支,则效果是检出给定分支,并在打印消息 Current branch fast-forwarded. 后结束。否则,我们继续以下步骤:

f41d1cfa707f80bbc90f4969c3c60b0

  • 失败案例:

    • 如果存在已暂存的添加或删除,则打印错误消息 You have uncommitted changes. 并退出。
    • 如果给定名称的分支不存在,则打印错误消息 A branch with that name does not exist.
    • 如果尝试将分支与自身合并,则打印错误消息 Cannot merge a branch with itself.
    • 如果合并会生成错误,因为所做的提交中没有更改,请让此错误的常规提交消息继续进行。
    • 如果当前提交中的一个未跟踪文件将被合并覆盖或删除,请打印 There is an untracked file in the way; delete it, or add and commit it first. 并退出;在执行任何其他操作之前执行此检查。
  • 思路

    • 错误检测

      • 检查Stage是否为空
      • 检查给定分支是否存在
      • 检查合并的是否为头指针
      • 第四没看懂
      • 同checkout中的思路
    • 特殊情况分析

      • 如果分割点是给定分支,不操作
      • 如果分支完成,输出······
      • 如果分割点是头指针,checkout给定分支并输出······
    • 正常情况分析

      • (xy为分支,s为分裂点)
      • 如果文件在x被修改,但在y没有修改 -> 版本为x
        • x给定分支指针, CWD改变
      • 如果文件在x,s不存在,在y中 -> 版本为y
        • y给定分支指针, CWD改变
      • 如果文件在x中不存在,但没被y修改 -> 删掉
        • x给定分支指针, CWD改变
        • 在分割点处存在但在当前分支中未被修改并且在给定分支中不存在的任何文件都应该被移除(并且未被跟踪)。
      • 如果文件同时在x, y中被修改且版本相等,随便了
      • 如果文件同时在x, y中被修改但版本不一样 -> 引发冲突
        • 在当前分支和给定分支中以不同方式被修改的任何文件都会冲突。“以不同方式被修改”可以意味着两者的内容都发生了变化且不同,或者一个文件的内容发生了变化而另一个文件被删除,或者文件在分割点处不存在,并且在给定分支和当前分支中具有不同的内容。
    • 方法编写

      • 找出分裂点
      • 找出改变的文件集 和 未改变的文件集
        1. 可以用HashMap来构建
        - 根据 修改未修改存在不存在 四个标签来标记文件
        2. 可以用Set,利用Set的相加减
      • 分为五个方法
        1. 一改一不变情况的方法
        2. 一存在两不存在的方法
        3. 一不存在一未修改的方法
        4. 两改一同的方法
        5. 两改不同的方法
    • 细节分析

      • 首先读取需要的文件

      • 先错误检测

      • 特殊情况两个if语句

      • 开始正常情况编写

      • 设计一个算法找到分裂点

      • 找出用于五个方法的文件集

        1. 遍历分割点的文件同两个分支文件比较
          • 修改未修改存在不存在 四个标签来标记文件
        2. 有Set的加减
          1. 思考中
          2. 用集合减分裂点
          3. 集合减分支 + 直接查
          4. 思考中
        3. Set和遍历结合
          • 对于存在和不存在,用Set
          • 对于有无修改,用遍历加标签
      • 对应方法进行操作:

        1. 对于文件集,若给定分支的提交文件修改了,当前分支的未修改,删除CWD文件,写入给定分支的版本
        2. 对于文件集,若当前分支的提交文件不存在,给定分支的文件存在,写入给定分支的版本
        3. 对于文件集,如果在分割点处存在的文件在其中一个分支不存在,另一个分支未修改,直接删掉
        4. 对于文件集,如果文件均被修改且版本相等,随便搞
        5. 对于文件集,如果文件均被修改但版本不一样,引发冲突
          1. 两者的内容都发生了变化且不同,或者一个文件的内容发生了变化而另一个文件被删除,或者文件在分割点处不存在,并且在给定分支和当前分支中具有不同的内容
      • 最后创建合并提交,注意:其第一个父提交为当前头指针

        骨架代码

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        // error check
        mergeErrCheck(branch);
        Commit branchCommit = readBranch(branch);
        HashMap<String, Integer> branchAncestors = new HashMap<>();
        HashMap<String, Integer> masterAncestors = new HashMap<>();
        CommitTree.readHEAD();
        findAncestors(branchCommit, branchAncestors, 0);
        findAncestors(HEAD, masterAncestors, 0);
        // get splitPoint
        Commit splitPoint = findSplitPoint(masterAncestors, branchAncestors);
        Set<String> splitSet = splitPoint.getVersion().keySet();
        HashMap<String, String> curVersion = HEAD.getVersion();
        // specialSituation check
        specialSituation(splitPoint, branchCommit, branch);
        // get changSet
        Set<String> changeFileSetOfBranch = getChangeSet(branchCommit, splitPoint);
        Set<String> noChangeFileSetOfBranch = opposeSet(changeFileSetOfBranch, branchCommit);
        Set<String> changeFileSetOfCurBranch = getChangeSet(HEAD, splitPoint);
        Set<String> noChangeFileSetOfCurBranch = opposeSet(changeFileSetOfCurBranch, HEAD);
        // get existSet
        Set<String> existFileSetOfBranch = getCompareFile(branchCommit, splitPoint, DELETE);
        Set<String> noExistFileSetOfBranch = noExistSet(splitSet, HEAD.getVersion().keySet(), branchCommit.getVersion().keySet());
        Set<String> existFileSetOfCurBranch = getCompareFile(HEAD, splitPoint, DELETE);
        Set<String> noExistFileSetOfCurBranch = noExistSet(splitSet, branchCommit.getVersion().keySet(), HEAD.getVersion().keySet());
        // normal situation
        branchChangeHeadKeep(branchCommit, noChangeFileSetOfCurBranch, changeFileSetOfBranch, curVersion);
        branchExistOthersNot(branchCommit, existFileSetOfBranch, existFileSetOfCurBranch, curVersion);
        OneNotExistOneKeep(splitSet, noExistFileSetOfBranch, noChangeFileSetOfCurBranch, curVersion);
        checkTwoChangeIfSame(branchCommit, splitSet, changeFileSetOfBranch, noExistFileSetOfBranch, changeFileSetOfCurBranch, noExistFileSetOfCurBranch,curVersion);
        String message = "Merged " + branch + " into " + curBranchName + ".";
        creatMergeCommmit(message, curVersion, HEAD.getShaName(), branchCommit.getShaName());

收获

  • 对文件的持续性操作有了更深的理解

  • 对文件的写入和读取有了实际操作体验

    • String readContent = new String(byte[] fileByte);
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197

      - 对函数式编程操作有了体会,面对工程量大的项目应该细化分开

      github仓库: [xxbaizero0/CS61B-Tutorial (github.com)](https://github.com/xxbaizero0/CS61B-Tutorial)

      # 有用的merge测试

      ```java
      package gitlet;

      import static org.junit.Assert.*;
      import org.junit.Test;
      import java.io.File;
      import java.util.HashMap;
      import java.util.Set;

      public class Text {
      static String[] init = new String[]{"init"};
      static String[] add = new String[]{"add", "hello.txt"};
      static String[] commit = new String[]{"commit", "hello"};
      static String[] log = new String[]{"log"};
      static String[] rm = new String[]{"rm", "hello.txt"};
      static String[] check = new String[]{"checkout", "--", "hello.txt"};
      String[] global_log = new String[]{"global-log"};
      static String[] status = new String[]{"status"};

      private static void creatNewFile(String name) {
      File file = Repository.CWD;
      File newFile = Utils.join(file, name);
      if (!newFile.exists()) {
      try {
      newFile.createNewFile();
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      }

      private static void branch(String name) {
      String[] branch = new String[]{"branch", name};
      Main.main(branch);
      }

      private static void add(String name) {
      Main.main(new String[]{"add", name});
      }
      private static void log() {
      Main.main(new String[]{"log"});
      }
      private static void glog() {
      Main.main(new String[]{"global-log"});
      }

      private static void rm(String name) {
      Main.main(new String[]{"rm", name});
      }

      private static void commit(String name) {
      Main.main(new String[]{"commit", name});
      }

      private static void find(String name) {
      Main.main(new String[]{"find", name});
      }
      private static void check(String checkBranch) {
      Main.main(new String[]{"checkout", checkBranch});
      }

      private static void merge(String checkBranch) {
      Main.main(new String[]{"merge", checkBranch});
      }

      private static void status() {
      Main.main(new String[]{"status"});
      }

      private static void global_log() {
      Main.main(new String[]{"global-log"});
      }

      static String g = "g.txt";
      static String f = "f.txt";
      static String h = "h.txt";
      static String k = "k.txt";


      @Test
      public void setup() {
      File rep = Repository.GITLET_DIR;
      if (rep.exists()) {
      deleteFolder(rep);
      }
      creatNewFile(g);
      creatNewFile(f);
      creatNewFile(h);
      creatNewFile(k);
      Main.main(init);
      }

      @Test
      public void test2() {
      setup();
      HashMap<String, Integer> masterAncestors = new HashMap<>();
      masterAncestors.put("1", 4);
      masterAncestors.put("2", 3);
      masterAncestors.put("3", 2);
      masterAncestors.put("4", 1);
      masterAncestors.put("5", 0);
      HashMap<String, Integer> branchAncestors = new HashMap<>();
      branchAncestors.put("1", 5);
      branchAncestors.put("22", 4);
      branchAncestors.put("33", 3);
      branchAncestors.put("44", 2);
      branchAncestors.put("55", 1);
      branchAncestors.put("66", 0);
      String split = findSplitPoint(masterAncestors, branchAncestors);
      assertEquals(split, "1");
      }

      @Test
      public void test() {
      setup();
      add(g);
      add(f);
      commit("1");
      branch("other");
      add(h);
      rm(g);
      commit("add h, rm g");
      check("other");
      rm(f);
      add(k);
      commit("rm f, add k");
      check("master");
      global_log();
      merge("other");
      log();
      }

      @Test
      public void test4() {
      add(g);
      add(f);
      commit("2");
      branch("other");
      add(h);
      rm(g);
      commit("a h r g");
      check("other");
      merge("other");
      }

      private void reset2() {
      add(g);
      add(f);
      commit("2");
      }
      @Test
      public void test5() {
      setup();
      reset2();
      branch("b1");
      add(h);
      commit("add h");
      branch("b2");
      rm(f);
      commit("rm f");
      //merge("b1");
      check("b2");
      merge("master");
      }
      private static void test3() {
      String branch = "new";
      branch(branch);
      add("hello.txt");
      add("hello2.txt");
      commit("2");
      check(branch);
      creatNewFile("hello2.txt");
      add("hello2.txt");
      commit("3");
      check("master");
      }

      public static void deleteFolder(File folder) {
      if (folder.isDirectory()) {
      File[] files = folder.listFiles();
      if (files != null) {
      for (File file : files) {
      deleteFolder(file);
      }
      }
      }
      folder.delete();
      }
      }