此博客记录针对 Git 的学习,整合资料。
参考:
版本控制工具是软件开发中不可或缺的一部分。它们用于记录文件的变化,使开发者能够回溯到之前的任何版本,并跟踪和管理项目的历史版本。这对于团队协作开发尤为重要,因为它能够帮助团队成员在不同的功能分支上独立工作,最后再将各自的工作合并到一起。
传统的版本控制系统(如 SVN)通常是集中式的,所有版本控制操作都通过一个中央服务器来完成。这种方法有一个显著的缺点:如果中央服务器出现问题,所有人都无法工作。这时,分布式版本控制系统(Distributed Version Control System,简称 DVCS)应运而生。
在 DVCS 中,如 Git、Mercurial 以及 Darcs,客户端并不只是提取最新版本的文件快照,而是把整个代码仓库完整地镜像下来,包括完整的历史记录。每个开发者都有一个完整的代码库副本,包含所有的分支和提交记录。
这样一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
具体实例
假设你有一个使用 DVCS 的项目,当你克隆(clone)一个远程仓库时,你得到的不是仅仅一个最新版本的代码快照,而是整个仓库的完整副本,包括所有的分支和历史记录。这意味着你可以在本地执行几乎所有的版本控制操作,而不需要依赖网络连接。
在团队协作中,每个开发者可以独立地在自己的分支上工作,当一个功能完成时,可以将其合并到主分支中。而且,如果一个开发者的工作出现问题,不会影响其他人的工作,因为每个人都有自己的代码库副本。
通常有两种获取 Git 项目仓库的方式:
将尚未进行版本控制的本地目录转换为 Git 仓库;
进入项目目录,再执行 git init
命令
1 | $ cd /my_project |
从其它服务器 克隆 一个已存在的 Git 仓库。
1 | $ git clone <url> |
克隆远程仓库到本地,还可以通过额外的参数指定克隆到本地的仓库的名字。
克隆之后,可以开始追踪仓库中的文件,使用git add
命令,热案后执行git commit
1 | $ git add *.c |
可以使用 git status
命令查看哪些文件处于什么状态。
使用命令 git add
开始跟踪一个文件。
如果修改了一个已经被跟踪的文件,再执行git status
命令,就会出现Changes not staged for commit
,说明跟踪文件内容发生了变化,但是没有放到暂存区,如果要暂存这次更新,还需要运行一次git add
git add
这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为“精确地将内容添加到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。
运行了
git add
之后又作了修订的文件,需要重新运行git add
把最新版本重新暂存起来
git status
命令的输出十分详细,但是内容有一点繁琐。
可以使用git status -s
命令得到更精简的输出。
1 | $ git status -s |
新添加的未跟踪文件前面有 ??
标记,新添加到暂存区中的文件前面有 A
标记,修改过的文件前面有 M
标记。 输出中有两栏,左栏指明了暂存区的状态,右栏指明了工作区的状态。例如,上面的状态报告显示: README
文件在工作区已修改但尚未暂存,而 lib/simplegit.rb
文件已修改且已暂存。 Rakefile
文件已修改,暂存后又作了修改,因此该文件的修改中既有已暂存的部分,又有未暂存的部分。
在 Git 仓库目录下,有一些文件可能还会随着项目的编译运行而产生,比如说日志或者编译产生的临时文件,我们并不想这些文件被 Git 标记为未跟踪状态,这时就可以创建一个名为.gitignore
文件,列出要忽略的文件模式。
1 | # 忽略所有的 .a 文件 |
使用git status
可以大致知道那些文件被修改了,但却不能精确知道修改的内容与位置。
要查看尚未暂存的文件更新了哪些部分,这时可以使用git diff
命令。
git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。
git diff --staged
这条命令将比对已暂存文件与最后一次提交的文件差异
现在的暂存区已经准备就绪,可以提交了。 在此之前,请务必确认还有什么已修改或新建的文件还没有 git add
过, 否则提交的时候不会记录这些尚未暂存的变化。 这些已修改但未暂存的文件只会保留在本地磁盘。 所以,每次准备提交前,先用 git status
看下,你所需要的文件是不是都已暂存起来了, 然后再运行提交命令 git commit
。
还可以添加-m
选项,将提交信息与命令放在同一行。
尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。 Git 提供了一个跳过使用暂存区域的方式, 只要在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤。
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。 可以用 git rm
命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
如果只是简单地从工作目录中手工删除文件,运行 git status
时就会在 “Changes not staged for commit” 部分(也就是 未暂存清单)看到:
1 | $ rm PROJECTS.md |
上面命令会将工作区的文件也删除,如果需要将文件从Git仓库中删除,但并不想从工作区中移除,就需要用到如下命令。
1 | git rm --cached filename |
如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。
要在 Git 中对文件改名,可以这么做:
1 | $ git mv file_from file_to |
运行 git mv
就相当于运行了下面三条命令:
1 | $ mv README.md README |
所以在使用其他工具重命名文件时,记得在提交前 git rm
删除旧文件名,再 git add
添加新文件名。
Git 文件在版本控制过程中的状态主要有以下三种:未跟踪、已跟踪(包括已修改和暂存)。
1 | Untracked --- git add ---> Staged --- git commit ---> Committed |
未跟踪的文件是指在 Git 仓库中尚未被 Git 跟踪的文件。它们通常是新添加到工作目录中的文件,但还没有被添加到版本控制中。
git add <file>
命令将文件添加到暂存区,使其进入已跟踪状态。已跟踪的文件是指已经被 Git 仓库跟踪的文件。已跟踪的文件分为以下三种状态:
已修改的文件是指文件内容发生了变化,但这些变化尚未被记录到暂存区。
git add <file>
命令将文件的修改添加到暂存区。暂存的文件是指文件的修改已经被添加到暂存区,将在下一次提交中被记录。
git commit -m "message"
命令将暂存区的修改提交到本地仓库。已提交的文件是指文件的修改已经被提交到本地仓库,成为项目历史的一部分。
用 git status
命令查看哪些文件处于什么状态
1 | # 初始化 Git 仓库 |
使用git log
命令查看提交历史。
1 | $ git log |
不加任何参数,git log
会按照时间先后顺序列出所有的commit
,最新的在上面。会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
git log
还有很多功能选项:
选项 | 说明 |
---|---|
-p |
按补丁格式显示每个提交引入的差异。 |
--stat |
显示每次提交的文件修改统计信息。 |
--shortstat |
只显示 –stat 中最后的行数修改添加移除统计。 |
--name-only |
仅在提交信息后显示已修改的文件清单。 |
--name-status |
显示新增、修改、删除的文件清单。 |
--abbrev-commit |
仅显示 SHA-1 校验和所有 40 个字符中的前几个字符。 |
--relative-date |
使用较短的相对时间而不是完整格式显示日期(比如“2 weeks ago”)。 |
--graph |
在日志旁以 ASCII 图形显示分支与合并历史。 |
--pretty |
使用其他格式显示历史提交信息。可用的选项包括 oneline、short、full、fuller 和 format(用来定义自己的格式)。 |
--oneline |
--pretty=oneline --abbrev-commit 合用的简写。 |
git reset
git reset
用于重置当前分支的 HEAD 到指定的提交,并可以选择性地重置暂存区和工作目录中的文件。
git reset --soft
**:重置 HEAD 到指定提交,但保留暂存区和工作目录中的更改。git reset --mixed
(默认):重置 HEAD 和暂存区到指定提交,但保留工作目录中的更改。git reset --hard
**:重置 HEAD、暂存区和工作目录到指定提交,丢弃所有更改。撤销提交:撤销一个或多个提交,同时选择保留或丢弃更改。
撤销文件暂存:git reset HEAD filename
这个命令用于取消对特定文件的暂存,但保留工作目录中的更改。这在你已经使用 git add
将文件添加到暂存区,但又希望将其从暂存区移除时非常有用。
1 | # 撤销最后一个提交,保留更改在暂存区和工作目录中 |
1 | # 取消暂存文件 |
git checkout
git checkout
主要用于切换分支和恢复工作目录中的文件。
1 | # 切换到分支 |
git checkout
在 Git 2.23.0 之后,被推荐使用 git switch
和 git restore
命令来替代,以减少命令的歧义。
git commit --amend
git commit --amend
用于修改最近一次的提交。它允许你在不创建新提交的情况下更新上一次提交的内容或提交信息。
当你在修补最后的提交时,与其说是修复旧提交,倒不如说是完全用一个 新的提交 替换旧的提交, 理解这一点非常重要。从效果上来说,就像是旧有的提交从未存在过一样,它并不会出现在仓库的历史中。
1 | # 修改最近一次提交的提交信息 |
git reset
**:重置当前分支的 HEAD、暂存区和工作目录到指定的提交。适用于撤销一个或多个提交,可以选择保留或丢弃更改。git checkout
**:切换分支或恢复文件到工作目录。适用于切换分支和恢复文件。git commit --amend
**:修改最近一次的提交。适用于修正提交信息或添加遗漏的更改。git reset
和 git commit --amend
都可以用于撤销提交,但 git reset
更强大,可以撤销多个提交。git commit --amend
专用于修改最近一次提交。git checkout
用于从某个提交或分支恢复文件。记住,在 Git 中任何 已提交 的东西几乎总是可以恢复的。 甚至那些被删除的分支中的提交或使用
--amend
选项覆盖的提交也可以恢复
在使用 Git 的过程中,远程仓库是一个非常重要的概念。远程仓库是托管在互联网上或其他网络上的 Git 仓库,它使得我们可以与其他开发者协同工作,共同开发项目。
为什么使用远程仓库?
协作开发:远程仓库允许多个开发者同时对同一个项目进行开发,团队成员可以各自提交自己的更改,并将这些更改推送到远程仓库。这样,所有人都可以同步到最新的代码状态。
备份代码:将代码推送到远程仓库可以作为代码的备份。即使本地仓库发生故障或丢失,也可以从远程仓库中恢复代码。
git remote add <name> <url>
在团队协作中,不同的开发者可能会有各自的远程仓库进行开发。你可以将他们的仓库添加为远程仓库,以便拉取他们的更改并与他们协作。
例如,你的同事 Alice
有一个仓库,你可以将她的仓库添加为 alice
:
1 | git remote add alice https://github.com/alice/repository.git |
这样你可以从 Alice
的仓库中获取代码:
1 | git fetch alice |
git fetch <remote>
从远程仓库获取数据,但不进行合并。它会更新本地存储的远程分支信息。
git pull <remote> <branch>
从远程仓库获取数据并自动合并到当前分支。它实际上是git fetch
和git merge
的组合。
有时候,你可能需要将你的代码推送到多个远程仓库。比如,你可以同时推送到 GitHub 和 GitLab:
1 | git remote add github https://github.com/username/repository.git |
然后你可以分别推送代码到两个远程仓库:
1 | git push github main |
git push <remote> <branch>
将本地分支的更新推送到远程仓库
git push origin main
git push origin main
命令用于将本地的 main
分支的更新推送(push)到远程仓库 origin
的 main
分支上。
具体含义如下:
git push
: 用于将本地仓库的提交推送到远程仓库。origin
: 远程仓库的名称,通常默认为 origin
,它是你本地仓库与远程仓库的关联名字。main
: 本地的分支名称,在这个例子中指的是你的本地 main
分支。但是若果是协作,提交时必须将上游的工作拉下来合并进自己的工作之后才能推送。
git remote rename
远程仓库重命名 git remote remove 移除远程仓库
分支让工作者可以从开发主线分离出来,在不影响主线的情况下进行开发、修复错误或尝试新的想法。分支是轻量级的,可以很容易地创建和删除。理解和使用分支对于高效的版本控制和团队协作非常重要。
1 | git branch |
这个命令会列出所有的本地分支,并在当前所在的分支前加上 *
标记。
1 | git branch <branch-name> |
1 | git checkout feature |
或者可以使用 -b
选项同时创建并切换到新分支:
1 | git checkout -b <branch-name> |
git checkout
还有恢复目录和文件的功能,为了避免混淆。有一个专门用于切换分支的命令
git switch
(Git 2.23.0 版本之后)切换到现有分支
1 git switch <branch-name>创建并切换到新分支
1 git switch -c <new-branch-name>
注意:切换分支会导致工作区也发生变化。
当你在一个分支上完成开发后,可以将这个分支合并到另一个分支。例如,合并 feature
分支到 main
分支:
首先切换到目标分支(例如 main
):
1 | git checkout main |
然后执行合并操作:
1 | git merge feature |
如果一个分支不再需要,可以将其删除:
1 | git branch -d <branch-name> |
例如,删除 feature
分支:
1 | git branch -d feature |
注意:如果分支未被合并到主分支中,-d
命令会拒绝删除。要强制删除,可以使用 -D
选项:
1 | git branch -D <branch-name> |
main分支和dev分支共同开发。
切换到main分支,执行git merge dev
命令,Git 会自动为我们产生一次提交,将dev分支的内容合并到main分支中。
注意:分支在合并之后并不会自动被删除,如果需要删除不再需要的分支需要手动删除分支。
如果两个分支的修改内容没有重合部分,那么合并分支非常简单且顺利。
但是如果两个分支修改了同一个文件的同一行代码,Git 就不知道应该保留哪个分支的内容了,这就是合并分支时产生的冲突,需要手动解决冲突。
示例:
假设你有两个分支:main
和 feature
,它们对同一个文件 example.txt
进行了不同的修改。
1 | git checkout main |
Git 会显示合并冲突的提示,告诉你哪些文件存在冲突:
1 | Auto-merging example.txt |
打开冲突的文件 example.txt
,你会看到冲突的标记:
1 | <<<<<<< HEAD |
<<<<<<< HEAD
和 =======
之间的部分是 main
分支的内容。
=======
和 >>>>>>> feature
之间的部分是 feature
分支的内容。
根据需要手动编辑冲突的文件,保留你想要的修改,直接修改example.txt文件内容。
编辑完冲突的文件后,保存并关闭文件。然后使用 git add
命令将解决冲突的文件标记为已解决:
1 | git add example.txt |
最后,提交合并的结果:
1 | git commit |
Rebase 的基本思想是将一个分支的所有提交(公共祖先节点之后)重新应用到另一个分支的顶部。这样可以避免产生不必要的合并提交,从而保持历史记录的线性。
所以也可以通过rebase
命令进行分支的“合并”。
如果遇到冲突,还是需要手动解决并暂存更改,然后使用git rebase -- continue
命令或者git rebase --abort
中止操作。
在开发新功能时,可以创建一个新的分支来进行开发。这样可以确保主分支保持稳定,并且可以随时在新分支上进行试验和修改。
在修复错误时,可以创建一个新的分支来进行修复工作。完成修复后,可以将修复分支合并回主分支。
在团队协作中,分支可以用来提交代码进行评审。在提交代码之前,可以将代码推送到一个单独的分支,并请求团队成员进行评审和测试。
创建新分支并切换到新分支:
1 | git checkout -b feature-branch |
在新分支上进行开发: 修改文件并提交更改:
1 | git add . |
将新分支推送到远程仓库:
1 | git push -u origin feature-branch |
这会在远程仓库
origin
中创建一个名为feature-branch
的分支,并将本地的feature-branch
分支内容推送到远程分支中。
创建拉取请求:在 GitHub 或其他托管平台上创建拉取请求,请求团队成员评审代码。
团队成员可以使用
git fetch
或git pull
命令将这个远程分支拉取到本地:
1 git fetch origin然后切换到该分支:
1 git checkout feature-branch这样将远程分支拉取到本地,允许团队成员共同访问和协作
合并分支:代码评审通过后,将新分支合并到主分支:
1 | git checkout main |
删除本地和远程分支:
1 | git branch -d feature-branch |
通过使用分支,你可以有效地管理不同的开发任务,保持代码库的整洁和有序。分支提供了灵活性,使你可以在不影响主分支的情况下进行各种尝试和开发工作。
main
**:主分支,永远保持稳定和可发布状态。develop
**:开发分支,所有的开发功能都基于这个分支。feature/*
**:功能分支,从 develop
分支创建,用于开发新功能。release/*
**:发布分支,从 develop
分支创建,用于准备新版本的发布。hotfix/*
**:修复分支,从 main
分支创建,用于修复生产环境的紧急问题。新功能开发:
develop
分支创建一个 feature
分支。feature
分支上进行开发。develop
分支。1 | git checkout develop |
发布准备:
develop
分支创建一个 release
分支。release
分支上进行发布前的准备工作(例如,修复bug、更新文档)。main
和 develop
分支,并打上标签。1 | git checkout develop |
修复紧急问题:
main
分支创建一个 hotfix
分支。hotfix
分支上进行修复。main
和 develop
分支,并打上标签。1 | git checkout main |
GitHub Flow 是一种更简单的工作流模型,适用于发布频繁的项目。它只有一个长期存在的主分支 main
,所有开发都在单独的功能分支上进行。
官方推荐流程:
推荐使用带有意义的描述性名称来命名分支
版本发布分支/Tag示例:v1.0.0
功能分支示例:feature-login-page
修复分支示例: hotfix-#issueid-desc
定期合并已经验证成功的分支,即使删除已经合并的分支
保持合适的分支数量
为分支设置合适的管理权限
国内网络直接 Git clone可能会出现网络错误
1 | PS C:\Users\23351\Desktop> git clone https://github.com/jay1an/jay1an.github.io.git jay1an |
需要在 Git 配置中添加代理
1 | PS C:\Users\23351\Desktop> git config --global http.proxy 127.0.0.1:7890 |
端口与本机的代理程序一致。
配置完之后就可以解决连接问题了。
1 | PS C:\Users\23351\Desktop> git clone https://github.com/jay1an/jay1an.github.io.git jay1an |