Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -477,10 +477,22 @@ void blob_resize(Blob *pBlob, unsigned int newSize){ pBlob->xRealloc(pBlob, newSize+1); pBlob->nUsed = newSize; pBlob->aData[newSize] = 0; } + +/* +** Ensures that the given blob has at least the given amount of memory +** allocated to it. Does not modify pBlob->nUsed nor will it reduce +** the currently-allocated amount of memory. +*/ +void blob_reserve(Blob *pBlob, unsigned int newSize){ + if(newSize>pBlob->nUsed){ + pBlob->xRealloc(pBlob, newSize); + pBlob->aData[newSize] = 0; + } +} /* ** Make sure a blob is nul-terminated and is not a pointer to unmanaged ** space. Return a pointer to the data. */ @@ -1167,11 +1179,10 @@ blob_reset(&b3); } fossil_print("ok\n"); } -#if defined(_WIN32) || defined(__CYGWIN__) /* ** Convert every \n character in the given blob into \r\n. */ void blob_add_cr(Blob *p){ char *z = p->aData; @@ -1191,11 +1202,10 @@ if( (z[--j] = z[--i]) =='\n' ){ z[--j] = '\r'; } } } -#endif /* ** Remove every \r character from the given blob, replacing each one with ** a \n character if it was not already part of a \r\n pair. */ Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -64,10 +64,21 @@ #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) +/* +** Shortcut for the cgi_printf() routine. Instead of using the +** +** @ ... +** +** notation provided by the translate.c utility, you can also +** optionally use: +** +** CX(...) +*/ +#define CX cgi_printf /* ** Destinations for output text. */ #define CGI_HEADER 0 Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -1399,10 +1399,28 @@ g.aCommitFile[jj] = 0; bag_clear(&toCommit); } return result; } + +/* +** Returns true if the checkin identified by the first parameter is +** older than the given (valid) date/time string, else returns false. +** Also returns true if rid does not refer to a checkin, but it is not +** intended to be used for that case. +*/ +int checkin_is_younger( + int rid, /* The record ID of the ancestor */ + const char *zDate /* Date & time of the current check-in */ +){ + return db_exists( + "SELECT 1 FROM event" + " WHERE datetime(mtime)>=%Q" + " AND type='ci' AND objid=%d", + zDate, rid + ) ? 0 : 1; +} /* ** Make sure the current check-in with timestamp zDate is younger than its ** ancestor identified rid and zUuid. Throw a fatal error if not. */ @@ -1410,23 +1428,18 @@ int rid, /* The record ID of the ancestor */ const char *zUuid, /* The artifact ID of the ancestor */ const char *zDate /* Date & time of the current check-in */ ){ #ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES - int b; - b = db_exists( - "SELECT 1 FROM event" - " WHERE datetime(mtime)>=%Q" - " AND type='ci' AND objid=%d", - zDate, rid - ); - if( b ){ + if(checkin_is_younger(rid,zDate)==0){ fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" " Use --allow-older to override.", zUuid, zDate); } #endif } + + /* ** zDate should be a valid date string. Convert this string into the ** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date, ** print a fatal error and quit. @@ -2329,13 +2342,11 @@ ** Do not allow a commit against a closed leaf unless the commit ** ends up on a different branch. */ if( /* parent check-in has the "closed" tag... */ - db_exists("SELECT 1 FROM tagxref" - " WHERE tagid=%d AND rid=%d AND tagtype>0", - TAG_CLOSED, vid) + leaf_is_closed(vid) /* ... and the new check-in has no --branch option or the --branch ** option does not actually change the branch */ && (sCiInfo.zBranch==0 || db_exists("SELECT 1 FROM tagxref" " WHERE tagid=%d AND rid=%d AND tagtype>0" Index: src/configure.c ================================================================== --- src/configure.c +++ src/configure.c @@ -148,10 +148,11 @@ { "parent-project-name", CONFIGSET_PROJ }, { "hash-policy", CONFIGSET_PROJ }, { "comment-format", CONFIGSET_PROJ }, { "mimetypes", CONFIGSET_PROJ }, { "forbid-delta-manifests", CONFIGSET_PROJ }, + { "fileedit-glob", CONFIGSET_PROJ }, #ifdef FOSSIL_ENABLE_LEGACY_MV_RM { "mv-rm-files", CONFIGSET_PROJ }, #endif Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -3409,10 +3409,19 @@ ** SETTING: exec-rel-paths boolean default=off ** When executing certain external commands (e.g. diff and ** gdiff), use relative paths. */ #endif + +/* +** SETTING: fileedit-glob width=40 block-text +** A comma- or newline-separated list of globs of filenames +** which are allowed to be edited using the /fileedit page. +** An empty list prohibits editing via that page. Note that +** it cannot edit binary files, so the glob should not +** contain any globs for, e.g., images or PDFs. +*/ /* ** SETTING: gdiff-command width=40 default=gdiff ** The value is an external command to run when performing a graphical ** diff. If undefined, text diff will be used. */ Index: src/default_css.txt ================================================================== --- src/default_css.txt +++ src/default_css.txt @@ -860,5 +860,97 @@ // } // #setup_skinedit_css_defaults > tbody > tr > td:nth-of-type(2) > div { // max-width: 30em; // overflow: auto; // } +// .fileedit-XXX => /fileedit page +form.fileedit textarea { + font-family: monospace; + width: 100%; +} +form.fileedit fieldset { + margin: 0.5em 0 0 0; + border-radius: 0.5em; + border-color: inherit; + border-width: 1px; +} +form.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; +} +form.fileedit fieldset > div > .input-with-label { + margin: 0.25em 0.5em; +} +form.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 { + background: yellow; + color: darkred; + margin: 1em 0; + padding: 0.5em; + border-radius: 0.5em; +} +code.fileedit-manifest { + display: block; + height: 16em; + overflow: auto; +} +div.fileedit-preview { + margin: 0; + padding: 0; +} +.fileedit-preview > div:first-child { + margin: 1em 0 0 0; + border-bottom: 1px dashed; +} +div.fileedit-diff { + margin: 0; + padding: 0; +} +.fileedit-diff > div:first-child { + border-bottom: 1px dashed; +} + +.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; +} +.input-with-label > * { + vertical-align: middle; +} +.input-with-label > input { + margin: 0; +} +.input-with-label > select { + margin: 0; +} +.input-with-label > input[type=text] { + margin: 0; +} +.input-with-label > textarea { + margin: 0; +} +.input-with-label > input[type=checkbox] { + vertical-align: sub; +} +.input-with-label > input[type=radio] { + vertical-align: sub; +} +.input-with-label > span { + margin: 0 0.25em 0 0.25em; + vertical-align: middle; +} ADDED src/fileedit.c Index: src/fileedit.c ================================================================== --- /dev/null +++ src/fileedit.c @@ -0,0 +1,1589 @@ +/* +** 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 for the /fileedit page and related code. +*/ +#include "config.h" +#include "fileedit.h" +#include +#include + +/* +** State for the "mini-checkin" infrastructure, which enables the +** ability to commit changes to a single file without a checkout +** 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 +** CheckinMiniInfo instance and is freed by CheckinMiniInfo_cleanup(). +*/ +struct CheckinMiniInfo { + Manifest * pParent; /* parent checkin. Memory is owned by this + object. */ + char *zParentUuid; /* Full UUID of pParent */ + char *zFilename; /* Name of single file to commit. Must be + relative to the top of the repo. */ + Blob fileContent; /* Content of file referred to by zFilename. */ + Blob fileHash; /* Hash of this->fileContent, using the repo's + preferred hash method. */ + Blob comment; /* Check-in comment text */ + char *zCommentMimetype; /* Mimetype of comment. May be NULL */ + char *zUser; /* User name */ + char *zDate; /* Optionally force this date string (anything + supported by date_in_standard_format()). + Maybe be NULL. */ + Blob *pMfOut; /* If not NULL, checkin_mini() will write a + copy of the generated manifest here. This + memory is NOT owned by CheckinMiniInfo. */ + int filePerm; /* Permissions (via file_perm()) of the input + file. We need to store this before calling + checkin_mini() because the real input file + name may differ from the repo-centric + this->zFilename, and checkin_mini() requires + the permissions of the original file. For + web commits, set this to PERM_REG or (when + editing executable scripts) PERM_EXE before + calling checkin_mini(). */ + int flags; /* Bitmask of fossil_cimini_flags. */ +}; +typedef struct CheckinMiniInfo CheckinMiniInfo; + +/* +** CheckinMiniInfo::flags values. +*/ +enum fossil_cimini_flags { +CIMINI_NONE = 0, +/* +** Tells checkin_mini() to use dry-run mode. +*/ +CIMINI_DRY_RUN = 1, +/* +** Tells checkin_mini() to allow forking from a non-leaf commit. +*/ +CIMINI_ALLOW_FORK = 1<<1, +/* +** Tells checkin_mini() to dump its generated manifest to stdout. +*/ +CIMINI_DUMP_MANIFEST = 1<<2, + +/* +** By default, content containing what appears to be a merge conflict +** marker is not permitted. This flag relaxes that requirement. +*/ +CIMINI_ALLOW_MERGE_MARKER = 1<<3, + +/* +** By default mini-checkins are not allowed to be "older" +** than their parent. i.e. they may not have a timestamp +** which predates their parent. This flag bypasses that +** check. +*/ +CIMINI_ALLOW_OLDER = 1<<4, + +/* +** Indicates that the content of the newly-checked-in file is +** converted, if needed, to use the same EOL style as the previous +** version of that file. Only the in-memory/in-repo copies are +** affected, not the original file (if any). +*/ +CIMINI_CONVERT_EOL_INHERIT = 1<<5, +/* +** Indicates that the input's EOLs should be converted to Unix-style. +*/ +CIMINI_CONVERT_EOL_UNIX = 1<<6, +/* +** Indicates that the input's EOLs should be converted to Windows-style. +*/ +CIMINI_CONVERT_EOL_WINDOWS = 1<<7, +/* +** A hint to checkin_mini() to "prefer" creation of a delta manifest. +** It may decide not to for various reasons. +*/ +CIMINI_PREFER_DELTA = 1<<8, +/* +** A "stronger hint" to checkin_mini() to prefer creation of a delta +** manifest if it at all can. It will decide not to only if creation +** of a delta is not a realistic option. For this to work, it must be +** set together with the CIMINI_PREFER_DELTA flag, but the two cannot +** be combined in this enum. +** +** This option is ONLY INTENDED FOR TESTING, used in bypassing +** heuristics which may otherwise disable generation of a delta on the +** grounds of efficiency (e.g. not generating a delta if the parent +** non-delta only has a few F-cards). +** +** The forbid-delta-manifests repo config option trumps this. +*/ +CIMINI_STRONGLY_PREFER_DELTA = 1<<9, +/* +** Tells checkin_mini() to permit the addition of a new file. Normally +** this is disabled because there are many cases where it could cause +** the inadvertent addition of a new file when an update to an +** existing was intended, as a side-effect of name-case differences. +*/ +CIMINI_ALLOW_NEW_FILE = 1<<10 +}; + +/* +** Initializes p to a known-valid default state. +*/ +static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ + memset(p, 0, sizeof(CheckinMiniInfo)); + p->flags = CIMINI_NONE; + p->filePerm = -1; + p->comment = p->fileContent = p->fileHash = empty_blob; +} + +/* +** Frees all memory owned by p, but does not free p. + */ +static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){ + blob_reset(&p->comment); + blob_reset(&p->fileContent); + blob_reset(&p->fileHash); + if(p->pParent){ + manifest_destroy(p->pParent); + } + fossil_free(p->zFilename); + fossil_free(p->zDate); + fossil_free(p->zParentUuid); + fossil_free(p->zCommentMimetype); + fossil_free(p->zUser); + CheckinMiniInfo_init(p); +} + +/* +** Internal helper which returns an F-card perms string suitable for +** writing into a manifest. +*/ +static const char * mfile_permint_mstring(int perm){ + switch(perm){ + case PERM_EXE: return " x"; + case PERM_LNK: return " l"; + default: return ""; + } +} + +/* +** Given a ManifestFile permission string (or NULL), it returns one of +** PERM_REG, PERM_EXE, or PERM_LNK. +*/ +static int mfile_permstr_int(const char *zPerm){ + if(!zPerm || !*zPerm) return PERM_REG; + 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){ + if(p->zUuid){ + assert(*p->zUuid); + blob_appendf(pOut, "F %F %s%s", p->zName, + p->zUuid, mfile_perm_mstring(p)); + if(p->zPrior){ + assert(*p->zPrior); + blob_appendf(pOut, " %F\n", p->zPrior); + }else{ + blob_append(pOut, "\n", 1); + } + }else{ + /* File was removed from parent delta. */ + blob_appendf(pOut, "F %F\n", p->zName); + } +} +/* +** Handles the F-card parts for create_manifest_mini(). +** +** If asDelta is true, F-cards will be handled as for a delta +** manifest, and the caller MUST have added a B-card to pOut before +** calling this. +** +** Returns 1 on success, 0 on error, and writes any error message to +** pErr (if it's not NULL). The only non-immediately-fatal/panic error +** is if pCI->filePerm is PERM_LNK or pCI would update a PERM_LNK +** in-repo file. +*/ +static int create_manifest_mini_fcards( Blob * pOut, + CheckinMiniInfo * pCI, + int asDelta, + Blob * pErr){ + int wroteThisCard = 0; + const ManifestFile * pFile; + int (*fncmp)(char const *, char const *) = /* filename comparator */ + filenames_are_case_sensitive() + ? fossil_strcmp + : fossil_stricmp; +#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 +#define write_this_card(NAME) \ + blob_appendf(pOut, "F %F %b%s\n", (NAME), &pCI->fileHash, \ + mfile_permint_mstring(pCI->filePerm)); \ + wroteThisCard = 1 + + assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); + assert(pCI->filePerm==PERM_REG || pCI->filePerm==PERM_EXE); + if(PERM_LNK==pCI->filePerm){ + goto err_no_symlink; + } + manifest_file_rewind(pCI->pParent); + if(asDelta!=0 && (pCI->pParent->zBaseline==0 + || pCI->pParent->nFile==0)){ + /* Parent is a baseline or a delta with no F-cards, so this is + ** the simplest case: create a delta with a single F-card. + */ + pFile = manifest_file_find(pCI->pParent, pCI->zFilename); + if(pFile!=0 && manifest_file_mperm(pFile)==PERM_LNK){ + goto err_no_symlink; + } + write_this_card(pFile ? pFile->zName : pCI->zFilename); + return 1; + } + while(1){ + int cmp; + if(asDelta==0){ + pFile = manifest_file_next(pCI->pParent, 0); + }else{ + /* Parent is a delta manifest with F-cards. Traversal of delta + ** manifest file entries is normally done via + ** manifest_file_next(), which takes into account the + ** differences between the delta and its parent and returns + ** F-cards from both. Each successive delta from the same + ** baseline includes all F-card changes from the previous + ** deltas, so we instead clone the parent's F-cards except for + ** the one (if any) which matches the new file. + */ + pFile = pCI->pParent->iFile < pCI->pParent->nFile + ? &pCI->pParent->aFile[pCI->pParent->iFile++] + : 0; + } + if(0==pFile) break; + cmp = fncmp(pFile->zName, pCI->zFilename); + if(cmp<0){ + checkin_mini_append_fcard(pOut,pFile); + }else{ + if(cmp==0 || 0==wroteThisCard){ + assert(0==wroteThisCard); + if(PERM_LNK==manifest_file_mperm(pFile)){ + goto err_no_symlink; + } + write_this_card(cmp==0 ? pFile->zName : pCI->zFilename); + } + if(cmp>0){ + assert(wroteThisCard!=0); + checkin_mini_append_fcard(pOut,pFile); + } + } + } + if(wroteThisCard==0){ + write_this_card(pCI->zFilename); + } + return 1; +err_no_symlink: + mf_err((pErr,"Cannot commit or overwrite symlinks " + "via mini-checkin.")); + return 0; +#undef write_this_card +#undef mf_err +} + +/* +** Creates a manifest file, written to pOut, from the state in the +** fully-populated and semantically valid pCI argument. pCI is not +** *semantically* modified but cannot be const because blob_str() may +** need to NUL-terminate any given blob. +** +** Returns true on success. On error, returns 0 and, if pErr is not +** NULL, writes an error message there. +** +** Intended only to be called via checkin_mini() or routines which +** have already completely vetted pCI. +*/ +static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI, + Blob * pErr){ + Blob zCard = empty_blob; /* Z-card checksum */ + int asDelta = 0; +#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 + + assert(blob_str(&pCI->fileHash)); + assert(pCI->pParent); + assert(pCI->zFilename); + assert(pCI->zUser); + assert(pCI->zDate); + + /* Potential TODOs include... + ** + ** - Maybe add support for tags. Those can be edited via /info page, + ** and feel like YAGNI/feature creep for this purpose. + */ + blob_zero(pOut); + manifest_file_rewind(pCI->pParent) /* force load of baseline */; + /* Determine whether we want to create a delta manifest... */ + if((CIMINI_PREFER_DELTA & pCI->flags) + && ((CIMINI_STRONGLY_PREFER_DELTA & pCI->flags) + || (pCI->pParent->pBaseline + ? pCI->pParent->pBaseline + : pCI->pParent)->nFile > 15 + /* 15 is arbitrary: don't create a delta when there is only a + ** tiny gain for doing so. That heuristic is not *quite* + ** right, in that when we're deriving from another delta, we + ** really should compare the F-card count between it and its + ** baseline, and create a delta if the baseline has (say) + ** twice or more as many F-cards as the previous delta. */) + && !db_get_boolean("forbid-delta-manifests",0) + ){ + asDelta = 1; + blob_appendf(pOut, "B %s\n", + pCI->pParent->zBaseline + ? pCI->pParent->zBaseline + : pCI->zParentUuid); + } + blob_reserve(pOut, 1024 * + (asDelta ? 2 : pCI->pParent->nFile/11+1 + /* In the fossil core repo, each 12-ish F-cards (on + ** average) take up roughly 1kb */)); + if(blob_size(&pCI->comment)!=0){ + blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); + }else{ + blob_append(pOut, "C (no\\scomment)\n", 16); + } + blob_appendf(pOut, "D %s\n", pCI->zDate); + if(create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)==0){ + return 0; + } + if(pCI->zCommentMimetype!=0 && pCI->zCommentMimetype[0]!=0){ + blob_appendf(pOut, "N %F\n", pCI->zCommentMimetype); + } + blob_appendf(pOut, "P %s\n", pCI->zParentUuid); + blob_appendf(pOut, "U %F\n", pCI->zUser); + md5sum_blob(pOut, &zCard); + blob_appendf(pOut, "Z %b\n", &zCard); + blob_reset(&zCard); + return 1; +#undef mf_err +} + +/* +** EXPERIMENTAL! Subject to change or removal at any time. +** +** A so-called "single-file/mini/web checkin" is a slimmed-down form +** of the checkin command which accepts only a single file and is +** intended to accept edits to a file via the web interface or from +** the CLI from outside of a checkout. +** +** Being fully non-interactive is a requirement for this function, +** thus it cannot perform autosync or similar activities. +** +** This routine uses the state from the given fully-populated pCI +** argument to add pCI->fileContent to the database, and create and +** save a manifest for that change. Ownership of pCI and its contents +** are unchanged. +** +** This function may may modify pCI as follows: +** +** - If one of Manifest pCI->pParent or pCI->zParentUuid are NULL, +** then the other will be assigned based on its counterpart. Both +** may not be NULL. +** +** - pCI->zDate is normalized to/replaced with a valid date/time +** string. If its original value cannot be validated then +** this function fails. If pCI->zDate is NULL, the current time +** is used. +** +** - If the CIMINI_CONVERT_EOL_INHERIT flag is set, +** pCI->fileContent appears to be plain text, and its line-ending +** style differs from its previous version, it is converted to the +** same EOL style as the previous version. If this is done, the +** pCI->fileHash is re-computed. Note that only pCI->fileContent, +** not the original file, is affected by the conversion. +** +** - If pCI->fileHash is empty, this routine populates it with the +** repository's preferred hash algorithm. +** +** - pCI->comment may be converted to Unix-style newlines. +** +** pCI's ownership is not modified. +** +** This function validates several of the inputs and fails if any +** validation fails. +** +** On error, returns false (0) and, if pErr is not NULL, writes a +** diagnostic message there. +** +** Returns true on success. If pRid is not NULL, the RID of the +** resulting manifest is written to *pRid. +** +** The checkin process is largely influenced by pCI->flags, and that +** must be populated before calling this. See the fossil_cimini_flags +** enum for the docs for each flag. +*/ +static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){ + Blob mf = empty_blob; /* output manifest */ + int rid = 0, frid = 0; /* various RIDs */ + int isPrivate; /* whether this is private content + or not */ + ManifestFile * zFilePrev; /* file entry from pCI->pParent */ + int prevFRid = 0; /* RID of file's prev. version */ +#define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error + + if(!(pCI->flags & CIMINI_DRY_RUN)){ + /* Until this feature is fully vetted, disallow it in the main + ** fossil repo unless dry-run mode is being used. */ + char * zProjCode = db_get("project-code",0); + assert(zProjCode); + if(0==fossil_stricmp("CE59BB9F186226D80E49D1FA2DB29F935CCA0333", + zProjCode)){ + fossil_fatal("Never, ever run this in/on the core fossil repo " + "in non-dry-run mode until it's been well-vetted. " + "Use a temp/test repo."); + } + fossil_free(zProjCode); + } + db_begin_transaction(); + + if(pCI->pParent==0 && pCI->zParentUuid==0){ + ci_err((pErr, "Cannot determine parent version.")); + } + else if(pCI->pParent==0){ + pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); + if(pCI->pParent==0){ + ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); + } + }else if(pCI->zParentUuid==0){ + pCI->zParentUuid = rid_to_uuid(pCI->pParent->rid); + assert(pCI->zParentUuid); + } + + assert(pCI->pParent->rid>0); + if(leaf_is_closed(pCI->pParent->rid)){ + ci_err((pErr,"Cannot commit to a closed leaf.")); + /* Remember that in order to override this we'd also need to + ** cancel TAG_CLOSED on pCI->pParent. There would seem to be no + ** reason we can't do that via the generated manifest, but the + ** commit command does not offer that option, so mini-checkin + ** probably shouldn't, either. + */ + } + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ + ci_err((pErr,"No such user: %s", pCI->zUser)); + } + if(!(CIMINI_ALLOW_FORK & pCI->flags) + && !is_a_leaf(pCI->pParent->rid)){ + ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", + pCI->zParentUuid)); + } + if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags) + && contains_merge_marker(&pCI->fileContent)){ + ci_err((pErr,"Content appears to contain a merge conflict marker.")); + } + if(!file_is_simple_pathname(pCI->zFilename, 1)){ + ci_err((pErr,"Invalid filename for use in a repository: %s", + pCI->zFilename)); + } + if(!(CIMINI_ALLOW_OLDER & pCI->flags) + && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ + ci_err((pErr,"Checkin time (%s) may not be older " + "than its parent (%z).", + pCI->zDate, + db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", + pCI->pParent->rDate) + )); + } + { + /* + ** Normalize the timestamp. We don't use date_in_standard_format() + ** because that has side-effects we don't want to trigger here. + */ + char * zDVal = db_text( + 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", + pCI->zDate ? pCI->zDate : "now"); + if(zDVal==0 || zDVal[0]==0){ + fossil_free(zDVal); + ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); + } + fossil_free(pCI->zDate); + pCI->zDate = zDVal; + } + { /* Confirm that only one EOL policy is in place. */ + int n = 0; + if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags) ++n; + if(CIMINI_CONVERT_EOL_UNIX & pCI->flags) ++n; + if(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags) ++n; + if(n>1){ + ci_err((pErr,"More than 1 EOL conversion policy was specified.")); + } + } + /* Potential TODOs include: + ** + ** - Commit allows an empty checkin only with a flag, but we + ** currently disallow it entirely. Conform with commit? + ** + ** Non-TODOs: + ** + ** - Check for a commit lock would require auto-sync, which this + ** code cannot do if it's going to be run via a web page. + */ + + /* + ** Confirm that pCI->zFilename can be found in pCI->pParent. If + ** not, fail unless the CIMINI_ALLOW_NEW_FILE flag is set. This is + ** admittedly an artificial limitation, not strictly necessary. We + ** do it to hopefully reduce the chance of an "oops" where file + ** X/Y/z gets committed as X/Y/Z or X/y/z due to a typo or + ** case-sensitivity mismatch between the user/repo/filesystem, or + ** some such. + */ + manifest_file_rewind(pCI->pParent); + zFilePrev = manifest_file_find(pCI->pParent, pCI->zFilename); + if(!(CIMINI_ALLOW_NEW_FILE & pCI->flags) + && (!zFilePrev + || !zFilePrev->zUuid/*was removed from parent delta manifest*/) + ){ + ci_err((pErr,"File [%s] not found in manifest [%S]. " + "Adding new files is currently not permitted.", + pCI->zFilename, pCI->zParentUuid)); + }else if(zFilePrev + && manifest_file_mperm(zFilePrev)==PERM_LNK){ + ci_err((pErr,"Cannot save a symlink via a mini-checkin.")); + } + if(zFilePrev){ + prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); + } + + if(((CIMINI_CONVERT_EOL_INHERIT & pCI->flags) + || (CIMINI_CONVERT_EOL_UNIX & pCI->flags) + || (CIMINI_CONVERT_EOL_WINDOWS & pCI->flags)) + && blob_size(&pCI->fileContent)>0 + ){ + /* Convert to the requested EOL style. Note that this inherently + ** runs a risk of breaking content, e.g. string literals which + ** contain embedded newlines. Note that HTML5 specifies that + ** form-submitted TEXTAREA content gets normalized to CRLF-style: + ** + ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element + */ + const int pseudoBinary = LOOK_LONG | LOOK_NUL; + const int lookFlags = LOOK_CRLF | pseudoBinary; + const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags ); + if(!(pseudoBinary & lookNew)){ + int rehash = 0; + if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags){ + Blob contentPrev = empty_blob; + int lookOrig, nOrig; + content_get(prevFRid, &contentPrev); + lookOrig = looks_like_utf8(&contentPrev, lookFlags); + nOrig = blob_size(&contentPrev); + blob_reset(&contentPrev); + if(nOrig>0 && lookOrig!=lookNew){ + /* If there is a newline-style mismatch, adjust the new + ** content version to the previous style, then re-hash the + ** content. Note that this means that what we insert is NOT + ** what's in the filesystem. + */ + if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){ + /* Old has Unix-style, new has Windows-style. */ + blob_to_lf_only(&pCI->fileContent); + rehash = 1; + }else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){ + /* Old has Windows-style, new has Unix-style. */ + blob_add_cr(&pCI->fileContent); + rehash = 1; + } + } + }else{ + const int oldSize = blob_size(&pCI->fileContent); + if(CIMINI_CONVERT_EOL_UNIX & pCI->flags){ + blob_to_lf_only(&pCI->fileContent); + }else{ + assert(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags); + blob_add_cr(&pCI->fileContent); + } + if(blob_size(&pCI->fileContent)!=oldSize){ + rehash = 1; + } + } + if(rehash!=0){ + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); + } + } + }/* end EOL conversion */ + + if(blob_size(&pCI->fileHash)==0){ + /* Hash the content if it's not done already... */ + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); + assert(blob_size(&pCI->fileHash)>0); + } + if(zFilePrev){ + /* Has this file been changed since its previous commit? Note + ** that we have to delay this check until after the potentially + ** expensive EOL conversion. */ + assert(blob_size(&pCI->fileHash)); + if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash)) + && manifest_file_mperm(zFilePrev)==pCI->filePerm){ + ci_err((pErr,"File is unchanged. Not saving.")); + } + } +#if 1 + /* Do we really want to normalize comment EOLs? Web-posting will + ** submit them in CRLF format. */ + blob_to_lf_only(&pCI->comment); +#endif + /* Create, save, deltify, and crosslink the manifest... */ + if(create_manifest_mini(&mf, pCI, pErr)==0){ + return 0; + } + isPrivate = content_is_private(pCI->pParent->rid); + rid = content_put_ex(&mf, 0, 0, 0, isPrivate); + if(pCI->flags & CIMINI_DUMP_MANIFEST){ + fossil_print("%b", &mf); + } + if(pCI->pMfOut!=0){ + /* Cross-linking clears mf, so we have to copy it, + ** instead of taking over its memory. */ + blob_reset(pCI->pMfOut); + blob_append(pCI->pMfOut, blob_buffer(&mf), blob_size(&mf)); + } + content_deltify(rid, &pCI->pParent->rid, 1, 0); + manifest_crosslink(rid, &mf, 0); + blob_reset(&mf); + /* Save and deltify the file content... */ + frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), + 0, 0, isPrivate); + if(zFilePrev!=0){ + assert(prevFRid>0); + content_deltify(frid, &prevFRid, 1, 0); + } + db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0); + if(pRid!=0){ + *pRid = rid; + } + return 1; +ci_error: + assert(db_transaction_nesting_depth()>0); + db_end_transaction(1); + return 0; +#undef ci_err +} + +/* +** COMMAND: test-ci-mini +** +** This is an on-going experiment, subject to change or removal at +** any time. +** +** Usage: %fossil test-ci-mini ?OPTIONS? FILENAME +** +** where FILENAME is a repo-relative name as it would appear in the +** vfile table. +** +** Options: +** +** --repository|-R REPO The repository file to commit to. +** --as FILENAME The repository-side name of the input +** file, relative to the top of the +** repository. Default is the same as the +** input file name. +** --comment|-m COMMENT Required checkin comment. +** --comment-file|-M FILE Reads checkin comment from the given file. +** --revision|-r VERSION Commit from this version. Default is +** the checkout version (if available) or +** trunk (if used without a checkout). +** --allow-fork Allows the commit to be made against a +** non-leaf parent. Note that no autosync +** is performed beforehand. +** --allow-merge-conflict Allows checkin of a file even if it +** appears to contain a fossil merge conflict +** marker. +** --user-override USER USER to use instead of the current +** default. +** --date-override DATETIME DATE to use instead of 'now'. +** --allow-older Allow a commit to be older than its +** ancestor. +** --convert-eol Convert EOL style of the checkin to match +** the previous version's content. Does not +** modify the input file, only the checked-in +** content. +** --delta Prefer to generate a delta manifest, if +** able. The forbid-delta-manifests repo +** config option trumps this, as do certain +** heuristics. +** --allow-new-file Allow addition of a new file this way. +** Disabled by default to avoid that case- +** sensitivity errors inadvertently lead to +** adding a new file where an update is +** intended. +** --dump-manifest|-d Dumps the generated manifest to stdout +** immediately after it's generated. +** --save-manifest FILE Saves the generated manifest to a file +** after successfully processing it. +** --wet-run Disables the default dry-run mode. +** +** Example: +** +** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c +** +*/ +void test_ci_mini_cmd(){ + 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 */ + const char * zAsFilename; /* --as filename */ + const char * zRevision; /* --revision|-r [=trunk|checkout] */ + const char * zUser; /* --user-override */ + const char * zDate; /* --date-override */ + char const * zManifestFile = 0;/* --save-manifest FILE */ + + /* This function should perform only the minimal "business logic" it + ** needs in order to fully/properly populate the CheckinMiniInfo and + ** then pass it on to checkin_mini() to do most of the validation + ** and work. The point of this is to avoid duplicate code when a web + ** front-end is added for checkin_mini(). + */ + CheckinMiniInfo_init(&cimi); + zComment = find_option("comment","m",1); + zCommentFile = find_option("comment-file","M",1); + zAsFilename = find_option("as",0,1); + zRevision = find_option("revision","r",1); + zUser = find_option("user-override",0,1); + zDate = find_option("date-override",0,1); + zManifestFile = find_option("save-manifest",0,1); + if(find_option("wet-run",0,0)==0){ + cimi.flags |= CIMINI_DRY_RUN; + } + if(find_option("allow-fork",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_FORK; + } + if(find_option("dump-manifest","d",0)!=0){ + cimi.flags |= CIMINI_DUMP_MANIFEST; + } + if(find_option("allow-merge-conflict",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_MERGE_MARKER; + } + if(find_option("allow-older",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_OLDER; + } + if(find_option("convert-eol-prev",0,0)!=0){ + cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; + } + if(find_option("delta",0,0)!=0){ + cimi.flags |= CIMINI_PREFER_DELTA; + } + if(find_option("delta2",0,0)!=0){ + /* Undocumented. For testing only. */ + cimi.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA; + } + if(find_option("allow-new-file",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_NEW_FILE; + } + db_find_and_open_repository(0, 0); + verify_all_options(); + user_select(); + if(g.argc!=3){ + usage("INFILE"); + } + if(zComment && zCommentFile){ + fossil_fatal("Only one of -m or -M, not both, may be used."); + }else{ + if(zCommentFile && *zCommentFile){ + blob_read_from_file(&cimi.comment, zCommentFile, ExtFILE); + }else if(zComment && *zComment){ + blob_append(&cimi.comment, zComment, -1); + } + if(!blob_size(&cimi.comment)){ + fossil_fatal("Non-empty checkin comment is required."); + } + } + db_begin_transaction(); + zFilename = g.argv[2]; + cimi.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); + cimi.filePerm = file_perm(zFilename, ExtFILE); + cimi.zUser = mprintf("%s", zUser ? zUser : login_name()); + if(zDate){ + cimi.zDate = mprintf("%s", zDate); + } + if(zRevision==0 || zRevision[0]==0){ + if(g.localOpen/*checkout*/){ + zRevision = db_lget("checkout-hash", 0)/*leak*/; + }else{ + zRevision = "trunk"; + } + } + name_to_uuid2(zRevision, "ci", &cimi.zParentUuid); + if(cimi.zParentUuid==0){ + fossil_fatal("Cannot determine version to commit to."); + } + blob_read_from_file(&cimi.fileContent, zFilename, ExtFILE); + { + Blob theManifest = empty_blob; /* --save-manifest target */ + Blob errMsg = empty_blob; + int rc; + if(zManifestFile){ + cimi.pMfOut = &theManifest; + } + rc = checkin_mini(&cimi, &newRid, &errMsg); + if(rc){ + assert(blob_size(&errMsg)==0); + }else{ + assert(blob_size(&errMsg)); + fossil_fatal("%b", &errMsg); + } + if(zManifestFile){ + fossil_print("Writing manifest to: %s\n", zManifestFile); + assert(blob_size(&theManifest)>0); + blob_write_to_file(&theManifest, zManifestFile); + blob_reset(&theManifest); + } + } + if(newRid!=0){ + fossil_print("New version%s: %z\n", + (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", + rid_to_uuid(newRid)); + } + db_end_transaction(0/*checkin_mini() will have triggered it to roll + ** back in dry-run mode, but we need access to + ** the transaction-written db state in this + ** routine.*/); + if(!(cimi.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ + fossil_warning("The checkout state is now out of sync " + "with regards to this commit. It needs to be " + "'update'd or 'close'd and re-'open'ed."); + } + CheckinMiniInfo_cleanup(&cimi); +} + + +/* +** Returns true if the given filename qualifies for online editing by +** the current user, else returns false. +** +** Editing requires that the user have the Write permission and that +** the filename match the glob defined by the fileedit-glob setting. +** A missing or empty value for that glob disables all editing. +*/ +int fileedit_is_editable(const char *zFilename){ + static Glob * pGlobs = 0; + static int once = 0; + if(0==g.perm.Write || zFilename==0 || *zFilename==0 + || (once!=0 && pGlobs==0)){ + return 0; + }else if(0==pGlobs){ + char * zGlobs = db_get("fileedit-glob",0); + once = 1; + if(0==zGlobs) return 0; + pGlobs = glob_create(zGlobs); + 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 { +FE_RENDER_GUESS = 0, +FE_RENDER_PLAIN_TEXT, +FE_RENDER_HTML, +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; + }else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 + || fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ + rc = FE_RENDER_WIKI; + } + } + return rc; +} + +/* +** Performs the PREVIEW mode for /filepage. +*/ +static void fileedit_render_preview(Blob * pContent, + const char *zFilename, + int flags, int renderMode, + int nIframeHeightEm){ + 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:{ + char * z64 = encode64(blob_str(pContent), blob_size(pContent)); + CX("", + nIframeHeightEm ? nIframeHeightEm : 40, + z64); + break; + } + case FE_RENDER_WIKI: + wiki_render_by_mimetype(pContent, zMime); + break; + default:{ + const char *zExt = strrchr(zFilename,'.'); + const char *zContent = blob_str(pContent); + if(FE_PREVIEW_LINE_NUMBERS & flags){ + output_text_with_line_numbers(zContent, "on"); + }else if(zExt && zExt[1]){ + CX("
%h
", + zExt+1, zContent); + }else{ + 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 +** from which pContent is based. zManifestUuid is the checkin version +** to which RID belongs - it is purely informational, for labeling the +** diff view. isSbs is true for side-by-side diffs, false for unified. +*/ +static void fileedit_render_diff(Blob * pContent, int frid, + const char * zManifestUuid, + int isSbs){ + Blob orig = empty_blob; + 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){ + CX("%b",&out); + }else{ + CX("
%b
",&out); + } + CX("
\n"); + blob_reset(&orig); + blob_reset(&out); +} + +/* +** Given a repo-relative filename and a manifest RID, returns the UUID +** of the corresponding file entry. Returns NULL if no match is +** found. If pFilePerm is not NULL, the file's permission flag value +** is written to *pFilePerm. +*/ +static char *fileedit_file_uuid(char const *zFilename, + int vid, int *pFilePerm){ + Stmt stmt = empty_Stmt; + char * zFileUuid = 0; + db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " + "WHERE filename=%Q %s AND checkinID=%d", + zFilename, filename_collation(), vid); + if(SQLITE_ROW==db_step(&stmt)){ + zFileUuid = mprintf("%s",db_column_text(&stmt, 0)); + if(pFilePerm){ + *pFilePerm = mfile_permstr_int(db_column_text(&stmt, 1)); + } + } + db_finalize(&stmt); + return zFileUuid; +} + +/* +** 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. +** +** 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' + 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 * 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 \n"); + } +} 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 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 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)\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)\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 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 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 >> $@ @@ -368,10 +368,16 @@ $(OBJDIR)\file$O : file_.c file.h $(TCC) -o$@ -c file_.c file_.c : $(SRCDIR)\file.c +translate$E $** > $@ + +$(OBJDIR)\fileedit$O : fileedit_.c fileedit.h + $(TCC) -o$@ -c fileedit_.c + +fileedit_.c : $(SRCDIR)\fileedit.c + +translate$E $** > $@ $(OBJDIR)\finfo$O : finfo_.c finfo.h $(TCC) -o$@ -c finfo_.c finfo_.c : $(SRCDIR)\finfo.c @@ -970,7 +976,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 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 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 @@ -476,10 +476,11 @@ $(SRCDIR)/etag.c \ $(SRCDIR)/event.c \ $(SRCDIR)/export.c \ $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ + $(SRCDIR)/fileedit.c \ $(SRCDIR)/finfo.c \ $(SRCDIR)/foci.c \ $(SRCDIR)/forum.c \ $(SRCDIR)/fshell.c \ $(SRCDIR)/fusefs.c \ @@ -709,10 +710,11 @@ $(OBJDIR)/etag_.c \ $(OBJDIR)/event_.c \ $(OBJDIR)/export_.c \ $(OBJDIR)/extcgi_.c \ $(OBJDIR)/file_.c \ + $(OBJDIR)/fileedit_.c \ $(OBJDIR)/finfo_.c \ $(OBJDIR)/foci_.c \ $(OBJDIR)/forum_.c \ $(OBJDIR)/fshell_.c \ $(OBJDIR)/fusefs_.c \ @@ -851,10 +853,11 @@ $(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 \ @@ -1213,10 +1216,11 @@ $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ + $(OBJDIR)/fileedit_.c:$(OBJDIR)/fileedit.h \ $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ @@ -1641,10 +1645,18 @@ $(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c $(OBJDIR)/file.h: $(OBJDIR)/headers + +$(OBJDIR)/fileedit_.c: $(SRCDIR)/fileedit.c $(TRANSLATE) + $(TRANSLATE) $(SRCDIR)/fileedit.c >$@ + +$(OBJDIR)/fileedit.o: $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fileedit.o -c $(OBJDIR)/fileedit_.c + +$(OBJDIR)/fileedit.h: $(OBJDIR)/headers $(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(TRANSLATE) $(TRANSLATE) $(SRCDIR)/finfo.c >$@ $(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h Index: win/Makefile.msc ================================================================== --- win/Makefile.msc +++ win/Makefile.msc @@ -384,10 +384,11 @@ etag_.c \ event_.c \ export_.c \ extcgi_.c \ file_.c \ + fileedit_.c \ finfo_.c \ foci_.c \ forum_.c \ fshell_.c \ fusefs_.c \ @@ -616,10 +617,11 @@ $(OX)\etag$O \ $(OX)\event$O \ $(OX)\export$O \ $(OX)\extcgi$O \ $(OX)\file$O \ + $(OX)\fileedit$O \ $(OX)\finfo$O \ $(OX)\foci$O \ $(OX)\forum$O \ $(OX)\fshell$O \ $(OX)\fusefs$O \ @@ -820,10 +822,11 @@ echo $(OX)\etag.obj >> $@ echo $(OX)\event.obj >> $@ echo $(OX)\export.obj >> $@ echo $(OX)\extcgi.obj >> $@ echo $(OX)\file.obj >> $@ + echo $(OX)\fileedit.obj >> $@ echo $(OX)\finfo.obj >> $@ echo $(OX)\foci.obj >> $@ echo $(OX)\forum.obj >> $@ echo $(OX)\fshell.obj >> $@ echo $(OX)\fusefs.obj >> $@ @@ -1282,10 +1285,16 @@ $(OX)\file$O : file_.c file.h $(TCC) /Fo$@ -c file_.c file_.c : $(SRCDIR)\file.c translate$E $** > $@ + +$(OX)\fileedit$O : fileedit_.c fileedit.h + $(TCC) /Fo$@ -c fileedit_.c + +fileedit_.c : $(SRCDIR)\fileedit.c + translate$E $** > $@ $(OX)\finfo$O : finfo_.c finfo.h $(TCC) /Fo$@ -c finfo_.c finfo_.c : $(SRCDIR)\finfo.c @@ -1927,10 +1936,11 @@ 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 \