summaryrefslogtreecommitdiff
path: root/src/json_tag.c
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
committercashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
commit4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch)
treec296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/json_tag.c
downloadrelic-master.tar.gz
initial commitHEADmaster
Diffstat (limited to 'src/json_tag.c')
-rw-r--r--src/json_tag.c479
1 files changed, 479 insertions, 0 deletions
diff --git a/src/json_tag.c b/src/json_tag.c
new file mode 100644
index 0000000..611c0fd
--- /dev/null
+++ b/src/json_tag.c
@@ -0,0 +1,479 @@
+#ifdef FOSSIL_ENABLE_JSON
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_tag.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+static cson_value * json_tag_add(void);
+static cson_value * json_tag_cancel(void);
+static cson_value * json_tag_find(void);
+static cson_value * json_tag_list(void);
+/*
+** Mapping of /json/tag/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Tag[] = {
+{"add", json_tag_add, 0},
+{"cancel", json_tag_cancel, 0},
+{"find", json_tag_find, 0},
+{"list", json_tag_list, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+/*
+** Implements the /json/tag family of pages/commands.
+**
+*/
+cson_value * json_page_tag(void){
+ return json_page_dispatch_helper(&JsonPageDefs_Tag[0]);
+}
+
+
+/*
+** Impl of /json/tag/add.
+*/
+static cson_value * json_tag_add(void){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ char const * zName = NULL;
+ char const * zCheckin = NULL;
+ char fRaw = 0;
+ char fPropagate = 0;
+ char const * zValue = NULL;
+ const char *zPrefix = NULL;
+
+ if( !g.perm.Write ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'i' permissions.");
+ return NULL;
+ }
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+ fPropagate = json_find_option_bool("propagate",NULL,NULL,0);
+ zName = json_find_option_cstr("name",NULL,NULL);
+ zPrefix = fRaw ? "" : "sym-";
+ if(!zName || !*zName){
+ if(!fossil_has_json()){
+ zName = json_command_arg(3);
+ }
+ if(!zName || !*zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ }
+
+ zCheckin = json_find_option_cstr("checkin",NULL,NULL);
+ if( !zCheckin ){
+ if(!fossil_has_json()){
+ zCheckin = json_command_arg(4);
+ }
+ if(!zCheckin || !*zCheckin){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'checkin' parameter is missing.");
+ return NULL;
+ }
+ }
+
+
+ zValue = json_find_option_cstr("value",NULL,NULL);
+ if(!zValue && !fossil_has_json()){
+ zValue = json_command_arg(5);
+ }
+
+ db_begin_transaction();
+ tag_add_artifact(zPrefix, zName, zCheckin, zValue,
+ 1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/);
+ db_end_transaction(0);
+
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set(pay, "name", json_new_string(zName) );
+ cson_object_set(pay, "value", (zValue&&*zValue)
+ ? json_new_string(zValue)
+ : cson_value_null());
+ cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate));
+ cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
+ {
+ Blob uu = empty_blob;
+ int rc;
+ blob_append(&uu, zName, -1);
+ rc = name_to_uuid(&uu, 9, "*");
+ if(0!=rc){
+ json_set_err(FSL_JSON_E_UNKNOWN,
+ "Could not convert name back to artifact hash!");
+ blob_reset(&uu);
+ goto error;
+ }
+ cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu)));
+ blob_reset(&uu);
+ }
+
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ return payV;
+}
+
+
+/*
+** Impl of /json/tag/cancel.
+*/
+static cson_value * json_tag_cancel(void){
+ char const * zName = NULL;
+ char const * zCheckin = NULL;
+ char fRaw = 0;
+ const char *zPrefix = NULL;
+
+ if( !g.perm.Write ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'i' permissions.");
+ return NULL;
+ }
+
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+ zPrefix = fRaw ? "" : "sym-";
+ zName = json_find_option_cstr("name",NULL,NULL);
+ if(!zName || !*zName){
+ if(!fossil_has_json()){
+ zName = json_command_arg(3);
+ }
+ if(!zName || !*zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ }
+
+ zCheckin = json_find_option_cstr("checkin",NULL,NULL);
+ if( !zCheckin ){
+ if(!fossil_has_json()){
+ zCheckin = json_command_arg(4);
+ }
+ if(!zCheckin || !*zCheckin){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'checkin' parameter is missing.");
+ return NULL;
+ }
+ }
+ /* FIXME?: verify that the tag is currently active. We have no real
+ error case unless we do that.
+ */
+ db_begin_transaction();
+ tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0);
+ db_end_transaction(0);
+ return NULL;
+}
+
+
+/*
+** Impl of /json/tag/find.
+*/
+static cson_value * json_tag_find(void){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value * listV = NULL;
+ cson_array * list = NULL;
+ char const * zName = NULL;
+ char const * zType = NULL;
+ char const * zType2 = NULL;
+ char fRaw = 0;
+ Stmt q = empty_Stmt;
+ int limit = 0;
+ int tagid = 0;
+
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ zName = json_find_option_cstr("name",NULL,NULL);
+ if(!zName || !*zName){
+ if(!fossil_has_json()){
+ zName = json_command_arg(3);
+ }
+ if(!zName || !*zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter is missing.");
+ return NULL;
+ }
+ }
+ zType = json_find_option_cstr("type",NULL,"t");
+ if(!zType || !*zType){
+ zType = "*";
+ zType2 = zType;
+ }else{
+ switch(*zType){
+ case 'c': zType = "ci"; zType2 = "checkin"; break;
+ case 'e': zType = "e"; zType2 = "event"; break;
+ case 'w': zType = "w"; zType2 = "wiki"; break;
+ case 't': zType = "t"; zType2 = "ticket"; break;
+ }
+ }
+
+ limit = json_find_option_int("limit",NULL,"n",0);
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+
+ tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q",
+ fRaw ? "" : "sym-",
+ zName);
+
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set(pay, "name", json_new_string(zName));
+ cson_object_set(pay, "raw", cson_value_new_bool(fRaw));
+ cson_object_set(pay, "type", json_new_string(zType2));
+ cson_object_set(pay, "limit", json_new_int(limit));
+
+#if 1
+ if( tagid<=0 ){
+ cson_object_set(pay,"artifacts", cson_value_null());
+ json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found.");
+ return payV;
+ }
+#endif
+
+ if( fRaw ){
+ db_prepare(&q,
+ "SELECT blob.uuid FROM tagxref, blob"
+ " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
+ " AND tagxref.tagtype>0"
+ " AND blob.rid=tagxref.rid"
+ "%s LIMIT %d",
+ zName,
+ (limit>0)?"":"--", limit
+ );
+ while( db_step(&q)==SQLITE_ROW ){
+ if(!listV){
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ }
+ cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0));
+ }
+ db_finalize(&q);
+ }else{
+ char const * zSqlBase = /*modified from timeline_query_for_tty()*/
+ " SELECT"
+#if 0
+ " blob.rid AS rid,"
+#endif
+ " uuid AS uuid,"
+ " cast(strftime('%s',event.mtime) as int) AS timestamp,"
+ " coalesce(ecomment,comment) AS comment,"
+ " coalesce(euser,user) AS user,"
+ " CASE event.type"
+ " WHEN 'ci' THEN 'checkin'"
+ " WHEN 'w' THEN 'wiki'"
+ " WHEN 'e' THEN 'event'"
+ " WHEN 't' THEN 'ticket'"
+ " ELSE 'unknown'"
+ " END"
+ " AS eventType"
+ " FROM event, blob"
+ " WHERE blob.rid=event.objid"
+ ;
+ /* FIXME: re-add tags. */
+ db_prepare(&q,
+ "%s"
+ " AND event.type GLOB '%q'"
+ " AND blob.rid IN ("
+ " SELECT rid FROM tagxref"
+ " WHERE tagtype>0 AND tagid=%d"
+ " )"
+ " ORDER BY event.mtime DESC"
+ "%s LIMIT %d",
+ zSqlBase /*safe-for-%s*/, zType, tagid,
+ (limit>0)?"":"--", limit
+ );
+ listV = json_stmt_to_array_of_obj(&q, NULL);
+ db_finalize(&q);
+ }
+
+ if(!listV) {
+ listV = cson_value_null();
+ }
+ cson_object_set(pay, "artifacts", listV);
+ return payV;
+}
+
+
+/*
+** Impl for /json/tag/list
+**
+** TODOs:
+**
+** Add -type TYPE (ci, w, e, t)
+*/
+static cson_value * json_tag_list(void){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ cson_value const * tagsVal = NULL;
+ char const * zCheckin = NULL;
+ char fRaw = 0;
+ char fTicket = 0;
+ Stmt q = empty_Stmt;
+
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+
+ fRaw = json_find_option_bool("raw",NULL,NULL,0);
+ fTicket = json_find_option_bool("includeTickets","tkt","t",0);
+ zCheckin = json_find_option_cstr("checkin",NULL,NULL);
+ if( !zCheckin ){
+ zCheckin = json_command_arg( g.json.dispatchDepth + 1);
+ if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){
+ zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
+ assert(zCheckin);
+ }
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ cson_object_set(pay, "raw", cson_value_new_bool(fRaw) );
+ if( zCheckin ){
+ /**
+ Tags for a specific check-in. Output format:
+
+ RAW mode:
+
+ {
+ "sym-tagname": (value || null),
+ ...other tags...
+ }
+
+ Non-raw:
+
+ {
+ "tagname": (value || null),
+ ...other tags...
+ }
+ */
+ cson_value * objV = NULL;
+ cson_object * obj = NULL;
+ int const rid = name_to_rid(zCheckin);
+ if(0==rid){
+ json_set_err(FSL_JSON_E_UNRESOLVED_UUID,
+ "Could not find artifact for check-in [%s].",
+ zCheckin);
+ goto error;
+ }
+ cson_object_set(pay, "checkin", json_new_string(zCheckin));
+ db_prepare(&q,
+ "SELECT tagname, value FROM tagxref, tag"
+ " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
+ " AND tagtype>%d"
+ " ORDER BY tagname",
+ rid,
+ fRaw ? -1 : 0
+ );
+ while( SQLITE_ROW == db_step(&q) ){
+ const char *zName = db_column_text(&q, 0);
+ const char *zValue = db_column_text(&q, 1);
+ if( fRaw==0 ){
+ if( 0!=strncmp(zName, "sym-", 4) ) continue;
+ zName += 4;
+ assert( *zName );
+ }
+ if(NULL==objV){
+ objV = cson_value_new_object();
+ obj = cson_value_get_object(objV);
+ tagsVal = objV;
+ cson_object_set( pay, "tags", objV );
+ }
+ if( zValue && zValue[0] ){
+ cson_object_set( obj, zName, json_new_string(zValue) );
+ }else{
+ cson_object_set( obj, zName, cson_value_null() );
+ }
+ }
+ db_finalize(&q);
+ }else{/* all tags */
+ /* Output format:
+
+ RAW mode:
+
+ ["tagname", "sym-tagname2",...]
+
+ Non-raw:
+
+ ["tagname", "tagname2",...]
+
+ i don't really like the discrepancy in the format but this list
+ can get really long and (A) most tags don't have values, (B) i
+ don't want to bloat it more, and (C) cson_object_set() is O(N)
+ (N=current number of properties) because it uses an unsorted list
+ internally (for memory reasons), so this can slow down appreciably
+ on a long list. The culprit is really tkt- tags, as there is one
+ for each ticket (941 in the main fossil repo as of this writing).
+ */
+ Blob sql = empty_blob;
+ cson_value * arV = NULL;
+ cson_array * ar = NULL;
+ blob_append(&sql,
+ "SELECT tagname FROM tag"
+ " WHERE EXISTS(SELECT 1 FROM tagxref"
+ " WHERE tagid=tag.tagid"
+ " AND tagtype>0)",
+ -1
+ );
+ if(!fTicket){
+ blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1);
+ }
+ blob_append(&sql,
+ " ORDER BY tagname", -1);
+ db_prepare(&q, "%s", blob_sql_text(&sql));
+ blob_reset(&sql);
+ cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) );
+ while( SQLITE_ROW == db_step(&q) ){
+ const char *zName = db_column_text(&q, 0);
+ if(NULL==arV){
+ arV = cson_value_new_array();
+ ar = cson_value_get_array(arV);
+ cson_object_set(pay, "tags", arV);
+ tagsVal = arV;
+ }
+ else if( !fRaw && (0==strncmp(zName, "sym-", 4))){
+ zName += 4;
+ assert( *zName );
+ }
+ cson_array_append(ar, json_new_string(zName));
+ }
+ db_finalize(&q);
+ }
+
+ goto end;
+ error:
+ assert(0 != g.json.resultCode);
+ cson_value_free(payV);
+ payV = NULL;
+ end:
+ if( payV && !tagsVal ){
+ cson_object_set( pay, "tags", cson_value_null() );
+ }
+ return payV;
+}
+#endif /* FOSSIL_ENABLE_JSON */