附录 D 使用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 ..." 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 ..." 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 master
- nothing 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
- 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 ..." to update what will be committed)
- # (use "git checkout -- ..." 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 ..." to update what will be committed)
- # (use "git checkout -- ..." 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 ..." 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
上,且没有任何未提交的修改(见❼)。
需要经过一定的练习才能学会使用版本控制,但一旦开始使用,你就再也离不开它。