ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Git从入门到实践:版本控制、分支管理与协作讲解

Git从入门到实践:版本控制、分支管理与协作讲解 1. 环境准备与基础概念1.1 安装Git与初始配置Git这东西装起来本身没啥难度真正的坑全躲在装完之后的配置环节。我在Windows、macOS和Linux三种系统上都折腾过Windows用户建议直接去Git官网下载安装包一路Next到底就行。装完之后打开Git Bash先干两件事确认版本和设置身份信息。git --version看到类似git version 2.40.0.windows.1这样的输出说明装好了。接着设置用户名和邮箱这一步是新手最容易跳过的但实际上下一次commit就会用到它不配的话提交记录里会显示一堆乱码或者unknown身份git config --global user.name your_name git config --global user.email your_emailexample.com--global参数的意思是全局生效也就是说这台机器上所有仓库都默认用这套身份。如果某个项目想用不同的身份把--global去掉在项目目录里单独配置就行。验证配置是否生效用git config --list查看所有配置项确认无遗漏。1.2 版本控制的底层逻辑我刚用Git那会儿最困惑的就是它和SVN这种老牌版本控制工具到底差在哪。后来才明白Git的核心理念是快照每次commit保存的是整个项目的当前状态而不是文件之间的差异记录。这就意味着历史记录是一条链每个节点都保存着完整的项目镜像。理解这个底层逻辑对后面理解为什么Git可以离线使用、为什么分支切换那么快都特别有帮助。Git把数据存在.git目录里这个目录是仓库的灵魂删了它就等于毁了所有历史记录。所以网上那些git目录泄露的新闻本质就是某台服务器上的.git文件夹被意外暴露出来了别人可以直接通过这层目录把整个代码库和历史commit扒下来这就是所谓的git目录泄露如何下载攻击手法的核心原理。再补一个基础认知Git里的文件有四种状态——未跟踪(untracked)、已暂存(staged)、已提交(committed)和已修改(modified)。日常开发的核心循环就是改文件 - 暂存 - 提交 - 再改文件 - 再暂存 - 再提交。理解这个循环后面所有命令都顺着这个思路走。2. 日常高频操作把代码提交到仓库2.1 add、commit、status三板斧日常开发中接触最多的就是这三个命令但很多人用得稀里糊涂。先说git status它是最安全的命令你随时可以执行不会改变任何东西。它会告诉你当前工作区是干净的还是有改动哪些文件被修改了哪些是新建的还没跟踪。再来看git add它做的事情是把文件放入暂存区index相当于告诉Git我准备提交这些文件。有几个常用变体git add file.txt # 添加指定文件 git add . # 添加当前目录下所有改动包括新文件 git add -u # 只添加已跟踪文件的改动不包含新文件 git add -p # 交互式添加逐个hunk确认-p参数我强烈推荐它能让你逐个代码块检查改动避免把调试日志或者临时代码误提交上去。有些人习惯git add .一把梭结果把编译产物、IDE配置啥的全提交进去了后面追踪历史记录时全是噪音。最后是git commit最常用的是带-m参数直接写提交信息git commit -m fix: 修复登录超时问题提交信息的规范能帮你省很多事。我自己的习惯是参考Conventional Commitsfeat表示新功能fix表示修bugdocs表示文档变更refactor表示重构。提交信息要短而明确别写更新或者修改这种没信息量的词。很多人没有养成频繁commit的习惯其实commit越频繁越安全每次commit都是一个还原点出了问题随时可以回退。2.2 log命令的可视化技巧git log是查看历史记录的命令新手直接敲一遍看到满屏输出就再也不碰它了。其实它有很多参数能让输出变得非常清晰git log --oneline # 每个commit显示一行精简版 git log --graph # 用线条画出分支结构 git log --authoryour_name # 按作者过滤 git log -p # 显示每次commit的具体diff git log --since2 weeks ago # 按时间过滤我自己的组合拳是git log --graph --oneline --decorate一条命令把提交历史可视化出来哪个分支在什么位置一目了然查问题的时候特别顺手。查看文件级别的历史用git log -- filename只显示与该文件相关的提交记录。配合git blame可以精确定位某一行代码是什么时候、被谁改的这在排查线上问题时是绝对利器。2.3 修改提交与回退写完commit发现漏了一个文件或者提交信息写错了最常用的操作是git commit --amend。它会把你当前暂存区的内容和上一次commit合并成一个新的commit替换掉原来的那个。常用于补丁式提交比如漏加了文件就补add一下再amendgit add forgotten_file.txt git commit --amend --no-edit--no-edit表示沿用上一次的提交信息不会弹编辑器。这里要注意amend会改写历史commit的hash值如果这个commit已经推送到远程并且别人已经拉取过就不要用amend了否则会把协作者的历史搞乱。回退操作是另一个重点很多人一提到回退就想着git reset但其实git revert在某些场景下更合适。区别在于reset是移动HEAD指针相当于删除历史记录revert是创建一个新的commit来反向改动保留原有历史。如果是合作分支上的代码出了问题推荐用revert因为不会改写已经共享的历史。git reset --hard HEAD~1 # 回退一个提交丢弃所有改动 git reset --soft HEAD~1 # 回退一个提交但保留改动在暂存区 git revert HEAD # 创建一个新的commit撤销HEAD的改动reset的三个模式刚性区别很大--soft保留暂存内容--mixed默认保留工作目录但取消暂存--hard直接丢弃所有改动。用--hard前一定确认清楚因为未提交的改动会被永久丢弃这可能是整个Git里最危险的参数之一。3. 分支管理并行开发的基石3.1 分支的创建与切换分支是Git最强大的功能也是很多人学了一半就懵的地方。Git的分支本质上只是一个指向某个commit的指针所以创建分支极快几乎不占用额外空间。git branch feature/login # 基于当前HEAD创建分支 git branch -a # 查看所有本地和远程分支 git checkout feature/login # 切换分支老式写法 git switch feature/login # 切换分支新式写法更语义化 git switch -c feature/login # 创建并切换Git 2.23以后的版本推荐用switch替代checkout做分支切换语义更清晰checkout的职责分得太杂。要注意的是切换分支时工作区里会有冲突的未提交改动Git会拒绝切换。这时候要么先commit/amend要么用git stash暂存起来后面详细讲。分支命名规范也值得提前形成习惯。我常用的格式是类型/描述比如fix/login-timeout、feature/export-report、refactor/refactor-db-layer。这样在分支列表里一眼就能看出这个分支在干嘛。3.2 合并merge和rebase的选型分支合并是多人协作中最容易出事的环节。合并方式主要有两种git merge和git rebase。merge的做法是把两个分支的历史合并成一个新的合并提交保留各自原有的时间线。它的好处是非破坏性历史真实完整但缺点是分支图会变得错综复杂全是蜘蛛网一样的线。git checkout main git merge feature/login # 把feature/login合并进main git merge --no-ff feature/login # 禁用快进合并保留分支历史rebase的做法是把当前分支的提交变基到目标分支上形成一条线性历史。它在重写提交所以看起来历史非常干净。但代价是如果这个rebase的分支已经推送到远程别人也基于它开发了再rebase就会造成历史冲突。git checkout feature/login git rebase main # 把feature分支的提交重新放到main的最新提交之上我自己的选择逻辑是这样的个人分支开发时用rebase保持历史干净合并回主分支时用--no-ff保留一个清晰的合并节点。如果是大型团队约定好一种策略并贯彻到底比纠结哪种更优重要得多。3.3 cherry-pick精准偷取提交合作过程中经常遇到这种情况同事在dev分支上写了一个功能提交刚好也是你正在修的bug需要的代码。你不可能把整个dev分支合过来这时候git cherry-pick就派上用场了它可以把任意一个commit单独应用到当前分支上git cherry-pick a1b2c3d # 应用某个commit到当前分支 git cherry-pick a1b2c3d..e4f5g6h # 应用一段范围 git cherry-pick -x a1b2c3d # 添加来源信息便于追溯用得多了会踩一个坑cherry-pick来的commit和原commit内容一样但hash值不一样因为父提交不同。如果两边重复修改相同代码大概率会产生冲突处理方式跟普通merge冲突一样。另外cherry-pick适合小颗粒度改动如果依赖关系复杂一个commit依赖前面几个commit的代码那不如重新梳理分支策略。4. 远程协作push、pull与SSH配置4.1 远程仓库与推送基础本地玩得再溜真正的战场在远程协作。核心命令其实就几个git remote add origin gitgithub.com:user/repo.git # 关联远程仓库 git remote -v # 查看远程地址 git push origin main # 推送本地分支到远程 git pull origin main # 拉取远程更新并合并 git fetch origin # 只拉取更新不自动合并pull和fetch的区别很多人搞不清。fetch只是把远程的commits下载到本地不会动你的工作区pull等于fetch加merge两步一起做了直接把远程改动合并进当前分支。所以我个人的习惯是在干净的工作区里用pull方便省事如果本地有未提交的改动先fetch看一眼远程改了什么再决定怎么处理。4.2 SSH密钥配置与免密登录每次push都输密码很烦人配置SSH密钥是正解这也是网上搜索git配置gitee密钥git免密最密集的原因。全套流程如下# 生成密钥对一路回车即可 ssh-keygen -t ed25519 -C your_emailexample.com # 查看公钥内容 cat ~/.ssh/id_ed25519.pub # 把这段公钥复制粘贴到Git平台GitHub/Gitee/GitLab的SSH Keys设置里 # 本地验证是否配置成功 ssh -T gitgithub.com换了ed25519算法替代传统的RSA长度短、安全性更高生成速度快很多旧机器如果用的旧算法建议重新生成一套。配置完成后clone远程仓库时记得选SSH地址不要选HTTPS地址因为只有SSH地址才会走密钥认证。重点说一下多账号场景。很多人工作和个人用的是同一个Git平台的账号或者两个不同平台想配置多套密钥。这时候就要用到~/.ssh/config文件# 个人账号 Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519 # 公司账号 Host gitlab.company.com HostName gitlab.company.com User git IdentityFile ~/.ssh/id_ed25519_company这样配置好之后使用不同的域名就会自动匹配不同的密钥文件。曾经踩过一个大坑多个密钥对应同一个Host时Git默认只读第一个匹配到的密钥文件导致认证失败。这个场景下要么配置Host别名要么用ssh-agent来管理。还有一个跟远程相关的坑HTTPS方式如果配了密码缓存之后可能需要清除账号密码特别是换了密码或者换人用同一台机器时。清除方式有几种Windows凭据管理器里删掉对应记录或者命令行执行git credential-manager uninstall视版本而定再或者直接删除~/.git-credentials文件。网上搜git清除账号密码的教程一堆核心就是这个。4.3 分支与远程的同步细节远程分支推不上去、被拒绝、需要强制推送这些场景基本是所有团队协作中最常见的报错现场。先说常规同步操作git push -u origin feature/login # 首次推送本地分支到远程并建立关联 git push origin :feature/login # 删除远程分支推送空分支 git branch -d feature/login # 删除本地已合并分支-u参数的作用是设置上游分支之后就可以直接敲git push、git pull不用每次都带远程名和分支名。设置完上游关系后git status还会额外显示本地分支和远程分支的领先/落后情况。推送被拒绝时最常见的原因是远程分支上有本地没有的提交。Git默认拒绝非快进推送意思是不能直接覆盖远程已有的历史。解决方案是先git pull --rebase把远程改动拉下来合并再重新push。注意我用的是git pull --rebase而不是git pull这是为了保持当前分支历史是线性的避免多出一个无意义的merge commit。如果确认本地历史就是要覆盖远程那就用git push --force-with-lease这个命令比--force多了一层保护如果远程分支在你上次fetch之后有更新它就会拒绝执行避免误覆盖别人的提交。push --force-with-lease还有一个贴心场景本地工作区比远程落后很多你想把本地当前commit强行推到远程。这时候如果远程出现了超过你预期的改动它不会盲目覆盖而是让你先处理。强烈建议把force-with-lease作为唯一的强制推送选项。5. 高阶技巧与疑难杂症排查5.1 stash临时保存现场开发过程中经常遇到手头代码改到一半但是需要紧急切换到别的分支去修一个bug的情况。直接commit吧提交记录会变得很碎直接切换吧Git会拒绝因为工作区有冲突。这时候git stash就是救命的git stash # 把当前改动暂存起来 git stash list # 查看所有暂存记录 git stash pop # 恢复最近一次暂存并删除记录 git stash apply # 恢复暂存但保留记录 git stash -u # 连同未跟踪的新文件一起暂存stash本质上是一个栈结构新的暂存记录会压在上面。恢复的时候如果后续改动跟暂存内容冲突需要手动解决。还有一个常用组合git stash branch new-branch它会基于当前暂存的改动创建一个新分支并恢复改动适合那些本来应该在独立分支上开发的场景。5.2 误删分支与回滚reflog救命Git最让人觉得心安的功能之一就是reflog。它记录了HEAD指针的所有历史移动相当于一份操作日志即使在git reset --hard之后也能借助它找回丢失的commitgit reflog # 查看HEAD移动历史 git reflog show feature/login # 查看某个分支的历史恢复误删分支的操作核心是先用git reflog找到最后一次指向该分支的commit hash然后git checkout -b feature/login 34abc56 # 用该hash重建分支还有一类常见的现象git reset --hard之后发现刚才丢弃的代码其实很重要。此时reflog里依然能看到被reset丢弃的那个commit的hash直接git cherry-pick回来就行。reflog有自己的过期机制默认大概90天只要你及时操作几乎不会追不回来。5.3 Git LFS大文件存储的正确姿势很多人平时用Git管理代码忽然有一天要把几百MB的设计稿、数据集、模型文件放进去仓库马上膨胀clone和fetch慢到怀疑人生。Git LFSLarge File Storage就是专门应对这个场景的它不把大文件本身放进Git仓库而是存储一个指针文件真正的大文件存到单独的服务器上。git lfs install # 初始化LFS git lfs track *.psd # 指定要跟踪的文件类型 git add .gitattributes # 把跟踪规则提交进仓库 git lfs ls-files # 查看当前LFS管理的文件列表配置完git lfs track后仓库里会多出一个.gitattributes文件这个文件必须提交否则别人clone你的仓库后不会应用LFS规则。有个常见的坑是已经用普通Git提交过大文件后想再改成LFS管理需要重写历史否则旧提交里的大文件依然混在Git对象里仓库大小不会变。这个操作很麻烦建议在项目一开始就规划好LFS策略或者用git filter-repo重写历史。另一个老生常谈的问题是git lfs clone卡住。git lfs clone其实已经弃用了现在推荐直接用git clone加上环境变量跳过LFS文件的自动下载可以加速GIT_LFS_SKIP_SMUDGE1 git clone gitgithub.com:user/repo.git这样clone下来的仓库LFS文件全是占位符等真正需要时才执行git lfs pull拉取内容。如果git lfs pull也卡住大概率是网络问题或者LFS服务器连接不稳定检查一下远端的lfs.url配置是否正确。5.4 常见报错与排查速查最后整理一张我这些年遇到的高频报错清单报错信息原因解决方案fatal: not a git repository当前目录不在Git仓库中检查是否在正确的目录下或用git init初始化Please tell me who you are未设置用户名/邮箱执行git config --global user.name/emailssh: Could not resolve hostnameSSH地址错误或网络不通检查远程地址和网络连通性Permission denied (publickey)SSH密钥未配置或不被识别检查公钥是否添加确认密钥文件路径正确fatal: refusing to merge unrelated histories两个独立仓库合并确认无误后加--allow-unrelated-historieserror: failed to push some refs远程有本地缺失的提交先git pull --rebase再pushgit lfs: failed to fetchLFS请求超时或路径错误检查网络和lfs.url配置尝试GIT_LFS_SKIP_SMUDGE1fatal: not a git repository这个报错新手最容易碰到。原因很简单当前shell所在目录不是Git仓库或者仓库的.git目录被误删了。解决思路是先ls -a看看有没有.git目录没有就用git init重新初始化如果只是目录结构在、commit记录丢了那基本救不回来了所以也不太建议随便删.git。还有一种情况是你在一个子目录里而这个子目录不属于任何Git仓库的根目录往上翻目录找到仓库根目录就好了。另外排查问题时可以开启git remote -v看远程地址git config --list看配置ssh -vT gitgithub.com看SSH握手详情这三个命令基本能覆盖九成的问题源头。我在实际排查中总结的经验是Git的报错信息其实已经写得很明确新人容易慌是因为第一反应不是读报错而是急着搜网页搜出来的答案五花八门而自己的具体语境完全不同。先把报错原文复制下来看一遍再结合上面的速查表对照往往比大海捞针高效得多。我自己这几年的体会是Git命令学起来最难的部分不在单个命令而在于建立一套版本管理思维——知道什么时候该commit、什么时候该分支、什么时候该rebase比背下所有参数更有价值。命令查不到就查文档思维模型不对命令再熟也会把仓库管理得一团乱。另外一个小建议花点时间配置一个友好的git config别名比如git lg显示美化过的log把高频命令缩到最短用起来会顺手很多。
返回列表