fnc

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From 0.13 To 0.14

2023-04-17 15:29
bump version number: 0.15 (check-in: c6cd3983d8 user: mark tags: trunk)
2023-04-17 14:10
CHANGES for 0.14 (check-in: 571ae0eed4 user: mark tags: trunk, 0.14)
2023-04-17 13:40
latest upstream libfossil: 9a8269d737 (check-in: b324288c18 user: mark tags: trunk)
2022-11-26 06:01
bump version number: 0.14 (check-in: be94040192 user: mark tags: trunk)
2022-11-26 06:00
CHANGES for 0.13 (check-in: 0b85faeb8a user: mark tags: trunk, 0.13)
2022-11-25 05:15
latest upstream libfossil: 46008704a620 (check-in: f745a2bcb8 user: mark tags: trunk)

Changes to CHANGES.md.






















1
2
3
4
5
6
7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







**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 <symbol>`
- 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
- fix `fnc blame -r` and make it behave like `fossil blame -o` for familiarity
- improve `fnc timeline <path>` lookup of repository paths not in the work tree
292
293
294
295
296
297
298


299
300
301
302
303
304
305
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328







+
+







- wrap commit comments to the current view width
- implement cmd_diff() to provide the 'fnc diff' interface
- 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
[0.11b]: https://fnc.bsdbox.org/vdiff?from=0.10&to=0.11
[0.10a]: https://fnc.bsdbox.org/timeline?p=0.10&bt=0.9

Changes to fnc.bld.mk.

1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
1
2
3
4
5
6
7
8

9
10
11
12
13
14
15
16








-
+







#
# FNC Common Build
#

# 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 \
		-DNDEBUG=1 \
		-DSQLITE_DQS=0 \

Changes to include/diff.h.

21
22
23
24
25
26
27
28

29
30
31
32
33
34
35







36
37
38
39
40
41
42
43
21
22
23
24
25
26
27

28
29






30
31
32
33
34
35
36

37
38
39
40
41
42
43







-
+

-
-
-
-
-
-
+
+
+
+
+
+
+
-







 * Flags set by callers of the below diff APIs to determine diff output.
 */
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_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
	FNC_DIFF_PROTOTYPE	= 1 << 12  /* show func sig in hunk header */
#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 */
};

/*
 * Compute the diff of changes to convert the file in fsl_buffer parameter 1

Changes to include/settings.h.

81
82
83
84
85
86
87






88
89
90
91
92
93
94


95
96
97
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

100
101
102
103
104







+
+
+
+
+
+






-
+
+



	_(pfx, DIFF_HUNK),					\
	_(pfx, DIFF_SEPARATOR)

#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)

Changes to lib/libfossil-config.h.

79
80
81
82
83
84
85
86
87
88



89
90
91
92
93
94
95
79
80
81
82
83
84
85



86
87
88
89
90
91
92
93
94
95







-
-
-
+
+
+







#if !defined(HAVE_STDINT_H)
#  define HAVE_STDINT_H 1
#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"
#define FSL_PLATFORM_PATH_SEPARATOR ";"
#define FSL_CHECKOUTDB_NAME "./_FOSSIL_"

Changes to lib/libfossil.c.

6512
6513
6514
6515
6516
6517
6518











6519

6520
6521
6522
6523
6524
6525
6526
6512
6513
6514
6515
6516
6517
6518
6519
6520
6521
6522
6523
6524
6525
6526
6527
6528
6529

6530
6531
6532
6533
6534
6535
6536
6537







+
+
+
+
+
+
+
+
+
+
+
-
+







                        "CASE vmerge.id WHEN -1 THEN '+' ELSE '-' END || mhash,"
                        "  merge"
                        "  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);
      /*
        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 ){
      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 );
      }
    }
    fsl_stmt_finalize(&q);
    RC;
14302
14303
14304
14305
14306
14307
14308
14309
14310



14311
14312
14313
14314
14315
14316
14317
14313
14314
14315
14316
14317
14318
14319


14320
14321
14322
14323
14324
14325
14326
14327
14328
14329







-
-
+
+
+








void fsl_cx_caches_reset(fsl_cx * const f){
  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{
    fsl__cx_ckout_clear(f);
  }
14618
14619
14620
14621
14622
14623
14624
14625

14626
14627
14628
14629
14630
14631
14632
14630
14631
14632
14633
14634
14635
14636

14637
14638
14639
14640
14641
14642
14643
14644







-
+







        break;
      default:
        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);
      if(db->role & counterpart){
        /* When closing the main db, detach the counterpart db first
14759
14760
14761
14762
14763
14764
14765

14766

14767
14768
14769
14770
14771
14772
14773
14774
14775
14776
14777

14778
14779
14780
14781
14782
14783
14784
14771
14772
14773
14774
14775
14776
14777
14778

14779
14780
14781
14782
14783
14784
14785
14786
14787
14788
14789

14790
14791
14792
14793
14794
14795
14796
14797







+
-
+










-
+







  if(fsl_cx_transaction_level(f)){
    /* TODO???: force a full rollback and close it */
    //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{
    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);
}

int fsl_ckout_close( fsl_cx * const f ){
15121
15122
15123
15124
15125
15126
15127

15128
15129
15130
15131
15132
15133
15134
15134
15135
15136
15137
15138
15139
15140
15141
15142
15143
15144
15145
15146
15147
15148







+








/**
   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;
}


static void fsl_cx_fetch_hash_policy(fsl_cx * f){
35250
35251
35252
35253
35254
35255
35256
35257

35258
35259
35260
35261
35262
35263
35264
35265
35266
35267
35268

35269
35270
35271
35272
35273
35274
35275
35264
35265
35266
35267
35268
35269
35270

35271
35272
35273
35274
35275
35276
35277
35278
35279
35280
35281

35282
35283
35284
35285
35286
35287
35288
35289







-
+










-
+







  if(!rc){
    rc = fsl_db_exec(db,
                     "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');"
                        "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 */;
  int rc = 0;
  char const * userName = 0;
36974
36975
36976
36977
36978
36979
36980






36981
36982
36983






36984
36985
36986
36987
36988









36989
36990
36991
36992
36993
36994
36995
36996
36997
36998
36999
37000
37001
37002
37003
37004
37005

37006


37007
37008
37009
37010
37011
37012
37013

37014
37015
37016
37017
37018


37019
37020


37021
37022

37023
37024
37025
37026

37027
37028
37029
37030
37031
37032
37033
37034
37035
37036

37037
37038

37039
37040
37041
37042
37043
37044
37045
36988
36989
36990
36991
36992
36993
36994
36995
36996
36997
36998
36999
37000



37001
37002
37003
37004
37005
37006





37007
37008
37009
37010
37011
37012
37013
37014
37015
37016
37017
37018
37019
37020
37021
37022
37023
37024
37025
37026
37027
37028
37029
37030
37031
37032
37033

37034
37035
37036
37037
37038
37039
37040
37041

37042
37043
37044
37045
37046
37047
37048
37049


37050
37051
37052

37053
37054
37055
37056

37057
37058
37059
37060
37061
37062
37063
37064
37065
37066

37067
37068

37069
37070
37071
37072
37073
37074
37075
37076







+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+

















+
-
+
+






-
+





+
+
-
-
+
+

-
+



-
+









-
+

-
+








/* Only for debugging */
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)

/**
   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 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),
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_DBROLE_REPO,
                                                     "ftsdocs")
      ? 1 : 0;
  }
  return f->cache.searchIndexExists ? true : false;
        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';
    case FSL_SATYPE_WIKI: return 'w';
    case FSL_SATYPE_TICKET: return 't';
    case FSL_SATYPE_FORUMPOST: return 'f';
    case FSL_SATYPE_TECHNOTE: return 'e';
    default:
      assert(!"Internal misuse of fsl_satype_letter()");
      return 0;
  }
}

int fsl__search_doc_touch(fsl_cx * const f, fsl_satype_e saType,
                         fsl_id_t rid, const char * docName){
  const int ftsVers = fsl__search_ndx_exists(f, false);
  if(!fsl_search_ndx_exists(f) || fsl_content_is_private(f, rid)) return 0;
  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 = 0;
  char const * zDocId = 4==ftsVers ? "docid" : "rowid";
  int rc = fsl_db_exec(db,
       "DELETE FROM ftsidx WHERE docid IN"
  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 */
  end:
  return rc;

Changes to lib/libfossil.h.

11908
11909
11910
11911
11912
11913
11914
11915
11916
11917



11918
11919
11920
11921


11922
11923
11924
11925
11926
11927
11928
11908
11909
11910
11911
11912
11913
11914



11915
11916
11917
11918
11919


11920
11921
11922
11923
11924
11925
11926
11927
11928







-
-
-
+
+
+


-
-
+
+








/**
   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
   NUL). If md5==NULL the current R value is cleared.

15992
15993
15994
15995
15996
15997
15998




15999
16000
16001
16002
16003
16004
16005
15992
15993
15994
15995
15996
15997
15998
15999
16000
16001
16002
16003
16004
16005
16006
16007
16008
16009







+
+
+
+







  fsl_checkin_queue_f callback;

  /**
     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;

/** Initialized-with-defaults fsl_checkin_queue_opt structure, intended for
    const-copy initialization. */

Deleted signify/fnc-12-release.pub.

1
2


-
-
untrusted comment: fnc 0.12 public key
RWRtqBYmaSguXJPDRSFPvxVuPNby5zfTslX9JA0pJlSMnOpDxW7WVHAQ

Added signify/fnc-14-release.pub.



1
2
+
+
untrusted comment: fnc 0.14 public key
RWTOJ8pPCpW9YMEsLrgLmTRk0huOrEnyiLZRYo36C/9JQF/H3JnCGpFc

Changes to src/diff.c.

96
97
98
99
100
101
102
103

104
105
106
107
108
109
110
96
97
98
99
100
101
102

103
104
105
106
107
108
109
110







-
+







struct diff_out_state {
	fsl_output_f	 out;		/* Output callback */
	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 */
		fsl_size_t		 offset;	/* Match byte offset */
	} proto;
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
145
130
131
132
133
134
135
136

137

138
139
140
141
142
143
144







-
+
-







			    fsl_output_f, void *, enum line_type **, uint32_t *,
			    /* void *regex, */ uint16_t, short, int, int **);
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,
static int		 match_hunk_function(struct hunk_scope *, fsl_int_t);
			    uint32_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); */
static int		 match_dline(fsl_dline *, fsl_dline *);
static bool		 find_lcs(const char *z, int, const char *, int, int *);
648
649
650
651
652
653
654
655
656


657
658
659
660





661




662
663
664
665
666










667
668
669

670
671

672
673
674
675
676
677


678
679
680
681
682
683
684
685
686
687
688
689
690

691
692
693
694
695
696
697
647
648
649
650
651
652
653


654
655

656
657

658
659
660
661
662
663
664
665
666
667





668
669
670
671
672
673
674
675
676
677

678

679


680
681
682


683

684
685
686
687
688
689
690
691
692
693
694
695
696
697

698
699
700
701
702
703
704
705







-
-
+
+
-


-
+
+
+
+
+

+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-

-
+
-
-
+


-
-

-
+
+












-
+







 * Starting from seek lines, copy n lines from src to dst. The seek starts
 * from offset bytes into src->mem. This routine does _not_ modify src.
 */
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;
	const char	*z = (const char *)src->mem;
	fsl_size_t	 i, start, ln;
	int rc = FSL_RC_OK;

	if (!n)
		return rc;
		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 (;;) {
	while (idx < src->used) {
		if (z[idx] == '\n') {
			if (++ln == seek)
				start = idx + 1;  /* skip leading '\n' */
			if (ln == seek + n) {
		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)
				++idx;
				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
 * corresponding int value.  GNU C and MSVC allow '$' in identifier names:
 *   https://gcc.gnu.org/onlinedocs/gcc/Dollar-Signs.html
 *   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;
		if (starts_with(str, "private"))
			return VSL_PRIVATE;
707
708
709
710
711
712
713
714

715
716
717
718
719

720
721
722
723


724
725
726
727
728




729
730
731
732
733
734
735
736
737
738
739


740
741
742
743


744
745
746
747


748
749


750
751


752
753
754
755
756
757
758


759
760
761
762
763
764

765
766
767
768
769
770
771
715
716
717
718
719
720
721

722
723
724
725
726

727
728
729


730
731
732




733
734
735
736
737
738


739
740
741
742
743


744
745
746
747


748
749
750
751


752
753
754
755
756
757


758
759
760
761
762
763
764


765
766
767
768
769
770
771

772
773
774
775
776
777
778
779







-
+




-
+


-
-
+
+

-
-
-
-
+
+
+
+


-
-





-
-
+
+


-
-
+
+


-
-
+
+


+
+
-
-
+
+





-
-
+
+





-
+







/*
 * Back scan the diffed file dst->proto.file from line pos, which is the first
 * 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:
			line[strcspn(line, "\n")] = '\0';
			line[strcspn(line, "\r")] = '\0';  /* fossil wiki */
			fsl_free(dst->proto.sig);
			dst->proto.sig = line;
			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;
}

/*
1363
1364
1365
1366
1367
1368
1369

1370

1371
1372

1373
1374
1375
1376

1377
1378
1379



1380
1381
1382
1383
1384
1385


1386
1387
1388
1389
1390
1391
1392
1393
1371
1372
1373
1374
1375
1376
1377
1378

1379
1380

1381

1382
1383
1384
1385



1386
1387
1388

1389
1390
1391


1392
1393

1394
1395
1396
1397
1398
1399
1400







+
-
+

-
+
-



+
-
-
-
+
+
+
-



-
-
+
+
-







			if (html) {
				rc = diff_outf(dst, "</span>");
				if (rc)
					return rc;
			}

			if (proto && li + skip > 1) {
				struct hunk_scope	*hs = &dst->proto;
				int n = 55;
				int			 n = 55;

				rc = match_hunk_function(dst,
				rc = match_hunk_function(hs, li + skip + context);
				    li + skip + context);
				if (rc)
					return rc;

				if (hs->spec)
				n -= fsl_strlen(dst->proto.spec);
				if (dst->proto.sig) {
					rc = diff_outf(dst, " %.*s", n,
					n -= fsl_strlen(hs->spec);
				if (hs->sig) {
					rc = diff_outf(dst, " %.*s", n, hs->sig);
					    dst->proto.sig);
					if (rc)
						return rc;
				}
				if (dst->proto.spec) {
					rc = diff_outf(dst, " %s",
				if (hs->spec) {
					rc = diff_outf(dst, " %s", hs->spec);
					    dst->proto.spec);
					if (rc)
						return rc;
				}
			}
			rc = diff_out(dst, "\n", 1);
			if (rc)
				return rc;

Changes to src/fnc.1.

165
166
167
168
169
170
171
172
173


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194

195
196
197
198
199
200
201
165
166
167
168
169
170
171


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

194
195
196
197
198
199
200
201







-
-
+
+




















-
+







.Ar setting ,
otherwise
.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.
If still not found,
.Nm
will fallback to default values.
Unless the
.Sy --repo
option is used, this command must be invoked from within a work tree; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
Options for
.Nm Cm config
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
for the current
.Nm Cm config

Changes to src/fnc.c.

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
48
49
50
51
52
53
54






55
56
57
58
59
60
61







-
-
-
-
-
-








#include <sys/queue.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef _WIN32
#include <windows.h>
#define ssleep(x) Sleep(x)
#else
#define ssleep(x) usleep((x) * 1000)
#endif
#include <fcntl.h>
#include <ctype.h>
#include <curses.h>
#include <panel.h>
#include <locale.h>
#include <stdlib.h>
#include <stdarg.h>
564
565
566
567
568
569
570
571

572
573
574
575
576
577
578
558
559
560
561
562
563
564

565
566
567
568
569
570
571
572







-
+







	    fcli_cliflag_empty_m
	}, /* End cliflags_blame. */

	{ /* 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", "<path>", NULL,
	    "Use the fossil(1) repository located at <path> for this config\n"
	    "    invocation."),
	    FCLI_FLAG_BOOL("u", "unset", &fnc_init.unset,
	    "Unset (i.e., remove) the specified repository setting."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_tree. */
802
803
804
805
806
807
808
809
810
811




812
813
814
815
816



817
818
819
820
821
822
823
796
797
798
799
800
801
802



803
804
805
806





807
808
809
810
811
812
813
814
815
816







-
-
-
+
+
+
+
-
-
-
-
-
+
+
+







	struct commit_queue	 commits;
	struct commit_entry	*first_commit_onscreen;
	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;
	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 */
		char		*type;
		fsl_id_t	 ogrid;
		fsl_id_t	 rid;
	} tagged;
	const char		*curr_ckout_uuid;
		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;
	sig_atomic_t		 quit;
	pthread_t		 thread_id;
1144
1145
1146
1147
1148
1149
1150
1151

1152
1153
1154

1155
1156
1157
1158
1159
1160
1161
1162
1163

1164
1165
1166
1167
1168
1169
1170
1137
1138
1139
1140
1141
1142
1143

1144
1145
1146

1147
1148
1149
1150
1151
1152
1153
1154
1155

1156
1157
1158
1159
1160
1161
1162
1163







-
+


-
+








-
+







static int		 write_commit_line(struct fnc_view *,
			    struct fnc_commit_artifact *, int);
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);
static enum view_mode	 view_get_split(struct fnc_view *, int *, int *);
static int		 split_view(struct fnc_view *, int *);
1205
1206
1207
1208
1209
1210
1211
1212
1213


1214
1215
1216
1217
1218
1219
1220
1221
1198
1199
1200
1201
1202
1203
1204


1205
1206

1207
1208
1209
1210
1211
1212
1213







-
-
+
+
-







static int		 diff_file(struct fnc_diff_view_state *, fsl_buffer *,
			    const char *, const char *, fsl_uuid_cstr,
			    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 *,
static int		 show_diff_view(struct fnc_view *);
static int		 draw_diff_headln(struct fnc_view *);
			    const char *);
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 *);
static int		 diff_input_handler(struct fnc_view **,
			    struct fnc_view *, int);
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243



1244
1245
1246
1247
1248
1249
1250
1226
1227
1228
1229
1230
1231
1232



1233
1234
1235
1236
1237
1238
1239
1240
1241
1242







-
-
-
+
+
+







static int		 fnc_stash_get(bool);
static int		 select_hunks(struct fnc_view *);
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 **,
			    struct patch_cx *, FILE *);
static int		 parse_filename(const char *, char **, int);
1389
1390
1391
1392
1393
1394
1395
1396

1397
1398
1399
1400
1401
1402
1403
1381
1382
1383
1384
1385
1386
1387

1388
1389
1390
1391
1392
1393
1394
1395







-
+







static bool		 view_is_parent(struct fnc_view *);
static void		 view_set_child(struct fnc_view *, struct fnc_view *);
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);
static int		 join_tl_thread(struct fnc_tl_view_state *);
static void		 fnc_free_commits(struct commit_queue *);
1421
1422
1423
1424
1425
1426
1427
1428
1429


1430
1431
1432
1433
1434
1435
1436
1413
1414
1415
1416
1417
1418
1419


1420
1421
1422
1423
1424
1425
1426
1427
1428







-
-
+
+







static bool		 fnc_str_has_upper(const char *);
static int		 fnc_make_sql_glob(char **, char **, const char *,
			    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);
static int		 init_colour(enum fnc_opt_id);
static int		 default_colour(enum fnc_opt_id);
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463

1464
1465
1466


1467
1468

1469
1470
1471
1472
1473
1474
1475
1476
1444
1445
1446
1447
1448
1449
1450





1451



1452
1453


1454

1455
1456
1457
1458
1459
1460
1461







-
-
-
-
-
+
-
-
-
+
+
-
-
+
-







int
main(int argc, const char **argv)
{
	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))) {
	if (!isatty(fileno(stdin)))
		rc = RC(FSL_RC_MISUSE, "invalid input device");
		goto end;
	}
		errx(1, "stdin is not a tty");


	if (!setlocale(LC_CTYPE, ""))
	setlocale(LC_CTYPE, "");
		fsl_fprintf(stderr, "[!] Warning: Can't set locale.\n");

	fnc_init.cmdarg = argv[1];	/* Which cmd to show usage if needed. */
#if DEBUG
	fcli.clientFlags.verbose = 2;	/* Verbose error reporting. */
#endif
	rc = fcli_setup_v2(argc, argv, fnc_init.cliflags_global,
	    &fnc_init.fnc_help);
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1792
1793
1794
1795
1796
1797
1798








1799
1800
1801
1802
1803
1804
1805







-
-
-
-
-
-
-
-







		rc = RC(FSL_RC_RANGE, "strlcpy");
		fsl_free(*child);
		*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;

	rc = fnc_set_signals();
	if (rc)
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945


1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958

1959
1960
1961
1962
1963
1964
1965
1901
1902
1903
1904
1905
1906
1907













1908

1909
1910
1911
1912
1913
1914


1915
1916
1917
1918
1919
1920

1921
1922
1923
1924
1925
1926
1927
1928







-
-
-
-
-
-
-
-
-
-
-
-
-

-
+
+




-
-






-
+







	if (path != s->path) {
		fsl_free(s->path);
		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 <repo> <path>' or 'fnc tl -c <sym> <path>' 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);

		rc = valid_path(path, id ? id : "tip");
		fsl_free(id);
2713
2714
2715
2716
2717
2718
2719
2720

2721
2722
2723
2724
2725
2726
2727
2676
2677
2678
2679
2680
2681
2682

2683
2684
2685
2686
2687
2688
2689
2690







-
+







				/* FALL THROUGH */
			default:
				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;
		}
	}
2813
2814
2815
2816
2817
2818
2819


2820
2821


2822

2823
2824
2825

2826

2827
2828

2829
2830
2831
2832
2833
2834
2835
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788

2789

2790
2791
2792

2793

2794
2795
2796
2797
2798
2799
2800
2801
2802







+
+


+
+
-
+
-


+
-
+
-

+







		entry = TAILQ_NEXT(entry, entries);
	}

	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 ((t->one && !t->two && entry->commit == t->one) ||
		if (ncommits == s->selected ||
		    ncommits == s->selected)
		    entry->commit->rid == s->tagged.rid)
			wattr_on(view->window, A_REVERSE, NULL);
		rc = write_commit_line(view, entry->commit, maxlen);
		if ((t->one && !t->two && entry->commit == t->one) ||
		if (ncommits == s->selected ||
		    ncommits == s->selected)
		    entry->commit->rid == s->tagged.rid)
			wattr_off(view->window, A_REVERSE, NULL);

		++ncommits;
		s->last_commit_onscreen = entry;
		entry = TAILQ_NEXT(entry, entries);
	}
	drawborder(view);

end:
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254











3255
3256
3257
3258
3259
3260
3261
3210
3211
3212
3213
3214
3215
3216





3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234







-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+







		break;
	case KEY_F(1):
	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':
		rc = toggle_fullscreen(new, view);
		break;
3357
3358
3359
3360
3361
3362
3363
3364

3365
3366
3367
3368
3369


3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383



3384
3385
3386
3387
3388
3389
3390
3330
3331
3332
3333
3334
3335
3336

3337
3338
3339
3340


3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353



3354
3355
3356
3357
3358
3359
3360
3361
3362
3363







-
+



-
-
+
+











-
-
-
+
+
+







			rc = offset_selected_line(view);
	}

	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❭ "},
	    {"  k ", "  ❬k❭ "},
	    {"  A ", "  ❬A❭ "},
	    {"  K ", "  ❬K❭ "},
	    {"  ? ", "  ❬?❭ "},
	    {"  q ", "  ❬q❭ "},
	    {"  Q ", "  ❬Q❭ "},
	    {""},
	    {""},
	    {0}
	    {"", ""},
	    {"", ""},
	    {NULL, NULL}
	};
	static const char *desc[] = {
	    "",
	    "Stash",
	    "- scroll back to the previous page^",
	    "- show more of this hunk on the next page^",
	    "- stash this hunk",
3614
3615
3616
3617
3618
3619
3620
3621

3622
3623
3624
3625
3626
3627
3628
3587
3588
3589
3590
3591
3592
3593

3594
3595
3596
3597
3598
3599
3600
3601







-
+







/*
 * Create popup pad in which to write the supplied txt string and optional
 * 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;
	size_t		 linesz;
	int		 ch, cury, curx, end, ln, width, wy, wx, x0, y0;
3636
3637
3638
3639
3640
3641
3642
3643
3644




3645

3646
3647
3648
3649
3650

3651
3652
3653
3654
3655
3656
3657
3609
3610
3611
3612
3613
3614
3615


3616
3617
3618
3619
3620
3621
3622
3623
3624
3625

3626
3627
3628
3629
3630
3631
3632
3633







-
-
+
+
+
+

+




-
+








	/*
	 * 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);

	x0 = 4;	 /* column number at which to start the help window */
	y0 = 2;	 /* line number at which to start the help window */
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3768
3769
3770
3771
3772
3773
3774


3775
3776
3777
3778
3779
3780
3781







-
-







static int
tl_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch)
{
	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 '$':
		view->pos.col = MAX(s->maxx - view->ncols / 2, 0);
		break;
3864
3865
3866
3867
3868
3869
3870


3871
3872


3873
3874
3875




3876
3877
3878
3879
3880
3881
3882
3883
3884

3885
3886
3887
3888
3889
3890







3891
3892
3893




3894
3895
3896




3897
3898
























3899
3900
3901



3902
3903
3904
3905
3906
3907
3908
3838
3839
3840
3841
3842
3843
3844
3845
3846


3847
3848
3849


3850
3851
3852
3853

3854
3855
3856
3857
3858
3859
3860

3861






3862
3863
3864
3865
3866
3867
3868
3869


3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880


3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904

3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916







+
+
-
-
+
+

-
-
+
+
+
+
-







-
+
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
+
+
+
+



+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-


+
+
+







		    !s->thread_cx.eotl) {
			s->thread_cx.ncommits_needed += (view->nlines - 1) -
			    s->commits.ncommits;
			rc = signal_tl_thread(view, 1);
		}
		break;
	case 'C': {
		fsl_cx *const f = fcli_cx();

		if (s->selected_entry->commit->type[0] != 'c') {
			rc = sitrep(view, SR_ALL,
		if (*s->selected_entry->commit->type != 'c')
			return sitrep(view, SR_ALL,
			    "-- requires check-in artifact --");
			break;
		}

		if (!fsl_needs_ckout(f))
			return sitrep(view, SR_ALL, "-- requires work tree --");

		fsl_cx *const f = fcli_cx();
		/*
		 * 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) {
		if (rc) {
			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 != 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 (*s->selected_entry->commit->type != 'c' &&
		    *s->selected_entry->commit->type != 'w')
			return sitrep(view, SR_ALL,
			    "-- requires check-in or wiki artifact --");
		if (!tagged_commit(s))
			break;

		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;
		/* FALL THROUGH */
	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;
	case 'c':
		s->colour = !s->colour;
3918
3919
3920
3921
3922
3923
3924

3925
3926
3927

3928
3929
3930













3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943


3944
3945
3946
3947
3948
3949

3950
3951


3952
3953
3954
3955
3956
3957
3958





3959
3960
3961
3962
3963

3964
3965

3966
3967




3968
3969


3970



3971
3972







3973
3974
3975
3976






3977
3978

3979
3980
3981
3982
3983
3984
3985
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935

3936
3937


3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961


3962
3963
3964





3965


3966
3967







3968
3969
3970
3971
3972





3973


3974
3975
3976
3977
3978
3979
3980


3981
3982
3983
3984
3985
3986


3987
3988
3989
3990
3991
3992
3993
3994



3995
3996
3997
3998
3999
4000
4001

4002
4003
4004
4005
4006
4007
4008
4009







+


-
+

-
-
+
+
+
+
+
+
+
+
+
+
+
+
+











-
-
+
+

-
-
-
-
-
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
-
-
+


+
+
+
+
-
-
+
+

+
+
+
-
-
+
+
+
+
+
+
+

-
-
-
+
+
+
+
+
+

-
+







			rc = sitrep(view, SR_ALL, "-- no matching commits --");
		}
		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:
		break;
	}

	return rc;
}

static bool
tagged_commit(struct fnc_tl_view_state *s)
static int
tag_timeline_entry(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"))
	if (s->selected_entry->commit->diff_type == FNC_DIFF_CKOUT ||
			return false;
		s->tagged.rid = s->selected_entry->commit->rid;
	    s->tag.one == NULL) {
		s->tag.one = s->selected_entry->commit;
		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) {
		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) {
		/* Untag currently tagged commit. */
		fsl_free(s->tagged.id);
		s->tagged.id = NULL;
		fsl_free(s->tagged.type);
		s->tagged.type = NULL;
		s->tag.one = NULL;	/* untag selected entry */
		s->tagged.rid = 0;
		return false;
		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 */
	/* A commit is tagged, diff selected commit against it. */
	if (s->selected_entry->commit->prid != s->tagged.rid)

	if (s->selected_entry->commit->prid != s->tag.one->rid)
		s->showmeta = false;

	if (s->selected_entry->commit->puuid != NULL) {
		/* initial commits have no parent, hence the check */
	s->tagged.ogid = fsl_strdup(s->selected_entry->commit->puuid);
	s->tagged.ogrid = s->selected_entry->commit->prid;
		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");

	s->selected_entry->commit->prid = s->tag.one->rid;
	s->tag.two = s->selected_entry->commit;

	return true;
	return FSL_RC_OK;
}

static int
move_tl_cursor_down(struct fnc_view *view, uint16_t page)
{
	struct fnc_tl_view_state	*s = &view->state.timeline;
	struct commit_entry		*first;
4602
4603
4604
4605
4606
4607
4608
4609

4610
4611

4612
4613

4614
4615
4616
4617
4618
4619

4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4626
4627
4628
4629
4630
4631
4632

4633
4634

4635
4636

4637
4638
4639
4640
4641
4642

4643
4644














4645
4646
4647
4648
4649
4650
4651







-
+

-
+

-
+





-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-







	return rc;
}

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;
	return rc1 ? rc1 : rc2;
}

/* static void */
/* sspinner(void) */
/* { */
/* 	int idx; */

/* 	while (1) { */
/* 		for (idx = 0; idx < 4; ++idx) { */
/* 			printf("\b%c", "|/-\\"[idx]); */
/* 			fflush(stdout); */
/* 			ssleep(SPIN_INTERVAL); */
/* 		} */
/* 	} */
/* } */

static int
join_tl_thread(struct fnc_tl_view_state *s)
{
	void	*err;
	int	 rc = 0;

4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4758
4759
4760
4761
4762
4763
4764


4765
4766
4767
4768
4769
4770
4771







-
-







	set_diff_opt(s);

	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;
	s->parent_view = parent_view;
	s->diff_mode = mode;
4782
4783
4784
4785
4786
4787
4788
4789

4790
4791
4792
4793
4794
4795
4796
4790
4791
4792
4793
4794
4795
4796

4797
4798
4799
4800
4801
4802
4803
4804







-
+







	rc = create_diff(s);
	if (rc) {
		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;

	return rc;
}
4810
4811
4812
4813
4814
4815
4816


4817
4818
4819
4820
4821
4822
4823
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833







+
+







static void
set_diff_opt(struct fnc_diff_view_state *s)
{
	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;
	fnc_init.eol ? FLAG_SET(s->diff_flags, FNC_DIFF_IGNORE_EOLWS) : 0;
	fnc_init.invert ? FLAG_SET(s->diff_flags, FNC_DIFF_INVERT) : 0;
4948
4949
4950
4951
4952
4953
4954
























4955
4956
4957
4958
4959
4960
4961
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







	if (!rc && s->diff_mode == COMMIT_META)
		rc = write_commit_meta(s);
	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;
	}

	/*
6184
6185
6186
6187
6188
6189
6190
6191

6192
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203
6204
6205
6206
6207
6208
6209
6210
6211
6212
6213

6214
6215
6216
6217
6218
6219
6220
6221
6222
6223
6224
6225
6226
6227

6228
6229
6230
6231
6232
6233
6234
6235
6236
6237
6238
6239

6240
6241
6242
6243
6244
6245
6246
6247
6248
6249
6250
6251
6252
6253
6254
6255
6256
6257
6258
6259
6260
6261
6262
6263
6264
6265
6266
6267
6268
6269
6270
6271
6272
6273
6218
6219
6220
6221
6222
6223
6224

6225
















6226
6227
6228
6229
6230

6231

6232
6233
6234
6235


6236
6237
6238
6239
6240
6241

6242
6243
6244
6245
6246
6247







6248







6249
6250
6251

















6252
6253
6254
6255
6256
6257
6258







-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-





-
+
-




-
-






-
+





-
-
-
-
-
-
-
+
-
-
-
-
-
-
-



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







	fsl_free(xplus0);
	fsl_buffer_clear(&fbuf1);
	fsl_buffer_clear(&fbuf2);
	return rc;
}

static int
show_diff(struct fnc_view *view)
show_diff_view(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)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	regmatch_t			*regmatch = &view->regmatch;
	struct fnc_colour		*c = NULL;
	wchar_t				*wcstr;
	char				*line;
	char				*line = NULL;
	static char			 pct[MAX_PCT_LEN];
	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");

	rc = draw_diff_headln(view);
	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;
	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) {
		col = wstrlen = 0;
		linelen = getline(&line, &linesz, s->f);
6349
6350
6351
6352
6353
6354
6355































































6356
6357
6358
6359
6360
6361
6362
6334
6335
6336
6337
6338
6339
6340
6341
6342
6343
6344
6345
6346
6347
6348
6349
6350
6351
6352
6353
6354
6355
6356
6357
6358
6359
6360
6361
6362
6363
6364
6365
6366
6367
6368
6369
6370
6371
6372
6373
6374
6375
6376
6377
6378
6379
6380
6381
6382
6383
6384
6385
6386
6387
6388
6389
6390
6391
6392
6393
6394
6395
6396
6397
6398
6399
6400
6401
6402
6403
6404
6405
6406
6407
6408
6409
6410







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







		wstandout(view->window);
		waddstr(view->window, "(END)");
		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)) {
		if (view->child == NULL || view->child->active ||
		    !screen_is_split(view->child))
6489
6490
6491
6492
6493
6494
6495
6496
6497
6498
6499
6500
6501
6502
6503
6504
6505
6537
6538
6539
6540
6541
6542
6543



6544
6545
6546
6547
6548
6549
6550







-
-
-







}

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;
	bool				 down = false;

6762
6763
6764
6765
6766
6767
6768

6769



6770
6771


6772
6773
6774


6775
6776
6777
6778

6779
6780



6781



6782



6783
6784

6785
6786
6787
6788
6789
6790
6791
6792
6793
6794
6795
6796
6797


6798
6799
6800
6801


6802
6803

6804
6805
6806
6807
6808
6809
6810
6807
6808
6809
6810
6811
6812
6813
6814
6815
6816
6817
6818


6819
6820
6821


6822
6823
6824
6825
6826

6827
6828
6829
6830
6831
6832

6833
6834
6835
6836
6837
6838
6839
6840

6841
6842
6843
6844
6845
6846
6847
6848
6849
6850
6851
6852
6853
6854
6855
6856
6857



6858
6859


6860
6861
6862
6863
6864
6865
6866
6867







+

+
+
+
-
-
+
+

-
-
+
+



-
+


+
+
+
-
+
+
+

+
+
+

-
+













+
+

-
-
-
+
+
-
-
+







		/* FALL THROUGH */
	case CTRL('k'):
	case '<':
	case ',':
	case 'K':
		if (s->parent_view == NULL)
			break;

		if (s->parent_view->vid == FNC_VIEW_TIMELINE) {
			struct fnc_tl_view_state	*ts;
			struct commit_entry		*prev;

			tlstate = &s->parent_view->state.timeline;
			previous_selection = tlstate->selected_entry;
			ts = &s->parent_view->state.timeline;
			prev = ts->selected_entry;

			rc = tl_input_handler(NULL, s->parent_view, down ?
			    KEY_DOWN : KEY_UP);
			rc = tl_input_handler(NULL, s->parent_view,
			    down ? KEY_DOWN : KEY_UP);
			if (rc)
				break;

			if (previous_selection == tlstate->selected_entry)
			if (prev == ts->selected_entry)
				break;

			if (s->diff_mode == DIFF_PLAIN)
				s->diff_mode = COMMIT_META;

			rc = set_selected_commit(s, tlstate->selected_entry);
			rc = set_selected_commit(s, ts->selected_entry);
			if (rc)
				break;
		} 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;

			if (!fsl_uuidcmp(get_selected_commit_id(bs->blame.lines,
			    bs->blame.nlines, bs->first_line_onscreen,
			    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);
		s->selected_line = 1;
		rc = reset_diff_view(view, false);
		}
		/* FALL THROUGH */
		break;
	default:
		break;
	}

	return rc;
}

7218
7219
7220
7221
7222
7223
7224










7225
7226
7227





7228
7229

7230
7231
7232
7233
7234
7235
7236
7275
7276
7277
7278
7279
7280
7281
7282
7283
7284
7285
7286
7287
7288
7289
7290
7291



7292
7293
7294
7295
7296
7297
7298
7299
7300
7301
7302
7303
7304
7305
7306







+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+


+







	/* 3. revert ckout to apply patches; vfile scanned in cmd_stash() */
	rc = revert_ckout(true, false);
	if (rc) {
		rc = RC(rc, "revert_ckout");
		goto end;
	}

	/*
	 * 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
	/* 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);
	/* 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 */
	rc = fnc_patch(&scx->pcx, scx->patch[0]);
	if (rc)
7280
7281
7282
7283
7284
7285
7286


7287
7288
7289


7290
7291
7292

7293
7294
7295
7296
7297
7298
7299
7350
7351
7352
7353
7354
7355
7356
7357
7358
7359


7360
7361

7362

7363
7364
7365
7366
7367
7368
7369
7370







+
+

-
-
+
+
-

-
+







static int
select_hunks(struct fnc_view *view)
{
	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, "
	if (!stashing || rc == FSL_RC_BREAK)
		return RC(FSL_RC_BREAK, "no hunks stashed, work tree unchanged");
		    "checkout state 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]"
 *   b - scroll back (only available if hunk occupies previous page)
 *   m - show more (only available if hunk occupies following page)
7319
7320
7321
7322
7323
7324
7325
7326

7327
7328
7329
7330
7331
7332
7333





7334
7335
7336
7337
7338
7339
7340
7390
7391
7392
7393
7394
7395
7396

7397
7398
7399





7400
7401
7402
7403
7404
7405
7406
7407
7408
7409
7410
7411







-
+


-
-
-
-
-
+
+
+
+
+







	uint32_t			*nh;
	int				 hl, rc = FSL_RC_OK;
	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) {
			nxt = 0;
			*in.buf = 'X';
7350
7351
7352
7353
7354
7355
7356

7357
7358
7359

7360

7361
7362
7363
7364
7365
7366



7367
7368
7369
7370
7371
7372



7373
7374
7375
7376


7377
7378
7379


7380
7381
7382

7383
7384
7385
7386
7387
7388

7389


7390




7391
7392


7393


7394
7395
7396
7397




7398
7399
7400
7401
7402

7403
7404
7405
7406
7407
7408

7409

7410
7411
7412
7413
7414


7415
7416
7417
7418

7419
7420

7421
7422

7423
7424
7425
7426

7427
7428
7429
7430

7431
7432


7433
7434
7435


7436
7437
7438
7439


7440
7441
7442
7443
7444
7445
7446



7447
7448
7449
7450
7451




7452
7453
7454

7455
7456
7457

7458
7459
7460

7461
7462


7463
7464
7465
7466

7467
7468
7469
7470
7471
7472
7473
7474

7475
7476
7477
7478
7479

7480
7481
7482
7483
7484
7485
7486
7421
7422
7423
7424
7425
7426
7427
7428
7429
7430
7431
7432
7433
7434
7435
7436
7437



7438
7439
7440
7441
7442
7443



7444
7445
7446




7447
7448
7449


7450
7451

7452

7453
7454
7455




7456
7457
7458
7459
7460
7461
7462
7463
7464


7465
7466
7467
7468
7469




7470
7471
7472
7473
7474
7475
7476
7477

7478
7479
7480
7481
7482
7483
7484
7485

7486
7487
7488
7489


7490
7491

7492
7493

7494
7495

7496


7497
7498
7499
7500

7501


7502

7503


7504
7505



7506
7507

7508


7509
7510

7511
7512
7513



7514
7515
7516





7517
7518
7519
7520



7521



7522



7523


7524
7525

7526


7527
7528
7529
7530
7531
7532
7533
7534

7535
7536
7537
7538
7539

7540
7541
7542
7543
7544
7545
7546
7547







+



+

+



-
-
-
+
+
+



-
-
-
+
+
+
-
-
-
-
+
+

-
-
+
+
-

-
+


-
-
-
-
+

+
+

+
+
+
+
-
-
+
+

+
+
-
-
-
-
+
+
+
+




-
+






+
-
+



-
-
+
+
-


-
+

-
+
-
-
+



-
+
-
-

-
+
-
-
+
+
-
-
-
+
+
-

-
-
+
+
-



-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
+
-
-
-
+
-
-
-
+
-
-
+
+
-

-
-
+







-
+




-
+







			s->first_line_onscreen = hunks->lineno[*nh] - 5;
		s->selected_line = 1;
		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) ||
			scroll = STASH_MVMT_UP;
		if (*nh < hunks->n - 1 && hunks->lineno[*nh + 1] > last)
			++scroll;
		    last < s->index.lineno[nf]) &&
		    last < hunks->lineno[*nh + 1]) ||
		    (*nh == hunks->n - 1 && last < s->nlines))
			scroll = scroll ? BOTH : DOWN;
		else if (*nh == hunks->n - 1 && last < s->nlines)
			++scroll;

		rc = generate_prompt(&ans, prompt, sizeof(prompt), scroll);
		if (rc) {
		rc = generate_prompt(ans, prompt, sizeof(prompt), scroll);
		if (rc)
			free_answers(ans);
			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 == '\0')
				*in.buf = 'y';
			else if (strlen(in.buf) > 1)
				*in.buf = '\0';
			if (*in.buf == '?' || *in.buf == 'H' ||
			    (int)*in.buf == (KEY_F(1)))
			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
 * Construct string ans of valid answers based on scroll, and generate the
 * corresponding prompt with the available answers from which to choose.
 * which to choose. *ptr must eventually be disposed of by the caller.
 */
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	a[] = "bmynakAK";
	char	 *opts[10] = {"b", "m", "y", "n", "a", "k", "A", "K", "", NULL};
	size_t	  n, oi, ai = 0;
	size_t	ai, pi;

	/* Set valid answers. */
	switch (scroll) {
		case 3:			/* BOTH  "bmynakAK?" */
	case STASH_MVMT_UPDOWN:
		oi = 0;
		n = 10;
		break;
		case 2:			/* UP  "bynakAK?" */
	case STASH_MVMT_UP:
		opts[0] = "m";
		opts[1] = "b";
		memmove(a + 1, a + 2, 7);
		break;
		/* FALL THROUGH */
	case 1:				/* DOWN  "mynakAK?" */
		oi = 1;
	case STASH_MVMT_DOWN:
		memmove(a, a + 1, 8);
		n = 9;
		break;
	case 0:				/* NONE  "ynakAK?" */
		oi = 2;
	case STASH_MVMT_NONE:
		memmove(a, a + 2, 7);
		n = 8;
		break;
	}

	/* Generate valid answers array. */
	ans = calloc(n, sizeof(*ans));
	if (ans == NULL)
	/* generate prompt string */
	pi = strlen(prompt);
	for (ai = 0; a[ai]; ++ai) {
		return RC(FSL_RC_ERROR, "calloc");
	while (ai < n)
		ans[ai++] = fsl_strdup(opts[oi++]);

	/* Generate prompt string. */
		prompt[pi++] = a[ai];
		prompt[pi++] = ',';
	}

	for (ai = 0; ai < n - 2; ++ai) {
		char *t = fsl_mprintf("%s%c", ans[ai], ',');
		if (t == NULL)
	if (memccpy(prompt + pi, "?)? [y] ", '\0', sz) == 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)",
		return RC(FSL_RC_RANGE, "memccpy");
			    prompt, t, sz);
		fsl_free(t);
	}

	if (fsl_strlcat(prompt, "?)? [y] ", sz) >= sz)
		return RC(FSL_RC_RANGE, "fsl_strlcat(%s, %s, %lu)",
	if (memccpy(ans, a, '\0', sizeof(a)) == NULL)
		return RC(FSL_RC_RANGE, "memccpy");
		    prompt, "?)? [y] ", sz);

	*ptr = ans;
	return FSL_RC_OK;;
	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;
}

/*
 * Set or clear the corresponding bit in bitstring s->scx.stash based on the
7532
7533
7534
7535
7536
7537
7538
7539
7540
7541
7542
7543
7544
7545
7546
7547
7548
7549
7550
7551
7552
7553
7554
7555
7593
7594
7595
7596
7597
7598
7599










7600
7601
7602
7603
7604
7605
7606







-
-
-
-
-
-
-
-
-
-







	while (*nf < files->n - 1 && *nh < hunks->n - 1 &&
	    files->lineno[*nf] + 5 <= hunks->lineno[*nh + 1])
		++(*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
revert_ckout(bool renames, bool scan)
{
8669
8670
8671
8672
8673
8674
8675
8676
8677
8678
8679
8680
8681
8682
8683
8684
8685
8686
8687
8688
8689
8690
8691
8692
8693
8694
8695
8696
8697
8698
8699
8700
8701
8702
8703
8704
8705
8706
8707
8708
8709
8710
8711
8712
8713
8714
8715
8716
8717
8718
8719
8720
8721
8720
8721
8722
8723
8724
8725
8726







































8727
8728
8729
8730
8731
8732
8733







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







		fsl_free(h->lines);
		fsl_free(h);
		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)
{
	fsl_cx	*const f = fcli_cx();
9034
9035
9036
9037
9038
9039
9040

9041
9042












9043
9044
9045

9046
9047
9048
9049
9050
9051
9052
9046
9047
9048
9049
9050
9051
9052
9053


9054
9055
9056
9057
9058
9059
9060
9061
9062
9063
9064
9065
9066
9067

9068
9069
9070
9071
9072
9073
9074
9075







+
-
-
+
+
+
+
+
+
+
+
+
+
+
+


-
+








	return rc;
}

static int
set_selected_commit(struct fnc_diff_view_state *s, struct commit_entry *entry)
{
	fsl_free(s->id2);
	s->id2 = entry->commit->uuid;
	s->id1 = entry->commit->puuid;
	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)
{
	struct fnc_diff_view_state *s = &view->state.diff;

9188
9189
9190
9191
9192
9193
9194




9195
9196
9197
9198
9199
9200
9201
9211
9212
9213
9214
9215
9216
9217
9218
9219
9220
9221
9222
9223
9224
9225
9226
9227
9228







+
+
+
+







close_diff_view(struct fnc_view *view)
{
	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);
	free_index(&s->hundex, false);
	fsl_free(s->dlines);
9211
9212
9213
9214
9215
9216
9217
9218
9219


9220


9221
9222





9223


9224
9225


9226
9227


9228
9229
9230
9231
9232



9233

9234
9235
9236
9237
9238
9239
9240







9241
9242
9243
9244
9245
9246
9247
9238
9239
9240
9241
9242
9243
9244


9245
9246
9247
9248
9249


9250
9251
9252
9253
9254
9255
9256
9257


9258
9259


9260
9261





9262
9263
9264

9265
9266
9267





9268
9269
9270
9271
9272
9273
9274
9275
9276
9277
9278
9279
9280
9281







-
-
+
+

+
+
-
-
+
+
+
+
+

+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
-
+


-
-
-
-
-
+
+
+
+
+
+
+







	index->n = 0;
	fsl_free(index->lineno);
	index->lineno = NULL;
	fsl_free(index->offset);
	index->offset = NULL;
}

static void
free_tags(struct fnc_tl_view_state *s, bool restore)
static int
reset_tags(struct fnc_tl_view_state *s)
{
	struct timeline_tag		*t = &s->tag;
	struct fnc_commit_artifact	*c = t->two;
	if (!s->tagged.ogrid)
		return;

	t->one = NULL;

	if (!t->ogrid)
		return FSL_RC_OK;

	fsl_free(c->puuid);
	c->puuid = NULL;
	/* Restore timeline commit queue entry. */
	if (restore) {

	/* restore commit's original parent */
		fsl_free(s->selected_entry->commit->puuid);
		s->selected_entry->commit->puuid = fsl_strdup(s->tagged.ogid);
	if (t->ogid != NULL) {
		c->puuid = fsl_strdup(t->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;
		fsl_free(t->ogid);
		t->ogid = NULL;
		if (c->puuid == NULL)
		s->showmeta = true;
			return RC(FSL_RC_ERROR, "fsl_strdup");
	}

	fsl_free(s->tagged.type);
	s->tagged.type = NULL;
	fsl_free(s->tagged.id);
	s->tagged.id = NULL;
	s->tagged.rid = 0;
	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)
{
	struct winsize	size;
	int		cols, lines;
9629
9630
9631
9632
9633
9634
9635



9636
9637
9638
9639



9640
9641
9642
9643
9644
9645
9646

9647
9648
9649
9650
9651
9652
9653
9654
9655
9656
9657
9658

9659
9660
9661
9662
9663
9664
9665
9663
9664
9665
9666
9667
9668
9669
9670
9671
9672
9673



9674
9675
9676
9677
9678
9679
9680
9681
9682
9683
9684
9685
9686
9687
9688
9689
9690
9691
9692
9693
9694
9695

9696
9697
9698
9699
9700
9701
9702
9703







+
+
+

-
-
-
+
+
+







+











-
+







	commit->uuid = fsl_strdup(commit->puuid);
	commit->type = fsl_strdup("checkin");
	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;
	}

	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;
	rc = fsl_vfile_changes_scan(f, rid, FSL_VFILE_CKSIG_HASH);
end:
9845
9846
9847
9848
9849
9850
9851


9852
9853
9854
9855





9856
9857
9858
9859
9860
9861
9862
9883
9884
9885
9886
9887
9888
9889
9890
9891
9892
9893
9894

9895
9896
9897
9898
9899
9900
9901
9902
9903
9904
9905
9906







+
+



-
+
+
+
+
+







 */
static int
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)
		goto end;

9873
9874
9875
9876
9877
9878
9879
9880
9881



9882
9883
9884
9885
9886
9887
9888
9917
9918
9919
9920
9921
9922
9923


9924
9925
9926
9927
9928
9929
9930
9931
9932
9933







-
-
+
+
+







			valid = true;

	} 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,
    struct commit_entry *entry, const char *path)
{
10253
10254
10255
10256
10257
10258
10259
10260
10261
10262
10263
10264
10265
10266
10267
10268
10269
10270
10271
10272
10273
10274
10275
10276
10277
10278
10279
10280
10281
10282
10283
10284
10285
10286
10287
10288
10289
10290
10291
10292
10298
10299
10300
10301
10302
10303
10304


























10305
10306
10307
10308
10309
10310
10311







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







		te->idx = i++;
	}
	(*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
 * will comprise the directory node's .children list, and each file node's
 * .sibling list; said directory will be each file node's .parent_dir. The
11287
11288
11289
11290
11291
11292
11293
11294

11295
11296
11297
11298
11299
11300
11301
11306
11307
11308
11309
11310
11311
11312

11313
11314
11315
11316
11317
11318
11319
11320







-
+







		return rc;

	opt = fcli_next_arg(true);
	if (opt == NULL || fnc_init.lsconf) {
		if (fnc_init.unset)
			return RC(FSL_RC_MISSING_INFO,
			    "-u|--unset requires <setting>");
		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);

	value = fcli_next_arg(true);
11329
11330
11331
11332
11333
11334
11335
11336

11337
11338
11339
11340
11341
11342
11343
11348
11349
11350
11351
11352
11353
11354

11355
11356
11357
11358
11359
11360
11361
11362







-
+







		last = (value = fnc_conf_getopt(idx, true)) ? idx : last;
		maxlen = MAX(fsl_strlen(fnc_opt_name[idx]), maxlen);
		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) {
		value = fnc_conf_getopt(idx, true);
		if (value || all)
14008
14009
14010
14011
14012
14013
14014
14015

14016
14017
14018
14019
14020
14021
14022
14027
14028
14029
14030
14031
14032
14033

14034
14035
14036
14037
14038
14039
14040
14041







-
+







#endif

/*
 * 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 |		\
				LANDLOCK_ACCESS_FS_MAKE_REG)
	/*