写点什么

Git 历险记(三)——创建一个自己的本地仓库

  • 2011-02-22
  • 本文字数:4233 字

    阅读完需:约 14 分钟

如果我们要把一个项目加入到 Git 的版本管理中,可以在项目所在的目录用 git init 命令建立一个空的本地仓库,然后再用 git add 命令把它们都加入到 Git 本地仓库的暂存区(stage or index)中,最后再用 git commit 命令提交到本地仓库里。

创建一个新的项目目录,并生成一些简单的文件内容:

复制代码
<b>$ mkdir test_proj</b>
<b>$ cd test_proj</b>
<b>$ echo “hello,world” > readme.txt</b>

在项目目录创建新的本地仓库,并把项目里的所有文件全部添加、提交到本地仓库中去:

复制代码
$ git init #在当前的目录下创建一个新的空的本地仓库
Initialized empty Git repository in /home/user/test_proj/.git/
$ git add . #把前目录下的所有文件全部添加到暂存区
$ git commit -m 'project init' #创建提交
[master (root-commit) b36a785] project init
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 readme.txt

Git 目录的结构

git init 命令在项目的顶层目录中建了一个名为:“.git”的目录,它的别名是 “Git 目录”(Git directory)。这时”Git 目录”中虽然有一些文件,但是没有任何提交(commit)在里面,所以我们叫它是空仓库(empty Git repository)。

和 SVN 不同,一个 Git 项目一般只在项目的根目录下建一个“.git”目录,而 SVN 则会在项目的每一个目录下建一个”.svn”目录;这也我喜欢 Git 的原因之一:)

Git 把所有的历史提交信息全部存储在“Git 目录”里,它就是一个 Git 项目的仓库;你对本地的源代码进行编辑修改后创建的提交也都会先保存在这里面,然后再推送到远端的服务器。当我们我把项目目录和“Git 目录”一起拷到其它电脑里,它能马上正常的工作(所有的提交信息全都保存在 Git 目录里);甚至可以只把“Git 目录”拷走也行,但是要再签出(checkout)一次。

Git 为了 调试的方便,它可以指定项目的 Git 目录的位置。有两种办法:一是设置“GIT_DIR”环境变量,二是在命令行里设定“–git-dir–git-dir”参数指定它的位置,大家可以看一下这里 ( git(1) Manual Page )。

庖丁解牛

前面的这些东东我在第一篇里也大概的讲过一些,但是今天我们想不但要开动这辆叫“Git”的跑车,还想看看它里面有些什么样的零件,是怎么构成的。

OK,我们来看看“test_proj”项目里的“Git 目录”的结构:

复制代码
$cd test_proj/.git
$ ls | more
branches/ # 新版的 Git 已经不再使用这个目录,所以大家看到它 #一般会是空的
COMMIT_EDITMSG # 保存着上一次提交时的注释信息
config # 项目的配置信息
description # 项目的描述信息
HEAD # 项目当前在哪个分支的信息
hooks/ # 默认的“hooks” 脚本文件
index # 索引文件,git add 后把要添加的项暂存到这里
info/ # 里面有一个 exclude 文件,指定本项目要忽略的文件 #,看一下这里
logs/ # 各个 refs 的历史信息
objects/ # 这个目录非常重要,里面存储都是 Git 的数据对象
# 包括:提交 (commits), 树对象 (trees),二进制对象 #(blobs), 标签对象(tags)。
#不明白没有关系,后面会讲的。
refs/ # 标识着你的每个分支指向哪个提交(commit)。

我先用 git log 命令来看一下这个 Git 项目里有哪些提交:

复制代码
$ git log
commit 58b53cfe12a9625865159b6fcf2738b2f6774844
Author: liuhui998 <liuhui998@nospam.com>
Date: Sat Feb 19 18:10:08 2011 +0800
project init

大家可以看到目前只有一个提交(commit)对象,而它的名字就是:”58b53cfe12a9625865159b6fcf2738b2f6774844”。这个名字就是对象内容的一个 SHA 签名串值,只要对象里面的内容不同,那么我们就可以认为对象的名字不会相同,反之也成立。我在使用时一般不用把这个 40 个字符输全,只要把前面的 5~8 个字符输完就可以(前提是和其它的对象名不冲突)。为了方便表示,在不影响表达的情况下,我会只写 SHA 串值的前 6 个字符。

我们可以用 git cat-file 来看一下这个提交里的内容是什么:

复制代码
$ git cat-file -p 58b53c
<b>tree 2bb9f0c9dc5caa1fb10f9e0ccbb3a7003c8a0e13</b>
author liuhui998 <liuhui998@nospam.com> 1298110208 +0800
committer liuhui998 <liuhui998@nospam.com> 1298110208 +0800
project init

大家可以看到:提交“58b53c” 是引用一个名为“2bb9f0”的树对象(tree)。一个树对象(tree)可以引用一个或多个二进制对象(blob), 每个二进制对象都对应一个文件。 更进一步, 树对象也可以引用其他的树对象,从而构成一个目录层次结构。我们再看一下这个树对象(tree)里面有什么东东:

复制代码
$ git cat-file -p 2bb9f0
100644 <b>blob 2d832d9044c698081e59c322d5a2a459da546469 readme.txt</b>

不难看出,2bb9f0”这个树对象(tree)包括了了一个二进制对象(blob),对应于我们在前面创建的那个叫 ”readme.txt”的文件。现在我们来看看这个”blob”里的数据是不是和前面的提交的内容一致:

复制代码
$ git cat-file -p 2d832d
hello,world

哈哈,熟悉的“hello,world”又回来了。

想不想看看提交对象、树对象和二进制对象是怎么在”Git 目录“中存储的;没有问题,执行下面的命令,看看”.git/objects”目录里的内容:

复制代码
$ find .git/objects
.git/objects
.git/objects/2b
.git/objects/<b>2b/b9f0c9dc5caa1fb10f9e0ccbb3a7003c8a0e13</b>
.git/objects/2d
.git/objects<b>/2d/832d9044c698081e59c322d5a2a459da546469</b>
.git/objects/58
.git/objects/<b>58/b53cfe12a9625865159b6fcf2738b2f6774844</b>
.git/objects/info
.git/objects/pack

如果大家仔细看上面命令执行结果中的粗体字,所有的对象都使用 SHA 签名串值作为索引存储在”.git/objects”目录之下;SHA 串的前两个字符作为目录名,后面的 38 个字符作为文件名。

这些文件的内容其实是压缩的数据外加一个标注类型和长度的头。类型可以是提交对象(commit)、二进制对象(blob)、 树对象(tree)或者标签对象(tag)。

如何 clone 一个远程项目

我身边的很多朋友是因为要得到某个开源项目的代码,所以才开始学习使用 Git。而获取一个项目的代码的一般的做法就是用 git clone 命令进行直接复制。

例如,有些朋友可能想看一下最新的 linux 内核源代码,当我们打开它的网站时,发现有如下面的一段提示:

复制代码
URL
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
http://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

URL 下面的三行字符串表示三个地址,我们可以通过这三个地址得到同样的一份 Linux 内核源代码。

也就是说下面这三条命令最终得到的是同一份源代码:

复制代码
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
git clone http://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
git cone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

我们先来看一下 URL,git://、http://、 https:// 这些代表是传输 git 仓库的协议形式,而“ git.kernel.org “则代表了 Git 仓库存储的服务器名字(域名),“ /pub/scm/linux/kernel/git/torvalds/linux-2.6.git” 则代表了 Git 仓库在服务器上位置。

Git 仓库除了可以通过上面的 git、http、https 协议传输外还可以通过 ssh、ftp(s)、rsync 等协议来传输。 git clone 的本质就是把“Git 目录”里面的内容拷贝过来,大家想想看,一般的“Git 目录”里有成千上万的各种对象(提交对象,树对象,二进制对象…),如果逐一复制的话,其效率就可想而知。

如果通过 git、ssh 协议传输,服务器端会在传输前把需要传输的各种对象先打好包再进行传输;而 http(s)协议则会反复请求要传输的不同对象。如果仓库里面的提交不多的话,前者和后者的效率相差不多;但是若仓库里有很多提交的话,git、ssh 协议进行传输则会更有效率。

不过现在 Git 对 http(s)协议传输 Git 仓库做了一定的优化,http(s)传输现在也能达到 ssh 协议的效率,有兴趣的朋友可以看一下这里( Smart HTTP Transport )。

好的,现在我们执行了下面这条命令,把 linux-2.6 的最新版源代码 clone 下来:

复制代码
<b>$cd ~/</b>
<b>$mkdir temp</b>
<b>$git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git</b>
Initialized empty Git repository in /home/liuhui/temp/linux-2.6/.git/
remote: Counting objects: 1889189, done.
remote: Compressing objects: 100% (303141/303141), done.
Receiving objects: 100% (1889189/1889189), 385.03 MiB | 1.64 MiB/s, done.
remote: Total 1889189 (delta 1570491), reused 1887756 (delta 1569178)
Resolving deltas: 100% (1570491/1570491), done.
Checking out files: 100% (35867/35867), done.

当我们执行了“git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git”这条命令后大家可以看到这条输出:

复制代码
Initialized empty Git repository in /home/user/temp/linux-2.6/.git/

这就是意味着我们在本地先建了一个“linux-2.6”目录,然后在这个目录建了一个空的 Git 本地仓库(Git 目录);里面将会存储从网上拉下来的历史提交。

下面两条输入代表服务器现在调用 git-pack-objects 对它的仓库进行打包和压缩:

复制代码
remote: Counting objects: 1888686, done.
remote: Compressing objects: 100% (302932/302932), done.

然后客户端接收服务器端发过送过来的数据:

复制代码
Receiving objects: 100% (1889189/1889189), 385.03 MiB | 1.64 MiB/s, done.

在我们执行完上面的 clone linux-2.6 代码的的操作后,Git 会从“Git 目录”里把最新的代码到签出(checkout)到“linux-2.6”这个目录里面。我们一般把本地的“linux-2.6”这个目录叫做”工作目录“(work directory),它里面保存着你从其它地方 clone(or checkout)过来的代码。当你在项目的不同分支间切换时,“工作目录”中的文件可能会被替换或者删除;“工作目录”只是保存着当前的工作,你可以修改里面文件的内容直到下次提交为止。

大家还记得前面的“庖丁解牛”吗,是不是觉得只杀一头叫“hello,world”的小牛太不过瘾了。没有问题,拿起前面的那把小刀,来剖析一下现在躺在你硬盘里这头叫“linux-2.6”大牛看看,我想一定很好玩。


在写篇文章的过程中,我要感谢在那些关心我并提出真诚意见的朋友,如果没有你们真诚的意见,我也许没有这么强烈的紧迫感,也不会深深的感到自己的不足。我是第一次写专栏,张凯锋同学给了我很大的帮助。最后还是要感谢我的家人,是他们让我有时间来进行写作:)

2011-02-22 21:3247941

评论

发布
暂无评论
发现更多内容

史海峰:成为技术领导者 从技术到管理的必经之路丨声网开发者创业讲堂 • 第 5 期

声网

技术管理 人工智能’

圆满落幕!回顾 eBPF 技术的发展与挑战

OpenAnolis小助手

Linux 云原生 ebpf 云栖大会 龙蜥社区

一次基于Fastjson的JNDI注入

网络安全学海

网络安全 安全 信息安全 渗透测试 漏洞挖掘

从0开始,用Go语言搭建一个简单的后端业务系统

闫同学

后端 go语言 11月月更

极速体验docker容器健康

程序员欣宸

容器化 docekr 11月月更

这样回答前端面试题才能拿到offer

loveX001

JavaScript

React组件之间的通信方式总结(上)

beifeng1996

React

StarRocks 技术内幕 | Join 查询优化

StarRocks

数据库

跟着卷卷龙一起学Camera--MIPI 03

卷卷龙

ISP camera 11月月更

企业级业务架构设计:方法论与实践 学习笔记

程序员架构进阶

业务架构 TOGAF 11月日更 Zachman

前端必会面试题总结

loveX001

JavaScript

百度前端react面试题总结

beifeng1996

React

决策树-泰坦尼克号幸存者预测

烧灯续昼2002

Python 机器学习 算法 sklearn 11月月更

📢利用Vite插件助力证书安装

小鑫同学

前端 插件 11月月更

Linux中 dir 命令还能这样玩!

wljslmz

Linux 11月月更

跟着卷卷龙一起学Camera--信号采样01

卷卷龙

ISP camera 11月月更

跟着卷卷龙一起学Camera--信号采样02

卷卷龙

ISP camera 11月月更

React组件之间的通信方式总结(下)

beifeng1996

React

React源码分析4-深度理解diff算法

goClient1992

React

从1开始,扩展Go语言后端业务系统的RPC功能

闫同学

后端 go语言 11月月更

从2开始,在Go语言后端业务系统中引入缓存

闫同学

Go 设计 后端 11月月更

互联网安全体制的挑战与机遇

阿泽🧸

互联网安全 11月月更

React源码分析3-render阶段(穿插scheduler和reconciler)

goClient1992

React

Ansible 部署的时候提示错误 SSH password instead

HoneyMoose

React源码分析2-深入理解fiber

goClient1992

React

设计模式之美-面向对象、设计原则、设计模式、编程规范、重构的关系

GalaxyCreater

设计模式

MySQL能力全开放,OceanBase 社区版 4.0 正式上线

OceanBase 数据库

js异步编程面试题你能答上来几道

loveX001

JavaScript

年前端react面试打怪升级之路

beifeng1996

React

如何在知乎平台上做营销推广:推荐几种引流方式

石头IT视角

promise执行顺序面试题令我头秃,你能作对几道

loveX001

JavaScript

Git 历险记(三)——创建一个自己的本地仓库_Java_刘辉_InfoQ精选文章