【Linux系统】9. 基础开发工具(三)
五. 版本控制器 -- Git
1)历史(简述)
Linux 内核开发团队与他们当时使用的版本控制系统 BitKeeper 决裂。为了解决迫在眉睫的代码管理问题,Linux 的缔造者 Linus Torvalds 在 2005 年 花了短短几天时间,亲自设计并创造了 Git。
1. Linux 内核是一个极其庞大且由全球开发者协同开发的开源项目。在 2002 年之前,大家主要通过手工合并补丁文件来协作,效率极低,容易出错。
2. 为了改善协作,团队开始使用一个名为 BitKeeper 的商用版本控制系统。它是一款分布式系统,性能强大,非常适合 Linux 的开发模式。当时 BitKeeper 的公司对Linux开源社区提供了免费使用权,但有一些限制。
3. 2005 年,Linux 社区的核心成员试图对 BitKeeper 进行反向工程,这违反了其许可证协议。BitKeeper 的公司随即收回了 Linux 社区的免费使用权。整个 Linux 内核开发瞬间面临“无工具可用”的窘境。
4. 面对危机,Linus Torvalds 决定自己动手。他从2005 年 4 月 3 日 开始开发 Git,到 4 月 7 日,Git 就已经实现了自我版本控制(即用 Git 来管理 Git 自身的源代码),随即将Git开源。
5.Git 本身一直在积极开发,不断加入新功能和性能优化,例如部分克隆、稀疏检出、换机工作树等,以应对更大更复杂的项目需求。相关生态也逐渐完善,GitHub、Gitee (他们是网站或客户端,底层基于Git)等平台的出现很大程度的降低了Git的使用门槛。
6. 在开源社区和 GitHub 等平台的推动下,Git 迅速超越了最初的替代 BitKeeper 的目标,成为了全球软件开发版本控制的绝对主流(其他的版本控制器几乎已经没有在使用了,Git是绝对的主流,还有一个勉强还有在用的:SVN)。
2)版本控制是什么?
版本控制:对历史数据进行多版本维护,我们可以随时回退到任意历史版本。
结论:
1. git进行版本控制的时候并不是采用对所有版本备份的方式,而是只记录变化(减少存储工作量)。
① git是一个版本控制器的软件;github && gitee是网站或者客户端,他们的底层是基于git的。
2. git是一个去中心化的、分布式的版本控制器。
① 虽然去中心化,但他依旧可以完成中心化的任务。我们在实际应用中,其实还是以中心化的模式为主。
② git不仅有版本控制的功能,还有网络通信功能。因此,简单理解中心化就是不同人从同一个地方(git server)上拉取资源到本地。去中心化就是不同人彼此之间可以直接拉取,不需要通过某个中心。
3)git命令行操作
1. 查看是否安装了git
- git --version
默认装好:显示版本号
没装好:command not found
安装命令:
sudo yum/apt install -y git
2. 创建一个远端仓库
https://gitee.com/
创建个人账号,并登陆
3. 克隆下来的just_test就是工作区(我们写代码的一个工作目录),.git就是本地git仓库,工作区中的代码不是全部都要上传到仓库中。
4. 所以一个目录是普通目录还是工作区要看它有没有隐藏目录.git。
3. git add .
5. 将其他目录下的代码拷贝到工作区,但是拷贝过来的东西并没有被git仓库管理起来。
我么可以通过git status查看当前工作区的状态,提示我们code_progressbar目录下的文件没有被添加到工作区中,建议我们使用add来添加。所以git add . (. 代表将工作区中的所有修改先添加到暂存区,等待后续推送动作),如果add完,有不想加进去的可以使用git reset HEAD <文件名>。
至此add结束。
4. git commit -m "提交日志"
现在我们要把暂存区添加的文件全部提交到本地仓库。
1. 直接使用git commit命令会打开一个类似记事本,不会帮我们提交,必须带上-m选项以及提交日志:git commit -m "提交日志,写你本次提交做了什么,不能乱写"
[lsy@hcss-ecs-116a just_test]$ git commit
[lsy@hcss-ecs-116a just_test]$ git commit -m "进度条代码"
2. 再次git status,提示我们没有可提交的了,工作区已经干净了。
但是我们可以去gitee的远端仓库看一下,commit并没有提交到远端仓库。git status还提示我们可以使用git push推送我们的本地仓库。
在.git中可以查看提交记录,我们可以使用tree .git命令查看commit前后的提交记录的变化:
至此,我们的添加和提交已经完成了本地的git操作,就是已经把我们的代码托管给了本地git仓库。
1. add不是直接把代码提交到真正的本地仓库,而是先放到暂存区中。
2. 暂存区的价值:为我们提供后悔的余地,增加提交效率(前面提过添加之后后悔了不想添加了应该用git reset HEAD <文件名>)。commit时,将最终暂存区中的东西一次性添加到本地仓库中。
3. 我们tree .git时看到的index就是暂存区,实际上他就是一个普通文件。先把我们可能多次的提交信息先临时全部写到index文件中,commit时将index中记录的文件全部提交到仓库中。
5. git push
4. 最终我们使用git push,将本地仓库向远端仓库进行同步。
此时,我们远端仓库就成功与本地仓库同步了。
6. 总结一
1. git add .
2. git commit -m "提交日志"
3. git push
7. git log
1. git log可以查看历史所有与git有关的提交日志。
2. 下图中的一长串字符:提交ID值,每一次commit都有一个,绝对不会重复。
我们之前tree .git时见到过,就是那个修改记录,他是一个类似于“版本号”的的东西。我们在进行版本调整时(我想回退到哪个版本或者恢复到哪个版本),都是通过这个ID值来调整的。
8. 多人协作
协作场景
1. 多人协作时我们可以在自己本地克隆远端的仓库,并进行相关git操作。
我在本地windows桌面上创建一个文件夹,并克隆刚刚我们创建的远端仓库到这个文件夹中(要安装好小乌龟工具)。
我们在windows下也可以查看提交记录。当然也可以将我们的代码协同推送到远端仓库。新建一个文件,并在终端打开,使用我们的add、commit、push就可以将代码提交到远端仓库了。
2. 多人协作时的提交冲突问题
① 一个远端仓库有多人提交,比如A提交了一份代码,但是并不会直接同步到B的本地仓库,此时如果B也想提交一份代码就会导致提交冲突(rejected),需要git pull一下。
② git pull的作用就是把远端仓库拉取一下,这样做是为了保证本地仓库必须 >= 远端仓库(远端仓库承载的信息量时本地仓库的子集)。本质上就是你要提交你的东西必须把别人历史提交的代码先同步到本地。
③ 拉取完直接git push即可(win下也是一样的反应和操作)。
[lsy@hcss-ecs-116a just_test]$ ll
total 12
drwxrwxr-x 2 lsy lsy 4096 Sep 26 19:14 code_progressbar
-rw-rw-r-- 1 lsy lsy 846 Sep 26 18:42 README.en.md
-rw-rw-r-- 1 lsy lsy 935 Sep 26 18:42 README.md
[lsy@hcss-ecs-116a just_test]$ touch Linux_code.c
[lsy@hcss-ecs-116a just_test]$ echo "hello world" > Linux_code.c
[lsy@hcss-ecs-116a just_test]$ ll
total 16
drwxrwxr-x 2 lsy lsy 4096 Sep 26 19:14 code_progressbar
-rw-rw-r-- 1 lsy lsy 12 Sep 27 17:16 Linux_code.c
-rw-rw-r-- 1 lsy lsy 846 Sep 26 18:42 README.en.md
-rw-rw-r-- 1 lsy lsy 935 Sep 26 18:42 README.md
[lsy@hcss-ecs-116a just_test]$ git add .
[lsy@hcss-ecs-116a just_test]$ git commit -m "Linux测试"
[master 024d2ba] Linux测试1 file changed, 1 insertion(+)create mode 100644 Linux_code.c
[lsy@hcss-ecs-116a just_test]$ git pull
git pull之后不知道跳到哪去了没关系,直接回车后强制退出。
[lsy@hcss-ecs-116a just_test]$ git push
3. 多人协作时的合并冲突问题
① 合并冲突的本质是:Git 无法自动判断如何合并两份不同来源对同一个文件的代码的修改。
② 用一个现实中的例子类比:两个人在同一时间修改了同一份文档的同一个段落。第一个人删除了这个段落,第二个人重写了这个段落。这时,文档合并工具(比如 Word)就会懵掉,不知道应该保留删除操作,还是保留重写的内容。它只能把两种可能性都标出来,让人类来做最终决定。
③ 对应到我们的git,他就会调出我们的编辑器(如vim)将两种版本同时呈现出来,让用户自己解决这个问题,自己决定到底听谁的,而不是冒险自己做出一个可能错误的选择。
9. 总结二
1. git clone --- 将远端仓库上的项目完整的复制到我们的本地计算机上
2. git log --- 查看历史所有与git有关的提交日志
3. git status --- 查看当前工作区的状态
4. git pull --- 将远端仓库与本地仓库协同
4)关于 .gitignore 的问题
1. 作用:记录所有不准提交的文件后缀。
① 我们可以看一眼.gitignore文件的具体内容:
[lsy@hcss-ecs-116a just_test]$ cat .gitignore
# Prerequisites
*.d# Object files
*.o
*.ko
*.obj
*.elf# Linker output
*.ilk
*.map
*.exp# Precompiled Headers
*.gch
*.pch# Libraries
*.lib
*.a
*.la
*.lo# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex# Debug files
*.dSYM/
*.su
*.idb
*.pdb# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
② 接下来我们可以演示一下.gitignore文件不允许其中的后缀文件提交的效果:我们用vim修改一下文件内容,添加一个.txt文件,理想的效果就是我们无法将工作区内以.txt为后缀的文件提交。
[lsy@hcss-ecs-116a just_test]$ touch hello.txt
[lsy@hcss-ecs-116a just_test]$ touch txt.hello
[lsy@hcss-ecs-116a just_test]$ ll
total 20
drwxrwxr-x 2 lsy lsy 4096 Sep 26 19:14 code_progressbar
-rw-rw-r-- 1 lsy lsy 0 Sep 29 19:08 hello.txt
-rw-rw-r-- 1 lsy lsy 12 Sep 27 17:16 Linux_code.c
-rw-rw-r-- 1 lsy lsy 846 Sep 26 18:42 README.en.md
-rw-rw-r-- 1 lsy lsy 935 Sep 26 18:42 README.md
-rw-rw-r-- 1 lsy lsy 0 Sep 29 19:08 txt.hello
-rw-rw-r-- 1 lsy lsy 64 Sep 27 17:27 win_code.c
③ 然后三部曲提交...
④ 虽然工作区中还有没有被提交的文件,但是我们的git status也认为工作区已经干净了。
⑤ 所以,.gitignore 中的东西不会被提交到本地仓库,更不会被提交到远端,git 认为该文件只是工作去中的一个普通文件,不会被 .git 仓库托管起来。
我们都知道Linux下是不在意文件后缀名的,但是之前我们也说过,不能因为在linux系统下不在意文件后缀就给文件乱加后缀,因为系统不在意,不代表系统中的这些工具不在意。比如gcc/g++,还有我们这里的.gitignore文件是严格对比文件后缀的,他不会去关心这个文件真正的文件类型,只要.gitignore文件中没有的,全部都给他提交,即使是一个后缀被命名为.c的.obj文件。
2. git上,如何配置免账号密码登录?
之前我们每次git push的时候都要输gitee登录时的账号和密码,太麻烦了,如何配置成免账号密码呢?
① 在家目录下创建一个文件.git-credentials,并向写入https://{username}:{password}@gitee.com,然后输入git config --global credential.helper store
cd ~
touch .git-credentials
vim .git-credentials
git config --global credential.helper store
② 到这里其实就已经配置好了,我们可以打开~/.gitconfig文件看一下,如果多出了两行内容,就没问题了:
③ 配置完成重新登录一下我们的云服务器。我们再提交一次,看一下有没有成功(这里我已经把.txt文件从.gitignore中删掉了,将.gitignore恢复原样,也就是现在可以提交以.txt为后缀的文件了)
④ 但是第一次提交的时候还是要输账号密码的,之后就都不用了。
第一次:
第二次:
[lsy@hcss-ecs-116a just_test]$ touch laosi.c
[lsy@hcss-ecs-116a just_test]$ echo "laosi.." > laosi.c
[lsy@hcss-ecs-116a just_test]$ ll
total 24
drwxrwxr-x 2 lsy lsy 4096 Sep 26 19:14 code_progressbar
-rw-rw-r-- 1 lsy lsy 8 Sep 29 20:31 laosi.c
-rw-rw-r-- 1 lsy lsy 0 Sep 29 20:21 laosi.txt
-rw-rw-r-- 1 lsy lsy 12 Sep 27 17:16 Linux_code.c
-rw-rw-r-- 1 lsy lsy 846 Sep 26 18:42 README.en.md
-rw-rw-r-- 1 lsy lsy 935 Sep 26 18:42 README.md
-rw-rw-r-- 1 lsy lsy 0 Sep 29 19:14 txt.hello
-rw-rw-r-- 1 lsy lsy 64 Sep 27 17:27 win_code.c
[lsy@hcss-ecs-116a just_test]$ git add .
[lsy@hcss-ecs-116a just_test]$ git commit -m "免密登录第二次"
[master f18a705] 免密登录第二次1 file changed, 1 insertion(+)create mode 100644 laosi.c
参考:https://blog.csdn.net/camillezj/article/details/55103149
至此我们的git就结束了,可以把仓库删掉了。
之后根据提示填写验证内容即可删除。
六. 代码调试工具gdb/cgdb
安装指令
gdb:sudo yum/apt install -y gdb
cgdb:sudo yum/apt install -y cgdb
1)启动和退出gdb
其实gdb就是一个普通指令,所以直接gdb就可以进入gdb交互界面,quit退出。以命令的形式调试,无图形化界面。:
2)gdb和cgdb的关系
1. 简单来说,cgdb是gdb的一个“外壳”或“前端”,它让gdb变得更好用。
2. gdb(GNU Debugger)是一个纯命令行界面的调试器,用户通过命令输入与它交互。虽然他功能强大全面,但是调试的时候我们不能直接看到代码,也不能直观看到我们命令的执行结果,比如我打了一个断点,但是不能像VS那样,一眼就看到这个断点我有没有打上...所以我们为gdb套上一个更可视直观的外壳。
3. cgdb(Curses GDB)一个使用 Curses 图形库来构建用户界面的gdb调试器,它不是一个独立的调试器。它在后台启动并驱动gdb,所有实际的调试命令都是由gdb执行的。
CGDB 在纯文本终端中提供一个分屏、高亮、可交互的界面:
① 上方窗口显示源代码。
② 下方窗口显示gdb命令输出。
4. cgdb是gdb的扩展,他们的指令集是相同的。
3)引入稍后要调试的程序
1. mycmd.c
#include <stdio.h>
// 实现从start加到end
int Sum(int s, int e)
{int ret = 0;int i = s;for(; i <= e; i++){ret += i;}return ret;
}int main()
{int x = 1, y = 100, sum = 0;sum = Sum(x, y);printf("%d~%d=%d\n", x, y, sum);return 0;
}
2. makefile
mycmd:mycmd.c@gcc -o $@ $^
.PHONY:clean
clean:@rm -rf mycmd
4)指令学习前的铺垫--调试信息
1. 我们先直接调试刚刚的可执行文件,发现一直在提醒我们no debugging symbols found,意思就是gdb启动程序时,在程序内部没有发现调试信息。
2. 出现这个现象的原因
gcc/g++编译代码形成的可执行程序默认是release模式,没有调试信息。我们在VS中开发时可以选择debug/release版本。
debug模式是程序员在开发期间所处的模式,其中包含调试信息,会增加可执行程序的体积。
而发布出来给用户使用的必须是release版本的软件,因为体积小。
简单来说,开发用debug,测试、发布的是release。
3. 怎么证明可执行文件是release的?
可执行程序是二进制文件,但它不仅仅是二进制的集合,他也有自己的固定格式:Linux中可执行程序的格式是ELF。
-
readelf -S 可执行程序名
这个指令可以读取可执行程序格式信息的细节。
所以我们可以通过管道和行文本过滤器查看是否有调试相关的信息:没有任何输出,即没有调试相关的信息,的确不是debug版本,而是release。
4. 默认是release版本,那么怎么变成以debug版本发布呢?
gcc/g++在编译时带上-g选项,我们再编译一个debug版本做对比。
我们再来看一下这个版本是否有调试信息
[lsy@hcss-ecs-116a code_gdb]$ readelf -S debug_mycmd
这就是所谓的调试信息
有了调试信息在进行调试就不会报错了,我们可以正式进入gdb相关命令的学习。
5)详细学习gdb相关命令
(下面的debug_mycmd我就简称mycmd了)
1. l (list) 罗列mycmd相关的源文件中的代码内容,也可查看指定想看的源代码,如 l src1.c
l 加数字可显示某一行前后代码
(gdb) l
7 for(; i <= e; i++)
8 {
9 ret += i;
10 }
11 return ret;
12 }
13
14 int main()
15 {
16 int x = 1, y = 100, sum = 0;
(gdb) l 0
1 #include <stdio.h>
2 // 实现从start加到end
3 int Sum(int s, int e)
4 {
5 int ret = 0;
6 int i = s;
7 for(; i <= e; i++)
8 {
9 ret += i;
10 }
(gdb) l 10
5 int ret = 0;
6 int i = s;
7 for(; i <= e; i++)
8 {
9 ret += i;
10 }
11 return ret;
12 }
13
14 int main()
(gdb) l 14
9 ret += i;
10 }
11 return ret;
12 }
13
14 int main()
15 {
16 int x = 1, y = 100, sum = 0;
17 sum = Sum(x, y);
18 printf("%d~%d=%d\n", x, y, sum);
2. r (run)类似VS中的快捷键F5 ,启动程序开始调试,但是没有断点的话直接就执行完了,所以我们想调试,第一步就得打断点。
(gdb) r
Starting program: /home/lsy/code/code_gdb/debug_mycmd
1~100=5050
[Inferior 1 (process 1800) exited normally]
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.3.x86_64
3. b (breakpoint)打断点
方式1:b +行号。如,b 20,这样就在第二十行打上了断点,此时再r就不会执行完,而是停在第二十行。
方式2:b +文件名:行号。 给指定的文件打断点。
(gdb) b 20
Breakpoint 1 at 0x4005ab: file mycmd.c, line 20.
但是打了断点之后我们看不到任何效果,之前在VS等环境下,我们能直观的看到断点。这样用起来就不太舒服,看源代码也不方便,所以我们改用cgdb。
1. cgdb安装指令:sudo yum/apt install -y cgdb
2. 启动指令:cgdb 可执行程序名
3. 自动分屏,上面代码,下面命令操作,不用 l 一点点看代码了。
代码前的绿箭头表示代码此刻运行到这里。
4. cgdb分屏下
① Esc进入代码屏,可以上下或者jk翻阅。
② 按 i 回到gdb屏。
我们再打一个断点看看效果:行号变色。
4. info b 查看我们打的断点,每个断点都有自己的断点号。
5. d +断点号 (delete) 删除断点,删断点不能删行号,要删断点号。
6. s (step)逐语句
逐语句就是按照语句,一句一句来。通常代表在一个函数入口时要进入函数。对应VS中的F11。
7. n (next)逐过程
把函数整体当作一个语句,也就代表着不进入函数。对应VS中的F10。
gdb自动记录最近一次执行的操作,所以s/n一次以后一直回车就能接着往下调试,不用一直输。
8. p +变量或表达式(print) 打印变量或表达式的值。
但是每次查看变量的变化都要p一下有点麻烦,怎么才能有类似VS中监视窗口的效果呢?
9. display +变量名/表达式 再n/s他就会自动更新
10. undisplay +长显示条目编号 去掉display中我不想看到的东西
11. until +行号 执行到指定行号(方便出循环)
意义:允许我们运行一个函数内指定区域的一块代码,方便快速结束一个长代码块或循环的调试。不用逐语句的来,可以直接跳过来观察某一块的代码有没有错。
12. finish
在函数内部执行finish。作用:执行到当前函数返回后停止。
13. c(continue)
跳到下一个断点处,并从当前位置开始连续执行程序。
1. until && finish && c 可以帮助我们确认问题的大致范围,然后再通过逐过程、逐语句等操作确定问题的具体所在。
2. 当代码出现了问题时,首先需要人来发现问题;然后需要调试工具来帮我们找到问题,并支持人来分析问题,这就是调试的核心工作;最后人来分析并解决问题。
14. disable +断点号/breakpoints 禁用某个/所有断点
15. enable +断点号/breakpoints 启用某个/所有断点
6)gdb调试技巧
1. watch +变量或表达式名
① 简单来说,就是看一个你要监视的东西的值有没有变化
执行时监视⼀个变量或表达式的值。如果监视的表达式在程序运行期间 值发生变化,gdb 会暂停程序的执行,并通知使用者。
② 本质上也是一个断点,但是有触发条件 --- 变量或表达式的值被修改
③ 通知我们时会展示新、旧值。
④ 意义:如果我们的程序中有一些值不应该被修改,所以怀疑是对他们不正确的修改导致了问题,此时就可以watch它,如果他真的发生了变化就会通知我们。
2. set var
① 在当前运行的调试会话里临时改变内存中该变量的值,但不会修改我们的源代码和可执行程序本身。
② 比如在调试的过程中,我们发现flag的值错了,不能得出正确结果,想要修改它的值。但是不想退出gdb,再vim修改源代码,重新编译调试。此时就可以用 set var 先暂时修复数据,不影响继续调试,提高效率。
3. 条件断点
① 条件断点也是断点,info b可以查看。
② b +行号 if +条件
③ 比如在一个循环里(第7行),希望直接跳到第53次循环即 i == 5 时触发断点:
b 7 if i == 53
此时进入循环后 c (continue)就可以直接跳到第53次循环。
④ 如果第7行已经有不是条件断点的断点了,那么如何给已经存在的断点新增条件呢?
condition +断点号 +要新增的条件 如:condition 2 i == 53(注意这里不需要 if 了)
⑤ 意义:怀疑变量在某个值上会出错,可以直接把断点定位到这个值上,或者其附近。