原文:http://inventwithpython.com/beyond/chapter12.html
版本控制系统是记录所有源代码变更的工具,使检索旧版本代码变得容易。把这些工具想象成复杂的撤销功能。例如,如果您替换了一个函数,但后来发现您更喜欢旧的函数,那么您可以将代码恢复到原始版本。或者,如果您发现了一个新的 bug,您可以回到早期版本来确定它何时首次出现,以及是哪个代码更改导致了它。
当您对文件进行更改时,版本控制系统会管理这些文件。这比复制你的myProject
文件夹并将其命名为myProject-copy
要好。如果您继续进行更改,您最终将不得不制作另一个副本,名为myProject-copy2
,然后是myProject-copy3
,myProject-copy3b
,myproject-copyAsOfWednesday
,等等。复制文件夹可能很简单,但是这种方法不可扩展。从长远来看,学习使用版本控制系统可以节省您的时间和麻烦。
Git、Mercurial 和 Subversion 是流行的版本控制应用,尽管 Git 是目前最流行的。在这一章中,你将学习如何为代码项目建立文件,并使用 Git 来跟踪它们的变化。
Git 允许您保存项目文件的状态,当您对它们进行更改时,这些文件被称为快照或提交。这样,如果需要,您可以回滚到任何以前的快照。“提交”是名词,也是动词;程序员提交(或保存)他们的提交(或快照)。签到也是一个不太流行的提交术语。
版本控制系统还使得软件开发团队在对项目源代码进行更改时保持彼此同步变得容易。当每个程序员提交他们的更改时,其他程序员可以将这些更新下载到他们的计算机上。版本控制系统跟踪提交了什么,谁提交的,什么时候提交的,以及开发人员描述变更的注释。
版本控制在一个叫做存储库的文件夹中管理项目的源代码,或者叫做仓库(Repo)。一般来说,您应该为您正在进行的每个项目保留一个单独的 Git 仓库。本章假设你大部分时间都在独立工作,不需要高级的 Git 特性,比如分支和合并,这些特性可以帮助程序员进行协作。但是即使你独自工作,也没有任何编程项目小到不能从版本控制中获益。
我们称包含所有源代码、文档、测试和其他与项目相关的文件的文件夹为工作目录或 Git 术语中的工作树,更一般地称之为项目文件夹。工作目录中的文件统称为工作副本。在创建 Git 仓库之前,让我们为 Python 项目创建文件。
每个程序员都有自己喜欢的方法。即便如此,Python 项目也遵循文件夹名称和层次结构的约定。您的简单程序可能只包含一个.py
文件。但是当你处理更复杂的项目时,你将开始包括额外的.py
文件、数据文件、文档、单元测试等等。通常,项目文件夹的根包含一个src
文件夹用于.py
源代码文件,一个tests
文件夹用于单元测试,一个docs
文件夹用于任何文档(例如由 Sphinx 文档工具生成的文档)。其他文件包含项目信息和工具配置: README.md
一般信息,.coveragerc
用于代码覆盖配置,LICENSE.txt
用于项目的软件许可,等等。这些工具和文件超出了本书的范围,但是它们值得研究。随着您获得更多的编码经验,为新的编程项目重新创建相同的基本文件变得很乏味。为了加速您的编码任务,您可以使用cookiecutter
Python 模块来自动创建这些文件和文件夹。你可以在cookiecutter.readthedocs.io
找到 Cookiecutter 模块和命令行程序的完整文档。
要安装 Cookiecutter,运行pip install --user cookiecutter
(在 Windows 上)或pip3 install --user cookiecutter
(在 MacOS 和 Linux 上)。这个安装包括 Cookiecutter 命令行程序和cookiecutter
Python 模块。输出可能会警告您命令行程序安装到了一个没有在PATH
环境变量中列出的文件夹中:
Installing collected packages: cookiecutter
WARNING: The script cookiecutter.exe is installed in 'C:\Users\Al\AppData\Roaming\Python\Python38\Scripts' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
按照第 35 页“环境变量和路径”中的说明,考虑将文件夹(C:\Users\Al\AppData\Roaming\Python\Python38\Scripts
)添加到PATH
环境变量中。否则,您将不得不通过输入python -m cookiecutter
(在 Windows 上)或python3 –m cookiecutter
(在 MacOS 和 Linux 上)而不是简单地输入cookiecutter
来将 Cookiecutter 作为 Python 模块运行。
在这一章中,我们将为一个名为wizcoin
的模块创建一个仓库协议,该模块处理虚拟巫师货币的帆船、镰刀和克努特硬币。cookiecutter
模块使用模板为几种不同类型的项目创建启动文件。通常,模板只是一个GitHub.com
链接。例如,从一个C:\Users\Al
文件夹中,您可以在终端窗口中输入以下内容来创建一个C:\Users\Al\wizcoin
文件夹,其中包含一个基本 Python 项目的样板文件。cookiecutter
模块从 GitHub 下载模板,并询问您一系列关于您想要创建的项目的问题:
C:\Users\Al>cookiecutter gh:asweigart/cookiecutter-basicpythonproject
project_name [Basic Python Project]: WizCoin
module_name [basicpythonproject]: wizcoin
author_name [Susie Softwaredeveloper]: Al Sweigart
author_email [email@protected]: email@protected
github_username [susieexample]: asweigart
project_version [0.1.0]:
project_short_description [A basic Python project.]: A Python module to represent the galleon, sickle, and knut coins of wizard currency.
如果你得到一个错误,你也可以运行python -m cookiecutter
而不是cookiecutter
。这个命令从github/asweigart/cookiecutter-basicpythonproject
下载我创建的模板。你可以在github/cookiecutter/cookiecutter
找到许多编程语言的模板。因为 Cookiecutter 模板通常托管在 GitHub 上,所以您也可以在命令行参数中输入gh:
作为github.com
的快捷方式。
当 Cookiecutter 问你问题时,你可以输入一个回答,或者简单地按下 Enter ,使用显示在方括号中的默认回答。例如,project_name [Basic Python Project]:
要求您为项目命名。如果不输入任何内容,Cookiecutter 将使用“基本 Python 项目”作为项目名称。这些默认值也暗示了预期的响应类型。project_name [Basic Python Project]:
提示显示包含空格的大写项目名,而module_name [basicpythonproject]:
提示显示模块名是小写的,没有空格。我们没有为project_version [0.1.0]:
提示输入响应,所以响应默认为“0.1.0”
回答完问题后,Cookiecutter 在当前工作目录下创建一个wizcoin
文件夹,里面有你做 Python 项目需要的基本文件,如图图 12-1 所示。
图 12-1:Cookiecutter 创建的wizcoin
文件夹中的文件
如果你不明白这些文件的用途也没关系。对每一个的完整解释超出了本书的范围,但是github/asweigart/cookiecutter-basicpythonproject
有进一步阅读的链接和描述。现在我们有了起始文件,让我们使用 Git 来跟踪它们。
Git 可能已经安装在您的计算机上。要找到答案,从命令行运行git --version
。如果你看到类似git version 2.29.0.windows.1
的消息,说明你已经有 Git 了。如果您看到command not found
错误消息,您必须安装 Git。在 Windows 上,转到git-scm.com/download
,然后下载并运行 Git 安装程序。在 MacOS Mavericks(10.9)及更高版本上,只需从终端运行git --version
,系统会提示你安装 Git,如图图 12-2 所示。
在 Ubuntu 或 Debian Linux 上,从终端运行sudo apt install git-all
。在 RedHat Linux 上,从终端运行sudo dnf install git-all
。在git-scm.com/download/linux
可以找到其他 Linux 发行版的说明。通过运行git --version
确认安装成功。
图 12-2:第一次在 MacOS 10.9 或更高版本上运行git --version
,会提示你安装 Git。
安装 Git 后,您需要配置您的姓名和电子邮件,以便您的提交包含您的作者信息。从终端,使用您的姓名和电子邮件信息运行以下git config
命令:
C:\Users\Al>git config --global user.name "Al Sweigart"
C:\Users\Al>git config --global user.email email@protected
这些配置信息存储在你的主文件夹(比如我的 Windows 笔记本电脑上的C:\Users\Al
)的.gitconfig
文件中。您永远不需要直接编辑这个文本文件。相反,您可以通过运行git config
命令来更改它。您可以使用git config --list
命令列出当前的 Git 配置设置。
本章重点介绍 Git 命令行工具,但是安装为 Git 添加 GUI 的软件可以帮助您处理日常任务。即使是知道 Git CLI 命令的专业软件开发人员也经常使用 Git GUI 的网页git-scm.com/downloads/guis
推荐的几个这样的工具,比如用于 Windows 的 TortoiseGit,用于 MacOS 的 GitHub Desktop,以及用于 Linux 的 GitExtensions。
例如,图 12-3 显示了 Windows 上的 TortoiseGit 如何根据文件资源管理器的图标状态添加覆盖图:绿色表示未修改的仓库文件,红色表示已修改的仓库文件(或包含已修改文件的文件夹),没有图标表示未跟踪的文件。检查这些覆盖图肯定比不断地向终端输入命令来获取这些信息要方便得多。TortoiseGit 还为运行 Git 命令添加了一个上下文菜单,如图 12-3 所示。
使用 Git GUI 工具很方便,但是它不能代替学习本章中介绍的命令行命令。请记住,有一天您可能需要在没有安装这些 GUI 工具的计算机上使用 Git。
图 12-3:Windows 上的 TortoiseGit 添加了一个 GUI 来从文件浏览器运行 Git 命令。
使用 Git 仓库包括以下步骤。首先,通过运行git init
或git clone
命令创建 Git 仓库。第二,你用git add
命令添加文件让仓库跟踪。第三,一旦你添加了文件,你可以用git commit -am "
命令提交它们。此时,您已经准备好对代码进行更多的更改了。
您可以通过运行git help
来查看这些命令的帮助文件,例如git help init
或git help add
。这些帮助页面便于参考,尽管它们太枯燥、太专业,不能用作教程。稍后您将了解到关于这些命令的更多细节,但是首先,您需要理解一些 Git 概念,以便更容易理解本章的其余部分。
Git 会跟踪或不跟踪工作目录中的所有文件。被跟踪的文件是已经添加并提交给仓库的文件,而其他所有文件都是未被跟踪的。对于 Git 仓库,工作副本中未跟踪的文件可能不存在。另一方面,被跟踪的文件以其他三种状态之一存在:
图 12-4 包含了一个文件如何在这四种状态之间移动的图表。您可以向 Git 仓库添加一个未跟踪的文件,在这种情况下,它会被跟踪和暂存。然后,您可以提交暂存文件,将它们置于已提交状态。您不需要任何 Git 命令来将文件置于修改状态;一旦您对已提交的文件进行了更改,它会自动标记为已修改。
图 12-4:Git 仓库中文件的可能状态以及它们之间的转换
在创建存储库后的任何步骤,运行git status
来查看存储库的当前状态及其文件的状态。在 Git 中工作时,您会经常运行这个命令。在下面的例子中,我在不同的州设置了文件。注意这四个文件是如何出现在git status
的输出中的:
C:\Users\Al\ExampleRepo>git status
On branch master
Changes to be committed:
(use "git restore --staged ..." to unstage)
1 new file: new_file.py
2 modified: staged_file.py
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
3 modified: modified_file.py
Untracked files:
(use "git add ..." to include in what will be committed)
4 untracked_file.py
在这个工作副本中,有一个new_file.py
1,它最近被添加到仓库中,因此处于暂存状态。还有两个被跟踪的文件,staged_file.py
2 和modified_file.py
3,分别处于已暂存和已修改状态。然后还有一个名为untracked_file.py
4 的未跟踪文件。git status
的输出也有 Git 命令的提醒,这些命令将文件转移到其他状态。
您可能想知道阶段状态有什么意义。为什么不直接在修改的和提交的之间切换,而不升级文件呢?处理暂存区充满了棘手的特殊情况,也是 Git 初学者的一大困惑来源。例如,文件可以在转移后进行修改,从而使文件同时处于已修改和已转移状态,如前一节所述。从技术上讲,暂存区不包含文件,因为单个已修改文件的某些部分可以暂存,而其他部分可以不暂存。诸如此类的案例是 Git 以复杂著称的原因,许多关于 Git 如何工作的信息来源往好里说是不精确的,往坏里说是误导的。
但是我们可以避免这种复杂性。在本章中,我建议通过使用git commit –am
命令在一个步骤中转移和提交修改的文件来避免这种情况。这样,它们将直接从修改状态转移到干净状态。此外,我建议在您的仓库中添加、重命名或删除文件后,总是立即提交文件。此外,使用 Git GUI 工具(稍后解释)而不是命令行可以帮助您避免这些棘手的情况。
Git 是一个分布式版本控制系统,这意味着它将所有的快照和仓库元数据存储在你本地计算机上一个名为.git
的文件夹中。。与集中式版本控制系统不同,Git 不需要通过互联网连接到服务器来进行提交。这使得 Git 很快,并且在离线时也可以使用。
从终端运行以下命令来创建.git
文件夹。(在 MacOS 和 Linux 上,你需要运行mkdir
而不是md
。)
C:\Users\Al>md wizcoin
C:\Users\Al>cd wizcoin
C:\Users\Al\wizcoin>git init
Initialized empty Git repository in C:/Users/Al/wizcoin/.git/
当您通过运行git init
将一个文件夹转换成 Git 仓库时,其中的所有文件都是未跟踪的。对于我们的wizcoin
文件夹,git init
命令创建了wizcoin/.git
文件夹,其中包含 Git 仓库元数据。这个.git
文件埃及的存在使一个文件夹成为 Git 存储库;没有它,你只是在一个普通的文件夹中有一个源代码文件的集合。你将永远不需要直接修改.git
中的文件,所以就忽略这个文件夹吧。事实上,它被命名为.git
因为大多数操作系统会自动隐藏名称以点号开头的文件夹和文件。
现在,在您的C:\Users\Al\wizcoin
工作目录中有一个仓库。你电脑上的仓库被称为本地仓库;位于他人电脑上的仓库被称为远程仓库。这种区别很重要,因为您经常需要在本地和远程仓库之间共享提交,这样您就可以在同一个项目中与其他开发人员合作。
您现在可以使用git
命令添加文件并跟踪工作目录中的变化。如果您在新创建的仓库中运行git status
,您将看到以下内容:
C:\Users\Al\wizcoin>git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
此命令的输出通知您在此仓库中还没有提交。
watch
命令运行git status
在使用 Git 命令行工具时,您会经常运行git status
来查看您的仓库的状态。您可以使用watch
命令来运行它,而不是手动输入这个命令。watch
命令每两秒钟重复运行一次给定的命令,用最新的输出刷新屏幕。
在 Windows 上,你可以通过下载inventwithpython.com/watch.exe
并将该文件放入PATH
文件夹,比如 C:\Windows 来获得watch
命令。在 MacOS 上,你可以去www.macports.org
下载安装 MacPorts,然后运行sudo ports install watch
。在 Linux 上,已经安装了watch
命令。安装完成后,打开一个新的命令提示符或终端窗口,运行cd
将目录切换到 Git 仓库的项目文件夹,然后运行watch "git status"
。watch
命令将每两秒运行git status
,在屏幕上显示最新结果。当您在不同的终端窗口中使用 Git 命令行工具时,您可以让此窗口保持打开状态,以查看您的仓库的状态如何实时变化。您可以打开另一个终端窗口并运行watch "git log –online"
来查看您所做的提交的概要,也是实时更新的。这些信息有助于消除关于您输入的 Git 命令对您的仓库做了什么的猜测。
只有被跟踪的文件可以通过git
命令提交、回滚或交互。运行git status
来查看项目文件夹中文件的状态:
C:\Users\Al\wizcoin>git status
On branch master
No commits yet
Untracked files: # 1
(use "git add ..." to include in what will be committed)
.coveragerc
.gitignore
LICENSE.txt
README.md
`--snip--`
tox.ini
nothing added to commit but untracked files present (use "git add" to track)
wizcoin
文件夹中的所有文件目前都没有被追踪 1 。我们可以通过对这些文件进行初始提交来跟踪它们,这需要两步:对每个要提交的文件运行git add
,然后运行git commit
来创建所有这些文件的提交。一旦提交了一个文件,Git 就会跟踪它。
git add
命令将文件从未跟踪状态或已修改状态移动到暂存状态。我们可以为计划转移的每个文件运行git add
(例如,git add .coveragerc
、git add .gitignore
、git add LICENSE.txt
等等),但是这很繁琐。相反,让我们使用*
通配符一次添加几个文件。比如git add *.py
,添加当前工作目录及其子目录中的全部.py
文件。要添加每个未跟踪的文件,使用一个点号(.
)告诉 Git 匹配所有文件:
C:\Users\Al\wizcoin>git add .
运行git status
查看您上传的文件:
C:\Users\Al\wizcoin>git status
On branch master
No commits yet
Changes to be committed: # 1
(use "git rm --cached ..." to unstage)
2 new file: .coveragerc
new file: .gitignore
`--snip--`
new file: tox.ini
git status
的输出告诉你下次运行git commit
1 时提交哪些文件。它还告诉您,这些是添加到仓库 2 中的新文件,而不是仓库中已被修改的现有文件。
运行git add
选择要添加到仓库的文件后,再次运行git commit –m "Adding new files to the repo."
(或类似的提交消息)和git status
查看仓库状态:
C:\Users\Al\wizcoin>git commit -m "Adding new files to the repo."
[master (root-commit) 65f3b4d] Adding new files to the repo.
15 files changed, 597 insertions(+)
create mode 100644 .coveragerc
create mode 100644 .gitignore
`--snip--`
create mode 100644 tox.ini
C:\Users\Al\wizcoin>git status
On branch master
nothing to commit, working tree clean
注意.gitignore
文件中列出的任何文件不会被添加到暂存中,我将在下一节解释。
当您运行git status
时,没有被 Git 跟踪的文件显示为未被跟踪。但是在编写代码的过程中,您可能希望将某些文件完全排除在版本控制之外,这样就不会意外地跟踪到它们。其中包括:
*.pyc
,.pyo
,和*.pyd
文件,Python 解释器运行.py
程序时生成.tox
、htmlcov
,以及其他各种软件开发工具生成的文件夹docs/_build
为了避免包含这些文件,创建一个名为.gitignore
的文本文件,列出 Git 不应该跟踪的文件夹和文件。Git 会自动将这些从git add
或git commit
命令中排除,当你运行git status
时它们不会出现。
*。cookiecutter-basicpythonproject
模板创建的. gitignore
文件如下所示:
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
`--snip--`
.gitignore
文件使用*
作为通配符,使用#
作为注释。你可以在git-scm.com/docs/gitignore
的在线文档中了解更多信息。
你应该加上实际的.gitignore
文件复制到 Git 仓库中,这样如果其他程序员克隆了你的仓库,他们就有了它。如果您想根据.gitignore
中的设置查看您的工作目录中哪些文件被忽略,运行git ls-files --other --ignored --exclude-standard
命令。
将新文件添加到仓库后,您可以继续为您的项目编写代码。当您想要创建另一个快照时,可以运行git add .
来转移所有修改的文件,运行git commit –m
来提交所有转移的文件。但是用单个git commit –am
命令这样做更容易:
C:\Users\Al\wizcoin>git commit -am "Fixed the currency conversion bug."
[master (root-commit) e1ae3a3] Fixed the currency conversion bug.
1 file changed, 12 insertions(+)
如果只想提交某些修改过的文件,而不是每个修改过的文件,可以省略–am
中的–a
选项,在提交消息后指定文件,如git commit –m
。
提交消息为将来的使用提供了一个提示:它提醒我们在这次提交中做了哪些更改。写一条简短的、通用的消息可能很有诱惑力,比如“更新的代码”,或者“修复了一些错误”,甚至只是x
(因为不允许空白的提交消息)。但是三周以后,当您需要回滚到代码的早期版本时,详细的提交消息将使您在确定需要回滚到多远时省去很多麻烦。
如果您忘记添加-m "
命令行参数,Git 将在终端窗口中打开 Vim 文本编辑器。Vim 超出了本书的范围,所以按下Esc
键并输入qa!
可以安全地退出 Vim 并取消提交。然后再次输入git commit
命令,这次使用-m "
命令行参数。
关于专业提交消息的例子,请查看 Django web 框架的提交历史,网址为github/django/django/commits/master
。因为 Django 是一个大型的开源项目,所以提交经常发生,并且是正式的提交消息。带有模糊提交消息的不频繁提交对于你的小型个人编程项目可能足够好,但是 Django 有超过 1000 个贡献者。来自其中任何一方的糟糕的提交消息都会成为他们所有人的问题。
文件现在被安全地提交给 Git 仓库。再次运行git status
查看他们的状态:
C:\Users\Al\wizcoin>git status
On branch master
nothing to commit, working tree clean
通过提交暂存文件,您已经将它们移回提交状态,Git 告诉我们工作树是干净的;换句话说,没有修改或转移的文件。概括地说,当我们向 Git 仓库添加文件时,文件从未跟踪到暂存,然后提交。这些文件现在已准备好供将来修改。
请注意,您不能将文件夹提交给 Git 仓库。当提交文件夹中的文件时,Git 会自动将文件夹包含在仓库中,但是您不能提交空文件夹。
如果你在最近的提交消息中打错了,你可以使用git commit --amend -m "
命令重写它。
git diff
在提交前查看更改在提交代码之前,您应该快速回顾一下运行git commit
时将要提交的更改。您可以使用git diff
命令查看当前工作副本中的代码和最近提交的代码之间的差异。
让我们看一个使用git diff
的例子。在文本编辑器或 IDE 中打开README.md
。(您应该已经在运行 Cookiecutter 时创建了这个文件。如果不存在,创建一个空白文本文件,并将其保存为README.md
。)这是一个 Markdown 格式的文件,但是像 Python 脚本一样,它是用明文编写的。将Quickstart Guide
部分中的TODO - fill this in later
文本更改如下(暂时保留其中的xample
错别字;我们稍后会修复):
Quickstart Guide
----------------
Here's some xample code demonstrating how this module is used:
>>> import wizcoin
>>> coin = wizcoin.WizCoin(2, 5, 10)
>>> str(coin)
'2g, 5s, 10k'
>>> coin.value()
1141
在我们添加并提交README.md
之前,运行git diff
命令来查看我们所做的更改:
C:\Users\Al\wizcoin>git diff
diff --git a/README.md b/README.md
index 76b5814..3be49c3 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,14 @@ To install with pip, run:
Quickstart Guide
----------------
-TODO - fill this in later
+Here's some xample code demonstrating how this module is used:
+
+ >>> import wizcoin
+ >>> coin = wizcoin.WizCoin(2, 5, 10)
+ >>> str(coin)
+ '2g, 5s, 10k'
+ >>> coin.value()
+ 1141
Contribute
----------
输出显示您的工作副本中的README.md
已经从README.md
改变,因为它存在于仓库的最新提交中。以减号–
开头的行已被删除;添加了以加号+
开头的行。
在查看更改时,您还会注意到我们写错了xample
而不是example
。我们不应该检查这个错别字。我们来纠正一下。然后再次运行git diff
来检查变更,添加并提交给仓库:
C:\Users\Al\wizcoin>git diff
diff --git a/README.md b/README.md
index 76b5814..3be49c3 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,14 @@ To install with pip, run:
Quickstart Guide
----------------
-TODO - fill this in later
+Here's some example code demonstrating how this module is used:
`--snip--`
C:\Users\Al\wizcoin>git add README.md
C:\Users\Al\wizcoin>git commit -m "Added example code to README.md"
[master 2a4c5b8] Added example code to README.md
1 file changed, 8 insertions(+), 1 deletion(-)
修正现在被安全地提交给仓库协议。
使用 GUI 的差异程序更容易看到变化。在 Windows 上,你可以下载 WinMerge(winmerge.org
),一个免费的开源差异程序,然后安装它。在 Linux 上,您可以使用sudo apt-get install meld
命令安装 Meld,或者使用sudo apt-get install kompare
命令安装 Kompare。在 MacOS 上,您可以使用首先安装和配置 Homebrew(安装软件的包管理器)的命令来安装tkdiff
,然后使用 Homebrew 来安装tkdiff
:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
brew install tkdiff
您可以通过运行git config diff.tool
来配置 Git 使用这些工具,其中
是winmerge
、tkdiff
、meld
或kompare
。然后运行git difftool
在工具的 GUI 中查看对文件所做的更改,如图图 12-5 所示。
图 12-5:一个 GUI 比较工具,在这个例子中是 WinMerge,比git diff
的文本输出可读性更好。
此外,运行git config --global difftool.prompt false
,这样 Git 就不会在每次打开差异工具时要求确认。如果您安装了一个 Git GUI 客户端,您也可以配置它使用这些工具(或者它可能自带一个可视化比较工具)。
尽管版本控制允许您将文件回滚到较早的提交,但是您可能想知道应该多长时间提交一次。如果过于频繁地提交,您将很难在大量无关紧要的提交中找到您要找的代码版本。如果提交的频率太低,每次提交都会包含大量的更改,而恢复到特定的提交将会撤销比你想要的更多的更改。一般来说,程序员提交的频率比他们应该提交的频率要低。
当你完成了一个完整的功能,比如一个特性,一个类,或者一个错误修复,你应该提交代码。不要提交任何包含语法错误或明显有问题的代码。提交可以由几行或几百行更改的代码组成,但无论哪种方式,您都应该能够跳回到任何更早的提交,并且仍然有一个工作程序。在提交之前,您应该总是运行任何单元测试。理想情况下,您的所有测试都应该通过(如果没有通过,在提交消息中提到这一点)。
如果不再需要 Git 来跟踪文件,就不能简单地从文件系统中删除该文件。您必须使用git rm
命令通过 Git 删除它,这也告诉 Git 取消对文件的跟踪。要练习这样做,运行echo "Test file" > deleteme.txt
命令创建一个名为deleteme.txt
的小文件,内容为"Test file"
。然后通过运行以下命令将其提交给仓库:
C:\Users\Al\wizcoin>echo "Test file" > deleteme.txt
C:\Users\Al\wizcoin>git add deleteme.txt
C:\Users\Al\wizcoin>git commit -m "Adding a file to test Git deletion."
[master 441556a] Adding a file to test Git deletion.
1 file changed, 1 insertion(+)
create mode 100644 deleteme.txt
C:\Users\Al\wizcoin>git status
On branch master
nothing to commit, working tree clean
不要使用 Windows 上的del
命令或 MacOS 和 Linux 上的rm
命令删除文件。(如果您这样做了,您可以运行git restore
来恢复它,或者简单地继续使用git rm
命令将它从仓库中删除。)相反,使用git rm
命令删除并暂存deleteme.txt
文件,如下例所示:
C:\Users\Al\wizcoin>git rm deleteme.txt
rm deleteme.txt'
git rm
命令从您的工作副本中删除文件,但是您还没有完成。像git add
一样,git rm
命令暂存文件。您需要像任何其他更改一样提交文件删除:
C:\Users\Al\wizcoin>git status
On branch master
Changes to be committed:
1 (use "git reset HEAD ..." to unstage)
deleted: deleteme.txt
C:\Users\Al\wizcoin>git commit -m "Deleting deleteme.txt from the repo to finish the deletion test."
[master 369de78] Deleting deleteme.txt from the repo to finish the deletion test.
1 file changed, 1 deletion(-)
delete mode 100644 deleteme.txt
C:\Users\Al\Desktop\wizcoin>git status
On branch master
nothing to commit, working tree clean
即使您已经从工作副本中删除了deleteme.txt
,它仍然存在于仓库的历史中。本章后面的“恢复旧的更改”一节介绍了如何恢复已删除的文件或撤消更改。
git rm
命令只对处于干净、已提交状态的文件有效,没有任何修改。否则,Git 会要求您使用git reset HEAD < filename >
命令提交或恢复更改。(git status
的输出提醒你这个命令 1 。)此过程防止您意外删除未提交的更改。
与删除文件类似,除非使用 Git,否则不应该在仓库中重命名或移动文件。如果你试图在不使用 Git 的情况下这样做,它会认为你删除了一个文件,然后创建了一个新文件,只是碰巧有相同的内容。相反,使用git mv
命令,后跟git commit
。让我们通过运行以下命令将README.md
文件重命名为README.txt
:
C:\Users\Al\wizcoin>git mv README.md README.txt
C:\Users\Al\wizcoin>git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
renamed: README.md -> README.txt
C:\Users\Al\wizcoin>git commit -m "Testing the renaming of files in Git."
[master 3fee6a6] Testing the renaming of files in Git.
1 file changed, 0 insertions(+), 0 deletions(-)
rename README.md => README.txt (100%)
这样,README.txt
的更改历史也包括了README.md
的历史。
我们也可以使用git mv
命令将文件移动到一个新的文件夹中。输入以下命令创建一个名为movetest
的新文件夹,并将README.txt
移入其中:
C:\Users\Al\wizcoin>mkdir movetest
C:\Users\Al\wizcoin>git mv README.txt movetest/README.txt
C:\Users\Al\wizcoin>git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
renamed: README.txt -> movetest/README.txt
C:\Users\Al\wizcoin>git commit -m "Testing the moving of files in Git."
[master 3ed22ed] Testing the moving of files in Git.
1 file changed, 0 insertions(+), 0 deletions(-)
rename README.txt => movetest/README.txt (100%)
您还可以通过向git mv
传递新的名称和位置来同时重命名和移动文件。让我们将README.txt
移回到它在工作目录根目录下的原来位置,并给它它原来的名字:
C:\Users\Al\wizcoin>git mv movetest/README.txt README.md
C:\Users\Al\wizcoin>git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
renamed: movetest/README.txt -> README.md
C:\Users\Al\wizcoin>git commit -m "Moving the README file back to its original place and name."
[master 962a8ba] Moving the README file back to its original place and name.
1 file changed, 0 insertions(+), 0 deletions(-)
rename movetest/README.txt => README.md (100%)
请注意,即使README.md
文件回到了它原来的文件夹,并且有了它原来的名字,Git 仓库会记住移动和名字的改变。您可以使用下一节中描述的git log
命令来查看这个历史。
git log
命令输出所有提交的列表:
C:\Users\Al\wizcoin>git log
commit 962a8baa29e452c74d40075d92b00897b02668fb (HEAD -> master)
Author: Al Sweigart <email@protected>
Date: Wed Sep 1 10:38:23 2021 -0700
Moving the README file back to its original place and name.
commit 3ed22ed7ae26220bbd4c4f6bc52f4700dbb7c1f1
Author: Al Sweigart <email@protected>
Date: Wed Sep 1 10:36:29 2021 -0700
Testing the moving of files in Git.
`--snip—`
这个命令可以显示大量的文本。如果日志不适合您的终端窗口,它会让您使用上下箭头键向上或向下滚动。要退出,按下q
键。
如果您想将您的文件设置为早于最近一次提交,您需要首先找到提交哈希,它是一个 40 个字符的十六进制数字串(由数字和字母A
到F
组成),作为提交的唯一标识符。例如,我们的仓库中最近提交的完整哈希是 962 a8baa29e452c74d40075d92b00897b02668fB
。但只使用前七位的也很常见:962a8ba。
随着时间的推移,日志会变得很长。--oneline
选项将输出整理成缩写的提交哈希和每个提交消息的第一行。在命令行中输入git log --oneline
:
C:\Users\Al\wizcoin>git log --oneline
962a8ba (HEAD -> master) Moving the README file back to its original place and name.
3ed22ed Testing the moving of files in Git.
15734e5 Deleting deleteme.txt from the repo to finish the deletion test.
441556a Adding a file to test Git deletion.
2a4c5b8 Added example code to README.md
e1ae3a3 An initial add of the project files.
如果这个日志仍然太长,您可以使用-n
将输出限制为最近的提交。尝试输入git log --oneline –n 3
以仅查看最后三次提交:
C:\Users\Al\wizcoin>git log --oneline -n 3
962a8ba (HEAD -> master) Moving the README file back to its original place and name.
3ed22ed Testing the moving of files in Git.
15734e5 Deleting deleteme.txt from the repo to finish the deletion test.
要显示文件在特定提交时的内容,可以运行git show
命令。但是 Git GUI 工具将提供比命令行 Git 工具更方便的界面来检查仓库日志。
假设您想要使用早期版本的源代码,因为您引入了一个 bug,或者您可能不小心删除了一个文件。一个版本控制系统让你撤销,或者回滚,你的工作副本到先前提交的内容。您将使用的确切命令取决于工作副本中文件的状态。
请记住,版本控制系统只添加信息。即使您从仓库中删除了一个文件,Git 也会记住它,以便您可以在以后恢复它。回滚一个更改实际上添加了一个新的更改,该更改将文件的内容设置为前一次提交时的状态。你可以在github.blog/2015-06-08-how-to-undo-almost-any-with-git
找到关于各种回滚的详细信息。
如果您对一个文件进行了未提交的更改,但是想要将它恢复到最近一次提交时的版本,您可以运行git restore
。在下面的例子中,我们修改了README.md
文件,但还没有登台或提交它:
C:\Users\Al\wizcoin>git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git restore ..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
C:\Users\Al\wizcoin>git restore README.md
C:\Users\Al\wizcoin>git status
On branch master
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
运行完git restore README.md
命令后,README.md
的内容会恢复到上次提交时的内容。这实际上是对您对文件所做更改的一个撤销(但尚未暂存或提交)。但是要小心:你不能撤销这个“撤销”来恢复那些改变。
您还可以运行git checkout .
来恢复您对工作副本中的每个文件所做的所有更改。
如果您已经通过在文件上运行git add
命令暂存了一个已修改的文件,但现在想要从暂存中删除它,以便它不会包含在下一次提交中,请运行git restore --staged < filename >
来取消它的暂存:
C:\Users\Al>git restore --staged README.md
Unstaged changes after reset:
M spam.txt
README.md
保持在git add
暂存文件之前的修改状态,但文件不再处于暂存状态。
假设您已经进行了几次无益的提交,并且想要从之前的提交重新开始。要撤销特定数量的最近提交,比如说三个,使用git revert -n HEAD~3..HEAD
命令。您可以用任意数量的提交来替换3
。例如,假设您跟踪了您正在编写的一部推理小说的更改,并拥有以下关于所有提交和提交消息的 Git 日志。
C:\Users\Al\novel>git log --oneline
de24642 (HEAD -> master) Changed the setting to outer space.
2be4163 Added a whacky sidekick.
97c655e Renamed the detective to 'Snuggles'.
8aa5222 Added an exciting plot twist.
2590860 Finished chapter 1.
2dece36 Started my novel.
后来,你决定要在哈希8aa5222
从激动人心的情节转折中重新开始。这意味着您应该撤销最近三次提交的更改:de24642
、2be4163
和97c655e
。运行git revert -n HEAD~3..HEAD
来撤销这些更改,然后运行git add .
和git commit -m "
来提交该内容,就像您处理任何其他更改一样:
C:\Users\Al\novel>git revert -n HEAD~3..HEAD
C:\Users\Al\novel>git add .
C:\Users\Al\novel>git commit -m "Starting over from the plot twist."
[master faec20e] Starting over from the plot twist.
1 file changed, 34 deletions(-)
C:\Users\Al\novel>git log --oneline
faec20e (HEAD -> master) Starting over from the plot twist.
de24642 Changed the setting to outer space.
2be4163 Added a whacky sidekick.
97c655e Renamed the detective to 'Snuggles'.
8aa5222 Added an exciting plot twist.
2590860 Finished chapter 1.
2dece36 Started my novel.
Git 仓库 s 通常只添加信息,所以撤销这些提交仍然会将它们留在提交历史中。如果您想要撤销这个“撤销”,您可以再次使用git revert
回滚。
因为提交会捕获整个存储库的状态,而不是单个文件的状态,所以如果您想要回滚单个文件的更改,您将需要一个不同的命令。例如,假设我有一个小型软件项目的 Git 仓库。我已经创建了一个eggs.py
文件并添加了函数spam()
和bacon()
,然后将bacon()
重命名为cheese()
。此仓库的日志如下所示:
C:\Users\Al\myproject>git log --oneline
895d220 (HEAD -> master) Adding email support to cheese().
df617da Renaming bacon() to cheese().
ef1e4bb Refactoring bacon().
ac27c9e Adding bacon() function.
009b7c0 Adding better documentation to spam().
0657588 Creating spam() function.
d811971 Initial add.
但是我决定将文件恢复到添加bacon()
之前的状态,而不改变仓库中的任何其他文件。我可以使用git show
命令显示特定提交后的文件。该命令类似于以下内容:
C:\Users\Al\myproject>git show 009b7c0:eggs.py
`<contents of eggs.py as it was at the 009b7c0 commit>`
使用git checkout
,我可以将eggs.py
的内容设置为这个版本,并正常提交更改后的文件。git checkout
命令只改变工作副本。您仍然需要像任何其他变更一样准备和提交这些变更:
C:\Users\Al\myproject>git checkout 009b7c0 -- eggs.py
C:\Users\Al\myproject>git add eggs.py
C:\Users\Al\myproject>git commit -m "Rolled back eggs.py to 009b7c0"
[master d41e595] Rolled back eggs.py to 009b7c0
1 file changed, 47 deletions(-)
C:\Users\Al\myproject>git log --oneline
d41e595 (HEAD -> master) Rolled back eggs.py to 009b7c0
895d220 Adding email support to cheese().
df617da Renaming bacon() to cheese().
ef1e4bb Refactoring bacon().
ac27c9e Adding bacon() function.
009b7c0 Adding better documentation to spam().
0657588 Creating spam() function.
d811971 Initial add.
eggs.py
文件已经回滚,其余的仓库保持不变。
如果您不小心提交了一个包含敏感信息的文件,比如密码、API 密匙或信用卡号,仅仅编辑掉这些信息并重新提交是不够的。任何有权访问仓库的人,无论是在您的计算机上还是远程克隆的,都可以回滚到包含此信息的提交。
实际上,从你的仓库中删除这些信息,使其不可恢复是棘手的,但也是可能的。确切的步骤超出了本书的范围,但是你可以使用git filter-branch
命令,或者最好是 BFG 仓库清理工具。你可以在help.github.com/en/articles/removing-sensitive-data-from-a-repository
上了解这两个网站。
对这个问题最简单的预防措施是建立一个secrets.txt
、confidential.py
或类似名称的文件,在其中放置敏感的私人信息,并将其添加到.gitignore
这样你就永远不会不小心把它提交给仓库。您的程序可以读取该文件中的敏感信息,而不是直接在其源代码中包含敏感信息。
git push
命令虽然 Git 仓库可以完全存在于你的电脑上,但许多免费网站可以在线托管你的仓库克隆,让其他人轻松下载并参与你的项目。这些网站中最大的是 GitHub。如果您在线保存项目的一个副本,其他人可以添加到您的代码中,即使您开发的计算机已经关闭。克隆也可以作为有效的备份。
虽然这些术语可能会引起混淆,但 Git 是维护仓库的版本控制软件,包括git
命令,而 GitHub 是一个托管在线 Git 仓库的网站。
去github.com
注册一个免费账户。从 GitHub 主页或您的个人资料页面的仓库选项卡,点击新建按钮开始一个新项目。输入wizcoin
作为存储库名称和我们在第 200 页“使用 Cookiecutter 创建新的 Python 项目”中给 Cookiecutter 的相同项目描述,如图图 12-6 所示。将仓库标记为公共并取消选择使用README
初始化仓库复选框,因为我们将导入一个现有的存储库。然后点击创建存储库。这些步骤实际上就像在 GitHub 网站上运行git init
。
图 12-6:在 GitHub 上创建新的仓库协议
你可以在https://github.com/
找到你仓库的网页。我的wizcoin
仓库托管在github/asweigart/wizcoin
。
要从命令行推送现有存储库,请输入以下内容:
C:\Users\Al\wizcoin>git remote add origin https://github.com、<github_username>/wizcoin.git
C:\Users\Al\wizcoin>git push -u origin master
Username for 'https://github.com': <github_username>
Password for 'https://``@github.com' : <github_password>
Counting objects: 3, done.
Writing objects: 100% (3/3), 213 bytes | 106.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/<your github>/wizcoin.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
git remote add origin https://github.com/
命令将 GitHub 添加为与本地仓库相对应的远程仓库。然后使用git push -u origin master
命令将您在本地仓库上所做的任何提交推送到远程仓库。在第一次推送之后,您可以通过简单地运行git push
从本地仓库中推送所有未来的提交。每次提交后将提交推送到 GitHub 是一个好主意,可以确保 GitHub 上的远程仓库与本地仓库保持一致,但这并不是绝对必要的。
当您在 GitHub 上重新加载仓库的网页时,您应该会看到站点上显示的文件和提交。关于 GitHub 还有很多东西需要学习,包括如何通过拉取请求(或者部分站点是合并请求)接受他人对你的仓库 s 的贡献。这些,以及 GitHub 的其他高级特性,已经超出了本书的范围。
也可以反过来做:在 GitHub 上创建一个新的仓库,克隆到你的电脑上。在 GitHub 网站上创建一个新的仓库,但是这一次,选择使用README
初始化仓库复选框。
要将这个仓库克隆到您的本地计算机上,请转到 GitHub 上的仓库页面,然后单击克隆或下载按钮,打开一个窗口,其 URL 应该类似于https://github.com/
。使用您的仓库网址和git clone
命令将其下载到您的电脑:
C:\Users\Al>git clone https://github.com/<github_username>/wizcoin.git
Cloning into 'wizcoin'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 5 (delta 0), reused 5 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
现在,您可以使用这个 Git 仓库提交和推送更改,就像运行git init
来创建仓库一样。
git clone
命令在你的本地仓库陷入你不知道如何撤销的状态时也很有用。尽管这并不理想,但您总是可以在工作目录中保存文件的副本,删除本地仓库,并使用git clone
重新创建仓库。这种情况经常发生,即使是对有经验的软件开发人员来说,这也是/xkcd.com/1597
的笑话的基础。
版本控制系统是程序员的救星。提交代码的快照可以方便地检查您的进度,在某些情况下,还可以回滚您不需要的更改。从长远来看,学习像 Git 这样的版本控制系统的基础知识肯定会节省您的时间。
Python 项目通常有几个标准文件和文件夹,而cookiecutter
模块可以帮助您为这些文件创建起始样板文件。这些文件构成了您提交到本地 Git 仓库的第一批文件。我们称包含所有这些内容的文件夹为工作目录或项目文件夹。
Git 跟踪工作目录中的文件,所有这些文件都可以以三种状态之一存在:提交(也称为未修改或干净)、修改或暂存。Git 命令行工具有几个命令,比如git status
或git log
,可以让您查看这些信息,但是您也可以安装几个第三方 Git GUI 工具。
git init
命令在本地计算机上创建一个新的空仓库。git clone
命令从远程服务器复制一个仓库,比如流行的 GitHub 网站。无论哪种方式,一旦你有了一个仓库,你可以使用git add
和git commit
提交对你的仓库的更改,并使用git push
将这些提交推送到一个远程 GitHub 仓库。本章还描述了几个命令来撤销你所做的提交。执行撤消允许您回滚到文件的早期版本。
Git 是一个具有许多特性的扩展工具,本章仅涵盖版本控制系统的基础知识。您可以使用许多资源来了解更多关于 Git 的高级特性。我推荐两本你可以在网上找到的免费书籍:Scott Charcon 的《Pro Git》和 Eric Sink 的《版本实例控制》。