Git 作为一个免费的、开源的分布式 版本控制系统,可以高效地处理包括轻量、重量级在内的各种项目。Git 有廉价的本地库,方便的暂存区域和多个工作流分支等特性。
同一项目 Project
在开发过程中可能出现形如 Project_20230616_byLee
,Project_20230617_byMa
,Project_20230630_backUp
,Project_20230701_rollBackOn0617
,Project_20230701_modified
,Project_20230703_finalByMa
等多个协同开发者版本、多个备份版本、跨版本回滚修改、跨协同开发者打包拷贝的混乱情况。为避免此,我们需要利用 Git 的如下特性:
如记录文档修改内容,新建项目版本号、分支(branch) 等。开发者为避免项目开发出现致命错误而无法回退到最近一次可行现场,有时会对上一可靠版本进行拷贝备份,但这样操作会开发过程中出现多个备份,占用了大量的存储空间。为此,Git 管理应运而生。
开发者可将本地资源推送(push)上传至远端进行云备份,同时多个开发者也可对此云项目进行拷贝(copy)、下拉(pull)至本地进行异地多分支开发,经合并(merge)后推送至远端主分支。常见的远端有 GitHub、Tencent CODING DevOps 等。
在 Windows cmd 下无法执行
mkdir
、ls
、touch
、vim
、cp
、rm
等常用 Linux 指令,但在 Git Bash Terminal 的帮助下可以实现此类交互。
官方地址:https://git-scm.com/
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.
定位到项目文件夹,右击鼠标在菜单栏选择 Git Bash Here,进入Terminal。并键入:
git -v
git -h
以查看已安装的 Git 版本号和帮助文档,检验是否安装成功。
注意,Git Terminal 的复制快捷键为 ctrl + insert ,粘贴快捷键为 shift + insert。
首先需为本台设备上的开发者配置 用户名、邮箱 等信息,用于记录项目各版本下的 录入人签名 以区分不同操作者身份,以及用于后续的 远端识别。
如某一项目分居实验室、寝室两地开发,我们可以将实验室设备用户名命为
my-lab
,将寝室设备用户名命为my-laptop
,这样可以在改项目回滚时确定某一功能块的修改者。
git config --global user.name "your name"
git config --global user.email "[email protected]"
如果读者想要在后续的远端部分使用 GitHub 作为资源库,建议用户名、邮箱与 GitHub 注册信息统一,以便管理。但二者并无必然联系。
cat ~/.gitconfig
现在采用如下指令在当前项目目录下建立一个 git 本地库:
git init
ls ./.git
执行完毕后,可以看到本地的项目目录文件夹内出现了一个名为 .git
的隐藏文件夹,内部存有一些配置信息,如索引、提交记录等。
Git 可记录当前项目的各个版本号以及其对应的修改记录,并支持回滚。Git 目前的状态可通过如下指令进行查询:
git status
截至目前,由于文件夹内没有增删文件、修改文件内容的操作发生,所以 Terminal 自然会显示 no commits yet
。我们现在创建一个新的文档并命名为 test.txt
,作为轻量级的项目示例。(如果读者已经拥有现成项目并知悉项目变动,可跳转至下一小节。)
创建好 test.txt
后,再次执行状态查询指令,我们发现 Terminal 中输出了未被追踪文件(Untracked files
),并被标记为了红色。
test.txt
作为新建的文件,与 Git 中记录中 版本历史仓库 出现的文件目录,和当前的 暂存区 中出现的文件目录存在冲突,所以被标记为了红色。后续开发者需要将test.txt
添加(add)至 暂存区 并提交(commit)至 版本历史仓库,才可在 Git 中完成记录。后面的章节会提到其工作机制以及操作细节。
Git 的工作流如图所示,框内部分为本地工作机制,框外传递部分为面向远端的工作机制:
在项目的当前 工作目录(Workspace) 下,开发者进行编写、新建、删除文件或文件内容。对于已修改的文件目录或者文件内容,开发者可以将其添加(add)至 暂存区(stage),在所有标红提示的 未被追踪文件 完成添加后,我们再一并将它们提交(commit)至 本地的版本历史仓库(Local Repository) 中。
如第一节中提到的 Git 作用,开发者可以将 本地仓库 推送(push)到位于 GitHub 的 远端仓库(Remote Repository) 作为代码托管;将 远程仓库 的分支最新内容完全拉取(pull)至本地 工作目录 并与当前本地分支直接合并,或将 远程仓库 的分支最新内容拿取(fetch)至 本地仓库 但不做合并。
如状态查询和工作机制章节中提到的情景,我们需要做出一些操作,来使得 Git 记录下本地的所有修改。同时需支持对修改记录的日志进行查询,以完成项目的版本穿梭。
以下是一些常用的 Git 本地管理相关命令:
指令 | 作用 | |
---|---|---|
1 | git add |
将文件 pathspec 添加至暂存区 |
2 | git commit -m |
提交本次修改并做 message 批注 |
3 | git reflog |
查看各提交的引用日志 |
4 | git log |
查看各提交的日志 |
5 | git reset --hard |
穿梭(回滚)到 reflog 对应的项目版本 |
通过指令 git add
,我们可以添加 git status
中被标红的 未被追踪文件 至暂存区,如:
git add test.txt
git status
再次查看 git status
,可以发现该文件已被标记为绿色。
键入 tab 可自动补全子目录名。
通过指令 git commit -m
,我们可以提交暂存区内容至本地仓库,如:
git commit -m "This is my first commit, and a new file is created." test.txt
如图所示,当 commit 完成后,我们又对 test.txt
的文件内容进行了修改,添加了两行文字。当同一文件被提交后再次被修改,查看 git status
依然会标记该文件为 未被追踪文件。此时我们需要将该文件再次 add 至暂存区,并进行二次 commit,以完成记录:
git status
git add test.txt
git status
git commit -m "This is my second commit, and the file is modified." test.txt
git status
操作完成后可发现文件内容修改已被记录,目前的工作区是干净的。
git reflog
用于查看当前仓库的引用日志(Reference log):引用日志记录了仓库中分支、标签和 HEAD 移动的历史记录。git log
用于查看 Git 仓库的提交日志:它显示了每个提交的作者、提交时间、提交信息等。git reflog
git log
Git HEAD 默认指向提交后的最新的版本,通过以下指令修改 Git HEAD 指向的引用版本号,可以进行本地仓库回滚,同时本地工作区也会同步回滚:
git reset --hard <reflog>
Git 分支(branch) 是一种用于在开发过程中并行工作的功能。它允许开发人员在不影响主分支的同时,与其他人合作开发新的功能或解决问题。在 Git 中,可以创建,切换和合并分支。分支的底层管理逻辑由指针完成,当前视角 HEAD
指向当前分支。
读者可以跳转至 A successful Git branching model 查看各较为成熟的分支团队开发模型,如在 主分支(master) 可分出 热补丁(hotfix),发布版(release),开发版(develop),特性(feature) 分支供团队各职能部门开发。此文章剩余部分仅对学生常见的多人开发、多地开发场景做示例(如小组作业合作、实验室与寝室跨地开发)。
以下是一些常用的 Git 分支相关命令:
指令 | 作用 | |
---|---|---|
1 | git branch -v |
查看各分支 |
2 | git branch |
创建名为 new-branch 的新分支 |
3 | git checkout |
切换到名为 branch 的分支 |
4 | git checkout -b |
创建并切换到名为 branch 的分支 |
5 | git merge |
将名为 branch 的分支合并到 当前 分支上 |
6 | git branch -d |
删除名为 branch 的分支 |
我们可以使用如下指令来查看本地仓库中包含的 所有开发分支,目前所处的分支将会被 标记为绿色,且在绝对路径后的 括号内 会有当前分支对应的名称标识:
git branch -v
可以看到当前项目的本地仓库内仅包含一个 主分支(master),我们位于主分支上,且其被标记为了绿色。
我们使用如下指令创建一个名为 dev
的新分支,并将视角切换到该分支上:
git branch dev
git checkout dev
git brach -v
此时,当前所处分支 dev
被标记为了绿色,且括号内展示了当前分支的名称标识 (dev)
:
接下来我们在 dev
分支上对 text.txt
的文件内容进行异步修改,将修改结果添加到暂存库,并提交至对应的仓库分支:
此处为了方便演示,修改文本内容可以使用 vim editor,教程可访问 https://www.openvim.com/。我们也可直接在 txt 中进行修改。
git checkout dev
vim test.txt
cat test.txt
git status
git add test.txt
git commit -m "The first commit on branch dev: contents modified."
可以发现 dev
分支上的文件内容已发生改变。现在我们将视角切换到主分支 master
上:
git checkout master
cat test.txt
由于 分支之间的开发进度在合并之前互不影响,可以发现文件内容 又回到了修改之前的状态(the second commit on branch ‘master’)。现在我们将分支 dev
上的修改内容合并到主分支(当前分支):
git checkout master
git merge dev
cat test.txt
git status
git branch -v
git reflog
至此我们才可以在主分支内看到合并过来的 dev
分支上的修改内容。
注意,
dev
分支虽然已经合并到了master
分支上,但是dev
分支本身依然存在。修改master
的内容如果 正确 add 并 commit,则 不会 对dev
进行 随动修改,同样地,切换到dev
分支上后进行修改 并正确 add 且 commit,master
的内容也 不会随动修改。如果 没有提交 commit 随即切换了分支,那么我们将会看到另一分支的 内容被随动修改(因为所有改动依然停留在工作区而没有上传至 Git 管理系统)。
在重量级项目的分支开发中,往往会合并时出现同一文件的同一位置(如某一行)对应了两种不同修改内容,自动合并产生了歧义。为了演示这一现象,我们又从主分支创建了分支 dev2
。创建分支后 对二者做了如下面的修改内容,add 至暂存区并 commit:
git checkout dev2
vim test.txt
git add test.txt
git commit -m "Merge conflict test on dev2."
git checkout master
vim test.txt
git add test.txt
git commit -m "Merge conflict test on master."
此时两分支内都对文档的后几行进行的修改,但修改内容不同。所以在主分支上,dev2
合并过来时出现了冲突现象(Merge conflict in test.txt
,Unmerged paths: both modified
):
git reflog
git merge dev2
git status
cat test.txt
其中冲突部分,<<<<<<< HEAD
与 =======
之间对应的为当前分支的文档内容,=======
与 >>>>>>> dev
之间对应的为 dev
分支的文档内容。 手动删除多余内容,保留兴趣内容后,add 至暂存区并提交,即可解决冲突:
vim test.txt
cat test.txt
git status
git add test.txt
git commit -m "merge after conflict"
git status
git branch -v
git reflog
cat test.txt
可以看到括号内提示的主分支合并中 (master|MERGING)
状态改为了正常 master
。
结合本地管理操作与分支的运用,我们可以将本地的项目内容向远端的代码托管中心进行交互了。此处的远端仓库以 GitHub 为例,创建一个私有仓库(不开源)并命名为 GitProject
:
指令 | 作用 | |
---|---|---|
1 | git remote -v |
查看当前所有远端仓库的地址及别名 |
2 | git remote add |
添加一个新的远端仓库 url 并命名为别名 name |
3 | git push |
将本地分支 branch 推送到远端仓库 name 上 |
4 | git push |
将远端仓库 name 上的 remote-branch 分支删除 |
5 | git clone |
将远端仓库 repo 的所有内容拷贝下来到本地当前目录 |
6 | git pull |
将远端仓库 repo 上的 remote-branch 分支下拉到本地并与当前本地分支直接合并 |
7 | git fetch |
将远端仓库 repo 上的所有分支拿取到本地并但不合并 |
8 | git fetch |
将远端仓库 repo 上的 remote-branch 分支拿取到本地并但不合并 |
clone,pull,与 fetch 的区别如图;其中 fetch 仅将远端仓库的分支副本拿取下来,但工作区的工作目录并不会发生改变,而 pull 则是对分支副本与仓库资源内容均做了下拉,工作区的工作目录内容会发生改变:
以前面在 GitHub 上的创建的 GitProject
为例,我们需要先获取其 ssh 地址并为其创建别名:
git remote add github-ssh "[email protected]:Sycamore-Ma/GitProject.git"
git remote -v
由于远端仓库受 SSH 秘钥保护,所以本地在向远端进行推送或拉取时,需要事先使用如下指令(默认回车)生成本计算机的公共秘钥,找到在本地计算机的秘钥文本存储位置,并将其粘贴至远端仓库的协议中:
ssh-keygen
具体操作流程读者可访问链接:如何解决 [email protected] permission denied (publickey). fatal could not read from remote repository。
如果未能解决 SSH 秘钥问题,在向远端私有仓库推送或拉取时,识别秘钥会出现
[email protected]: Permission denied (publickey).
、fatal: Could not read from remote repository.
等错误。
同样地,GitHub 上可以邀请其他成员使用 Git 管理系统进行协同开发,读者可移步至 Git&GitHub 团队协作 了解详情。
通过以下指令,我们可以将本地的 master
分支推送至 github-ssh
别名对应的远端仓库:
git push github-ssh master
可以看到 GitHub 代码托管已经出现了新内容,远端分支为 master
,与本地 master
分支一致。
同样地我们可以在另外一台设备上测试拉取(该设备秘钥已经达成协议,此处不赘述),我们创建一个新的空文件夹,并在其路径上 Bash,完成前面章节的 初始化 与远端连接工作,执行远端拉取:
git init
git remote add github-ssh "[email protected]:Sycamore-Ma/GitProject.git"
git remote -v
git pull github-ssh master
git status
git branch -v
git reflog
可以看到,另外一台设备也分布式地获取了远端代码托管中心的所有资源,可用于后续的协同修改、合并、推送。这样跨地开发的需求就达成了。