借 Git 的第一個(gè) commit 探索 Git 原理

最近想了解一下 Git 的實(shí)現(xiàn)耽梅,首先是看了 pro git 的 git 原理部分挨约。看完之后對(duì) git 的實(shí)現(xiàn)有了個(gè)大概的了解儒飒,但還是不過(guò)癮谬莹。于是把 git 的源碼 clone 下來(lái)看源碼是如何實(shí)現(xiàn)的,但是 git 的源碼實(shí)在太多了桩了,要耗費(fèi)太多精力了附帽。

我嘗試把跳轉(zhuǎn)到 Git 的第一個(gè) commit,代碼量就少了很多井誉,只有差不多 1000 行左右蕉扮。修改了一些代碼把程序編譯起來(lái)了】攀ィ看了下初始版本的 git喳钟,發(fā)現(xiàn)很多概念還是可以和現(xiàn)在的 git 的相通屁使,有閱讀的價(jià)值。

之后還看到 jacob Stopak 的解析奔则,對(duì) git 的代碼做了很多注釋蛮寂。看到的時(shí)候很是驚喜易茬,因?yàn)榫W(wǎng)上對(duì) git 這樣的解析很少酬蹋,看日期還是今年的 5 月 22 號(hào)發(fā)布的,新鮮的 >_<抽莱。

What Can We Learn from the Code in Git’s Initial Commit?

獲取源代碼并編譯

在這里使用 Jacob Stopak 的項(xiàng)目范抓,它對(duì) Git 的第一個(gè) commit 里面的代碼添加了大量注釋?zhuān)€修改了一些代碼方便我們?cè)诂F(xiàn)代操作系統(tǒng)上編譯。

獲取代碼

git clone https://bitbucket.org/jacobstopak/baby-git.git

編譯直接 make 就好了岸蜗,不過(guò)這里還是會(huì)出現(xiàn)編譯錯(cuò)誤尉咕,需要在 cache.h 文件修改下面的變量,加上 extern

extern const char *sha1_file_directory;
extern struct cache_entry **active_cache;
extern unsigned int active_nr, active_alloc;

直接 make

make

如果你想要嘗試使用 github 上 git 的源碼來(lái)編譯的話(huà)璃岳,可以進(jìn)行下面操作

# 先 clone 源碼
git clone https://github.com/git/git.git
# 根據(jù) log 找到第一個(gè) commit
git log --reverse
# 檢出第一個(gè) commit
git checkout e83c5163316f89bfbde7d9ab23ca2e25604af290

這里的源碼直接 make 的話(huà)會(huì)編譯失敗年缎,還需要作出下面的修改

首先是 Makefile 文件

# 把 LIBS 這行修改成這樣
LIBS= -lcrypto -lz

然后在 cache.h 中

#添加頭文件
#include <string.h>

和前面同樣的操作,把下面幾個(gè)變量加上 extern

extern const char *sha1_file_directory;
extern struct cache_entry **active_cache;
extern unsigned int active_nr, active_alloc;

之后就可以直接 make 了铃慷〉ノ撸或者你嫌麻煩可以用我 z.diff 文件,直接 apply 一下犁柜。

git apply z.diff

效果一樣洲鸠。

源碼結(jié)構(gòu)

可以看到 git 的開(kāi)始的時(shí)候只有 8 個(gè) .c 文件和 1 個(gè) .h 文件。

可以看到一下包含代碼和注釋行數(shù)只有 1037 行

cat *.c *.h | wc -l
1037

其中 7 個(gè) .c 文件可以對(duì)應(yīng)現(xiàn)在的 7 個(gè) git 命令

文件 當(dāng)前 Git 作用
init-db git init 初始化 git 倉(cāng)庫(kù)
update-cache git add 添加文件到暫存區(qū)
write-tree git write-tree 將暫存區(qū)的內(nèi)容寫(xiě)入到一個(gè) tree 對(duì)象到 git 的倉(cāng)庫(kù)
commit-tree git commit 基于指定的 tree 對(duì)象創(chuàng)建一個(gè) commit 對(duì)象到 git 的 倉(cāng)庫(kù)
read-tree git read-tree 顯示 git 倉(cāng)庫(kù)的樹(shù)對(duì)象內(nèi)容
show-diff diff 顯示暫存的文件和工作目錄的文件差異
cat-file git cat-file 顯示存儲(chǔ)在 Git 倉(cāng)庫(kù)中的對(duì)象內(nèi)容

至于 read-cache.c 文件則定義了一些程序一些公用的函數(shù)和幾個(gè)全局變量馋缅。

概念分析

Linus Torvalds 在 readme 中對(duì) git 的實(shí)現(xiàn)原理作出了一些解釋扒腕。

首先它給出了為什么要使用 git 這個(gè)名字的原因

  • 隨機(jī)的三個(gè)字母組合,沒(méi)有和其他的 unix 命令沖突
  • 簡(jiǎn)單
  • 當(dāng)它好使的時(shí)候叫 global information tracker
  • 不好使的時(shí)候叫 goddam idiotic truckload of sh*t
  • random three-letter combination that is pronounceable, and not
    actually used by any common UNIX command. The fact that it is a
    mispronounciation of "get" may or may not be relevant.
  • stupid. contemptible and despicable. simple. Take your pick from the
    dictionary of slang.
  • "global information tracker": you're in a good mood, and it actually
    works for you. Angels sing, and a light suddenly fills the room.
  • "goddamn idiotic truckload of sh*t": when it breaks

它有兩個(gè)核心的概念

  • objects databases
  • current directory cache (相當(dāng)于現(xiàn)在的暫存區(qū))

Object Databases

object database 相當(dāng)于一個(gè)基于文件系統(tǒng)的鍵值對(duì)數(shù)據(jù)庫(kù)萤悴,用文件的 sha-1 值作為鍵瘾腰,在 .dircache/objects 中存放各種類(lèi)型的數(shù)據(jù)。

在這里介紹三種類(lèi)型:

  • blob
  • tree
  • commit

blob 對(duì)象用來(lái)存儲(chǔ)完整的文件內(nèi)容覆履,用于 git 追蹤文件內(nèi)容蹋盆。blob 的結(jié)構(gòu)

blob size\0blobdata(file content)

git 除了要追蹤文件內(nèi)容外還要追蹤文件名、文件存儲(chǔ)路徑和權(quán)限屬性之類(lèi)的信息硝全。這個(gè)時(shí)候我們引入 tree 對(duì)象來(lái)存儲(chǔ)這些信息栖雾。tree 只會(huì)保留 blob 對(duì)象的 sha-1 值,不會(huì)追蹤文件內(nèi)容伟众。(現(xiàn)在的 tree 對(duì)象還會(huì)可以 tree 對(duì)象嵌套 tree 對(duì)象析藕,現(xiàn)在這里還沒(méi)有)

大致的結(jié)構(gòu)是這樣的(省略了頭)

100644 a.txt (6e666502660a7e810b276afd62523c56b34c1671)
100644 b.txt (b 的 sha-1 值)
100644 c.txt (c 的 sha-1 值)

commit 對(duì)象,可以理解為我們的一個(gè) commit赂鲤,它記錄了特定時(shí)間的目錄樹(shù)(tree 對(duì)象)噪径、父 commit 的 sha-id(可以有多個(gè)或 0 個(gè))柱恤、作者和提交者信息和對(duì)應(yīng)的 commit message。git 通過(guò) commit 對(duì)象來(lái)追蹤存儲(chǔ)庫(kù)的完整歷史開(kāi)發(fā)記錄找爱。

同樣是大致結(jié)構(gòu):

tree 1c93ac491de01f734fedbe70f31c47ca965c93b6
parent 4ae1f8aae02d7178aa4fc52f45f068cd750aa3de
author  <zzk@archlinux> Sat Jun 27 14:41:43 2020
committer  <zzk@archlinux> Sat Jun 27 14:41:43 2020

second commit

現(xiàn)在的 git 同樣還是一直在使用上面這些概念梗顺,通過(guò)管理這些對(duì)象來(lái)實(shí)現(xiàn)我們的版本控制。

Git 把每個(gè)文件的版本都完全保存下來(lái)车摄,會(huì)不會(huì)占用很多空間寺谤?是不是太暴力了?

git 會(huì)使用 zlib 對(duì)所有對(duì)象進(jìn)行壓縮吮播,代碼這類(lèi)文本數(shù)據(jù)可以做到很高的壓縮率(因?yàn)榇a中很多都是重復(fù)的变屁,比如關(guān)鍵字、函數(shù)調(diào)用)意狠。在現(xiàn)在的 git 還引入了 packfiles 機(jī)制粟关,會(huì)查找找命名及大小相近的文件,只保存文件不同版本之間的差異环戈,也是可以只保存 diff 的闷板。關(guān)于暴力的話(huà),就是空間換時(shí)間方式了院塞,這樣做在版本跳轉(zhuǎn)的時(shí)候很快遮晚,不用一個(gè)一個(gè)地應(yīng)用 diff,直接拿出來(lái)就好了拦止。

Git 如何存儲(chǔ)這些對(duì)象县遣?

它們都被存放在 .dircache/objects 文件夾中,通過(guò)內(nèi)容的 sha-1 值來(lái)索引對(duì)用的對(duì)象汹族。在 dircache/objects 下還會(huì)生成 256 個(gè)子目錄(00~ff)萧求,用來(lái)索引前兩位的 sha-1 值。

Current directory cache

current directory cache 就是我們使用的暫存區(qū)顶瞒,它存儲(chǔ)在 .dircache/index 文件中饭聚,可以看作是一個(gè)臨時(shí)的 tree 對(duì)象。當(dāng)執(zhí)行 update-cache 時(shí)搁拙,就會(huì)創(chuàng)建對(duì)應(yīng)文件的 blob 對(duì)象,并將樹(shù)信息加到 .dircache/index 文件中法绵。

如何使用

前面說(shuō)的那些可能沒(méi)有實(shí)際使用會(huì)不太好理解箕速,下面我們可以實(shí)戰(zhàn)一下,使用一些初始版本的 git朋譬。

在這里我們會(huì)利用這些現(xiàn)有的命令

  • 把暫存區(qū)的文件取出來(lái)(相當(dāng)于 git checkout -- file)

  • 提交兩個(gè) commit 并在這兩個(gè) commit 的之間切換

先使用 init-db 創(chuàng)建存儲(chǔ)庫(kù)

$ ./init-db

可以看到創(chuàng)建了 .dircache 文件夾盐茎,和現(xiàn)在的 .git 文件是一樣的。

創(chuàng)建一個(gè)文件寫(xiě)入到暫存區(qū)徙赢,執(zhí)行 update-cache 會(huì)在 .dircache/objects 文件夾中生成一個(gè) blob 對(duì)象并把它加入到 .dircache/index 暫存區(qū)中字柠。

$ echo "123456" > a.txt
$ ./update-cache a.txt

使用 find 查看創(chuàng)建的 blob 對(duì)象

$ find .dircache/objects -type f
.dircache/objects/6e/666502660a7e810b276afd62523c56b34c1671

查看一下

$ cat .dircache/objects/6e/666502660a7e810b276afd62523c56b34c1671
xKOR0g0426156%

由于是用 zlib 壓縮過(guò)的沒(méi)解壓看不出來(lái)啥探越,我們可以使用 cat-file 查看

$ ./cat-file 6e666502660a7e810b276afd62523c56b34c1671
temp_git_file_O5J3ZL: blob

他會(huì)生成一個(gè)臨時(shí)文件,里面是解壓好的文件內(nèi)容窑业,可以看到我們保存到存儲(chǔ)庫(kù)里 a.txt 的內(nèi)容

$ cat temp_git_file_O5J3ZL
123456

現(xiàn)在我們把當(dāng)前的暫存區(qū)的內(nèi)容保存為 tree 對(duì)象到 .dircache/objects

$ ./write-tree
433aef473a665a9efe1cf21fbc617fbf833c71b5

寫(xiě)入成功會(huì)打印對(duì)象的 SHA-1 值钦幔,我們可以用 read-tree 查看這個(gè) tree 對(duì)象有什么

$ ./read-tree 433aef473a665a9efe1cf21fbc617fbf833c71b5
100644 a.txt (6e666502660a7e810b276afd62523c56b34c1671)

現(xiàn)在可以提交我們的第一個(gè) commit 了,填寫(xiě)完 commit message 后按 crtrl+d 退出

$ ./commit-tree 433aef473a665a9efe1cf21fbc617fbf833c71b5
Committing initial tree 433aef473a665a9efe1cf21fbc617fbf833c71b5
first commit 
4ae1f8aae02d7178aa4fc52f45f068cd750aa3de

可以查看一下 commit 對(duì)象有什么東西

$ ./cat-file 4ae1f8aae02d7178aa4fc52f45f068cd750aa3de
temp_git_file_pNLmni: commit
$ cat temp_git_file_pNLmni
tree 433aef473a665a9efe1cf21fbc617fbf833c71b5
author  <zzk@archlinux> Sat Jun 27 14:20:55 2020
committer  <zzk@archlinux> Sat Jun 27 14:20:55 2020

first commit

我們完成了第一個(gè)提交常柄,現(xiàn)在我們對(duì) a.txt 做一下修改

$ echo "version2" > a.txt

用 show-diff 可以查看和暫存區(qū)的版本的差異

$ ./show-diff
a.txt:  6e666502660a7e810b276afd62523c56b34c1671
--- -   2020-06-27 14:27:19.975168003 +0800
+++ a.txt       2020-06-27 14:27:18.048129094 +0800
@@ -1 +1 @@
-123456
+version2

如果想取出暫存區(qū)的文件鲤氢,有兩個(gè)方法

  • 使用生成的 diff,修改文件
  • 用 cat-file 取出暫存區(qū)文件內(nèi)容(第二行有文件的 SHA-1 值)

先用 diff 試試

$ ./show-diff > a.diff
$ patch --reverse a.txt a.diff
patching file a.txt
$ cat a.txt
123456

可以看到文件又回到暫存區(qū)的版本了∥髋耍現(xiàn)在把文件再修改回去卷玉,使用 cat-file 來(lái)試試

$ echo "version2" > a.txt
$ ./show-diff
a.txt:  6e666502660a7e810b276afd62523c56b34c1671
--- -   2020-06-27 14:33:53.795263485 +0800
+++ a.txt       2020-06-27 14:33:50.726893274 +0800
@@ -1 +1 @@
-123456
+version2
$ ./cat-file 6e666502660a7e810b276afd62523c56b34c1671
temp_git_file_d4cTZ7: blob
$ mv temp_git_file_d4cTZ7 a.txt
$ cat a.txt
123456

現(xiàn)在我們準(zhǔn)備第二個(gè) commit ,再執(zhí)行一遍之前的操作

$ echo "verison2" > a.txt
$ ./update-cache a.txt
$ ./write-tree
1c93ac491de01f734fedbe70f31c47ca965c93b6

執(zhí)行 commit-tree 時(shí)候要注意喷市,由于這是第二個(gè)提交相种,需要用 -p 指定一下父提交的 SHA-id 也就是第一個(gè) commit

$ ./commit-tree 1c93ac491de01f734fedbe70f31c47ca965c93b6 -p 4ae1f8aae02d7178aa4fc52f45f068cd750aa3de
second commit
3d4340867cb1dd857987fc2db9b1b4bc14af7051

看一下這個(gè) commit

$ ./cat-file 3d4340867cb1dd857987fc2db9b1b4bc14af7051
temp_git_file_8Cguru: commit
$ cat temp_git_file_8Cguru 
tree 1c93ac491de01f734fedbe70f31c47ca965c93b6
parent 4ae1f8aae02d7178aa4fc52f45f068cd750aa3de
author  <zzk@archlinux> Sat Jun 27 14:41:43 2020
committer  <zzk@archlinux> Sat Jun 27 14:41:43 2020

second commit

現(xiàn)在我們要把 a.txt 的版本切換到上一個(gè) commit,從上面的輸出可以找出 parent 提交的 SHA-id

我們利用這個(gè)父提交的 sha-id 找到對(duì)應(yīng)的 tree object 的 sha-id品姓,再?gòu)?tree object 找到對(duì)應(yīng) a.txt 的 blob object 的 sha-id寝并。最后利用 cat-file 就可以還原出來(lái)原始 a.txt 的內(nèi)容了。

$ ./cat-file 4ae1f8aae02d7178aa4fc52f45f068cd750aa3de
temp_git_file_MssRdY: commit
$ cat temp_git_file_MssRdY
tree 433aef473a665a9efe1cf21fbc617fbf833c71b5
author  <zzk@archlinux> Sat Jun 27 14:20:55 2020
committer  <zzk@archlinux> Sat Jun 27 14:20:55 2020

first commit
$ ./read-tree 433aef473a665a9efe1cf21fbc617fbf833c71b5
100644 a.txt (6e666502660a7e810b276afd62523c56b34c1671)
$ ./cat-file 6e666502660a7e810b276afd62523c56b34c1671
temp_git_file_2RlDAI: blob
$ mv temp_git_file_2RlDAI a.txt
$ cat a.txt
123456

現(xiàn)在 a.txt 就被還原到了 第一個(gè) commit 時(shí)的狀態(tài)缭黔。

總結(jié):原始的 git 很難用食茎。經(jīng)過(guò)不斷地打磨才成了今天這樣子好用,現(xiàn)在的 git 很多人只要會(huì) add馏谨、commit别渔、push 就完事了,也不用了解 git 原理惧互。

代碼分析

這里對(duì) git 的部分源碼進(jìn)行分析哎媚,由于前面已經(jīng)寫(xiě)了很多篇幅了,這里就不會(huì)對(duì)太多的代碼進(jìn)行分析喊儡,里面的代碼也是超簡(jiǎn)單拨与,可以說(shuō)是 linus 代碼寫(xiě)得漂亮。你自己看估計(jì)也花不了一會(huì)兒艾猜,而且 Jacob Stopak 對(duì)代碼做超多的注釋?zhuān)娴臎](méi)啥好說(shuō)的买喧。 甚至你可以根據(jù)前面的知識(shí)腦補(bǔ)出大概源碼了>_<。

在這里推薦一個(gè)閱讀源碼的工具 sourcetrail匆赃∮倜看源碼時(shí)很方便。

init-db.c

int main(int argc, char **argv)
{
    char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
    int len, i, fd;
    
    // 創(chuàng)建 .dircache 的目錄
    if (mkdir(".dircache", 0700) < 0) {
        perror("unable to create .dircache");
        exit(1);
    }

    /*
     * If you want to, you can share the DB area with any number of branches.
     * That has advantages: you can save space by sharing all the SHA1 objects.
     * On the other hand, it might just make lookup slower and messier. You
     * be the judge.
     */
    sha1_dir = getenv(DB_ENVIRONMENT);
    if (sha1_dir) {
        struct stat st;
        if (!stat(sha1_dir, &st) < 0 && S_ISDIR(st.st_mode))
            return 1;
        fprintf(stderr, "DB_ENVIRONMENT set to bad directory %s: ", sha1_dir);
    }

    /*
     * The default case is to have a DB per managed directory. 
     */
    sha1_dir = DEFAULT_DB_ENVIRONMENT;
    fprintf(stderr, "defaulting to private storage area\n");
    len = strlen(sha1_dir);
    if (mkdir(sha1_dir, 0700) < 0) {
        if (errno != EEXIST) {
            perror(sha1_dir);
            exit(1);
        }
    }
    path = malloc(len + 40);
    memcpy(path, sha1_dir, len);
    // 創(chuàng)建 .dircache/objects 下的 256 個(gè)子目錄
    for (i = 0; i < 256; i++) {
        sprintf(path+len, "/%02x", i);
        if (mkdir(path, 0700) < 0) {
            if (errno != EEXIST) {
                perror(path);
                exit(1);
            }
        }
    }
    return 0;
}

init-db 就只是單純創(chuàng)建好這些目錄而已算柳,和我們前面看到的效果也一樣低淡。

這里介紹 update-cache.c 文件,主要說(shuō)一下怎么創(chuàng)建 blob 和加入 .dircache/index 文件。

首先看 main

int main(int argc, char **argv)
{
    int i, newfd, entries;
    
    // 將 .dircache/index 中的內(nèi)容讀入到 active_cache 這個(gè)全局變量中
    entries = read_cache();
    if (entries < 0) {
        perror("cache corrupted");
        return -1;
    }
    
    // 這里創(chuàng)建 lock 文件是為了不讓多個(gè) update-cahce 同時(shí)運(yùn)行的鎖文件
    newfd = open(".dircache/index.lock", O_RDWR | O_CREAT | O_EXCL, 0600);
    if (newfd < 0) {
        perror("unable to create new cachefile");
        return -1;
    }
    for (i = 1 ; i < argc; i++) {
        char *path = argv[i];
        // 驗(yàn)證路徑
        if (!verify_path(path)) {
            fprintf(stderr, "Ignoring path %s\n", argv[i]);
            continue;
        }
        // 把文件添加到 object 數(shù)據(jù)庫(kù)中
        if (add_file_to_cache(path)) {
            fprintf(stderr, "Unable to add %s to database\n", path);
            goto out;
        }
    }
    // 更新 index 文件
    if (!write_cache(newfd, active_cache, active_nr) && !rename(".dircache/index.lock", ".dircache/index"))
        return 0;
out:
    unlink(".dircache/index.lock");
}

通過(guò) main 函數(shù)可以看到生成 blob 應(yīng)該是在 add_file_to_cache 中做的蔗蹋、而更新 index 則是在 write_cache 中做的何荚。后面的可以自己追蹤一下。我說(shuō)一下大概做了啥

add_file_to_cache 中讀取文件然后構(gòu)造一個(gè) blob 對(duì)象進(jìn)行壓縮猪杭,最后計(jì)算 sha-1 值餐塘,把 壓縮好的 blob 對(duì)象存到 sha-1 值對(duì)應(yīng)的位置。(現(xiàn)在看文檔好像是先計(jì)算 sha-1 值再進(jìn)行壓縮了)之后通過(guò)文件名二分查找放入 active_cache 中(也就是 .dircahce/index 暫存區(qū)胁孙,這個(gè)變量是個(gè)全局變量開(kāi)始時(shí)將 .dircache/index 中的內(nèi)容讀入到里面)唠倦,因?yàn)槭菚捍鎱^(qū)是通過(guò)文件名進(jìn)行排序的,所以確認(rèn)一個(gè)文件是否在暫存區(qū)很快涮较,二分只要 logn稠鼻。

write_cache 中就是把 active_cache 再寫(xiě)回 .dircache/index.lock 文件,最后把鎖文件重命名為 .dircache/index 暫存區(qū)文件狂票。

這里是我的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末候齿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闺属,更是在濱河造成了極大的恐慌慌盯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掂器,死亡現(xiàn)場(chǎng)離奇詭異亚皂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)国瓮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)灭必,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人乃摹,你說(shuō)我怎么就攤上這事禁漓。” “怎么了孵睬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵播歼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我掰读,道長(zhǎng)秘狞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任蹈集,我火速辦了婚禮谒撼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雾狈。我一直安慰自己,他們只是感情好抵皱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布善榛。 她就那樣靜靜地躺著辩蛋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪移盆。 梳的紋絲不亂的頭發(fā)上悼院,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音咒循,去河邊找鬼据途。 笑死,一個(gè)胖子當(dāng)著我的面吹牛叙甸,可吹牛的內(nèi)容都是我干的颖医。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼裆蒸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼熔萧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起僚祷,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤佛致,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后辙谜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俺榆,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年装哆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罐脊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烂琴,死狀恐怖爹殊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奸绷,我是刑警寧澤梗夸,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站号醉,受9級(jí)特大地震影響反症,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜畔派,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一铅碍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧线椰,春花似錦胞谈、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)卿捎。三九已至,卻和暖如春径密,著一層夾襖步出監(jiān)牢的瞬間午阵,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工享扔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留底桂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓惧眠,卻偏偏與公主長(zhǎng)得像籽懦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锉试,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345