Skip to main content
Version: 0.3.36

4. Branching Workflow

In this tutorial you take an article through draft, review and publish on branches, then tag the published state. Everything runs as SQL over HTTP, so you only need curl and a token.

What you'll learn

  • Creating a branch for editorial work
  • Writing on a branch in isolation
  • Reviewing what changed and merging it into main
  • Tagging the published revision and reading it back later

Prerequisites

A running server, a token, and a repository with a content workspace that accepts raisin:Page nodes. If you are starting fresh:

T=<your token>; H="Authorization: Bearer $T"; J="Content-Type: application/json"
curl -s -X POST localhost:8080/api/repositories -H "$H" -H "$J" -d '{"repo_id":"blog"}'
curl -s -X PUT localhost:8080/api/workspaces/blog/content -H "$H" -H "$J" \
-d '{"name":"content","allowed_node_types":["raisin:Folder","raisin:Page"],"allowed_root_node_types":["raisin:Folder","raisin:Page"]}'

A small helper keeps the examples short. It posts SQL to a branch (the second argument), defaulting to main:

sql() { curl -s -X POST "localhost:8080/api/sql/blog/${2:-main}" -H "$H" -H "$J" \
-d "{\"sql\":$(printf '%s' "$1" | python3 -c 'import json,sys;print(json.dumps(sys.stdin.read()))')}"; echo; }

Step 1: Publish a first article on main

sql "INSERT INTO 'content' (path, node_type, name, properties)
VALUES ('/hello', 'raisin:Page', 'hello', '{\"title\":\"Hello\"}'::jsonb)"
# {"columns":["affected_rows"],"rows":[{"affected_rows":1}],"row_count":1,...}

Step 2: Create a draft branch

sql "CREATE BRANCH 'draft/hello-rewrite' FROM 'main'"
# {"columns":["result"],"rows":[{"result":"Branch 'draft/hello-rewrite' created"}],...}

The branch is a copy of main at this moment: same nodes, same schema.

Step 3: Write on the branch

Address the branch in the URL. A slash in the name is URL-encoded:

sql "UPDATE 'content' SET properties = '{\"title\":\"Hello from the draft\"}'::jsonb WHERE path = '/hello'" draft%2Fhello-rewrite
sql "INSERT INTO 'content' (path, node_type, name, properties)
VALUES ('/hello-part-2', 'raisin:Page', 'hello-part-2', '{\"title\":\"Part two\"}'::jsonb)" draft%2Fhello-rewrite

main still shows the original:

sql "SELECT path, properties->>'title' AS title FROM 'content'"
# rows: [{"path":"/hello","title":"Hello"}]
sql "SELECT path, properties->>'title' AS title FROM 'content'" draft%2Fhello-rewrite
# rows: [{"path":"/hello","title":"Hello from the draft"},{"path":"/hello-part-2","title":"Part two"}]

Step 4: Review the change

The editor asks what the draft changes relative to main:

sql "SHOW DIVERGENCE 'draft/hello-rewrite' FROM 'main'"
# rows: [{"branch":"draft/hello-rewrite","base":"main","ahead":2,"behind":0,"common_ancestor":"1788719729588-0"}]

curl -s "localhost:8080/api/management/repositories/default/blog/branches/draft%2Fhello-rewrite/diff/main" -H "$H"
# {"common_ancestor":"1788719729588-0",
# "added":[{"node_id":"...","workspace":"content","path":"/hello-part-2","operation":"added"}],
# "modified":[{"node_id":"...","workspace":"content","path":"/hello","operation":"modified"}],
# "deleted":[]}

Step 5: Merge into main

sql "MERGE BRANCH 'draft/hello-rewrite' INTO 'main' MESSAGE 'Publish hello rewrite'"
# rows: [{"result":"Merge completed","revision":1788719765785,"fast_forward":false,"nodes_changed":2}]

sql "SELECT path, properties->>'title' AS title FROM 'content'"
# rows: [{"path":"/hello","title":"Hello from the draft"},{"path":"/hello-part-2","title":"Part two"}]

sql "DROP BRANCH 'draft/hello-rewrite'"

Had someone edited /hello on main while the draft was open, the merge would have failed with a conflict for that node. SHOW CONFLICTS FOR MERGE ... lists the two versions, and Merging Changes shows how to resolve them.

Step 6: Tag the release

Tags are created over the management API from a revision. Take the main head after the merge:

REV=$(curl -s localhost:8080/api/management/repositories/default/blog/branches/main/head -H "$H" | python3 -c 'import json,sys;print(json.load(sys.stdin)["revision"])')
curl -s -X POST localhost:8080/api/management/repositories/default/blog/tags -H "$H" -H "$J" \
-d "{\"name\":\"release-1\",\"revision\":\"$REV\",\"message\":\"First published rewrite\"}"
# {"name":"release-1","revision":"1788719765785-0","created_at":"...","created_by":"system","message":"First published rewrite","protected":false}

Step 7: Read the release later

Keep editing main:

sql "UPDATE 'content' SET properties = '{\"title\":\"Hello v3\"}'::jsonb WHERE path = '/hello'"

The tag still reads the released state, over REST or SQL:

curl -s "localhost:8080/api/repository/blog/main/rev/$REV/content/hello" -H "$H"
# {"path":"/hello","properties":{"title":"Hello from the draft"}, ...}

sql "SELECT path, properties->>'title' AS title FROM 'content' WHERE __revision = '$REV'"
# rows: [{"path":"/hello","title":"Hello from the draft"},{"path":"/hello-part-2","title":"Part two"}]

If the newest edit was a mistake, move main back to the tag. History is kept, so this can be undone the same way:

curl -s -X PUT localhost:8080/api/management/repositories/default/blog/branches/main/head \
-H "$H" -H "$J" -d "{\"revision\":\"$REV\"}"
# 204

Next steps