Skip to content
This page has been auto-translated and may contain errors.View in English

你的第一个仓库 ​

docs.scrimba.com

一般来说,走到这一步通常有两种情况。一种是你已经有一个项目文件夹了,比如一直在本地写的 weather-app,现在想让 Git 开始记录它的历史。另一种是别人已经把项目建好了,放在 GitHub 上,你想在自己的电脑上弄一份能干活的副本。这两种情况都是从三个简单的命令开始的,读完这一章你就会把这三个命令都实际用一遍。不过在那之前,先花十秒钟做个检查:确认你的电脑上装了 Git。

你安装了 Git 吗? ​

这本手册里的所有内容都是在终端里进行的,所以第一步是先把终端打开。**终端**是一个你输入文字命令、程序用文字回复你的应用。你的电脑上其实已经装好了:

  • macOS:这个应用叫做终端(Terminal)。按 Cmd+Space,输入"terminal",回车。
  • Windows:先打开PowerShell(在开始菜单里搜索"powershell")。下面安装 Git 的时候还会顺带装上Git Bash,一个专门为此打造的终端,两者运行的都是同样的 Git 命令。
  • **Linux:**找一个叫 Terminal 或 Console 的应用;很多发行版里按 Ctrl+Alt+T 就能打开。

Git 本身是一个命令行程序:它没有自己的窗口,你要输入以 git 开头的命令,然后看它打印出来的结果。这本手册里所有代码块也都是这么读的:以 $ 开头的那一行是你要输入的命令(不包括 $ 本身),下面几行是 Git 的回复。

好了,来做个检查。在终端里输入下面这行,然后回车:

bash
$ git --version
git version 2.45.1

只要显示出版本号,就说明 Git 已经装好了,可以开始用了。你看到的版本号很可能跟这里不一样,没关系。如果你看到的是"command not found"或者"git is not recognized",说明你的电脑上还没装 Git,装一次就够了:

  • macOS:运行 git --version 通常会弹出一个对话框,提示安装苹果的命令行工具(Command Line Tools)。同意就行,macOS 会帮你把 Git 装上。
  • Windows:从 git-scm.com/downloads 下载Git for Windows并运行安装程序。默认选项基本都没问题,安装过程中会顺带装上 Git Bash。
  • **Linux:**用你所在发行版的包管理器安装 git 包,比如在 Ubuntu 和 Debian 上运行 sudo apt install git。

安装完成后,关掉终端,重新打开一个,再运行一次 git --version。看到版本号就说明这一步的安装工作永久完成了,这本手册后面的内容不需要再装任何东西。

这个版本号比看上去更重要一点。这本手册用的是 git switch 和 git restore 这两个比较新的命令,它们是 2019 年 Git 2.23 版本才加进来的,所以如果你的电脑上预装的 Git 很老,运行本来正确的示例时可能会遇到"not a git command"的报错。这几年的 Git 版本都带这两个命令;如果你的版本比 2.23 还老,去上面那几个地方更新一下就行。

你后面还会遇到一些图形化的 Git 工具:GitHub Desktop、VS Code 里的 Git 面板、大多数编辑器自带的支持。这些工具底层跑的都是同一个 Git 程序,只是加了一层界面。先学会命令,这些工具里的每一个按钮对你来说都会一目了然,因为它们对应的都是你已经理解的命令。

Git 是一个独立自足的程序,不是一个服务。它不会在后台运行,不会监视你的文件夹,也没有什么账号需要登录:每个命令启动后,在仓库里读写文件,打印结果,然后退出。这也是为什么它所有的功能都能离线使用。运行 which git(Windows 上是 where git)可以看到这个程序具体在磁盘上的哪个位置。

有个平台上的小差异值得了解:在 macOS 上,苹果通过命令行工具附带的是自己打包的 Git,通常比 git-scm.com 上的最新版本落后一两个版本号。对这本手册里的所有内容,以及日常工作中几乎所有场景来说,这点差异都不重要。

Juno你安装了 Git 吗? Git 是一个你通过在终端里输入命令来与之交流的程序,git --version 能告诉你它是否已经在你的电脑上了:显示版本号说明已经装好了,显示"command not found"就说明得去 git-scm.com 或用你系统自带的安装程序装一下。这一步你只需要做一次。安装配置是任何一本手册里最没意思的部分,能顺利过完这一关,真不错!
Juno你安装了 Git 吗?git --version 一行就能确定装没装。只要版本不算太老都能用,不过这本手册用到的 switch 和 restore 命令需要 Git 2.23 及以上,落后于这个版本的话就更新一下。像 GitHub Desktop 这样的图形化工具底层跑的还是同一个程序,所以你在这里学到的东西完全可以直接套用到那些工具上。
Juno你安装了 Git 吗? Git 是一个本地程序,没有后台进程,也没有账号:你一输入命令它就运行,读写仓库里的文件,然后退出,这也是为什么它的每个功能都能离线使用。which git 能告诉你二进制文件的位置。苹果自带的版本比最新发布版落后一两个版本号,这些年下来这点差距对我没造成过任何麻烦。

把一个文件夹变成仓库 ​

打开终端,用 cd("change directory" 的缩写)进入你的项目文件夹,然后运行一个命令,git init:

bash
$ cd weather-app
$ git init
Initialized empty Git repository in /Users/mara/projects/weather-app/.git/

这个文件夹现在就是一个**仓库**了:一个 Git 会持续关注的项目,随时准备在你工作的过程中记录下它的快照。git init 每次做的事情都一样。它会去找一个隐藏的 .git 文件夹,如果没有就创建一个,从这一刻起,这个文件夹就在 Git 的关注之下了。

新建仓库的默认分支名叫 main。如果你碰到的是一个年代久一点的教程、旧仓库,或者几年前录的课程,你会看到同样的东西用的是 master 这个名字:其实是同一个初始分支,只是叫法比较老。这本手册统一用 main。

什么是分支?

分支是你的提交落脚的一条独立历史线,main 就是每个新仓库自带的那一条。分支这一章会讲怎么创建分支、怎么在分支之间切换。

git init 不管这个文件夹是空的还是满的。在 weather-app 里,即使已经有 index.html 和 src/ 文件夹了,运行它也没问题,Git 会从项目现有的状态开始追踪,不会改动任何已有的文件。在同一个仓库里再跑一次 git init 也没什么坏处。Git 会发现 .git 文件夹已经存在,只是原地重新初始化一下,不会碰你已有的任何历史,所以哪怕是习惯性地又跑了一遍,也不会造成什么损失。

git init 只做一件事:创建 .git 文件夹,这一章后面会详细拆解这个文件夹。仓库的一切——历史、设置、引用——从 init 创建它的那一刻起,就都存在这个文件夹里。你在项目里看到并编辑的那些文件,是被追踪内容的一份工作副本,而 .git 文件夹才是仓库本身。

Juno把一个文件夹变成仓库git init 能把任何文件夹变成一个 Git 仓库,让 Git 开始追踪它。在项目文件夹里跑一次就行,之后再跑一次也没关系。新建仓库默认的分支名叫 main,不过你在外面碰到的老仓库很多时候管同一个东西叫 master。
Juno把一个文件夹变成仓库git init 无论是空文件夹还是已经装满文件的文件夹都能用,在已经设置好的仓库里再跑一次也不会有什么损害,因为 Git 会认出 .git 文件夹已经存在了。默认分支名是 main;在老一点的项目里常见的是 master。
Juno把一个文件夹变成仓库git init 只会创建 .git 文件夹,那才是真正的仓库。你的工作文件只是那个文件夹所追踪内容的一份检出(checkout)。记住工作文件和追踪它们的 .git 文件夹之间的这层区分。

告诉 Git 你是谁 ​

在你第一次提交之前,Git 想知道是谁在做这次提交。设置两个值一次就够,Git 会记住它们,用在你在这台机器上做的每一次提交上:

bash
$ git config --global user.name "陈玛拉"
$ git config --global user.email "[email protected]"

你创建的每一次提交都会带上这个名字和邮箱。跳过这一步,Git 会用从你电脑的用户名和主机名猜出来的东西顶替,这样你的提交就带着一个你从未选过的身份。

在第一次提交之前设置好你的姓名和邮箱。如果先提交、后设置,这个错误就会被固化进那次提交的历史里。更麻烦的是,有些托管平台(包括 GitHub)会通过邮箱把提交和你的账号对上号,所以用错误地址或者被猜出来的地址做的提交,看起来就像是某个匿名陌生人的工作成果,而不是你的。

--global 这个标志设置的是一个**配置作用域**:--global 对你机器上所有的仓库都生效,而 --local 只对你运行命令时所在的那个仓库生效。这样你就可以分层设置。默认情况下,你的全局邮箱适用于所有仓库,而在某个特定仓库里用 --local 覆盖一下,就只在那个仓库里优先生效,别的地方不受影响,这在工作项目需要用跟个人项目不同的邮箱地址时很有用:

bash
$ cd weather-app
$ git config --local user.email "[email protected]"
$ git config user.email
[email protected]

不带作用域标志运行 git config user.email,读到的是当前实际生效的那个值:如果设置了本地值就是本地值,没设置就是全局值。

这两个作用域都是 Git 按固定顺序读取的文本文件。全局配置放在你主目录下的 ~/.gitconfig 里;本地配置放在仓库自己的 .git 文件夹里,路径是 .git/config。Git 先读本地设置,再让它覆盖全局设置,这就是整个分层机制的全部内容,都写在普通文本文件里,你自己也能打开看。

Juno告诉 Git 你是谁git config --global user.name 和 git config --global user.email 设置的是 Git 会盖在你每次提交上的姓名和邮箱。在第一次提交前就把它们设好,因为在设置邮箱之前做的提交会被归到一个被猜出来的身份上,像 GitHub 这样的托管平台甚至可能完全对不上你的账号。设一次,你机器上的每个仓库都会记住它。
Juno告诉 Git 你是谁--global 对你机器上所有的仓库生效,--local 只覆盖当前这个仓库,适合把个人邮箱和工作邮箱分开用。两者都设置的话本地优先,git config user.email 能告诉你某个仓库里实际生效的是哪个值。
Juno告诉 Git 你是谁 配置作用域是分层的文本文件:全局配置在 ~/.gitconfig,本地配置在仓库内的 .git/config,两者都设置时本地优先。不设置邮箱的话,Git 会退回到一个从你的用户名和机器名猜出来的身份,这也是为什么某次带着奇怪名字的提交,几乎总能追溯到没设置 user.email 这个原因上。

用 git clone 拷贝一个已有的项目 ​

有时候项目已经存在于别的地方了,你只是想在自己电脑上弄一份能干活的副本。git clone 一个命令就能搞定:

bash
$ git clone https://github.com/octocat/Hello-World.git
Cloning into 'Hello-World'...
remote: Enumerating objects: 13, done.
remote: Total 13 (delta 0), reused 0 (delta 0), pack-reused 13
Receiving objects: 100% (13/13), done.

Git 会创建一个名为 Hello-World 的新文件夹,把项目的全部历史下载进去,并把文件放进这个文件夹里,你马上就能开始读或者编辑了。不需要另外再跑一次 git init。克隆的过程里已经把仓库设置好了,是下载的一部分。

当项目已经存在于某处,而你想要一份跟它保持连接的副本时,用 git clone。当你要从零开始一个还不存在于任何地方的新项目时,用 git init。克隆还会自动连回你克隆的来源,起名叫 origin,这是单独用 init 永远做不到的一步;后面的章节会用这个连接来发送和接收提交。

克隆会把项目的整个历史——它有过的每一次提交——全部下载下来,并把这些内容填满你机器上完整的 .git 文件夹,而 git init 会让这个文件夹保持空的。正因为如此,一个历史悠久的项目克隆起来可能要花点时间,即便克隆完之后你看到的只是最新文件的一份快照。

Juno用 git clone 拷贝一个已有的项目git clone <url> 会把别人的仓库,连同整个历史,下载到一个以项目名命名的新文件夹里。当项目已经存在于某处时用它;当你要从零开始一个项目时用 git init。克隆一结束,你手上就是一份完整的工作副本,随时可以查看或添加内容。
Juno用 git clone 拷贝一个已有的项目 当项目已经存在于某处,你想要一份跟它保持连接的副本时用克隆;当什么都还不存在时用初始化。克隆会自动连回你克隆的来源,起名叫 origin,单独用初始化永远不会自动设置这个连接。
Juno用 git clone 拷贝一个已有的项目 克隆会重建整个 .git 文件夹,包含项目有过的每一次提交。这包括完整的历史,这也是为什么一个历史悠久的项目克隆起来可能要花点时间,即便你之后只能看到它当前的文件。

隐藏的 .git 文件夹里都有什么 ​

无论你是用 git init 还是 git clone 建立的,每个仓库根目录下都有一个隐藏的 .git 文件夹。它默认是隐藏的,用 ls -a(ls 列出文件夹的内容,-a 会把隐藏项也列出来)让终端显示完整列表,就能看到它和你的普通项目文件排在一起:

bash
$ ls -a
.  ..  .git  README.md  src

那个文件夹才是真正的仓库。它保存着你做过的每一次提交、你的分支,以及上一节讲到的配置设置。删掉 .git 文件夹,项目就丢掉了它的全部历史,重新变回一个 Git 不再追踪的普通文件夹。项目里其他的一切,都只是 .git 里保存内容的一份工作副本。

打开看看,你会发现几个能认出来的东西:一个 config 文件(就是上一节讲的本地设置)、一个 HEAD 文件,以及名为 objects 和 refs 的文件夹。你不需要手动打开或编辑这些东西,但认识这些名字,等它们出现在报错信息或搜索结果里时会有帮助。

这里有几个名字值得认真了解一下。objects 文件夹是仓库的**对象数据库**:你做过的每个文件的每个版本、每一次提交,都按内容本身而不是文件名压缩存储在这里。refs 文件夹保存着你的分支,每个分支就是一个指向某次提交 id 的小文件。HEAD 是一个记录你当前检出的是哪个分支的单一文件。config 就是前面讲过的本地设置文件。

这些东西日常都不需要你手动编辑。看到你的整个历史就以普通文件的形式摆在那里,而不是藏在某个服务器上,这就能解释为什么克隆能让你离线拥有完整历史,也能解释为什么删掉 .git 不是什么"清理",而是抹去了这个项目曾有过的每一次提交。

Juno隐藏的 .git 文件夹里都有什么 每个仓库的根目录下都有一个隐藏的 .git 文件夹,它才是真正的仓库:你的整个历史和相关设置都存在那里。在项目里运行 ls -a 就能看到它,因为它默认是隐藏的。除非你真的想抹掉项目的全部历史,否则千万别删它,因为删掉它会让这个文件夹变回一个 Git 不再追踪的普通文件夹。
Juno隐藏的 .git 文件夹里都有什么.git 里面你会认出一个 config 文件(你的本地设置)、一个 HEAD 文件,以及 objects 和 refs 文件夹。日常你不会手动动这些东西,但这些名字会出现在报错信息和 Git 文档里。克隆会把这整个文件夹一起复制过来,这也是为什么克隆下来的是完整历史,而不只是最新的文件。
Juno隐藏的 .git 文件夹里都有什么objects 是仓库的对象数据库,保存着每个文件的每个版本和每一次提交,按内容而不是文件名来定位。refs 把你的分支保存为指向某次提交 id 的普通文件,HEAD 记录着你当前检出的是哪一个。这里的东西都不需要手动编辑,但看到它们以普通文件的形式存在磁盘上,就能明白为什么克隆能给你完整的离线历史,也能明白为什么删掉 .git 是抹去一切,而不只是清理一下。

如果仓库和提交这些概念对你来说还有点模糊,什么是 Git 讲的是这些命令背后建立在的那套心智模型,术语表 对这一章出现的每个词都有简短的解释。等你的身份信息设置好、也有了一个仓库可以练手之后,提交循环 会讲编辑、暂存、提交这套你以后会一直用到的日常流程。