Fossil SCM

fossil-scm / src / json_branch.c
Blame History Raw 387 lines
1
#ifdef FOSSIL_ENABLE_JSON
2
/*
3
** Copyright (c) 2011 D. Richard Hipp
4
**
5
** This program is free software; you can redistribute it and/or
6
** modify it under the terms of the Simplified BSD License (also
7
** known as the "2-Clause License" or "FreeBSD License".)
8
**
9
** This program is distributed in the hope that it will be useful,
10
** but without any warranty; without even the implied warranty of
11
** merchantability or fitness for a particular purpose.
12
**
13
** Author contact information:
14
** [email protected]
15
** http://www.hwaci.com/drh/
16
**
17
*/
18
#include "VERSION.h"
19
#include "config.h"
20
#include "json_branch.h"
21
22
#if INTERFACE
23
#include "json_detail.h"
24
#endif
25
26
27
static cson_value * json_branch_list(void);
28
static cson_value * json_branch_create(void);
29
/*
30
** Mapping of /json/branch/XXX commands/paths to callbacks.
31
*/
32
static const JsonPageDef JsonPageDefs_Branch[] = {
33
{"create", json_branch_create, 0},
34
{"list", json_branch_list, 0},
35
{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/},
36
/* Last entry MUST have a NULL name. */
37
{NULL,NULL,0}
38
};
39
40
/*
41
** Implements the /json/branch family of pages/commands. Far from
42
** complete.
43
**
44
*/
45
cson_value * json_page_branch(void){
46
return json_page_dispatch_helper(&JsonPageDefs_Branch[0]);
47
}
48
49
/*
50
** Impl for /json/branch/list
51
**
52
**
53
** CLI mode options:
54
**
55
** -r|--range X, where X is one of (open,closed,all)
56
** (only the first letter is significant, default=open)
57
** -a (same as --range a)
58
** -c (same as --range c)
59
**
60
** HTTP mode options:
61
**
62
** "range" GET/POST.payload parameter. FIXME: currently we also use
63
** POST, but really want to restrict this to POST.payload.
64
*/
65
static cson_value * json_branch_list(void){
66
cson_value * payV;
67
cson_object * pay;
68
cson_value * listV;
69
cson_array * list;
70
char const * range = NULL;
71
int branchListFlags = BRL_OPEN_ONLY;
72
char * sawConversionError = NULL;
73
Stmt q = empty_Stmt;
74
if( !g.perm.Read ){
75
json_set_err(FSL_JSON_E_DENIED,
76
"Requires 'o' permissions.");
77
return NULL;
78
}
79
payV = cson_value_new_object();
80
pay = cson_value_get_object(payV);
81
listV = cson_value_new_array();
82
list = cson_value_get_array(listV);
83
if(fossil_has_json()){
84
range = json_getenv_cstr("range");
85
}
86
87
range = json_find_option_cstr("range",NULL,"r");
88
if((!range||!*range) && !g.isHTTP){
89
range = find_option("all","a",0);
90
if(range && *range){
91
range = "a";
92
}else{
93
range = find_option("closed","c",0);
94
if(range&&*range){
95
range = "c";
96
}
97
}
98
}
99
100
if(!range || !*range){
101
range = "o";
102
}
103
/* Normalize range values... */
104
switch(*range){
105
case 'c':
106
range = "closed";
107
branchListFlags = BRL_CLOSED_ONLY;
108
break;
109
case 'a':
110
range = "all";
111
branchListFlags = BRL_BOTH;
112
break;
113
default:
114
range = "open";
115
branchListFlags = BRL_OPEN_ONLY;
116
break;
117
};
118
cson_object_set(pay,"range",json_new_string(range));
119
120
if( g.localOpen ){ /* add "current" property (branch name). */
121
int vid = db_lget_int("checkout", 0);
122
char const * zCurrent = vid
123
? db_text(0, "SELECT value FROM tagxref"
124
" WHERE rid=%d AND tagid=%d",
125
vid, TAG_BRANCH)
126
: 0;
127
if(zCurrent){
128
cson_object_set(pay,"current",json_new_string(zCurrent));
129
}
130
}
131
132
133
branch_prepare_list_query(&q, branchListFlags, 0, 0, 0); /* Allow a user? */
134
cson_object_set(pay,"branches",listV);
135
while((SQLITE_ROW==db_step(&q))){
136
cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0);
137
if(v){
138
cson_array_append(list,v);
139
}else if(!sawConversionError){
140
sawConversionError = mprintf("Column-to-json failed @ %s:%d",
141
__FILE__,__LINE__);
142
}
143
}
144
if( sawConversionError ){
145
json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,"%s",sawConversionError);
146
free(sawConversionError);
147
}
148
db_finalize(&q);
149
return payV;
150
}
151
152
/*
153
** Parameters for the create-branch operation.
154
*/
155
typedef struct BranchCreateOptions{
156
char const * zName;
157
char const * zBasis;
158
char const * zColor;
159
int isPrivate;
160
/**
161
Might be set to an error string by
162
json_branch_new().
163
*/
164
char const * rcErrMsg;
165
} BranchCreateOptions;
166
167
/*
168
** Tries to create a new branch based on the options set in zOpt. If
169
** an error is encountered, zOpt->rcErrMsg _might_ be set to a
170
** descriptive string and one of the FossilJsonCodes values will be
171
** returned. Or fossil_fatal() (or similar) might be called, exiting
172
** the app.
173
**
174
** On success 0 is returned and if zNewRid is not NULL then the rid of
175
** the new branch is assigned to it.
176
**
177
** If zOpt->isPrivate is 0 but the parent branch is private,
178
** zOpt->isPrivate will be set to a non-zero value and the new branch
179
** will be private.
180
*/
181
static int json_branch_new(BranchCreateOptions * zOpt,
182
int *zNewRid){
183
/* Mostly copied from branch.c:branch_new(), but refactored a small
184
bit to not produce output or interact with the user. The
185
down-side to that is that we dropped the gpg-signing. It was
186
either that or abort the creation if we couldn't sign. We can't
187
sign over HTTP mode, anyway.
188
*/
189
char const * zBranch = zOpt->zName;
190
char const * zBasis = zOpt->zBasis;
191
char const * zColor = zOpt->zColor;
192
int rootid; /* RID of the root check-in - what we branch off of */
193
int brid; /* RID of the branch check-in */
194
int i; /* Loop counter */
195
char *zUuid; /* Artifact ID of origin */
196
Stmt q; /* Generic query */
197
char *zDate; /* Date that branch was created */
198
char *zComment; /* Check-in comment for the new branch */
199
Blob branch; /* manifest for the new branch */
200
Manifest *pParent; /* Parsed parent manifest */
201
Blob mcksum; /* Self-checksum on the manifest */
202
203
/* fossil branch new name */
204
if( zBranch==0 || zBranch[0]==0 ){
205
zOpt->rcErrMsg = "Branch name may not be null/empty.";
206
return FSL_JSON_E_INVALID_ARGS;
207
}
208
if( db_exists(
209
"SELECT 1 FROM tagxref"
210
" WHERE tagtype>0"
211
" AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%q')",
212
zBranch)!=0 ){
213
zOpt->rcErrMsg = "Branch already exists.";
214
return FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
215
}
216
217
db_begin_transaction();
218
rootid = name_to_typed_rid(zBasis, "ci");
219
if( rootid==0 ){
220
zOpt->rcErrMsg = "Basis branch not found.";
221
return FSL_JSON_E_RESOURCE_NOT_FOUND;
222
}
223
224
pParent = manifest_get(rootid, CFTYPE_MANIFEST, 0);
225
if( pParent==0 ){
226
zOpt->rcErrMsg = "Could not read parent manifest.";
227
return FSL_JSON_E_UNKNOWN;
228
}
229
230
/* Create a manifest for the new branch */
231
blob_zero(&branch);
232
if( pParent->zBaseline ){
233
blob_appendf(&branch, "B %s\n", pParent->zBaseline);
234
}
235
zComment = mprintf("Create new branch named \"%s\" "
236
"from \"%s\".", zBranch, zBasis);
237
blob_appendf(&branch, "C %F\n", zComment);
238
free(zComment);
239
zDate = date_in_standard_format("now");
240
blob_appendf(&branch, "D %s\n", zDate);
241
free(zDate);
242
243
/* Copy all of the content from the parent into the branch */
244
for(i=0; i<pParent->nFile; ++i){
245
blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
246
if( pParent->aFile[i].zUuid ){
247
blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
248
if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
249
blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
250
}
251
}
252
blob_append(&branch, "\n", 1);
253
}
254
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
255
blob_appendf(&branch, "P %s\n", zUuid);
256
free(zUuid);
257
if( pParent->zRepoCksum ){
258
blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
259
}
260
manifest_destroy(pParent);
261
262
/* Add the symbolic branch name and the "branch" tag to identify
263
** this as a new branch */
264
if( content_is_private(rootid) ) zOpt->isPrivate = 1;
265
if( zColor!=0 ){
266
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
267
}
268
blob_appendf(&branch, "T *branch * %F\n", zBranch);
269
blob_appendf(&branch, "T *sym-%F *\n", zBranch);
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_ex(&branch, 0, 0, 0, zOpt->isPrivate);
289
if( brid==0 ){
290
fossil_panic("Problem committing manifest: %s", g.zErrMsg);
291
}
292
db_add_unsent(brid);
293
if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
294
fossil_panic("%s", g.zErrMsg);
295
}
296
assert( blob_is_reset(&branch) );
297
content_deltify(rootid, &brid, 1, 0);
298
if( zNewRid ){
299
*zNewRid = brid;
300
}
301
302
/* Commit */
303
db_end_transaction(0);
304
305
return 0;
306
}
307
308
309
/*
310
** Impl of /json/branch/create.
311
*/
312
static cson_value * json_branch_create(void){
313
cson_value * payV = NULL;
314
cson_object * pay = NULL;
315
int rc = 0;
316
BranchCreateOptions opt;
317
char * zUuid = NULL;
318
const char *zMainBranch = db_main_branch();
319
int rid = 0;
320
if( !g.perm.Write ){
321
json_set_err(FSL_JSON_E_DENIED,
322
"Requires 'i' permissions.");
323
return NULL;
324
}
325
memset(&opt,0,sizeof(BranchCreateOptions));
326
if(fossil_has_json()){
327
opt.zName = json_getenv_cstr("name");
328
}
329
330
if(!opt.zName){
331
opt.zName = json_command_arg(g.json.dispatchDepth+1);
332
}
333
334
if(!opt.zName){
335
json_set_err(FSL_JSON_E_MISSING_ARGS,
336
"'name' parameter was not specified." );
337
return NULL;
338
}
339
340
opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL);
341
opt.zBasis = json_find_option_cstr("basis",NULL,NULL);
342
if(!opt.zBasis && !g.isHTTP){
343
opt.zBasis = json_command_arg(g.json.dispatchDepth+2);
344
}
345
if(!opt.zBasis){
346
opt.zBasis = fossil_strdup(zMainBranch);
347
}
348
opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1);
349
if(-1==opt.isPrivate){
350
if(!g.isHTTP){
351
opt.isPrivate = (NULL != find_option("private","",0));
352
}else{
353
opt.isPrivate = 0;
354
}
355
}
356
357
rc = json_branch_new( &opt, &rid );
358
if(rc){
359
json_set_err(rc, "%s", opt.rcErrMsg);
360
goto error;
361
}
362
assert(0 != rid);
363
payV = cson_value_new_object();
364
pay = cson_value_get_object(payV);
365
366
cson_object_set(pay,"name",json_new_string(opt.zName));
367
cson_object_set(pay,"basis",json_new_string(opt.zBasis));
368
cson_object_set(pay,"rid",json_new_int(rid));
369
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
370
cson_object_set(pay,"uuid", json_new_string(zUuid));
371
cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate));
372
free(zUuid);
373
if(opt.zColor){
374
cson_object_set(pay,"bgColor",json_new_string(opt.zColor));
375
}
376
377
goto ok;
378
error:
379
assert( 0 != g.json.resultCode );
380
cson_value_free(payV);
381
payV = NULL;
382
ok:
383
return payV;
384
}
385
386
#endif /* FOSSIL_ENABLE_JSON */
387

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button