Git/简介
这里,我们将介绍最简单的 Git 命令:创建新的仓库、添加和提交文件、删除文件、回退错误提交、丢弃未提交的更改以及查看特定提交时的文件。
创建新的 Git 仓库很简单。有两个命令可以实现此功能:git-init(1) 和 git-clone(1)。克隆现有的仓库将在后面介绍。现在,让我们在新的目录中创建一个新的仓库
$ git init myrepo
在
- /home/username/myrepo/.git/ 初始化空 Git 仓库 (在 Linux 上)。
- C:/Users/username/myrepo/.git/ (在 Windows 上)。
如果您已经有一个想要变成 Git 仓库的目录
$ cd $my_preexisting_repo $ git init
以第一个例子为例,让我们看看发生了什么
$ cd myrepo $ ls -A .git
您的仓库的全部内容都将包含在 .git
目录中。相反,一些 SCM 会在您的工作目录中到处放置文件(例如,.svn
、.cvs
、.acodeic
等)。Git 会避免这种情况,并将所有内容放到仓库根目录下的一个名为 .git
的子目录中。
备注:要在 Windows 下设置 Git 打开时默认指向的目录,右键单击快捷方式,更改名为“起始位置”的字段的路径。
要检查仓库的状态,请使用 git-status(1) 命令。例如,一个新创建的仓库,还没有任何提交,应该会显示以下内容
$ git status On branch master Initial commit nothing to commit (create/copy files and use "git add" to track)
养成经常使用 git-status
的习惯,确保您在做您认为您正在做的事情。:)
与大多数其他 VCS 不同,Git 不假设您希望提交每个修改过的文件。相反,用户将他们希望提交的文件添加到暂存区(也称为索引或缓存,具体取决于您阅读的文档的哪一部分)。暂存区中的所有内容都会被提交。您可以使用 git-status(1) 或 git diff --staged
检查要提交的内容。
要将文件暂存到下一个提交,请使用命令 git-add(1)。
$ nano file.txt hack hack hack... $ git status # On branch master # # Initial commit # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # file.txt nothing added to commit but untracked files present (use "git add" to track)
这表明我们正在使用名为“master”的分支,并且有一个文件 Git 没有跟踪(没有提交历史)。Git 友好地指出,可以通过 git add file.txt
将该文件包含到下一个提交中。
$ git add file.txt $ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: file.txt #
添加文件后,它将显示为已准备好提交。现在就提交吧。
$ git commit -m 'My first commit' [master (root-commit) be8bf6d] My first commit 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file.txt
在大多数情况下,您不会希望使用 -m 'Commit message'
形式,而是将其省略,打开 $EDITOR
以便您能编写一个合适的提交信息。我们将在后面介绍,但在示例中,将使用 -m 'Commit message'
形式,以便读者能轻松地了解发生了什么。
您可以使用 git add -A
自动将所有更改的和未跟踪的文件暂存到下一个提交中。一旦文件被跟踪,git add -u
将在它发生改变时暂存它。
如果您改变主意,不想暂存某个文件,并且还没有提交,可以使用 git-reset(1) 命令的最简单形式来取消暂存该文件
$ git reset file.txt
只取消暂存一个文件,或者
$ git reset
移除暂存区中的所有内容。
git-reset
有比这更多的功能,例如
- 取消最近的两个提交,但不触碰文件:
git reset 'HEAD~2'
。 - 取消最近的两个提交及其对文件的修改:
git reset HEAD~2 --hard
。 - 取消分支上的最后两个操作:
git reset 'HEAD@{2}'
(它使用git reflog
)。这可以用来取消一个不希望的重置操作。
要排除某些未跟踪的文件,使 git add -A
看不到它们,请继续阅读……
git restore
回到参数中指定的[1]文件的版本。
通常,您的工作区中有一些文件您不想添加到仓库中。例如,emacs会将您使用它编辑的任何文件的备份副本写入,并在后面加上波浪号,例如filename~. 即使您可以手动避免将它们添加到提交中(这意味着永远不要使用 git add -A
),它们也会使状态列表变得混乱。
为了告诉 Git 忽略某些文件,您可以创建一个忽略文件,该文件的每一行都代表一个要忽略的文件规范(使用通配符)。可以通过在行首使用空格或#字符来添加注释。
例如
# Ignore emacs backup files: *~ # Ignore everything in the cache directory: app/cache
Git 在两个名称下查找忽略文件
- .git/info/exclude— 这是针对您自己的仓库副本的,而不是仓库的公共部分。
- .gitignore— 由于它在.git目录之外,它通常会像仓库中的任何其他文件一样被 Git 跟踪。
您在这两个文件(或两者)中放入的内容取决于您的需要。.gitignore是一个很好的地方来提及所有使用此仓库副本的人可能希望忽略的东西,例如构建产品。如果您正在进行自己的个人实验,这些实验不太可能涉及其他代码贡献者,那么您可以在.git/info/exclude.
中放入相关的忽略行。请注意,忽略文件条目只与 git status
和 git add -A
(添加所有新的和更改的文件)命令相关。任何您使用 git add filename
显式添加的文件都将始终被添加到仓库中,无论其名称是否与忽略条目匹配。一旦它们被添加到仓库中,对它们的更改将从今以后自动被 git add -u
跟踪。
Short (50 chars or less) summary of changes More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of an email and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); tools like rebase can get confused if you run the two together. Write your commit message in the present tense: "Fix bug" and not "Fixed bug." This convention matches up with commit messages generated by commands like git merge and git revert. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here - Use a hanging indent
让我们从几个原因开始,说明为什么将提交信息包装到 72 列是一个好主意。
- Git log 不会对提交信息进行任何特殊的包装。使用
less -S
的默认分页器,这意味着您的段落会远远超出屏幕边缘,难以阅读。在一个 80 列的终端上,如果我们减去 4 列用于左侧的缩进,再减去 4 列用于右侧的对称,我们就剩下 72 列。 git format-patch --stdout
将一系列提交转换为一系列电子邮件,使用信息作为邮件正文。良好的电子邮件礼仪要求我们包装纯文本电子邮件,以便在一个 80 列的终端上,即使嵌套了几层回复指示符也不会溢出。
Vim 用户可以通过安装我的 vim-git 运行时文件,或者简单地在 Git 提交信息文件中设置以下选项来满足此要求
:set textwidth=72
对于 Textmate,您可以在视图菜单下调整“换行列”选项,然后使用 ^Q 重新包装段落(确保之后有一个空行,以避免将注释混入其中)。以下是一个 shell 命令,可以将 72 添加到菜单中,这样您就不必每次都拖动来选择。
$ defaults write com.macromates.textmate OakWrapColumns '( 40, 72, 78 )'
比格式化主体内容更重要的是编写主题行。如示例所示,您应该力求主题行不超过 50 个字符(尽管这不是硬性限制),并且始终在主题行后面添加一个空行。第一行应该是提交引入的更改的简要摘要;如果有任何技术细节无法在这些严格的尺寸限制内表达,则将其放在正文中。主题行在整个 Git 中被使用,如果消息过长,则通常会被截断。以下是一些主题行出现的位置:
git log --pretty=oneline
显示一个简洁的历史映射,包含提交 ID 和摘要git rebase --interactive
在它调用的编辑器中为每个提交提供摘要- 如果配置选项merge.summary被设置,来自所有合并提交的摘要将被写入合并提交消息中
git shortlog
在它产生的类似变更日志的输出中使用摘要行- git-format-patch(1)、git-send-email(1) 和相关的工具使用它作为电子邮件的主题
- git-reflog(1),一个旨在帮助您从错误中恢复的本地历史记录,可以获取摘要的副本
gitk
,一个图形界面,其中包含一个用于显示摘要的列- Gitweb 和其他 Web 界面,例如 GitHub,在它们的界面中不同地方使用摘要。
主题/正文之间的区别可能看起来并不重要,但它是使 Git 历史比 Subversion 更易于使用的许多微妙因素之一。
删除文件
[edit | edit source]让我们继续进行一些提交,向您展示如何删除文件
$ echo 'more stuff' >> file.txt $ git status # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: file.txt # no changes added to commit (use "git add" and/or "git commit -a")
尽管 Git 不会强制用户提交所有修改过的文件,但这是一种常见的情况。如 git status
的最后一行所示,使用 git commit -a
可以提交所有修改过的文件,而无需先读取它们
$ git commit -a -m 'My second commit' [master e633787] My second commit 1 files changed, 1 insertions(+), 0 deletions(-)
看到提交后 Git 输出中的随机字符字符串(在上面的示例中以粗体显示)了吗?这是 Git 用于跟踪对象(在本例中为提交对象)的标识符的缩写。每个对象都使用 SHA-1 进行哈希运算,并通过该字符串进行引用。在本例中,完整字符串为e6337879cbb42a2ddfc1a1602ee785b4bfbde518,但您通常只需要前 8 个字符才能唯一识别该对象,因此 Git 只显示这么多。我们以后需要使用这些标识符来引用特定的提交。
要删除文件,请使用 Git 的“rm”子命令
$ git rm file.txt rm 'file.txt' $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # deleted: file.txt # $ ls -a . .. .git
请注意,这会从您的磁盘中删除该文件。如果您只想从 Git 仓库中删除该文件,但要保留该文件在您的工作目录中,请使用 git rm --cached
。
$ git commit -m 'Removed file.txt' [master b7deafa] Removed file.txt 1 files changed, 0 insertions(+), 2 deletions(-) delete mode 100644 file.txt
撤销提交
[edit | edit source]要使用另一个提交撤销提交,请使用 git revert
$ git revert HEAD Finished one revert. [master 47e3b6c] Revert "My second commit" 1 files changed, 0 insertions(+), 1 deletions(-) $ ls -a . .. file.txt .git
您可以指定任何提交,而不是 HEAD。例如
- HEAD 之前的提交
git revert HEAD^
- 五次提交之前的提交
git revert HEAD~5
- 由给定哈希值标识的提交
git revert e6337879
重置提交
[edit | edit source]git reset
提供与 git revert
相同的选项,但它不会创建撤销提交,而是直接取消提交,并让文件处于未提交状态。
要取消重置,请使用:git reset 'HEAD@{2}'
。
丢弃本地、未提交的更改
[edit | edit source]要丢弃更改并返回到最近提交的状态
$ git reset --hard HEAD
如上所述,您可以指定任何其他提交
$ git reset --hard e6337879
如果您只想重置一个文件(您在上次提交后对该文件进行了一些错误的更改),可以使用
$ git checkout filename
这将删除自上次提交以来对该文件的所有更改,但会保留其他文件不变。
获取文件的特定版本
[edit | edit source]要获取已提交文件的特定版本,您需要该提交的哈希值。您可以使用 git-log(1) 找到它
$ git log commit 47e3b6cb6427f8ce0818f5d3a4b2e762b72dbd89 Author: Mike.lifeguard <[email protected]> Date: Sat Mar 6 22:24:00 2010 -0400 Revert "My second commit" This reverts commit e6337879cbb42a2ddfc1a1602ee785b4bfbde518. commit e6337879cbb42a2ddfc1a1602ee785b4bfbde518 Author: Mike.lifeguard <[email protected]> Date: Sat Mar 6 22:17:20 2010 -0400 My second commit commit be8bf6da4db2ea32c10c74c7d6f366be114d18f0 Author: Mike.lifeguard <[email protected]> Date: Sat Mar 6 22:11:57 2010 -0400 My first commit
然后,您可以使用 git show
$ git show e6337879cbb42a2ddfc1a1602ee785b4bfbde518:file.txt hack hack hack... more stuff
Git 的 Checkout 与 Subversion 的 Checkout 不同
[edit | edit source]如果您在使用过 Subversion 集中式版本控制系统后才开始使用 Git,您可能会认为 Git 中的 checkout 操作与 Subversion 中的类似。并非如此。虽然 Git 和 Subversion 都允许您从仓库中检出源代码树的旧版本,但只有 Subversion 会跟踪您检出了哪个修订版。Git 不会。 git-status(1) 只会显示源代码树与当前分支 HEAD 不一致;它不会检查源代码树是否与历史记录中的某个之前的提交一致。
diff和patch:开源协作的货币
[edit | edit source]了解早期的使用非常重要diff(1)和patch(1)实用程序。diff是一个用于显示两个文本文件之间逐行差异的工具。特别是,统一 diff 会将添加/删除/更改的行并排显示,并用上下文行包围,这些行在两个版本中都是相同的。假设file1.txt的内容为
this is the first line. this is the same line. this is the last line.
而file2.txt的内容为
this is the first line. this line has changed. this is the last line.
那么统一 diff 如下所示
$ diff -u file1.txt file2.txt --- file1.txt 2014-04-18 11:56:35.307111991 +1200 +++ file2.txt 2014-04-18 11:56:51.611010079 +1200 @@ -1,3 +1,3 @@ this is the first line. -this is the same line. +this line has changed. this is the last line. $
注意每行开头多了一列,包含一个“-”(表示第一文件中存在但第二文件中不存在的行),一个“+”(表示第二文件中存在但第一文件中不存在的行),或一个空格(表示未更改的行)。有一些特殊的格式行用于标识正在比较的文件以及出现差异行的行号;所有这些都可以被patch工具理解,以便更改file1.txt的副本,使其完全像file2.txt:
$ diff -u file1.txt file2.txt >patch.txt $ patch <patch.txt patching file file1.txt $ diff -u file1.txt file2.txt $
注意第二个diff命令不再产生任何输出:文件现在完全相同了!
这就是协作软件开发的开始:人们不再交换整个源文件,而是以统一diff或patch格式(相同的东西)分发他们的更改。其他人只需将补丁应用到他们的副本即可。并且,只要更改没有重叠,您甚至可以将补丁应用到已经用来自其他人的另一个diff修补过的文件上!这样,来自多个来源的更改就可以合并到一个公共版本中,其中包含社区贡献的所有新功能和错误修复。
即使在今天,即使版本控制系统已经普遍使用,这些 diff/补丁仍然是分发更改的基础。 git-diff(1) 和 git-format-patch(1) 都产生与 diff -u
兼容的输出,并且可以被patch相应地理解。因此,即使您的补丁接收者没有使用 Git,他们仍然可以接受您的补丁。或者,您可能会收到来自没有使用 Git 的用户的补丁,因此他们没有使用git-format-patch,因此您无法将其提供给 git-am(1) 以自动应用它并保存提交;但这没关系,您可以使用 git-apply(1),甚至patch在您的源代码树上使用它,然后代表他们进行提交。
结论
[edit | edit source]您现在知道如何创建一个新仓库,或将您的源代码树转换为 Git 仓库。您可以添加和提交文件,还可以撤销错误的提交。您可以删除文件,查看特定提交中文件的狀態,还可以丢弃未提交的更改。
接下来,我们将看看 Git 项目的历史记录,使用命令行和一些 GUI 工具,并了解 Git 的强大分支模型。