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
的存储库等)。请忽略这些,因为它们对你本学期没有提供任何有用的信息。作业的实际内容没有改变。
- Git intro - Part 1
Git介绍 - 第1部分 - Git intro - Part 2
Git简介 - 第2部分 - Live lecture 12 直播讲座12
- Gitlet intro playlist Gitlet介绍播放列表
- Merge overview and example
合并概述和示例 - Branching overview and example
分支概述和示例 - Testing 测试
- Designing Persistence (written notes)
设计持久化(书面笔记) - Spring 2021 Office Hours Presentations:
2021年春季办公时间演示:- Getting started on Gitlet
开始使用Gitlet - Designing Gitlet 设计Gitlet
- Merge 合并
- Getting started on Gitlet
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主要支持的功能有:
-
Saving the contents of entire directories of files. In Gitlet, this is called committing, and the saved contents themselves are called commits.
保存整个目录文件的内容。在Gitlet中,这被称为提交,而保存的内容本身被称为提交。 -
Restoring a version of one or more files or entire commits. In Gitlet, this is called checking out those files or that commit.
恢复一个或多个文件或整个提交的版本。在Gitlet中,这被称为检出这些文件或该提交。 -
Viewing the history of your backups. In Gitlet, you view this history in something called the log.
查看您备份的历史记录。在Gitlet中,您可以在一个称为日志的地方查看此历史记录。 -
Maintaining related sequences of commits, called branches.
维护相关的提交序列,称为分支。 -
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的项目,我们向其中添加一些文本,并进行提交。然后我们修改文件并提交这些更改。然后我们再次修改文件,并再次提交更改。现在我们已经保存了这个文件的三个版本,每个版本都比之前晚。我们可以这样可视化这些提交:
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指针)的东西。头指针用来跟踪我们当前在链表中的位置。通常情况下,当我们进行提交时,头指针会保持在链表的前端,表示最新的提交反映了文件的当前状态。
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的状态(从技术上讲,这是重置命令,您将在规范中看到)。我们将头指针移回以显示这一点。
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允许你保存这两个版本,并随时在它们之间切换。以下是我们图片中可能看到的情况:
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:
它不再是一个链表了,更像是一棵树。我们将这个东西称为提交树。按照这个比喻,每个独立的版本被称为树的一个分支。你可以分别开发每个版本:
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区分了几种不同类型的对象。对于我们的目的来说,重要的是
- blobs: The saved contents of files. Since Gitlet saves many versions of
files, a single file might correspond to multiple blobs: each being
tracked in a different commit.
文件的保存内容。由于Gitlet保存了许多文件的版本,一个单独的文件可能对应多个blobs:每个blobs在不同的提交中被跟踪。 - trees: Directory structures mapping names to references to blobs and
other trees (subdirectories).
树:将名称映射到对blob和其他树(子目录)的引用的目录结构。 - commits: Combinations of log messages,
other metadata (commit date, author,
etc.), a reference to a tree, and references to
parent commits.
The repository also maintains a mapping from branch heads to references to
commits, so that certain important commits have symbolic names.
提交:日志消息、其他元数据(提交日期、作者等)、对树的引用以及对父提交的引用的组合。仓库还维护了从分支头到提交引用的映射,以便某些重要的提交具有符号名称。
Gitlet simplifies from Git still further by
Gitlet进一步简化了Git
- Incorporating trees into commits and not dealing with subdirectories (so
there will be one
“flat” directory of plain files for each repository).
将树结构合并到提交中,不处理子目录(因此每个仓库将有一个“扁平”的目录,其中包含普通文件)。 - Limiting ourselves to merges that reference two parents (in real Git, there
can be any number of parents.)
限制我们只合并引用两个父节点的情况(在真实的Git中,可以有任意数量的父节点)。 - Having our metadata consist only of a timestamp and log message.
A commit, therefore, will consist of a log message,
timestamp, a mapping of file names to blob references, a parent
reference, and (for merges) a second parent reference.
我们的元数据仅包含时间戳和日志消息。因此,一个提交将包括日志消息、时间戳、文件名到blob引用的映射、一个父引用,以及(对于合并)第二个父引用。
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值的库类,因此您不必处理实际的算法。您只需要确保正确标记所有对象。特别是,这涉及
- Including all metadata and references when hashing a commit.
在对提交进行哈希时,包括所有元数据和引用。 - Distinguishing somehow between hashes for commits and hashes for blobs. A
good way of doing this involves a well-thought out directory structure
within the
.gitlet
directory. Another way to do so is to hash in an extra word for each object that has one value for blobs and another for commits.
在提交的哈希和数据块的哈希之间以某种方式进行区分。一个好的方法是在.gitlet
目录中使用经过深思熟虑的目录结构。另一种方法是为每个对象添加一个额外的单词进行哈希,其中一个值用于数据块,另一个值用于提交。
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
的相同版本。您的提交类将以某种方式存储此图表显示的所有信息:仔细选择内部数据结构将使实现变得更容易或更困难,因此您应该花时间计划和思考存储所有内容的最佳方式。
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.
我们还添加了两个建议的类: Commit
和 Repository
,以帮助您入门。当然,您可以编写额外的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中的 CapersRepository
和 Main
类,了解我们推荐的结构示例。
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命令作为命令行参数时,它应该如何反应。但在我们逐个命令进行分解之前,这里有一些整体指导方针,整个项目应该满足:
-
In order for Gitlet to work, it will need a place to store old copies of files and other metadata. All of this stuff must be stored in a directory called
.gitlet
, just as this information is stored in directory.git
for the real git system (files with a.
in front are hidden files. You will not be able to see them by default on most operating systems. On Unix, the commandls -a
will show them.) A Gitlet system is considered “initialized” in a particular location if it has a.gitlet
directory there. Most Gitlet commands (except for theinit
command) only need to work when used from a directory where a Gitlet system has been initialized–i.e. a directory that has a.gitlet
directory. The files that aren’t in your.gitlet
directory (which are copies of files from the repository that you are using and editing, as well as files you plan to add to the repository) are referred to as the files in your working directory.
为了使Gitlet正常工作,它需要一个存储旧文件副本和其他元数据的位置。所有这些东西必须存储在一个名为.gitlet
的目录中,就像真正的git系统中的信息存储在.git
目录中一样(以.
开头的文件是隐藏文件,在大多数操作系统上默认情况下无法看到它们。在Unix上,使用ls -a
命令可以显示它们)。只有在已经初始化了Gitlet系统的目录中(即具有.gitlet
目录的目录)才被认为是“初始化”的。大多数Gitlet命令(除了init
命令)只需要在已经初始化了Gitlet系统的目录中使用时才能正常工作。不在.gitlet
目录中的文件(这些文件是您正在使用和编辑的存储库的副本,以及您计划添加到存储库的文件)被称为您工作目录中的文件。 -
Most commands have runtime or memory usage requirements. You must follow these. Some of the runtimes are described as constant “relative to any significant measure”. The significant measures are: any measure of number or size of files, any measure of number of commits. You can ignore time required to serialize or deserialize, with the one caveat that your serialization time cannot depend in any way on the total size of files that have been added, committed, etc (what is serialization? Revisit Lab 6 if you don’t know!). You can also pretend that getting from a hash table is constant time.
大多数命令都有运行时或内存使用要求。您必须遵循这些要求。其中一些运行时被描述为“相对于任何重要度量的常数”。重要度量包括:文件数量或大小的任何度量,提交数量的任何度量。您可以忽略序列化或反序列化所需的时间,但有一个限制,即您的序列化时间不能以任何方式依赖于已添加、提交等文件的总大小(什么是序列化?如果您不知道,请回顾实验6!)。您还可以假设从哈希表中获取数据的时间是恒定的。 -
Some commands have failure cases with a specified error message. The exact formats of these are specified later in the spec. All error message end with a period; since our autograding is literal, be sure to include it. If your program ever encounters one of these failure cases, it must print the error message and not change anything else. You don’t need to handle any other error cases except the ones listed as failure cases.
一些命令具有指定错误消息的失败情况。这些错误消息的确切格式在规范中稍后指定。所有错误消息都以句号结尾;由于我们的自动评分是字面的,请确保包含它。如果您的程序遇到其中一种失败情况,必须打印错误消息并不更改任何其他内容。除了列出的失败情况,您不需要处理任何其他错误情况。 -
There are some failure cases you need to handle that don’t apply to a particular command. Here they are:
这里有一些你需要处理的失败情况,它们不适用于特定的命令。-
If a user doesn’t input any arguments, print the message
Please enter a command.
and exit.
如果用户没有输入任何参数,则打印消息Please enter a command.
并退出。 -
If a user inputs a command that doesn’t exist, print the message
No command with that name exists.
and exit.
如果用户输入的命令不存在,则打印消息No command with that name exists.
并退出。 -
If a user inputs a command with the wrong number or format of operands, print the message
Incorrect operands.
and exit.
如果用户输入的命令操作数数量或格式错误,请打印消息Incorrect operands.
并退出。 -
If a user inputs a command that requires being in an initialized Gitlet working directory (i.e., one containing a
.gitlet
subdirectory), but is not in such a directory, print the messageNot in an initialized Gitlet directory.
如果用户输入的命令需要在已初始化的Gitlet工作目录中执行(即包含一个.gitlet
子目录),但不在这样的目录中,则打印消息Not in an initialized Gitlet directory.
。
-
-
Some of the commands have their differences from real Git listed. The spec is not exhaustive in listing all differences from Git, but it does list some of the bigger or potentially confusing and misleading ones.
一些命令与真正的Git有所不同。该规范并不详尽地列出了与Git的所有差异,但它确实列出了一些较大的、可能令人困惑和误导的差异。 -
Do NOT print out anything except for what the spec says. Some of our autograder tests will break if you print anything more than necessary.
除了规范要求的内容外,请勿打印任何其他内容。如果打印超出必要范围的内容,我们的自动评分系统中的一些测试将会出错。 -
To exit your program immediately, you may call
System.exit(0)
. For example, if an error
要立即退出程序,您可以调用System.exit(0)
。例如,如果发生错误
occurs in the middle of a helper function and you want gitlet to terminate immediately, you would call this function. Note: You should always supply the argument 0 to theSystem.exit(0)
command. In 61C, you will learn what the argument (known as an error code) means.
在一个辅助函数中发生,并且你希望gitlet立即终止,你会调用这个函数。注意:你应该始终向System.exit(0)
命令提供参数0。在61C中,你将学习这个参数(称为错误代码)的含义。 -
The spec classifies some commands as “dangerous”. Dangerous commands are ones that potentially overwrite files (that aren’t just metadata)–for example, if a user tells Gitlet to restore files to older versions, Gitlet may overwrite the current versions of the files. Just FYI. So put a helmet on before you test these commands :)
该规范将某些命令分类为“危险命令”。危险命令是指可能覆盖文件(不仅仅是元数据)的命令 - 例如,如果用户告诉Gitlet将文件恢复到旧版本,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 初始化
-
Usage:
java gitlet.Main init
用法:java gitlet.Main init
-
Description: Creates a new Gitlet version-control system in the current directory. This system will automatically start with one commit: a commit that contains no files and has the commit message
initial commit
(just like that, with no punctuation). It will have a single branch:master
, which initially points to this initial commit, andmaster
will be the current branch. The timestamp for this initial commit will be 00:00:00 UTC, Thursday, 1 January 1970 in whatever format you choose for dates (this is called “The (Unix) Epoch”, represented internally by the time 0.) Since the initial commit in all repositories created by Gitlet will have exactly the same content, it follows that all repositories will automatically share this commit (they will all have the same UID) and all commits in all repositories will trace back to it.
描述:在当前目录中创建一个新的Gitlet版本控制系统。该系统将自动开始一个提交:一个不包含任何文件且提交消息为initial commit
(就是这样,没有标点符号)。它将有一个单独的分支:master
,最初指向这个初始提交,并且master
将是当前分支。这个初始提交的时间戳将是00:00:00 UTC,1970年1月1日星期四,以您选择的日期格式(这被称为“(Unix)纪元”,由时间0内部表示)。由于由Gitlet创建的所有存储库中的初始提交将具有完全相同的内容,因此所有存储库将自动共享此提交(它们将具有相同的UID),并且所有存储库中的所有提交都将追溯到它。 -
Runtime: Should be constant relative to any significant measure.
运行时间:相对于任何重要的度量应该是恒定的。 -
Failure cases: If there is already a Gitlet version-control system in the current directory, it should abort. It should NOT overwrite the existing system with a new one. Should print the error message
A Gitlet version-control system already exists in the current directory.
失败情况:如果当前目录中已经存在一个Gitlet版本控制系统,则应中止。不应该用新的系统覆盖现有系统。应该打印错误信息A Gitlet version-control system already exists in the current directory.
。 -
Dangerous?: No 危险吗?不
-
Our line count: ~15 我们的行数:约15
add
-
Usage:
java gitlet.Main add [file name]
用法:java gitlet.Main add [file name]
-
Description: Adds a copy of the file as it currently exists to the staging area (see the description of the
commit
command). For this reason, adding a file is also called staging the file for addition. Staging an already-staged file overwrites the previous entry in the staging area with the new contents. The staging area should be somewhere in.gitlet
. If the current working version of the file is identical to the version in the current commit, do not stage it to be added, and remove it from the staging area if it is already there (as can happen when a file is changed, added, and then changed back to it’s original version). The file will no longer be staged for removal (seegitlet rm
), if it was at the time of the command.
描述:将文件的当前副本添加到暂存区(参见commit
命令的描述)。因此,添加文件也被称为将文件暂存以进行添加。将已经暂存的文件重新暂存会用新内容覆盖暂存区中的先前条目。暂存区应该在.gitlet
中的某个位置。如果文件的当前工作版本与当前提交中的版本相同,则不要将其暂存以进行添加,并且如果已经存在于暂存区中(当文件被更改、添加,然后又改回原始版本时可能发生这种情况),则从暂存区中删除它。如果在命令执行时文件已经被暂存以进行删除(参见gitlet rm
),则文件将不再被暂存。 -
Runtime: In the worst case, should run in linear time relative to the size of the file being added and , for the number of files in the commit.
运行时间:在最坏的情况下,应该相对于要添加的文件的大小和提交中文件的数量而言是线性时间。 -
Failure cases: If the file does not exist, print the error message
File does not exist.
and exit without changing anything.
失败情况:如果文件不存在,则打印错误信息File does not exist.
并退出,不做任何更改。 -
Dangerous?: No 危险吗?不
-
Our line count: ~20 我们的行数:约20
-
Differences from real git: In real git, multiple files may be added at once. In gitlet, only one file may be added at a time.
与真正的git的区别:在真正的git中,可以一次添加多个文件。在gitlet中,每次只能添加一个文件。 -
Suggested Lecture(s): Lecture 16 (Sets, Maps, ADTs), Lecture 19 (Hashing)
建议的讲座:讲座16(集合,映射,抽象数据类型),讲座19(哈希)
commit 承诺
-
Usage:
java gitlet.Main commit [message]
用法:java gitlet.Main commit [message]
-
Description: Saves a snapshot of tracked files in the current commit and staging area so they can be restored at a later time, creating a new commit. The commit is said to be tracking the saved files. By default, each commit’s snapshot of files will be exactly the same as its parent commit’s snapshot of files; it will keep versions of files exactly as they are, and not update them. A commit will only update the contents of files it is tracking that have been staged for addition at the time of commit, in which case the commit will now include the version of the file that was staged instead of the version it got from its parent. A commit will save and start tracking any files that were staged for addition but weren’t tracked by its parent. Finally, files tracked in the current commit may be untracked in the new commit as a result being staged for removal by the
rm
command (below).
描述:保存当前提交和暂存区中跟踪文件的快照,以便以后可以恢复,创建一个新的提交。该提交被称为跟踪保存的文件。默认情况下,每个提交的文件快照将与其父提交的文件快照完全相同;它将保留文件的版本,不对其进行更新。提交只会更新它正在跟踪的文件的内容,这些文件在提交时已经被暂存添加,此时提交将包含暂存的文件版本,而不是从父提交获取的版本。提交将保存并开始跟踪任何被暂存添加但未被其父提交跟踪的文件。最后,当前提交中跟踪的文件可能在新提交中不再被跟踪,因为它们被rm
命令(下面)暂存以进行删除。The bottom line: By default a commit has the same file contents as its parent. Files staged for addition and removal are the updates to the commit. Of course, the date (and likely the mesage) will also different from the parent.
底线:默认情况下,提交的文件内容与其父提交相同。被标记为添加和删除的文件是提交的更新。当然,日期(和可能的消息)也与父提交不同。Some additional points about commit:
关于提交的一些额外要点:-
The staging area is cleared after a commit.
提交后,暂存区被清空。 -
The commit command never adds, changes, or removes files in the working directory (other than those in the
.gitlet
directory). Therm
command will remove such files, as well as staging them for removal, so that they will be untracked after acommit
.
commit命令不会在工作目录中添加、修改或删除文件(除了.gitlet
目录中的文件)。rm
命令将删除这些文件,并将它们标记为待删除,以便在commit
之后它们将不再被跟踪。 -
Any changes made to files after staging for addition or removal are ignored by the
commit
command, which only modifies the contents of the.gitlet
directory. For example, if you remove a tracked file using the Unixrm
command (rather than Gitlet’s command of the same name), it has no effect on the next commit, which will still contain the (now deleted) version of the file.
将文件添加或删除后进行的任何更改都会被commit
命令忽略,该命令只修改.gitlet
目录的内容。例如,如果您使用Unix的rm
命令(而不是Gitlet的同名命令)删除已跟踪的文件,则对下一次提交没有影响,提交仍将包含(现已删除的)文件的版本。 -
After the commit command, the new commit is added as a new node in the commit tree.
提交命令后,新的提交被添加为提交树中的一个新节点。 -
The commit just made becomes the “current commit”, and the head pointer now points to it. The previous head commit is this commit’s parent commit.
提交刚刚完成后成为“当前提交”,头指针现在指向它。前一个头提交是这个提交的父提交。 -
Each commit should contain the date and time it was made.
每个提交应包含其制作日期和时间。 -
Each commit has a log message associated with it that describes the changes to the files in the commit. This is specified by the user. The entire message should take up only one entry in the array
args
that is passed tomain
. To include multiword messages, you’ll have to surround them in quotes.
每个提交都有一个与之关联的日志消息,描述了提交中文件的更改。这由用户指定。整个消息应该只占用传递给main
的数组args
中的一个条目。要包含多个单词的消息,您需要将其用引号括起来。 -
Each commit is identified by its SHA-1 id, which must include the file (blob) references of its files, parent reference, log message, and commit time.
每个提交都由其SHA-1 id标识,其中必须包括其文件(blob)引用、父引用、日志消息和提交时间。
-
-
Runtime: Runtime should be constant with respect to any measure of number of commits. Runtime must be no worse than linear with respect to the total size of files the commit is tracking. Additionally, this command has a memory requirement: Committing must increase the size of the
.gitlet
directory by no more than the total size of the files staged for addition at the time of commit, not including additional metadata. This means don’t store redundant copies of versions of files that a commit receives from its parent (hint: remember that blobs are content addressable and use the SHA1 to your advantage). You are allowed to save whole additional copies of files; don’t worry about only saving diffs, or anything like that.
运行时间:运行时间应该与提交数量的任何度量保持恒定。运行时间必须至少与提交跟踪的文件的总大小成线性关系。此外,此命令还有一个内存要求:提交必须使.gitlet
目录的大小增加的量不得超过提交时添加的文件的总大小,不包括额外的元数据。这意味着不要存储从其父提交接收到的文件版本的冗余副本(提示:记住blob是内容可寻址的,并利用SHA1)。您可以保存整个额外的文件副本;不必担心只保存差异或其他类似的内容。 -
Failure cases: If no files have been staged, abort. Print the message
No changes added to the commit.
Every commit must have a non-blank message. If it doesn’t, print the error messagePlease enter a commit message.
It is not a failure for tracked files to be missing from the working directory or changed in the working directory. Just ignore everything outside the.gitlet
directory entirely.
失败情况:如果没有文件被暂存,中止。打印消息No changes added to the commit.
每个提交必须有非空的消息。如果没有,打印错误消息Please enter a commit message.
跟踪的文件在工作目录中缺失或者在工作目录中被修改并不算是失败。完全忽略.gitlet
目录之外的一切。 -
Dangerous?: No 危险吗?不
-
Differences from real git: In real git, commits may have multiple parents (due to merging) and also have considerably more metadata.
与真实的git的区别:在真实的git中,提交可能有多个父提交(由于合并)并且还有更多的元数据。 -
Our line count: ~35 我们的行数:约35行
-
Suggested Lecture(s): Lecture 19 (Sets, Maps, ADTs), Lecture 19 (Hashing)
建议的讲座:讲座19(集合,映射,抽象数据类型),讲座19(哈希)
Here’s a picture of before-and-after commit:
这是一张提交前后的图片
rm
-
Usage:
java gitlet.Main rm [file name]
用法:java gitlet.Main rm [file name]
-
Description: Unstage the file if it is currently staged for addition. If the file is tracked in the current commit, stage it for removal and remove the file from the working directory if the user has not already done so (do not remove it unless it is tracked in the current commit).
描述:如果文件当前处于添加阶段,则取消添加。如果文件在当前提交中被跟踪,则将其标记为删除并从工作目录中删除该文件(除非用户已经这样做,否则不要删除它)。 -
Runtime: Should run in constant time relative to any significant measure.
运行时间:相对于任何重要的度量标准,应该保持恒定时间。 -
Failure cases: If the file is neither staged nor tracked by the head commit, print the error message
No reason to remove the file.
失败情况:如果文件既没有被暂存,也没有被最新提交跟踪,请打印错误信息No reason to remove the file.
-
Dangerous?: Yes (although if you use our utility methods, you will only hurt your repository files, and not all the other files in your directory.)
危险吗?是的(尽管如果您使用我们的实用方法,只会损坏您的存储库文件,而不会损坏目录中的其他文件)。 -
Our line count: ~20 我们的行数:约20
log
-
Usage:
java gitlet.Main log
用法:java gitlet.Main log
-
Description: Starting at the current head commit, display information about each commit backwards along the commit tree until the initial commit, following the first parent commit links, ignoring any second parents found in merge commits. (In regular Git, this is what you get with
git log --first-parent
). This set of commit nodes is called the commit’s history. For every node in this history, the information it should display is the commit id, the time the commit was made, and the commit message. Here is an example of the exact format it should follow:
描述:从当前的头提交开始,沿着提交树向后显示每个提交的信息,直到初始提交为止,按照第一个父提交链接进行,忽略在合并提交中找到的任何第二个父提交。(在常规的Git中,这是使用git log --first-parent
获得的)。这组提交节点称为提交的历史记录。对于这个历史记录中的每个节点,它应该显示的信息是提交ID、提交时间和提交消息。以下是应该遵循的确切格式的示例:
===
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.Date
和 java.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操作相同。
-
Runtime: Should be linear with respect to the number of nodes in head’s history.
运行时间:应该与头节点历史中的节点数量成线性关系。 -
Failure cases: None 失败案例:无
-
Dangerous?: No 危险吗?不
-
Our line count: ~20 我们的行数:约20
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:
这是一个特定提交的历史图片。如果当前分支的头指针恰好指向该提交,日志将打印出有关被圈出的提交的信息。
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 全球日志
-
Usage:
java gitlet.Main global-log
用法:java gitlet.Main global-log
-
Description: Like log, except displays information about all commits ever made. The order of the commits does not matter. Hint: there is a useful method in
gitlet.Utils
that will help you iterate over files within a directory.
描述:与日志类似,但显示有关所有提交的信息。提交的顺序无关紧要。提示:在gitlet.Utils
中有一个有用的方法,可以帮助您迭代目录中的文件。 -
Runtime: Linear with respect to the number of commits ever made.
运行时间:与已经进行的提交数量成线性关系。 -
Failure cases: None 失败案例:无
-
Dangerous?: No 危险吗?不
-
Our line count: ~10 我们的行数:约10行
find 找到
-
Usage:
java gitlet.Main find [commit message]
用法:java gitlet.Main find [commit message]
-
Description: Prints out the ids of all commits that have the given commit message, one per line. If there are multiple such commits, it prints the ids out on separate lines. The commit message is a single operand; to indicate a multiword message, put the operand in quotation marks, as for the
commit
command below. Hint: the hint for this command is the same as the one forglobal-log
.
描述:逐行打印出具有给定提交信息的所有提交的ID。如果有多个这样的提交,它们将在不同的行上打印出来。提交信息是一个单个操作数;为了表示多个单词的消息,请将操作数放在引号中,如下面的commit
命令所示。提示:这个命令的提示与global-log
的提示相同。 -
Runtime: Should be linear relative to the number of commits.
运行时间:应该与提交数量成线性关系。 -
Failure cases: If no such commit exists, prints the error message
Found no commit with that message.
失败情况:如果不存在这样的提交,则打印错误信息Found no commit with that message.
-
Dangerous?: No 危险吗?不
-
Differences from real git: Doesn’t exist in real git. Similar effects can be achieved by grepping the output of log.
与真正的git的区别:在真正的git中不存在。可以通过对日志输出进行grep来实现类似的效果。 -
Our line count: ~15 我们的行数:约15
status 状态
-
Usage:
java gitlet.Main status
用法:java gitlet.Main status
-
Description: Displays what branches currently exist, and marks the current branch with a
*
. Also displays what files have been staged for addition or removal. An example of the exact format it should follow is as follows.
描述:显示当前存在的分支,并用*
标记当前分支。还显示已经暂存的文件添加或删除的情况。应该遵循的确切格式示例如下。=== Branches === *master other-branch === Staged Files === wug.txt wug2.txt === Removed Files === goodbye.txt === Modifications Not Staged For Commit === junk.txt (deleted) wug3.txt (modified) === Untracked Files === random.stuff
The last two sections (modifications not staged and untracked files) are extra credit, worth 32 points. Feel free to leave them blank (leaving just the headers).
最后两个部分(未暂存的修改和未跟踪的文件)是额外的加分题,价值32分。可以选择留空(只保留标题)。There is an empty line between sections, and the entire status ends in an empty line as well. Entries should be listed in lexicographic order, using the Java string-comparison order (the asterisk doesn’t count). A file in the working directory is “modified but not staged” if it is
在各个部分之间有一行空行,并且整个状态也以一行空行结束。条目应按字典顺序列出,使用Java字符串比较顺序(星号不计算在内)。如果工作目录中的文件是“已修改但未暂存”,则为- Tracked in the current commit, changed in the working directory,
but not staged; or
在当前提交中被跟踪,在工作目录中发生了变化,但未暂存;或者 - Staged for addition, but with different contents than in the working
directory; or
已准备添加,但与工作目录中的内容不同;或者 - Staged for addition, but deleted in the working directory; or
在工作目录中被标记为添加,但被删除;或者 - Not staged for removal, but tracked in the current commit and deleted from
the working directory.
未被设置为删除,但在当前提交中被跟踪并从工作目录中删除。
The final category (“Untracked Files”) is for files present in the working directory but neither staged for addition nor tracked. This includes files that have been staged for removal, but then re-created without Gitlet’s knowledge. Ignore any subdirectories that may have been introduced, since Gitlet does not deal with them.
最后一个类别(“未跟踪的文件”)是指工作目录中存在但既没有被暂存添加也没有被跟踪的文件。这包括已经被暂存删除,但在没有 Gitlet 知识的情况下重新创建的文件。忽略可能引入的任何子目录,因为 Gitlet 不处理它们。 - Tracked in the current commit, changed in the working directory,
but not staged; or
-
Runtime: Make sure this depends only on the amount of data in the working directory plus the number of files staged to be added or deleted plus the number of branches.
运行时间:确保它仅取决于工作目录中的数据量,加上要添加或删除的文件数量,再加上分支的数量。 -
Failure cases: None 失败案例:无
-
Dangerous?: No 危险吗?不
-
Our line count: ~45 我们的行数:约45行
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的相应用法。
-
Usages: 用法:
-
java gitlet.Main checkout -- [file name]
-
java gitlet.Main checkout [commit id] -- [file name]
-
java gitlet.Main checkout [branch name]
-
-
Descriptions: 描述:
-
Takes the version of the file as it exists in the head commit and puts it in the working directory, overwriting the version of the file that’s already there if there is one. The new version of the file is not staged.
将文件的版本从头提交中获取,并将其放入工作目录中,如果工作目录中已经存在该文件,则覆盖原有版本。新版本的文件不会被暂存。 -
Takes the version of the file as it exists in the commit with the given id, and puts it in the working directory, overwriting the version of the file that’s already there if there is one. The new version of the file is not staged.
将给定id的提交中的文件版本取出,并将其放入工作目录中,如果工作目录中已存在该文件,则覆盖原有版本。新版本的文件不会被暂存。 -
Takes all files in the commit at the head of the given branch, and puts them in the working directory, overwriting the versions of the files that are already there if they exist. Also, at the end of this command, the given branch will now be considered the current branch (HEAD). Any files that are tracked in the current branch but are not present in the checked-out branch are deleted. The staging area is cleared, unless the checked-out branch is the current branch (see Failure cases below).
将给定分支头部提交中的所有文件放入工作目录中,如果已存在,则覆盖已有文件的版本。此命令结束后,给定分支将被视为当前分支(HEAD)。当前分支中跟踪的但在检出分支中不存在的任何文件都将被删除。除非检出的分支是当前分支(参见下面的失败情况),否则暂存区将被清空。
-
-
Runtimes: 运行时
-
Should be linear relative to the size of the file being checked out.
应该与被检出的文件的大小成线性关系。 -
Should be linear with respect to the total size of the files in the commit’s snapshot. Should be constant with respect to any measure involving number of commits. Should be constant with respect to the number of branches.
应该与提交快照中文件的总大小成线性关系。与涉及提交数量的任何度量相关的应该是恒定的。与分支数量相关的应该是恒定的。
-
-
Failure cases: 失败案例
-
If the file does not exist in the previous commit, abort, printing the error message
File does not exist in that commit.
Do not change the CWD.
如果在上一次提交中文件不存在,则中止操作,并打印错误信息File does not exist in that commit.
不要改变当前工作目录。 -
If no commit with the given id exists, print
No commit with that id exists.
Otherwise, if the file does not exist in the given commit, print the same message as for failure case 1. Do not change the CWD.
如果给定的id不存在任何提交,则打印No commit with that id exists.
。否则,如果文件在给定的提交中不存在,则打印与失败情况1相同的消息。不要更改当前工作目录。 -
If no branch with that name exists, print
No such branch exists.
If that branch is the current branch, printNo need to checkout the current branch.
If a working file is untracked in the current branch and would be overwritten by the checkout, printThere is an untracked file in the way; delete it, or add and commit it first.
and exit; perform this check before doing anything else. Do not change the CWD.
如果不存在具有该名称的分支,请打印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.
并退出;在执行任何其他操作之前进行此检查。不要更改当前工作目录。
-
-
Differences from real git: Real git does not clear the staging area and stages the file that is checked out. Also, it won’t do a checkout that would overwrite or undo changes (additions or removals) that you have staged.
与真正的git的区别:真正的git不会清除暂存区,并将检出的文件暂存。此外,它不会执行会覆盖或撤销你已暂存的更改(添加或删除)的检出操作。
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(完全检出一个分支)会修改暂存区:否则,计划添加或删除的文件仍然保持不变。
-
Dangerous?: Yes! 危险吗?是的!
-
Our line counts: 我们的线路计数:
- ~15
- ~5
- ~15
branch 分支
-
Usage:
java gitlet.Main branch [branch name]
用法:java gitlet.Main branch [branch name]
-
Description: Creates a new branch with the given name, and points it at the current head commit. A branch is nothing more than a name for a reference (a SHA-1 identifier) to a commit node. This command does NOT immediately switch to the newly created branch (just as in real Git). Before you ever call branch, your code should be running with a default branch called “master”.
描述:使用给定的名称创建一个新的分支,并将其指向当前的头提交。分支只是一个对提交节点的引用(一个SHA-1标识符)的名称。该命令不会立即切换到新创建的分支(就像真正的Git一样)。在调用分支之前,您的代码应该使用一个名为“master”的默认分支运行。 -
Runtime: Should be constant relative to any significant measure.
运行时间:相对于任何重要的度量应该是恒定的。 -
Failure cases: If a branch with the given name already exists, print the error message
A branch with that name already exists.
失败情况:如果已存在具有给定名称的分支,则打印错误消息A branch with that name already exists.
-
Dangerous?: No 危险吗?不
-
Our line count: ~10 我们的行数:约10行
All right, let’s see what branch does in detail. Suppose our state
looks like this:
好的,让我们详细看一下分支的作用。假设我们的状态是这样的:
Now we call java gitlet.Main branch cool-beans
. Then we get this:
现在我们称之为 java gitlet.Main branch cool-beans
。然后我们得到这个:
Hmm… nothing much happened. Let’s switch to the branch with java gitlet.Main
checkout cool-beans
:
嗯...没什么大事发生。让我们切换到带有 java gitlet.Main
checkout cool-beans
的分支:
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...
。
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
。
Now I make a commit…
现在我提交了一个更改..
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!
确保您的 branch
、 checkout
和 commit
的行为与我们上面描述的相匹配。这是Gitlet的核心功能,许多其他命令将依赖于此。如果其中任何核心功能出现问题,我们的自动评分测试将无法正常工作!
rm-branch 删除分支
-
Usage:
java gitlet.Main rm-branch [branch name]
用法:java gitlet.Main rm-branch [branch name]
-
Description: Deletes the branch with the given name. This only means to delete the pointer associated with the branch; it does not mean to delete all commits that were created under the branch, or anything like that.
描述:删除给定名称的分支。这只是删除与分支关联的指针,不意味着删除在该分支下创建的所有提交或类似的内容。 -
Runtime: Should be constant relative to any significant measure.
运行时间:相对于任何重要的度量应该是恒定的。 -
Failure cases: If a branch with the given name does not exist, aborts. Print the error message
A branch with that name does not exist.
If you try to remove the branch you’re currently on, aborts, printing the error messageCannot remove the current branch.
失败情况:如果不存在具有给定名称的分支,则中止。打印错误信息A branch with that name does not exist.
如果您尝试删除当前所在的分支,则中止,并打印错误信息Cannot remove the current branch.
。 -
Dangerous?: No 危险吗?不
-
Our line count: ~15 我们的行数:约15
reset 重置
-
Usage:
java gitlet.Main reset [commit id]
用法:java gitlet.Main reset [commit id]
-
Description: Checks out all the files tracked by the given commit. Removes tracked files that are not present in that commit. Also moves the current branch’s head to that commit node. See the intro for an example of what happens to the head pointer after using reset. The
[commit id]
may be abbreviated as forcheckout
. The staging area is cleared. The command is essentiallycheckout
of an arbitrary commit that also changes the current branch head.
描述:检查给定提交所跟踪的所有文件。删除在该提交中不存在的已跟踪文件。还将当前分支的头指针移动到该提交节点。有关使用重置后头指针发生的情况的示例,请参见介绍。可以将[commit id]
缩写为checkout
。暂存区被清空。该命令本质上是对任意提交的checkout
,同时也更改了当前分支的头指针。 -
Runtime: Should be linear with respect to the total size of files tracked by the given commit’s snapshot. Should be constant with respect to any measure involving number of commits.
运行时间:应该与给定提交的快照所跟踪的文件总大小成线性关系。对于涉及提交数量的任何度量,应该是恒定的。 - Failure case: If no commit with the given id exists, print
No commit with that id exists.
If a working file is untracked in the current branch and would be overwritten by the reset, print`There is an untracked file in the way; delete it, or add and commit it first.`
失败情况:如果不存在具有给定id的提交,请打印No commit with that id exists.
如果当前分支中存在未跟踪的工作文件,并且将被重置覆盖,请打印“路径中存在未跟踪的文件;请先删除它,或者添加并提交它。”and exit; perform this check before doing anything else.
并退出;在执行任何其他操作之前进行此检查。 -
Dangerous?: Yes! 危险吗?是的!
-
Differences from real git: This command is closest to using the
--hard
option, as ingit reset --hard [commit hash]
.
与真正的git的区别:这个命令最接近使用--hard
选项,就像git reset --hard [commit hash]
一样。 - Our line count: ~10 How did we get such a small line count? Recall that
you should reuse your code :)
我们的行数:~10我们是如何得到这么少的行数的?记住你应该重复使用你的代码 :)
merge 合并
-
Usage:
java gitlet.Main merge [branch name]
用法:java gitlet.Main merge [branch name]
-
Description: Merges files from the given branch into the current branch. This method is a bit complicated, so here’s a more detailed description:
描述:将给定分支的文件合并到当前分支。这个方法有点复杂,所以这里有一个更详细的描述:- First consider what we call the split point of the
current branch and the given branch.
For example, if
master
is the current branch andbranch
is the given branch:
首先考虑当前分支和给定分支的分割点。例如,如果master
是当前分支,branch
是给定分支: The split point is a latest common ancestor of the current and given branch heads: - A common ancestor is a commit to which there is a path (of 0 or more parent pointers) from both branch heads. - A latest common ancestor is a common ancestor that is not an ancestor of any other common ancestor. For example, although the leftmost commit in the diagram above is a common ancestor ofmaster
andbranch
, it is also an ancestor of the commit immediately to its right, so it is not a latest common ancestor. If the split point is the same commit as the given branch, then we do nothing; the merge is complete, and the operation ends with the messageGiven branch is an ancestor of the current branch.
If the split point is the current branch, then the effect is to check out the given branch, and the operation ends after printing the messageCurrent branch fast-forwarded.
Otherwise, we continue with the steps below.
分割点是当前分支和给定分支头部的最新共同祖先:- 共同祖先是指从两个分支头部都存在路径(0个或多个父指针)的提交。- 最新共同祖先是指不是其他共同祖先的祖先的共同祖先。例如,尽管上图中最左边的提交是master
和branch
的共同祖先,但它也是紧邻其右侧的提交的祖先,因此它不是最新的共同祖先。如果分割点与给定分支相同,则我们不做任何操作;合并完成,并以消息Given branch is an ancestor of the current branch.
结束操作。如果分割点是当前分支,则效果是切换到给定分支,并在打印消息Current branch fast-forwarded.
后结束操作。否则,我们继续以下步骤。
-
Any files that have been modified in the given branch since the split point, but not modified in the current branch since the split point should be changed to their versions in the given branch (checked out from the commit at the front of the given branch). These files should then all be automatically staged. To clarify, if a file is “modified in the given branch since the split point” this means the version of the file as it exists in the commit at the front of the given branch has different content from the version of the file at the split point. Remember: blobs are content addressable!
自从分支点以来在给定分支中被修改过的任何文件,但自从分支点以来在当前分支中没有被修改过的文件,应该被更改为它们在给定分支中的版本(从给定分支前面的提交中检出)。然后,这些文件应该自动被全部暂存。需要澄清的是,如果一个文件“自从分支点以来在给定分支中被修改过”,这意味着该文件在给定分支前面的提交中的版本与分支点处的版本内容不同。请记住:blob是内容可寻址的! -
Any files that have been modified in the current branch but not in the given branch since the split point should stay as they are.
自分岐点以来在当前分支中被修改但在给定分支中未被修改的任何文件应保持原样。 -
Any files that have been modified in both the current and given branch in the same way (i.e., both files now have the same content or were both removed) are left unchanged by the merge. If a file was removed from both the current and given branch, but a file of the same name is present in the working directory, it is left alone and continues to be absent (not tracked nor staged) in the merge.
在合并中,如果当前分支和给定分支中的某个文件以相同的方式进行了修改(即,这两个文件现在具有相同的内容或都被删除了),则这些文件不会发生变化。如果一个文件在当前分支和给定分支中都被删除了,但是工作目录中存在同名的文件,那么这个文件将保持不变,并且在合并中仍然不存在(不被跟踪或暂存)。 -
Any files that were not present at the split point and are present only in the current branch should remain as they are.
任何在分割点不存在且仅存在于当前分支的文件应保持原样。 -
Any files that were not present at the split point and are present only in the given branch should be checked out and staged.
应检出并暂存在给定分支中仅存在而在分割点不存在的任何文件。 -
Any files present at the split point, unmodified in the current branch, and absent in the given branch should be removed (and untracked).
在分割点处存在的任何文件,在当前分支中未修改且在给定分支中不存在的文件应该被删除(并且不被跟踪)。 -
Any files present at the split point, unmodified in the given branch, and absent in the current branch should remain absent.
在分割点存在的任何文件,在给定分支中未修改,并且在当前分支中不存在的文件应保持不存在。 -
Any files modified in different ways in the current and given branches are in conflict. “Modified in different ways” can mean that the contents of both are changed and different from other, or the contents of one are changed and the other file is deleted, or the file was absent at the split point and has different contents in the given and current branches. In this case, replace the contents of the conflicted file with
当前分支和给定分支中以不同方式修改的任何文件都存在冲突。 "以不同方式修改" 可以意味着两者的内容都被更改并且与其他不同,或者其中一个文件的内容被更改并且另一个文件被删除,或者该文件在分割点不存在,并且在给定和当前分支中具有不同的内容。在这种情况下,用冲突文件的内容替换。
- First consider what we call the split point of the
current branch and the given branch.
For example, if
<<<<<<< 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.
顺便说一下,我们希望你已经注意到,提交集从简单的序列发展到树,现在最终发展成了一个完整的有向无环图。
-
Runtime: , where is the total number of ancestor commits for the two branches and is the total amount of data in all the files under these commits.
运行时间: ,其中 是两个分支的所有祖先提交的总数, 是这些提交下所有文件的总数据量。 -
Failure cases: If there are staged additions or removals present, print the error message
You have uncommitted changes.
and exit. If a branch with the given name does not exist, print the error messageA branch with that name does not exist.
If attempting to merge a branch with itself, print the error messageCannot merge a branch with itself.
If merge would generate an error because the commit that it does has no changes in it, just let the normal commit error message for this go through. If an untracked file in the current commit would be overwritten or deleted by the merge, printThere is an untracked file in the way; delete it, or add and commit it first.
and exit; perform this check before doing anything else.
失败情况:如果存在分阶段的添加或删除操作,请打印错误信息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.
并退出;在执行其他任何操作之前进行此检查。 -
Dangerous?: Yes! 危险吗?是的!
-
Differences from real git: Real Git does a more subtle job of merging files, displaying conflicts only in places where both files have changed since the split point.
与真正的Git的区别:真正的Git在合并文件时更加细致,仅在两个文件自分叉点以来都发生了更改的地方显示冲突。Real Git has a different way to decide which of multiple possible split points to use.
真正的Git有一种不同的方式来决定使用哪个可能的分割点。Real Git will force the user to resolve the merge conflicts before committing to complete the merge. Gitlet just commits the merge, conflicts and all, so that you must use a separate commit to resolve problems.
真正的Git会强制用户在提交合并之前解决冲突。Gitlet只是提交合并,包括冲突,所以你必须使用单独的提交来解决问题。Real Git will complain if there are unstaged changes to a file that would be changed by a merge. You may do so as well if you want, but we will not test that case.
如果有未暂存的更改会被合并修改的文件,真正的Git会发出警告。如果你愿意,你也可以这样做,但我们不会测试这种情况。 -
Our line count: ~70 我们的行数:约70行
-
Suggested Lecture(s): Lecture 19 (Sets, Maps, ADTs), Lecture 22 (Graph Traversal)
建议的讲座:讲座19(集合、映射、抽象数据类型),讲座22(图遍历)
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: 它将进行测试
- Your program compiles. 你的程序编译成功。
- You pass the sample tests from the skeleton:
testing/samples/*.in
. These require you to implement:
您通过了来自骨架的样本测试:testing/samples/*.in
。这些测试要求您实现:init
add
commit
,checkout -- [file name]
checkout [commit id] -- [file name]
, and , 和log
In addition, it will comment on (but not score):
此外,它将进行评论(但不评分):
- Whether you pass style checks (it will ignore
TODO
-type comments for now; we won’t in the final submission.)
无论你是否通过样式检查(目前会忽略TODO
类型的注释;在最终提交中我们不会忽略)。
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个令牌。以下是令牌充值速率的时间表:
- 2/20 - 3/19: Once every 6 hours
2/20 - 3/19:每6小时一次 - 3/20 - 3/26: Once every 3 hours
3/20 - 3/26:每3小时一次 - 3/26 - 4/2: Once every 20 minutes
3/26 - 4/2:每20分钟一次
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个额外学分点可能
- 16 for the checkpoint
16用于检查点 - 32 for the
status
command printing theModifications Not Staged For Commit
andUntracked Files
sections
32用于打印status
命令的Modifications Not Staged For Commit
和Untracked Files
部分 - 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:
这是一个雄心勃勃的项目,你可能会感到迷茫,不知道从哪里开始。因此,与他人的合作可以更加密切一些,但请注意以下几点:
- Acknowledge all collaborators in comments near the beginning of your
gitlet/Main.java
file.
在您的文件开头附近的注释中承认所有合作者。 - Don’t share specific code; all collaborators must produce their own versions
of the algorithms they come up with, so that we can see they differ.
不要分享具体的代码;所有合作者必须自己编写算法的版本,以便我们可以看到它们的差异。
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.File
和 java.nio.file.Files
类很有帮助。实际上,你可能会在 java.io
和 java.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),其中包含了一些命令
- Set up or remove files from a testing directory;
设置或删除测试目录中的文件; - Run
java gitlet.Main
; 运行; - Check the output of Gitlet against a specific output or a regular expression
describing possible outputs;
检查Gitlet的输出与特定输出或描述可能输出的正则表达式相匹配; - Check the presence, absence, and contents of files.
Running the command
检查文件的存在、不存在和内容。运行命令。
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
命令,您需要:
- Initialize a Gitlet Repository
初始化一个Gitlet仓库 - Create a commit with a file in some version (v1)
在某个版本(v1)中创建一个包含文件的提交 - Create another commit with that file in some other version (v2)
使用另一个版本(v2)创建包含该文件的另一个提交 - 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
这样的输出。有几个原因:
- 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的变化。 - Your date will change every time since time only moves forwards.
你的日期会每次改变,因为时间只会向前流动。 - 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-remote
, rm-remote
, push
, fetch
和 pull
。完成它们将获得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:
关于远程命令的几点说明:
-
Execution time will not be graded. For your own edification, please don’t do anything ridiculous, though.
执行时间不会被评分。为了你自己的启发,请不要做任何荒谬的事情。 -
All the commands are significantly simplified from their git equivalents, so specific differences from git are usually not notated. Be aware they are there, however.
所有的命令都从git的等效命令中大幅简化,因此通常不会注明与git的具体差异。然而,请注意它们的存在。
So now let’s go over the commands:
现在让我们来看一下命令:
add-remote 添加远程
-
Usage:
java gitlet.Main add-remote [remote name] [name of remote directory]/.gitlet
用法:java gitlet.Main add-remote [remote name] [name of remote directory]/.gitlet
-
Description: Saves the given login information under the given remote name. Attempts to push or pull from the given remote name will then attempt to use this
.gitlet
directory. By writing, e.g., java gitlet.Main add-remote other ../testing/otherdir/.gitlet you can provide tests of remotes that will work from all locations (on your home machine or within the grading program’s software). Always use forward slashes in these commands. Have your program convert all the forward slashes into the path separator character (forward slash on Unix and backslash on Windows). Java helpfully defines the class variablejava.io.File.separator
as this character.
描述:将给定的登录信息保存在给定的远程名称下。然后尝试从给定的远程名称推送或拉取将尝试使用此.gitlet
目录。通过编写,例如,java gitlet.Main add-remote other ../testing/otherdir/.gitlet,您可以提供从所有位置(在您的家用计算机上或在评分程序的软件中)工作的远程测试。在这些命令中始终使用正斜杠。让您的程序将所有正斜杠转换为路径分隔符字符(Unix上的正斜杠和Windows上的反斜杠)。Java有用地将类变量java.io.File.separator
定义为此字符。 -
Failure cases: If a remote with the given name already exists, print the error message:
A remote with that name already exists.
You don’t have to check if the user name and server information are legit.
失败情况:如果已存在具有给定名称的远程,则打印错误消息:A remote with that name already exists.
您无需检查用户名和服务器信息是否合法。 -
Dangerous?: No. 危险吗?不。
rm-remote 删除远程
-
Usage:
java gitlet.Main rm-remote [remote name]
用法:java gitlet.Main rm-remote [remote name]
-
Description: Remove information associated with the given remote name. The idea here is that if you ever wanted to change a remote that you added, you would have to first remove it and then re-add it.
描述:删除与给定远程名称相关联的信息。这里的想法是,如果您想要更改已添加的远程名称,您首先必须将其删除,然后重新添加。 -
Failure cases: If a remote with the given name does not exist, print the error message:
A remote with that name does not exist.
失败情况:如果给定名称的远程不存在,则打印错误消息:A remote with that name does not exist.
-
Dangerous?: No. 危险吗?不。
push 推
-
Usage:
java gitlet.Main push [remote name] [remote branch name]
用法:java gitlet.Main push [remote name] [remote branch name]
-
Description: Attempts to append the current branch’s commits to the end of the given branch at the given remote. Details:
描述:尝试将当前分支的提交附加到给定远程分支的末尾。详情:This command only works if the remote branch’s head is in the history of the current local head, which means that the local branch contains some commits in the future of the remote branch. In this case, append the future commits to the remote branch. Then, the remote should reset to the front of the appended commits (so its head will be the same as the local head). This is called fast-forwarding.
如果远程分支的头部在当前本地分支的历史中,那么这个命令才有效,这意味着本地分支包含了一些在远程分支未来的提交。在这种情况下,将未来的提交追加到远程分支。然后,远程分支应该重置到追加提交的前面(这样它的头部将与本地头部相同)。这被称为快速前进。If the Gitlet system on the remote machine exists but does not have the input branch, then simply add the branch to the remote Gitlet.
如果远程机器上的Gitlet系统存在但没有输入的分支,则只需将该分支添加到远程Gitlet中。 -
Failure cases: If the remote branch’s head is not in the history of the current local head, print the error message
Please pull down remote changes before pushing.
If the remote.gitlet
directory does not exist, printRemote directory not found.
失败情况:如果远程分支的头部不在当前本地头部的历史记录中,则打印错误信息Please pull down remote changes before pushing.
如果远程.gitlet
目录不存在,则打印Remote directory not found.
-
Dangerous?: No. 危险吗?不。
fetch 取回
-
Usage:
java gitlet.Main fetch [remote name] [remote branch name]
用法:java gitlet.Main fetch [remote name] [remote branch name]
-
Description: Brings down commits from the remote Gitlet repository into the local Gitlet repository. Basically, this copies all commits and blobs from the given branch in the remote repository (that are not already in the current repository) into a branch named
[remote name]/[remote branch name]
in the local.gitlet
(just as in real Git), changing[remote name]/[remote branch name]
to point to the head commit (thus copying the contents of the branch from the remote repository to the current one). This branch is created in the local repository if it did not previously exist.
描述:将远程Gitlet存储库中的提交拉取到本地Gitlet存储库。基本上,这将从远程存储库中的给定分支复制所有提交和blob(尚未在当前存储库中)到本地存储库中的一个名为[remote name]/[remote branch name]
的分支(就像真正的Git一样),将[remote name]/[remote branch name]
更改为指向头提交(从而将远程存储库中的分支内容复制到当前存储库中)。如果此分支之前不存在,则在本地存储库中创建此分支。 -
Failure cases: If the remote Gitlet repository does not have the given branch name, print the error message
That remote does not have that branch.
If the remote.gitlet
directory does not exist, printRemote directory not found.
失败情况:如果远程Gitlet仓库没有给定的分支名称,则打印错误消息That remote does not have that branch.
如果远程.gitlet
目录不存在,则打印Remote directory not found.
-
Dangerous? No 危险?不
pull 拉
-
Usage:
java gitlet.Main pull [remote name] [remote branch name]
用法:java gitlet.Main pull [remote name] [remote branch name]
-
Description: Fetches branch
[remote name]/[remote branch name]
as for thefetch
command, and then merges that fetch into the current branch.
描述:与fetch
命令一样,获取分支[remote name]/[remote branch name]
,然后将该获取合并到当前分支。 -
Failure cases: Just the failure cases of
fetch
andmerge
together.
失败案例:仅包括fetch
和merge
的失败案例。 -
Dangerous? Yes! 危险?是的!
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”)。
-
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
andFile.listFiles
produce file names in an undefined order. If you use them to implement thelog
command, in particular, you can get random results.
由于您可能会在文件中保存各种信息(例如提交),您可能会倾向于使用看似方便的文件系统操作(例如列出目录)来顺序处理它们。但请小心。诸如File.list
和File.listFiles
等方法会以未定义的顺序生成文件名。特别是如果您使用它们来实现log
命令,可能会得到随机结果。 - 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 toFile
.
Windows用户尤其要注意,在Unix(或MacOS)上文件分隔符是/
,而在Windows上是'\'。因此,如果您在程序中通过将一些目录名和文件名与明确的/
s或\
s连接起来来形成文件名,您可以确保它在其中一个系统上无法工作。Java提供了一个系统相关的文件分隔符字符(System.getProperty("file.separator")
),或者您可以使用多参数构造函数来File
。 - Be careful using a
HashMap
when serializing! The order of things within theHashMap
is non-deterministic. The solution is to use aTreeMap
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年秋季进行了修改。