Git基础及常用命令前言一、git是什么Git的安装二、git的三种状态三、本地仓库及其常用命令3.1 在已有项目上创建仓库3.1.1 初始化仓库3.1.2 初次提交前的准备工作3.1.3 首次提交3.2 克隆开源仓库3.3 本地仓库的管理3.3.1 git log【git log】相关用法3.3.2 git show【git show】相关用法3.3.3 git diff【git diff】相关用法3.3.4 三种会修改文件的操作--【checkout、reset、rm】3.4 分支3.4.1 分支的创建3.4.2 切换分支与checkout的说明【git checkout】相关用法【git stash】相关用法3.4.3 分支的合并【git merge】相关用法【git rebase -i】相关用法【git rebase】相关用法四、git远程仓库快马加鞭中...前言笔者在使用git时因为对原理的理解不够深刻而有时对工程造成不可逆的破坏鉴于教训决心将git钻研透彻并将它们整理成文档帮助大家避开一些可能的错误。本文完全基于 Git-book 原文 并结合个人使用经验编写如有疑问还请及时查看原文档对应章节。与命令有关的用法在目录中均用【】标识方便查找。文章将从「git 是什么」讲起以便没接触过 Git 的新手也能明白其强大的作用并快速上手。所有命令都是在Linux TerminalLinux终端上运行的Windows 下也可以使用 Git Bash。一、git是什么它是一个[分布式][版本控制工具]。[版本控制]简单讲就是记录项目每一次具体的改动称为版本快照方便开发者与阅读者进行版本对比与还原。它还能同时对多个文件进行撤销对某次提交进行回滚其撤销的内容是透明且可控的这从根本上杜绝了开发者不知道修改了什么、需要撤销什么、会不会撤销过头了等等的一系列问题让编码历史有迹可循编码结果可控可靠。[分布式]是因为同一项目的所有开发者可以共享一个远程仓库他们自己也有一个本地仓库开发过程是在本地仓库完成的通过pull拉取与push推送两个操作可以完成仓库的同步。Git的安装从该网址Git-install选择对应的操作系统进行安装。安装过程中可能会有很多选项大多按照默认的就行后续也可以重新修改。二、git的三种状态git给开发者的信息实际上十分简单它总共就三种状态分别是工作区、暂存区stage/add、版本库commit以下分别对它们做详细介绍版本库当我们对本次编码的内容进行测试并确认无误之后我们一般会选择进行一次提交commit这时git会记录所有内容包括完全没有修改的文件对于使用者来说只会看到有变更的内容但要记住git的底层不是单纯的增量记录这是git与其他版本控制工具最大的一个区别之一后续展开说明并生成一个40个十六进制数组成的哈希值如488c42802f5109d002c2467af96d046ac9091663该哈希值完全不与所有的其他仓库与提交重复仅用于标识本仓库的本次提交。暂存区提交只会提交暂存区的内容这是暂存区最重要的作用。没有暂存区你就只能将本次修改全部提交但有时候编码并不是一次性完成的。工作区相比上次未提交且当前未暂存的内容都视为在工作区内它们不会被提交上去。这三种状态是任何其他操作的基础无非就是将文件从一种状态变为另一种状态。跟踪(tracked)与未跟踪untracked创建新的文件并不会被立马被跟踪untracked但放入暂存区后将被永远跟踪即使将其从暂存区撤回你可以使用git -rm --cached来取消跟踪但这一做法有一些风险后面将结合3个常见的撤销操作进行对比讲解另外首次提交的内容自然会被跟踪。未被跟踪的文件其状态是??在vscode中其也表示为绿色的U一旦文件被跟踪它将被git的快照记录反之则被git忽略。除了上述的??状态指示还有M、A、D、R、T、U、C、!!。使用git status --short可以看到文件状态栏有两行如下图其中左边的M是暂存区vs版本库的状态右边的M是工作区vs暂存区的状态。这里需要注意的是暂存区的内容可以与工作区不一致。比如一开始修改了某文件a.c并将它暂存此时暂存区与工作区一致git status只能看到左边的M右边没有M之后又修改了a.c但没将其暂存那么右边的M就出现了因为暂存区与工作区不一致。状态指示含义MModified更改AAdded新文件被git addDDeleted文件被删除RRenamed重命名TType Changed类型被改变UUnmerged合并冲突CCopied文件是复制过来的??文件未被跟踪!!文件被.gitignore忽略其将在第三章本地仓库的创建中讲解三、本地仓库及其常用命令本地仓库的创建方式有两种一是把自己的项目作为仓库创建二是获取开源项目即克隆clone。3.1 在已有项目上创建仓库3.1.1 初始化仓库$cd/home/user/my_project $gitinit这会在my_project下生成.git仓库初始化完成但此时还没有任何快照记录。3.1.2 初次提交前的准备工作在进行git add操作之前应该仔细思考是否要将该目录的所有文件进行追踪。例如编译产物、自动生成的配置产物等是不可控的些许改动就会引起它们很大的变化因此它们并不适合被追踪否则后续使用git log会有很多无关内容妨碍我们查看核心改动。建仓初期应该保持追踪干净以免后续多出许多与项目无关的提交来修正这一错误。以下的命令均在my_project下即.git同目录下执行。引自git官方教程文件.gitignore的格式规范如下所有空行或者以#开头的行都会被 Git 忽略可以使用标准的 glob 模式匹配它会递归地应用在整个工作区中匹配模式可以以/开头防止递归匹配模式可以以/结尾指定目录要忽略指定模式以外的文件或目录可以在模式前加上叹号!取反。$nano.gitignore#.git是个文件夹 .git/ .vscode/ #忽略根目录下以binary_|config_|image_开头的文件夹 /binary_*/ /config_*/ /image_*/ #忽略所有目录下build_开头的文件夹 build_*/ #忽略所有目录下的.o与.d文件 *.[od] #但保留对build_rtos下所有子文件夹中lib.o的跟踪 !build_rtos/**/lib.o值得注意的是AI的使用越来越广泛开发项目的过程可能生成AI的一些配置文件要记得将它们也都添加到.gitignore中特别是涉及到API的部分。3.1.3 首次提交$gitadd.$gitcommit-m[init] first commitadd .代表暂存当前目录下所有除了被.gitignore设置的文件快照。commit -m表示提交并写一个备注[init] first commit是-m的参数。3.2 克隆开源仓库$cd/home/user/my_project $gitclone https://github.com/libgit2/libgit2这会在当前目录下创建一个名为 “libgit2” 的目录并在这个目录下初始化一个.git文件夹 从远程仓库拉取下所有数据放入.git文件夹然后从中读取最新版本的文件的拷贝。除了https://协议的克隆还有git://、file://、SSH等远程仓库的内容将在下一章介绍。3.3 本地仓库的管理git的提交很简单无非就是addcommit但如果对其中的原理理解不深就很容易出问题出了问题又该怎么补救本章针对一些常用的git命令进行详解。3.3.1 git log上文提到每次提交都会产生一个哈希值提交的内容需要通过哈希值来查看而获取哈希值的方式就是git log。我们可以明显地感觉到 log 与 commit 有紧密的关联因此每次提交的备注应该足够清晰与简洁最好使用简单的、带有关键词的英文去描述比如[lcd] add lcd driver、solve lcd flicker。单独使用git log的输出效果是这样的commit 488c42802f5109d002c2467af96d046ac9091663 Author: username useremail Date: Wed Jun314:44:2420260800[Display]android and linux on SPD OK还可以增加选项来修饰输出效果每一个选项之间都应有一个空格隔开【git log】相关用法git log选项描述--grepxxx查找备注中含有xxx关键词的提交记录上面示例中lcd是否带[]也是为了这里可以快速区分重要提交--oneline每个提交只占用一行输出的信息只保留简短哈希值与提交备注-- 文件-- 目录/筛选某一文件有改动的提交记录。注意这里的-- 之后是有空格的该选项必须放在所有选项之后。--raw附带有变动的文件目录--authoruser筛选提交人--since2025-01-01--sinceJun 3 2026 14:00:00--since2 weeks ago--since2 month ago从多久前的提交记录开始输出--until2025-01-10输出到什么时候的提交记录就停止3.3.2 git show用于输出某次提交的内容。基本格式是git show [哈希]提交记录的哈希值一般推荐使用前7位。git show 也可增加选项哈希的位置没有限制单纯使用git show时输出最新的提交内容。【git show】相关用法git show选项描述--oneline一行输出-- 文件-- 目录/查看文件/目录的改动位置同样要放最后--raw输出变动的文件目录--stat只查看提交摘要3.3.3 git diff用于查看当前的修改记录单纯使用git diff对比的是工作区 vs 暂存区的修改。注意假如暂存区为空那么该命令输出结果也为空这不代表工作区没改动。此时需要使用git diff HEAD来查看。【git diff】相关用法git diff选项描述--cached--staged暂存区vs最新一次提交的内容两个选项没有区别-- 文件-- 目录/参数前需要空格指定对比的范围--summary输出修改摘要3.3.4 三种会修改文件的操作–【checkout、reset、rm】在讲解git add与git commit之前先对它们的风险及补救措施进行讲解。checkout -- [file]\[directory/]假如该文件(夹)没有被add过那么将使用最近的提交记录覆盖工作区假如该文件(夹)被add过那么将使用暂存区的内容覆盖工作区。特点只修改工作区。git reset --[mode] [哈希]git reset --mixed [哈希]等于git reset [哈希]用于删除提交记录清空暂存区但不会删除任何工作区代码。常用的有git reset HEAD~1撤销最新一次提交。主要是发现提交的内容有缺陷需要补充或删除后重新提交。如果要把当前暂存的撤回可以git reset -- [file]\[directory/]。哈希不需要是最新的一次提交切到某个提交a后如果进行add会将所有与当次提交快照不同的内容都添加到暂存区此时再次进行提交实际上就是完成了从提交a到最新工作区状态之间的提交合并中间零碎的提交不会出现在git log里。git reset --soft [哈希]与mixed模式一样但不会清空暂存区。git reset --hard [哈希]彻底回退会删除工作区让文件与哈希对应的快照一模一样。git rmgit rm与rm类似主要用于删除删除目录时同样需要带递归参数-r。git rm [file]\[directory/]工作区删除暂存区记录删除会记录整个文件的内容git rm --cached [file]\[directory/]取消跟踪清除暂存区有关的内容但暂存区会记录这部分删除所以无论如何都会在暂存区留下记录。注意该命令主要作用是取消跟踪清除暂存区的根本原因是git不再跟踪它了它自然就被移出了暂存区不能把它当移出暂存区来用取消跟踪的文件大概率后续也不再需要跟踪所以进行此操作后要将该目录加入.gitignore。场景假设由于首次提交的内容非常非常多并没办法仔细审查所有提交内容难免会有些不该提交的内容被跟踪在后续一直潜伏在你的项目管理中。直到某次修改触发了该文件的修改比如某些目录下面的编译产物或者自动生成的配置文件而你刚好没仔细审查就暂存并提交此时正确的补救措施应该是什么解决方法一第一步肯定是撤回提交–使用git reset由于暂存区是不干净的所以应该使用--mixed模式将暂存区一并撤回。这里不会改变工作区的内容不用担心代码丢失。现在我们分析有效记录的比例用目录的数量衡量而非代码改动量可以用git diff --raw。如果有效记录多那就先git rm --cached [file]\[directory/]用nano .gitignore将它们写入后add .gitignore并直接提交这是因为git reset --mixed后暂存区干净git rm --cached会把被取消追踪的内容暂存此时它们的status正是第二章提到的D添加到.gitignore是必须的步骤否则下次git add时依然会将它们暂存并追踪。进行本次提交使提交变得冗余是不可避免的但这是为了下次的提交更加干净。如果是无效记录多那就先进行git add -- 有效提交的目录并提交此时只剩下一种变更进行全量暂存git add .并根据情况进行提交描述。解决方法二方法一是比较常用的第二种方法只适用于个人开发团队合作或开源的情况下慎用。同样地将其移出暂存区使用git update-index --assume-unchanged [file]\[directory/]将其忽略git标记为“不在乎其改变”所以git diff与git add也会忽略它git add 该文件可以强制进行本次暂存后续依然被忽略 。实际上git依然会跟踪它在拉取团队代码时这部分依然会被覆盖邪修方法风险较大必须慎用解除忽略使用git update-index --no-assume-unchanged [file]\[directory/]。3.4 分支假如现在某一功能的开发进行到一半当前状态连编译都不通过。突然来了一个很紧急的bug修改请求难道我们要先提交一次然后进行版本回退去更改当然不是。你可以创建一个新的分支对主分支进行修改合并而完全不影响当前分支所在的内容即使你不对当前内容进行暂存或提交这就是分支最重要的作用–在一个主干道上独立出多个开发路线逐步壮大主干又支持多个内容的细分你完全可以在创建的分支上再创建分支。本地分支与远程分支的原理是一样的但是管理目的有差别本节只讲解本地分支远程仓库的内容在下一章展开。3.4.1 分支的创建现在回顾一下本地仓库的创建流程其中git branch用来看仓库中的分支情况$gitinit $gitbranch#会发现没有任何输出$gitadd.$gitcommit-m[init] first commit$gitbranch * master#输出了master实际上在进行首次提交的时候git已经默认创建了一个master分支了可以通过git init -b barnch_name自定义初始分支的名称。分支的示意如下图每一个分支都是一个移动的指针它指向了某次提交。在master分支下一直提交到f30ab此时使用git branch testing创建一个名为testing的分支原理是创建一个指针指向本次提交该分支享有之前全部的提交记录这是因为每次提交都会包含一个指针指向上一次的提交对象。图中黄色的HEAD指针非常重要它表明了当前工作区处于哪一提交节点上图表明当前工作区处于master分支。一般来说它会指向分支名但它也可以直接指向某次提交此时HEAD处于游离状态。游离状态下可以进行提交但它不属于任何分支。git branch描述git branch -[选项] [分支名]通用格式不加选项与分支名默认是列出所有本地分支分支前带*****的表示当前所在分支创建分支git branch [分支名]创建名为xxx的分支但不切换如果再进行git checkout xxx就等于git checkout -b xxx创建并切换分支-m [old分支名] [new分支名]改名查看分支情况-a列出本地分支远程分支-r列出远程分支-v列出每个分支的最后一次提交--merged/--no-merged哪些分支已合并到当前分支/未合并到当前分支删除分支-d [分支名]安全删除只能删除已被合并的分支-D [分支名]强制删除可以删除未被合并的分支3.4.2 切换分支与checkout的说明git checkout功能丰富前文已经讲述了一种用法实际上它更多地用于切换分支中。【git checkout】相关用法git checkout选项描述-b [new_branch]基于当前节点创建名为new_branch的分支并立马切换过去-b [new_branch] [哈希/分支名/tag名]基于哈希/分支名/tag名创建new_branch并立马切换[空格]-类似cd -回到上一个分支用于两个分支来回切换上文提到的HEAD指针游离大多都是该命令产生实际上只要是直接作用于哈希的命令大多都会导致指针游离这并不是什么错误。使用git checkout testing切换到testing分支后做出修改并提交其指针情况如下图HEAD-testing-87ab2-f30ab而master-f30ab也就是说testing后续的所有提交与master都没有关系了。以上三图来源Git - 分支简介有时工作区不干净但需要切换分支这是git不允许的使用暂存可能会带来麻烦特别是新文件、提交又会产生垃圾记录。此时就需要使用git stash进行储藏储藏后工作区与暂存区都是干净的。储藏实际是个栈它是全局共享的即任何分支的任何提交都能获取它存储是依然是当前的快照所以完全不受其他记录影响。【git stash】相关用法git stash描述储藏git stash push/save [参数] [-选项] [-- 文件]通用格式save是旧写法-m xxxx备注-u / --include-untracked包含未被跟踪的内容-a / --all所有文件-p / --patch按块选择--staged只储藏已暂存的内容查看储藏git stash list列出已储藏的快照记录git stash show查看最新一次储藏内容如果加上list结果中的stash{x}参数则查看该次储藏x是储藏序号这个参数显然说明了它是一个栈取出储藏git stash apply恢复最新的一次储藏如果加上stash{x}参数则恢复指定储藏恢复后该记录不会被删除git stash pop与apply同理但记录会被删除取出储藏是有可能产生冲突的冲突的概念将在下一节「分支的合并」展开。3.4.3 分支的合并在对分支合并进行说明之前先介绍一下分叉的概念如上图master分支上创建了iss91v2分支并且master与iss91v2后续均有提交就将它们称为分叉假如只有iss91v2在提交它们虽然是两条分支但实质上还是可以延展为一条直线所以不是分叉。分叉的两条分支在合并时会有两个父指针分别指向两个分支并且产生一次提交如图中C1即三方合并称为三方是因为该过程还需要分叉的共同祖先作为参考分叉双方各自与祖先进行对比与祖先不同地方的修改直接被采纳相同地方的修改就会报冲突而不分叉的两条分支在合并的时候只需要将指针移动此时不会产生提交即快进合并。以下将展示合并的过程$gitcheckout master $gitmerge iss91v2#删除iss91v2分支$gitbranch-diss91v2要先切到主分支再进行合并合并后使用git branch -d将临时的分支删除。注意仓库中的主分支只有一个传统命名为master现大多主动更名为main。一个主分支可以拆分出来多个独立的分支用于不同的技术路线和硬件适配前文所述的主分支是引申了独立分支的含义不一定就是master或main只有合并后才能将分支删除否则git是不允许的。由于主分支可能有内容还未提交而这部分内容可能与要合并的内容存在冲突此时需要手动选择保留哪一部分以下是官方给出的的示例HEAD:index.htmldividfootercontact:email.supportgithub.com/divdividfooterplease contact us at supportgithub.com/diviss53:index.html示例中是git在工作区中插入冲突的标记你只能保留其中的一个代码块否则编译无法通过冲突也会一直存在之上的是主分支内容之下的是另一分支的内容它们不一致所以导致冲突在保留其中一个的同时也要删除、、三行vscode甚至提供了按钮选择的功能更加高效且不容易出错。【git merge】相关用法git merge选项描述--no-ff [分支名]强制生成合并提交记录即使是快进合并--abort回退到合并前--no-commit不自动提交--no-edit自动填充默认合并备注不弹出编辑器--dry-run只做逻辑检查检查是否有冲突不真正地合并[commit-id]合并指定的提交除了上述的普通合并还有一种很搞基高级的合并–变基合并git rebase这种方式对本地分支的整理很有帮助。它的本质是重写提交历史找到两个分支的共同祖先将当前分支的所有提交逐一 “移植” 到目标分支的最新提交之后形成线性历史。$gitcheckout feature-branch $gitrebase master这是将feature-branch合并到master执行逻辑如下找到feature-branch和master的共同祖先暂存feature-branch上相对于祖先的所有提交将feature-branch指针移动到master的最新提交按顺序逐个应用之前暂存的提交。聪明的你一定发现这样操作有一个致命的麻烦–它不像普通合并一样直接应用最后提交的快照而是逐个应用所有提交假如feature-branch不断地在同一个地方修改那在执行第4步的时候绝对会出现一次又一次的冲突。此时应该先使用交互式变基的方式来批处理#整理最近n次提交$gitrebase-iHEAD~n#最近的n次记录这会打开编辑器如下pick d81e326 # [eth]add eth driver pick cdca92c # [eth]add eth reset pick 17ce305 # [eth]solve eth disconnet pick f7792d9 # [eth]solve eth prove与git log不同的是最上面的才是最旧的提交记录依次排序。【git rebase -i】相关用法操作描述pick/p不做任何修改reword/r保留提交内容修改提交信息批处理到该提交时会自动暂停并弹出提交备注编辑页面退出页面后git继续自动执行edit/e保留本次提交记录允许修改提交信息与内容批处理到该提交时会自动暂停并弹出如下代码块的提示此时工作区是这个提交提交完成后的状态你可以对它进行修改add后用它提示的git commit --amend对该提交进行覆盖然后再git rebase --continue继续往下执行squash/s合并到上一次提交并保留本提交信息即该提交备注会在上次提交记录中出现合并之后本提交记录会从git log中删除注意实际上这一过程就是打补丁不冲突的代码内容直接保留冲突的代码直接应用新代码下面的fixup也是如此fixup/f合并到上一个提交后丢弃本提交信息即不保留备注也不保留提交记录drop/d删除该提交注意只能从最新的往前删否则依赖于它的提交会出错#edit暂停提示You can amend the commit now, withgitcommit--amendOnce you are satisfied with your changes, rungitrebase--continue为了解决上述问题将最新的几个记录由pick改为squash来将多个提交记录合并为一个之后再执行变基合并就很方便了你只需要处理一次冲突。【git rebase】相关用法git rebase选项描述git rebase [old-branch] [new-branch]以new-branch为基底合并old-branch-i HEAD~n批处理该分支从最新的提交到第n个的提交--abort撤销rebase的操作--skip跳过当前提交的处理慎用四、git远程仓库快马加鞭中…