使用 Git 进行版本控制
使用 Git 进行版本控制
版本控制软件让你能够拍摄处于可行状态的项目的快照。修改项目(如实现新功能)后,如果项目不能正常运行,可恢复到前一个可行状态。
通过使用版本控制软件,你可以无忧无虑地改进项目,不用担心项目因你犯了错而遭到破坏。对大型项目来说,这显得尤其重要,但对于较小的项目,哪怕是只包含
一个文件的程序,这也大有裨益。
在这个附录中,你将学习如何安装 Git ,以及如何使用它来对当前开发的程序进行版本控制。 Git 是当前最流行的版本控制软件,它包含很多高级工具,可帮助团队协作
开发大型项目,但其最基本的功能也非常适合独立开发人员使用。 Git 通过跟踪对项目中每个文件的修改来实现版本控制,如果你犯了错,只需恢复到保存的前一个状
态即可。
D.1 安装 Git
Git 可在所有操作系统上运行,但其安装方法因操作系统而异。接下来的几节详细说明了如何在各种操作系统中安装它。
D.1.1 在 Linux 系统中安装 Git
要在 Linux 系统中安装 Git ,请执行如下命令:
$ sudo apt-get install git
这就成了。你现在可以在项目中使用 Git 了。
D.1.2 在 OS X 系统中安装 Git
你的 OS X 系统可能已经安装了 Git ,因此请尝试执行命令 git --version 。如果你在输出中看到了具体的版本号,说明你的系统安装了 Git ;如果你看到一条消息,提示你安装
或升级 Git ,只需按屏幕上的说明做即可。
你也可以访问 https://git-scm.com/ ,单击链接 Downloads ,再单击适合你所用系统的安装程序。
D.1.3 在 Windows 系统中安装 Git
要在 Windows 系统中安装 Git ,请访问 http://msysgit.github.io/ ,并单击 Download 。
D.1.4 配置 Git
Git 跟踪谁修改了项目,哪怕参与项目开发的人只有一个。为此, Git 需要知道你的用户名和电子邮件地址。你必须提供用户名,但可以使用虚构的电子邮件地址:
$ git config --global user.name "username"
$ git config --global user.email "username@example.com"
如果你忘记了这一步,在你首次提交时, Git 将提示你提供这些信息。
D.2 创建项目
我们来创建一个要进行版本控制的项目。在你的系统中创建一个文件夹,并将其命名为 git_practice 。在这个文件夹中,创建一个简单的 Python 程序:
hello_world.py
print("Hello Git world!")我们将使用这个程序来探索 Git 的基本功能。
D.3 忽略文件
扩展名为 .pyc 的文件是根据 .py 文件自动生成的,因此我们无需让 Git 跟踪它们。这些文件存储在目录 __pycache__ 中。为让 Git 忽略这个目录,创建一个名为 .gitignore 的特殊文件(这个
文件名以句点打头,且没有扩展名),并在其中添加下面一行内容:
.gitignore
__pycache__/
这让 Git 忽略目录 __pycache__ 中的所有文件。使用文件 .gitignore 可避免项目混乱,开发起来更容易。
注意 如果你使用的是 Python 2.7 ,请将这行内容改为 *.pyc 。 Python 2.7 不会创建目录 __pycache__ ,它将每个 .pyc 文件都存储在相应 .py 文件所在的目录中。其中的星
号让 Git 忽略所有扩展名为 .pyc 的文件。
你可能需要修改文本编辑器的设置,使其显示隐藏的文件,这样才能使用它来打开文件 .gitignore 。有些编辑器被设置成忽略名称以句点打头的文件。
D.4 初始化仓库
你创建了一个目录,其中包含一个 Python 文件和一个 .gitignore 文件,可以初始化一个 Git 仓库了。为此,打开一个终端窗口,切换到文件夹 git_practice ,并执行如下命令:
git_practice$ git init
Initialized empty Git repository in git_practice/.git/
git_practice$
输出表明 Git 在 git_practice 中初始化了一个空仓库。仓库是程序中被 Git 主动跟踪的一组文件。 Git 用来管理仓库的文件都存储在隐藏的 .git/ 中,你根本不需要与这个目录打交道,但千
万不要删除这个目录,否则将丢弃项目的所有历史记录。
D.5 检查状态
执行其他操作前,先来看一下项目的状态:
git_practice$ git status
# On branch master
#
# Initial commit
#
❷ # Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# .gitignore
# hello_world.py
#
❸ nothing added to commit but untracked files present (use "git add" to track)
git_practice$
❶
在 Git 中,分支 是项目的一个版本。从这里的输出可知,我们位于分支 master 上(见❶)。你每次查看项目的状态时,输出都将指出你位于分支 master 上。接下来的输出表明,我们
将进行初始提交。提交 是项目在特定时间点的快照。
Git 指出了项目中未被跟踪的文件(见❷),因为我们还没有告诉它要跟踪哪些文件。接下来,我们被告知没有将任何东西添加到当前提交中,但我们可能需要将未跟踪的文件加
入到仓库中(见❸)。
D.6 将文件加入到仓库中
下面将这两个文件加入到仓库中,并再次检查状态:
❶
❷
git_practice$ git add .
git_practice$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
❸ #
new file: .gitignore
# new file: hello_world.py
#
git_practice$
命令 git add . 将项目中未被跟踪的所有文件都加入到仓库中(见❶)。它不提交这些文件,而只是让 Git 开始关注它们。现在我们检查项目的状态时,发现 Git 找出了需要提交
的一些修改(见❷)。标签 new file 意味着这些文件是新添加到仓库中的(见❸)。
D.7 执行提交
下面来执行第一次提交:
❶
❷
❸
git_practice$ git commit -m "Started project."
[master (root-commit) c03d2a3] Started project.
2 files changed, 1 insertion(+)
create mode 100644 .gitignore
create mode 100644 hello_world.py
❹ git_practice$ git status
# On branch masternothing to commit, working directory clean
git_practice$
我们执行命令 git commit -m " message " (见❶)以拍摄项目的快照。标志 -m 让 Git 将接下来的消息( "Started project." )记录到项目的历史记录中。输出表明我
们在分支 master 上(见❷),且有两个文件被修改了(见❸)。
现在我们检查状态时,发现我们在分支 master 上,且工作目录是干净的(见❹)。这是你每次提交项目的可行状态时都希望看到的消息。如果显示的消息不是这样的,请仔细阅
读,很可能你在提交前忘记了添加文件。
D.8 查看提交历史
Git 记录所有的项目提交。下面来看一下提交历史:
git_practice$ git log
commit a9d74d87f1aa3b8f5b2688cb586eac1a908cfc7f
Author: Eric Matthes <eric@example.com>
Date: Mon Mar 16 07:23:32 2015 -0800
Started project.
git_practice$
你每次提交时, Git 都会生成一个包含 40 字符的独一无二的引用 ID 。它记录提交是谁执行的、提交的时间以及提交时指定的消息。并非在任何情况下你都需要所有这些信息,因此
Git 提供了一个选项,让你能够打印提交历史条目的更简单的版本:
git_practice$ git log --pretty=oneline
a9d74d87f1aa3b8f5b2688cb586eac1a908cfc7f Started project.
git_practice$
标志 --pretty=oneline 指定显示两项最重要的信息:提交的引用 ID 以及为提交记录的消息。
D.9 第二次提交
为展示版本控制的强大威力,我们需要对项目进行修改,并提交所做的修改。为此,我们在 hello_world.py 中再添加一行代码:
hello_world.py
print("Hello Git world!")
print("Hello everyone.")
如果我们现在查看项目的状态,将发现 Git 注意到了这个文件发生了变化:
git_practice$ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
❷ #
modified: hello_world.py
#
❸ no changes added to commit (use "git add" and/or "git commit -a")
git_practice$
❶
输出指出了我们当前所在的分支(见❶)、被修改了的文件的名称(见❷),还指出了所做的修改未提交(见❸)。下面来提交所做的修改,并再次查看状态:
❶
git_practice$ git commit -am "Extended greeting."
[master 08d4d5e] Extended greeting.
1 file changed, 1 insertion(+)
❷ git_practice$ git status
# On branch master
nothing to commit, working directory clean
❸ git_practice$ git log --pretty=oneline
08d4d5e39cb906f6cff197bd48e9ab32203d7ed6 Extended greeting.
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$
我们再次执行了提交,并在执行命令 git commit 时指定了标志 -am (见❶)。标志 -a 让 Git 将仓库中所有修改了的文件都加入到当前提交中(如果你在两次提交之间创建了新
文件,可再次执行命令 git add . 将这些新文件加入到仓库中)。标志 -m 让 Git 在提交历史中记录一条消息。
我们查看项目的状态时,发现工作目录也是干净的(见❷)。最后,我们发现提交历史中包含两个提交(见❸)。
D.10 撤销修改
下面来看看如何放弃所做的修改,恢复到前一个可行状态。为此,首先在 hello_world.py 中再添加一行代码:
hello_world.py
print("Hello Git world!")
print("Hello everyone.")
print("Oh no, I broke the project!")保存并运行这个文件。
我们查看状态,发现 Git 注意到了所做的修改:
git_practice$ git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
❶ #
modified: hello_world.py
#
no changes added to commit (use "git add" and/or "git commit -a")
git_practice$
Git 注意到我们修改了 hello_world.py (见❶)。我们可以提交所做的修改,但这次我们不提交所做的修改,而要恢复到最后一个提交(我们知道,那次提交时项目能够正常地运
行)。为此,我们不对 hello_world.py 执行任何操作 —— 不删除刚添加的代码行,也不使用文本编辑器的撤销功能,而在终端会话中执行如下命令:
git_practice$ git checkout .
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$
命令 git checkout 让你能够恢复到以前的任何提交。命令 git checkout . 放弃自最后一次提交后所做的所有修改,将项目恢复到最后一次提交的状态。
如果我们返回到文本编辑器,将发现 hello_world.py 被修改成了下面这样:
print("Hello Git world!")
print("Hello everyone.")
就这个项目而言,恢复到前一个状态微不足道,但如果我们开发的是大型项目,其中数十个文件都被修改了,那么恢复到前一个状态,将撤销自最后一次提交后对这些文件所做
的所有修改。这个功能很有用:实现新功能时,你可以根据需要做任意数量的修改,如果这些修改不可行,可撤销它们,而不会对项目有任何伤害。你无需记住做了哪些修改,
因而不必手工撤销所做的修改, Git 会替你完成所有这些工作。
注意 想要看到以前的版本,你可能需要在编辑器窗口中单击,以刷新文件。
D.11 检出以前的提交
你可以检出提交历史中的任何提交,而不仅仅是最后一次提交,为此可在命令 git check 末尾指定该提交的引用 ID 的前 6 个字符(而不是句点)。通过检出以前的提交,你可以
对其进行审核,然后返回到最后一次提交,或者放弃最近所做的工作,并选择以前的提交:
git_practice$ git log --pretty=oneline
08d4d5e39cb906f6cff197bd48e9ab32203d7ed6 Extended greeting.
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$ git checkout be017b
Note: checking out 'be017b'.
❶
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at be017b7... Started project.
git_practice$
检出以前的提交后,你将离开分支 master ,并进入 Git 所说的分离头指针( detached HEAD )状态(见❶)。 HEAD 表示项目的当前状态,之所以说我们处于分离状态,是因为我们离
开了一个命名分支(这里是 master )。
要回到分支 master ,可检出它:
git_practice$ git checkout master
Previous HEAD position was be017b7... Started project.
Switched to branch 'master'
git_practice$
这让你回到分支 master 。除非你要使用 Git 的高级功能,否则在检出以前的提交后,最好不要对项目做任何修改。然而,如果参与项目开发的人只有你自己,而你又想放弃较近
的所有提交,并恢复到以前的状态,也可以将项目重置到以前的提交。为此,可在处于分支 master 上的情况下,执行如下命令:
❶
git_practice$ git status
# On branch master
nothing to commit, working directory clean
❷ git_practice$ git log --pretty=oneline
08d4d5e39cb906f6cff197bd48e9ab32203d7ed6 Extended greeting.
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
❸ git_practice$ git reset --hard be017b
HEAD is now at be017b7 Started project.
❹ git_practice$ git status
# On branch master
nothing to commit, working directory clean
❺ git_practice$ git log --pretty=oneline
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$我们首先查看了状态,确认我们在分支 master 上(见❶)。查看提交历史时,我们看到了两个提交(见❷)。接下来,我们执行命令 git reset --hard ,并在其中指定了要
永久地恢复到的提交的引用 ID 的前 6 个字符(见❸)。再次查看状态,发现我们在分支 master 上,且没有需要提交的修改(见❹)。再次查看提交历史时,发现我们处于要从它
重新开始的提交中(见❺)。
D.12 删除仓库
有时候,仓库的历史记录被你搞乱了,而你又不知道如何恢复。在这种情况下,你首先应考虑使用附录 C 介绍的方法寻求帮助。如果无法恢复且参与项目开发的只有你一个人,
可继续使用这些文件,但要将项目的历史记录删除 —— 删除目录 .git 。这不会影响任何文件的当前状态,而只会删除所有的提交,因此你将无法检出项目的其他任何状态。
为此,可打开一个文件浏览器,并将目录 .git 删除,也可通过命令行完成这个任务。这样做后,你需要重新创建一个仓库,以重新对修改进行跟踪。下面演示了如何在终端会话中
完成这个过程:
❶
git_practice$ git status
# On branch master
nothing to commit, working directory clean
❷ git_practice$ rm -rf .git
❸ git_practice$ git status
fatal: Not a git repository (or any of the parent directories): .git
❹ git_practice$ git init
Initialized empty Git repository in git_practice/.git/
❺ git_practice$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# .gitignore
# hello_world.py
#
nothing added to commit but untracked files present (use "git add" to track)
❻ git_practice$ git add .
git_practice$ git commit -m "Starting over."
[master (root-commit) 05f5e01] Starting over.
2 files changed, 2 insertions(+)
create mode 100644 .gitignore
create mode 100644 hello_world.py
❼ git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$
我们首先查看了状态,发现工作目录是干净的(见❶)。接下来,我们使用命令 rm -rf .git (在 Windows 系统中,应使用命令 rmdir /s .git )删除了目录 .git (见❷)。
删除文件夹 .git 后,当我们再次查看状态时,被告知这不是一个 Git 仓库(见❸)。 Git 用来跟踪仓库的信息都存储在文件夹 .git 中,因此删除该文件夹也将删除整个仓库。
接下来,我们使用命令 git init 新建一个全新的仓库(见❹)。然后,我们查看状态,发现又回到了初始状态,等待着第一次提交(见❺)。我们将所有文件都加入仓库,并
执行第一次提交(见❻)。然后,我们再次查看状态,发现我们在分支 master 上,且没有任何未提交的修改(见❼)。
需要经过一定的练习才能学会使用版本控制,但一旦开始使用,你就再也离不开它。
中文字体: Noto Sans CJK SC Regular
英文字体: Liberation Serif