Mac 终端显示中文乱码解决办法



vi /Users/USERNAME/.inputrc
添加如下内容并保存

set meta-flag on
set convert-meta off
set input-meta on
set output-meta on

vi /etc/profile

添加下列内容退出,(放在/Users/USERNAME/.profile 也一样)

export LANG=zh_CN.UTF-8

阅读全文

nginx 的安装和php-fpm的关联

阅读全文

Git面向对象思想(1)tag blob commit tree

git 很好得使用了面向对象思想,将git内的文件抽象成了blob,目录树抽象成了tree,一次commit又是一个对象,tag指向一个commit,tag也是一个新的对象。

对象存储位置

每一个对象都是用40位的SHA1来进行存储,git将其拆分了两部分,一部分为2位的目录,一部分为后38位的文件名称,将这个

2位的目录和38位的文件存放在当前git目录下的.git/objects中。

假如有一个SHA1为b406bccf047e597135f5744fac2212e7a6050764的对象,我们可以在.git/objects下的目录b4中找到一个名称为06bccf047e597135f5744fac2212e7a6050764的文件,这个文件就对应的是b406bccf047e597135f5744fac2212e7a6050764这个对象。



git中查看一个SHA1的类型和内容,可以使用git cat-file增加不同参数来进行实现,我们就使用该命令,尝试对commit进行相关分析



1、commit

首先我们从git log中获取到一个commit的SHA1值

$ git log –oneline

b406bcc

//–oneline,增加该参数,会使log显示只显示所需的SHA1值,通常为前几位。但是调用结果和40位的SHA1值相同。无任何影响,所以之后我们都用前位的SHA1值进行代替



我们使用git cat-file -t 查看该SHA1的类型

$ git cat-file -t b406bcc

commit

//git提示这个SHA1的类型为一个commit,



我们接着往下找

$ git cat-file -p b406bcc

//git cat-file commit b406bcc也可实现相同效果

tree a415400a2c6a2157a620802f4b0c455632e54f63

parent 073fd6332ae6fc0235d3af4d516fee944fe261d7

author may <xjmarui@gmail.com> 1328795643 +0800

committer may <xjmarui@gmail.com> 1328795643 +0800



页面返回该commit的相关细信息,

我们发现该commit具有parent、tree、author、commiter等信息。

我们可以猜测tree应该就是tree对象,author和committer就是这次提交的属性,但是parent显示的也是SHA1,到底这是什么。我们用cat-file重新进行一次检测。

$ git cat-file -t 073fd6

commit

$ git cat-file -p 073fd6

tree 6d19ab1dff69838d446f2eb8c4d1f02dd082068b

parent ef33e3f3a20b10e49249a4e761d30b1e3875ff4a

author may <xjmarui@gmail.com> 1328787090 +0800

committer may <xjmarui@gmail.com> 1328787090 +0800

update bootstrap



也是一个commit,git log查看后发现,是HEAD的commit的上一个commit,即HEAD^



我们推测commit对象的属性,commit对象具有parent属性,parent也是另外一个commit,由于有merge的存在,可能commit对象的parent为多个,author、committer即为当时commit提交者的相关信息。commit还有一个message属性,保存commit提交时的相关描述信息。我们再看一个commit的tree属性



2、tree

$ git cat-file -t a415400

tree

提示为tree

$ git cat-file -p a415400

//查看该tree的相关信息,也可使用git ls-tree命令

100755 blob bfb5a81d1b3abab7d7377caa03a994af6dd74ccf .htaccess

100644 blob a58ef11241c281ce02d80fe7ea5a9fd990f32959 config.php

100644 blob 1a124f6e0a1d3a10b694a9f8bea90a4d1f72f394 index.php

040000 tree 7bb37f5bdef8b6282625df5d9ff3767bae5fc65d libraries

040000 tree d175b24632dfc5614dcf5076623b510ac281552c projects

040000 tree d59bdd1200c24ef0426785efd12d48b2c28ce87a shell

040000 tree 6648723260208038dea26d89f60a16db88ad5f32 update

我们可以看到,一个tree可以有多个tree和多个blob对象。

我们ls查看当前目录

$ ls -al

.htaccess config.php index.php libraries projects shell update

我们发现,当前目录下的文件和目录,就是这个tree,只不在git中将文件抽象为blob对象,将目录抽象为tree对象。每个tree对象一般都有多个blob对象和tree对象,但是blob对象不可能有tree对象。

我们查看blob对象相关信息



3、blob

$ git cat-file -p bfb5a81d

RewriteEngine On

RewriteRule ^~([a-zA-Z0-9-]+)~(.*)$ "projects/$1/wwwroot/$2" [L]

RewriteRule ^(?!(?:wwwroot/|index.php|projects/[a-zA-Z0-9-]+/wwwroot/)).+$ wwwroot/$0 [PT,NS]

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-l

RewriteCond %{REQUESTFILENAME} !-d

RewriteRule wwwroot/(.*)$ index.php/$1 [PT,L]



$ cat .htaccess

RewriteEngine On

RewriteRule ^~([a-zA-Z0-9-]+)~(.)$ "projects/$1/wwwroot/$2" [L]

RewriteRule ^(?!(?:wwwroot/|index.php|projects/[a-zA-Z0-9-_]+/wwwroot/)).+$ wwwroot/$0 [PT,NS]

RewriteCond %{REQUEST_FILENAME} !-f

RewriteCond %{REQUEST_FILENAME} !-l

RewriteCond %{REQUEST_FILENAME} !-d

RewriteRule wwwroot/(.)$ index.php/$1 [PT,L]

与当前内容一致



我们删除.htaccess内容后进行提交

提交后commit为aee5233024fcaa66447ff0e1ff5fbf23f544317a

该commit内的.htaccess对应的blob对象的SHA1为e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

我们发现现在.htaccess这文件对应的blob对象名称发生了变化,获取内容发现为空

$ git cat-file -p e69de29bb2d1



我们可以总结,同一个文件只要发生修改,会在文件的新状态下产生一个blob对象,该blob对象指向该文件当时的一种状态,包含了文件的各种属性,我们可以通过git cat-file获取该blob的内容,即当时文件的内容。



4、tag

tag是一个特殊形式的文件,它可指向任意的上述三个对象,所以git中将它归为git中的一个对象,其实我觉得它不是对象,应该是相当于一个refs,不然为啥它没SHA1值,而且文件是存储在.git/refs/tags下的。

获取tag的SHA1(tag对应的对象的SHA1值)命令为

$ git rev-parse tag_name

其实可以直接进入.git/refs/tags/查看tag_name对应的名称相同的文件的内容,内容为SHA1














































阅读全文

Git 面向对象思想

Git很好得使用了面向对象的思想对文件、commit进行管理

使用一下几个简单的命令就可以看出来

首先,我们创建一个新的库,并产生相关commit

$ mkdir myGit

$ cd myGit

$ git init

$ echo ‘<?php ‘ > index.php

$ git add index.php

$ git commit -m ‘add index.php’

//以上为创建repository和增加index.php文件相关代码

$ mkdir config

$ echo ‘<?php $config = NULL;’ > config/config.php

$ git add config

$ git commit -m ‘add config’

//增加新目录config以及config目录下的config.php文件

//增加至repository中去



$ git log –oneline

c6f9f23 add config

bb08f6c add index.php

我们可以看到这两个commit-id

先查看第一个commit-id相关信息

$ git cat-file -t c6f9f23

commit

//获取第一个commit-id 对应的类型,提示类型为commit,即为一次提交 cat-file 为获取文件内容,-t参数为获取type,即类型



$ git cat-file commit c6f9f23

//查看这个commit 的相关内容



tree 19de6039e22c7e19011f5c1e49632f52062776ca
parent bb08f6cd25a4cdfabb98b9124708f1d6532e1aae
author global <rui.ma@geneegroup.com> 1329655970 +0800
committer global <rui.ma@geneegroup.com> 1329655970 +0800

add config


//以上内容显示了一个commit指向一个tree,一个parent,并且这个commit包含了相关的author committer等。以及commit的时候提交的commit message。//commit信息。



我们看一下parent,发现parent指向的也是40位的hash字符串。仔细比对后发现和第一个commit的commit-id相同(前9位相同,其实真的是相同的,只不过查看log的时候用的是 –oneline,会自动截取前9位而已)

查看一下parent是不是指向上一个 commit

$ git cat-file -t bb08f6c

commit

$ git cat-file commit bb08f6c





tree 0b1f852200468dbce415877d81f280c291b0480b
author global <rui.ma@geneegroup.com> 1329655776 +0800
committer global <rui.ma@geneegroup.com> 1329655776 +0800

add index.php


显示内容如上:

发现commit message和第一个commit相同。这就说明了

1、每次commit都有一个parent属性,这个parent指向上一次(不是说时间史的上一个,就是由某个或多个commit merge、继续开发产生了当前commit )commit (s)



我们再看看 tree是什么东西



$ git cat-file -t 19de6039

tree

提示这是一个tree,到底什么是tree,继续看下去

$ git ls-tree 19de6039



040000 tree 5403102af0d8c495fe65e5a7ea4e4afde426dbe1 config
100644 blob dc84e23eee4f2bb27933347ab6d9f352f393134e index.php

我们使用ls-tree来查看一个tree对象的相关内容,cat-file用来查看commit blob 等相关对象内容。页面 提示,当前tree对应了另外一个tree和一个blob。我们发现blob后面跟着一个index.php文件名称。难道这个blob对象就是一个文件吗?尝试查看一下这个blob的内容

$ git cat-file blob dc84e23

<?php

页面提示内容和index.php内容一致,说明blob就是一个文件对象的别称,如果这么说,那我们可以猜想,tree对应的为一个目录,刚才的那个tree就是config目录,如果这么说得话,ls-tree这个tree,应该有一个名称为config.php的blob,并且这个blob内容和config目录下的config.php内容相同

$ git ls-tree 5403102

100644 blob 1d81c43a10211e4a5320311aab34940763158a78 config.php

我们看到了这个blob,第一步猜想正确

$ git cat-file blob 1d81c43

<?php $config = NULL;

内容和config目录下的config.php内容相同,也就是说tree对象其实就是对应一个目录

总结

2、一个commit如果在提交了新目录的情况下,会有tree这个属性,tree为一个目录对象的别称,可以通过ls-tree这个目录,查看这个tree下的blob(文件)

我们再做一个尝试

//我们修改index.php的第二行为hello后commit -a -m ‘modify index.php on master’

//checkout到foobar branch上,修改index.php第二行内容为world,然后commit -a -m ‘modify index.php on foobar’

//切换到master,但是不merge,修改index.php第二行内容为nice,commit -a -m ‘modify index.php on master’

//然后我们merge,系统提示错误,修改index.php 第二、三行内容分贝为nice 和world,然后commit -a -m ‘merge foobar’

这个时候我们看一下最后的commit-id的相关属性

$ git log –oneline

685ca0d merge foobar
15908a1 modify index.php on branch
dac22cb modify index.php on foobar
1659405 modify index.php on master
c6f9f23 add config
bb08f6c add index.php

$ git cat-file commit 685ca0d

tree d4e84b12563da39f56633e29ad29277d05d4e5c0
parent 15908a1a343cdef1b2b46cc126a7fa2e880464f6
parent dac22cbcd81a7f0a1a2930d5361a379eb342248b
author global <rui.ma@geneegroup.com> 1329659410 +0800
committer global <rui.ma@geneegroup.com> 1329659410 +0800

merge foobar

我们发现这个commit有两个parent,就说明这个commit是两次commit merge得到的。这两个parent均为两次commit,分别是HEAD^1 HEAD^2(请自行比对commit-id)

同样,我们看一下tree

$ git ls-tree d4e84b

040000 tree 5403102af0d8c495fe65e5a7ea4e4afde426dbe1 config
100644 blob 159f1c4a088c797ff6ba3e0bb28474251f1acf87 index.php

同样为当前repository的文件和目录对应的blob 和tree对象。

git中,所有的文件和目录都会转换为自身相应的对象,文件对应blob对象,目录对应tree对象。

我们add并commit的时候,实际上是对这些blob和tree进行操作,然后产生新的commit对象。commit对象包含上次的commit以及commit相关的tree和blob




我们进入repository的.git/objects目录看一下,发现目录下有很多文件。调一个

c3目录下有个文件名称为e4cfc298be6b6f12eaf244c4ccc26fad5ddd3c

$ git cat-file -t e4cfc298be6b6f12eaf244c4ccc26fad5ddd3c

提示出错,我们尝试和目录名称链接起来试试

$ git cat-file c3e4cfc298be6b6f12eaf244c4ccc26fad5ddd3c

tree

$ git ls-tree c3e4cfc298be6b6f12eaf244c4ccc26fad5ddd3c



100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 foobar.php

$ git cat-file blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

内容为空,估计是某次commit时的foobar.php当时的blob对象的相关信息。

//不知道上述说法是否正确。TODO

所有的对象会以对象名前两位为目录名称,其余为文件名称,保存在.git/objects/目录下。不信可以自己去试





















阅读全文

过去历史commit 剔除命令

有些时候,我们需要对过去的历史commit进行一次删除。我们通常的想法是,reset到需要剔除的前一个commit后,再重新进行开发。但是这样的结果是之后的提交不会重新链接到现有的master分支上。其实git中已经提供了一个对过去分支进行删除的命令。应该说不是对过去的分支进行删除的命令,而是多个命令的组合使用,来达到对过去commit删除

1、创建4个commit,从早到晚,分别为commit打上标签为A、B、C、D

2、假设我们需要从中删除B这个tag对应的commit,先直接checkout到A上 git checkout A,进入断头模式

3、使用git cherry-pick commit-id来补充C、D两个commit

4、记录当下的 commit-id ,git log –oneline -1

5、切换到 master分支, 离开断头模式 ,git checkout master

6、git reset –hard 最后记录的commit-id,重置master到最后的commit-id

7、查看git log –oneline

以上操作安成了对B这个commit的删除



我们回复到A、B、C、D都存在时的状态

1、git reflog show master,查看一下master分支的log,可以找到之前updating前的commit-id

2、git reset –hard commit-id恢复之前状态

3、git rebase –onto A B D,从A开始,删除B 最后到D

4、记录当前commit-id,然后在master分支上reset到当前commit-id即可



1、git reflog show master,查看master分支log,查看并记录之前 updating前的commit-id

2、git checkout A,进入断头模式

3、git rebase –onto HEAD B master

从HEAD开始,删除B,链接到master



1、回复之前状态

2、git rebase -i A

3、删除B提交一行后保存












阅读全文

git submodule 断头模式开发

主库main、submodule

clone后的库

main->main

submodule->submodule

foobar->main



进入clone后的main库

$ git submodule add /path/to/submodule

$ git submodule init

$ git submodule update

$ cd submodule

$ git log

//记录下最后的log的commit-id

$ git checkout 1325aec23cbde3da629cd01f2ab74a342d4a6bdb

//设定submodule进入断头模式

$ cd ../

$ git submodule init

$ git add ./

$ git commit -m ‘add submodule’

$ git push



进入foobar

$ cd /path/to/foobar

$ git pull

//提示有submodule的修改

$ git submodule init

$ git submodule update即可同步submodule的文件了



更新submodule

进入clone后的submodule,正常提交文件

$ cd submodule

$ touch bootstrap.php

$ git add bootstrap.php

$ git commit -m ‘add bootstrap.php’

$ git push





进入main

$ cd main

$ cd submodule

$ git checkout master

$ git pull

$ git log

//记录最后一次log的commit-id

$ git checkout commit-id切换到最后的commit-id上,进入断头模式

$ cd ../

$ git submodule init

$ git submodule

$ git commit -m ‘update submodule’

$ git push



进入foobar

$ git pull

$ git submodule update即可





断头模式可以使用户无法对submodule进行提交,但是可以获取。其实是可以提交的,需要在submodule中checkout到对应的branch中。然后commit再提交。但是在主库中可以设定用户不可提交,需要提交时可临时设定可提交,提交submodule后再关闭,即完成对submodule的更新。

现在公司就是使用这种模式进行开发,而且我们也不可直接对submodule进行修改,submodule就是处于断头模式,即使checkout到master后提交,也提示错误。

CTO需要对submodule修改得话,需要进入主库后git config receive.denyCurrentBranch ignore后,在submodule clone后的库中,进行修改然后push,再删除git config中的 receive.denyCurrentBranch设置。git config –unset receive.denyCurrentBranch。然后再在开发的库中(已增加submodule),讲submodule切换到master,然后pull下来刚修改的代码,将submodule checkout到最后的提交commit上,然后进入上层目录,git submodule init后,commit后push,其他用户pull下来后git submodule init,git submodule update即可,或者直接删除掉submodule目录重新git submodule init git submodule update也可。


阅读全文

多remote协同开发

假设现在有3个人,其中两两可见,不可能三个人都在一起。如何进行同时开发?

假设这三个人是A、B、C



首先,在A、C上创建两个repository

A:

$ mkdir reposiory1

$ cd repository1

$ git init

$ touch index.php

$ git add index.php

$ git commit -m ‘init’

$ git checkout -b branch_a

$ git merge master

$ git config receive.denyCurrentBranch ignore //设置其他用户可以pull和push



C:

$ mkdir repository2

$ cd repository2

$ git init

$ touch index.php

$ git add index.php

$ git commit -m ‘init’

$ git checkout -b branch_c

$ git merge master

$ git config receive.denyCurrentBanch ignore //设置其他用户可以pull和push



B用户的操作

$ gti clone username@hostofa.com:/path/to/repository1 repository //clone A的repository1到本地repository

$ cd repository

$ git checkout branch_a //切换到branch_a ,使B本地repository有branch_a

$ git checkout -b branch_c //创建branch_c,使B本地repository有branch_c

修改.git/config

[remote "A"]

fetch = +refs/heads/branch_a:refs/remotes/A/branch_a

url = user@hostofa.com:/path/to/repository1/

[remote "C"]

fetch = +refs/heads/branch_c:refs/remotes/C/branch_c

url = user@hostofc.com:/path/to/repository2/

[branch ‘branch_a"]

remote = A

merge = refs/heads/branch_a

[branch "branch_c"]

remote =C

merge = refs/heads/branch_c



设定完成

A和C可以在本地重新开发clone自己的库,然后自行开发,需要开发的代码push到自己所在的库上自己的branch_a/c上去。

B的操作相对比较繁琐

B如果需要抓取C的代码,需要做的是

$ git checkout branch_c

$ git pull C branch_c



B同样可获取A的代码

$ git checkout branch_a

$ git pull A branch_c



获取后A和C的代码都在不同分支,可以使用merge进行代码合并

假设当前在branch_a

$ git branch

* branch_a

branch_c

master



$ git merge branch_c

如果出现冲突,手动修复后commit

$ git push A branch_a:branch_a //将 branch_a push到A的branch_a分支

$ git checkout branch_c

$ git merge branch_a

$ git push C branch_c:branch_c //merge branch_a的代码,然后将branch_c push到C的branch_c分支,达到A、C代码交互。



当然B在master分支可也开发自己的代码,也可正常在其他分支merge master的内容,使B的代码分支branch_a branch_c不要偏离太远,适度merge并push,才可维持这种情况。



其实就是想在此总结一下branch remote的要点。


阅读全文

Git 回到历史

假如我们在某个库上开发,开发到1这个时候我就继续开发,直到2这个状态,我们想要回到1这个状态,可以使用git reset 和 git branch等相关命令进行相关操作。达到git 回到历史的做法



首先,我们假设当前开发的分支为develop分支

首先对当前分支进行一个备份

$ git branch bak_develop

$ git checkout bak_develop

$ git merge develop



完成对本地的备份

然后我们把本地备份的在远程服务器进行一次备份

$ git push origin bak_develop



在本地执行 git reset 将当前develop的开发状态reset到1这个状态时(可通过git log 查看相关commit-id)

$ git reset 11df052c52 //这个id实际使用中不同的



$ 删除远程的develop 分支

$ git push origin :develop



将现有的develop分支push到origin上的develop分支

$ git push origin develop:develop



所有人都删除自己的develop分支

$ git checkout master

$ git brand -D develop



做操作的主端进行如下操作

$ git push origin develop:develop

$ git config branch.develop.remote origin

$ git config branch.develop.merge refs/heads/develop



其他所有用户重新

$ git pull

$ git checkout develop

即可完成对一个分支的回到历史操作。

这个是比较笨的一种做法需要所有人都进行操作


阅读全文

Git 中HEAD相关内容

git 中,我们有时候进行reset等操作的时候,会提示HEAD被重置了,其他一些相关的文档、书籍内提示HEAD为一个指针类的东西,我们可以理解为游标。下面对HEAD进行相关学习和记录

.git/HEAD内,设定了HEAD指向的地址

$ git branch

develop

* master

当前,我们所在的branch为master,我们去查看一下HEAD

$ cat .git/HEAD

ref: refs/heads/master

ref是reference的缩写,就是说,参考refs/heads/master的内容

如果我们切换一下branch,再看看HEAD

$ git checkout develop

$ cat .git/HEAD

ref: refs/heads/develop

就是说参考 refs/heads/develop的内容,我们查看一下refs/heads/develop的内容

$ cat .git/refs/heads/develop

0196d532a17b91ad6408320911f5ecf52148bb70

发现是SHA1字符串

查看一下类型和内容

$ git cat-file -t 0196d532a17b91ad6408320911f5ecf52148bb70

commit

$ git cat-file commit f0196d532a17b91ad6408320911f5ecf52148bb70



tree 7e2b2eb31b5077b424c8ba3030daa03f2b1838f2
author global <rui.ma@geneegroup.com> 1329737841 +0800
committer global <rui.ma@geneegroup.com> 1329737841 +0800

init

发现HEAD指向为一个commit,我们查看git log后,发现就是当前分支的最上端的commit

也就是说,通常情况下,HEAD都指向当前分支的最后提交的那个commit

HEAD->refs/heads/branch_name->commit-id->commit

阅读全文

Git 不同命令对应不同区域思想记录

正常情况下,我们假想Git有6个区域,分别为:

git对文件无法进行跟踪的区域,我们称为1区

git正常进行开发的所有已跟踪文件的区域,但是该区域文件未进行修改,为当前正常开发的“老文件”,我们称为2区

git开发和修改后的文件的区域,任何文件被修改后,都会将修改后的文件复制到该区域中,但是文件并没有被增加到暂存区中,我们称为3区(很多教程中都将2区、3区称为working tree,其实这个地方就是working tree,只是在此称为2区、3区,便于区分而已)

git本身的暂存区,所有文件修改后,如果确定无误,可通过git add命令将已修改的文件从开发区域(3)复制一份到暂存区域中,我们称为4区(部分教程称为index索引)

git 本地的repository,如果文件没有任何修改,则和区域2的文件相同,我们称为5区

git 远程的repository,我们称为6区



如果我们从远程的6区中通过git clone来获取到一个空的repository后,我们其他的2、3、4、5区状态就会变为和6区状态一样。



我们修改2区的一个内容为A的文件为B后,2区文件依然为A,4、5、6区文件也依然为A。但是3区的文件的内容就为B了,因为2区文件内容发生变化,那新文件内容会移动到3区,文件便为B。我们执行git diff命令,即可查看到2区和3区的文件内容不同情况。



我们再执行git add file后,2区、3区、4区的文件都会变为B,这时候再执行git diff,查看2区、3区的文件,会发现2区、3区文件没有差别。但是执行git diff –cached 或者git diff –staged

会比对4区和5区的文件差异,发现文件内容从A变为了B。



我们执行git commit 后,4区内的文件内容复制了一份到5区,这个时候,2、3、4、5区内的文件内容都从A变为了B。



执行git push ,将5区内的文件发送到远程的6区。文件整个过程修改过程完成。





我们再看看git checkout命令。

在我们把文件内容从B修改为C时,2区内容为B,3区内容为C,4、5、6区内容均为B,通过git checkout file,讲该文件在3区状态清空。与2、4、5、6区状态一致。通过git status 和git diff等命令发现没有文件被修改。



如果文件从C修改为D后,已增加到2、3、4区了,这个时候我们还没commit ,但是文件需要修改,我们可以通过git reset file,将文件从4区重置到3区,这个时候3区内容为D,2、4、5、6区内容均为C。我们也可以通过git reset file将文件从4区重置到3区。。同样,执行reset后我们git checkout file,可同样将文件从3区重置到2区。



一个未跟踪的文件创建后,文件所在区域为1区,我们通过git add 改文件,将该文件放置到2、3、4区,git commit 后讲文件重置到5区。如果文件在4区的时候,我想放弃该文件,使用git reset untrack_file,可将该文件重置到1区,其他区域清空。

阅读全文