Branches

Cloud CMS stands apart from just about every other content management product on the market in that it offers a versioning model that is based around changesets. It is inspired by Git and Mercurial which are popular source-control products within the software development world. As such, Cloud CMS provides a changeset versioning system that captures every touch - every create, update or delete - that you make.

Nothing is ever lost. Every data operation is fully transactional with every transaction payload is fully captured into its own changesets. Transactions span multiple documents and full "units of work" are written into the changeset history with every operation.

In the end, the changeset history is fully descriptive of every operation and data change that has occurred. This allows you to snapshot the repository at any moment in time. Or to roll back or forward to a specific moment in time.

This "Copy on Write" approach ensures that you're always able to make changes and restore a previous state should something go wrong. It allows you to try things out, see how they look and then approve them for go live or roll back in case you don't like it turned out. It is also the mechanism by which you can create vast updates, stage them for release and then push them live at a certain point in time.

Branches

Similar to Git or Mercurial, Cloud CMS offers branches so that you may maintain multiple, concurrent workspaces within which content is being worked on. Every repository has a master branch. A new branch is created by forking the master branch.

You can create as many branches as you like (by forking a new branch off from a previous branch). For all practical purposes, you can think of a branch as a copy of the branch from which it is forked. If you fork the master branch, then your new branch is basically a copy of the master branch. Under the hood, the implementation is optimized so that there isn't a literal copy of stuff until you touch something. But in essence, it is the same idea.

Once you have a branch, you can work on it at your own pace. The branch is effectively a copy and so if you make changes to things on it, you won't affect others. A change to a piece of content on a branch doesn't affect the original content on the original branch.

So you're free to make changes and try things out. If you don't like the way things are going, you can simply discard the branch. This tears down the branch and throws it away. Or, if you have something on the branch that you think might be interesting some day, you can keep it around. For example, you could freeze the branch to prevent others from making changes and then simply switch to another branch to keep on working.

Branches effectively provide expendable workspaces where you can build things at your own pace. Once you have something that you're happy with, you can merge the content. You can either merge the content back to the branch from which you forked, which is the most common scenario, you can merge the content to a completely different branch. This is sometimes referred to as pushing and pulling content between branches.

Tip View

Every branch that you create automatically maintains a "tip view" of the content in that branch within Mongo DB and Elastic Search. The "tip view" contains all of the content as seen from the tip (where all deletes are actually removed, updates write over creates and so on).

At the same time, branches also fully capture every create, update and delete is retained in a master record. This master record can then be used at any time to reconstruct the state of the branch at any moment in time.

Using these "tip views", Cloud CMS provides support for Elastic Search and MongoDB on a per-branch basis. Each branch maintains its own managed indexes to support these. Thus, it is possible and very common within Cloud CMS to have two branches that yield completely different search or query results. In fact, this should very much be the case if you're using branches in any kind of interesting way.

Thus, Cloud CMS makes sure that branches are moving targets. You can make changes to them and Cloud CMS maintains all of the indexes and things behind the scenes so that they just work as expected. You just flip branch IDs in your API calls and you can run the same application logic against different sets of data.

Snapshots

A snapshot is a special kind of branch that is always frozen. You can create them at any time by identifying a changeset against which they should be created. A snapshot computes all of these indexes and everything else but locks itself against that specific changeset. It is used to capture a moment in time.

Snapshots are useful in two ways:

  • to provide your application with a "released" or "approved" view of your content. By creating a snapshot, you produce a frozen branch that can be used within your API calls to inspect the repository at that exact moment in time.
  • to improve performance of changeset history calculation for differencing during merge. Snapshots basically provide interleave frames that the algorithm can look to for a precomputed view of the world.

Aliases

Branches supports alias. Using an alias, you can provide an alternate way to reference your branch from your application. For example, you might have a live alias that you switch from one branch to another branch when you decide to go live with a new set of content.

Branch API

As noted, every repository has a master branch which is referred to as master. This is the branch that is used most often.

If you aren't taking advantage of branching within Cloud CMS, then chances are that you've been working against the master branch all this time without knowing it.

Read a Branch

From an API perspective, you can retrieve a repository branch like this:

GET /repositories/{repositoryId}/branches/{branchId}

Here is what the master branch might look like:

{
    "_doc": "b7ccbaccf8dd30db947b",
    "root": "0:root",
    "tip": "24:897ec2b2d22bf7ee5ad4",
    "title": "master",
    "type": "MASTER",
    "snapshot": false
}

Where the properties are these:

  • root - the changeset where the branch begins and is rooted against
  • tip - the latest changeset written to the branch
  • title - a name for the branch
  • type - either MASTER for the master branch or CUSTOM for any other branch
  • snapshot - whether the branch is a snapshot

List Branches

You can list of all of the branches for a repository like this:

GET /repositories/{repositoryId}/branches

Create a Branch (Fork)

To create a branch, you must fork at an existing changeset.

POST /repositories/{repositoryId}/branches?changeset={changesetId}

Archive a Branch

When you're done using a branch, you may opt to archive it. Archiving a branch will free up disk resources from both MongoDB and Elastic Search as it will tear down any runtime "tip views" that are being managed. That said, it won't cause any data loss -- the master record is never affected. You can restore a branch's "tip view" at any time.

POST /repositories/{repositoryId}/branches/{branchId}/archive

Note that you cannot perform data operations against an archived branch. Any API calls that seek to create, read, update, query or search content within an archived branch will not work. To do so, you will first need to unarchive the branch.

Unarchive a Branch

When you want to restore a branch's runtime "tip view", you can unarchive it. You must unarchive a branch before performing any operations that will create, read, update, query or search against the contents in a branch.

POST /repositories/{repositoryId}/branches/{branchId}/unarchive

Merge a Branch

You can start a merge of the contents of one branch into another like this:

POST /repositories/{repositoryId}/branches/{branchId}/merge/start?id={otherBranchId}

The merge process starts with the tips of both branches and then works backwards to find the changeset that they have in common. In Cloud CMS, since all branches (except the master branch) are required to have parent branches, there is always a changeset somewhere in the past that is a common ancestor.

It then computes the changeset history for both branches starting from this common ancestral changeset and walking forward to the tip changeset. These changeset histories are then compared and merged.

The merge is run as an asynchronous background job. This is because the merge may be very complex (for very long changeset histories) and may take an indeterminant amount of time. As such, when you start a merge, you will get back a JSON response like this:

{
    "_doc": "JOB ID"
}

Which provides the ID of the running job. You can then poll this job to check when it completes and when it does, you can inspect the job itself to see how the merge went.

Further Reading