参考文章:图文并茂详解 Git,看了必懂! (qq.com)
Learn Git Branching
SVN(Apache Subversion)和 Git 是两种流行的版本控制系统(VCS),用于跟踪和管理代码和其他文件的变化。
SVN(Subversion)和 Git 是两种常见的版本控制系统,它们在很多方面存在显著的区别。
分布式 vs 集中式:SVN 是集中式版本控制系统,意味着所有版本历史记录都存储在中央服务器上,开发者从中央服务器检出代码并进行提交。而 Git 是分布式版本控制系统,每个开发者的工作目录都是一个完整的代码库,包含项目的所有历史记录。
分支和合并:Git 的分支和合并操作非常轻量级和高效,分支创建和切换的速度非常快,合并时的冲突处理也较为方便。相对而言,SVN 的分支和合并操作较为繁琐,创建分支和切换分支的速度较慢,合并冲突的处理也不如 Git 方便。
存储管理:Git 采用快照存储的方式,记录每次提交时整个项目的快照。即使文件没有发生变化,Git 也会存储整个文件的引用。SVN 则采用增量存储的方式,仅记录文件的变化部分。
离线工作能力:由于 Git 是分布式系统,开发者可以在本地仓库进行所有操作(如提交、分支、合并等),然后再将更改推送到远程仓库。SVN 则需要与中央服务器保持连接,许多操作(如提交)必须在线进行。
速度:Git 在执行大部分操作时(如提交、分支、合并)速度非常快,因为这些操作都是在本地仓库中完成的。SVN 的很多操作需要访问中央服务器,因此速度较慢,尤其是在网络状况不佳时。
安全性:由于 Git 每个开发者都有一个完整的代码库副本,即使中央服务器崩溃,项目的历史记录也不会丢失。而在 SVN 中,中央服务器是单点故障,服务器崩溃可能导致数据丢失。
学习曲线:SVN 的命令和操作相对简单,比较容易上手。Git 功能强大,但命令和概念较多,学习曲线相对较陡,需要一些时间来掌握。
社区支持:Git 的社区支持和生态系统非常丰富,很多现代化的开发工具和平台都与 Git 有良好的集成。SVN 也有良好的社区支持,但相对 Git 而言稍逊一筹。
总结来说,Git 更适合分布式团队开发,特别是需要频繁进行分支和合并操作的项目;而 SVN 更适合结构相对简单、集中管理的项目。选择哪种版本控制系统,主要取决于项目需求和团队的工作方式。
三个部分
Git 可以分为本地项目、本地仓库和远程仓库
本地项目:这是你正在开发的工作目录,其中包含了项目的所有文件和子目录。工作目录中的文件是实际的文件,你可以编辑、添加、删除这些文件。
本地仓库:这是一个隐藏的 .git
目录,包含了 Git 所需的所有元数据和对象数据库。它记录了项目的所有历史版本和提交。你在本地进行的所有操作(如提交、创建分支等)都会在本地仓库中进行。
远程仓库:这是一个托管在服务器上的 Git 仓库,通常用于团队协作。常见的远程仓库托管服务有 GitHub、GitLab、Bitbucket 等。远程仓库用来集中存储代码和版本历史,团队成员可以从远程仓库拉取最新的代码,也可以将自己的更改推送到远程仓库。
在 Git 的日常使用中,这三部分是如何协同工作的:
初始化本地仓库:使用 git init
命令在你的本地项目中初始化一个新的 Git 仓库。
添加远程仓库:使用 git remote add <name> <url>
命令将一个远程仓库添加到你的本地仓库中。
拉取代码:使用 git pull
命令从远程仓库获取最新的代码并合并到你的本地分支。
提交更改:使用 git add
和 git commit
命令将你的更改提交到本地仓库。
推送代码:使用 git push
命令将你的本地提交推送到远程仓库,以便其他团队成员可以获取到最新的更改。
文件的状态
在 Git 中,文件有不同的状态,反映了文件在工作区、暂存区和仓库中的情况。
-
修改:Git可以感知到工作目录中哪些文件被修改了,然后把修改的文件加入到modified区域
-
暂存:通过add命令将工作目录中修改的文件提交到暂存区,等候被commit
-
提交:将暂存区文件commit至Git目录中永久保存
未跟踪(Untracked)未跟踪文件是指在工作区中存在,但未被添加到 Git 仓库中的文件。Git 不会对未跟踪文件进行版本控制。
已跟踪(Tracked)已跟踪文件是指已经被添加到 Git 仓库中,并且 Git 会对其进行版本控制。已跟踪文件可以进一步细分为以下几种状态:
已修改(Modified)已修改文件是指已跟踪的文件在工作区中被修改,但这些修改还没有被添加到暂存区。
已暂存(Staged)git add xx.txt 已暂存文件是指已跟踪的文件的修改被添加到了暂存区,准备在下一次提交时提交到仓库中。
未修改(Unmodified)未修改文件是指已跟踪的文件没有任何修改。它们在工作区和暂存区中的状态与仓库中的状态一致。
忽略的文件(Ignored)忽略文件是指被列在 .gitignore
文件中的文件或目录。Git 会忽略这些文件,不会对其进行版本控制,也不会显示在未跟踪文件列表中。
HEAD
是一个指针/符号引用,指向你当前所在的分支的最新提交。在 Git 仓库中,HEAD
通常指向一个分支的名字,这个分支的名字又指向该分支的最新提交。
HEAD 总是指向当前分支上最近一次提交记录。大多数修改提交树的 Git 命令都是从改变 HEAD 的指向开始的。
HEAD
可以处于以下两种状态:
-
Attached HEAD(附着状态的 HEAD):这是最常见的状态。
HEAD
指向一个分支的名字,而这个分支的名字指向一个具体的提交。例如,在main
分支上工作时,HEAD
会指向main
,而main
指向该分支的最新提交。 -
Detached HEAD(分离状态的 HEAD):在这种状态下,
HEAD
直接指向某个具体的提交,而不是指向某个分支的名字。这通常发生在你检出一个具体的提交时(而不是分支)。在这种状态下进行的提交不会自动包含在任何分支中。 -
可以使用以下命令查看
HEAD
指向的内容:cat .git/HEAD -
如果 HEAD 指向的是一个引用,还可以用
git symbolic-ref HEAD
查看它的指向。-
如果
HEAD
指向一个分支:ref: refs/heads/main -
如果
HEAD
分离了:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9(基于 SHA-1,共 40 位,Git 对哈希的处理很智能。你只需要提供能够唯一标识提交记录的前几个字符即可)
-
通过哈希值指定提交记录很不方便,所以 Git 引入了相对引用。使用相对引用的话,你就可以从一个易于记忆的地方(比如 bugFix
分支或 HEAD
)开始计算。
- 使用
^
向上移动 1 个提交记录 - 使用
~<num>
向上移动多个提交记录,如~3
main^
相当于“main
的 parent 节点”。git checkout main^main^^
是main
的第二个 parent 节点-
用相对引用最多的就是移动分支。可以直接使用
-f
选项让分支指向另一个提交。例如:git branch -f main HEAD~3
-
上面的命令会将 main 分支强制指向 HEAD 的第 3 级 parent 提交。
-f
容许我们将分支强制移动到那个位置。
节点
在 Git 中,节点通常指的是提交对象(commits)。每个提交都是一个快照,记录了项目在某个时间点的状态,以及相关的元数据。
提交对象包含以下信息:父提交(Parent Commits),指向前一个提交(或多个前一个提交,对于合并提交);树对象(Tree Object),指向记录了项目文件目录结构和内容的树对象;提交信息(Commit Message),描述这次更改的文字说明;作者信息(Author Information),包含提交的作者和提交时间;提交者信息(Committer Information),如果不同于作者,还会包含提交者的信息和提交时间。
每个提交对象都有一个唯一的 SHA-1 哈希值,这个哈希值用来标识该提交。它是通过提交的内容、作者信息、提交信息等计算得出的,因此每次提交都会生成一个唯一的哈希值。
树对象表示目录结构。它包含指向多个子对象(包括文件对象和其他树对象)的指针。树对象允许 Git 存储和管理整个目录结构和文件内容。
文件对象(Blob)表示文件的内容。每个文件对象也有一个 SHA-1 哈希值,通过文件内容计算得出。因此,相同内容的文件在不同的提交中会有相同的哈希值。
分支是指向提交对象的可变指针。默认情况下,Git 使用 main
分支。你可以创建新的分支,这些分支是提交对象的可变指针,指向不同的提交。
标签是指向提交对象的不可变指针。标签通常用于标记发布版本,提供一种方式来标识某个重要的提交点。
合并提交是一个特殊的提交,它有多个父提交。它表示将两个或多个分支合并到一起的结果。
在 Git 中,节点(提交对象)之间形成一个有向无环图(DAG),描述了提交之间的关系。每个节点指向一个或多个父节点,形成历史记录的链条。
查看提交历史:git log
创建新的提交:git commit -m "Your commit message"
创建分支:git branch <branch-name>
检出分支:git checkout <branch-name>
合并分支:git merge <branch-name>
查看分支图:git log --graph --oneline
常用的 Git 命令及其作用
-
git init
:初始化一个新的 Git 仓库。在现有项目目录中运行此命令,会创建一个新的子目录.git
,其中包含了 Git 仓库所需的所有元数据。 -
git clone <repository>
:从远程仓库克隆一个完整的 Git 仓库,包括其所有历史记录。此命令会在当前目录下创建一个新目录,并将远程仓库的内容复制到该目录中。 -
git status
:显示工作目录和暂存区的状态。它可以帮助你查看哪些文件已更改、哪些文件被暂存、哪些文件没有被跟踪。 -
git add <file>
:将文件添加到暂存区。你可以使用通配符来添加多个文件,例如git add .
会添加当前目录下的所有文件。 -
git commit -m "message"
:将暂存区的更改提交到本地仓库,并附上提交信息。提交信息应该简洁明了,描述本次提交的更改内容。 -
git push <remote> <branch>
:将本地仓库中的提交推送到远程仓库。例如,git push origin main
会将main
分支的提交推送到名为origin
的远程仓库。 -
git fetch <remote>
:从远程仓库获取最新的更改,但不进行合并操作。你需要手动合并这些更改。 -
git merge <branch>
:将指定分支合并到当前分支。例如,git merge feature-branch
会将feature-branch
分支的更改合并到当前分支。 -
git branch
:列出所有本地分支。使用git branch <branch>
可以创建一个新分支,git branch -d <branch>
可以删除一个分支。 -
git rebase
用于将一个分支上的提交应用到另一个分支上,将当前分支的基点(base)移动到另一个分支的末端。它可以帮助你保持更干净的提交历史,使之看起来像是线性的,而不是由多条分支组成的网络。与git merge
不同,git rebase
不会创建一个新的合并提交,而是将提交一个接一个地重新应用。 -
git checkout <branch>
:切换到指定分支。例如,git checkout main
会切换到main
分支。 -
git log
:显示提交历史记录。使用git log --oneline
可以简洁地显示每个提交的一行摘要。 -
git diff
:显示工作目录和暂存区之间的差异。可以使用git diff <commit>
查看某个提交和当前工作目录之间的差异。 -
git stash
:暂时保存未提交的更改,清理工作目录以便进行其他操作。使用git stash pop
可以恢复这些更改。 -
git reset <commit>
:重置当前分支的 HEAD 到指定的提交。git reset --hard <commit>
会丢弃所有未提交的更改,git reset --soft <commit>
只会重置 HEAD 而不影响工作目录。git reset HEAD~2,把分支记录回退2个提交记录来实现撤销改动。git reset对远程分支无效。 -
git revert
<commit>:
git revert
命令用于撤销一个或多个指定的提交。与git reset
不同的是,git revert
会创建一个新的提交,用于撤销之前的更改,而不会修改提交历史git log中保留数据。这使得它在协作开发中更安全,因为它不会破坏其他开发者的提交历史。 -
git remote
:管理远程仓库。git remote add <name> <url>
添加一个远程仓库,git remote -v
显示所有远程仓库的详细信息。 -
git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。从远程仓库下载本地仓库中缺失的提交记录,更新远程分支指针(如o/main
)git fetch
通常通过互联网(使用http://
或git://
协议) 与远程仓库通信。git fetch
会首先检查本地仓库和远程仓库的差异,检查哪些不存在于本地仓库,然后将这些变动的提交拉取到本地。但是,这里请注意,它是把远程提交拉取到本地仓库,而不是本地工作目录,它不会自行将这些新数据合并到当前工作目录中,我们需要继续执行git merge会把这些变动合并到当前工作目录。
-
git pull <remote> <branch>
:从远程仓库拉取最新的更改并合并到当前分支。例如,git pull origin main
会从origin
远程仓库拉取main
分支的最新更改。pull
的本质其实就是fetch
+merge,
git pull --rebase
就是 fetch 和 rebase
在 Git 中撤销更改是一个常见需求,具体取决于你希望撤销的更改类型和状态。以下是一些常见情况的详细介绍和对应的命令示例。
一、撤销未提交的更改
1. 丢弃工作目录中的未提交更改
如果你希望丢弃工作目录中的所有未提交更改(不包括暂存区的更改),可以使用以下命令:git checkout -- <file>
例子:# 丢弃 file1.txt 的更改git checkout -- file1.txt
2. 丢弃暂存区和工作目录中的所有未提交更改
如果你希望丢弃暂存区和工作目录中的所有未提交更改,可以使用以下命令:git reset --hard
丢弃所有未提交的更改(包括暂存区和工作目录)git reset --hard
二、撤销已暂存的更改
1. 取消暂存文件的更改
如果你已经将文件的更改添加到暂存区,但还没有提交,可以使用以下命令取消暂存:
git reset HEAD <file>
取消暂存 file1.txt 的更改
git reset HEAD file1.txt
三、撤销已提交但尚未推送的更改
1. 修改最后一次提交
如果你只是想修改最后一次提交的信息或添加新的文件,可以使用以下命令:
git commit --amend
# 添加新的文件并修改最后一次提交
git add newfile.txt
git commit --amend
2. 撤销最后一次提交并保留更改
如果你希望撤销最后一次提交,但保留更改在工作目录中,可以使用以下命令:
git reset --soft HEAD~
撤销最后一次提交并保留更改在工作目录中
git reset --soft HEAD~
3. 撤销最后一次提交并丢弃更改
如果你希望撤销最后一次提交并丢弃所有更改,可以使用以下命令:
git reset --hard HEAD~
撤销最后一次提交并丢弃所有更改
git reset --hard HEAD~
四、撤销已经推送到远程仓库的更改
1. 回滚到某个特定提交并推送
如果你希望将远程仓库回滚到某个特定提交,可以使用以下命令:
git revert <commit>
git push
回滚到某个特定提交
git revert abcdef12345
git push
2. 强制推送到远程仓库
如果你需要强制推送本地的更改到远程仓库(慎用,因为这会覆盖远程仓库的历史),可以使用以下命令:git push --force
强制推送本地更改到远程仓库
git push --force
五、使用 `git stash` 临时保存更改
1. 临时保存未提交的更改
如果你希望临时保存工作目录中的未提交更改,可以使用以下命令:
git stash
临时保存未提交的更改
git stash
2. 恢复已保存的更改
如果你希望恢复已保存的更改,可以使用以下命令:
git stash pop
恢复已保存的更改
git stash pop
这些命令和例子涵盖了在 Git 中撤销更改的主要方法和场景。根据具体需求选择合适的命令,可以有效地管理和控制项目的历史版本。