1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** drh@hwaci.com
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code used to create new branches within a repository.
19 */
20 #include "config.h"
21 #include "branch.h"
22 #include <assert.h>
23
24 /*
25 ** fossil branch new BRANCH-NAME ?ORIGIN-CHECK-IN? ?-bgcolor COLOR?
26 ** argv0 argv1 argv2 argv3 argv4
27 */
28 void branch_new(void){
29 int rootid; /* RID of the root check-in - what we branch off of */
30 int brid; /* RID of the branch check-in */
31 int noSign; /* True if the branch is unsigned */
32 int i; /* Loop counter */
33 char *zUuid; /* Artifact ID of origin */
34 Stmt q; /* Generic query */
35 const char *zBranch; /* Name of the new branch */
36 char *zDate; /* Date that branch was created */
37 char *zComment; /* Check-in comment for the new branch */
38 const char *zColor; /* Color of the new branch */
39 Blob branch; /* manifest for the new branch */
40 Manifest *pParent; /* Parsed parent manifest */
41 Blob mcksum; /* Self-checksum on the manifest */
42 const char *zDateOvrd; /* Override date string */
43 const char *zUserOvrd; /* Override user name */
44
45 noSign = find_option("nosign","",0)!=0;
46 zColor = find_option("bgcolor","c",1);
47 zDateOvrd = find_option("date-override",0,1);
48 zUserOvrd = find_option("user-override",0,1);
49 verify_all_options();
50 if( g.argc<5 ){
51 usage("new BRANCH-NAME CHECK-IN ?-bgcolor COLOR?");
52 }
53 db_find_and_open_repository(0, 0);
54 noSign = db_get_int("omitsign", 0)|noSign;
55
56 /* fossil branch new name */
57 zBranch = g.argv[3];
58 if( zBranch==0 || zBranch[0]==0 ){
59 fossil_panic("branch name cannot be empty");
60 }
61 if( db_exists(
62 "SELECT 1 FROM tagxref"
63 " WHERE tagtype>0"
64 " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')",
65 zBranch)!=0 ){
66 fossil_fatal("branch \"%s\" already exists", zBranch);
67 }
68
69 user_select();
70 db_begin_transaction();
71 rootid = name_to_rid(g.argv[4]);
72 if( rootid==0 ){
73 fossil_fatal("unable to locate check-in off of which to branch");
74 }
75
76 pParent = manifest_get(rootid, CFTYPE_MANIFEST);
77 if( pParent==0 ){
78 fossil_fatal("%s is not a valid check-in", g.argv[4]);
79 }
80
81 /* Create a manifest for the new branch */
82 blob_zero(&branch);
83 if( pParent->zBaseline ){
84 blob_appendf(&branch, "B %s\n", pParent->zBaseline);
85 }
86 zComment = mprintf("Create new branch named \"%h\"", zBranch);
87 blob_appendf(&branch, "C %F\n", zComment);
88 zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
89 blob_appendf(&branch, "D %s\n", zDate);
90
91 /* Copy all of the content from the parent into the branch */
92 for(i=0; i<pParent->nFile; ++i){
93 blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
94 if( pParent->aFile[i].zUuid ){
95 blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
96 if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
97 blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
98 }
99 }
100 blob_append(&branch, "\n", 1);
101 }
102 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
103 blob_appendf(&branch, "P %s\n", zUuid);
104 blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
105 manifest_destroy(pParent);
106
107 /* Add the symbolic branch name and the "branch" tag to identify
108 ** this as a new branch */
109 if( zColor!=0 ){
110 blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
111 }
112 blob_appendf(&branch, "T *branch * %F\n", zBranch);
113 blob_appendf(&branch, "T *sym-%F *\n", zBranch);
114
115 /* Cancel all other symbolic tags */
116 db_prepare(&q,
117 "SELECT tagname FROM tagxref, tag"
118 " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
119 " AND tagtype>0 AND tagname GLOB 'sym-*'"
120 " ORDER BY tagname",
121 rootid);
122 while( db_step(&q)==SQLITE_ROW ){
123 const char *zTag = db_column_text(&q, 0);
124 blob_appendf(&branch, "T -%F *\n", zTag);
125 }
126 db_finalize(&q);
127
128 blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
129 md5sum_blob(&branch, &mcksum);
130 blob_appendf(&branch, "Z %b\n", &mcksum);
131 if( !noSign && clearsign(&branch, &branch) ){
132 Blob ans;
133 blob_zero(&ans);
134 prompt_user("unable to sign manifest. continue (y/N)? ", &ans);
135 if( blob_str(&ans)[0]!='y' ){
136 db_end_transaction(1);
137 fossil_exit(1);
138 }
139 }
140
141 brid = content_put(&branch, 0, 0, 0);
142 if( brid==0 ){
143 fossil_panic("trouble committing manifest: %s", g.zErrMsg);
144 }
145 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
146 if( manifest_crosslink(brid, &branch)==0 ){
147 fossil_panic("unable to install new manifest");
148 }
149 content_deltify(rootid, brid, 0);
150 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
151 printf("New branch: %s\n", zUuid);
152 if( g.argc==3 ){
153 printf(
154 "\n"
155 "Note: the local check-out has not been updated to the new\n"
156 " branch. To begin working on the new branch, do this:\n"
157 "\n"
158 " %s update %s\n",
159 fossil_nameofexe(), zBranch
160 );
161 }
162
163
164 /* Commit */
165 db_end_transaction(0);
166
167 /* Do an autosync push, if requested */
168 autosync(AUTOSYNC_PUSH);
169 }
170
171 /*
172 ** COMMAND: branch
173 **
174 ** Usage: %fossil branch SUBCOMMAND ... ?-R|--repository FILE?
175 **
176 ** Run various subcommands to manage branches of the open repository or
177 ** of the repository identified by the -R or --repository option.
178 **
179 ** %fossil branch new BRANCH-NAME BASIS ?-bgcolor COLOR?
180 **
181 ** Create a new branch BRANCH-NAME off of check-in BASIS.
182 ** You can optionally give the branch a default color.
183 **
184 ** %fossil branch list
185 **
186 ** List all branches
187 **
188 */
189 void branch_cmd(void){
190 int n;
191 const char *zCmd = "list";
192 db_find_and_open_repository(0, 0);
193 if( g.argc<2 ){
194 usage("new|list ...");
195 }
196 if( g.argc>=3 ) zCmd = g.argv[2];
197 n = strlen(zCmd);
198 if( strncmp(zCmd,"new",n)==0 ){
199 branch_new();
200 }else if( strncmp(zCmd,"list",n)==0 ){
201 Stmt q;
202 int vid;
203 char *zCurrent = 0;
204
205 if( g.localOpen ){
206 vid = db_lget_int("checkout", 0);
207 zCurrent = db_text(0, "SELECT value FROM tagxref"
208 " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH);
209 }
210 compute_leaves(0, 1);
211 db_prepare(&q,
212 "SELECT DISTINCT value FROM tagxref"
213 " WHERE tagid=%d AND value NOT NULL AND rid IN leaves"
214 " ORDER BY value /*sort*/",
215 TAG_BRANCH
216 );
217 while( db_step(&q)==SQLITE_ROW ){
218 const char *zBr = db_column_text(&q, 0);
219 int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0;
220 printf("%s%s\n", (isCur ? "* " : " "), zBr);
221 }
222 db_finalize(&q);
223 }else{
224 fossil_panic("branch subcommand should be one of: "
225 "new list");
226 }
227 }
228
229 /*
230 ** WEBPAGE: brlist
231 **
232 ** Show a timeline of all branches
233 */
234 void brlist_page(void){
235 Stmt q;
236 int cnt;
237 int showClosed = P("closed")!=0;
238
239 login_check_credentials();
240 if( !g.okRead ){ login_needed(); return; }
241
242 style_header(showClosed ? "Closed Branches" : "Open Branches");
243 style_submenu_element("Timeline", "Timeline", "brtimeline");
244 if( showClosed ){
245 style_submenu_element("Open","Open","brlist");
246 }else{
247 style_submenu_element("Closed","Closed","brlist?closed");
248 }
249 login_anonymous_available();
250 compute_leaves(0, 1);
251 style_sidebox_begin("Nomenclature:", "33%");
252 @ <ol>
253 @ <li> An <div class="sideboxDescribed"><a href="brlist">
254 @ open branch</a></div> is a branch that has one or
255 @ more <a href="leaves">open leaves.</a>
256 @ The presence of open leaves presumably means
257 @ that the branch is still being extended with new check-ins.</li>
258 @ <li> A <div class="sideboxDescribed"><a href="brlist?closed">
259 @ closed branch</a></div> is a branch with only
260 @ <div class="sideboxDescribed"><a href="leaves?closed">
261 @ closed leaves</a></div>.
262 @ Closed branches are fixed and do not change (unless they are first
263 @ reopened)</li>
264 @ </ol>
265 style_sidebox_end();
266
267 cnt = 0;
268 if( !showClosed ){
269 db_prepare(&q,
270 "SELECT DISTINCT value FROM tagxref"
271 " WHERE tagid=%d AND value NOT NULL"
272 " AND rid IN leaves"
273 " ORDER BY value /*sort*/",
274 TAG_BRANCH
275 );
276 }else{
277 db_prepare(&q,
278 "SELECT value FROM tagxref"
279 " WHERE tagid=%d AND value NOT NULL"
280 " EXCEPT "
281 "SELECT value FROM tagxref"
282 " WHERE tagid=%d AND value NOT NULL"
283 " AND rid IN leaves"
284 " ORDER BY value /*sort*/",
285 TAG_BRANCH, TAG_BRANCH
286 );
287 }
288 while( db_step(&q)==SQLITE_ROW ){
289 const char *zBr = db_column_text(&q, 0);
290 if( cnt==0 ){
291 if( showClosed ){
292 @ <h2>Closed Branches:</h2>
293 }else{
294 @ <h2>Open Branches:</h2>
295 }
296 @ <ul>
297 cnt++;
298 }
299 if( g.okHistory ){
300 @ <li><a href="%s(g.zTop)/timeline?r=%T(zBr)">%h(zBr)</a></li>
301 }else{
302 @ <li><b>%h(zBr)</b></li>
303 }
304 }
305 if( cnt ){
306 @ </ul>
307 }
308 db_finalize(&q);
309 @ <script type="text/JavaScript">
310 @ function xin(id){
311 @ }
312 @ function xout(id){
313 @ }
314 @ </script>
315 style_footer();
316 }
317
318 /*
319 ** This routine is called while for each check-in that is rendered by
320 ** the timeline of a "brlist" page. Add some additional hyperlinks
321 ** to the end of the line.
322 */
323 static void brtimeline_extra(int rid){
324 Stmt q;
325 if( !g.okHistory ) return;
326 db_prepare(&q,
327 "SELECT substr(tagname,5) FROM tagxref, tag"
328 " WHERE tagxref.rid=%d"
329 " AND tagxref.tagid=tag.tagid"
330 " AND tagxref.tagtype>0"
331 " AND tag.tagname GLOB 'sym-*'",
332 rid
333 );
334 while( db_step(&q)==SQLITE_ROW ){
335 const char *zTagName = db_column_text(&q, 0);
336 @ <a href="%s(g.zTop)/timeline?r=%T(zTagName)">[timeline]</a>
337 }
338 db_finalize(&q);
339 }
340
341 /*
342 ** WEBPAGE: brtimeline
343 **
344 ** Show a timeline of all branches
345 */
346 void brtimeline_page(void){
347 Stmt q;
348
349 login_check_credentials();
350 if( !g.okRead ){ login_needed(); return; }
351
352 style_header("Branches");
353 style_submenu_element("List", "List", "brlist");
354 login_anonymous_available();
355 @ <h2>The initial check-in for each branch:</h2>
356 db_prepare(&q,
357 "%s AND blob.rid IN (SELECT rid FROM tagxref"
358 " WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
359 " ORDER BY event.mtime DESC",
360 timeline_query_for_www(), TAG_BRANCH
361 );
362 www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
363 db_finalize(&q);
364 @ <script type="text/JavaScript">
365 @ function xin(id){
366 @ }
367 @ function xout(id){
368 @ }
369 @ </script>
370 style_footer();
371 }