本篇文章给大家谈谈python编写的入门简单小游戏有哪些,以及python编写的入门简单小游戏代码,希望对各位有所帮助,不要忘了收藏本站喔。
简介:最近(2017-04)我编写了大约 500 行 Python 代码,它们实现了足够的 Git 客户端来创建存储库,将文件添加到索引,提交并将自身推送到GitHub。本文提供了一些关于我的黑客的背景知识并介绍了代码。
Git 因其非常简单的对象模型而闻名(其中包括) - 并且有充分的理由。学习时git我发现本地对象数据库只是目录中的一堆普通文件.git。除了 index(.git/index)和 pack 文件(它们是可选的)之外,这些文件的布局和格式非常简单。
有点受到 Mary Rose Cook 的类似努力的启发,我想看看我是否能够实现足够的git创建回购,执行提交,并推送到真正的服务器(在这种情况下为 GitHub)。
玛丽的gitlet课程更多的是教育重点; 我将自己推向了 GitHub,因此(以我的拙见)具有更多的黑客价值。在某些领域,她实施了更多的 Git(包括基本合并),但在其他方面更少。例如,她使用了一种更简单的基于文本的索引格式,而不是使用的二进制格式git。此外,虽然她gitlet确实支持推送,但它只推送到本地存在的另一个存储库,而不是远程服务器上。
在本练习中,我想编写一个可以执行所有步骤的版本,包括推送到真正的 Git 服务器。我也想使用相同的二进制索引格式,git因此我可以git在每一步使用命令检查我的工作。
我的版本叫做pygitPython(3.5+)并且只使用标准库模块。它只有 500 多行代码,包括空白行和注释。至少我所需要的init,add,commit,和push命令,但 pygit 还实现了status,diff,cat-file,ls-files,和hash-object。后面的命令本身很有用,但在调试 pygit 时它们也非常有用。
那么让我们深入研究代码吧!您可以在 GitHub 上查看所有 ,或者在我查看下面的各个部分时跟随它。
1. 初始化 Repo
初始化本地 Git 仓库只需要创建.git目录以及其下的一些文件和目录。在定义read_file和write_file辅助函数之后,我们可以写init():
你会注意到,没有很多优雅的错误处理。毕竟,这是一个 500 行的子集。如果 repo 目录已经存在,那么它将使用回溯失败。
2. 散列对象
该hash_object函数将单个对象哈希并写入.git/objects"数据库”。Git 模型中有三种类型的对象:blob(普通文件),提交和树(这些表示单个目录的状态)。
每个对象都有一个小标题,包括字节的类型和大小。接下来是 NUL 字节,然后是文件的数据字节。整个事情是 zlib 压缩并写入.git/objects/ab/cd...,其中ab是 40 个字符的 SHA-1 哈希的前两个字符,cd...其余部分。
请注意使用 Python 标准库的主题,我们可以(os和hashlib)。Python 附带 "包含电池”。
然后是find_object(),它通过散列(或散列前缀)找到一个对象,并且read_object()读取一个对象及其类型 - 基本上是反转的hash_object()。最后,cat_file是一个实现 pygit 等价的函数git cat-file:它将对象的内容(或其大小或类型)漂亮地打印到 stdout。
3. Git 索引
我们希望能够做的下一件事是将文件添加到索引或暂存区域。索引是按路径排序的文件条目列表,每个条目包含路径名,修改时间,SHA-1 哈希等。请注意,索引列出了当前树中的_所有文件_,而不仅仅是要提交的文件马上。
索引是单个文件.git/index,以自定义二进制格式存储。它并不是很复杂,但它确实涉及一些 struct用法,加上一点舞蹈来到可变长度路径字段后的下一个索引条目。
前 12 个字节是标题,最后 20 个字节是索引的 SHA-1 散列,其间的字节是索引条目,每个 62 字节加上路径的长度和一些填充。这是我们的IndexEntrynamedtuple 和read_index函数:
此功能之后ls_files,status和diff,所有这些基本上不同的方式来打印索引的状态:
ls_files只打印索引中的所有文件(以及它们的模式和散列,如果-s指定)
status用于 get_status()将索引中的文件与当前目录树中的文件进行比较,并打印出修改,新建和删除的文件
diff打印每个修改过的文件的差异,显示索引中的内容与当前工作副本中的内容(使用 Python 的 difflib模块执行脏工作)
我 100%肯定git使用索引,这些命令的实现比我的更有效,考虑到文件修改时间和所有这些。我只是通过一个完整的目录列表()获取文件路径,并使用一些设置操作,然后比较哈希。例如,这是我用来确定更改路径列表的集合理解:
最后有一个write_index函数可以将索引写回,并向索引add()添加一个或多个路径 - 后者只需读取整个索引,添加路径,重新排序,然后再将其写出来。
此时我们可以将文件添加到索引中,我们已准备好进行提交。
4. Git commit
执行提交包括编写两个对象:
首先,树对象,它是提交时当前目录(或实际上是索引)的快照。树只列出目录中文件(blob)和子树的哈希值 - 它是递归的。
因此,每次提交都是整个目录树的快照。但是这种通过散列存储事物的方式的巧妙之处在于,如果树中的任何文件发生变化,整个树的散列也会发生变化。相反,如果文件或子树没有改变,它只会被相同的散列引用。因此,您可以有效地存储目录树中的更改。
这是一个打印的树对象的示例cat-file pretty 2226(每行显示文件模式,对象类型,哈希和文件名):
write_tree奇怪的是,该函数用于编写树对象。关于某些 Git 文件格式的一个奇怪的事情是它们是混合二进制和文本的事实 - 例如,树对象中的每个 "行” 是"模式空间路径”作为文本,然后是 NUL 字节,然后是二进制 SHA-1 哈希。这是我们的write_tree():
第二,提交对象。这会记录树形哈希,父提交,作者和时间戳以及提交消息。合并当然是关于 Git 的好东西之一,但 pygit 只支持单个线性分支,所以只有一个父级(或者在第一次提交的情况下没有父级!)。
这是一个提交对象的示例,再次使用cat-file pretty aa8d以下方式打印:
这是我们的commit功能 - 再次,感谢 Git 的对象模型,几乎是行人:
5. Git push
接下来是稍微更难的部分,其中我们将 pygit 与真实的 Git 服务器进行对话(我将 pygit 推送到 GitHub,但它也适用于 Bitbucket 和其他服务器)。
基本思想是查询服务器的主分支以了解它所在的提交,然后确定它需要赶上当前本地提交的对象集。最后,更新远程的提交哈希并发送所有缺失对象的 "包文件”。
这被称为 "智能协议” - 截至 2011 年,GitHub 停止了对 "哑” 传输协议的支持,该协议只是.git直接传输文件,并且在某种程度上更容易实现。因此,我们必须使用 "智能” 协议并将对象打包到包文件中。
不幸的是,当我实现智能协议时,我犯了一个愚蠢的错误 - 我没有找到关于 HTTP 协议和打包协议的主要技术文档,直到我完成它。我正在使用 Git Book 的相当手动的传输协议部分以及 packfile 格式的 Git 代码库。
在使其工作的最后阶段,我还使用 Python 的 http.server模块实现了一个小型 HTTP 服务器,因此我可以git针对它运行常规客户端并查看一些实际请求。一些逆向工程值得一千行代码。
5.1. pkt-line 格式
传输协议的关键部分之一是所谓的 "pkt-line” 格式,它是一种长度前缀的数据包格式,用于发送提交哈希等元数据。每个 "行” 具有 4 位十六进制长度(加上 4 以包括长度的长度),然后长度减去 4 个字节的数据。每行通常LF在末尾也有一个字节。特殊长度0000用作节标记并位于数据的末尾。
例如,这是 GitHub 对git-receive-packGET 请求的响应。请注意,其他换行符和缩进不是实际数据的一部分:
所以我们需要两个函数,一个用于将 pkt-line 数据转换为一个行列表,另一个用于将行列表转换为 pkt-line 格式:
5.2. 发出 HTTPS 请求
下一个技巧 - 因为我只想使用标准库 - 是在没有 requests库的情况下进行经过身份验证的 HTTPS 请求。这是代码:
以上是确切requests存在的原因的一个例子。您_可以_使用标准库的urllib.request模块执行所有操作,但有时会很痛苦。大多数 Python stdlib 都很棒,其他部分则不是很多。使用的等效代码requests甚至不需要辅助函数:
我们可以使用上面的内容来询问服务器_它的_主分支是什么提交,就像这样(这个函数相当脆弱,但可以很容易地推广):
5.3. 确定丢失的对象
接下来,我们需要确定服务器所需的对象,而不是已有的对象。pygit 假设它具有本地的所有内容(它不支持 "拉”),所以我有一个read_tree函数(与之相反write_tree)然后以下两个函数以递归方式查找给定树和给定提交中的对象哈希集:
然后我们需要做的就是获取本地提交引用的对象集,并减去远程提交中引用的对象集。此设置差异是远程端丢失的对象。我确信有更有效的方法来生成这个集合,但这对于 pygit 来说已经足够好了:
5.4. Push
要执行推送,我们需要发送一个 pkt-line 请求来说 "将主分支更新为此提交哈希”,然后是一个包含上面找到的所有缺失对象的连接内容的包文件。
包文件有一个 12 字节的标题(以... 开头PACK),然后每个对象用可变长度编码并使用 zlib 压缩,最后是整个包文件的 20 字节散列。我们使用对象的 "未定义” 表示来保持简单 - 有更复杂的方法来根据对象之间的增量来缩小包文件,但这对我们来说太过分了:
然后,所有这一切的最后一步,push()本身 - 为了简洁,删除了一些外围代码
6. 命令行解析
pygit 也使用标准库的一个相当不错的示例 argparse模块,包括子命令(pygit init,pygit commit等)。我不会在这里复制代码,但请查看源代码中的 argparse 代码。
如果你到了这里,你只是走了大约 500 行没有价值的 Python - 哦等等,除了教育和工匠黑客的价值。? 希望你也学到了一些关于 Git 内部的东西。