写点什么

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

评论

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

密码学系列之:NIST和SHA算法

程序那些事

数据结构 密码学 程序那些事

Nginx基础配置-资源缓存配置

梁龙先森

nginx 大前端 缓存;

STM32F103C8/BT6最小系统原理图、PCB

不脱发的程序猿

嵌入式 单片机 STM32F103C8T6 MCU ST

一文带你全面了解java对象的序列化和反序列化

华为云开发者联盟

Java 序列化 java对象 反序列化 Serializable接口

打破思维定式(五)

Changing Lin

5月日更

【LeetCode】叶子相似的树Java题解

Albert

算法 LeetCode 5月日更

GitHub开源的文言文编程语言、程序生成中国山水画、格律诗编辑程序

不脱发的程序猿

GitHub 开源 编程语言 传统文化

Java程序员面试必备——过得了面试官,过不了HR?我教你

比伯

Java 编程 架构 程序人生 计算机

10个 解放双手的 IDEA 插件,这些代码都不用写(第二弹)

程序员小富

Java 后端 IDEA

IM扫码登录技术专题(三):通俗易懂,IM扫码登录功能详细原理一篇就够

JackJiang

即时通讯 IM 扫码

个站建立基础教程

Damon

网站 5月日更

C语言0数组\柔性数组使用介绍

良知犹存

c

图算法系列之计算图中最短路径

Silently9527

数据结构和算法 图算法 广度优先搜素

关于中台,聊聊我认为相对客观的三点认知

架构精进之路

中台 5月日更

苹果移动设备用什么管理比较好?有什么推荐?

懒得勤快

imazing 手机管理

【技术干货】文件系统中的“锁”

焱融科技

容器 分布式 云原生 高性能 文件存储

百度大脑开放日厦门站-企业服务专场报名

百度大脑

百度大脑 开放日 企业服务

STM32如何计算RTC时钟异步预分频和同步预分频

不脱发的程序猿

嵌入式 RTC stm32 单片机 ST

414天前,我以为这是编程玄学...

why技术

Java JVM JMM

毕业前写了20万行代码,让我从成为同学眼里的面霸!

小傅哥

Java 面试 小傅哥 求职 毕业生

怎样使用过程自动化来实现过程的习惯性和持久性?

IPD产品研发管理

自动化 开发 CMMI

想要做网页游戏怎么办 ?PixiJs 篇(三)

空城机

大前端 游戏 pixi 5月日更

停止维护的CentOS6,怎么使用yum?

运维研习社

Linux 5月日更

青海大学智慧微能源数字孪生可视化系统

ThingJS数字孪生引擎

大前端 可视化 3D可视化 数字孪生

优柔寡断的人,能成什么大事

Kareza

个人成长 5月日更 反思总结

秘笈分享! 24 小时无人自习室为什么这么火?

IoT云工坊

小程序 人工智能 物联网 无人自习室

一线大厂最新总结Spring Security Oauth2.0认证授权全彩笔记

Java架构追梦

Java 阿里巴巴 架构 面试 spring security

缓存系统稳定性 - 架构师峰会演讲实录

万俊峰Kevin

缓存 微服务 分布式缓存 Go 语言

读完你就知道对话式人工智能的数据采集如何解决啦!

澳鹏Appen

人工智能 自然语言处理 聊天机器人 nlp nlu

Python OOP-4

若尘

面向对象 oop Python编程 5月日更

干好开发者关系的十个职业发展秘诀

开发者关系

开发者关系 技术运营 DevRel

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