Fossil SCM

Merged trunk changes in

wyoung 2024-02-08 20:35 inskinerator-modern-backport merge
Commit 9a9276d69326d94faea311ec28fe97ff953eb5f6d4c0d0b870530682383c85a7
+66 -1
--- extsrc/pikchr.c
+++ extsrc/pikchr.c
@@ -3577,10 +3577,12 @@
35773577
{ "color", 0.0 },
35783578
{ "cylht", 0.5 },
35793579
{ "cylrad", 0.075 },
35803580
{ "cylwid", 0.75 },
35813581
{ "dashwid", 0.05 },
3582
+ { "diamondht", 0.75 },
3583
+ { "diamondwid", 1.0 },
35823584
{ "dotrad", 0.015 },
35833585
{ "ellipseht", 0.5 },
35843586
{ "ellipsewid", 0.75 },
35853587
{ "fileht", 0.75 },
35863588
{ "filerad", 0.15 },
@@ -3957,10 +3959,62 @@
39573959
pik_append(p,"\" />\n", -1);
39583960
}
39593961
pik_append_txt(p, pObj, 0);
39603962
}
39613963
3964
+/* Methods for the "diamond" class */
3965
+static void diamondInit(Pik *p, PObj *pObj){
3966
+ pObj->w = pik_value(p, "diamondwid",10,0);
3967
+ pObj->h = pik_value(p, "diamondht",9,0);
3968
+}
3969
+/* Return offset from the center of the box to the compass point
3970
+** given by parameter cp */
3971
+static PPoint diamondOffset(Pik *p, PObj *pObj, int cp){
3972
+ PPoint pt = cZeroPoint;
3973
+ PNum w2 = 0.5*pObj->w;
3974
+ PNum w4 = 0.25*pObj->w;
3975
+ PNum h2 = 0.5*pObj->h;
3976
+ PNum h4 = 0.25*pObj->h;
3977
+ switch( cp ){
3978
+ case CP_C: break;
3979
+ case CP_N: pt.x = 0.0; pt.y = h2; break;
3980
+ case CP_NE: pt.x = w4; pt.y = h4; break;
3981
+ case CP_E: pt.x = w2; pt.y = 0.0; break;
3982
+ case CP_SE: pt.x = w4; pt.y = -h4; break;
3983
+ case CP_S: pt.x = 0.0; pt.y = -h2; break;
3984
+ case CP_SW: pt.x = -w4; pt.y = -h4; break;
3985
+ case CP_W: pt.x = -w2; pt.y = 0.0; break;
3986
+ case CP_NW: pt.x = -w4; pt.y = h4; break;
3987
+ default: assert(0);
3988
+ }
3989
+ UNUSED_PARAMETER(p);
3990
+ return pt;
3991
+}
3992
+static void diamondFit(Pik *p, PObj *pObj, PNum w, PNum h){
3993
+ if( pObj->w>0 && pObj->h>0 ){
3994
+ PNum x = pObj->w*h/pObj->h + w;
3995
+ PNum y = pObj->h*x/pObj->w;
3996
+ pObj->w = x;
3997
+ pObj->h = y;
3998
+ }
3999
+ UNUSED_PARAMETER(p);
4000
+}
4001
+static void diamondRender(Pik *p, PObj *pObj){
4002
+ PNum w2 = 0.5*pObj->w;
4003
+ PNum h2 = 0.5*pObj->h;
4004
+ PPoint pt = pObj->ptAt;
4005
+ if( pObj->sw>=0.0 ){
4006
+ pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y);
4007
+ pik_append_xy(p,"L", pt.x,pt.y-h2);
4008
+ pik_append_xy(p,"L", pt.x+w2,pt.y);
4009
+ pik_append_xy(p,"L", pt.x,pt.y+h2);
4010
+ pik_append(p,"Z\" ",-1);
4011
+ pik_append_style(p,pObj,3);
4012
+ pik_append(p,"\" />\n", -1);
4013
+ }
4014
+ pik_append_txt(p, pObj, 0);
4015
+}
39624016
39634017
39644018
/* Methods for the "ellipse" class */
39654019
static void ellipseInit(Pik *p, PObj *pObj){
39664020
pObj->w = pik_value(p, "ellipsewid",10,0);
@@ -4341,10 +4395,21 @@
43414395
/* xChop */ boxChop,
43424396
/* xOffset */ cylinderOffset,
43434397
/* xFit */ cylinderFit,
43444398
/* xRender */ cylinderRender
43454399
},
4400
+ { /* name */ "diamond",
4401
+ /* isline */ 0,
4402
+ /* eJust */ 0,
4403
+ /* xInit */ diamondInit,
4404
+ /* xNumProp */ 0,
4405
+ /* xCheck */ 0,
4406
+ /* xChop */ boxChop,
4407
+ /* xOffset */ diamondOffset,
4408
+ /* xFit */ diamondFit,
4409
+ /* xRender */ diamondRender
4410
+ },
43464411
{ /* name */ "dot",
43474412
/* isline */ 0,
43484413
/* eJust */ 0,
43494414
/* xInit */ dotInit,
43504415
/* xNumProp */ dotNumProp,
@@ -8143,6 +8208,6 @@
81438208
81448209
81458210
#endif /* PIKCHR_TCL */
81468211
81478212
8148
-#line 8173 "pikchr.c"
8213
+#line 8238 "pikchr.c"
81498214
--- extsrc/pikchr.c
+++ extsrc/pikchr.c
@@ -3577,10 +3577,12 @@
3577 { "color", 0.0 },
3578 { "cylht", 0.5 },
3579 { "cylrad", 0.075 },
3580 { "cylwid", 0.75 },
3581 { "dashwid", 0.05 },
 
 
3582 { "dotrad", 0.015 },
3583 { "ellipseht", 0.5 },
3584 { "ellipsewid", 0.75 },
3585 { "fileht", 0.75 },
3586 { "filerad", 0.15 },
@@ -3957,10 +3959,62 @@
3957 pik_append(p,"\" />\n", -1);
3958 }
3959 pik_append_txt(p, pObj, 0);
3960 }
3961
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3962
3963
3964 /* Methods for the "ellipse" class */
3965 static void ellipseInit(Pik *p, PObj *pObj){
3966 pObj->w = pik_value(p, "ellipsewid",10,0);
@@ -4341,10 +4395,21 @@
4341 /* xChop */ boxChop,
4342 /* xOffset */ cylinderOffset,
4343 /* xFit */ cylinderFit,
4344 /* xRender */ cylinderRender
4345 },
 
 
 
 
 
 
 
 
 
 
 
4346 { /* name */ "dot",
4347 /* isline */ 0,
4348 /* eJust */ 0,
4349 /* xInit */ dotInit,
4350 /* xNumProp */ dotNumProp,
@@ -8143,6 +8208,6 @@
8143
8144
8145 #endif /* PIKCHR_TCL */
8146
8147
8148 #line 8173 "pikchr.c"
8149
--- extsrc/pikchr.c
+++ extsrc/pikchr.c
@@ -3577,10 +3577,12 @@
3577 { "color", 0.0 },
3578 { "cylht", 0.5 },
3579 { "cylrad", 0.075 },
3580 { "cylwid", 0.75 },
3581 { "dashwid", 0.05 },
3582 { "diamondht", 0.75 },
3583 { "diamondwid", 1.0 },
3584 { "dotrad", 0.015 },
3585 { "ellipseht", 0.5 },
3586 { "ellipsewid", 0.75 },
3587 { "fileht", 0.75 },
3588 { "filerad", 0.15 },
@@ -3957,10 +3959,62 @@
3959 pik_append(p,"\" />\n", -1);
3960 }
3961 pik_append_txt(p, pObj, 0);
3962 }
3963
3964 /* Methods for the "diamond" class */
3965 static void diamondInit(Pik *p, PObj *pObj){
3966 pObj->w = pik_value(p, "diamondwid",10,0);
3967 pObj->h = pik_value(p, "diamondht",9,0);
3968 }
3969 /* Return offset from the center of the box to the compass point
3970 ** given by parameter cp */
3971 static PPoint diamondOffset(Pik *p, PObj *pObj, int cp){
3972 PPoint pt = cZeroPoint;
3973 PNum w2 = 0.5*pObj->w;
3974 PNum w4 = 0.25*pObj->w;
3975 PNum h2 = 0.5*pObj->h;
3976 PNum h4 = 0.25*pObj->h;
3977 switch( cp ){
3978 case CP_C: break;
3979 case CP_N: pt.x = 0.0; pt.y = h2; break;
3980 case CP_NE: pt.x = w4; pt.y = h4; break;
3981 case CP_E: pt.x = w2; pt.y = 0.0; break;
3982 case CP_SE: pt.x = w4; pt.y = -h4; break;
3983 case CP_S: pt.x = 0.0; pt.y = -h2; break;
3984 case CP_SW: pt.x = -w4; pt.y = -h4; break;
3985 case CP_W: pt.x = -w2; pt.y = 0.0; break;
3986 case CP_NW: pt.x = -w4; pt.y = h4; break;
3987 default: assert(0);
3988 }
3989 UNUSED_PARAMETER(p);
3990 return pt;
3991 }
3992 static void diamondFit(Pik *p, PObj *pObj, PNum w, PNum h){
3993 if( pObj->w>0 && pObj->h>0 ){
3994 PNum x = pObj->w*h/pObj->h + w;
3995 PNum y = pObj->h*x/pObj->w;
3996 pObj->w = x;
3997 pObj->h = y;
3998 }
3999 UNUSED_PARAMETER(p);
4000 }
4001 static void diamondRender(Pik *p, PObj *pObj){
4002 PNum w2 = 0.5*pObj->w;
4003 PNum h2 = 0.5*pObj->h;
4004 PPoint pt = pObj->ptAt;
4005 if( pObj->sw>=0.0 ){
4006 pik_append_xy(p,"<path d=\"M", pt.x-w2,pt.y);
4007 pik_append_xy(p,"L", pt.x,pt.y-h2);
4008 pik_append_xy(p,"L", pt.x+w2,pt.y);
4009 pik_append_xy(p,"L", pt.x,pt.y+h2);
4010 pik_append(p,"Z\" ",-1);
4011 pik_append_style(p,pObj,3);
4012 pik_append(p,"\" />\n", -1);
4013 }
4014 pik_append_txt(p, pObj, 0);
4015 }
4016
4017
4018 /* Methods for the "ellipse" class */
4019 static void ellipseInit(Pik *p, PObj *pObj){
4020 pObj->w = pik_value(p, "ellipsewid",10,0);
@@ -4341,10 +4395,21 @@
4395 /* xChop */ boxChop,
4396 /* xOffset */ cylinderOffset,
4397 /* xFit */ cylinderFit,
4398 /* xRender */ cylinderRender
4399 },
4400 { /* name */ "diamond",
4401 /* isline */ 0,
4402 /* eJust */ 0,
4403 /* xInit */ diamondInit,
4404 /* xNumProp */ 0,
4405 /* xCheck */ 0,
4406 /* xChop */ boxChop,
4407 /* xOffset */ diamondOffset,
4408 /* xFit */ diamondFit,
4409 /* xRender */ diamondRender
4410 },
4411 { /* name */ "dot",
4412 /* isline */ 0,
4413 /* eJust */ 0,
4414 /* xInit */ dotInit,
4415 /* xNumProp */ dotNumProp,
@@ -8143,6 +8208,6 @@
8208
8209
8210 #endif /* PIKCHR_TCL */
8211
8212
8213 #line 8238 "pikchr.c"
8214
--- extsrc/pikchr.wasm
+++ extsrc/pikchr.wasm
cannot compute difference between binary files
11
--- extsrc/pikchr.wasm
+++ extsrc/pikchr.wasm
0 annot compute difference between binary files
1
--- extsrc/pikchr.wasm
+++ extsrc/pikchr.wasm
0 annot compute difference between binary files
1
+8 -3
--- src/diff.c
+++ src/diff.c
@@ -19,10 +19,11 @@
1919
** text files.
2020
*/
2121
#include "config.h"
2222
#include "diff.h"
2323
#include <assert.h>
24
+#include <errno.h>
2425
2526
2627
#if INTERFACE
2728
/*
2829
** Flag parameters to the text_diff() routine used to control the formatting
@@ -3157,13 +3158,17 @@
31573158
/* Undocumented and unsupported flags used for development
31583159
** debugging and analysis: */
31593160
if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
31603161
if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
31613162
}
3162
- if( (z = find_option("context","c",1))!=0 && (f = atoi(z))!=0 ){
3163
- pCfg->nContext = f;
3164
- diffFlags |= DIFF_CONTEXT_EX;
3163
+ if( (z = find_option("context","c",1))!=0 ){
3164
+ char *zEnd;
3165
+ f = (int)strtol(z, &zEnd, 10);
3166
+ if( zEnd[0]==0 && errno!=ERANGE ){
3167
+ pCfg->nContext = f;
3168
+ diffFlags |= DIFF_CONTEXT_EX;
3169
+ }
31653170
}
31663171
if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
31673172
pCfg->wColumn = f;
31683173
}
31693174
if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
31703175
--- src/diff.c
+++ src/diff.c
@@ -19,10 +19,11 @@
19 ** text files.
20 */
21 #include "config.h"
22 #include "diff.h"
23 #include <assert.h>
 
24
25
26 #if INTERFACE
27 /*
28 ** Flag parameters to the text_diff() routine used to control the formatting
@@ -3157,13 +3158,17 @@
3157 /* Undocumented and unsupported flags used for development
3158 ** debugging and analysis: */
3159 if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
3160 if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
3161 }
3162 if( (z = find_option("context","c",1))!=0 && (f = atoi(z))!=0 ){
3163 pCfg->nContext = f;
3164 diffFlags |= DIFF_CONTEXT_EX;
 
 
 
 
3165 }
3166 if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
3167 pCfg->wColumn = f;
3168 }
3169 if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
3170
--- src/diff.c
+++ src/diff.c
@@ -19,10 +19,11 @@
19 ** text files.
20 */
21 #include "config.h"
22 #include "diff.h"
23 #include <assert.h>
24 #include <errno.h>
25
26
27 #if INTERFACE
28 /*
29 ** Flag parameters to the text_diff() routine used to control the formatting
@@ -3157,13 +3158,17 @@
3158 /* Undocumented and unsupported flags used for development
3159 ** debugging and analysis: */
3160 if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
3161 if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
3162 }
3163 if( (z = find_option("context","c",1))!=0 ){
3164 char *zEnd;
3165 f = (int)strtol(z, &zEnd, 10);
3166 if( zEnd[0]==0 && errno!=ERANGE ){
3167 pCfg->nContext = f;
3168 diffFlags |= DIFF_CONTEXT_EX;
3169 }
3170 }
3171 if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
3172 pCfg->wColumn = f;
3173 }
3174 if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
3175
+165 -2
--- src/http.c
+++ src/http.c
@@ -269,10 +269,130 @@
269269
blob_read_from_file(pReply, zDownlink, ExtFILE);
270270
file_delete(zDownlink);
271271
}
272272
return rc;
273273
}
274
+
275
+/* If iTruth<0 then guess as to whether or not a PATH= argument is required
276
+** when using ssh to run fossil on a remote machine name zHostname. Return
277
+** true if a PATH= should be provided and 0 if not.
278
+**
279
+** If iTruth is 1 or 0 then that means that the PATH= is or is not required,
280
+** respectively. Record this fact for future reference.
281
+**
282
+** If iTruth is 99 or more, then toggle the value that will be returned
283
+** for future iTruth==(-1) queries.
284
+*/
285
+int ssh_needs_path_argument(const char *zHostname, int iTruth){
286
+ int ans = 0; /* Default to "no" */
287
+ char *z = mprintf("use-path-for-ssh:%s", zHostname);
288
+ if( iTruth<0 ){
289
+ if( db_get_boolean(z/*works-like:"x"*/, 0) ) ans = 1;
290
+ }else{
291
+ if( iTruth>=99 ){
292
+ iTruth = !db_get_boolean(z/*works-like:"x"*/, 0);
293
+ }
294
+ if( iTruth ){
295
+ ans = 1;
296
+ db_set(z/*works-like:"x"*/, "1", 1);
297
+ }else{
298
+ db_unset(z/*works-like:"x"*/, 1);
299
+ }
300
+ }
301
+ fossil_free(z);
302
+ return ans;
303
+}
304
+
305
+/*
306
+** COMMAND: test-ssh-needs-path
307
+**
308
+** Usage: fossil test-ssh-needs-path ?HOSTNAME? ?BOOLEAN?
309
+**
310
+** With one argument, show whether or not the PATH= argument is included
311
+** by default for HOSTNAME. If the second argument is a boolean, then
312
+** change the value.
313
+**
314
+** With no arguments, show all hosts for which ssh-needs-path is true.
315
+*/
316
+void test_ssh_needs_path(void){
317
+ db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
318
+ db_open_config(0,0);
319
+ if( g.argc>=3 ){
320
+ const char *zHost = g.argv[2];
321
+ int a = -1;
322
+ int rc;
323
+ if( g.argc>=4 ) a = is_truth(g.argv[3]);
324
+ rc = ssh_needs_path_argument(zHost, a);
325
+ fossil_print("%-20s %s\n", zHost, rc ? "yes" : "no");
326
+ }else{
327
+ Stmt s;
328
+ db_swap_connections();
329
+ db_prepare(&s, "SELECT substr(name,18) FROM global_config"
330
+ " WHERE name GLOB 'use-path-for-ssh:*'");
331
+ while( db_step(&s)==SQLITE_ROW ){
332
+ const char *zHost = db_column_text(&s,0);
333
+ fossil_print("%-20s yes\n", zHost);
334
+ }
335
+ db_finalize(&s);
336
+ db_swap_connections();
337
+ }
338
+}
339
+
340
+/* Add an approprate PATH= argument to the SSH command under construction
341
+** in pCmd.
342
+**
343
+** About This Feature
344
+** ==================
345
+**
346
+** On some ssh servers (Macs in particular are guilty of this) the PATH
347
+** variable in the shell that runs the command that is sent to the remote
348
+** host contains a limited number of read-only system directories:
349
+**
350
+** /usr/bin:/bin:/usr/sbin:/sbin
351
+**
352
+** The fossil executable cannot be installed into any of those directories
353
+** because they are locked down, and so the "fossil" command cannot run.
354
+**
355
+** To work around this, the fossil command is prefixed with the PATH=
356
+** argument, inserted by this function, to augment the PATH with additional
357
+** directories in which the fossil executable is often found.
358
+**
359
+** But other ssh servers are confused by this initial PATH= argument.
360
+** Some ssh servers have a list of programs that they are allowed to run
361
+** and will fail if the first argument is not on that list, and PATH=....
362
+** is not on that list.
363
+**
364
+** So that various commands that use ssh can run seamlessly on a variety
365
+** of systems (commands that use ssh include "fossil sync" with an ssh:
366
+** URL and the "fossil patch pull" and "fossil patch push" commands where
367
+** the destination directory starts with HOSTNAME: or USER@HOSTNAME:.)
368
+** the following algorithm is used:
369
+**
370
+** * First try running the fossil without any PATH= argument. If that
371
+** works (and it does on a majority of systems) then we are done.
372
+**
373
+** * If the first attempt fails, then try again after adding the
374
+** PATH= prefix argument. (This function is what adds that
375
+** argument.) If the retry works, then remember that fact using
376
+** the use-path-for-ssh:HOSTNAME setting so that the first step
377
+** is skipped on subsequent uses of the same command.
378
+**
379
+** See the forum thread at
380
+** https://fossil-scm.org/forum/forumpost/4903cb4b691af7ce for more
381
+** background.
382
+**
383
+** See also:
384
+**
385
+** * The ssh_needs_path_argument() function above.
386
+** * The test-ssh-needs-path command that shows the settings
387
+** that cache whether or not a PATH= is needed for a particular
388
+** HOSTNAME.
389
+*/
390
+void ssh_add_path_argument(Blob *pCmd){
391
+ blob_append_escaped_arg(pCmd,
392
+ "PATH=$HOME/bin:/usr/local/bin:/opt/homebrew/bin:$PATH", 1);
393
+}
274394
275395
/*
276396
** Sign the content in pSend, compress it, and send it to the server
277397
** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply
278398
** in pRecv. pRecv is assumed to be uninitialized when
@@ -304,10 +424,20 @@
304424
305425
if( g.zHttpCmd!=0 ){
306426
/* Handle the --transport-command option for "fossil sync" and similar */
307427
return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
308428
}
429
+
430
+ /* Activate the PATH= auxiliary argument to the ssh command if that
431
+ ** is called for.
432
+ */
433
+ if( g.url.isSsh
434
+ && (g.url.flags & URL_SSH_RETRY)==0
435
+ && ssh_needs_path_argument(g.url.hostname, -1)
436
+ ){
437
+ g.url.flags |= URL_SSH_PATH;
438
+ }
309439
310440
if( transport_open(&g.url) ){
311441
fossil_warning("%s", transport_errmsg(&g.url));
312442
return 1;
313443
}
@@ -484,12 +614,45 @@
484614
}
485615
}
486616
}
487617
}
488618
if( iLength<0 ){
489
- fossil_warning("server did not reply");
490
- goto write_err;
619
+ /* We got nothing back from the server. If using the ssh: protocol,
620
+ ** this might mean we need to add or remove the PATH=... argument
621
+ ** to the SSH command being sent. If that is the case, retry the
622
+ ** request after adding or removing the PATH= argument.
623
+ */
624
+ if( g.url.isSsh /* This is an SSH: sync */
625
+ && (g.url.flags & URL_SSH_EXE)==0 /* Does not have ?fossil=.... */
626
+ && (g.url.flags & URL_SSH_RETRY)==0 /* Not retried already */
627
+ ){
628
+ /* Retry after flipping the SSH_PATH setting */
629
+ transport_close(&g.url);
630
+ fossil_print(
631
+ "First attempt to run fossil on %s using SSH failed.\n"
632
+ "Retrying %s the PATH= argument.\n",
633
+ g.url.hostname,
634
+ (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with"
635
+ );
636
+ g.url.flags ^= URL_SSH_PATH|URL_SSH_RETRY;
637
+ rc = http_exchange(pSend,pReply,mHttpFlags,0,zAltMimetype);
638
+ if( rc==0 ){
639
+ (void)ssh_needs_path_argument(g.url.hostname,
640
+ (g.url.flags & URL_SSH_PATH)!=0);
641
+ }
642
+ return rc;
643
+ }else{
644
+ /* The problem could not be corrected by retrying. Report the
645
+ ** the error. */
646
+ if( g.url.isSsh && !g.fSshTrace ){
647
+ fossil_warning("server did not reply: "
648
+ " rerun with --sshtrace for diagnostics");
649
+ }else{
650
+ fossil_warning("server did not reply");
651
+ }
652
+ goto write_err;
653
+ }
491654
}
492655
if( rc!=200 ){
493656
fossil_warning("\"location:\" missing from %d redirect reply", rc);
494657
goto write_err;
495658
}
496659
--- src/http.c
+++ src/http.c
@@ -269,10 +269,130 @@
269 blob_read_from_file(pReply, zDownlink, ExtFILE);
270 file_delete(zDownlink);
271 }
272 return rc;
273 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
275 /*
276 ** Sign the content in pSend, compress it, and send it to the server
277 ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply
278 ** in pRecv. pRecv is assumed to be uninitialized when
@@ -304,10 +424,20 @@
304
305 if( g.zHttpCmd!=0 ){
306 /* Handle the --transport-command option for "fossil sync" and similar */
307 return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
308 }
 
 
 
 
 
 
 
 
 
 
309
310 if( transport_open(&g.url) ){
311 fossil_warning("%s", transport_errmsg(&g.url));
312 return 1;
313 }
@@ -484,12 +614,45 @@
484 }
485 }
486 }
487 }
488 if( iLength<0 ){
489 fossil_warning("server did not reply");
490 goto write_err;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
491 }
492 if( rc!=200 ){
493 fossil_warning("\"location:\" missing from %d redirect reply", rc);
494 goto write_err;
495 }
496
--- src/http.c
+++ src/http.c
@@ -269,10 +269,130 @@
269 blob_read_from_file(pReply, zDownlink, ExtFILE);
270 file_delete(zDownlink);
271 }
272 return rc;
273 }
274
275 /* If iTruth<0 then guess as to whether or not a PATH= argument is required
276 ** when using ssh to run fossil on a remote machine name zHostname. Return
277 ** true if a PATH= should be provided and 0 if not.
278 **
279 ** If iTruth is 1 or 0 then that means that the PATH= is or is not required,
280 ** respectively. Record this fact for future reference.
281 **
282 ** If iTruth is 99 or more, then toggle the value that will be returned
283 ** for future iTruth==(-1) queries.
284 */
285 int ssh_needs_path_argument(const char *zHostname, int iTruth){
286 int ans = 0; /* Default to "no" */
287 char *z = mprintf("use-path-for-ssh:%s", zHostname);
288 if( iTruth<0 ){
289 if( db_get_boolean(z/*works-like:"x"*/, 0) ) ans = 1;
290 }else{
291 if( iTruth>=99 ){
292 iTruth = !db_get_boolean(z/*works-like:"x"*/, 0);
293 }
294 if( iTruth ){
295 ans = 1;
296 db_set(z/*works-like:"x"*/, "1", 1);
297 }else{
298 db_unset(z/*works-like:"x"*/, 1);
299 }
300 }
301 fossil_free(z);
302 return ans;
303 }
304
305 /*
306 ** COMMAND: test-ssh-needs-path
307 **
308 ** Usage: fossil test-ssh-needs-path ?HOSTNAME? ?BOOLEAN?
309 **
310 ** With one argument, show whether or not the PATH= argument is included
311 ** by default for HOSTNAME. If the second argument is a boolean, then
312 ** change the value.
313 **
314 ** With no arguments, show all hosts for which ssh-needs-path is true.
315 */
316 void test_ssh_needs_path(void){
317 db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
318 db_open_config(0,0);
319 if( g.argc>=3 ){
320 const char *zHost = g.argv[2];
321 int a = -1;
322 int rc;
323 if( g.argc>=4 ) a = is_truth(g.argv[3]);
324 rc = ssh_needs_path_argument(zHost, a);
325 fossil_print("%-20s %s\n", zHost, rc ? "yes" : "no");
326 }else{
327 Stmt s;
328 db_swap_connections();
329 db_prepare(&s, "SELECT substr(name,18) FROM global_config"
330 " WHERE name GLOB 'use-path-for-ssh:*'");
331 while( db_step(&s)==SQLITE_ROW ){
332 const char *zHost = db_column_text(&s,0);
333 fossil_print("%-20s yes\n", zHost);
334 }
335 db_finalize(&s);
336 db_swap_connections();
337 }
338 }
339
340 /* Add an approprate PATH= argument to the SSH command under construction
341 ** in pCmd.
342 **
343 ** About This Feature
344 ** ==================
345 **
346 ** On some ssh servers (Macs in particular are guilty of this) the PATH
347 ** variable in the shell that runs the command that is sent to the remote
348 ** host contains a limited number of read-only system directories:
349 **
350 ** /usr/bin:/bin:/usr/sbin:/sbin
351 **
352 ** The fossil executable cannot be installed into any of those directories
353 ** because they are locked down, and so the "fossil" command cannot run.
354 **
355 ** To work around this, the fossil command is prefixed with the PATH=
356 ** argument, inserted by this function, to augment the PATH with additional
357 ** directories in which the fossil executable is often found.
358 **
359 ** But other ssh servers are confused by this initial PATH= argument.
360 ** Some ssh servers have a list of programs that they are allowed to run
361 ** and will fail if the first argument is not on that list, and PATH=....
362 ** is not on that list.
363 **
364 ** So that various commands that use ssh can run seamlessly on a variety
365 ** of systems (commands that use ssh include "fossil sync" with an ssh:
366 ** URL and the "fossil patch pull" and "fossil patch push" commands where
367 ** the destination directory starts with HOSTNAME: or USER@HOSTNAME:.)
368 ** the following algorithm is used:
369 **
370 ** * First try running the fossil without any PATH= argument. If that
371 ** works (and it does on a majority of systems) then we are done.
372 **
373 ** * If the first attempt fails, then try again after adding the
374 ** PATH= prefix argument. (This function is what adds that
375 ** argument.) If the retry works, then remember that fact using
376 ** the use-path-for-ssh:HOSTNAME setting so that the first step
377 ** is skipped on subsequent uses of the same command.
378 **
379 ** See the forum thread at
380 ** https://fossil-scm.org/forum/forumpost/4903cb4b691af7ce for more
381 ** background.
382 **
383 ** See also:
384 **
385 ** * The ssh_needs_path_argument() function above.
386 ** * The test-ssh-needs-path command that shows the settings
387 ** that cache whether or not a PATH= is needed for a particular
388 ** HOSTNAME.
389 */
390 void ssh_add_path_argument(Blob *pCmd){
391 blob_append_escaped_arg(pCmd,
392 "PATH=$HOME/bin:/usr/local/bin:/opt/homebrew/bin:$PATH", 1);
393 }
394
395 /*
396 ** Sign the content in pSend, compress it, and send it to the server
397 ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply
398 ** in pRecv. pRecv is assumed to be uninitialized when
@@ -304,10 +424,20 @@
424
425 if( g.zHttpCmd!=0 ){
426 /* Handle the --transport-command option for "fossil sync" and similar */
427 return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
428 }
429
430 /* Activate the PATH= auxiliary argument to the ssh command if that
431 ** is called for.
432 */
433 if( g.url.isSsh
434 && (g.url.flags & URL_SSH_RETRY)==0
435 && ssh_needs_path_argument(g.url.hostname, -1)
436 ){
437 g.url.flags |= URL_SSH_PATH;
438 }
439
440 if( transport_open(&g.url) ){
441 fossil_warning("%s", transport_errmsg(&g.url));
442 return 1;
443 }
@@ -484,12 +614,45 @@
614 }
615 }
616 }
617 }
618 if( iLength<0 ){
619 /* We got nothing back from the server. If using the ssh: protocol,
620 ** this might mean we need to add or remove the PATH=... argument
621 ** to the SSH command being sent. If that is the case, retry the
622 ** request after adding or removing the PATH= argument.
623 */
624 if( g.url.isSsh /* This is an SSH: sync */
625 && (g.url.flags & URL_SSH_EXE)==0 /* Does not have ?fossil=.... */
626 && (g.url.flags & URL_SSH_RETRY)==0 /* Not retried already */
627 ){
628 /* Retry after flipping the SSH_PATH setting */
629 transport_close(&g.url);
630 fossil_print(
631 "First attempt to run fossil on %s using SSH failed.\n"
632 "Retrying %s the PATH= argument.\n",
633 g.url.hostname,
634 (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with"
635 );
636 g.url.flags ^= URL_SSH_PATH|URL_SSH_RETRY;
637 rc = http_exchange(pSend,pReply,mHttpFlags,0,zAltMimetype);
638 if( rc==0 ){
639 (void)ssh_needs_path_argument(g.url.hostname,
640 (g.url.flags & URL_SSH_PATH)!=0);
641 }
642 return rc;
643 }else{
644 /* The problem could not be corrected by retrying. Report the
645 ** the error. */
646 if( g.url.isSsh && !g.fSshTrace ){
647 fossil_warning("server did not reply: "
648 " rerun with --sshtrace for diagnostics");
649 }else{
650 fossil_warning("server did not reply");
651 }
652 goto write_err;
653 }
654 }
655 if( rc!=200 ){
656 fossil_warning("\"location:\" missing from %d redirect reply", rc);
657 goto write_err;
658 }
659
--- src/http_transport.c
+++ src/http_transport.c
@@ -131,15 +131,21 @@
131131
blob_append_escaped_arg(&zCmd, zHost, 0);
132132
fossil_free(zHost);
133133
}else{
134134
blob_append_escaped_arg(&zCmd, pUrlData->name, 0);
135135
}
136
- if( !is_safe_fossil_command(pUrlData->fossil) ){
136
+ if( (pUrlData->flags & URL_SSH_EXE)!=0
137
+ && !is_safe_fossil_command(pUrlData->fossil)
138
+ ){
137139
fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
138140
"the server.", pUrlData->fossil);
139141
}
140
- blob_append_escaped_arg(&zCmd, "PATH=$HOME/bin:$PATH", 1);
142
+ if( (pUrlData->flags & URL_SSH_EXE)==0
143
+ && (pUrlData->flags & URL_SSH_PATH)!=0
144
+ ){
145
+ ssh_add_path_argument(&zCmd);
146
+ }
141147
blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1);
142148
blob_append(&zCmd, " test-http", 10);
143149
if( pUrlData->path && pUrlData->path[0] ){
144150
blob_append_escaped_arg(&zCmd, pUrlData->path, 1);
145151
}else{
@@ -313,11 +319,11 @@
313319
** Read N bytes of content directly from the wire and write into
314320
** the buffer.
315321
*/
316322
static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
317323
int got;
318
- if( sshIn ){
324
+ if( pUrlData->isSsh ){
319325
int x;
320326
int wanted = N;
321327
got = 0;
322328
while( wanted>0 ){
323329
x = read(sshIn, &zBuf[got], wanted);
324330
--- src/http_transport.c
+++ src/http_transport.c
@@ -131,15 +131,21 @@
131 blob_append_escaped_arg(&zCmd, zHost, 0);
132 fossil_free(zHost);
133 }else{
134 blob_append_escaped_arg(&zCmd, pUrlData->name, 0);
135 }
136 if( !is_safe_fossil_command(pUrlData->fossil) ){
 
 
137 fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
138 "the server.", pUrlData->fossil);
139 }
140 blob_append_escaped_arg(&zCmd, "PATH=$HOME/bin:$PATH", 1);
 
 
 
 
141 blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1);
142 blob_append(&zCmd, " test-http", 10);
143 if( pUrlData->path && pUrlData->path[0] ){
144 blob_append_escaped_arg(&zCmd, pUrlData->path, 1);
145 }else{
@@ -313,11 +319,11 @@
313 ** Read N bytes of content directly from the wire and write into
314 ** the buffer.
315 */
316 static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
317 int got;
318 if( sshIn ){
319 int x;
320 int wanted = N;
321 got = 0;
322 while( wanted>0 ){
323 x = read(sshIn, &zBuf[got], wanted);
324
--- src/http_transport.c
+++ src/http_transport.c
@@ -131,15 +131,21 @@
131 blob_append_escaped_arg(&zCmd, zHost, 0);
132 fossil_free(zHost);
133 }else{
134 blob_append_escaped_arg(&zCmd, pUrlData->name, 0);
135 }
136 if( (pUrlData->flags & URL_SSH_EXE)!=0
137 && !is_safe_fossil_command(pUrlData->fossil)
138 ){
139 fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
140 "the server.", pUrlData->fossil);
141 }
142 if( (pUrlData->flags & URL_SSH_EXE)==0
143 && (pUrlData->flags & URL_SSH_PATH)!=0
144 ){
145 ssh_add_path_argument(&zCmd);
146 }
147 blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1);
148 blob_append(&zCmd, " test-http", 10);
149 if( pUrlData->path && pUrlData->path[0] ){
150 blob_append_escaped_arg(&zCmd, pUrlData->path, 1);
151 }else{
@@ -313,11 +319,11 @@
319 ** Read N bytes of content directly from the wire and write into
320 ** the buffer.
321 */
322 static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
323 int got;
324 if( pUrlData->isSsh ){
325 int x;
326 int wanted = N;
327 got = 0;
328 while( wanted>0 ){
329 x = read(sshIn, &zBuf[got], wanted);
330
+61 -41
--- src/main.c
+++ src/main.c
@@ -3065,14 +3065,15 @@
30653065
**
30663066
** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then
30673067
** the command is run on the remote host specified and the results are
30683068
** tunneled back to the local machine via SSH. This feature only works for
30693069
** the "fossil ui" command, not the "fossil server" command. The name of the
3070
-** fossil executable on the remote host is specified by the --fossilcmd option,
3071
-** or if there is no --fossilcmd, it first tries "$HOME/bin/fossil" and if
3072
-** not found there it searches for any executable named "fossil" on the
3073
-** default $PATH set by SSH on the remote.
3070
+** fossil executable on the remote host is specified by the --fossilcmd
3071
+** option, or if there is no --fossilcmd, it first tries "fossil" and if it
3072
+** is not found in the default $PATH set by SSH on the remote, it then adds
3073
+** "$HOME/bin:/usr/local/bin:/opt/homebrew/bin" to the PATH and tries again to
3074
+** run "fossil".
30743075
**
30753076
** REPOSITORY may also be a directory (aka folder) that contains one or
30763077
** more repositories with names ending in ".fossil". In this case, a
30773078
** prefix of the URL pathname is used to search the directory for an
30783079
** appropriate repository. To thwart mischief, the pathname in the URL must
@@ -3343,49 +3344,68 @@
33433344
/* If a USER@HOST:REPO argument is supplied, then use SSH to run
33443345
** "fossil ui --nobrowser" on the remote system and to set up a
33453346
** tunnel from the local machine to the remote. */
33463347
FILE *sshIn;
33473348
Blob ssh;
3349
+ int bRunning = 0; /* True when fossil starts up on the remote */
3350
+ int isRetry; /* True if on the second attempt */
33483351
char zLine[1000];
3352
+
33493353
blob_init(&ssh, 0, 0);
3350
- transport_ssh_command(&ssh);
3351
- db_close_config();
3352
- blob_appendf(&ssh,
3353
- " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$",
3354
- iPort, iPort, zRemote
3355
- );
3356
- if( zFossilCmd==0 ){
3357
- blob_appendf(&ssh, " %$ fossil", "PATH=$HOME/bin:$PATH");
3358
- }else{
3359
- blob_appendf(&ssh, " %$", zFossilCmd);
3360
- }
3361
- blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3362
- if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3363
- if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3364
- if( g.zCkoutAlias ) blob_appendf(&ssh, " --ckout-alias %!$",g.zCkoutAlias);
3365
- if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3366
- if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3367
- if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3368
- if( fCreate ) blob_appendf(&ssh, " --create");
3369
- blob_appendf(&ssh, " %$", g.argv[2]);
3370
- fossil_print("%s\n", blob_str(&ssh));
3371
- sshIn = popen(blob_str(&ssh), "r");
3372
- if( sshIn==0 ){
3373
- fossil_fatal("unable to %s", blob_str(&ssh));
3374
- }
3375
- while( fgets(zLine, sizeof(zLine), sshIn) ){
3376
- fputs(zLine, stdout);
3377
- fflush(stdout);
3378
- if( zBrowserCmd && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){
3379
- char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort);
3380
- fossil_system(zCmd);
3381
- fossil_free(zCmd);
3382
- fossil_free(zBrowserCmd);
3383
- zBrowserCmd = 0;
3384
- }
3385
- }
3386
- pclose(sshIn);
3354
+ for(isRetry=0; isRetry<2 && !bRunning; isRetry++){
3355
+ blob_reset(&ssh);
3356
+ transport_ssh_command(&ssh);
3357
+ blob_appendf(&ssh,
3358
+ " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$",
3359
+ iPort, iPort, zRemote
3360
+ );
3361
+ if( zFossilCmd==0 ){
3362
+ if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
3363
+ ssh_add_path_argument(&ssh);
3364
+ }
3365
+ blob_append_escaped_arg(&ssh, "fossil", 1);
3366
+ }else{
3367
+ blob_appendf(&ssh, " %$", zFossilCmd);
3368
+ }
3369
+ blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3370
+ if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3371
+ if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3372
+ if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3373
+ if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3374
+ if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3375
+ if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3376
+ if( fCreate ) blob_appendf(&ssh, " --create");
3377
+ blob_appendf(&ssh, " %$", g.argv[2]);
3378
+ if( isRetry ){
3379
+ fossil_print("First attempt to run \"fossil\" on %s failed\n"
3380
+ "Retry: ", zRemote);
3381
+ }
3382
+ fossil_print("%s\n", blob_str(&ssh));
3383
+ sshIn = popen(blob_str(&ssh), "r");
3384
+ if( sshIn==0 ){
3385
+ fossil_fatal("unable to %s", blob_str(&ssh));
3386
+ }
3387
+ while( fgets(zLine, sizeof(zLine), sshIn) ){
3388
+ fputs(zLine, stdout);
3389
+ fflush(stdout);
3390
+ if( !bRunning && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){
3391
+ bRunning = 1;
3392
+ if( isRetry ){
3393
+ ssh_needs_path_argument(zRemote,99);
3394
+ }
3395
+ db_close_config();
3396
+ if( zBrowserCmd ){
3397
+ char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort);
3398
+ fossil_system(zCmd);
3399
+ fossil_free(zCmd);
3400
+ fossil_free(zBrowserCmd);
3401
+ zBrowserCmd = 0;
3402
+ }
3403
+ }
3404
+ }
3405
+ pclose(sshIn);
3406
+ }
33873407
fossil_free(zBrowserCmd);
33883408
return;
33893409
}
33903410
if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
33913411
if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
33923412
--- src/main.c
+++ src/main.c
@@ -3065,14 +3065,15 @@
3065 **
3066 ** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then
3067 ** the command is run on the remote host specified and the results are
3068 ** tunneled back to the local machine via SSH. This feature only works for
3069 ** the "fossil ui" command, not the "fossil server" command. The name of the
3070 ** fossil executable on the remote host is specified by the --fossilcmd option,
3071 ** or if there is no --fossilcmd, it first tries "$HOME/bin/fossil" and if
3072 ** not found there it searches for any executable named "fossil" on the
3073 ** default $PATH set by SSH on the remote.
 
3074 **
3075 ** REPOSITORY may also be a directory (aka folder) that contains one or
3076 ** more repositories with names ending in ".fossil". In this case, a
3077 ** prefix of the URL pathname is used to search the directory for an
3078 ** appropriate repository. To thwart mischief, the pathname in the URL must
@@ -3343,49 +3344,68 @@
3343 /* If a USER@HOST:REPO argument is supplied, then use SSH to run
3344 ** "fossil ui --nobrowser" on the remote system and to set up a
3345 ** tunnel from the local machine to the remote. */
3346 FILE *sshIn;
3347 Blob ssh;
 
 
3348 char zLine[1000];
 
3349 blob_init(&ssh, 0, 0);
3350 transport_ssh_command(&ssh);
3351 db_close_config();
3352 blob_appendf(&ssh,
3353 " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$",
3354 iPort, iPort, zRemote
3355 );
3356 if( zFossilCmd==0 ){
3357 blob_appendf(&ssh, " %$ fossil", "PATH=$HOME/bin:$PATH");
3358 }else{
3359 blob_appendf(&ssh, " %$", zFossilCmd);
3360 }
3361 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3362 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3363 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3364 if( g.zCkoutAlias ) blob_appendf(&ssh, " --ckout-alias %!$",g.zCkoutAlias);
3365 if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3366 if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3367 if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3368 if( fCreate ) blob_appendf(&ssh, " --create");
3369 blob_appendf(&ssh, " %$", g.argv[2]);
3370 fossil_print("%s\n", blob_str(&ssh));
3371 sshIn = popen(blob_str(&ssh), "r");
3372 if( sshIn==0 ){
3373 fossil_fatal("unable to %s", blob_str(&ssh));
3374 }
3375 while( fgets(zLine, sizeof(zLine), sshIn) ){
3376 fputs(zLine, stdout);
3377 fflush(stdout);
3378 if( zBrowserCmd && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){
3379 char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort);
3380 fossil_system(zCmd);
3381 fossil_free(zCmd);
3382 fossil_free(zBrowserCmd);
3383 zBrowserCmd = 0;
3384 }
3385 }
3386 pclose(sshIn);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3387 fossil_free(zBrowserCmd);
3388 return;
3389 }
3390 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
3391 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
3392
--- src/main.c
+++ src/main.c
@@ -3065,14 +3065,15 @@
3065 **
3066 ** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then
3067 ** the command is run on the remote host specified and the results are
3068 ** tunneled back to the local machine via SSH. This feature only works for
3069 ** the "fossil ui" command, not the "fossil server" command. The name of the
3070 ** fossil executable on the remote host is specified by the --fossilcmd
3071 ** option, or if there is no --fossilcmd, it first tries "fossil" and if it
3072 ** is not found in the default $PATH set by SSH on the remote, it then adds
3073 ** "$HOME/bin:/usr/local/bin:/opt/homebrew/bin" to the PATH and tries again to
3074 ** run "fossil".
3075 **
3076 ** REPOSITORY may also be a directory (aka folder) that contains one or
3077 ** more repositories with names ending in ".fossil". In this case, a
3078 ** prefix of the URL pathname is used to search the directory for an
3079 ** appropriate repository. To thwart mischief, the pathname in the URL must
@@ -3343,49 +3344,68 @@
3344 /* If a USER@HOST:REPO argument is supplied, then use SSH to run
3345 ** "fossil ui --nobrowser" on the remote system and to set up a
3346 ** tunnel from the local machine to the remote. */
3347 FILE *sshIn;
3348 Blob ssh;
3349 int bRunning = 0; /* True when fossil starts up on the remote */
3350 int isRetry; /* True if on the second attempt */
3351 char zLine[1000];
3352
3353 blob_init(&ssh, 0, 0);
3354 for(isRetry=0; isRetry<2 && !bRunning; isRetry++){
3355 blob_reset(&ssh);
3356 transport_ssh_command(&ssh);
3357 blob_appendf(&ssh,
3358 " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$",
3359 iPort, iPort, zRemote
3360 );
3361 if( zFossilCmd==0 ){
3362 if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
3363 ssh_add_path_argument(&ssh);
3364 }
3365 blob_append_escaped_arg(&ssh, "fossil", 1);
3366 }else{
3367 blob_appendf(&ssh, " %$", zFossilCmd);
3368 }
3369 blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
3370 if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3371 if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3372 if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3373 if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3374 if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3375 if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3376 if( fCreate ) blob_appendf(&ssh, " --create");
3377 blob_appendf(&ssh, " %$", g.argv[2]);
3378 if( isRetry ){
3379 fossil_print("First attempt to run \"fossil\" on %s failed\n"
3380 "Retry: ", zRemote);
3381 }
3382 fossil_print("%s\n", blob_str(&ssh));
3383 sshIn = popen(blob_str(&ssh), "r");
3384 if( sshIn==0 ){
3385 fossil_fatal("unable to %s", blob_str(&ssh));
3386 }
3387 while( fgets(zLine, sizeof(zLine), sshIn) ){
3388 fputs(zLine, stdout);
3389 fflush(stdout);
3390 if( !bRunning && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){
3391 bRunning = 1;
3392 if( isRetry ){
3393 ssh_needs_path_argument(zRemote,99);
3394 }
3395 db_close_config();
3396 if( zBrowserCmd ){
3397 char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort);
3398 fossil_system(zCmd);
3399 fossil_free(zCmd);
3400 fossil_free(zBrowserCmd);
3401 zBrowserCmd = 0;
3402 }
3403 }
3404 }
3405 pclose(sshIn);
3406 }
3407 fossil_free(zBrowserCmd);
3408 return;
3409 }
3410 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
3411 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
3412
+1 -1
--- src/name.c
+++ src/name.c
@@ -210,11 +210,11 @@
210210
**
211211
** Return 0 if there are no matches.
212212
**
213213
** This is a tricky query to do efficiently.
214214
** If the tag is very common (ex: "trunk") then
215
-** we want to use the query identified below as Q1 - which searching
215
+** we want to use the query identified below as Q1 - which searches
216216
** the most recent EVENT table entries for the most recent with the tag.
217217
** But if the tag is relatively scarce (anything other than "trunk", basically)
218218
** then we want to do the indexed search show below as Q2.
219219
*/
220220
static int most_recent_event_with_tag(const char *zTag, const char *zType){
221221
--- src/name.c
+++ src/name.c
@@ -210,11 +210,11 @@
210 **
211 ** Return 0 if there are no matches.
212 **
213 ** This is a tricky query to do efficiently.
214 ** If the tag is very common (ex: "trunk") then
215 ** we want to use the query identified below as Q1 - which searching
216 ** the most recent EVENT table entries for the most recent with the tag.
217 ** But if the tag is relatively scarce (anything other than "trunk", basically)
218 ** then we want to do the indexed search show below as Q2.
219 */
220 static int most_recent_event_with_tag(const char *zTag, const char *zType){
221
--- src/name.c
+++ src/name.c
@@ -210,11 +210,11 @@
210 **
211 ** Return 0 if there are no matches.
212 **
213 ** This is a tricky query to do efficiently.
214 ** If the tag is very common (ex: "trunk") then
215 ** we want to use the query identified below as Q1 - which searches
216 ** the most recent EVENT table entries for the most recent with the tag.
217 ** But if the tag is relatively scarce (anything other than "trunk", basically)
218 ** then we want to do the indexed search show below as Q2.
219 */
220 static int most_recent_event_with_tag(const char *zTag, const char *zType){
221
+70 -16
--- src/patch.c
+++ src/patch.c
@@ -45,10 +45,11 @@
4545
** to implement the various subcommands.
4646
*/
4747
#define PATCH_DRYRUN 0x0001
4848
#define PATCH_VERBOSE 0x0002
4949
#define PATCH_FORCE 0x0004
50
+#define PATCH_RETRY 0x0008 /* Second attempt */
5051
5152
/*
5253
** Implementation of the "readfile(X)" SQL function. The entire content
5354
** of the check-out file named X is read and returned as a BLOB.
5455
*/
@@ -132,11 +133,13 @@
132133
}
133134
134135
135136
/*
136137
** Generate a binary patch file and store it into the file
137
-** named zOut.
138
+** named zOut. Or if zOut is NULL, write it into out.
139
+**
140
+** Return the number of errors.
138141
*/
139142
void patch_create(unsigned mFlags, const char *zOut, FILE *out){
140143
int vid;
141144
char *z;
142145
@@ -248,21 +251,22 @@
248251
}
249252
#ifdef _WIN32
250253
fflush(out);
251254
_setmode(_fileno(out), _O_BINARY);
252255
#endif
253
- fwrite(pData, sz, 1, out);
254
- sqlite3_free(pData);
256
+ fwrite(pData, 1, sz, out);
255257
fflush(out);
258
+ sqlite3_free(pData);
256259
}
260
+ db_multi_exec("DETACH patch;");
257261
}
258262
259263
/*
260264
** Attempt to load and validate a patchfile identified by the first
261265
** argument.
262266
*/
263
-void patch_attach(const char *zIn, FILE *in){
267
+void patch_attach(const char *zIn, FILE *in, int bIgnoreEmptyPatch){
264268
Stmt q;
265269
if( g.db==0 ){
266270
sqlite3_open(":memory:", &g.db);
267271
}
268272
if( zIn==0 ){
@@ -274,10 +278,15 @@
274278
#ifdef _WIN32
275279
_setmode(_fileno(in), _O_BINARY);
276280
#endif
277281
sz = blob_read_from_channel(&buf, in, -1);
278282
pData = (unsigned char*)blob_buffer(&buf);
283
+ if( sz<512 ){
284
+ blob_reset(&buf);
285
+ if( bIgnoreEmptyPatch ) return;
286
+ fossil_fatal("input is too small to be a patch file");
287
+ }
279288
db_multi_exec("ATTACH ':memory:' AS patch");
280289
if( g.fSqlTrace ){
281290
fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz);
282291
}
283292
rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0);
@@ -664,18 +673,20 @@
664673
const char *zThisCmd, /* "push" or "pull" */
665674
const char *zRemoteCmd, /* "apply" or "create" */
666675
const char *zFossilCmd, /* Name of "fossil" on remote system */
667676
const char *zRW /* "w" or "r" */
668677
){
669
- char *zRemote;
670
- char *zDir;
678
+ char *zRemote = 0;
679
+ char *zDir = 0;
671680
Blob cmd;
672
- FILE *f;
681
+ FILE *f = 0;
673682
Blob flgs;
674
- char *zForce;
683
+ char *zForce = 0;
684
+ int isRetry = (mFlags & PATCH_RETRY)!=0;
675685
676686
blob_init(&flgs, 0, 0);
687
+ blob_init(&cmd, 0, 0);
677688
if( mFlags & PATCH_FORCE ) blob_appendf(&flgs, " -f");
678689
if( mFlags & PATCH_VERBOSE ) blob_appendf(&flgs, " -v");
679690
if( mFlags & PATCH_DRYRUN ) blob_appendf(&flgs, " -n");
680691
zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : "";
681692
if( g.argc!=4 ){
@@ -682,12 +693,12 @@
682693
usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd));
683694
}
684695
zRemote = fossil_strdup(g.argv[3]);
685696
zDir = (char*)file_skip_userhost(zRemote);
686697
if( zDir==0 ){
698
+ if( isRetry ) goto remote_command_error;
687699
zDir = zRemote;
688
- blob_init(&cmd, 0, 0);
689700
blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
690701
blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir);
691702
}else{
692703
Blob remote;
693704
*(char*)(zDir-1) = 0;
@@ -694,28 +705,52 @@
694705
transport_ssh_command(&cmd);
695706
blob_appendf(&cmd, " -T");
696707
blob_append_escaped_arg(&cmd, zRemote, 0);
697708
blob_init(&remote, 0, 0);
698709
if( zFossilCmd==0 ){
699
- blob_append_escaped_arg(&cmd, "PATH=$HOME/bin:$PATH", 0);
710
+ if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
711
+ ssh_add_path_argument(&cmd);
712
+ }
700713
zFossilCmd = "fossil";
714
+ }else if( mFlags & PATCH_RETRY ){
715
+ goto remote_command_error;
701716
}
702717
blob_appendf(&remote, "%$ patch %s%s --dir64 %z -",
703718
zFossilCmd, zRemoteCmd, zForce, encode64(zDir, -1));
704719
blob_append_escaped_arg(&cmd, blob_str(&remote), 0);
705720
blob_reset(&remote);
706721
}
722
+ if( isRetry ){
723
+ fossil_print("First attempt to run \"fossil\" on %s failed\n"
724
+ "Retry: ", zRemote);
725
+ }
707726
fossil_print("%s\n", blob_str(&cmd));
708727
fflush(stdout);
709728
f = popen(blob_str(&cmd), zRW);
710729
if( f==0 ){
711730
fossil_fatal("cannot run command: %s", blob_str(&cmd));
712731
}
732
+remote_command_error:
733
+ fossil_free(zRemote);
713734
blob_reset(&cmd);
714735
blob_reset(&flgs);
715736
return f;
716737
}
738
+
739
+/*
740
+** Toggle the use-path-for-ssh setting for the remote host defined
741
+** by g.argv[3].
742
+*/
743
+static void patch_toggle_ssh_needs_path(void){
744
+ char *zRemote = fossil_strdup(g.argv[3]);
745
+ char *zDir = (char*)file_skip_userhost(zRemote);
746
+ if( zDir ){
747
+ *(char*)(zDir - 1) = 0;
748
+ ssh_needs_path_argument(zRemote, 99);
749
+ }
750
+ fossil_free(zRemote);
751
+}
717752
718753
/*
719754
** Show a diff for the patch currently loaded into database "patch".
720755
*/
721756
static void patch_diff(
@@ -934,11 +969,11 @@
934969
if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN;
935970
if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
936971
if( find_option("force","f",0) ) flags |= PATCH_FORCE;
937972
zIn = patch_find_patch_filename("apply");
938973
db_must_be_within_tree();
939
- patch_attach(zIn, stdin);
974
+ patch_attach(zIn, stdin, 0);
940975
patch_apply(flags);
941976
fossil_free(zIn);
942977
}else
943978
if( strncmp(zCmd, "create", n)==0 ){
944979
char *zOut;
@@ -963,11 +998,11 @@
963998
db_find_and_open_repository(0, 0);
964999
if( find_option("force","f",0) ) flags |= PATCH_FORCE;
9651000
diff_options(&DCfg, zCmd[0]=='g', 0);
9661001
verify_all_options();
9671002
zIn = patch_find_patch_filename("apply");
968
- patch_attach(zIn, stdin);
1003
+ patch_attach(zIn, stdin, 0);
9691004
patch_diff(flags, &DCfg);
9701005
fossil_free(zIn);
9711006
}else
9721007
if( strncmp(zCmd, "pull", n)==0 ){
9731008
FILE *pIn = 0;
@@ -979,12 +1014,22 @@
9791014
db_must_be_within_tree();
9801015
verify_all_options();
9811016
pIn = patch_remote_command(flags & (~PATCH_FORCE),
9821017
"pull", "create", zFossilCmd, "r");
9831018
if( pIn ){
984
- patch_attach(0, pIn);
985
- pclose(pIn);
1019
+ patch_attach(0, pIn, 1);
1020
+ if( pclose(pIn) ){
1021
+ flags |= PATCH_RETRY;
1022
+ pIn = patch_remote_command(flags & (~PATCH_FORCE),
1023
+ "pull", "create", zFossilCmd, "r");
1024
+ if( pIn ){
1025
+ patch_attach(0, pIn, 0);
1026
+ if( pclose(pIn)==0 ){
1027
+ patch_toggle_ssh_needs_path();
1028
+ }
1029
+ }
1030
+ }
9861031
patch_apply(flags);
9871032
}
9881033
}else
9891034
if( strncmp(zCmd, "push", n)==0 ){
9901035
FILE *pOut = 0;
@@ -996,11 +1041,20 @@
9961041
db_must_be_within_tree();
9971042
verify_all_options();
9981043
pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w");
9991044
if( pOut ){
10001045
patch_create(0, 0, pOut);
1001
- pclose(pOut);
1046
+ if( pclose(pOut)!=0 ){
1047
+ flags |= PATCH_RETRY;
1048
+ pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w");
1049
+ if( pOut ){
1050
+ patch_create(0, 0, pOut);
1051
+ if( pclose(pOut)==0 ){
1052
+ patch_toggle_ssh_needs_path();
1053
+ }
1054
+ }
1055
+ }
10021056
}
10031057
}else
10041058
if( strncmp(zCmd, "view", n)==0 ){
10051059
const char *zIn;
10061060
unsigned int flags = 0;
@@ -1009,12 +1063,12 @@
10091063
if( g.argc!=4 ){
10101064
usage("view FILENAME");
10111065
}
10121066
zIn = g.argv[3];
10131067
if( fossil_strcmp(zIn, "-")==0 ) zIn = 0;
1014
- patch_attach(zIn, stdin);
1068
+ patch_attach(zIn, stdin, 0);
10151069
patch_view(flags);
10161070
}else
10171071
{
10181072
goto patch_usage;
10191073
}
10201074
}
10211075
--- src/patch.c
+++ src/patch.c
@@ -45,10 +45,11 @@
45 ** to implement the various subcommands.
46 */
47 #define PATCH_DRYRUN 0x0001
48 #define PATCH_VERBOSE 0x0002
49 #define PATCH_FORCE 0x0004
 
50
51 /*
52 ** Implementation of the "readfile(X)" SQL function. The entire content
53 ** of the check-out file named X is read and returned as a BLOB.
54 */
@@ -132,11 +133,13 @@
132 }
133
134
135 /*
136 ** Generate a binary patch file and store it into the file
137 ** named zOut.
 
 
138 */
139 void patch_create(unsigned mFlags, const char *zOut, FILE *out){
140 int vid;
141 char *z;
142
@@ -248,21 +251,22 @@
248 }
249 #ifdef _WIN32
250 fflush(out);
251 _setmode(_fileno(out), _O_BINARY);
252 #endif
253 fwrite(pData, sz, 1, out);
254 sqlite3_free(pData);
255 fflush(out);
 
256 }
 
257 }
258
259 /*
260 ** Attempt to load and validate a patchfile identified by the first
261 ** argument.
262 */
263 void patch_attach(const char *zIn, FILE *in){
264 Stmt q;
265 if( g.db==0 ){
266 sqlite3_open(":memory:", &g.db);
267 }
268 if( zIn==0 ){
@@ -274,10 +278,15 @@
274 #ifdef _WIN32
275 _setmode(_fileno(in), _O_BINARY);
276 #endif
277 sz = blob_read_from_channel(&buf, in, -1);
278 pData = (unsigned char*)blob_buffer(&buf);
 
 
 
 
 
279 db_multi_exec("ATTACH ':memory:' AS patch");
280 if( g.fSqlTrace ){
281 fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz);
282 }
283 rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0);
@@ -664,18 +673,20 @@
664 const char *zThisCmd, /* "push" or "pull" */
665 const char *zRemoteCmd, /* "apply" or "create" */
666 const char *zFossilCmd, /* Name of "fossil" on remote system */
667 const char *zRW /* "w" or "r" */
668 ){
669 char *zRemote;
670 char *zDir;
671 Blob cmd;
672 FILE *f;
673 Blob flgs;
674 char *zForce;
 
675
676 blob_init(&flgs, 0, 0);
 
677 if( mFlags & PATCH_FORCE ) blob_appendf(&flgs, " -f");
678 if( mFlags & PATCH_VERBOSE ) blob_appendf(&flgs, " -v");
679 if( mFlags & PATCH_DRYRUN ) blob_appendf(&flgs, " -n");
680 zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : "";
681 if( g.argc!=4 ){
@@ -682,12 +693,12 @@
682 usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd));
683 }
684 zRemote = fossil_strdup(g.argv[3]);
685 zDir = (char*)file_skip_userhost(zRemote);
686 if( zDir==0 ){
 
687 zDir = zRemote;
688 blob_init(&cmd, 0, 0);
689 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
690 blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir);
691 }else{
692 Blob remote;
693 *(char*)(zDir-1) = 0;
@@ -694,28 +705,52 @@
694 transport_ssh_command(&cmd);
695 blob_appendf(&cmd, " -T");
696 blob_append_escaped_arg(&cmd, zRemote, 0);
697 blob_init(&remote, 0, 0);
698 if( zFossilCmd==0 ){
699 blob_append_escaped_arg(&cmd, "PATH=$HOME/bin:$PATH", 0);
 
 
700 zFossilCmd = "fossil";
 
 
701 }
702 blob_appendf(&remote, "%$ patch %s%s --dir64 %z -",
703 zFossilCmd, zRemoteCmd, zForce, encode64(zDir, -1));
704 blob_append_escaped_arg(&cmd, blob_str(&remote), 0);
705 blob_reset(&remote);
706 }
 
 
 
 
707 fossil_print("%s\n", blob_str(&cmd));
708 fflush(stdout);
709 f = popen(blob_str(&cmd), zRW);
710 if( f==0 ){
711 fossil_fatal("cannot run command: %s", blob_str(&cmd));
712 }
 
 
713 blob_reset(&cmd);
714 blob_reset(&flgs);
715 return f;
716 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
718 /*
719 ** Show a diff for the patch currently loaded into database "patch".
720 */
721 static void patch_diff(
@@ -934,11 +969,11 @@
934 if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN;
935 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
936 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
937 zIn = patch_find_patch_filename("apply");
938 db_must_be_within_tree();
939 patch_attach(zIn, stdin);
940 patch_apply(flags);
941 fossil_free(zIn);
942 }else
943 if( strncmp(zCmd, "create", n)==0 ){
944 char *zOut;
@@ -963,11 +998,11 @@
963 db_find_and_open_repository(0, 0);
964 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
965 diff_options(&DCfg, zCmd[0]=='g', 0);
966 verify_all_options();
967 zIn = patch_find_patch_filename("apply");
968 patch_attach(zIn, stdin);
969 patch_diff(flags, &DCfg);
970 fossil_free(zIn);
971 }else
972 if( strncmp(zCmd, "pull", n)==0 ){
973 FILE *pIn = 0;
@@ -979,12 +1014,22 @@
979 db_must_be_within_tree();
980 verify_all_options();
981 pIn = patch_remote_command(flags & (~PATCH_FORCE),
982 "pull", "create", zFossilCmd, "r");
983 if( pIn ){
984 patch_attach(0, pIn);
985 pclose(pIn);
 
 
 
 
 
 
 
 
 
 
986 patch_apply(flags);
987 }
988 }else
989 if( strncmp(zCmd, "push", n)==0 ){
990 FILE *pOut = 0;
@@ -996,11 +1041,20 @@
996 db_must_be_within_tree();
997 verify_all_options();
998 pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w");
999 if( pOut ){
1000 patch_create(0, 0, pOut);
1001 pclose(pOut);
 
 
 
 
 
 
 
 
 
1002 }
1003 }else
1004 if( strncmp(zCmd, "view", n)==0 ){
1005 const char *zIn;
1006 unsigned int flags = 0;
@@ -1009,12 +1063,12 @@
1009 if( g.argc!=4 ){
1010 usage("view FILENAME");
1011 }
1012 zIn = g.argv[3];
1013 if( fossil_strcmp(zIn, "-")==0 ) zIn = 0;
1014 patch_attach(zIn, stdin);
1015 patch_view(flags);
1016 }else
1017 {
1018 goto patch_usage;
1019 }
1020 }
1021
--- src/patch.c
+++ src/patch.c
@@ -45,10 +45,11 @@
45 ** to implement the various subcommands.
46 */
47 #define PATCH_DRYRUN 0x0001
48 #define PATCH_VERBOSE 0x0002
49 #define PATCH_FORCE 0x0004
50 #define PATCH_RETRY 0x0008 /* Second attempt */
51
52 /*
53 ** Implementation of the "readfile(X)" SQL function. The entire content
54 ** of the check-out file named X is read and returned as a BLOB.
55 */
@@ -132,11 +133,13 @@
133 }
134
135
136 /*
137 ** Generate a binary patch file and store it into the file
138 ** named zOut. Or if zOut is NULL, write it into out.
139 **
140 ** Return the number of errors.
141 */
142 void patch_create(unsigned mFlags, const char *zOut, FILE *out){
143 int vid;
144 char *z;
145
@@ -248,21 +251,22 @@
251 }
252 #ifdef _WIN32
253 fflush(out);
254 _setmode(_fileno(out), _O_BINARY);
255 #endif
256 fwrite(pData, 1, sz, out);
 
257 fflush(out);
258 sqlite3_free(pData);
259 }
260 db_multi_exec("DETACH patch;");
261 }
262
263 /*
264 ** Attempt to load and validate a patchfile identified by the first
265 ** argument.
266 */
267 void patch_attach(const char *zIn, FILE *in, int bIgnoreEmptyPatch){
268 Stmt q;
269 if( g.db==0 ){
270 sqlite3_open(":memory:", &g.db);
271 }
272 if( zIn==0 ){
@@ -274,10 +278,15 @@
278 #ifdef _WIN32
279 _setmode(_fileno(in), _O_BINARY);
280 #endif
281 sz = blob_read_from_channel(&buf, in, -1);
282 pData = (unsigned char*)blob_buffer(&buf);
283 if( sz<512 ){
284 blob_reset(&buf);
285 if( bIgnoreEmptyPatch ) return;
286 fossil_fatal("input is too small to be a patch file");
287 }
288 db_multi_exec("ATTACH ':memory:' AS patch");
289 if( g.fSqlTrace ){
290 fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz);
291 }
292 rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0);
@@ -664,18 +673,20 @@
673 const char *zThisCmd, /* "push" or "pull" */
674 const char *zRemoteCmd, /* "apply" or "create" */
675 const char *zFossilCmd, /* Name of "fossil" on remote system */
676 const char *zRW /* "w" or "r" */
677 ){
678 char *zRemote = 0;
679 char *zDir = 0;
680 Blob cmd;
681 FILE *f = 0;
682 Blob flgs;
683 char *zForce = 0;
684 int isRetry = (mFlags & PATCH_RETRY)!=0;
685
686 blob_init(&flgs, 0, 0);
687 blob_init(&cmd, 0, 0);
688 if( mFlags & PATCH_FORCE ) blob_appendf(&flgs, " -f");
689 if( mFlags & PATCH_VERBOSE ) blob_appendf(&flgs, " -v");
690 if( mFlags & PATCH_DRYRUN ) blob_appendf(&flgs, " -n");
691 zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : "";
692 if( g.argc!=4 ){
@@ -682,12 +693,12 @@
693 usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd));
694 }
695 zRemote = fossil_strdup(g.argv[3]);
696 zDir = (char*)file_skip_userhost(zRemote);
697 if( zDir==0 ){
698 if( isRetry ) goto remote_command_error;
699 zDir = zRemote;
 
700 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
701 blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir);
702 }else{
703 Blob remote;
704 *(char*)(zDir-1) = 0;
@@ -694,28 +705,52 @@
705 transport_ssh_command(&cmd);
706 blob_appendf(&cmd, " -T");
707 blob_append_escaped_arg(&cmd, zRemote, 0);
708 blob_init(&remote, 0, 0);
709 if( zFossilCmd==0 ){
710 if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
711 ssh_add_path_argument(&cmd);
712 }
713 zFossilCmd = "fossil";
714 }else if( mFlags & PATCH_RETRY ){
715 goto remote_command_error;
716 }
717 blob_appendf(&remote, "%$ patch %s%s --dir64 %z -",
718 zFossilCmd, zRemoteCmd, zForce, encode64(zDir, -1));
719 blob_append_escaped_arg(&cmd, blob_str(&remote), 0);
720 blob_reset(&remote);
721 }
722 if( isRetry ){
723 fossil_print("First attempt to run \"fossil\" on %s failed\n"
724 "Retry: ", zRemote);
725 }
726 fossil_print("%s\n", blob_str(&cmd));
727 fflush(stdout);
728 f = popen(blob_str(&cmd), zRW);
729 if( f==0 ){
730 fossil_fatal("cannot run command: %s", blob_str(&cmd));
731 }
732 remote_command_error:
733 fossil_free(zRemote);
734 blob_reset(&cmd);
735 blob_reset(&flgs);
736 return f;
737 }
738
739 /*
740 ** Toggle the use-path-for-ssh setting for the remote host defined
741 ** by g.argv[3].
742 */
743 static void patch_toggle_ssh_needs_path(void){
744 char *zRemote = fossil_strdup(g.argv[3]);
745 char *zDir = (char*)file_skip_userhost(zRemote);
746 if( zDir ){
747 *(char*)(zDir - 1) = 0;
748 ssh_needs_path_argument(zRemote, 99);
749 }
750 fossil_free(zRemote);
751 }
752
753 /*
754 ** Show a diff for the patch currently loaded into database "patch".
755 */
756 static void patch_diff(
@@ -934,11 +969,11 @@
969 if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN;
970 if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE;
971 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
972 zIn = patch_find_patch_filename("apply");
973 db_must_be_within_tree();
974 patch_attach(zIn, stdin, 0);
975 patch_apply(flags);
976 fossil_free(zIn);
977 }else
978 if( strncmp(zCmd, "create", n)==0 ){
979 char *zOut;
@@ -963,11 +998,11 @@
998 db_find_and_open_repository(0, 0);
999 if( find_option("force","f",0) ) flags |= PATCH_FORCE;
1000 diff_options(&DCfg, zCmd[0]=='g', 0);
1001 verify_all_options();
1002 zIn = patch_find_patch_filename("apply");
1003 patch_attach(zIn, stdin, 0);
1004 patch_diff(flags, &DCfg);
1005 fossil_free(zIn);
1006 }else
1007 if( strncmp(zCmd, "pull", n)==0 ){
1008 FILE *pIn = 0;
@@ -979,12 +1014,22 @@
1014 db_must_be_within_tree();
1015 verify_all_options();
1016 pIn = patch_remote_command(flags & (~PATCH_FORCE),
1017 "pull", "create", zFossilCmd, "r");
1018 if( pIn ){
1019 patch_attach(0, pIn, 1);
1020 if( pclose(pIn) ){
1021 flags |= PATCH_RETRY;
1022 pIn = patch_remote_command(flags & (~PATCH_FORCE),
1023 "pull", "create", zFossilCmd, "r");
1024 if( pIn ){
1025 patch_attach(0, pIn, 0);
1026 if( pclose(pIn)==0 ){
1027 patch_toggle_ssh_needs_path();
1028 }
1029 }
1030 }
1031 patch_apply(flags);
1032 }
1033 }else
1034 if( strncmp(zCmd, "push", n)==0 ){
1035 FILE *pOut = 0;
@@ -996,11 +1041,20 @@
1041 db_must_be_within_tree();
1042 verify_all_options();
1043 pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w");
1044 if( pOut ){
1045 patch_create(0, 0, pOut);
1046 if( pclose(pOut)!=0 ){
1047 flags |= PATCH_RETRY;
1048 pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w");
1049 if( pOut ){
1050 patch_create(0, 0, pOut);
1051 if( pclose(pOut)==0 ){
1052 patch_toggle_ssh_needs_path();
1053 }
1054 }
1055 }
1056 }
1057 }else
1058 if( strncmp(zCmd, "view", n)==0 ){
1059 const char *zIn;
1060 unsigned int flags = 0;
@@ -1009,12 +1063,12 @@
1063 if( g.argc!=4 ){
1064 usage("view FILENAME");
1065 }
1066 zIn = g.argv[3];
1067 if( fossil_strcmp(zIn, "-")==0 ) zIn = 0;
1068 patch_attach(zIn, stdin, 0);
1069 patch_view(flags);
1070 }else
1071 {
1072 goto patch_usage;
1073 }
1074 }
1075
+2 -6
--- src/pqueue.c
+++ src/pqueue.c
@@ -42,11 +42,10 @@
4242
struct PQueue {
4343
int cnt; /* Number of entries in the queue */
4444
int sz; /* Number of slots in a[] */
4545
struct QueueElement {
4646
int id; /* ID of the element */
47
- void *p; /* Content pointer */
4847
double value; /* Value of element. Kept in ascending order */
4948
} *a;
5049
};
5150
#endif
5251
@@ -74,11 +73,11 @@
7473
}
7574
7675
/*
7776
** Insert element e into the queue.
7877
*/
79
-void pqueuex_insert(PQueue *p, int e, double v, void *pData){
78
+void pqueuex_insert(PQueue *p, int e, double v){
8079
int i, j;
8180
if( p->cnt+1>p->sz ){
8281
pqueuex_resize(p, p->cnt+5);
8382
}
8483
for(i=0; i<p->cnt; i++){
@@ -88,29 +87,26 @@
8887
}
8988
break;
9089
}
9190
}
9291
p->a[i].id = e;
93
- p->a[i].p = pData;
9492
p->a[i].value = v;
9593
p->cnt++;
9694
}
9795
9896
/*
9997
** Extract the first element from the queue (the element with
10098
** the smallest value) and return its ID. Return 0 if the queue
10199
** is empty.
102100
*/
103
-int pqueuex_extract(PQueue *p, void **pp){
101
+int pqueuex_extract(PQueue *p){
104102
int e, i;
105103
if( p->cnt==0 ){
106
- if( pp ) *pp = 0;
107104
return 0;
108105
}
109106
e = p->a[0].id;
110
- if( pp ) *pp = p->a[0].p;
111107
for(i=0; i<p->cnt-1; i++){
112108
p->a[i] = p->a[i+1];
113109
}
114110
p->cnt--;
115111
return e;
116112
}
117113
--- src/pqueue.c
+++ src/pqueue.c
@@ -42,11 +42,10 @@
42 struct PQueue {
43 int cnt; /* Number of entries in the queue */
44 int sz; /* Number of slots in a[] */
45 struct QueueElement {
46 int id; /* ID of the element */
47 void *p; /* Content pointer */
48 double value; /* Value of element. Kept in ascending order */
49 } *a;
50 };
51 #endif
52
@@ -74,11 +73,11 @@
74 }
75
76 /*
77 ** Insert element e into the queue.
78 */
79 void pqueuex_insert(PQueue *p, int e, double v, void *pData){
80 int i, j;
81 if( p->cnt+1>p->sz ){
82 pqueuex_resize(p, p->cnt+5);
83 }
84 for(i=0; i<p->cnt; i++){
@@ -88,29 +87,26 @@
88 }
89 break;
90 }
91 }
92 p->a[i].id = e;
93 p->a[i].p = pData;
94 p->a[i].value = v;
95 p->cnt++;
96 }
97
98 /*
99 ** Extract the first element from the queue (the element with
100 ** the smallest value) and return its ID. Return 0 if the queue
101 ** is empty.
102 */
103 int pqueuex_extract(PQueue *p, void **pp){
104 int e, i;
105 if( p->cnt==0 ){
106 if( pp ) *pp = 0;
107 return 0;
108 }
109 e = p->a[0].id;
110 if( pp ) *pp = p->a[0].p;
111 for(i=0; i<p->cnt-1; i++){
112 p->a[i] = p->a[i+1];
113 }
114 p->cnt--;
115 return e;
116 }
117
--- src/pqueue.c
+++ src/pqueue.c
@@ -42,11 +42,10 @@
42 struct PQueue {
43 int cnt; /* Number of entries in the queue */
44 int sz; /* Number of slots in a[] */
45 struct QueueElement {
46 int id; /* ID of the element */
 
47 double value; /* Value of element. Kept in ascending order */
48 } *a;
49 };
50 #endif
51
@@ -74,11 +73,11 @@
73 }
74
75 /*
76 ** Insert element e into the queue.
77 */
78 void pqueuex_insert(PQueue *p, int e, double v){
79 int i, j;
80 if( p->cnt+1>p->sz ){
81 pqueuex_resize(p, p->cnt+5);
82 }
83 for(i=0; i<p->cnt; i++){
@@ -88,29 +87,26 @@
87 }
88 break;
89 }
90 }
91 p->a[i].id = e;
 
92 p->a[i].value = v;
93 p->cnt++;
94 }
95
96 /*
97 ** Extract the first element from the queue (the element with
98 ** the smallest value) and return its ID. Return 0 if the queue
99 ** is empty.
100 */
101 int pqueuex_extract(PQueue *p){
102 int e, i;
103 if( p->cnt==0 ){
 
104 return 0;
105 }
106 e = p->a[0].id;
 
107 for(i=0; i<p->cnt-1; i++){
108 p->a[i] = p->a[i+1];
109 }
110 p->cnt--;
111 return e;
112 }
113
+3 -3
--- src/tag.c
+++ src/tag.c
@@ -44,11 +44,11 @@
4444
Stmt ins; /* INSERT INTO tagxref */
4545
Stmt eventupdate; /* UPDATE event */
4646
4747
assert( tagType==0 || tagType==2 );
4848
pqueuex_init(&queue);
49
- pqueuex_insert(&queue, pid, 0.0, 0);
49
+ pqueuex_insert(&queue, pid, 0.0);
5050
5151
/* Query for children of :pid to which to propagate the tag.
5252
** Three returns: (1) rid of the child. (2) timestamp of child.
5353
** (3) True to propagate or false to block.
5454
*/
@@ -79,18 +79,18 @@
7979
if( tagid==TAG_BGCOLOR ){
8080
db_prepare(&eventupdate,
8181
"UPDATE event SET bgcolor=%Q WHERE objid=:rid", zValue
8282
);
8383
}
84
- while( (pid = pqueuex_extract(&queue, 0))!=0 ){
84
+ while( (pid = pqueuex_extract(&queue))!=0 ){
8585
db_bind_int(&s, ":pid", pid);
8686
while( db_step(&s)==SQLITE_ROW ){
8787
int doit = db_column_int(&s, 2);
8888
if( doit ){
8989
int cid = db_column_int(&s, 0);
9090
double mtime = db_column_double(&s, 1);
91
- pqueuex_insert(&queue, cid, mtime, 0);
91
+ pqueuex_insert(&queue, cid, mtime);
9292
db_bind_int(&ins, ":rid", cid);
9393
db_step(&ins);
9494
db_reset(&ins);
9595
if( tagid==TAG_BGCOLOR ){
9696
db_bind_int(&eventupdate, ":rid", cid);
9797
--- src/tag.c
+++ src/tag.c
@@ -44,11 +44,11 @@
44 Stmt ins; /* INSERT INTO tagxref */
45 Stmt eventupdate; /* UPDATE event */
46
47 assert( tagType==0 || tagType==2 );
48 pqueuex_init(&queue);
49 pqueuex_insert(&queue, pid, 0.0, 0);
50
51 /* Query for children of :pid to which to propagate the tag.
52 ** Three returns: (1) rid of the child. (2) timestamp of child.
53 ** (3) True to propagate or false to block.
54 */
@@ -79,18 +79,18 @@
79 if( tagid==TAG_BGCOLOR ){
80 db_prepare(&eventupdate,
81 "UPDATE event SET bgcolor=%Q WHERE objid=:rid", zValue
82 );
83 }
84 while( (pid = pqueuex_extract(&queue, 0))!=0 ){
85 db_bind_int(&s, ":pid", pid);
86 while( db_step(&s)==SQLITE_ROW ){
87 int doit = db_column_int(&s, 2);
88 if( doit ){
89 int cid = db_column_int(&s, 0);
90 double mtime = db_column_double(&s, 1);
91 pqueuex_insert(&queue, cid, mtime, 0);
92 db_bind_int(&ins, ":rid", cid);
93 db_step(&ins);
94 db_reset(&ins);
95 if( tagid==TAG_BGCOLOR ){
96 db_bind_int(&eventupdate, ":rid", cid);
97
--- src/tag.c
+++ src/tag.c
@@ -44,11 +44,11 @@
44 Stmt ins; /* INSERT INTO tagxref */
45 Stmt eventupdate; /* UPDATE event */
46
47 assert( tagType==0 || tagType==2 );
48 pqueuex_init(&queue);
49 pqueuex_insert(&queue, pid, 0.0);
50
51 /* Query for children of :pid to which to propagate the tag.
52 ** Three returns: (1) rid of the child. (2) timestamp of child.
53 ** (3) True to propagate or false to block.
54 */
@@ -79,18 +79,18 @@
79 if( tagid==TAG_BGCOLOR ){
80 db_prepare(&eventupdate,
81 "UPDATE event SET bgcolor=%Q WHERE objid=:rid", zValue
82 );
83 }
84 while( (pid = pqueuex_extract(&queue))!=0 ){
85 db_bind_int(&s, ":pid", pid);
86 while( db_step(&s)==SQLITE_ROW ){
87 int doit = db_column_int(&s, 2);
88 if( doit ){
89 int cid = db_column_int(&s, 0);
90 double mtime = db_column_double(&s, 1);
91 pqueuex_insert(&queue, cid, mtime);
92 db_bind_int(&ins, ":rid", cid);
93 db_step(&ins);
94 db_reset(&ins);
95 if( tagid==TAG_BGCOLOR ){
96 db_bind_int(&eventupdate, ":rid", cid);
97
+187 -13
--- src/timeline.c
+++ src/timeline.c
@@ -1568,10 +1568,134 @@
15681568
15691569
/* It looks like this may be a date. Return it with punctuation added. */
15701570
return zEDate;
15711571
}
15721572
1573
+/*
1574
+** Find the first check-in encountered with a particular tag
1575
+** when moving either forwards are backwards in time from a
1576
+** particular starting point (iFrom). Return the rid of that
1577
+** first check-in. If there are no check-ins in the decendent
1578
+** or ancestor set of check-in iFrom that match the tag, then
1579
+** return 0.
1580
+*/
1581
+static int timeline_endpoint(
1582
+ int iFrom, /* Starting point */
1583
+ const char *zEnd, /* Tag we are searching for */
1584
+ int bForward /* 1: forwards in time (descendents) 0: backwards */
1585
+){
1586
+ int tagId;
1587
+ int endId = 0;
1588
+ Stmt q;
1589
+ int ans = 0;
1590
+
1591
+ tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd);
1592
+ if( tagId==0 ){
1593
+ endId = symbolic_name_to_rid(zEnd, "ci");
1594
+ if( endId==0 ) return 0;
1595
+ }
1596
+ if( bForward ){
1597
+ if( tagId ){
1598
+ db_prepare(&q,
1599
+ "WITH RECURSIVE dx(id,mtime) AS ("
1600
+ " SELECT %d, event.mtime FROM event WHERE objid=%d"
1601
+ " UNION"
1602
+ " SELECT plink.cid, plink.mtime"
1603
+ " FROM dx, plink"
1604
+ " WHERE plink.pid=dx.id"
1605
+ " AND plink.mtime<=(SELECT max(event.mtime) FROM tagxref, event"
1606
+ " WHERE tagxref.tagid=%d AND tagxref.tagtype>0"
1607
+ " AND event.objid=tagxref.rid)"
1608
+ " ORDER BY plink.mtime)"
1609
+ "SELECT id FROM dx, tagxref"
1610
+ " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1",
1611
+ iFrom, iFrom, tagId, tagId
1612
+ );
1613
+ }else{
1614
+ db_prepare(&q,
1615
+ "WITH RECURSIVE dx(id,mtime) AS ("
1616
+ " SELECT %d, event.mtime FROM event WHERE objid=%d"
1617
+ " UNION"
1618
+ " SELECT plink.cid, plink.mtime"
1619
+ " FROM dx, plink"
1620
+ " WHERE plink.pid=dx.id"
1621
+ " AND plink.mtime<=(SELECT mtime FROM event WHERE objid=%d)"
1622
+ " ORDER BY plink.mtime)"
1623
+ "SELECT id FROM dx WHERE id=%d",
1624
+ iFrom, iFrom, endId, endId
1625
+ );
1626
+ }
1627
+ }else{
1628
+ if( tagId ){
1629
+ db_prepare(&q,
1630
+ "WITH RECURSIVE dx(id,mtime) AS ("
1631
+ " SELECT %d, event.mtime FROM event WHERE objid=%d"
1632
+ " UNION"
1633
+ " SELECT plink.pid, event.mtime"
1634
+ " FROM dx, plink, event"
1635
+ " WHERE plink.cid=dx.id AND event.objid=plink.pid"
1636
+ " AND event.mtime>=(SELECT min(event.mtime) FROM tagxref, event"
1637
+ " WHERE tagxref.tagid=%d AND tagxref.tagtype>0"
1638
+ " AND event.objid=tagxref.rid)"
1639
+ " ORDER BY event.mtime DESC)"
1640
+ "SELECT id FROM dx, tagxref"
1641
+ " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1",
1642
+ iFrom, iFrom, tagId, tagId
1643
+ );
1644
+ }else{
1645
+ db_prepare(&q,
1646
+ "WITH RECURSIVE dx(id,mtime) AS ("
1647
+ " SELECT %d, event.mtime FROM event WHERE objid=%d"
1648
+ " UNION"
1649
+ " SELECT plink.pid, event.mtime"
1650
+ " FROM dx, plink, event"
1651
+ " WHERE plink.cid=dx.id AND event.objid=plink.pid"
1652
+ " AND event.mtime>=(SELECT mtime FROM event WHERE objid=%d)"
1653
+ " ORDER BY event.mtime DESC)"
1654
+ "SELECT id FROM dx WHERE id=%d",
1655
+ iFrom, iFrom, endId, endId
1656
+ );
1657
+ }
1658
+ }
1659
+ if( db_step(&q)==SQLITE_ROW ){
1660
+ ans = db_column_int(&q, 0);
1661
+ }
1662
+ db_finalize(&q);
1663
+ return ans;
1664
+}
1665
+
1666
+/*
1667
+** COMMAND: test-endpoint
1668
+**
1669
+** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
1670
+**
1671
+** Show the first check-in with TAG that is a descendent or ancestor
1672
+** of BASE. The first descendent checkin is shown by default. Use
1673
+** the --backto to see the first ancestor checkin.
1674
+**
1675
+** Options:
1676
+**
1677
+** --backto Show ancestor. Others defaults to descendents.
1678
+*/
1679
+void timeline_test_endpoint(void){
1680
+ int bForward = find_option("backto",0,0)==0;
1681
+ int from_rid;
1682
+ int ans;
1683
+ db_find_and_open_repository(0, 0);
1684
+ verify_all_options();
1685
+ if( g.argc!=4 ){
1686
+ usage("BASE-CHECKIN TAG ?--backto?");
1687
+ }
1688
+ from_rid = symbolic_name_to_rid(g.argv[2],"ci");
1689
+ ans = timeline_endpoint(from_rid, g.argv[3], bForward);
1690
+ if( ans ){
1691
+ fossil_print("Result: %d (%S)\n", ans, rid_to_uuid(ans));
1692
+ }else{
1693
+ fossil_print("No path found\n");
1694
+ }
1695
+}
1696
+
15731697
15741698
/*
15751699
** WEBPAGE: timeline
15761700
**
15771701
** Query parameters:
@@ -1598,14 +1722,16 @@
15981722
** bt=PRIOR ... going back to PRIOR
15991723
** d=CHECKIN Children and descendants of CHECKIN
16001724
** ft=DESCENDANT ... going forward to DESCENDANT
16011725
** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN'
16021726
** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From"
1603
-** bt=CHECKIN In conjunction with p=CX, this means show all
1604
-** ancestors of CX going back to the time of CHECKIN.
1605
-** All qualifying check-ins are shown unless there
1606
-** is also an n= or n1= query parameter.
1727
+** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN
1728
+** p=CX ... from CX back to time of CHECKIN
1729
+** from=CX ... shortest path from CX back to CHECKIN
1730
+** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN
1731
+** d=CX ... from CX up to the time of CHECKIN
1732
+** from=CX ... shortest path from CX up to CHECKIN
16071733
** t=TAG Show only check-ins with the given TAG
16081734
** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel
16091735
** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist
16101736
** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist
16111737
** rel Show related check-ins as well as those matching t=TAG
@@ -1627,10 +1753,12 @@
16271753
** f=CHECKIN Show family (immediate parents and children) of CHECKIN
16281754
** from=CHECKIN Path from...
16291755
** to=CHECKIN ... to this
16301756
** shortest ... show only the shortest path
16311757
** rel ... also show related checkins
1758
+** bt=PRIOR ... path from CHECKIN back to PRIOR
1759
+** ft=LATER ... path from CHECKIN forward to LATER
16321760
** uf=FILE_HASH Show only check-ins that contain the given file version
16331761
** All qualifying check-ins are shown unless there is
16341762
** also an n= or n1= query parameter.
16351763
** chng=GLOBLIST Show only check-ins that involve changes to a file whose
16361764
** name matches one of the comma-separate GLOBLIST
@@ -1727,10 +1855,11 @@
17271855
int disableY = 0; /* Disable type selector on submenu */
17281856
int advancedMenu = 0; /* Use the advanced menu design */
17291857
char *zPlural; /* Ending for plural forms */
17301858
int showCherrypicks = 1; /* True to show cherrypick merges */
17311859
int haveParameterN; /* True if n= query parameter present */
1860
+ int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */
17321861
17331862
url_initialize(&url, "timeline");
17341863
cgi_query_parameters_to_url(&url);
17351864
17361865
(void)P_NoBot("ss")
@@ -2043,10 +2172,30 @@
20432172
blob_append_sql(&sql,
20442173
" AND NOT EXISTS(SELECT 1 FROM tagxref"
20452174
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
20462175
TAG_HIDDEN
20472176
);
2177
+ }
2178
+ if( from_rid && !to_rid && (P("ft")!=0 || P("bt")!=0) ){
2179
+ const char *zTo = P("ft");
2180
+ if( zTo ){
2181
+ from_to_mode = 1;
2182
+ to_rid = timeline_endpoint(from_rid, zTo, 1);
2183
+ }else{
2184
+ from_to_mode = 2;
2185
+ zTo = P("bt");
2186
+ to_rid = timeline_endpoint(from_rid, zTo, 0);
2187
+ }
2188
+ if( to_rid ){
2189
+ cgi_replace_parameter("to", zTo);
2190
+ if( selectedRid==0 ) selectedRid = from_rid;
2191
+ if( secondaryRid==0 ) secondaryRid = to_rid;
2192
+ }else{
2193
+ to_rid = from_rid;
2194
+ blob_appendf(&desc, "There is no path from %h %s to %h.<br>Instead: ",
2195
+ P("from"), from_to_mode==1 ? "forward" : "back", zTo);
2196
+ }
20482197
}
20492198
if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
20502199
/* If from= and to= are present, display all nodes on a path connecting
20512200
** the two */
20522201
PathNode *p = 0;
@@ -2054,11 +2203,17 @@
20542203
const char *zTo = 0;
20552204
Blob ins;
20562205
int nNodeOnPath = 0;
20572206
20582207
if( from_rid && to_rid ){
2059
- p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2208
+ if( from_to_mode==0 ){
2209
+ p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2210
+ }else if( from_to_mode==1 ){
2211
+ p = path_shortest(from_rid, to_rid, 0, 1, 0);
2212
+ }else{
2213
+ p = path_shortest(to_rid, from_rid, 0, 1, 0);
2214
+ }
20602215
zFrom = P("from");
20612216
zTo = P("to");
20622217
}else{
20632218
if( path_common_ancestor(me_rid, you_rid) ){
20642219
p = path_first();
@@ -2122,19 +2277,37 @@
21222277
db_multi_exec("%s", blob_sql_text(&sql));
21232278
if( advancedMenu ){
21242279
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
21252280
}
21262281
nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode");
2127
- blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
2282
+ if( nNodeOnPath==1 && from_to_mode>0 ){
2283
+ blob_appendf(&desc,"Check-in ");
2284
+ }else if( from_to_mode>0 ){
2285
+ blob_appendf(&desc, "%d check-ins on the shorted path from ",nNodeOnPath);
2286
+ }else{
2287
+ blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
2288
+ }
2289
+ if( from_rid==selectedRid ){
2290
+ blob_appendf(&desc, "<span class='timelineSelected'>");
2291
+ }
21282292
blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
2129
- blob_append(&desc, " to ", -1);
2130
- blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo);
2131
- if( related ){
2132
- int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath;
2133
- if( nRelated>0 ){
2134
- blob_appendf(&desc, " and %d related check-in%s", nRelated,
2135
- nRelated>1 ? "s" : "");
2293
+ if( from_rid==selectedRid ) blob_appendf(&desc, "</span>");
2294
+ if( nNodeOnPath==1 && from_to_mode>0 ){
2295
+ blob_appendf(&desc, " only");
2296
+ }else{
2297
+ blob_append(&desc, " to ", -1);
2298
+ if( to_rid==secondaryRid ){
2299
+ blob_appendf(&desc,"<span class='timelineSelected timelineSecondary'>");
2300
+ }
2301
+ blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo);
2302
+ if( to_rid==secondaryRid ) blob_appendf(&desc, "</span>");
2303
+ if( related ){
2304
+ int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath;
2305
+ if( nRelated>0 ){
2306
+ blob_appendf(&desc, " and %d related check-in%s", nRelated,
2307
+ nRelated>1 ? "s" : "");
2308
+ }
21362309
}
21372310
}
21382311
addFileGlobDescription(zChng, &desc);
21392312
}else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
21402313
/* If p= or d= is present, ignore all other parameters other than n= */
@@ -2680,10 +2853,11 @@
26802853
@ <pre>%h(blob_sql_text(&sql2))</pre>
26812854
}
26822855
db_multi_exec("%s", blob_sql_text(&sql2));
26832856
if( nEntry>0 ){
26842857
nEntry -= db_int(0,"select count(*) from timeline");
2858
+ if( nEntry<=0 ) nEntry = 1;
26852859
}
26862860
blob_reset(&sql2);
26872861
blob_append_sql(&sql,
26882862
" AND event.mtime<=%f ORDER BY event.mtime DESC",
26892863
rCirca
26902864
--- src/timeline.c
+++ src/timeline.c
@@ -1568,10 +1568,134 @@
1568
1569 /* It looks like this may be a date. Return it with punctuation added. */
1570 return zEDate;
1571 }
1572
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1573
1574 /*
1575 ** WEBPAGE: timeline
1576 **
1577 ** Query parameters:
@@ -1598,14 +1722,16 @@
1598 ** bt=PRIOR ... going back to PRIOR
1599 ** d=CHECKIN Children and descendants of CHECKIN
1600 ** ft=DESCENDANT ... going forward to DESCENDANT
1601 ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN'
1602 ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From"
1603 ** bt=CHECKIN In conjunction with p=CX, this means show all
1604 ** ancestors of CX going back to the time of CHECKIN.
1605 ** All qualifying check-ins are shown unless there
1606 ** is also an n= or n1= query parameter.
 
 
1607 ** t=TAG Show only check-ins with the given TAG
1608 ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel
1609 ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist
1610 ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist
1611 ** rel Show related check-ins as well as those matching t=TAG
@@ -1627,10 +1753,12 @@
1627 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1628 ** from=CHECKIN Path from...
1629 ** to=CHECKIN ... to this
1630 ** shortest ... show only the shortest path
1631 ** rel ... also show related checkins
 
 
1632 ** uf=FILE_HASH Show only check-ins that contain the given file version
1633 ** All qualifying check-ins are shown unless there is
1634 ** also an n= or n1= query parameter.
1635 ** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1636 ** name matches one of the comma-separate GLOBLIST
@@ -1727,10 +1855,11 @@
1727 int disableY = 0; /* Disable type selector on submenu */
1728 int advancedMenu = 0; /* Use the advanced menu design */
1729 char *zPlural; /* Ending for plural forms */
1730 int showCherrypicks = 1; /* True to show cherrypick merges */
1731 int haveParameterN; /* True if n= query parameter present */
 
1732
1733 url_initialize(&url, "timeline");
1734 cgi_query_parameters_to_url(&url);
1735
1736 (void)P_NoBot("ss")
@@ -2043,10 +2172,30 @@
2043 blob_append_sql(&sql,
2044 " AND NOT EXISTS(SELECT 1 FROM tagxref"
2045 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
2046 TAG_HIDDEN
2047 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2048 }
2049 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
2050 /* If from= and to= are present, display all nodes on a path connecting
2051 ** the two */
2052 PathNode *p = 0;
@@ -2054,11 +2203,17 @@
2054 const char *zTo = 0;
2055 Blob ins;
2056 int nNodeOnPath = 0;
2057
2058 if( from_rid && to_rid ){
2059 p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
 
 
 
 
 
 
2060 zFrom = P("from");
2061 zTo = P("to");
2062 }else{
2063 if( path_common_ancestor(me_rid, you_rid) ){
2064 p = path_first();
@@ -2122,19 +2277,37 @@
2122 db_multi_exec("%s", blob_sql_text(&sql));
2123 if( advancedMenu ){
2124 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
2125 }
2126 nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode");
2127 blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
 
 
 
 
 
 
 
 
 
2128 blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
2129 blob_append(&desc, " to ", -1);
2130 blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo);
2131 if( related ){
2132 int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath;
2133 if( nRelated>0 ){
2134 blob_appendf(&desc, " and %d related check-in%s", nRelated,
2135 nRelated>1 ? "s" : "");
 
 
 
 
 
 
 
 
 
2136 }
2137 }
2138 addFileGlobDescription(zChng, &desc);
2139 }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
2140 /* If p= or d= is present, ignore all other parameters other than n= */
@@ -2680,10 +2853,11 @@
2680 @ <pre>%h(blob_sql_text(&sql2))</pre>
2681 }
2682 db_multi_exec("%s", blob_sql_text(&sql2));
2683 if( nEntry>0 ){
2684 nEntry -= db_int(0,"select count(*) from timeline");
 
2685 }
2686 blob_reset(&sql2);
2687 blob_append_sql(&sql,
2688 " AND event.mtime<=%f ORDER BY event.mtime DESC",
2689 rCirca
2690
--- src/timeline.c
+++ src/timeline.c
@@ -1568,10 +1568,134 @@
1568
1569 /* It looks like this may be a date. Return it with punctuation added. */
1570 return zEDate;
1571 }
1572
1573 /*
1574 ** Find the first check-in encountered with a particular tag
1575 ** when moving either forwards are backwards in time from a
1576 ** particular starting point (iFrom). Return the rid of that
1577 ** first check-in. If there are no check-ins in the decendent
1578 ** or ancestor set of check-in iFrom that match the tag, then
1579 ** return 0.
1580 */
1581 static int timeline_endpoint(
1582 int iFrom, /* Starting point */
1583 const char *zEnd, /* Tag we are searching for */
1584 int bForward /* 1: forwards in time (descendents) 0: backwards */
1585 ){
1586 int tagId;
1587 int endId = 0;
1588 Stmt q;
1589 int ans = 0;
1590
1591 tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd);
1592 if( tagId==0 ){
1593 endId = symbolic_name_to_rid(zEnd, "ci");
1594 if( endId==0 ) return 0;
1595 }
1596 if( bForward ){
1597 if( tagId ){
1598 db_prepare(&q,
1599 "WITH RECURSIVE dx(id,mtime) AS ("
1600 " SELECT %d, event.mtime FROM event WHERE objid=%d"
1601 " UNION"
1602 " SELECT plink.cid, plink.mtime"
1603 " FROM dx, plink"
1604 " WHERE plink.pid=dx.id"
1605 " AND plink.mtime<=(SELECT max(event.mtime) FROM tagxref, event"
1606 " WHERE tagxref.tagid=%d AND tagxref.tagtype>0"
1607 " AND event.objid=tagxref.rid)"
1608 " ORDER BY plink.mtime)"
1609 "SELECT id FROM dx, tagxref"
1610 " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1",
1611 iFrom, iFrom, tagId, tagId
1612 );
1613 }else{
1614 db_prepare(&q,
1615 "WITH RECURSIVE dx(id,mtime) AS ("
1616 " SELECT %d, event.mtime FROM event WHERE objid=%d"
1617 " UNION"
1618 " SELECT plink.cid, plink.mtime"
1619 " FROM dx, plink"
1620 " WHERE plink.pid=dx.id"
1621 " AND plink.mtime<=(SELECT mtime FROM event WHERE objid=%d)"
1622 " ORDER BY plink.mtime)"
1623 "SELECT id FROM dx WHERE id=%d",
1624 iFrom, iFrom, endId, endId
1625 );
1626 }
1627 }else{
1628 if( tagId ){
1629 db_prepare(&q,
1630 "WITH RECURSIVE dx(id,mtime) AS ("
1631 " SELECT %d, event.mtime FROM event WHERE objid=%d"
1632 " UNION"
1633 " SELECT plink.pid, event.mtime"
1634 " FROM dx, plink, event"
1635 " WHERE plink.cid=dx.id AND event.objid=plink.pid"
1636 " AND event.mtime>=(SELECT min(event.mtime) FROM tagxref, event"
1637 " WHERE tagxref.tagid=%d AND tagxref.tagtype>0"
1638 " AND event.objid=tagxref.rid)"
1639 " ORDER BY event.mtime DESC)"
1640 "SELECT id FROM dx, tagxref"
1641 " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1",
1642 iFrom, iFrom, tagId, tagId
1643 );
1644 }else{
1645 db_prepare(&q,
1646 "WITH RECURSIVE dx(id,mtime) AS ("
1647 " SELECT %d, event.mtime FROM event WHERE objid=%d"
1648 " UNION"
1649 " SELECT plink.pid, event.mtime"
1650 " FROM dx, plink, event"
1651 " WHERE plink.cid=dx.id AND event.objid=plink.pid"
1652 " AND event.mtime>=(SELECT mtime FROM event WHERE objid=%d)"
1653 " ORDER BY event.mtime DESC)"
1654 "SELECT id FROM dx WHERE id=%d",
1655 iFrom, iFrom, endId, endId
1656 );
1657 }
1658 }
1659 if( db_step(&q)==SQLITE_ROW ){
1660 ans = db_column_int(&q, 0);
1661 }
1662 db_finalize(&q);
1663 return ans;
1664 }
1665
1666 /*
1667 ** COMMAND: test-endpoint
1668 **
1669 ** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
1670 **
1671 ** Show the first check-in with TAG that is a descendent or ancestor
1672 ** of BASE. The first descendent checkin is shown by default. Use
1673 ** the --backto to see the first ancestor checkin.
1674 **
1675 ** Options:
1676 **
1677 ** --backto Show ancestor. Others defaults to descendents.
1678 */
1679 void timeline_test_endpoint(void){
1680 int bForward = find_option("backto",0,0)==0;
1681 int from_rid;
1682 int ans;
1683 db_find_and_open_repository(0, 0);
1684 verify_all_options();
1685 if( g.argc!=4 ){
1686 usage("BASE-CHECKIN TAG ?--backto?");
1687 }
1688 from_rid = symbolic_name_to_rid(g.argv[2],"ci");
1689 ans = timeline_endpoint(from_rid, g.argv[3], bForward);
1690 if( ans ){
1691 fossil_print("Result: %d (%S)\n", ans, rid_to_uuid(ans));
1692 }else{
1693 fossil_print("No path found\n");
1694 }
1695 }
1696
1697
1698 /*
1699 ** WEBPAGE: timeline
1700 **
1701 ** Query parameters:
@@ -1598,14 +1722,16 @@
1722 ** bt=PRIOR ... going back to PRIOR
1723 ** d=CHECKIN Children and descendants of CHECKIN
1724 ** ft=DESCENDANT ... going forward to DESCENDANT
1725 ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN'
1726 ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From"
1727 ** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN
1728 ** p=CX ... from CX back to time of CHECKIN
1729 ** from=CX ... shortest path from CX back to CHECKIN
1730 ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN
1731 ** d=CX ... from CX up to the time of CHECKIN
1732 ** from=CX ... shortest path from CX up to CHECKIN
1733 ** t=TAG Show only check-ins with the given TAG
1734 ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel
1735 ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist
1736 ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist
1737 ** rel Show related check-ins as well as those matching t=TAG
@@ -1627,10 +1753,12 @@
1753 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1754 ** from=CHECKIN Path from...
1755 ** to=CHECKIN ... to this
1756 ** shortest ... show only the shortest path
1757 ** rel ... also show related checkins
1758 ** bt=PRIOR ... path from CHECKIN back to PRIOR
1759 ** ft=LATER ... path from CHECKIN forward to LATER
1760 ** uf=FILE_HASH Show only check-ins that contain the given file version
1761 ** All qualifying check-ins are shown unless there is
1762 ** also an n= or n1= query parameter.
1763 ** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1764 ** name matches one of the comma-separate GLOBLIST
@@ -1727,10 +1855,11 @@
1855 int disableY = 0; /* Disable type selector on submenu */
1856 int advancedMenu = 0; /* Use the advanced menu design */
1857 char *zPlural; /* Ending for plural forms */
1858 int showCherrypicks = 1; /* True to show cherrypick merges */
1859 int haveParameterN; /* True if n= query parameter present */
1860 int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */
1861
1862 url_initialize(&url, "timeline");
1863 cgi_query_parameters_to_url(&url);
1864
1865 (void)P_NoBot("ss")
@@ -2043,10 +2172,30 @@
2172 blob_append_sql(&sql,
2173 " AND NOT EXISTS(SELECT 1 FROM tagxref"
2174 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
2175 TAG_HIDDEN
2176 );
2177 }
2178 if( from_rid && !to_rid && (P("ft")!=0 || P("bt")!=0) ){
2179 const char *zTo = P("ft");
2180 if( zTo ){
2181 from_to_mode = 1;
2182 to_rid = timeline_endpoint(from_rid, zTo, 1);
2183 }else{
2184 from_to_mode = 2;
2185 zTo = P("bt");
2186 to_rid = timeline_endpoint(from_rid, zTo, 0);
2187 }
2188 if( to_rid ){
2189 cgi_replace_parameter("to", zTo);
2190 if( selectedRid==0 ) selectedRid = from_rid;
2191 if( secondaryRid==0 ) secondaryRid = to_rid;
2192 }else{
2193 to_rid = from_rid;
2194 blob_appendf(&desc, "There is no path from %h %s to %h.<br>Instead: ",
2195 P("from"), from_to_mode==1 ? "forward" : "back", zTo);
2196 }
2197 }
2198 if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){
2199 /* If from= and to= are present, display all nodes on a path connecting
2200 ** the two */
2201 PathNode *p = 0;
@@ -2054,11 +2203,17 @@
2203 const char *zTo = 0;
2204 Blob ins;
2205 int nNodeOnPath = 0;
2206
2207 if( from_rid && to_rid ){
2208 if( from_to_mode==0 ){
2209 p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2210 }else if( from_to_mode==1 ){
2211 p = path_shortest(from_rid, to_rid, 0, 1, 0);
2212 }else{
2213 p = path_shortest(to_rid, from_rid, 0, 1, 0);
2214 }
2215 zFrom = P("from");
2216 zTo = P("to");
2217 }else{
2218 if( path_common_ancestor(me_rid, you_rid) ){
2219 p = path_first();
@@ -2122,19 +2277,37 @@
2277 db_multi_exec("%s", blob_sql_text(&sql));
2278 if( advancedMenu ){
2279 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
2280 }
2281 nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode");
2282 if( nNodeOnPath==1 && from_to_mode>0 ){
2283 blob_appendf(&desc,"Check-in ");
2284 }else if( from_to_mode>0 ){
2285 blob_appendf(&desc, "%d check-ins on the shorted path from ",nNodeOnPath);
2286 }else{
2287 blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
2288 }
2289 if( from_rid==selectedRid ){
2290 blob_appendf(&desc, "<span class='timelineSelected'>");
2291 }
2292 blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
2293 if( from_rid==selectedRid ) blob_appendf(&desc, "</span>");
2294 if( nNodeOnPath==1 && from_to_mode>0 ){
2295 blob_appendf(&desc, " only");
2296 }else{
2297 blob_append(&desc, " to ", -1);
2298 if( to_rid==secondaryRid ){
2299 blob_appendf(&desc,"<span class='timelineSelected timelineSecondary'>");
2300 }
2301 blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo);
2302 if( to_rid==secondaryRid ) blob_appendf(&desc, "</span>");
2303 if( related ){
2304 int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath;
2305 if( nRelated>0 ){
2306 blob_appendf(&desc, " and %d related check-in%s", nRelated,
2307 nRelated>1 ? "s" : "");
2308 }
2309 }
2310 }
2311 addFileGlobDescription(zChng, &desc);
2312 }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
2313 /* If p= or d= is present, ignore all other parameters other than n= */
@@ -2680,10 +2853,11 @@
2853 @ <pre>%h(blob_sql_text(&sql2))</pre>
2854 }
2855 db_multi_exec("%s", blob_sql_text(&sql2));
2856 if( nEntry>0 ){
2857 nEntry -= db_int(0,"select count(*) from timeline");
2858 if( nEntry<=0 ) nEntry = 1;
2859 }
2860 blob_reset(&sql2);
2861 blob_append_sql(&sql,
2862 " AND event.mtime<=%f ORDER BY event.mtime DESC",
2863 rCirca
2864
+41 -31
--- src/url.c
+++ src/url.c
@@ -33,18 +33,21 @@
3333
3434
#if INTERFACE
3535
/*
3636
** Flags for url_parse()
3737
*/
38
-#define URL_PROMPT_PW 0x001 /* Prompt for password if needed */
39
-#define URL_REMEMBER 0x002 /* Remember the url for later reuse */
40
-#define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */
41
-#define URL_REMEMBER_PW 0x008 /* Should remember pw */
42
-#define URL_PROMPTED 0x010 /* Prompted for PW already */
43
-#define URL_OMIT_USER 0x020 /* Omit the user name from URL */
44
-#define URL_USE_CONFIG 0x040 /* Use remembered URLs from CONFIG table */
45
-#define URL_USE_PARENT 0x080 /* Use the URL of the parent project */
38
+#define URL_PROMPT_PW 0x0001 /* Prompt for password if needed */
39
+#define URL_REMEMBER 0x0002 /* Remember the url for later reuse */
40
+#define URL_ASK_REMEMBER_PW 0x0004 /* Ask whether to remember prompted pw */
41
+#define URL_REMEMBER_PW 0x0008 /* Should remember pw */
42
+#define URL_PROMPTED 0x0010 /* Prompted for PW already */
43
+#define URL_OMIT_USER 0x0020 /* Omit the user name from URL */
44
+#define URL_USE_CONFIG 0x0040 /* Use remembered URLs from CONFIG table */
45
+#define URL_USE_PARENT 0x0080 /* Use the URL of the parent project */
46
+#define URL_SSH_PATH 0x0100 /* Include PATH= on SSH syncs */
47
+#define URL_SSH_RETRY 0x0200 /* This a retry of an SSH */
48
+#define URL_SSH_EXE 0x0400 /* ssh: URL contains fossil= query param*/
4649
4750
/*
4851
** The URL related data used with this subsystem.
4952
*/
5053
struct UrlData {
@@ -110,11 +113,11 @@
110113
UrlData *pUrlData
111114
){
112115
int i, j, c;
113116
char *zFile = 0;
114117
115
- pUrlData->pwConfig = 0;
118
+ memset(pUrlData, 0, sizeof(*pUrlData));
116119
if( urlFlags & URL_USE_CONFIG ){
117120
if( zUrl==0 || strcmp(zUrl,"default")==0 ){
118121
const char *zPwConfig = "last-sync-pw";
119122
if( urlFlags & URL_USE_PARENT ){
120123
zUrl = db_get("parent-project-url", 0);
@@ -159,12 +162,10 @@
159162
int iStart;
160163
char *zLogin;
161164
char *zExe;
162165
char cQuerySep = '?';
163166
164
- pUrlData->isFile = 0;
165
- pUrlData->useProxy = 0;
166167
if( zUrl[4]=='s' ){
167168
pUrlData->isHttps = 1;
168169
pUrlData->protocol = "https";
169170
pUrlData->dfltPort = 443;
170171
iStart = 8;
@@ -255,15 +256,17 @@
255256
if( pUrlData->path[i] ){
256257
pUrlData->path[i] = 0;
257258
i++;
258259
}
259260
if( fossil_strcmp(zName,"fossil")==0 ){
261
+ fossil_free(pUrlData->fossil);
260262
pUrlData->fossil = fossil_strdup(zValue);
261263
dehttpize(pUrlData->fossil);
262264
fossil_free(zExe);
263265
zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil);
264266
cQuerySep = '&';
267
+ urlFlags |= URL_SSH_EXE;
265268
}
266269
}
267270
268271
dehttpize(pUrlData->path);
269272
if( pUrlData->dfltPort==pUrlData->port ){
@@ -453,10 +456,36 @@
453456
** g.url.pwConfig is NULL.
454457
*/
455458
void url_parse(const char *zUrl, unsigned int urlFlags){
456459
url_parse_local(zUrl, urlFlags, &g.url);
457460
}
461
+
462
+/*
463
+** Print the content of g.url
464
+*/
465
+void urlparse_print(int showPw){
466
+ fossil_print("g.url.isFile = %d\n", g.url.isFile);
467
+ fossil_print("g.url.isHttps = %d\n", g.url.isHttps);
468
+ fossil_print("g.url.isSsh = %d\n", g.url.isSsh);
469
+ fossil_print("g.url.protocol = %s\n", g.url.protocol);
470
+ fossil_print("g.url.name = %s\n", g.url.name);
471
+ fossil_print("g.url.port = %d\n", g.url.port);
472
+ fossil_print("g.url.dfltPort = %d\n", g.url.dfltPort);
473
+ fossil_print("g.url.hostname = %s\n", g.url.hostname);
474
+ fossil_print("g.url.path = %s\n", g.url.path);
475
+ fossil_print("g.url.user = %s\n", g.url.user);
476
+ if( showPw || g.url.pwConfig==0 ){
477
+ fossil_print("g.url.passwd = %s\n", g.url.passwd);
478
+ }else{
479
+ fossil_print("g.url.passwd = ************\n");
480
+ }
481
+ fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig);
482
+ fossil_print("g.url.canonical = %s\n", g.url.canonical);
483
+ fossil_print("g.url.fossil = %s\n", g.url.fossil);
484
+ fossil_print("g.url.flags = 0x%04x\n", g.url.flags);
485
+ fossil_print("url_full(g.url) = %z\n", url_full(&g.url));
486
+}
458487
459488
/*
460489
** COMMAND: test-urlparser
461490
**
462491
** Usage: %fossil test-urlparser URL ?options?
@@ -482,30 +511,11 @@
482511
if( g.argc!=3 && g.argc!=4 ){
483512
usage("URL");
484513
}
485514
url_parse(g.argv[2], fg);
486515
for(i=0; i<2; i++){
487
- fossil_print("g.url.isFile = %d\n", g.url.isFile);
488
- fossil_print("g.url.isHttps = %d\n", g.url.isHttps);
489
- fossil_print("g.url.isSsh = %d\n", g.url.isSsh);
490
- fossil_print("g.url.protocol = %s\n", g.url.protocol);
491
- fossil_print("g.url.name = %s\n", g.url.name);
492
- fossil_print("g.url.port = %d\n", g.url.port);
493
- fossil_print("g.url.dfltPort = %d\n", g.url.dfltPort);
494
- fossil_print("g.url.hostname = %s\n", g.url.hostname);
495
- fossil_print("g.url.path = %s\n", g.url.path);
496
- fossil_print("g.url.user = %s\n", g.url.user);
497
- if( showPw || g.url.pwConfig==0 ){
498
- fossil_print("g.url.passwd = %s\n", g.url.passwd);
499
- }else{
500
- fossil_print("g.url.passwd = ************\n");
501
- }
502
- fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig);
503
- fossil_print("g.url.canonical = %s\n", g.url.canonical);
504
- fossil_print("g.url.fossil = %s\n", g.url.fossil);
505
- fossil_print("g.url.flags = 0x%02x\n", g.url.flags);
506
- fossil_print("url_full(g.url) = %z\n", url_full(&g.url));
516
+ urlparse_print(showPw);
507517
if( g.url.isFile || g.url.isSsh ) break;
508518
if( i==0 ){
509519
fossil_print("********\n");
510520
url_enable_proxy("Using proxy: ");
511521
}
512522
--- src/url.c
+++ src/url.c
@@ -33,18 +33,21 @@
33
34 #if INTERFACE
35 /*
36 ** Flags for url_parse()
37 */
38 #define URL_PROMPT_PW 0x001 /* Prompt for password if needed */
39 #define URL_REMEMBER 0x002 /* Remember the url for later reuse */
40 #define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */
41 #define URL_REMEMBER_PW 0x008 /* Should remember pw */
42 #define URL_PROMPTED 0x010 /* Prompted for PW already */
43 #define URL_OMIT_USER 0x020 /* Omit the user name from URL */
44 #define URL_USE_CONFIG 0x040 /* Use remembered URLs from CONFIG table */
45 #define URL_USE_PARENT 0x080 /* Use the URL of the parent project */
 
 
 
46
47 /*
48 ** The URL related data used with this subsystem.
49 */
50 struct UrlData {
@@ -110,11 +113,11 @@
110 UrlData *pUrlData
111 ){
112 int i, j, c;
113 char *zFile = 0;
114
115 pUrlData->pwConfig = 0;
116 if( urlFlags & URL_USE_CONFIG ){
117 if( zUrl==0 || strcmp(zUrl,"default")==0 ){
118 const char *zPwConfig = "last-sync-pw";
119 if( urlFlags & URL_USE_PARENT ){
120 zUrl = db_get("parent-project-url", 0);
@@ -159,12 +162,10 @@
159 int iStart;
160 char *zLogin;
161 char *zExe;
162 char cQuerySep = '?';
163
164 pUrlData->isFile = 0;
165 pUrlData->useProxy = 0;
166 if( zUrl[4]=='s' ){
167 pUrlData->isHttps = 1;
168 pUrlData->protocol = "https";
169 pUrlData->dfltPort = 443;
170 iStart = 8;
@@ -255,15 +256,17 @@
255 if( pUrlData->path[i] ){
256 pUrlData->path[i] = 0;
257 i++;
258 }
259 if( fossil_strcmp(zName,"fossil")==0 ){
 
260 pUrlData->fossil = fossil_strdup(zValue);
261 dehttpize(pUrlData->fossil);
262 fossil_free(zExe);
263 zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil);
264 cQuerySep = '&';
 
265 }
266 }
267
268 dehttpize(pUrlData->path);
269 if( pUrlData->dfltPort==pUrlData->port ){
@@ -453,10 +456,36 @@
453 ** g.url.pwConfig is NULL.
454 */
455 void url_parse(const char *zUrl, unsigned int urlFlags){
456 url_parse_local(zUrl, urlFlags, &g.url);
457 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
459 /*
460 ** COMMAND: test-urlparser
461 **
462 ** Usage: %fossil test-urlparser URL ?options?
@@ -482,30 +511,11 @@
482 if( g.argc!=3 && g.argc!=4 ){
483 usage("URL");
484 }
485 url_parse(g.argv[2], fg);
486 for(i=0; i<2; i++){
487 fossil_print("g.url.isFile = %d\n", g.url.isFile);
488 fossil_print("g.url.isHttps = %d\n", g.url.isHttps);
489 fossil_print("g.url.isSsh = %d\n", g.url.isSsh);
490 fossil_print("g.url.protocol = %s\n", g.url.protocol);
491 fossil_print("g.url.name = %s\n", g.url.name);
492 fossil_print("g.url.port = %d\n", g.url.port);
493 fossil_print("g.url.dfltPort = %d\n", g.url.dfltPort);
494 fossil_print("g.url.hostname = %s\n", g.url.hostname);
495 fossil_print("g.url.path = %s\n", g.url.path);
496 fossil_print("g.url.user = %s\n", g.url.user);
497 if( showPw || g.url.pwConfig==0 ){
498 fossil_print("g.url.passwd = %s\n", g.url.passwd);
499 }else{
500 fossil_print("g.url.passwd = ************\n");
501 }
502 fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig);
503 fossil_print("g.url.canonical = %s\n", g.url.canonical);
504 fossil_print("g.url.fossil = %s\n", g.url.fossil);
505 fossil_print("g.url.flags = 0x%02x\n", g.url.flags);
506 fossil_print("url_full(g.url) = %z\n", url_full(&g.url));
507 if( g.url.isFile || g.url.isSsh ) break;
508 if( i==0 ){
509 fossil_print("********\n");
510 url_enable_proxy("Using proxy: ");
511 }
512
--- src/url.c
+++ src/url.c
@@ -33,18 +33,21 @@
33
34 #if INTERFACE
35 /*
36 ** Flags for url_parse()
37 */
38 #define URL_PROMPT_PW 0x0001 /* Prompt for password if needed */
39 #define URL_REMEMBER 0x0002 /* Remember the url for later reuse */
40 #define URL_ASK_REMEMBER_PW 0x0004 /* Ask whether to remember prompted pw */
41 #define URL_REMEMBER_PW 0x0008 /* Should remember pw */
42 #define URL_PROMPTED 0x0010 /* Prompted for PW already */
43 #define URL_OMIT_USER 0x0020 /* Omit the user name from URL */
44 #define URL_USE_CONFIG 0x0040 /* Use remembered URLs from CONFIG table */
45 #define URL_USE_PARENT 0x0080 /* Use the URL of the parent project */
46 #define URL_SSH_PATH 0x0100 /* Include PATH= on SSH syncs */
47 #define URL_SSH_RETRY 0x0200 /* This a retry of an SSH */
48 #define URL_SSH_EXE 0x0400 /* ssh: URL contains fossil= query param*/
49
50 /*
51 ** The URL related data used with this subsystem.
52 */
53 struct UrlData {
@@ -110,11 +113,11 @@
113 UrlData *pUrlData
114 ){
115 int i, j, c;
116 char *zFile = 0;
117
118 memset(pUrlData, 0, sizeof(*pUrlData));
119 if( urlFlags & URL_USE_CONFIG ){
120 if( zUrl==0 || strcmp(zUrl,"default")==0 ){
121 const char *zPwConfig = "last-sync-pw";
122 if( urlFlags & URL_USE_PARENT ){
123 zUrl = db_get("parent-project-url", 0);
@@ -159,12 +162,10 @@
162 int iStart;
163 char *zLogin;
164 char *zExe;
165 char cQuerySep = '?';
166
 
 
167 if( zUrl[4]=='s' ){
168 pUrlData->isHttps = 1;
169 pUrlData->protocol = "https";
170 pUrlData->dfltPort = 443;
171 iStart = 8;
@@ -255,15 +256,17 @@
256 if( pUrlData->path[i] ){
257 pUrlData->path[i] = 0;
258 i++;
259 }
260 if( fossil_strcmp(zName,"fossil")==0 ){
261 fossil_free(pUrlData->fossil);
262 pUrlData->fossil = fossil_strdup(zValue);
263 dehttpize(pUrlData->fossil);
264 fossil_free(zExe);
265 zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil);
266 cQuerySep = '&';
267 urlFlags |= URL_SSH_EXE;
268 }
269 }
270
271 dehttpize(pUrlData->path);
272 if( pUrlData->dfltPort==pUrlData->port ){
@@ -453,10 +456,36 @@
456 ** g.url.pwConfig is NULL.
457 */
458 void url_parse(const char *zUrl, unsigned int urlFlags){
459 url_parse_local(zUrl, urlFlags, &g.url);
460 }
461
462 /*
463 ** Print the content of g.url
464 */
465 void urlparse_print(int showPw){
466 fossil_print("g.url.isFile = %d\n", g.url.isFile);
467 fossil_print("g.url.isHttps = %d\n", g.url.isHttps);
468 fossil_print("g.url.isSsh = %d\n", g.url.isSsh);
469 fossil_print("g.url.protocol = %s\n", g.url.protocol);
470 fossil_print("g.url.name = %s\n", g.url.name);
471 fossil_print("g.url.port = %d\n", g.url.port);
472 fossil_print("g.url.dfltPort = %d\n", g.url.dfltPort);
473 fossil_print("g.url.hostname = %s\n", g.url.hostname);
474 fossil_print("g.url.path = %s\n", g.url.path);
475 fossil_print("g.url.user = %s\n", g.url.user);
476 if( showPw || g.url.pwConfig==0 ){
477 fossil_print("g.url.passwd = %s\n", g.url.passwd);
478 }else{
479 fossil_print("g.url.passwd = ************\n");
480 }
481 fossil_print("g.url.pwConfig = %s\n", g.url.pwConfig);
482 fossil_print("g.url.canonical = %s\n", g.url.canonical);
483 fossil_print("g.url.fossil = %s\n", g.url.fossil);
484 fossil_print("g.url.flags = 0x%04x\n", g.url.flags);
485 fossil_print("url_full(g.url) = %z\n", url_full(&g.url));
486 }
487
488 /*
489 ** COMMAND: test-urlparser
490 **
491 ** Usage: %fossil test-urlparser URL ?options?
@@ -482,30 +511,11 @@
511 if( g.argc!=3 && g.argc!=4 ){
512 usage("URL");
513 }
514 url_parse(g.argv[2], fg);
515 for(i=0; i<2; i++){
516 urlparse_print(showPw);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517 if( g.url.isFile || g.url.isSsh ) break;
518 if( i==0 ){
519 fossil_print("********\n");
520 url_enable_proxy("Using proxy: ");
521 }
522
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,9 +1,13 @@
11
<title>Change Log</title>
22
33
<h2 id='v2_24'>Changes for version 2.24 (pending)</h2>
44
5
+ * If an ssh: sync fails in a way that suggests that the fossil executable
6
+ could not be found on the remote host, then retry after adding a PATH=
7
+ prefix to the command. This helps "ssh:" to "just work" when the server
8
+ is a Mac.
59
* Add the x= query paramater to the [/help?cmd=/timeline|/timeline page].
610
* Moved the /museum/repo.fossil file referenced from the Dockerfile from
711
the ENTRYPOINT to the CMD part to allow use of --repolist mode.
812
* The /uvlist page now shows the hash algorithm used so that
913
outsiders don't have to guess it from the hash length when
1014
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,9 +1,13 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_24'>Changes for version 2.24 (pending)</h2>
4
 
 
 
 
5 * Add the x= query paramater to the [/help?cmd=/timeline|/timeline page].
6 * Moved the /museum/repo.fossil file referenced from the Dockerfile from
7 the ENTRYPOINT to the CMD part to allow use of --repolist mode.
8 * The /uvlist page now shows the hash algorithm used so that
9 outsiders don't have to guess it from the hash length when
10
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,9 +1,13 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_24'>Changes for version 2.24 (pending)</h2>
4
5 * If an ssh: sync fails in a way that suggests that the fossil executable
6 could not be found on the remote host, then retry after adding a PATH=
7 prefix to the command. This helps "ssh:" to "just work" when the server
8 is a Mac.
9 * Add the x= query paramater to the [/help?cmd=/timeline|/timeline page].
10 * Moved the /museum/repo.fossil file referenced from the Dockerfile from
11 the ENTRYPOINT to the CMD part to allow use of --repolist mode.
12 * The /uvlist page now shows the hash algorithm used so that
13 outsiders don't have to guess it from the hash length when
14
+5 -2
--- www/loadmgmt.md
+++ www/loadmgmt.md
@@ -25,15 +25,18 @@
2525
1. An optional cache is available that remembers the 10 most recently
2626
requested `/zip` or `/tarball` pages and returns the precomputed
2727
answer if the same page is requested again.
2828
2929
2. Page requests can be configured to fail with a
30
- “[503 Server Overload][503]” HTTP error if an expensive request is
30
+ “[503 Server Overload][503]” HTTP error if any request is
3131
received while the host load average is too high.
3232
3333
Both of these load-control mechanisms are turned off by default, but
34
-they are recommended for high-traffic sites.
34
+they are recommended for high-traffic sites. Users with [admin
35
+permissions](caps/index.md) are exempt from these restrictions,
36
+provided they are logged in before the load gets too high (login is
37
+disabled under high load).
3538
3639
The webpage cache is activated using the [`fossil cache init`](/help/cache)
3740
command-line on the server. Add a `-R` option to
3841
specify the specific repository for which to enable caching. If running
3942
this command as root, be sure to “`chown`” the cache database to give
4043
--- www/loadmgmt.md
+++ www/loadmgmt.md
@@ -25,15 +25,18 @@
25 1. An optional cache is available that remembers the 10 most recently
26 requested `/zip` or `/tarball` pages and returns the precomputed
27 answer if the same page is requested again.
28
29 2. Page requests can be configured to fail with a
30 “[503 Server Overload][503]” HTTP error if an expensive request is
31 received while the host load average is too high.
32
33 Both of these load-control mechanisms are turned off by default, but
34 they are recommended for high-traffic sites.
 
 
 
35
36 The webpage cache is activated using the [`fossil cache init`](/help/cache)
37 command-line on the server. Add a `-R` option to
38 specify the specific repository for which to enable caching. If running
39 this command as root, be sure to “`chown`” the cache database to give
40
--- www/loadmgmt.md
+++ www/loadmgmt.md
@@ -25,15 +25,18 @@
25 1. An optional cache is available that remembers the 10 most recently
26 requested `/zip` or `/tarball` pages and returns the precomputed
27 answer if the same page is requested again.
28
29 2. Page requests can be configured to fail with a
30 “[503 Server Overload][503]” HTTP error if any request is
31 received while the host load average is too high.
32
33 Both of these load-control mechanisms are turned off by default, but
34 they are recommended for high-traffic sites. Users with [admin
35 permissions](caps/index.md) are exempt from these restrictions,
36 provided they are logged in before the load gets too high (login is
37 disabled under high load).
38
39 The webpage cache is activated using the [`fossil cache init`](/help/cache)
40 command-line on the server. Add a `-R` option to
41 specify the specific repository for which to enable caching. If running
42 this command as root, be sure to “`chown`” the cache database to give
43
+22 -24
--- www/newrepo.wiki
+++ www/newrepo.wiki
@@ -7,11 +7,11 @@
77
sharing it over the web.
88
99
The first thing we need to do is create a fossil repository file:
1010
1111
<verbatim>
12
-stephan@ludo:~/fossil$ fossil new demo.fossil
12
+$ fossil new demo.fossil
1313
project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a
1414
server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe
1515
admin-user: stephan (initial password is ******)
1616
</verbatim>
1717
@@ -24,11 +24,11 @@
2424
2525
The first thing we normally want to do is to run fossil as a local server so
2626
that you can configure the access rights to the repo:
2727
2828
<verbatim>
29
-stephan@ludo:~/fossil$ fossil ui demo.fossil
29
+$ fossil ui demo.fossil
3030
</verbatim>
3131
3232
The <tt>ui</tt> command starts up a server (with an optional <tt>-port
3333
NUMBER</tt> argument) and launches a web browser pointing at the
3434
fossil server. From there it takes just a few moments to configure the
@@ -50,14 +50,13 @@
5050
5151
The next thing we need to do is <em>open</em> the repository. To do so
5252
we create a working directory and then <tt>cd</tt> to it:
5353
5454
<verbatim>
55
-stephan@ludo:~/fossil$ mkdir demo
56
-stephan@ludo:~/fossil$ cd demo
57
-stephan@ludo:~/fossil/demo$ fossil open ../demo.fossil
58
-stephan@ludo:~/fossil/demo$
55
+$ mkdir demo
56
+$ cd demo
57
+$ fossil open ../demo.fossil
5958
</verbatim>
6059
6160
That creates a file called <tt>_FOSSIL_</tt> in the current
6261
directory, and this file contains all kinds of fossil-related
6362
information about your local repository. You can ignore it
@@ -67,36 +66,36 @@
6766
The next thing we need to do is add files to our repository. As it
6867
happens, we have a few C source files lying around, which we'll
6968
simply copy into our working directory.
7069
7170
<verbatim>
72
-stephan@ludo:~/fossil/demo$ cp ../csnip/*.{c,h} .
73
-stephan@ludo:~/fossil/demo$ ls
74
-clob.c clob.h clobz.c _FOSSIL_ mkdep.c test-clob.c
71
+$ cp ../csnip/*.{c,h} .
72
+$ ls
73
+clob.c clob.h clobz.c mkdep.c test-clob.c
7574
tokenize_path.c tokenize_path.h vappendf.c vappendf.h
7675
</verbatim>
7776
7877
Fossil doesn't know about those files yet. Telling fossil about
7978
a new file is a two-step process. First we <em>add</em> the file
8079
to the repository, then we <em>commit</em> the file. This is a familiar
8180
process for anyone who's worked with SCM systems before:
8281
8382
<verbatim>
84
-stephan@ludo:~/fossil/demo$ fossil add *.{c,h}
85
-stephan@ludo:~/fossil/demo$ fossil commit -m "egg"
83
+$ fossil add *.{c,h}
84
+$ fossil commit -m "egg"
8685
New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91
8786
</verbatim>
8887
8988
We now have a working repository! The file <tt>demo.fossil</tt>
9089
is the central storage, and we can share it amongst an arbitrary
9190
number of trees. As a silly example:
9291
9392
<verbatim>
94
-stephan@ludo:~/fossil/demo$ cd ~/fossil
95
-stephan@ludo:~/fossil$ mkdir demo2
96
-stephan@ludo:~/fossil$ cd demo2
97
-stephan@ludo:~/fossil/demo2$ fossil open ../demo.fossil
93
+$ cd ~/fossil
94
+$ mkdir demo2
95
+$ cd demo2
96
+$ fossil open ../demo.fossil
9897
ADD clob.c
9998
ADD clob.h
10099
ADD clobz.c
101100
ADD mkdep.c
102101
ADD test-clob.c
@@ -112,12 +111,12 @@
112111
Making your repository available over the web is trivial to do. We
113112
assume you have some web space where you can store your fossil file
114113
and run a CGI script. If not, then this option is not for you. If
115114
you do, then here's how...
116115
117
-Copy the fossil repository file to your web server (it doesn't
118
-matter where, really).
116
+Copy the fossil repository file to your web server (it doesn't matter
117
+where, really, but it "should" be unreachable by web browser traffic).
119118
120119
In your <tt>cgi-bin</tt> (or equivalent) directory, create a file
121120
which looks like this:
122121
123122
<verbatim>
@@ -126,28 +125,27 @@
126125
</verbatim>
127126
128127
Make that script executable, and you're all ready to go:
129128
130129
<verbatim>
131
-~/www/cgi-bin> chmod +x myrepo.cgi
130
+$ chmod +x ~/www/cgi-bin/myrepo.cgi
132131
</verbatim>
133132
134133
Now simply point your browser to
135
-<tt>http://my.domain/cgi-bin/myrepo.cgi</tt> and you should
134
+<tt>https://my.domain/cgi-bin/myrepo.cgi</tt> and you should
136135
be able to manage the repository from there.
137136
138137
To check out a copy of your remote repository, use the
139138
<em>clone</em> command:
140139
141140
<verbatim>
142
-stephan@ludo:~/fossil$ fossil clone \
143
- http://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
141
+$ fossil clone \
142
+ https://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
144143
</verbatim>
145144
146
-Note that you should pass your fossil login name and password (as set
147
-via local server mode) during the clone - that ensures that fossil
148
-won't ask you for it on each commit!
145
+If you do not provide your password in the URL, fossil will
146
+interactively prompt you for it.
149147
150148
A clone is a local copy of a remote repository, and can be opened just
151149
like a local one (as shown above). It is treated identically to your
152150
local repository, with one very important difference. When you commit
153151
changes to a cloned remote repository, they will be pushed back to the
154152
--- www/newrepo.wiki
+++ www/newrepo.wiki
@@ -7,11 +7,11 @@
7 sharing it over the web.
8
9 The first thing we need to do is create a fossil repository file:
10
11 <verbatim>
12 stephan@ludo:~/fossil$ fossil new demo.fossil
13 project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a
14 server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe
15 admin-user: stephan (initial password is ******)
16 </verbatim>
17
@@ -24,11 +24,11 @@
24
25 The first thing we normally want to do is to run fossil as a local server so
26 that you can configure the access rights to the repo:
27
28 <verbatim>
29 stephan@ludo:~/fossil$ fossil ui demo.fossil
30 </verbatim>
31
32 The <tt>ui</tt> command starts up a server (with an optional <tt>-port
33 NUMBER</tt> argument) and launches a web browser pointing at the
34 fossil server. From there it takes just a few moments to configure the
@@ -50,14 +50,13 @@
50
51 The next thing we need to do is <em>open</em> the repository. To do so
52 we create a working directory and then <tt>cd</tt> to it:
53
54 <verbatim>
55 stephan@ludo:~/fossil$ mkdir demo
56 stephan@ludo:~/fossil$ cd demo
57 stephan@ludo:~/fossil/demo$ fossil open ../demo.fossil
58 stephan@ludo:~/fossil/demo$
59 </verbatim>
60
61 That creates a file called <tt>_FOSSIL_</tt> in the current
62 directory, and this file contains all kinds of fossil-related
63 information about your local repository. You can ignore it
@@ -67,36 +66,36 @@
67 The next thing we need to do is add files to our repository. As it
68 happens, we have a few C source files lying around, which we'll
69 simply copy into our working directory.
70
71 <verbatim>
72 stephan@ludo:~/fossil/demo$ cp ../csnip/*.{c,h} .
73 stephan@ludo:~/fossil/demo$ ls
74 clob.c clob.h clobz.c _FOSSIL_ mkdep.c test-clob.c
75 tokenize_path.c tokenize_path.h vappendf.c vappendf.h
76 </verbatim>
77
78 Fossil doesn't know about those files yet. Telling fossil about
79 a new file is a two-step process. First we <em>add</em> the file
80 to the repository, then we <em>commit</em> the file. This is a familiar
81 process for anyone who's worked with SCM systems before:
82
83 <verbatim>
84 stephan@ludo:~/fossil/demo$ fossil add *.{c,h}
85 stephan@ludo:~/fossil/demo$ fossil commit -m "egg"
86 New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91
87 </verbatim>
88
89 We now have a working repository! The file <tt>demo.fossil</tt>
90 is the central storage, and we can share it amongst an arbitrary
91 number of trees. As a silly example:
92
93 <verbatim>
94 stephan@ludo:~/fossil/demo$ cd ~/fossil
95 stephan@ludo:~/fossil$ mkdir demo2
96 stephan@ludo:~/fossil$ cd demo2
97 stephan@ludo:~/fossil/demo2$ fossil open ../demo.fossil
98 ADD clob.c
99 ADD clob.h
100 ADD clobz.c
101 ADD mkdep.c
102 ADD test-clob.c
@@ -112,12 +111,12 @@
112 Making your repository available over the web is trivial to do. We
113 assume you have some web space where you can store your fossil file
114 and run a CGI script. If not, then this option is not for you. If
115 you do, then here's how...
116
117 Copy the fossil repository file to your web server (it doesn't
118 matter where, really).
119
120 In your <tt>cgi-bin</tt> (or equivalent) directory, create a file
121 which looks like this:
122
123 <verbatim>
@@ -126,28 +125,27 @@
126 </verbatim>
127
128 Make that script executable, and you're all ready to go:
129
130 <verbatim>
131 ~/www/cgi-bin> chmod +x myrepo.cgi
132 </verbatim>
133
134 Now simply point your browser to
135 <tt>http://my.domain/cgi-bin/myrepo.cgi</tt> and you should
136 be able to manage the repository from there.
137
138 To check out a copy of your remote repository, use the
139 <em>clone</em> command:
140
141 <verbatim>
142 stephan@ludo:~/fossil$ fossil clone \
143 http://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
144 </verbatim>
145
146 Note that you should pass your fossil login name and password (as set
147 via local server mode) during the clone - that ensures that fossil
148 won't ask you for it on each commit!
149
150 A clone is a local copy of a remote repository, and can be opened just
151 like a local one (as shown above). It is treated identically to your
152 local repository, with one very important difference. When you commit
153 changes to a cloned remote repository, they will be pushed back to the
154
--- www/newrepo.wiki
+++ www/newrepo.wiki
@@ -7,11 +7,11 @@
7 sharing it over the web.
8
9 The first thing we need to do is create a fossil repository file:
10
11 <verbatim>
12 $ fossil new demo.fossil
13 project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a
14 server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe
15 admin-user: stephan (initial password is ******)
16 </verbatim>
17
@@ -24,11 +24,11 @@
24
25 The first thing we normally want to do is to run fossil as a local server so
26 that you can configure the access rights to the repo:
27
28 <verbatim>
29 $ fossil ui demo.fossil
30 </verbatim>
31
32 The <tt>ui</tt> command starts up a server (with an optional <tt>-port
33 NUMBER</tt> argument) and launches a web browser pointing at the
34 fossil server. From there it takes just a few moments to configure the
@@ -50,14 +50,13 @@
50
51 The next thing we need to do is <em>open</em> the repository. To do so
52 we create a working directory and then <tt>cd</tt> to it:
53
54 <verbatim>
55 $ mkdir demo
56 $ cd demo
57 $ fossil open ../demo.fossil
 
58 </verbatim>
59
60 That creates a file called <tt>_FOSSIL_</tt> in the current
61 directory, and this file contains all kinds of fossil-related
62 information about your local repository. You can ignore it
@@ -67,36 +66,36 @@
66 The next thing we need to do is add files to our repository. As it
67 happens, we have a few C source files lying around, which we'll
68 simply copy into our working directory.
69
70 <verbatim>
71 $ cp ../csnip/*.{c,h} .
72 $ ls
73 clob.c clob.h clobz.c mkdep.c test-clob.c
74 tokenize_path.c tokenize_path.h vappendf.c vappendf.h
75 </verbatim>
76
77 Fossil doesn't know about those files yet. Telling fossil about
78 a new file is a two-step process. First we <em>add</em> the file
79 to the repository, then we <em>commit</em> the file. This is a familiar
80 process for anyone who's worked with SCM systems before:
81
82 <verbatim>
83 $ fossil add *.{c,h}
84 $ fossil commit -m "egg"
85 New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91
86 </verbatim>
87
88 We now have a working repository! The file <tt>demo.fossil</tt>
89 is the central storage, and we can share it amongst an arbitrary
90 number of trees. As a silly example:
91
92 <verbatim>
93 $ cd ~/fossil
94 $ mkdir demo2
95 $ cd demo2
96 $ fossil open ../demo.fossil
97 ADD clob.c
98 ADD clob.h
99 ADD clobz.c
100 ADD mkdep.c
101 ADD test-clob.c
@@ -112,12 +111,12 @@
111 Making your repository available over the web is trivial to do. We
112 assume you have some web space where you can store your fossil file
113 and run a CGI script. If not, then this option is not for you. If
114 you do, then here's how...
115
116 Copy the fossil repository file to your web server (it doesn't matter
117 where, really, but it "should" be unreachable by web browser traffic).
118
119 In your <tt>cgi-bin</tt> (or equivalent) directory, create a file
120 which looks like this:
121
122 <verbatim>
@@ -126,28 +125,27 @@
125 </verbatim>
126
127 Make that script executable, and you're all ready to go:
128
129 <verbatim>
130 $ chmod +x ~/www/cgi-bin/myrepo.cgi
131 </verbatim>
132
133 Now simply point your browser to
134 <tt>https://my.domain/cgi-bin/myrepo.cgi</tt> and you should
135 be able to manage the repository from there.
136
137 To check out a copy of your remote repository, use the
138 <em>clone</em> command:
139
140 <verbatim>
141 $ fossil clone \
142 https://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
143 </verbatim>
144
145 If you do not provide your password in the URL, fossil will
146 interactively prompt you for it.
 
147
148 A clone is a local copy of a remote repository, and can be opened just
149 like a local one (as shown above). It is treated identically to your
150 local repository, with one very important difference. When you commit
151 changes to a cloned remote repository, they will be pushed back to the
152
+22 -24
--- www/newrepo.wiki
+++ www/newrepo.wiki
@@ -7,11 +7,11 @@
77
sharing it over the web.
88
99
The first thing we need to do is create a fossil repository file:
1010
1111
<verbatim>
12
-stephan@ludo:~/fossil$ fossil new demo.fossil
12
+$ fossil new demo.fossil
1313
project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a
1414
server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe
1515
admin-user: stephan (initial password is ******)
1616
</verbatim>
1717
@@ -24,11 +24,11 @@
2424
2525
The first thing we normally want to do is to run fossil as a local server so
2626
that you can configure the access rights to the repo:
2727
2828
<verbatim>
29
-stephan@ludo:~/fossil$ fossil ui demo.fossil
29
+$ fossil ui demo.fossil
3030
</verbatim>
3131
3232
The <tt>ui</tt> command starts up a server (with an optional <tt>-port
3333
NUMBER</tt> argument) and launches a web browser pointing at the
3434
fossil server. From there it takes just a few moments to configure the
@@ -50,14 +50,13 @@
5050
5151
The next thing we need to do is <em>open</em> the repository. To do so
5252
we create a working directory and then <tt>cd</tt> to it:
5353
5454
<verbatim>
55
-stephan@ludo:~/fossil$ mkdir demo
56
-stephan@ludo:~/fossil$ cd demo
57
-stephan@ludo:~/fossil/demo$ fossil open ../demo.fossil
58
-stephan@ludo:~/fossil/demo$
55
+$ mkdir demo
56
+$ cd demo
57
+$ fossil open ../demo.fossil
5958
</verbatim>
6059
6160
That creates a file called <tt>_FOSSIL_</tt> in the current
6261
directory, and this file contains all kinds of fossil-related
6362
information about your local repository. You can ignore it
@@ -67,36 +66,36 @@
6766
The next thing we need to do is add files to our repository. As it
6867
happens, we have a few C source files lying around, which we'll
6968
simply copy into our working directory.
7069
7170
<verbatim>
72
-stephan@ludo:~/fossil/demo$ cp ../csnip/*.{c,h} .
73
-stephan@ludo:~/fossil/demo$ ls
74
-clob.c clob.h clobz.c _FOSSIL_ mkdep.c test-clob.c
71
+$ cp ../csnip/*.{c,h} .
72
+$ ls
73
+clob.c clob.h clobz.c mkdep.c test-clob.c
7574
tokenize_path.c tokenize_path.h vappendf.c vappendf.h
7675
</verbatim>
7776
7877
Fossil doesn't know about those files yet. Telling fossil about
7978
a new file is a two-step process. First we <em>add</em> the file
8079
to the repository, then we <em>commit</em> the file. This is a familiar
8180
process for anyone who's worked with SCM systems before:
8281
8382
<verbatim>
84
-stephan@ludo:~/fossil/demo$ fossil add *.{c,h}
85
-stephan@ludo:~/fossil/demo$ fossil commit -m "egg"
83
+$ fossil add *.{c,h}
84
+$ fossil commit -m "egg"
8685
New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91
8786
</verbatim>
8887
8988
We now have a working repository! The file <tt>demo.fossil</tt>
9089
is the central storage, and we can share it amongst an arbitrary
9190
number of trees. As a silly example:
9291
9392
<verbatim>
94
-stephan@ludo:~/fossil/demo$ cd ~/fossil
95
-stephan@ludo:~/fossil$ mkdir demo2
96
-stephan@ludo:~/fossil$ cd demo2
97
-stephan@ludo:~/fossil/demo2$ fossil open ../demo.fossil
93
+$ cd ~/fossil
94
+$ mkdir demo2
95
+$ cd demo2
96
+$ fossil open ../demo.fossil
9897
ADD clob.c
9998
ADD clob.h
10099
ADD clobz.c
101100
ADD mkdep.c
102101
ADD test-clob.c
@@ -112,12 +111,12 @@
112111
Making your repository available over the web is trivial to do. We
113112
assume you have some web space where you can store your fossil file
114113
and run a CGI script. If not, then this option is not for you. If
115114
you do, then here's how...
116115
117
-Copy the fossil repository file to your web server (it doesn't
118
-matter where, really).
116
+Copy the fossil repository file to your web server (it doesn't matter
117
+where, really, but it "should" be unreachable by web browser traffic).
119118
120119
In your <tt>cgi-bin</tt> (or equivalent) directory, create a file
121120
which looks like this:
122121
123122
<verbatim>
@@ -126,28 +125,27 @@
126125
</verbatim>
127126
128127
Make that script executable, and you're all ready to go:
129128
130129
<verbatim>
131
-~/www/cgi-bin> chmod +x myrepo.cgi
130
+$ chmod +x ~/www/cgi-bin/myrepo.cgi
132131
</verbatim>
133132
134133
Now simply point your browser to
135
-<tt>http://my.domain/cgi-bin/myrepo.cgi</tt> and you should
134
+<tt>https://my.domain/cgi-bin/myrepo.cgi</tt> and you should
136135
be able to manage the repository from there.
137136
138137
To check out a copy of your remote repository, use the
139138
<em>clone</em> command:
140139
141140
<verbatim>
142
-stephan@ludo:~/fossil$ fossil clone \
143
- http://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
141
+$ fossil clone \
142
+ https://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
144143
</verbatim>
145144
146
-Note that you should pass your fossil login name and password (as set
147
-via local server mode) during the clone - that ensures that fossil
148
-won't ask you for it on each commit!
145
+If you do not provide your password in the URL, fossil will
146
+interactively prompt you for it.
149147
150148
A clone is a local copy of a remote repository, and can be opened just
151149
like a local one (as shown above). It is treated identically to your
152150
local repository, with one very important difference. When you commit
153151
changes to a cloned remote repository, they will be pushed back to the
154152
--- www/newrepo.wiki
+++ www/newrepo.wiki
@@ -7,11 +7,11 @@
7 sharing it over the web.
8
9 The first thing we need to do is create a fossil repository file:
10
11 <verbatim>
12 stephan@ludo:~/fossil$ fossil new demo.fossil
13 project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a
14 server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe
15 admin-user: stephan (initial password is ******)
16 </verbatim>
17
@@ -24,11 +24,11 @@
24
25 The first thing we normally want to do is to run fossil as a local server so
26 that you can configure the access rights to the repo:
27
28 <verbatim>
29 stephan@ludo:~/fossil$ fossil ui demo.fossil
30 </verbatim>
31
32 The <tt>ui</tt> command starts up a server (with an optional <tt>-port
33 NUMBER</tt> argument) and launches a web browser pointing at the
34 fossil server. From there it takes just a few moments to configure the
@@ -50,14 +50,13 @@
50
51 The next thing we need to do is <em>open</em> the repository. To do so
52 we create a working directory and then <tt>cd</tt> to it:
53
54 <verbatim>
55 stephan@ludo:~/fossil$ mkdir demo
56 stephan@ludo:~/fossil$ cd demo
57 stephan@ludo:~/fossil/demo$ fossil open ../demo.fossil
58 stephan@ludo:~/fossil/demo$
59 </verbatim>
60
61 That creates a file called <tt>_FOSSIL_</tt> in the current
62 directory, and this file contains all kinds of fossil-related
63 information about your local repository. You can ignore it
@@ -67,36 +66,36 @@
67 The next thing we need to do is add files to our repository. As it
68 happens, we have a few C source files lying around, which we'll
69 simply copy into our working directory.
70
71 <verbatim>
72 stephan@ludo:~/fossil/demo$ cp ../csnip/*.{c,h} .
73 stephan@ludo:~/fossil/demo$ ls
74 clob.c clob.h clobz.c _FOSSIL_ mkdep.c test-clob.c
75 tokenize_path.c tokenize_path.h vappendf.c vappendf.h
76 </verbatim>
77
78 Fossil doesn't know about those files yet. Telling fossil about
79 a new file is a two-step process. First we <em>add</em> the file
80 to the repository, then we <em>commit</em> the file. This is a familiar
81 process for anyone who's worked with SCM systems before:
82
83 <verbatim>
84 stephan@ludo:~/fossil/demo$ fossil add *.{c,h}
85 stephan@ludo:~/fossil/demo$ fossil commit -m "egg"
86 New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91
87 </verbatim>
88
89 We now have a working repository! The file <tt>demo.fossil</tt>
90 is the central storage, and we can share it amongst an arbitrary
91 number of trees. As a silly example:
92
93 <verbatim>
94 stephan@ludo:~/fossil/demo$ cd ~/fossil
95 stephan@ludo:~/fossil$ mkdir demo2
96 stephan@ludo:~/fossil$ cd demo2
97 stephan@ludo:~/fossil/demo2$ fossil open ../demo.fossil
98 ADD clob.c
99 ADD clob.h
100 ADD clobz.c
101 ADD mkdep.c
102 ADD test-clob.c
@@ -112,12 +111,12 @@
112 Making your repository available over the web is trivial to do. We
113 assume you have some web space where you can store your fossil file
114 and run a CGI script. If not, then this option is not for you. If
115 you do, then here's how...
116
117 Copy the fossil repository file to your web server (it doesn't
118 matter where, really).
119
120 In your <tt>cgi-bin</tt> (or equivalent) directory, create a file
121 which looks like this:
122
123 <verbatim>
@@ -126,28 +125,27 @@
126 </verbatim>
127
128 Make that script executable, and you're all ready to go:
129
130 <verbatim>
131 ~/www/cgi-bin> chmod +x myrepo.cgi
132 </verbatim>
133
134 Now simply point your browser to
135 <tt>http://my.domain/cgi-bin/myrepo.cgi</tt> and you should
136 be able to manage the repository from there.
137
138 To check out a copy of your remote repository, use the
139 <em>clone</em> command:
140
141 <verbatim>
142 stephan@ludo:~/fossil$ fossil clone \
143 http://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
144 </verbatim>
145
146 Note that you should pass your fossil login name and password (as set
147 via local server mode) during the clone - that ensures that fossil
148 won't ask you for it on each commit!
149
150 A clone is a local copy of a remote repository, and can be opened just
151 like a local one (as shown above). It is treated identically to your
152 local repository, with one very important difference. When you commit
153 changes to a cloned remote repository, they will be pushed back to the
154
--- www/newrepo.wiki
+++ www/newrepo.wiki
@@ -7,11 +7,11 @@
7 sharing it over the web.
8
9 The first thing we need to do is create a fossil repository file:
10
11 <verbatim>
12 $ fossil new demo.fossil
13 project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a
14 server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe
15 admin-user: stephan (initial password is ******)
16 </verbatim>
17
@@ -24,11 +24,11 @@
24
25 The first thing we normally want to do is to run fossil as a local server so
26 that you can configure the access rights to the repo:
27
28 <verbatim>
29 $ fossil ui demo.fossil
30 </verbatim>
31
32 The <tt>ui</tt> command starts up a server (with an optional <tt>-port
33 NUMBER</tt> argument) and launches a web browser pointing at the
34 fossil server. From there it takes just a few moments to configure the
@@ -50,14 +50,13 @@
50
51 The next thing we need to do is <em>open</em> the repository. To do so
52 we create a working directory and then <tt>cd</tt> to it:
53
54 <verbatim>
55 $ mkdir demo
56 $ cd demo
57 $ fossil open ../demo.fossil
 
58 </verbatim>
59
60 That creates a file called <tt>_FOSSIL_</tt> in the current
61 directory, and this file contains all kinds of fossil-related
62 information about your local repository. You can ignore it
@@ -67,36 +66,36 @@
66 The next thing we need to do is add files to our repository. As it
67 happens, we have a few C source files lying around, which we'll
68 simply copy into our working directory.
69
70 <verbatim>
71 $ cp ../csnip/*.{c,h} .
72 $ ls
73 clob.c clob.h clobz.c mkdep.c test-clob.c
74 tokenize_path.c tokenize_path.h vappendf.c vappendf.h
75 </verbatim>
76
77 Fossil doesn't know about those files yet. Telling fossil about
78 a new file is a two-step process. First we <em>add</em> the file
79 to the repository, then we <em>commit</em> the file. This is a familiar
80 process for anyone who's worked with SCM systems before:
81
82 <verbatim>
83 $ fossil add *.{c,h}
84 $ fossil commit -m "egg"
85 New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91
86 </verbatim>
87
88 We now have a working repository! The file <tt>demo.fossil</tt>
89 is the central storage, and we can share it amongst an arbitrary
90 number of trees. As a silly example:
91
92 <verbatim>
93 $ cd ~/fossil
94 $ mkdir demo2
95 $ cd demo2
96 $ fossil open ../demo.fossil
97 ADD clob.c
98 ADD clob.h
99 ADD clobz.c
100 ADD mkdep.c
101 ADD test-clob.c
@@ -112,12 +111,12 @@
111 Making your repository available over the web is trivial to do. We
112 assume you have some web space where you can store your fossil file
113 and run a CGI script. If not, then this option is not for you. If
114 you do, then here's how...
115
116 Copy the fossil repository file to your web server (it doesn't matter
117 where, really, but it "should" be unreachable by web browser traffic).
118
119 In your <tt>cgi-bin</tt> (or equivalent) directory, create a file
120 which looks like this:
121
122 <verbatim>
@@ -126,28 +125,27 @@
125 </verbatim>
126
127 Make that script executable, and you're all ready to go:
128
129 <verbatim>
130 $ chmod +x ~/www/cgi-bin/myrepo.cgi
131 </verbatim>
132
133 Now simply point your browser to
134 <tt>https://my.domain/cgi-bin/myrepo.cgi</tt> and you should
135 be able to manage the repository from there.
136
137 To check out a copy of your remote repository, use the
138 <em>clone</em> command:
139
140 <verbatim>
141 $ fossil clone \
142 https://MyAccountName:[email protected]/cgi-bin/myrepo.cgi
143 </verbatim>
144
145 If you do not provide your password in the URL, fossil will
146 interactively prompt you for it.
 
147
148 A clone is a local copy of a remote repository, and can be opened just
149 like a local one (as shown above). It is treated identically to your
150 local repository, with one very important difference. When you commit
151 changes to a cloned remote repository, they will be pushed back to the
152

Keyboard Shortcuts

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