这是一篇简单的入门级文章,旨在让从没有使用过git的人群能够明白git的初步用法,以及git用于工作的基本思想。看完本文后,希望大家能够上手实现基本功能的操作。只要入了门,再进阶就有章可循了。
思虑再三,决定采用轻小说的形式。以一个虚拟项目从初始化构建到代码上传、合并、解决冲突等实现开发协作。
git本身就是一个很复杂的工具了,它每个命令中都有多项参数。如果要穷尽学习所有命令与参数之后再来工作,那显然时间成本太高。所以本文以工作实用为目标,尽力做到一边学习一边就能上手用于实际工作。
人物设定
老马:资深码农,作风沉稳,五年以上工作经验,技术负责人。
小蔡:新晋码农,求知欲强,基础扎实。
公司最近决定将源码管理系统从SVN迁移到git上,于是就由老马来牵头整这个事。老马自己找资料、试用,又琢磨了一番,觉得差不多了,就招呼道:“小蔡,你过来一下,咱合计个事。”
小蔡立马应道:“来了,马哥,啥事?”
小蔡刚毕业不久,在公司已经结束实习期,签了正式合同。他一直跟着老马做事,因为小蔡专业基础扎实,反应机敏,老马带着不累,因此也越来越倚重他。
老马慢条斯理地说道:“听说过git这个工具吗?”
“知道,是个分布式源码管理系统。我同学他们公司就在用这个呢。马哥,咱公司也要用上这个了?”
“嘿,你小子脑子转得倒快,省得我跟你废话了。现在咱们要趟条路出来,才好给其他兄弟们做演示。那你自己玩过没?”
“这个真没有,马哥。要不我现在就去搭个demo环境试试看?”
“行,最简单的就可以,纯演示用。那些个安全性、管理界面的都不用考虑。”
“明白,马哥。”
小蔡回到工位,通过ssh终端连接上公司内网centos linux服务器,他决定使用yum工具安装git。
$ yum search git
$ yum install git.x86_64
$ git version
git version 1.8.3.1
至此,系统里已经有了git命令,接下来他又搜索了一下初始配置方法。于是执行以下命令:
$ git config --global user.email "macai@vision.com"
$ git config --global user.name "macai"
$ git config -l
user.email=macai@vision.com
user.name=macai
做完这些,小蔡就跟老马碰了一次头。老马表示基本满意,接下来就是如何新建仓库并提交代码了,老马示意小蔡把椅子挪过来,他打算两个人一块看着弄。
老马:“小蔡你看,项目版本库所在目录通常称之为工作区。我们先初始化一个工作区吧。”
$ mkdir vision_prj
$ cd vision_prj/
$ git init
初始化空的 Git 版本库于 /home/macai/web/vision_prj/.git/
小蔡:“马哥,可我看目录下什么也没有啊。”
老马:“你仔细看,其实有一个.git隐藏文件夹。仓库相关的配置、版本管理文件都在那里面。”
小蔡:“果然如此。”
$ ls .git/
branches config description HEAD hooks info objects refs
老马:“接下来我们放一个文件进去,再添加到仓库里。最后看下状态吧。”
$ vim vision_main.c
$ git add .
$ git status
# 位于分支 master
#
# 初始提交
#
# 要提交的变更:
# (使用 "git rm --cached <file>..." 撤出暂存区)
#
# 新文件: vision_main.c
#
文件内容vision_main.c:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("hello, vision\n");
}
小蔡:“嘿,马哥,这就说我们刚创建的文件已经被纳入版本管理了吗?这跟svn一样啊。”
老马:“不对,这里就跟svn有差别了。在git里这一步只是将文件放入了暂存区,还要经过一次提交才会被正式纳入git版本管理。当然,在暂存区中的文件是可以被撤销的,这个我们以后再说。”
$ git commit -m "create vision_main.c"
[master(根提交) d4fd427] create vision_main.c
1 file changed, 6 insertions(+)
create mode 100644 vision_main.c
老马:“对于刚完成的操作,我们可以通过查看日志命令来回顾。”
$ git log
commit d4fd427ec364c14cb84efc41d646a3189ccac5e1
Author: macai <macai@vision.com>
Date: Tue Jan 14 11:08:28 2020 +0800
create vision_main.c
老马:“小蔡你看,在commit后面跟着的那一长串值就是本次提交的哈希值。有了这个值,我们可以通过它做很多事情,例如比较各个提交之间的差异、打标签。甚至你不小心执行了误删除命令,都可以通过这个哈希值把内容恢复出来。”
小蔡:“这么神奇。”
老马:“那是当然。接下来我们要模拟一下分布式操作了。”
老马:“我们先来实现第一个目标,就是你能够在自己的电脑上获取工程。这需要我先建立一个裸仓库。”
“什么是裸仓库?” 小蔡很自然地问了出来。
老马:“简单理解,就是不包含工作区的源码仓库。它可以追踪我们做出的提交变更,一般用来作为远程中心仓库。”
小蔡又冒出了另一个问题:“马哥,咱不是说要去中心化吗?怎么还是弄了一个中心出来呢?”
老马这回笑了:“哈,傻小子,你仔细想想。这个中心对源码管理来说是必须的吗?它相比于SVN式的中心服务已经大大弱化了,这个中心最大的用途是为了方便开发者远程协作,以及源码备份。假如这个中心出了故障,那从任意一位开发者手中的本地仓库就能快速重建一个。你说SVN能这么轻易地做到吗?”
小蔡茅塞顿开:“啊,是这个意思,我明白了。”
老马不再多话,在服务端运行了如下命令:
$ git clone --bare ./vision_prj/ ./vision_prj_repo.git
克隆到裸版本库 './vision_prj_repo.git'...
完成。
老马:“好了,小蔡你回去把这个工程clone到本地吧。”
小蔡回到工位以后,按照老马的吩咐,在自己的电脑上运行如下命令:
$ git clone macai@172.16.127.43:/home/macai/web/vision_prj_repo.git
正克隆到 'vision_prj_repo'...
The authenticity of host '172.16.127.43 (172.16.127.43)' can't be established.
ECDSA key fingerprint is SHA256:d2UJyLYxdTOsdoHqubiNgWwbUHRcKwCKo9wyHx2MNMo.
ECDSA key fingerprint is MD5:01:d6:df:44:01:c1:bc:46:d8:89:7f:06:bf:ab:28:bd.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.16.127.43' (ECDSA) to the list of known hosts.
macai@172.16.127.43's password:
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
接收对象中: 100% (3/3), done.
因为小蔡是第一次连接这台内网服务器,所以经过了一次权限确认的步骤。他一看本地果然有了一个vision_prj_repo目录,他迫不及待地进入目录,一看vision_main.c赫然在目,并且内容也分毫不差。小蔡笑着向老马打了个胜利的V字手势。
老马又埋头操作了一番,他添加了一些新代码,并执行了向远端仓库提交修改的push命令,操作如下:
$ git push
macai@172.16.127.43's password:
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 322 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To macai@172.16.127.43:/home/macai/web/vision_prj_repo.git
6c2473b..9162fd1 master -> master
然后老马向小蔡说:“你再运行一下git pull命令看看。这条命令的作用是从远端仓库同步最新数据至本地,它实际包含了两个步骤,fetch和merge。即拉取数据并且和本地修改提交进行合并。”
“另外,”老马想了想还是补充说:“diff命令可以查看前后版本的差异,这个你自己研究一下吧。”
小蔡依言操作,果然有了变化。
$ git pull
macai@172.16.127.43's password:
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
来自 172.16.127.43:/home/macai/web/vision_prj_repo
d4fd427..0dd7e62 master -> origin/master
更新 d4fd427..0dd7e62
Fast-forward
vision_main.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
小蔡再一查看文件内容,原来是添加了一个新方法showMessage。
#include <stdio.h>
void showMessage()
{
printf("hello, vision\n");
}
int main(int argc, char *argv[])
{
showMessage();
}
小蔡再查看一下日志,果然有了上次提交的内容:
commit 0dd7e62b481bac323c5e1e4d2c8bf6c86458381d
Author: macai <macai@vision.com>
Date: Thu Aug 6 15:09:34 2020 +0800
add(vision_main): add showMessage
小蔡又在自己的本地代码中进行修改,然后提交。老马那边执行git pull一看,内容也都同步过来了。这一来一回,小蔡算是搞清楚怎么进行分布式协作了。
老马招手又将小蔡叫了过去,开口问道:“小蔡你想过一个问题没有,就是假如我们同时修改这个文件并且提交,会发生什么事情?”
小蔡没有马上开口,而是思索了一会儿,说:“那就会产生代码冲突,要做合并的工作,对吧?”
老马笑了笑,说:“git在这方面还是比较智能的,它会根据时间线来判定先后关系。有些合并工作就自动完成了,不过也有一些更复杂的情况是需要我们手工处理的。废话不多说,我们先试一把,你回去改动一下做个提交吧。”
小蔡再次回到工位,修改了showMessage方法的显示字符串printf("hello, vision world\n");,然后提交。老马没有立即执行git pull,而是对vision_main.c文件进行编辑,增加了两行代码并提交。这时他再执行git pull命令时,他的终端就进入了一个文本编辑提示界面:
$ git pull
自动合并 vision_main.c
Merge branch 'master' of /home/macai/web/vision_prj_repo
# 请输入一个提交信息以解释此合并的必要性,尤其是将一个更新后的上游分支
# 合并到主题分支。
#
# 以 '#' 开头的行将被忽略,而且空提交说明将会终止提交。
小蔡这时也来到老马身后看着,老马在文本编辑器里输入了更改原因然后退出。老马笑道:“合并完成。” 小蔡有些诧异地问:“这就行了?”
老马:“不信你看。” 说着他就把代码显示了出来:
#include <stdio.h>
void showMessage()
{
printf("hello, vision world\n");
}
int main(int argc, char *argv[])
{
int i;
i = 0;
showMessage();
}
“哟嗬,果然是自动合并呢。” 小蔡频频点头。
老马:“你刚才第一次执行git pull命令的时候,我就提示过你,pull包含了两个步骤,对吧?”
小蔡再次点头:“对,拉取和合并。所以pull命令会将远程的修改和本地的提交进行合并,这个过程我明白了。”
老马总结道:“所以每次开始工作前执行一次git pull是个好习惯,可以避免潜在的代码冲突。但代码冲突那些更复杂的情况,就留给你小蔡自己去琢磨吧。好了,目前我们只有一个分支,就是master。但你也知道只在一个分支上做开发是会出问题的,下面我们看看分支操作怎么弄吧。”
老马:“先让我们来考虑简单一些的情形。例如master分支就作为产品线的稳定发布分支,就是最终上线部署的程序,就从master分支编译得到。但master分支的代码不允许直接编辑,它必须来自于另一个分支,我们称它为develop分支吧。”
小蔡:“这个我理解。我们开发就在develop分支上进行,测试没问题了再合并到master分支上,对吧?”
老马:“孺子可教。那我们就来创建一个新分支吧。” 说完,老马在终端上执行git branch develop命令,但执行完后没有任何提示,老马又执行查看分支的命令:
$ git branch develop
$ git branch -l
develop
* master
老马一边敲键盘一边解说:“你看,分支已经有了吧。接下来我们再切换到它上面去。”
$ git checkout develop
切换到分支 'develop'
老马:“切换成功。小蔡,我这是分步演示的,其实还有更简便的办法,就是git checkout -b develop,一条命令就包含了创建并切换的动作。回头你自己去试试啊,我们现在看看怎么合并分支。”
老马打开vision_main.c文件进行编辑,提交之后就切换回主分支并进行合并,执行如下命令:
$ git checkout master
$ git merge develop
更新 6c2473b..f7d9d9a
Fast-forward
vision_main.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
再一查看文件内容,果然和develop分支保持一致了。老马指点着屏幕上的“Fast-forward”,说:“看到这个提示了吗?这就是说git判断develop是相对于master的增量变化,没有其他差异,git就简单地将master分支置于develop处,这样实现快速合并的功能。”
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。