Index: CHANGES.md ================================================================== --- CHANGES.md +++ CHANGES.md @@ -1,5 +1,26 @@ +**fnc 0.14** 2023-04-18 [[history][0.14a] / [diff][0.14b]] + +- show msg rather than error if timeline C keymap is used outside a work tree +- check path is in the requested version if T keymap is used with `fnc tl path` +- display the work tree path in checkout diff headlines +- fix diff display bug by resetting diff mode after timeline C keymap is used +- list only user-defined settings with `fnc config`, use --ls flag to show all +- fix unexpected behaviour when switching between diff and timeline views + after requesting a diff by tagging arbitrary commits with the `space` keymap +- disallow tagging different wiki pages to request a diff +- warn user rather than silently ignore attempts to tag non-diffable artifacts +- fix bug if timeline C keymap is used when a commit is already tagged +- change diff flags that modify diff views to resolve conflicts with libfossil +- catch trailing `\r` in CRLF files when finding scope for hunk headers +- show unchanged work tree report for all stash abort cases +- fix bug where the stash 'm' option was not available for the first hunk +- display "[current]" label with `fnc timeline -c ` +- disable landlock support for the `fnc stash` command to fix reparenting bug +- zap dead code +- latest upstream libfossil: [libf:9a8269d737] + **fnc 0.13** 2022-11-26 [[history][0.13a] / [diff][0.13b]] - improve reporting of invalid diff command arguments (reported by Dan Shearer) - accept libfossil global `-V|--verbose` option for all commands - plug memleak when using the `p` keymap in blame view @@ -294,10 +315,12 @@ - tailor help/usage output to the specified command - fix invalid memory read in diff routine - add support for repository fingerprint version 1 - fix line wrap bug that truncated lines in fullscreen mode +[0.14a]: https://fnc.bsdbox.org/timeline?p=0.14&bt=0.13 +[0.14b]: https://fnc.bsdbox.org/vdiff?from=0.13&to=0.14 [0.13a]: https://fnc.bsdbox.org/timeline?p=0.13&bt=0.12 [0.13b]: https://fnc.bsdbox.org/vdiff?from=0.12&to=0.13 [0.12a]: https://fnc.bsdbox.org/timeline?p=0.12&bt=0.11 [0.12b]: https://fnc.bsdbox.org/vdiff?from=0.11&to=0.12 [0.11a]: https://fnc.bsdbox.org/timeline?p=0.11&bt=0.10 Index: fnc.bld.mk ================================================================== --- fnc.bld.mk +++ fnc.bld.mk @@ -4,11 +4,11 @@ # CONFIGURATION CC ?= cc PREFIX ?= /usr/local MANDIR ?= /share/man -VERSION ?= 0.13 +VERSION ?= 0.14 HASH != cut -f 1 manifest.uuid DATE != sed '2q;d' manifest | cut -d ' ' -f 2 | tr T ' ' # FLAGS NEEDED TO BUILD SQLITE3 SQLITE_CFLAGS = ${CFLAGS} -Wall -Werror -Wno-sign-compare -pedantic -std=c99 \ Index: include/diff.h ================================================================== --- include/diff.h +++ include/diff.h @@ -23,19 +23,19 @@ enum fnc_diff_flag { FNC_DIFF_IGNORE_EOLWS = 0x01, FNC_DIFF_IGNORE_ALLWS = 0x03, FNC_DIFF_SIDEBYSIDE = 1 << 2, /* output side-by-side diff */ FNC_DIFF_VERBOSE = 1 << 3, /* show added/rm'd file content */ - FNC_DIFF_BRIEF = 1 << 4, + FNC_DIFF_BRIEF = 1 << 4, /* no content, just index lines */ FNC_DIFF_HTML = 1 << 5, - FNC_DIFF_LINENO = 1 << 6, /* output diff with line numbers */ - FNC_DIFF_NOOPT = 1 << 7, /* og. 0x0100 */ - FNC_DIFF_INVERT = 1 << 8, /* og. 0x0200 */ - FNC_DIFF_NOTTOOBIG = 1 << 9, /* og. 0x0800 */ - FNC_DIFF_STRIP_EOLCR = 1 << 10, /* og. 0x1000 */ - FNC_DIFF_ANSI_COLOR = 1 << 11, /* og. 0x2000 */ - FNC_DIFF_PROTOTYPE = 1 << 12 /* show func sig in hunk header */ + FNC_DIFF_LINENO = 1 << 6, /* show file line numbers */ + FNC_DIFF_NOOPT = 1 << 8, /* suppress optimisations (debug) */ + FNC_DIFF_INVERT = 1 << 9, + FNC_DIFF_PROTOTYPE = 1 << 10, /* show scope in hunk header */ + FNC_DIFF_NOTTOOBIG = 1 << 11, /* don't compute "large" diffs */ + FNC_DIFF_STRIP_EOLCR = 1 << 12, /* strip trailing '\r' */ + FNC_DIFF_ANSI_COLOR = 1 << 13 #define FNC_DIFF_CONTEXT_EX (((uint64_t)0x04) << 32) /* Allow 0 context */ #define FNC_DIFF_CONTEXT_MASK ((uint64_t)0x0000ffff) /* Default context */ #define FNC_DIFF_WIDTH_MASK ((uint64_t)0x00ff0000) /* SBS column width */ }; Index: include/settings.h ================================================================== --- include/settings.h +++ include/settings.h @@ -83,15 +83,22 @@ #define VIEW_MODE_ENUM(pfx, _) \ _(pfx, NONE), \ _(pfx, VERT), \ _(pfx, HRZN) + +#define STASH_MVMT_ENUM(pfx, _) \ + _(pfx, NONE), \ + _(pfx, DOWN), \ + _(pfx, UP), \ + _(pfx, UPDOWN) #define ENUM_INFO(_) \ _(fnc_opt_id, FNC, USER_OPTIONS) \ _(line_attr, SLINE, LINE_ATTR_ENUM) \ _(input_type, INPUT, INPUT_TYPE_ENUM) \ _(line_type, LINE, LINE_TYPE_ENUM) \ - _(view_mode, VIEW_SPLIT, VIEW_MODE_ENUM) + _(view_mode, VIEW_SPLIT, VIEW_MODE_ENUM) \ + _(stash_mvmt, STASH_MVMT, STASH_MVMT_ENUM) #define GEN_ENUMS(name, pfx, info) GEN_ENUM(name, pfx, info) ENUM_INFO(GEN_ENUMS) Index: lib/libfossil-config.h ================================================================== --- lib/libfossil-config.h +++ lib/libfossil-config.h @@ -81,13 +81,13 @@ #endif #endif /* _WIN32 */ -#define FSL_LIB_VERSION_HASH "46008704a620cc770bce96cc4313b0bf96498390" -#define FSL_LIB_VERSION_TIMESTAMP "2022-11-24 13:51:38.504 UTC" -#define FSL_LIB_CONFIG_TIME "2022-11-24 14:03 GMT" +#define FSL_LIB_VERSION_HASH "9a8269d7377f77c179c4bfb05cda36c9f01976e8" +#define FSL_LIB_VERSION_TIMESTAMP "2023-03-07 14:32:32.997 UTC" +#define FSL_LIB_CONFIG_TIME "2023-04-17 13:37 GMT" #if defined(_MSC_VER) #define FSL_PLATFORM_OS "windows" #define FSL_PLATFORM_IS_WINDOWS 1 #define FSL_PLATFORM_IS_UNIX 0 #define FSL_PLATFORM_PLATFORM "windows" Index: lib/libfossil.c ================================================================== --- lib/libfossil.c +++ lib/libfossil.c @@ -6514,11 +6514,22 @@ " FROM vmerge" " WHERE (vmerge.id=-1 OR vmerge.id=-2)" " ORDER BY 1"); while( !rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&q)) ){ fsl_id_t const mid = fsl_stmt_g_id(&q, 1); - if( mid != basedOnVid ){ + /* + The is-private check is taken from: + + https://fossil-scm.org/home/info/52a66829d655ff35dc52: + + noting that that patch also has (!g.markPrivate), a flag we do + not currently have in fsl_cx but perhaps should, so that we + can flag privacy at a higher level when doing a checkin. As of + this writing (2023-02-02), we do not have any infrastructure + for making private checkins. + */ + if( mid != basedOnVid && !fsl_content_is_private(f, mid) ){ const char *zCherrypickUuid = fsl_stmt_g_text(&q, 0, NULL); int const qType = '+'==*(zCherrypickUuid++) ? 1 : -1; rc = fsl_deck_Q_add( d, qType, zCherrypickUuid, NULL ); } } @@ -14304,12 +14315,13 @@ fsl__bccache_reset(&f->cache.blobContent); fsl__cx_mcache_clear(f); fsl__cx_clear_mf_seen(f, false); f->cache.allowSymlinks = f->cache.caseInsensitive = - f->cache.seenDeltaManifest = - f->cache.manifestSetting = -1; + f->cache.manifestSetting = + f->cache.searchIndexExists = + f->cache.seenDeltaManifest = -1; if(fsl_cx_db_ckout(f)){ fsl__ckout_version_fetch(f) /* FIXME: this "really should" be fsl__cx_ckout_clear(), but that data is not fetched on demand (maybe it should be?). */; }else{ @@ -14620,11 +14632,11 @@ fsl__fatal(FSL_RC_ERROR, "Cannot happen. Really."); } fsl__cx_finalize_cached_stmt(f); fsl__db_cached_clear_role(f->dbMain, r) /* Make sure that we destroy any cached statements which are - known to be tied to this db role. */; + known to be tied to this db role. */; if(db->dbh){ /* This is our MAIN db. CLOSE it. If we still have a secondary/counterpart db open, we'll detach it first. */ fsl_dbrole_e const counterpart = fsl__dbrole_counterpart(r); assert(f->dbMain == db); @@ -14761,11 +14773,12 @@ //if(f->repo.db.dbh) fsl_db_rollback_force(&f->repo.db); //if(f->ckout.db.dbh) fsl_db_rollback_force(&f->ckout.db); return fsl_cx_err_set(f, FSL_RC_MISUSE, "Cannot close repo or checkout with an " "opened transaction."); - }else if(!f->dbMain){ + } + if(!f->dbMain){ // Make sure that all string resources are cleaned up... fsl_db_close(&f->repo.db); fsl_db_close(&f->ckout.db); return 0; }else{ @@ -14772,11 +14785,11 @@ fsl_db * const dbR = &f->repo.db; return fsl__cx_detach_role(f, f->dbMain == dbR ? FSL_DBROLE_REPO : FSL_DBROLE_CKOUT) /* Will also close the counterpart db. */; - } + } } int fsl_repo_close( fsl_cx * const f ){ return fsl_close_scm_dbs(f); } @@ -15123,10 +15136,11 @@ To be called after a repo or checkout/repo combination has been opened. This updates some internal cached info based on the checkout and/or repo. */ static int fsl_cx_after_open(fsl_cx * f){ + f->cache.searchIndexExists = -1; int rc = fsl__ckout_version_fetch(f); if(!rc) rc = fsl_cx_load_glob_lists(f); return rc; } @@ -35252,11 +35266,11 @@ "UPDATE user SET cap='s', pw=lower(hex(randomblob(3)))" " WHERE login=%Q", defaultUser); if( !rc && !addOnlyUser ){ fsl_db_exec_multi(db, "INSERT OR IGNORE INTO user(login,pw,cap,info)" - " VALUES('anonymous',hex(randomblob(8)),'hmncz'," + " VALUES('anonymous',hex(randomblob(8)),'hz'," " 'Anon');" "INSERT OR IGNORE INTO user(login,pw,cap,info)" " VALUES('nobody','','gjor','Nobody');" "INSERT OR IGNORE INTO user(login,pw,cap,info)" " VALUES('developer','','dei','Dev');" @@ -35263,11 +35277,11 @@ "INSERT OR IGNORE INTO user(login,pw,cap,info)" " VALUES('reader','','kptw','Reader');" ); } } - return rc; + return rc; } int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt ){ fsl_db * db = 0; fsl_cx F = fsl_cx_empty /* used if !f */; @@ -36976,18 +36990,31 @@ #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) -static bool fsl_search_ndx_exists(fsl_cx * const f){ - if(f->cache.searchIndexExists<0){ - f->cache.searchIndexExists = fsl_db_table_exists(fsl_cx_db_repo(f), - FSL_DBROLE_REPO, - "ftsdocs") - ? 1 : 0; - } - return f->cache.searchIndexExists ? true : false; +/** + Returns 0 if f has no search index or a repo is not opened, else 4 + for an FTS4 index and 5 for an FTS5. If forceRecheck is false then + a cached values is returned after the first call. If forceRecheck + is true, the cached value is updated. + */ +static int fsl__search_ndx_exists(fsl_cx * const f, bool forceRecheck){ + fsl_db * const db = fsl_cx_db_repo(f); + if(db && (f->cache.searchIndexExists<0 || forceRecheck) ){ + const bool b = fsl_db_table_exists(db, FSL_DBROLE_REPO, "ftsdocs"); + if(b){ + f->cache.searchIndexExists = + fsl_db_table_has_column(db, "ftsdocs", "rowid" ) + ? 5 // FTS5 (fossil as of 2023-01-24) + : (fsl_db_table_has_column(db, "ftsdocs", "docid" ) ? 4 : 0); + assert(f->cache.searchIndexExists==4 || f->cache.searchIndexExists==5); + }else{ + f->cache.searchIndexExists = 0; + } + } + return f->cache.searchIndexExists; } static char fsl_satype_letter(fsl_satype_e t){ switch(t){ case FSL_SATYPE_CHECKIN: return 'c'; @@ -37001,43 +37028,47 @@ } } int fsl__search_doc_touch(fsl_cx * const f, fsl_satype_e saType, fsl_id_t rid, const char * docName){ - if(!fsl_search_ndx_exists(f) || fsl_content_is_private(f, rid)) return 0; + const int ftsVers = fsl__search_ndx_exists(f, false); + if(!ftsVers || fsl_content_is_private(f, rid)) return 0; + assert(ftsVers==4 || ftsVers==5 || !"If this fails then our search-index-exists check is wrong."); char zType[2] = {0,0}; zType[0] = fsl_satype_letter(saType); #if 0 /* See MARKER() call in the #else block */ assert(*zType); return *zType ? 0 : FSL_RC_MISUSE; -#else +#else /* Reminder: fossil(1) does some once-per-connection init here which installs UDFs used by the search process. Those will be significant for us if we add the search features to the library. */ assert(zType[0] && "Misuse of fsl__search_doc_touch()'s 2nd parameter."); fsl_db * const db = fsl_cx_db_repo(f); - int rc = fsl_db_exec(db, - "DELETE FROM ftsidx WHERE docid IN" + int rc = 0; + char const * zDocId = 4==ftsVers ? "docid" : "rowid"; + rc = fsl_db_exec(db, + "DELETE FROM ftsidx WHERE %s IN" " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%"FSL_ID_T_PFMT" AND idxed)", - zType, rid ); + zDocId, zType, rid ); if(rc){ // For reasons i don't understand, this query fails with "SQL logic error" // when run from here, but succeeds fine in fossil and fossil's SQL shell. - /*MARKER(("type=%s rid=%d rc=%s\n",zType, (int)rid, fsl_rc_cstr(rc)));*/ + MARKER(("type=%s rid=%d rc=%s\n",zType, (int)rid, fsl_rc_cstr(rc))); goto end; } rc = fsl_db_exec(db, "REPLACE INTO ftsdocs(type,rid,name,idxed)" " VALUES(%Q,%"FSL_ID_T_PFMT",%Q,0)", zType, rid, docName ); if(rc) goto end; if( FSL_SATYPE_WIKI==saType || FSL_SATYPE_TECHNOTE==saType ){ rc = fsl_db_exec(db, - "DELETE FROM ftsidx WHERE docid IN" + "DELETE FROM ftsidx WHERE %s IN" " (SELECT rowid FROM ftsdocs WHERE type=%Q AND name=%Q AND idxed)", - zType, docName ); + zDocId, zType, docName ); if(!rc) rc = fsl_db_exec(db, "DELETE FROM ftsdocs WHERE type=%Q AND name=%Q AND rid!=%"FSL_ID_T_PFMT, zType, docName, rid ); } /* All forum posts are always indexed */ Index: lib/libfossil.h ================================================================== --- lib/libfossil.h +++ lib/libfossil.h @@ -11910,17 +11910,17 @@ Adds a Q-card record to the given deck. The type argument must be negative for a backed-out change, positive for a cherrypicked change. target must be a valid UUID string. If baseline is not NULL then it also must be a valid UUID. - Returns 0 on success, non-0 on error. FSL_RC_MISUSE if !mf - or !target, FSL_RC_RANGE if target/baseline are not valid - UUID strings (baseline may be NULL). + Returns 0 on success, non-0 on error. FSL_RC_MISUSE if !mf or + !target, FSL_RC_SYNTAX if type is 0 or target/baseline are not + valid UUID strings (baseline may be NULL). */ FSL_EXPORT int fsl_deck_Q_add( fsl_deck * const mf, int type, - fsl_uuid_cstr target, - fsl_uuid_cstr baseline ); + fsl_uuid_cstr target, + fsl_uuid_cstr baseline ); /** Functionally identical to fsl_deck_B_set() except that it sets the R-card. Returns 0 on succes, FSL_RC_RANGE if md5 is not NULL or exactly FSL_STRLEN_MD5 bytes long (not including trailing @@ -15994,10 +15994,14 @@ /** Opaque client-side state for use as the 2nd argument to this->callback. */ void * callbackState; + + /* TODO (2023-02-02): any state necessary for indicating that this + checking should be flagged as "private". + */ }; /** Convenience typedef. */ typedef struct fsl_checkin_queue_opt fsl_checkin_queue_opt; DELETED signify/fnc-12-release.pub Index: signify/fnc-12-release.pub ================================================================== --- signify/fnc-12-release.pub +++ signify/fnc-12-release.pub @@ -1,2 +0,0 @@ -untrusted comment: fnc 0.12 public key -RWRtqBYmaSguXJPDRSFPvxVuPNby5zfTslX9JA0pJlSMnOpDxW7WVHAQ ADDED signify/fnc-14-release.pub Index: signify/fnc-14-release.pub ================================================================== --- signify/fnc-14-release.pub +++ signify/fnc-14-release.pub @@ -0,0 +1,2 @@ +untrusted comment: fnc 0.14 public key +RWTOJ8pPCpW9YMEsLrgLmTRk0huOrEnyiLZRYo36C/9JQF/H3JnCGpFc Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -98,11 +98,11 @@ void *state; /* State for this->out() */ enum line_type *lines; /* Diff line type (e.g., minus, plus) */ uint32_t nlines; /* Index into this->lines */ int rc; /* Error reporting */ char ansi; /* ANSI colour code */ - struct { + struct hunk_scope { const fsl_buffer *file; /* Diffed file */ char *sig; /* Matching function */ char *spec; /* C++ specifier */ uint32_t lastmatch; /* Match line index */ uint32_t lastline; /* Last line scanned */ @@ -132,12 +132,11 @@ static int fnc_output_f_diff_out(void *, void const *, fsl_size_t); static int diff_outf(struct diff_out_state *, char const *, ... ); static int diff_out(struct diff_out_state * const, void const *, fsl_int_t); static int validate_scope_line(const char *); -static int match_hunk_function(struct diff_out_state *const, - uint32_t); +static int match_hunk_function(struct hunk_scope *, fsl_int_t); static int buffer_copy_lines_from(fsl_buffer *const, const fsl_buffer *const, fsl_size_t *, fsl_size_t, fsl_size_t); /* static uint64_t fnc_diff_flags_convert(int); */ /* static int diff_context_lines(uint64_t); */ @@ -650,33 +649,42 @@ */ static int buffer_copy_lines_from(fsl_buffer *const dst, const fsl_buffer *const src, fsl_size_t *offset, fsl_size_t seek, fsl_size_t n) { - const char *z = (const char *)src->mem; - fsl_size_t idx = *offset, ln = 0, start = 0; - int rc = FSL_RC_OK; + const char *z = (const char *)src->mem; + fsl_size_t i, start, ln; if (!n) - return rc; - - while (idx < src->used) { - if (z[idx] == '\n') { - if (++ln == seek) - start = idx + 1; /* skip leading '\n' */ - if (ln == seek + n) { - ++idx; + return FSL_RC_OK; + if (dst == NULL) + return FSL_RC_MISUSE; + if (*offset > src->used) + return FSL_RC_RANGE; + + i = *offset; + ln = start = 0; + + for (;;) { + if (i == src->used) { + if (ln == seek && start > 0) + break; /* no EOF '\n' so this is seek + n */ + return FSL_RC_NOT_FOUND; + } + if (z[i] == '\n') { + ++ln; + if (ln == seek) + start = i + 1; /* skip '\n' */ + if (ln == seek + n) break; - } } - ++idx; + ++i; } - if (dst) /* trim trailing '\n' when copying */ - rc = fsl_buffer_append(dst, &src->mem[start], idx - start - 1); *offset = start; - return rc; + + return fsl_buffer_append(dst, &src->mem[start], i - start); } /* * If str is a valid scope line (i.e., function prototype; class, namespace, * enum, struct, or union declaration; or C++ class specifier) return the @@ -685,11 +693,11 @@ * https://docs.microsoft.com/en-us/cpp/cpp/identifiers-cpp */ static int validate_scope_line(const char *str) { - if (fsl_isalpha(*str) || *str == '_' || *str == '$') { + if (str && (fsl_isalpha(*str) || *str == '_' || *str == '$')) { if (strchr(str, '(') || strchr(str, '{') || strstr(str, "struct ") || strstr(str, "union ") || strstr(str, "enum ") || strstr(str, "class ") || strstr(str, "namespace ")) return VSL_TRUE; @@ -709,61 +717,61 @@ * changed line of the current hunk, for the enclosing function in which the * hunk resides. Point dst->proto.sig to the heap-allocated matching line, * which the caller must eventually free. Return FSL_RC_OK on success. */ static int -match_hunk_function(struct diff_out_state *const dst, uint32_t pos) +match_hunk_function(struct hunk_scope *hs, fsl_int_t pos) { fsl_buffer buf = fsl_buffer_empty; char *line = NULL; fsl_size_t offset; - uint32_t last = dst->proto.lastline; + uint32_t last = hs->lastline; int rc = FSL_RC_OK; - dst->proto.lastline = pos; - offset = dst->proto.offset; /* Begin seek from last match */ + hs->lastline = pos; + offset = hs->offset; /* begin seek from last match */ - while (pos > 1 && pos > last) { - rc = buffer_copy_lines_from(&buf, dst->proto.file, &offset, - pos - dst->proto.lastmatch, 1); - if (rc) + while (pos >= 0 && pos >= last) { + rc = buffer_copy_lines_from(&buf, hs->file, &offset, + pos - hs->lastmatch, 1); + if (rc && rc != FSL_RC_NOT_FOUND) return rc; line = fsl_buffer_take(&buf); - if (line == NULL) - continue; switch (validate_scope_line(line)) { case VSL_FALSE: break; case VSL_PRIVATE: - if (!dst->proto.spec) - dst->proto.spec = " (private)"; + if (!hs->spec) + hs->spec = " (private)"; break; case VSL_PROTECT: - if (!dst->proto.spec) - dst->proto.spec = " (protected)"; + if (!hs->spec) + hs->spec = " (protected)"; break; case VSL_PUBLIC: - if (!dst->proto.spec) - dst->proto.spec = " (public)"; + if (!hs->spec) + hs->spec = " (public)"; break; case VSL_TRUE: - fsl_free(dst->proto.sig); - dst->proto.sig = line; + line[strcspn(line, "\n")] = '\0'; + line[strcspn(line, "\r")] = '\0'; /* fossil wiki */ + fsl_free(hs->sig); + hs->sig = line; /* * It's expensive to seek from the start of the * file for each hunk when diffing large files, * so save offset and line index of this match. */ - dst->proto.lastmatch = pos; - dst->proto.offset = offset; + hs->lastmatch = pos; + hs->offset = offset; return FSL_RC_OK; } /* No match, revert to last offset. */ fsl_free(line); - offset = dst->proto.offset; + offset = hs->offset; --pos; } return FSL_RC_OK; } @@ -1365,27 +1373,26 @@ if (rc) return rc; } if (proto && li + skip > 1) { - int n = 55; + struct hunk_scope *hs = &dst->proto; + int n = 55; - rc = match_hunk_function(dst, - li + skip + context); + rc = match_hunk_function(hs, li + skip + context); if (rc) return rc; - n -= fsl_strlen(dst->proto.spec); - if (dst->proto.sig) { - rc = diff_outf(dst, " %.*s", n, - dst->proto.sig); + if (hs->spec) + n -= fsl_strlen(hs->spec); + if (hs->sig) { + rc = diff_outf(dst, " %.*s", n, hs->sig); if (rc) return rc; } - if (dst->proto.spec) { - rc = diff_outf(dst, " %s", - dst->proto.spec); + if (hs->spec) { + rc = diff_outf(dst, " %s", hs->spec); if (rc) return rc; } } rc = diff_out(dst, "\n", 1); Index: src/fnc.1 ================================================================== --- src/fnc.1 +++ src/fnc.1 @@ -167,12 +167,12 @@ .Nm will display the current value of .Ar setting . With no arguments, .Nm Cm config -will display a list of all configurable settings. -Alternatively, see +will display a list of all user-defined settings. +See .Sx ENVIRONMENT for a detailed list of available settings used in the display or processing of data. When no value is defined for a given setting in the local repository, environment variables will be searched. @@ -189,11 +189,11 @@ are as follows: .Bl -tag -width Ds .It Fl h , -help Display config command help and usage information then exit. .It Fl -ls -List all currently defined settings. +List all available settings. .It Fl R , -repo Ar path Use the .Xr fossil 1 repository at the specified .Ar path Index: src/fnc.c ================================================================== --- src/fnc.c +++ src/fnc.c @@ -50,16 +50,10 @@ #include #include #include #include -#ifdef _WIN32 -#include -#define ssleep(x) Sleep(x) -#else -#define ssleep(x) usleep((x) * 1000) -#endif #include #include #include #include #include @@ -566,11 +560,11 @@ { /* cliflags_config config command related options. */ FCLI_FLAG_BOOL("h", "help", NULL, "Display config command help and usage."), FCLI_FLAG_BOOL(NULL, "ls", &fnc_init.lsconf, - "Display a list of all currently defined settings."), + "Display a list of all available settings."), FCLI_FLAG_CSTR("R", "repo", "", NULL, "Use the fossil(1) repository located at for this config\n" " invocation."), FCLI_FLAG_BOOL("u", "unset", &fnc_init.unset, "Unset (i.e., remove) the specified repository setting."), @@ -804,18 +798,17 @@ struct commit_entry *last_commit_onscreen; struct commit_entry *selected_entry; struct commit_entry *matched_commit; struct commit_entry *search_commit; struct fnc_colours colours; - struct { - fsl_uuid_str ogid; - fsl_uuid_str id; - char *type; - fsl_id_t ogrid; - fsl_id_t rid; - } tagged; - const char *curr_ckout_uuid; + struct timeline_tag { + struct fnc_commit_artifact *one; /* 1st tagged entry */ + struct fnc_commit_artifact *two; /* 2nd tagged entry */ + fsl_uuid_str ogid; /* parent uuid of two */ + fsl_id_t ogrid; /* parent rid of two */ + } tag; + const char *ckout_id; const char *glob; /* Match commits containing glob. */ char *path; /* Match commits involving path. */ int selected; int nscrolled; uint16_t maxx; @@ -1146,23 +1139,23 @@ static int view_input(struct fnc_view **, int *, struct fnc_view *, struct view_tailhead *); static int cycle_view(struct fnc_view *); static int toggle_fullscreen(struct fnc_view **, struct fnc_view *); -static int stash_help(struct fnc_view *, int8_t); +static int stash_help(struct fnc_view *, enum stash_mvmt); static int help(struct fnc_view *); static int padpopup(struct fnc_view *, const char *[][2], - const char **, const char *, int8_t); + const char **, const char *, enum stash_mvmt); static int centerprint(WINDOW *, size_t, size_t, size_t, const char *, chtype); static int tl_input_handler(struct fnc_view **, struct fnc_view *, int); static int move_tl_cursor_down(struct fnc_view *, uint16_t); static void move_tl_cursor_up(struct fnc_view *, uint16_t, bool); static int timeline_scroll_down(struct fnc_view *, int); static void timeline_scroll_up(struct fnc_tl_view_state *, int); -static bool tagged_commit(struct fnc_tl_view_state *); +static int tag_timeline_entry(struct fnc_tl_view_state *); static void select_commit(struct fnc_tl_view_state *); static int request_view(struct fnc_view **, struct fnc_view *, enum fnc_view_id); static int init_view(struct fnc_view **, struct fnc_view *, enum fnc_view_id, int, int); @@ -1207,13 +1200,12 @@ const char *, const fsl_ckout_change_e); static int diff_non_checkin(struct fnc_diff_view_state *); static int diff_file_artifact(struct fnc_diff_view_state *, fsl_id_t, const fsl_card_F *, const fsl_card_F *, const fsl_ckout_change_e); -static int show_diff(struct fnc_view *); -static int write_diff(struct fnc_view *, const char *, - const char *); +static int show_diff_view(struct fnc_view *); +static int draw_diff_headln(struct fnc_view *); static int match_line(const char *, regex_t *, size_t, regmatch_t *); static int draw_matched_line(struct fnc_view *, const char *, int *, int, int, regmatch_t *, attr_t); static void drawborder(struct fnc_view *); @@ -1236,13 +1228,13 @@ static int stash_input_handler(struct fnc_view *, bool *); static void set_choice(struct fnc_diff_view_state *, bool *, struct input *, struct index *, uint32_t *, size_t *, size_t *, bool *, enum stash_opt *); static unsigned char *alloc_bitstring(size_t); -static int generate_prompt(char ***, char *, size_t, short); -static void free_answers(char **); -static bool valid_input(const char *, char **); +static int generate_prompt(char *, char *, size_t, + enum stash_mvmt); +static bool valid_input(const char, char *); static int revert_ckout(bool, bool); static int rm_vfile_renames_cb(fsl_stmt *, void *); static int fnc_patch(struct patch_cx *, const char *); static int scan_patch(struct patch_cx *, FILE *); static int find_patch_file(struct fnc_patch_file **, @@ -1391,11 +1383,11 @@ static int view_close_child(struct fnc_view *); static int close_tree_view(struct fnc_view *); static int close_timeline_view(struct fnc_view *); static int close_diff_view(struct fnc_view *); static void free_index(struct index *, bool); -static void free_tags(struct fnc_tl_view_state *, bool); +static int reset_tags(struct fnc_tl_view_state *); static int view_resize(struct fnc_view *, bool); static bool screen_is_split(struct fnc_view *); static bool screen_is_shared(struct fnc_view *); static void updatescreen(WINDOW *, bool, bool); static void fnc_resizeterm(void); @@ -1423,12 +1415,12 @@ bool); static const char *gettzfile(void); #ifndef HAVE_LANDLOCK static int init_unveil(const char **, const char **, int, bool); #else -static int init_landlock(const char **, const int); -#define init_unveil(_p, _m, _n, _d) init_landlock(_p, _n) +static int init_landlock(const char **, const char **, const int); +#define init_unveil(_p, _m, _n, _d) init_landlock(_p, _m, _n) #endif /* HAVE_LANDLOCK */ static const char *getdirname(const char *, fsl_int_t, bool); static int set_colours(struct fnc_colours *, enum fnc_view_id); static int set_colour_scheme(struct fnc_colours *, const int (*)[2], const char **, int); @@ -1454,21 +1446,14 @@ { const fcli_command *cmd = NULL; char *path = NULL; int rc = FSL_RC_OK; - /* - * XXX Guard against misuse. Will have to take another approach once - * the test harness is finished as we pipe input for our tests cases. - */ - if (!isatty(fileno(stdin))) { - rc = RC(FSL_RC_MISUSE, "invalid input device"); - goto end; - } - - if (!setlocale(LC_CTYPE, "")) - fsl_fprintf(stderr, "[!] Warning: Can't set locale.\n"); + if (!isatty(fileno(stdin))) + errx(1, "stdin is not a tty"); + + setlocale(LC_CTYPE, ""); fnc_init.cmdarg = argv[1]; /* Which cmd to show usage if needed. */ #if DEBUG fcli.clientFlags.verbose = 2; /* Verbose error reporting. */ #endif @@ -1809,18 +1794,10 @@ *child = NULL; } return rc; } -#if 0 -static bool -fnc_path_is_cwd(const char *path) -{ - return (path[0] == '.' && path[1] == '\0'); -} -#endif - static int init_curses(void) { int rc; @@ -1926,38 +1903,24 @@ s->path = fsl_strdup(path); if (s->path == NULL) return RC(FSL_RC_ERROR, "fsl_strdup"); } - /* - * TODO: See about opening this API. - * If a path has been supplied, create a table of all path's - * ancestors and add "AND blob.rid IN fsl_computed_ancestors" to query. - */ - /* if (path[1]) { */ - /* rc = fsl_compute_ancestors(db, rid, 0, 0); */ - /* if (rc) */ - /* return RC(FSL_RC_DB, "fsl_compute_ancestors"); */ - /* } */ - s->thread_cx.q = NULL; - /* s->selected = 0; */ /* Unnecessary? */ - TAILQ_INIT(&s->commits.head); - s->commits.ncommits = 0; + + fsl_ckout_version_info(f, NULL, &s->ckout_id); if (rid) startdate = fsl_mprintf("(SELECT mtime FROM event " "WHERE objid=%d)", rid); - else - fsl_ckout_version_info(f, NULL, &s->curr_ckout_uuid); /* * In 'fnc tl -R ' or 'fnc tl -c ' case, * check that path is a valid path in the tree as at either the * latest checkin or the specified commit. */ - if (s->curr_ckout_uuid == NULL && path) { + if (path) { fsl_uuid_str id = NULL; if (rid) id = fsl_rid_to_uuid(f, rid); @@ -2715,11 +2678,11 @@ break; } } if ((idxstr = fsl_mprintf("%s [%d/%d] %s", - !fsl_strcmp(uuid, s->curr_ckout_uuid) ? " [current]" : "", + !fsl_strcmp(uuid, s->ckout_id) ? " [current]" : "", entry ? entry->idx + 1 : 0, s->commits.ncommits, search_str ? search_str : (branch ? branch : ""))) == NULL) { rc = RC(FSL_RC_RANGE, "fsl_mprintf"); goto end; @@ -2815,19 +2778,23 @@ ncommits = 0; entry = s->first_commit_onscreen; s->last_commit_onscreen = s->first_commit_onscreen; while (entry) { + struct timeline_tag *t = &s->tag; + if (ncommits >= MIN(view->nlines - 1, view->lines - 1)) break; - if (ncommits == s->selected || - entry->commit->rid == s->tagged.rid) + + if ((t->one && !t->two && entry->commit == t->one) || + ncommits == s->selected) wattr_on(view->window, A_REVERSE, NULL); rc = write_commit_line(view, entry->commit, maxlen); - if (ncommits == s->selected || - entry->commit->rid == s->tagged.rid) + if ((t->one && !t->two && entry->commit == t->one) || + ncommits == s->selected) wattr_off(view->window, A_REVERSE, NULL); + ++ncommits; s->last_commit_onscreen = entry; entry = TAILQ_NEXT(entry, entries); } drawborder(view); @@ -3245,15 +3212,21 @@ case 'H': case '?': rc = help(view); break; case 'q': - if (view->parent && view->parent->vid == FNC_VIEW_TIMELINE && - view->mode == VIEW_SPLIT_HRZN) { - /* May need more commits to fill fullscreen. */ - rc = request_tl_commits(view->parent); - view->parent->mode = VIEW_SPLIT_NONE; + if (view->parent && view->parent->vid == FNC_VIEW_TIMELINE) { + rc = reset_tags(&view->parent->state.timeline); + if (rc) + return rc; + if (view->mode == VIEW_SPLIT_HRZN) { + /* may need more commits to fill fullscreen */ + rc = request_tl_commits(view->parent); + if (rc) + return rc; + view->parent->mode = VIEW_SPLIT_NONE; + } } rc = view->input(new, view, ch); view->egress = true; break; case 'f': @@ -3359,16 +3332,16 @@ return rc; } static int -stash_help(struct fnc_view *view, int8_t scroll) +stash_help(struct fnc_view *view, enum stash_mvmt scroll) { char *title = NULL; static const char *keys[][2] = { - {""}, - {""}, + {"", ""}, + {"", ""}, {" b ", " ❬b❭ "}, {" m ", " ❬m❭ "}, {" y ", " ❬y❭ "}, {" n ", " ❬n❭ "}, {" a ", " ❬a❭ "}, @@ -3376,13 +3349,13 @@ {" A ", " ❬A❭ "}, {" K ", " ❬K❭ "}, {" ? ", " ❬?❭ "}, {" q ", " ❬q❭ "}, {" Q ", " ❬Q❭ "}, - {""}, - {""}, - {0} + {"", ""}, + {"", ""}, + {NULL, NULL} }; static const char *desc[] = { "", "Stash", "- scroll back to the previous page^", @@ -3616,11 +3589,11 @@ * title. The pad is contained within a window that is offset four columns in * and two lines down from the parent window. */ static int padpopup(struct fnc_view *view, const char *keys[][2], const char **desc, - const char *title, int8_t stash) + const char *title, enum stash_mvmt stash) { WINDOW *win, *content; FILE *txt; char *line = NULL; ssize_t linelen; @@ -3638,18 +3611,21 @@ * Format help text, and compute longest line and total number of * lines in text to be displayed to determine pad dimensions. */ width = fsl_strlen(title); for (ln = 0; keys[ln][0]; ++ln) { - if ((!stash && (ln == 2 || ln == 3)) || (stash < 1 && ln == 14) - || (stash == 1 && ln == 2) || (stash == 2 && ln == 3)) + if ((stash == STASH_MVMT_NONE && (ln == 2 || ln == 3)) || + (stash == STASH_MVMT_NONE && ln == 14) || + (stash == STASH_MVMT_DOWN && ln == 2) || + (stash == STASH_MVMT_UP && ln == 3)) continue; /* only show available stash keymaps */ + if (keys[ln][1]) { width = MAX((fsl_size_t)width, fsl_strlen(keys[ln][cs]) + fsl_strlen(desc[ln])); } - fsl_fprintf(txt, "%s%s%c", keys[ln][cs], desc[ln], + fprintf(txt, "%s%s%c", keys[ln][cs], desc[ln], keys[ln + 1][0] ? '\n' : 0); } ++width; rewind(txt); @@ -3794,12 +3770,10 @@ { struct fnc_tl_view_state *s = &view->state.timeline; int rc = FSL_RC_OK; uint16_t nscroll = view->nlines - 2; - free_tags(s, true); - switch (ch) { case '0': view->pos.col = 0; break; case '$': @@ -3866,41 +3840,75 @@ s->commits.ncommits; rc = signal_tl_thread(view, 1); } break; case 'C': { - if (s->selected_entry->commit->type[0] != 'c') { - rc = sitrep(view, SR_ALL, + fsl_cx *const f = fcli_cx(); + + if (*s->selected_entry->commit->type != 'c') + return sitrep(view, SR_ALL, "-- requires check-in artifact --"); - break; - } - fsl_cx *const f = fcli_cx(); + + if (!fsl_needs_ckout(f)) + return sitrep(view, SR_ALL, "-- requires work tree --"); + /* * XXX This is not good but I can't think of an alternative * without patching libf: fsl_ckout_changes_scan() returns a * db lock error via fsl_vfile_changes_scan() when versioned * files are modified at runtime. Clear it and notify user. */ rc = fsl_ckout_changes_scan(f); - if (rc == FSL_RC_DB) { - rc = sitrep(view, SR_ALL, "-- checkout db busy --"); - break; - } else if (rc) - return RC(rc, "fsl_ckout_changes_scan"); - if (!fsl_ckout_has_changes(f)) { - sitrep(view, SR_CLREOL | SR_UPDATE | SR_SLEEP, + if (rc) { + if (rc != FSL_RC_DB) + return RC(rc, "fsl_ckout_changes_scan"); + return sitrep(view, SR_ALL, "-- checkout db busy --"); + } + + if (!fsl_ckout_has_changes(f)) + return sitrep(view, SR_CLREOL | SR_UPDATE | SR_SLEEP, "-- no local changes --"); - break; - } + + rc = reset_tags(s); + if (rc) + return rc; s->selected_entry->commit->diff_type = FNC_DIFF_CKOUT; } /* FALL THROUGH */ case ' ': - if (!tagged_commit(s)) - break; - /* FALL THROUGH */ + if (*s->selected_entry->commit->type != 'c' && + *s->selected_entry->commit->type != 'w') + return sitrep(view, SR_ALL, + "-- requires check-in or wiki artifact --"); + + if (s->tag.two != NULL) { + rc = reset_tags(s); + if (rc) + return rc; + } + + rc = tag_timeline_entry(s); + if (rc) { + if (rc == FSL_RC_BREAK) + return FSL_RC_OK; + if (rc == FSL_RC_TYPE) + return sitrep(view, SR_ALL, + "-- tag must be another %s artifact -- ", + *s->tag.one->type == 'c' ? + "check-in" : "wiki"); + if (rc == FSL_RC_MISUSE) + return sitrep(view, SR_ALL, + "-- tag must be another %s wiki page -- ", + strchr(s->tag.one->comment, ':') + 2); + return rc; + } + rc = request_view(new_view, view, FNC_VIEW_DIFF); + break; case KEY_ENTER: case '\r': + rc = reset_tags(s); + if (rc) + return rc; rc = request_view(new_view, view, FNC_VIEW_DIFF); break; case 'b': rc = request_view(new_view, view, FNC_VIEW_BRANCH); break; @@ -3920,16 +3928,28 @@ break; } case 't': if (s->selected_entry == NULL) break; + if (!fsl_rid_is_a_checkin(fcli_cx(), s->selected_entry->commit->rid)) - sitrep(view, SR_CLREOL | SR_UPDATE | SR_SLEEP, + return sitrep(view, SR_CLREOL | SR_UPDATE | SR_SLEEP, "-- tree requires check-in artifact --"); - else - rc = request_view(new_view, view, FNC_VIEW_TREE); + + if (s->path) { + rc = valid_path(s->path, + s->selected_entry->commit->uuid); + if (rc) { + if (rc != FSL_RC_UNKNOWN_RESOURCE) + return rc; + return sitrep(view, SR_ALL, + "-- '%s' not in requested tree --", s->path); + } + } + + rc = request_view(new_view, view, FNC_VIEW_TREE); break; case 'q': s->quit = 1; break; default: @@ -3937,47 +3957,51 @@ } return rc; } -static bool -tagged_commit(struct fnc_tl_view_state *s) -{ - /* If a commit is not already tagged, tag the selected commit. */ - if (!s->tagged.rid) { - /* Only tag checkin and wiki commits */ - if (strcmp(s->selected_entry->commit->type, "checkin") && - strcmp(s->selected_entry->commit->type, "wiki")) - return false; - s->tagged.rid = s->selected_entry->commit->rid; - s->tagged.id = fsl_strdup(s->selected_entry->commit->uuid); - s->tagged.type = fsl_strdup(s->selected_entry->commit->type); - if (s->selected_entry->commit->diff_type != FNC_DIFF_CKOUT) - return false; - } else if (fsl_strcmp(s->selected_entry->commit->type, s->tagged.type)) - return false; /* Can't diff different types */ - else if (s->selected_entry->commit->rid == s->tagged.rid) { - /* Untag currently tagged commit. */ - fsl_free(s->tagged.id); - s->tagged.id = NULL; - fsl_free(s->tagged.type); - s->tagged.type = NULL; - s->tagged.rid = 0; - return false; - } - - /* A commit is tagged, diff selected commit against it. */ - if (s->selected_entry->commit->prid != s->tagged.rid) +static int +tag_timeline_entry(struct fnc_tl_view_state *s) +{ + if (s->selected_entry->commit->diff_type == FNC_DIFF_CKOUT || + s->tag.one == NULL) { + s->tag.one = s->selected_entry->commit; + if (s->tag.one->diff_type != FNC_DIFF_CKOUT) + return FSL_RC_BREAK; + } else if (*s->selected_entry->commit->type != *s->tag.one->type) + return FSL_RC_TYPE; + else if (s->selected_entry->commit->rid == s->tag.one->rid) { + s->tag.one = NULL; /* untag selected entry */ + return FSL_RC_BREAK; + } + + if (*s->tag.one->type == 'w' && + fsl_strcmp(strchr(s->tag.one->comment, ':') + 2, + strchr(s->selected_entry->commit->comment, ':') + 2)) + return FSL_RC_MISUSE; /* don't diff different wiki pages */ + + if (s->selected_entry->commit->prid != s->tag.one->rid) s->showmeta = false; - s->tagged.ogid = fsl_strdup(s->selected_entry->commit->puuid); - s->tagged.ogrid = s->selected_entry->commit->prid; + + if (s->selected_entry->commit->puuid != NULL) { + /* initial commits have no parent, hence the check */ + s->tag.ogid = fsl_strdup(s->selected_entry->commit->puuid); + if (s->tag.ogid == NULL) + return RC(FSL_RC_ERROR, "fsl_strdup"); + } + + s->tag.ogrid = s->selected_entry->commit->prid; + fsl_free(s->selected_entry->commit->puuid); - s->selected_entry->commit->puuid = fsl_strdup(s->tagged.id); - s->selected_entry->commit->prid = s->tagged.rid; - s->tagged.rid = 0; /* Don't continue highlighting tagged commit. */ + s->selected_entry->commit->puuid = fsl_strdup(s->tag.one->uuid); + if (s->selected_entry->commit->puuid == NULL) + return RC(FSL_RC_ERROR, "fsl_strdup"); - return true; + s->selected_entry->commit->prid = s->tag.one->rid; + s->tag.two = s->selected_entry->commit; + + return FSL_RC_OK; } static int move_tl_cursor_down(struct fnc_view *view, uint16_t page) { @@ -4604,36 +4628,22 @@ static int close_timeline_view(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; - int rc = 0; + int rc1, rc2; - rc = join_tl_thread(s); + rc1 = join_tl_thread(s); fsl_stmt_finalize(s->thread_cx.q); - free_tags(s, true); /* Must be before fnc_free_commits() */ + rc2 = reset_tags(s); /* must be before fnc_free_commits() */ fnc_free_commits(&s->commits); free_colours(&s->colours); fsl_free(s->path); s->path = NULL; - return rc; -} - -/* static void */ -/* sspinner(void) */ -/* { */ -/* int idx; */ - -/* while (1) { */ -/* for (idx = 0; idx < 4; ++idx) { */ -/* printf("\b%c", "|/-\\"[idx]); */ -/* fflush(stdout); */ -/* ssleep(SPIN_INTERVAL); */ -/* } */ -/* } */ -/* } */ + return rc1 ? rc1 : rc2; +} static int join_tl_thread(struct fnc_tl_view_state *s) { void *err; @@ -4750,12 +4760,10 @@ s->index.n = 0; s->index.idx = 0; s->hundex.n = 0; s->paths = paths; s->selected_entry = commit; - s->id1 = s->selected_entry->puuid; - s->id2 = s->selected_entry->uuid; s->first_line_onscreen = 1; s->last_line_onscreen = view->nlines; s->selected_line = 1; s->f = NULL; s->view = view; @@ -4784,11 +4792,11 @@ if (s->colour) free_colours(&s->colours); return rc; } - view->show = show_diff; + view->show = show_diff_view; view->input = diff_input_handler; view->close = close_diff_view; view->grep_init = diff_grep_init; view->grep = find_next_match; @@ -4812,10 +4820,12 @@ { char *opt; char ch; long ctx = DEF_DIFF_CTX; int i = 0; + + FLAG_SET(s->diff_flags, FNC_DIFF_STRIP_EOLCR); /* Command line options. */ fnc_init.verbose ? FLAG_SET(s->diff_flags, FNC_DIFF_VERBOSE) : 0; fnc_init.proto ? FLAG_SET(s->diff_flags, FNC_DIFF_PROTOTYPE) : 0; fnc_init.ws ? FLAG_SET(s->diff_flags, FNC_DIFF_IGNORE_ALLWS) : 0; @@ -4950,10 +4960,34 @@ if (!rc && s->selected_entry->diff_type == FNC_DIFF_WIKI) rc = diff_non_checkin(s); if (rc) goto end; + /* + * diff headline label (i.e., diff id1 id2) assignment is delayed till + * now because wiki parent commits are obtained in diff_non_checkin() + */ + if (s->selected_entry->puuid) { + fsl_free(s->id1); + s->id1 = fsl_strdup(s->selected_entry->puuid); + if (s->id1 == NULL) { + rc = RC(FSL_RC_ERROR, "fsl_strdup"); + goto end; + } + } else + s->id1 = NULL; /* initial commit, tag, technote, etc. */ + + if (s->selected_entry->uuid) { + fsl_free(s->id2); + s->id2 = fsl_strdup(s->selected_entry->uuid); + if (s->id2 == NULL) { + rc = RC(FSL_RC_ERROR, "fsl_strdup"); + goto end; + } + } else + s->id2 = NULL; /* local work tree */ + if (s->diff_mode != COMMIT_META) { s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(off_t)); s->index.offset[s->index.n++] = 0; } @@ -6186,86 +6220,37 @@ fsl_buffer_clear(&fbuf2); return rc; } static int -show_diff(struct fnc_view *view) -{ - struct fnc_diff_view_state *s = &view->state.diff; - char *id2, *id1; - enum fnc_diff_type diff = s->selected_entry->diff_type; - - /* Some diffs (e.g., technote, tag) have no parent hash to display. */ - id1 = s->id1 ? s->id1 : "/dev/null"; - - /* If diffing the work tree, we have no hash to display for it. */ - id2 = !s->id2 || diff == FNC_DIFF_CKOUT ? "(checkout)" : s->id2; - - return write_diff(view, id1, id2); -} - -static int -write_diff(struct fnc_view *view, const char *id1, const char *id2) +show_diff_view(struct fnc_view *view) { struct fnc_diff_view_state *s = &view->state.diff; regmatch_t *regmatch = &view->regmatch; struct fnc_colour *c = NULL; wchar_t *wcstr; - char *line; - static char pct[MAX_PCT_LEN]; + char *line = NULL; size_t lidx, linesz = 0; ssize_t linelen; off_t line_offset; attr_t rx = 0; - double percent; - int ln, pctlen; int col, wstrlen, max_lines = view->nlines; int nlines = s->nlines; int nprinted = 0, rc = FSL_RC_OK; bool selected; s->lineno = s->first_line_onscreen - 1; - line_offset = s->line_offsets[s->first_line_onscreen - 1]; + line_offset = s->line_offsets[s->lineno]; if (fseeko(s->f, line_offset, SEEK_SET)) return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "fseeko"); werase(view->window); - ln = s->gtl ? s->gtl : s->lineno + s->selected_line; - percent = 100.00 * ln / nlines; - pctlen = snprintf(pct, MAX_PCT_LEN, "%.*lf%%", - percent > 99.99 ? 0 : 2, percent); - if (pctlen < 0 || pctlen >= MAX_PCT_LEN) - return RC(fsl_errno_to_rc(errno, FSL_RC_RANGE), "snprintf"); - - line = fsl_mprintf("[%d/%d] diff %.40s %.40s", ln, nlines, id1, id2); - if (line == NULL) - return RC(FSL_RC_RANGE, "fsl_mprintf"); - - rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0, false); - fsl_free(line); - line = NULL; + rc = draw_diff_headln(view); if (rc) return rc; - if (screen_is_shared(view) || view->active) - rx = FNC_HIGHLIGHT; - - wattron(view->window, rx); - - waddwstr(view->window, wcstr); - fsl_free(wcstr); - wcstr = NULL; - - col = wstrlen; - while (col++ < view->ncols) - waddch(view->window, ' '); - if (wstrlen < view->ncols - pctlen) - mvwaddstr(view->window, 0, view->ncols - pctlen, pct); - - wattroff(view->window, rx); - if (--max_lines < 1) return rc; s->eof = false; while (max_lines > 0 && nprinted < max_lines) { @@ -6351,10 +6336,73 @@ wstandend(view->window); } return rc; } + +static int +draw_diff_headln(struct fnc_view *view) +{ + struct fnc_diff_view_state *s = &view->state.diff; + wchar_t *wstr; + const char *id1, *id2; + char hdr[view->ncols + 1], pct[MAX_PCT_LEN]; + double percent; + fsl_size_t len = 0; + int col, dlen, ln, n, npct, rc; + attr_t rx = 0; + bool wt = false; + + ln = s->gtl ? s->gtl : s->lineno + s->selected_line; + percent = 100.00 * ln / s->nlines; + + npct = snprintf(pct, MAX_PCT_LEN, "%.*lf%%", + percent > 99.99 ? 0 : 2, percent); + if (npct < 0 || npct >= MAX_PCT_LEN) + return RC(FSL_RC_RANGE, "snprintf"); + + /* some diffs (e.g., technote, tag) have no parent hash to display */ + id1 = s->id1 ? s->id1 : "/dev/null"; + + /* if diffing the work tree, display work tree path (trim trailing /) */ + if (s->selected_entry->diff_type == FNC_DIFF_CKOUT || + fsl_strcmp(s->id1, s->id2) == 0) { + id2 = fsl_cx_ckout_dir_name(fcli_cx(), &len); + wt = true; + } else if (s->id2) + id2 = s->id2; + else + id2 = "/dev/null"; + + dlen = len; + n = snprintf(hdr, view->ncols + 1, "[%d/%zu] diff %.40s %.*s", ln, + s->nlines, id1, wt ? dlen - 1 : 40, id2); + if (n < 0) /* ignore truncation */ + return RC(FSL_RC_RANGE, "snprintf"); + + rc = formatln(&wstr, &n, hdr, view->ncols, 0, false); + if (rc) + return rc; + + if (screen_is_shared(view) || view->active) + rx = FNC_HIGHLIGHT; + + wattron(view->window, rx); + + waddwstr(view->window, wstr); + fsl_free(wstr); + + col = n; + while (col++ < view->ncols) + waddch(view->window, ' '); + if (n < view->ncols - npct) + mvwaddstr(view->window, 0, view->ncols - npct, pct); + + wattroff(view->window, rx); + + return FSL_RC_OK; +} static bool screen_is_shared(struct fnc_view *view) { if (view_is_parent(view)) { @@ -6491,13 +6539,10 @@ static int diff_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch) { struct fnc_view *branch_view; struct fnc_diff_view_state *s = &view->state.diff; - struct fnc_blame_view_state *bs; - struct fnc_tl_view_state *tlstate; - struct commit_entry *previous_selection; char *line = NULL; ssize_t linelen; size_t linesz = 0; int nlines, i = 0, rc = FSL_RC_OK; uint16_t nscroll = view->nlines - 2; @@ -6764,26 +6809,38 @@ case '<': case ',': case 'K': if (s->parent_view == NULL) break; + if (s->parent_view->vid == FNC_VIEW_TIMELINE) { - tlstate = &s->parent_view->state.timeline; - previous_selection = tlstate->selected_entry; + struct fnc_tl_view_state *ts; + struct commit_entry *prev; - rc = tl_input_handler(NULL, s->parent_view, down ? - KEY_DOWN : KEY_UP); + ts = &s->parent_view->state.timeline; + prev = ts->selected_entry; + + rc = tl_input_handler(NULL, s->parent_view, + down ? KEY_DOWN : KEY_UP); + if (rc) + break; + + if (prev == ts->selected_entry) + break; + + if (s->diff_mode == DIFF_PLAIN) + s->diff_mode = COMMIT_META; + + rc = set_selected_commit(s, ts->selected_entry); if (rc) break; - - if (previous_selection == tlstate->selected_entry) - break; - - rc = set_selected_commit(s, tlstate->selected_entry); } else if (s->parent_view->vid == FNC_VIEW_BLAME) { + struct fnc_blame_view_state *bs; + fsl_uuid_cstr prev_id; + bs = &s->parent_view->state.blame; - fsl_uuid_cstr prev_id = bs->selected_entry->uuid; + prev_id = bs->selected_entry->uuid; rc = blame_input_handler(&view, s->parent_view, down ? KEY_DOWN : KEY_UP); if (rc) break; @@ -6793,16 +6850,16 @@ bs->selected_line), prev_id)) break; rc = blame_input_handler(&view, s->parent_view, KEY_ENTER); + if (rc) + break; } - if (!rc) { - s->selected_line = 1; - rc = reset_diff_view(view, false); - } - /* FALL THROUGH */ + s->selected_line = 1; + rc = reset_diff_view(view, false); + break; default: break; } return rc; @@ -7220,15 +7277,28 @@ if (rc) { rc = RC(rc, "revert_ckout"); goto end; } - /* XXX With revert_ckout() finished, we can drop privileges further. */ - rc = init_unveil(((const char *[]){"/usr/bin/patch", "fossil"}), - ((const char *[]){"rx", "rx"}), 2, true); + /* + * XXX Older landlock versions do not allow file reparenting (i.e., + * linking and renaming files into different directories). This makes + * landlock unusable with our interactive stash implementation (see + * apply_patch()) where patches are made to a temporary file in the + * work tree root and renamed into the versioned file, which could be + * in any number of project subdirectories. Disable landlock support + * for the stash command until the newer landlock is ubiquitous. + */ +#ifndef HAVE_LANDLOCK + /* with revert_ckout() finished, we can revoke root dir perms */ + rc = init_unveil( + ((const char *[]){ REPODIR, CKOUTDIR, P_tmpdir, gettzfile() }), + ((const char *[]){ "rwc", "rwc", "rwc", "r" }), 4, true + ); if (rc) goto end; +#endif scx->pcx.context = s->context; scx->pcx.report = true; /* report files with changes stashed */ /* 4. apply patch of hunks selected to stash */ @@ -7282,16 +7352,17 @@ { int rc = FSL_RC_OK; bool stashing = false; rc = stash_input_handler(view, &stashing); + if (rc && rc != FSL_RC_BREAK) + return rc; - if (!rc && !stashing) - rc = RC(FSL_RC_BREAK, "No hunks selected to stash, " - "checkout state unchanged."); + if (!stashing || rc == FSL_RC_BREAK) + return RC(FSL_RC_BREAK, "no hunks stashed, work tree unchanged"); - return rc; + return FSL_RC_OK; } /* * Iterate each hunk of changes in the local checkout, and prompt the user * for their choice with: "stash this hunk (b,m,y,n,a,k,A,K,?)? [y]" @@ -7321,18 +7392,18 @@ enum stash_opt choice = NO_CHOICE; bool lastfile = false; hunks = &s->hundex; nh = &hunks->idx; - in = (struct input){NULL, NULL, INPUT_ALPHA, SR_CLREOL, "X"}; + in = (struct input){NULL, NULL, INPUT_ALPHA, SR_CLREOL, ""}; /* Iterate hunks and prompt user to stash or keep in ckout. */ - while (!rc && *nh < hunks->n) { - char **ans = NULL; - char prompt[64]; - int len; - enum { NONE, DOWN, UP, BOTH } scroll; + while (*nh < hunks->n) { + char ans[10]; + char prompt[64]; + int len; + enum stash_mvmt scroll; /* * If not yet in the last file of the diff and the next hunk * to choose is in the next file, reset any sticky file choice. */ if (!lastfile && nxt && hunks->lineno[*nh] >= nxt) { @@ -7352,133 +7423,123 @@ hl = s->first_line_onscreen; /* current hunk start line */ redraw: rc = view->show(view); if (rc) return rc; + updatescreen(view->window, true, true); keypad(view->window, false); /* don't accept arrow keys */ + memset(prompt, '\0', sizeof(prompt)); last = s->last_line_onscreen; + len = snprintf(prompt, sizeof(prompt), "[%u/%u] stash this hunk (", hunks->idx + 1, hunks->n); if (len < 0 || (len > 0 && (size_t)len >= sizeof(prompt))) - return RC(fsl_errno_to_rc(errno, FSL_RC_RANGE), - "snprintf"); - scroll = NONE; + return RC(FSL_RC_RANGE, "snprintf"); + + scroll = STASH_MVMT_NONE; /* Enable 'b,m' answers if hunk occupies multiple pages. */ if (s->first_line_onscreen > hl) - scroll = UP; - if ((*nh < hunks->n - 1 && ((nf == s->index.n - 1 && - hunks->lineno[*nh] >= s->index.lineno[nf] + 5) || - last < s->index.lineno[nf]) && - last < hunks->lineno[*nh + 1]) || - (*nh == hunks->n - 1 && last < s->nlines)) - scroll = scroll ? BOTH : DOWN; - - rc = generate_prompt(&ans, prompt, sizeof(prompt), scroll); - if (rc) { - free_answers(ans); + scroll = STASH_MVMT_UP; + if (*nh < hunks->n - 1 && hunks->lineno[*nh + 1] > last) + ++scroll; + else if (*nh == hunks->n - 1 && last < s->nlines) + ++scroll; + + rc = generate_prompt(ans, prompt, sizeof(prompt), scroll); + if (rc) return RC(rc, "generate_prompt(%s)", STRINGIFY(scroll)); - } + in.prompt = prompt; - do { - if (choice || valid_input(in.buf, ans)) - break; /* ongoing persistent answer */ - + while (!choice && !valid_input(*in.buf, ans)) { rc = fnc_prompt_input(view, &in); + if (rc) + return rc; - if (*in.buf == '?' || *in.buf == 'H' || - (int)*in.buf == (KEY_F(1))) + if (*in.buf == '\0') + *in.buf = 'y'; + else if (strlen(in.buf) > 1) + *in.buf = '\0'; + else if (*in.buf == '?' || *in.buf == 'H' || + (int)*in.buf == (KEY_F(1))) { rc = stash_help(view, scroll); + if (rc) + return rc; + } - } while (!rc && !choice && !valid_input(in.buf, ans)); + } - free_answers(ans); if (*in.buf == 'm' || *in.buf == 'b') { /* scroll pgup/pgdn */ s->first_line_onscreen = *in.buf == 'm' ? s->last_line_onscreen : MAX(hl, s->first_line_onscreen - view->nlines + 2); - *in.buf = 'X'; + *in.buf = '\0'; goto redraw; } set_choice(s, stashing, &in, hunks, nh, &nxt, &nf, &lastfile, &choice); } - return rc; + + return FSL_RC_OK; } /* - * Construct a set of valid answers based on scroll and assign to *ptr, and - * generate the corresponding prompt containing the available answers from - * which to choose. *ptr must eventually be disposed of by the caller. + * Construct string ans of valid answers based on scroll, and generate the + * corresponding prompt with the available answers from which to choose. */ static int -generate_prompt(char ***ptr, char *prompt, size_t sz, short scroll) +generate_prompt(char *ans, char *prompt, size_t sz, enum stash_mvmt scroll) { - char **ans; - char *opts[10] = {"b", "m", "y", "n", "a", "k", "A", "K", "", NULL}; - size_t n, oi, ai = 0; + char a[] = "bmynakAK"; + size_t ai, pi; /* Set valid answers. */ switch (scroll) { - case 3: /* BOTH "bmynakAK?" */ - oi = 0; - n = 10; - break; - case 2: /* UP "bynakAK?" */ - opts[0] = "m"; - opts[1] = "b"; - /* FALL THROUGH */ - case 1: /* DOWN "mynakAK?" */ - oi = 1; - n = 9; - break; - case 0: /* NONE "ynakAK?" */ - oi = 2; - n = 8; - break; - } - - /* Generate valid answers array. */ - ans = calloc(n, sizeof(*ans)); - if (ans == NULL) - return RC(FSL_RC_ERROR, "calloc"); - while (ai < n) - ans[ai++] = fsl_strdup(opts[oi++]); - - /* Generate prompt string. */ - for (ai = 0; ai < n - 2; ++ai) { - char *t = fsl_mprintf("%s%c", ans[ai], ','); - if (t == NULL) - return RC(FSL_RC_ERROR, "fsl_mprintf"); - if (fsl_strlcat(prompt, t, sz) >= sz) - return RC(FSL_RC_RANGE, "fsl_strlcat(%s, %s, %lu)", - prompt, t, sz); - fsl_free(t); - } - if (fsl_strlcat(prompt, "?)? [y] ", sz) >= sz) - return RC(FSL_RC_RANGE, "fsl_strlcat(%s, %s, %lu)", - prompt, "?)? [y] ", sz); - - *ptr = ans; - return FSL_RC_OK;; + case STASH_MVMT_UPDOWN: + break; + case STASH_MVMT_UP: + memmove(a + 1, a + 2, 7); + break; + case STASH_MVMT_DOWN: + memmove(a, a + 1, 8); + break; + case STASH_MVMT_NONE: + memmove(a, a + 2, 7); + break; + } + + /* generate prompt string */ + pi = strlen(prompt); + for (ai = 0; a[ai]; ++ai) { + prompt[pi++] = a[ai]; + prompt[pi++] = ','; + } + + if (memccpy(prompt + pi, "?)? [y] ", '\0', sz) == NULL) + return RC(FSL_RC_RANGE, "memccpy"); + + if (memccpy(ans, a, '\0', sizeof(a)) == NULL) + return RC(FSL_RC_RANGE, "memccpy"); + + return FSL_RC_OK; } /* * Return true if in is found in valid, else return false. * valid must be terminated with a sentinel (NULL) pointer. */ static bool -valid_input(const char *in, char **valid) +valid_input(const char in, char *valid) { size_t idx; for (idx = 0; valid[idx]; ++idx) - if (!fsl_strcmp(in, valid[idx])) + if (in == valid[idx]) return true; return false; } @@ -7534,20 +7595,10 @@ ++(*nf); ++(*nh); } -static void -free_answers(char **ans) -{ - size_t i = 0; - - while (ans[i]) - fsl_free(ans[i++]); - fsl_free(ans); -} - /* * Revert the current checkout. If renames is set, don't revert files that are * renamed with _no_ changes. If scan is set, scan for changes before reverting. */ static int @@ -8671,49 +8722,10 @@ h = NULL; } fsl_free(p); } -/* - * exec(2) arg[0] with args arg after a fork(2). Optionally wait wait seconds - * for child process to complete; set to zero or a negative integer to not wait. - */ -#if 0 -static int -fnc_execp(const char *const *arg, const int wait) -{ - pid_t pid; - int status, timeout, rc = FSL_RC_OK; - - pid = fork(); - if (pid == -1) - return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "fork"); - else if (pid == 0) - if (execvp(arg[0], (char *const *)arg) == -1) - return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), - "ecexve"); - - if (wait < 1) - return rc; - - timeout = wait; - - while (waitpid(pid , &status , WNOHANG) == 0) { - if ( --timeout < 0 ) - return RC(FSL_RC_RANGE, "timeout for child [%d]", pid); - sleep(1); - } - - if (WIFEXITED(status) != 1 || WEXITSTATUS(status) != 0) - return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), - "%s: WEXITSTATUS %d | WIFEXITED %d [status %d]", arg[0], - WEXITSTATUS(status), WIFEXITED(status), status); - - return rc; -} -#endif - /* * Create new stash of changes in checkout vid with stash message msg. */ static int f__stash_create(const char *msg, int vid) @@ -9036,15 +9048,26 @@ } static int set_selected_commit(struct fnc_diff_view_state *s, struct commit_entry *entry) { - s->id2 = entry->commit->uuid; - s->id1 = entry->commit->puuid; + fsl_free(s->id2); + s->id2 = fsl_strdup(entry->commit->uuid); + if (s->id2 == NULL) + return RC(FSL_RC_ERROR, "fsl_strdup"); + + fsl_free(s->id1); + if (entry->commit->puuid) { + s->id1 = fsl_strdup(entry->commit->puuid); + if (s->id1 == NULL) + return RC(FSL_RC_ERROR, "fsl_strdup"); + } else + s->id1 = NULL; + s->selected_entry = entry->commit; - return 0; + return FSL_RC_OK; } static void diff_grep_init(struct fnc_view *view) { @@ -9190,10 +9213,14 @@ struct fnc_diff_view_state *s = &view->state.diff; int rc = 0; if (s->f && fclose(s->f) == EOF) rc = RC(fsl_errno_to_rc(errno, FSL_RC_IO), "fclose"); + fsl_free(s->id1); + s->id1 = NULL; + fsl_free(s->id2); + s->id2 = NULL; fsl_free(s->line_offsets); free_colours(&s->colours); s->line_offsets = NULL; s->nlines = 0; free_index(&s->index, true); @@ -9213,33 +9240,40 @@ index->lineno = NULL; fsl_free(index->offset); index->offset = NULL; } -static void -free_tags(struct fnc_tl_view_state *s, bool restore) -{ - if (!s->tagged.ogrid) - return; - - /* Restore timeline commit queue entry. */ - if (restore) { - fsl_free(s->selected_entry->commit->puuid); - s->selected_entry->commit->puuid = fsl_strdup(s->tagged.ogid); - s->selected_entry->commit->prid = s->tagged.ogrid; - s->selected_entry->commit->diff_type = FNC_DIFF_COMMIT; - fsl_free(s->tagged.ogid); - s->tagged.ogid = NULL; - s->tagged.ogrid = 0; - s->showmeta = true; - } - - fsl_free(s->tagged.type); - s->tagged.type = NULL; - fsl_free(s->tagged.id); - s->tagged.id = NULL; - s->tagged.rid = 0; +static int +reset_tags(struct fnc_tl_view_state *s) +{ + struct timeline_tag *t = &s->tag; + struct fnc_commit_artifact *c = t->two; + + t->one = NULL; + + if (!t->ogrid) + return FSL_RC_OK; + + fsl_free(c->puuid); + c->puuid = NULL; + + /* restore commit's original parent */ + if (t->ogid != NULL) { + c->puuid = fsl_strdup(t->ogid); + fsl_free(t->ogid); + t->ogid = NULL; + if (c->puuid == NULL) + return RC(FSL_RC_ERROR, "fsl_strdup"); + } + + c->diff_type = *c->type == 'c' ? FNC_DIFF_COMMIT : FNC_DIFF_WIKI; + c->prid = t->ogrid; + t->ogrid = 0; + t->two = NULL; + s->showmeta = true; + + return FSL_RC_OK; } static void fnc_resizeterm(void) { @@ -9631,21 +9665,25 @@ commit->diff_type = diff_type; rc = init_curses(); if (rc) goto end; + + /* XXX see comment in fnc_stash() for why landlock is disabled */ +#ifndef HAVE_LANDLOCK /* - * XXX revert_ckout:fsl_ckout_revert()^ walks the tree from / to the - * ckout stat(2)ing every dir so we need rx on root. Revoke privileges - * on root after returning from revert_ckout(). + * XXX revert_ckout:fsl_ckout_revert()^ walks the tree from + * / to the ckout stat(2)ing every dir so we need rx on root. + * Revoke root privileges after returning from revert_ckout(). * ^fsl__vfile_to_ckout:fsl_mkdir_for_file:fsl_dir_check() */ rc = init_unveil(((const char *[]){"/", REPODIR, CKOUTDIR, P_tmpdir, gettzfile()}), ((const char *[]){"rx", "rwc", "rwc", "rwc", "r"}), 5, false); if (rc) goto end; +#endif view = view_open(0, 0, 0, 0, FNC_VIEW_DIFF); if (view == NULL) { rc = RC(FSL_RC_ERROR, "view_open"); goto end; @@ -9653,11 +9691,11 @@ rc = open_diff_view(view, commit, NULL, NULL, diff_mode); if (rc) goto end; - rc = show_diff(view); + rc = show_diff_view(view); if (rc) goto end; rc = fnc_stash(view); if (rc) goto end; @@ -9847,14 +9885,20 @@ valid_path(const char *path, const char *sym) { fsl_cx *const f = fcli_cx(); fsl_deck d = fsl_deck_empty; const fsl_card_F *cf; + fsl_uuid_str id; + fsl_id_t rid; int rc; bool valid = false; - rc = fsl_deck_load_sym(f, &d, sym, FSL_SATYPE_CHECKIN); + rc = fsl_sym_to_uuid(f, sym, FSL_SATYPE_CHECKIN, &id, &rid); + if (rc) + goto end; + + rc = fsl_deck_load_rid(f, &d, rid, FSL_SATYPE_CHECKIN); if (rc) goto end; valid = !!(cf = fsl_deck_F_search(&d, path)); if (valid) @@ -9875,12 +9919,13 @@ } while (cf && !valid); end: fsl_deck_finalize(&d); if (!valid && !rc) - rc = RC(FSL_RC_UNKNOWN_RESOURCE, "path not found in [%s]: %s", - sym, path); + rc = RC(FSL_RC_UNKNOWN_RESOURCE, "path not found in [%S]: %s", + id, path); + fsl_free(id); return rc; } static int browse_commit_tree(struct fnc_view **new_view, int start_col, int start_ln, @@ -10255,36 +10300,10 @@ (*tree)->nentries = i; return FSL_RC_OK; } -#if 0 -static void -delete_tree_node(struct fnc_tree_entry **head, struct fnc_tree_entry *del) -{ - struct fnc_tree_entry *temp = *head, *prev; - - if (temp == del) { - *head = temp->next; - fsl_free(temp); - return; - } - - while (temp != NULL && temp != del) { - prev = temp; - temp = temp->next; - } - - if (temp == NULL) - return; - - prev->next = temp->next; - - fsl_free(temp); -} -#endif - /* * This routine inserts nodes into the doubly-linked repository tree. Each * path component of path (i.e., tokens delimited by '/') becomes a node in * tree. The final path component of each segment is the node's .basename, and * its full repository relative path its .path. All files in a given directory @@ -11289,11 +11308,11 @@ opt = fcli_next_arg(true); if (opt == NULL || fnc_init.lsconf) { if (fnc_init.unset) return RC(FSL_RC_MISSING_INFO, "-u|--unset requires "); - return fnc_conf_lsopt(fnc_init.lsconf ? false : true); + return fnc_conf_lsopt(fnc_init.lsconf); } setid = fnc_conf_str2enum(opt); if (!setid) return RC(FSL_RC_NOT_FOUND, "invalid setting: %s", opt); @@ -11331,11 +11350,11 @@ fsl_free(value); } if (!last && !all) { f_out("No user-defined settings: " - "'%s config' for list of available settings.", + "'%s config --ls' for list of available settings.", fcli_progname()); return 0; } for (idx = FNC_START_SETTINGS + 1; idx < FNC_EOF_SETTINGS; ++idx) { @@ -14010,11 +14029,11 @@ /* * Similar to unveil(), grant read and write permissisions to the repo and * ckout files, and create permissions to the ckout, repo, and tmp dirs. */ static int -init_landlock(const char **paths, const int n) +init_landlock(const char **paths, const char **perms, const int n) { #define LANDLOCK_ACCESS_DIR (LANDLOCK_ACCESS_FS_READ_FILE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_REMOVE_FILE | \ LANDLOCK_ACCESS_FS_READ_DIR | \