Git使用教程

一、Git的概念:

Git是Linus花了两周的时间用C语言编写的一个版本控制系统,它是目前世界上最先进的分布式版本控制系统。

集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。

集中式版本控制系统最大的毛病就是必须有联网才能工作,如果在局域网内还好,带宽够大,速度够快,可如果在互联网上,遇到网速慢的话,可能提交一个10M的文件就需要5分钟,提交的速度会很慢。

那分布式版本控制系统与集中式版本控制系统有何不同呢?首先,分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。

和集中式版本控制系统相比,分布式版本控制系统的安全性要高很多,因为每个人电脑里都有完整的版本库,某一个人的电脑坏掉了不要紧,随便从其他人那里复制一个就可以了。而集中式版本控制系统的中央服务器要是出了问题,所有人都没法干活了。

在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。一般用来充当“中央服务器”的电脑就是一些代码托管网站,比如GitHub、GitLab、码云等。

二、本机的分区:

用git的方式管理代码就会在本机上产生如下的几个概念:

1、工作区(Working Directory):

电脑里能看到的目录就是工作区,如下图所示就是工作区。

2、版本库(Repository):

工作区有一个隐藏目录.git,如下图所示,在键盘上同时按下如下的几个键就能显示出隐藏文件。

这个.git文件不算工作区,而是git的版本库,如下图所示就是版本库。

git的版本库里存了很多东西,其中最重要的就是被称为stage(或者index)的暂存区,还有git为我们自动创建的第一个分支master,以及指向master的一个指针HEAD。

3、暂存区(stage):

在工作区中把文件修改以后,然后在终端中键入命令行语句”git add”,意味着把文件的修改添加到了版本库中的暂存区,然后再键入”git commit”命令行语句提交修改,意味着把暂存区的所有内容提交到了当前的分支上,整个过程如下图所示:

三、git的使用:

一般Git有三种使用方法:第一种是利用Xcode自带的Git把代码存储在远端的代码托管网站上;第二种是利用命令行的方式使用Git把代码存储在远端的代码托管网站上;第三种是利用Sourcetree这种可视化的方式利用Git把代码存储在远端的代码托管网站上。下面重点介绍命令行的使用方法。

这个部分的目录如下:

1、命令行方式下的git的基本使用:

2、命令行方式下的git的版本回退:

3、命令行方式下的git的文件撤销修改:

4、命令行方式下的git的文件删除:

5、命令行方式下的git的远程库的添加:

6、命令行方式下的git的冲突解决:

7、命令行方式下的git的分支管理策略:

8、命令行方式下的git的Bug分支处理:

9、命令行方式下的git的Feature分支:

10、命令行方式下的git的多人协作:

11、命令行方式下的git的标签创建:

12、命令行方式下的git的操作标签:

13、如何使用Github:

14、如何使用码云:

15、命令行方式下的git的配置别名:

1、命令行方式下的git的基本使用:

(1)创建空白目录:

在创建版本库之前要先创建一个空白的目录,把这个空白的目录当做工作区。用如下的命令行创建一个空白的目录:

$ mkdir learngit

$ cd learngit

$ pwd

“pwd"命令行用于显示当前的目录,在我的Mac上,这个仓库位于/Users/zhaopeng/learngit。在终端中运行上述命令行之后的截图如下:

在电脑中创建的空白目录的截图如下:

(2)创建版本库(Repository):

       版本库又名仓库,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。

       通过”git init”命令行在上面(1)步骤中创建的空白目录(工作区)中创建版本库,在终端中运行前述的命令行之后截图如下:

运行”git init”命令行之后在之前创建的空白目录(工作区)中会出现一个隐藏的.git文件,截图如下:

这个.git文件就是版本库了。版本库中存放了很多内容,其中最重要的就是暂存区(stage),还有git为我们自动创建的第一个分支master,以及指向master的一个指针HEAD。一般情况下不去修改这个.git文件夹里面的文件,如果乱改的话就会把git仓库给破坏了。

(3)把要进行版本控制的文件添加到新创建的版本库中:

       所有的版本控制系统,其实只能跟踪文本文件的改动,比如TXT文件、网页、所有的程序代码等等,Git也不例外。版本控制系统可以告诉你每次的改动,比如在第5行加了一个单词”Linux",在第8行删了一个单词”Windows"。而图片、视频这些二进制文件,虽然也能由版本控制系统进行管理,但没法跟踪这些文件的变化,只能把这些二进制文件的每次改动串起来,也就是只能知道图片从100KB改成了120KB,但到底改了什么,版本控制系统不知道,也没法知道。

       因为文本是有编码的,比如中文有常用的GBK编码,日文有Shift_JIS编码,如果没有历史遗留问题,强烈建议使用标准的UTF-8编码,所有语言使用同一种编码,既没有冲突,又被所有平台所支持。

编写一个简单的.rtf文件,内容如下:

Gitisa version control system.

Gitisfree software.

然后把这个文件放到上述在(1)中创建的工作区中,放入之后的截图如下:

利用"git add 文件名称"命令行再把这个文件放到上述在(2)中创建好的版本库中,在终端操作的截图如下:

如果执行完"git add 文件名称”命令行之后终端没有任何提示的话就说明添加成功了,意味着把文件由工作区放到了版本库中的暂存区(stage)中,但是在.git文件中看不到新添加的文件,实际上是已经添加进去了。

(4)把文件由版本库中的暂存区提交到版本库中的master分支上:

在上述的(2)中创建版本库之后,版本库中包含很多东西,除了暂存区(stage)之外还有git为我们自动创建的第一个分支master。在终端中执行"git commit -m “提交说明""命令行,意味着把文件从版本库中的暂存区中提交到当前分支上,在终端的操作截图如下:

终端中”-m"后面输入的是本次提交的说明,这样就能从历史记录里方便地找到改动的记录。”git commit”命令执行成功后会告诉你”1 file changed”:1个文件被改动(我们新添加的readme.txt文件);"2 insertions":插入了两行内容(readme.rtf有两行内容)。

       为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以可以多次add不同的文件,比如:

$ git add file1.txt

$ git add file2.txt file3.txt

$git commit -m"add 3 files.”

上述的就是在使用命令行的方式下git的基本使用方法。

       总结:把工作区中的文件往版本库中添加的时候分为两个步骤,第一步是用”git add”命令行把文件的修改添加到版本库中的暂存区中,如下图所示:

第二步是用”git commit”命令行把版本库中的暂存区中的所有内容提交到当前分支中,如下图所示:

2、命令行方式下的git的版本回退:

       在上述1中已经成功地在版本库中添加并提交了一个readme.rtf文件了,现在在工作区中对这个文件进行修改,在工作区中修改的这个文件其实可以看成是在修改版本库中的暂存区中的那个文件。把文件的内容修改为如下:

Gitisa distributed version control system.

Gitisfree software.

修改完以后在终端中运行”git status”命令行,”git status”命令行的意思是显示当前版本库的状态。运行之后的截图如下:

从上面的命令输出可以得知暂存区中的readme.rtf文件被修改了,但还没有准备提交修改了。在终端中可以通过”git diff 文件名称”命令行查看文件修改前和修改后的不同之处,在查看完不同之处后再在终端中输入”q”命令行跳出,在终端运行之后的截图如下:

把工作区中修改过后的readme.rtf文件在终端中用”git add 文件名称”命令行提交到版本库中的暂存区中,在终端中运行之后的截图如下:

同样没有任何输出就证明已经成功地将文件从工作区添加到了版本库中的暂存区中了。在提交之前再运行”git status”命令行,查看当前版本库的状态,在终端中运行之后的截图如下:

最后在终端中执行”gitcommit -m “提交说明"”命令行,把版本库中的暂存区中的新修改的文件提交到版本库中的master分支上,在终端中运行之后的截图如下:

提交后再运行”git status”命令行,查看当前版本库的状态,终端的截图如下:

Git告诉我们当前没有需要提交的修改,而且工作目录是干净(working tree clean)的。

再次修改工作区中的readme.rtf文件,把文件的内容修改如下:

Gitisa distributed version control system.

Gitisfree software distributed under the GPL.

然后把新修改的文件添加到版本库中的暂存区中,最后提交新修改的文件到版本库中的master分支上,在终端中运行的截图如下:

像这样不断对文件进行修改,然后不断提交修改到版本库里的master分支上,让git为你做一个修改文件的记录者,一旦你把文件改乱了或者误删了文件,还可以从最近的一个commit进行恢复,然后继续工作,而不是把这几个月的工作成果全部丢失。

现在,回顾一下readme.rtf文件一共有几个版本被提交到Git版本库里的master分支上了:

版本1:wrote a readme file

Gitisa version control system.

Gitisfree software.

版本2:add distributed

Gitisa distributed version control system.

Gitisfree software.

版本3:append GPL

Gitisa distributed version control system.

Gitisfree software distributed under the GPL.

在终端中执行”git log”命令行,用来查看文件的修改记录,截图如下:

"git log”命令行显示从近到远的提交日志,可以看到3次提交,最近的一次是"append GPL",上一次是"add distributed",最早的一次是"wrote a readme file"。如果嫌输出信息太多,看得眼花缭乱的,可以在"git log”后面加上"--pretty=oneline”,截图如下:

在终端上看到的类似”9ece8301d9350a0b71e309912ff0f661cfac3a8b”的一大长串数组和字母的组合其实是每次commit的版本号(commit id),它是一个由SHA1计算出来的一个非常大的数字,用十六进制表示。那为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,如果大家都用1,2,3……作为版本号,那肯定就冲突了。

       现在准备把readme.rtf文件回退到上一个版本,也就是”add distributed”的那个版本。首先,Git必须知道当前版本是哪个版本,在Git中用HEAD表示当前的版本,也就是最新提交的,commit id为”9ece8301d9350a0b71e309912ff0f661cfac3a8b",上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。现在,我们要把当前版本"append GPL"回退到上一个版本"add distributed",就可以使用"git reset --hardcommit id”命令行,截图如下:

这个时候打开工作区中的文件,果然内容变成了上一个版本的内容。也可以在终端中通过”cat 文件名称"命令行的方式查看工作区中的文件内容,截图如下:

再用”git log”查看现在版本库的状态,截图如下:

发现最新的那个版本"append GPL"已经看不到了,如果要想回到最新的那个版本就在终端上往上倒,找到”append GPL”版本对应的commit id(9ece83…),然后执行"git reset --hardcommit id”命令行,截图如下:

版本号没必要写全,前几位就可以了,Git会自动去找,当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。打开工作区中的readme.rtf文件,可以看到文件的内容已经变成了最新的那个”append GPL”版本了。Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD指针从指向append GPL:

改为指向add distributed:

然后顺便把工作区的文件更新了。所以你让HEAD指针指向哪个版本号,你就把当前版本定位在哪。如果当回退到了某个版本之后关掉了电脑,第二天早上想恢复到新版本怎么办?找不到新版本的commit id怎么办?现在执行”git reset --hard HEAD^”命令行,文件回退到”add distributed”版本,截图如下:

再想恢复到"append GPL",就必须找到"append GPL"的commit id。Git提供了一个命令行"git reflog"用来记录你的每一次命令,截图如下:

就可以找到”append GPL”版本的commit id(9ece830)了,从而可以把文件更新到最新的版本了,截图如下:

打开工作区中的文件,果然文件更新到了最新的版本了。

3、命令行方式下的git的文件撤销修改:

       在工作区中给文件readme.rtf不小心添加了一句”My stupid boss still prefers SVN.”,以至于readme.rtf文件的内容变为了:

Gitisa distributed version control system.

Gitisfree software distributed under the GPL.

My stupid boss still prefers SVN.

这个时候还没有执行”git add”命令行,意味着已经被修改的文件还没有被加入到版本库中的暂存区中了,这个时候执行命令”git status”,截图如下:

上面的"Changes not staged for commit这句话代表工作区中的文件修改还没有被加入到版本库中的暂存区中了。上面的”git checkout -- <file>…”的意思是可以丢弃工作区的修改,所以在终端中执行”git checkout -- 文件名称”命令行,用来把工作区中的文件的修改进行撤销,截图如下所示:

执行完命令之后查看工作区中的文件,果然那句话被撤回了。

      “git checkout -- 文件名称”命令行中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。

在工作区中修改readme.rtf文件后,执行”git add”命令行把文件修改添加到版本库中的暂存区中,截图如下:

然后执行”git status”命令行,截图如下:

上面的”Changes to be committed”代表工作区中文件的修改已经添加到了版本库中的暂存区中了,只是还没有commit了。执行"git reset HEAD 文件名称”命令行,可以把暂存区的文件修改撤销掉,重新放回工作区。截图如下:

执行之后再用”git status”指令查看暂存区的状态,截图如下:

上面的”Changes not staged for commit”语句已经说明之前存在暂存区中的文件修改已经被撤销了,工作区中的文件有修改。最后再执行”git checkout -- 文件名称”指令把工作区中的文件的修改撤回,截图如下:

执行之后打开工作区中的文件,果然修改被撤回了。

总结:

场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令行”git checkout -- 文件名称”。

场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令行"git reset HEAD 文件名称",撤回暂存区中的文件的修改,然后就回到了场景1,第二步按场景1来操作。

场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。

4、命令行方式下的git的文件删除:

       在工作区中创建一个新的文件,然后利用”git add 文件名称”命令行把工作区中新创建的文件添加到版本库中的暂存区中,最后再利用"gitcommit -m “提交说明””命令行把版本库中的暂存区中的文件提交到当前的分支上。在终端中运行后截图如下:

此时工作区中的情况如图所示:

一般情况下,通常会直接在文件管理器中或者在终端中利用"rm 文件名称"命令行把工作区中的文件删除掉,截图如下:

此时工作区中的情况如图所示,刚才新添加的文件被删除了:

这个时候,Git知道你删除了工作区中的文件,因此工作区和版本库就不一致了,利用"git status”命令行来查看哪些文件被删除了,截图如下:

现在有两种情况:

(1)确实要把文件从版本库中删除:利用”git rm 文件名称”把文件从版本库中的暂存区中删除,并且利用"gitcommit -m “提交说明””把文件的变化提交到当前的分支中,截图如下:

现在文件就从版本库中删除了,并且同步到了当前的分支中。

(2)在工作区中误删文件了,把不应该删除的文件删除了。针对这种情况,因为版本库中还有此文件了,所以可以利用”git checkout -- 文件名称"把误删的文件恢复到最新的版本,截图如下:

此时工作区中的情况如图所示,被删除的文件又被加了回来:

“git checkout"其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。"rm 文件名称”命令行用于删除工作区中的某个文件,如果一个文件已经被提交到了版本库,那么永远不用担心被误删,但是要小心,只能恢复文件到最新的版本,会丢失最近一次提交后修改的内容。

5、命令行方式下的git的远程库的添加:

      现在的情形是在本地已经创建了一个工作区和版本库了,并且readme.rtf文件已经提交到了版本库中的当前的分支中了。现在想要在github网站上创建一个git的远程仓库,让本地仓库和远程仓库进行同步,这样gitHub上的远程仓库既可以作为备份,又可以让其他人通过该仓库来进行协作,一举多得。

       登录github后点击页面右上角的绿色按钮”New”,创建一个新的git的远程仓库,如图所示:

在仓库名称中填入learngit,并且填入描述信息,然后点击“Create repository”按钮,创建一个git的远程仓库,如图所示:

创建完的远程仓库是空的。GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后把本地仓库的内容推送到GitHub远程仓库中,如图所示:

根据github上面的提示在终端中运行”git remote add origin github地址”和”git push -u origin master"命令行,把本地仓库和远程仓库进行关联并且把本地仓库中的master上的内容推送到远程仓库中的master分支上,截图如下:

关联成功后远程仓库的名字就默认叫做origin了。

用”git push”命令行把本地仓库中的内容推送到远程仓库中,实际上就是把本地的版本库中的master分支上的文件推送到远程仓库中的master分支上。由于远程仓库是空的,当第一次推送master分支时会使用”git push -u origin master”命令行,这样做git不但会把本地的master分支上的内容推送到远程的master分支上,还会把本地的master分支和远程的的master分支关联起来,在以后的推送或者拉取时就可以简化命令行了。

推送成功后,就可以立刻在GitHub页面中看到远程仓库中的内容已经和本地仓库中的一模一样了,截图如下:

从现在起,只要把本地版本库中的暂存区中的文件提交到了本地版本库中的master分支后,就可以通过”git push -u origin master”命令行把本地master分支中的最新文件推送到远程仓库中的master分支中。现在就拥有了真正的分布式版本库了!

备注:

SSH警告:当第一次使用Git的”clone”或者”push”命令行连接GitHub时,会得到一个警告:

The authenticity of host 'github.com(xx.xx.xx.xx)' can't be established.

RSA key fingerprint is xx.xx.xx.xx.xx.

Are you sure you want to continue connecting (yes/no)?

这是因为git使用SSH连接,而SSH连接在第一次验证GitHub服务器的Key时需要你确认GitHub的Key的指纹信息是否真的来自GitHub的服务器,输入yes回车即可。

Git会输出一个警告,告诉你已经把GitHub的Key添加到本机的一个信任列表里了:

Warning: Permanently added'github.com'(RSA) to thelistof known hosts.

这个警告只会出现一次,后面的操作就不会有任何警告了。

如果实在担心有人冒充GitHub服务器,输入yes前可以对照GitHub的RSA Key的指纹信息是否与SSH连接给出的一致即可。

分布式版本系统的最大好处之一就是在本地工作完全不需要考虑远程仓库的存在,也就是有没有联网都可以正常工作,当有网络的时候再把本地提交推送一下就完成了同步。而SVN在没有联网的时候是拒绝干活的。

6、命令行方式下的git从远程仓库克隆:

现在远程库已经准备好了,下一步是用命令行把远程库克隆一个到本地。

在终端中执行”git clone github地址”命令行,把远程库克隆到本地,截图如下:

一般会下载到本地的个人文件夹里面,并且已经把远程库中的README.md文件克隆了下来,如图所示:

如果是多人协作开发的话,则每个人各自从远程克隆一份到本地就可以了。

7、命令行方式下的git的创建与合并分支:

       在上面的“2、命令行方式下的git的版本回退”中已经知道,每次提交,git都会把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在git里把这个分支叫做主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以HEAD指向的就是当前分支。

       一开始的时候,master分支是一条线,git用master指向最新的提交,再用HEAD指向master,就能确定当前的分支以及当前分支的提交点,如图所示:

当在本地修改工作区中的文件以后,在终端中利用”git add 文件名称”命令行把之前的修改提交到本地的版本库中的暂存区中,然后再利用"git commit -m “提交说明””命令行把在本地版本库中的暂存区中的文件的修改提交到本地的master分支中。这样每次提交,本地版本库中的master分支都会向前移动一步,这样,随着不断地提交,本地master分支的线也就越来越长,如图所示:

当创建新的分支时(例如dev),git新建了一个指针叫做dev,指向master相同的提交,再把HEAD指向dev,就表示当前的分支是dev分支了,如图所示:

从前述中可以看出git创建一个新分支很快,因为除了增加一个dev指针、改变HEAD的指向外,工作区的文件没有任何的变化。不过,从现在开始,对工作区的修改和提交就是针对dev分支了。比如,这个时候修改本地工作区中的文件以后,在终端中利用”git add 文件名称”命令行把之前的修改提交到本地的版本库中的暂存区中,然后再利用"git commit -m “提交说明””命令行把暂存区中的修改提交到本地的版本库中的dev分支上,这样,dev指针就往前移动了一步,而master指针则不变,如图所示:

假如我们在dev分支上的工作完成了,就可以把dev分支合并到master分支上了。合并最简单的方法就是直接把master指向dev的当前提交,就完成了合并,所以git的合并分支也很快,就是改改指针,工作区的内容也不变。如图所示:

合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删除掉,删掉后就剩下了一条master分支了,如图所示:


上面是一些理论的知识,下面进行实战:

在终端中利用”git checkout -b 分支名称”命令行在本地的版本库中创建一个新的分支并由原来的master分支切换到这个新的分支上,截图如下:

“git checkout”命令后面加上”-b"参数表示创建并切换,相当于”git branch dev”和”git checkout dev”两条命令行的作用。

可以用”git branch”命令行来查看本地版本库中的当前分支,截图如下:

“git branch”命令行会列出本地版本库中的所有分支,当前分支前面会用一个*号来标识。然后我们就可以在dev分支上正常地做提交了。比如,在readme.rtf文件中加上一行"Creating anewbranch is quick.”,然后利用”git add 文件名称”命令行把在本地的工作区中做的修改添加到本地的版本库中的暂存区中,然后利用"git commit -m “提交说明””命令行把本地的暂存区中的修改提交到本地的版本库中的dev分支上,截图如下:


现在,dev分支的工作完成了,然后可以利用”git checkout master”命令行切换回master分支了,截图如下:

切换回master分支后,再查看本地的工作区中的readme.rtf文件,发现刚才添加的内容不见了,这是因为刚才提交的文件是提交到了本地的版本库中的dev分支上了,而master分支此刻的提交点并没有改变,如图所示:

现在,利用”git merge dev”命令行把本地的版本库中的dev分支上的工作成果合并到本地的版本库中的master分支上,截图如下:

“git merge 分支名称”命令行用于合并指定分支到当前的分支上。合并后再查看本地的工作区中的readme.rtf文件的内容就可以看到和dev分支上的最新提交的内容是完全一样的了。

注意截图上的Fast-forward信息,git告诉我们这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并的速度非常快。当然,也不是每次合并都能Fast-forward。

合并完成后,就可以利用”git branch -d dev”命令行放心地删除dev分支了,截图如下:

删除后利用”git branch”命令行查看branch,现在就只剩下master分支了,截图如下:

因为创建、合并和删除分支的速度非常的快,所以git鼓励使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更为安全。

备注:

git鼓励大量使用分支:

查看分支:git branch

创建分支:git branch <name>

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并某分支到当前的分支:git merge <name>

删除分支:git branch -d <name>

6、命令行方式下的git的冲突解决:

利用”git checkout -b 分支名称”命令行在本地的版本库中创建新的feature1分支并切换到这个新分支上,继续新分支的开发,截图如下:

创建并切换到新的分支以后,在本地的工作区中修改readme.rtf文件的内容,在此文件中增加一句”Creating anewbranch is quickANDsimple.”,然后利用"git add 文件名称”命令行把本地的工作区中的文件修改添加到本地的版本库中的暂存区中,然后再利用"git commit -m “提交说明””命令行把本地的版本库中的暂存区中的文件的修改添加到新创建的feature1分支上,截图如下:

然后利用”git checkout master”命令行切换到master分支上,截图如下:

上面截图中的”Your branch is ahead of 'origin/master' by 1 commit.”语句代表着git自动提示当前本地的版本库中的master分支比远程的master分支要超前一个提交。

现在在master分支下打开本地工作区中的readme.rtf文件,并在此文件中添加”Creating anewbranch is quick & simple.”语句,然后利用"git add 文件名称”和 "git commit -m “提交说明””命令行把文件的修改提交到本地的版本库中的master分支上,截图如下:

现在本地的master分支和feature1分支各自都分别有新的提交,如图所示:

如果一个分支上有新的提交,而另外一个分支上没有新的提交,则这种情况下适合上述的git的"快速合并”,像现在这种情况,两个分支上都有新的提交,则就不适用git的“快速合并”了,只能试图把各自的修改合并起来,但这种合并就有可能会造成冲突。利用”git merge feature1”命令行把feature1分支上的内容合并到master分支上,截图如下:

git告诉我们,readme.rtf文件存在冲突,必须手动解决冲突后再提交才可以。

可以利用”git status”命令行来查看冲突的文件,截图如下:

或者直接打开本地工作区中的readme.rtf文件,查看里面的内容,截图所示:

git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,把这些内容删除后更改为”Creating anewbranch is quickandsimple.”,然后再利用命令行把解决冲突后的文件提交到本地的版本库中的当前分支(master)上,截图如下:

现在,master分支和feature1分支的关系如下图所示:

利用”git log --graph --pretty=oneline --abbrev-commit”命令行可以看到两个分支的合并情况,截图如下:

最后利用”git branch -d feature1”命令行删除本地的版本库中的feature1分支,截图如下:

至此,解决冲突的工作完成。

备注:

当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成;

解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。

7、命令行方式下的git的分支管理策略:

通常合并分支时,如果可能的话git会用Fast forward(快进)模式,但在这种模式下删除分支后,会丢掉分支上的信息。

如果想要强制禁用Fast forward模式的话,就要使用--no-ff方式的git merge,示例如下:

利用”git checkout -b dev”命令行在本地的版本库中创建并切换到dev分支上,截图如下:

在dev分支上修改本地工作区中的readme.rtf文件的内容,修改之后利用"git add 文件名称”命令行把文件的修改添加到本地的版本库中的暂存区中,然后利用"git commit -m “提交说明””命令行把本地的版本库中的暂存区中的修改提交到本地的版本库中的dev分支中,截图如下:

然后利用”git checkout master”命令行切换回本地的master分支中,截图如下:

然后利用”git merge --no-ff -m"文字说明"dev”命令行把dev分支上的内容合并到当前的master分支上,命令行中的--no-ff参数表示禁用Fast forward(快进)模式,截图如下:

因为本次合并要创建一个新的commit,所以加上-m参数,要把commit的描述写进去。

合并后利用”git log--graph --pretty=oneline --abbrev-commit”命令行来查看分支的历史,截图如下:

可以看到在不使用Fast forward模式的情况下,merge后就如下图所示:

分支策略:

在实际的开发过程中,应该按照如下的几个基本原则进行分支管理:

首先,master分支应该是非常稳定的,也就是仅用来发布新版本的,平时不能在上面进行开发;

如下图所示,先在master分支之外创建一个dev分支,然后基于dev分支再创建不同开发者的分支,每个开发者都有一个属于他们自己的独立分支,可以使用开发者的名字来给这些分支进行命名。每个开发者都在属于他们自己的分支上进行开发,然后时不时地往dev分支上进行合并,到某个时候,比如1.0版本发布的时候,再把dev分支合并到master分支上,最后在master分支上发布1.0版本。

合并分支时,加上--no-ff参数就可以用普通的模式进行合并了,用这种方式进行的合并可以看出合并的历史记录,而fast forward合并就看不出来曾经合并的历史记录。

8、命令行方式下的git的Bug分支处理:

       在软件开发过程中,一旦有了bug就需要及时地进行修复。在git中由于分支是如此的强大,所以每个bug都可以通过一个新的临时分支来进行修复,修复后合并分支,然后将临时的分支删除就可以了。

       当开发者接到一个修复一个代号为101的bug的任务时,很自然地首先想创建一个名为issue-101的分支来修复它,但是当前正在dev分支上进行的工作还没有提交了,截图如下:


并不是开发者不想提交,而是工作只进行到了一半,没有办法进行提交,预计完成还需要一天的时间。但是,必须在两个小时内修复该bug,此时应该怎么办呢?针对这种情况,可以在终端中执行”git stash”命令行,它的作用是把当前工作的现场“储藏”起来,等以后恢复现场了再继续进行工作,截图如下:

然后利用”git status”命令行来查看当前dev分支的情况,发现当前dev分支是干净的(除非有没有被Git管理的文件),因此可以放心地创建新的分支来修复bug了,截图如下:

首先要确定在哪个分支上修复该bug,假定需要在master分支上进行修复,就从master分支上创建临时的分支。利用”git checkout master”命令行切换到master分支上,截图如下:

然后利用”git checkout -b 分支名称”命令行在master分支上创建一个临时分支来处理此bug,截图如下:

打开本地工作区中的readme.rtf文件,在此文件中修复bug,然后利用"git add 文件名称”命令行把本地文件的修改添加到本地版本库中的暂存区中,然后再利用"git commit -m “提交说明””命令行把本地版本库中的暂存区中的文件的修改提交到本地版本库中的临时分支issue-101上,截图如下:

修复完成后,利用”git checkout master”命令行切换到master分支上,然后再利用”git merge --no-ff -m"提交说明"issue-101”命令行把issue-101分支上的内容合并到当前的master分支上,最后再利用”git branch -dissue-101”命令行删除issue-101分支即可。这样一波操作下来,原计划两个小时的bug修复只花了五分钟,然后再利用”git checkout dev”命令行切回到dev分支继续干活,截图如下:

切换回dev分支以后利用”git status”命令行查看当前工作区的情况,发现当前的工作区中是干净的,截图如下:

然后利用”git stash list”命令行查看之前把dev分支上的内容存到了什么地方,截图如下:

从终端中可以看出,把之前的内容存到了stash里面了。有两种恢复的方式,第一种是利用”git stash apply”命令行进行恢复,但是恢复之后stash里面的内容不能删除,还需要再利用"git stash drop”命令行来删除。另一种方式是利用”git stash pop”命令行在恢复的同时把stash里面的内容也删除了,用第二种方式操作的截图如下:

然后再利用”git stash list”命令行进行查看就看不到stash里面的内容了,截图如下:

可以在终端中多次使用”git stash”命令行,然后在恢复的时候先用”git stash list”命令行进行查看,然后再利用”git stash apply stash@{指定数字}”命令行恢复指定的stash即可。

备注:

修复bug时,会通过创建新的bug分支来进行修复,然后合并,最后删除;

当手头的工作没有完成时,先把工作现场利用”git stash”命令行存储一下,然后去修复bug,修复完成后再利用”git stash pop”命令行回到工作现场继续之前的开发。

9、命令行方式下的git的Feature分支:

在软件开发中,总会有无穷无尽的新功能不断地添加进来。当每添加一个新功能时,肯定不希望因为一些实验性质的代码而把主分支搞乱了,所以当每添加一个新功能时,最好新创建一个feature分支,在上面进行开发,待开发完成后再进行合并,最后删除该feature分支即可。

       现在开发名为”Vulcan”的新功能。利用”git checkout -b 分支名称”命令行创建并切换到一个新的分支上,截图如下:

然后打开feature-vulcan分支上的本地的工作区中的readme.rtf文件,修改其中的内容,然后利用"git add 文件名称”命令行和"git commit -m “提交说明””命令行把文件的修改提交到本地的版本库中的feature-vulcan分支上,截图如下:

然后利用”git checkout dev”命令行切回dev分支准备合并,截图如下:

一切顺利的话,feature分支和bug分支是类似的,合并,然后删除就可以了,但是此时接到上级的命令,由于某种原因新功能必须取消。虽然白干了,但是这个包含机密资料的分支还是必须就地销毁,利用”git branch -d feature-vulcan”命令行把feature-vulcan分支删除,截图如下:

销毁失败,原因是feature-vulcan分支还没有被合并了,如果删除将丢失掉修改,如果要强行删除,就需要使用大写的-D参数,即”git branch -Dfeature-vulcan”命令行,截图如下:

备注:

开发一个新的功能,最好新建一个feature分支;

如果要丢弃一个没有被合并过的分支,可以通过”git branch -D <name>”命令行强行进行删除。

10、命令行方式下的git的多人协作:

       当从远程仓库克隆时,实际上git自动把本地的master分支和远程的master分支对应起来了,并且远程仓库的默认名称是origin。

       利用”git remote”命令行查看远程仓库的信息,截图如下:

还可以利用”git remote -v”命令行查看更为详细的远程仓库的信息,截图如下:

上面显示了可以抓取和推送的origin的地址。如果没有推送权限,就看不到push的地址了。

推送分支:

推送分支就是把该分支上的所有的本地提交推送到远程库中。可以利用”git pushorigin分支名称”命令行把指定本地的分支推送到远程分支上,截图如下:

但是,并不是一定要把所有的本地分支都往远程进行推送,比如:

(1)master分支是主分支,因此要时刻与远程进行同步;

(2)dev分支是开发分支,团队所有成员都需要在上面进行工作,所以也需要与远程进行同步;

(3)bug分支只用于在本地修复bug,就没必要推送到远程了;

(4)feature分支是否推到远程要取决于实际的情况。

现在模拟项目中的另一个开发者在他的电脑中克隆远程仓库(在同一台电脑下的另一个目录中对远程仓库进行克隆)。可以使用”git clone 远程仓库地址”命令行实现上述的目的,截图如下:

当另外的开发者从远程仓库克隆时,默认情况下只能克隆到远程仓库的master分支,利用”git branch”命令行进行查看,截图如下:

现在,另外的开发者要在dev分支上进行开发,就必须创建本地的版本库中的dev分支和远程仓库origin的dev分支,并且把它们两个进行关联,可以利用”git checkout -b dev origin/dev”命令行达到上述的目的,截图如下:

现在这个开发者就可以在本地的版本库中的dev分支上进行文件的修改,然后把这个修改push到远程仓库origin的dev分支上了。

在本地的工作区中创建一个名为"clone test.docx”的新的文件,然后使用"git add 文件名称”命令行把这个新创建的文件从本地的工作区中添加到本地的版本库中的暂存区中,然后再使用"git commit -m “提交说明””命令行把这个文件添加到本地的版本库中的dev分支上,截图如下:

然后再利用”git push origin dev”命令行把本地的文件推送到远程仓库origin的dev分支上,截图如下:

再看此时相应的github上,多了一个dev分支了,截图如下:

另一个开发者已经向远程仓库origin的dev分支上推送了他的提交,而碰巧自己在自己电脑上的工作区中(dev分支)对文件也进行了修改,并提交到了本地的版本库中的dev分支上了,然后将进一步提交到远程仓库origin的dev分支上,截图如下:

从截图中可以看出推送失败了,因为之前另一个开发者推送到远程仓库的提交和你试图推送的提交有冲突。解决办法如下:

使用"git pullorigin/dev”命令行把远程仓库origin中的dev分支上的最新的文件提交拉取下来,截图如下:

从截图中的”There is no tracking information for the current branch.”语句可以看出拉取失败,原因是没有指定本地dev分支与远程origin/dev分支的链接。利用”git branch--set-upstream-to=origin/dev dev”命令行把本地dev分支与远程origin/dev分支进行连接,截图如下:

然后再使用”git pull”命令行进行拉取,截图如下:

拉取成功。如果拉取的时候有冲突的话需要先手动解决冲突,解决完冲突后再利用"git add 文件名称”命令行把本地工作区中的文件添加到本地的版本库中的暂存区中,然后再利用"git commit -m “提交说明””命令行把文件提交到本地的版本库中的dev分支上,最后再push到远程仓库origin中的dev分支上。

总结:

多人协作的工作模式通常是这样的:

(1)首先,可以使用”git push origin <branch-name>”命令行把本地的版本库中的相应的分支中的内容推送到远程仓库origin中的对应的分支上;

(2)如果推送失败,原因是远程仓库的分支比本地仓库的分支更新,需要先使用”git pull origin/分支名称”命令行把远程仓库中的内容拉取过来;

(3)如果在拉取的过程中发现有冲突的话则应该先在本地手动解决冲突,解决完之后再使用”git push origin <branch-name>”命令行推送到远程仓库中。

备注:

(1)利用”git remote -v”命令行查看更为详细的远程仓库的信息;

(2)在本地新建的分支如果不推送到远程的话其他人是不可见的;

(3)使用”git push origin<branch-name>”命令行把本地的文件推送到远程仓库中;

(4)如果推送失败的话是因为远程仓库的分支比本地仓库的分支更新,应该使用”git pull origin/分支名称”命令行把远程仓库中的内容拉取过来,如果发现有冲突的话则应该在本地手动解决冲突,然后再利用”git push origin <branch-name>”命令行推送到远程仓库中;

(5)如果git pull的时候提示no tracking information,则说明本地的分支和远程的分支的链接关系没有创建,需要用"git branch --set-upstream-to <branch-name> origin/<branch-name>”命令行先创建链接,然后再使用”git pull”命令行从远程仓库中进行拉取。

11、命令行方式下的git的标签创建:

       首先要使用”git checkout 分支名称”命令行切换到需要打标签的分支上,截图如下:


然后使用“git tag 标签名称”命令行给这个分支打一个标签,截图如下:

然后可以使用”git tag”命令行查看此分支上的所有标签,截图如下:

默认标签是打在最新提交的commit上的,但有时候忘了打标签,比如现在已经是周五了,但应该在周一打的标签没有打,如果想在再打周一的标签的话则首先应该使用”git log --pretty=oneline --abbrev-commit”命令行来查看文件的修改记录,截图如下:

然后对想要打标签的版本打标签。比如希望给”creat test.rtf"操作打标签,找到它对应的版本号”966273f”,然后使用”git tag 标签名称 版本号”命令行打标签,截图如下:

再使用”git tag”命令行查看标签,截图如下:

标签不是按时间顺序列出的,而是按字母排序的。

然后可以使用”git show 标签名称”来查看标签的信息,截图如下:

还可以使用"git tag -a 标签名称-m“说明文字”指定的版本号”命令行来创建带有说明的标签,命令行中的-a用来指定标签名,-m用来指定说明文字,截图如下:

然后可以使用”git show 标签名称”命令行来查看说明文字,截图如下:

备注:

(1)”git tag 标签名称”命令行用于新建一个标签,默认为HEAD,也可以指定一个commit id;

(2)"git tag -a 标签名称-m“说明文字”指定的版本号”命令行来创建带有说明的标签;

(3)”git tag”命令行用来查看所有的标签。

12、命令行方式下的git的操作标签:

如果标签打错了可以使用”git tag -d 标签名称”命令行来删除这个标签,截图如下:

因为创建的标签都只存储在本地,不会自动推送到远程,所以打错的标签可以在本地安全地删除。

可以使用”git push origin 标签名称”命令行把本地的标签推送到远程仓库,截图如下:

或者使用”git push origin—tags”命令行一次性地把本地标签都推送到远程仓库中:

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:

然后从远程删除。删除命令也是push,但是格式如下:

备注:

(1)”git push origin 标签名称”命令行可以把本地的标签推送到远程仓库;

(2)”git push origin --tags”命令行可以推送全部未推送过的本地标签到远程仓库;

(3)”git tag -d 标签名称”命令行来删除一个本地标签;

(4)”git push origin :refs/tags/标签名称”来删除一个远程仓库中的标签。

13、如何使用Github:

       我们一直用GitHub作为免费的远程仓库,如果是个人的开源项目,放到GitHub上是完全没有问题的。其实GitHub还是一个开源协作社区,通过GitHub既可以让别人参与你的开源项目,同时也可以参与别人的开源项目。

       在GitHub出现以前,开源项目开源容易,但让广大开发者参与进来就是比较困难的事情了。因为要参与,就要提交代码,而给每个想提交代码的开发者都开一个账号那是不现实的,因此开发者也仅限于报个bug,即使能改掉bug,也只能把diff文件用邮件发过去,很不方便。但是在GitHub上,利用git极其强大的克隆和分支功能真正实现了广大开发者自由参与各种开源项目的目的了。

       如果想在GitHub上为某个库修复一个bug或者增加一个新功能,那就要先fork那个库到自己的GitHub上,实际上是给原来的那个库增加了一个新分支,然后从自己的GitHub(远程库)中把项目克隆到本地,然后在本地的工作区中给这个项目增加一个新文件,然后再把这个项目由本地的工作区中添加到本地的版本库中的暂存区中,然后再把这个项目提交到本地的版本库中的master分支中,最后再push到自己的GitHub(远程库)中。如果希望原来的官方库能够接受自己的修改,就要在自己的GitHub上发起一个pull request请求,当然对方是否接受请求就不一定了。

       下面以fork廖雪峰的GitHub上的一个库"https://github.com/michaelliao/learngit”为例进行说明,截图如下:

对应的本地的工作区中的截图如下:

提交完pull request请求之后,在原库的GitHub上就显示如下的截图:

总结:

(1)在GitHub上,可以任意fork开源仓库;

(2)自己拥有fork后的仓库的读写权限;

(3)可以推送pull request给官方仓库来贡献代码。

14、如何使用码云:

       中国大陆用户在使用GitHub时由于国内防火墙的原因,用户经常会遇到访问速度太慢的问题,有时候还会出现无法连接的情况。如果想在国内环境下正常使用git的话可以使用国内的git托管服务“码云”。

       和GitHub相比,码云也提供了免费的git仓库。此外还集成了代码的质量检测、项目演示等功能。对于团队协作开发,码云还提供了项目管理、代码托管、文档管理的服务,5人以下小团队免费。

       如果我们已经有了一个本地的git仓库(例如,一个名为mayun的本地库),可以按照如下的步骤把它关联到码云的远程库中。首先,在码云上创建一个新的仓库,如图所示:

在填写相关信息后,点击“创建”按钮来正式创建码云的远程库,创建成功后截图如下:

然后在本地的终端中利用”cd 本地的工作区的地址”命令行打开本地的工作区,然后再利用”git init”命令行在本地的工作区中创建版本库,然后在本地的工作区中创建一个”test.docx”文件,截图如下:

然后利用"git add 文件名称”命令行把文件由本地的工作区添加到本地的版本库中的暂存区中,然后再利用"git commit -m “提交说明””命令行把文件由本地的版本库中的暂存区中提交到本地的版本库中的master分支上,截图如下:

然后再利用"git remote add origin 码云远程仓库地址”命令行和”git push -u origin master"命令行,把本地仓库和远程仓库进行关联并把本地仓库中的master分支上的内容推送到远程仓库中的master分支上,截图如下:

可以看到当推送的时候会报错,这是因为码云中的README.md文件不在本地代码的目录中。可以在终端中运行”git pull --rebase origin master”命令行来解决这个问题,然后再运行”git push -u origin master”命令行把本地的版本库中的master分支上的内容上传到码云的远程仓库中的master分支中,截图如下:

再看码云远程库中,已经出现了在本地新添加的文件了,说明push成功,截图如下:

利用"git remote -v”命令行查看远程库信息,截图如下:

备注:一个本地库既可以关联码云又可以关联Github,只不过要把两个远程库的名字做区别。

15、命令行方式下的git的配置别名:

       有的指令比较长,不便于记忆,可以在终端中使用"git config --globalalias.简化后的指令 简化前的指令”命令行来把指令进行简化。

       如果想用co表示checkout,ci表示commit,br表示branch,则可以在终端中运行如下的命令行:

$ git config --globalalias.co checkout

$ git config --globalalias.ci commit

$ git config --globalalias.br branch

上面命令行中的”--global”代表的是是全局参数,也就是说这些指令在这台电脑的所有Git仓库下都有用。

通过上面的修改,以后再提交时就可以简写成如下的命令行了:

$git ci -m"bala bala bala..."

在“3、命令行方式下的git的文件撤销修改”一节中可以知道,”git reset HEAD file”命令行可以把暂存区的修改撤销掉(unstage),重新放回到工作区中。既然是一个unstage操作,就可以用如下的命令行配置一个unstage别名:

$git config --globalalias.unstage'reset HEAD’

当你敲入命令行:

$git unstagetest.py

实际上git执行的是:

$ git resetHEADtest.py

       配置一个"git last"指令,让其显示最后一次提交的信息:

$git config --globalalias.last'log -1’

这样,用git last就能显示最近一次的提交了:

在配置git的时候,加上”--global”参数代表的是这些指令在这台电脑的所有Git仓库下都有用,如果不加,那只针对当前的仓库起作用。

每个仓库的git配置文件都放在本地的版本库中的config文件中,截图如下:

在终端中用命令行打开config文件,如下图所示:

别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容