写点什么

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:3247781

评论

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

(28DW-S8-Day3) 比特币、 区块链是什么?

mtfelix

比特币 区块链 28天写作

前端面试常考题:JS垃圾回收机制

华为云开发者联盟

JavaScript Vue 大前端 js 垃圾回收

Fast AI人工智能审图平台-建筑图纸设计效率的倍增器

AI AI审图

go get下载包失败问题

happlyfox

28天写作 2月春节不断更 Go 语言

关于GaussDB(DWS)的正则表达式知多少?人人都能看得懂的详解来了!

华为云开发者联盟

正则表达式 GaussDB

解决dyld: Library not loaded icu4c

一个大红包

brew icu4c

教你如何优雅的改写“if-else”

华为云开发者联盟

代码

「产品经理训练营」第五章作业

Sòrγy_じò ぴé

产品经理训练营

上天的源码要不要——GitHub 热点速览 v.21.08

HelloGitHub

Python GitHub 开源 Go 语言

如何解决深度推荐系统中的Embedding冷启动问题?

王喆

机器学习 深度学习 推荐系统 计算广告 Embedding

【进阶】面试官问我Chrome浏览器的渲染原理(6000字长文)

我是哪吒

学习 程序员 chorme 28天写作 2月春节不断更

透彻解析!如何快速的开发一个完整的直播app,薪资翻倍

欢喜学安卓

android 程序员 面试 移动开发

西北大学研发猴脸识别技术,金丝猴可刷脸打卡;IJCAI 2020丨基于学习实例隐空间的文本风格转换

京东科技开发者

人脸识别 IT 量子通讯

《第一财经(月刊2021年02期)》

石云升

读书笔记 28天写作 2月春节不断更 第一财经

Kafka.05 - 生产者与消费者介绍

insight

kafak 2月春节不断更

基于matlab的控制系统与仿真2-传递函数模型

AXYZdong

matlab 2月春节不断更

透彻解析!在字节跳动我是如何当面试官的,讲的明明白白!

欢喜学安卓

android 程序员 面试 移动开发

LiteOS:盘点那些重要的数据结构

华为云开发者联盟

源码 数据结构 LiteOS LOS_DL_LIST Priority Queue

话题讨论 | 技术人员的职业发展困惑,你也有么?

架构精进之路

职业规划 话题讨论 28天写作 技术人员

读书笔记-MySQL

yunCrush

MySQL

产业实践推动科技创新,京东科技集团3篇论文入选ICASSP 2021

京东科技开发者

人工智能 机器学习 信号 语音识别

什么容易被记住——造梦师指南

Justin

心理学 28天写作 游戏设计

工业互联网助力数字中国建设(新论)

工业互联网

诊所数字化:最大的数据资产-患者数字档案内容

boshi

电子病历 数字化医疗 七日更 28天写作

Selenium 常用方法与属性、鼠标悬停与 Select 操作

梦想橡皮擦

Python 28天写作 2月春节不断更

线程池面试必考

叫练

面试 线程池 线程池工作原理

Java lambda表达式人类使用指南

ES_her0

28天写作

产品经理训练营第四周总结

产品经理训练营

“云原生”的应用价值及关键属性解读

这些面试题你会吗?连续四年百度Android岗必问面试题!深度好文

欢喜学安卓

android 程序员 面试 移动开发

从磁盘读取成本分析两种 100% 遍历思路:按格子遍历 & 按线遍历

宫水三叶的刷题日记

面试 LeetCode 数据结构与算法

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