这是用户在 2024-3-13 18:37 为 https://sp21.datastructur.es/materials/proj/proj2/proj2#overview-of-gitlet 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
Project 2: Gitlet

A note on this spec
关于这个规格的说明

This spec is fairly long. The first half is a verbose and detailed description of every command you’ll support, and the other half is the testing details and some words of advice. To help you digest this, we’ve prepared many high quality videos describing portions of the spec and giving advice on how and where to begin. All videos are linked throughout this spec in the relevant location, but we’ll also list them right here for your convenience. Note: some of these videos were created in Spring 2020 when Gitlet was Project 3 and Capers was Lab 12, and some videos briefly mention Professor Hilfinger’s CS 61B setup (including a remote called shared, a repository called repo, etc). Please ignore these as they do not provide any useful information for you this semester. The actual content of the assignment is unchanged.
这个规范相当长。前半部分是对你将支持的每个命令进行冗长而详细的描述,而后半部分是测试细节和一些建议。为了帮助你理解,我们准备了许多高质量的视频,描述规范的部分内容并提供如何开始和何处开始的建议。所有视频都在规范的相关位置进行了链接,但我们也会在这里列出它们以方便你。注意:其中一些视频是在2020年春季创建的,当时Gitlet是项目3,Capers是实验室12,并且一些视频简要提到了Hilfinger教授的CS 61B设置(包括一个名为 shared 的远程仓库,一个名为 repo 的存储库等)。请忽略这些,因为它们对你本学期没有提供任何有用的信息。作业的实际内容没有改变。

As more resources are created, we’ll add them here, so refresh often!
随着更多资源的创建,我们将在这里添加它们,所以请经常刷新!

Overview of Gitlet Gitlet概述

Warning: Ensure you’ve completed Lab 6: Canine Capers before this project. Lab 6 is intended to be an introduction to this project and will be very helpful in getting you started and ensure you’re all set up. You should also have watched Lecture 12: Gitlet, which introduces many useful ideas for this project.
警告:在开始这个项目之前,请确保您已经完成了实验6:狗狗的疯狂。实验6旨在为这个项目提供介绍,并且在开始和设置方面非常有帮助。您还应该观看了第12讲:Gitlet,该讲座介绍了许多对这个项目有用的想法。

In this project you’ll be implementing a version-control system that mimics some of the basic features of the popular system Git. Ours is smaller and simpler, however, so we have named it Gitlet.
在这个项目中,你将实现一个模拟流行系统Git的版本控制系统。然而,我们的系统更小更简单,所以我们将其命名为Gitlet。

A version-control system is essentially a backup system for related collections of files. The main functionality that Gitlet supports is:
版本控制系统本质上是一种相关文件的备份系统。Gitlet主要支持的功能有:

  1. Saving the contents of entire directories of files. In Gitlet, this is called committing, and the saved contents themselves are called commits.
    保存整个目录文件的内容。在Gitlet中,这被称为提交,而保存的内容本身被称为提交。

  2. Restoring a version of one or more files or entire commits. In Gitlet, this is called checking out those files or that commit.
    恢复一个或多个文件或整个提交的版本。在Gitlet中,这被称为检出这些文件或该提交。

  3. Viewing the history of your backups. In Gitlet, you view this history in something called the log.
    查看您备份的历史记录。在Gitlet中,您可以在一个称为日志的地方查看此历史记录。

  4. Maintaining related sequences of commits, called branches.
    维护相关的提交序列,称为分支。

  5. Merging changes made in one branch into another.
    将在一个分支中进行的更改合并到另一个分支中。

The point of a version-control system is to help you when creating complicated (or even not-so-complicated) projects, or when collaborating with others on a project. You save versions of the project periodically. If at some later point in time you accidentally mess up your code, then you can restore your source to a previously committed version (without losing any of the changes you made since then). If your collaborators make changes embodied in a commit, you can incorporate (merge) these changes into your own version.
版本控制系统的目的是在创建复杂(甚至不那么复杂)的项目或与他人合作时提供帮助。您可以定期保存项目的版本。如果在以后的某个时间点上意外破坏了您的代码,那么您可以将源代码恢复到先前提交的版本(而不会丢失此后所做的任何更改)。如果您的合作者在提交中进行了更改,您可以将这些更改合并到自己的版本中。

In Gitlet, you don’t just commit individual files at a time. Instead, you can commit a coherent set of files at the same time. We like to think of each commit as a snapshot of your entire project at one point in time. However, for simplicity, many of the examples in the remainder of this document involve changes to just one file at a time. Just keep in mind you could change multiple files in each commit.
在Gitlet中,您不仅可以一次提交单个文件。相反,您可以同时提交一组相关的文件。我们喜欢将每个提交视为您整个项目在某一时间点的快照。然而,为了简单起见,本文档的其余部分中的许多示例仅涉及一次更改一个文件。只需记住,您可以在每次提交中更改多个文件。

In this project, it will be helpful for us to visualize the commits we make over time. Suppose we have a project consisting just of the file wug.txt, we add some text to it, and commit it. Then we modify the file and commit these changes. Then we modify the file again, and commit the changes again. Now we have saved three total versions of this file, each one later in time than the previous. We can visualize these commits like so:
在这个项目中,对我们来说,将我们随时间进行的提交可视化是有帮助的。假设我们有一个只包含文件wug.txt的项目,我们向其中添加一些文本,并进行提交。然后我们修改文件并提交这些更改。然后我们再次修改文件,并再次提交更改。现在我们已经保存了这个文件的三个版本,每个版本都比之前晚。我们可以这样可视化这些提交:

Three commits

Here we’ve drawn an arrow indicating that each commit contains some kind of reference to the commit that came before it. We call the commit that came before it the parent commit–this will be important later. But for now, does this drawing look familiar? That’s right; it’s a linked list!
这里我们画了一个箭头,表示每个提交都包含对之前提交的某种引用。我们称之为前一个提交为父提交-这在后面会很重要。但现在,这个图看起来熟悉吗?没错,它是一个链表!

The big idea behind Gitlet is that we can visualize the history of the different versions of our files in a list like this. Then it’s easy for us to restore old versions of files. You can imagine making a command like: “Gitlet, please revert to the state of the files at commit #2”, and it would go to the second node in the linked list and restore the copies of files found there, while removing any files that are in the first node, but not the second.
Gitlet的核心思想是我们可以将文件的不同版本的历史可视化为一个类似于这样的列表。这样,我们就可以轻松地恢复旧版本的文件。你可以想象一下,做一个命令:“Gitlet,请恢复到提交#2时的文件状态”,它会跳转到链表中的第二个节点,并恢复那里找到的文件副本,同时删除第一个节点中存在但第二个节点中不存在的文件。

If we tell Gitlet to revert to an old commit, the front of the linked list will no longer reflect the current state of your files, which might be a little misleading. In order to fix this problem, we introduce something called the head pointer (also called the HEAD pointer). The head pointer keeps track of where in the linked list we currently are. Normally, as we make commits, the head pointer will stay at the front of the linked list, indicating that the latest commit reflects the current state of the files:
如果我们告诉Gitlet回滚到一个旧的提交,那么链表的前端将不再反映您文件的当前状态,这可能会有点误导。为了解决这个问题,我们引入了一个叫做头指针(也叫做HEAD指针)的东西。头指针用来跟踪我们当前在链表中的位置。通常情况下,当我们进行提交时,头指针会保持在链表的前端,表示最新的提交反映了文件的当前状态。

Simple head

However, let’s say we revert to the state of the files at commit #2 (technically, this is the reset command, which you’ll see later in the spec). We move the head pointer back to show this:
然而,假设我们将文件恢复到提交#2的状态(从技术上讲,这是重置命令,您将在规范中看到)。我们将头指针移回以显示这一点。

Reverted head

Here we say that we are in a detatched head state which you may have encountered yourself before. This is what it means!
我们说我们处于一个分离的头状态,你可能之前也遇到过。这就是它的意思!

EDITED 3/5: Note that in Gitlet, there is no way to be in a detached head state since there is no checkout command that will move the HEAD pointer to a specific commit. The reset command will do that, though it also moves the branch pointer. Thus, in Gitlet, you will never be in a detached HEAD state.
请注意,在Gitlet中,由于没有将HEAD指针移动到特定提交的命令,所以不可能处于分离头状态。尽管 reset 命令可以实现这一点,但它也会移动分支指针。因此,在Gitlet中,你永远不会处于分离HEAD状态。

All right, now, if this were all Gitlet could do, it would be a pretty simple system. But Gitlet has one more trick up its sleeve: it doesn’t just maintain older and newer versions of files, it can maintain differing versions. Imagine you’re coding a project, and you have two ideas about how to proceed: let’s call one Plan A, and the other Plan B. Gitlet allows you to save both versions, and switch between them at will. Here’s what this might look like, in our pictures:
好的,现在,如果这是Gitlet能做的全部,那它将是一个相当简单的系统。但是Gitlet还有一个更厉害的功能:它不仅可以维护文件的旧版本和新版本,还可以维护不同的版本。想象一下,你正在编写一个项目,对于如何进行有两个想法:我们称之为计划A和计划B。Gitlet允许你保存这两个版本,并随时在它们之间切换。以下是我们图片中可能看到的情况:

Two versions

It’s not really a linked list anymore. It’s more like a tree. We’ll call this thing the commit tree. Keeping with this metaphor, each of the separate versions is called a branch of the tree. You can develop each version separately:
它不再是一个链表了,更像是一棵树。我们将这个东西称为提交树。按照这个比喻,每个独立的版本被称为树的一个分支。你可以分别开发每个版本:

Two developed versions

There are two pointers into the tree, representing the furthest point of each branch. At any given time, only one of these is the currently active pointer, and this is what’s called the head pointer. The head pointer is the pointer at the front of the current branch.
树中有两个指针,分别代表每个分支的最远点。在任何给定的时间,只有一个指针是当前活动的指针,这就是所谓的头指针。头指针是当前分支前面的指针。

That’s it for our brief overview of the Gitlet system! Don’t worry if you don’t fully understand it yet; the section above was just to give you a high level picture of what its meant to do. A detailed spec of what you’re supposed to do for this project follows this section.
这就是我们对Gitlet系统的简要概述!如果你还没有完全理解,不要担心;上面的部分只是为了给你一个高层次的了解它的目的。在本节之后,将会有一个详细的规范,告诉你在这个项目中你应该做什么。

But a last word here: commit trees are immutable: once a commit node has been created, it can never be destroyed (or changed at all). We can only add new things to the commit tree, not modify existing things. This is an important feature of Gitlet! One of Gitlet’s goals is to allow us to save things so we don’t delete them accidentally.
但是在这里有一个最后的话:提交树是不可变的:一旦创建了一个提交节点,它就永远不能被销毁(或者改变)。我们只能向提交树中添加新的内容,而不能修改现有的内容。这是Gitlet的一个重要特性!Gitlet的一个目标是允许我们保存东西,以防止意外删除。

Internal Structures 内部结构

Real Git distinguishes several different kinds of objects. For our purposes, the important ones are
真正的Git区分了几种不同类型的对象。对于我们的目的来说,重要的是

Gitlet simplifies from Git still further by
Gitlet进一步简化了Git

Every object–every blob and every commit in our case–has a unique integer id that serves as a reference to the object. An interesting feature of Git is that these ids are universal: unlike a typical Java implementation, two objects with exactly the same content will have the same id on all systems (i.e. my computer, your computer, and anyone else’s computer will compute this same exact id). In the case of blobs, “same content” means the same file contents. In the case of commits, it means the same metadata, the same mapping of names to references, and the same parent reference. The objects in a repository are thus said to be content addressable.
每个对象 - 在我们的情况下,每个blob和每个提交 - 都有一个唯一的整数id,用作对该对象的引用。Git的一个有趣特性是这些id是通用的:与典型的Java实现不同,具有完全相同内容的两个对象在所有系统上都具有相同的id(即我的计算机、你的计算机和其他任何人的计算机都会计算出完全相同的id)。对于blob,"相同内容"意味着相同的文件内容。对于提交,它意味着相同的元数据,相同的名称到引用的映射,以及相同的父引用。因此,存储库中的对象被称为内容可寻址的。

Both Git and Gitlet accomplish this the same way: by using a cryptographic hash function called SHA-1 (Secure Hash 1), which produces a 160-bit integer hash from any sequence of bytes. Cryptographic hash functions have the property that it is extremely difficult to find two different byte streams with the same hash value (or indeed to find any byte stream given just its hash value), so that essentially, we may assume that the probability that any two objects with different contents have the same SHA-1 hash value is 2-160 or about 10-48. Basically, we simply ignore the possibility of a hashing collision, so that the system has, in principle, a fundamental bug that in practice never occurs!
Git和Gitlet都是通过使用一个称为SHA-1(安全哈希1)的加密哈希函数来实现这一点,它可以从任何字节序列生成一个160位整数哈希值。加密哈希函数具有这样的特性,即极其困难找到两个不同的字节流具有相同的哈希值(或者仅通过其哈希值找到任何字节流),因此我们可以假设任何两个具有不同内容的对象具有相同的SHA-1哈希值的概率是2的负零次方,即约为10的负一次方。基本上,我们简单地忽略了哈希碰撞的可能性,因此系统原则上存在一个基本错误,但实际上从未发生过!

Fortunately, there are library classes for computing SHA-1 values, so you won’t have to deal with the actual algorithm. All you have to do is to make sure that you correctly label all your objects. In particular, this involves
幸运的是,有用于计算SHA-1值的库类,因此您不必处理实际的算法。您只需要确保正确标记所有对象。特别是,这涉及

By the way, the SHA-1 hash value, rendered as a 40-character hexadecimal string, makes a convenient file name for storing your data in your .gitlet directory (more on that below). It also gives you a convenient way to compare two files (blobs) to see if they have the same contents: if their SHA-1s are the same, we simply assume the files are the same.
顺便说一下,SHA-1哈希值以40个字符的十六进制字符串形式呈现,可以作为一个方便的文件名存储在你的目录中(下面会详细介绍)。它还提供了一种方便的方法来比较两个文件(块),以查看它们是否具有相同的内容:如果它们的SHA-1值相同,我们就简单地认为这些文件是相同的。

For remotes (like skeleton which we’ve been using all semester), we’ll simply use other Gitlet repositories. Pushing simply means copying all commits and blobs that the remote repository does not yet have to the remote repository, and resetting a branch reference. Pulling is the same, but in the other direction. Remotes are extra credit in this project and not required for full credit.
对于远程仓库(例如我们整个学期都在使用的 skeleton ),我们将简单地使用其他Gitlet仓库。推送意味着将远程仓库尚未拥有的所有提交和数据块复制到远程仓库,并重置分支引用。拉取则是相反的操作。在这个项目中,远程仓库是额外的加分项,不是必须完成的。

Reading and writing your internal objects from and to files is actually pretty easy, thanks to Java’s serialization facilities. The interface java.io.Serializable has no methods, but if a class implements it, then the Java runtime will automatically provide a way to convert to and from a stream of bytes, which you can then write to a file using the I/O class java.io.ObjectOutputStream and read back (and deserialize) with java.io.ObjectInputStream. The term “serialization” refers to the conversion from some arbitrary structure (array, tree, graph, etc.) to a serial sequence of bytes. You should have seen and gotten practice with serialization in lab 6. You’ll be using a very similar approach here, so do use your lab6 as a resource when it comes to persistence and serialization.
通过Java的序列化功能,从文件中读取和写入内部对象实际上非常简单。接口 java.io.Serializable 没有方法,但如果一个类实现了它,那么Java运行时将自动提供一种将对象转换为字节流的方法,然后可以使用I/O类 java.io.ObjectOutputStream 将其写入文件,并使用 java.io.ObjectInputStream 读取(反序列化)。术语“序列化”指的是将某个任意结构(数组、树、图等)转换为一系列字节的过程。你应该在实验6中见过并练习过序列化。在这里,你将使用非常类似的方法,所以在持久化和序列化方面可以使用实验6作为参考。

Here is a summary example of the structures discussed in this section. As you can see, each commit (rectangle) points to some blobs (circles), which contain file contents. The commits contain the file names and references to these blobs, as well as a parent link. These references, depicted as arrows, are represented in the .gitlet directory using their SHA-1 hash values (the small hexadecimal numerals above the commits and below the blobs). The newer commit contains an updated version of wug1.txt, but shares the same version of wug2.txt as the older commit. Your commit class will somehow store all of the information that this diagram shows: a careful selection of internal data structures will make the implementation easier or harder, so it behooves you to spend time planning and thinking about the best way to store everything.
这是本节讨论的结构的摘要示例。如您所见,每个提交(矩形)指向一些包含文件内容的blob(圆圈)。提交包含文件名和对这些blob的引用,以及一个父链接。这些引用被表示为箭头,并在 .gitlet 目录中使用它们的SHA-1哈希值(在提交上方和blob下方的小十六进制数字)表示。较新的提交包含 wug1.txt 的更新版本,但与较旧的提交共享 wug2.txt 的相同版本。您的提交类将以某种方式存储此图表显示的所有信息:仔细选择内部数据结构将使实现变得更容易或更困难,因此您应该花时间计划和思考存储所有内容的最佳方式。

Two commits and their blobs

Detailed Spec of Behavior
行为的详细规范

Overall Spec 总体规格

The only structure requirement we’re giving you is that you have a class named gitlet.Main and that it has a main method.
我们唯一给你的结构要求是你必须有一个名为 gitlet.Main 的类,并且该类必须有一个main方法。

We are also giving you some utility methods for performing a number of mostly file-system-related tasks, so that you can concentrate on the logic of the project rather than the peculiarities of dealing with the OS.
我们还为您提供了一些实用方法,用于执行许多与文件系统相关的任务,这样您就可以专注于项目的逻辑,而不是处理操作系统的特殊性。

We have also added two suggested classes: Commit, and Repository to get you started. You may, of course, write additional Java classes to support your project or remove our suggested classes if you’d like. But don’t use any external code (aside from JUnit), and don’t use any programming language other than Java. You can use all of the Java Standard Library that you wish, plus utilities we provide.
我们还添加了两个建议的类: CommitRepository ,以帮助您入门。当然,您可以编写额外的Java类来支持您的项目,或者如果您愿意,删除我们的建议类。但是,请不要使用任何外部代码(除了JUnit),也不要使用除Java之外的任何编程语言。您可以使用所有您希望使用的Java标准库,以及我们提供的工具。

You should not do everything in the Main class. Your Main class should mostly be calling helper methods in the the Repository class. See the CapersRepository and Main classes from lab 6 for examples of the structure that we recommend.
你不应该在Main类中做所有的事情。你的Main类应该主要调用 Repository 类中的辅助方法。参考实验6中的 CapersRepositoryMain 类,了解我们推荐的结构示例。

The majority of this spec will describe how Gitlet.java’s main method must react when it receives various gitlet commands as command-line arguments. But before we break down command-by-command, here are some overall guidelines the whole project should satisfy:
大部分规范将描述当 Gitlet.java 的主方法接收到不同的gitlet命令作为命令行参数时,它应该如何反应。但在我们逐个命令进行分解之前,这里有一些整体指导方针,整个项目应该满足:

The Commands 命令

We now go through each command you must support in detail. Remember that good programmers always care about their data structures: as you read these commands, you should think first about how you should store your data to easily support these commands and second about if there is any opportunity to reuse commands that you’ve already implemented (hint: there is ample opportunity in this project to reuse code in later parts of project 2 that you’ve already written in earlier parts of project 2). We have listed lectures in some methods that we have found useful, but you are not required to use concepts from these lectures. There are conceptual quizzes on some of the more confusing commands that you should definately use to check your understanding. The quizzes are not for a grade, they are only there to help you check your understanding before trying to implement the command.
我们现在详细介绍每个你必须支持的命令。记住,优秀的程序员总是关心他们的数据结构:当你阅读这些命令时,你应该首先考虑如何存储你的数据以便轻松支持这些命令,其次考虑是否有机会重用你已经实现的命令(提示:在项目2的后续部分中,你已经在项目2的早期部分中编写了很多可以重用的代码)。我们列出了一些我们发现有用的方法,但你不需要使用这些方法中的概念。对于一些更加混淆的命令,我们有概念性的测验,你应该确保使用它们来检查你的理解。这些测验不计分,只是为了帮助你在尝试实现命令之前检查你的理解。

init 初始化

add

commit 承诺

Here’s a picture of before-and-after commit:
这是一张提交前后的图片

Before and after commit

rm

log

===
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

There is a === before each commit and an empty line after it. As in real Git, each entry displays the unique SHA-1 id of the commit object. The timestamps displayed in the commits reflect the current timezone, not UTC; as a result, the timestamp for the initial commit does not read Thursday, January 1st, 1970, 00:00:00, but rather the equivalent Pacific Standard Time. Your timezone might be different depending on where you live, and that’s fine.
每个提交之前都有一个 === ,并且在其后有一个空行。与真实的Git一样,每个条目显示了提交对象的唯一SHA-1 id。提交中显示的时间戳反映了当前时区,而不是UTC;因此,初始提交的时间戳不是读作1970年1月1日星期四00:00:00,而是相应的太平洋标准时间。根据您所在的位置,您的时区可能会有所不同,这是正常的。

Display commits with the most recent at the top. By the way, you’ll find that the Java classes java.util.Date and java.util.Formatter are useful for getting and formatting times. Look into them instead of trying to construct it manually yourself!
显示最新的提交在顶部。顺便说一下,你会发现Java类 java.util.Datejava.util.Formatter 在获取和格式化时间方面非常有用。请查看它们,而不是试图手动构建!

Of course, the SHA1 identifiers are going to be different, so don’t worry about those. Our tests will ensure that you have something that “looks like” a SHA1 identifier (more on that in the testing section below).
当然,SHA1标识符会有所不同,所以不用担心。我们的测试将确保您拥有一个“看起来像”SHA1标识符的东西(有关此内容,请参阅下面的测试部分)。

For merge commits (those that have two parent commits), add a line just below the first, as in
对于合并提交(具有两个父提交的提交),在第一行的下方添加一行,如下所示:

===
commit 3e8bf1d794ca2e9ef8a4007275acf3751c7170ff
Merge: 4975af1 2c1ead1
Date: Sat Nov 11 12:30:00 2017 -0800
Merged development into master.

where the two hexadecimal numerals following “Merge:” consist of the first seven digits of the first and second parents’ commit ids, in that order. The first parent is the branch you were on when you did the merge; the second is that of the merged-in branch. This is as in regular Git.
“Merge:”后面的两个十六进制数字由第一个和第二个父提交的前七位数字组成,按顺序排列。第一个父提交是在合并时所在的分支;第二个父提交是被合并的分支。这与常规的Git操作相同。

Here’s a picture of the history of a particular commit. If the current branch’s head pointer happened to be pointing to that commit, log would print out information about the circled commits:
这是一个特定提交的历史图片。如果当前分支的头指针恰好指向该提交,日志将打印出有关被圈出的提交的信息。

History

The history ignores other branches and the future. Now that we have the concept of history, let’s refine what we said earlier about the commit tree being immutable. It is immutable precisely in the sense that the history of a commit with a particular id may never change, ever. If you think of the commit tree as nothing more than a collection of histories, then what we’re really saying is that each history is immutable.
历史忽略了其他分支和未来。既然我们有了历史的概念,让我们来完善一下之前关于提交树是不可变的说法。它之所以是不可变的,恰恰是因为具有特定id的提交的历史永远不会改变。如果你把提交树看作是一组历史的集合,那么我们真正要说的是每个历史都是不可变的。

global-log 全球日志

find 找到

status 状态

checkout 结账

Checkout is a kind of general command that can do a few different things depending on what its arguments are. There are 3 possible use cases. In each section below, you’ll see 3 numbered points. Each corresponds to the respective usage of checkout.
结账是一种通用命令,根据其参数的不同可以执行几种不同的操作。有三种可能的用例。在下面的每个部分中,您将看到3个编号的点。每个点对应于checkout的相应用法。

A [commit id] is, as described earlier, a hexadecimal numeral. A convenient feature of real Git is that one can abbreviate commits with a unique prefix. For example, one can abbreviate
一个 [commit id] 是一个十六进制数字。真正的Git的一个方便的特性是可以用唯一的前缀缩写提交。例如,可以缩写为

a0da1ea5a15ab613bf9961fd86f010cf74c7ee48

as

a0da1e

in the (likely) event that no other object exists with a SHA-1 identifier that starts with the same six digits. You should arrange for the same thing to happen for commit ids that contain fewer than 40 characters. Unfortunately, using shortened ids might slow down the finding of objects if implemented naively (making the time to find a file linear in the number of objects), so we won’t worry about timing for commands that use shortened ids. We suggest, however, that you poke around in a .git directory (specifically, .git/objects) and see how it manages to speed up its search. You will perhaps recognize a familiar data structure implemented with the file system rather than pointers.
在(可能的)情况下,没有其他以相同六位数字开头的SHA-1标识符的对象存在。您应该安排相同的事情发生在包含少于40个字符的提交ID上。不幸的是,如果实现得不够聪明,使用缩短的ID可能会减慢查找对象的速度(使查找文件的时间与对象数量成线性关系),因此我们不会担心使用缩短的ID的命令的时间。然而,我们建议您在一个 .git 目录(具体来说, .git/objects )中探索一下,看看它是如何加速搜索的。您可能会认出一个熟悉的数据结构,它是通过文件系统而不是指针实现的。

Only version 3 (checkout of a full branch) modifies the staging area: otherwise files scheduled for addition or removal remain so.
只有版本3(完全检出一个分支)会修改暂存区:否则,计划添加或删除的文件仍然保持不变。

branch 分支

All right, let’s see what branch does in detail. Suppose our state looks like this:
好的,让我们详细看一下分支的作用。假设我们的状态是这样的:

Simple history

Now we call java gitlet.Main branch cool-beans. Then we get this:
现在我们称之为 java gitlet.Main branch cool-beans 。然后我们得到这个:

Just called branch

Hmm… nothing much happened. Let’s switch to the branch with java gitlet.Main checkout cool-beans:
嗯...没什么大事发生。让我们切换到带有 java gitlet.Main checkout cool-beans 的分支:

Just switched branch

Nothing much happened again?! Okay, say we make a commit now. Modify some files, then java gitlet.Main add... then java gitlet.Main commit...
什么都没发生?!好吧,我们现在进行一次提交。修改一些文件,然后 java gitlet.Main add... 然后 java gitlet.Main commit...

Commit on branch

I was told there would be branching. But all I see is a straight line. What’s going on? Maybe I should go back to my other branch with java gitlet.Main checkout master:
我被告知会有分支。但我看到的只是一条直线。发生了什么?也许我应该回到我的另一个分支: java gitlet.Main checkout master

Checkout master

Now I make a commit…
现在我提交了一个更改..

Branched

Phew! So that’s the whole idea of branching. Did you catch what’s going on? All that creating a branch does is to give us a new pointer. At any given time, one of these pointers is considered the currently active pointer, also called the HEAD pointer (indicated by *). We can switch the currently active head pointer with checkout [branch name]. Whenever we commit, it means we add a child commit to the currently active HEAD commit even if there is already a child commit. This naturally creates branching behavior as a commit can now have multiple children.
呼!这就是分支的整个概念。你明白发生了什么吗?创建分支只是给我们一个新的指针。在任何给定的时间,其中一个指针被认为是当前活动的指针,也被称为HEAD指针(用*表示)。我们可以用 checkout [branch name] 切换当前活动的HEAD指针。每当我们提交时,意味着我们将一个子提交添加到当前活动的HEAD提交中,即使已经有一个子提交。这自然地创建了分支行为,因为一个提交现在可以有多个子提交。

A video example and overview of branching can be found here
这里可以找到有关分支的视频示例和概述

Make sure that the behavior of your branch, checkout, and commit match what we’ve described above. This is pretty core functionality of Gitlet that many other commands will depend upon. If any of this core functionality is broken, very many of our autograder tests won’t work!
确保您的 branchcheckoutcommit 的行为与我们上面描述的相匹配。这是Gitlet的核心功能,许多其他命令将依赖于此。如果其中任何核心功能出现问题,我们的自动评分测试将无法正常工作!

rm-branch 删除分支

reset 重置

merge 合并

<<<<<<< HEAD
contents of file in current branch
=======
contents of file in given branch
>>>>>>>

(replacing “contents of…” with the indicated file’s contents) and stage the result. Treat a deleted file in a branch as an empty file. Use straight concatenation here. In the case of a file with no newline at the end, you might well end up with something like this:
(将“...的内容”替换为指定文件的内容)并暂存结果。将分支中的已删除文件视为空文件。在此处使用直接连接。如果文件末尾没有换行符,可能会得到类似以下内容的结果:

<<<<<<< HEAD
contents of file in current branch=======
contents of file in given branch>>>>>>>

This is fine; people who produce non-standard, pathological files because they don’t know the difference between a line terminator and a line separator deserve what they get.
这很好;那些因为不了解换行符和行分隔符的区别而产生非标准、病态文件的人,他们得到的就是他们应得的。

Once files have been updated according to the above, and the split point was not the current branch or the given branch, merge automatically commits with the log message Merged [given branch name] into [current branch name]. Then, if the merge encountered a conflict, print the message Encountered a merge conflict. on the terminal (not the log). Merge commits differ from other commits: they record as parents both the head of the current branch (called the first parent) and the head of the branch given on the command line to be merged in.
一旦文件根据上述内容进行了更新,并且分割点不是当前分支或给定的分支,则自动合并提交并记录日志信息 Merged [given branch name] into [current branch name]. 。然后,如果合并遇到冲突,在终端上打印消息 Encountered a merge conflict. (而不是日志)。合并提交与其他提交不同:它们将当前分支的头(称为第一个父节点)和命令行中给定的要合并的分支的头作为父节点记录。

A video walkthrough of this command can be found here.
这个命令的视频演示可以在这里找到。

By the way, we hope you’ve noticed that the set of commits has progressed from a simple sequence to a tree and now, finally, to a full directed acyclic graph.
顺便说一下,我们希望你已经注意到,提交集从简单的序列发展到树,现在最终发展成了一个完整的有向无环图。

Skeleton 骨架

The skeleton is fairly bare bones with mostly empty classes. We’ve provided helpful javadoc comments hinting at what you might want to include in each file. You should follow a similar approach to Capers where your Main class doesn’t do a whole lot of work by itself, but rather simply calls other methods depending on the args. You’re absolutely welcome to delete the other classes or add your own, but the Main class should remain otherwise our tests won’t be able to find your code.
骨架相当简单,大部分类都是空的。我们提供了有用的javadoc注释,提示您在每个文件中可能需要包含的内容。您应该采用类似Capers的方法,其中您的 Main 类本身并不做太多工作,而是根据 args 调用其他方法。您完全可以删除其他类或添加自己的类,但 Main 类应该保留,否则我们的测试将无法找到您的代码。

If you’re confused on where to start, we suggest looking over Lab 6: Canine Capers.
如果你不知道从哪里开始,我们建议先看一下实验6:狗狗的冒险。

Design Document 设计文档

Since you are not working from a substantial skeleton this time, we are asking that everybody submit a design document describing their implementation strategy. It is not graded, but you must have an up-to-date and completed design document before we help you in Office Hours or on a Gitbug. If you do not have one or it’s not up-to-date/not complete, we cannot help you. This is for both of our sakes: by having a design doc, you have written out a road map for how you will tackle the assignment. If you need help creating a design document, we can definately help with that :) Here are some guidelines, as well as an example from the Capers lab.
由于这次你没有一个实质性的框架,我们要求每个人提交一个描述实施策略的设计文档。这不会被评分,但在我们的办公时间或Gitbug上提供帮助之前,你必须有一个最新且完整的设计文档。如果你没有或者它不是最新/不完整的,我们无法帮助你。这对我们双方都有好处:通过拥有一个设计文档,你已经写出了一个解决任务的路线图。如果你需要帮助创建设计文档,我们肯定可以帮助你 :) 这里有一些指导方针,以及Capers实验室的一个示例。

Grader Details 评分员详细信息

We have three graders for Gitlet: the checkpoint grader, the full grader, and the snaps grader.
我们为Gitlet准备了三个评分者:检查点评分者、完整评分者和快照评分者。

Checkpoint Grader 检查点评分员

Due 3/12 at 11:59 PM for 16 extra credit points.
截止日期为3月12日晚上11:59,可获得16个额外学分。

Submit to the Project 2: Gitlet Checkpoint autograder on Gradescope.
提交到Gradescope上的自动评分器。

It will test: 它将进行测试

In addition, it will comment on (but not score):
此外,它将进行评论(但不评分):

We will score these in your final submission. EDITED 3/4: It’s ok to have compiler warnings.
我们将在您的最终提交中评分。编辑于3/4:拥有编译器警告是可以的。

You’ll have a maximum capacity of 1 token which will refresh every 20 minutes. You will not get full logs on these failures (i.e. you will be told what test you failed but not any additional message), though since you have the tests themselves you can simply debug it locally.
您每20分钟可以获得1个令牌的最大容量。对于这些失败,您将不会获得完整的日志(即您将被告知您失败的测试,但不包括任何附加消息),但由于您拥有测试本身,您可以在本地进行简单的调试。

Full Grader 全级划分

Due 4/2 at 11:59 PM for 1600 points.
截止日期为4月2日晚上11:59,总分1600分。

The full grader is a more substantial and comprehensive test suite. You’ll have a maximum capacity of 1 token. Here is the schedule of token recharge rates:
完整的评分器是一个更加实质和全面的测试套件。您的最大容量为1个令牌。以下是令牌充值速率的时间表:

You’ll see that, like Project 1, there is limited access to the grader. Please be kind to yourself and write tests along the way so you do not become too reliant on the autograder for checking your work.
你会发现,与项目1一样,对于评分者的访问是有限的。请对自己友善,并在写作过程中编写测试,这样你就不会过于依赖自动评分器来检查你的工作。

Similar to the checkpoint, the full grader will have English hints on what each test does but not the actual .in file.
与检查点类似,完整的评分器将提供每个测试的英文提示,但不包括实际的 .in 文件。

Snaps Grader 快照分级器

Due 4/9 at 11:59 PM. Your Gradescope score will not be transferred to Beacon until you’ve pushed your snaps repo and submitted to the Snaps Gradescope assignment. To push your snaps repo, run these commands:
截止日期为4月9日晚上11:59。在您推送snaps存储库并提交到Snaps Gradescope作业之前,您的Gradescope分数将不会转移到Beacon。要推送您的snaps存储库,请运行以下命令:

cd $SNAPS_DIR
git push

After you’ve pushed your snaps repository, there is a Gradescope assignment that you will submit your snaps-sp21-s*** repository to (similar to Project 1). This is only for the full grader (not the checkpoint nor the extra credit assignment).
在你推送了你的snaps存储库之后,会有一个Gradescope作业,你需要将你的snaps-sp21-s***存储库提交到该作业中(类似于项目1)。这仅适用于完整的评分器(不包括检查点和额外学分作业)。

You can do this up to a week after the deadline as well in case you forget. If you forget to push after a week, then you’ll have to use slip days.
如果你忘记了,你可以在截止日期后一周内完成这个任务。如果你在一周后还是忘记了,那么你将不得不使用延期天数。

Extra credit 额外学分

There are a total of 16 + 32 + 64 = 112 extra credit points possible:
总共有16 + 32 + 64 = 112个额外学分点可能

  1. 16 for the checkpoint
    16用于检查点
  2. 32 for the status command printing the Modifications Not Staged For Commit and Untracked Files sections
    32用于打印 status 命令的 Modifications Not Staged For CommitUntracked Files 部分
  3. 64 for the remote commands
    远程命令的64

The rest of this spec is filled resources for you that you should read to get you started. The section on testing/debugging will be extremely helpful to you as testing and debugging in this project will be different than previous projects, but not so complicated.
这个规范的其余部分为您提供了一些资源,您应该阅读以帮助您入门。关于测试/调试的部分对您非常有帮助,因为在这个项目中,测试和调试与以前的项目不同,但并不复杂。

Miscellaneous Things to Know about the Project
项目中需要了解的杂项事项

Phew! That was a lot of commands to go over just now. But don’t worry, not all commands are of the same difficulty. You can see for each command the approximate number of lines we took to do each part (this only counts code specific to that command – it doesn’t double-count code reused in multiple commands). You shouldn’t worry about matching our solution exactly, but hopefully it gives you an idea about the relative time consumed by each command. Merge is a lengthier command than the others, so don’t leave it for the last minute!
哇!刚才要处理的命令太多了。但是不用担心,不是所有的命令都一样难。你可以看到每个命令我们花了大约多少行来完成每个部分(这只计算与该命令相关的代码 - 不会重复计算在多个命令中重复使用的代码)。你不必担心完全匹配我们的解决方案,但希望它能给你一个关于每个命令所需时间的相对概念。合并是比其他命令更耗时的命令,所以不要等到最后一分钟再处理!

This is an ambitious project, and it would not be surprising for you to feel lost as to where to begin. Therefore, feel free to collaborate with others a little more closely than usual, with the following caveats:
这是一个雄心勃勃的项目,你可能会感到迷茫,不知道从哪里开始。因此,与他人的合作可以更加密切一些,但请注意以下几点:

The Ed megathreads typically get very long for Gitlet, but they are full of very good conversation and discussion on the approach for particular commits. In this project more than any you should take advantage of the size of the class and see if you can find someone with a similar question to you on the megathread. It’s very unlikely that your question is so unique to you that nobody else has had it (unless it is a bug that relates to your design, in which case you should submit a Gitbug).
Ed的超级线程通常会变得非常长,但它们充满了关于特定提交方法的非常好的对话和讨论。在这个项目中,你应该充分利用班级的规模,看看是否能在超级线程中找到一个与你类似的问题的人。你的问题很可能不是独一无二的,没有其他人遇到过(除非它是与你的设计相关的错误,这种情况下你应该提交一个Gitbug)。

By now this spec has given you enough information to get working on the project. But to help you out some more, there are a couple of things you should be aware of:
到目前为止,这个规格已经给了你足够的信息来开始进行这个项目。但为了帮助你更多,有几件事情你应该知道:

Dealing with Files 处理文件

This project requires reading and writing of files. In order to do these operations, you might find the classes java.io.File and java.nio.file.Files helpful. Actually, you may find various things in the java.io and java.nio packages helpful. Be sure to read the gitlet.Utils package for other things we’ve written for you. If you do a little digging through all of these, you might find a couple of methods that will make the io portion of this project much easier! One warning: If you find yourself using readers, writers, scanners, or streams, you’re making things more complicated than need be.
这个项目需要读写文件。为了进行这些操作,你可能会发现 java.io.Filejava.nio.file.Files 类很有帮助。实际上,你可能会在 java.iojava.nio 包中找到各种有用的东西。一定要阅读 gitlet.Utils 包中我们为你编写的其他内容。如果你在所有这些内容中进行一些挖掘,你可能会找到一些方法,使得这个项目的io部分更容易!一个警告:如果你发现自己在使用读取器、写入器、扫描器或流,那么你正在使事情变得更加复杂。

Serialization Details 序列化细节

If you think about Gitlet, you’ll notice that you can only run one command every time you run the program. In order to successfully complete your version-control system, you’ll need to remember the commit tree across commands. This means you’ll have to design not just a set of classes to represent internal Gitlet structures during execution, but you’ll need an analogous representation as files within your .gitlet directories, which will carry across multiple runs of your program.
如果你考虑Gitlet,你会注意到每次运行程序只能运行一个命令。为了成功完成你的版本控制系统,你需要记住命令之间的提交树。这意味着你不仅需要设计一组类来表示执行过程中的内部Gitlet结构,还需要一个类似的表示作为你的目录中的文件,这些文件将在程序的多次运行中保持不变。

As indicated earlier, the convenient way to do this is to serialize the runtime objects that you will need to store permanently in files. The Java runtime does all the work of figuring out what fields need to be converted to bytes and how to do so.
如前所述,方便的方法是将您需要永久存储在文件中的运行时对象进行序列化。Java运行时会自动处理将哪些字段转换为字节以及如何进行转换的工作。

You’ve already done serialization in lab6 and so we will not repeat the information here. If you are still confused on some aspect of serialization, re-read the relevant portion of the lab6 spec and also look over your code.
你已经在实验6中完成了序列化,所以我们不会在这里重复信息。如果你对序列化的某个方面仍感到困惑,请重新阅读实验6规范的相关部分,并检查你的代码。

There is, however, one annoying subtlety to watch out for: Java serialization follows pointers. That is, not only is the object you pass into writeObject serialized and written, but any object it points to as well. If your internal representation of commits, for example, represents the parent commits as pointers to other commit objects, then writing the head of a branch will write all the commits (and blobs) in the entire subgraph of commits into one file, which is generally not what you want. To avoid this, don’t use Java pointers to refer to commits and blobs in your runtime objects, but instead use SHA-1 hash strings. Maintain a runtime map between these strings and the runtime objects they refer to. You create and fill in this map while Gitlet is running, but never read or write it to a file.
然而,有一个令人讨厌的细微之处需要注意:Java序列化遵循指针。也就是说,不仅会序列化和写入您传入的对象,还会序列化和写入它所指向的任何对象。例如,如果您的提交的内部表示将父提交表示为指向其他提交对象的指针,那么写入分支的头将会将整个提交子图中的所有提交(和blob)写入一个文件中,这通常不是您想要的。为了避免这种情况,不要在运行时对象中使用Java指针来引用提交和blob,而是使用SHA-1哈希字符串。在这些字符串和运行时对象之间维护一个运行时映射。您可以在Gitlet运行时创建和填充此映射,但不要将其读取或写入文件中。

You might find it convenient to have (redundant) pointers commits as well as SHA-1 strings to avoid the bother and execution time required to look them up each time. You can store such pointers in your objects while still avoiding having them written out by declaring them “transient”, as in
你可能会发现,同时拥有(冗余的)指针提交和SHA-1字符串是很方便的,以避免每次查找时所需的麻烦和执行时间。你可以将这些指针存储在对象中,同时通过将它们声明为“瞬态”来避免将它们写出。

    private transient MyCommitType parent1;

Such fields will not be serialized, and when back in and deserialized, will be set to their default values (null for reference types). You must be careful when reading the objects that contain transient fields back in to set the transient fields to appropriate values.
这些字段不会被序列化,当重新反序列化时,它们将被设置为默认值(引用类型为null)。在读取包含瞬态字段的对象时,您必须小心地将瞬态字段设置为适当的值。

Unfortunately, looking at the serialized files your program has produced with a text editor (for debugging purposes) would be rather unrevealing; the contents are encoded in Java’s private serialization encoding. We have therefore provided a simple debugging utility program you might find useful: gitlet.DumpObj. See the Javadoc comment on gitlet/DumpObj.java for details.
很遗憾,使用文本编辑器查看您的程序生成的序列化文件(用于调试目的)将会非常无法揭示;内容是使用Java的私有序列化编码进行编码的。因此,我们提供了一个简单的调试工具程序,您可能会发现有用: gitlet.DumpObj 。有关详细信息,请参阅 gitlet/DumpObj.java 上的Javadoc注释。

Testing 测试

You should read through this entire section, though a video is also avilable for your convenience.
您应该阅读整个部分,但也可以选择观看视频以方便您。

As usual, testing is part of the project. Be sure to provide your own integration tests for each of the commands, covering all the specified functionality. Also, feel free add any unit tests you’d like. We don’t provide any unit tests since unit tests are highly dependent on your implementation.
如往常一样,测试是项目的一部分。请确保为每个命令提供自己的集成测试,覆盖所有指定的功能。此外,随意添加任何您想要的单元测试。我们不提供任何单元测试,因为单元测试高度依赖于您的实现。

We have provided a testing program that makes it relatively easy to write integration tests: testing/tester.py. This interprets testing files with an .in extension. You may run all of the tests with the command
我们提供了一个测试程序,可以相对容易地编写集成测试: testing/tester.py 。这个程序会解释带有 .in 扩展名的测试文件。您可以使用以下命令运行所有的测试。

make check

If you’d like additional information on the failed tests, such as what your program is outputting, run:
如果您想获取有关失败测试的其他信息,例如您的程序的输出内容,请运行:

make check TESTER_FLAGS="--verbose"

If you’d like to run a single test, within the testing subdirectory, run the command
如果您想在 testing 子目录中运行单个测试,请运行以下命令

python3 tester.py --verbose FILE.in ...

where FILE.in ... is a list of specific .in files you want to check.
其中 FILE.in ... 是您想要检查的特定 .in 文件的列表。

CAREFUL RUNNING THIS COMMAND as it does not recompile your code. Every time you run a python command, you must first compile your code (via make).
小心运行此命令,因为它不会重新编译您的代码。每次运行一个 python 命令之前,您必须先编译您的代码(通过 make )。

The command 该命令

python3 tester.py --verbose --keep FILE.in

will, in addition, keep around the directory that tester.py produces so that you can examine its files at the point the tester script detected an error. If your test did not error, then the directory will still remain there with the final contents of everything.
此外,还会保留 tester.py 生成的目录,以便在测试脚本检测到错误时可以查看其文件。如果您的测试没有出错,那么该目录仍将保留,并包含最终的所有内容。

In effect, the tester implements a very simple domain-specific language (DSL) that contains commands to
实际上,测试器实现了一个非常简单的领域特定语言(DSL),其中包含了一些命令

python3 testing/tester.py

(with no operands, as shown) will provide a message documenting this language. We’ve provided some examples in the directory testing/samples. Don’t put your own tests in that subdirectory; place them somewhere distinct so you don’t get confused with our tests vs your tests (which may be buggy!). Put all your .in files in another folder called student_tests within the testing directory. In the skeleton, this folder is blank.
(如所示,没有操作数)将提供一个记录此语言的消息。我们在目录 testing/samples 中提供了一些示例。不要将您自己的测试放在该子目录中;将它们放在一个不同的地方,以免将我们的测试与您的测试(可能有错误)混淆。将所有的 .in 文件放在 testing 目录中的另一个名为 student_tests 的文件夹中。在骨架中,这个文件夹是空的。

We’ve added a few things to the Makefile to adjust for differences in people’s setups. If your system’s command for invoking Python 3 is simply python, you can still use our makefile unchanged by using
我们在Makefile中添加了一些内容,以适应人们设置的差异。如果您的系统调用Python 3的命令只是 python ,您仍然可以使用我们的makefile而不需要任何更改。

make PYTHON=python check

You can pass additional flags to tester.py with, for example:
您可以通过以下方式向 tester.py 传递附加标志:

make TESTER_FLAGS="--keep --verbose"

Testing on the Staff Solution
员工解决方案的测试

As of Sunday February 28th, there is now a way for you to use the staff solution to verify your understanding of commands as well as verify your own tests! The guide is here.
截至2月28日星期日,现在有一种方法可以使用员工解决方案来验证您对命令的理解以及验证您自己的测试!指南在这里。

Understanding Integration Tests
理解集成测试

The first thing we’ll ask for in Gitbugs and when you come to receive help in Office Hours is a test that you’re failing, so it’s paramount that you learn to write tests in this project. We’ve done a lot of work to make this as painless as possible, so please take the time to read through this section so you can understand the provided tests and write good tests yourself.
在Gitbugs中,我们将首先要求您提供一个您未通过的测试,因此学会在这个项目中编写测试非常重要。我们已经做了很多工作,使这个过程尽可能简单,所以请花时间阅读本节内容,以便您能够理解提供的测试并自己编写好的测试。

The integration tests are of similar format to those from Capers. If you don’t know how the Capers integration tests (i.e. the .in files) work, then read that section from the capers spec first.
集成测试的格式与Capers的类似。如果你不知道Capers的集成测试(即 .in 文件)是如何工作的,请先阅读Capers规范中的该部分。

The provided tests are hardly comprehensive, and you’ll definitely need to write your own tests to get a full score on the project. To write a test, let’s first understand how this all works.
提供的测试远非全面,你肯定需要编写自己的测试来获得项目的满分。为了编写测试,让我们首先了解这一切是如何工作的。

Here is the structure of the testing directory:
这是 testing 目录的结构

.
├── Makefile
├── student_tests                    <==== Your .in files will go here
├── samples                          <==== Sample .in files we provide
│   ├── test01-init.in               <==== An example test
│   ├── test02-basic-checkout.in
│   ├── test03-basic-log.in
│   ├── test04-prev-checkout.in
│   └── definitions.inc
├── src                              <==== Contains files used for testing
│   ├── notwug.txt
│   └── wug.txt
├── runner.py                        <==== Script to help debug your program
└── tester.py                        <==== Script that tests your program

Just like Capers, these tests work by creating a temporary directory within the testing directory and running the commands specified by a .in file. If you use the --keep flag, this temporary directory will remain after the test finishes so you can inspect it.
就像Capers一样,这些测试通过在 testing 目录中创建一个临时目录并运行由 .in 文件指定的命令来工作。如果使用 --keep 标志,测试完成后临时目录将保留下来,以便您进行检查。

Unlike Capers, we’ll need to deal with the contents of files in our working directory. So in this testing folder, we have an additional folder called src. This directory stores many pre-filled .txt files that have particular contents we need. We’ll come back to this later, but for now just know that src stores actual file contents. samples has the .in files of the sample tests (which are the checkpoint tests). When you create your own tests, you should add them to the student_tests folder which is initially empty in the skeleton.
与Capers不同,我们需要处理工作目录中文件的内容。所以在这个 testing 文件夹中,我们有一个额外的文件夹叫做 src 。这个目录存储了许多预填充的 .txt 文件,这些文件具有我们需要的特定内容。我们稍后会回到这个问题,但现在只需要知道 src 存储实际的文件内容。 samples 有示例测试的 .in 文件(即检查点测试)。当你创建自己的测试时,应将它们添加到 student_tests 文件夹中,该文件夹在骨架中最初是空的。

The .in files have more functions in Gitlet. Here is the explanation straight from the tester.py file:
.in 文件在Gitlet中具有更多的功能。以下是来自 tester.py 文件的解释。

# ...  A comment, producing no effect.
I FILE Include.  Replace this statement with the contents of FILE,
      interpreted relative to the directory containing the .in file.
C DIR  Create, if necessary, and switch to a subdirectory named DIR under
      the main directory for this test.  If DIR is missing, changes
      back to the default directory.  This command is principally
      intended to let you set up remote repositories.
T N    Set the timeout for gitlet commands in the rest of this test to N
      seconds.
+ NAME F
      Copy the contents of src/F into a file named NAME.
- NAME
      Delete the file named NAME.
> COMMAND OPERANDS
LINE1
LINE2
...
<<<
      Run gitlet.Main with COMMAND ARGUMENTS as its parameters.  Compare
      its output with LINE1, LINE2, etc., reporting an error if there is
      "sufficient" discrepency.  The <<< delimiter may be followed by
      an asterisk (*), in which case, the preceding lines are treated as
      Python regular expressions and matched accordingly. The directory
      or JAR file containing the gitlet.Main program is assumed to be
      in directory DIR specifed by --progdir (default is ..).
= NAME F
      Check that the file named NAME is identical to src/F, and report an
      error if not.
* NAME
      Check that the file NAME does not exist, and report an error if it
      does.
E NAME
      Check that file or directory NAME exists, and report an error if it
      does not.
D VAR "VALUE"
      Defines the variable VAR to have the literal value VALUE.  VALUE is
      taken to be a raw Python string (as in r"VALUE").  Substitutions are
      first applied to VALUE.

Don’t worry about the Python regular expressions thing mentioned in the above description: we’ll show you that it’s fairly straightforward and even go through an example of how to use it.
不要担心上述描述中提到的Python正则表达式的事情:我们将向您展示它非常简单,甚至会通过一个示例来演示如何使用它。

Let’s walk through a test to see what happens from start to finish. Let’s examine test02-basic-checkout.in.
让我们来进行一项测试,看看从开始到结束会发生什么。让我们来检查 test02-basic-checkout.in

Example test 示例测试

When we first run this test, a temporary directory gets created that is initially empty. Our directory structure is now:
当我们首次运行此测试时,会创建一个初始为空的临时目录。我们的目录结构现在是:

.
├── Makefile
├── student_tests
├── samples
│   ├── test01-init.in
│   ├── test02-basic-checkout.in
│   ├── test03-basic-log.in
│   ├── test04-prev-checkout.in
│   └── definitions.inc
├── src
│   ├── notwug.txt
│   └── wug.txt
├── test02-basic-checkout_0          <==== Just created
├── runner.py
└── tester.py

This temporary directory is the Gitlet repository that will be used for this execution of the test, so we will add things there and run all of our Gitlet commands there as well. If you ran the test a second time without deleting the directory, it’ll create a new directory called test02-basic-checkout_1, and so on. Each execution of a test uses it’s own directory, so don’t worry about tests interfering with each other as that cannot happen.
这个临时目录是用于执行测试的Gitlet存储库,因此我们将在其中添加内容并在其中运行所有Gitlet命令。如果您在不删除目录的情况下再次运行测试,它将创建一个名为 test02-basic-checkout_1 的新目录,依此类推。每次测试的执行都使用自己的目录,所以不必担心测试之间的干扰,因为这是不可能发生的。

The first line of the test is a comment, so we ignore it.
测试的第一行是一个注释,所以我们忽略它。

The next section is:
下一部分是:

> init
<<<

This shouldn’t have any output as we can tell by this section not having any text between the first line with > and the line with <<<. But, as we know, this should create a .gitlet folder. So our directory structure is now:
这应该没有任何输出,因为我们可以通过这一部分没有在第一行 > 和第二行 <<< 之间有任何文本来判断。但是,正如我们所知,这应该创建一个 .gitlet 文件夹。所以我们的目录结构现在是:

.
├── Makefile
├── student_tests
├── samples
│   ├── test01-init.in
│   ├── test02-basic-checkout.in
│   ├── test03-basic-log.in
│   ├── test04-prev-checkout.in
│   └── definitions.inc
├── src
│   ├── notwug.txt
│   └── wug.txt
├── test02-basic-checkout_0
│   └── .gitlet                     <==== Just created
├── runner.py
└── tester.py

The next section is:
下一部分是:

+ wug.txt wug.txt

This line uses the + command. This will take the file on the right-hand side from the src directory and copy its contents to the file on the left-hand side in the temporary directory (creating it if it doesn’t exist). They happen to have the same name, but that doesn’t matter since they’re in different directories. After this command, our directory structure is now:
这行使用了 + 命令。它将右侧目录中的文件复制到临时目录中的左侧文件中(如果不存在,则创建)。它们恰好具有相同的名称,但这并不重要,因为它们位于不同的目录中。执行此命令后,我们的目录结构如下:

.
├── Makefile
├── student_tests
├── samples
│   ├── test01-init.in
│   ├── test02-basic-checkout.in
│   ├── test03-basic-log.in
│   ├── test04-prev-checkout.in
│   └── definitions.inc
├── src
│   ├── notwug.txt
│   └── wug.txt
├── test02-basic-checkout_0
│   ├── .gitlet
│   └── wug.txt                     <==== Just created
├── runner.py
└── tester.py

Now we see what the src directory is used for: it contains file contents that the tests can use to set up the Gitlet repository however you wants. If you want to add special contents to a file, you should add those contents to an appropriately named file in src and then use the same + command as we have here. It’s easy to get confused with the order of arguments, so make sure the right-hand side is referencing the file in the src directory, and the left-hand side is referencing the file in the temporary directory.
现在我们看到了 src 目录的用途:它包含了测试可以使用的文件内容,用于设置Gitlet存储库的任何方式。如果您想向文件添加特殊内容,应将这些内容添加到 src 中的相应命名文件中,然后使用与此处相同的 + 命令。参数的顺序很容易混淆,所以请确保右侧引用的是 src 目录中的文件,而左侧引用的是临时目录中的文件。

The next section is:
下一部分是:

> add wug.txt
<<<

As you can see, there should be no output. The wug.txt file is now staged for addition in the temporary directory. At this point, your directory structure will likely change within the test02-basic-checkout_0/.gitlet directory since you’ll need to somehow persist the fact that wug.txt is staged for addition.
如您所见,不应有任何输出。文件 wug.txt 现在已经在临时目录中准备添加。此时,您的目录结构可能会在 test02-basic-checkout_0/.gitlet 目录中发生变化,因为您需要以某种方式保留 wug.txt 已准备添加的事实。

The next section is:
下一部分是:

> commit "added wug"
<<<

And, again, there is no output, and, again, your directory strcuture within .gitlet might change.
而且,再次没有输出,而且,再次,你的 .gitlet 内的目录结构可能会改变。

The next section is:
下一部分是:

+ wug.txt notwug.txt

Since wug.txt already exists in our temporary directory, its contents changes to be whatever was in src/notwug.txt.
由于 wug.txt 已经存在于我们的临时目录中,其内容将更改为 src/notwug.txt 中的内容。

The next section is
下一部分是

> checkout -- wug.txt
<<<

Which, again, has no output. However, it should change the contents of wug.txt in our temporary directory back to its original contents which is exactly the contents of src/wug.txt. The next command is what asserts that:
然后,再次没有输出。然而,它应该将我们临时目录中 wug.txt 的内容更改回其原始内容,即 src/wug.txt 的内容。下一个命令就是断言:

= wug.txt wug.txt

This is an assertion: if the file on the left-hand side (again, this is in the temporary directory) doesn’t have the exact contents of the file on the right-hand side (from the src directory), the testing script will error and say your file contents are not correct.
这是一个断言:如果左侧的文件(再次强调,这是在临时目录中)与右侧的文件(来自 src 目录)的内容不完全相同,测试脚本将报错并指出您的文件内容不正确。

There are two other assertion commands available to you:
有另外两个可用的断言命令

E NAME

Will assert that there exists a file/folder named NAME in the temporary directory. It doesn’t check the contents, only that it exists. If no file/folder with that name exists, the test will fail.
将断言在临时目录中存在一个名为 NAME 的文件/文件夹。它不检查内容,只检查是否存在。如果不存在该名称的文件/文件夹,测试将失败。

* NAME

Will assert that there does NOT exist a file/folder named NAME in the temporary directory. If there does exist a file/folder with that name, the test will fail.
将断言在临时目录中不存在名为 NAME 的文件/文件夹。如果存在该名称的文件/文件夹,则测试将失败。

That happened to be the last line of the test, so the test finishes. If the --keep flag was provided, the temporary directory will remain, otherwise it will be deleted. You might want to keep it if you suspect your .gitlet directory is not being properly setup or there is some issue with persistence.
那恰好是测试的最后一行,所以测试结束。如果提供了 --keep 标志,临时目录将保留,否则将被删除。如果您怀疑您的 .gitlet 目录没有正确设置或存在某些持久性问题,您可能希望保留它。

Setup for a test
测试设置

As you’ll soon discover, there can be a lot of repeated setup to test a particular command: for example, if you’re testing the checkout command you need to:
如您很快会发现的那样,测试特定命令可能需要大量重复的设置:例如,如果您正在测试 checkout 命令,您需要:

  1. Initialize a Gitlet Repository
    初始化一个Gitlet仓库
  2. Create a commit with a file in some version (v1)
    在某个版本(v1)中创建一个包含文件的提交
  3. Create another commit with that file in some other version (v2)
    使用另一个版本(v2)创建包含该文件的另一个提交
  4. Checkout that file to v1
    将该文件检出到v1版本

And perhaps even more if you want to test with files that were untracked in the second commit but tracked in the first.
如果你想测试第二次提交中未跟踪但在第一次提交中已跟踪的文件,可能会有更多的情况。

So the way you can save yourself time is by adding all that setup in a file and using the I command. Say we do that here:
所以你可以节省时间的方法是将所有的设置添加到一个文件中,并使用 I 命令。在这里我们这样做:

# Initialize, add, and commit a file.
> init
<<<
+ a.txt wug.txt
> add a.txt
<<<
> commit "a is a wug"
<<<

We should place this file with the rest of the tests in the samples directory, but with a file extension .inc, so maybe we name it samples/commit_setup.inc. If we gave it the file extension .in, our testing script will mistake it for a test and try to run it individually. Now, in our actual test, we simply use the command:
我们应该将这个文件与其他测试文件放在 samples 目录中,但是文件扩展名应该是 .inc ,所以我们可以将其命名为 samples/commit_setup.inc 。如果我们给它文件扩展名 .in ,我们的测试脚本会误将其视为一个独立的测试并尝试运行它。现在,在我们实际的测试中,我们只需使用以下命令:

I commit_setup.inc

This will have the testing script run all of the commands in that file and keep the temporary directory it creates. This keeps your tests relatively short and thus easier to read.
这将使测试脚本运行文件中的所有命令,并保留它创建的临时目录。这样可以使您的测试相对较短,从而更容易阅读。

We’ve included one .inc file called definitions.inc that will set up patterns for your convenience. Let’s understand what patterns are.
我们已经包含了一个名为 .inc 的文件,它将为您方便地设置模式。让我们了解一下什么是模式。

Pattern matching output 模式匹配输出

The most confusing part of testing is the output for something like log. There are a few reasons why:
测试中最令人困惑的部分是像 log 这样的输出。有几个原因:

  1. The commit SHA will change as you modify your code and hash more things, so you would have to continually modify your test to keep up with the changes to the SHA.
    提交的SHA会随着您修改代码和哈希更多内容而改变,因此您需要不断修改测试以跟上SHA的变化。
  2. Your date will change every time since time only moves forwards.
    你的日期会每次改变,因为时间只会向前流动。
  3. It makes the tests very long.
    这使得测试非常漫长。

We also don’t really care the exact text: just that there is some SHA there and something with the right date format. For this reason, our tests use pattern matching.
我们也不太关心具体的文本内容:只要有一些SHA和符合正确日期格式的内容即可。因此,我们的测试使用模式匹配。

This is not a concept you will need to understand, but at a high level we define a pattern for some text (i.e. a commit SHA) and then just check that the output has that pattern (without caring about the actual letters and numbers).
这不是一个你需要理解的概念,但在高层次上,我们定义了一种模式用于一些文本(例如提交的SHA),然后只需检查输出是否符合该模式(不关心实际的字母和数字)。

Here is how you’d do that for the output of log and check that it matches the pattern:
这是您对 log 的输出进行匹配模式的方法:

# First "import" the pattern defintions from our setup
I definitions.inc
# You would add your lines here that create commits with the
# specified messages. We'll omit this for this example.
> log
===
${COMMIT_HEAD}
added wug

===
${COMMIT_HEAD}
initial commit

<<<*

The section we see is the same as a normal Gitlet command, except it ends in <<<* which tells the testing script to use patterns. The patterns are enclosed in ${PATTERN_NAME}.
我们看到的部分与普通的Gitlet命令相同,只是以 <<<* 结尾,这告诉测试脚本使用模式。模式被包含在 ${PATTERN_NAME} 中。

All the patterns are defined in samples/definitions.inc. You don’t need to understand the actual pattern, just the thing it matches. For example, HEADER matches the header of a commit which should look something like:
所有的模式都在 samples/definitions.inc 中定义。你不需要理解实际的模式,只需要知道它所匹配的内容。例如, HEADER 匹配一个提交的标题,应该是类似这样的:

commit fc26c386f550fc17a0d4d359d70bae33c47c54b9

That’s just some random commit SHA.
这只是一个随机的提交SHA。

So when we create the expected output for this test, we’ll need to know how many entries are in this log and what the commit messages are.
因此,当我们为这个测试创建预期的输出时,我们需要知道日志中有多少条目以及提交消息是什么。

You can do similar things for the status command:
您可以对 status 命令执行类似的操作

I definitions.inc
# Add commands here to setup the status. We'll omit them here.
> status
=== Branches ===
\*master

=== Staged Files ===
g.txt

=== Removed Files ===

=== Modifications Not Staged For Commit ===

=== Untracked Files ===
${ARBLINES}

<<<*

The pattern we used here is ARBLINES which is arbitrary lines. If you actually care what is untracked, then you can add that here without the pattern, but perhaps we’re more interested in seeing g.txt staged for addition.
我们在这里使用的模式是 ARBLINES ,即任意行。如果你真的关心未跟踪的内容,那么你可以在这里添加,而不使用模式,但也许我们更感兴趣的是看到 g.txt 已准备添加。

Notice the \* on the branch master. Recall that in the status command, you should prefix the HEAD branch with a *. If you use a pattern, you’ll need to replace this * with a \* in the expected output. The reason is out of the scope of the class, but it is called “escaping” the asterisk. If you don’t use a pattern (i.e. your command ends in <<< not <<<*, then you can use the * without the \).
注意分支上的 \* 。回想一下,在 status 命令中,你应该在HEAD分支前加上 * 。如果你使用一个模式,你需要将预期输出中的 * 替换为 \* 。原因超出了课程范围,但它被称为“转义”星号。如果你不使用模式(即你的命令以 <<< 而不是 <<<* 结尾),那么你可以使用 * 而不需要 \

The final thing you can do with these patterns is “save” a matched portion. Warning: this seems like magic and we don’t care at all if you understand how this works, just know that it does and it is available to you. You can copy and paste the relevant part from our provided tests so you don’t need to worry too much about making these from scratch. With that out of the way, let’s see what this is.
你可以使用这些模式的最后一项功能是“保存”匹配的部分。警告:这似乎像是魔法,我们并不在乎你是否理解它的工作原理,只要知道它确实可以使用并且对你可用即可。你可以从我们提供的测试中复制和粘贴相关部分,所以你不需要太担心从头开始创建这些。既然这样说了,让我们看看这是什么。

If you’re doing a checkout command, you need to use the SHA identifier to specify which commit to checkout to/from. But remember we used patterns, so we don’t actually know the SHA identifier at the time of creating the test. That is problematic. We’ll use test04-prev-checkout.in to see how you can “capture” or “save” the SHA:
如果你正在执行一个 checkout 命令,你需要使用SHA标识符来指定要检出/检入的提交。但是请记住,我们使用了模式,所以在创建测试时实际上并不知道SHA标识符。这是有问题的。我们将使用 test04-prev-checkout.in 来看看如何“捕获”或“保存”SHA:

I definitions.inc
# Each ${COMMIT_HEAD} captures its commit UID.
# Not shown here, but the test sets up the log by making many commits
# with specific messages.
> log
===
${COMMIT_HEAD}
version 2 of wug.txt

===
${COMMIT_HEAD}
version 1 of wug.txt

===
${COMMIT_HEAD}
initial commit

<<<*

This will set up the UID (SHA) to be captured after the log command. So right after this command runs, we can use the D command to define the UIDs to variables:
这将在 log 命令之后设置UID(SHA)以被捕获。因此,在此命令运行后,我们可以使用 D 命令将UID定义为变量:

# UID of second version
D UID2 "${1}"
# UID of first version
D UID1 "${2}"

Notice how the numbering is backwards: the numbering begins at 1 and starts at the top of the log. That is why the current version (i.e. second version) is defined as "${1}". We don’t care about the initial commit, so we don’t bother capturing it’s UID.
请注意编号是倒过来的:编号从1开始,并从日志的顶部开始。这就是为什么当前版本(即第二个版本)被定义为 "${1}" 。我们不关心初始提交,所以我们不会去捕获它的UID。

Now we can use that definition to checkout to that captured SHA:
现在我们可以使用该定义来检出捕获的SHA

> checkout ${UID1} -- wug.txt
<<<

And now you can make your assertions to ensure the checkout was successful.
现在,您可以进行断言以确保结账成功。

Testing conclusion 测试结论

There are many more complex things you can do with our testing script, but this is enough to write very good tests. You should use our provided tests as an example to get started, and also feel free to discuss on Ed high level ideas of how to test things. You may also share your .in files, but please make sure they’re correct before posting them and add comments so other students and staff can see what is going on.
使用我们的测试脚本,您可以做更多复杂的事情,但这已足够编写非常好的测试。您应该使用我们提供的测试作为示例来入门,并且可以自由地讨论如何测试事物的高级想法。您还可以分享您的 .in 文件,但请确保在发布之前它们是正确的,并添加注释以便其他学生和员工能够看到发生了什么。

Debugging Integration Tests
调试集成测试

Recall from Lab 6 that debugging integration tests is a bit different with the new setup. The runner.py script will work just as it did for Capers, so you should read through that section in the Lab 6 spec and watch the video linked there. Here we describe strategies to debug:
回想一下实验6中,使用新的设置进行集成测试的调试有些不同。 runner.py

Finding the right execution to debug
找到正确的执行方式来调试

Each test runs your program multiple times, and each one of them has the potential to introduce a bug. The first priority is to identify the right execution of the program that introduces the bug. What we mean by this: imagine you’re failing a test that checks the status command. Say that the output differs by just one file: you say it’s untracked, but the test says it should be staged for addition. This does not mean the status command has a bug. It’s possible that the status command is buggy, but not guaranteed. It could be that your add command didn’t properly persist the fact that a file has been staged for addition! If that is the case, then even with a fully functioning status command, your program would error.
每个测试都会多次运行您的程序,每次运行都有可能引入一个错误。首要任务是确定引入错误的程序执行方式。我们所指的是:假设您未通过检查 status 命令的测试。假设输出只有一个文件不同:您认为它是未跟踪的,但测试说它应该被标记为待添加。这并不意味着 status 命令有错误。可能 status 命令有错误,但不能保证。可能是您的 add 命令没有正确地保存文件被标记为待添加的事实!如果是这种情况,即使有一个完全正常运行的 status 命令,您的程序也会出错。

So finding the right (i.e. buggy) execution of the program is very important: how do we do that? You step through every single execution of the program using the runner.py script, and after every execution you look at your temporary directory to make sure everything has been written to a file correctly. This will be harder for serialized objects since, as we know, their contents will be a stream of unintelligable bytes: for serialized objects you can simply check that at the time of serialization they have the correct contents. You may even find that you never serialized it!
因此,找到正确(即有错误)的程序执行非常重要:我们该如何做到这一点?您可以使用脚本逐步执行程序的每个执行,并在每次执行后查看临时目录,以确保所有内容都已正确写入文件。对于序列化对象来说,这将更加困难,因为我们知道它们的内容将是一串无法理解的字节:对于序列化对象,您只需检查在序列化时它们是否具有正确的内容。您甚至可能会发现您从未对其进行过序列化!

Eventually, you’ll find the bug. If you cannot, then that is when you can come to Office Hours or post a Gitbug. Be warned: we can only spend 10 minutes with each student in Office Hours, so if you have a nasty bug that you think would take a TA more than 10 minutes, then you should instead submit a Gitbug with as much information as possible. The better your Gitbug, the better/faster your response will be. Don’t forget to update your design doc: remember we will reject Gitbugs that do not have an up-to-date or complete design document.
最终,你会找到这个错误。如果你找不到,那就是你可以来办公时间或发布一个Gitbug的时候了。请注意:我们在办公时间里只能花10分钟与每个学生交流,所以如果你有一个棘手的错误,认为TA需要超过10分钟的时间来解决,那么你应该提交一个尽可能提供详细信息的Gitbug。你的Gitbug越好,回复就会越好/越快。不要忘记更新你的设计文档:记住,我们会拒绝那些没有最新或完整设计文档的Gitbug。

Going Remote (Extra Credit)
远程办公(额外学分)

This project is all about mimicking git’s local features. These are useful because they allow you to backup your own files and maintain multiple versions of them. However, git’s true power is really in its remote features, allowing collaboration with other people over the internet. The point is that both you and your friend could be collaborating on a single code base. If you make changes to the files, you can send them to your friend, and vice versa. And you’ll both have access to a shared history of all the changes either of you have made.
这个项目的目的是模仿git的本地功能。这些功能非常有用,因为它们允许你备份自己的文件并维护多个版本。然而,git的真正强大之处在于它的远程功能,允许你与其他人在互联网上进行协作。关键是你和你的朋友可以共同合作一个代码库。如果你对文件进行了更改,你可以将它们发送给你的朋友,反之亦然。而且你们两个都可以访问到彼此所做的所有更改的共享历史。

To get extra credit, implement some basic remote commands: namely add-remote, rm-remote, push, fetch, and pull You will get 64 extra-credit points for completing them. Don’t attempt or plan for extra credit until you have completed the rest of the project.
为了获得额外学分,请实现一些基本的远程命令:即 add-remoterm-remotepushfetchpull 。完成它们将获得64个额外学分。在完成项目的其余部分之前,请不要尝试或计划额外学分。

Depending on how flexibly you have designed the rest of the project, 64 extra-credit points may not be worth the amount of effort it takes to do this section. We’re certainly not expecting everyone to do it. Our priority will be in helping students complete the main project; if you’re doing the extra credit, we expect you to be able to stand on your own a little bit more than most students.
根据您在项目的其他部分设计的灵活性,额外的64个学分可能不值得花费这部分工作所需的努力。我们当然不期望每个人都去做这个。我们的重点将是帮助学生完成主要项目;如果您正在做额外的学分,我们希望您能够比大多数学生更加独立一些。

The Commands 命令

A few notes about the remote commands:
关于远程命令的几点说明:

So now let’s go over the commands:
现在让我们来看一下命令:

add-remote 添加远程

rm-remote 删除远程

push 

fetch 取回

pull 

I. Things to Avoid
一. 需要避免的事情

There are few practices that experience has shown will cause you endless grief in the form of programs that don’t work and bugs that are very hard to find and sometimes not repeatable (“Heisenbugs”).
有一些经验表明会给你带来无尽痛苦的做法,表现为程序无法运行和难以找到的错误(“Heisenbugs”)。

  1. Since you are likely to keep various information in files (such as commits), you might be tempted to use apparently convenient file-system operations (such as listing a directory) to sequence through all of them. Be careful. Methods such as File.list and File.listFiles produce file names in an undefined order. If you use them to implement the log command, in particular, you can get random results.
    由于您可能会在文件中保存各种信息(例如提交),您可能会倾向于使用看似方便的文件系统操作(例如列出目录)来顺序处理它们。但请小心。诸如 File.listFile.listFiles 等方法会以未定义的顺序生成文件名。特别是如果您使用它们来实现 log 命令,可能会得到随机结果。

  2. Windows users especially should beware that the file separator character is / on Unix (or MacOS) and ‘\’ on Windows. So if you form file names in your program by concatenating some directory names and a file name together with explicit /s or \s, you can be sure that it won’t work on one system or the other. Java provides a system-dependent file separator character (System.getProperty("file.separator")), or you can use the multi-argument constructors to File.
    Windows用户尤其要注意,在Unix(或MacOS)上文件分隔符是 / ,而在Windows上是'\'。因此,如果您在程序中通过将一些目录名和文件名与明确的 / s或 \ s连接起来来形成文件名,您可以确保它在其中一个系统上无法工作。Java提供了一个系统相关的文件分隔符字符( System.getProperty("file.separator") ),或者您可以使用多参数构造函数来 File
  3. Be careful using a HashMap when serializing! The order of things within the HashMap is non-deterministic. The solution is to use a TreeMap which will always have the same order. More details here
    使用序列化时要小心!在 HashMap 中的事物顺序是不确定的。解决方案是使用 TreeMap ,它的顺序始终相同。更多详细信息请参见此处。

J. Acknowledgments J. 致谢

Thanks to Alicia Luengo, Josh Hug, Sarah Kim, Austin Chen, Andrew Huang, Yan Zhao, Matthew Chow, especially Alan Yao, Daniel Nguyen, and Armani Ferrante for providing feedback on this project. Thanks to git for being awesome.
感谢Alicia Luengo、Josh Hug、Sarah Kim、Austin Chen、Andrew Huang、Yan Zhao、Matthew Chow,特别感谢Alan Yao、Daniel Nguyen和Armani Ferrante对这个项目提供的反馈。感谢git的出色表现。

This project was largely inspired by [this][Nilsson Article] excellent article by Philip Nilsson.
这个项目在很大程度上受到Philip Nilsson的[这篇][Nilsson Article]优秀文章的启发。

This project was created by Joseph Moghadam. Modifications for Fall 2015, Fall 2017, and Fall 2019 by Paul Hilfinger.
该项目由Joseph Moghadam创建。Paul Hilfinger对2015年秋季、2017年秋季和2019年秋季进行了修改。