Fossil SCM
Merged trunk changes in
Commit
9a9276d69326d94faea311ec28fe97ff953eb5f6d4c0d0b870530682383c85a7
Parent
701ea4d00410858…
16 files changed
+66
-1
+8
-3
+165
-2
+9
-3
+61
-41
+1
-1
+70
-16
+2
-6
+3
-3
+187
-13
+41
-31
+4
+5
-2
+22
-24
+22
-24
+66
-1
| --- extsrc/pikchr.c | ||
| +++ extsrc/pikchr.c | ||
| @@ -3577,10 +3577,12 @@ | ||
| 3577 | 3577 | { "color", 0.0 }, |
| 3578 | 3578 | { "cylht", 0.5 }, |
| 3579 | 3579 | { "cylrad", 0.075 }, |
| 3580 | 3580 | { "cylwid", 0.75 }, |
| 3581 | 3581 | { "dashwid", 0.05 }, |
| 3582 | + { "diamondht", 0.75 }, | |
| 3583 | + { "diamondwid", 1.0 }, | |
| 3582 | 3584 | { "dotrad", 0.015 }, |
| 3583 | 3585 | { "ellipseht", 0.5 }, |
| 3584 | 3586 | { "ellipsewid", 0.75 }, |
| 3585 | 3587 | { "fileht", 0.75 }, |
| 3586 | 3588 | { "filerad", 0.15 }, |
| @@ -3957,10 +3959,62 @@ | ||
| 3957 | 3959 | pik_append(p,"\" />\n", -1); |
| 3958 | 3960 | } |
| 3959 | 3961 | pik_append_txt(p, pObj, 0); |
| 3960 | 3962 | } |
| 3961 | 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 | +} | |
| 3962 | 4016 | |
| 3963 | 4017 | |
| 3964 | 4018 | /* Methods for the "ellipse" class */ |
| 3965 | 4019 | static void ellipseInit(Pik *p, PObj *pObj){ |
| 3966 | 4020 | pObj->w = pik_value(p, "ellipsewid",10,0); |
| @@ -4341,10 +4395,21 @@ | ||
| 4341 | 4395 | /* xChop */ boxChop, |
| 4342 | 4396 | /* xOffset */ cylinderOffset, |
| 4343 | 4397 | /* xFit */ cylinderFit, |
| 4344 | 4398 | /* xRender */ cylinderRender |
| 4345 | 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 | + }, | |
| 4346 | 4411 | { /* name */ "dot", |
| 4347 | 4412 | /* isline */ 0, |
| 4348 | 4413 | /* eJust */ 0, |
| 4349 | 4414 | /* xInit */ dotInit, |
| 4350 | 4415 | /* xNumProp */ dotNumProp, |
| @@ -8143,6 +8208,6 @@ | ||
| 8143 | 8208 | |
| 8144 | 8209 | |
| 8145 | 8210 | #endif /* PIKCHR_TCL */ |
| 8146 | 8211 | |
| 8147 | 8212 | |
| 8148 | -#line 8173 "pikchr.c" | |
| 8213 | +#line 8238 "pikchr.c" | |
| 8149 | 8214 |
| --- 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 | ||
| 1 | 1 |
| --- 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 @@ | ||
| 19 | 19 | ** text files. |
| 20 | 20 | */ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "diff.h" |
| 23 | 23 | #include <assert.h> |
| 24 | +#include <errno.h> | |
| 24 | 25 | |
| 25 | 26 | |
| 26 | 27 | #if INTERFACE |
| 27 | 28 | /* |
| 28 | 29 | ** Flag parameters to the text_diff() routine used to control the formatting |
| @@ -3157,13 +3158,17 @@ | ||
| 3157 | 3158 | /* Undocumented and unsupported flags used for development |
| 3158 | 3159 | ** debugging and analysis: */ |
| 3159 | 3160 | if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG; |
| 3160 | 3161 | if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW; |
| 3161 | 3162 | } |
| 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 | + } | |
| 3165 | 3170 | } |
| 3166 | 3171 | if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){ |
| 3167 | 3172 | pCfg->wColumn = f; |
| 3168 | 3173 | } |
| 3169 | 3174 | if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO; |
| 3170 | 3175 |
| --- 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 @@ | ||
| 269 | 269 | blob_read_from_file(pReply, zDownlink, ExtFILE); |
| 270 | 270 | file_delete(zDownlink); |
| 271 | 271 | } |
| 272 | 272 | return rc; |
| 273 | 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 | +} | |
| 274 | 394 | |
| 275 | 395 | /* |
| 276 | 396 | ** Sign the content in pSend, compress it, and send it to the server |
| 277 | 397 | ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply |
| 278 | 398 | ** in pRecv. pRecv is assumed to be uninitialized when |
| @@ -304,10 +424,20 @@ | ||
| 304 | 424 | |
| 305 | 425 | if( g.zHttpCmd!=0 ){ |
| 306 | 426 | /* Handle the --transport-command option for "fossil sync" and similar */ |
| 307 | 427 | return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype); |
| 308 | 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 | + } | |
| 309 | 439 | |
| 310 | 440 | if( transport_open(&g.url) ){ |
| 311 | 441 | fossil_warning("%s", transport_errmsg(&g.url)); |
| 312 | 442 | return 1; |
| 313 | 443 | } |
| @@ -484,12 +614,45 @@ | ||
| 484 | 614 | } |
| 485 | 615 | } |
| 486 | 616 | } |
| 487 | 617 | } |
| 488 | 618 | 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 | + } | |
| 491 | 654 | } |
| 492 | 655 | if( rc!=200 ){ |
| 493 | 656 | fossil_warning("\"location:\" missing from %d redirect reply", rc); |
| 494 | 657 | goto write_err; |
| 495 | 658 | } |
| 496 | 659 |
| --- 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 |
+9
-3
| --- src/http_transport.c | ||
| +++ src/http_transport.c | ||
| @@ -131,15 +131,21 @@ | ||
| 131 | 131 | blob_append_escaped_arg(&zCmd, zHost, 0); |
| 132 | 132 | fossil_free(zHost); |
| 133 | 133 | }else{ |
| 134 | 134 | blob_append_escaped_arg(&zCmd, pUrlData->name, 0); |
| 135 | 135 | } |
| 136 | - if( !is_safe_fossil_command(pUrlData->fossil) ){ | |
| 136 | + if( (pUrlData->flags & URL_SSH_EXE)!=0 | |
| 137 | + && !is_safe_fossil_command(pUrlData->fossil) | |
| 138 | + ){ | |
| 137 | 139 | fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on " |
| 138 | 140 | "the server.", pUrlData->fossil); |
| 139 | 141 | } |
| 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 | + } | |
| 141 | 147 | blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1); |
| 142 | 148 | blob_append(&zCmd, " test-http", 10); |
| 143 | 149 | if( pUrlData->path && pUrlData->path[0] ){ |
| 144 | 150 | blob_append_escaped_arg(&zCmd, pUrlData->path, 1); |
| 145 | 151 | }else{ |
| @@ -313,11 +319,11 @@ | ||
| 313 | 319 | ** Read N bytes of content directly from the wire and write into |
| 314 | 320 | ** the buffer. |
| 315 | 321 | */ |
| 316 | 322 | static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){ |
| 317 | 323 | int got; |
| 318 | - if( sshIn ){ | |
| 324 | + if( pUrlData->isSsh ){ | |
| 319 | 325 | int x; |
| 320 | 326 | int wanted = N; |
| 321 | 327 | got = 0; |
| 322 | 328 | while( wanted>0 ){ |
| 323 | 329 | x = read(sshIn, &zBuf[got], wanted); |
| 324 | 330 |
| --- 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 @@ | ||
| 3065 | 3065 | ** |
| 3066 | 3066 | ** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then |
| 3067 | 3067 | ** the command is run on the remote host specified and the results are |
| 3068 | 3068 | ** tunneled back to the local machine via SSH. This feature only works for |
| 3069 | 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. | |
| 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". | |
| 3074 | 3075 | ** |
| 3075 | 3076 | ** REPOSITORY may also be a directory (aka folder) that contains one or |
| 3076 | 3077 | ** more repositories with names ending in ".fossil". In this case, a |
| 3077 | 3078 | ** prefix of the URL pathname is used to search the directory for an |
| 3078 | 3079 | ** appropriate repository. To thwart mischief, the pathname in the URL must |
| @@ -3343,49 +3344,68 @@ | ||
| 3343 | 3344 | /* If a USER@HOST:REPO argument is supplied, then use SSH to run |
| 3344 | 3345 | ** "fossil ui --nobrowser" on the remote system and to set up a |
| 3345 | 3346 | ** tunnel from the local machine to the remote. */ |
| 3346 | 3347 | FILE *sshIn; |
| 3347 | 3348 | Blob ssh; |
| 3349 | + int bRunning = 0; /* True when fossil starts up on the remote */ | |
| 3350 | + int isRetry; /* True if on the second attempt */ | |
| 3348 | 3351 | char zLine[1000]; |
| 3352 | + | |
| 3349 | 3353 | 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 | + } | |
| 3387 | 3407 | fossil_free(zBrowserCmd); |
| 3388 | 3408 | return; |
| 3389 | 3409 | } |
| 3390 | 3410 | if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; |
| 3391 | 3411 | if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; |
| 3392 | 3412 |
| --- 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 @@ | ||
| 210 | 210 | ** |
| 211 | 211 | ** Return 0 if there are no matches. |
| 212 | 212 | ** |
| 213 | 213 | ** This is a tricky query to do efficiently. |
| 214 | 214 | ** 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 | |
| 216 | 216 | ** the most recent EVENT table entries for the most recent with the tag. |
| 217 | 217 | ** But if the tag is relatively scarce (anything other than "trunk", basically) |
| 218 | 218 | ** then we want to do the indexed search show below as Q2. |
| 219 | 219 | */ |
| 220 | 220 | static int most_recent_event_with_tag(const char *zTag, const char *zType){ |
| 221 | 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 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 @@ | ||
| 45 | 45 | ** to implement the various subcommands. |
| 46 | 46 | */ |
| 47 | 47 | #define PATCH_DRYRUN 0x0001 |
| 48 | 48 | #define PATCH_VERBOSE 0x0002 |
| 49 | 49 | #define PATCH_FORCE 0x0004 |
| 50 | +#define PATCH_RETRY 0x0008 /* Second attempt */ | |
| 50 | 51 | |
| 51 | 52 | /* |
| 52 | 53 | ** Implementation of the "readfile(X)" SQL function. The entire content |
| 53 | 54 | ** of the check-out file named X is read and returned as a BLOB. |
| 54 | 55 | */ |
| @@ -132,11 +133,13 @@ | ||
| 132 | 133 | } |
| 133 | 134 | |
| 134 | 135 | |
| 135 | 136 | /* |
| 136 | 137 | ** 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. | |
| 138 | 141 | */ |
| 139 | 142 | void patch_create(unsigned mFlags, const char *zOut, FILE *out){ |
| 140 | 143 | int vid; |
| 141 | 144 | char *z; |
| 142 | 145 | |
| @@ -248,21 +251,22 @@ | ||
| 248 | 251 | } |
| 249 | 252 | #ifdef _WIN32 |
| 250 | 253 | fflush(out); |
| 251 | 254 | _setmode(_fileno(out), _O_BINARY); |
| 252 | 255 | #endif |
| 253 | - fwrite(pData, sz, 1, out); | |
| 254 | - sqlite3_free(pData); | |
| 256 | + fwrite(pData, 1, sz, out); | |
| 255 | 257 | fflush(out); |
| 258 | + sqlite3_free(pData); | |
| 256 | 259 | } |
| 260 | + db_multi_exec("DETACH patch;"); | |
| 257 | 261 | } |
| 258 | 262 | |
| 259 | 263 | /* |
| 260 | 264 | ** Attempt to load and validate a patchfile identified by the first |
| 261 | 265 | ** argument. |
| 262 | 266 | */ |
| 263 | -void patch_attach(const char *zIn, FILE *in){ | |
| 267 | +void patch_attach(const char *zIn, FILE *in, int bIgnoreEmptyPatch){ | |
| 264 | 268 | Stmt q; |
| 265 | 269 | if( g.db==0 ){ |
| 266 | 270 | sqlite3_open(":memory:", &g.db); |
| 267 | 271 | } |
| 268 | 272 | if( zIn==0 ){ |
| @@ -274,10 +278,15 @@ | ||
| 274 | 278 | #ifdef _WIN32 |
| 275 | 279 | _setmode(_fileno(in), _O_BINARY); |
| 276 | 280 | #endif |
| 277 | 281 | sz = blob_read_from_channel(&buf, in, -1); |
| 278 | 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 | + } | |
| 279 | 288 | db_multi_exec("ATTACH ':memory:' AS patch"); |
| 280 | 289 | if( g.fSqlTrace ){ |
| 281 | 290 | fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz); |
| 282 | 291 | } |
| 283 | 292 | rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0); |
| @@ -664,18 +673,20 @@ | ||
| 664 | 673 | const char *zThisCmd, /* "push" or "pull" */ |
| 665 | 674 | const char *zRemoteCmd, /* "apply" or "create" */ |
| 666 | 675 | const char *zFossilCmd, /* Name of "fossil" on remote system */ |
| 667 | 676 | const char *zRW /* "w" or "r" */ |
| 668 | 677 | ){ |
| 669 | - char *zRemote; | |
| 670 | - char *zDir; | |
| 678 | + char *zRemote = 0; | |
| 679 | + char *zDir = 0; | |
| 671 | 680 | Blob cmd; |
| 672 | - FILE *f; | |
| 681 | + FILE *f = 0; | |
| 673 | 682 | Blob flgs; |
| 674 | - char *zForce; | |
| 683 | + char *zForce = 0; | |
| 684 | + int isRetry = (mFlags & PATCH_RETRY)!=0; | |
| 675 | 685 | |
| 676 | 686 | blob_init(&flgs, 0, 0); |
| 687 | + blob_init(&cmd, 0, 0); | |
| 677 | 688 | if( mFlags & PATCH_FORCE ) blob_appendf(&flgs, " -f"); |
| 678 | 689 | if( mFlags & PATCH_VERBOSE ) blob_appendf(&flgs, " -v"); |
| 679 | 690 | if( mFlags & PATCH_DRYRUN ) blob_appendf(&flgs, " -n"); |
| 680 | 691 | zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : ""; |
| 681 | 692 | if( g.argc!=4 ){ |
| @@ -682,12 +693,12 @@ | ||
| 682 | 693 | usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd)); |
| 683 | 694 | } |
| 684 | 695 | zRemote = fossil_strdup(g.argv[3]); |
| 685 | 696 | zDir = (char*)file_skip_userhost(zRemote); |
| 686 | 697 | if( zDir==0 ){ |
| 698 | + if( isRetry ) goto remote_command_error; | |
| 687 | 699 | zDir = zRemote; |
| 688 | - blob_init(&cmd, 0, 0); | |
| 689 | 700 | blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
| 690 | 701 | blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir); |
| 691 | 702 | }else{ |
| 692 | 703 | Blob remote; |
| 693 | 704 | *(char*)(zDir-1) = 0; |
| @@ -694,28 +705,52 @@ | ||
| 694 | 705 | transport_ssh_command(&cmd); |
| 695 | 706 | blob_appendf(&cmd, " -T"); |
| 696 | 707 | blob_append_escaped_arg(&cmd, zRemote, 0); |
| 697 | 708 | blob_init(&remote, 0, 0); |
| 698 | 709 | 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 | + } | |
| 700 | 713 | zFossilCmd = "fossil"; |
| 714 | + }else if( mFlags & PATCH_RETRY ){ | |
| 715 | + goto remote_command_error; | |
| 701 | 716 | } |
| 702 | 717 | blob_appendf(&remote, "%$ patch %s%s --dir64 %z -", |
| 703 | 718 | zFossilCmd, zRemoteCmd, zForce, encode64(zDir, -1)); |
| 704 | 719 | blob_append_escaped_arg(&cmd, blob_str(&remote), 0); |
| 705 | 720 | blob_reset(&remote); |
| 706 | 721 | } |
| 722 | + if( isRetry ){ | |
| 723 | + fossil_print("First attempt to run \"fossil\" on %s failed\n" | |
| 724 | + "Retry: ", zRemote); | |
| 725 | + } | |
| 707 | 726 | fossil_print("%s\n", blob_str(&cmd)); |
| 708 | 727 | fflush(stdout); |
| 709 | 728 | f = popen(blob_str(&cmd), zRW); |
| 710 | 729 | if( f==0 ){ |
| 711 | 730 | fossil_fatal("cannot run command: %s", blob_str(&cmd)); |
| 712 | 731 | } |
| 732 | +remote_command_error: | |
| 733 | + fossil_free(zRemote); | |
| 713 | 734 | blob_reset(&cmd); |
| 714 | 735 | blob_reset(&flgs); |
| 715 | 736 | return f; |
| 716 | 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 | +} | |
| 717 | 752 | |
| 718 | 753 | /* |
| 719 | 754 | ** Show a diff for the patch currently loaded into database "patch". |
| 720 | 755 | */ |
| 721 | 756 | static void patch_diff( |
| @@ -934,11 +969,11 @@ | ||
| 934 | 969 | if( find_option("dry-run","n",0) ) flags |= PATCH_DRYRUN; |
| 935 | 970 | if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; |
| 936 | 971 | if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
| 937 | 972 | zIn = patch_find_patch_filename("apply"); |
| 938 | 973 | db_must_be_within_tree(); |
| 939 | - patch_attach(zIn, stdin); | |
| 974 | + patch_attach(zIn, stdin, 0); | |
| 940 | 975 | patch_apply(flags); |
| 941 | 976 | fossil_free(zIn); |
| 942 | 977 | }else |
| 943 | 978 | if( strncmp(zCmd, "create", n)==0 ){ |
| 944 | 979 | char *zOut; |
| @@ -963,11 +998,11 @@ | ||
| 963 | 998 | db_find_and_open_repository(0, 0); |
| 964 | 999 | if( find_option("force","f",0) ) flags |= PATCH_FORCE; |
| 965 | 1000 | diff_options(&DCfg, zCmd[0]=='g', 0); |
| 966 | 1001 | verify_all_options(); |
| 967 | 1002 | zIn = patch_find_patch_filename("apply"); |
| 968 | - patch_attach(zIn, stdin); | |
| 1003 | + patch_attach(zIn, stdin, 0); | |
| 969 | 1004 | patch_diff(flags, &DCfg); |
| 970 | 1005 | fossil_free(zIn); |
| 971 | 1006 | }else |
| 972 | 1007 | if( strncmp(zCmd, "pull", n)==0 ){ |
| 973 | 1008 | FILE *pIn = 0; |
| @@ -979,12 +1014,22 @@ | ||
| 979 | 1014 | db_must_be_within_tree(); |
| 980 | 1015 | verify_all_options(); |
| 981 | 1016 | pIn = patch_remote_command(flags & (~PATCH_FORCE), |
| 982 | 1017 | "pull", "create", zFossilCmd, "r"); |
| 983 | 1018 | 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 | + } | |
| 986 | 1031 | patch_apply(flags); |
| 987 | 1032 | } |
| 988 | 1033 | }else |
| 989 | 1034 | if( strncmp(zCmd, "push", n)==0 ){ |
| 990 | 1035 | FILE *pOut = 0; |
| @@ -996,11 +1041,20 @@ | ||
| 996 | 1041 | db_must_be_within_tree(); |
| 997 | 1042 | verify_all_options(); |
| 998 | 1043 | pOut = patch_remote_command(flags, "push", "apply", zFossilCmd, "w"); |
| 999 | 1044 | if( pOut ){ |
| 1000 | 1045 | 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 | + } | |
| 1002 | 1056 | } |
| 1003 | 1057 | }else |
| 1004 | 1058 | if( strncmp(zCmd, "view", n)==0 ){ |
| 1005 | 1059 | const char *zIn; |
| 1006 | 1060 | unsigned int flags = 0; |
| @@ -1009,12 +1063,12 @@ | ||
| 1009 | 1063 | if( g.argc!=4 ){ |
| 1010 | 1064 | usage("view FILENAME"); |
| 1011 | 1065 | } |
| 1012 | 1066 | zIn = g.argv[3]; |
| 1013 | 1067 | if( fossil_strcmp(zIn, "-")==0 ) zIn = 0; |
| 1014 | - patch_attach(zIn, stdin); | |
| 1068 | + patch_attach(zIn, stdin, 0); | |
| 1015 | 1069 | patch_view(flags); |
| 1016 | 1070 | }else |
| 1017 | 1071 | { |
| 1018 | 1072 | goto patch_usage; |
| 1019 | 1073 | } |
| 1020 | 1074 | } |
| 1021 | 1075 |
| --- 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 @@ | ||
| 42 | 42 | struct PQueue { |
| 43 | 43 | int cnt; /* Number of entries in the queue */ |
| 44 | 44 | int sz; /* Number of slots in a[] */ |
| 45 | 45 | struct QueueElement { |
| 46 | 46 | int id; /* ID of the element */ |
| 47 | - void *p; /* Content pointer */ | |
| 48 | 47 | double value; /* Value of element. Kept in ascending order */ |
| 49 | 48 | } *a; |
| 50 | 49 | }; |
| 51 | 50 | #endif |
| 52 | 51 | |
| @@ -74,11 +73,11 @@ | ||
| 74 | 73 | } |
| 75 | 74 | |
| 76 | 75 | /* |
| 77 | 76 | ** Insert element e into the queue. |
| 78 | 77 | */ |
| 79 | -void pqueuex_insert(PQueue *p, int e, double v, void *pData){ | |
| 78 | +void pqueuex_insert(PQueue *p, int e, double v){ | |
| 80 | 79 | int i, j; |
| 81 | 80 | if( p->cnt+1>p->sz ){ |
| 82 | 81 | pqueuex_resize(p, p->cnt+5); |
| 83 | 82 | } |
| 84 | 83 | for(i=0; i<p->cnt; i++){ |
| @@ -88,29 +87,26 @@ | ||
| 88 | 87 | } |
| 89 | 88 | break; |
| 90 | 89 | } |
| 91 | 90 | } |
| 92 | 91 | p->a[i].id = e; |
| 93 | - p->a[i].p = pData; | |
| 94 | 92 | p->a[i].value = v; |
| 95 | 93 | p->cnt++; |
| 96 | 94 | } |
| 97 | 95 | |
| 98 | 96 | /* |
| 99 | 97 | ** Extract the first element from the queue (the element with |
| 100 | 98 | ** the smallest value) and return its ID. Return 0 if the queue |
| 101 | 99 | ** is empty. |
| 102 | 100 | */ |
| 103 | -int pqueuex_extract(PQueue *p, void **pp){ | |
| 101 | +int pqueuex_extract(PQueue *p){ | |
| 104 | 102 | int e, i; |
| 105 | 103 | if( p->cnt==0 ){ |
| 106 | - if( pp ) *pp = 0; | |
| 107 | 104 | return 0; |
| 108 | 105 | } |
| 109 | 106 | e = p->a[0].id; |
| 110 | - if( pp ) *pp = p->a[0].p; | |
| 111 | 107 | for(i=0; i<p->cnt-1; i++){ |
| 112 | 108 | p->a[i] = p->a[i+1]; |
| 113 | 109 | } |
| 114 | 110 | p->cnt--; |
| 115 | 111 | return e; |
| 116 | 112 | } |
| 117 | 113 |
| --- 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 @@ | ||
| 44 | 44 | Stmt ins; /* INSERT INTO tagxref */ |
| 45 | 45 | Stmt eventupdate; /* UPDATE event */ |
| 46 | 46 | |
| 47 | 47 | assert( tagType==0 || tagType==2 ); |
| 48 | 48 | pqueuex_init(&queue); |
| 49 | - pqueuex_insert(&queue, pid, 0.0, 0); | |
| 49 | + pqueuex_insert(&queue, pid, 0.0); | |
| 50 | 50 | |
| 51 | 51 | /* Query for children of :pid to which to propagate the tag. |
| 52 | 52 | ** Three returns: (1) rid of the child. (2) timestamp of child. |
| 53 | 53 | ** (3) True to propagate or false to block. |
| 54 | 54 | */ |
| @@ -79,18 +79,18 @@ | ||
| 79 | 79 | if( tagid==TAG_BGCOLOR ){ |
| 80 | 80 | db_prepare(&eventupdate, |
| 81 | 81 | "UPDATE event SET bgcolor=%Q WHERE objid=:rid", zValue |
| 82 | 82 | ); |
| 83 | 83 | } |
| 84 | - while( (pid = pqueuex_extract(&queue, 0))!=0 ){ | |
| 84 | + while( (pid = pqueuex_extract(&queue))!=0 ){ | |
| 85 | 85 | db_bind_int(&s, ":pid", pid); |
| 86 | 86 | while( db_step(&s)==SQLITE_ROW ){ |
| 87 | 87 | int doit = db_column_int(&s, 2); |
| 88 | 88 | if( doit ){ |
| 89 | 89 | int cid = db_column_int(&s, 0); |
| 90 | 90 | double mtime = db_column_double(&s, 1); |
| 91 | - pqueuex_insert(&queue, cid, mtime, 0); | |
| 91 | + pqueuex_insert(&queue, cid, mtime); | |
| 92 | 92 | db_bind_int(&ins, ":rid", cid); |
| 93 | 93 | db_step(&ins); |
| 94 | 94 | db_reset(&ins); |
| 95 | 95 | if( tagid==TAG_BGCOLOR ){ |
| 96 | 96 | db_bind_int(&eventupdate, ":rid", cid); |
| 97 | 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, 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 @@ | ||
| 1568 | 1568 | |
| 1569 | 1569 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1570 | 1570 | return zEDate; |
| 1571 | 1571 | } |
| 1572 | 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 | + | |
| 1573 | 1697 | |
| 1574 | 1698 | /* |
| 1575 | 1699 | ** WEBPAGE: timeline |
| 1576 | 1700 | ** |
| 1577 | 1701 | ** Query parameters: |
| @@ -1598,14 +1722,16 @@ | ||
| 1598 | 1722 | ** bt=PRIOR ... going back to PRIOR |
| 1599 | 1723 | ** d=CHECKIN Children and descendants of CHECKIN |
| 1600 | 1724 | ** ft=DESCENDANT ... going forward to DESCENDANT |
| 1601 | 1725 | ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN' |
| 1602 | 1726 | ** 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 | |
| 1607 | 1733 | ** t=TAG Show only check-ins with the given TAG |
| 1608 | 1734 | ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel |
| 1609 | 1735 | ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist |
| 1610 | 1736 | ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist |
| 1611 | 1737 | ** rel Show related check-ins as well as those matching t=TAG |
| @@ -1627,10 +1753,12 @@ | ||
| 1627 | 1753 | ** f=CHECKIN Show family (immediate parents and children) of CHECKIN |
| 1628 | 1754 | ** from=CHECKIN Path from... |
| 1629 | 1755 | ** to=CHECKIN ... to this |
| 1630 | 1756 | ** shortest ... show only the shortest path |
| 1631 | 1757 | ** rel ... also show related checkins |
| 1758 | +** bt=PRIOR ... path from CHECKIN back to PRIOR | |
| 1759 | +** ft=LATER ... path from CHECKIN forward to LATER | |
| 1632 | 1760 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1633 | 1761 | ** All qualifying check-ins are shown unless there is |
| 1634 | 1762 | ** also an n= or n1= query parameter. |
| 1635 | 1763 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1636 | 1764 | ** name matches one of the comma-separate GLOBLIST |
| @@ -1727,10 +1855,11 @@ | ||
| 1727 | 1855 | int disableY = 0; /* Disable type selector on submenu */ |
| 1728 | 1856 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1729 | 1857 | char *zPlural; /* Ending for plural forms */ |
| 1730 | 1858 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1731 | 1859 | int haveParameterN; /* True if n= query parameter present */ |
| 1860 | + int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */ | |
| 1732 | 1861 | |
| 1733 | 1862 | url_initialize(&url, "timeline"); |
| 1734 | 1863 | cgi_query_parameters_to_url(&url); |
| 1735 | 1864 | |
| 1736 | 1865 | (void)P_NoBot("ss") |
| @@ -2043,10 +2172,30 @@ | ||
| 2043 | 2172 | blob_append_sql(&sql, |
| 2044 | 2173 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 2045 | 2174 | " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", |
| 2046 | 2175 | TAG_HIDDEN |
| 2047 | 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 | + } | |
| 2048 | 2197 | } |
| 2049 | 2198 | if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){ |
| 2050 | 2199 | /* If from= and to= are present, display all nodes on a path connecting |
| 2051 | 2200 | ** the two */ |
| 2052 | 2201 | PathNode *p = 0; |
| @@ -2054,11 +2203,17 @@ | ||
| 2054 | 2203 | const char *zTo = 0; |
| 2055 | 2204 | Blob ins; |
| 2056 | 2205 | int nNodeOnPath = 0; |
| 2057 | 2206 | |
| 2058 | 2207 | 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 | + } | |
| 2060 | 2215 | zFrom = P("from"); |
| 2061 | 2216 | zTo = P("to"); |
| 2062 | 2217 | }else{ |
| 2063 | 2218 | if( path_common_ancestor(me_rid, you_rid) ){ |
| 2064 | 2219 | p = path_first(); |
| @@ -2122,19 +2277,37 @@ | ||
| 2122 | 2277 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2123 | 2278 | if( advancedMenu ){ |
| 2124 | 2279 | style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0); |
| 2125 | 2280 | } |
| 2126 | 2281 | 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 | + } | |
| 2128 | 2292 | 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 | + } | |
| 2136 | 2309 | } |
| 2137 | 2310 | } |
| 2138 | 2311 | addFileGlobDescription(zChng, &desc); |
| 2139 | 2312 | }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){ |
| 2140 | 2313 | /* If p= or d= is present, ignore all other parameters other than n= */ |
| @@ -2680,10 +2853,11 @@ | ||
| 2680 | 2853 | @ <pre>%h(blob_sql_text(&sql2))</pre> |
| 2681 | 2854 | } |
| 2682 | 2855 | db_multi_exec("%s", blob_sql_text(&sql2)); |
| 2683 | 2856 | if( nEntry>0 ){ |
| 2684 | 2857 | nEntry -= db_int(0,"select count(*) from timeline"); |
| 2858 | + if( nEntry<=0 ) nEntry = 1; | |
| 2685 | 2859 | } |
| 2686 | 2860 | blob_reset(&sql2); |
| 2687 | 2861 | blob_append_sql(&sql, |
| 2688 | 2862 | " AND event.mtime<=%f ORDER BY event.mtime DESC", |
| 2689 | 2863 | rCirca |
| 2690 | 2864 |
| --- 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 @@ | ||
| 33 | 33 | |
| 34 | 34 | #if INTERFACE |
| 35 | 35 | /* |
| 36 | 36 | ** Flags for url_parse() |
| 37 | 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 */ | |
| 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*/ | |
| 46 | 49 | |
| 47 | 50 | /* |
| 48 | 51 | ** The URL related data used with this subsystem. |
| 49 | 52 | */ |
| 50 | 53 | struct UrlData { |
| @@ -110,11 +113,11 @@ | ||
| 110 | 113 | UrlData *pUrlData |
| 111 | 114 | ){ |
| 112 | 115 | int i, j, c; |
| 113 | 116 | char *zFile = 0; |
| 114 | 117 | |
| 115 | - pUrlData->pwConfig = 0; | |
| 118 | + memset(pUrlData, 0, sizeof(*pUrlData)); | |
| 116 | 119 | if( urlFlags & URL_USE_CONFIG ){ |
| 117 | 120 | if( zUrl==0 || strcmp(zUrl,"default")==0 ){ |
| 118 | 121 | const char *zPwConfig = "last-sync-pw"; |
| 119 | 122 | if( urlFlags & URL_USE_PARENT ){ |
| 120 | 123 | zUrl = db_get("parent-project-url", 0); |
| @@ -159,12 +162,10 @@ | ||
| 159 | 162 | int iStart; |
| 160 | 163 | char *zLogin; |
| 161 | 164 | char *zExe; |
| 162 | 165 | char cQuerySep = '?'; |
| 163 | 166 | |
| 164 | - pUrlData->isFile = 0; | |
| 165 | - pUrlData->useProxy = 0; | |
| 166 | 167 | if( zUrl[4]=='s' ){ |
| 167 | 168 | pUrlData->isHttps = 1; |
| 168 | 169 | pUrlData->protocol = "https"; |
| 169 | 170 | pUrlData->dfltPort = 443; |
| 170 | 171 | iStart = 8; |
| @@ -255,15 +256,17 @@ | ||
| 255 | 256 | if( pUrlData->path[i] ){ |
| 256 | 257 | pUrlData->path[i] = 0; |
| 257 | 258 | i++; |
| 258 | 259 | } |
| 259 | 260 | if( fossil_strcmp(zName,"fossil")==0 ){ |
| 261 | + fossil_free(pUrlData->fossil); | |
| 260 | 262 | pUrlData->fossil = fossil_strdup(zValue); |
| 261 | 263 | dehttpize(pUrlData->fossil); |
| 262 | 264 | fossil_free(zExe); |
| 263 | 265 | zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil); |
| 264 | 266 | cQuerySep = '&'; |
| 267 | + urlFlags |= URL_SSH_EXE; | |
| 265 | 268 | } |
| 266 | 269 | } |
| 267 | 270 | |
| 268 | 271 | dehttpize(pUrlData->path); |
| 269 | 272 | if( pUrlData->dfltPort==pUrlData->port ){ |
| @@ -453,10 +456,36 @@ | ||
| 453 | 456 | ** g.url.pwConfig is NULL. |
| 454 | 457 | */ |
| 455 | 458 | void url_parse(const char *zUrl, unsigned int urlFlags){ |
| 456 | 459 | url_parse_local(zUrl, urlFlags, &g.url); |
| 457 | 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 | +} | |
| 458 | 487 | |
| 459 | 488 | /* |
| 460 | 489 | ** COMMAND: test-urlparser |
| 461 | 490 | ** |
| 462 | 491 | ** Usage: %fossil test-urlparser URL ?options? |
| @@ -482,30 +511,11 @@ | ||
| 482 | 511 | if( g.argc!=3 && g.argc!=4 ){ |
| 483 | 512 | usage("URL"); |
| 484 | 513 | } |
| 485 | 514 | url_parse(g.argv[2], fg); |
| 486 | 515 | 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); | |
| 507 | 517 | if( g.url.isFile || g.url.isSsh ) break; |
| 508 | 518 | if( i==0 ){ |
| 509 | 519 | fossil_print("********\n"); |
| 510 | 520 | url_enable_proxy("Using proxy: "); |
| 511 | 521 | } |
| 512 | 522 |
| --- 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 |
+4
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -1,9 +1,13 @@ | ||
| 1 | 1 | <title>Change Log</title> |
| 2 | 2 | |
| 3 | 3 | <h2 id='v2_24'>Changes for version 2.24 (pending)</h2> |
| 4 | 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. | |
| 5 | 9 | * Add the x= query paramater to the [/help?cmd=/timeline|/timeline page]. |
| 6 | 10 | * Moved the /museum/repo.fossil file referenced from the Dockerfile from |
| 7 | 11 | the ENTRYPOINT to the CMD part to allow use of --repolist mode. |
| 8 | 12 | * The /uvlist page now shows the hash algorithm used so that |
| 9 | 13 | outsiders don't have to guess it from the hash length when |
| 10 | 14 |
| --- 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 @@ | ||
| 25 | 25 | 1. An optional cache is available that remembers the 10 most recently |
| 26 | 26 | requested `/zip` or `/tarball` pages and returns the precomputed |
| 27 | 27 | answer if the same page is requested again. |
| 28 | 28 | |
| 29 | 29 | 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 | |
| 31 | 31 | received while the host load average is too high. |
| 32 | 32 | |
| 33 | 33 | 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). | |
| 35 | 38 | |
| 36 | 39 | The webpage cache is activated using the [`fossil cache init`](/help/cache) |
| 37 | 40 | command-line on the server. Add a `-R` option to |
| 38 | 41 | specify the specific repository for which to enable caching. If running |
| 39 | 42 | this command as root, be sure to “`chown`” the cache database to give |
| 40 | 43 |
| --- 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 @@ | ||
| 7 | 7 | sharing it over the web. |
| 8 | 8 | |
| 9 | 9 | The first thing we need to do is create a fossil repository file: |
| 10 | 10 | |
| 11 | 11 | <verbatim> |
| 12 | -stephan@ludo:~/fossil$ fossil new demo.fossil | |
| 12 | +$ fossil new demo.fossil | |
| 13 | 13 | project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a |
| 14 | 14 | server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe |
| 15 | 15 | admin-user: stephan (initial password is ******) |
| 16 | 16 | </verbatim> |
| 17 | 17 | |
| @@ -24,11 +24,11 @@ | ||
| 24 | 24 | |
| 25 | 25 | The first thing we normally want to do is to run fossil as a local server so |
| 26 | 26 | that you can configure the access rights to the repo: |
| 27 | 27 | |
| 28 | 28 | <verbatim> |
| 29 | -stephan@ludo:~/fossil$ fossil ui demo.fossil | |
| 29 | +$ fossil ui demo.fossil | |
| 30 | 30 | </verbatim> |
| 31 | 31 | |
| 32 | 32 | The <tt>ui</tt> command starts up a server (with an optional <tt>-port |
| 33 | 33 | NUMBER</tt> argument) and launches a web browser pointing at the |
| 34 | 34 | fossil server. From there it takes just a few moments to configure the |
| @@ -50,14 +50,13 @@ | ||
| 50 | 50 | |
| 51 | 51 | The next thing we need to do is <em>open</em> the repository. To do so |
| 52 | 52 | we create a working directory and then <tt>cd</tt> to it: |
| 53 | 53 | |
| 54 | 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$ | |
| 55 | +$ mkdir demo | |
| 56 | +$ cd demo | |
| 57 | +$ fossil open ../demo.fossil | |
| 59 | 58 | </verbatim> |
| 60 | 59 | |
| 61 | 60 | That creates a file called <tt>_FOSSIL_</tt> in the current |
| 62 | 61 | directory, and this file contains all kinds of fossil-related |
| 63 | 62 | information about your local repository. You can ignore it |
| @@ -67,36 +66,36 @@ | ||
| 67 | 66 | The next thing we need to do is add files to our repository. As it |
| 68 | 67 | happens, we have a few C source files lying around, which we'll |
| 69 | 68 | simply copy into our working directory. |
| 70 | 69 | |
| 71 | 70 | <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 | |
| 75 | 74 | tokenize_path.c tokenize_path.h vappendf.c vappendf.h |
| 76 | 75 | </verbatim> |
| 77 | 76 | |
| 78 | 77 | Fossil doesn't know about those files yet. Telling fossil about |
| 79 | 78 | a new file is a two-step process. First we <em>add</em> the file |
| 80 | 79 | to the repository, then we <em>commit</em> the file. This is a familiar |
| 81 | 80 | process for anyone who's worked with SCM systems before: |
| 82 | 81 | |
| 83 | 82 | <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" | |
| 86 | 85 | New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91 |
| 87 | 86 | </verbatim> |
| 88 | 87 | |
| 89 | 88 | We now have a working repository! The file <tt>demo.fossil</tt> |
| 90 | 89 | is the central storage, and we can share it amongst an arbitrary |
| 91 | 90 | number of trees. As a silly example: |
| 92 | 91 | |
| 93 | 92 | <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 | |
| 98 | 97 | ADD clob.c |
| 99 | 98 | ADD clob.h |
| 100 | 99 | ADD clobz.c |
| 101 | 100 | ADD mkdep.c |
| 102 | 101 | ADD test-clob.c |
| @@ -112,12 +111,12 @@ | ||
| 112 | 111 | Making your repository available over the web is trivial to do. We |
| 113 | 112 | assume you have some web space where you can store your fossil file |
| 114 | 113 | and run a CGI script. If not, then this option is not for you. If |
| 115 | 114 | you do, then here's how... |
| 116 | 115 | |
| 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). | |
| 119 | 118 | |
| 120 | 119 | In your <tt>cgi-bin</tt> (or equivalent) directory, create a file |
| 121 | 120 | which looks like this: |
| 122 | 121 | |
| 123 | 122 | <verbatim> |
| @@ -126,28 +125,27 @@ | ||
| 126 | 125 | </verbatim> |
| 127 | 126 | |
| 128 | 127 | Make that script executable, and you're all ready to go: |
| 129 | 128 | |
| 130 | 129 | <verbatim> |
| 131 | -~/www/cgi-bin> chmod +x myrepo.cgi | |
| 130 | +$ chmod +x ~/www/cgi-bin/myrepo.cgi | |
| 132 | 131 | </verbatim> |
| 133 | 132 | |
| 134 | 133 | 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 | |
| 136 | 135 | be able to manage the repository from there. |
| 137 | 136 | |
| 138 | 137 | To check out a copy of your remote repository, use the |
| 139 | 138 | <em>clone</em> command: |
| 140 | 139 | |
| 141 | 140 | <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 | |
| 144 | 143 | </verbatim> |
| 145 | 144 | |
| 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. | |
| 149 | 147 | |
| 150 | 148 | A clone is a local copy of a remote repository, and can be opened just |
| 151 | 149 | like a local one (as shown above). It is treated identically to your |
| 152 | 150 | local repository, with one very important difference. When you commit |
| 153 | 151 | changes to a cloned remote repository, they will be pushed back to the |
| 154 | 152 |
| --- 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 @@ | ||
| 7 | 7 | sharing it over the web. |
| 8 | 8 | |
| 9 | 9 | The first thing we need to do is create a fossil repository file: |
| 10 | 10 | |
| 11 | 11 | <verbatim> |
| 12 | -stephan@ludo:~/fossil$ fossil new demo.fossil | |
| 12 | +$ fossil new demo.fossil | |
| 13 | 13 | project-id: 9d8ccff5671796ee04e60af6932aa7788f0a990a |
| 14 | 14 | server-id: 145fe7d71e3b513ac37ac283979d73e12ca04bfe |
| 15 | 15 | admin-user: stephan (initial password is ******) |
| 16 | 16 | </verbatim> |
| 17 | 17 | |
| @@ -24,11 +24,11 @@ | ||
| 24 | 24 | |
| 25 | 25 | The first thing we normally want to do is to run fossil as a local server so |
| 26 | 26 | that you can configure the access rights to the repo: |
| 27 | 27 | |
| 28 | 28 | <verbatim> |
| 29 | -stephan@ludo:~/fossil$ fossil ui demo.fossil | |
| 29 | +$ fossil ui demo.fossil | |
| 30 | 30 | </verbatim> |
| 31 | 31 | |
| 32 | 32 | The <tt>ui</tt> command starts up a server (with an optional <tt>-port |
| 33 | 33 | NUMBER</tt> argument) and launches a web browser pointing at the |
| 34 | 34 | fossil server. From there it takes just a few moments to configure the |
| @@ -50,14 +50,13 @@ | ||
| 50 | 50 | |
| 51 | 51 | The next thing we need to do is <em>open</em> the repository. To do so |
| 52 | 52 | we create a working directory and then <tt>cd</tt> to it: |
| 53 | 53 | |
| 54 | 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$ | |
| 55 | +$ mkdir demo | |
| 56 | +$ cd demo | |
| 57 | +$ fossil open ../demo.fossil | |
| 59 | 58 | </verbatim> |
| 60 | 59 | |
| 61 | 60 | That creates a file called <tt>_FOSSIL_</tt> in the current |
| 62 | 61 | directory, and this file contains all kinds of fossil-related |
| 63 | 62 | information about your local repository. You can ignore it |
| @@ -67,36 +66,36 @@ | ||
| 67 | 66 | The next thing we need to do is add files to our repository. As it |
| 68 | 67 | happens, we have a few C source files lying around, which we'll |
| 69 | 68 | simply copy into our working directory. |
| 70 | 69 | |
| 71 | 70 | <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 | |
| 75 | 74 | tokenize_path.c tokenize_path.h vappendf.c vappendf.h |
| 76 | 75 | </verbatim> |
| 77 | 76 | |
| 78 | 77 | Fossil doesn't know about those files yet. Telling fossil about |
| 79 | 78 | a new file is a two-step process. First we <em>add</em> the file |
| 80 | 79 | to the repository, then we <em>commit</em> the file. This is a familiar |
| 81 | 80 | process for anyone who's worked with SCM systems before: |
| 82 | 81 | |
| 83 | 82 | <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" | |
| 86 | 85 | New_Version: d1296b4a08b9f8b943bb6c73698e51eed23f8f91 |
| 87 | 86 | </verbatim> |
| 88 | 87 | |
| 89 | 88 | We now have a working repository! The file <tt>demo.fossil</tt> |
| 90 | 89 | is the central storage, and we can share it amongst an arbitrary |
| 91 | 90 | number of trees. As a silly example: |
| 92 | 91 | |
| 93 | 92 | <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 | |
| 98 | 97 | ADD clob.c |
| 99 | 98 | ADD clob.h |
| 100 | 99 | ADD clobz.c |
| 101 | 100 | ADD mkdep.c |
| 102 | 101 | ADD test-clob.c |
| @@ -112,12 +111,12 @@ | ||
| 112 | 111 | Making your repository available over the web is trivial to do. We |
| 113 | 112 | assume you have some web space where you can store your fossil file |
| 114 | 113 | and run a CGI script. If not, then this option is not for you. If |
| 115 | 114 | you do, then here's how... |
| 116 | 115 | |
| 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). | |
| 119 | 118 | |
| 120 | 119 | In your <tt>cgi-bin</tt> (or equivalent) directory, create a file |
| 121 | 120 | which looks like this: |
| 122 | 121 | |
| 123 | 122 | <verbatim> |
| @@ -126,28 +125,27 @@ | ||
| 126 | 125 | </verbatim> |
| 127 | 126 | |
| 128 | 127 | Make that script executable, and you're all ready to go: |
| 129 | 128 | |
| 130 | 129 | <verbatim> |
| 131 | -~/www/cgi-bin> chmod +x myrepo.cgi | |
| 130 | +$ chmod +x ~/www/cgi-bin/myrepo.cgi | |
| 132 | 131 | </verbatim> |
| 133 | 132 | |
| 134 | 133 | 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 | |
| 136 | 135 | be able to manage the repository from there. |
| 137 | 136 | |
| 138 | 137 | To check out a copy of your remote repository, use the |
| 139 | 138 | <em>clone</em> command: |
| 140 | 139 | |
| 141 | 140 | <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 | |
| 144 | 143 | </verbatim> |
| 145 | 144 | |
| 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. | |
| 149 | 147 | |
| 150 | 148 | A clone is a local copy of a remote repository, and can be opened just |
| 151 | 149 | like a local one (as shown above). It is treated identically to your |
| 152 | 150 | local repository, with one very important difference. When you commit |
| 153 | 151 | changes to a cloned remote repository, they will be pushed back to the |
| 154 | 152 |
| --- 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 |