2.3 初始化本地仓库(git init)


2.3 初始化本地仓库(git init)

本节摘要git init 是 Git 世界的起点——它把一个普通目录变成 Git 仓库,核心动作是在目录里创建隐藏的 .git 子目录。本节讲清 init 做什么、两种初始化场景(现有目录/新目录)、两个关键选项(--bare 裸仓库、--initial-branch 指定初始分支名),以及 init 之后为什么还需要 add 和 commit 才真正"开工"。

上手前先明确

阅读完本节,你应当能够:

  1. 解释 git init 的作用:创建 .git 目录,把普通目录变成仓库。
  2. 在现有项目目录和新目录两种场景下正确执行 git init。
  3. 区分裸仓库与普通仓库,说出裸仓库的典型用途。
  4. 用 --initial-branch 指定初始分支名,理解它与全局配置的关系。

一、问题与直觉

想象你有一堆文件——代码、文档、配置——躺在某个文件夹里,你已经用"文件名带日期"的方式手动管理了两周。现在你想让 Git 接管:记录变化、随时回滚、未来还能推送出去协作。

第一步是什么?既不是复制文件,也不是安装插件,而是跑一条极短的命令:

git init

跑完之后文件夹里多了一个隐藏的 .git 目录,你的目录就"变身"成 Git 仓库了。这个命令简单到容易被忽略,但它的意义非同小可:它划定了"版本控制从哪一层的文件夹开始生效"——.git 所在的位置就是仓库的根,里面所有子目录都被 Git 管辖。

一个常被问的问题:init 之后我怎么感觉什么都没发生?文件还在原地,也没多出什么界面。这种感觉是对的——init 只搭骨架,不碰你的文件。它像给档案库装好了书架,但一本档案都还没放进去。真正的"开始记录",要等接下来的 add 和 commit。

二、核心原理

init 到底创建了什么

git init 的实质是在当前目录(或指定目录)创建一个 .git 子目录。这个隐藏目录是仓库的核心,内含:

组成 作用
objects/ 对象数据库:存文件内容、目录结构、提交信息
refs/ 分支与标签的指针
HEAD 指向当前所在分支
index 暂存区(此刻是空的)
config 仓库本地配置
hooks/ 钩子脚本示例(第 5 章启用)

其中 objects/ 是重中之重——你之后每个提交产生的 Blob、Tree、Commit 对象都存在这里。可以说,init 创建了"仓库的骨架",add/commit 才往里填充血肉。

一个重要事实:.git 默认是隐藏的。Windows 上要在文件管理器里勾选"显示隐藏项目"才能看到,macOS 里按 Command+Shift+. 切换显示。看不到不代表没有——用 ls -a 命令在终端里一定能看到它。

两种初始化场景

场景一:现有项目目录。 你已经写好了代码,想把它们纳入版本控制:

cd 你的项目目录 git init

init 只在当前目录创建 .git,不动你的任何文件。执行后你的项目就成了"工作树",现有文件会显示为未跟踪状态,等你去 add。

场景二:新项目目录。 目录还不存在,用 init 一步创建:

git init my_new_project cd my_new_project

如果目录不存在,Git 会先创建它再初始化。这一条省掉了 mkdir + cd + git init 三步。

用 ls -a 亲手看看 .git

光看文字描述不如自己验证一遍。执行完 init 后,在终端里看一眼仓库内部结构:

ls -a # 能看到 .git 目录 ls -a .git # 查看 .git 内部

你会看到 HEAD、config、description、hooks/、info/、objects/、refs/ 等条目。逐一观察它们的名字——HEAD 是当前分支指针,objects 是对象库,refs 是分支标签指针。再看一眼:

cat .git/HEAD

输出通常是 ref: refs/heads/main(取决于你设的默认分支),意思是"当前 HEAD 指向 refs/heads/main 这个分支"。这条信息的价值在于:你现在亲眼看到了"分支是指针"这个第 3 章核心概念的物理形态——它不过就是 .git/refs 下的一个小文件。

对象库的"空"与"不空"

init 刚完成时,objects/ 目录几乎是空的(只有一些初始化占位)。但这不代表对象库没用——它是仓库的心脏,只是还没跳。接下来每次 add 和 commit,Git 都会往这里写入对象:

  • git add 时,被暂存的文件内容写成 Blob 对象。
  • git commit 时,生成 Tree 对象(目录结构)和 Commit 对象(提交元数据)。
  • 之后每次提交,都在已有对象基础上新增,未变的文件复用旧对象。

理解这个"写入即对象"的机制,你就明白 Git 为什么快、为什么省空间,也明白 2.2 节"add 是快照不是移动"在物理上意味着什么——快照就是往对象库写了一份内容并登记到索引。

init 只是第一步。跑完它,你的目录还"没有历史",必须走完 add 和 commit,仓库才真正开始记录。

--bare 裸仓库

普通仓库有工作区(能看到的文件)和仓库数据(.git)两部分。裸仓库只保留仓库数据,没有工作区——你在裸仓库里看不到任何项目文件,它纯粹是"存储与同步的枢纽"。

创建方式:

git init --bare /path/to/repo.git

裸仓库有什么用?它是多人共享时的中央仓库。当团队里每个人都 clone 自己的副本,需要一个大家共同 push/pull 的地方,裸仓库就是那个地方——它不需要工作区,也正因如此不会出现"工作区状态和推送内容打架"的问题。

习惯上裸仓库目录名以 .git 结尾(如 repo.git),这是约定俗成,不是强制。第 4 章远程协作时你会再见到它——远程仓库本质上就是一个裸仓库。

--initial-branch 指定初始分支名

git init 默认创建的分支名受全局配置控制:如果你在 2.1 节设了 init.defaultBranch main,新仓库初始分支是 main;否则是 master。你也可以在 init 时显式指定:

git init --initial-branch=main

效果是 init 后 HEAD 直接指向 main 分支。对团队而言,统一用 main 作为主分支名是现代实践的主流,理由除了避免 master 的历史语义外,还有各托管平台默认分支的兼容性——GitHub 新仓库默认 main,本地与远程一致能省掉不少改名麻烦。

三、工程实践要点

三个必须知道的事实

第一,init 是本地操作。 它只在你的文件系统里建仓库,跟 GitHub/GitLab 没有任何关系。想让代码出现在远程平台,需要先有远程仓库地址,再用 remote add 关联(第 4 章)。

第二,别在已初始化的仓库里再 init。 在已有 .git 的目录里重复 init 通常无害但无意义,它不会把仓库"重置"成空的,也不会清空历史。想重置仓库,正确做法是删除 .git 目录重新 init(会丢失全部本地历史,慎重)。

第三,.git 目录决定仓库边界。 init 在哪里执行,哪里就是仓库根。把 .git 放在项目根,所有子目录都被管理;如果把 .git 误放到某个子目录,Git 只管理那个子目录及以下,根目录其他文件不在控制范围内。

init 之后该做什么

init 完,你的目录还"有骨架无血肉"。标准衔接动作是:

git init git add . # 把所有文件加入暂存区 git commit -m "初始提交"

这三步跑完,你的仓库才拥有了第一条历史记录。此后每一次修改都按"改 → add → commit"循环推进。很多教程把 init 讲得玄乎,实际它就是"开仓库"三个字——真正的工作从 add 开始。

什么时候会用到裸仓库

场景 用普通仓库 用裸仓库
自己本地开发
当团队共享的中央仓库
搭建本地备份镜像 可选 推荐

日常开发 99% 的时间面对的都是普通仓库。裸仓库的概念先在脑子里挂个号,第 4 章配合 remote 一起消化。

⚠️ 常见坑:在已有 .git 的项目里执行 git init --bare 到同一目录,会报错或产生混乱。裸仓库请单独建目录,别和普通仓库混在一起。

💡 关键直觉:把 init 想成"圈地仪式"——它宣布"从这里开始,这块地盘归版本控制管"。圈地只立界碑,不盖房子;盖房子(add/commit)是后面的事。

常见问题与 FAQ

"git init 后,.git 目录可以改名或移动吗?" 不该改。Git 靠 .git 的相对位置找到仓库数据,移动它等于弄丢仓库。想换位置,应该用克隆或重新 init,而不是移动 .git。

"我不小心在错误目录执行了 init,怎么撤销?" 删除该目录下的 .git 文件夹即可,项目文件不受影响。前提是确认这个 .git 确实是误建的——删掉它,该目录就变回普通目录。

"init 和 clone 有什么区别?" init 是"从零建仓库",clone 是"从已有仓库复制一份"。init 不需要远程,clone 必须有源。第 4 章讲远程协作时 clone 是主角,这里先记住分工。

"为什么 init 后 git status 提示我 'No commits yet'?" 因为仓库还没有任何提交。这是正常提示,不是错误。走完 add + commit,这个提示就消失了。

"init 之后,我改动的文件 Git 会自动跟踪吗?" 不会。init 只建立骨架,新文件一律是未跟踪状态,修改过的已跟踪文件也只是被"看着",需要你手动 add 才会进暂存区。Git 的哲学是"你不喊停,我不动手"——一切记录都由你主动触发。这种"手动"刚开始显得繁琐,但它保证历史里只有你想记录的版本,没有系统自作主张的噪音。

几个不常提但值得知道的 init 细节

关于 .git 的"可丢弃性":.git 目录本身是可删除的——删掉它,仓库变回普通目录,历史全部丢失。这个事实听起来可怕,但换个角度看是自由:你的项目文件与历史记录是分离的,前者任何时候都可以重新纳入新仓库。所以"仓库搞乱了"完全不是灾难,删掉 .git 重新 init 即可。这也是为什么我们反复建议练习时大胆造——代价极低。

关于 init 的"无参数"形式git init 不指定目录时,就在当前目录生效;指定目录时在该目录生效。还有两个相对冷门的选项值得知道名字:git init --shared 用于搭建多人共享仓库时设置权限组,git init --template 可以指定自定义模板(给新仓库注入预设的钩子或配置文件)。日常用不上,但面试或管理多仓库时偶尔会遇到。

关于初始分支名的团队决策:如果你所在团队还在用 master,而托管平台默认 main,那么 clone 下来的仓库会叫 main,你的本地老仓库叫 master,两套名字并存容易出乱子。统一到 main 的建议很简单:新仓库用 init.defaultBranch 全局配置保证一致,老仓库用改名命令调整(第 3 章会讲到分支改名)。早统一早省心。

给练习的一个小建议

很多教程让你 init 之后立刻去 add 全部文件,但真实项目里"全部文件"往往包含不该进仓库的东西——编译产物、日志、本地配置。建议从第一个项目起就养成"先建忽略规则,再 add"的习惯:在项目根放一个 .gitignore 文件,把临时文件、依赖目录、密钥写进去。init 之后顺手建 .gitignore,比以后费劲从历史里清理干净得多。这个文件本身值得提交,它是项目的一部分。

记住一个心智锚点:init 圈地,add 搬货,commit 存档——三步是仓库从出生到首次工作的完整序曲,任何一个环节都别跳过。

温故知新

  • init 的本质:创建 .git 目录,把普通目录变成 Git 仓库,不碰现有文件。
  • .git 的组成:objects、refs、HEAD、index、config、hooks 六大部分。
  • 两种场景:现有目录直接 init,新目录用 git init 目录名 一步创建。
  • 裸仓库:无工作区、只有仓库数据,用于团队共享的中央仓库。
  • 初始分支名:init 时可用 --initial-branch 指定,受全局 init.defaultBranch 影响。
  • init 后的动作:add + commit 走完,仓库才算真正"开工"。
  • 心智锚点:init 圈地、add 搬货、commit 存档,三步是仓库的完整序曲。

下一节进入暂存:git add 把工作区的修改登记到暂存区——三区联动从这里正式开始。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U