Git/简介
在这里,我们将介绍最简单的 Git 命令:创建新仓库,添加和提交文件,删除文件,撤销错误提交,丢弃未提交的更改以及查看特定提交的文件。
创建新的 Git 仓库很简单。有两个命令涵盖了此功能:git-init(1) 和 git-clone(1)。克隆现有的仓库将在后面介绍。现在,让我们在新的目录中创建一个新的仓库
$ git init myrepo
已在
- /home/username/myrepo/.git/ 上初始化空的 Git 仓库(Linux)。
- C:/Users/username/myrepo/.git/ 上初始化空的 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 中的签出操作类似于 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 存储库。您可以添加和提交文件,还可以撤销错误的提交。您可以删除文件、查看某个提交中文件的狀態,还可以丢弃未提交的更改。
接下来,我们将从命令行和一些 GUI 工具中查看 git 项目的历史记录,并了解 git 的强大分支模型。