本节摘要:
git init是 Git 世界的起点——它把一个普通目录变成 Git 仓库,核心动作是在目录里创建隐藏的 .git 子目录。本节讲清 init 做什么、两种初始化场景(现有目录/新目录)、两个关键选项(--bare 裸仓库、--initial-branch 指定初始分支名),以及 init 之后为什么还需要 add 和 commit 才真正"开工"。
阅读完本节,你应当能够:
想象你有一堆文件——代码、文档、配置——躺在某个文件夹里,你已经用"文件名带日期"的方式手动管理了两周。现在你想让 Git 接管:记录变化、随时回滚、未来还能推送出去协作。
第一步是什么?既不是复制文件,也不是安装插件,而是跑一条极短的命令:
git init
跑完之后文件夹里多了一个隐藏的 .git 目录,你的目录就"变身"成 Git 仓库了。这个命令简单到容易被忽略,但它的意义非同小可:它划定了"版本控制从哪一层的文件夹开始生效"——.git 所在的位置就是仓库的根,里面所有子目录都被 Git 管辖。
一个常被问的问题:init 之后我怎么感觉什么都没发生?文件还在原地,也没多出什么界面。这种感觉是对的——init 只搭骨架,不碰你的文件。它像给档案库装好了书架,但一本档案都还没放进去。真正的"开始记录",要等接下来的 add 和 commit。
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 三步。
光看文字描述不如自己验证一遍。执行完 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,仓库才真正开始记录。
普通仓库有工作区(能看到的文件)和仓库数据(.git)两部分。裸仓库只保留仓库数据,没有工作区——你在裸仓库里看不到任何项目文件,它纯粹是"存储与同步的枢纽"。
创建方式:
git init --bare /path/to/repo.git
裸仓库有什么用?它是多人共享时的中央仓库。当团队里每个人都 clone 自己的副本,需要一个大家共同 push/pull 的地方,裸仓库就是那个地方——它不需要工作区,也正因如此不会出现"工作区状态和推送内容打架"的问题。
习惯上裸仓库目录名以 .git 结尾(如 repo.git),这是约定俗成,不是强制。第 4 章远程协作时你会再见到它——远程仓库本质上就是一个裸仓库。
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 完,你的目录还"有骨架无血肉"。标准衔接动作是:
git init git add . # 把所有文件加入暂存区 git commit -m "初始提交"
这三步跑完,你的仓库才拥有了第一条历史记录。此后每一次修改都按"改 → add → commit"循环推进。很多教程把 init 讲得玄乎,实际它就是"开仓库"三个字——真正的工作从 add 开始。
| 场景 | 用普通仓库 | 用裸仓库 |
|---|---|---|
| 自己本地开发 | 是 | 否 |
| 当团队共享的中央仓库 | 否 | 是 |
| 搭建本地备份镜像 | 可选 | 推荐 |
日常开发 99% 的时间面对的都是普通仓库。裸仓库的概念先在脑子里挂个号,第 4 章配合 remote 一起消化。
⚠️ 常见坑:在已有 .git 的项目里执行
git init --bare到同一目录,会报错或产生混乱。裸仓库请单独建目录,别和普通仓库混在一起。
💡 关键直觉:把 init 想成"圈地仪式"——它宣布"从这里开始,这块地盘归版本控制管"。圈地只立界碑,不盖房子;盖房子(add/commit)是后面的事。
"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 的哲学是"你不喊停,我不动手"——一切记录都由你主动触发。这种"手动"刚开始显得繁琐,但它保证历史里只有你想记录的版本,没有系统自作主张的噪音。
关于 .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 存档——三步是仓库从出生到首次工作的完整序曲,任何一个环节都别跳过。
git init 目录名 一步创建。下一节进入暂存:
git add把工作区的修改登记到暂存区——三区联动从这里正式开始。