`

架构师必知:了解 Git 的工作方式,而不仅仅是了解命令

 
阅读更多
it 是一种常用的分布式源代码库。它是由 Linux 创作者 Linus Torvalds 创建的,旨在管理 Linux 内核源代码。GitHub 等整体服务都是基于它创建的。甚至 IBM 的 DevOps Services 和 IBM Rational Team Concert™ 源代码库也使用它。因此,如果您想在 Linux 领域中编写程序,或者是结合使用 IBM 的 DevOps Services 和 Git,则对 Git 有很好的了解会很有帮助。
 
当我开始使用 Git 时,我拥有 Concurrent Versions System (CVS) 和 Apache Subversion (SVN) 方面的经验,因此我试着从这些经典源代码库系统方面来了解它。这种思考方式让我对 Git 的能力了解有限。从那时起,我对 Git 的了解逐渐增加,本文是一种 “自我提醒” 方式的记录,提醒自己 Git 的工作方式并向新手介绍 Git。我假设您了解其他经典源代码库(比如 CVS 或 SVN)的工作方式。
 
基础知识
从经典源代码库中的一个基本示例开始,如图 1 所示。在经典源代码库中,会将包含文件和子文件夹的文件夹视为内容进行处理(CVS 和 Git 实际上不会处理文件夹,仅处理路径位置下的文件)。存储库包含所有内容版本,而工作目录是修改代码的位置。将代码从存储库签出(checkout)到工作目录,并将在此工作目录中所做的更改提交给存储库中的新内容版本。
 
图 1. 经典源代码库工作区处理
将提交发送给存储库,并签出检索结果
每次提交创建一个来自之前修改的父版本的新子内容版本,如图 2 所示。内容存储为一系列版本,也称为快照,由提交操作创建的父子关系进行链接。提交的父版本和子版本之间的信息更改称为变更集。
 
此系列版本称为流或分支。在 SVN 中,主要流称为 trunk;在 CVS 中它通常称为 HEAD;在 Git 中它通常称为 master。可以在实施项目中使用分支来分离特定功能开发,或者使用分支维护旧版本。
 
图 2. 在经典存储库中创建新版本
签出内容,更改,然后提交
到目前为止,Git 看起来很像此类经典源代码库,是吧?不幸的是,相似之处到此就结束了。CVS 和 SVN 的一个主要特点是它们拥有一个中央存储库。Git 是分布式的。多个存储库可以在软件开发中一起工作,实际上,每个开发人员的存储库与任意基于服务器的 Git 存储库的工作和通信方式相同。
 
Git 如何工作?
一旦您真正了解了 Git,会发现它的主要原则非常简单。
首先,Git 以快照方式处理内容,每个提交一个快照,并且知道如何在两个快照之间应用或回滚变更集。这是一个重要的概念。在我看来,了解应用和回滚变更集的概念让 Git 更容易理解,且使用起来更加简单。
 
这是真正的基本原理。其他一切都是由此衍生的。下面深入介绍一下 Git。
 
使用 Git
git init
初始化存储库。
 
git checkout <branch>
将分支从存储库签入到工作目录。
 
git add <file>
将文件中的更改添加到变更集。
 
git commit
将变更集从工作目录提交到存储库。
 
要开始使用 Git,只需要运行 "git init" 命令。这会将当前目录直接转换为 Git 工作目录并在此处创建的 .git(隐藏)目录中创建存储库。然后就可以开始使用 Git 了。checkout 和commit 命令与其他源代码库中的同名命令相同,但关注变更集是因为在 Git 中添加了 add命令(与 SVN 相同)。使用此命令,会将工作目录中的更改添加到下一次提交的暂存区。此暂存区通常名为 index。图 3 展示了从快照版本 A 到快照版本 B 创建变更集的过程。
 
git status 有助于跟踪已添加的更改、未添加的更改以及更改所在的分支。
 
图 3. 在 Git 中创建变更集
将更改添加到暂存区,然后提交到存储库
git status
显示工作目录的状态。
 
git log
显示工作目录的更改历史。
 
git diff
显示可能更改的差异。
 
git diff --cached
显示暂存区的差异。
 
git diff <name> -- <path>
显示工作目录和指定提交(id 或名称)之间的 <path> 差异。
 
git log 显示工作目录中的更改(即提交)历史,或者使用 git log <path> 显示应用于指定路径的更改。
 
尽管 git status 列出了工作区中的修改文件和 index 中的文件,但是您可以使用 git diff 命令查看文件之间的差异。仅使用 git diff(不使用参数)会只显示工作目录中已添加到 index 的更改。需要使用 git diff --cached 来查看 index 中的实际内容:暂存的更改。git diff <name>或 git diff <name> -- <path> 显示当前工作目录和工作目录的命名提交或指定路径之间的差异。名称可能是 commit ID、分支名或其他名称。这是介绍命名的好机会。
 
命名
备注:
由于 commit ID 很长,我在图中仅使用缩写词,比如 "(A)"、"(B)" 等。
 
我们来看一下 Git 中的命名方式。快照是 Git 中的主要元素。它们被命名为 commit ID,这一种哈希 ID,比如 "c69e0cc32f3c1c8f2730cade36a8f75dc8e3d480"。这是来自快照的内容,由实际内容和一些元数据组成,比如提交时间、作者信息、父内容等。快照没有 CVS 的点数字版本,也没有 SVN 的交易号(和 /branches 顶级目录下的路径)。因此,与在其他存储库中一样,从 Git 快照名中无法确定任何顺序。为了方便起见,Git 可以将这些长哈希值缩写为短名称,方法是从 ID 开头提取最小字符数量,这样短名称在存储库中仍然是惟一的。在上述示例中,短名称是 "c69e0cc"。
 
注意术语提交(commit),作为动词是创建快照,而作为名词是生成的快照。
 
通常不需要使用 commit ID;而是使用分支。在其他源代码库中,更改的命名流被称为分支。在 Git 中,更改流是一个有序的变更集列表,因为它们是一个接一个地应用于快照的。Git 中的分支 只是具体快照的指定指针。它记录使用此分支时新更改要应用的位置。将更改应用于分支时,分支标签会移动到新提交。
 
Git 如何知道将来自工作区的更改放在哪里?这就是 HEAD 指向的位置。开发的 HEAD 是您最后签出工作区的位置,并且最重要的是,这也是提交更改的位置。它通常指向最后签出的分支。请注意,这与 CVS 对术语 HEAD 的诠释不同,因为这将作为默认分支的开发提示。
 
标签(tag) 命令命名提交,并允许使用一个可读的名称解决各个提交。基本上,标签是 commit ID 的别名,但也可以使用一些快捷方式解决提交。HEAD 是工作目录中的开发提示。HEAD^1 是 HEAD 提交的第一父提交,HEAD^2 是第二父提交,以此类推。
 
有关更多细节,请参见 gitrevisions 的帮助页面。由于标签或分支名称等名称是提交的引用,因此将它们命名为 refnames。reflog 显示在名称的生命周期内更改的内容、何时由谁创建(通常由分支创建),以及当前状态。
 
分支
分支背后的理念是每个快照可以有多个子快照。为同一快照应用第二个变更集会创建一个新的单独的开发流。并且如果对它进行命名,会将它命名为 branch。
 
图 4. Git 中的示例分支结构
分支结构流
图 4 用 Git 中的一个示例分支结构演示了这一点。master 分支是目前指向快照 F 的一些开发。另一个 old 分支表示较旧的快照,也许是可能的修复开发点。feature 分支具有特定功能的其他更改。变更集是从一个版本到另一个版本,例如 "[B->D]"。在本例中,快照 B 有两个子快照,并且它有两个开发流,一个是针对功能分支的开发流,另一个是针对其他内容的开发流。提交 A 也被标记为修复 bug 号 123。
 
git branch <branchname>
根据当前 HEAD(工作目录)创建一个新分支。
 
git checkout -b <branchname>
根据当前 HEAD 创建一个新分支,并将工作目录切换到新分支。
 
git diff <branchname> -- <path>
显示工作目录和指定分支之间 <path> 的差异。
 
git checkout <branchname> -- <path>
将文件从指定分支签入到工作目录。
 
git merge <branchname>
将指定分支合并到当前分支。
 
git merge --abort
终止会造成冲突的合并。
 
分支是对当前 HEAD 使用 git branch <branch name> 命令创建的,或者是对任意有效快照版本使用 git branch <branch name> <commit id> 命令创建的。这会在存储库中创建一个新的分支指针。小心,以这种方式进行分支会让工作区保持为旧分支。您首先需要签出新分支。使用 git checkout -b <branch name> 命令会创建新分支,并且您的工作区也会移动至新分支。
 
另外两个命令也非常有用:
git diff <branch> -- <path> 如上所述,输出当前工作目录和指定分支之间指定路径(文件或目录)的差异。
git checkout <branch> -- <path> 将文件从不同的分支嵌入到工作目录,这样就可以从另一个分支选择更改。
 
合并
实施新功能时,您会将它签入到存储库,例如,签入到 "feature" 分支的存储库中。功能完成后,您需要将它合并到主分支。可以通过签出主分支并使用 git merge <branch name> 命令来完成此操作。然后,Git 会将指定分支的更改合并到签出分支中。Git 实现此操作的方法是,将所有变更集从功能分支应用至主分支。
 
根据两个分支中的更改类型和可能产生的冲突,可能会出现三种可能性。
快进(Fast forward )合并:接收分支不会获得任何更改,因为两个分支分叉了。接收分支仍指向另一个分支分叉之前的最后一次提交。在这种情况下,Git 会将接收分支的分支指针向前移动,如图 5 所示。因为除了将分支指针向前移动外什么也做不了,所以 Git 将此过程称为快进合并。
 
图 5. 快进合并
将接收分支指针移动至最后一次提交
非冲突合并:两个分支都有更改,但它们并不冲突。例如,如果两个分支的更改影响不同的文件,就会出现这种情况。Git 可以自动将其他分支的所有更改应用至接收分支,并创建一个包含这些更改的新提交。然后,接收分支将向前移动至此新提交,如图 6 所示。
 
注意生成的提交,merge commit 有两个父提交。我没有注意到这里的变更集。原则上,从 (E) 到 (H) 的变更集将是子两个分支分叉后来自功能分支的所有变更集的合并,但这可能将类比弄过了头。
 
图 6. 非冲突合并
新提交包含合并内容并且接收分支指向它
冲突合并:两个分支都有更改,但它们是冲突的。在这种情况下,冲突结果会保留在工作目录中,留给用户进行修复并提交,或者是使用 git merge –abort 命令终止合并。
 
有一点很有趣且值得注意:合并寻找同一补丁应用至两个分支的实例。由于两个分支都有更改,这通常会导致冲突,但是 Git 很智能,可以检测到这种情况,因此仍然可以进行快进合并。
 
变基(rebasing)和随意选取(cherry picking)等高级功能进一步发展了回滚和重演变更集的概念。
有时您开发一个功能,但主开发是齐头并进的,您还不想合并功能。结果是两个分支很快会彼此分离开来。但是,可以将变更集从一个分支应用至另一个分支。为此,Git 提供了变基和随意选取功能。
 
变基
假设您正在开发功能,并且需要纳入来自主分支的最新更改以紧跟总体开发。这就称为对功能分支进行变基;这会将两个分支之间的分支点向上移动到一个分支。然后,Git 会在另一个分支顶部重演一个分支的更改,为每个原始提交创建新提交。在如图 7 所示的示例中,它尝试在主分支顶部应用来自功能分支的更改。
 
图 7. 变基分支
在新 base 提交/分支提示上重演更改
git rebase <otherbranch>
将当前分支重置到其他指定分支。
 
git rebase -i <otherbranch>
交互式变基。
 
git cherry-pick <commit>
将指定提交的变更集应用至(干净的)工作目录。
 
git cherry-pick --abort
终止导致冲突的随意选取。
 
git revert
还原补丁。
 
如果重演导致出现冲突,则变基会在第一次发现冲突时停止,并将冲突状态保留在工作目录中,以供用户进行修复。然后可以继续或终止变基。
使用 --onto 选项,变基实际上可以将分支点移动到其他分支中的任意新快照上。
 
随意选取
假设您现在正在开发功能,并且已经开发了一些应立即放入主开发的更改。这可能是一个 bug 修复,或者是一个很棒的功能,但您还不想合并分支或对分支进行变基。Git 允许使用随意选取功能将变更集从一个分支复制到另一个分支。
 
在这种情况下,如图 8 所示,Git 仅将变更集应用至 HEAD(即主分支)上的所选快照。在这里通常实际使用的是 commit ID,也称为哈希值。
 
图 8. 随意选取提交
在另一个分支上重演提交
还原
revert 命令在工作目录上回滚一个或多个补丁集,然后创建一个新的提交结果。revert 与随意选取是相反的。请查看图 9 来了解示例。
 
图 9. 还原提交
在分支上向后重演较旧的提交
revert 命令将还原记录为新提交。如果您不想进行记录,可以将分支指针重置到较早的提交,但这不在本文的介绍范围内。
那么为什么我会详细介绍这一部分呢?这是因为,在介绍下一部分的协作功能时,它对了解这些功能非常重要。事实上,了解了这个第一部分后,就会了解第二部分了。大多数协作功能都基于目前介绍的基本功能。
 
协作
在经典源代码库中,始终有一个什么是分支的清晰概念;它位于中央存储库。
但是,在 Git 中,没有所谓的主分支。等一下,我是不是在上面写了通常有一个主分支?是的,我是这样写了。但是,此主分支仅存在于本地。一个存储库中的主分支与另一个存储库中的主分支没有任何关系,除非您创建了关系。
图 10. 两个存储库
两个存储库没有关系?
如果您已经有了一个存储库,则可以使用 git remote add 命令添加远程存储库。然后,可以使用 fetch 命令在自己的存储库中获得远程分支的镜像。这称为远程跟踪分支,因为它跟踪远程系统开发。
 
当您签出仅作为远程跟踪分支(而不是本地分支)存在的分支时,Git 会根据远程跟踪分支自动创建本地分支,并签出本地分支。
 
在此之后,可以将远程分支的内容合并到自己的分支。图 11 显示签入到了本地主分支,但并不需要如此,您可以使用 normal merge 命令等共同历史将它合并到任意其他分支中。
 
图 11. 获取并检查远程分支
为远程分支创建一个本地跟踪分支
另一种方法是使用 git clone 命令从托管服务获取远程存储库。这会自动获取所有远程分支(但不是本地引用)并检查主分支。
您可以看到,会出现一种模式。由于远程存储库分支 “仅仅是一个分支” ,因此上面介绍的有关分支、合并的所有内容在这里都适用,尤其是从远程存储库获得更改时。
 
图 12. 获取远程更改
更改影响远程分支的本地分支
git clone <clone-url>
创建远程存储库的 "克隆"。
 
git remote add <origin> <url>
使用指定连接 URL 添加名为 <origin> 的远程存储库。
 
git fetch <origin> <branch>
从远程存储库 <origin> 获取 <branch> 远程跟踪分支的更改。
 
git pull <origin> <branch>
获取,然后合并。
 
git push <origin> <branch>
通过远程跟踪分支将更改从本地分支推送到远程存储库。
 
在图 12 中,显示了 git fetch;它更新远程跟踪分支。然后,在远程跟踪分支和本地分支之间执行常规的合并操作,在本例中使用 git checkout master; git merge repo1/master 命令。在获取之后,可以在合并命令中使用 FETCH_HEAD 名称,将其作为所获取的远程分支的快捷方式,比如使用 git merge FETCH_HEAD 命令。此外,与上述讨论类似,这一合并可能是快进合并、非冲突合并或需要手动解决的冲突合并。
 
git pull 命令是一个方便的命令,可结合使用 fetch 和 merge。
 
将更改提交给本地分支后,会将更改传输给远程分支。可使用 push 命令完成此操作,此命令会将本地更改推送到远程分支。这是 fetch 而不是 pull 的相反操作。但是,它不仅仅是远程获取,因为它会更新远程分支的本地副本,以及其他存储库中的远程分支,如图 13 所示。push 还允许您在远程存储库中创建新分支。
 
图 13. 推送更改
将更改推送到本地分支,然后再推送到远程分支
有一个保护措施。当推送导致对远程存储库中的远程分支进行快进合并时,会成功执行推送,否则会终止推送。如果不是这样,则远程分支可能已经拥有来自其他存储库或提交者的一些更改(提交)。Git 会终止推送并让一切内容保持不变。然后,您必须获取更改,将它们合并到自己的本地分支,并尝试再次推送。
 
注意,在这种情况下,可以进行常规合并,但还可以进行变基合并,以将本地分支的更改变基为远程分支的新更新 head。
除了 fetch 和 push 命令外,还有另一种分发补丁的方式;是通过邮件的旧方法。为此提供了 git format-patch <start-name> 命令,它会为每次提交创建一个补丁文件,将其从指定提交引领至当前分支状态。git am <mail files> 会将这些补丁文件应用至当前分支。
 
警告
一个警告:如果您尝试推送到存储库,有人实际在这里跟踪分支并在本地处理分支。这可能会打乱分支管理,因此 Git 会警告您并告诉您首先使用 pull 命令同步远程分支的状态。
 
还要清楚不应该对远程跟踪分支进行变基。它将不再匹配远程分支,因此不会对 push 进行快进合并。您已经破坏了存储库结构。
 
高级 Git
图 14. 多存储库结构示例
许多存储库使用 fetch 和 push 进行交流
通常情况下,即使使用 Git,也会出现星型结构,其中有一个中央存储库充当主存储库,还未每位用户提供了本地存储库。但并不一定都是如此。例如,您可以使用交叉连接在 Web 中添加远程存储库连接,如图 14 所示。
 
在上面,我将变基描述为在原始分支的不同分支(或分流)点上重演变更集。Git 通常会以提交顺序进行重演。高级功能 git rebase -i 允许您实际选择提交的顺序,即使可以删除提交或者是可以合并两个提交也是如此(“压缩”)。只需确保您不会对已推送的提交进行此操作,否则根据这些提交进行的推送会产生大量冲突。
 
我已经介绍了如何查看特定分支,还可以查看任意提交(快照)。这会让 HEAD 指针指向提交而不是分支。这称为 detached HEAD 模式。在这种情况下提交更改时,会启动新开发流。基本上,您会进行分支,但不会为此新分支命名。只可以使用 commit ID 搜索开发提示;不能使用任意 refname 搜索它。可以通过使用常规的 "git branch <branchname>" 命令根据此 HEAD 创建分支。
 
无法通过任何引用进行搜索的提交会出现什么情况?好吧,如果您不做什么特别的操作,它们会保留在存储库中。但是,托管服务可能会实际运行 git gc,Git 垃圾收集器会删除不必要的文件。无法根据任意引用名称搜索的提交是不必要的,因此将删除它们。始终在真正的分支上工作是一种好方法,尤其是当在 Git 中创建新分支是如此简单快速时。
 
结束语
一方面,Git 基于简单的原则,但有时它会提供非常大的灵活性。主要要点是 Git 管理快照及快照之间的变更集。最常见的命令应用并回滚不同分支之间的那些变更集。第二个要点是,处理远程分支与处理本地分支的操作基本相同,因为有一个远程分支的本地镜像。
 
至此,我已经完成了 Git 工作原理的简单介绍。这些命令基本上涵盖了我使用 Git 可以执行的所有操作。有关所有这些命令的更多信息,可以查看相应的帮助页面。希望本文介绍的这些知识能帮助您更好地了解并使用这些命令。此外,命令本身和 git status 通常为下一步操作提供了一些宝贵提示。
另一个有助于了解 Git 的有用工具是图形化的 gitk 工具,可现实本地存储库的结构。使用 gitk --all 来显示所有分支和标记。它还提供了一个简单的界面来在 Git 上发起操作。
 
Git 通常已安装在您的 Linux 系统上。您可以需要从软件包管理器安装开发工具。对于 Windows 系统,可以在 Git 主页 上下载它。
现在,我希望您对 Git 的工作原理已经有了更深入的了解,并且不害怕使用它所提供的灵活性。
 
 
http://it.dataguru.cn/article-7111-1.html
分享到:
评论

相关推荐

    git必知命令

    git必知必会总结,该文档总结了git基本用法,可以应对大部分新手面临的困难

    Git常用的33个命令

    获取Git仓库的两种方式: 在本地初始化一个Git仓库:git init 从远程仓库克隆 :git clone [url] 本地仓库命令: git status:查看文件状态 git add [文件名]:将文件的修改加入暂存区 git reset [文件名]:将暂存区...

    git常用命令总结git常用命令总结git常用命令总结git常用命令总结

    git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git常用命令总结git...

    git命令集合 git命令.md

    git命令集合 git命令.md

    git常用命令.txt

    进入到上传的文件的目录下,使用命令初始化本地仓库git init 2:git add . 把本地文件添加到本地仓库暂存区,.的意思是把当前目录下所有的文件及子目录都添加管理,也可以把.换成相应的文件名 git add .或...

    git命令-git命令-git命令-git命令

    git命令-git命令-git命令-git命令

    git init xxxx的时候报错: fatal: git 1.7.2 or later

    git init xxxx的时候报错: fatal: git 1.7.2 or later required 或许是git版本太低,你可以自己编译最新git源码进行安装。

    git命令总结文档

    Git常用操作命令: 因为Git是分布式版本控制系统,所以需要填写用户名和邮箱作为一个标识。

    linux系统安装git及git常用命令

    2 下载远程项目的GIT库到本地[code]$ git clone git://远程Git库地址 filename[code] filename 是你本地的文件夹名字将远程库克隆到这个文件夹,此文件是自己建立的 3 常用命令 (1)git branch 查看本地分支

    git-gateway:Git API的网关

    这使人们可以编写和编辑内容,而无需编写代码或不了解有关Git,markdown,YAML,JSON等的任何信息。 但是,在大多数情况下,您并不需要所有内容编辑器都拥有一个可以完全访问您网站的源代码存储库的帐户。 使用...

    git不是内部或外部的命令是的解决.docx

    为什么要编写这个教程?因为我在学习Git的过程...过,或者,只⽀支离破碎地介绍Git的某⼏几个命令,还有直接从Git⼿手册粘贴帮助⽂文档的,总 之,初学者很难找到⼀一个由浅⼊入深, 学完后能⽴立刻上⼿手的Git教程。

    git命令.pdf

    git命令,git基本命令列表,使用说明: 如检查git版本、设置基本配置、pull、push和merge等。

    git命令git命令git命令git命令

    最全套的git操作命令。包含新建代码库、配置、增加/删除文件、代码提交、分支、标签、查看信息、远程同步、撤销、其他等等

    解决git:fatal:Unable to create”…/.git/index.lock” 的错误

    index.lock文件是在.git下面, 而.git是一般是隐藏的, 那么可以通过以下命令删除即可. rm -rf /Users/dianji/project/ccpay.h5/.git/index.lock ///Users/dianji/project/ccpay.h5/.git/index.lock是你的index.lock...

    Git常用命令

    Git 常用命令使用 1)、本地库初始化 git init 2)、设置签名 作用:区分不同开发人员的身份。 说明:这里设置的签名和登录远程库(代码托管中心)的账户没有关系。 a)、项目级别签名: git config user.name ...

    git常用命令20160531

    平时常用的git命令

    Git命令一览表

    是最常用的Git命令,开发过程中不可缺少的部分,适合快速检索Git命令,使用起来方便快捷,尤其针对新手使用

    Git教程,讲解基本概念、架构和原理、具体使用

    内容: Git基本概念 Git架构和工作原理 Git具体使用 目标: 了解Git是什么 配置和搭建Git环境 使用Git控制程序版本

    git常见指令大全脑图 超详细的git命令集合

    git常见指令大全脑图 超详细的git命令集合供大家学习下载,后续会持续进行更新,方便记忆git常见指令大全脑图 超详细的git命令集合供大家学习下载,后续会持续进行更新,方便记忆git常见指令大全脑图 超详细的git...

    Git 命令参考手册

    Git 官方命令手册

Global site tag (gtag.js) - Google Analytics