GIT开发模型文档整理

原文推荐:

A successful Git branching model

这个文章讲的是Git分支模型的原理及规范,充分理解会对于分支管理起到很大帮助的。下面是我在学习这篇文章的同时做的一些简单翻译,仅供参考。


                   A successful Git branching model

In this post I presentthe development model that I’ve introduced for some of my projects (both atwork and private) about a year ago, and which has turned out to be verysuccessful. I’ve been meaning to write about it for a while now, but I’ve neverreally found the time to do so thoroughly, until now. I won’t talk about any ofthe projects’ details, merely about the branching strategy and release management.

这篇文章里面我将会介绍我这里一年来项目(包含工作上的和私人的)中使用的的开发模型,并且这些模型在后面被证实非常成功。我一直打算把这些内容写下来,但直到最近我才有时间好好整理这些内容。这里我不会说项目中的细节,更多的内容集中在分支策略和发布管理。

完整的分支流程图

Why git?

为什么是GIT

For a thoroughdiscussion on the pros and cons of Git compared to centralized source codecontrol systems, see the web. There are plenty of flame wars going on there. Asa developer, I prefer Git above all other tools around today. Git reallychanged the way developers think of merging and branching. From the classicCVS/Subversion world I came from, merging/branching has always been considereda bit scary (“beware of merge conflicts, they bite you!”) and something youonly do every once in a while.

在网上我们经常能看到有人讨论GIT相对于集中式源码版本控制系统的的优点和缺点,对于GIT和集中式版本控制系统网上有大量的争论。作为一个开发者,工具充斥的今天我更喜欢GIT。 Git真的改变了开发者对于分支和合并分支的想法。在传统的集中式版本控制系统/Subversion中,合并分支/创建分支会让人觉有有点可怕(当心合并分支带来的冲突,他们真的会咬你),而且我们对于他们的操作还不是特别多。

But with Git, theseactions are extremely cheap and simple, and they are considered one of the coreparts of your daily workflow, really. For example, in CVS/Subversion books,branching and merging is first discussed in the later chapters (for advancedusers), while in every Git book, it’s already covered in chapter 3 (basics).

但是有了Git,这些操作就变得非常廉价、简单,这是你日常工作流中优点中的一个,真的是这样。举个例子,在CVS/Subversion书中,创建分支和合并分支是在后续的章节才会被讨论的内容,而且这些内容仅仅是对高级用户,但是在Git的书中,它已经在基础内容的第三章中出现了。

As a consequence ofits simplicity and repetitive nature, branching and merging are no longersomething to be afraid of. Version control tools are supposed to assist inbranching/merging more than anything else.

由于它的简单性和重复性,创建分支和合并分支不再是让人害怕的事情。对于版本控制工具来说支持分支的创建和分支的合并更为重要。

Enough about thetools, let’s head onto the development model. The model that I’m going topresent here is essentially no more than a set of procedures that every teammember has to follow in order to come to a managed software developmentprocess.

GIT的内容说的也挺多了,接下来我们开始进入说这个开发模型的事情。我即将说的开发模型本质上来说不过是每个团队都会遵循的一系列流程,从而达到管理软件开发流程这个目的。


Decentralized but centralized

分散且集中

The repository setup that we use andthat works well with this branching model, is that with a central “truth” repo.Note that this repo is only considered to be the central one (since Git is aDVCS, there is no such thing as a central repo at a technical level). We willrefer to this repo as origin, since this name is familiar to all Git users.

仓库设有一个中央“真实”库,我们可以加以使用并同时使用分支模型起到一个不错的效果。直的注意的是,这个库是唯一被当做中央仓库的(因为Git是一个DVCS Decentralized Version Control System,在技术层面来说Git没有中央仓库这样的说法,这是和传统的集中式版本控制系统最大的不同)我们倾向于称这个库为origin,因此所有git用户都是那么熟悉origin这个名字。

每个成员的分支操作是灵活的哦

Each developer pullsand pushes to origin. But besides the centralized push-pull relationships, eachdeveloper may also pull changes from other peers to form sub teams. Forexample, this might be useful to work together with two or more developers on abig new feature, before pushing the work in progress to origin prematurely. Inthe figure above, there are subteams of Alice and Bob, Alice and David, andClair and David.

每个开发人员都可以对origin进行push和pull,但是除了集中式的push-pull关系,他们也都可以从其他同伴那里拉取到内容变化从而形成一个子团队。举个栗子,对于两个或更多开发者的团队在开发一个大功能时来说,向origin推送代码之前,提前这样整合会很有用的。在上面的图表中,有三个子团队:Alice和Bob,Alice和David,Clair和David

Technically, this means nothing more than that Alice has defined a Gitremote, named bob, pointing to Bob’s repository, and vice versa.

技术上来讲,这仅仅意味着Alice定义了名字为bob的Git远程分支,指向Bob的仓库,反之亦然。


The main branches

主分支

At the core, thedevelopment model is greatly inspired by existing models out there. The centralrepo holds two main branches with an infinite lifetime:

开发模型核心的灵感很大程度上是来源于已存在的模型。中央仓库持有两条无限生命周期的主分支:

Master

 主分支

Develop

 开发分支

主分支和开发分支

The master branch atorigin should be familiar to every Git user. Parallel to the master branch,another branch exists called develop.

所有的Git用户应该都有了解到master分支。和master分支平行的另一条分支就是develop。

We considerorigin/master to be the main branch where the source code of HEAD alwaysreflects a production-ready state.

我们认为在HEAD指向master分支是当前版本已就绪状态,这个时候origin/master作为主分支。

We considerorigin/develop to be the main branch where the source code of HEAD alwaysreflects a state with the latest delivered development changes for the nextrelease. Some would call this the “integration branch”. This is where anyautomatic nightly builds are built from.

我们认为HEAD指向develop分支是最新开发内容(下一个版本),这个时候origin/develop作为主分支。一些人称这个分支为集成分支。这就是自动隔夜构建的地方。

When the source codein the develop branch reaches a stable point and is ready to be released, allof the changes should be merged back into master somehow and then tagged with arelease number. How this is done in detail will be discussed further on.

当在develop分支的源码到达一个稳定的节点——将要发布的时候,所有的改变都将合并回master分支,然后用一个版本号标记。如何做到合并我们将会在下面详细描述。

Therefore, each timewhen changes are merged back into master, this is a new production release bydefinition. We tend to be very strict at this, so that theoretically, we coulduse a Git hook script to automatically build and roll-out our software to ourproduction servers everytime there was a commit on master.

因此,当每次把改变合并到master时,根据定义,这会是一个新的项目版本。我们尝试对此更加严格,所以从理论上讲,每有一个在master的提交,我们可以使用一个Git脚本自动构建并将我们的软件部署到我们的生产服务器上。


Supporting branches

支持分支

Next to the mainbranches master and develop, our development model uses a variety of supportingbranches to aid parallel development between team members, ease tracking offeatures, prepare for production releases and to assist in quickly fixing liveproduction problems. Unlike the main branches, these branches always have alimited life time, since they will be removed eventually.

不仅仅是只有主分支master和develop,在团队成员间,我们开发模型可以使用许多支持的分支来协助平行开发、实现轻松跟踪、为产品发布做准备,并且支持快速修复现场产品问题。不像主分支那样,这些支持分支最终都将被移除——只有有限的生命周期。

The different types ofbranches we may use are:

我们可能会使用到的分支类型有:

Feature branches 功能分支Release branches 发布分支 Hotfix branches 热修复分支 Each of these branches have aspecific purpose and are bound to strict rules as to which branches may betheir originating branch and which branches must be their merge targets. Wewill walk through them in a minute.

每个分支都有一个特定的目的,并且要遵循严格的规则,即哪些分支可能是它们的原始分支,哪些分支必须是它们的合并目标。我们在下面的内容会了解一下它们。

By no means are thesebranches “special” from a technical perspective. The branch types are categorizedby how we use them. They are of course plain old Git branches.

从技术层面上看这些分支并没有“特殊”。这些分支类型是根据我们如何使用做的定义,当然它们都是普通且原始的Git分支。


Feature branches

功能分支

May branch off from:

develop

分支可能来自:develop

Must merge back into:

develop

一定合并到:develop

Branch namingconvention: anything except master, develop, release-, or hotfix-

分支命名约定:除了master,develop,release-,hotfix-之外任何东西


功能分支来源于开发分支

Feature branches (orsometimes called topic branches) are used to develop new features for theupcoming or a distant future release. When starting development of a feature,the target release in which this feature will be incorporated may well beunknown at that point. The essence of a feature branch is that it exists aslong as the feature is in development, but will eventually be merged back intodevelop (to definitely add the new feature to the upcoming release) ordiscarded (in case of a disappointing experiment).

功能分支(或是一些被称之为主题分支)是被用做为将来版本开发新功能。当开始开发一个新功能时,这个功能将会被合并到的目标版本在那个时候可能是未知的。功能分支的本质在于,只要功能在开发,它就存在,但是最终会被合并回develop(为了在即将发布的版本中添加新功能)或者丢弃(在不满意的情况下)。

Feature branchestypically exist in developer repos only, not in origin.

功能分支通常只存在于开发人员的repos,而不是在origin

Creating a feature branch

创建一个功能分支

When starting work ona new feature, branch off from the develop branch.

要开发新功能时,分支是来自develop分支

$ git checkout -b myfeature develop

Switched to a new branch"myfeature"

切换到了新分支“myfeature”

Incorporating a finished feature on develop

合并一个已完成的功能到develop上

Finished features maybe merged into the develop branch to definitely add them to the upcoming release:

完成的功能可以合并到develop分支中,从而可以明确地将它们添加到即将发布的版本中

$ git checkout develop

Switched to branch 'develop'

 $ git merge --no-ff myfeature

Updating ea1b82a..05e9557 (Summaryof changes)

$ git branch -d myfeature

Deleted branch myfeature (was05e9557).

$ git push origin develop

The --no-ff flagcauses the merge to always create a new commit object, even if the merge could be performed with a fast-forward. This avoids losing information about the historical existence of a feature branch and groups together all commits that together added the feature. Compare:

--no-ff标记可以让合并总是创建一个新的提交对象,虽然合并可能是快进的情况下执行。这可以避免丢失功能分支和组所有一起提交到该功能分支的历史信息。

--no-ff 可以保存历史信息

In the latter case, itis impossible to see from the Git history which of the commit objects togetherhave implemented a feature—you would have to manually read all the logmessages. Reverting a whole feature (i.e. a group of commits), is a trueheadache in the latter situation, whereas it is easily done if the --no-ff flagwas used.

在后种情况下,从Git历史中无法看出哪些提交对象一起实现了这个功能,你需要自己去读取所有的日志消息。返回一个完整的功能(即一组提交)后者情况是真的让人头痛,而如果使用--no-ff标记则很容易完成。

Yes, it will create afew more (empty) commit objects, but the gain is much bigger than the cost.

是的,这样会生成额外(空的)提交对象,但是收益比成本要大。


Release branches

发布分支

May branch off from:develop

分支可能来自:develop

Must merge back into:develop and master

一定合并到:develop和master

Branch namingconvention: release-*

分支命名约定:release-*

Release branchessupport preparation of a new production release. They allow for last-minutedotting of i’s and crossing t’s. Furthermore, they allow for minor bug fixesand preparing meta-data for a release (version number, build dates, etc.). Bydoing all of this work on a release branch, the develop branch is cleared toreceive features for the next big release.

release分支为新产品发布提供了准备。它们允许我们可以在最后一刻对我的或他的进行交叉处理。此外,它们允许小bug修复,并为发行版准备元数据(版本号、构建日期等)。通过在release分支上完成所有这些工作,develop分支将被清除以接收下一个大版本的功能。

The key moment to branch off a new release branch from develop is when

develop (almost) reflects the desired state of the new release. At least all features that aretargeted for the release-to-be-built must be merged in to develop at this pointin time. All features targeted at future releases may not—they must wait untilafter the release branch is branched off.

新发布分支的关键时刻是当开发(几乎)反映新版本的期望状态时,至少对于所有版本构建的功能在这个点要及时合并。所有出现在未来版本的功能或许都不需要等到release分支进行分支之后才去执行。

It is exactly at thestart of a release branch that the upcoming release gets assigned a versionnumber—not any earlier. Up until that moment, the develop branch reflected changesfor the “next release”, but it is unclear whether that “next release” willeventually become 0.3 or 1.0, until the release branch is started. Thatdecision is made on the start of the release branch and is carried out by theproject’s rules on version number bumping.

确切的说,在release分支一开始的时候,这个即将发布的版本会被分配一个版本号,而不是之前的版本号。直到那一刻,release分支反映了“下一个版本”的变化,但不清楚“下一个版本”最终会变成0.3还是1.0,直到release分支开始。这个决定是在release分支一开始时做出的,并且是由项目的版本号的规则执行的。

Creating a release branch

创建一个release分支

Release branches arecreated from the develop branch. For example, say version 1.1.5 is the currentproduction release and we have a big release coming up. The state of develop isready for the “next release” and we have decided that this will become version1.2 (rather than 1.1.6 or 2.0). So we branch off and give the release branch aname reflecting the new version number:

release分支是从develop分支上创建的。例如,假设版本1.1.5是当前的产品版本,我们要提出一个大版本。develop的已经为“下个版本”做好了准备,我们也已经定好下一个版本号为1.2(而不是1.1.6或者2.0)。所以我们进行分支并给出可以反映当前版本好的版本分支名:

$ git checkout -b release-1.2develop

Switched to a new branch"release-1.2"

$ ./bump-version.sh 1.2 Filesmodified successfully, version bumped to 1.2.

$ git commit -a -m "Bumpedversion number to 1.2"

[release-1.2 74d9424] Bumpedversion number to 1.2 1 files changed, 1 insertions(+), 1 deletions(-)

After creating a newbranch and switching to it, we bump the version number. Here, bump-version.shis a fictional shell script that changes some files in the working copy toreflect the new version. (This can of course be a manual change—the point beingthat some files change.) Then, the bumped version number is committed.

创建一个新的分支并切换到该分支之后,我们会看到版本号。这里,bump-version.sh是一个虚拟的命令脚本,可以修改一些文件的工作副本以反映新版本(当然我们也可以手动的修改——是指一些文件改动)。随后版本号就被提交了。

This new branch mayexist there for a while, until the release may be rolled out definitely. Duringthat time, bug fixes may be applied in this branch (rather than on the developbranch). Adding large new features here is strictly prohibited. They must bemerged into develop, and therefore, wait for the next big release.

这个新分支或许可能存在一段时间,直到确定的发行版出现。在此期间,bug修复会应用于分支上(而不是在develop分支上)。在这里添加大的新功能是严格禁止的。它们必须被合并到develop,因此等待下个发布版吧。

Finishing a release branch

完成一个release分支

When the state of therelease branch is ready to become a real release, some actions need to becarried out. First, the release branch is merged into master (since everycommit on master is a new release by definition, remember). Next, that commiton master must be tagged for easy future reference to this historical version.Finally, the changes made on the release branch need to be merged back intodevelop, so that future releases also contain these bug fixes.

当release分支的状态已经准备好成为一个真正的发布时,我们需要执行一些操作。首先,release分支被合并到master(因为根据定义每个在master上的提交都是一个新的版本,请记住这点).然后,在master上的提交务必要打上tag标记,这样可以便于在将来引用这个历史版本。最后,这些在发布分支上的更改都是需要被整合回develop的,这样将来的发布中也是可以包含这些bug修复。

The first two steps in Git:

 Git中前两步:

$ git checkout master

Switched to branch 'master'

$ git merge --no-ff release-1.2 Mergemade by recursive.

(Summary of changes)

$ git tag -a 1.2

The release is nowdone, and tagged for future reference.

现在已经完成了发布,并为将来的引用做了标记。

Edit: You might aswell want to use the -s or -u  flags tosign your tag cryptographically.

编辑:你也可能想要用-s或者-u标志来秘密地标记你的标记。

To keep the changesmade in the release branch, we need to merge those back into develop, though.In Git:

为了保存release分支中所做的更改,我们需要将这些更改合并到develop中。在Git中:

$ git checkout develop

Switched to branch 'develop'

$ git merge --no-ff release-1.2

Merge made by recursive.

(Summary of changes)

This step may welllead to a merge conflict (probably even, since we have changed the versionnumber). If so, fix it and commit.

这个步骤能够很好地引导合并冲突(可能不常见,因为我们已经改了版本号)如果这样的话,就修复并提交。

Now we are really doneand the release branch may be removed, since we don’t need it anymore:

现在我们已经完成了,这个发布分支或许就要被移除掉了,因为我们不在需要它了:

$ git branch -d release-1.2

Deleted branch release-1.2 (wasff452fe).


Hotfix branches

热修复分支

May branch off from: master

分支可能来自:master

Must merge back into:develop and master

一定合并到:develop和master

Branch namingconvention: hotfix-*

分支命名约定:hotfix-*

即时修复分支

Hotfix branches arevery much like release branches in that they are also meant to prepare for anew production release, albeit unplanned. They arise from the necessity to actimmediately upon an undesired state of a live production version. When acritical bug in a production version must be resolved immediately, a hotfixbranch may be branched off from the corresponding tag on the master branch thatmarks the production version.

Hotfix分支非常类似与发布分支,它们也是为了准备一个新的产品发布,尽管是没有计划的。当产品出现突发情况,它们就需要开始执行。当产品版本中的一个关键错误需要立即解决时,一个Hotfix分支可以从标记产品版本的主分支上的相应标记点分支出来。

The essence is thatwork of team members (on the develop branch) can continue, while another personis preparing a quick production fix.

核心在于当一个人在修复产品问题是,团队成员(在develop分支)的工作可以继续执行。

Creating the hotfix branch

创建hotfix分支

Hotfix branches arecreated from the master branch. For example, say version 1.2 is the currentproduction release running live and causing troubles due to a severe bug. Butchanges on develop are yet unstable. We may then branch off a hotfix branch andstart fixing the problem:

Hotfix分支是从master分支上创建的。举个栗子,假设当前实时运行的产品发布版是1.2,一个服务bug导致出现了问题。但是在develop上更改仍然是不稳定,我们就可以分支出一个hotfix分支然后开始修复问题:

$ git checkout -b hotfix-1.2.1master Switched to a new branch "hotfix-1.2.1"

$ ./bump-version.sh 1.2.1 Filesmodified successfully, version bumped to 1.2.1.

$ git commit -a -m "Bumpedversion number to 1.2.1" [hotfix-1.2.1 41e61bb]

Bumped version number to 1.2.1 1files changed, 1 insertions(+), 1 deletions(-)

Don’t forget to bumpthe version number after branching off!

不要忘了分支后注上版本号。

Then, fix the bug andcommit the fix in one or more separate commits.

然后,修复bug并在一个或多个单独提交中提交修复程序。

$ git commit -m "Fixed severeproduction problem"

[hotfix-1.2.1 abbe5d6] Fixed severeproduction problem 5 files changed, 32 insertions(+), 17 deletions(-)

Finishing a hotfix branch

完成一个hotfix分支

When finished, thebugfix needs to be merged back into master, but also needs to be merged backinto develop, in order to safeguard that the bugfix is included in the nextrelease as well. This is completely similar to how release branches arefinished.

当完成时,bugfix过的需要重新合并到master中,同时也需要重新合并到develop中,以确保在下一个版本中包含bugfix。这完全类似于如何完成release分支。

First, update master and tag therelease. 首先,更新master并标记发布。

$ git checkout master

Switched to branch 'master'

$ git merge --no-ff hotfix-1.2.1Merge made by recursive.

(Summary of changes)

$ git tag -a 1.2.1

 Edit: You might as well want to use the -s or-u  flags to sign your tagcryptographically.

编辑:你也可能想要用-s或者-u标志来秘密地标记你的标记。

Next, include thebugfix in develop, too:

接下来,把bugfix也包括到develop中:

$ git checkout develop

Switched to branch 'develop'

$ git merge --no-ff hotfix-1.2.1Merge made by recursive.

(Summary of changes)

The one exception tothe rule here is that, when a release branch currently exists, the hotfixchanges need to be merged into that release branch, instead of develop.Back-merging the bugfix into the release branch will eventually result in thebugfix being merged into develop too, when the release branch is finished. (Ifwork in develop immediately requires this bugfix and cannot wait for therelease branch to be finished, you may safely merge the bugfix into develop nowalready as well.)

有个例外是,当一个release分支当前存在时,hotfix的更改需要合并到这个release分支中,而不是develop分支。当develop分支完成时,bugfix合并到release分支中,同样可以使bugfix被合并到develop中。(如果develop中的工作立即要求这个bugfix,并且不能等待release分支完成,那么您现在已经可以安全地将bugfix合并到develop中了。)

Finally, remove thetemporary branch:

最后,移除暂时分支:

$ git branch -d hotfix-1.2.1

Deleted branch hotfix-1.2.1 (was abbe5d6).


Summary

总结

While there is nothingreally shocking new to this branching model, the “big picture” figure that thispost began with has turned out to be tremendously useful in our projects. Itforms an elegant mental model that is easy to comprehend and allows teammembers to develop a shared understanding of the branching and releasingprocesses.

虽然对于分支模型来说,没有什么震惊的新东西,但这篇文章开篇提到的“big picture”图片在我们的项目中是非常有用的。它形成了一种良好的思维模式,易于理解,并且让团队成员对分支和发布流程有共同的理解。


Ending..

如果看起来不是很方便的话,可以留下联系方式,还留有word的呢,这里面貌似不能上传附件。

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

推荐阅读更多精彩内容