- First commit of a group of scripts
- Change a script, see it as U, commit again and read the history
- Hotfix: commit a single script, not the whole group
- Review: what changed between two commits
- Rollback: restore the previous version of a script
- Retire a web service: D in versioning
- Export a group to files and import it back
- Package a release folder with a manifest
- One deployment file: scripts and parameters
- Roll a whole group back to a release
First commit of a group of scripts
A project is a group of scripts. The release manager creates the group, grants the two web services to it, asks versioning what is new (I = inserted), commits the group with a version message, and checks that nothing is left to commit.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; #2. What does the group contain, what is new; -> "[members]" (string regexp_replace (group get script "tuto_cf_rel") "\s+" ""); -> "[before]" (version check_group "tuto_cf_rel"); #3. Commit and check again; -> "[id]" (version commit_group "tuto_cf_rel" "1.0.0: VAT and discount services"); -> "[after]" (version check_group "tuto_cf_rel"); #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat "members: " [members] [_n_] "before the commit:" [_n_] [before] "commit id: " [id] [_n_] "after the commit:" [_n_] [after] "(nothing left to commit)";
members: {"tuto.cf.price.vat.post":0,"tuto.cf.price.discount.post":0}
before the commit:
STATE : SCRIPT
I : tuto.cf.price.discount.post
I : tuto.cf.price.vat.post
commit id: 20261101172022_Gr3bEL_40P3Wl_CMT
after the commit:
STATE : SCRIPT
(nothing left to commit)- The commit id starts with the date and time: sort commits by name to get the history
version commit_grouprefuses an empty commit with "Sorry, no change detected.": catch it withtryin a release script
Change a script, see it as U, commit again and read the history
The VAT rate changes. The developer updates the web service, versioning flags it U (updated), the release manager commits version 1.1.0, reads the history of the group, newest first, with the user and the message of each commit, and finds the 1.0 commit with a filter.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; version commit_group "tuto_cf_rel" "1.0.0: first release"; #2. One second later, the VAT rate changes; wait 1000; script update "tuto.cf.price.vat.post" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT, overseas rate 8.5%" {bd* [ht] 1.085;} "The price with tax"; -> "[check]" (version check_group "tuto_cf_rel"); version commit_group "tuto_cf_rel" "1.1.0: overseas VAT rate"; #3. History, newest first, the listing starts with a 39 chars editor tag; -> "[history]" (string substring (version show_commits "tuto_cf_rel" ".*" 10) 39); -> "[nb]" (string regexp_count [history] "_CMT>"); #The filter is applied before nb_row: the newest 1.0 commit, even with nb_row 1; -> "[v10]" (string regexp_extract (version show_commits "tuto_cf_rel" "1\.0\..*" 1) "admin - ([^\n]+)" 1); #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat [check] [nb] " commits, newest 1.0 commit found with nb_row 1: " [v10] [_n_] [history];
STATE : SCRIPT
U : tuto.cf.price.vat.post
2 commits, newest 1.0 commit found with nb_row 1: 1.0.0: first release
COMMITS
<20261101175213_OBLsck_3JLtfL_CMT>
admin - 1.1.0: overseas VAT rate
version show_scriptnames tuto_cf_rel 20261101175213_OBLsck_3JLtfL_CMT;
version show_scripts tuto_cf_rel 20261101175213_OBLsck_3JLtfL_CMT;
<20261101175212_wqBhoI_aauFao_CMT>
admin - 1.0.0: first release
version show_scriptnames tuto_cf_rel 20261101175212_wqBhoI_aauFao_CMT;
version show_scripts tuto_cf_rel 20261101175212_wqBhoI_aauFao_CMT;- The filter is applied before nb_row:
version show_commits "tuto_cf_rel" "1\.0\..*" 1returns the newest 1.0 commit, even when newer commits exist. - The two lines under each commit are ready-to-run commands: copy them into the editor
Hotfix: commit a single script, not the whole group
Two scripts changed but only the VAT fix must ship today. version commit_script commits only the scripts whose name starts with a filter; the commit content is listed with show_scriptnames and check_group shows that the discount change is still waiting.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; version commit_group "tuto_cf_rel" "1.0.0: first release"; #2. Two changes are made; script update "tuto.cf.price.vat.post" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT, overseas rate 8.5%" {bd* [ht] 1.085;} "The price with tax"; script update "tuto.cf.price.discount.post" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 10% discount" {bd* [price] 0.90;} "The discounted price"; #3. Ship only the VAT script; -> "[id]" (version commit_script "tuto_cf_rel" "tuto.cf.price.vat" "1.0.1 hotfix: VAT only"); -> "[content]" (string substring (version show_scriptnames "tuto_cf_rel" [id]) 39); -> "[pending]" (version check_group "tuto_cf_rel"); #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat "content of the hotfix commit:" [_n_] [content] "still waiting for the next release:" [_n_] [pending];
content of the hotfix commit: <20261101172024_PjNzqB_WoCF2E_CMT> version show_script tuto_cf_rel 20261101172024_PjNzqB_WoCF2E_CMT tuto.cf.price.vat.post.mql; still waiting for the next release: STATE : SCRIPT U : tuto.cf.price.discount.post
- The filter is a prefix:
tuto.cf.pricewould commit both scripts - Name your scripts by module (orders.*, invoices.*) so that a prefix is a module
Review: what changed between two commits
Before approving a release, the reviewer compares the committed code of the VAT script in version 1.0.0 and 1.1.0. The script reads both versions with show_script, extracts the rate and the description with a regex, and counts the scripts of the whole commit with show_scripts.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; -> "[c1]" (version commit_group "tuto_cf_rel" "1.0.0: first release"); script update "tuto.cf.price.vat.post" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT, overseas rate 8.5%" {bd* [ht] 1.085;} "The price with tax"; -> "[c2]" (version commit_group "tuto_cf_rel" "1.1.0: overseas VAT rate"); #2. The committed code of the script in each version; -> "[v1]" (version show_script "tuto_cf_rel" [c1] "tuto.cf.price.vat.post.mql"); -> "[v2]" (version show_script "tuto_cf_rel" [c2] "tuto.cf.price.vat.post.mql"); #3. Extract the rate and the description; -> "[rate1]" (string regexp_extract [v1] "bd\* \S+ ([0-9.]+);" 1); -> "[rate2]" (string regexp_extract [v2] "bd\* \S+ ([0-9.]+);" 1); -> "[desc2]" (string regexp_extract [v2] "\"(Price with VAT[^\"]*)\"" 1); #4. How many scripts in each full commit; -> "[n1]" (string regexp_count (version show_scripts "tuto_cf_rel" [c1]) "script merge"); -> "[n2]" (string regexp_count (version show_scripts "tuto_cf_rel" [c2]) "script merge"); #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat "tuto.cf.price.vat.post: rate " [rate1] " -> " [rate2] [_n_] "new description: " [desc2] [_n_] "scripts in commit 1.0.0: " [n1] ", in commit 1.1.0: " [n2] " (only the changed one)";
tuto.cf.price.vat.post: rate 1.2 -> 1.085 new description: Price with VAT, overseas rate 8.5% scripts in commit 1.0.0: 2, in commit 1.1.0: 1 (only the changed one)
version show_scriptsreturnsscript mergecode: paste it on another server to install that exact version- Compare whole code with
string levenshtein_distanceto measure the size of a change
Rollback: restore the previous version of a script
Version 1.1.0 shipped a typo (rate 12 instead of 1.2) and prices are 10 times too high. The release manager finds the 1.0.0 commit by its message, replays its committed code with eval, checks the price is right again, and commits the rollback so the history tells the story.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; version commit_group "tuto_cf_rel" "1.0.0: first release"; script update "tuto.cf.price.vat.post" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 12;} "The price with tax"; version commit_group "tuto_cf_rel" "1.1.0: new VAT"; -> "[bad]" (execute "tuto.cf.price.vat.post" "[ht]" 100); #2. Find the commit 1.0.0 from its message; -> "[c1]" (string regexp_extract (version show_commits "tuto_cf_rel" "1\.0\.0.*" 10) "<(\w+_CMT)>" 1); #3. Replay its code, after the 39 chars editor tag; eval (string substring (version show_script "tuto_cf_rel" [c1] "tuto.cf.price.vat.post.mql") 39); -> "[good]" (execute "tuto.cf.price.vat.post" "[ht]" 100); -> "[state]" (version check_group "tuto_cf_rel"); version commit_group "tuto_cf_rel" "1.1.1: rollback of the VAT script to 1.0.0"; -> "[nb]" (string regexp_count (version show_commits "tuto_cf_rel" ".*" 10) "_CMT>"); #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat "price of 100 in 1.1.0: " [bad] [_n_] "after the restore: " [good] [_n_] "versioning sees: " [state] "commits in the history: " [nb];
price of 100 in 1.1.0: 1200 after the restore: 120.0 versioning sees: STATE : SCRIPT U : tuto.cf.price.vat.post commits in the history: 3
version show_scripts+evalrestores every script of a commit in one go- Keep the bad commit in the history: the rollback commit documents the incident
Retire a web service: D in versioning
The discount service is retired. Ungranting it from the group (or deleting it) makes versioning flag it D (deleted); the release manager commits the removal, then checks with show_scriptnames that the latest commit no longer ships it.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; version commit_group "tuto_cf_rel" "1.0.0: first release"; #2. Retire the discount service from the project; group ungrant script "tuto.cf.price.discount.post" "tuto_cf_rel"; -> "[granted]" (group is granted script "tuto.cf.price.discount.post" "tuto_cf_rel"); -> "[state]" (version check_group "tuto_cf_rel"); -> "[id]" (version commit_group "tuto_cf_rel" "2.0.0: discount service retired"); -> "[content]" (string substring (version show_scriptnames "tuto_cf_rel" [id]) 39); #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat "still granted: " [granted] [_n_] "versioning sees: " [state] "files written by the 2.0.0 commit: " (string regexp_count [content] "\.mql;");
still granted: 0 versioning sees: STATE : SCRIPT D : tuto.cf.price.discount.post files written by the 2.0.0 commit: 0
- A D commit writes no file: the removal is recorded in the commit and the script disappears from the group snapshot
- Use
group get scriptbefore and after to document the scope of a release
Export a group to files and import it back
To move a project to another server, script export_group writes one .mql file per script under data/scripts/<group>/ with a scripts.json index. The script exports, lists the files, deletes the scripts as if on an empty server, imports them back and calls the web service again.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; #2. Export the group; script export_group "tuto_cf_rel"; -> "[files]" (string regexp_replace (file dir_list "data/scripts/tuto_cf_rel") "\s+" ""); -> "[index]" (file load "data/scripts/tuto_cf_rel/scripts.json"); #3. Simulate an empty server; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; -> "[gone]" (script exist "tuto.cf.price.vat.post"); #4. Import and call; script import_group "tuto_cf_rel"; -> "[back]" (script exist "tuto.cf.price.vat.post"); -> "[price]" (execute "tuto.cf.price.vat.post" "[ht]" 100); #5. A deleted script comes back without its group: grant it again; -> "[in_group]" (group is granted script "tuto.cf.price.vat.post" "tuto_cf_rel"); group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; file delete "data/scripts/tuto_cf_rel"; #Cleanup: scripts and group; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; concat "exported files: " [files] [_n_] "index: " [index] "exists after delete: " [gone] ", after import: " [back] [_n_] "price of 100: " [price] [_n_] "in the group after import: " [in_group] " (granted again)";
exported files: ["tuto.cf.price.vat.post.mql","tuto.cf.price.discount.post.mql","scripts.json"] index: ["tuto.cf.price.vat.post","tuto.cf.price.discount.post"] exists after delete: 0, after import: 1 price of 100: 120.0 in the group after import: 0 (granted again)
- Each exported file starts with
if (script exist ...) {script delete ...}: importing twice is safe - Copy the data/scripts/<group> folder to the target server, then run
script import_groupthere
Package a release folder with a manifest
The ops team wants a folder per release. The script commits version 1.0.0, exports the group, copies the export into tmp/tuto_cf_release_1.0.0, writes a RELEASE.txt manifest with the commit id and the script list, and checks the content and size of the package.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; -> "[id]" (version commit_group "tuto_cf_rel" "1.0.0: first release"); #2. Export and copy into a release folder; script export_group "tuto_cf_rel"; -> "[pkg]" "tmp/tuto_cf_release_1.0.0"; file copy_dir "data/scripts/tuto_cf_rel" [pkg]; #3. The manifest; file create (concat [pkg] "/RELEASE.txt") (concat "release=1.0.0" [_n_] "commit=" [id] [_n_] "scripts=" (file load (concat [pkg] "/scripts.json")) [_n_] "install=script import_group tuto_cf_rel"); -> "[content]" (string regexp_replace (file dir_list [pkg]) "\s+" ""); -> "[lines]" (file count_lines (concat [pkg] "/RELEASE.txt")); -> "[size]" (> (file size [pkg]) 0); -> "[manifest]" (file load (concat [pkg] "/RELEASE.txt")); file delete [pkg]; file delete "data/scripts/tuto_cf_rel"; #Cleanup: scripts, group and the commits folder; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; file delete "commits/tuto_cf_rel"; concat "package: " [content] [_n_] "manifest lines: " [lines] ", package not empty: " [size] [_n_] [manifest];
package: ["tuto.cf.price.vat.post.mql","tuto.cf.price.discount.post.mql","RELEASE.txt","scripts.json"] manifest lines: 5, package not empty: 1 release=1.0.0 commit=20261101172024_LnUoxS_EP5Ndu_CMT scripts=["tuto.cf.price.vat.post","tuto.cf.price.discount.post"] install=script import_group tuto_cf_rel
- Zip the folder before sending it with
compress zip - Add the parameters of the release with
parameter generate mergeinto the package
One deployment file: scripts and parameters
For a server without access to the export folder, the release manager builds a single .mql file: the script merge code of every script of the group, generated with script generate merge, plus the merge of the parameter the scripts use. The file is checked and its first lines shown.
#1. A group with two web services; group add "tuto_cf_rel"; script create post "tuto.cf.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.cf.price.discount" false 1 (param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");) "Price after a 5% discount" {bd* [price] 0.95;} "The discounted price"; group grant script "tuto.cf.price.vat.post" "tuto_cf_rel"; group grant script "tuto.cf.price.discount.post" "tuto_cf_rel"; parameter merge "tuto_cf_vat_rate" "1.2"; #2. Merge code of every script of the group; json load "tuto_cf_members" (group get script "tuto_cf_rel"); -> "[bundle]" (concat "#Bundle of the group tuto_cf_rel;" [_n_]); json parse_obj "tuto_cf_members" "/" "[name]" "[v]" { -> "[bundle]" (concat [bundle] (script generate merge [name]) [_n_]); }; json unload "tuto_cf_members"; #3. The parameter; -> "[bundle]" (concat [bundle] (parameter generate merge "tuto_cf_vat_rate")); file create "tmp/tuto_cf_bundle_1.0.0.mql" [bundle]; -> "[lines]" (file count_lines "tmp/tuto_cf_bundle_1.0.0.mql"); -> "[nb]" (string regexp_count [bundle] "script merge"); file delete "tmp/tuto_cf_bundle_1.0.0.mql"; parameter remove "tuto_cf_vat_rate"; #Cleanup: scripts and group; script delete "tuto.cf.price.vat.post"; script delete "tuto.cf.price.discount.post"; group remove "tuto_cf_rel"; concat "bundle: " [lines] " lines, " [nb] " scripts" [_n_] [bundle];
bundle: 10 lines, 2 scripts
#Bundle of the group tuto_cf_rel;
script merge "tuto.cf.price.vat.post" false 1
(param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");)
"Price with VAT"
{bd* [ht] 1.2;} "The price with tax";
script merge "tuto.cf.price.discount.post" false 1
(param (var "[price]" {type is_double [price]} "Price" is_null:false is_empty:false "100");)
"Price after a 5% discount"
{bd* [price] 0.95;} "The discounted price";
parameter merge "tuto_cf_vat_rate" "1.2";- Merge code is idempotent: run the bundle twice on the target, the result is the same
script generate creategives a create request when the target must not already have the script
Roll a whole group back to a release
Release 1.1.0 merged a wrong VAT rate and deleted a service by mistake. The release manager evaluates the script merge code stored in the 1.0.0 commit: merge updates the changed script and recreates the deleted one, then a new commit records the rollback.
#1. Release 1.0.0 of a pricing group; group add "tuto_p4m_rel"; script create post "tuto.p4m.price.vat" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.2;} "The price with tax"; script create post "tuto.p4m.price.round" false 1 (param (var "[p]" {type is_double [p]} "Price" is_null:false is_empty:false "12.345");) "Commercial rounding" {math round [p] 2;} "The rounded price"; group grant script "tuto.p4m.price.vat.post" "tuto_p4m_rel"; group grant script "tuto.p4m.price.round.post" "tuto_p4m_rel"; -> "[r100]" (version commit_group "tuto_p4m_rel" "1.0.0: VAT and rounding"); #2. Release 1.1.0 is a mistake: a wrong rate merged, and the rounding service deleted; wait 1000; script merge "tuto.p4m.price.vat.post" false 1 (param (var "[ht]" {type is_double [ht]} "Price without tax" is_null:false is_empty:false "100");) "Price with VAT" {bd* [ht] 1.02;} "The price with tax"; script delete "tuto.p4m.price.round.post"; -> "[bad]" (version check_group "tuto_p4m_rel"); version commit_group "tuto_p4m_rel" "1.1.0: new rate"; -> "[broken]" (execute "tuto.p4m.price.vat.post" "[ht]" 100); #3. Roll the whole group back: the commit holds the script merge code of every script, after a 39 chars editor tag; -> "[code]" (string substring (version show_scripts "tuto_p4m_rel" [r100]) 39); -> "[n]" (string regexp_count [code] "script merge "); eval [code]; #merge recreated the deleted script, grant it again; group grant script "tuto.p4m.price.round.post" "tuto_p4m_rel"; -> "[back]" (version check_group "tuto_p4m_rel"); -> "[r111]" (version commit_group "tuto_p4m_rel" "1.1.1: rollback to 1.0.0"); -> "[ok]" (concat (execute "tuto.p4m.price.vat.post" "[ht]" 100) " and " (execute "tuto.p4m.price.round.post" "[p]" 12.345)); #Cleanup: scripts, group and the commits folder; script delete "tuto.p4m.price.vat.post"; script delete "tuto.p4m.price.round.post"; group remove "tuto_p4m_rel"; file delete "commits/tuto_p4m_rel"; concat "after the bad release:" [_n_] [bad] "100 with VAT = " [broken] [_n_] "rollback: " [n] " script merge statements evaluated" [_n_] [back] "after the rollback: " [ok] [_n_] "rollback commit: " (string regexp_extract [r111] "^[0-9]{8}" 0) "..."
after the bad release: STATE : SCRIPT D : tuto.p4m.price.round.post U : tuto.p4m.price.vat.post 100 with VAT = 102.00 rollback: 2 script merge statements evaluated STATE : SCRIPT I : tuto.p4m.price.round.post U : tuto.p4m.price.vat.post after the rollback: 120.0 and 12.35 rollback commit: 20261101...
script mergecreates the script when it is missing and updates it otherwise: the code of a commit can be replayed on any server.- A merge does not grant: grant the recreated scripts to the group again, or they stay out of the next commits.
