summaryrefslogtreecommitdiff
path: root/src/json_branch.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/json_branch.c')
-rw-r--r--src/json_branch.c386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/json_branch.c b/src/json_branch.c
new file mode 100644
index 0000000..e8dd41a
--- /dev/null
+++ b/src/json_branch.c
@@ -0,0 +1,386 @@
+#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_branch.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+
+static cson_value * json_branch_list(void);
+static cson_value * json_branch_create(void);
+/*
+** Mapping of /json/branch/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Branch[] = {
+{"create", json_branch_create, 0},
+{"list", json_branch_list, 0},
+{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+/*
+** Implements the /json/branch family of pages/commands. Far from
+** complete.
+**
+*/
+cson_value * json_page_branch(void){
+ return json_page_dispatch_helper(&JsonPageDefs_Branch[0]);
+}
+
+/*
+** Impl for /json/branch/list
+**
+**
+** CLI mode options:
+**
+** -r|--range X, where X is one of (open,closed,all)
+** (only the first letter is significant, default=open)
+** -a (same as --range a)
+** -c (same as --range c)
+**
+** HTTP mode options:
+**
+** "range" GET/POST.payload parameter. FIXME: currently we also use
+** POST, but really want to restrict this to POST.payload.
+*/
+static cson_value * json_branch_list(void){
+ cson_value * payV;
+ cson_object * pay;
+ cson_value * listV;
+ cson_array * list;
+ char const * range = NULL;
+ int branchListFlags = BRL_OPEN_ONLY;
+ char * sawConversionError = NULL;
+ Stmt q = empty_Stmt;
+ if( !g.perm.Read ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'o' permissions.");
+ return NULL;
+ }
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+ listV = cson_value_new_array();
+ list = cson_value_get_array(listV);
+ if(fossil_has_json()){
+ range = json_getenv_cstr("range");
+ }
+
+ range = json_find_option_cstr("range",NULL,"r");
+ if((!range||!*range) && !g.isHTTP){
+ range = find_option("all","a",0);
+ if(range && *range){
+ range = "a";
+ }else{
+ range = find_option("closed","c",0);
+ if(range&&*range){
+ range = "c";
+ }
+ }
+ }
+
+ if(!range || !*range){
+ range = "o";
+ }
+ /* Normalize range values... */
+ switch(*range){
+ case 'c':
+ range = "closed";
+ branchListFlags = BRL_CLOSED_ONLY;
+ break;
+ case 'a':
+ range = "all";
+ branchListFlags = BRL_BOTH;
+ break;
+ default:
+ range = "open";
+ branchListFlags = BRL_OPEN_ONLY;
+ break;
+ };
+ cson_object_set(pay,"range",json_new_string(range));
+
+ if( g.localOpen ){ /* add "current" property (branch name). */
+ int vid = db_lget_int("checkout", 0);
+ char const * zCurrent = vid
+ ? db_text(0, "SELECT value FROM tagxref"
+ " WHERE rid=%d AND tagid=%d",
+ vid, TAG_BRANCH)
+ : 0;
+ if(zCurrent){
+ cson_object_set(pay,"current",json_new_string(zCurrent));
+ }
+ }
+
+
+ branch_prepare_list_query(&q, branchListFlags, 0, 0, 0); /* Allow a user? */
+ cson_object_set(pay,"branches",listV);
+ while((SQLITE_ROW==db_step(&q))){
+ cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
+ if(v){
+ cson_array_append(list,v);
+ }else if(!sawConversionError){
+ sawConversionError = mprintf("Column-to-json failed @ %s:%d",
+ __FILE__,__LINE__);
+ }
+ }
+ if( sawConversionError ){
+ json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,"%s",sawConversionError);
+ free(sawConversionError);
+ }
+ db_finalize(&q);
+ return payV;
+}
+
+/*
+** Parameters for the create-branch operation.
+*/
+typedef struct BranchCreateOptions{
+ char const * zName;
+ char const * zBasis;
+ char const * zColor;
+ int isPrivate;
+ /**
+ Might be set to an error string by
+ json_branch_new().
+ */
+ char const * rcErrMsg;
+} BranchCreateOptions;
+
+/*
+** Tries to create a new branch based on the options set in zOpt. If
+** an error is encountered, zOpt->rcErrMsg _might_ be set to a
+** descriptive string and one of the FossilJsonCodes values will be
+** returned. Or fossil_fatal() (or similar) might be called, exiting
+** the app.
+**
+** On success 0 is returned and if zNewRid is not NULL then the rid of
+** the new branch is assigned to it.
+**
+** If zOpt->isPrivate is 0 but the parent branch is private,
+** zOpt->isPrivate will be set to a non-zero value and the new branch
+** will be private.
+*/
+static int json_branch_new(BranchCreateOptions * zOpt,
+ int *zNewRid){
+ /* Mostly copied from branch.c:branch_new(), but refactored a small
+ bit to not produce output or interact with the user. The
+ down-side to that is that we dropped the gpg-signing. It was
+ either that or abort the creation if we couldn't sign. We can't
+ sign over HTTP mode, anyway.
+ */
+ char const * zBranch = zOpt->zName;
+ char const * zBasis = zOpt->zBasis;
+ char const * zColor = zOpt->zColor;
+ int rootid; /* RID of the root check-in - what we branch off of */
+ int brid; /* RID of the branch check-in */
+ int i; /* Loop counter */
+ char *zUuid; /* Artifact ID of origin */
+ Stmt q; /* Generic query */
+ char *zDate; /* Date that branch was created */
+ char *zComment; /* Check-in comment for the new branch */
+ Blob branch; /* manifest for the new branch */
+ Manifest *pParent; /* Parsed parent manifest */
+ Blob mcksum; /* Self-checksum on the manifest */
+
+ /* fossil branch new name */
+ if( zBranch==0 || zBranch[0]==0 ){
+ zOpt->rcErrMsg = "Branch name may not be null/empty.";
+ return FSL_JSON_E_INVALID_ARGS;
+ }
+ if( db_exists(
+ "SELECT 1 FROM tagxref"
+ " WHERE tagtype>0"
+ " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%q')",
+ zBranch)!=0 ){
+ zOpt->rcErrMsg = "Branch already exists.";
+ return FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
+ }
+
+ db_begin_transaction();
+ rootid = name_to_typed_rid(zBasis, "ci");
+ if( rootid==0 ){
+ zOpt->rcErrMsg = "Basis branch not found.";
+ return FSL_JSON_E_RESOURCE_NOT_FOUND;
+ }
+
+ pParent = manifest_get(rootid, CFTYPE_MANIFEST, 0);
+ if( pParent==0 ){
+ zOpt->rcErrMsg = "Could not read parent manifest.";
+ return FSL_JSON_E_UNKNOWN;
+ }
+
+ /* Create a manifest for the new branch */
+ blob_zero(&branch);
+ if( pParent->zBaseline ){
+ blob_appendf(&branch, "B %s\n", pParent->zBaseline);
+ }
+ zComment = mprintf("Create new branch named \"%s\" "
+ "from \"%s\".", zBranch, zBasis);
+ blob_appendf(&branch, "C %F\n", zComment);
+ free(zComment);
+ zDate = date_in_standard_format("now");
+ blob_appendf(&branch, "D %s\n", zDate);
+ free(zDate);
+
+ /* Copy all of the content from the parent into the branch */
+ for(i=0; i<pParent->nFile; ++i){
+ blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
+ if( pParent->aFile[i].zUuid ){
+ blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
+ if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
+ blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
+ }
+ }
+ blob_append(&branch, "\n", 1);
+ }
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
+ blob_appendf(&branch, "P %s\n", zUuid);
+ free(zUuid);
+ if( pParent->zRepoCksum ){
+ blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
+ }
+ manifest_destroy(pParent);
+
+ /* Add the symbolic branch name and the "branch" tag to identify
+ ** this as a new branch */
+ if( content_is_private(rootid) ) zOpt->isPrivate = 1;
+ if( zColor!=0 ){
+ blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
+ }
+ blob_appendf(&branch, "T *branch * %F\n", zBranch);
+ blob_appendf(&branch, "T *sym-%F *\n", zBranch);
+
+ /* Cancel all other symbolic tags */
+ db_prepare(&q,
+ "SELECT tagname FROM tagxref, tag"
+ " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
+ " AND tagtype>0 AND tagname GLOB 'sym-*'"
+ " ORDER BY tagname",
+ rootid);
+ while( db_step(&q)==SQLITE_ROW ){
+ const char *zTag = db_column_text(&q, 0);
+ blob_appendf(&branch, "T -%F *\n", zTag);
+ }
+ db_finalize(&q);
+
+ blob_appendf(&branch, "U %F\n", g.zLogin);
+ md5sum_blob(&branch, &mcksum);
+ blob_appendf(&branch, "Z %b\n", &mcksum);
+
+ brid = content_put_ex(&branch, 0, 0, 0, zOpt->isPrivate);
+ if( brid==0 ){
+ fossil_panic("Problem committing manifest: %s", g.zErrMsg);
+ }
+ db_add_unsent(brid);
+ if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
+ fossil_panic("%s", g.zErrMsg);
+ }
+ assert( blob_is_reset(&branch) );
+ content_deltify(rootid, &brid, 1, 0);
+ if( zNewRid ){
+ *zNewRid = brid;
+ }
+
+ /* Commit */
+ db_end_transaction(0);
+
+ return 0;
+}
+
+
+/*
+** Impl of /json/branch/create.
+*/
+static cson_value * json_branch_create(void){
+ cson_value * payV = NULL;
+ cson_object * pay = NULL;
+ int rc = 0;
+ BranchCreateOptions opt;
+ char * zUuid = NULL;
+ const char *zMainBranch = db_main_branch();
+ int rid = 0;
+ if( !g.perm.Write ){
+ json_set_err(FSL_JSON_E_DENIED,
+ "Requires 'i' permissions.");
+ return NULL;
+ }
+ memset(&opt,0,sizeof(BranchCreateOptions));
+ if(fossil_has_json()){
+ opt.zName = json_getenv_cstr("name");
+ }
+
+ if(!opt.zName){
+ opt.zName = json_command_arg(g.json.dispatchDepth+1);
+ }
+
+ if(!opt.zName){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "'name' parameter was not specified." );
+ return NULL;
+ }
+
+ opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL);
+ opt.zBasis = json_find_option_cstr("basis",NULL,NULL);
+ if(!opt.zBasis && !g.isHTTP){
+ opt.zBasis = json_command_arg(g.json.dispatchDepth+2);
+ }
+ if(!opt.zBasis){
+ opt.zBasis = fossil_strdup(zMainBranch);
+ }
+ opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1);
+ if(-1==opt.isPrivate){
+ if(!g.isHTTP){
+ opt.isPrivate = (NULL != find_option("private","",0));
+ }else{
+ opt.isPrivate = 0;
+ }
+ }
+
+ rc = json_branch_new( &opt, &rid );
+ if(rc){
+ json_set_err(rc, "%s", opt.rcErrMsg);
+ goto error;
+ }
+ assert(0 != rid);
+ payV = cson_value_new_object();
+ pay = cson_value_get_object(payV);
+
+ cson_object_set(pay,"name",json_new_string(opt.zName));
+ cson_object_set(pay,"basis",json_new_string(opt.zBasis));
+ cson_object_set(pay,"rid",json_new_int(rid));
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
+ cson_object_set(pay,"uuid", json_new_string(zUuid));
+ cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate));
+ free(zUuid);
+ if(opt.zColor){
+ cson_object_set(pay,"bgColor",json_new_string(opt.zColor));
+ }
+
+ goto ok;
+ error:
+ assert( 0 != g.json.resultCode );
+ cson_value_free(payV);
+ payV = NULL;
+ ok:
+ return payV;
+}
+
+#endif /* FOSSIL_ENABLE_JSON */