Index: src/allrepo.c ================================================================== --- src/allrepo.c +++ src/allrepo.c @@ -128,11 +128,11 @@ ** supported by the rebuild command itself, if any are ** present, are passed along verbatim. The --force and ** --randomize options are not supported. ** ** sync Run a "sync" on all repositories. Only the --verbose -** option is supported. +** and --unversioned options are supported. ** ** setting Run the "setting", "set", or "unset" commands on all ** set repositories. These command are particularly useful in ** unset conjunction with the "max-loadavg" setting which cannot ** otherwise be set globally. Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -133,21 +133,18 @@ Manifest *pM = 0; const char *zSubdirLink; int linkTrunk = 1; int linkTip = 1; HQuery sURI; + int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ + char *zHeader = 0; + if( zCI && strlen(zCI)==0 ){ zCI = 0; } if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } - style_header("File List"); - style_adunit_config(ADUNIT_RIGHT_OK); - sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, - pathelementFunc, 0, 0); - url_initialize(&sURI, "dir"); - cgi_query_parameters_to_url(&sURI); /* If the name= parameter is an empty string, make it a NULL pointer */ if( zD && strlen(zD)==0 ){ zD = 0; } /* If a specific check-in is requested, fetch and parse it. If the @@ -159,14 +156,31 @@ if( pM ){ int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); linkTrunk = trunkRid && rid != trunkRid; linkTip = rid != symbolic_name_to_rid("tip", "ci"); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); }else{ zCI = 0; } } + + assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); + if( isSymbolicCI ) { + zHeader = mprintf("%s at %s", (zD ? zD : "Files"), zCI); + }else if( zUuid && strlen(zUuid) ){ + zHeader = mprintf("%s at [%S]", (zD ? zD : "Files"), zUuid); + }else{ + zHeader = mprintf("%s", (zD ? zD : "All Files")); + } + style_header("%s", zHeader); + fossil_free(zHeader); + style_adunit_config(ADUNIT_RIGHT_OK); + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, + pathelementFunc, 0, 0); + url_initialize(&sURI, "dir"); + cgi_query_parameters_to_url(&sURI); /* Compute the title of the page */ blob_zero(&dirname); if( zD ){ blob_append(&dirname, "in directory ", -1); @@ -184,11 +198,11 @@ } if( linkTip ){ style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); } if( zCI ){ - @

Files of check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)] + @

Files at check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)] @ %s(blob_str(&dirname)) if( zD ){ @   %z(href("%R/timeline?chng=%T/*", zD))[history] } @

@@ -195,11 +209,11 @@ zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix); if( nD==0 ){ style_submenu_element("File Ages", "%R/fileage?name=%!S", zUuid); } }else{ - @

The union of all files from all check-ins + @

All files known in the repository @ %s(blob_str(&dirname)) if( zD ){ @   %z(href("%R/timeline?chng=%T/*", zD))[history] } @

@@ -282,12 +296,11 @@ zFN++; @
  • %z(href("%s%T",zSubdirLink,zFN))%h(zFN)
  • }else{ const char *zLink; if( zCI ){ - const char *zUuid = db_column_text(&q, 1); - zLink = href("%R/artifact/%!S",zUuid); + zLink = href("%R/file?name=%T%T&ci=%!S",zPrefix,zFN,zCI); }else{ zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); } @
  • %z(zLink)%h(zFN)
  • } @@ -612,11 +625,14 @@ HQuery sURI; /* Hyperlink */ int startExpanded; /* True to start out with the tree expanded */ int showDirOnly; /* Show directories only. Omit files */ int nDir = 0; /* Number of directories. Used for ID attributes */ char *zProjectName = db_get("project-name", 0); + int isSymbolicCI = 0; /* ci= is a symbolic name, not a hash prefix */ + char *zHeader = 0; + if( zCI && strlen(zCI)==0 ){ zCI = 0; } if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } memset(&sTree, 0, sizeof(sTree)); login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } @@ -624,14 +640,12 @@ pathelementFunc, 0, 0); url_initialize(&sURI, "tree"); cgi_query_parameters_to_url(&sURI); if( PB("nofiles") ){ showDirOnly = 1; - style_header("Folder Hierarchy"); }else{ showDirOnly = 0; - style_header("File Tree"); } style_adunit_config(ADUNIT_RIGHT_OK); if( PB("expand") ){ startExpanded = 1; }else{ @@ -660,18 +674,33 @@ linkTip = rid != symbolic_name_to_rid("tip", "ci"); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); zNow = db_text("", "SELECT datetime(mtime,toLocal())" " FROM event WHERE objid=%d", rid); + isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); }else{ zCI = 0; } } if( zCI==0 ){ rNow = db_double(0.0, "SELECT max(mtime) FROM event"); zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); } + + assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); + if( isSymbolicCI ) { + zHeader = mprintf("%s at %s", + (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zCI); + }else if( zUuid && strlen(zUuid) ){ + zHeader = mprintf("%s at [%S]", + (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zUuid); + }else{ + zHeader = mprintf("%s", + (zD ? zD : (showDirOnly ?"All Folders Hierarchy":"All Files Tree-View"))); + } + style_header("%s", zHeader); + fossil_free(zHeader); /* Compute the title of the page */ blob_zero(&dirname); if( zD ){ blob_append(&dirname, "within directory ", -1); @@ -758,11 +787,11 @@ } style_submenu_checkbox("nofiles", "Folders Only", 0, 0); if( zCI ){ - @

    %s(zObjType) from + @

    %s(zObjType) at check-in if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){ @ "%h(zCI)" } @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)] %s(blob_str(&dirname)) }else{ @@ -824,11 +853,11 @@ nDir++; }else if( !showDirOnly ){ const char *zFileClass = fileext_class(p->zName); char *zLink; if( zCI ){ - zLink = href("%R/artifact/%!S",p->zUuid); + zLink = href("%R/file?name=%T&ci=%!S",p->zFullName,zCI); }else{ zLink = href("%R/finfo?name=%T",p->zFullName); } @
  • @ %z(zLink)%h(p->zName) Index: src/builtin.c ================================================================== --- src/builtin.c +++ src/builtin.c @@ -55,17 +55,25 @@ return (char*)builtin_file(zFilename, 0); } /* ** COMMAND: test-builtin-list +** +** If -verbose is used, it outputs a line at the end +** with the total item count and size. ** ** List the names and sizes of all built-in resources. */ void test_builtin_list(void){ - int i; + int i, size = 0;; for(i=0; i0 && iReplyStatus != 304 + if( total_size>0 + && iReplyStatus!=304 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0 ){ int i, size; for(i=0; i<2; i++){ size = blob_size(&cgiContent[i]); Index: src/comformat.c ================================================================== --- src/comformat.c +++ src/comformat.c @@ -19,16 +19,10 @@ ** text on a TTY. */ #include "config.h" #include "comformat.h" #include -#ifdef _WIN32 -# include -#else -# include -# include -#endif #if INTERFACE #define COMMENT_PRINT_NONE ((u32)0x00000000) /* No flags = non-legacy. */ #define COMMENT_PRINT_LEGACY ((u32)0x00000001) /* Use legacy algorithm. */ #define COMMENT_PRINT_TRIM_CRLF ((u32)0x00000002) /* Trim leading CR/LF. */ @@ -67,35 +61,27 @@ */ static int comment_set_maxchars( int indent, int *pMaxChars ){ -#if defined(_WIN32) - CONSOLE_SCREEN_BUFFER_INFO csbi; - memset(&csbi, 0, sizeof(CONSOLE_SCREEN_BUFFER_INFO)); - if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) ){ - *pMaxChars = csbi.srWindow.Right - csbi.srWindow.Left - indent; - return 1; - } - return 0; -#elif defined(TIOCGWINSZ) - struct winsize w; - memset(&w, 0, sizeof(struct winsize)); - if( ioctl(0, TIOCGWINSZ, &w)!=-1 ){ - *pMaxChars = w.ws_col - indent; - return 1; - } - return 0; -#else - /* - ** Fallback to using more-or-less the "legacy semantics" of hard-coding - ** the maximum line length to a value reasonable for the vast majority - ** of supported systems. - */ - *pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent; - return -1; -#endif + struct TerminalSize ts; + if ( !terminal_get_size(&ts) ){ + return 0; + } + + if( ts.nColumns ){ + *pMaxChars = ts.nColumns - indent; + return 1; + }else{ + /* + ** Fallback to using more-or-less the "legacy semantics" of hard-coding + ** the maximum line length to a value reasonable for the vast majority + ** of supported systems. + */ + *pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent; + return -1; + } } /* ** This function checks the current line being printed against the original ** comment text. Upon matching, it updates the provided character and line Index: src/default_css.txt ================================================================== --- src/default_css.txt +++ src/default_css.txt @@ -861,36 +861,116 @@ // #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div { // max-width: 30em; // overflow: auto; // } // .fileedit-XXX => /fileedit page -form.fileedit textarea { +.hidden { + position: absolute; + opacity: 0; + pointer-events: none; +} +//.hidden { +// display: none; +//} +#fossil-status-bar { + display: block; + font-family: monospace; + border-width: 1px; + border-style: inset; + border-color: inherit; + min-height: 1.5em; + font-size: 1.2em; + padding: 0.2em; + margin: 0.25em 0; + flex: 0 0 auto; +} +#fossil-status-bar.error { + color: darkred; + background: yellow; +} +////////////////////////////////// +// Styles for fossil.tabs.js: +.tab-container { + width: 100%; + display: flex; + flex-direction: column; + align-items: stretch; +} +.tab-container > #fossil-status-bar { + margin-top: 0; +} +.tab-container > .tabs { + padding: 0.25em; + margin: 0; + display: flex; + flex-direction: column; + border-width: 1px; + border-style: outset; + border-color: inherit; +} +.tab-container > .tabs > .tab-panel { + align-self: stretch; + flex: 10 1 auto; + display: block; +} +.tab-container > .tab-bar { + display: flex; + flex-direction: row; + flex: 1 10 auto; + align-self: stretch; + flex-wrap: wrap; +} +.tab-container > .tab-bar > .tab-button { + display: inline-block; + border-radius: 0.5em 0.5em 0 0; + margin: 0 0.1em; + padding: 0.25em 0.75em; + align-self: baseline; + border-color: inherit; + border-width: 1px; + border-bottom: none; + border-top-style: inset; + border-left-style: inset; + border-right-style: inset; + cursor: pointer; + opacity: 0.6; +} +.tab-container > .tab-bar > .tab-button.selected { + text-decoration: underline; + opacity: 1.0; + border-top-style: outset; + border-left-style: outset; + border-right-style: outset; +} +////////////////////////////////// +// Styles for /fileedit: +textarea.fileedit { font-family: monospace; width: 100%; + flex: 10 1 auto; } -form.fileedit fieldset { - margin: 0.5em 0 0 0; +body.fileedit fieldset { + margin: 0.5em 0 0.5em 0; border-radius: 0.5em; border-color: inherit; border-width: 1px; + font-size: 85%; } -form.fileedit fieldset > legend { +body.fileedit fieldset > legend { margin: 0 0 0 1em; padding: 0 0.5em 0 0.5em; } -form.fileedit fieldset > div { - margin: 0 0.25em 0.25em 0.25em; +body.fileedit fieldset > div { + margin: 0 0.25em 0 0.25em; + padding: 0; } -form.fileedit fieldset > div > .input-with-label { +body.fileedit fieldset > div > .input-with-label { margin: 0.25em 0.5em; } -form.fileedit fieldset > div > button { +body.fileedit fieldset > div > button { margin: 0.25em 0.5em; } -form.fileedit input:invalid { - border-left: 0.2em dashed red; -} .fileedit-hint { font-size: 80%; opacity: 0.75; } .fileedit-error-report { @@ -902,40 +982,158 @@ } code.fileedit-manifest { display: block; height: 16em; overflow: auto; + white-space: pre; } div.fileedit-preview { margin: 0; padding: 0; } -.fileedit-preview > div:first-child { - margin: 1em 0 0 0; - border-bottom: 1px dashed; +#fileedit-tab-diff-wrapper { + margin: 0; + padding: 0; + overflow: auto; +} +#fileedit-tab-preview-wrapper { + overflow: auto; +} +.fileedit-options.commit-message > div { + display: flex; + flex-direction: column; + justify-content: stretch; + font-family: monospace; +} +.fileedit-options.commit-message > div > * { + margin: 0.25em; +} +#fileedit-commit-button-wrapper { + margin: 0.25em; +} +.tab-container > .tabs > .tab-panel > .fileedit-options { + margin-top: 0; + border: none; + border-radius: 0; + border-bottom-width: 1px; + border-bottom-style: dotted; +} +.tab-container > .tabs > .tab-panel > .fileedit-options > button { + vertical-align: middle; + margin: 0.5em; +} +.tab-container > .tabs > .tab-panel > .fileedit-options > input { + vertical-align: middle; + margin: 0.5em; +} +.tab-container > .tabs > .tab-panel > .fileedit-options > .input-with-label { + vertical-align: middle; + margin: 0.5em; +} +//////////////////////////////////////////////////////////////////// +// Styles developed for /fileedit but which have wider +// applicability: +.flex-container { + display: flex; +} +.flex-container.flex-row { + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} +.fileedit-options.flex-container.flex-row { + align-items: first baseline; +} +.flex-container .flex-grow { + flex-grow: 10; + flex-shrink: 0; +} +.flex-container .flex-shrink { + flex-grow: 0; + flex-shrink: 10; +} +.fileedit-options > div > * { + margin: 0.25em; } -div.fileedit-diff { +#fileedit-file-selector { + display: flex; + flex-direction: column; + align-content: flex-start; + border-color: inherit; + border-width: 1px; + border-style: inset; + border-radius: 0.5em; + padding: 0 0.25em; margin: 0; + min-height: 12em; +} +#fileedit-file-selector select { + margin: 0 0 0.5em 0; + height: initial; + font-family: monospace; +} +#fileedit-file-selector select:focus { + border: none; +} +#fileedit-file-selector > div { padding: 0; + margin: 0; +} +#fileedit-file-selector > div > * { + margin: 0.25em 0.5em 0.25em 0; +} +.flex-container.flex-row.stretch { + flex-wrap: wrap; + align-items: baseline; + justify-content: stretch; + margin: 0; +} +.flex-container.flex-column { + flex-direction: column; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} +.flex-container.flex-column.stretch { + align-items: stretch; + margin: 0; +} +.flex-container.child-gap-small > * { + margin: 0.25em; +} +.font-size-100 { + font-size: 100%; +} +.font-size-125 { + font-size: 125%; +} +.font-size-150 { + font-size: 150%; +} +.font-size-175 { + font-size: 175%; } -.fileedit-diff > div:first-child { - border-bottom: 1px dashed; +.font-size-200 { + font-size: 200%; } - +////////////////////////////////////////////////////////////////// +// .input-with-label is intended to be a wrapper element which +// contains a SPAN label and an INPUT control. .input-with-label { border: 1px inset #808080; border-radius: 0.5em; padding: 0.25em 0.4em; margin: 0 0.5em; display: inline-block; - cursor: pointer; + cursor: default; } .input-with-label > * { vertical-align: middle; } .input-with-label > input { - margin: 0; + margin: 0; } .input-with-label > select { margin: 0; } .input-with-label > input[type=text] { @@ -943,14 +1141,14 @@ } .input-with-label > textarea { margin: 0; } .input-with-label > input[type=checkbox] { - vertical-align: sub; + vertical-align: sub; } .input-with-label > input[type=radio] { - vertical-align: sub; + vertical-align: sub; } .input-with-label > span { - margin: 0 0.25em 0 0.25em; - vertical-align: middle; + margin: 0 0.25em 0 0.25em; + vertical-align: middle; } Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -1826,14 +1826,39 @@ } /* ** Extract the width of columns for side-by-side diff. Supply an ** appropriate default if no width is given. +** +** Calculate the default automatically, based on terminal's current width: +** term-width = 2*diff-col + diff-marker + 1 +** diff-col = lineno + lmargin + text-width + rmargin +** +** text-width = (term-width - diff-marker - 1)/2 - lineno - lmargin - rmargin */ int diff_width(u64 diffFlags){ int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1); - if( w==0 ) w = 80; + if( w==0 ){ + static struct { + unsigned int lineno, lmargin, text, rmargin, marker; + } sbsW = { 5, 2, 0, 0, 3 }; + const unsigned int wMin = 24, wMax = 132; + unsigned int tw = terminal_get_width(80); + unsigned int twMin = + (wMin + sbsW.lineno + sbsW.lmargin + sbsW.rmargin)*2 + sbsW.marker + 1; + unsigned int twMax = + (wMax + sbsW.lineno + sbsW.lmargin + sbsW.rmargin)*2 + sbsW.marker + 1; + + if( twtwMax ){ + tw = twMax; + } + sbsW.text = + (tw - sbsW.marker - 1)/2 - sbsW.lineno - sbsW.lmargin - sbsW.rmargin; + w = sbsW.text; + } return w; } /* ** Append the error message to pOut. Index: src/etag.c ================================================================== --- src/etag.c +++ src/etag.c @@ -60,32 +60,51 @@ */ #define ETAG_CONFIG 0x01 /* Output depends on the CONFIG table */ #define ETAG_DATA 0x02 /* Output depends on the EVENT table */ #define ETAG_COOKIE 0x04 /* Output depends on a display cookie value */ #define ETAG_HASH 0x08 /* Output depends on a hash */ +#define ETAG_QUERY 0x10 /* Output depends on PATH_INFO and QUERY_STRING */ #endif static char zETag[33]; /* The generated ETag */ static int iMaxAge = 0; /* The max-age parameter in the reply */ static sqlite3_int64 iEtagMtime = 0; /* Last-Modified time */ + +/* +** Return a hash that changes every time the Fossil source code is +** rebuilt. +** +** The current implementation is a hash of MANIFEST_UUID, __DATE__, and +** __TIME__. But this might change in the future if we think of a better +** way to compute an identifier that changes with each build. +*/ +const char *fossil_exe_id(void){ + static char zExecId[33]; + if( zExecId[0]==0 ){ + Blob x; + blob_init(&x, MANIFEST_UUID "," __DATE__ "," __TIME__, -1); + md5sum_blob(&x, &x); + memcpy(zExecId, x.aData, 32); + } + return zExecId; +} /* ** Generate an ETag */ void etag_check(unsigned eFlags, const char *zHash){ - sqlite3_int64 mtime; const char *zIfNoneMatch; char zBuf[50]; assert( zETag[0]==0 ); /* Only call this routine once! */ iMaxAge = 86400; md5sum_init(); - /* Always include the mtime of the executable as part of the hash */ - mtime = file_mtime(g.nameOfExe, ExtFILE); - sqlite3_snprintf(sizeof(zBuf),zBuf,"mtime: %lld\n", mtime); - md5sum_step_text(zBuf, -1); + /* Always include the executable ID as part of the hash */ + md5sum_step_text("exe-id: ", -1); + md5sum_step_text(fossil_exe_id(), -1); + md5sum_step_text("\n", 1); if( (eFlags & ETAG_HASH)!=0 && zHash ){ md5sum_step_text("hash: ", -1); md5sum_step_text(zHash, -1); md5sum_step_text("\n", 1); @@ -98,11 +117,11 @@ md5sum_step_text("\n", 1); iMaxAge = 60; }else if( eFlags & ETAG_CONFIG ){ int iKey = db_int(0, "SELECT value FROM config WHERE name='cfgcnt'"); sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",iKey); - md5sum_step_text("data: ", -1); + md5sum_step_text("config: ", -1); md5sum_step_text(zBuf, -1); md5sum_step_text("\n", 1); iMaxAge = 3600; } @@ -111,10 +130,22 @@ md5sum_step_text("display-cookie: ", -1); md5sum_step_text(PD(DISPLAY_SETTINGS_COOKIE,""), -1); md5sum_step_text("\n", 1); iMaxAge = 0; } + + /* Output depends on PATH_INFO and QUERY_STRING */ + if( eFlags & ETAG_QUERY ){ + const char *zQS = P("QUERY_STRING"); + md5sum_step_text("query: ", -1); + md5sum_step_text(PD("PATH_INFO",""), -1); + if( zQS ){ + md5sum_step_text("?", 1); + md5sum_step_text(zQS, -1); + } + md5sum_step_text("\n",1); + } /* Generate the ETag */ memcpy(zETag, md5sum_finish(0), 33); /* Check to see if the generated ETag matches If-None-Match and Index: src/fileedit.c ================================================================== --- src/fileedit.c +++ src/fileedit.c @@ -28,12 +28,13 @@ ** db, e.g. for use via an HTTP request. ** ** Use CheckinMiniInfo_init() to cleanly initialize one to a known ** valid/empty default state. ** -** Memory for all non-const (char *) members is owned by the +** Memory for all non-const pointer members is owned by the ** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). +** Similarly, each instance owns any memory for its Blob members. */ struct CheckinMiniInfo { Manifest * pParent; /* parent checkin. Memory is owned by this object. */ char *zParentUuid; /* Full UUID of pParent */ @@ -168,11 +169,12 @@ CheckinMiniInfo_init(p); } /* ** Internal helper which returns an F-card perms string suitable for -** writing into a manifest. +** writing as-is into a manifest. If it's not empty, it includes a +** leading space to separate it from the F-card's hash field. */ static const char * mfile_permint_mstring(int perm){ switch(perm){ case PERM_EXE: return " x"; case PERM_LNK: return " l"; @@ -189,23 +191,21 @@ else if(strstr(zPerm,"x")) return PERM_EXE; else if(strstr(zPerm,"l")) return PERM_LNK; else return PERM_REG/*???*/; } -static const char * mfile_perm_mstring(const ManifestFile * p){ - return mfile_permint_mstring(manifest_file_mperm(p)); -} - /* ** Internal helper for checkin_mini() and friends. Appends an F-card ** for p to pOut. */ -static void checkin_mini_append_fcard(Blob *pOut, const ManifestFile *p){ +static void checkin_mini_append_fcard(Blob *pOut, + const ManifestFile *p){ if(p->zUuid){ assert(*p->zUuid); blob_appendf(pOut, "F %F %s%s", p->zName, - p->zUuid, mfile_perm_mstring(p)); + p->zUuid, + mfile_permint_mstring(manifest_file_mperm(p))); if(p->zPrior){ assert(*p->zPrior); blob_appendf(pOut, " %F\n", p->zPrior); }else{ blob_append(pOut, "\n", 1); @@ -743,11 +743,11 @@ ** Example: ** ** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c ** */ -void test_ci_mini_cmd(){ +void test_ci_mini_cmd(void){ CheckinMiniInfo cimi; /* checkin state */ int newRid = 0; /* RID of new version */ const char * zFilename; /* argv[2] */ const char * zComment; /* -m comment */ const char * zCommentFile; /* -M FILE */ @@ -898,129 +898,28 @@ fossil_free(zGlobs); } return glob_match(pGlobs, zFilename); } -/* -** Emits a script tag which defines window.fossil.fetch(), which works -** similarly (not identically) to the not-quite-ubiquitous global -** fetch(). -** -** JS usages: -** -** fossilFetch( URI, onLoadCallback ); -** -** fossilFetch( URI, optionsObject ); -** -** Noting that URI must be relative to the top of the repository and -** must not start with a slash (if it does, it is stripped). It gets -** %R/ prepended to it. -** -** The optionsObject may be an onload callback or an object with any -** of these properties: -** -** - onload: callback(responseData) (default = output response to -** console). -** -** - onerror: callback(XHR onload event) (default = console output) -** -** - method: 'POST' | 'GET' (default = 'GET') -** -** - payload: anything acceptable by XHR2.send(ARG) (DOMString, -** Document, FormData, Blob, File, ArrayBuffer), or a plain object -** or array, either of which gets JSON.stringify()'d. If set then -** the method is automatically set to 'POST'. If an object/array is -** converted to JSON, the content-type is set to 'application/json'. -** By default XHR2 will set the content type based on the payload -** type. -** -** - contentType: Optional request content type when POSTing. Ignored -** if the method is not 'POST'. -** -** - responseType: optional string. One of ("text", "arraybuffer", -** "blob", or "document") (as specified by XHR2). Default = "text". -** -** - urlParams: string|object. If a string, it is assumed to be a -** URI-encoded list of params in the form "key1=val1&key2=val2...", -** with NO leading '?'. If it is an object, all of its properties -** get converted to that form. Either way, the parameters get -** appended to the URL. -** -*/ -void fileedit_emit_script_fetch(){ - style_emit_script_tag(0); - CX("fossil.fetch = function(path,opt){\n"); - CX(" if('/'===path[0]) path = path.substr(1);\n"); - CX(" if(!opt){\n"); - CX(" opt = {onload:(r)=>console.debug('response:',r)};\n"); - CX(" }else if('function'===typeof opt){\n"); - CX(" opt={onload:opt,\n"); - CX(" onerror:(e)=>console.error('ajax error:',e)};\n"); - CX(" }\n"); - CX(" let payload = opt.payload;\n"); - CX(" if(payload){\n"); - CX(" opt.method = 'POST';\n"); - CX(" if(!(payload instanceof FormData)\n"); - CX(" && !(payload instanceof Document)\n"); - CX(" && !(payload instanceof Blob)\n"); - CX(" && !(payload instanceof File)\n"); - CX(" && !(payload instanceof ArrayBuffer)){\n"); - CX(" if('object'===typeof payload || payload instanceof Array){\n"); - CX(" payload = JSON.stringify(payload);\n"); - CX(" opt.contentType = 'application/json';\n"); - CX(" }\n"); - CX(" }\n"); - CX(" }\n"); - CX(" const url=['%R/'+path], x=new XMLHttpRequest();\n"); - CX(" if(opt.urlParams){\n"); - CX(" url.push('?');\n"); - CX(" if('string'===typeof opt.urlParams){\n"); - CX(" url.push(opt.urlParams);\n"); - CX(" }else{/*assume object*/\n"); - CX(" let k, i = 0;\n"); - CX(" for( k in opt.urlParams ){\n"); - CX(" if(i++) url.push('&');\n"); - CX(" url.push(k,'=',encodeURIComponent(opt.urlParams[k]));\n"); - CX(" }\n"); - CX(" }\n"); - CX(" }\n"); - CX(" if('POST'===opt.method && 'string'===typeof opt.contentType){\n"); - CX(" x.setRequestHeader('Content-Type',opt.contentType);\n"); - CX(" }\n"); - CX(" x.open(opt.method||'GET', url.join(''), true);\n"); - CX(" x.responseType=opt.responseType||'text';\n"); - CX(" if(opt.onload){\n"); - CX(" x.onload = function(e){\n"); - CX(" if(200!==this.status){\n"); - CX(" if(opt.onerror) opt.onerror(e);\n"); - CX(" return;\n"); - CX(" }\n"); - CX(" opt.onload(this.response);\n"); - CX(" }\n"); - CX(" }\n"); - CX(" if(payload) x.send(payload);\n"); - CX(" else x.send();\n"); - CX("};\n"); - style_emit_script_tag(1); -}; - enum fileedit_render_preview_flags { FE_PREVIEW_LINE_NUMBERS = 1 }; enum fileedit_render_modes { +/* GUESS must be 0. All others have unspecified values. */ FE_RENDER_GUESS = 0, FE_RENDER_PLAIN_TEXT, -FE_RENDER_HTML, +FE_RENDER_HTML_IFRAME, +FE_RENDER_HTML_INLINE, FE_RENDER_WIKI }; static int fileedit_render_mode_for_mimetype(const char * zMimetype){ int rc = FE_RENDER_PLAIN_TEXT; if( zMimetype ){ if( fossil_strcmp(zMimetype, "text/html")==0 ){ - rc = FE_RENDER_HTML; + rc = FE_RENDER_HTML_IFRAME; }else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 || fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ rc = FE_RENDER_WIKI; } } @@ -1037,23 +936,25 @@ const char * zMime; zMime = mimetype_from_name(zFilename); if(FE_RENDER_GUESS==renderMode){ renderMode = fileedit_render_mode_for_mimetype(zMime); } - CX("
    "); - CX("
    Preview
    "); switch(renderMode){ - case FE_RENDER_HTML:{ + case FE_RENDER_HTML_IFRAME:{ char * z64 = encode64(blob_str(pContent), blob_size(pContent)); CX("", nIframeHeightEm ? nIframeHeightEm : 40, z64); break; + } + case FE_RENDER_HTML_INLINE:{ + CX("%b",pContent); + break; } case FE_RENDER_WIKI: wiki_render_by_mimetype(pContent, zMime); break; default:{ @@ -1068,11 +969,10 @@ CX("
    %h
    ", zExt+1, zContent); } break; } } - CX("
    \n"); } /* ** Renders diffs for the /fileedit page. pContent is the ** locally-edited content. frid is the RID of the file's blob entry @@ -1087,19 +987,15 @@ Blob out = empty_blob; u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR | (isSbs ? DIFF_SIDEBYSIDE : DIFF_LINENO); content_get(frid, &orig); text_diff(&orig, pContent, &out, 0, diffFlags); - CX("
    "); - CX("
    Diff [%S] → Local Edits
    ", - zManifestUuid); - if(isSbs){ + if(isSbs || blob_size(&out)==0){ CX("%b",&out); }else{ CX("
    %b
    ",&out); } - CX("
    \n"); blob_reset(&orig); blob_reset(&out); } /* @@ -1122,468 +1018,990 @@ } } db_finalize(&stmt); return zFileUuid; } + +/* +** Helper for /fileedit_xyz routes. Clears the CGI content buffer, +** sets an error status code, and queues up a JSON response in the +** form of an object: +** +** {error: formatted message} +** +** After calling this, the caller should immediately return. + */ +static void fileedit_ajax_error(int httpCode, const char * zFmt, ...){ + Blob msg = empty_blob; + Blob content = empty_blob; + va_list vargs; + va_start(vargs,zFmt); + blob_vappendf(&msg, zFmt, vargs); + va_end(vargs); + blob_appendf(&content,"{\"error\":\"%j\"}", blob_str(&msg)); + blob_reset(&msg); + cgi_set_content(&content); + cgi_set_status(httpCode, "Error"); + cgi_set_content_type("application/json"); +} + +/* +** Performs bootstrapping common to the /fileedit_xyz AJAX routes. +** Returns 0 if bootstrapping fails (wrong permissions), in which +** case it has reported the error and the route should immediately +** return. Returns true on success. +*/ +static int fileedit_ajax_boostrap(void){ + login_check_credentials(); + if( !g.perm.Write ){ + fileedit_ajax_error(403,"Write permissions required."); + return 0; + } + + return 1; +} +/* +** Returns true if the current user is allowed to edit the given +** filename, as determined by fileedit_is_editable(), else false, +** in which case it queues up an error response and the caller +** must return immediately. +*/ +static int fileedit_ajax_check_filename(const char * zFilename){ + if(0==fileedit_is_editable(zFilename)){ + fileedit_ajax_error(403, "File is disallowed by the " + "fileedit-glob setting."); + return 0; + } + return 1; +} + + +/* +** If zFn is not NULL, it is assigned the value of the first one of +** the "filename" or "fn" CGI parameters which is set. +** +** If zCi is not NULL, it is assigned the value of the first one of +** the "checkin" or "ci" CGI parameters which is set. +** +** If a parameter is not NULL, it will be assigned NULL if the +** corresponding parameter is not set. +** +** Returns the number of non-NULL values it assigns to arguments. Thus +** if passed (&x, NULL), it returns 1 if it assigns non-NULL to *x and +** 0 if it assigns NULL to *x. +*/ +static int fileedit_get_fnci_args( const char **zFn, const char **zCi ){ + int rc = 0; + if(zCi!=0){ + *zCi = PD("checkin",P("ci")); + if( *zCi ) ++rc; + } + if(zFn!=0){ + *zFn = PD("filename",P("fn")); + if (*zFn) ++rc; + } + return rc; +} + +/* +** Passed the values of the "checkin" and "filename" request +** properties, this function verifies that they are valid and +** populates: +** +** - *zRevUuid = the fully-expanded value of zRev (owned by the +** caller). zRevUuid may be NULL. +** +** - *vid = the RID of zRevUuid. May not be NULL. +** +** - *frid = the RID of zFilename's blob content. May not be NULL +** unless zFilename is also NULL. If BOTH of zFilename and frid are +** NULL then no confirmation is done on the filename argument - only +** zRev is checked. +** +** Returns 0 if the given file is not in the given checkin or if +** fileedit_ajax_check_filename() fails, else returns true. If it +** returns false, it queues up an error response and the caller must +** return immediately. +*/ +static int fileedit_ajax_setup_filerev(const char * zRev, + char ** zRevUuid, + int * vid, + const char * zFilename, + int * frid){ + char * zFileUuid; /* file UUID */ + const int checkFile = zFilename!=0 || frid!=0; + + if(checkFile && !fileedit_ajax_check_filename(zFilename)){ + return 0; + } + *vid = symbolic_name_to_rid(zRev, "ci"); + if(0==*vid){ + fileedit_ajax_error(404,"Cannot resolve name as a checkin: %s", + zRev); + return 0; + }else if(*vid<0){ + fileedit_ajax_error(400,"Checkin name is ambiguous: %s", + zRev); + return 0; + } + if(checkFile){ + zFileUuid = fileedit_file_uuid(zFilename, *vid, 0); + if(zFileUuid==0){ + fileedit_ajax_error(404,"Checkin does not contain file."); + return 0; + } + } + if(zRevUuid!=0){ + *zRevUuid = rid_to_uuid(*vid); + } + if(checkFile){ + assert(zFileUuid!=0); + if(frid!=0){ + *frid = fast_uuid_to_rid(zFileUuid); + } + fossil_free(zFileUuid); + } + return 1; +} + +/* +** AJAX route /fileedit?ajax=content +** +** Query parameters: +** +** filename=FILENAME +** checkin=CHECKIN_NAME +** +** User must have Write access to use this page. +** +** Responds with the raw content of the given page. On error it +** produces a JSON response as documented for fileedit_ajax_error(). +*/ +static void fileedit_ajax_content(void){ + const char * zFilename = 0; + const char * zRev = 0; + int vid, frid; + Blob content = empty_blob; + const char * zMime; + + fileedit_get_fnci_args( &zFilename, &zRev ); + if(!fileedit_ajax_boostrap() + || !fileedit_ajax_setup_filerev(zRev, 0, &vid, + zFilename, &frid)){ + return; + } + zMime = mimetype_from_name(zFilename); + content_get(frid, &content); + if(0==zMime){ + if(looks_like_binary(&content)){ + zMime = "application/octet-stream"; + }else{ + zMime = "text/plain"; + } + } + cgi_set_content_type(zMime); + cgi_set_content(&content); +} + +/* +** AJAX route /fileedit?ajax=preview +** +** Required query parameters: +** +** filename=FILENAME +** content=text +** +** Optional query parameters: +** +** render_mode=integer (FE_RENDER_xxx) (default=FE_RENDER_GUESS) +** +** ln=0 or 1 to disable/enable line number mode in +** FE_RENDER_PLAIN_TEXT mode. +** +** iframe_height=integer (default=40) Height, in EMs of HTML preview +** iframe. +** +** User must have Write access to use this page. +** +** Responds with the HTML content of the preview. On error it produces +** a JSON response as documented for fileedit_ajax_error(). +*/ +static void fileedit_ajax_preview(void){ + const char * zFilename = 0; + const char * zContent = P("content"); + int renderMode = atoi(PD("render_mode","0")); + int ln = atoi(PD("ln","0")); + int iframeHeight = atoi(PD("iframe_height","40")); + Blob content = empty_blob; + + fileedit_get_fnci_args( &zFilename, 0 ); + if(!fileedit_ajax_boostrap() + || !fileedit_ajax_check_filename(zFilename)){ + return; + } + cgi_set_content_type("text/html"); + blob_init(&content, zContent, -1); + fileedit_render_preview(&content, zFilename, + ln ? FE_PREVIEW_LINE_NUMBERS : 0, + renderMode, iframeHeight); +} + +/* +** AJAX route /fileedit?ajax=diff +** +** Required query parameters: +** +** filename=FILENAME +** content=text +** checkin=checkin version +** +** Optional parameters: +** +** sbs=integer (1=side-by-side or 0=unified, default=0) +** +** User must have Write access to use this page. +** +** Responds with the HTML content of the diff. On error it produces a +** JSON response as documented for fileedit_ajax_error(). +*/ +static void fileedit_ajax_diff(void){ + /* + ** Reminder: we only need the filename to perform valdiation + ** against fileedit_is_editable(), else this route could be + ** abused to get diffs against content disallowed by the + ** whitelist. + */ + const char * zFilename = 0; + const char * zRev = 0; + const char * zContent = P("content"); + char * zRevUuid = 0; + int isSbs = atoi(PD("sbs","0")); + int vid, frid; + Blob content = empty_blob; + + fileedit_get_fnci_args( &zFilename, &zRev ); + if(!fileedit_ajax_boostrap() + || !fileedit_ajax_setup_filerev(zRev, &zRevUuid, &vid, + zFilename, &frid)){ + return; + } + if(!zContent){ + zContent = ""; + } + cgi_set_content_type("text/html"); + blob_init(&content, zContent, -1); + fileedit_render_diff(&content, frid, zRevUuid, isSbs); + fossil_free(zRevUuid); + blob_reset(&content); +} + +/* +** Sets up and validates most, but not all, of p's checkin-related +** state from the CGI environment. Returns 0 on success or a suggested +** HTTP result code on error, in which case a message will have been +** written to pErr. +** +** It always fails if it cannot completely resolve the 'file' and 'r' +** parameters, including verifying that the refer to a real +** file/version combination and editable by the current user. All +** others are optional (at this level, anyway, but upstream code might +** require them). +** +** Intended to be used only by /filepage and /filepage_commit. +*/ +static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr){ + char * zFileUuid = 0; /* UUID of file content */ + const char * zFlag; /* generic flag */ + int rc = 0, vid = 0, frid = 0; /* result code, checkin/file rids */ + +#define fail(EXPR) blob_appendf EXPR; goto end_fail + zFlag = PD("filename",P("fn")); + if(zFlag==0 || !*zFlag){ + rc = 400; + fail((pErr,"Missing required 'filename' parameter.")); + } + p->zFilename = mprintf("%s",zFlag); + + if(0==fileedit_is_editable(p->zFilename)){ + rc = 403; + fail((pErr,"Filename [%h] is disallowed " + "by the [fileedit-glob] repository " + "setting.", + p->zFilename)); + } + + zFlag = PD("checkin",P("ci")); + if(!zFlag){ + rc = 400; + fail((pErr,"Missing required 'checkin' parameter.")); + } + vid = symbolic_name_to_rid(zFlag, "ci"); + if(0==vid){ + rc = 404; + fail((pErr,"Could not resolve checkin version.")); + }else if(vid<0){ + rc = 400; + fail((pErr,"Checkin name is ambiguous.")); + } + p->zParentUuid = rid_to_uuid(vid)/*fully expand it*/; + + zFileUuid = fileedit_file_uuid(p->zFilename, vid, &p->filePerm); + if(!zFileUuid){ + rc = 404; + fail((pErr,"Checkin [%S] does not contain file: " + "[%h]", p->zParentUuid, p->zFilename)); + }else if(PERM_LNK==p->filePerm){ + rc = 400; + fail((pErr,"Editing symlinks is not permitted.")); + } + + /* Find the repo-side file entry or fail... */ + frid = fast_uuid_to_rid(zFileUuid); + assert(frid); + + /* Read file content from submit request or repo... */ + zFlag = P("content"); + if(zFlag==0){ + content_get(frid, &p->fileContent); + }else{ + blob_init(&p->fileContent,zFlag,-1); + } + if(looks_like_binary(&p->fileContent)){ + rc = 400; + fail((pErr,"File appears to be binary. Cannot edit: " + "[%h]",p->zFilename)); + } + + zFlag = PT("comment"); + if(zFlag!=0 && *zFlag!=0){ + blob_append(&p->comment, zFlag, -1); + } + zFlag = P("comment_mimetype"); + if(zFlag){ + p->zCommentMimetype = mprintf("%s",zFlag); + zFlag = 0; + } +#define p_int(K) atoi(PD(K,"0")) + if(p_int("dry_run")!=0){ + p->flags |= CIMINI_DRY_RUN; + } + if(p_int("allow_fork")!=0){ + p->flags |= CIMINI_ALLOW_FORK; + } + if(p_int("allow_older")!=0){ + p->flags |= CIMINI_ALLOW_OLDER; + } + if(p_int("exec_bit")!=0){ + p->filePerm = PERM_EXE; + } + if(p_int("allow_merge_conflict")!=0){ + p->flags |= CIMINI_ALLOW_MERGE_MARKER; + } + if(p_int("prefer_delta")!=0){ + p->flags |= CIMINI_PREFER_DELTA; + } + + /* EOL conversion policy... */ + switch(p_int("eol")){ + case 1: p->flags |= CIMINI_CONVERT_EOL_UNIX; break; + case 2: p->flags |= CIMINI_CONVERT_EOL_WINDOWS; break; + default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break; + } +#undef p_int + /* + ** TODO?: date-override date selection field. Maybe use + ** an input[type=datetime-local]. + */ + p->zUser = mprintf("%s",g.zLogin); + return 0; +end_fail: +#undef fail + fossil_free(zFileUuid); + return rc ? rc : 500; +} + +/* +** AJAX route /fileedit?ajax=filelist +** +** Fetches a JSON-format list of leaves and/or filenames for use in +** creating a file selection list in /fileedit. It has different modes +** of operation depending on its arguments: +** +** 'leaves': just fetch a list of open leaf versions, in this +** format: +** +** [ +** {checkin: UUID, branch: branchName, timestamp: string} +** ] +** +** The entries are ordered newest first. +** +** 'checkin=CHECKIN_NAME': fetch the current list of is-editable files +** for the current user and given checkin name: +** +** { +** checkin: UUID, +** editableFiles: [ filename1, ... filenameN ] // sorted by name +** } +** +** On error it produces a JSON response as documented for +** fileedit_ajax_error(). +*/ +static void fileedit_ajax_filelist(void){ + const char * zCi = PD("checkin",P("ci")); + Blob sql = empty_blob; + Stmt q = empty_Stmt; + int i = 0; + + if(!fileedit_ajax_boostrap()){ + return; + } + cgi_set_content_type("application/json"); + if(zCi!=0){ + char * zCiFull = 0; + int vid = 0; + if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, &vid, 0, 0)){ + /* Error already reported */ + return; + } + CX("{\"checkin\":\"%j\"," + "\"editableFiles\":[", zCiFull); + blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) " + "ORDER BY filename %s", + zCiFull, filename_collation()); + db_prepare_blob(&q, &sql); + while( SQLITE_ROW==db_step(&q) ){ + const char * zFilename = db_column_text(&q, 0); + if(fileedit_is_editable(zFilename)){ + if(i++){ + CX(","); + } + CX("\"%j\"", zFilename); + } + } + db_finalize(&q); + CX("]}"); + }else if(P("leaves")!=0){ + blob_append(&sql, timeline_query_for_tty(), -1); + blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf " + "WHERE NOT EXISTS(" + "SELECT 1 from tagxref WHERE tagid=%d AND " + "tagtype>0 AND rid=leaf.rid" + ")) " + "ORDER BY mtime DESC", TAG_CLOSED); + db_prepare_blob(&q, &sql); + CX("["); + while( SQLITE_ROW==db_step(&q) ){ + if(i++){ + CX(","); + } + CX("{"); + CX("\"checkin\":\"%j\",", db_column_text(&q, 1)); + CX("\"timestamp\":\"%j\",", db_column_text(&q, 2)); + CX("\"branch\":\"%j\"", db_column_text(&q, 7)); + CX("}"); + } + CX("]"); + db_finalize(&q); + }else{ + fileedit_ajax_error(500, "Unhandled URL argument."); + } +} + +/* +** AJAX route /fileedit?ajax=commit +** +** Required query parameters: +** +** filename=FILENAME +** checkin=Parent checkin UUID +** content=text +** comment=text +** +** Optional query parameters: +** +** comment_mimetype=text +** dry_run=int (1 or 0) +** +** +** User must have Write access to use this page. +** +** Responds with JSON: +** +** { +** uuid: newUUID, +** manifest: text of manifest, +** dryRun: bool +** } +** +** On error it produces a JSON response as documented for +** fileedit_ajax_error(). +*/ +static void fileedit_ajax_commit(void){ + Blob err = empty_blob; /* Error messages */ + Blob manifest = empty_blob; /* raw new manifest */ + CheckinMiniInfo cimi; /* checkin state */ + int rc; /* generic result code */ + int newVid = 0; /* new version's RID */ + char * zNewUuid = 0; /* newVid's UUID */ + + if(!fileedit_ajax_boostrap()){ + return; + } + db_begin_transaction(); + CheckinMiniInfo_init(&cimi); + rc = fileedit_setup_cimi_from_p(&cimi, &err); + if(0!=rc){ + fileedit_ajax_error(rc,"%b",&err); + goto end_cleanup; + } + if(blob_size(&cimi.comment)==0){ + fileedit_ajax_error(400,"Empty checkin comment is not permitted."); + goto end_cleanup; + } + cimi.pMfOut = &manifest; + checkin_mini(&cimi, &newVid, &err); + if(blob_size(&err)){ + fileedit_ajax_error(500,"%b",&err); + goto end_cleanup; + } + assert(newVid>0); + zNewUuid = rid_to_uuid(newVid); + cgi_set_content_type("application/json"); + CX("{"); + CX("\"uuid\":\"%j\",", zNewUuid); + CX("\"dryRun\": %s,", + (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false"); + CX("\"manifest\": \"%j\"", blob_str(&manifest)); + CX("}"); + db_end_transaction(0/*noting that dry-run mode will have already + ** set this to rollback mode. */); +end_cleanup: + fossil_free(zNewUuid); + blob_reset(&err); + blob_reset(&manifest); + CheckinMiniInfo_cleanup(&cimi); +} /* ** WEBPAGE: fileedit ** ** EXPERIMENTAL and subject to change and removal at any time. The goal ** is to allow online edits of files. ** ** Query parameters: ** -** file=FILENAME Repo-relative path to the file. -** r=VERSION Checkin version, using any unambiguous -** supported symbolic version name. +** filename=FILENAME Repo-relative path to the file. +** checkin=VERSION Checkin version, using any unambiguous +** supported symbolic version name. ** ** All other parameters are for internal use only, submitted via the ** form-submission process, and may change with any given revision of ** this code. */ -void fileedit_page(){ - enum submit_modes { - SUBMIT_NONE = 0, SUBMIT_SAVE, SUBMIT_PREVIEW, - SUBMIT_DIFF_SBS, SUBMIT_DIFF_UNIFIED, - SUBMIT_end /* sentinel for range validation */ - }; - const char * zFilename = PD("file",P("name")); - /* filename. We'll accept 'name' +void fileedit_page(void){ + const char * zFilename = 0; /* filename. We'll accept 'name' because that param is handled specially by the core. */ - const char * zRev = P("r"); /* checkin version */ - const char * zContent = P("content"); /* file content */ - const char * zComment = P("comment"); /* checkin comment */ + const char * zRev = 0; /* checkin version */ const char * zFileMime = 0; /* File mime type guess */ CheckinMiniInfo cimi; /* Checkin state */ - int submitMode = SUBMIT_NONE; /* See mapping below */ - int vid, newVid = 0; /* checkin rid */ - int frid = 0; /* File content rid */ - int previewLn = P("preview_ln")!=0; /* Line number mode */ int previewHtmlHeight = 0; /* iframe height (EMs) */ int previewRenderMode = FE_RENDER_GUESS; /* preview mode */ - char * zFileUuid = 0; /* File content UUID */ Blob err = empty_blob; /* Error report */ - Blob submitResult = empty_blob; /* Error report */ - const char * zFlagCheck = 0; /* Temp url flag holder */ Blob endScript = empty_blob; /* Script code to run at the end. This content will be combined into a single JS function call, thus each entry must end with a semicolon. */ Stmt stmt = empty_Stmt; - const int loadMode = 0; /* See next comment block */ - /* loadMode: How to populate the TEXTAREA: - ** - ** 0: HTML encode: despite my personal reservations regarding HTML - ** escaping, this seems to be the only reliable approach - ** until/unless we completely AJAXify this page. - ** - ** 1: JSON mode: JSON-izes the file content and injects it, via JS, - ** into the editor TEXTAREA. This works wonderfully until the input - ** file contains an raw + @ } /* ** All extra JS files to load. */ @@ -1259,10 +1259,11 @@ @ anonymous-adds = %s(find_anon_capabilities(zCap))
    } @ g.zRepositoryName = %h(g.zRepositoryName)
    @ load_average() = %f(load_average())
    @ cgi_csrf_safe(0) = %d(cgi_csrf_safe(0))
    + @ fossil_exe_id() = %h(fossil_exe_id())
    @
    P("HTTP_USER_AGENT"); cgi_print_all(showAll, 0); if( showAll && blob_size(&g.httpHeader)>0 ){ @
    @@ -1297,42 +1298,51 @@ #if INTERFACE # define webpage_assert(T) if(!(T)){webpage_assert_page(__FILE__,__LINE__,#T);} #endif /* -** Outputs a labeled checkbox element. zFieldName is the form element -** name. zLabel is the label for the checkbox. zValue is the optional -** value for the checkbox. zTip is an optional tooltip, which gets set -** as the "title" attribute of the outermost element. If isChecked is -** true, the checkbox gets the "checked" attribute set, else it is -** not. +** Outputs a labeled checkbox element. zWrapperId is an optional ID +** value for the containing element (see below). zFieldName is the +** form element name. zLabel is the label for the checkbox. zValue is +** the optional value for the checkbox. zTip is an optional tooltip, +** which gets set as the "title" attribute of the outermost +** element. If isChecked is true, the checkbox gets the "checked" +** attribute set, else it is not. ** ** Resulting structure: ** -**
    +** ** ** {{zLabel}} -**
    +** ** -** zFieldName, zLabel, and zValue are required. zTip is optional. +** zLabel, and zValue are required. zFieldName, zWrapperId, and zTip +** are may be NULL or empty. ** ** Be sure that the input-with-label CSS class is defined sensibly, in ** particular, having its display:inline-block is useful for alignment ** purposes. */ -void style_labeled_checkbox(const char *zFieldName, const char * zLabel, - const char * zValue, const char * zTip, - int isChecked){ - CX("
    ", - zFieldName, + if(zWrapperId && *zWrapperId){ + CX(" id='%s'",zWrapperId); + } + CX(">", zValue ? zValue : "", isChecked ? " checked" : ""); - CX("%h
    ", zLabel); + CX("%h", zLabel); } /* ** Outputs a SELECT list from a compile-time list of integers. ** The vargs must be a list of (const char *, int) pairs, terminated @@ -1347,41 +1357,50 @@ ** ** Note that the pairs are not in (int, const char *) order because ** there is no well-known integer value which we can definitively use ** as a list terminator. ** -** zFieldName is the value of the form element's name attribute. +** zWrapperId is an optional ID value for the containing element (see +** below). +** +** zFieldName is the value of the form element's name attribute. Note +** that fossil prefers underscores over '-' for separators in form +** element names. ** ** zLabel is an optional string to use as a "label" for the element ** (see below). ** ** zTooltip is an optional value for the SELECT's title attribute. ** ** The structure of the emitted HTML is: ** -**
    +** ** {{zLabel}} ** -**
    +** ** ** Example: ** -** style_select_list_int("my_field", "Grapes", +** style_select_list_int("my-grapes", "my_grapes", "Grapes", ** "Select the number of grapes", ** atoi(PD("my_field","0")), ** "", 1, "2", 2, "Three", 3, ** NULL); ** */ -void style_select_list_int(const char *zFieldName, const char * zLabel, +void style_select_list_int(const char * zWrapperId, + const char *zFieldName, const char * zLabel, const char * zToolTip, int selectedVal, ... ){ va_list vargs; va_start(vargs,selectedVal); - CX("
    "); if(zLabel && *zLabel){ CX("%h", zLabel); } @@ -1401,33 +1420,249 @@ CX("%d",v); } CX("\n"); } CX("\n"); + CX("\n"); + va_end(vargs); +} + +/* +** The C-string counterpart of style_select_list_int(), this variant +** differs only in that its variadic arguments are C-strings in pairs +** of (optionLabel, optionValue). If a given optionLabel is an empty +** string, the corresponding optionValue is used as its label. If any +** given value matches zSelectedVal, that option gets preselected. If +** no options match zSelectedVal then the first entry is selected by +** default. +** +** Any of (zWrapperId, zTooltip, zSelectedVal) may be NULL or empty. +** +** Example: +** +** style_select_list_str("my-grapes", "my_grapes", "Grapes", +** "Select the number of grapes", +** P("my_field"), +** "1", "One", "2", "Two", "", "3", +** NULL); +*/ +void style_select_list_str(const char * zWrapperId, + const char *zFieldName, const char * zLabel, + const char * zToolTip, char const * zSelectedVal, + ... ){ + va_list vargs; + + va_start(vargs,zSelectedVal); + if(!zSelectedVal){ + zSelectedVal = __FILE__/*some string we'll never match*/; + } + CX(""); if(zLabel && *zLabel){ - CX("
    \n"); + CX("%h", zLabel); + } + CX("\n"); + CX("\n"); va_end(vargs); } /* -** If passed 0, it emits a script opener tag with this session's -** nonce. If passed non-0 it emits a script closing tag. The very -** first time it is called, it emits some bootstrapping JS code -** immediately after the script opener. Specifically, it defines -** window.fossil if it's not already defined, and may set some -** properties on it. +** The first time this is called, it emits code to install and +** bootstrap the window.fossil object, using the built-in file +** fossil.bootstrap.js (not to be confused with bootstrap.js). +** +** Subsequent calls are no-ops. +** +** If passed a true value, it emits the contents directly to the page +** output, else it emits a script tag with a src=builtin/... to load +** the script. It always outputs a small pre-bootstrap element in its +** own script tag to initialize parts which need C-runtime-level +** information, before loading the main fossil.bootstrap.js either +** inline or via a +** +** zSrc is always assumed to be a repository-relative path without +** a leading slash, and has %R/ prepended to it. +** +** Meaning that no follow-up call to pass a non-0 first argument +** to close the tag. zSrc is ignored if the first argument is not +** 0. +** +*/ +void style_emit_script_tag(int isCloser, const char * zSrc){ + if(0==isCloser){ + if(zSrc!=0 && zSrc[0]!=0){ + CX("\n", zSrc); + }else{ + CX("\n"); } } + +/* +** Emits a script tag which uses content from a builtin script file. +** +** If asInline is true, it is emitted directly as an opening tag, the +** content of the zName builtin file, and a closing tag. +** +** If it is false, a script tag loading it via +** src=builtin/{{zName}}?cache=XYZ is emitted, where XYZ is a +** build-time-dependent cache-buster value. +*/ +void style_emit_script_builtin(int asInline, char const * zName){ + if(asInline){ + style_emit_script_tag(0,0); + CX("%s", builtin_text(zName)); + style_emit_script_tag(1,0); + }else{ + char * zFullName = mprintf("builtin/%s",zName); + const char * zHash = fossil_exe_id(); + CX("\n", + zFullName, zHash); + fossil_free(zFullName); + } +} + +/* +** The first time this is called it emits the JS code from the +** built-in file fossil.fossil.js. Subsequent calls are no-ops. +** +** If passed a true value, it emits the contents directly +** to the page output, else it emits a script tag with a +** src=builtin/... to load the script. +** +** Note that this code relies on that loaded via +** style_emit_script_fossil_bootstrap() but it does not call that +** routine. +*/ +void style_emit_script_fetch(int asInline){ + static int once = 0; + if(0==once++){ + style_emit_script_builtin(asInline, "fossil.fetch.js"); + } +} + +/* +** The first time this is called it emits the JS code from the +** built-in file fossil.dom.js. Subsequent calls are no-ops. +** +** If passed a true value, it emits the contents directly +** to the page output, else it emits a script tag with a +** src=builtin/... to load the script. +** +** Note that this code relies on that loaded via +** style_emit_script_fossil_bootstrap(), but it does not call that +** routine. +*/ +void style_emit_script_dom(int asInline){ + static int once = 0; + if(0==once++){ + style_emit_script_builtin(asInline, "fossil.dom.js"); + } +} + +/* +** The first time this is called, it calls style_emit_script_dom(), +** passing it the given asInline value, and emits the JS code from the +** built-in file fossil.tabs.js. Subsequent calls are no-ops. +** +** If passed a true value, it emits the contents directly +** to the page output, else it emits a script tag with a +** src=builtin/... to load the script. +*/ +void style_emit_script_tabs(int asInline){ + static int once = 0; + if(0==once++){ + style_emit_script_dom(asInline); + style_emit_script_builtin(asInline, "fossil.tabs.js"); + } +} + +/* +** The first time this is called it emits the JS code from the +** built-in file fossil.confirmer.js. Subsequent calls are no-ops. +** +** If passed a true value, it emits the contents directly +** to the page output, else it emits a script tag with a +** src=builtin/... to load the script. +*/ +void style_emit_script_confirmer(int asInline){ + static int once = 0; + if(0==once++){ + style_emit_script_builtin(asInline, "fossil.confirmer.js"); + } +} ADDED src/terminal.c Index: src/terminal.c ================================================================== --- /dev/null +++ src/terminal.c @@ -0,0 +1,130 @@ +/* +** Copyright (c) 2020 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code used to query terminal info +*/ + +#include "config.h" +#include "terminal.h" +#include +#ifdef _WIN32 +# include +#else +#include +#include +#include +#endif + + + +#if INTERFACE +/* +** Terminal size defined in terms of columns and lines. +*/ +struct TerminalSize { + unsigned int nColumns; /* Number of characters on a single line */ + unsigned int nLines; /* Number of lines */ +}; +#endif + + +/* Get the current terminal size by calling a system service. +** +** Return 1 on success. This sets the size parameters to the values retured by +** the system call, when such is supported; set the size to zero otherwise. +** Return 0 on the system service call failure. +** +** Under Linux/bash the size info is also available from env $LINES, $COLUMNS. +** Or it can be queried using tput `echo -e "lines\ncols"|tput -S`. +** Technically, this info could be cached, but then we'd need to handle +** SIGWINCH signal to requery the terminal on resize event. +*/ +int terminal_get_size(TerminalSize *t){ + memset(t, 0, sizeof(*t)); + +#if defined(TIOCGSIZE) + { + struct ttysize ts; + if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)!=-1 ){ + t->nColumns = ts.ts_cols; + t->nLines = ts.ts_lines; + return 1; + } + return 0; + } +#elif defined(TIOCGWINSZ) + { + struct winsize ws; + if( ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)!=-1 ){ + t->nColumns = ws.ws_col; + t->nLines = ws.ws_row; + return 1; + } + return 0; + } +#elif defined(_WIN32) + { + CONSOLE_SCREEN_BUFFER_INFO csbi; + if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) ){ + t->nColumns = csbi.srWindow.Right - csbi.srWindow.Left + 1; + t->nLines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + return 1; + } + return 0; + } +#else + return 1; +#endif +} + +/* +** Return the terminal's current width in columns when available, otherwise +** return the specified default value. +*/ +unsigned int terminal_get_width(unsigned int nDefault){ + TerminalSize ts; + if( terminal_get_size(&ts) ){ + return ts.nColumns; + } + return nDefault; +} + +/* +** Return the terminal's current height in lines when available, otherwise +** return the specified default value. +*/ +unsigned int terminal_get_height(unsigned int nDefault){ + TerminalSize ts; + if( terminal_get_size(&ts) ){ + return ts.nLines; + } + return nDefault; +} + +/* +** COMMAND: test-terminal-size +** +** Show the size of the terminal window from which the command is launched +** as two integers, the width in charaters and the height in lines. +** +** If the size cannot be determined, two zeros are shown. +*/ +void test_terminal_size_cmd(void){ + TerminalSize ts; + terminal_get_size(&ts); + fossil_print("%d %d\n", ts.nColumns, ts.nLines); +} Index: src/timeline.c ================================================================== --- src/timeline.c +++ src/timeline.c @@ -1312,11 +1312,11 @@ ){ if( zChng==0 || zChng[0]==0 ) return; blob_append_sql(pSql," AND event.objid IN (" "SELECT mlink.mid FROM mlink, filename" " WHERE mlink.fnid=filename.fnid AND %s)", - glob_expr("filename.name", zChng)); + glob_expr("filename.name", mprintf("\"%s\"", zChng))); } static void addFileGlobDescription( const char *zChng, /* The filename GLOB list */ Blob *pDescription /* Result description */ ){ @@ -1742,10 +1742,11 @@ || (bisectLocal && !g.perm.Setup) ){ login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); return; } + etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA, 0); cookie_read_parameter("y","y"); zType = P("y"); if( zType==0 ){ zType = g.perm.Read ? "ci" : "all"; cgi_set_parameter("y", zType); Index: src/unversioned.c ================================================================== --- src/unversioned.c +++ src/unversioned.c @@ -524,10 +524,11 @@ int showDel = 0; char zSzName[100]; login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + etag_check(ETAG_DATA,0); style_header("Unversioned Files"); if( !db_table_exists("repository","unversioned") ){ @ No unversioned files on this server style_footer(); return; @@ -636,10 +637,11 @@ Blob json; login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } cgi_set_content_type("text/json"); + etag_check(ETAG_DATA,0); if( !db_table_exists("repository","unversioned") ){ blob_init(&json, "[]", -1); cgi_set_content(&json); return; } Index: src/url.c ================================================================== --- src/url.c +++ src/url.c @@ -377,13 +377,15 @@ */ void url_proxy_options(void){ zProxyOpt = find_option("proxy", 0, 1); if( find_option("nosync",0,0) ) g.fNoSync = 1; if( find_option("ipv4",0,0) ) g.fIPv4 = 1; +#ifdef FOSSIL_ENABLE_SSL if( find_option("accept-any-cert",0,0) ){ ssl_disable_cert_verification(); } +#endif /* FOSSIL_ENABLE_SSL */ } /* ** If the "proxy" setting is defined, then change the URL settings ** (initialized by a prior call to url_parse()) so that the HTTP ADDED test/subdir with spaces/filename with spaces.txt Index: test/subdir with spaces/filename with spaces.txt ================================================================== --- /dev/null +++ test/subdir with spaces/filename with spaces.txt @@ -0,0 +1,2 @@ +This file has a name that contains spaces. It is used to help verify +that fossil can handle filenames that contain spaces. ADDED tools/fossil-diff-log Index: tools/fossil-diff-log ================================================================== --- /dev/null +++ tools/fossil-diff-log @@ -0,0 +1,72 @@ +#!/usr/bin/env perl +# Fossil emulation of the "git log --patch / -p" feature: emit a stream +# of diffs from one version to the next for each file named on the +# command line. +# +# LIMITATIONS: It does not assume "all files" if you give no args, and +# it cannot take a directory to mean "all files under this parent". +# +# PREREQUISITES: This script needs several CPAN modules to run properly. +# There are multiple methods to install them: +# +# sudo dnf install perl-File-Which perl-IO-Interactive +# sudo apt install libfile-which-perl libio-interactive-perl +# sudo cpanm File::Which IO::Interactive +# ...etc... + +use strict; +use warnings; + +use Carp; +use File::Which; +use IO::Interactive qw(is_interactive); + +die "usage: $0 \n\n" unless @ARGV; + +my $out; +if (is_interactive()) { + my $pager = $ENV{PAGER} || which('less') || which('more'); + open $out, '|-', $pager or croak "Cannot pipe to $pager: $!"; +} +else { + $out = *STDOUT; +} + +open my $bcmd, '-|', 'fossil branch current' + or die "Cannot get branch: $!\n"; +my $cbranch = <$bcmd>; +chomp $cbranch; +close $bcmd; + +for my $file (@ARGV) { + my $lastckid; + open my $finfo, '-|', "fossil finfo --brief --limit 0 '$file'" + or die "Failed to get file info: $!\n"; + my @filines = <$finfo>; + close $finfo; + + for my $line (@filines) { + my ($currckid, $date, $user, $branch, @cwords) = split ' ', $line; + next unless $branch eq $cbranch; + if (defined $lastckid and defined $branch) { + my $comment = join ' ', @cwords; + open my $diff, '-|', 'fossil', 'diff', $file, + '--from', $currckid, + '--to', $lastckid, + or die "Failed to diff $currckid -> $lastckid: $!\n"; + my @dl = <$diff>; + close $diff; + my $patch = join '', @dl; + + print $out <<"OUT" +Checkin ID $currckid to $branch by $user on $date +Comment: $comment + +$patch + +OUT + } + + $lastckid = $currckid; + } +} Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -28,13 +28,13 @@ SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_GET_TABLE -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_GET_TABLE -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_USE_ALLOCA -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_INTROSPECTION_PRAGMAS -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen -SRC = add_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c +SRC = add_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c piechart_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c webmail_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c -OBJ = $(OBJDIR)\add$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O +OBJ = $(OBJDIR)\add$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\webmail$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O RC=$(DMDIR)\bin\rcc RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ @@ -49,11 +49,11 @@ $(OBJDIR)\fossil.res: $B\win\fossil.rc $(RC) $(RCFLAGS) -o$@ $** $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res - +echo add alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ + +echo add alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi checkin checkout clearsign clone comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path piechart pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile webmail wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@ +echo fossil >> $@ +echo fossil >> $@ +echo $(LIBS) >> $@ +echo. >> $@ +echo fossil >> $@ @@ -836,10 +836,16 @@ $(OBJDIR)\tar$O : tar_.c tar.h $(TCC) -o$@ -c tar_.c tar_.c : $(SRCDIR)\tar.c +translate$E $** > $@ + +$(OBJDIR)\terminal$O : terminal_.c terminal.h + $(TCC) -o$@ -c terminal_.c + +terminal_.c : $(SRCDIR)\terminal.c + +translate$E $** > $@ $(OBJDIR)\th_main$O : th_main_.c th_main.h $(TCC) -o$@ -c th_main_.c th_main_.c : $(SRCDIR)\th_main.c @@ -976,7 +982,7 @@ zip_.c : $(SRCDIR)\zip.c +translate$E $** > $@ headers: makeheaders$E page_index.h builtin_data.h default_css.h VERSION.h - +makeheaders$E add_.c:add.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h + +makeheaders$E add_.c:add.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h piechart_.c:piechart.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h unversioned_.c:unversioned.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h webmail_.c:webmail.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h @copy /Y nul: headers Index: win/Makefile.mingw ================================================================== --- win/Makefile.mingw +++ win/Makefile.mingw @@ -554,10 +554,11 @@ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ + $(SRCDIR)/terminal.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ @@ -640,10 +641,16 @@ $(SRCDIR)/accordion.js \ $(SRCDIR)/ci_edit.js \ $(SRCDIR)/copybtn.js \ $(SRCDIR)/diff.tcl \ $(SRCDIR)/forum.js \ + $(SRCDIR)/fossil.bootstrap.js \ + $(SRCDIR)/fossil.confirmer.js \ + $(SRCDIR)/fossil.dom.js \ + $(SRCDIR)/fossil.fetch.js \ + $(SRCDIR)/fossil.page.fileedit.js \ + $(SRCDIR)/fossil.tabs.js \ $(SRCDIR)/graph.js \ $(SRCDIR)/href.js \ $(SRCDIR)/login.js \ $(SRCDIR)/markdown.md \ $(SRCDIR)/menu.js \ @@ -788,10 +795,11 @@ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ + $(OBJDIR)/terminal_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ @@ -931,10 +939,11 @@ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ + $(OBJDIR)/terminal.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ @@ -1294,10 +1303,11 @@ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ + $(OBJDIR)/terminal_.c:$(OBJDIR)/terminal.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ @@ -2269,10 +2279,18 @@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers + +$(OBJDIR)/terminal_.c: $(SRCDIR)/terminal.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/terminal.c >$@ + +$(OBJDIR)/terminal.o: $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/terminal.o -c $(OBJDIR)/terminal_.c + +$(OBJDIR)/terminal.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h Index: win/Makefile.mingw.mistachkin ================================================================== --- win/Makefile.mingw.mistachkin +++ win/Makefile.mingw.mistachkin @@ -553,10 +553,11 @@ $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/tar.c \ + $(SRCDIR)/terminal.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ @@ -786,10 +787,11 @@ $(OBJDIR)/statrep_.c \ $(OBJDIR)/style_.c \ $(OBJDIR)/sync_.c \ $(OBJDIR)/tag_.c \ $(OBJDIR)/tar_.c \ + $(OBJDIR)/terminal_.c \ $(OBJDIR)/th_main_.c \ $(OBJDIR)/timeline_.c \ $(OBJDIR)/tkt_.c \ $(OBJDIR)/tktsetup_.c \ $(OBJDIR)/undo_.c \ @@ -928,10 +930,11 @@ $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ $(OBJDIR)/tar.o \ + $(OBJDIR)/terminal.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ @@ -1290,10 +1293,11 @@ $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ + $(OBJDIR)/terminal_.c:$(OBJDIR)/terminal.h \ $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ @@ -2257,10 +2261,18 @@ $(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c $(OBJDIR)/tar.h: $(OBJDIR)/headers + +$(OBJDIR)/terminal_.c: $(SRCDIR)/terminal.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/terminal.c >$@ + +$(OBJDIR)/terminal.o: $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/terminal.o -c $(OBJDIR)/terminal_.c + +$(OBJDIR)/terminal.h: $(OBJDIR)/headers $(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/th_main.c >$@ $(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -462,10 +462,11 @@ statrep_.c \ style_.c \ sync_.c \ tag_.c \ tar_.c \ + terminal_.c \ th_main_.c \ timeline_.c \ tkt_.c \ tktsetup_.c \ undo_.c \ @@ -547,10 +548,16 @@ $(SRCDIR)\accordion.js \ $(SRCDIR)\ci_edit.js \ $(SRCDIR)\copybtn.js \ $(SRCDIR)\diff.tcl \ $(SRCDIR)\forum.js \ + $(SRCDIR)\fossil.bootstrap.js \ + $(SRCDIR)\fossil.confirmer.js \ + $(SRCDIR)\fossil.dom.js \ + $(SRCDIR)\fossil.fetch.js \ + $(SRCDIR)\fossil.page.fileedit.js \ + $(SRCDIR)\fossil.tabs.js \ $(SRCDIR)\graph.js \ $(SRCDIR)\href.js \ $(SRCDIR)\login.js \ $(SRCDIR)\markdown.md \ $(SRCDIR)\menu.js \ @@ -697,10 +704,11 @@ $(OX)\statrep$O \ $(OX)\style$O \ $(OX)\sync$O \ $(OX)\tag$O \ $(OX)\tar$O \ + $(OX)\terminal$O \ $(OX)\th$O \ $(OX)\th_lang$O \ $(OX)\th_main$O \ $(OX)\th_tcl$O \ $(OX)\timeline$O \ @@ -902,10 +910,11 @@ echo $(OX)\statrep.obj >> $@ echo $(OX)\style.obj >> $@ echo $(OX)\sync.obj >> $@ echo $(OX)\tag.obj >> $@ echo $(OX)\tar.obj >> $@ + echo $(OX)\terminal.obj >> $@ echo $(OX)\th.obj >> $@ echo $(OX)\th_lang.obj >> $@ echo $(OX)\th_main.obj >> $@ echo $(OX)\th_tcl.obj >> $@ echo $(OX)\timeline.obj >> $@ @@ -1753,10 +1762,16 @@ $(OX)\tar$O : tar_.c tar.h $(TCC) /Fo$@ -c tar_.c tar_.c : $(SRCDIR)\tar.c translate$E $** > $@ + +$(OX)\terminal$O : terminal_.c terminal.h + $(TCC) /Fo$@ -c terminal_.c + +terminal_.c : $(SRCDIR)\terminal.c + translate$E $** > $@ $(OX)\th_main$O : th_main_.c th_main.h $(TCC) /Fo$@ -c th_main_.c th_main_.c : $(SRCDIR)\th_main.c @@ -2014,10 +2029,11 @@ statrep_.c:statrep.h \ style_.c:style.h \ sync_.c:sync.h \ tag_.c:tag.h \ tar_.c:tar.h \ + terminal_.c:terminal.h \ th_main_.c:th_main.h \ timeline_.c:timeline.h \ tkt_.c:tkt.h \ tktsetup_.c:tktsetup.h \ undo_.c:undo.h \ Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -55,10 +55,12 @@ * Security: Fossil now puts the Content-Security-Policy in the HTTP reply header, in addition to also leaving it in the HTML <head> section, so that it is always available, even if a custom skin overrides the HTML <head> and omits the CSP in the process. + * Output of the [/help?cmd=diff|fossil diff -y] command automatically + adjusts according to the terminal width. * The Content-Security-Policy is now set using the [/help?cmd=default-csp|default-csp setting]. * Merge conflicts caused via the [/help?cmd=merge|merge] and [/help?cmd=update|update] commands no longer leave temporary files behind unless the new --keep-merge-files flag