| | @@ -22,16 +22,18 @@ |
| 22 | 22 | #include "json_detail.h" |
| 23 | 23 | #endif |
| 24 | 24 | |
| 25 | 25 | |
| 26 | 26 | static cson_value * json_branch_list(); |
| 27 | +static cson_value * json_branch_create(); |
| 27 | 28 | /* |
| 28 | 29 | ** Mapping of /json/branch/XXX commands/paths to callbacks. |
| 29 | 30 | */ |
| 30 | 31 | static const JsonPageDef JsonPageDefs_Branch[] = { |
| 32 | +{"create", json_branch_create, 0}, |
| 31 | 33 | {"list", json_branch_list, 0}, |
| 32 | | -{"create", json_page_nyi, 1}, |
| 34 | +{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/}, |
| 33 | 35 | /* Last entry MUST have a NULL name. */ |
| 34 | 36 | {NULL,NULL,0} |
| 35 | 37 | }; |
| 36 | 38 | |
| 37 | 39 | /* |
| | @@ -137,8 +139,244 @@ |
| 137 | 139 | } |
| 138 | 140 | } |
| 139 | 141 | if( sawConversionError ){ |
| 140 | 142 | json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,sawConversionError); |
| 141 | 143 | free(sawConversionError); |
| 144 | + } |
| 145 | + return payV; |
| 146 | +} |
| 147 | + |
| 148 | +/* |
| 149 | +** Parameters for the create-branch operation. |
| 150 | +*/ |
| 151 | +typedef struct BranchCreateOptions{ |
| 152 | + char const * zName; |
| 153 | + char const * zBasis; |
| 154 | + char const * zColor; |
| 155 | + char isPrivate; |
| 156 | + /** |
| 157 | + Might be set to an error string by |
| 158 | + json_branch_new(). |
| 159 | + */ |
| 160 | + char const * rcErrMsg; |
| 161 | +} BranchCreateOptions; |
| 162 | + |
| 163 | +/* |
| 164 | +** Tries to create a new branch based on the options set in zOpt. If |
| 165 | +** an error is encountered, zOpt->rcErrMsg _might_ be set to a |
| 166 | +** descriptive string and one of the FossilJsonCodes values will be |
| 167 | +** returned. Or fossil_fatal() (or similar) might be called, exiting |
| 168 | +** the app. |
| 169 | +** |
| 170 | +** On success 0 is returned and if zNewRid is not NULL then the rid of |
| 171 | +** the new branch is assigned to it. |
| 172 | +** |
| 173 | +** If zOpt->isPrivate is 0 but the parent branch is private, |
| 174 | +** zOpt->isPrivate will be set to a non-zero value and the new branch |
| 175 | +** will be private. |
| 176 | +*/ |
| 177 | +static int json_branch_new(BranchCreateOptions * zOpt, |
| 178 | + int *zNewRid){ |
| 179 | + /* Mostly copied from branch.c:branch_new(), but refactored a small |
| 180 | + bit to not produce output or interact with the user. The |
| 181 | + down-side to that is that we dropped the gpg-signing. It was |
| 182 | + either that or abort the creation if we couldn't sign. We can't |
| 183 | + sign over HTTP mode, anyway. |
| 184 | + */ |
| 185 | + char const * zBranch = zOpt->zName; |
| 186 | + char const * zBasis = zOpt->zBasis; |
| 187 | + char const * zColor = zOpt->zColor; |
| 188 | + int rootid; /* RID of the root check-in - what we branch off of */ |
| 189 | + int brid; /* RID of the branch check-in */ |
| 190 | + int i; /* Loop counter */ |
| 191 | + char *zUuid; /* Artifact ID of origin */ |
| 192 | + Stmt q; /* Generic query */ |
| 193 | + char *zDate; /* Date that branch was created */ |
| 194 | + char *zComment; /* Check-in comment for the new branch */ |
| 195 | + Blob branch; /* manifest for the new branch */ |
| 196 | + Manifest *pParent; /* Parsed parent manifest */ |
| 197 | + Blob mcksum; /* Self-checksum on the manifest */ |
| 198 | + |
| 199 | + /* fossil branch new name */ |
| 200 | + if( zBranch==0 || zBranch[0]==0 ){ |
| 201 | + zOpt->rcErrMsg = "Branch name may not be null/empty."; |
| 202 | + return FSL_JSON_E_INVALID_ARGS; |
| 203 | + } |
| 204 | + if( db_exists( |
| 205 | + "SELECT 1 FROM tagxref" |
| 206 | + " WHERE tagtype>0" |
| 207 | + " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')", |
| 208 | + zBranch)!=0 ){ |
| 209 | + zOpt->rcErrMsg = "Branch already exists."; |
| 210 | + return FSL_JSON_E_RESOURCE_ALREADY_EXISTS; |
| 211 | + } |
| 212 | + |
| 213 | + db_begin_transaction(); |
| 214 | + rootid = name_to_typed_rid(zBasis, "ci"); |
| 215 | + if( rootid==0 ){ |
| 216 | + zOpt->rcErrMsg = "Basis branch not found."; |
| 217 | + return FSL_JSON_E_RESOURCE_NOT_FOUND; |
| 218 | + } |
| 219 | + |
| 220 | + pParent = manifest_get(rootid, CFTYPE_MANIFEST); |
| 221 | + if( pParent==0 ){ |
| 222 | + zOpt->rcErrMsg = "Could not read parent manifest."; |
| 223 | + return FSL_JSON_E_UNKNOWN; |
| 224 | + } |
| 225 | + |
| 226 | + /* Create a manifest for the new branch */ |
| 227 | + blob_zero(&branch); |
| 228 | + if( pParent->zBaseline ){ |
| 229 | + blob_appendf(&branch, "B %s\n", pParent->zBaseline); |
| 230 | + } |
| 231 | + zComment = mprintf("Create new branch named \"%s\" " |
| 232 | + "from \"%s\".", zBranch, zBasis); |
| 233 | + blob_appendf(&branch, "C %F\n", zComment); |
| 234 | + free(zComment); |
| 235 | + zDate = date_in_standard_format("now"); |
| 236 | + blob_appendf(&branch, "D %s\n", zDate); |
| 237 | + free(zDate); |
| 238 | + |
| 239 | + /* Copy all of the content from the parent into the branch */ |
| 240 | + for(i=0; i<pParent->nFile; ++i){ |
| 241 | + blob_appendf(&branch, "F %F", pParent->aFile[i].zName); |
| 242 | + if( pParent->aFile[i].zUuid ){ |
| 243 | + blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); |
| 244 | + if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){ |
| 245 | + blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); |
| 246 | + } |
| 247 | + } |
| 248 | + blob_append(&branch, "\n", 1); |
| 249 | + } |
| 250 | + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); |
| 251 | + blob_appendf(&branch, "P %s\n", zUuid); |
| 252 | + free(zUuid); |
| 253 | + if( pParent->zRepoCksum ){ |
| 254 | + blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); |
| 255 | + } |
| 256 | + manifest_destroy(pParent); |
| 257 | + |
| 258 | + /* Add the symbolic branch name and the "branch" tag to identify |
| 259 | + ** this as a new branch */ |
| 260 | + if( content_is_private(rootid) ) zOpt->isPrivate = 1; |
| 261 | + if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084"; |
| 262 | + if( zColor!=0 ){ |
| 263 | + blob_appendf(&branch, "T *bgcolor * %F\n", zColor); |
| 264 | + } |
| 265 | + blob_appendf(&branch, "T *branch * %F\n", zBranch); |
| 266 | + blob_appendf(&branch, "T *sym-%F *\n", zBranch); |
| 267 | + if( zOpt->isPrivate ){ |
| 268 | + blob_appendf(&branch, "T +private *\n"); |
| 269 | + } |
| 270 | + |
| 271 | + /* Cancel all other symbolic tags */ |
| 272 | + db_prepare(&q, |
| 273 | + "SELECT tagname FROM tagxref, tag" |
| 274 | + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" |
| 275 | + " AND tagtype>0 AND tagname GLOB 'sym-*'" |
| 276 | + " ORDER BY tagname", |
| 277 | + rootid); |
| 278 | + while( db_step(&q)==SQLITE_ROW ){ |
| 279 | + const char *zTag = db_column_text(&q, 0); |
| 280 | + blob_appendf(&branch, "T -%F *\n", zTag); |
| 281 | + } |
| 282 | + db_finalize(&q); |
| 283 | + |
| 284 | + blob_appendf(&branch, "U %F\n", g.zLogin); |
| 285 | + md5sum_blob(&branch, &mcksum); |
| 286 | + blob_appendf(&branch, "Z %b\n", &mcksum); |
| 287 | + |
| 288 | + brid = content_put(&branch); |
| 289 | + if( brid==0 ){ |
| 290 | + fossil_panic("Problem committing manifest: %s", g.zErrMsg); |
| 291 | + } |
| 292 | + db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid); |
| 293 | + if( manifest_crosslink(brid, &branch)==0 ){ |
| 294 | + fossil_panic("unable to install new manifest"); |
| 295 | + } |
| 296 | + assert( blob_is_reset(&branch) ); |
| 297 | + content_deltify(rootid, brid, 0); |
| 298 | + if( zNewRid ){ |
| 299 | + *zNewRid = brid; |
| 300 | + } |
| 301 | + |
| 302 | + /* Commit */ |
| 303 | + db_end_transaction(0); |
| 304 | + |
| 305 | +#if 0 /* Do an autosync push, if requested */ |
| 306 | + /* arugable for JSON mode? */ |
| 307 | + if( !g.isHTTP && !isPrivate ) autosync(AUTOSYNC_PUSH); |
| 308 | +#endif |
| 309 | + return 0; |
| 142 | 310 | } |
| 311 | + |
| 312 | + |
| 313 | +static cson_value * json_branch_create(){ |
| 314 | + cson_value * payV = NULL; |
| 315 | + cson_object * pay = NULL; |
| 316 | + int rc = 0; |
| 317 | + BranchCreateOptions opt; |
| 318 | + char * zUuid = NULL; |
| 319 | + int rid = 0; |
| 320 | + if( !g.perm.Write ){ |
| 321 | + g.json.resultCode = FSL_JSON_E_DENIED; |
| 322 | + return NULL; |
| 323 | + } |
| 324 | + if(0){ |
| 325 | + char const * x = json_command_arg(g.json.dispatchDepth+1); |
| 326 | + fprintf(stderr,"command arg=%s\n",x); |
| 327 | + assert(0); |
| 328 | + } |
| 329 | + memset(&opt,0,sizeof(BranchCreateOptions)); |
| 330 | + opt.zName = g.json.post.v |
| 331 | + ? json_getenv_cstr("name") |
| 332 | + : json_command_arg(g.json.dispatchDepth+1); |
| 333 | + |
| 334 | + if(!opt.zName){ |
| 335 | + json_set_err(FSL_JSON_E_MISSING_ARGS, "'name' parameter was not specified." ); |
| 336 | + return NULL; |
| 337 | + } |
| 338 | + |
| 339 | + opt.zBasis = g.json.post.v |
| 340 | + ? json_getenv_cstr("basis") |
| 341 | + : json_command_arg(g.json.dispatchDepth+2); |
| 342 | + if(!opt.zBasis || ('-'==*opt.zBasis/*assume CLI flag*/)){ |
| 343 | + opt.zBasis = "trunk"; |
| 344 | + } |
| 345 | + |
| 346 | + opt.zColor = g.json.post.v |
| 347 | + ? json_getenv_cstr("bgColor") |
| 348 | + : find_option("bgcolor","",1); |
| 349 | + |
| 350 | + opt.isPrivate = g.json.post.v |
| 351 | + ? json_getenv_bool("private",0) |
| 352 | + : (NULL != find_option("private","",0)) |
| 353 | + ; |
| 354 | + |
| 355 | + rc = json_branch_new( &opt, &rid ); |
| 356 | + if(rc){ |
| 357 | + json_set_err(rc, opt.rcErrMsg ); |
| 358 | + goto error; |
| 359 | + } |
| 360 | + payV = cson_value_new_object(); |
| 361 | + pay = cson_value_get_object(payV); |
| 362 | + |
| 363 | + cson_object_set(pay,"name",json_new_string(opt.zName)); |
| 364 | + cson_object_set(pay,"basis",json_new_string(opt.zBasis)); |
| 365 | + cson_object_set(pay,"rid",json_new_int(rid)); |
| 366 | + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 367 | + cson_object_set(pay,"uuid", json_new_string(zUuid)); |
| 368 | + cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate)); |
| 369 | + free(zUuid); |
| 370 | + if(opt.zColor){ |
| 371 | + cson_object_set(pay,"bgColor",json_new_string(opt.zColor)); |
| 372 | + } |
| 373 | + |
| 374 | + goto ok; |
| 375 | + error: |
| 376 | + assert( 0 != g.json.resultCode ); |
| 377 | + cson_value_free(payV); |
| 378 | + payV = NULL; |
| 379 | + ok: |
| 143 | 380 | return payV; |
| 144 | 381 | } |
| 382 | + |
| 145 | 383 | |