关于GIT & 杂项的注意事项
这是关于 GIT & 杂项主题的笔记。
背景
GIT 最初由 Linus Torvalds 编写。GIT 是一个源代码控制系统,而且 GIT 不仅仅是一个源代码控制系统。我找到的最佳 GIT 参考是 这本书,我强烈建议您阅读。但在本文中,我将范围限制在日常生活中最常用的 GIT 功能。
安装最新版 GIT
我用于编写此笔记的计算机是“Linux Mint 18.3 Cinnamon 64位”虚拟机。Ubuntu 默认存储库中没有最新的 GIT。要安装最新版 GIT,我需要将“Ubuntu Git Maintainers”添加到存储库列表中。
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
撰写本文时,最新的 GIT 版本是“2.18.0”。
然后,我们可以使用以下命令安装最新版 GIT:
sudo apt-get install git
我们可以使用以下命令检查所用 GIT 的版本:
git --version
GIT 可用于所有操作系统,并且在最近的版本中一直非常稳定。在其他操作系统上使用不同版本时,您应该不会看到太大差异。
第一个 GIT 仓库 & ".gitignore" & ".git"
初始化一个 GIT 仓库
GIT 仓库只是一个目录,其中包含一个可选文件,名为“.gitignore”。在我的示例中,我添加了一个最简单的“.gitignore”,它告诉 GIT 不要忽略任何文件,特别是“.gitignore”文件。
# Do not ignore anything
!.gitignore
要将目录转换为 GIT 仓库,我们只需在目录中发出以下命令:
git init
“git init
”命令将“git-test”目录初始化为 GIT 仓库,并在其中添加了“.git”目录。
将文件添加到仓库并提交
初始化仓库后,我们可以向其中添加一些文件:
touch A B C
在 GIT 仓库中,我们可以使用“git status
”命令检查文件的状态。
GIT 识别出新文件(包括“.gitignore”文件),并建议我们提交它们。要将文件提交到仓库,我们首先需要暂存它们。
例如,如果我们想暂存文件“A”,我们可以发出以下命令:
git add -- A
如果我们想暂存所有文件,我们可以发出以下命令:
git add .
我们还可以使用“-A
”选项暂存所有文件,这等同于“.
”选项。如果我们只想暂存已修改和已删除的文件,可以使用“-u
”选项。
git add -u
暂存文件后,我们可以进行一次 commit
。
git commit -m "First commit"
如果我们提交后检查仓库状态,GIT 会告诉我们一切都已妥善保管。
如果这是您第一次进行“commit
”,GIT 会要求您提供一些信息用于簿记。您可以通过以下命令设置 GIT 中的姓名和电子邮件:
git config --global user.email "song.li@example.com"
git config --global user.name "Song Li"
如果您只想将姓名和电子邮件应用于当前仓库,可以使用以下命令:
git config user.email "song.li@example.com"
git config user.name "Song Li"
".git" 目录包含一切
GIT 将所有配置和提交保存在“.git”目录中。要体验 GIT 的功能,我们可以删除除“.git”目录外的所有文件。
rm -f A
rm -f B
rm -f C
rm -f .gitignore
GIT 可以告诉我们这些文件已被删除。
我们可以使用“git checkout
”命令将所有文件恢复到上一次提交的状态。
git checkout -- .
如果我们想将“git-test”目录从 GIT 控制中移除,只需删除“.git”目录即可,“git-test”目录将不再是 GIT 仓库。
GIT 分支 & "git checkout"
创建新分支
与任何源代码控制系统一样,人们通常通过分支使用 GIT。我们可以使用“git branch
”命令查找仓库中的分支。
默认情况下,在初始化 GIT 仓库时会创建“master
”分支。当我们处于 GIT 分支中时,我们可以基于当前工作分支创建新分支。
git branch branch-1
由于活动分支是“master
”,新创建的“branch-1
”的初始状态将与“master
”分支的最后一次提交完全相同。作为快捷方式,我们也可以使用“git checkout
”命令创建分支。
git checkout -b branch-2
“git checkout -b
”命令会创建一个新分支并立即切换到该分支。
现在我们已经创建了两个新分支,活动工作分支是“branch-2
”。
"git checkout" 一个分支
在 GIT 术语中,切换到分支称为检出(checkout)一个分支。如果我们想切换到“branch-1
”,我们可以发出以下命令:
git checkout branch-1
删除分支
有时您可能需要删除一个分支。让我们先创建一个名为“to-be-deleted
”的分支。
git checkout -b to-be-deleted
除非强制执行,否则您无法删除当前正在检出的分支。我们通常会切换到另一个分支并使用“-d
”选项进行删除。
git checkout master
git branch -d to-be-deleted
在 GIT 分支中工作
虽然我们经常切换分支,但大部分时间都在特定分支中工作。在本节中,我将花更多时间介绍如何在分支中撤销更改,而不是专注于在分支中提交更改。
git checkout -b working-branch
由于到目前为止,整个仓库中只有一次提交,“working-branch
”与“master
”分支匹配。其中有三个文件。
现在,让我们在该分支中添加一个文件、删除一个文件并修改一个文件。
rm -f C
touch D
echo "Modified in working-branch" >> A
为了继续进行其余的示例,让我们现在暂存更改。
git add .
提交之前的撤销暂存
暂存后,GIT 已准备好提交暂存的更改。在此示例中,我们删除了“C
”并添加了“D
”。GIT 将删除/添加识别为文件名更改,这是可以的。
如果我们不想提交对“C
”的删除,可以发出以下命令来撤销暂存:
git reset -- C
如果我们想撤销所有暂存的更改,可以发出以下命令:
git reset
调用“git reset
”后,所有文件都已从暂存中移除。
将文件恢复到上次提交
对于未暂存的文件,我们可以使用以下命令将其恢复到上次提交的状态:
git checkout -- A
如果我们想恢复所有文件,可以发出以下命令:
git checkout -- .
我们可以发现,“git checkout
”并未删除新添加的文件。在 GIT 中,新添加的文件称为未跟踪文件。要删除未跟踪的文件,可以使用以下命令:
git clean -fd
“-f
”选项告诉 GIT 清理未跟踪文件,“-d
”选项告诉 GIT 清理未跟踪目录。如果我们想清理甚至被忽略的文件和目录,可以使用“-x
”选项。
git clean -fdx
无论文件是否已暂存,如果您想将分支中所有文件的状态设置为上次提交的状态,可以使用以下命令:
git reset --hard
丢弃提交
如果我们已经进行了一次提交,GIT 仍然允许我们丢弃它。
rm -f C
touch D
echo "Modified in working-branch" >> A
git add .
git commit -m "A commit to regret"
提交后,我们可以使用“git log
”查找分支上的所有提交。
git log
要丢弃“A commit to regret”(一次令人后悔的提交),我们可以发出以下命令:
git reset --hard c3b2c91f00ff4e4c97ba4484592c5c0284ae198e
我们还可以使用“HEAD~
”来表示前一次提交的哈希码。
git reset --hard HEAD~
“HEAD”代表最近的提交,“HEAD~
”代表父提交。在合并的情况下,提交可能有两个父提交。“HEAD^1
”是第一个父提交,“HEAD^2
”是第二个父提交。
撤销已发布的提交
如果您已将提交“推”送到远程仓库,最好添加一个新提交来维护您的提交历史记录。您可以使用“git revert”命令。
git revert -n 2870f17ac38e68a0..HEAD
其中“2870f17ac38e68a0
”是要还原到的提交的哈希值。然后,您可以提交更改并将新提交推送到远程仓库。
从提交中获取文件
如果您想从某个提交或分支中获取文件到当前分支,可以使用以下命令:
git checkout 2870f17ac38e -- path/to/file
其中“2870f17ac38e
”是要从中获取文件的所需提交的哈希值。
"git diff"
GIT 提供了一个很好的工具“git diff
”来比较文件与其暂存状态。如果没有暂存新更改,它会比较文件与其上次提交的状态。
git checkout working-branch
echo "AAA" > A
git commit -am "Commit No.1"
echo "BBB" >A
git diff -- A
如果我们想比较分支中的所有文件,可以使用以下命令:
git diff
如果我们想与另一个分支(例如“master
”分支)进行比较,可以使用以下命令:
git diff master
"git checkout" 与 "git reset"
GIT 有许多高度重载的命令,这很容易让我们感到困惑。“git checkout
”和“git reset
”可能会让人相当困惑,因为它们在内部是相关的。在不深入内部实现的情况下,我只会谈论它们最常见的用法。
"git checkout"
“git checkout
”的典型用例是检出分支。
git checkout branch-1
如果您想同时创建并切换到一个分支,可以使用“-b
”选项。
git checkout -b a-new-branch
“git checkout
”还可以用于撤销未暂存的更改。例如,以下命令将文件“A”的更改恢复到其上次提交的状态。
git checkout -- A
如果您想丢弃所有未暂存的更改,可以使用以下命令:
git checkout -- .
"git reset"
“git reset
”最常见的用例之一是撤销暂存文件。例如,我们可以使用以下命令撤销文件“A”的暂存:
git reset -- A
如果我们想撤销所有暂存的更改,可以发出以下命令。
git reset
如果我们想完全丢弃分支上最近的提交,可以发出以下命令:
git reset --hard HEAD^1
根据文档,“git reset
”有三种常用模式:
- "--soft" 模式 - 完全不触碰索引文件或工作树(但会将 HEAD 重置到 <commit>,就像所有模式一样)。这会使您所有已更改的文件都处于“待提交的更改”状态,正如 git status 命令所显示的。
- "--mixed" 模式 - 重置索引但不重置工作树(即,保留更改的文件但未标记为提交),并报告未更新的内容。这是默认操作。
- "--hard" 模式 - 将索引和工作树重置到 <commit>。自 <commit> 以来对跟踪文件所做的任何更改都将被丢弃。
“--mixed
”模式是默认模式,这解释了为什么我们可以使用“git reset
”来撤销所有我们不想提交的暂存更改。
关于 ".git" 目录的一点说明
正如我们所知,GIT 将所有信息保存在“.git”目录中。在不费力去理解 GIT 如何工作的每个细节的情况下,至少快速浏览一下“.git”目录是有益的。
"config" & "description" 文件
在 GIT 仓库中,“config”文件保存与仓库相关的基本配置信息。
例如,在我们的仓库中,它包含用户名和电子邮件。“description”文件保存仓库的名称。创建仓库时,我们没有给它命名。但我们可以通过修改“description”文件来给它命名。
echo "git-test" > description
"HEAD" 文件 & "refs" 目录
我们可能好奇 GIT 如何知道所有分支。让我们看看“HEAD”文件。
“HEAD”文件是一个小文件,其中包含当前工作分支的名称。分支名“working-branch”对应于“refs/heads”目录中的一个文件。
对于仓库中的每个分支,我们都可以在“refs/heads”目录中找到同名的文件。当我们切换分支时,我们会修改“HEAD”文件以指向新的工作分支。现在让我们看看“master”分支文件。
“master”文件是一个小文件,它只有一个哈希码,指向由该哈希码表示的提交。当我们检出一个分支时,GIT 可以通过分支名称找到提交并重建工作目录。重要的是要知道不同的分支可以指向同一个提交。
"objects" 目录 & "git cat-file"
GIT 仓库中的每个提交以及所有提交的所有文件版本都保存在“objects”目录中。Linus Torvalds 将数据组织成分层子目录,以便于管理和检索。
我们可以使用“git cat-file”查看“objects”目录中保存的数据。例如,我们可以探索提交“c3b2c91f00ff4e4c97ba4484592c5c0284ae198e
”的数据,这是“master”分支的 HEAD。
git cat-file -p c3b2c91f00
您可能会注意到我没有使用完整的哈希码,只使用了前几个字母。在 GIT 中,在大多数情况下,仅使用几个字母足以识别完整的哈希码。上面的命令告诉我们,该提交有一个由另一个哈希码表示的树结构。让我们进一步查看这个树。
我们可以在树中看到我们提交的所有文件。我们可以进一步查看此分支中“.gitignore”文件的版本。
git cat-file -p b4e54723341
这正是我们在初始化 GIT 仓库时“.gitignore”文件的内容。GIT 在“objects”目录中为我们保存了它。
"index" 文件 & "git ls-files"
索引文件充当提交和工作目录之间的枢纽。
- 当我们检出一个分支时,索引会被更新以匹配分支的 HEAD 文件指向的提交信息,工作目录也会被更新以匹配内容。
- 当我们暂存一个文件时,索引会针对该文件进行更新,因此 GIT 知道我们已经暂存了一个文件,但尚未提交。
- 当我们进行提交时,会创建一个新的提交,其中索引中的所有信息都将存储到“objects”目录中。
“index”文件是一个二进制文件,我们无法直接查看其内容。但我们可以使用“git ls-files”命令来查看它。
git checkout working-branch
git ls-files --stage
我们可以看到所有文件都有相同的哈希码,除了“.gitignore”,因为此时所有文件都是空的。
echo "Add some content" >> A
git add -- .
如果我们修改文件“A”并暂存它,我们可以看到索引已更新。
“git cat-file”可以告诉我们已暂存的确切内容。
GIT 合并 & 冲突 & "--continue"
在使用任何源代码控制系统时,您最终都会遇到合并,GIT 也不例外。一个简单但经常被忽视的问题是“谁是谁”。
谁是谁?
感谢这篇笔记明确清晰地解答了“谁是谁”的问题。在 Git 中,执行合并需要两个步骤:
- 检出应接收更改的分支。
- 调用“
git merge
”命令,并指定包含所需更改的分支名称。
这清楚地回答了成功合并后,您的当前工作分支将被更新的问题。
快进合并 (Fast Forward Merge)
在进行合并之前,让我们先看看我们分支的状态。
git show-ref
我所有的分支都指向同一个提交。这意味着我所有的分支内容都完全相同。现在,让我们在“branch-1
”中进行一些更改并提交。
git checkout branch-1
echo "Mofified in branch-1" >> A
git add -- .
git commit -m "branch-1 is updated"
现在,让我们检出“branch-1
”的 HEAD 信息。
git rev-parse HEAD
git cat-file -p HEAD
每当 GIT 进行提交时,它都会记录其父提交。如果我们现在想将“branch-1
”合并到“master
”分支,GIT 就有足够的信息来完成合并,而无需查看每个分支的内容。
- 如果接收分支的 HEAD 是期望分支的父链上的一个父提交或远程父提交,GIT 会识别出在接收分支中没有进行任何更改。
- 在这种情况下,GIT 将简单地用期望分支替换接收分支的所有内容。在 GIT 术语中,这称为“快进”合并。
现在,让我们将“branch-1
”合并到“master
”分支。
git checkout master
git merge branch-1
在快进合并的情况下,GIT 将简单地用“branch-1
”的 HEAD 更新“master
”分支的 HEAD。
“master
”分支的结果等同于发出以下命令时的结果:
git reset --hard f5e3b0658afeb194340620d912e389d9c06f2cd0
三方合并 (Three Way Merge)
如果两个分支都进行了更改,GIT 需要查看每个分支的内容。最常见的情况是 GIT 将执行三方合并。GIT 将比较每个分支的 HEAD 提交及其最近的共同祖先,以确定文件是添加、删除还是修改。GIT 将在以下情况下声明冲突,并将决策留给我们:
- 如果一个文件在两个分支中都被修改,并且同一行或相邻行的内容不同。
- 如果一个文件在一个分支中被删除,而在另一个分支中被修改。
- 如果一个文件在两个分支中都被添加,但同一行或相邻行的内容不同。
现在,让我们向“branch-1
”和“branch-2
”提交一些更改,以完成三方合并。
git checkout branch-1
echo "Modified in branch-1" > A
rm -f B
echo "Added in branch-1" >> D
git add -- .
git commit -m "Prepare for merge with branch-2"
在“branch-1”中,我们修改了文件“A”,删除了文件“B”,并添加了包含内容的文件“D”。
git checkout branch-2
echo "Modified in branch-2" > A
echo "B is modified" >> B
echo "Added in branch-2" >> D
git add -- .
git commit -m "Prepare for merge with branch-1"
在“branch-2”中,我们修改了文件“A”,修改了文件“B”,并添加了包含不同内容的文件“D”。现在,让我们将“branch-2”合并到“branch-1”。
git checkout branch-1
git merge --no-commit branch-2
我们遇到了冲突,可以使用“git status
”进一步查看冲突。
在发生冲突的情况下,GIT 会更改冲突文件的内容,以帮助我们做出决策。例如,文件“D”中的内容如下:
由于冲突,GIT 无法为我们做出决策。我们需要检查每个文件,以决定我们要做什么。如果我们想保留文件“B”并手动解决“A”和“D”上的冲突,可以发出以下命令:
git checkout --theirs B
echo "Actual merge result for A" > A
echo "Actual merge result for D" > D
当然,这是一个过度简化的冲突解决。在实际情况中,您需要使用自己喜欢的编辑器仔细查看每个文件,以决定对每个文件的最终决策。解决冲突后,您可以提交合并。
git add -- .
git commit -m "Merge branch 2"
您还可以使用“git merge --continue
”来完成合并。
git merge --continue
如果您现在检查分支的 HEAD,您会发现提交有两个父提交:
这两个父提交都是新提交合并的来源。
GIT 标签 (Tag)
- Git 能够将仓库历史中的特定点标记为重要。通常,人们使用此功能来标记发布点。
- 标签非常类似于一个不变的分支,它只是一个指向特定提交的指针。
轻量级标签和带注释标签
您可以使用以下命令创建轻量级标签:
git tag v01.01.01
您还可以通过创建带注释的标签来包含更多信息:
git tag -a v01.01.02 -m "This is an annotated tag"
您可以使用以下命令列出所有标签:
git tag -l
您还可以通过在命令中提供过滤模式来过滤标签:
git tag -l v01.01.*
如果您想在列出标签时看到注释,可以使用“-n
”选项:
git tag -n
您可以使用以下命令删除标签:
git tag -d v01.01.02
如果您想查看与标签提交相关的哈希值,可以使用以下命令:
git show-ref tags v01.01.01
将标签推送到远程仓库
如果您想将标签推送到远程仓库,可以使用以下命令:
git push origin v01.01.01
以下命令会将所有标签推送到远程仓库:
git push --tags
您可以使用以下命令删除远程标签:
git push --delete origin v01.01.01
仓库大小 & "reflog" & "git gc"
GIT 在最小化仓库大小时做得很好。在大多数情况下,您不必担心。但如果您好奇,可以看看 GIT 的垃圾回收。这是我找到的关于 GIT 垃圾回收的最佳笔记,我推荐您阅读。为了了解 GIT 垃圾回收的工作原理,让我们进行一次提交然后丢弃它。
git checkout working-branch
echo "Whatever modification" >> B
git add -- .
git commit -m "Commit to be discarded"
我们可以使用以下命令找到此提交的哈希码:
git rev-parse HEAD
bffe4e1bfcbbf026e3e9b34dacc66cf18dcb501c
我们可以发出以下命令来丢弃此提交并将分支恢复到前一次提交:
git reset --hard HEAD~
丢弃提交后,“bffe4e1...
”这个提交不再与任何分支关联。理想情况下,我们应该能够对其进行垃圾回收。
git gc --prune=now
但如果我们进一步查看,会发现提交仍在仓库中:
git cat-file -p bffe4e1
垃圾回收未能处理此提交的原因是它与日志相关联。当我们进行提交时,该提交会被添加到日志中,我们以后可以引用它。在 GIT 中,这称为“reflog”。我们需要清空日志,以便可以回收此提交。
git reflog expire --expire=now --all
清空日志后,我们可以再次运行“git gc --prune=now
”。我们应该会看到悬空提交已从仓库中清除。
如果您想查找所有悬空提交和对象,可以使用以下命令:
git fsck --full
我无法从GIT 网站上找到“reflog
”的默认过期时间。但从这个链接来看,默认时间是 90 天,并且可以配置。
远程仓库
使用本地仓库使我们有机会学习 GIT 的大部分内容,但没有远程仓库,您就无法与团队共享工作。学习使用远程仓库所付出的努力与我们在本地仓库上付出的努力不成比例。我不会花太多时间来讨论它。为了完整起见,我将只列出最常用的命令。对大多数人来说,处理远程仓库的第一件事就是“git clone
”。
git clone https://github.com/BigMountainTiger/lu-decomposition.git
我在“GITHUB”上创建了一个名为“lu-decomposition”的仓库,请随时克隆。如果您想查找所有远程分支,可以使用“-r
”选项。
git branch -r
如果您有权限,您也可以将更改推送到远程仓库。如果您创建了一个本地分支,而远程仓库尚不知晓,可以使用以下命令将其发布到远程:
git checkout -b new-branch
git push --set-upstream origin new-branch
发布分支后,您可以简单地使用“git push
”将本地提交发送到远程。
git push
您可以使用“git pull
”来获取其他人推送到远程的提交。
git pull
如果您想检查远程分支是否有任何新更新,但又不想在本地拉取,可以使用 fetch
命令。
git fetch
如果您想获取远程仓库的状态,但不仅限于您的工作分支,可以使用以下命令:
git remote update
您可以删除远程分支:
git push origin --delete new-branch
当然,您始终可以删除本地分支:
git branch -d new-branch
如果您想让 GIT 记住您对远程仓库的凭据,可以使用以下命令:
git config --global credential.helper store
关注点
- 这是关于 GIT & 杂项主题的笔记。
- 我找到的最佳 GIT 参考是 这本书,我强烈建议您阅读。本文只是将最常用的 GIT 命令汇总在一起。
- 希望您喜欢我的博文,希望这篇笔记能在某种程度上帮助到您。
历史
- 2018年6月27日:首次修订