fnc

Check-in Differences
Login

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

Difference From 0.13 To 0.12

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)
2022-05-09 12:18
Bump version number: 0.13 (check-in: b56d1390f5 user: mark tags: trunk)
2022-05-09 12:15
CHANGES for 0.12 (check-in: 671ad5159a user: mark tags: trunk, 0.12)
2022-05-09 11:33
Fix blame regression when opening child diff view in vsplit. (check-in: 5d1ac567c2 user: mark tags: trunk)

Changes to CHANGES.md.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
**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
- make tree navigation more intuitive when opening repository subtrees
- implement `[` & `]` keymaps to navigate to the previous/next hunk in the diff
- allow `fnc stash get` to take an optional `<stash_id>` argument
- document fnc(1) missing `W` diff view keymap to ignore end-of-line whitespace
- document optional `<stash_id>` argument of `fnc stash pop [stash_id]`
- fix typo producing gcc 12.1.0 compiler warning (comparison always true)
- improve scope matching for function prototype in diff hunk headers
- ifdef out `A_BOLD` attr on OpenBSD to fix diff view line highlight in xterm(1)
- display checkout hash and build date with `fnc -v` like fossil(1)
- miscellaneous diff API implementation improvements
- protect against out-of-bound reads now line_type array is optionally populated
- add `-b|--brief` option to `fnc diff` and `b` diff view keymap
- general fnc(1) manual page markup, syntax, and documentation improvements
- rename FNC_DIFF_COLOUR_CHUNK to FNC_DIFF_COLOUR_HUNK to match internal naming  
  (*breaking change: existing FNC_DIFF_COLOUR_CHUNK will be silently ignored*)
- ensure diff parser does not internally mark modified files as renamed
- improve const correctness with fsl_uuid_cstr where possible
- fix contrived empty diff case when work tree root is passed to `fnc diff`
- improve path handling of files on disk vs. files in the repository database
- produce less verbose output on error by not presumptuously dumping help
- zap unnecessary allocation and minor refactor in the path parser
- don't display empty diff in contrived modified checkout edge case
- catch missed fsl_content_get() fsl_unlink_file() & fsl_mprintf() return codes
- miscellaneous style(9) fixes
- fix missed format specifier argument in blame path error message
- ensure `fnc stash (get|pop)` doesn't report updated files as renames
- use "(checkout)" label in diff header when diffing or stashing the work tree
- internal stash refactor in preparation to move code into libfossil
- catched missed SIGINT and SIGTERM signals
- use CHAR_BIT for bitstring manipulation macros
- fix assumption of invalid command argument as a path to pass to `fnc timeline`
- merge upstream libfossil with various changes

**fnc 0.12** 2022-05-09 [[history][0.12a] / [diff][0.12b]]

- replace \\s regexp with portable [[:space:]] character class (patch by Ashish)
- fix blame->diff child split view regression from 0.9 introduced in [d05828fbb]

**fnc 0.11** 2022-05-08 [[history][0.11a] / [diff][0.11b]]

- handle diff of non-versioned files outside the work tree (reported by Dan)
- replace getpagesize() with portable sysconf(_SC_PAGESIZE) (patch by mgagnon)
- improve robustness by guarding against piped input abuse (reported by Dan)
- improve documentation regarding UTF-8 character encoding and fonts
- implement horizontal scroll of the log message summary line in timeline view
- fix out-of-bounds UB on 32-bit builds (reported by mgagnon)
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|

|


|















































1
2
3
4
5
6
7
8
9
10
11
12
13








































**fnc 0.12** 2022-05-09

- replace \s regexp with portable [[:space:]] character class (patch by Ashish)
- fix blame->diff child split view regression from 0.9 introduced in [d05828fbb]

**fnc 0.11** 2022-05-08

- handle diff of non-versioned files outside the work tree (reported by Dan)
- replace getpagesize() with portable sysconf(_SC_PAGESIZE) (patch by mgagnon)
- improve robustness by guarding against piped input abuse (reported by Dan)
- improve documentation regarding UTF-8 character encoding and fonts
- implement horizontal scroll of the log message summary line in timeline view
- fix out-of-bounds UB on 32-bit builds (reported by mgagnon)
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
- display hunk index in `fnc stash` input prompt
- documentation improvements in README
- update in-tree SQLite lib to 3.38.5 with multiple bug fixes
- add `apply` alias for `fnc stash get` to facilitate Fossiler muscle memory
- simplify error, and tailor usage, reporting when handling invalid input
- remove redundant fcli_has_unused_args() call in main()

**fnc 0.10** 2022-03-24 [[history][0.10a] / [diff][0.10b]]

- fix gcc 9.3 compiler warnings (i.e., unused variable) (reported by stephan)
- restrict `C` key map for diffing local changes to check-in artifacts
- ensure timeline --branch option ignores cancelled branches (reported by sean)
- fix landlock initialisation of handled fs access perms (patch by Ashish)
- tighten landlock ruleset depending on which fnc command is called
- improve branch view rendering of last modified date and hash id







|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- display hunk index in `fnc stash` input prompt
- documentation improvements in README
- update in-tree SQLite lib to 3.38.5 with multiple bug fixes
- add `apply` alias for `fnc stash get` to facilitate Fossiler muscle memory
- simplify error, and tailor usage, reporting when handling invalid input
- remove redundant fcli_has_unused_args() call in main()

**fnc 0.10** 2022-03-24

- fix gcc 9.3 compiler warnings (i.e., unused variable) (reported by stephan)
- restrict `C` key map for diffing local changes to check-in artifacts
- ensure timeline --branch option ignores cancelled branches (reported by sean)
- fix landlock initialisation of handled fs access perms (patch by Ashish)
- tighten landlock ruleset depending on which fnc command is called
- improve branch view rendering of last modified date and hash id
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
- return to blocking on user input when tl search is aborted (reported by Dan)
- implement persistent diff options for global and per-repo defaults
- implement `--whitespace-eol` and `W` key map to only ignore eol whitespace
- implement horizontal scroll in the in-app help
- add `Q` key map to in-app help to directly quit fnc
- implement blame navigation from diff view with `C-{j,k}` key maps

**fnc 0.9** 2022-03-04 [[history][0.9a] / [diff][0.9b]]

- Add blame command `--line` option to open annotated file at the specified line
- merge upstream libfossil changes that eliminate gcc compiler warnings
- adopt libfossil diff v1 implementation into fnc tree to replace v2 API
- refactor diff implementation to comport with code style
- fix `--whitespace` option (`w` keymap) diffv2 regression from 0.7 [105123b40e]
- implement diff `--line-numbers` opt (`L` keymap) to display file line numbers







|







51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
- return to blocking on user input when tl search is aborted (reported by Dan)
- implement persistent diff options for global and per-repo defaults
- implement `--whitespace-eol` and `W` key map to only ignore eol whitespace
- implement horizontal scroll in the in-app help
- add `Q` key map to in-app help to directly quit fnc
- implement blame navigation from diff view with `C-{j,k}` key maps

**fnc 0.9** 2022-03-04

- Add blame command `--line` option to open annotated file at the specified line
- merge upstream libfossil changes that eliminate gcc compiler warnings
- adopt libfossil diff v1 implementation into fnc tree to replace v2 API
- refactor diff implementation to comport with code style
- fix `--whitespace` option (`w` keymap) diffv2 regression from 0.7 [105123b40e]
- implement diff `--line-numbers` opt (`L` keymap) to display file line numbers
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
- clean code of OS-dependent ifdefs by consolidating them in called functions
- add support for landlock Linux security module in Linux builds
- significant diff driver refactoring for finer granularity in view manipulation
- implement `--sbs` (`S` keymap) to display side-by-side formatted diffs
- add FNC_COLOUR_DIFF_SBS_EDIT option to set colour of edited lines in SBS diff 
- plug small memory leak when interactively changing diff format

**fnc 0.8** 2022-01-10 [[history][0.8a] / [diff][0.8b]]

- fix vertical split view init regression from 0.7 when terminal < 120 cols wide 
- fix DB lock when opening horizontal split that signals the timeline thread

**fnc 0.7** 2022-01-09 [[history][0.7a] / [diff][0.7b]]

- factor out common make(1) and gmake build bits
- make build depend on make file
- make all commands compatible with `-R|--repository` (i.e., no checkout needed)
- implement Vim-like smartcase for commands that filter repository results
- improve timeline arg parsing for more informative output upon invalid input
- plug small memory leak when the timeline command fails to initialise a view







|




|







75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
- clean code of OS-dependent ifdefs by consolidating them in called functions
- add support for landlock Linux security module in Linux builds
- significant diff driver refactoring for finer granularity in view manipulation
- implement `--sbs` (`S` keymap) to display side-by-side formatted diffs
- add FNC_COLOUR_DIFF_SBS_EDIT option to set colour of edited lines in SBS diff 
- plug small memory leak when interactively changing diff format

**fnc 0.8** 2022-01-10

- fix vertical split view init regression from 0.7 when terminal < 120 cols wide 
- fix DB lock when opening horizontal split that signals the timeline thread

**fnc 0.7** 2022-01-09

- factor out common make(1) and gmake build bits
- make build depend on make file
- make all commands compatible with `-R|--repository` (i.e., no checkout needed)
- implement Vim-like smartcase for commands that filter repository results
- improve timeline arg parsing for more informative output upon invalid input
- plug small memory leak when the timeline command fails to initialise a view
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
- improve view request handling and optimise new view initialisation
- implement `C-n/C-p` keymap to navigate to the next/previous file in the diff
- implement (F)ile keymap to navigate directly to a file in the diff
- apply upstream diff v2 fix for rendering untouched lines as changed
- implement Vim-like `C-d/C-u` keymaps to scroll the view down/up half a page
- enhance search behaviour in all views

**fnc 0.6** 2021-11-22 [[history][0.6a] / [diff][0.6b]]

- implement support for user-defined colours in tl, diff, tree and blame views
- make branch view accessible from all other views
- optimise colour scheme initialisation
- implement new 'fnc config' interface to configure per-project settings
- improve build by making make file make(1) compatible
- rename old make file to GNUmakefile to keep gmake support







|







123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
- improve view request handling and optimise new view initialisation
- implement `C-n/C-p` keymap to navigate to the next/previous file in the diff
- implement (F)ile keymap to navigate directly to a file in the diff
- apply upstream diff v2 fix for rendering untouched lines as changed
- implement Vim-like `C-d/C-u` keymaps to scroll the view down/up half a page
- enhance search behaviour in all views

**fnc 0.6** 2021-11-22

- implement support for user-defined colours in tl, diff, tree and blame views
- make branch view accessible from all other views
- optimise colour scheme initialisation
- implement new 'fnc config' interface to configure per-project settings
- improve build by making make file make(1) compatible
- rename old make file to GNUmakefile to keep gmake support
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
- improved branch view resource deallocation
- implement new timeline command --filter option and key binding
- enhance timeline --type option with support for tag control artifacts
- general documentation improvements
- assorted updates from upstream libfossil including compiler warning fix
- expand support for user-defined colours to include the branch view

**fnc 0.5** 2021-11-03 [[history][0.5a] / [diff][0.5b]]

- simplify the build system to make-only and remove autosetup (patch from sdk)
- remove invalid fsl_errno_to_rc() call and redundant alloc error check
- reduce noise on app exit by removing redundant error output
- relocate fnc_strlcpy() and fnc_strlcat() routines to the library

**fnc 0.4** 2021-10-31 [[history][0.4a] / [diff][0.4b]]

- resolve database bug in the commit builder logic
- prune dead code leftover from the initial single-threaded blame implementation
- improve error handling of commands that return no records
- update in-app help with blame key bindings
- expand tree command's ability to display trees of versions with missing files
- improve tree command error handling of non-checkin artifacts







|






|







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
- improved branch view resource deallocation
- implement new timeline command --filter option and key binding
- enhance timeline --type option with support for tag control artifacts
- general documentation improvements
- assorted updates from upstream libfossil including compiler warning fix
- expand support for user-defined colours to include the branch view

**fnc 0.5** 2021-11-03

- simplify the build system to make-only and remove autosetup (patch from sdk)
- remove invalid fsl_errno_to_rc() call and redundant alloc error check
- reduce noise on app exit by removing redundant error output
- relocate fnc_strlcpy() and fnc_strlcat() routines to the library

**fnc 0.4** 2021-10-31

- resolve database bug in the commit builder logic
- prune dead code leftover from the initial single-threaded blame implementation
- improve error handling of commands that return no records
- update in-app help with blame key bindings
- expand tree command's ability to display trees of versions with missing files
- improve tree command error handling of non-checkin artifacts
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
- optimise parsing of artifacts to determine which diff routine to call
- implement `fnc branch` which displays a navigable list of repository branches
- expand available sort options to the branch command
- simplify usage output on error or `-h|--help` with usage callback
- general man page and documentation improvements
- fix incorrect configure `--prefix` install path in the docs (reported by sdk)

**fnc 0.3** 2021-10-17 [[history][0.3a] / [diff][0.3b]]

- add in-app help with H|?|F1 key binding
- improvements to the build system
- decompose build_commits() to make reusable commit_builder() method
- dynamically size header so metadata is not truncated in timeline view
- fix highlight of coloured search results in tmux with A_REVERSE attribute
- correct install path of man pages with 'make install'







|







187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
- optimise parsing of artifacts to determine which diff routine to call
- implement `fnc branch` which displays a navigable list of repository branches
- expand available sort options to the branch command
- simplify usage output on error or `-h|--help` with usage callback
- general man page and documentation improvements
- fix incorrect configure `--prefix` install path in the docs (reported by sdk)

**fnc 0.3** 2021-10-17

- add in-app help with H|?|F1 key binding
- improvements to the build system
- decompose build_commits() to make reusable commit_builder() method
- dynamically size header so metadata is not truncated in timeline view
- fix highlight of coloured search results in tmux with A_REVERSE attribute
- correct install path of man pages with 'make install'
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
- substantial UX and performance improvements by making 'fnc blame' threaded
- fix bug in 'fnc blame --limit|-n' calls when lines were not annotated
- enable handling of master branches not named 'trunk' in 'fnc blame'
- enable accessing the tree interface from the timeline with new 't' key binding
- add '-C|--no-colour' and 'c' key binding to 'fnc blame'
- enhance and standardise parsing of path arguments for all commands

**fnc 0.2** 2021-09-04 [[history][0.2a] / [diff][0.2b]]

- fix iconv lib linking in macOS builds
- use pkg-config for detecting ncurses libs
- prune dead auto.def code
- bypass pkg-config in configure script on macOS systems
- major overhaul to use amalgamation build
- add key bindings to jump to start and end of view







|







211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
- substantial UX and performance improvements by making 'fnc blame' threaded
- fix bug in 'fnc blame --limit|-n' calls when lines were not annotated
- enable handling of master branches not named 'trunk' in 'fnc blame'
- enable accessing the tree interface from the timeline with new 't' key binding
- add '-C|--no-colour' and 'c' key binding to 'fnc blame'
- enhance and standardise parsing of path arguments for all commands

**fnc 0.2** 2021-09-04

- fix iconv lib linking in macOS builds
- use pkg-config for detecting ncurses libs
- prune dead auto.def code
- bypass pkg-config in configure script on macOS systems
- major overhaul to use amalgamation build
- add key bindings to jump to start and end of view
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
- enhance timeline --commit|-c option to accept symbolic check-in names
- add man page installation to make install target
- plug small memory leak in cmd_diff() routine
- fix screen rendering bug due to late deallocation of resources
- improve multibyte to wide character conversion error handling/reporting
- fix bug involving renamed files in diff view

**fnc 0.1** 2021-08-28 [[history][0.1a] / [diff][0.1b]]

- initial commit of infrastructure for the timeline command
- add branch names and tags to search criteria
- enhance parsing of malformed RFC822 email addresses in commit usernames
- display wiki page content of initial wiki commits
- make display of technote and ticket commits more consistent with fossil(1) ui
- fix bug where diff algorithm choked on binary files
- add coloured output to diff view
- replace BSD-specific getprogname() calls with libf API
- enhance diff view to display more informative control artifact commits
- add Linux support so we now build and run on OpenBSD, macOS, and Linux
- 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.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
[0.10b]: https://fnc.bsdbox.org/vdiff?from=0.9&to=0.10
[0.9a]: https://fnc.bsdbox.org/timeline?p=0.9&bt=0.8
[0.9b]: https://fnc.bsdbox.org/vdiff?from=0.8&to=0.9
[0.8a]: https://fnc.bsdbox.org/timeline?p=0.8&bt=0.7
[0.8b]: https://fnc.bsdbox.org/vdiff?from=0.7&to=0.8
[0.7a]: https://fnc.bsdbox.org/timeline?p=0.7&bt=0.6
[0.7b]: https://fnc.bsdbox.org/vdiff?from=0.6&to=0.7
[0.6a]: https://fnc.bsdbox.org/timeline?p=0.6&bt=0.5
[0.6b]: https://fnc.bsdbox.org/vdiff?from=0.5&to=0.6
[0.5a]: https://fnc.bsdbox.org/timeline?p=0.5&bt=0.4
[0.5b]: https://fnc.bsdbox.org/vdiff?from=0.4&to=0.5
[0.4a]: https://fnc.bsdbox.org/timeline?p=0.4&bt=0.3
[0.4b]: https://fnc.bsdbox.org/vdiff?from=0.3&to=0.4
[0.3a]: https://fnc.bsdbox.org/timeline?p=0.3&bt=0.2
[0.3b]: https://fnc.bsdbox.org/vdiff?from=689182448e&to=0.3
[0.2a]: https://fnc.bsdbox.org/timeline?p=0.2&bt=96e142ed8e
[0.2b]: https://fnc.bsdbox.org/vdiff?from=96e142ed8e&to=689182448e
[0.1a]: https://fnc.bsdbox.org/timeline?p=689182448e&bt=6eaea2465f
[0.1b]: https://fnc.bsdbox.org/vdiff?from=6eaea2465f&to=96e142ed8e







|


















<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258


























- enhance timeline --commit|-c option to accept symbolic check-in names
- add man page installation to make install target
- plug small memory leak in cmd_diff() routine
- fix screen rendering bug due to late deallocation of resources
- improve multibyte to wide character conversion error handling/reporting
- fix bug involving renamed files in diff view

**fnc 0.1** 2021-08-28

- initial commit of infrastructure for the timeline command
- add branch names and tags to search criteria
- enhance parsing of malformed RFC822 email addresses in commit usernames
- display wiki page content of initial wiki commits
- make display of technote and ticket commits more consistent with fossil(1) ui
- fix bug where diff algorithm choked on binary files
- add coloured output to diff view
- replace BSD-specific getprogname() calls with libf API
- enhance diff view to display more informative control artifact commits
- add Linux support so we now build and run on OpenBSD, macOS, and Linux
- 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



























Changes to README.md.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

134
135
136
137
138
139
140
141

142

143
144
145
146
147
148
149
# README


# fnc: an interactive text-based user interface for [Fossil]


`fnc` uses [ncurses] and [libfossil] to create a [`fossil ui`][fui] experience
in the terminal, and parse local changes at the hunk level to prepare atomic
commits.

Tested and confirmed to run on the following amd64 systems (additional
platforms noted inline):

1. OpenBSD 6.8-, 6.9-, 7.0-, 7.1-, and 7.2-{current,release}
2. macOS Catalina 10.15.7, Big Sur 11.5.2, and Ventura 13.0.1
3. Linux Mint 20.2 (32- and 64-bit ARM)
4. Ubuntu 18.04, 21.04, 21.10, and 22.04 running Linux 5.1{1,3} (32-bit ARM)
5. Debian GNU/Linux 8, 9, and 10
6. CentOS 6.5 (32-bit)

Alpha development notwithstanding, the `timeline`, `diff`, `tree`, `blame`,
`branch`, and `stash` commands are relatively stable; however, there is no
commitment to refrain from breaking changes.

# Install

* **OpenBSD**
  - `doas pkg_add fnc`
* **macOS**
  - `sudo port install fnc`
* **FreeBSD**
  - <u>package</u>: `pkg install fnc`
  - <u>port</u>: `cd /usr/ports/devel/fnc/ && make install clean`
* **NixOS**
  - `nix-env -iA nixos.fnc`
* **Alpine Linux**
  - `apk add fnc`  
  n.b. Ensure _testing_ repository is enabled (see <https://wiki.alpinelinux.org/wiki/Repositories#Testing>)

Check [repology] to find if a package is provided for your operating system.
If no package exists, [download] and install the binary on your path.

# Build

1. clone the repository
  - `fossil clone https://fnc.bsdbox.org`
2. move into the repository checkout
  - `cd fnc`
3. build fnc
  - `make`
4. install the `fnc` binary (*requires privileges*)
  - `doas make install`
5. move into an open Fossil checkout, and run it:
  - `cd ~/museum/repo && fossil open ../repo.fossil && fnc`

This will install the `fnc` executable and man page into `/usr/local/bin` and
`/usr/local/share/man/man1`, respectively. Alternatively, cryptographically
signed tarballs of the source code and binaries for some of the abovementioned
platforms are available to [download].

# Doc

Commands available in **fnc**:

1. [**stash**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#stash)
  - interactively select hunks to stash from the diff of local changes on disk
2. [**timeline**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#timeline)
  - hyperlinked chronological commit history of the repository
3. [**diff**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#diff)
  - diff of all changes between commits or blobs
4. [**blame**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#blame)
  - annotated file displaying commit attribution history of each line
5. [**tree**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#tree)
  - navigable file hierarchy of the repository tree
6. [**branch**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#branch)
  - hyperlinked list of all public and private branches
7. [**config**](https://fnc.bsdbox.org/uv/doc/fnc.1.html#config)
  - configure or view fnc settings

See `fnc --help` for a quick reference, and the [fnc(1)] manual page for more
comprehensive documentation. Runtime help can also be accessed with the `?`,
`F1`, or `H` key binding. The following video briefly demonstrates some of the
key bindings in use.

[![fnc demo][demoimg]][demo]

# Why

`fnc` is heavily inspired by [`tog`], which I missed when I left [Got] behind
and started using Fossil. The objective is to provide an alternative to
`fossil ui` without leaving the terminal.

# Problems & Patches

Please submit bug reports via [email], the [forum], or by creating a new
[ticket]. As a rule, all reports should include a bug reproduction recipe; that
is, either (1) the series of steps beginning with `fossil init` to create a new
repository through to the `fnc` command that triggers the unexpected behaviour;
or, if possible, (2) a shell script that contains all necessary ingredients to
reproduce the problem.

Patches are thoughtfully considered and can be sent to the [mailing list].
While `diff -up` patches (or `fnc` diffs saved with the [`P` keymap]) are
preferred, `fossil patch create` and `fossil diff` patches are also welcomed.
Please ensure code conforms to the C99 standard, and complies with OpenBSD's

KNF [style(9)]. Any patch containing user-visible code addition, modification,
or deletion (i.e., code that impacts user interfaces) should concomitantly
include updating documentation affected by the change.

# Screenshots

![stash](https://fnc.bsdbox.org/uv/img/fnc-stash.png "fnc stash")
![stash more](https://fnc.bsdbox.org/uv/img/fnc-stash-more.png "stash more")
![stash help](https://fnc.bsdbox.org/uv/img/fnc-stash-help.png "stash help")
![diff vsplit](https://fnc.bsdbox.org/uv/img/fnc-diff-vsplit.png "diff vertical split")
![diff hsplit renamed](https://fnc.bsdbox.org/uv/img/fnc-diff-hsplit-renamed.png "diff horizontal split file renamed")
![diff vsplit added](https://fnc.bsdbox.org/uv/img/fnc-diff-vsplit-added.png "diff vertical split file added")
![diff vsplit removed](https://fnc.bsdbox.org/uv/img/fnc-diff-vsplit-removed.png "diff vertical split file removed")
![blame vsplit](https://fnc.bsdbox.org/uv/img/fnc-blame-vsplit.png "blame vertical split")
![tree vsplit](https://fnc.bsdbox.org/uv/img/fnc-tree-vsplit.png "tree vertical split")
![branch hsplit](https://fnc.bsdbox.org/uv/img/fnc-branch-hsplit.png "branch horizontal split")
![runtime help](https://fnc.bsdbox.org/uv/img/fnc-runtime-help.png "fnc runtime help")
![timeline help](https://fnc.bsdbox.org/uv/img/fnc-timeline-help.png "fnc timeline help")

# Trivia

**fnc** [fɪŋk]  
*noun* (n.)  
1. an interactive ncurses browser for [Fossil] repositories  
*verb* (v.)  
2. to inform  
etymology  
From the German word *Fink*, meaning "finch", a type of bird.

[demoimg]:	https://fnc.bsdbox.org/uv/img/fnc-timeline-fullscreen.png

[demo]:		https://itac.bsdbox.org/fnc-demo.mp4
[download]:	https://fnc.bsdbox.org/uv/download.html
[email]:	mailto:fnc@bsdbox.org
[fnc(1)]:	https://fnc.bsdbox.org/uv/doc/fnc.1.html
[forum]:	https://fnc.bsdbox.org/forum
[Fossil]:	https://fossil-scm.org
[fui]:		https://fossil-scm.org/home/help?cmd=ui
[Got]:		https://gameoftrees.org

[libfossil]:	https://fossil.wanderinghorse.net/r/libfossil

[mailing list]:	https://itac.bsdbox.org/listinfo/fnc
[ncurses]:	https://invisible-island.net/ncurses/ncurses.html
[repology]:	https://repology.org/project/fnc/versions
[style(9)]:	https://man.openbsd.org/style.9
[ticket]:	https://fnc.bsdbox.org/ticket
[`tog`]:	https://gameoftrees.org/tog.1.html
[`P` keymap]:	https://fnc.bsdbox.org/uv/doc/fnc.1.html#P~3


>
|
>

|
<
|

|
|

|
|

|



|
|
|










<
<
|
<
<
<
<
|

















|





|

|

|

|

|

|

|


|
|



|



|
|
|



|
|
|
|
|
|

|
<
|
|
>
|
|
|



|
|
|
|
|
|
|
|
|
|
|
|





|





|
>
|
|
<
|
|
|
|
|
>
|
>
|
<
<
|
<
<
<
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
29
30
31
32


33




34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131

132
133
134
135
136
137
138
139
140


141



# README

# fnc 0.12

## An interactive ncurses browser for [Fossil][0] repositories.

`fnc` uses [libfossil][1] to create a [`fossil ui`][2] experience in the

terminal.

Tested and confirmed to run on the following amd64 systems (additional platforms
noted inline):

1. OpenBSD 6.8-, 6.9-, and 7.0-release
2. macOS 10.15.7 (Catalina) and 11.5.2 (Big Sur)
3. Linux Mint 20.2 (32- and 64-bit ARM)
4. Ubuntu 18.04 running Linux kernel 5.11 (32-bit ARM)
5. Debian GNU/Linux 8, 9, and 10
6. CentOS 6.5 (32-bit)

Alpha development notwithstanding, the `timeline`, `diff`, `tree`, `blame`, and
`branch` commands are relatively stable; however, there is no commitment to
refrain from breaking changes.

# Install

* **OpenBSD**
  - `doas pkg_add fnc`
* **macOS**
  - `sudo port install fnc`
* **FreeBSD**
  - <u>package</u>: `pkg install fnc`
  - <u>port</u>: `cd /usr/ports/devel/fnc/ && make install clean`


* **Linux**




  - [Download](/uv/download.html) and install the binary on your path

# Build

1. clone the repository
  - `fossil clone https://fnc.bsdbox.org`
2. move into the repository checkout
  - `cd fnc`
3. build fnc
  - `make`
4. install the `fnc` binary (*requires privileges*)
  - `doas make install`
5. move into an open Fossil checkout, and run it:
  - `cd ~/museum/repo && fossil open ../repo.fossil && fnc`

This will install the `fnc` executable and man page into `/usr/local/bin` and
`/usr/local/share/man/man1`, respectively. Alternatively, cryptographically
signed tarballs of the source code and binaries for some of the abovementioned
platforms are available to [download][3].

# Doc

Commands available in **fnc**:

1. [**stash**](/uv/resources/doc/fnc.1.html#stash)
  - interactively select hunks to stash from the diff of local changes on disk
2. [**timeline**](/uv/resources/doc/fnc.1.html#timeline)
  - hyperlinked chronological commit history of the repository
3. [**diff**](/uv/resources/doc/fnc.1.html#diff)
  - diff of all changes between commits or blobs
4. [**blame**](/uv/resources/doc/fnc.1.html#blame)
  - annotated file displaying commit attribution history of each line
5. [**tree**](/uv/resources/doc/fnc.1.html#tree)
  - navigable file hierarchy of the repository tree
6. [**branch**](/uv/resources/doc/fnc.1.html#branch)
  - hyperlinked list of all public and private branches
7. [**config**](/uv/resources/doc/fnc.1.html#config)
  - configure or view fnc settings

See `fnc --help` for a quick reference, and the [fnc(1)][4] manual page for more
comprehensive documentation. In-app help can also be accessed with the `?`,
`F1`, or `H` key binding. The following video briefly demonstrates some of the
key bindings in use.

[![fnc demo][5]][6]

# Why

`fnc` is heavily inspired by [`tog`][7], which I missed when I left [Got][8]
(Git) behind and started using Fossil. The objective is to provide an
alternative to `fossil ui` without leaving the terminal.

# Problems & Patches

Please submit bug reports via [email][9], the [forum][10], or by creating a new
[ticket][11]. As a rule, all reports should include a bug reproduction recipe;
that is, either (1) the series of steps beginning with `fossil init` to create a
new repository through to the `fnc` command that triggers the unexpected
behaviour; or, if possible, (2) a shell script that contains all necessary
ingredients to reproduce the problem.

Patches are thoughtfully considered and can be sent to the [mailing list][12].

While `diff -up` patches are preferred, `fossil patch create` and `fossil diff`
patches are also welcomed. Please ensure code conforms to the C99 standard,
and complies with OpenBSD's KNF [style(9)][13]. Any patch containing
user-visible code addition, modification, or deletion (i.e., code that impacts
user interfaces) should concomitantly include updating documentation affected
by the change.

# Screenshots

![stash](https://fnc.bsdbox.org/uv/resources/img/fnc-stash.png "fnc stash")
![stash more](https://fnc.bsdbox.org/uv/resources/img/fnc-stash-more.png "stash more")
![stash help](https://fnc.bsdbox.org/uv/resources/img/fnc-stash-help.png "stash help")
![diff vsplit](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-vsplit.png "diff vertical split")
![diff hsplit renamed](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-hsplit-renamed.png "diff horizontal split file renamed")
![diff vsplit added](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-vsplit-added.png "diff vertical split file added")
![diff vsplit removed](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-vsplit-removed.png "diff vertical split file removed")
![blame vsplit](https://fnc.bsdbox.org/uv/resources/img/fnc-blame-vsplit.png "blame vertical split")
![tree vsplit](https://fnc.bsdbox.org/uv/resources/img/fnc-tree-vsplit.png "tree vertical split")
![branch hsplit](https://fnc.bsdbox.org/uv/resources/img/fnc-branch-hsplit.png "branch horizontal split")
![in-app help](https://fnc.bsdbox.org/uv/resources/img/fnc-inapp_help.png "fnc in-app help")
![timeline help](https://fnc.bsdbox.org/uv/resources/img/fnc-timeline-help.png "fnc timeline help")

# Trivia

**fnc** [fɪŋk]  
*noun* (n.)  
1. an interactive ncurses browser for [Fossil][0] repositories  
*verb* (v.)  
2. to inform  
etymology  
From the German word *Fink*, meaning "finch", a type of bird.

[0]: https://fossil-scm.org
[1]: https://fossil.wanderinghorse.net/r/libfossil
[2]: https://fossil-scm.org/home/help?cmd=ui
[3]: https://fnc.bsdbox.org/uv/download.html

[4]: https://fnc.bsdbox.org/uv/resources/doc/fnc.1.html
[5]: https://fnc.bsdbox.org/uv/resources/img/fnc-timeline-fullscreen.png
[6]: https://itac.bsdbox.org/fnc-demo.mp4
[7]: https://gameoftrees.org/tog.1.html
[8]: https://gameoftrees.org
[9]: mailto:fnc@bsdbox.org
[10]: https://fnc.bsdbox.org/forum
[11]: https://fnc.bsdbox.org/ticket
[12]: https://itac.bsdbox.org/listinfo/fnc


[13]: https://man.openbsd.org/style.9



Changes to fnc.bld.mk.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#
# FNC Common Build
#

# CONFIGURATION
CC ?=		cc
PREFIX ?=	/usr/local
MANDIR ?=	/share/man
VERSION ?=	0.13
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 \
		-DSQLITE_DEFAULT_MEMSTATUS=0 \
		-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \








|
<
<







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.12



# FLAGS NEEDED TO BUILD SQLITE3
SQLITE_CFLAGS =	${CFLAGS} -Wall -Werror -Wno-sign-compare -pedantic -std=c99 \
		-DNDEBUG=1 \
		-DSQLITE_DQS=0 \
		-DSQLITE_DEFAULT_MEMSTATUS=0 \
		-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
FOSSIL_CFLAGS =	${CFLAGS} -Wall -Werror -Wsign-compare -pedantic -std=c99

# On SOME Linux (e.g., Ubuntu 18.04.6), we have to include wchar curses from
# I/.../ncursesw, but linking to -lncursesw (w/ no special -L path) works fine.
# FLAGS NEEDED TO BUILD FNC
FNC_CFLAGS =	${CFLAGS} -Wall -Werror -Wsign-compare -pedantic -std=c99 \
		-I./lib -I./include -I/usr/include/ncursesw \
		-D_XOPEN_SOURCE_EXTENDED -DVERSION=${VERSION} -DHASH=${HASH} \
		-DDATE="${DATE}"

FNC_LDFLAGS =	${LDFLAGS} -lm -lutil -lz -lpthread -fPIC

all: bin

bin: lib/sqlite3.o lib/libfossil.o src/fnc.o src/fnc








|
<







38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
FOSSIL_CFLAGS =	${CFLAGS} -Wall -Werror -Wsign-compare -pedantic -std=c99

# On SOME Linux (e.g., Ubuntu 18.04.6), we have to include wchar curses from
# I/.../ncursesw, but linking to -lncursesw (w/ no special -L path) works fine.
# FLAGS NEEDED TO BUILD FNC
FNC_CFLAGS =	${CFLAGS} -Wall -Werror -Wsign-compare -pedantic -std=c99 \
		-I./lib -I./include -I/usr/include/ncursesw \
		-D_XOPEN_SOURCE_EXTENDED -DVERSION=${VERSION}


FNC_LDFLAGS =	${LDFLAGS} -lm -lutil -lz -lpthread -fPIC

all: bin

bin: lib/sqlite3.o lib/libfossil.o src/fnc.o src/fnc

77
78
79
80
81
82
83
84
85
86
87
88
89
uninstall:
	rm -f ${PREFIX}/bin/fnc ${PREFIX}${MANDIR}/man1/fnc.1

clean:
	rm -f lib/*.o src/*.o src/fnc

release: clean
	mkdir /tmp/fnc-${VERSION}
	pax -rw * /tmp/fnc-${VERSION}
	tar czvf ../fnc-${VERSION}.tgz -C /tmp fnc-${VERSION}
	rm -rf /tmp/fnc-${VERSION}

.PHONY: clean release







<
<
|
<


74
75
76
77
78
79
80


81

82
83
uninstall:
	rm -f ${PREFIX}/bin/fnc ${PREFIX}${MANDIR}/man1/fnc.1

clean:
	rm -f lib/*.o src/*.o src/fnc

release: clean


	tar czvf ../fnc-${VERSION}.tgz -C .. fnc-${VERSION}


.PHONY: clean release

Changes to include/diff.h.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
































40
41

42
43
44

45
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60
61
62
63
64
65



66
67
68
69
70
71

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "libfossil.h"
#include "settings.h"

/*
 * 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_HTML		= 1 << 5,
	FNC_DIFF_LINENO		= 1 << 6,  /* output diff with line numbers */
	FNC_DIFF_NOOPT		= 1 << 7,  /* og. 0x0100 */
	FNC_DIFF_INVERT		= 1 << 8,  /* og. 0x0200 */
	FNC_DIFF_NOTTOOBIG	= 1 << 9,  /* og. 0x0800 */
	FNC_DIFF_STRIP_EOLCR	= 1 << 10, /* og. 0x1000 */
	FNC_DIFF_ANSI_COLOR	= 1 << 11, /* og. 0x2000 */
	FNC_DIFF_PROTOTYPE	= 1 << 12  /* show func sig in hunk header */
#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
 * to the file in fsl_buffer parameter 2 and save the result to the provided

 * output fsl_buffer in parameter 3.
 *
 * A unified diff is output by default. This, along with other diff options
 * (detailed in the above fnc_diff_flag enum), can be changed by setting the
 * corresponding flags passed in int parameter 8.
 *
 * If a unified diff, parameter 7 is ignored, and the number of context lines
 * is specified in short parameter 6. Negative values fallback to default. If

 * a side-by-side diff, parameter 6 is ignored, and the column width of each
 * side is specified in short parameter 7; only values larger than the longest
 * line are honoured, otherwise the column width of each side will
 * automatically grow to accommodate the longest line in the diff.

 * If not NULL, the enum array pointer in paramater 4 and uint32_t pointer in
 * parameter 5 will be populated with each line_type and the total number of
 * lines in the diff, respectively.  Both pointers and the output buffer can
 * be prepopulated and must be disposed of by the caller.
 */
int fnc_diff_text_to_buffer(const fsl_buffer *, const fsl_buffer *,
    fsl_buffer *, enum line_type **, uint32_t *, short, short, int);




/*
 * Compute the diff of changes to convert the file in fsl_buffer parameter 1
 * to the file in fsl_buffer parameter 2 and invoke the fsl_output_f callback
 * in parameter 3 for each computed line.  The callback receives the provided
 * void parameter 4 as its output state and a char pointer of the diffed line
 * or diff metadata (e.g., hunk header, index).  Remaining parameters are the

 * same as the above fnc_diff_text_to_buffer() routine.
 */
int fnc_diff_text(const fsl_buffer *, const fsl_buffer *, fsl_output_f, void *,
    enum line_type **, uint32_t *, short, short, int);

/*
 * Compute and save to the int array pointer in parameter 4 the array of
 * copy/delete/insert triples that describes the sequence of changes to convert
 * the file in fsl_buffer parameter 1 to the file in fsl_buffer parameter 2.
 * Diff format and related options are set via flags passed in int parameter 3.
 */
int fnc_diff_text_raw(const fsl_buffer *, const fsl_buffer *, int, int **);

/*
 * Return the number of columns required to draw to the screen the char pointer
 * in parameter 1 by expanding any tabs and accounting for unicode continuation
 * bytes. Short parameter 2 may either be the byte size of the string or
 * a negative value.
 */
unsigned short etcount(const char *, unsigned short);

/*
 * Save the line_type specified in parameter 3 to the nth index denoted by the
 * uint32_t pointer in parameter 2 of the line_type array in parameter 1. The
 * uint32_t value pointed to by the 2nd parameter will be incremented.
 */
int add_line_type(enum line_type **, uint32_t *, enum line_type);







<
<
<



|
|
|
|
|
|
|
|
|
|
|


|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>
|
|
|
>
|
<
|
|
<
|
<
|
>
|
|
|
<
|
<
<
|
<
<
|
|
|
>
>
>
|
<
<
<
|
|
>
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
|
13
14
15
16
17
18
19



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

77
78

79

80
81
82
83
84

85


86


87
88
89
90
91
92
93



94
95
96
97
98
99
100
101

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117




118
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "libfossil.h"
#include "settings.h"




enum fnc_diff_flag {
	FNC_DIFF_IGNORE_EOLWS	= 0x01,
	FNC_DIFF_IGNORE_ALLWS	= 0x03,
	FNC_DIFF_SIDEBYSIDE	= 1 <<  2,
	FNC_DIFF_VERBOSE	= 1 <<  3,
	FNC_DIFF_BRIEF		= 1 <<  4,
	FNC_DIFF_HTML		= 1 <<  5,
	FNC_DIFF_LINENO		= 1 <<  6,
	FNC_DIFF_NOOPT		= 1 <<  7,  /* og. 0x0100 */
	FNC_DIFF_INVERT		= 1 <<  8,  /* og. 0x0200 */
	FNC_DIFF_NOTTOOBIG	= 1 <<  9,  /* og. 0x0800 */
	FNC_DIFF_STRIP_EOLCR	= 1 << 10,  /* og. 0x1000 */
	FNC_DIFF_ANSI_COLOR	= 1 << 11,  /* og. 0x2000 */
	FNC_DIFF_PROTOTYPE	= 1 << 12
#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)0x0fff0000)    /* SBS column width */
};

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 {
		const fsl_buffer	*file;		/* Diffed file */
		char			*signature;	/* Matching function */
		uint32_t		 lastmatch;	/* Match line index */
		uint32_t		 lastline;	/* Last line scanned */
		fsl_size_t		 offset;	/* Match byte offset */
	} proto;
};
static const struct diff_out_state diff_out_state_empty =
    { NULL, NULL, NULL, 0, 0, 0, { NULL, NULL, 0, 0, 0 } };

struct sbsline {
	struct diff_out_state	*output;
	fsl_buffer		*cols[5];	/* Pointers to output columns */
	const char		*tag;		/* <span> tag */
	const char		*tag2;		/* <span> tag */
	int			 idx;		/* Write tag before idx */
	int			 end;		/* Close tag before end */
	int			 idx2;		/* Write tag2 before idx2 */
	int			 end2;		/* Close tag2 before end2 */
	int			 width;		/* Max column width in diff */
	bool			 esc;		/* Escape html characters */
	void			*regex;		/* Colour matching lines */
};

int		 fnc_diff_text_raw(fsl_buffer const *, fsl_buffer const *,
		    int, int **);
int		 fnc_diff_text_to_buffer(fsl_buffer const *, fsl_buffer const *,
		    fsl_buffer *, enum line_type **, uint32_t *, short, short,
		    int );
int		 fnc_diff_text(fsl_buffer const *, fsl_buffer const *,

		    fsl_output_f, void *, short, short, int );
int		 fnc_diff_blobs(fsl_buffer const *, fsl_buffer const *,

		    fsl_output_f, void *, enum line_type **, uint32_t *,

		    uint16_t, short, int, int **);
int		 fnc_output_f_diff_out(void *, void const *, fsl_size_t);
int		 diff_outf(struct diff_out_state *, char const *, ... );
int		 diff_out(struct diff_out_state * const, void const *,
		    fsl_int_t);

char		*match_chunk_function(struct diff_out_state *const, uint32_t);


int		 buffer_copy_lines_from(fsl_buffer *const,


		    const fsl_buffer *const, fsl_size_t *, fsl_size_t,
		    fsl_size_t);
uint64_t	 fnc_diff_flags_convert(int);
int		 diff_context_lines(uint64_t);
int		 match_dline(fsl_dline *, fsl_dline *);
bool		 longest_common_subsequence(const char *z, int, const char *,
		    int, int *);



int		 unidiff(fsl__diff_cx *, struct diff_out_state *, void *,
		    uint16_t, uint64_t);
int		 unidiff_lineno(struct diff_out_state *, int, int, bool);
int		 unidiff_txt( struct diff_out_state *const, char, fsl_dline *,
		    int, void *);
int		 sbsdiff(fsl__diff_cx *, struct diff_out_state *, void *,
		    uint16_t, uint64_t);
int		 max_sbs_width(fsl__diff_cx *, int *, uint16_t);

unsigned short	 etcount(const char *str, unsigned short n);
int		 sbsdiff_width(uint64_t);
int		 sbsdiff_separator(struct sbsline *, int, int);
int		 sbsdiff_lineno(struct sbsline *, int, int);
void		 sbsdiff_shift_left(struct sbsline *, const char *);
void		 sbsdiff_simplify_line(struct sbsline *, const char *);
int		 sbsdiff_column(struct diff_out_state *,
		    fsl_buffer const *, int);
int		 sbsdiff_txt(struct sbsline *, fsl_dline *, int);
int		 sbsdiff_newline(struct sbsline *);
int		 sbsdiff_space(struct sbsline *, int, int);
int		 sbsdiff_marker(struct sbsline *, const char *, const char *);
int		 sbsdiff_close_gap(int *);
unsigned char	*sbsdiff_align(fsl_dline *, int, fsl_dline *, int);
int		 sbsdiff_write_change(struct sbsline *, fsl_dline *, int,
		    fsl_dline *, int);




int		 add_line_type(enum line_type **, uint32_t *, enum line_type);

Changes to include/settings.h.

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
	_(pfx, START_SETTINGS),					\
	_(pfx, COLOUR_COMMIT),					\
	_(pfx, COLOUR_USER),					\
	_(pfx, COLOUR_DATE),					\
	_(pfx, COLOUR_DIFF_META),				\
	_(pfx, COLOUR_DIFF_MINUS),				\
	_(pfx, COLOUR_DIFF_PLUS),				\
	_(pfx, COLOUR_DIFF_HUNK),				\
	_(pfx, COLOUR_DIFF_TAGS),				\
	_(pfx, COLOUR_DIFF_SBS_EDIT),				\
	_(pfx, COLOUR_TREE_LINK),				\
	_(pfx, COLOUR_TREE_DIR),				\
	_(pfx, COLOUR_TREE_EXEC),				\
	_(pfx, COLOUR_BRANCH_OPEN),				\
	_(pfx, COLOUR_BRANCH_CLOSED),				\







|







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
	_(pfx, START_SETTINGS),					\
	_(pfx, COLOUR_COMMIT),					\
	_(pfx, COLOUR_USER),					\
	_(pfx, COLOUR_DATE),					\
	_(pfx, COLOUR_DIFF_META),				\
	_(pfx, COLOUR_DIFF_MINUS),				\
	_(pfx, COLOUR_DIFF_PLUS),				\
	_(pfx, COLOUR_DIFF_CHUNK),				\
	_(pfx, COLOUR_DIFF_TAGS),				\
	_(pfx, COLOUR_DIFF_SBS_EDIT),				\
	_(pfx, COLOUR_TREE_LINK),				\
	_(pfx, COLOUR_TREE_DIR),				\
	_(pfx, COLOUR_TREE_EXEC),				\
	_(pfx, COLOUR_BRANCH_OPEN),				\
	_(pfx, COLOUR_BRANCH_CLOSED),				\
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
	_(pfx, DIFF_CHANGESET),					\
	_(pfx, DIFF_INDEX),					\
	_(pfx, DIFF_META),					\
	_(pfx, DIFF_MINUS),					\
	_(pfx, DIFF_PLUS),					\
	_(pfx, DIFF_EDIT),					\
	_(pfx, DIFF_CONTEXT),					\
	_(pfx, DIFF_HUNK),					\
	_(pfx, DIFF_SEPARATOR)

#define VIEW_MODE_ENUM(pfx, _)					\
	_(pfx, NONE),						\
	_(pfx, VERT),						\
	_(pfx, HRZN)








|







74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
	_(pfx, DIFF_CHANGESET),					\
	_(pfx, DIFF_INDEX),					\
	_(pfx, DIFF_META),					\
	_(pfx, DIFF_MINUS),					\
	_(pfx, DIFF_PLUS),					\
	_(pfx, DIFF_EDIT),					\
	_(pfx, DIFF_CONTEXT),					\
	_(pfx, DIFF_CHUNK),					\
	_(pfx, DIFF_SEPARATOR)

#define VIEW_MODE_ENUM(pfx, _)					\
	_(pfx, NONE),						\
	_(pfx, VERT),						\
	_(pfx, HRZN)

Changes to lib/libfossil-config.h.

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"
#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_"







|
|
|







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 "2a405470c0d32a84f8644de1b5c509635a7e78cb"
#define FSL_LIB_VERSION_TIMESTAMP "2021-11-18 17:40:56.695 UTC"
#define FSL_LIB_CONFIG_TIME "2021-11-18 18:02 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.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
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
706
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
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
#include "libfossil.h"
/* start of file ./src/xdirent.h */
/**
  Origin: https://gist.github.com/isakbosman/758eb668938806aabb04830736f4ac41

  Modified only very slightly for use in the libfossil project: a
  couple of #if's were added to allow us to include this file without
  having to check which platform we're building on. In non-Windows builds
  it uses the corresponding POSIX APIs.
*/
/*
 * dirent.h - dirent API for Microsoft Visual Studio
 *
 * Copyright (C) 2006-2012 Toni Ronkko
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * ``Software''), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * $Id: dirent.h,v 1.20 2014/03/19 17:52:23 tronkko Exp $
 */
#ifndef DIRENT_H
#define DIRENT_H

#if FSL_PLATFORM_IS_WINDOWS
/*
 * Define architecture flags so we don't need to include windows.h.
 * Avoiding windows.h makes it simpler to use windows sockets in conjunction
 * with dirent.h.
 */
#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_IX86)
#   define _X86_
#endif
#if !defined(_68K_) && !defined(_MPPC_) && !defined(_X86_) && !defined(_IA64_) && !defined(_AMD64_) && defined(_M_AMD64)
#define _AMD64_
#endif

#include <stdio.h>
#include <stdarg.h>
#include <windef.h>
#include <winbase.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

/* Indicates that d_type field is available in dirent structure */
#define _DIRENT_HAVE_D_TYPE

/* Indicates that d_namlen field is available in dirent structure */
#define _DIRENT_HAVE_D_NAMLEN

/* Entries missing from MSVC 6.0 */
#if !defined(FILE_ATTRIBUTE_DEVICE)
#   define FILE_ATTRIBUTE_DEVICE 0x40
#endif

/* File type and permission flags for stat() */
#if !defined(S_IFMT)
#   define S_IFMT   _S_IFMT                     /* File type mask */
#endif
#if !defined(S_IFDIR)
#   define S_IFDIR  _S_IFDIR                    /* Directory */
#endif
#if !defined(S_IFCHR)
#   define S_IFCHR  _S_IFCHR                    /* Character device */
#endif
#if !defined(S_IFFIFO)
#   define S_IFFIFO _S_IFFIFO                   /* Pipe */
#endif
#if !defined(S_IFREG)
#   define S_IFREG  _S_IFREG                    /* Regular file */
#endif
#if !defined(S_IREAD)
#   define S_IREAD  _S_IREAD                    /* Read permission */
#endif
#if !defined(S_IWRITE)
#   define S_IWRITE _S_IWRITE                   /* Write permission */
#endif
#if !defined(S_IEXEC)
#   define S_IEXEC  _S_IEXEC                    /* Execute permission */
#endif
#if !defined(S_IFIFO)
#   define S_IFIFO _S_IFIFO                     /* Pipe */
#endif
#if !defined(S_IFBLK)
#   define S_IFBLK   0                          /* Block device */
#endif
#if !defined(S_IFLNK)
#   define S_IFLNK   0                          /* Link */
#endif
#if !defined(S_IFSOCK)
#   define S_IFSOCK  0                          /* Socket */
#endif

#if defined(_MSC_VER)
#   define S_IRUSR  S_IREAD                     /* Read user */
#   define S_IWUSR  S_IWRITE                    /* Write user */
#   define S_IXUSR  0                           /* Execute user */
#   define S_IRGRP  0                           /* Read group */
#   define S_IWGRP  0                           /* Write group */
#   define S_IXGRP  0                           /* Execute group */
#   define S_IROTH  0                           /* Read others */
#   define S_IWOTH  0                           /* Write others */
#   define S_IXOTH  0                           /* Execute others */
#endif

/* Maximum length of file name */
#if !defined(PATH_MAX)
#   define PATH_MAX MAX_PATH
#endif
#if !defined(FILENAME_MAX)
#   define FILENAME_MAX MAX_PATH
#endif
#if !defined(NAME_MAX)
#   define NAME_MAX FILENAME_MAX
#endif

/* File type flags for d_type */
#define DT_UNKNOWN  0
#define DT_REG      S_IFREG
#define DT_DIR      S_IFDIR
#define DT_FIFO     S_IFIFO
#define DT_SOCK     S_IFSOCK
#define DT_CHR      S_IFCHR
#define DT_BLK      S_IFBLK
#define DT_LNK      S_IFLNK

/* Macros for converting between st_mode and d_type */
#define IFTODT(mode) ((mode) & S_IFMT)
#define DTTOIF(type) (type)

/*
 * File type macros.  Note that block devices, sockets and links cannot be
 * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are
 * only defined for compatibility.  These macros should always return false
 * on Windows.
 */
#define	S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO)
#define	S_ISDIR(mode)  (((mode) & S_IFMT) == S_IFDIR)
#define	S_ISREG(mode)  (((mode) & S_IFMT) == S_IFREG)
#define	S_ISLNK(mode)  (((mode) & S_IFMT) == S_IFLNK)
#define	S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)
#define	S_ISCHR(mode)  (((mode) & S_IFMT) == S_IFCHR)
#define	S_ISBLK(mode)  (((mode) & S_IFMT) == S_IFBLK)

/* Return the exact length of d_namlen without zero terminator */
#define _D_EXACT_NAMLEN(p) ((p)->d_namlen)

/* Return number of bytes needed to store d_namlen */
#define _D_ALLOC_NAMLEN(p) (PATH_MAX)


#ifdef __cplusplus
extern "C" {
#endif


/* Wide-character version */
struct _wdirent {
    long d_ino;                                 /* Always zero */
    unsigned short d_reclen;                    /* Structure size */
    size_t d_namlen;                            /* Length of name without \0 */
    int d_type;                                 /* File type */
    wchar_t d_name[PATH_MAX];                   /* File name */
};
typedef struct _wdirent _wdirent;

struct _WDIR {
    struct _wdirent ent;                        /* Current directory entry */
    WIN32_FIND_DATAW data;                      /* Private file data */
    int cached;                                 /* True if data is valid */
    HANDLE handle;                              /* Win32 search handle */
    wchar_t *patt;                              /* Initial directory name */
};
typedef struct _WDIR _WDIR;

static _WDIR *_wopendir (const wchar_t *dirname);
static struct _wdirent *_wreaddir (_WDIR *dirp);
static int _wclosedir (_WDIR *dirp);
static void _wrewinddir (_WDIR* dirp);


/* For compatibility with Symbian */
#define wdirent _wdirent
#define WDIR _WDIR
#define wopendir _wopendir
#define wreaddir _wreaddir
#define wclosedir _wclosedir
#define wrewinddir _wrewinddir


/* Multi-byte character versions */
struct dirent {
    long d_ino;                                 /* Always zero */
    unsigned short d_reclen;                    /* Structure size */
    size_t d_namlen;                            /* Length of name without \0 */
    int d_type;                                 /* File type */
    char d_name[PATH_MAX];                      /* File name */
};
typedef struct dirent dirent;

struct DIR {
    struct dirent ent;
    struct _WDIR *wdirp;
};
typedef struct DIR DIR;

static DIR *opendir (const char *dirname);
static struct dirent *readdir (DIR *dirp);
static int closedir (DIR *dirp);
static void rewinddir (DIR* dirp);


/* Internal utility functions */
static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp);
static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp);

static int dirent_mbstowcs_s(
    size_t *pReturnValue,
    wchar_t *wcstr,
    size_t sizeInWords,
    const char *mbstr,
    size_t count);

static int dirent_wcstombs_s(
    size_t *pReturnValue,
    char *mbstr,
    size_t sizeInBytes,
    const wchar_t *wcstr,
    size_t count);

static void dirent_set_errno (int error);

/*
 * Open directory stream DIRNAME for read and return a pointer to the
 * internal working area that is used to retrieve individual directory
 * entries.
 */
static _WDIR*
_wopendir(
    const wchar_t *dirname)
{
    _WDIR *dirp = NULL;
    int error;

    /* Must have directory name */
    if (dirname == NULL  ||  dirname[0] == '\0') {
        dirent_set_errno (ENOENT);
        return NULL;
    }

    /* Allocate new _WDIR structure */
    dirp = (_WDIR*) malloc (sizeof (struct _WDIR));
    if (dirp != NULL) {
        DWORD n;

        /* Reset _WDIR structure */
        dirp->handle = INVALID_HANDLE_VALUE;
        dirp->patt = NULL;
        dirp->cached = 0;

        /* Compute the length of full path plus zero terminator */
        n = GetFullPathNameW (dirname, 0, NULL, NULL);

        /* Allocate room for absolute directory name and search pattern */
        dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16);
        if (dirp->patt) {

            /*
             * Convert relative directory name to an absolute one.  This
             * allows rewinddir() to function correctly even when current
             * working directory is changed between opendir() and rewinddir().
             */
            n = GetFullPathNameW (dirname, n, dirp->patt, NULL);
            if (n > 0) {
                wchar_t *p;

                /* Append search pattern \* to the directory name */
                p = dirp->patt + n;
                if (dirp->patt < p) {
                    switch (p[-1]) {
                    case '\\':
                    case '/':
                    case ':':
                        /* Directory ends in path separator, e.g. c:\temp\ */
                        /*NOP*/;
                        break;

                    default:
                        /* Directory name doesn't end in path separator */
                        *p++ = '\\';
                    }
                }
                *p++ = '*';
                *p = '\0';

                /* Open directory stream and retrieve the first entry */
                if (dirent_first (dirp)) {
                    /* Directory stream opened successfully */
                    error = 0;
                } else {
                    /* Cannot retrieve first entry */
                    error = 1;
                    dirent_set_errno (ENOENT);
                }

            } else {
                /* Cannot retrieve full path name */
                dirent_set_errno (ENOENT);
                error = 1;
            }

        } else {
            /* Cannot allocate memory for search pattern */
            error = 1;
        }

    } else {
        /* Cannot allocate _WDIR structure */
        error = 1;
    }

    /* Clean up in case of error */
    if (error  &&  dirp) {
        _wclosedir (dirp);
        dirp = NULL;
    }

    return dirp;
}

/*
 * Read next directory entry.  The directory entry is returned in dirent
 * structure in the d_name field.  Individual directory entries returned by
 * this function include regular files, sub-directories, pseudo-directories
 * "." and ".." as well as volume labels, hidden files and system files.
 */
static struct _wdirent*
_wreaddir(
    _WDIR *dirp)
{
    WIN32_FIND_DATAW *datap;
    struct _wdirent *entp;

    /* Read next directory entry */
    datap = dirent_next (dirp);
    if (datap) {
        size_t n;
        DWORD attr;
        
        /* Pointer to directory entry to return */
        entp = &dirp->ent;

        /* 
         * Copy file name as wide-character string.  If the file name is too
         * long to fit in to the destination buffer, then truncate file name
         * to PATH_MAX characters and zero-terminate the buffer.
         */
        n = 0;
        while (n + 1 < PATH_MAX  &&  datap->cFileName[n] != 0) {
            entp->d_name[n] = datap->cFileName[n];
            n++;
        }
        dirp->ent.d_name[n] = 0;

        /* Length of file name excluding zero terminator */
        entp->d_namlen = n;

        /* File type */
        attr = datap->dwFileAttributes;
        if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {
            entp->d_type = DT_CHR;
        } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
            entp->d_type = DT_DIR;
        } else {
            entp->d_type = DT_REG;
        }

        /* Reset dummy fields */
        entp->d_ino = 0;
        entp->d_reclen = sizeof (struct _wdirent);

    } else {

        /* Last directory entry read */
        entp = NULL;

    }

    return entp;
}

/*
 * Close directory stream opened by opendir() function.  This invalidates the
 * DIR structure as well as any directory entry read previously by
 * _wreaddir().
 */
static int
_wclosedir(
    _WDIR *dirp)
{
    int ok;
    if (dirp) {

        /* Release search handle */
        if (dirp->handle != INVALID_HANDLE_VALUE) {
            FindClose (dirp->handle);
            dirp->handle = INVALID_HANDLE_VALUE;
        }

        /* Release search pattern */
        if (dirp->patt) {
            free (dirp->patt);
            dirp->patt = NULL;
        }

        /* Release directory structure */
        free (dirp);
        ok = /*success*/0;

    } else {
        /* Invalid directory stream */
        dirent_set_errno (EBADF);
        ok = /*failure*/-1;
    }
    return ok;
}

/*
 * Rewind directory stream such that _wreaddir() returns the very first
 * file name again.
 */
static void
_wrewinddir(
    _WDIR* dirp)
{
    if (dirp) {
        /* Release existing search handle */
        if (dirp->handle != INVALID_HANDLE_VALUE) {
            FindClose (dirp->handle);
        }

        /* Open new search handle */
        dirent_first (dirp);
    }
}

/* Get first directory entry (internal) */
static WIN32_FIND_DATAW*
dirent_first(
    _WDIR *dirp)
{
    WIN32_FIND_DATAW *datap;

    /* Open directory and retrieve the first entry */
    dirp->handle = FindFirstFileW (dirp->patt, &dirp->data);
    if (dirp->handle != INVALID_HANDLE_VALUE) {

        /* a directory entry is now waiting in memory */
        datap = &dirp->data;
        dirp->cached = 1;

    } else {

        /* Failed to re-open directory: no directory entry in memory */
        dirp->cached = 0;
        datap = NULL;

    }
    return datap;
}

/* Get next directory entry (internal) */
static WIN32_FIND_DATAW*
dirent_next(
    _WDIR *dirp)
{
    WIN32_FIND_DATAW *p;

    /* Get next directory entry */
    if (dirp->cached != 0) {

        /* A valid directory entry already in memory */
        p = &dirp->data;
        dirp->cached = 0;

    } else if (dirp->handle != INVALID_HANDLE_VALUE) {

        /* Get the next directory entry from stream */
        if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) {
            /* Got a file */
            p = &dirp->data;
        } else {
            /* The very last entry has been processed or an error occured */
            FindClose (dirp->handle);
            dirp->handle = INVALID_HANDLE_VALUE;
            p = NULL;
        }

    } else {

        /* End of directory stream reached */
        p = NULL;

    }

    return p;
}

/* 
 * Open directory stream using plain old C-string.
 */
static DIR*
opendir(
    const char *dirname) 
{
    struct DIR *dirp;
    int error;

    /* Must have directory name */
    if (dirname == NULL  ||  dirname[0] == '\0') {
        dirent_set_errno (ENOENT);
        return NULL;
    }

    /* Allocate memory for DIR structure */
    dirp = (DIR*) malloc (sizeof (struct DIR));
    if (dirp) {
        wchar_t wname[PATH_MAX];
        size_t n;

        /* Convert directory name to wide-character string */
        error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX);
        if (!error) {

            /* Open directory stream using wide-character name */
            dirp->wdirp = _wopendir (wname);
            if (dirp->wdirp) {
                /* Directory stream opened */
                error = 0;
            } else {
                /* Failed to open directory stream */
                error = 1;
            }

        } else {
            /* 
             * Cannot convert file name to wide-character string.  This
             * occurs if the string contains invalid multi-byte sequences or
             * the output buffer is too small to contain the resulting
             * string.
             */
            error = 1;
        }

    } else {
        /* Cannot allocate DIR structure */
        error = 1;
    }

    /* Clean up in case of error */
    if (error  &&  dirp) {
        free (dirp);
        dirp = NULL;
    }

    return dirp;
}

/*
 * Read next directory entry.
 *
 * When working with text consoles, please note that file names returned by
 * readdir() are represented in the default ANSI code page while any output to
 * console is typically formatted on another code page.  Thus, non-ASCII
 * characters in file names will not usually display correctly on console.  The
 * problem can be fixed in two ways: (1) change the character set of console
 * to 1252 using chcp utility and use Lucida Console font, or (2) use
 * _cprintf function when writing to console.  The _cprinf() will re-encode
 * ANSI strings to the console code page so many non-ASCII characters will
 * display correcly.
 */
static struct dirent*
readdir(
    DIR *dirp) 
{
    WIN32_FIND_DATAW *datap;
    struct dirent *entp;

    /* Read next directory entry */
    datap = dirent_next (dirp->wdirp);
    if (datap) {
        size_t n;
        int error;

        /* Attempt to convert file name to multi-byte string */
        error = dirent_wcstombs_s(
            &n, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX);

        /* 
         * If the file name cannot be represented by a multi-byte string,
         * then attempt to use old 8+3 file name.  This allows traditional
         * Unix-code to access some file names despite of unicode
         * characters, although file names may seem unfamiliar to the user.
         *
         * Be ware that the code below cannot come up with a short file
         * name unless the file system provides one.  At least
         * VirtualBox shared folders fail to do this.
         */
        if (error  &&  datap->cAlternateFileName[0] != '\0') {
            error = dirent_wcstombs_s(
                &n, dirp->ent.d_name, PATH_MAX, 
                datap->cAlternateFileName, PATH_MAX);
        }

        if (!error) {
            DWORD attr;

            /* Initialize directory entry for return */
            entp = &dirp->ent;

            /* Length of file name excluding zero terminator */
            entp->d_namlen = n - 1;

            /* File attributes */
            attr = datap->dwFileAttributes;
            if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {
                entp->d_type = DT_CHR;
            } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
                entp->d_type = DT_DIR;
            } else {
                entp->d_type = DT_REG;
            }

            /* Reset dummy fields */
            entp->d_ino = 0;
            entp->d_reclen = sizeof (struct dirent);

        } else {
            /* 
             * Cannot convert file name to multi-byte string so construct
             * an errornous directory entry and return that.  Note that
             * we cannot return NULL as that would stop the processing
             * of directory entries completely.
             */
            entp = &dirp->ent;
            entp->d_name[0] = '?';
            entp->d_name[1] = '\0';
            entp->d_namlen = 1;
            entp->d_type = DT_UNKNOWN;
            entp->d_ino = 0;
            entp->d_reclen = 0;
        }

    } else {
        /* No more directory entries */
        entp = NULL;
    }

    return entp;
}

/*
 * Close directory stream.
 */
static int
closedir(
    DIR *dirp) 
{
    int ok;
    if (dirp) {

        /* Close wide-character directory stream */
        ok = _wclosedir (dirp->wdirp);
        dirp->wdirp = NULL;

        /* Release multi-byte character version */
        free (dirp);

    } else {

        /* Invalid directory stream */
        dirent_set_errno (EBADF);
        ok = /*failure*/-1;

    }
    return ok;
}

/*
 * Rewind directory stream to beginning.
 */
static void
rewinddir(
    DIR* dirp) 
{
    /* Rewind wide-character string directory stream */
    _wrewinddir (dirp->wdirp);
}

/* Convert multi-byte string to wide character string */
static int
dirent_mbstowcs_s(
    size_t *pReturnValue,
    wchar_t *wcstr,
    size_t sizeInWords,
    const char *mbstr,
    size_t count)
{
    int error;

#if defined(_MSC_VER)  &&  _MSC_VER >= 1400

    /* Microsoft Visual Studio 2005 or later */
    error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count);

#else

    /* Older Visual Studio or non-Microsoft compiler */
    size_t n;

    /* Convert to wide-character string (or count characters) */
    n = mbstowcs (wcstr, mbstr, sizeInWords);
    if (!wcstr  ||  n < count) {

        /* Zero-terminate output buffer */
        if (wcstr  &&  sizeInWords) {
            if (n >= sizeInWords) {
                n = sizeInWords - 1;
            }
            wcstr[n] = 0;
        }

        /* Length of resuting multi-byte string WITH zero terminator */
        if (pReturnValue) {
            *pReturnValue = n + 1;
        }

        /* Success */
        error = 0;

    } else {

        /* Could not convert string */
        error = 1;

    }

#endif

    return error;
}

/* Convert wide-character string to multi-byte string */
static int
dirent_wcstombs_s(
    size_t *pReturnValue,
    char *mbstr,
    size_t sizeInBytes, /* max size of mbstr */
    const wchar_t *wcstr,
    size_t count)
{
    int error;

#if defined(_MSC_VER)  &&  _MSC_VER >= 1400

    /* Microsoft Visual Studio 2005 or later */
    error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count);

#else

    /* Older Visual Studio or non-Microsoft compiler */
    size_t n;

    /* Convert to multi-byte string (or count the number of bytes needed) */
    n = wcstombs (mbstr, wcstr, sizeInBytes);
    if (!mbstr  ||  n < count) {

        /* Zero-terminate output buffer */
        if (mbstr  &&  sizeInBytes) {
            if (n >= sizeInBytes) {
                n = sizeInBytes - 1;
            }
            mbstr[n] = '\0';
        }

        /* Lenght of resulting multi-bytes string WITH zero-terminator */
        if (pReturnValue) {
            *pReturnValue = n + 1;
        }

        /* Success */
        error = 0;

    } else {

        /* Cannot convert string */
        error = 1;

    }

#endif

    return error;
}

/* Set errno variable */
static void
dirent_set_errno(
    int error)
{
#if defined(_MSC_VER)  &&  _MSC_VER >= 1400

    /* Microsoft Visual Studio 2005 and later */
    _set_errno (error);

#else

    /* Non-Microsoft compiler or older Microsoft compiler */
    errno = error;

#endif
}


#ifdef __cplusplus
}
#endif

#else /* !FSL_PLATFORM_IS_WINDOWS */
#include <sys/types.h>
#include <dirent.h>
#endif /* !FSL_PLATFORM_IS_WINDOWS */

#endif /*DIRENT_H*/
/* end of file ./src/xdirent.h */
/* start of file ./src/fsl.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1






















































































































































































































































































































































































































































































































































































































































































































































































































































































2
3
4
5
6
7
8
#include "libfossil.h"






















































































































































































































































































































































































































































































































































































































































































































































































































































































/* start of file ./src/fsl.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
const fsl_card_Q fsl_card_Q_empty = fsl_card_Q_empty_m;
const fsl_card_T fsl_card_T_empty = fsl_card_T_empty_m;
const fsl_checkin_opt fsl_checkin_opt_empty = fsl_checkin_opt_empty_m;
const fsl_cidiff_opt fsl_cidiff_opt_empty = fsl_cidiff_opt_empty_m;
const fsl_cidiff_state fsl_cidiff_state_empty = fsl_cidiff_state_empty_m;
const fsl_ckout_manage_opt fsl_ckout_manage_opt_empty =
  fsl_ckout_manage_opt_empty_m;
const fsl_ckout_rename_opt fsl_ckout_rename_opt_empty =
  fsl_ckout_rename_opt_empty_m;
const fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt_empty =
  fsl_ckout_unmanage_opt_empty_m;
const fsl_ckup_opt fsl_ckup_opt_empty = fsl_ckup_opt_m;
const fsl_confirmer fsl_confirmer_empty = fsl_confirmer_empty_m;
const fsl_cx fsl_cx_empty = fsl_cx_empty_m;
const fsl_cx_config fsl_cx_config_empty = fsl_cx_config_empty_m;
const fsl_cx_init_opt fsl_cx_init_opt_default = fsl_cx_init_opt_default_m;







<
<







48
49
50
51
52
53
54


55
56
57
58
59
60
61
const fsl_card_Q fsl_card_Q_empty = fsl_card_Q_empty_m;
const fsl_card_T fsl_card_T_empty = fsl_card_T_empty_m;
const fsl_checkin_opt fsl_checkin_opt_empty = fsl_checkin_opt_empty_m;
const fsl_cidiff_opt fsl_cidiff_opt_empty = fsl_cidiff_opt_empty_m;
const fsl_cidiff_state fsl_cidiff_state_empty = fsl_cidiff_state_empty_m;
const fsl_ckout_manage_opt fsl_ckout_manage_opt_empty =
  fsl_ckout_manage_opt_empty_m;


const fsl_ckout_unmanage_opt fsl_ckout_unmanage_opt_empty =
  fsl_ckout_unmanage_opt_empty_m;
const fsl_ckup_opt fsl_ckup_opt_empty = fsl_ckup_opt_m;
const fsl_confirmer fsl_confirmer_empty = fsl_confirmer_empty_m;
const fsl_cx fsl_cx_empty = fsl_cx_empty_m;
const fsl_cx_config fsl_cx_config_empty = fsl_cx_config_empty_m;
const fsl_cx_init_opt fsl_cx_init_opt_default = fsl_cx_init_opt_default_m;
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
  }else{
    /* realloc() */
    return FLCA.f(FLCA.state, mem, n);
  }
#undef FLCA
}

void * fsl_realloc_f_stdalloc(void * state __unused, void * mem, fsl_size_t n){
  if(!mem){
    return malloc(n);
  }else if(!n){
    free(mem);
    return NULL;
  }else{
    return realloc(mem, n);







|







129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  }else{
    /* realloc() */
    return FLCA.f(FLCA.state, mem, n);
  }
#undef FLCA
}

void * fsl_realloc_f_stdalloc(void * state, void * mem, fsl_size_t n){
  if(!mem){
    return malloc(n);
  }else if(!n){
    free(mem);
    return NULL;
  }else{
    return realloc(mem, n);
1022
1023
1024
1025
1026
1027
1028
1029

1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054



1055



1056
1057

1058
1059

1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
void fsl_error_clear( fsl_error * const err ){
  fsl_buffer_clear(&err->msg);
  *err = fsl_error_empty;
}

void fsl_error_reset( fsl_error * const err ){
  err->code = 0;
  fsl_buffer_reuse(&err->msg);

}

int fsl_error_copy( fsl_error const * const src, fsl_error * const dest ){
  if(src==dest) return FSL_RC_MISUSE;
  else {
    int rc = 0;
    fsl_buffer_reuse(&dest->msg);
    dest->code = src->code;
    if(FSL_RC_OOM!=src->code){
      rc = fsl_buffer_append( &dest->msg, src->msg.mem, src->msg.used );
    }
    return rc;
  }
}

void fsl_error_move( fsl_error * const lower, fsl_error * const higher ){
  fsl_error const err = *lower;
  *lower = *higher;
  lower->code = 0;
  lower->msg.used = lower->msg.cursor = 0;
  *higher = err;
}

int fsl_error_setv( fsl_error * const err, int code, char const * fmt,
                    va_list args ){



  fsl_buffer_reuse(&err->msg);



  if(code){
    int rc = 0;

    err->code = code;
    if(FSL_RC_OOM!=code){

      if(fmt) rc = fsl_buffer_appendfv(&err->msg, fmt, args);
      else rc = fsl_buffer_appendf(&err->msg, "fsl_rc_e #%d: %s",
                                   code, fsl_rc_cstr(code));
      if(rc) err->code = rc;
    }
    return rc ? rc : code;
  }else{ /* clear error state */
    err->code = 0;
    return 0;
  }

}

int fsl_error_set( fsl_error * const err, int code, char const * fmt,
                   ... ){
  int rc;
  va_list args;
  va_start(args,fmt);







|
>






|


















>
>
>
|
>
>
>
|

>


>
|
<
|



<
<
<

<







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
202
203
204
205
206
207
208
209
210
211
212
213

214
215
216
217



218

219
220
221
222
223
224
225
void fsl_error_clear( fsl_error * const err ){
  fsl_buffer_clear(&err->msg);
  *err = fsl_error_empty;
}

void fsl_error_reset( fsl_error * const err ){
  err->code = 0;
  err->msg.used = err->msg.cursor = 0;
  if(err->msg.mem) err->msg.mem[0] = 0;
}

int fsl_error_copy( fsl_error const * const src, fsl_error * const dest ){
  if(src==dest) return FSL_RC_MISUSE;
  else {
    int rc = 0;
    dest->msg.used = dest->msg.cursor = 0;
    dest->code = src->code;
    if(FSL_RC_OOM!=src->code){
      rc = fsl_buffer_append( &dest->msg, src->msg.mem, src->msg.used );
    }
    return rc;
  }
}

void fsl_error_move( fsl_error * const lower, fsl_error * const higher ){
  fsl_error const err = *lower;
  *lower = *higher;
  lower->code = 0;
  lower->msg.used = lower->msg.cursor = 0;
  *higher = err;
}

int fsl_error_setv( fsl_error * const err, int code, char const * fmt,
                    va_list args ){
  if(!code){ /* clear error state */
    err->code = 0;
    err->msg.used = err->msg.cursor = 0;
    if(err->msg.mem){
      err->msg.mem[0] = 0;
    }
    return 0;
  }else{
    int rc = 0;
    err->msg.used = err->msg.cursor = 0;
    err->code = code;
    if(FSL_RC_OOM!=code){
      rc = fmt
        ? fsl_buffer_appendfv(&err->msg, fmt, args)

        : fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1);
      if(rc) err->code = rc;
    }
    return rc ? rc : code;



  }

}

int fsl_error_set( fsl_error * const err, int code, char const * fmt,
                   ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097


1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
            : NULL;
  if(len) *len = err->msg.used;
  return err->code;
}


char const * fsl_rc_cstr(int rc){
  switch((fsl_rc_e)rc){
    /* we cast ^^^^ so that gcc will warn if the switch() below is
       missing any fsl_rc_e entries. */;


#define STR(T) case FSL_RC_##T: return "FSL_RC_" #T
    STR(ACCESS);
    STR(ALREADY_EXISTS);
    STR(AMBIGUOUS);
    STR(BREAK);
    STR(SYNTAX);
    STR(CANNOT_HAPPEN);
    STR(CHECKSUM_MISMATCH);
    STR(CONFLICT);
    STR(CONSISTENCY);
    STR(DB);
    STR(DELTA_INVALID_OPERATOR);
    STR(DELTA_INVALID_SEPARATOR);
    STR(DELTA_INVALID_SIZE);
    STR(DELTA_INVALID_TERMINATOR);
    STR(DIFF_BINARY);
    STR(DIFF_WS_ONLY);
    STR(end);
    STR(ERROR);
    STR(INTERRUPTED);
    STR(IO);
    STR(LOCKED);
    STR(MISSING_INFO);
    STR(MISUSE);
    STR(NOOP);
    STR(NOT_A_CKOUT);
    STR(NOT_A_REPO);
    STR(NOT_FOUND);
    STR(NYI);







|
|
|
>
>






<














<







236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253

254
255
256
257
258
259
260
261
262
263
264
265
266
267

268
269
270
271
272
273
274
            : NULL;
  if(len) *len = err->msg.used;
  return err->code;
}


char const * fsl_rc_cstr(int rc){
  fsl_rc_e const RC = (fsl_rc_e)rc
    /* we do this so that gcc will warn if the switch() below is
       missing any fsl_rc_e entries. */
    ;
  switch(RC){
#define STR(T) case FSL_RC_##T: return "FSL_RC_" #T
    STR(ACCESS);
    STR(ALREADY_EXISTS);
    STR(AMBIGUOUS);
    STR(BREAK);
    STR(SYNTAX);

    STR(CHECKSUM_MISMATCH);
    STR(CONFLICT);
    STR(CONSISTENCY);
    STR(DB);
    STR(DELTA_INVALID_OPERATOR);
    STR(DELTA_INVALID_SEPARATOR);
    STR(DELTA_INVALID_SIZE);
    STR(DELTA_INVALID_TERMINATOR);
    STR(DIFF_BINARY);
    STR(DIFF_WS_ONLY);
    STR(end);
    STR(ERROR);
    STR(INTERRUPTED);
    STR(IO);

    STR(MISSING_INFO);
    STR(MISUSE);
    STR(NOOP);
    STR(NOT_A_CKOUT);
    STR(NOT_A_REPO);
    STR(NOT_FOUND);
    STR(NYI);
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
    STR(SIZE_MISMATCH);
    STR(STEP_DONE);
    STR(STEP_ERROR);
    STR(STEP_ROW);
    STR(TYPE);
    STR(UNKNOWN_RESOURCE);
    STR(UNSUPPORTED);
    STR(WOULD_FORK);
#undef STR
  }
  return NULL;
}

char const * fsl_library_version(){
  return FSL_LIBRARY_VERSION;
}

bool fsl_library_version_matches(char const * yourLibVersion){







<


|







282
283
284
285
286
287
288

289
290
291
292
293
294
295
296
297
298
    STR(SIZE_MISMATCH);
    STR(STEP_DONE);
    STR(STEP_ERROR);
    STR(STEP_ROW);
    STR(TYPE);
    STR(UNKNOWN_RESOURCE);
    STR(UNSUPPORTED);

#undef STR
  }
  return "Unknown result code";
}

char const * fsl_library_version(){
  return FSL_LIBRARY_VERSION;
}

bool fsl_library_version_matches(char const * yourLibVersion){
1252
1253
1254
1255
1256
1257
1258
1259


1260
1261
1262
1263
1264
1265
1266

1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
char * fsl_strdup( char const * src ){
  return fsl_strndup(src, -1);
}

fsl_size_t fsl_strlcpy(char * dst, const char * src, fsl_size_t dstsz){
  fsl_size_t offset = 0;

  if(dstsz){


    while((*(dst+offset) = *(src+offset))!='\0'){
      if(++offset == dstsz){
        --offset;
        break;
      }
    }
  }

  *(dst+offset) = '\0';
  while(*(src+offset)!='\0'){
    ++offset;	/* Return src length. */
  }
  return offset;
}

fsl_size_t fsl_strlcat(char *dst, const char *src, fsl_size_t dstsz){
  fsl_size_t offset;
  int dstlen, srclen, idx = 0;

  offset = dstlen = fsl_strlen(dst);
  srclen = fsl_strlen(src);
  if( offset>=dstsz-1 )
    return dstlen+srclen;

  while((*(dst+offset++) = *(src+idx++))!='\0'){
    if(offset==dstsz-1){
      break;
    }
  }
  *(dst+offset)='\0';







|
>
>
|
|
|
|
|
|
<
>













<
<







399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414

415
416
417
418
419
420
421
422
423
424
425
426
427
428


429
430
431
432
433
434
435
char * fsl_strdup( char const * src ){
  return fsl_strndup(src, -1);
}

fsl_size_t fsl_strlcpy(char * dst, const char * src, fsl_size_t dstsz){
  fsl_size_t offset = 0;

  if(dstsz<1){
    goto end;
  }
  while((*(dst+offset) = *(src+offset))!='\0'){
    if(++offset == dstsz){
      --offset;
      break;
    }
  }

end:
  *(dst+offset) = '\0';
  while(*(src+offset)!='\0'){
    ++offset;	/* Return src length. */
  }
  return offset;
}

fsl_size_t fsl_strlcat(char *dst, const char *src, fsl_size_t dstsz){
  fsl_size_t offset;
  int dstlen, srclen, idx = 0;

  offset = dstlen = fsl_strlen(dst);
  srclen = fsl_strlen(src);



  while((*(dst+offset++) = *(src+idx++))!='\0'){
    if(offset==dstsz-1){
      break;
    }
  }
  *(dst+offset)='\0';
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
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
    }
  }
}

void fsl_randomness(unsigned int n, void *tgt){
  sqlite3_randomness((int)n, tgt);
}

int fsl_system(const char *zOrigCmd){
  int rc;
  /* The following was ported over from fossil(1). As of this writing,
     the Windows version is completely untested even for
     compilability. */
#if defined(_WIN32)
  /* On windows, we have to put double-quotes around the entire command.
  ** Who knows why - this is just the way windows works.
  */
  char *zNewCmd = fsl_mprintf("\"%s\"", zOrigCmd);
  if(!zNewCmd){FSL__WARN_OOM; return FSL_RC_OOM;}
  wchar_t *zUnicode = (wchar_t *)fsl_utf8_to_unicode(zNewCmd);
  if(!zUnicode){
    fsl_free(zNewCmd);
    FSL__WARN_OOM;
    return FSL_RC_OOM;
  }
  //fossil_assert_safe_command_string(zOrigCmd);
  rc = _wsystem(zUnicode);
  fsl_unicode_free(zUnicode);
  free(zNewCmd);
#else
  /* On unix, evaluate the command directly.
  */
  //fossil_assert_safe_command_string(zOrigCmd);
  /* The regular system() call works to get a shell on unix */
  rc = system(zOrigCmd);
  if(rc) {
    if(-1==rc) rc = errno;
    else if(rc>0){
      rc = FSL_RC_ERROR;
    }
  }
#endif
  return rc ? fsl_errno_to_rc(rc, FSL_RC_ERROR) : 0;
}


#undef MARKER
#if defined(_WIN32) || defined(WIN32)
#undef isatty
#endif
/* end of file ./src/fsl.c */
/* start of file ./src/annotate.c */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1023
1024
1025
1026
1027
1028
1029






































1030
1031
1032
1033
1034
1035
1036
    }
  }
}

void fsl_randomness(unsigned int n, void *tgt){
  sqlite3_randomness((int)n, tgt);
}







































#undef MARKER
#if defined(_WIN32) || defined(WIN32)
#undef isatty
#endif
/* end of file ./src/fsl.c */
/* start of file ./src/annotate.c */
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346

int fsl_annotate( fsl_cx * const f, fsl_annotate_opt const * const opt ){
  int rc;
  Annotator ann = Annotator_empty;
  unsigned int i;
  fsl_buffer * const scratch = fsl__cx_scratchpad(f);
  fsl_annotate_step aStep;

  if(!opt->out){
    return fsl_cx_err_set(f, FSL_RC_MISUSE,
                          "fsl_annotate_opt is missing its output function.");
  }
  if(opt->limitMs>0) fsl_timer_start(&ann.timer);
  rc = fsl__annotate_file(f, &ann, opt);
  if(rc) goto end;
  memset(&aStep,0,sizeof(fsl_annotate_step));

  if(opt->dumpVersions){
    struct AnnVers *av;







<
|
<
<
|







1437
1438
1439
1440
1441
1442
1443

1444


1445
1446
1447
1448
1449
1450
1451
1452

int fsl_annotate( fsl_cx * const f, fsl_annotate_opt const * const opt ){
  int rc;
  Annotator ann = Annotator_empty;
  unsigned int i;
  fsl_buffer * const scratch = fsl__cx_scratchpad(f);
  fsl_annotate_step aStep;

  assert(opt->out);



  if(opt->limitMs>0) fsl_timer_start(&ann.timer);
  rc = fsl__annotate_file(f, &ann, opt);
  if(rc) goto end;
  memset(&aStep,0,sizeof(fsl_annotate_step));

  if(opt->dumpVersions){
    struct AnnVers *av;
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
   If true, the %j (JSON string) format is enabled.
*/
#define FSLPRINTF_ENABLE_JSON 1

/*
  Most C compilers handle variable-sized arrays, so we enable
  that by default. Some (e.g. tcc) do not, so we provide a way
  to disable it: set FSLPRINTF_HAVE_VARARRAY to 0.

  One approach would be to look at:

  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)

  but some compilers support variable-sized arrays even when not
  explicitly running in c99 mode.
*/
/*
  2022-05-17: apparently VLAs were made OPTIONAL in C11 and MSVC
  decided not to support them. So we'll go ahead and remove the VLA
  usage altogether.
*/
#define FSLPRINTF_HAVE_VARARRAY 0
#if 0
#if !defined(FSLPRINTF_HAVE_VARARRAY)
#  if defined(__TINYC__)
#    define FSLPRINTF_HAVE_VARARRAY 0
#  else
#    if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#        define FSLPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */
#    else
#        define FSLPRINTF_HAVE_VARARRAY 0
#    endif
#  endif
#endif
#endif

/*
  Conversion types fall into various categories as defined by the
  following enumeration.
*/
enum PrintfCategory {etRADIX = 1, /* Integer types.  %d, %x, %o, and so forth */







|








<
<
<
<
<
<
<










<







1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595







1596
1597
1598
1599
1600
1601
1602
1603
1604
1605

1606
1607
1608
1609
1610
1611
1612
   If true, the %j (JSON string) format is enabled.
*/
#define FSLPRINTF_ENABLE_JSON 1

/*
  Most C compilers handle variable-sized arrays, so we enable
  that by default. Some (e.g. tcc) do not, so we provide a way
  to disable it: set FSLPRINTF_HAVE_VARARRAY to 0

  One approach would be to look at:

  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)

  but some compilers support variable-sized arrays even when not
  explicitly running in c99 mode.
*/







#if !defined(FSLPRINTF_HAVE_VARARRAY)
#  if defined(__TINYC__)
#    define FSLPRINTF_HAVE_VARARRAY 0
#  else
#    if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#        define FSLPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */
#    else
#        define FSLPRINTF_HAVE_VARARRAY 0
#    endif
#  endif

#endif

/*
  Conversion types fall into various categories as defined by the
  following enumeration.
*/
enum PrintfCategory {etRADIX = 1, /* Integer types.  %d, %x, %o, and so forth */
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
   It expects varg to be a string value, which it will preceed to
   encode using an URL encoding algothrim (certain characters are
   converted to %XX, where XX is their hex value) and passes the
   encoded string to pf(). It returns the total length of the output
   string.
*/
static int spech_urlencode( fsl_output_f pf, void * pfArg,
                            unsigned int pfLen __unused, void * varg ){
  char const * str = (char const *) varg;
  int rc = 0;
  char ch = 0;
  char const * hex = "0123456789ABCDEF";
#define xbufsz 10
  char xbuf[xbufsz];
  int slen = 0;







|







1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
   It expects varg to be a string value, which it will preceed to
   encode using an URL encoding algothrim (certain characters are
   converted to %XX, where XX is their hex value) and passes the
   encoded string to pf(). It returns the total length of the output
   string.
*/
static int spech_urlencode( fsl_output_f pf, void * pfArg,
                            unsigned int pfLen, void * varg ){
  char const * str = (char const *) varg;
  int rc = 0;
  char ch = 0;
  char const * hex = "0123456789ABCDEF";
#define xbufsz 10
  char xbuf[xbufsz];
  int slen = 0;
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
#  define FSLPRINTF_CHARARRAY_STACK(V) 
#  define FSLPRINTF_CHARARRAY(V,N) char V##2[256]; \
  char * V;                                                      \
  if((int)(N)<((int)sizeof(V##2))){                              \
    V = V##2;                                   \
  }else{                                        \
    V = (char *)fsl_malloc(N+1);       \
    if(!V) {FSLPRINTF_RETURN(FSL_RC_OOM);} \
  }
#  define FSLPRINTF_CHARARRAY_FREE(V) if(V!=V##2) fsl_free(V)
#endif

  /* FSLPRINTF_RETURN, FSLPRINTF_CHECKERR, and FSLPRINTF_SPACES
     are internal helpers.
  */
#define FSLPRINTF_RETURN(RC) if( zExtra ) fsl_free(zExtra); return RC
#define FSLPRINTF_CHECKERR if( 0!=pfrc ) { FSLPRINTF_RETURN(pfrc); } (void)0
#define FSLPRINTF_SPACES(N)                     \
  {                                             \
    FSLPRINTF_CHARARRAY(zSpaces,N);             \
    memset( zSpaces,' ',N);                     \
    pfrc = pfAppend(pfAppendArg, zSpaces, N);   \
    FSLPRINTF_CHARARRAY_FREE(zSpaces);          \
    FSLPRINTF_CHECKERR;                \







|







|
|







2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
#  define FSLPRINTF_CHARARRAY_STACK(V) 
#  define FSLPRINTF_CHARARRAY(V,N) char V##2[256]; \
  char * V;                                                      \
  if((int)(N)<((int)sizeof(V##2))){                              \
    V = V##2;                                   \
  }else{                                        \
    V = (char *)fsl_malloc(N+1);       \
    if(!V) {FSLPRINTF_RETURN;}         \
  }
#  define FSLPRINTF_CHARARRAY_FREE(V) if(V!=V##2) fsl_free(V)
#endif

  /* FSLPRINTF_RETURN, FSLPRINTF_CHECKERR, and FSLPRINTF_SPACES
     are internal helpers.
  */
#define FSLPRINTF_RETURN if( zExtra ) fsl_free(zExtra); return pfrc
#define FSLPRINTF_CHECKERR if( 0!=pfrc ) { FSLPRINTF_RETURN; } (void)0
#define FSLPRINTF_SPACES(N)                     \
  {                                             \
    FSLPRINTF_CHARARRAY(zSpaces,N);             \
    memset( zSpaces,' ',N);                     \
    pfrc = pfAppend(pfAppendArg, zSpaces, N);   \
    FSLPRINTF_CHARARRAY_FREE(zSpaces);          \
    FSLPRINTF_CHECKERR;                \
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
      : 0;
    /*fprintf(stderr,"char '%c'/%d @ %d,  type=%c/%d\n",c,c,FMTNDX(c),infop->fmttype,infop->type);*/
    if( infop ) xtype = infop->type;
#undef FMTINFO
#undef FMTNDX
    zExtra = 0;
    if( (!infop) || (!infop->type) ){
      FSLPRINTF_RETURN(FSL_RC_RANGE);
    }

    /* Limit the precision to prevent overflowing buf[] during conversion */
    if( precision>FSLPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){
      precision = FSLPRINTF_BUF_SIZE-40;
    }








|







2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
      : 0;
    /*fprintf(stderr,"char '%c'/%d @ %d,  type=%c/%d\n",c,c,FMTNDX(c),infop->fmttype,infop->type);*/
    if( infop ) xtype = infop->type;
#undef FMTINFO
#undef FMTNDX
    zExtra = 0;
    if( (!infop) || (!infop->type) ){
      FSLPRINTF_RETURN;
    }

    /* Limit the precision to prevent overflowing buf[] during conversion */
    if( precision>FSLPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){
      precision = FSLPRINTF_BUF_SIZE-40;
    }

3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
      xtype                       The class of the conversion.
      infop                       Pointer to the appropriate info struct.
    */
    switch( xtype ){
      case etPOINTER:
        flag_longlong = sizeof(char*)==sizeof(int64_t);
        flag_long = sizeof(char*)==sizeof(long int);
        FSL_SWITCH_FALL_THROUGH;
      case etORDINAL:
      case etRADIX:
        if( infop->flags & FLAG_SIGNED ){
          int64_t v;
          if( flag_longlong )   v = va_arg(ap,int64_t);
          else if( flag_long )  v = va_arg(ap,long int);
          else                  v = va_arg(ap,int);







|







2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
      xtype                       The class of the conversion.
      infop                       Pointer to the appropriate info struct.
    */
    switch( xtype ){
      case etPOINTER:
        flag_longlong = sizeof(char*)==sizeof(int64_t);
        flag_long = sizeof(char*)==sizeof(long int);
        /* Fall through into the next case */
      case etORDINAL:
      case etRADIX:
        if( infop->flags & FLAG_SIGNED ){
          int64_t v;
          if( flag_longlong )   v = va_arg(ap,int64_t);
          else if( flag_long )  v = va_arg(ap,long int);
          else                  v = va_arg(ap,int);
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
        /* Sanitize path-like inputs, replacing \\ with /. */
        int i;
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        char const *e = va_arg(ap,char const*);
        if( e && *e ){
          length = StrNLen32(e, limit);
          zExtra = bufpt = fsl_malloc(length+1);
          if(!zExtra) return FSL_RC_OOM;
          for( i=0; i<length; i++ ){
            if( e[i]=='\\' ){
              bufpt[i]='/';
            }else{
              bufpt[i]=e[i];
            }
          }
          bufpt[length]='\0';
        }
        break;
      }
      case etSTRINGID: {
        precision = flag_altform2 ? -1 : 16
          /* In fossil(1) this is configurable, but in this lib we
             don't have access to that state from here. Fossil also
             has the '!' flag_altform2, which indicates that it
             should be for a URL, and thus longer than the default.
             We are only roughly approximating that behaviour here. */;
        FSL_SWITCH_FALL_THROUGH;
      }
      case etSTRING: {
        bufpt = va_arg(ap,char*);
        length = bufpt
          ? StrNLen32(bufpt,
                      (precision>0 && flag_alternateform)
                      ? precision*4/*max bytes per char*/







|


















|







2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
        /* Sanitize path-like inputs, replacing \\ with /. */
        int i;
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        char const *e = va_arg(ap,char const*);
        if( e && *e ){
          length = StrNLen32(e, limit);
          zExtra = bufpt = fsl_malloc(length+1);
          if(!zExtra) return -1;
          for( i=0; i<length; i++ ){
            if( e[i]=='\\' ){
              bufpt[i]='/';
            }else{
              bufpt[i]=e[i];
            }
          }
          bufpt[length]='\0';
        }
        break;
      }
      case etSTRINGID: {
        precision = flag_altform2 ? -1 : 16
          /* In fossil(1) this is configurable, but in this lib we
             don't have access to that state from here. Fossil also
             has the '!' flag_altform2, which indicates that it
             should be for a URL, and thus longer than the default.
             We are only roughly approximating that behaviour here. */;
        /* Fall through */
      }
      case etSTRING: {
        bufpt = va_arg(ap,char*);
        length = bufpt
          ? StrNLen32(bufpt,
                      (precision>0 && flag_alternateform)
                      ? precision*4/*max bytes per char*/
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
        int check;
        bufpt = va_arg(ap,char*);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        if((limit>=0) && (length>limit)) length = limit;
        check = fsl_bytes_fossilize((unsigned char const *)bufpt, length, &fb);
        if(check){
          fsl_buffer_reserve(&fb,0);
          FSLPRINTF_RETURN(check);
        }
        zExtra = bufpt = (char*)fb.mem
          /*transfer ownership*/;
        length = (int)fb.used;
        if( precision>=0 && precision<length ) length = precision;
        break;
      }







|







2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
        int check;
        bufpt = va_arg(ap,char*);
        length = bufpt ? (int)fsl_strlen(bufpt) : 0;
        if((limit>=0) && (length>limit)) length = limit;
        check = fsl_bytes_fossilize((unsigned char const *)bufpt, length, &fb);
        if(check){
          fsl_buffer_reserve(&fb,0);
          FSLPRINTF_RETURN;
        }
        zExtra = bufpt = (char*)fb.mem
          /*transfer ownership*/;
        length = (int)fb.used;
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
      }
    }
    if( zExtra ){
      fsl_free(zExtra);
      zExtra = 0;
    }
  }/* End for loop over the format string */
  FSLPRINTF_RETURN(0);
} /* End of function */


#undef FSLPRINTF_CHARARRAY_STACK
#undef FSLPRINTF_CHARARRAY
#undef FSLPRINTF_CHARARRAY_FREE
#undef FSLPRINTF_SPACES







|







3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
      }
    }
    if( zExtra ){
      fsl_free(zExtra);
      zExtra = 0;
    }
  }/* End for loop over the format string */
  FSLPRINTF_RETURN;
} /* End of function */


#undef FSLPRINTF_CHARARRAY_STACK
#undef FSLPRINTF_CHARARRAY
#undef FSLPRINTF_CHARARRAY_FREE
#undef FSLPRINTF_SPACES
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
  va_end(vargs);
  return ret;
}

int fsl_fprintfv( FILE * fp, char const * fmt, va_list args ){
  return (fp && fmt)
    ? fsl_appendfv( fsl_output_f_FILE, fp, fmt, args )
    :  FSL_RC_MISUSE;
}

int fsl_fprintf( FILE * fp, char const * fmt, ... ){
  int ret;
  va_list vargs;
  va_start( vargs, fmt );
  ret = fsl_appendfv( fsl_output_f_FILE, fp, fmt, vargs );







|







3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
  va_end(vargs);
  return ret;
}

int fsl_fprintfv( FILE * fp, char const * fmt, va_list args ){
  return (fp && fmt)
    ? fsl_appendfv( fsl_output_f_FILE, fp, fmt, args )
    :  -1;
}

int fsl_fprintf( FILE * fp, char const * fmt, ... ){
  int ret;
  va_list vargs;
  va_start( vargs, fmt );
  ret = fsl_appendfv( fsl_output_f_FILE, fp, fmt, vargs );
4092
4093
4094
4095
4096
4097
4098

4099
4100
4101
4102
4103
4104
4105
typedef struct fsl_snp_state fsl_snp_state;

static int fsl_output_f_snprintf( void * arg,
                                  void const * data_,
                                  fsl_size_t n ){
  char const * data = (char const *)data_;
  fsl_snp_state * st = (fsl_snp_state*) arg;

  if(n==0 || (st->pos >= st->len)) return 0;
  else if((n + st->pos) > st->len){
    n = st->len - st->pos;
  }
  memcpy(st->dest + st->pos, data, n);
  st->pos += n;
  assert(st->pos <= st->len);







>







3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
typedef struct fsl_snp_state fsl_snp_state;

static int fsl_output_f_snprintf( void * arg,
                                  void const * data_,
                                  fsl_size_t n ){
  char const * data = (char const *)data_;
  fsl_snp_state * st = (fsl_snp_state*) arg;
  assert(n>=0);
  if(n==0 || (st->pos >= st->len)) return 0;
  else if((n + st->pos) > st->len){
    n = st->len - st->pos;
  }
  memcpy(st->dest + st->pos, data, n);
  st->pos += n;
  assert(st->pos <= st->len);
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555

4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621


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

#define buf__is_external(b) (b->mem && 0==b->capacity)

#define buff__errcheck(B) if((B)->errCode) return (B)->errCode
/**
   Materializes external buffer b by allocating b->used+extra+1
   bytes, copying b->used bytes from b->mem to the new block,
   NUL-terminating the block, and replacing b->mem with the new
   block. Returns 0 on success, else FSL_RC_OOM.

   Asserts that b is an external buffer.
*/
static int fsl__buffer_materialize( fsl_buffer * const b, fsl_size_t extra ){
  assert(buf__is_external(b));
  buff__errcheck(b);
  fsl_size_t const n = b->used + extra + 1;
  unsigned char * x = (unsigned char *)fsl_malloc(n);
  if(!x) return b->errCode = FSL_RC_OOM;
  memcpy(x, b->mem, b->used);
  b->capacity = n;
  x[b->used] = 0;
  b->mem = x;
  return 0;
}

int fsl_buffer_err( fsl_buffer const * b ){
  return b->errCode;
}

void fsl_buffer_err_clear(fsl_buffer * const b){
  b->errCode = 0;
}

int fsl_buffer_materialize( fsl_buffer * const b ){
  buff__errcheck(b);
  return buf__is_external(b) ? fsl__buffer_materialize(b, 0) : 0;
}

#define buf__materialize(B,N) (buf__is_external(B) ? fsl__buffer_materialize((B),(N)) : 0)

void fsl_buffer_external( fsl_buffer * const b, void const * mem, fsl_int_t n ){
  if(b->mem) fsl_buffer_clear(b);
  if(n<0) n =(fsl_int_t)fsl_strlen((char const *)mem);
  b->used = n;
  b->cursor = 0;
  b->errCode = 0;
  b->mem = (unsigned char *)mem;
  b->capacity = 0;
}

fsl_buffer * fsl_buffer_reuse( fsl_buffer * const b ){
  if(buf__is_external(b)){
    *b = fsl_buffer_empty;
  }else{
    if(b->capacity){
      assert(b->mem);
      b->mem[0] = 0;
      b->used = 0;
    }
    b->cursor = 0;
    b->errCode = 0;
  }
  return b;
}

void fsl_buffer_clear( fsl_buffer * const buf ){
  if(buf->capacity) fsl_free(buf->mem);
  *buf = fsl_buffer_empty;
}

int fsl_buffer_reserve( fsl_buffer * const buf, fsl_size_t n ){

  if( 0 == n ){
    if(!buf__is_external(buf)){
      fsl_free(buf->mem);
    }/* else if it has memory, it's owned elsewhere */
    *buf = fsl_buffer_empty;
    return 0;
  }
  else buff__errcheck(buf);
  else if( !buf__is_external(buf) && buf->capacity >= n ){
    assert(buf->mem);
    return 0;
  }else{
    unsigned char * x;
    bool const isExt = buf__is_external(buf);
    assert((buf->used < n) && "Buffer in-use greater than capacity!");
    if(isExt && n<=buf->used){
      /*For external buffers, always keep at least the initially-pointed-to
        size. */
      n = buf->used + 1;
    }
    x = (unsigned char *)fsl_realloc( isExt ? NULL : buf->mem, n );
    if( !x ) return buf->errCode = FSL_RC_OOM;
    else if(isExt){
      memcpy( x, buf->mem, buf->used );
      x[buf->used] = 0;
    }else{
      memset( x + buf->used, 0, n - buf->used );
    }
    buf->mem = x;
    buf->capacity = n;
    return 0;
  }
}

int fsl_buffer_resize( fsl_buffer * const b, fsl_size_t n ){
  buff__errcheck(b);
  else if(buf__is_external(b)){
    if(n==b->used) return 0;
    else if(n==0){
      b->capacity = 0;
      fsl_buffer_external(b, "", 0);
      return 0;
    }
    unsigned char * x = (unsigned char *)fsl_malloc( n+1/*NUL*/ );
    if( !x ) return b->errCode = FSL_RC_OOM;
    memcpy(x, b->mem, n < b->used ? n : b->used);
    x[n] = 0;
    b->mem = x;
    b->capacity = n+1;
    b->used = n;
    return 0;
  }else if(n && (b->capacity == n+1)){
    b->used = n;
    b->mem[n] = 0;
    return 0;
  }else{
    unsigned char * x = (unsigned char *)fsl_realloc( b->mem,
                                                      n+1/*NUL*/ );
    if( ! x ) return b->errCode = FSL_RC_OOM;
    if(n > b->capacity){
      /* zero-fill new parts */
      memset( x + b->capacity, 0, n - b->capacity +1/*NUL*/ );
    }
    b->capacity = n + 1 /*NUL*/;
    b->used = n;
    b->mem = x;







|

<









|
<


|







<
<
<
<
<
<
<
<

<
|


|






<





|








<










>
|
|




<
<
|




|







|













<
|







|













|







3578
3579
3580
3581
3582
3583
3584
3585
3586

3587
3588
3589
3590
3591
3592
3593
3594
3595
3596

3597
3598
3599
3600
3601
3602
3603
3604
3605
3606








3607

3608
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
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648


3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675

3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705


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

#define buffer_is_external(b) (b->mem && 0==b->capacity)


/**
   Materializes external buffer b by allocating b->used+extra+1
   bytes, copying b->used bytes from b->mem to the new block,
   NUL-terminating the block, and replacing b->mem with the new
   block. Returns 0 on success, else FSL_RC_OOM.

   Asserts that b is an external buffer.
*/
static int fsl__buffer_materialize( fsl_buffer * const b, fsl_size_t extra ){
  assert(buffer_is_external(b));

  fsl_size_t const n = b->used + extra + 1;
  unsigned char * x = (unsigned char *)fsl_malloc(n);
  if(!x) return FSL_RC_OOM;
  memcpy(x, b->mem, b->used);
  b->capacity = n;
  x[b->used] = 0;
  b->mem = x;
  return 0;
}









int fsl_buffer_materialize( fsl_buffer * const b ){

  return buffer_is_external(b) ? fsl__buffer_materialize(b, 0) : 0;
}

#define buffer_materialize(B,N) (buffer_is_external(B) ? fsl__buffer_materialize((B),(N)) : 0)

void fsl_buffer_external( fsl_buffer * const b, void const * mem, fsl_int_t n ){
  if(b->mem) fsl_buffer_clear(b);
  if(n<0) n =(fsl_int_t)fsl_strlen((char const *)mem);
  b->used = n;
  b->cursor = 0;

  b->mem = (unsigned char *)mem;
  b->capacity = 0;
}

fsl_buffer * fsl_buffer_reuse( fsl_buffer * const b ){
  if(buffer_is_external(b)){
    *b = fsl_buffer_empty;
  }else{
    if(b->capacity){
      assert(b->mem);
      b->mem[0] = 0;
      b->used = 0;
    }
    b->cursor = 0;

  }
  return b;
}

void fsl_buffer_clear( fsl_buffer * const buf ){
  if(buf->capacity) fsl_free(buf->mem);
  *buf = fsl_buffer_empty;
}

int fsl_buffer_reserve( fsl_buffer * const buf, fsl_size_t n ){
  if( ! buf ) return FSL_RC_MISUSE;
  else if( 0 == n ){
    if(!buffer_is_external(buf)){
      fsl_free(buf->mem);
    }/* else if it has memory, it's owned elsewhere */
    *buf = fsl_buffer_empty;
    return 0;


  }else if( !buffer_is_external(buf) && buf->capacity >= n ){
    assert(buf->mem);
    return 0;
  }else{
    unsigned char * x;
    bool const isExt = buffer_is_external(buf);
    assert((buf->used < n) && "Buffer in-use greater than capacity!");
    if(isExt && n<=buf->used){
      /*For external buffers, always keep at least the initially-pointed-to
        size. */
      n = buf->used + 1;
    }
    x = (unsigned char *)fsl_realloc( isExt ? NULL : buf->mem, n );
    if( !x ) return FSL_RC_OOM;
    else if(isExt){
      memcpy( x, buf->mem, buf->used );
      x[buf->used] = 0;
    }else{
      memset( x + buf->used, 0, n - buf->used );
    }
    buf->mem = x;
    buf->capacity = n;
    return 0;
  }
}

int fsl_buffer_resize( fsl_buffer * const b, fsl_size_t n ){

  if(buffer_is_external(b)){
    if(n==b->used) return 0;
    else if(n==0){
      b->capacity = 0;
      fsl_buffer_external(b, "", 0);
      return 0;
    }
    unsigned char * x = (unsigned char *)fsl_malloc( n+1/*NUL*/ );
    if( !x ) return FSL_RC_OOM;
    memcpy(x, b->mem, n < b->used ? n : b->used);
    x[n] = 0;
    b->mem = x;
    b->capacity = n+1;
    b->used = n;
    return 0;
  }else if(n && (b->capacity == n+1)){
    b->used = n;
    b->mem[n] = 0;
    return 0;
  }else{
    unsigned char * x = (unsigned char *)fsl_realloc( b->mem,
                                                      n+1/*NUL*/ );
    if( ! x ) return FSL_RC_OOM;
    if(n > b->capacity){
      /* zero-fill new parts */
      memset( x + b->capacity, 0, n - b->capacity +1/*NUL*/ );
    }
    b->capacity = n + 1 /*NUL*/;
    b->used = n;
    b->mem = x;
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
    rc = (szL==szR)
      ? 0
      : ((szL<szR) ? -1 : 1);
  }
  return rc;
}

bool fsl_buffer_eq(fsl_buffer const * const b, char const * str,
                   fsl_int_t nStr){
  if(nStr<0) nStr = (fsl_int_t)fsl_strlen(str);
  fsl_buffer rhs = fsl_buffer_empty;
  fsl_buffer_external(&rhs, str, nStr);
  return 0==fsl_buffer_compare(b, &rhs);
}

/*
   Compare two blobs in constant time and return zero if they are equal.
   Constant time comparison only applies for blobs of the same length.
   If lengths are different, immediately returns 1.
*/
int fsl_buffer_compare_O1(fsl_buffer const * const lhs, fsl_buffer const * const rhs){
  fsl_size_t const szL = lhs->used;







<
<
<
<
<
<
<
<







3717
3718
3719
3720
3721
3722
3723








3724
3725
3726
3727
3728
3729
3730
    rc = (szL==szR)
      ? 0
      : ((szL<szR) ? -1 : 1);
  }
  return rc;
}









/*
   Compare two blobs in constant time and return zero if they are equal.
   Constant time comparison only applies for blobs of the same length.
   If lengths are different, immediately returns 1.
*/
int fsl_buffer_compare_O1(fsl_buffer const * const lhs, fsl_buffer const * const rhs){
  fsl_size_t const szL = lhs->used;
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700

4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
  return rc;
}


int fsl_buffer_append( fsl_buffer * const b,
                       void const * const data,
                       fsl_int_t len ){
  if(0==b->errCode){
    fsl_size_t sz = b->used;
    if(len<0) len = (fsl_int_t)fsl_strlen((char const *)data);
    if(buf__materialize(b, (fsl_size_t)len + 1)) return b->errCode;
    assert(b->capacity ? !!b->mem : !b->mem);
    assert(b->used <= b->capacity);
    sz += len + 1/*NUL*/;
    if(b->capacity<sz) fsl_buffer_reserve( b, sz );
    if(!b->errCode){
      assert(b->capacity >= sz);
      if(len>0) memcpy(b->mem + b->used, data, (size_t)len);
      b->used += len;
      b->mem[b->used] = 0;
    }
  }
  return b->errCode;
}

int fsl_buffer_appendfv( fsl_buffer * const b, char const * fmt,
                         va_list args){
  return fsl_appendfv( fsl_output_f_buffer, b, fmt, args );
}


int fsl_buffer_appendf( fsl_buffer * const b,
                        char const * fmt, ... ){
  buff__errcheck(b);
  else{

    va_list args;
    va_start(args,fmt);
    fsl_buffer_appendfv( b, fmt, args );
    va_end(args);
    return b->errCode;
  }
}

char const * fsl_buffer_cstr(fsl_buffer const * const b){
  return b->errCode ? NULL : (char const *)b->mem;
}

char const * fsl_buffer_cstr2(fsl_buffer const * const b, fsl_size_t * const len){
  char const * rc = NULL;
  if(0==b->errCode){
    rc = (char const *)b->mem;
    if(len) *len = b->used;
  }
  return rc;
}

char * fsl_buffer_str(fsl_buffer const * const b){
  return b->errCode ? NULL : (char *)b->mem;
}


#if 0
fsl_size_t fsl_buffer_size(fsl_buffer const * const b){
  return b->used;
}







<
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|










|

>


|

|




|




|







|







3742
3743
3744
3745
3746
3747
3748

3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761

3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
  return rc;
}


int fsl_buffer_append( fsl_buffer * const b,
                       void const * const data,
                       fsl_int_t len ){

  fsl_size_t sz = b->used;
  if(len<0) len = (fsl_int_t)fsl_strlen((char const *)data);
  if(buffer_materialize(b, (fsl_size_t)len + 1)) return FSL_RC_OOM;
  assert(b->capacity ? !!b->mem : !b->mem);
  assert(b->used <= b->capacity);
  sz += len + 1/*NUL*/;
  int const rc = b->capacity<sz ? fsl_buffer_reserve( b, sz ) : 0;
  if(!rc){
    assert(b->capacity >= sz);
    if(len>0) memcpy(b->mem + b->used, data, (size_t)len);
    b->used += len;
    b->mem[b->used] = 0;
  }

  return rc;
}

int fsl_buffer_appendfv( fsl_buffer * const b, char const * fmt,
                         va_list args){
  return fsl_appendfv( fsl_output_f_buffer, b, fmt, args );
}


int fsl_buffer_appendf( fsl_buffer * const b,
                        char const * fmt, ... ){
  if(!b || !fmt) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_buffer_appendfv( b, fmt, args );
    va_end(args);
    return rc;
  }
}

char const * fsl_buffer_cstr(fsl_buffer const * const b){
  return b ? (char const *)b->mem : NULL;
}

char const * fsl_buffer_cstr2(fsl_buffer const * const b, fsl_size_t * const len){
  char const * rc = NULL;
  if(b){
    rc = (char const *)b->mem;
    if(len) *len = b->used;
  }
  return rc;
}

char * fsl_buffer_str(fsl_buffer const * const b){
  return (char *)b->mem;
}


#if 0
fsl_size_t fsl_buffer_size(fsl_buffer const * const b){
  return b->used;
}
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
    : -1;
}

fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b){
  return fsl_data_uncompressed_size(b->mem, b->used);
}

int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer * const pOut){
  buff__errcheck(pIn);
  else buff__errcheck(pOut);
  unsigned int nIn = pIn->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned long int nOut2;







|
<
<







3894
3895
3896
3897
3898
3899
3900
3901


3902
3903
3904
3905
3906
3907
3908
    : -1;
}

fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b){
  return fsl_data_uncompressed_size(b->mem, b->used);
}

int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer *pOut){


  unsigned int nIn = pIn->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned long int nOut2;
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
      assert(outSize==pOut->used);
    }
    return rc;
  }
}

int fsl_buffer_compress2(fsl_buffer const *pIn1,
                         fsl_buffer const *pIn2, fsl_buffer * const pOut){
  buff__errcheck(pIn1);
  else buff__errcheck(pIn2);
  else buff__errcheck(pOut);
  unsigned int nIn = pIn1->used + pIn2->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{







|
<
<
<







3930
3931
3932
3933
3934
3935
3936
3937



3938
3939
3940
3941
3942
3943
3944
      assert(outSize==pOut->used);
    }
    return rc;
  }
}

int fsl_buffer_compress2(fsl_buffer const *pIn1,
                         fsl_buffer const *pIn2, fsl_buffer *pOut){



  unsigned int nIn = pIn1->used + pIn2->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
      fsl_buffer_reserve(&temp, 0);
    }
    return rc;
  }
}

int fsl_buffer_uncompress(fsl_buffer const * const pIn, fsl_buffer * const pOut){
  buff__errcheck(pIn);
  else buff__errcheck(pOut);
  unsigned int nOut;
  unsigned char *inBuf;
  unsigned int const nIn = pIn->used;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  unsigned long int nOut2;
  if(nIn<=4 || !fsl_data_is_compressed(pIn->mem, pIn->used)){
    if(pIn==pOut || !pIn->mem) rc = 0;
    else{
      fsl_buffer_reuse(pOut);
      rc = fsl_buffer_append(pOut, pIn->mem, pIn->used);
    }
    assert(pOut->errCode == rc);
    return rc;
  }
  inBuf = pIn->mem;
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  /* MARKER(("decompress size: %u\n", nOut)); */
  if(pIn!=pOut && pOut->capacity>=nOut+1){
    assert(pIn->mem != pOut->mem);







<
<












<







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
      fsl_buffer_reserve(&temp, 0);
    }
    return rc;
  }
}

int fsl_buffer_uncompress(fsl_buffer const * const pIn, fsl_buffer * const pOut){


  unsigned int nOut;
  unsigned char *inBuf;
  unsigned int const nIn = pIn->used;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  unsigned long int nOut2;
  if(nIn<=4 || !fsl_data_is_compressed(pIn->mem, pIn->used)){
    if(pIn==pOut || !pIn->mem) rc = 0;
    else{
      fsl_buffer_reuse(pOut);
      rc = fsl_buffer_append(pOut, pIn->mem, pIn->used);
    }

    return rc;
  }
  inBuf = pIn->mem;
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  /* MARKER(("decompress size: %u\n", nOut)); */
  if(pIn!=pOut && pOut->capacity>=nOut+1){
    assert(pIn->mem != pOut->mem);
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
    */
    fsl_buffer_external(&temp, pOut->mem, pOut->capacity);
#else
    fsl_buffer_swap(&temp, pOut);
#endif
  }else{
    rc = fsl_buffer_reserve(&temp, nOut+1);
    if(rc) return pOut->errCode = rc;
    temp.mem[nOut] = 0;
  }
  
  nOut2 = (long int)nOut;
  rc = uncompress(temp.mem, &nOut2, &inBuf[4], nIn - 4)
    /* In some libz versions (<1.2.4, apparently), valgrind says
       there's an uninitialized memory access somewhere under







|







4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
    */
    fsl_buffer_external(&temp, pOut->mem, pOut->capacity);
#else
    fsl_buffer_swap(&temp, pOut);
#endif
  }else{
    rc = fsl_buffer_reserve(&temp, nOut+1);
    if(rc) return rc;
    temp.mem[nOut] = 0;
  }
  
  nOut2 = (long int)nOut;
  rc = uncompress(temp.mem, &nOut2, &inBuf[4], nIn - 4)
    /* In some libz versions (<1.2.4, apparently), valgrind says
       there's an uninitialized memory access somewhere under
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998

4999
5000
5001
5002
5003

5004
5005
5006
5007
5008
5009
5010
    case Z_MEM_ERROR: rc = FSL_RC_OOM; break;
    case Z_BUF_ERROR:
      assert(!"Cannot happen!");
      rc = FSL_RC_RANGE; break;
    default: rc = FSL_RC_ERROR; break;
  }
  if(temp.mem!=pOut->mem) fsl_buffer_clear(&temp);
  return pOut->errCode = rc;
}


int fsl_buffer_fill_from( fsl_buffer * const dest, fsl_input_f src,
                          void * const state ){
  buff__errcheck(dest);

  int rc;
  enum { BufSize = 512 * 8 };
  char rbuf[BufSize];
  fsl_size_t total = 0;
  fsl_size_t rlen = 0;

  fsl_buffer_reuse(dest);
  while(1){
    rlen = BufSize;
    rc = src( state, rbuf, &rlen );
    if( rc ) break;
    total += rlen;
    if(total<rlen){







|




|
<
>





>







4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064

4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
    case Z_MEM_ERROR: rc = FSL_RC_OOM; break;
    case Z_BUF_ERROR:
      assert(!"Cannot happen!");
      rc = FSL_RC_RANGE; break;
    default: rc = FSL_RC_ERROR; break;
  }
  if(temp.mem!=pOut->mem) fsl_buffer_clear(&temp);
  return rc;
}


int fsl_buffer_fill_from( fsl_buffer * const dest, fsl_input_f src,
                          void * const state )

{
  int rc;
  enum { BufSize = 512 * 8 };
  char rbuf[BufSize];
  fsl_size_t total = 0;
  fsl_size_t rlen = 0;
  if( !dest || ! src ) return FSL_RC_MISUSE;
  fsl_buffer_reuse(dest);
  while(1){
    rlen = BufSize;
    rc = src( state, rbuf, &rlen );
    if( rc ) break;
    total += rlen;
    if(total<rlen){
5033
5034
5035
5036
5037
5038
5039
5040
5041
5042
5043
5044
5045
5046
5047
5048
5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
5088
5089
5090
5091
5092
5093
5094
5095
5096
5097
5098
5099
5100
5101
5102

5103
5104
5105
5106
5107
5108
5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
                               FILE * const src ){
  return fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
}          


int fsl_buffer_fill_from_filename( fsl_buffer * const dest,
                                   char const * filename ){
  buff__errcheck(dest);
  int rc;
  FILE * src;
  fsl_fstat st = fsl_fstat_empty;
  /* This stat() is only an optimization to reserve all needed
     memory up front.
  */
  rc = fsl_stat( filename, &st, 1 );
  if(!rc && st.size>0){
    rc = fsl_buffer_reserve(dest, st.size +1/*NUL terminator*/);
    if(rc) return rc;
  } /* Else it might not be a real file, e.g. "-", so we'll try anyway... */
  src = fsl_fopen(filename,"rb");
  if(!src) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  else {
    rc = fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
    fsl_fclose(src);
  }
  return rc;
}

void fsl_buffer_swap( fsl_buffer * const left, fsl_buffer * const right ){
  fsl_buffer const tmp = *left;
  *left = *right;
  *right = tmp;
}

void fsl_buffer_swap_free( fsl_buffer * const left, fsl_buffer * const right, int clearWhich ){
  fsl_buffer_swap(left, right);
  if(0 != clearWhich) fsl_buffer_reserve((clearWhich<0) ? left : right, 0);
}

int fsl_buffer_copy( fsl_buffer * const dest,
                     fsl_buffer const * const src ){
  fsl_buffer_reuse(dest);
  return src->used
    ? fsl_buffer_append( dest, src->mem, src->used )
    : 0;
}

int fsl_buffer_delta_apply2( fsl_buffer const * const orig,
                             fsl_buffer const * const pDelta,
                             fsl_buffer * const pTarget,
                             fsl_error * const pErr){
  buff__errcheck(orig);
  else buff__errcheck(pDelta);
  else buff__errcheck(pTarget);
  int rc;
  fsl_size_t n = 0;
  fsl_buffer out = fsl_buffer_empty;
  rc = fsl_delta_applied_size( pDelta->mem, pDelta->used, &n);
  if(rc){
    if(pErr){
      fsl_error_set(pErr, rc, "fsl_delta_applied_size() failed.");
    }
    return rc;
  }
  assert(n>0);
  rc = fsl_buffer_resize( &out, n );
  if(0==rc){
    rc = fsl_delta_apply2( orig->mem, orig->used,
                          pDelta->mem, pDelta->used,
                          out.mem, pErr);

    if(0==rc) fsl_buffer_swap(&out, pTarget);
  }
  fsl_buffer_clear(&out);
  return rc;
}

int fsl_buffer_delta_apply( fsl_buffer const * const orig,
                            fsl_buffer const * const pDelta,
                            fsl_buffer * const pTarget){
  return fsl_buffer_delta_apply2(orig, pDelta, pTarget, NULL);
}

void fsl_buffer_defossilize( fsl_buffer * const b ){
  fsl_bytes_defossilize( b->mem, &b->used );
}

int fsl_buffer_to_filename( fsl_buffer const * const b, char const * fname ){
  buff__errcheck(b);
  FILE * f;
  int rc = 0;
  if(!b || !fname) return FSL_RC_MISUSE;
  f = fsl_fopen(fname, "wb");
  if(!f) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  else{
    if(b->used) {







<




















|





|
















<
<
<










<

|
|
|
|
>
|
















<







4101
4102
4103
4104
4105
4106
4107

4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150



4151
4152
4153
4154
4155
4156
4157
4158
4159
4160

4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183

4184
4185
4186
4187
4188
4189
4190
                               FILE * const src ){
  return fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
}          


int fsl_buffer_fill_from_filename( fsl_buffer * const dest,
                                   char const * filename ){

  int rc;
  FILE * src;
  fsl_fstat st = fsl_fstat_empty;
  /* This stat() is only an optimization to reserve all needed
     memory up front.
  */
  rc = fsl_stat( filename, &st, 1 );
  if(!rc && st.size>0){
    rc = fsl_buffer_reserve(dest, st.size +1/*NUL terminator*/);
    if(rc) return rc;
  } /* Else it might not be a real file, e.g. "-", so we'll try anyway... */
  src = fsl_fopen(filename,"rb");
  if(!src) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  else {
    rc = fsl_buffer_fill_from( dest, fsl_input_f_FILE, src );
    fsl_fclose(src);
  }
  return rc;
}

void fsl_buffer_swap( fsl_buffer * left, fsl_buffer * right ){
  fsl_buffer const tmp = *left;
  *left = *right;
  *right = tmp;
}

void fsl_buffer_swap_free( fsl_buffer * left, fsl_buffer * right, int clearWhich ){
  fsl_buffer_swap(left, right);
  if(0 != clearWhich) fsl_buffer_reserve((clearWhich<0) ? left : right, 0);
}

int fsl_buffer_copy( fsl_buffer * const dest,
                     fsl_buffer const * const src ){
  fsl_buffer_reuse(dest);
  return src->used
    ? fsl_buffer_append( dest, src->mem, src->used )
    : 0;
}

int fsl_buffer_delta_apply2( fsl_buffer const * const orig,
                             fsl_buffer const * const pDelta,
                             fsl_buffer * const pTarget,
                             fsl_error * const pErr){



  int rc;
  fsl_size_t n = 0;
  fsl_buffer out = fsl_buffer_empty;
  rc = fsl_delta_applied_size( pDelta->mem, pDelta->used, &n);
  if(rc){
    if(pErr){
      fsl_error_set(pErr, rc, "fsl_delta_applied_size() failed.");
    }
    return rc;
  }

  rc = fsl_buffer_resize( &out, n );
  if(rc) return rc;
  rc = fsl_delta_apply2( orig->mem, orig->used,
                        pDelta->mem, pDelta->used,
                        out.mem, pErr);
  if(0==rc){
    fsl_buffer_swap(&out, pTarget);
  }
  fsl_buffer_clear(&out);
  return rc;
}

int fsl_buffer_delta_apply( fsl_buffer const * const orig,
                            fsl_buffer const * const pDelta,
                            fsl_buffer * const pTarget){
  return fsl_buffer_delta_apply2(orig, pDelta, pTarget, NULL);
}

void fsl_buffer_defossilize( fsl_buffer * const b ){
  fsl_bytes_defossilize( b->mem, &b->used );
}

int fsl_buffer_to_filename( fsl_buffer const * const b, char const * fname ){

  FILE * f;
  int rc = 0;
  if(!b || !fname) return FSL_RC_MISUSE;
  f = fsl_fopen(fname, "wb");
  if(!f) rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  else{
    if(b->used) {
5147
5148
5149
5150
5151
5152
5153


5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
                           delta->mem, &delta->used );
  }
  return rc;
}


int fsl_output_f_buffer( void * state, void const * src, fsl_size_t n ){


  return fsl_buffer_append((fsl_buffer*)state, src, n);
}

int fsl_finalizer_f_buffer( void * state __unused, void * mem ){
  fsl_buffer * b = (fsl_buffer*)mem;
  fsl_buffer_reserve(b, 0);
  *b = fsl_buffer_empty;
  return 0;
}

int fsl_buffer_strftime(fsl_buffer * const b, char const * format,
                        const struct tm *timeptr){
  if(!b || !format || !*format || !timeptr) return FSL_RC_MISUSE;
  else{
    enum {BufSize = 128};
    char buf[BufSize];
    fsl_size_t const len = fsl_strftime(buf, BufSize, format, timeptr);
    return len ? fsl_buffer_append(b, buf, (fsl_int_t)len) : FSL_RC_RANGE;
  }
}

int fsl_buffer_stream_lines(fsl_output_f fTo, void * const toState,
                            fsl_buffer * const pFrom, fsl_size_t N){
  buff__errcheck(pFrom);
  char *z = (char *)pFrom->mem;
  fsl_size_t i = pFrom->cursor;
  fsl_size_t n = pFrom->used;
  fsl_size_t cnt = 0;
  int rc = 0;
  if( N==0 ) return 0;
  while( i<n ){







>
>
|


|



















<







4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241

4242
4243
4244
4245
4246
4247
4248
                           delta->mem, &delta->used );
  }
  return rc;
}


int fsl_output_f_buffer( void * state, void const * src, fsl_size_t n ){
  return (!state || !src)
    ? FSL_RC_MISUSE
    : fsl_buffer_append((fsl_buffer*)state, src, n);
}

int fsl_finalizer_f_buffer( void * state, void * mem ){
  fsl_buffer * b = (fsl_buffer*)mem;
  fsl_buffer_reserve(b, 0);
  *b = fsl_buffer_empty;
  return 0;
}

int fsl_buffer_strftime(fsl_buffer * const b, char const * format,
                        const struct tm *timeptr){
  if(!b || !format || !*format || !timeptr) return FSL_RC_MISUSE;
  else{
    enum {BufSize = 128};
    char buf[BufSize];
    fsl_size_t const len = fsl_strftime(buf, BufSize, format, timeptr);
    return len ? fsl_buffer_append(b, buf, (fsl_int_t)len) : FSL_RC_RANGE;
  }
}

int fsl_buffer_stream_lines(fsl_output_f fTo, void * const toState,
                            fsl_buffer * const pFrom, fsl_size_t N){

  char *z = (char *)pFrom->mem;
  fsl_size_t i = pFrom->cursor;
  fsl_size_t n = pFrom->used;
  fsl_size_t cnt = 0;
  int rc = 0;
  if( N==0 ) return 0;
  while( i<n ){
5201
5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
5215
}


int fsl_buffer_copy_lines(fsl_buffer * const pTo,
                          fsl_buffer * const pFrom,
                          fsl_size_t N){
#if 1
  if(pTo) buff__errcheck(pTo);
  return fsl_buffer_stream_lines( pTo ? fsl_output_f_buffer : NULL, pTo,
                                  pFrom, N );
#else
  char *z = (char *)pFrom->mem;
  fsl_size_t i = pFrom->cursor;
  fsl_size_t n = pFrom->used;
  fsl_size_t cnt = 0;







<







4265
4266
4267
4268
4269
4270
4271

4272
4273
4274
4275
4276
4277
4278
}


int fsl_buffer_copy_lines(fsl_buffer * const pTo,
                          fsl_buffer * const pFrom,
                          fsl_size_t N){
#if 1

  return fsl_buffer_stream_lines( pTo ? fsl_output_f_buffer : NULL, pTo,
                                  pFrom, N );
#else
  char *z = (char *)pFrom->mem;
  fsl_size_t i = pFrom->cursor;
  fsl_size_t n = pFrom->used;
  fsl_size_t cnt = 0;
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
    pFrom->cursor = i;
  }
  return rc;
#endif
}

int fsl_input_f_buffer( void * state, void * dest, fsl_size_t * n ){
  fsl_buffer * const b = (fsl_buffer*)state;
  buff__errcheck(b);
  fsl_size_t const from = b->cursor;
  fsl_size_t to;
  fsl_size_t c;
  if(from >= b->used){
    *n = 0;
    return 0;
  }







|
<







4295
4296
4297
4298
4299
4300
4301
4302

4303
4304
4305
4306
4307
4308
4309
    pFrom->cursor = i;
  }
  return rc;
#endif
}

int fsl_input_f_buffer( void * state, void * dest, fsl_size_t * n ){
  fsl_buffer * b = (fsl_buffer*)state;

  fsl_size_t const from = b->cursor;
  fsl_size_t to;
  fsl_size_t c;
  if(from >= b->used){
    *n = 0;
    return 0;
  }
5289
5290
5291
5292
5293
5294
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305
5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
#endif
    return rc;
  }
}

char * fsl_buffer_take(fsl_buffer * const b){
  char * z = NULL;
  if(0==buf__materialize(b,0)){
    z = (char *)b->mem;
    *b = fsl_buffer_empty;
  }
  return z;
}

fsl_size_t fsl_buffer_seek(fsl_buffer * const b, fsl_int_t offset,
                           fsl_buffer_seek_e  whence){
  int64_t c = (int64_t)b->cursor;
  switch(whence){
    case FSL_BUFFER_SEEK_SET: c = offset;
    __attribute__ ((fallthrough));
    case FSL_BUFFER_SEEK_CUR: c = (int64_t)b->cursor + offset; break;
    case FSL_BUFFER_SEEK_END:
      c = (int64_t)b->used + offset;
      /* ^^^^^ fossil(1) uses (used + offset - 1) but

         That seems somewhat arguable because (used + 0 - 1) is at the
         last-written byte (or 1 before the begining), not the







|











<







4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369

4370
4371
4372
4373
4374
4375
4376
#endif
    return rc;
  }
}

char * fsl_buffer_take(fsl_buffer * const b){
  char * z = NULL;
  if(0==buffer_materialize(b,0)){
    z = (char *)b->mem;
    *b = fsl_buffer_empty;
  }
  return z;
}

fsl_size_t fsl_buffer_seek(fsl_buffer * const b, fsl_int_t offset,
                           fsl_buffer_seek_e  whence){
  int64_t c = (int64_t)b->cursor;
  switch(whence){
    case FSL_BUFFER_SEEK_SET: c = offset;

    case FSL_BUFFER_SEEK_CUR: c = (int64_t)b->cursor + offset; break;
    case FSL_BUFFER_SEEK_END:
      c = (int64_t)b->used + offset;
      /* ^^^^^ fossil(1) uses (used + offset - 1) but

         That seems somewhat arguable because (used + 0 - 1) is at the
         last-written byte (or 1 before the begining), not the
5334
5335
5336
5337
5338
5339
5340
5341
5342
5343
5344
5345
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
5362
5363
5364
5365
5366
5367
5368
5369
5370
5371
5372
5373
5374
5375
5376
5377
5378
5379
5380
5381
5382
5383
5384
5385
5386
5387
5388
5389
5390
5391
5392
5393
5394
5395
  return b->cursor;
}

void fsl_buffer_rewind(fsl_buffer * const b){
  b->cursor = 0;
}

int fsl_id_bag_to_buffer(fsl_id_bag const * bag, fsl_buffer * const b,
                         char const * separator){
  int i = 0;
  fsl_int_t const sepLen = (fsl_id_t)fsl_strlen(separator);
  fsl_buffer_reserve(b, b->used + (bag->entryCount * 7)
                     + (bag->entryCount * sepLen));
  for(fsl_id_t e = fsl_id_bag_first(bag);
      !b->errCode && e; e = fsl_id_bag_next(bag, e)){
    if(i++) fsl_buffer_append(b, separator, sepLen);
    fsl_buffer_appendf(b, "%" FSL_ID_T_PFMT, e);
  }
  return b->errCode;
}

int fsl_buffer_append_tcl_literal(fsl_buffer * const b,
                                  bool escapeSquigglies,
                                  char const * z, fsl_int_t n){
  buff__errcheck(b);
  int rc;
  if(n<0) n = fsl_strlen(z);
  fsl_buffer_append(b, "\"", 1);
  for(fsl_int_t i=0; 0==b->errCode && i<n; ++i){
    char c = z[i];
    bool skipSlash = false;
    switch( c ){
      case '\r':  c = 'r'; goto slash;
      case '}': case '{': skipSlash = !escapeSquigglies;
        /* fall through */
      case '[':
      case ']':
      case '$':
      case '"':
      case '\\':
      slash:
        if(!skipSlash && (rc = fsl_buffer_append(b, "\\", 1))) break;
        /* fall through */
      default:
        fsl_buffer_append(b, &c, 1);
    }
  }
  fsl_buffer_append(b, "\"", 1);
  return b->errCode;
}

#undef MARKER
#undef buf__is_external
#undef buf__errcheck
#undef buf__materialize
/* end of file ./src/buffer.c */
/* start of file ./src/cache.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt








|



|
|

|
|
|

|





<


|
|















|


|
|



|
<
|







4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418

4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446

4447
4448
4449
4450
4451
4452
4453
4454
  return b->cursor;
}

void fsl_buffer_rewind(fsl_buffer * const b){
  b->cursor = 0;
}

int fsl_id_bag_to_buffer(fsl_id_bag const * bag, fsl_buffer * b,
                         char const * separator){
  int i = 0;
  fsl_int_t const sepLen = (fsl_id_t)fsl_strlen(separator);
  int rc = fsl_buffer_reserve(b, b->used + (bag->entryCount * 7)
                              + (bag->entryCount * sepLen));
  for(fsl_id_t e = fsl_id_bag_first(bag);
      !rc && e; e = fsl_id_bag_next(bag, e)){
    if(i++) rc = fsl_buffer_append(b, separator, sepLen);
    if(!rc) rc = fsl_buffer_appendf(b, "%" FSL_ID_T_PFMT, e);
  }
  return rc;
}

int fsl_buffer_append_tcl_literal(fsl_buffer * const b,
                                  bool escapeSquigglies,
                                  char const * z, fsl_int_t n){

  int rc;
  if(n<0) n = fsl_strlen(z);
  rc = fsl_buffer_append(b, "\"", 1);
  for(fsl_int_t i=0; 0==rc && i<n; ++i){
    char c = z[i];
    bool skipSlash = false;
    switch( c ){
      case '\r':  c = 'r'; goto slash;
      case '}': case '{': skipSlash = !escapeSquigglies;
        /* fall through */
      case '[':
      case ']':
      case '$':
      case '"':
      case '\\':
      slash:
        if(!skipSlash && (rc = fsl_buffer_append(b, "\\", 1))) break;
        /* fall through */
      default:
        rc = fsl_buffer_append(b, &c, 1);
    }
  }
  if(0==rc) rc = fsl_buffer_append(b, "\"", 1);
  return rc;
}

#undef MARKER
#undef buffer_is_external

#undef buffer_materialize
/* end of file ./src/buffer.c */
/* start of file ./src/cache.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

5662
5663
5664
5665
5666
5667
5668
5669
5670
5671
5672
5673
5674
5675
5676
5677
5678

5679
5680
5681
5682

5683
5684

5685
5686
5687
5688
5689

5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
  int rc;
  fsl_buffer * sql = 0;
  if(!db) return FSL_RC_NOT_A_CKOUT;
  sql = fsl__cx_scratchpad(f);
  if(0>=vid) vid = f->ckout.rid;
  if(zName && *zName
     && !('.'==*zName && !zName[1])){
    fsl_buffer_appendf(sql,
                       "SELECT id FROM vfile WHERE vid=%"
                       FSL_ID_T_PFMT
                       " AND fsl_match_vfile_or_dir(pathname,%Q)",
                       vid, zName);
  }else{
    fsl_buffer_appendf(sql,
                       "SELECT id FROM vfile WHERE vid=%" FSL_ID_T_PFMT,
                       vid);
  }

  if(changedOnly){
    fsl_buffer_append(sql, " AND (chnged OR deleted OR rid=0 "
                      "OR (origname IS NOT NULL AND "
                      "    origname<>pathname))", -1);

  }
  rc = fsl_buffer_appendf(sql, " /* %s() */", __func__);

  if(0==rc) rc = fsl_db_prepare(db, &st, "%b", sql);
  while(!rc && (FSL_RC_STEP_ROW == (rc=fsl_stmt_step(&st)))){
    rc = fsl_id_bag_insert( dest, fsl_stmt_g_id(&st, 0) );
  }
  if(FSL_RC_STEP_DONE==rc) rc = 0;

  fsl__cx_scratchpad_yield(f, sql);
  fsl_stmt_finalize(&st);
  if(rc && !f->error.code && db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

int fsl_filename_to_vfile_id( fsl_cx * const f, fsl_id_t vid,
                              char const * zName,
                              fsl_id_t * const vfid ){
  fsl_db * db = fsl_needs_ckout(f);
  int rc;
  fsl_stmt st = fsl_stmt_empty;
  assert(db);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  else if(!zName || !fsl_is_simple_pathname(zName, true)){
    return fsl_cx_err_set(f, FSL_RC_RANGE,







|
|
|
|
|

|
|
|

>
|
|
|
|
>


>
|




>








|
<
<







4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761


4762
4763
4764
4765
4766
4767
4768
  int rc;
  fsl_buffer * sql = 0;
  if(!db) return FSL_RC_NOT_A_CKOUT;
  sql = fsl__cx_scratchpad(f);
  if(0>=vid) vid = f->ckout.rid;
  if(zName && *zName
     && !('.'==*zName && !zName[1])){
    rc = fsl_buffer_appendf(sql,
                            "SELECT id FROM vfile WHERE vid=%"
                            FSL_ID_T_PFMT
                            " AND fsl_match_vfile_or_dir(pathname,%Q)",
                            vid, zName);
  }else{
    rc = fsl_buffer_appendf(sql,
                            "SELECT id FROM vfile WHERE vid=%" FSL_ID_T_PFMT,
                            vid);
  }
  if(rc) goto end;
  else if(changedOnly){
    rc = fsl_buffer_append(sql, " AND (chnged OR deleted OR rid=0 "
                           "OR (origname IS NOT NULL AND "
                           "    origname<>pathname))", -1);
    if(rc) goto end;
  }
  rc = fsl_buffer_appendf(sql, " /* %s() */", __func__);
  if(rc) goto end;
  rc = fsl_db_prepare(db, &st, "%b", sql);
  while(!rc && (FSL_RC_STEP_ROW == (rc=fsl_stmt_step(&st)))){
    rc = fsl_id_bag_insert( dest, fsl_stmt_g_id(&st, 0) );
  }
  if(FSL_RC_STEP_DONE==rc) rc = 0;
  end:
  fsl__cx_scratchpad_yield(f, sql);
  fsl_stmt_finalize(&st);
  if(rc && !f->error.code && db->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid, char const * zName, fsl_id_t * vfid ){


  fsl_db * db = fsl_needs_ckout(f);
  int rc;
  fsl_stmt st = fsl_stmt_empty;
  assert(db);
  if(!db) return FSL_RC_NOT_A_CKOUT;
  else if(!zName || !fsl_is_simple_pathname(zName, true)){
    return fsl_cx_err_set(f, FSL_RC_RANGE,
9710
9711
9712
9713
9714
9715
9716
9717
9718
9719
9720
9721
9722
9723
9724
9725
9726
9727
9728
9729
9730
9731
9732
9733
9734
9735
9736
9737
9738
9739
9740
9741
9742
9743
9744
9745
9746
9747
9748
9749
9750
9751
9752
9753
9754
9755
9756
9757
9758
9759
9760
9761
9762
9763
9764
9765
9766
9767
9768
9769
9770
9771
9772
9773
9774
9775
9776
9777
9778
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
9791
9792
9793
9794
9795
9796
9797
9798
9799
9800
9801
9802
9803
9804
9805
9806
9807
9808
9809
9810
9811
9812
9813
9814
9815
9816
9817
9818
9819
9820
9821
9822
9823
9824
9825
9826
9827
9828
9829
9830
9831
9832
9833
9834
9835
9836
9837
9838
9839
9840
9841
9842
9843
9844
9845
9846
9847
9848
9849
9850
9851
9852
9853
9854
9855
9856
9857
9858
9859
9860
9861
9862
9863
9864
9865
9866
9867
9868
9869
9870
9871
9872
9873
9874
9875
9876
9877
9878
9879
9880
9881
9882
9883
9884
9885
9886
9887
9888
9889
9890
9891
9892
9893
9894
9895
9896
9897
9898
9899
9900
9901
9902
9903
9904
9905
9906
9907
9908
9909
9910
9911
9912
9913
9914
9915
9916
9917
9918
9919
9920
9921
9922
9923
9924
9925
9926
9927
9928
9929
9930
9931
9932
9933
9934
9935
9936
9937
9938
9939
9940
9941
9942
9943
9944
9945
9946
9947
9948
9949
9950
9951
9952
9953
9954
9955
9956
9957
9958
9959
9960
9961
9962
9963
9964
9965
9966
9967
9968
9969
9970
9971
9972
9973
9974
9975
9976
9977
9978
9979
9980
9981
9982
9983
9984
9985
9986
9987
9988
9989
9990
9991
9992
9993
9994
9995
9996
9997
9998
9999
10000
10001
10002
10003
10004
10005
10006
10007
10008
10009
10010
10011
10012
10013
10014
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024
10025
10026
10027
10028
10029
10030
10031
10032
10033
10034
10035
10036
10037
10038
10039
10040
10041
10042
10043
10044
10045
10046
10047
10048
10049
10050
10051
10052
10053
10054
10055
10056
10057
10058
10059
10060
10061
10062
10063
10064
10065
10066
10067
10068
10069
10070
10071
10072
10073
10074
10075
10076
10077
10078
10079
10080
10081
10082
10083
10084
10085
10086
10087
10088
10089
10090
10091
10092
10093
10094
10095
10096
10097
10098
10099
10100
10101
10102
  for( int i = 0; NULL!=(dbName=dbNames[i]); ++i){
    fsl_strlcpy(z + sz , dbName, (fsl_size_t)BufLen - sz);
    if(fsl_file_size(z)>=1024) return dbName;
  }
  return NULL;
}

/**
   Internal helper for fsl_ckout_rename(). Performs the vfile update
   for renaming zFrom to zTo, taking into account certain
   vfile-semantics error conditions.
*/
static int fsl__mv_one_file(fsl_cx * const f, char const * zFrom,
                            char const * zTo, bool dryRun){
  fsl_db * const db = fsl_cx_db_ckout(f);
  int deleted = fsl_db_g_int32(db, -1,
                               "SELECT deleted FROM vfile WHERE vid=%"FSL_ID_T_PFMT
                               " AND pathname=%Q %s",
                               f->ckout.rid, zTo, fsl_cx_filename_collation(f));
  if(deleted>=0){
    if(0==deleted){
      if( !fsl_cx_is_case_sensitive(f,false) &&
          0==fsl_stricmp(zFrom, zTo) ){
        /* Case change only */
      }else{
        return fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS,
                              "Cannot rename '%s' to '%s' because "
                              "another file named '%s' is already "
                              "under management.", zFrom, zTo, zTo);
      }
    }else{
      return fsl_cx_err_set(f, FSL_RC_CONSISTENCY,
                            "Cannot rename '%s' to '%s' because "
                            "a pending deletion of '%s' has not "
                            "yet been checked in.", zFrom, zTo, zTo);
    }
  }
  int rc = 0;
  if( !dryRun ){
    rc = fsl_cx_exec(f, "UPDATE vfile SET pathname=%Q WHERE "
                     "pathname=%Q %s AND vid=%"FSL_ID_T_PFMT,
                     zTo, zFrom, fsl_cx_filename_collation(f),
                     f->ckout.rid);
  }
  return rc;
}

/**
   Internal helper for fsl_ckout_rename(). Performs the filesystem-level
   moving of all files in the TEMP.ckout_mv table. All fs-level errors
   _are ignored_.
*/
static int fsl__rename_process_fmove(fsl_cx * const f){
  int rc = 0;
  fsl_stmt q = fsl_stmt_empty;
  bool const allowSymlinks = fsl_cx_allows_symlinks(f, false);
  rc = fsl_cx_prepare(f, &q, "SELECT fsl_ckout_dir()||f, "
                      "fsl_ckout_dir()||t "
                      "FROM ckout_mv ORDER BY 1");
  while(0==rc && FSL_RC_STEP_ROW==fsl_stmt_step(&q)){
    char const * zFrom = fsl_stmt_g_text(&q, 0, NULL);
    char const * zTo = fsl_stmt_g_text(&q, 1, NULL);
    if(!zFrom || !zTo){FSL__WARN_OOM; rc = FSL_RC_OOM; break;}
    //MARKER(("MOVING: %s ==> %s\n", zFrom, zTo));
    int const fromDirCheck = fsl_dir_check(zFrom);
    int fsrc;
    if(fromDirCheck>0){
      /* This case is "impossible." Unless... perhaps... a user
         somehow moves things around in the filesystem during the
         fsl_ckout_rename(), such that a to-be-renamed entry which was
         formerly a file is not a directory. */
#if 0
      assert(!"This case cannot possibly happen.");
      fsl__fatal(FSL_RC_CANNOT_HAPPEN,
                "Input name for a file-rename is a directory: %s",
                zFrom)/*does not return*/;
#endif
      int const toDirCheck = fsl_dir_check(zTo);
      if(0==toDirCheck){
        fsl_file_rename(zFrom, zTo);
      }
    }else if(fromDirCheck<0){
      if(fsl_is_symlink(zFrom)){
        fsrc = fsl_symlink_copy(zFrom, zTo, allowSymlinks);
      }else{
        fsrc = fsl_file_copy(zFrom, zTo);
      }
      if(0==fsrc){
        /* fossil(1) unconditionally unlinks zFrom if zFrom is not a
           directory. Maybe we should too? */
        fsl_file_unlink(zFrom);
      }
    }
  }
  fsl_stmt_finalize(&q);
  return rc;
}

int fsl_ckout_rename(fsl_cx * const f, fsl_ckout_rename_opt const * opt){
  int rc = 0;
  bool inTrans = false;
  fsl_buffer * const bDest = fsl__cx_scratchpad(f)
    /* Destination directory */;
  fsl_buffer * const bSrc = fsl__cx_scratchpad(f)
    /* One source file/dir */;
  fsl_stmt qName = fsl_stmt_empty;
  fsl_stmt qMv = fsl_stmt_empty;
  int origType = 0
    /* -1 == multiple input files, 0 == one file, 1 == directory */;
  int destType = 0
    /* >0==directory, 0==does not exist, <0==non-dir */;
  uint32_t srcCount = 0;

  if(!opt->src->used){
    rc = fsl_cx_err_set(f, FSL_RC_RANGE,
                        "Expecting 1 or more source files/directories.");
    goto end;
  }
  rc = fsl_cx_transaction_begin(f);
  if(rc) goto end;
  inTrans = true;
  rc = fsl_ckout_filename_check(f, opt->relativeToCwd, opt->dest,
                                bDest);
  if(rc) goto end;
  fsl_buffer_strip_slashes(bDest);

  rc = fsl_cx_exec_multi(f, "DROP TABLE IF EXISTS TEMP.ckout_mv; "
                         "CREATE TEMP TABLE ckout_mv("
                         "f TEXT UNIQUE ON CONFLICT IGNORE, t TEXT)");
  if(rc) goto end;
  rc = fsl_cx_exec(f, "UPDATE vfile SET origname=pathname "
                   "WHERE origname IS NULL");
  if(rc) goto end;

  if(opt->src->used > 1){
    origType = -1;
  }else{
    /* Make opt->src->list[0] absolute and see if it resolves to an
       existing dir. */
    char const * zSrc= (char const *)opt->src->list[0];
    rc = fsl_ckout_filename_check(f, opt->relativeToCwd, zSrc, bSrc);
    if(rc) goto end;
    fsl_buffer * const bCheck = fsl__cx_scratchpad(f);
    int oCheck = 0;
    rc = fsl_buffer_append(bCheck, f->ckout.dir, f->ckout.dirLen);
    if(0==rc) rc = fsl_buffer_append(bCheck, bSrc->mem, bSrc->used);
    if(0==rc) oCheck = fsl_dir_check(fsl_buffer_cstr(bCheck));
    fsl__cx_scratchpad_yield(f, bCheck);
    if(rc){FSL__WARN_OOM; goto end;}
    if(oCheck>0) origType = 1;
    else if(oCheck<0) origType = 0;
  }
  {
    /* Make bDest absolute and see if it resolves to an existing dir. */
    fsl_buffer * const bCheck = fsl__cx_scratchpad(f);
    rc = fsl_buffer_append(bCheck, f->ckout.dir, f->ckout.dirLen);
    if(0==rc) rc = fsl_buffer_append(bCheck, bDest->mem, bDest->used);
    if(0==rc) destType = fsl_dir_check(fsl_buffer_cstr(bCheck));
    fsl__cx_scratchpad_yield(f, bCheck);
    if(rc){FSL__WARN_OOM; goto end;}
  }
  if(-1==origType && destType<=0){
    rc = fsl_cx_err_set(f, FSL_RC_MISUSE,
                        "Multiple source files provided, so "
                        "destination must be an existing directory.");
    goto end;
  }else if(1==origType && destType<0){
    rc = fsl_cx_err_set(f, FSL_RC_TYPE,
                        "Cannot rename '%s' to '%s' "
                        "because a non-directory named '%s' already exists.",
                        (char const *)opt->src->list[0],
                        opt->dest, opt->dest);
    goto end;
  }else if( 0==origType && destType<=0 ){
    /* Move single file to dest. */
    fsl_id_t vfidCheck = 0;
    rc = fsl_filename_to_vfile_id(f, 0, fsl_buffer_cstr(bSrc),
                                  &vfidCheck);
    if(rc) goto end;
    else if(!vfidCheck){
      rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                          "File not under SCM management: %B",
                          bSrc);
      goto end;
    }
    rc = fsl_cx_exec(f, "INSERT INTO ckout_mv(f,t) VALUES(%B,%B)",
                     bSrc, bDest);
    if(rc) goto end;
    else ++srcCount;
  } else {
    if(fsl_buffer_eq(bDest, ".", 1)){
      fsl_buffer_reuse(bDest);
    }else{
      rc = fsl_buffer_append(bDest, "/", 1);
      if(rc){FSL__WARN_OOM; goto end;}
    }
    rc = fsl_cx_prepare(f, &qName, "SELECT pathname FROM vfile"
                        " WHERE vid=%"FSL_ID_T_PFMT
                        " AND fsl_match_vfile_or_dir(pathname,?1)"
                        " ORDER BY 1", f->ckout.rid);
    if(rc) goto end;
    rc = fsl_cx_prepare(f, &qMv, "INSERT INTO ckout_mv(f,t) VALUES("
                        "?1, ?2||?3)");
    if(rc) goto end;
    for(fsl_size_t i = 0; i < opt->src->used; ++i){
      uint32_t nFound = 0;
      char const * zSrc = (char const *)opt->src->list[i];
      fsl_buffer_reuse(bSrc);
      rc = fsl_ckout_filename_check(f, opt->relativeToCwd,
                                    zSrc, bSrc);
      if(rc) goto end;
      fsl_size_t nOrig = 0;
      char const * const zOrig = fsl_buffer_cstr2(bSrc, &nOrig);
      rc = fsl_stmt_bind_text(&qName, 1, zOrig, (fsl_int_t)nOrig, false);
      if(rc) goto end;
      while(FSL_RC_STEP_ROW==fsl_stmt_step(&qName)){
        fsl_size_t nPath = 0;
        char const * zPath = NULL;
        ++nFound;
        rc = fsl_stmt_get_text(&qName, 0, &zPath, &nPath);
        if(rc){fsl_cx_uplift_db_error(f, qName.db); goto end;}
        else if(!zPath){FSL__WARN_OOM; rc = FSL_RC_OOM; goto end;}
        char const * zTail;
        if(nPath==nOrig){
          zTail = fsl_file_tail(zPath);
        }else if(origType!=0 && destType>0 ){
          zTail = &zPath[nOrig-fsl_strlen(fsl_file_tail(zOrig))];
        }else{
          zTail = &zPath[nOrig+1];
        }
        rc = fsl_stmt_bind_step(&qMv, "sbs", zPath, bDest, zTail);
        if(0!=rc){
          fsl_cx_uplift_db_error(f, qMv.db);
          goto end;
        }
      }
      srcCount += nFound;
      fsl_stmt_reset(&qName);
      if(!nFound){
        rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                            "Name does not resolve to any "
                            "SCM-managed files: %B",
                            bSrc);
        goto end;
      }
    }/*for each opt->src*/
  }
  assert(0==rc);
  fsl_stmt_finalize(&qName);
  fsl_stmt_finalize(&qMv);
  if(0==srcCount){
    rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
                        "Source name(s) do not resolve to "
                        "any managed files.");
    goto end;
  }
  rc = fsl_cx_prepare(f, &qName, "SELECT f, t FROM ckout_mv ORDER BY f");
  if(rc) goto end;
  //rc = fsl_cx_prepare(f, &qMv, "INSERT
  while(FSL_RC_STEP_ROW==fsl_stmt_step(&qName)){
    char const * zFrom = fsl_stmt_g_text(&qName, 0, NULL);
    char const * zTo = fsl_stmt_g_text(&qName, 1, NULL);
    rc = fsl__mv_one_file(f, zFrom, zTo, opt->dryRun);
    if(rc) goto end;
    if(opt->callback){
      rc = opt->callback(f, opt, zFrom, zTo);
      if(rc) goto end;
    }
  }
  end:
  fsl_stmt_finalize(&qName);
  fsl_stmt_finalize(&qMv);
  fsl__cx_scratchpad_yield(f, bDest);
  fsl__cx_scratchpad_yield(f, bSrc);
  if(0==rc){
    assert(inTrans);
    if(!opt->dryRun && opt->doFsMv){
      rc = fsl__rename_process_fmove(f);
    }
    fsl_cx_exec(f, "DROP TABLE TEMP.ckout_mv");
  }
  if(inTrans){
    if(rc) fsl_cx_transaction_end(f, true);
    else rc = fsl_cx_transaction_end(f, false);
  }
  return rc;
}


int fsl_ckout_rename_revert(fsl_cx * const f, char const *zNewName,
                            bool relativeToCwd, bool doFsMv,
                            bool *didSomething){
  fsl_buffer * bufFName = fsl__cx_scratchpad(f);
  int rc = 0;
  bool inTrans = false;
  fsl_db * const dbC = fsl_needs_ckout(f);
  fsl_stmt q = fsl_stmt_empty;
  if(!dbC) return FSL_RC_NOT_A_CKOUT;
  rc = fsl_ckout_filename_check(f, relativeToCwd, zNewName, bufFName);
  if(rc) goto end;
  rc = fsl_cx_transaction_begin(f);
  if(rc) goto end;
  inTrans = true;
  rc = fsl_cx_prepare(f, &q,
                      "SELECT id FROM vfile "
                      "WHERE pathname=%Q AND origname<>pathname "
                      "and origname IS NOT NULL %s",
                      fsl_buffer_cstr(bufFName),
                      fsl_cx_filename_collation(f));
  if(rc) goto end;
  switch(fsl_stmt_step(&q)){
    case FSL_RC_STEP_ROW: {
      char const * zNameP = NULL;
      char const * zNameO = NULL;
      fsl_id_t const vfid = fsl_stmt_g_id(&q, 0);
      assert(vfid>0);
      fsl_stmt_finalize(&q);
      if(doFsMv){
        rc = fsl_cx_prepare(f, &q, "SELECT fsl_ckout_dir()||pathname, "
                            "fsl_ckout_dir()||origname FROM vfile "
                            "WHERE id=%"FSL_ID_T_PFMT, vfid);
        if(rc) goto end;
        rc = fsl_stmt_step(&q);
        assert(FSL_RC_STEP_ROW==rc && "We _just_ confirmed that these are there.");
        zNameP = fsl_stmt_g_text(&q, 0, NULL);
        zNameO = fsl_stmt_g_text(&q, 1, NULL);
        if(!zNameO || !zNameO) {FSL__WARN_OOM; rc = FSL_RC_OOM; goto end;}
      }
      rc = fsl_cx_exec(f, "UPDATE vfile SET pathname=origname, origname=NULL "
                       "WHERE id=%"FSL_ID_T_PFMT, vfid);
      if(rc) goto end;
      if(didSomething) *didSomething = true;
      if(doFsMv && fsl_is_file(zNameP)){
        assert(zNameO && zNameP);
        fsl_file_unlink(zNameO);
        rc = fsl_file_rename(zNameP, zNameO);
        if(rc){
          rc = fsl_cx_err_set(f, rc, "File rename failed with code %s: "
                              "'%s' => '%s'", fsl_rc_cstr(rc),
                              zNameO + f->ckout.dirLen,
                              zNameP + f->ckout.dirLen);
        }
      }
      break;
    }
    case FSL_RC_STEP_DONE:
      if(didSomething) *didSomething = false;
      goto end;
    default:
      rc = fsl_cx_uplift_db_error(f, dbC);
      goto end;
  }
  
  end:
  fsl_stmt_finalize(&q);
  fsl__cx_scratchpad_yield(f, bufFName);
  if(inTrans){
    if(0==rc) rc = fsl_cx_transaction_end(f, false);
    else fsl_cx_transaction_end(f, true);
  }
  return rc;

}

#undef MARKER
/* end of file ./src/checkout.c */
/* start of file ./src/cli.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  SPDX-FileCopyrightText: 2021 The Libfossil Authors
  SPDX-ArtifactOfProjectName: Libfossil
  SPDX-FileType: Code

  Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
#include <string.h> /* for strchr() */
#include <errno.h>
#if !defined(FSL_AMALGAMATION_BUILD)
/* When not in the amalgamation build, force assert() to always work... */
#  if defined(NDEBUG)
#    undef NDEBUG
#    undef DEBUG
#    define DEBUG 1
#  endif
#endif
#include <assert.h> /* for the benefit of test apps */

/* Only for debugging */
#include <stdio.h>







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















<
|



<







8771
8772
8773
8774
8775
8776
8777





































































































































































































































































































































































8778
8779
8780
8781
8782
8783
8784
8785
8786
8787
8788
8789
8790
8791
8792
8793

8794
8795
8796
8797

8798
8799
8800
8801
8802
8803
8804
  for( int i = 0; NULL!=(dbName=dbNames[i]); ++i){
    fsl_strlcpy(z + sz , dbName, (fsl_size_t)BufLen - sz);
    if(fsl_file_size(z)>=1024) return dbName;
  }
  return NULL;
}






































































































































































































































































































































































#undef MARKER
/* end of file ./src/checkout.c */
/* start of file ./src/cli.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
  SPDX-FileCopyrightText: 2021 The Libfossil Authors
  SPDX-ArtifactOfProjectName: Libfossil
  SPDX-FileType: Code

  Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
*/
#include <string.h> /* for strchr() */

#if !defined(ORG_FOSSIL_SCM_FSL_CORE_H_INCLUDED)
/* When not in the amalgamation build, force assert() to always work... */
#  if defined(NDEBUG)
#    undef NDEBUG

#    define DEBUG 1
#  endif
#endif
#include <assert.h> /* for the benefit of test apps */

/* Only for debugging */
#include <stdio.h>
10124
10125
10126
10127
10128
10129
10130
10131
10132
10133
10134
10135
10136
10137
10138
    0/*helpRequested*/, \
    false/*versionRequested*/\
  },                    \
  {/*config*/      \
    -1/*traceSql*/,      \
    fsl_outputer_empty_m      \
  },                          \
  {/*paths*/fsl_pathfinder_empty_m/*bins*/},    \
  fsl_error_empty_m/*err*/  \
}

const fcli_t fcli_empty = fcli_empty_m;
fcli_t fcli = fcli_empty_m;
const fcli_cliflag fcli_cliflag_empty = fcli_cliflag_empty_m;
static fsl_timer_state fcliTimer = fsl_timer_state_empty_m;







<







8826
8827
8828
8829
8830
8831
8832

8833
8834
8835
8836
8837
8838
8839
    0/*helpRequested*/, \
    false/*versionRequested*/\
  },                    \
  {/*config*/      \
    -1/*traceSql*/,      \
    fsl_outputer_empty_m      \
  },                          \

  fsl_error_empty_m/*err*/  \
}

const fcli_t fcli_empty = fcli_empty_m;
fcli_t fcli = fcli_empty_m;
const fcli_cliflag fcli_cliflag_empty = fcli_cliflag_empty_m;
static fsl_timer_state fcliTimer = fsl_timer_state_empty_m;
10241
10242
10243
10244
10245
10246
10247
10248
10249
10250
10251
10252
10253
10254
10255

static void fcli_shutdown(void){
  fsl_cx * const f = fcli.f;
  int rc = 0;
 
  fsl_error_clear(&fcli.err);
  fsl_free(fcli.argv)/*contents are in the FCliFree list*/;
  fsl_pathfinder_clear(&fcli.paths.bins);

  if(f){
    while(fsl_cx_transaction_level(f)){
      MARKER(("WARNING: open db transaction at shutdown-time. "
              "Rolling back.\n"));
      fsl_cx_transaction_end(f, true);
    }







<







8942
8943
8944
8945
8946
8947
8948

8949
8950
8951
8952
8953
8954
8955

static void fcli_shutdown(void){
  fsl_cx * const f = fcli.f;
  int rc = 0;
 
  fsl_error_clear(&fcli.err);
  fsl_free(fcli.argv)/*contents are in the FCliFree list*/;


  if(f){
    while(fsl_cx_transaction_level(f)){
      MARKER(("WARNING: open db transaction at shutdown-time. "
              "Rolling back.\n"));
      fsl_cx_transaction_end(f, true);
    }
10302
10303
10304
10305
10306
10307
10308
10309
10310
10311
10312
10313
10314
10315
10316
}

static const fcli_cliflag FCliFlagsGlobal[] = {
  FCLI_FLAG_BOOL_X("?","help",NULL,
                   fcli_flag_f_help,
                   "Show app help. Also triggered if the first non-flag is \"help\"."),
  FCLI_FLAG_BOOL(0,"lib-version", &fcli.transient.versionRequested,
                 "Show libfossil version number."),
  FCLI_FLAG("R","repo","REPO-FILE",&fcli.transient.repoDbArg,
            "Selects a specific repository database, ignoring the one "
            "used by the current directory's checkout (if any)."),
  FCLI_FLAG(NULL,"user","username",&fcli.transient.userArg,
            "Sets the name of the fossil user name for this session."),
  FCLI_FLAG_BOOL_X(NULL, "no-checkout",NULL,fcli_flag_f_nocheckoutDir,
                   "Disable automatic attempt to open checkout."),







|







9002
9003
9004
9005
9006
9007
9008
9009
9010
9011
9012
9013
9014
9015
9016
}

static const fcli_cliflag FCliFlagsGlobal[] = {
  FCLI_FLAG_BOOL_X("?","help",NULL,
                   fcli_flag_f_help,
                   "Show app help. Also triggered if the first non-flag is \"help\"."),
  FCLI_FLAG_BOOL(0,"lib-version", &fcli.transient.versionRequested,
                 "Show app version number."),
  FCLI_FLAG("R","repo","REPO-FILE",&fcli.transient.repoDbArg,
            "Selects a specific repository database, ignoring the one "
            "used by the current directory's checkout (if any)."),
  FCLI_FLAG(NULL,"user","username",&fcli.transient.userArg,
            "Sets the name of the fossil user name for this session."),
  FCLI_FLAG_BOOL_X(NULL, "no-checkout",NULL,fcli_flag_f_nocheckoutDir,
                   "Disable automatic attempt to open checkout."),
10451
10452
10453
10454
10455
10456
10457
10458
10459
10460
10461
10462
10463
10464
10465
     f-ci -m="message" ...

     simply doesn't fit the age-old muscle memory of:

     svn ci -m ...
     cvs ci -m ...
     fossil ci -m ...
     f-ci -m ...
  */
  for( f = defs; f->flagShort || f->flagLong; ++f ){
    if(!f->flagValue && !f->callback){
      /* We accept these for purposes of generating the --help text,
         but we can't otherwise do anything sensible with them and
         assume the app will handle such flags downstream or ignore
         them altogether.*/







<







9151
9152
9153
9154
9155
9156
9157

9158
9159
9160
9161
9162
9163
9164
     f-ci -m="message" ...

     simply doesn't fit the age-old muscle memory of:

     svn ci -m ...
     cvs ci -m ...
     fossil ci -m ...

  */
  for( f = defs; f->flagShort || f->flagLong; ++f ){
    if(!f->flagValue && !f->callback){
      /* We accept these for purposes of generating the --help text,
         but we can't otherwise do anything sensible with them and
         assume the app will handle such flags downstream or ignore
         them altogether.*/
10489
10490
10491
10492
10493
10494
10495

10496
10497
10498
10499
10500
10501
10502
      rc = fcli__error->code;
      break;
    }
    //MARKER(("Got?=%d flag: %s/%s %s\n",gotIt, f->flagShort, f->flagLong, v ? v : ""));
    if(!gotIt){
      continue;
    }

    if(f->flagValue) switch(f->flagType){
      case FCLI_FLAG_TYPE_BOOL:
        *((bool*)f->flagValue) = true;
        break;
      case FCLI_FLAG_TYPE_BOOL_INVERT:
        *((bool*)f->flagValue) = false;
        break;







>







9188
9189
9190
9191
9192
9193
9194
9195
9196
9197
9198
9199
9200
9201
9202
      rc = fcli__error->code;
      break;
    }
    //MARKER(("Got?=%d flag: %s/%s %s\n",gotIt, f->flagShort, f->flagLong, v ? v : ""));
    if(!gotIt){
      continue;
    }
    assert(f->flagValue || f->callback);
    if(f->flagValue) switch(f->flagType){
      case FCLI_FLAG_TYPE_BOOL:
        *((bool*)f->flagValue) = true;
        break;
      case FCLI_FLAG_TYPE_BOOL_INVERT:
        *((bool*)f->flagValue) = false;
        break;
10578
10579
10580
10581
10582
10583
10584








10585
10586
10587
10588
10589
10590
10591
        FLAG("?") {
          ++fcli.transient.helpRequested;
          continue;
        }
        FLAG("V") {
          fcli.clientFlags.verbose += 1;
          continue;








        }
        FLAG("verbose") {
          fcli.clientFlags.verbose += 1;
          continue;
        }
      }
#undef FLAG







>
>
>
>
>
>
>
>







9278
9279
9280
9281
9282
9283
9284
9285
9286
9287
9288
9289
9290
9291
9292
9293
9294
9295
9296
9297
9298
9299
        FLAG("?") {
          ++fcli.transient.helpRequested;
          continue;
        }
        FLAG("V") {
          fcli.clientFlags.verbose += 1;
          continue;
        }
        FLAG("VV") {
          fcli.clientFlags.verbose += 2;
          continue;
        }
        FLAG("VVV") {
          fcli.clientFlags.verbose += 3;
          continue;
        }
        FLAG("verbose") {
          fcli.clientFlags.verbose += 1;
          continue;
        }
      }
#undef FLAG
10710
10711
10712
10713
10714
10715
10716
10717
10718
10719
10720
10721
10722
10723
10724
    We copy fsl_lib_configurable.allocator as a base allocator.
 */
static fsl_allocator fslAllocOrig;

/**
    Proxies fslAllocOrig.f() and abort()s on OOM conditions.
*/
static void * fsl_realloc_f_failing(void * state __unused, void * mem, fsl_size_t n){
  void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n);
  if(n && !rv){
    fsl__fatal(FSL_RC_OOM, NULL)/*does not return*/;
  }
  return rv;
}








|







9418
9419
9420
9421
9422
9423
9424
9425
9426
9427
9428
9429
9430
9431
9432
    We copy fsl_lib_configurable.allocator as a base allocator.
 */
static fsl_allocator fslAllocOrig;

/**
    Proxies fslAllocOrig.f() and abort()s on OOM conditions.
*/
static void * fsl_realloc_f_failing(void * state, void * mem, fsl_size_t n){
  void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n);
  if(n && !rv){
    fsl__fatal(FSL_RC_OOM, NULL)/*does not return*/;
  }
  return rv;
}

11033
11034
11035
11036
11037
11038
11039
11040
11041
11042
11043
11044
11045
11046
11047
      /* Accept either (help command) or (command help) as help. */
      /* Except that it turns out that fcli_setup() will trump the
         former and doesn't have the fcli_command state, so can't do
         this. Maybe we can change that somehow. */
      helpState = 1;
      helpPos = orig;
      arg = fcli_next_arg(1); // consume it
    }else if(0==fsl_strcmp(arg,cmd->name) || 0==fcli_cmd_aliascmp(cmd,arg)){
      if(!cmd->f){
        rc = fcli_err_set(FSL_RC_NYI,
                               "Command [%s] has no "
                               "callback function.");
      }else{
        fcli_next_arg(1)/*consume it*/;
        if(helpState){







|







9741
9742
9743
9744
9745
9746
9747
9748
9749
9750
9751
9752
9753
9754
9755
      /* Accept either (help command) or (command help) as help. */
      /* Except that it turns out that fcli_setup() will trump the
         former and doesn't have the fcli_command state, so can't do
         this. Maybe we can change that somehow. */
      helpState = 1;
      helpPos = orig;
      arg = fcli_next_arg(1); // consume it
    }else if(0==fsl_strcmp(arg,cmd->name) || fcli_cmd_aliascmp(cmd,arg)){
      if(!cmd->f){
        rc = fcli_err_set(FSL_RC_NYI,
                               "Command [%s] has no "
                               "callback function.");
      }else{
        fcli_next_arg(1)/*consume it*/;
        if(helpState){
11061
11062
11063
11064
11065
11066
11067
11068
11069
11070
11071
11072
11073
11074
11075
11076
11077
11078
11079
11080
11081
11082
11083
11084
11085
11086
11087
11088
11089
11090
11091
11092
11093
11094
11095
11096
11097
11098
11099
11100
11101
11102
11103
11104
11105
11106
11107
11108
11109
11110
      }
      break;
    }
  }
  if(helpState){
    f_out("\n");
    fcli_command_help(helpPos, true, helpState>1);
    fcli.transient.helpRequested++;
  }else if(!cmd->name){
    fsl_buffer msg = fsl_buffer_empty;
    int rc2;
    if(!arg){
      rc2 = FSL_RC_MISUSE;
      fsl_buffer_appendf(&msg, "No command provided.");
    }else{
      rc2 = FCLI_RC_NO_CMD;
      fsl_buffer_appendf(&msg, "Command not found: %s.",arg);
    }
    fsl_buffer_appendf(&msg, " Available commands: ");
    cmd = orig;
    for( ; cmd && cmd->name; ++cmd ){
      fsl_buffer_appendf( &msg, "%s%s",
                          (cmd==orig) ? "" : ", ",
                          cmd->name);
    }
    rc = fcli_err_set(rc2, "%b", &msg);
    fsl_buffer_clear(&msg);
  }
  if(rc && reportErrors){
    fcli_err_report(0);
  }
  return rc;
}

int fcli_cmd_aliascmp(fcli_command const * cmd, char const * arg){
  char const * alias = cmd->aliases;
  while ( alias && *alias!=0 ){
    if( 0==fsl_strcmp(alias, arg) ){
      return 0;
    }
    alias = strchr(alias, 0) + 1;
  }
  return 1;
}

void fcli_command_help(fcli_command const * cmd, bool showUsage, bool onlyOne){
  fcli_command const * c = cmd;
  for( ; c->name; ++c ){
    f_out("[%s] command:\n\n", c->name);
    if(c->briefDescription){







<







|


















|



|



|







9769
9770
9771
9772
9773
9774
9775

9776
9777
9778
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
9791
9792
9793
9794
9795
9796
9797
9798
9799
9800
9801
9802
9803
9804
9805
9806
9807
9808
9809
9810
9811
9812
9813
9814
9815
9816
9817
      }
      break;
    }
  }
  if(helpState){
    f_out("\n");
    fcli_command_help(helpPos, true, helpState>1);

  }else if(!cmd->name){
    fsl_buffer msg = fsl_buffer_empty;
    int rc2;
    if(!arg){
      rc2 = FSL_RC_MISUSE;
      fsl_buffer_appendf(&msg, "No command provided.");
    }else{
      rc2 = FSL_RC_NOT_FOUND;
      fsl_buffer_appendf(&msg, "Command not found: %s.",arg);
    }
    fsl_buffer_appendf(&msg, " Available commands: ");
    cmd = orig;
    for( ; cmd && cmd->name; ++cmd ){
      fsl_buffer_appendf( &msg, "%s%s",
                          (cmd==orig) ? "" : ", ",
                          cmd->name);
    }
    rc = fcli_err_set(rc2, "%b", &msg);
    fsl_buffer_clear(&msg);
  }
  if(rc && reportErrors){
    fcli_err_report(0);
  }
  return rc;
}

bool fcli_cmd_aliascmp(fcli_command const * cmd, char const * arg){
  char const * alias = cmd->aliases;
  while ( alias && *alias!=0 ){
    if( 0==fsl_strcmp(alias, arg) ){
      return true;
    }
    alias = strchr(alias, 0) + 1;
  }
  return false;
}

void fcli_command_help(fcli_command const * cmd, bool showUsage, bool onlyOne){
  fcli_command const * c = cmd;
  for( ; c->name; ++c ){
    f_out("[%s] command:\n\n", c->name);
    if(c->briefDescription){
11424
11425
11426
11427
11428
11429
11430
11431
11432
11433
11434
11435
11436
11437
11438
11439
11440
11441
11442
11443
11444
11445
11446
11447
11448
11449
11450
11451
11452
11453
11454
11455
11456
11457
11458
11459
11460
11461
11462
11463
11464
11465
11466
11467
11468
11469
11470
11471
11472
11473
11474
11475
11476
11477
11478
11479
11480
11481
11482
11483
11484
11485
11486
11487
11488
11489
11490
11491
11492
11493
11494
11495
11496
11497
11498
11499
11500
11501
11502
11503
11504
11505
11506
11507
11508
11509
11510
11511
11512
11513
11514
11515
11516
11517
11518
11519
11520
11521
11522
11523
11524
11525
11526
11527
11528
11529
11530
11531
11532
11533
11534
11535
11536
11537
11538
11539
11540
11541
11542
11543
11544
11545
        "Entry count=%u totaling %u byte(s).\n",
        f->cache.blobContent.metrics.hits,
        f->cache.blobContent.metrics.misses,
        f->cache.blobContent.used,
        f->cache.blobContent.szTotal);
}

char const * fcli_fossil_binary(bool errIfNotFound, int reportPolicy){
  static bool once = false;
  if(!once){
    int rc = 0;
    char const * path = getenv("PATH");
    if(path && *path){
       fsl_path_splitter pt = fsl_path_splitter_empty;
       fsl_size_t tLen = 0;
       char const * t = 0;
       fsl_path_splitter_init(&pt, path, -1);
       while(0==rc && 0==fsl_path_splitter_next(&pt, &t, &tLen)){
         rc = fsl_pathfinder_dir_add2(&fcli.paths.bins,
                                      t, (fsl_int_t)tLen);
       }
    }
    if(0==rc){
      fsl_pathfinder_ext_add2(&fcli.paths.bins,".exe", 4);
    }
    once = true;
  }
  char const * z = NULL;
  fsl_pathfinder_search(&fcli.paths.bins, "fossil", &z, NULL);
  if(!z && errIfNotFound){
    fcli_err_set(FSL_RC_NOT_FOUND,
                 "Fossil binary not found in $PATH.");
    if(reportPolicy){
      fcli_err_report(reportPolicy>0);
    }
  } 
  return z;
}

static int fcli__transaction_check(void){
  if(fsl_cx_transaction_level(fcli.f)){
    return fcli_err_set(FSL_RC_LOCKED,
                        "Sync cannot succeed if a transaction "
                        "is opened. Close all transactions before "
                        "calling %s().", __func__);
  }
  return 0;
}

static bool fcli__autosync_setting(void){
  return fsl_configs_get_bool(fcli.f, "crg",
                              fsl_configs_get_bool(fcli.f, "crg",
                                                   false, "autosync"),
                              "fcli.autosync");
}

int fcli_sync( int ops ){
  int rc = 0;
  if((rc = fcli__transaction_check())) return rc;

  int doPush = -1;
  int doPull = -1;
  char const * zSuppressOut = "";
  fsl_db * const dbR = fsl_needs_repo(fcli.f);
  if(!dbR){
    return FSL_RC_NOT_A_REPO;
  }else if(!fsl_db_exists(dbR, "select 1 from config "
                          "where name like 'syncwith:%%'")){
    /* No remote, so nothing to do (and any attempt would fail). */
    return 0;
  }
  if(FCLI_SYNC_PULL & ops){
    doPull = 1;
  }
  if(FCLI_SYNC_PUSH & ops){
    doPush = 1;
  }
#if !FSL_PLATFORM_IS_WINDOWS
  if(FCLI_SYNC_NO_OUTPUT & ops){
    zSuppressOut = " >/dev/null 2>&1";
  }else if(FCLI_SYNC_NO_STDOUT & ops){
    zSuppressOut = " >/dev/null";
  }
#endif
  bool const autosync = fcli__autosync_setting();
  if(!autosync && (FCLI_SYNC_AUTO & ops)){
    return 0;
  }
  if(doPull<=0 && doPush<=0){
    return 0;
  }
  char const * zCmd;
  char const * fslBin;
  if(doPull>0 && doPush>0) zCmd = "sync";
  else if(doPull>0) zCmd = "pull";
  else{
    assert(doPush>0);
    zCmd = "push";
  }
  fslBin = fcli_fossil_binary(true, 0);
  if(!fslBin){
    assert(fcli__error->code);
    return fcli__error->code;
  }
  ;
  char * cmd = fsl_mprintf("%s %s%s", fslBin, zCmd, zSuppressOut);
  rc = fsl_system(cmd);
  if(rc){
    fsl_cx_caches_reset(fcli.f);
    rc = fcli_err_set(rc, "Command exited with non-0 result: %s", cmd);
  }
  fsl_free(cmd);
  return rc;
}

#undef FCLI_V3
#undef fcli_empty_m
#undef fcli__error
#undef MARKER
#undef FCLI_USE_SIGACTION
/* end of file ./src/cli.c */
/* start of file ./src/content.c */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







10131
10132
10133
10134
10135
10136
10137












































































































10138
10139
10140
10141
10142
10143
10144
        "Entry count=%u totaling %u byte(s).\n",
        f->cache.blobContent.metrics.hits,
        f->cache.blobContent.metrics.misses,
        f->cache.blobContent.used,
        f->cache.blobContent.szTotal);
}













































































































#undef FCLI_V3
#undef fcli_empty_m
#undef fcli__error
#undef MARKER
#undef FCLI_USE_SIGACTION
/* end of file ./src/cli.c */
/* start of file ./src/content.c */
13052
13053
13054
13055
13056
13057
13058
13059
13060
13061
13062
13063
13064
13065
13066
13067
13068
13069
13070
13071
13072
13073
13074
13075
13076
13077
13078
13079
13080
13081
13082
13083
13084
13085
13086
13087
13088
13089
13090
13091
13092
13093
13094
13095
13096
13097
13098
13099
13100
13101
      assert(!"Invalid fsl_confdb_e value");
      return NULL;
  }
}

int fsl_config_versionable_filename(fsl_cx *f, char const * key,
                                    fsl_buffer *b){
  if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  else if(!key || !*key || !fsl_is_simple_pathname(key, true)){
    return FSL_RC_MISUSE;
  }
  fsl_buffer_reuse(b);
  return fsl_buffer_appendf(b, "%s.fossil-settings/%s",
                            f->ckout.dir, key);
}


int fsl_config_unset( fsl_cx * const f, fsl_confdb_e mode, char const * key ){
  fsl_db * const db = fsl_config_for_role(f, mode);
  if(!db || !key || !*key) return FSL_RC_MISUSE;
  else if(mode==FSL_CONFDB_VERSIONABLE) return FSL_RC_UNSUPPORTED;
  else{
    char const * table = fsl_config_table_for_role(mode);
    assert(table);
    return fsl_db_exec(db, "DELETE FROM %s WHERE name=%Q", table, key);
  }
}

int32_t fsl_config_get_int32( fsl_cx * const f, fsl_confdb_e mode,
                              int32_t dflt, char const * key ){
  int32_t rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * const val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = (int32_t)atoi(val);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * const db = fsl_config_for_role(f, mode);
      char const * const table = fsl_config_table_for_role(mode);
      assert(table);
      if(db){
        fsl_stmt * st = NULL;
        fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                              table, __FILE__);
        if(st){
          st->role = fsl__confdb_to_role(mode);







|










|














|








|







11651
11652
11653
11654
11655
11656
11657
11658
11659
11660
11661
11662
11663
11664
11665
11666
11667
11668
11669
11670
11671
11672
11673
11674
11675
11676
11677
11678
11679
11680
11681
11682
11683
11684
11685
11686
11687
11688
11689
11690
11691
11692
11693
11694
11695
11696
11697
11698
11699
11700
      assert(!"Invalid fsl_confdb_e value");
      return NULL;
  }
}

int fsl_config_versionable_filename(fsl_cx *f, char const * key,
                                    fsl_buffer *b){
  if(!f || !fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT;
  else if(!key || !*key || !fsl_is_simple_pathname(key, true)){
    return FSL_RC_MISUSE;
  }
  fsl_buffer_reuse(b);
  return fsl_buffer_appendf(b, "%s.fossil-settings/%s",
                            f->ckout.dir, key);
}


int fsl_config_unset( fsl_cx * const f, fsl_confdb_e mode, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  if(!db || !key || !*key) return FSL_RC_MISUSE;
  else if(mode==FSL_CONFDB_VERSIONABLE) return FSL_RC_UNSUPPORTED;
  else{
    char const * table = fsl_config_table_for_role(mode);
    assert(table);
    return fsl_db_exec(db, "DELETE FROM %s WHERE name=%Q", table, key);
  }
}

int32_t fsl_config_get_int32( fsl_cx * const f, fsl_confdb_e mode,
                              int32_t dflt, char const * key ){
  int32_t rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = (int32_t)atoi(val);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * const db = fsl_config_for_role(f, mode);
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      if(db){
        fsl_stmt * st = NULL;
        fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                              table, __FILE__);
        if(st){
          st->role = fsl__confdb_to_role(mode);
13113
13114
13115
13116
13117
13118
13119
13120
13121
13122
13123
13124
13125
13126
13127
13128
13129
13130
13131
13132
13133
13134
13135
13136
}

int64_t fsl_config_get_int64( fsl_cx * const f, fsl_confdb_e mode,
                              int64_t dflt, char const * key ){
  int64_t rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * const val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = (int64_t)strtoll(val, NULL, 10);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * const db = fsl_config_for_role(f, mode);
      char const * const table = fsl_config_table_for_role(mode);
      assert(table);
      if(db){
        fsl_stmt * st = NULL;
        fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                              table, __FILE__);
        if(st){
          st->role = fsl__confdb_to_role(mode);







|







|
|







11712
11713
11714
11715
11716
11717
11718
11719
11720
11721
11722
11723
11724
11725
11726
11727
11728
11729
11730
11731
11732
11733
11734
11735
}

int64_t fsl_config_get_int64( fsl_cx * const f, fsl_confdb_e mode,
                              int64_t dflt, char const * key ){
  int64_t rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = (int64_t)strtoll(val, NULL, 10);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * db = fsl_config_for_role(f, mode);
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      if(db){
        fsl_stmt * st = NULL;
        fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                              table, __FILE__);
        if(st){
          st->role = fsl__confdb_to_role(mode);
13155
13156
13157
13158
13159
13160
13161
13162
13163
13164
13165
13166
13167
13168
13169
13170
13171
13172
13173
13174
13175
13176
13177
13178
13179
13180
}

double fsl_config_get_double( fsl_cx * const f, fsl_confdb_e mode,
                              double dflt, char const * key ){
  double rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * const val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = strtod(val, NULL);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * const db = fsl_config_for_role(f, mode);
      if(!db) break/*e.g. global config is not opened*/;
      fsl_stmt * st = NULL;
      char const * const table = fsl_config_table_for_role(mode);
      assert(table);
      fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                            table, __FILE__);
      if(st){
        st->role = fsl__confdb_to_role(mode);
        fsl_stmt_bind_text(st, 1, key, -1, 0);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){







|







|


|







11754
11755
11756
11757
11758
11759
11760
11761
11762
11763
11764
11765
11766
11767
11768
11769
11770
11771
11772
11773
11774
11775
11776
11777
11778
11779
}

double fsl_config_get_double( fsl_cx * const f, fsl_confdb_e mode,
                              double dflt, char const * key ){
  double rv = dflt;
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = strtod(val, NULL);
        fsl_free(val);
      }
      break;
    }
    default: {
      fsl_db * db = fsl_config_for_role(f, mode);
      if(!db) break/*e.g. global config is not opened*/;
      fsl_stmt * st = NULL;
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                            table, __FILE__);
      if(st){
        st->role = fsl__confdb_to_role(mode);
        fsl_stmt_bind_text(st, 1, key, -1, 0);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
13209
13210
13211
13212
13213
13214
13215
13216
13217
13218
13219
13220
13221
13222
13223
13224
13225
13226
13227
13228
13229
13230
13231
13232
13233
13234
13235
13236
13237
13238
13239
13240
13241
13242
13243
13244
13245
13246
13247
13248
13249
13250
13251
13252
13253
13254
13255
13256
13257
13258
13259
13260
13261
13262
13263
13264
13265
13266
13267
13268
13269
13270
13271
13272
13273
13274
13275
13276

13277
13278
13279
13280
13281
13282
13283
13284
13285
13286
13287
13288
13289
13290
13291
13292
13293
  fsl_buffer_reuse(b);
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      if(!fsl_needs_ckout(f)){
        rc = FSL_RC_NOT_A_CKOUT;
        break;
      }
      fsl_buffer * const fname = fsl__cx_scratchpad(f);
      rc = fsl_config_versionable_filename(f, key, fname);
      if(!rc){
        char const * const zFile = fsl_buffer_cstr(fname);
        rc = fsl_stat(zFile, 0, false);
        if(rc){
          rc = fsl_cx_err_set(f, rc, "Could not stat file: %s",
                              zFile);
        }else{
          rc = fsl_buffer_fill_from_filename(b, zFile);
        }
      }
      fsl__cx_scratchpad_yield(f,fname);
      break;
    }
    default: {
      char const * const table = fsl_config_table_for_role(mode);
      assert(table);
      fsl_db * const db = fsl_config_for_role(f, mode);
      if(!db) break;
      fsl_stmt * st = NULL;
      rc = fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                                 table, __FILE__);
      if(rc){
        rc = fsl_cx_uplift_db_error2(f, db, rc);
        break;
      }
      st->role = fsl__confdb_to_role(mode);
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        fsl_size_t len = 0;
        char const * const s = fsl_stmt_g_text(st, 0, &len);
        rc = s ? fsl_buffer_append(b, s, len) : 0;
      }else{
        rc = FSL_RC_NOT_FOUND;
      }
      fsl_stmt_cached_yield(st);
      break;
    }
  }
  return rc;
}

bool fsl_config_get_bool( fsl_cx * const f, fsl_confdb_e mode,
                          bool dflt, char const * key ){
  bool rv = dflt;
  switch((key && *key) ? mode : 999){
    case 999: break;
    case FSL_CONFDB_VERSIONABLE:{
      char * const val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = fsl_str_bool(val);
        fsl_free(val);
      }
      break;
    }
    default:{
      int rc;
      fsl_stmt * st = NULL;
      char const * const table = fsl_config_table_for_role(mode);
      fsl_db * db;

      db = fsl_config_for_role(f, mode);
      if(!db) break;
      assert(table);
      rc = fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                                 table, __FILE__);
      if(!rc){
        st->role = fsl__confdb_to_role(mode);
        fsl_stmt_bind_text(st, 1, key, -1, 0);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
          char const * const col = fsl_stmt_g_text(st, 0, NULL);
          rv = col ? fsl_str_bool(col) : dflt /* 0? */;
        }
        fsl_stmt_cached_yield(st);
      }
      break;
    }
  }







|


|












|














|














|
<

|









|

>









|







11808
11809
11810
11811
11812
11813
11814
11815
11816
11817
11818
11819
11820
11821
11822
11823
11824
11825
11826
11827
11828
11829
11830
11831
11832
11833
11834
11835
11836
11837
11838
11839
11840
11841
11842
11843
11844
11845
11846
11847
11848
11849
11850
11851
11852
11853
11854
11855
11856
11857
11858
11859
11860
11861

11862
11863
11864
11865
11866
11867
11868
11869
11870
11871
11872
11873
11874
11875
11876
11877
11878
11879
11880
11881
11882
11883
11884
11885
11886
11887
11888
11889
11890
11891
11892
  fsl_buffer_reuse(b);
  switch(mode){
    case FSL_CONFDB_VERSIONABLE:{
      if(!fsl_needs_ckout(f)){
        rc = FSL_RC_NOT_A_CKOUT;
        break;
      }
      fsl_buffer * fname = fsl__cx_scratchpad(f);
      rc = fsl_config_versionable_filename(f, key, fname);
      if(!rc){
        char const * zFile = fsl_buffer_cstr(fname);
        rc = fsl_stat(zFile, 0, false);
        if(rc){
          rc = fsl_cx_err_set(f, rc, "Could not stat file: %s",
                              zFile);
        }else{
          rc = fsl_buffer_fill_from_filename(b, zFile);
        }
      }
      fsl__cx_scratchpad_yield(f,fname);
      break;
    }
    default: {
      char const * table = fsl_config_table_for_role(mode);
      assert(table);
      fsl_db * const db = fsl_config_for_role(f, mode);
      if(!db) break;
      fsl_stmt * st = NULL;
      rc = fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                                 table, __FILE__);
      if(rc){
        rc = fsl_cx_uplift_db_error2(f, db, rc);
        break;
      }
      st->role = fsl__confdb_to_role(mode);
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        fsl_size_t len = 0;
        char const * s = fsl_stmt_g_text(st, 0, &len);
        rc = s ? fsl_buffer_append(b, s, len) : 0;
      }else{
        rc = FSL_RC_NOT_FOUND;
      }
      fsl_stmt_cached_yield(st);
      break;
    }
  }
  return rc;
}

bool fsl_config_get_bool( fsl_cx * const f, fsl_confdb_e mode,
                          bool dflt, char const * key ){
  bool rv = dflt;
  switch(mode){

    case FSL_CONFDB_VERSIONABLE:{
      char * val = fsl_config_get_text(f, mode, key, NULL);
      if(val){
        rv = fsl_str_bool(val);
        fsl_free(val);
      }
      break;
    }
    default:{
      int rc;
      fsl_stmt * st = NULL;
      char const * table = fsl_config_table_for_role(mode);
      fsl_db * db;
      if(!f || !key || !*key) break;
      db = fsl_config_for_role(f, mode);
      if(!db) break;
      assert(table);
      rc = fsl_db_prepare_cached(db, &st, SELECT_FROM_CONFIG,
                                 table, __FILE__);
      if(!rc){
        st->role = fsl__confdb_to_role(mode);
        fsl_stmt_bind_text(st, 1, key, -1, 0);
        if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
          char const * col = fsl_stmt_g_text(st, 0, NULL);
          rv = col ? fsl_str_bool(col) : dflt /* 0? */;
        }
        fsl_stmt_cached_yield(st);
      }
      break;
    }
  }
13301
13302
13303
13304
13305
13306
13307
13308
13309
13310
13311
13312
13313
13314
13315
13316
13317
13318
13319
13320
13321
13322
13323
13324
13325
13326

13327
13328
13329
13330
13331
13332
13333
    fsl_stmt_cached_yield() it.
   
    Returns non-0 on error.
*/
static int fsl_config_set_prepare( fsl_cx * const f, fsl_stmt **st,
                                   fsl_confdb_e mode, char const * key ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * const db = fsl_config_for_role(f,mode);
  assert(table);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    const char * sql = FSL_CONFDB_REPO==mode
      ? "REPLACE INTO %!Q(name,value,mtime) VALUES(?,?,now())/*%s()*/"
      : "REPLACE INTO %!Q(name,value) VALUES(?,?)/*%s()*/";
    int rc = fsl_db_prepare_cached(db, st, sql,  table, __func__);
    if(!rc){
      (*st)->role = fsl__confdb_to_role(mode);
      rc = fsl_stmt_bind_text(*st, 1, key, -1, 1);
    }
    if(rc && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }
}


/*
  TODO/FIXME: the fsl_config_set_xxx() routines all use the same basic
  structure, differing only in the concrete bind() op they call. They
  should be consolidated somehow.
*/








|


















>







11900
11901
11902
11903
11904
11905
11906
11907
11908
11909
11910
11911
11912
11913
11914
11915
11916
11917
11918
11919
11920
11921
11922
11923
11924
11925
11926
11927
11928
11929
11930
11931
11932
11933
    fsl_stmt_cached_yield() it.
   
    Returns non-0 on error.
*/
static int fsl_config_set_prepare( fsl_cx * const f, fsl_stmt **st,
                                   fsl_confdb_e mode, char const * key ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  assert(table);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    const char * sql = FSL_CONFDB_REPO==mode
      ? "REPLACE INTO %!Q(name,value,mtime) VALUES(?,?,now())/*%s()*/"
      : "REPLACE INTO %!Q(name,value) VALUES(?,?)/*%s()*/";
    int rc = fsl_db_prepare_cached(db, st, sql,  table, __func__);
    if(!rc){
      (*st)->role = fsl__confdb_to_role(mode);
      rc = fsl_stmt_bind_text(*st, 1, key, -1, 1);
    }
    if(rc && !f->error.code){
      fsl_cx_uplift_db_error(f, db);
    }
    return rc;
  }
}


/*
  TODO/FIXME: the fsl_config_set_xxx() routines all use the same basic
  structure, differing only in the concrete bind() op they call. They
  should be consolidated somehow.
*/

14296
14297
14298
14299
14300
14301
14302
14303
14304
14305
14306
14307
14308
14309
14310
14311
14312
14313
14314
14315
14316
14317
14318
14319
14320
14321
14322
14323
14324
               /sizeof(fsl__mcache_empty.aAge[0]));
  for(unsigned i = 0; i < cacheLen; ++i){
    fsl_deck_finalize(&f->cache.mcache.decks[i]);
  }
  f->cache.mcache = fsl__mcache_empty;
}

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;
  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);
  }
}

static void fsl__cx_finalize_cached_stmt(fsl_cx * const f){
#define STMT(X) fsl_stmt_finalize(&f->cache.stmt.X)
  STMT(deltaSrcId);
  STMT(uuidToRid);
  STMT(uuidToRidGlob);







|







<
<
<
<
<
<
<







12896
12897
12898
12899
12900
12901
12902
12903
12904
12905
12906
12907
12908
12909
12910







12911
12912
12913
12914
12915
12916
12917
               /sizeof(fsl__mcache_empty.aAge[0]));
  for(unsigned i = 0; i < cacheLen; ++i){
    fsl_deck_finalize(&f->cache.mcache.decks[i]);
  }
  f->cache.mcache = fsl__mcache_empty;
}

void fsl__cx_clear_repo_caches(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;







}

static void fsl__cx_finalize_cached_stmt(fsl_cx * const f){
#define STMT(X) fsl_stmt_finalize(&f->cache.stmt.X)
  STMT(deltaSrcId);
  STMT(uuidToRid);
  STMT(uuidToRidGlob);
14359
14360
14361
14362
14363
14364
14365
14366
14367
14368
14369
14370
14371
14372
14373
  fsl__card_J_list_free(&f->ticket.customFields, true);
  fsl_buffer_clear(&f->cache.fileContent);
  fsl_buffer_clear(&f->cache.deltaContent);
  for(int i = 0; i < FSL_CX_NSCRATCH; ++i){
    fsl_buffer_clear(&f->scratchpads.buf[i]);
    f->scratchpads.used[i] = false;
  }
  fsl_cx_caches_reset(f);
  fsl__bccache_clear(&f->cache.blobContent);
  fsl__cx_clear_mf_seen(f, true);
  fsl_id_bag_clear(&f->cache.leafCheck);
  fsl_id_bag_clear(&f->cache.toVerify);
  assert(NULL==f->cache.mfSeen.list);
  if(f->xlinkers.list){
    fsl_free(f->xlinkers.list);







|







12952
12953
12954
12955
12956
12957
12958
12959
12960
12961
12962
12963
12964
12965
12966
  fsl__card_J_list_free(&f->ticket.customFields, true);
  fsl_buffer_clear(&f->cache.fileContent);
  fsl_buffer_clear(&f->cache.deltaContent);
  for(int i = 0; i < FSL_CX_NSCRATCH; ++i){
    fsl_buffer_clear(&f->scratchpads.buf[i]);
    f->scratchpads.used[i] = false;
  }
  fsl__cx_clear_repo_caches(f);
  fsl__bccache_clear(&f->cache.blobContent);
  fsl__cx_clear_mf_seen(f, true);
  fsl_id_bag_clear(&f->cache.leafCheck);
  fsl_id_bag_clear(&f->cache.toVerify);
  assert(NULL==f->cache.mfSeen.list);
  if(f->xlinkers.list){
    fsl_free(f->xlinkers.list);
14427
14428
14429
14430
14431
14432
14433

14434
14435
14436
14437
14438
14439
14440
14441
14442
14443
14444

14445

14446
14447
14448
14449


14450
14451
14452
14453
14454
14455

14456
14457
14458
14459
14460
14461
14462
  fsl_error_reset(&f->error);
  fsl_db_err_reset(&f->repo.db);
  fsl_db_err_reset(&f->config.db);
  fsl_db_err_reset(&f->ckout.db);
}

int fsl_cx_err_set_e( fsl_cx * const f, fsl_error * const err ){

  if(!err){
    return fsl_cx_err_set(f, 0, NULL);
  }else{
    fsl_error_move(err, &f->error);
    fsl_error_clear(err);
    return f->error.code;
  }
}

int fsl_cx_err_setv( fsl_cx * const f, int code, char const * fmt,
                     va_list args ){

  return fsl_error_setv( &f->error, code, fmt, args );

}

int fsl_cx_err_set( fsl_cx * const f, int code, char const * fmt,
                    ... ){


  int rc;
  va_list args;
  va_start(args,fmt);
  rc = fsl_error_setv( &f->error, code, fmt, args );
  va_end(args);
  return rc;

}

int fsl_cx_err_get( fsl_cx * const f, char const ** str, fsl_size_t * len ){
#if 1
  return fsl_error_get( &f->error, str, len );
#else
  /* For the docs: 







>
|










>
|
>




>
>
|
|
|
|
|
|
>







13020
13021
13022
13023
13024
13025
13026
13027
13028
13029
13030
13031
13032
13033
13034
13035
13036
13037
13038
13039
13040
13041
13042
13043
13044
13045
13046
13047
13048
13049
13050
13051
13052
13053
13054
13055
13056
13057
13058
13059
13060
13061
  fsl_error_reset(&f->error);
  fsl_db_err_reset(&f->repo.db);
  fsl_db_err_reset(&f->config.db);
  fsl_db_err_reset(&f->ckout.db);
}

int fsl_cx_err_set_e( fsl_cx * const f, fsl_error * const err ){
  if(!f) return FSL_RC_MISUSE;
  else if(!err){
    return fsl_cx_err_set(f, 0, NULL);
  }else{
    fsl_error_move(err, &f->error);
    fsl_error_clear(err);
    return f->error.code;
  }
}

int fsl_cx_err_setv( fsl_cx * const f, int code, char const * fmt,
                     va_list args ){
  return f
    ? fsl_error_setv( &f->error, code, fmt, args )
    : FSL_RC_MISUSE;
}

int fsl_cx_err_set( fsl_cx * const f, int code, char const * fmt,
                    ... ){
  if(!f) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_error_setv( &f->error, code, fmt, args );
    va_end(args);
    return rc;
  }
}

int fsl_cx_err_get( fsl_cx * const f, char const ** str, fsl_size_t * len ){
#if 1
  return fsl_error_get( &f->error, str, len );
#else
  /* For the docs: 
14609
14610
14611
14612
14613
14614
14615


14616
14617
14618
14619
14620
14621
14622
14623
14624
                          "Cannot close/detach unattached role: %s",
                          fsl_db_role_name(r));
  }else{
    fsl_db * const db = fsl__cx_db_for_role(f,r);
    int rc = 0;
    switch(r){
      case FSL_DBROLE_REPO:


      case FSL_DBROLE_CKOUT:
        fsl_cx_caches_reset(f);
        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







>
>

|







13208
13209
13210
13211
13212
13213
13214
13215
13216
13217
13218
13219
13220
13221
13222
13223
13224
13225
                          "Cannot close/detach unattached role: %s",
                          fsl_db_role_name(r));
  }else{
    fsl_db * const db = fsl__cx_db_for_role(f,r);
    int rc = 0;
    switch(r){
      case FSL_DBROLE_REPO:
        fsl__cx_clear_repo_caches(f);
        break;
      case FSL_DBROLE_CKOUT:
        fsl__cx_ckout_clear(f);
        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
14914
14915
14916
14917
14918
14919
14920
14921
14922
14923
14924

14925
14926
14927
14928
14929
14930
14931
14932
14933
14934
14935
14936
14937
14938
14939
14940
14941
14942
14943
14944
14945
14946
14947
14948
14949
14950
14951

14952
14953
14954
14955
14956
14957
14958
14959
14960
14961
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  if(isAttached) fsl_db_detach(db, zPrefix);
  fsl_db_close(db);
  return rc;
}

int fsl_config_global_preferred_name(char ** zOut){
  char * zEnv = 0 /* from fsl_getenv(). Note the special-case free()
                     semantics!!! */;
  char * zRc = 0 /* `*zOut` result, from fsl_mprintf() */;
  int rc = 0;


#if FSL_PLATFORM_IS_WINDOWS
  zEnv = fsl_getenv("FOSSIL_HOME");
  if( zEnv==0 ){
    zEnv = fsl_getenv("LOCALAPPDATA");
    if( zEnv==0 ){
      zEnv = fsl_getenv("APPDATA");
      if( zEnv==0 ){
        zEnv = fsl_getenv("USERPROFILE");
        if( zEnv==0 ){
          char * const zDrive = fsl_getenv("HOMEDRIVE");
          char * const zPath = fsl_getenv("HOMEPATH");
          if( zDrive && zPath ){
            zRc = fsl_mprintf("%s%//_fossil", zDrive, zPath);
          }
          if(zDrive) fsl_filename_free(zDrive);
          if(zPath) fsl_filename_free(zPath);
        }
      }
    }
  }
  if(!zRc){
    if(!zEnv) rc = FSL_RC_NOT_FOUND;
    else{
      zRc = fsl_mprintf("%//_fossil", zEnv);
      if(!zRc) rc = FSL_RC_OOM;
    }

  }
#else
  fsl_buffer buf = fsl_buffer_empty;
  /* Option 1: $FOSSIL_HOME/.fossil */
  zEnv = fsl_getenv("FOSSIL_HOME");
  if(zEnv){
    zRc = fsl_mprintf("%s/.fossil", zEnv);
    if(!zRc) rc = FSL_RC_OOM;
    goto end;
  }







|
<
|

>


<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
|
<
<
|
>
|
<
<







13515
13516
13517
13518
13519
13520
13521
13522

13523
13524
13525
13526
13527











13528









13529


13530
13531
13532


13533
13534
13535
13536
13537
13538
13539
  rc = fsl_cx_uplift_db_error2(f, db, rc);
  if(isAttached) fsl_db_detach(db, zPrefix);
  fsl_db_close(db);
  return rc;
}

int fsl_config_global_preferred_name(char ** zOut){
  char * zEnv = 0;

  char * zRc = 0;
  int rc = 0;
  fsl_buffer buf = fsl_buffer_empty;

#if FSL_PLATFORM_IS_WINDOWS











#  error "TODO: port in fossil(1) db.c:db_configdb_name() Windows bits"









#else


  
#endif



  /* Option 1: $FOSSIL_HOME/.fossil */
  zEnv = fsl_getenv("FOSSIL_HOME");
  if(zEnv){
    zRc = fsl_mprintf("%s/.fossil", zEnv);
    if(!zRc) rc = FSL_RC_OOM;
    goto end;
  }
14988
14989
14990
14991
14992
14993
14994

14995
14996
14997
14998
14999

15000
15001
15002

15003
15004
15005
15006
15007
15008
15009
    goto end;
  }
  /* Option 5: fall back to $HOME/.fossil */
  buf.used -= 8 /* "/.config" */;
  buf.mem[buf.used] = 0;
  rc = fsl_buffer_append(&buf, "/.fossil", 8);
  if(!rc) zRc = fsl_buffer_take(&buf);

  end:
  fsl_buffer_clear(&buf);
#endif
  if(zEnv) fsl_filename_free(zEnv);
  if(!rc){

    if(zRc) *zOut = zRc;
    else rc = FSL_RC_OOM;
  }

  return rc;
}

int fsl_config_open( fsl_cx * const f, char const * openDbName ){
  int rc = 0;
  const char * zDbName = 0;
  char * zPrefName = 0;







>

<
<


>
|
<

>







13566
13567
13568
13569
13570
13571
13572
13573
13574


13575
13576
13577
13578

13579
13580
13581
13582
13583
13584
13585
13586
13587
    goto end;
  }
  /* Option 5: fall back to $HOME/.fossil */
  buf.used -= 8 /* "/.config" */;
  buf.mem[buf.used] = 0;
  rc = fsl_buffer_append(&buf, "/.fossil", 8);
  if(!rc) zRc = fsl_buffer_take(&buf);

  end:


  if(zEnv) fsl_filename_free(zEnv);
  if(!rc){
    assert(zRc);
    *zOut = zRc;

  }
  fsl_buffer_clear(&buf);
  return rc;
}

int fsl_config_open( fsl_cx * const f, char const * openDbName ){
  int rc = 0;
  const char * zDbName = 0;
  char * zPrefName = 0;
15289
15290
15291
15292
15293
15294
15295

15296
15297
15298


15299
15300
15301
15302
15303
15304
15305
}

int fsl__ckout_version_fetch( fsl_cx * const f ){
  fsl_id_t rid = 0;
  int rc = 0;
  fsl_db * dbC = fsl_cx_db_ckout(f);
  fsl_db * dbR = dbC ? fsl_needs_repo(f) : NULL;

  fsl__cx_ckout_clear(f);
  if(!dbC) return 0;
  else if(!dbR) return FSL_RC_NOT_A_REPO;


  rid = fsl_config_get_id(f, FSL_CONFDB_CKOUT, -1, "checkout");
  //MARKER(("rc=%s rid=%d\n",fsl_rc_cstr(f->error.code), (int)rid));
  if(rid>0){
    f->ckout.uuid = fsl_rid_to_uuid(f, rid);
    if(!f->ckout.uuid){
      assert(f->error.code);
      if(!f->error.code){







>

|
|
>
>







13867
13868
13869
13870
13871
13872
13873
13874
13875
13876
13877
13878
13879
13880
13881
13882
13883
13884
13885
13886
}

int fsl__ckout_version_fetch( fsl_cx * const f ){
  fsl_id_t rid = 0;
  int rc = 0;
  fsl_db * dbC = fsl_cx_db_ckout(f);
  fsl_db * dbR = dbC ? fsl_needs_repo(f) : NULL;
  assert(!dbC || (dbC && dbR));
  fsl__cx_ckout_clear(f);
  if(!dbC){
    return 0;
  }
  fsl_cx_err_reset(f);
  rid = fsl_config_get_id(f, FSL_CONFDB_CKOUT, -1, "checkout");
  //MARKER(("rc=%s rid=%d\n",fsl_rc_cstr(f->error.code), (int)rid));
  if(rid>0){
    f->ckout.uuid = fsl_rid_to_uuid(f, rid);
    if(!f->ckout.uuid){
      assert(f->error.code);
      if(!f->error.code){
16377
16378
16379
16380
16381
16382
16383
16384
16385
16386
16387
16388
16389
16390
16391
    case SQLITE_TOOBIG:
    case SQLITE_FULL:
    case SQLITE_NOLFS:
    case SQLITE_RANGE: rc = FSL_RC_RANGE; break;
    case SQLITE_NOTFOUND: rc = FSL_RC_NOT_FOUND; break;
    case SQLITE_PERM:
    case SQLITE_AUTH:
    case SQLITE_LOCKED: rc = FSL_RC_LOCKED; break;
    case SQLITE_READONLY: rc = FSL_RC_ACCESS; break;
    case SQLITE_CORRUPT: rc = FSL_RC_CONSISTENCY; break;
    case SQLITE_CANTOPEN:
    case SQLITE_IOERR:
      rc = FSL_RC_IO; break;
    default:
      //MARKER(("sqlite3_errcode()=0x%04x\n", rc));







|







14958
14959
14960
14961
14962
14963
14964
14965
14966
14967
14968
14969
14970
14971
14972
    case SQLITE_TOOBIG:
    case SQLITE_FULL:
    case SQLITE_NOLFS:
    case SQLITE_RANGE: rc = FSL_RC_RANGE; break;
    case SQLITE_NOTFOUND: rc = FSL_RC_NOT_FOUND; break;
    case SQLITE_PERM:
    case SQLITE_AUTH:
    case SQLITE_LOCKED:
    case SQLITE_READONLY: rc = FSL_RC_ACCESS; break;
    case SQLITE_CORRUPT: rc = FSL_RC_CONSISTENCY; break;
    case SQLITE_CANTOPEN:
    case SQLITE_IOERR:
      rc = FSL_RC_IO; break;
    default:
      //MARKER(("sqlite3_errcode()=0x%04x\n", rc));
17108
17109
17110
17111
17112
17113
17114
17115
17116
17117
17118
17119
17120
17121
17122
17123
        break;
      case FSL_RC_STEP_ROW:
        /* Don't reset() for ROW b/c that clears the column
           data! */
        break;
      default:
        rc = fsl_error_set(&st->db->error, rc,
                           "Error stepping statement: %s",
                           sqlite3_errmsg(st->db->dbh));
        break;
    }
  }
  return rc;
}

int fsl_stmt_bind_step( fsl_stmt * st, char const * fmt, ... ){







|
<







15689
15690
15691
15692
15693
15694
15695
15696

15697
15698
15699
15700
15701
15702
15703
        break;
      case FSL_RC_STEP_ROW:
        /* Don't reset() for ROW b/c that clears the column
           data! */
        break;
      default:
        rc = fsl_error_set(&st->db->error, rc,
                           "Error stepping statement.");

        break;
    }
  }
  return rc;
}

int fsl_stmt_bind_step( fsl_stmt * st, char const * fmt, ... ){
17728
17729
17730
17731
17732
17733
17734


17735






17736
17737
17738
17739
17740
17741
17742

17743










17744









17745

17746
17747
17748
17749
17750
17751
17752
17753
       IDs which were injected as part of the being-rolled-back
       transaction. The only(?) reasonably sane way to deal with that
       is to flush all relevant caches. It is unfortunate that this
       bit is in the db class, as opposed to the fsl_cx class, but we
       currently have no hook which would allow us to trigger this
       from that class.
    */


    fsl_cx_caches_reset(db->f);






  }
  fsl_db_cleanup_beforeCommit(db);
  fsl_db_reset_change_count(db);
  rc = fsl_db_exec(db, db->doRollback ? "ROLLBACK" : "COMMIT");
  if(db->doRollback && db->f && changeCount>0 && db->f->ckout.rid>0){
    int const rc2 = fsl__ckout_version_fetch(db->f)
      /*Else it might be out of sync, leading to chaos.*/;

    if(0==rc && rc2!=0) rc = rc2;










  }









  db->doRollback = 0;

  return rc;
}

int fsl_db_get_int32v( fsl_db * const db, int32_t * rv,
                       char const * sql, va_list args){
  /* Potential fixme: the fsl_db_get_XXX() funcs are 95%
     code duplicates. We "could" replace these with a macro
     or supermacro, though the latter would be problematic







>
>
|
>
>
>
>
>
>




|
|
|
>
|
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
>
|







16308
16309
16310
16311
16312
16313
16314
16315
16316
16317
16318
16319
16320
16321
16322
16323
16324
16325
16326
16327
16328
16329
16330
16331
16332
16333
16334
16335
16336
16337
16338
16339
16340
16341
16342
16343
16344
16345
16346
16347
16348
16349
16350
16351
16352
16353
16354
16355
16356
16357
16358
16359
16360
16361
16362
       IDs which were injected as part of the being-rolled-back
       transaction. The only(?) reasonably sane way to deal with that
       is to flush all relevant caches. It is unfortunate that this
       bit is in the db class, as opposed to the fsl_cx class, but we
       currently have no hook which would allow us to trigger this
       from that class.
    */
#if 1
    fsl__bccache_reset(&db->f->cache.blobContent);
    fsl__cx_mcache_clear(db->f);
#else
    /* This one resets all of ^^^ plus certain repo-side config
       settings, but it's not yet clear whether that they will be
       reloaded when needed. */
    fsl__cx_clear_repo_caches(d->f);
#endif
  }
  fsl_db_cleanup_beforeCommit(db);
  fsl_db_reset_change_count(db);
  rc = fsl_db_exec(db, db->doRollback ? "ROLLBACK" : "COMMIT");
  db->doRollback = 0;
  return rc;
#if 0
  /* original impl, for reference purposes during testing */
  if( g.db==0 ) return;
  if( db.nBegin<=0 ) return;
  if( rollbackFlag ) db.doRollback = 1;
  db.nBegin--;
  if( db.nBegin==0 ){
    int i;
    if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
      while( db.nBeforeCommit ){
        db.nBeforeCommit--;
        sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0);
        sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]);
      }
      leaf_do_pending_checks();
    }
    for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
      db.doRollback |= db.aHook[i].xHook();
    }
    while( db.pAllStmt ){
      db_finalize(db.pAllStmt);
    }
    db_multi_exec(db.doRollback ? "ROLLBACK" : "COMMIT");
    db.doRollback = 0;
  }
#endif  
}

int fsl_db_get_int32v( fsl_db * const db, int32_t * rv,
                       char const * sql, va_list args){
  /* Potential fixme: the fsl_db_get_XXX() funcs are 95%
     code duplicates. We "could" replace these with a macro
     or supermacro, though the latter would be problematic
18042
18043
18044
18045
18046
18047
18048
18049
18050
18051
18052
18053
18054
18055
18056
18057
18058
18059
18060
18061
18062
18063
18064
18065

18066
18067
18068
18069
18070
18071
18072
  va_end(args);
  return rc;
}

int fsl_db_get_bufferv( fsl_db * const db, fsl_buffer * const b,
                        bool asBlob, char const * sql,
                        va_list args){
  if(!sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:{
        void const * str = asBlob
          ? sqlite3_column_blob(st.stmt, 0)
          : (void const *)sqlite3_column_text(st.stmt, 0);
        int const len = sqlite3_column_bytes(st.stmt,0);
        if(len && !str){
          rc = FSL_RC_OOM;
        }else{
          rc = 0;

          rc = fsl_buffer_append( b, str, len );
        }
        break;
      }
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;







|
















>







16651
16652
16653
16654
16655
16656
16657
16658
16659
16660
16661
16662
16663
16664
16665
16666
16667
16668
16669
16670
16671
16672
16673
16674
16675
16676
16677
16678
16679
16680
16681
16682
  va_end(args);
  return rc;
}

int fsl_db_get_bufferv( fsl_db * const db, fsl_buffer * const b,
                        bool asBlob, char const * sql,
                        va_list args){
  if(!db || !db->dbh || !b || !sql || !*sql) return FSL_RC_MISUSE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = 0;
    rc = fsl_db_preparev( db, &st, sql, args );
    if(rc) return rc;
    rc = fsl_stmt_step( &st );
    switch(rc){
      case FSL_RC_STEP_ROW:{
        void const * str = asBlob
          ? sqlite3_column_blob(st.stmt, 0)
          : (void const *)sqlite3_column_text(st.stmt, 0);
        int const len = sqlite3_column_bytes(st.stmt,0);
        if(len && !str){
          rc = FSL_RC_OOM;
        }else{
          rc = 0;
          b->used = 0;
          rc = fsl_buffer_append( b, str, len );
        }
        break;
      }
      case FSL_RC_STEP_DONE:
        rc = 0;
        break;
18187
18188
18189
18190
18191
18192
18193
18194
18195
18196
18197
18198
18199
18200
18201

bool fsl_db_existsv(fsl_db * const db, char const * sql, va_list args ){
  if(!db || !db->dbh || !sql) return 0;
  else if(!*sql) return 0;
  else{
    fsl_stmt st = fsl_stmt_empty;
    bool rv = false;
    if(0==fsl_db_preparev(db, &st, sql, args)){
      rv = FSL_RC_STEP_ROW==fsl_stmt_step(&st) ? true : false;
    }
    fsl_stmt_finalize(&st);
    return rv;
  }

}







|







16797
16798
16799
16800
16801
16802
16803
16804
16805
16806
16807
16808
16809
16810
16811

bool fsl_db_existsv(fsl_db * const db, char const * sql, va_list args ){
  if(!db || !db->dbh || !sql) return 0;
  else if(!*sql) return 0;
  else{
    fsl_stmt st = fsl_stmt_empty;
    bool rv = false;
    if(!fsl_db_preparev(db, &st, sql, args)){
      rv = FSL_RC_STEP_ROW==fsl_stmt_step(&st) ? true : false;
    }
    fsl_stmt_finalize(&st);
    return rv;
  }

}
18715
18716
18717
18718
18719
18720
18721
18722
18723
18724
18725
18726
18727
18728
18729
18730
18731
18732
18733
18734
18735
18736
18737
18738
18739
  }
}

/**
    fsl_list_visitor_f() impl which requires that obj be-a (fsl_card_T*),
    which this function passes to fsl_card_T_free().
*/
static int fsl_list_v_card_T_free(void * obj, void * visitorState __unused){
  if(obj) fsl_card_T_free( (fsl_card_T*)obj );
  return 0;
}

static int fsl_list_v_card_Q_free(void * obj, void * visitorState __unused ){
  if(obj) fsl_card_Q_free( (fsl_card_Q*)obj );
  return 0;
}

static int fsl_list_v_card_J_free(void * obj, void * visitorState __unused){
  if(obj) fsl_card_J_free( (fsl_card_J*)obj );
  return 0;
}

fsl_deck * fsl_deck_malloc(){
  fsl_deck * rc = (fsl_deck *)fsl_malloc(sizeof(fsl_deck));
  if(rc){







|




|




|







17325
17326
17327
17328
17329
17330
17331
17332
17333
17334
17335
17336
17337
17338
17339
17340
17341
17342
17343
17344
17345
17346
17347
17348
17349
  }
}

/**
    fsl_list_visitor_f() impl which requires that obj be-a (fsl_card_T*),
    which this function passes to fsl_card_T_free().
*/
static int fsl_list_v_card_T_free(void * obj, void * visitorState ){
  if(obj) fsl_card_T_free( (fsl_card_T*)obj );
  return 0;
}

static int fsl_list_v_card_Q_free(void * obj, void * visitorState ){
  if(obj) fsl_card_Q_free( (fsl_card_Q*)obj );
  return 0;
}

static int fsl_list_v_card_J_free(void * obj, void * visitorState ){
  if(obj) fsl_card_J_free( (fsl_card_J*)obj );
  return 0;
}

fsl_deck * fsl_deck_malloc(){
  fsl_deck * rc = (fsl_deck *)fsl_malloc(sizeof(fsl_deck));
  if(rc){
21087
21088
21089
21090
21091
21092
21093
21094
21095
21096
21097
21098
21099
21100
21101
21102
21103
21104
21105
21106
21107
21108
21109
21110
21111
21112
21113
21114
21115
21116
21117
21118
21119
21120
21121
21122
  fsl_int_t i;
  assert(d);
  assert(zName && *zName);
  if(!d->F.used) return NULL;
  else if(FSL_CARD_F_LIST_NEEDS_SORT & d->F.flags){
    fsl_card_F_list_sort(&d->F);
  }
  bool const caseSensitive = fsl_cx_is_case_sensitive(d->f, false);
#define FCARD(NDX) F_at(&d->F, (NDX))
  lwr = 0;
  upr = d->F.used-1;
  if( d->F.cursor>=lwr && d->F.cursor<upr ){
    c = (d->f && caseSensitive)
      ? fsl_strcmp(FCARD(d->F.cursor+1)->name, zName)
      : fsl_stricmp(FCARD(d->F.cursor+1)->name, zName);
    if( c==0 ){
      if(atNdx) *atNdx = (uint32_t)d->F.cursor+1;
      return FCARD(++d->F.cursor);
    }else if( c>0 ){
      upr = d->F.cursor;
    }else{
      lwr = d->F.cursor+1;
    }
  }
  while( lwr<=upr ){
    i = (lwr+upr)/2;
    c = (d->f && caseSensitive)
      ? fsl_strcmp(FCARD(i)->name, zName)
      : fsl_stricmp(FCARD(i)->name, zName);
    if( c<0 ){
      lwr = i+1;
    }else if( c>0 ){
      upr = i-1;
    }else{
      d->F.cursor = i;
      if(atNdx) *atNdx = (uint32_t)i;







<




|
|
|











|
|
|







19697
19698
19699
19700
19701
19702
19703

19704
19705
19706
19707
19708
19709
19710
19711
19712
19713
19714
19715
19716
19717
19718
19719
19720
19721
19722
19723
19724
19725
19726
19727
19728
19729
19730
19731
  fsl_int_t i;
  assert(d);
  assert(zName && *zName);
  if(!d->F.used) return NULL;
  else if(FSL_CARD_F_LIST_NEEDS_SORT & d->F.flags){
    fsl_card_F_list_sort(&d->F);
  }

#define FCARD(NDX) F_at(&d->F, (NDX))
  lwr = 0;
  upr = d->F.used-1;
  if( d->F.cursor>=lwr && d->F.cursor<upr ){
    c = (d->f && d->f->cache.caseInsensitive)
      ? fsl_stricmp(FCARD(d->F.cursor+1)->name, zName)
      : fsl_strcmp(FCARD(d->F.cursor+1)->name, zName);
    if( c==0 ){
      if(atNdx) *atNdx = (uint32_t)d->F.cursor+1;
      return FCARD(++d->F.cursor);
    }else if( c>0 ){
      upr = d->F.cursor;
    }else{
      lwr = d->F.cursor+1;
    }
  }
  while( lwr<=upr ){
    i = (lwr+upr)/2;
    c = (d->f && d->f->cache.caseInsensitive)
      ? fsl_stricmp(FCARD(i)->name, zName)
      : fsl_strcmp(FCARD(i)->name, zName);
    if( c<0 ){
      lwr = i+1;
    }else if( c>0 ){
      upr = i-1;
    }else{
      d->F.cursor = i;
      if(atNdx) *atNdx = (uint32_t)i;
22000
22001
22002
22003
22004
22005
22006
22007
22008
22009
22010
22011
22012
22013
22014
  return rc;
}

/**
   Overrideable crosslink listener which updates the timeline for
   attachment records.
*/
static int fsl_deck_xlink_f_attachment(fsl_deck * const d, void * state __unused){
  if(FSL_SATYPE_ATTACHMENT!=d->type) return 0;
  int rc;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  fsl_buffer * const comment = fsl__cx_scratchpad(d->f);
  const bool isAdd = (d->A.src && *d->A.src) ? 1 : 0;
  char attachToType = 'w'
    /* Assume wiki until we know otherwise, keeping in mind that the







|







20609
20610
20611
20612
20613
20614
20615
20616
20617
20618
20619
20620
20621
20622
20623
  return rc;
}

/**
   Overrideable crosslink listener which updates the timeline for
   attachment records.
*/
static int fsl_deck_xlink_f_attachment(fsl_deck * const d, void * state){
  if(FSL_SATYPE_ATTACHMENT!=d->type) return 0;
  int rc;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  fsl_buffer * const comment = fsl__cx_scratchpad(d->f);
  const bool isAdd = (d->A.src && *d->A.src) ? 1 : 0;
  char attachToType = 'w'
    /* Assume wiki until we know otherwise, keeping in mind that the
22078
22079
22080
22081
22082
22083
22084
22085
22086
22087
22088
22089
22090
22091
22092
  return rc;
}

/**
   Overrideable crosslink listener which updates the timeline for
   checkin records.
*/
static int fsl_deck_xlink_f_checkin(fsl_deck * const d, void * state __unused){
  if(FSL_SATYPE_CHECKIN!=d->type) return 0;
  int rc;
  fsl_db * db;
  db = fsl_cx_db_repo(d->f);
  assert(db);
  rc = fsl_db_exec(db,
       "REPLACE INTO event(type,mtime,objid,user,comment,"







|







20687
20688
20689
20690
20691
20692
20693
20694
20695
20696
20697
20698
20699
20700
20701
  return rc;
}

/**
   Overrideable crosslink listener which updates the timeline for
   checkin records.
*/
static int fsl_deck_xlink_f_checkin(fsl_deck * const d, void * state){
  if(FSL_SATYPE_CHECKIN!=d->type) return 0;
  int rc;
  fsl_db * db;
  db = fsl_cx_db_repo(d->f);
  assert(db);
  rc = fsl_db_exec(db,
       "REPLACE INTO event(type,mtime,objid,user,comment,"
22126
22127
22128
22129
22130
22131
22132
22133
22134
22135
22136
22137
22138
22139
22140
       (int)FSL_TAGID_BGCOLOR, d->rid,
       (int)FSL_TAGID_USER, d->rid,
       (int)FSL_TAGID_COMMENT, d->rid, d->D
  );
  return fsl_cx_uplift_db_error2(d->f, db, rc);
}

static int fsl_deck_xlink_f_control(fsl_deck * const d, void * state __unused){
  if(FSL_SATYPE_CONTROL!=d->type) return 0;
  /*
    Create timeline event entry for all tags in this control
    construct. Note that we are using a lot of historical code which
    hard-codes english-lanuage text and links which only work in
    fossil(1). i would prefer to farm this out to a crosslink
    callback, and provide a default implementation which more or







|







20735
20736
20737
20738
20739
20740
20741
20742
20743
20744
20745
20746
20747
20748
20749
       (int)FSL_TAGID_BGCOLOR, d->rid,
       (int)FSL_TAGID_USER, d->rid,
       (int)FSL_TAGID_COMMENT, d->rid, d->D
  );
  return fsl_cx_uplift_db_error2(d->f, db, rc);
}

static int fsl_deck_xlink_f_control(fsl_deck * const d, void * state){
  if(FSL_SATYPE_CONTROL!=d->type) return 0;
  /*
    Create timeline event entry for all tags in this control
    construct. Note that we are using a lot of historical code which
    hard-codes english-lanuage text and links which only work in
    fossil(1). i would prefer to farm this out to a crosslink
    callback, and provide a default implementation which more or
22273
22274
22275
22276
22277
22278
22279
22280
22281
22282
22283
22284
22285
22286
22287

  end:
  fsl__cx_scratchpad_yield(d->f, comment);
  return rc;

}

static int fsl_deck_xlink_f_forum(fsl_deck * const d, void * state __unused){
  if(FSL_SATYPE_FORUMPOST!=d->type) return 0;
  int rc = 0;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  assert(db);
  fsl_cx * const f = d->f;
  fsl_id_t const froot = d->G ? fsl_uuid_to_rid(f, d->G) : d->rid;
  fsl_id_t const fprev = d->P.used ? fsl_uuid_to_rid(f, (char const *)d->P.list[0]): 0;







|







20882
20883
20884
20885
20886
20887
20888
20889
20890
20891
20892
20893
20894
20895
20896

  end:
  fsl__cx_scratchpad_yield(d->f, comment);
  return rc;

}

static int fsl_deck_xlink_f_forum(fsl_deck * const d, void * state){
  if(FSL_SATYPE_FORUMPOST!=d->type) return 0;
  int rc = 0;
  fsl_db * const db = fsl_cx_db_repo(d->f);
  assert(db);
  fsl_cx * const f = d->f;
  fsl_id_t const froot = d->G ? fsl_uuid_to_rid(f, d->G) : d->rid;
  fsl_id_t const fprev = d->P.used ? fsl_uuid_to_rid(f, (char const *)d->P.list[0]): 0;
22359
22360
22361
22362
22363
22364
22365
22366
22367
22368
22369
22370
22371
22372
22373
  dberr:
  assert(rc);
  assert(db->error.code);
  return fsl_cx_uplift_db_error(f, db);
}


static int fsl_deck_xlink_f_technote(fsl_deck * const d, void * state __unused){
  if(FSL_SATYPE_TECHNOTE!=d->type) return 0;
  char buf[FSL_STRLEN_K256 + 7 /* event-UUID\0 */] = {0};
  fsl_id_t tagid;
  char const * zTag;
  int rc = 0;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(d->f);







|







20968
20969
20970
20971
20972
20973
20974
20975
20976
20977
20978
20979
20980
20981
20982
  dberr:
  assert(rc);
  assert(db->error.code);
  return fsl_cx_uplift_db_error(f, db);
}


static int fsl_deck_xlink_f_technote(fsl_deck * const d, void * state){
  if(FSL_SATYPE_TECHNOTE!=d->type) return 0;
  char buf[FSL_STRLEN_K256 + 7 /* event-UUID\0 */] = {0};
  fsl_id_t tagid;
  char const * zTag;
  int rc = 0;
  fsl_cx * const f = d->f;
  fsl_db * const db = fsl_cx_db_repo(d->f);
22408
22409
22410
22411
22412
22413
22414
22415
22416
22417
22418
22419
22420
22421
22422
                     d->E.julian, d->rid, tagid,
                     d->U, d->C, 
                     (int)FSL_TAGID_BGCOLOR, d->rid);
  }
  return rc;
}

static int fsl_deck_xlink_f_wiki(fsl_deck * const d, void * state __unused){
  if(FSL_SATYPE_WIKI!=d->type) return 0;
  int rc;
  char const * zWiki;
  fsl_size_t nWiki = 0;
  char cPrefix = 0;
  char * zTag = fsl_mprintf("wiki-%s", d->L);
  if(!zTag) return FSL_RC_OOM;







|







21017
21018
21019
21020
21021
21022
21023
21024
21025
21026
21027
21028
21029
21030
21031
                     d->E.julian, d->rid, tagid,
                     d->U, d->C, 
                     (int)FSL_TAGID_BGCOLOR, d->rid);
  }
  return rc;
}

static int fsl_deck_xlink_f_wiki(fsl_deck * const d, void * state){
  if(FSL_SATYPE_WIKI!=d->type) return 0;
  int rc;
  char const * zWiki;
  fsl_size_t nWiki = 0;
  char cPrefix = 0;
  char * zTag = fsl_mprintf("wiki-%s", d->L);
  if(!zTag) return FSL_RC_OOM;
24807
24808
24809
24810
24811
24812
24813
24814
24815
24816
24817
24818
24819
24820
24821
24822
24823
24824
24825
    sum3 += z[3];
    z += 4;
    N -= 4;
  }
  sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
  switch(N){
    case 3:   sum3 += (z[2] << 8);
      __attribute__ ((fallthrough));
    case 2:   sum3 += (z[1] << 16);
      __attribute__ ((fallthrough));
    case 1:   sum3 += (z[0] << 24);
      __attribute__ ((fallthrough));
    default:  ;
  }
  return sum3;
}

int fsl_delta_create2( unsigned char const *zSrc, fsl_size_t lenSrc,
                       unsigned char const *zOut, fsl_size_t lenOut,







<

<

<







23416
23417
23418
23419
23420
23421
23422

23423

23424

23425
23426
23427
23428
23429
23430
23431
    sum3 += z[3];
    z += 4;
    N -= 4;
  }
  sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
  switch(N){
    case 3:   sum3 += (z[2] << 8);

    case 2:   sum3 += (z[1] << 16);

    case 1:   sum3 += (z[0] << 24);

    default:  ;
  }
  return sum3;
}

int fsl_delta_create2( unsigned char const *zSrc, fsl_size_t lenSrc,
                       unsigned char const *zOut, fsl_size_t lenOut,
25040
25041
25042
25043
25044
25045
25046

25047
25048
25049
25050
25051
25052
25053
25054
25055
25056
25057
25058
25059
25060
}

int fsl_delta_create( unsigned char const *zSrc, fsl_size_t lenSrc,
                      unsigned char const *zOut, fsl_size_t lenOut,
                      unsigned char *zDelta, fsl_size_t * deltaSize){
  int rc;
  DeltaOutputString os;

  os.mem = (unsigned char *)zDelta;
  os.cursor = 0;
  rc = fsl_delta_create2( zSrc, lenSrc, zOut, lenOut,
                          fsl_output_f_ostring, &os );
  if(!rc){
    os.mem[os.cursor] = 0;
    *deltaSize = os.cursor;
  }
  return rc;
}

/*
   Calculates the size (in bytes) of the output from applying a
   delta. On success 0 is returned and *deltaSize will be updated with







>






|







23646
23647
23648
23649
23650
23651
23652
23653
23654
23655
23656
23657
23658
23659
23660
23661
23662
23663
23664
23665
23666
23667
}

int fsl_delta_create( unsigned char const *zSrc, fsl_size_t lenSrc,
                      unsigned char const *zOut, fsl_size_t lenOut,
                      unsigned char *zDelta, fsl_size_t * deltaSize){
  int rc;
  DeltaOutputString os;
  if(!zSrc || !zOut || !zDelta || !deltaSize) return FSL_RC_MISUSE;
  os.mem = (unsigned char *)zDelta;
  os.cursor = 0;
  rc = fsl_delta_create2( zSrc, lenSrc, zOut, lenOut,
                          fsl_output_f_ostring, &os );
  if(!rc){
    os.mem[os.cursor] = 0;
    if(deltaSize) *deltaSize = os.cursor;
  }
  return rc;
}

/*
   Calculates the size (in bytes) of the output from applying a
   delta. On success 0 is returned and *deltaSize will be updated with
25401
25402
25403
25404
25405
25406
25407
25408
25409
25410
25411
25412
25413
25414
25415
25416
25417
25418
25419
                                  uint32_t lnnoRHS, uint32_t linesRHS ){
#if 1
  if(1==b->passNumber){
    DICOSTATE(sst);
    ++sst->displayLines;
    return 0;
  }
  if(b->lnLHS+1==lnnoLHS && b->lnRHS+1==lnnoRHS){
    fdb__outf(b, "<<<Unfortunate chunk separator."
              "Ticket 746ebbe86c20b5c0f96cdadd19abd8284770de16.>>>\n");
  }
  //fdb__outf(b, "lnLHS=%d, lnRHS=%d\n", (int)b->lnLHS, (int)b->lnRHS);
  return fdb__outf(b, "@@ -%" PRIu32 ",%" PRIu32
                " +%" PRIu32 ",%" PRIu32 " @@\n",
                lnnoLHS, linesLHS, lnnoRHS, linesRHS);
#else
  return 0;
#endif
}







<
<
<
<
<







24008
24009
24010
24011
24012
24013
24014





24015
24016
24017
24018
24019
24020
24021
                                  uint32_t lnnoRHS, uint32_t linesRHS ){
#if 1
  if(1==b->passNumber){
    DICOSTATE(sst);
    ++sst->displayLines;
    return 0;
  }





  return fdb__outf(b, "@@ -%" PRIu32 ",%" PRIu32
                " +%" PRIu32 ",%" PRIu32 " @@\n",
                lnnoLHS, linesLHS, lnnoRHS, linesRHS);
#else
  return 0;
#endif
}
25825
25826
25827
25828
25829
25830
25831
25832
25833
25834
25835
25836
25837
25838
25839
25840
25841
25842
25843
25844
25845
  return rc;
}

static int fdb__utxt_chunkHeader(fsl_dibu* const b,
                                 uint32_t lnnoLHS, uint32_t linesLHS,
                                 uint32_t lnnoRHS, uint32_t linesRHS ){
  /*
    Ticket 746ebbe86c20b5c0f96cdadd19abd8284770de16:

    Annoying cosmetic bug: the libf impl of this diff will sometimes
    render two directly-adjecent chunks with a separator, e.g.:
  */

  // $ f-vdiff --format u 072d63965188 a725befe5863 -l '*vdiff*' | head -30
  // Index: f-apps/f-vdiff.c
  // ==================================================================
  // --- f-apps/f-vdiff.c
  // +++ f-apps/f-vdiff.c
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //     36     36    fsl_buffer fname;
  //     37     37    fsl_buffer fcontent1;







<
<
|
|


|







24427
24428
24429
24430
24431
24432
24433


24434
24435
24436
24437
24438
24439
24440
24441
24442
24443
24444
24445
  return rc;
}

static int fdb__utxt_chunkHeader(fsl_dibu* const b,
                                 uint32_t lnnoLHS, uint32_t linesLHS,
                                 uint32_t lnnoRHS, uint32_t linesRHS ){
  /*


     Annoying cosmetic bug: the libf impl of this diff will sometimes
     render two directly-adjecent chunks with a separator, e.g.:
  */

  // $ f-vdiff --forat u 072d63965188 a725befe5863 -l '*vdiff*' | head -30
  // Index: f-apps/f-vdiff.c
  // ==================================================================
  // --- f-apps/f-vdiff.c
  // +++ f-apps/f-vdiff.c
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //     36     36    fsl_buffer fname;
  //     37     37    fsl_buffer fcontent1;
25861
25862
25863
25864
25865
25866
25867
25868
25869
25870
25871
25872
25873
25874
25875
  /*
    Note now the chunks before/after the second ~~~ line are
    consecutive lines of code. In fossil(1) that case is accounted for
    in the higher-level diff engine, which can not only collapse
    adjacent blocks but also does the rendering of chunk headers in
    that main algorithm (something we cannot do in the library because
    we need the fsl_dibu to be able to output to arbitrary
    destinations). We can only _partially_ account for it here,
    eliminating the extraneous ~~~ line when we're in line-number
    mode. In non-line-number mode we have to output the chunk header
    as-is. If we skip it then the _previous_ chunk header, if any,
    will contain incorrect numbers for the chunk, invaliding the diff
    for purposes of tools which import unified-format diffs.
  */
  int rc = fdb__utxt_flush_ins(b);







|







24461
24462
24463
24464
24465
24466
24467
24468
24469
24470
24471
24472
24473
24474
24475
  /*
    Note now the chunks before/after the second ~~~ line are
    consecutive lines of code. In fossil(1) that case is accounted for
    in the higher-level diff engine, which can not only collapse
    adjacent blocks but also does the rendering of chunk headers in
    that main algorithm (something we cannot do in the library because
    we need the fsl_dibu to be able to output to arbitrary
    distinations). We can only _partially_ account for it here,
    eliminating the extraneous ~~~ line when we're in line-number
    mode. In non-line-number mode we have to output the chunk header
    as-is. If we skip it then the _previous_ chunk header, if any,
    will contain incorrect numbers for the chunk, invaliding the diff
    for purposes of tools which import unified-format diffs.
  */
  int rc = fdb__utxt_flush_ins(b);
26160
26161
26162
26163
26164
26165
26166
26167
26168
26169
26170
26171
26172
26173
26174
  BR_CLOSE; RC;
  rc = fdb__out(b, "\n", 1);
  end:
#undef RC
  return rc;
}

static int fdb__tcl_finish(fsl_dibu * const b __unused){
  int rc = 0;
#if 0
  BR_CLOSE;
  if(0==rc && FSL_DIBU_TCL_BRACES & b->implFlags){
    rc = fdb__out(b, "\n", 1);
  }
#endif







|







24760
24761
24762
24763
24764
24765
24766
24767
24768
24769
24770
24771
24772
24773
24774
  BR_CLOSE; RC;
  rc = fdb__out(b, "\n", 1);
  end:
#undef RC
  return rc;
}

static int fdb__tcl_finish(fsl_dibu * const b){
  int rc = 0;
#if 0
  BR_CLOSE;
  if(0==rc && FSL_DIBU_TCL_BRACES & b->implFlags){
    rc = fdb__out(b, "\n", 1);
  }
#endif
26788
26789
26790
26791
26792
26793
26794
26795
26796
26797
26798
26799
26800
26801
26802
26803
26804
26805
26806
26807
26808
26809
26810
26811
26812
26813
26814
26815
26816
26817
26818
26819
26820
26821
26822
26823
26824
26825
26826
26827
26828
26829
26830
26831
26832
26833
26834
26835
26836
26837
26838
26839
26840
26841
26842
26843
26844
26845
26846
26847
26848
26849
26850
26851
26852
26853
26854
26855
26856
26857
26858
26859
26860
26861
26862
26863
26864
26865
26866
26867
26868
26869
26870
26871
26872
26873
26874
26875
26876
26877
26878
26879
26880
26881
26882
26883
** See also discussion at https://fossil-scm.org/forum/forumpost/9ba3284295
**
** ALGORITHM (subject to change and refinement):
**
**    1.  If the subsequence is larger than 1/7th of the original span,
**        then consider it valid.  --> return 1
**
**    2.  If no lines of the subsequence contains more than one
**        non-whitespace character,  --> return 0
**
**    3.  If any line of the subsequence contains more than one non-whitespace
**        character and is unique across the entire sequence after ignoring
**        leading and trailing whitespace   --> return 1
**
**    4.  Otherwise, it is potentially an artifact of an indentation
**        change. --> return 0
*/
static bool likelyNotIndentChngArtifact(
  fsl__diff_cx const * const p,     /* The complete diff context */
  int iS1,         /* Start of the main segment */
  int iSX,         /* Start of the subsequence */
  int iEX,         /* First row past the end of the subsequence */
  int iE1          /* First row past the end of the main segment */
){
  int i, j, n;

  /* Rule (1) */
  if( (iEX-iSX)*7 >= (iE1-iS1) ) return 1;

  /* Compute fsl_dline.indent and fsl_dline.nw for all lines of the subsequence.
  ** If no lines contain more than one non-whitespace character return
  ** 0 because the subsequence could be due to an indentation change.
  ** Rule (2).
  */
  n = 0;
  for(i=iSX; i<iEX; i++){
    fsl_dline *pA = &p->aFrom[i];
    if( pA->nw==0 && pA->n ){
      const char *zA = pA->z;
      const int nn = pA->n;
      int ii, jj;
      for(ii=0; ii<nn && diff_isspace(zA[ii]); ii++){}
      pA->indent = ii;
      for(jj=nn-1; jj>ii && diff_isspace(zA[jj]); jj--){}
      pA->nw = jj - ii + 1;
    }
    if( pA->nw>1 ) n++;
  }
  if( n==0 ) return 0;

  /* Compute fsl_dline.indent and fsl_dline.nw for the entire sequence */
  for(i=iS1; i<iE1; i++){
    fsl_dline *pA;
    if( i==iSX ){
      i = iEX;
      if( i>=iE1 ) break;
    }
    pA = &p->aFrom[i];
    if( pA->nw==0 && pA->n ){
      const char *zA = pA->z;
      const int nn = pA->n;
      int ii, jj;
      for(ii=0; ii<nn && diff_isspace(zA[ii]); ii++){}
      pA->indent = ii;
      for(jj=nn-1; jj>ii && diff_isspace(zA[jj]); jj--){}
      pA->nw = jj - ii + 1;
    }
  }

  /* Check to see if any subsequence line that has more than one
  ** non-whitespace character is unique across the entire sequence.
  ** Rule (3)
  */
  for(i=iSX; i<iEX; i++){
    const char *z = p->aFrom[i].z + p->aFrom[i].indent;
    const int nw = p->aFrom[i].nw;
    if( nw<=1 ) continue;
    for(j=iS1; j<iSX; j++){
      if( p->aFrom[j].nw!=nw ) continue;
      if( memcmp(p->aFrom[j].z+p->aFrom[j].indent,z,nw)==0 ) break;
    }
    if( j<iSX ) continue;
    for(j=iEX; j<iE1; j++){
      if( p->aFrom[j].nw!=nw ) continue;
      if( memcmp(p->aFrom[j].z+p->aFrom[j].indent,z,nw)==0 ) break;
    }
    if( j>=iE1 ) break;
  }
  return i<iEX;
}

/**
    Do a single step in the difference.  Compute a sequence of
    copy/delete/insert steps that will convert lines iS1 through iE1-1
    of the input into lines iS2 through iE2-1 of the output and write
    that sequence into the difference context.







|
<
<
<
<
|

|









|
<
<
|
<
<
<
<
<
<
<

<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
|
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<

<
<
<
<
|
<
<
|







25388
25389
25390
25391
25392
25393
25394
25395




25396
25397
25398
25399
25400
25401
25402
25403
25404
25405
25406
25407
25408


25409







25410


25411


















25412

25413








25414











25415




25416


25417
25418
25419
25420
25421
25422
25423
25424
** See also discussion at https://fossil-scm.org/forum/forumpost/9ba3284295
**
** ALGORITHM (subject to change and refinement):
**
**    1.  If the subsequence is larger than 1/7th of the original span,
**        then consider it valid.  --> return 1
**
**    2.  If the subsequence contains any charaters other than '}', '{",




**        or whitespace, then consider it valid. --> return 1
**
**    3.  Otherwise, it is potentially an artifact of an indentation
**        change. --> return 0
*/
static bool likelyNotIndentChngArtifact(
  fsl__diff_cx const * const p,     /* The complete diff context */
  int iS1,         /* Start of the main segment */
  int iSX,         /* Start of the subsequence */
  int iEX,         /* First row past the end of the subsequence */
  int iE1          /* First row past the end of the main segment */
){
  int i, j;


  if( (iEX-iSX)*7 >= (iE1-iS1) ) return true;







  for(i=iSX; i<iEX; i++){


    const char *z = p->aFrom[i].z;


















    for(j=p->aFrom[i].n-1; j>=0; j--){

      char c = z[j];








      if( c!='}' && c!='{' && !diff_isspace(c) ) return true;











    }




  }


  return false;
}

/**
    Do a single step in the difference.  Compute a sequence of
    copy/delete/insert steps that will convert lines iS1 through iE1-1
    of the input into lines iS2 through iE2-1 of the output and write
    that sequence into the difference context.
27729
27730
27731
27732
27733
27734
27735
27736
27737
27738
27739
27740
27741
27742
27743
  fsl_dline * const aRight, int nRight,
  fsl_dibu_opt const * pOpt,
  unsigned char **pResult,
  unsigned *pNResult
);

/*
** Make a copy of a list of nLine fsl_dline objects from one array to
** another.  Hash the new array to ignore whitespace.
*/
static void diffDLineXfer(
  fsl_dline *aTo,
  const fsl_dline *aFrom,
  int nLine
){







|







26270
26271
26272
26273
26274
26275
26276
26277
26278
26279
26280
26281
26282
26283
26284
  fsl_dline * const aRight, int nRight,
  fsl_dibu_opt const * pOpt,
  unsigned char **pResult,
  unsigned *pNResult
);

/*
** Make a copy of a list of nLine DLine objects from one array to
** another.  Hash the new array to ignore whitespace.
*/
static void diffDLineXfer(
  fsl_dline *aTo,
  const fsl_dline *aFrom,
  int nLine
){
28807
28808
28809
28810
28811
28812
28813
28814
28815
28816
28817
28818
28819
28820
28821
28822
28823
28824
28825
28826
28827
28828
28829
28830
28831
28832
28833
28834
28835
28836
28837
28838
28839
28840
28841
28842
28843
28844
28845
28846
28847
28848
28849
28850
28851
28852
28853
28854
28855
28856
28857
28858
28859
28860
28861
28862
28863
28864
28865
28866
28867
28868
28869
28870
28871
28872
28873
28874
28875
28876
28877
28878
28879
28880
28881
28882
28883
28884
28885
28886
28887
28888
28889
28890
28891
28892
28893
28894
28895
28896
28897
28898
28899
28900
28901
28902
28903
28904
28905
28906
28907
28908
28909
28910
28911
28912
28913
28914
28915
28916
28917
28918
28919
28920
28921
28922
28923
28924
28925
28926
28927
28928
28929
28930
28931
28932
28933
28934
28935
28936
28937
28938
28939
28940
28941
28942
28943
28944
28945
28946
28947
28948
28949
28950
28951
28952
28953
28954
28955
28956
28957
28958
28959
28960
28961
28962
28963
28964
28965
28966
28967
28968
28969
28970
28971
28972
28973
28974
28975
28976
28977
28978
28979
28980
28981
28982
28983
28984
28985
28986
28987
28988


28989
28990
28991
28992
28993
28994

28995
28996
28997
28998
28999
29000
29001
29002
29003
29004
29005
29006
29007
29008
29009
29010
29011
29012
29013
29014
29015
29016
29017
29018
29019
29020
29021
29022
29023
29024
29025
29026
29027
29028
29029
29030
29031
29032
29033
29034
29035
29036
29037
29038
29039
29040
29041
29042
29043
29044
29045
29046
29047
29048
29049
29050
29051
29052
29053
29054
29055
29056
29057
29058
29059
29060
29061
29062
29063
29064
29065
29066
29067
29068
29069
29070
29071
29072
29073
29074
29075
29076
29077
29078
29079
29080
29081
29082
29083
29084
29085
29086
29087
29088
29089
29090
29091
29092
29093
29094
29095
29096
29097
29098
29099
29100
29101
29102
29103
29104
29105
29106
29107
29108
29109
29110
29111
29112
29113
29114
29115
29116
29117
29118
29119
29120
29121
29122
29123
29124
29125
29126
29127
29128
29129
29130
29131
29132
29133
29134
29135
29136
29137
29138
29139
29140
29141
29142
29143
29144
29145
29146
29147
29148
29149
29150
29151
29152
29153
29154
29155
29156
29157
29158
29159
29160
29161
29162
29163
29164
29165
29166
29167
29168
29169
29170
29171
29172
29173
29174
29175
29176
29177
29178
*/
/************************************************************************
  This file implements technote (formerly known as event)-related
  parts of the library.
*/
#include <assert.h>


/**
   Fetches all "technote" (formerly "event") IDs from the repository
   and appends each one to the given list in the form of a
   (`char*`). This function relies on the `event-` tag prefix being
   reserved for technotes and that the technote IDS are all exactly 40
   bytes long.
   
   Returns 0 on success, FSL_RC_NOT_A_REPO if f has no repository db
   opened, FSL_RC_OOM if allocation of a new list entry fails, or
   propagates db-related code on any other error. Results are
   undefined if either argument is NULL.

   TODO? Reformulate this to be like fsl_tkt_id_to_rids(), returning
   the list as RIDs?
*/
/*FSL_EXPORT*/ int fsl_technote_ids_get(fsl_cx * const f, fsl_list * const tgt );

int fsl_technote_ids_get( fsl_cx * const f, fsl_list * const tgt ){
  fsl_db * const db = fsl_needs_repo(f);
  if(!db) return FSL_RC_NOT_A_REPO;
  int rc = fsl_db_select_slist( db, tgt,
                                "SELECT substr(tagname,7) AS n "
                                "FROM tag "
                                "WHERE tagname GLOB 'event-*' "
                                "AND length(tagname)=46 "
                                "ORDER BY n");
  if(rc && db->error.code && !f->error.code){
    fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}
/* end of file ./src/event.c */
/* start of file ./src/foci.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
 * Copyright 2022 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt
 *
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 * SPDX-FileCopyrightText: 2021 The Libfossil Authors
 * SPDX-ArtifactOfProjectName: Libfossil
 * SPDX-FileType: Code
 *
 * Heavily indebted to the Fossil SCM project (https://fossil-scm.org).
 */

/*
 * This file implements the files-of-checkin (foci) API used to construct a
 * SQLite3 virtual table via a table-valued function to aggregate all files
 * pertaining to a specific check-in. This table is used in repository
 * queries such as listing all files belonging to a specific version.
 *
 * Usage (from fossil(1) /src/foci.c:24):
 *
 *    SELECT * FROM fsl_foci('trunk');
 *
 * temp.foci table schema:
 *
 *     CREATE TABLE fsl_foci(
 *       checkinID    INTEGER,    -- RID for the check-in manifest
 *       filename     TEXT,       -- Name of a file
 *       uuid         TEXT,       -- hash of the file
 *       previousName TEXT,       -- Name of the file in previous check-in
 *       perm         TEXT,       -- Permissions on the file
 *       symname      TEXT HIDDEN -- Symbolic name of the check-in.
 *     );
 *
 * The hidden symname column is (optionally) used as a query parameter to
 * identify the particular check-in to parse.  The checkinID parameter
 * (such is a unique numeric RID rather than symbolic name) can also be used
 * to identify the check-in.  Example:
 *
 *    SELECT * FROM fsl_foci
 *     WHERE checkinID=fsl_sym2rid('trunk');
 *
 */
#include <string.h>/*memset()*/
#include <assert.h>

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

enum {
FOCI_CHECKINID = 0,
FOCI_FILENAME = 1,
FOCI_UUID = 2,
FOCI_PREVNAME = 3,
FOCI_PERM = 4,
FOCI_SYMNAME = 5
};

typedef struct FociCursor FociCursor;
struct FociCursor {
  sqlite3_vtab_cursor base; /* Base class - must be first */
  fsl_deck d;           /* Current manifest */
  const fsl_card_F *cf;  /* Current file */
  int idx;                /* File index */
};

typedef struct FociTable FociTable;
struct FociTable {
  sqlite3_vtab base;        /* Base class - must be first */
  fsl_cx * f;               /* libfossil context */
};

/*
 * The schema for the virtual table:
 */
static const char zFociSchema[] =
  " CREATE TABLE fsl_foci("
  "  checkinID    INTEGER,    -- RID for the check-in manifest\n"
  "  filename     TEXT,       -- Name of a file\n"
  "  uuid         TEXT,       -- hash of the file\n"
  "  previousName TEXT,       -- Name of the file in previous check-in\n"
  "  perm         TEXT,       -- Permissions on the file\n"
  "  symname      TEXT HIDDEN -- Symbolic name of the check-in\n"
  " );";

/*
 * Connect to or create a foci virtual table.
 */
static int fociConnect(
  sqlite3 *db,
  void *pAux /*a (fsl_cx*) */,
  int argc __unused,
  const char * const * argv __unused,
  sqlite3_vtab **ppVtab,
  char **pzErr __unused
){
  FociTable *pTab;
  int rc = SQLITE_OK;

  pTab = (FociTable *)sqlite3_malloc(sizeof(FociTable));
  if( !pTab ){
    return SQLITE_NOMEM;
  }
  memset(pTab, 0, sizeof(FociTable));
  rc = sqlite3_declare_vtab(db, zFociSchema);
  if( rc==SQLITE_OK ){
    pTab->f = (fsl_cx*)pAux;
    *ppVtab = &pTab->base;
  }
  return rc;
}

/*
 * Disconnect from or destroy a focivfs virtual table.
 */
static int fociDisconnect(sqlite3_vtab *pVtab){
  sqlite3_free(pVtab);
  return SQLITE_OK;
}

/*
 * Available scan methods:
 *
 *   (0)     A full scan.  Visit every manifest in the repo.  (Slow)
 *   (1)     checkinID=?.  visit only the single manifest specified.
 *   (2)     symName=?     visit only the single manifest specified.
 */
static int fociBestIndex(sqlite3_vtab *tab __unused, sqlite3_index_info *pIdxInfo){
  int i;
  pIdxInfo->estimatedCost = 1000000000.0;
  for( i=0; i<pIdxInfo->nConstraint; i++ ){
    if( !pIdxInfo->aConstraint[i].usable ) continue;
    if( pIdxInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ
     && (pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID
            || pIdxInfo->aConstraint[i].iColumn==FOCI_SYMNAME)
    ){
      if( pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID ){
        pIdxInfo->idxNum = 1;


      }else{
        pIdxInfo->idxNum = 2;
      }
      pIdxInfo->estimatedCost = 1.0;
      pIdxInfo->aConstraintUsage[i].argvIndex = 1;
      pIdxInfo->aConstraintUsage[i].omit = 1;

      break;
    }
  }
  return SQLITE_OK;
}

/*
 * Open a new focivfs cursor.
 */
static int fociOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
  FociCursor *pCsr;
  pCsr = (FociCursor *)sqlite3_malloc(sizeof(FociCursor));
  if( !pCsr ){
    return SQLITE_NOMEM;
  }
  memset(pCsr, 0, sizeof(FociCursor));
  pCsr->d = fsl_deck_empty;
  pCsr->base.pVtab = pVTab;
  *ppCursor = (sqlite3_vtab_cursor *)pCsr;
  return SQLITE_OK;
}

/*
 * Close a focivfs cursor.
 */
static int fociClose(sqlite3_vtab_cursor *pCursor){
  FociCursor *pCsr = (FociCursor *)pCursor;
  fsl_deck_finalize(&pCsr->d);
  sqlite3_free(pCsr);
  return SQLITE_OK;
}

/*
 * Move a focivfs cursor to the next F card entry in the deck. If this fails,
 * pass the vtab cursor to fociClose and return the failing result code.
 */
static int fociNext(sqlite3_vtab_cursor *pCursor){
  int rc = SQLITE_OK;

  FociCursor *pCsr = (FociCursor *)pCursor;
  rc = fsl_deck_F_next(&pCsr->d, &pCsr->cf);
  if( !rc ){
    pCsr->idx++;
  }else{
    fociClose(pCursor);
  }
  return rc;
}

static int fociEof(sqlite3_vtab_cursor *pCursor){
  FociCursor *pCsr = (FociCursor *)pCursor;
  return pCsr->cf==0;
}

static int fociFilter(
  sqlite3_vtab_cursor *pCursor,
  int idxNum, const char *idxStr __unused,
  int argc __unused, sqlite3_value **argv
){
  int rc = SQLITE_OK;
  FociCursor *const pCur = (FociCursor *)pCursor;
  fsl_cx * const f = ((FociTable*)pCur->base.pVtab)->f;

  fsl_deck_finalize(&pCur->d);
  if( idxNum ){
    fsl_id_t rid;
    if( idxNum==1 ){
      rid = sqlite3_value_int(argv[0]);
    }else{
      rc = fsl_sym_to_rid(f, (const char *)sqlite3_value_text(argv[0]),
       FSL_SATYPE_CHECKIN, &rid);
      if( rc ){
        goto end;
      }
    }
    rc = fsl_deck_load_rid(f, &pCur->d, rid, FSL_SATYPE_CHECKIN);
    if( rc ){
      goto end;
    }
    if( pCur->d.rid ){
      rc = fsl_deck_F_rewind(&pCur->d);
      if( !rc ){
        rc = fsl_deck_F_next(&pCur->d, &pCur->cf);
      }
      if( rc ){
        goto end;
      }
    }
  }
  pCur->idx = 0;
end:
  if( rc ){
    fsl_deck_finalize(&pCur->d);
  }
  return rc;
}

static int fociColumn(
  sqlite3_vtab_cursor *pCursor,
  sqlite3_context *ctx,
  int i
){
  FociCursor *pCsr = (FociCursor *)pCursor;
  switch( i ){
    case FOCI_CHECKINID:
      sqlite3_result_int(ctx, pCsr->d.rid);
      break;
    case FOCI_FILENAME:
      sqlite3_result_text(ctx, pCsr->cf->name, -1, SQLITE_TRANSIENT);
      break;
    case FOCI_UUID:
      sqlite3_result_text(ctx, pCsr->cf->uuid, -1, SQLITE_TRANSIENT);
      break;
    case FOCI_PREVNAME:
      sqlite3_result_text(ctx, pCsr->cf->priorName, -1, SQLITE_TRANSIENT);
      break;
    case FOCI_PERM: {
      char *perm[3] = {"l", "w", "x"};
      int i = 1;
      switch( pCsr->cf->perm ){
        case FSL_FILE_PERM_LINK:
          i = 0; break;
        case FSL_FILE_PERM_EXE:
          i = 2; break;
        default:
          break;
      }
      sqlite3_result_text(ctx, perm[i], 1, SQLITE_TRANSIENT);
      break;
    }
    case FOCI_SYMNAME:
      break;
  }
  return SQLITE_OK;
}

static int fociRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
  FociCursor *pCsr = (FociCursor *)pCursor;
  *pRowid = pCsr->idx;
  return SQLITE_OK;
}

int fsl__foci_register(fsl_db * const db){
  static sqlite3_module foci_module = {
    0,                            /* iVersion */
    fociConnect,                  /* xCreate */
    fociConnect,                  /* xConnect */
    fociBestIndex,                /* xBestIndex */
    fociDisconnect,               /* xDisconnect */
    fociDisconnect,               /* xDestroy */
    fociOpen,                     /* xOpen - open a cursor */
    fociClose,                    /* xClose - close a cursor */
    fociFilter,                   /* xFilter - configure scan constraints */
    fociNext,                     /* xNext - advance a cursor */
    fociEof,                      /* xEof - check for end of scan */
    fociColumn,                   /* xColumn - read data */
    fociRowid,                    /* xRowid - read data */
    0,                            /* xUpdate */
    0,                            /* xBegin */
    0,                            /* xSync */
    0,                            /* xCommit */
    0,                            /* xRollback */
    0,                            /* xFindMethod */
    0,                            /* xRename */
    0,                            /* xSavepoint */
    0,                            /* xRelease */
    0,                            /* xRollbackTo */
    0                             /* xShadowName */
  };
  assert(db->f);
  int rc = sqlite3_create_module(db->dbh, "fsl_foci",
                                 &foci_module, db->f);
  return fsl__db_errcode(db, rc);
}

#undef MARKER
/* end of file ./src/foci.c */
/* start of file ./src/fs.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<





<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
>
>
|
|
<
<
<
<
>
|
<
<
<
<
|
<
|
<
<
<
<
|
<
<
<
|
<
<
<
|
<
<
<
<
<
<
<
<
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


|







27348
27349
27350
27351
27352
27353
27354
27355













































































27356


27357
27358
27359
27360
27361





































































27362
27363

















27364
27365
27366
27367
27368




27369
27370




27371

27372




27373



27374



27375








27376
27377
27378













27379
































































































































27380
27381
27382
27383
27384
27385
27386
27387
27388
27389
*/
/************************************************************************
  This file implements technote (formerly known as event)-related
  parts of the library.
*/
#include <assert.h>

/* Only for debugging */













































































#include <stdio.h>


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







































































int fsl_event_ids_get( fsl_cx * f, fsl_list * tgt ){

















  fsl_db * db = fsl_needs_repo(f);
  if(!f || !tgt) return FSL_RC_MISUSE;
  else if(!db) return FSL_RC_NOT_A_REPO;
  else {
    int rc = fsl_db_select_slist( db, tgt,




                                  "SELECT substr(tagname,7) AS n "
                                  "FROM tag "




                                  "WHERE tagname GLOB 'event-*' "

                                  "ORDER BY n");




    if(rc && db->error.code && !f->error.code){



      fsl_cx_uplift_db_error(f, db);



    }








    return rc;
  }
}















































































































































#undef MARKER
/* end of file ./src/event.c */
/* start of file ./src/fs.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt

  SPDX-License-Identifier: BSD-2-Clause-FreeBSD
29187
29188
29189
29190
29191
29192
29193

29194








29195
29196
29197
29198
29199
29200
29201
29202
29203
29204
29205
29206
29207
29208
29209
29210
29211
29212

29213
29214
29215
29216
29217
29218
29219
#endif

#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <ctype.h>
#include <errno.h>

#if FSL_PLATFORM_IS_WINDOWS








# if !defined(ELOOP)
#  define ELOOP 114 /* Missing in MinGW */
# endif
#else
# include <unistd.h> /* access(2), readlink(2) */
# include <sys/types.h>
# include <sys/time.h>
#endif
#include <sys/stat.h>

const fsl_path_splitter fsl_path_splitter_empty = fsl_path_splitter_empty_m;

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


FILE *fsl_fopen(const char *zName, const char *zMode){
  FILE *f;
  if(zName && ('-'==*zName && !zName[1])){
    f = (strchr(zMode, 'w') || strchr(zMode,'+'))
      ? stdout
      : stdin







>

>
>
>
>
>
>
>
>










<
<






>







27398
27399
27400
27401
27402
27403
27404
27405
27406
27407
27408
27409
27410
27411
27412
27413
27414
27415
27416
27417
27418
27419
27420
27421
27422
27423
27424


27425
27426
27427
27428
27429
27430
27431
27432
27433
27434
27435
27436
27437
27438
#endif

#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <ctype.h>
#include <errno.h>
#include <dirent.h>
#if FSL_PLATFORM_IS_WINDOWS
# define DIR _WDIR
# define dirent _wdirent
# define opendir _wopendir
# define readdir _wreaddir
# define closedir _wclosedir
# include <direct.h>
# include <windows.h>
# include <sys/utime.h>
# if !defined(ELOOP)
#  define ELOOP 114 /* Missing in MinGW */
# endif
#else
# include <unistd.h> /* access(2), readlink(2) */
# include <sys/types.h>
# include <sys/time.h>
#endif
#include <sys/stat.h>



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


FILE *fsl_fopen(const char *zName, const char *zMode){
  FILE *f;
  if(zName && ('-'==*zName && !zName[1])){
    f = (strchr(zMode, 'w') || strchr(zMode,'+'))
      ? stdout
      : stdin
29681
29682
29683
29684
29685
29686
29687
29688
29689
29690
29691
29692
29693
29694
29695
29696
29697
29698
29699
29700
29701
29702
29703
29704
29705
29706
29707
29708
29709
29710
29711
      return (doBreak && leaveSlash)
        ? fsl_buffer_append(pOut, zFilename, 1)
        : fsl_buffer_append(pOut, "", 0) /* ensure a NUL terminator */;
    }else{
      return fsl_buffer_append(pOut, zFilename, z-zFilename + 1);
    }
  }
}

const char *fsl_file_tail(const char *z){
  const char *zTail = z;
  if( !zTail ) return 0;
  while( z[0] ){
    if( '/'==z[0] || '\\'==z[0] ) zTail = &z[1];
    z++;
  }
  return zTail;
}


int fsl_find_home_dir( fsl_buffer * const tgt, bool requireWriteAccess ){
  char * zHome = NULL;
  int rc = 0;
  fsl_buffer_reuse(tgt);
#if defined(_WIN32) || defined(__CYGWIN__)
  zHome = fsl_getenv("LOCALAPPDATA");
  if( zHome==0 ){
    zHome = fsl_getenv("APPDATA");
    if( zHome==0 ){
      char *zDrive = fsl_getenv("HOMEDRIVE");
      zHome = fsl_getenv("HOMEPATH");







|
|
<
<
<
<
<
<
|
<
<
<
<
|


|







27900
27901
27902
27903
27904
27905
27906
27907
27908






27909




27910
27911
27912
27913
27914
27915
27916
27917
27918
27919
27920
      return (doBreak && leaveSlash)
        ? fsl_buffer_append(pOut, zFilename, 1)
        : fsl_buffer_append(pOut, "", 0) /* ensure a NUL terminator */;
    }else{
      return fsl_buffer_append(pOut, zFilename, z-zFilename + 1);
    }
  }

}











int fsl_find_home_dir( fsl_buffer * tgt, bool requireWriteAccess ){
  char * zHome = NULL;
  int rc = 0;
  tgt->used = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
  zHome = fsl_getenv("LOCALAPPDATA");
  if( zHome==0 ){
    zHome = fsl_getenv("APPDATA");
    if( zHome==0 ){
      char *zDrive = fsl_getenv("HOMEDRIVE");
      zHome = fsl_getenv("HOMEPATH");
30107
30108
30109
30110
30111
30112
30113



30114
30115
30116
30117
30118
30119
30120
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? -1
    : fst.size;
}
#endif




int fsl_file_mtime_set(const char *zFilename, fsl_time_t newMTime){
  if(!zFilename || !*zFilename) return FSL_RC_MISUSE;
  else{
    int rc;
    void * zMbcs;
#if !defined(_WIN32)
    struct timeval tv[2];







>
>
>







28316
28317
28318
28319
28320
28321
28322
28323
28324
28325
28326
28327
28328
28329
28330
28331
28332
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? -1
    : fst.size;
}
#endif

/*
   Set the mtime for a file.
*/
int fsl_file_mtime_set(const char *zFilename, fsl_time_t newMTime){
  if(!zFilename || !*zFilename) return FSL_RC_MISUSE;
  else{
    int rc;
    void * zMbcs;
#if !defined(_WIN32)
    struct timeval tv[2];
30146
30147
30148
30149
30150
30151
30152
30153
30154
30155
30156
30157
30158
30159
30160
30161
30162
30163
30164
30165
30166
30167
30168
30169
30170
30171
30172
30173
30174
30175
30176
30177
30178
30179
30180
30181
30182
30183
30184
30185
30186
30187
30188
30189
30190
30191
30192
30193
30194
30195
30196
30197
30198
30199
30200
30201
30202
30203
30204
30205
30206
30207
30208
30209
30210
30211
30212
30213

30214
30215
30216
30217
30218
30219
30220
    fsl_list_visit_free(&pf->ext, 1);
    fsl_list_visit_free(&pf->dirs, 1);
    fsl_buffer_clear(&pf->buf);
    *pf = fsl_pathfinder_empty;
  }
}

static int fsl__pathfinder_add_impl(fsl_list * const li, char const * str,
                                   fsl_int_t strLen){
  char * cp = fsl_strndup(str, strLen);
  int rc;
  if(!cp) rc = FSL_RC_OOM;
  else{
    rc = fsl_list_append(li, cp);
    if(rc) fsl_free(cp);
  }
  return rc;
}

int fsl_pathfinder_dir_add(fsl_pathfinder * const pf, char const * const dir){
  return dir
    ? fsl__pathfinder_add_impl(&pf->dirs, dir, -1)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_dir_add2(fsl_pathfinder * const pf, char const * const dir,
                            fsl_int_t strLen){
  return dir
    ? fsl__pathfinder_add_impl(&pf->dirs, dir, strLen)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_ext_add(fsl_pathfinder * const pf, char const * const ext){
  return (pf && ext)
    ? fsl__pathfinder_add_impl(&pf->ext, ext, -1)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_ext_add2(fsl_pathfinder * const pf, char const * const ext,
                           fsl_int_t strLen){
  return (pf && ext)
    ? fsl__pathfinder_add_impl(&pf->ext, ext, strLen)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_search(fsl_pathfinder * const pf,
                          char const * const base,
                          char const ** pOut,
                          fsl_size_t * const outLen ){
  fsl_buffer * const buf = &pf->buf;
  fsl_list * ext;
  fsl_list * dirs;
  int rc = 0;
  fsl_size_t d, x, nD, nX, resetLen = 0;
  fsl_size_t baseLen;
  static char const pathSep =
#if defined(_WIN32)
    '\\' /* TODO: confirm whether we can always use '/', and do so if
            we can. */
#else
    '/'
#endif
    ;
  if(!base || !*base) return FSL_RC_MISUSE;
  else if(!*base) return FSL_RC_RANGE;
  else if(0==fsl_file_access( base, 0 )){
    /* Special case: if base is found as-is, without a path search,
       use it. */

    if(pOut) *pOut = base;
    if(outLen) *outLen = fsl_strlen(base);
    return 0;
  }
  baseLen = fsl_strlen(base);
  ext = &pf->ext;
  dirs = &pf->dirs;







|
<
|










|
|
<
<
<
<
<
<
<





|
<
<
<
<
<
<
<







|







|
<




|



|
>







28358
28359
28360
28361
28362
28363
28364
28365

28366
28367
28368
28369
28370
28371
28372
28373
28374
28375
28376
28377
28378







28379
28380
28381
28382
28383
28384







28385
28386
28387
28388
28389
28390
28391
28392
28393
28394
28395
28396
28397
28398
28399
28400

28401
28402
28403
28404
28405
28406
28407
28408
28409
28410
28411
28412
28413
28414
28415
28416
28417
    fsl_list_visit_free(&pf->ext, 1);
    fsl_list_visit_free(&pf->dirs, 1);
    fsl_buffer_clear(&pf->buf);
    *pf = fsl_pathfinder_empty;
  }
}

static int fsl_pathfinder_add(fsl_list * const li, char const * str){

  char * cp = fsl_strdup(str);
  int rc;
  if(!cp) rc = FSL_RC_OOM;
  else{
    rc = fsl_list_append(li, cp);
    if(rc) fsl_free(cp);
  }
  return rc;
}

int fsl_pathfinder_dir_add(fsl_pathfinder * const pf, char const * const dir){
  return (pf && dir)
    ? fsl_pathfinder_add(&pf->dirs, dir)







    : FSL_RC_MISUSE;
}

int fsl_pathfinder_ext_add(fsl_pathfinder * const pf, char const * const ext){
  return (pf && ext)
    ? fsl_pathfinder_add(&pf->ext, ext)







    : FSL_RC_MISUSE;
}

int fsl_pathfinder_search(fsl_pathfinder * const pf,
                          char const * const base,
                          char const ** pOut,
                          fsl_size_t * const outLen ){
  fsl_buffer * buf = pf ? &pf->buf : NULL;
  fsl_list * ext;
  fsl_list * dirs;
  int rc = 0;
  fsl_size_t d, x, nD, nX, resetLen = 0;
  fsl_size_t baseLen;
  static char const pathSep =
#if defined(_WIN32)
    '\\'

#else
    '/'
#endif
    ;
  if(!buf || !base || !*base) return FSL_RC_MISUSE;
  else if(!*base) return FSL_RC_RANGE;
  else if(0==fsl_file_access( base, 0 )){
    /* Special case: if base is found as-is, without a path search,
       use it. This is arguable behaviour, though.
    */
    if(pOut) *pOut = base;
    if(outLen) *outLen = fsl_strlen(base);
    return 0;
  }
  baseLen = fsl_strlen(base);
  ext = &pf->ext;
  dirs = &pf->dirs;
30250
30251
30252
30253
30254
30255
30256

30257

30258
30259
30260
30261
30262
30263
30264
30265
30266
30267
30268
30269
30270
30271
30272
30273
30274
30275
30276
30277
30278
30279
30280
30281
30282
30283
30284
30285
30286
30287
30288
30289
30290
30291
30292
30293
30294
30295
30296
30297
30298
30299
30300
30301
30302
30303
30304
30305
30306
30307
30308
30309
30310
30311
30312
30313
30314
30315
30316
30317
30318
30319
30320
30321
30322
30323
30324
30325
30326
30327
30328
30329
30330
30331
30332
      assert(buf->used < buf->capacity);
      buf->mem[buf->used] = 0;
      if(0==fsl_file_access( (char const *)buf->mem, 0 )){
        goto gotone;
      }
    }
  }

  return FSL_RC_NOT_FOUND;

  gotone:
  if(outLen) *outLen = buf->used;
  if(pOut) *pOut = (char const *)buf->mem;
  return 0;
}

void fsl_path_splitter_init( fsl_path_splitter * pt, char const * path, fsl_int_t len ){
  *pt = fsl_path_splitter_empty;
  pt->pos = pt->begin = path;
  pt->end = pt->begin + ((len>=0) ? (fsl_size_t)len : fsl_strlen(path));
}

int fsl_path_splitter_next( fsl_path_splitter * const pt, char const ** token,
                            fsl_size_t * const len ){
  if(!pt->pos || pt->pos>=pt->end) return FSL_RC_RANGE;
  else if(!pt->separators || !*pt->separators) return FSL_RC_MISUSE;
  else{
    char const * pos = pt->pos;
    char const * t;
    char const * sep;
    for( sep = pt->separators; *sep; ++sep){
      if(*sep & 0x80) return FSL_RC_MISUSE;
      /* non-ASCII */
    }
    for( ; pos<pt->end; ){
      /*skip leading separators*/
      for( sep = pt->separators;
           *sep && *pos!=*sep; ++sep ){
      }
      if(*pos == *sep) ++pos;
      else break;
    }
    t = pos;
    for( ; pos<pt->end; ){
      /*skip until the next separator*/
      for( sep = pt->separators;
           *sep && *pos!=*sep; ++sep ){
      }
      if(*pos == *sep) break;
      else ++pos;
    }
    pt->pos = pos;
    if(pos>t){
      *token = t;
      *len = (fsl_size_t)(pos - t);
      return 0;
    }
    return FSL_RC_NOT_FOUND;
  }
}

int fsl_pathfinder_split( fsl_pathfinder * const tgt,
                          bool isDirs,
                          char const * path,
                          fsl_int_t pathLen ){
  int rc = 0;
  char const * t = 0;
  fsl_size_t tLen = 0;
  fsl_path_splitter pt = fsl_path_splitter_empty;
  fsl_path_splitter_init(&pt, path, pathLen);
  while(0==rc && 0==fsl_path_splitter_next(&pt, &t, &tLen)){
    rc = isDirs
      ? fsl_pathfinder_dir_add2(tgt, t, (fsl_int_t)tLen)
      : fsl_pathfinder_ext_add2(tgt, t, (fsl_int_t)tLen);
  }
  return rc;
}

char * fsl__file_without_drive_letter(char * zIn){
#ifdef _WIN32
  if( zIn && fsl_isalpha(zIn[0]) && zIn[1]==':' ) zIn += 2;
#endif
  return zIn;
}








>

>






<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







28447
28448
28449
28450
28451
28452
28453
28454
28455
28456
28457
28458
28459
28460
28461
28462






























































28463
28464
28465
28466
28467
28468
28469
      assert(buf->used < buf->capacity);
      buf->mem[buf->used] = 0;
      if(0==fsl_file_access( (char const *)buf->mem, 0 )){
        goto gotone;
      }
    }
  }

  return FSL_RC_NOT_FOUND;

  gotone:
  if(outLen) *outLen = buf->used;
  if(pOut) *pOut = (char const *)buf->mem;
  return 0;
}































































char * fsl__file_without_drive_letter(char * zIn){
#ifdef _WIN32
  if( zIn && fsl_isalpha(zIn[0]) && zIn[1]==':' ) zIn += 2;
#endif
  return zIn;
}

30481
30482
30483
30484
30485
30486
30487
30488
30489
30490
30491
30492
30493
30494
30495
      *z = 0;
      ++rc;
    }
  }
  return rc;
}

void fsl_buffer_strip_slashes(fsl_buffer * const b){
  b->used -= fsl_strip_trailing_slashes((char *)b->mem,
                                        (fsl_int_t)b->used);
}

int fsl_file_rename(const char *zFrom, const char *zTo){
  int rc;
#if defined(_WIN32)







|







28618
28619
28620
28621
28622
28623
28624
28625
28626
28627
28628
28629
28630
28631
28632
      *z = 0;
      ++rc;
    }
  }
  return rc;
}

void fsl_buffer_strip_slashes(fsl_buffer * b){
  b->used -= fsl_strip_trailing_slashes((char *)b->mem,
                                        (fsl_int_t)b->used);
}

int fsl_file_rename(const char *zFrom, const char *zTo){
  int rc;
#if defined(_WIN32)
30706
30707
30708
30709
30710
30711
30712
30713
30714
30715
30716
30717
30718
30719
30720
30721
30722
30723
30724
30725
30726
30727
30728
30729
30730
30731
30732
30733
30734
30735
30736
30737
30738
30739
30740
30741
           to overwrite the symlinked-to file */;
      rc = fsl_buffer_to_filename(&content, zLinkFile);
    }
  }
  return rc;
}

int fsl_symlink_copy(char const *zFrom, char const *zTo, bool realLink){
  int rc;
  fsl_buffer b = fsl_buffer_empty;
  rc = fsl_symlink_read(&b, zFrom);
  if(0==rc){
    rc = fsl_symlink_create(fsl_buffer_cstr(&b), zTo, realLink);
  }
  fsl_buffer_clear(&b);
  return rc;
}

char const * fsl_last_path_sep(char const * str, fsl_int_t slen ){
  if(slen<0) slen = (fsl_int_t)fsl_strlen(str);
  unsigned char const * pos = (unsigned char const *)str + slen;
  while( --pos >= (unsigned char const *)str ){
    if('/'==*pos || '\\'==*pos){
      return (char const *)pos;
    }
  }
  return NULL;
}


#if 0
int fsl_file_relative_name( char const * zRoot, char const * zPath,
                            fsl_buffer * pOut, char retainSlash ){
  int rc = FSL_RC_NYI;
  char * zPath;
  fsl_size_t rootLen;







|









<
<
<
<
<
<
<
<
<
<
<
<







28843
28844
28845
28846
28847
28848
28849
28850
28851
28852
28853
28854
28855
28856
28857
28858
28859












28860
28861
28862
28863
28864
28865
28866
           to overwrite the symlinked-to file */;
      rc = fsl_buffer_to_filename(&content, zLinkFile);
    }
  }
  return rc;
}

int fsl__symlink_copy(char const *zFrom, char const *zTo, bool realLink){
  int rc;
  fsl_buffer b = fsl_buffer_empty;
  rc = fsl_symlink_read(&b, zFrom);
  if(0==rc){
    rc = fsl_symlink_create(fsl_buffer_cstr(&b), zTo, realLink);
  }
  fsl_buffer_clear(&b);
  return rc;
}













#if 0
int fsl_file_relative_name( char const * zRoot, char const * zPath,
                            fsl_buffer * pOut, char retainSlash ){
  int rc = FSL_RC_NYI;
  char * zPath;
  fsl_size_t rootLen;
30786
30787
30788
30789
30790
30791
30792

30793
30794
30795
30796
30797
30798
30799
int fsl_repo_install_schema_forum(fsl_cx *f){
  int rc;
  fsl_db * db = fsl_needs_repo(f);
  if(!db) return FSL_RC_NOT_A_REPO;
  if(fsl_db_table_exists(db, FSL_DBROLE_REPO, "forumpost")){
    return 0;
  }

  rc = fsl_db_exec_multi(db, "%s",fsl_schema_forum());
  if(rc){
    rc = fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}








>







28911
28912
28913
28914
28915
28916
28917
28918
28919
28920
28921
28922
28923
28924
28925
int fsl_repo_install_schema_forum(fsl_cx *f){
  int rc;
  fsl_db * db = fsl_needs_repo(f);
  if(!db) return FSL_RC_NOT_A_REPO;
  if(fsl_db_table_exists(db, FSL_DBROLE_REPO, "forumpost")){
    return 0;
  }
  MARKER(("table not exists?\n"));
  rc = fsl_db_exec_multi(db, "%s",fsl_schema_forum());
  if(rc){
    rc = fsl_cx_uplift_db_error(f, db);
  }
  return rc;
}

31050
31051
31052
31053
31054
31055
31056
31057
31058
31059
31060
31061
31062
31063
31064
  FILE * f = (FILE*) state;
  *n = (fsl_size_t)fread( dest, 1, *n, f );
  return *n
    ? 0
    : (feof(f) ? 0 : FSL_RC_IO);
}

void fsl_finalizer_f_FILE( void * state __unused, void * mem ){
  if(mem){
    fsl_fclose((FILE*)mem);
  }
}

int fsl_stream( fsl_input_f inF, void * inState,
                fsl_output_f outF, void * outState ){







|







29176
29177
29178
29179
29180
29181
29182
29183
29184
29185
29186
29187
29188
29189
29190
  FILE * f = (FILE*) state;
  *n = (fsl_size_t)fread( dest, 1, *n, f );
  return *n
    ? 0
    : (feof(f) ? 0 : FSL_RC_IO);
}

void fsl_finalizer_f_FILE( void * state, void * mem ){
  if(mem){
    fsl_fclose((FILE*)mem);
  }
}

int fsl_stream( fsl_input_f inF, void * inState,
                fsl_output_f outF, void * outState ){
31534
31535
31536
31537
31538
31539
31540
31541
31542
31543
31544
31545
31546
31547
31548
    if(rc) return rc;
  }
  self->list[self->used++] = cp;
  if(self->used<self->capacity) self->list[self->used]=NULL;
  return 0;
}

int fsl_list_v_fsl_free(void * obj, void * visitorState __unused){
  if(obj) fsl_free( obj );
  return 0;
}

int fsl_list_clear( fsl_list * const self, fsl_list_visitor_f childFinalizer,
                    void * finalizerState ){
  /*







|







29660
29661
29662
29663
29664
29665
29666
29667
29668
29669
29670
29671
29672
29673
29674
    if(rc) return rc;
  }
  self->list[self->used++] = cp;
  if(self->used<self->capacity) self->list[self->used]=NULL;
  return 0;
}

int fsl_list_v_fsl_free(void * obj, void * visitorState ){
  if(obj) fsl_free( obj );
  return 0;
}

int fsl_list_clear( fsl_list * const self, fsl_list_visitor_f childFinalizer,
                    void * finalizerState ){
  /*
32865
32866
32867
32868
32869
32870
32871
32872
32873
32874
32875
32876
32877
32878
32879
32880
32881
  );
}

#define fsl_merge_state_empty_m {        \
  NULL/*f*/,                             \
  NULL/*opt*/,                           \
  NULL/*filename*/,                      \
  NULL/*priorName*/,                     \
  FSL_MERGE_FCHANGE_NONE/*fileChangeType*/,\
  FSL_CKUP_RM_NOT/* fileRmInfo */        \
}
/**
   Initialized-with-defaults fsl_merge_state instance,
   intended for use in non-const copy initialization.
*/
const fsl_merge_state fsl_merge_state_empty = fsl_merge_state_empty_m;








|
|
<







30991
30992
30993
30994
30995
30996
30997
30998
30999

31000
31001
31002
31003
31004
31005
31006
  );
}

#define fsl_merge_state_empty_m {        \
  NULL/*f*/,                             \
  NULL/*opt*/,                           \
  NULL/*filename*/,                      \
  NULL/*prevName*/,                      \
  FSL_MERGE_FCHANGE_NONE/*fileChangeType*/ \

}
/**
   Initialized-with-defaults fsl_merge_state instance,
   intended for use in non-const copy initialization.
*/
const fsl_merge_state fsl_merge_state_empty = fsl_merge_state_empty_m;

33360
33361
33362
33363
33364
33365
33366
33367
33368
33369
33370
33371
33372
33373
33374
33375
33376
33377
33378
33379
33380
33381
33382
33383
      if( fsl_file_size(zFullNewPath)>=0 ){
        rc = fsl_file_tempname(bTmp, "", NULL);
        if(rc) goto merge_rename_end;
        rc = fsl_cx_exec(f, "INSERT INTO tmprn(fn,tmpfn) VALUES(%Q,%B)",
                         zNewName, bTmp);
        if(rc) goto merge_rename_end;
        rc = fsl_is_symlink(zFullNewPath)
          ? fsl_symlink_copy(zFullNewPath, fsl_buffer_cstr(bTmp), realSymlinks)
          : fsl_file_copy(zFullNewPath, fsl_buffer_cstr(bTmp));
        if(rc){
          rc = fsl_cx_err_set(f, rc, "Error copying file [%s].",
                              zFullNewPath);
        }
        if(rc) goto merge_rename_end;
      }
      rc = fsl_is_symlink(zFullOldPath)
        ? fsl_symlink_copy(zFullOldPath, zFullNewPath, realSymlinks)
        : fsl_file_copy(zFullOldPath, zFullNewPath);
      if(0==rc){
        fsl_file_exec_set(zFullNewPath, !!isExe);
        fsl_file_unlink(zFullOldPath);
        /* ^^^ Ignore errors: not critical here */
      }
      merge_rename_end:







|








|







31485
31486
31487
31488
31489
31490
31491
31492
31493
31494
31495
31496
31497
31498
31499
31500
31501
31502
31503
31504
31505
31506
31507
31508
      if( fsl_file_size(zFullNewPath)>=0 ){
        rc = fsl_file_tempname(bTmp, "", NULL);
        if(rc) goto merge_rename_end;
        rc = fsl_cx_exec(f, "INSERT INTO tmprn(fn,tmpfn) VALUES(%Q,%B)",
                         zNewName, bTmp);
        if(rc) goto merge_rename_end;
        rc = fsl_is_symlink(zFullNewPath)
          ? fsl__symlink_copy(zFullNewPath, fsl_buffer_cstr(bTmp), realSymlinks)
          : fsl_file_copy(zFullNewPath, fsl_buffer_cstr(bTmp));
        if(rc){
          rc = fsl_cx_err_set(f, rc, "Error copying file [%s].",
                              zFullNewPath);
        }
        if(rc) goto merge_rename_end;
      }
      rc = fsl_is_symlink(zFullOldPath)
        ? fsl__symlink_copy(zFullOldPath, zFullNewPath, realSymlinks)
        : fsl_file_copy(zFullOldPath, zFullNewPath);
      if(0==rc){
        fsl_file_exec_set(zFullNewPath, !!isExe);
        fsl_file_unlink(zFullOldPath);
        /* ^^^ Ignore errors: not critical here */
      }
      merge_rename_end:
34456
34457
34458
34459
34460
34461
34462
34463
34464
34465
34466
34467
34468
34469
34470
   This is a tricky query to do efficiently.  If the tag is very
   common (ex: "trunk") then we want to use the query identified below
   as Q1 - which searching the most recent EVENT table entries for the
   most recent with the tag.  But if the tag is relatively scarce
   (anything other than "trunk", basically) then we want to do the
   indexed search show below as Q2.
*/
static fsl_id_t fsl__morewt(fsl_cx * const f, const char *zTag, fsl_satype_e type){
  char const * zType = fsl_satype_event_cstr(type);
  return fsl_db_g_id(fsl_cx_db_repo(f), 0,
    "SELECT objid FROM ("
      /* Q1:  Begin by looking for the tag in the 30 most recent events */
      "SELECT objid"
       " FROM (SELECT * FROM event ORDER BY mtime DESC LIMIT 30) AS ex"
      " WHERE type GLOB '%q'"







|







32581
32582
32583
32584
32585
32586
32587
32588
32589
32590
32591
32592
32593
32594
32595
   This is a tricky query to do efficiently.  If the tag is very
   common (ex: "trunk") then we want to use the query identified below
   as Q1 - which searching the most recent EVENT table entries for the
   most recent with the tag.  But if the tag is relatively scarce
   (anything other than "trunk", basically) then we want to do the
   indexed search show below as Q2.
*/
static fsl_id_t fsl_morewt(fsl_cx * const f, const char *zTag, fsl_satype_e type){
  char const * zType = fsl_satype_event_cstr(type);
  return fsl_db_g_id(fsl_cx_db_repo(f), 0,
    "SELECT objid FROM ("
      /* Q1:  Begin by looking for the tag in the 30 most recent events */
      "SELECT objid"
       " FROM (SELECT * FROM event ORDER BY mtime DESC LIMIT 30) AS ex"
      " WHERE type GLOB '%q'"
34488
34489
34490
34491
34492
34493
34494
34495
34496
34497
34498
34499
34500
34501
34502
34503
34504
    ") LIMIT 1;",
    zType, zTag, zTag, zType
  );
}

/**
   Modes for fsl_start_of_branch().

   These values are hard-coded and must retain these values,
   else queries will break.
*/
enum fsl_stobr_type {
/**
   The check-in of the parent branch off of which
   the branch containing RID originally diverged.
*/
FSL_STOBR_ORIGIN = 0,







<
<
<







32613
32614
32615
32616
32617
32618
32619



32620
32621
32622
32623
32624
32625
32626
    ") LIMIT 1;",
    zType, zTag, zTag, zType
  );
}

/**
   Modes for fsl_start_of_branch().



*/
enum fsl_stobr_type {
/**
   The check-in of the parent branch off of which
   the branch containing RID originally diverged.
*/
FSL_STOBR_ORIGIN = 0,
34514
34515
34516
34517
34518
34519
34520
34521
34522

34523
34524
34525
34526
34527
34528
34529
34530
34531

34532
34533
34534
34535
34536
34537
34538
34539
34540
34541
34542
34543
34544
34545
34546
34547
34548
34549
34550
34551

34552
34553
34554



34555

34556

34557


34558
34559
34560
34561
34562
34563
34564
34565
34566
34567
34568
34569
34570
34571
34572
34573
34574
34575
34576
34577
34578
34579
34580
34581
34582
34583
34584
34585
34586
34587
34588
34589
34590
34591
34592
34593
34594
34595
};

/*
** Return the RID that is the "root" of the branch that contains
** check-in "rid".  Details depending on eType. If not found, rid is
** returned.
*/
static fsl_id_t fsl_start_of_branch(fsl_cx * const f, fsl_id_t rid,
                                    enum fsl_stobr_type eType){

  fsl_stmt q = fsl_stmt_empty;
  int rc;
  fsl_id_t ans = rid;
  char * zBr = 0;
  rc = fsl_branch_of_rid(f, rid, true, &zBr);
  if(rc) return -1;
  rc = fsl_cx_prepare(f, &q,
    "WITH RECURSIVE"
    "  par(pid, ex, cnt) as ("

    "    SELECT pid, EXISTS(SELECT 1 FROM tagxref"
    "                        WHERE tagid=%d AND tagtype>0"
    "                          AND value=%Q AND rid=plink.pid), 1"
    "    FROM plink WHERE cid=%"FSL_ID_T_PFMT" AND isprim"
    "    UNION ALL "
    "    SELECT plink.pid, EXISTS(SELECT 1 FROM tagxref "
    "                              WHERE tagid=%d AND tagtype>0" 
    "                                AND value=%Q AND rid=plink.pid),"
    "           1+par.cnt"
    "      FROM plink, par"
    "     WHERE cid=par.pid AND isprim AND par.ex "
    "     LIMIT 100000 "
    "  )"
    " SELECT pid FROM par WHERE ex>=%d ORDER BY cnt DESC LIMIT 1",
    FSL_TAGID_BRANCH, zBr, ans, FSL_TAGID_BRANCH, zBr, eType%2
  );
  fsl_free(zBr);
  zBr = 0;
  if(rc){
    ans = -2;

    MARKER(("Internal error: fsl_db_prepare() says: %s\n", fsl_rc_cstr(rc)));
    goto end;
  }



  if( FSL_RC_STEP_ROW == fsl_stmt_step(&q) ) {

    ans = fsl_stmt_g_id(&q, 0);

  }


  fsl_stmt_finalize(&q);
  end:
  if( ans>0 && eType==FSL_STOBR_YOAN ){
    rc = fsl_branch_of_rid(f, ans, true, &zBr);
    if(rc) goto oom;
    else{
      ans = fsl_youngest_ancestor_in_branch(f, rid, zBr);
      fsl_free(zBr);
    }
  }
  return ans;
  oom:
  if(!f->error.code){
    fsl_cx_err_set(f, FSL_RC_OOM, NULL);
  }/* Else assume the OOM is really a misleading
      side-effect of another failure. */
  return -1;
}

int fsl_sym_to_rid( fsl_cx * const f, char const * sym,
                    fsl_satype_e type, fsl_id_t * const rv ){
  fsl_id_t rid = 0;
  fsl_id_t vid;
  fsl_size_t symLen;
  /* fsl_int_t i; */
  fsl_db * const dbR = fsl_cx_db_repo(f);
  fsl_db * const dbC = fsl_cx_db_ckout(f);
  bool startOfBranch = 0;
  int rc = 0;

  if(!sym || !*sym || !rv) return FSL_RC_MISUSE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;

  if(FSL_SATYPE_BRANCH_START==type){
    /* The original implementation takes a (char const *) for the
       type, and treats "b" (branch?) as a special case of
       FSL_SATYPE_CHECKIN, resets the type to "ci", then sets
       startOfBranch to 1. We introduced the FSL_SATYPE_BRANCH







|

>





|
|
<
|
>
|
|
|
<
<
<
<
<
<
|
|
<
<
<
|





>



>
>
>
|
>
|
>
|
>
>




















|




|
|



|







32636
32637
32638
32639
32640
32641
32642
32643
32644
32645
32646
32647
32648
32649
32650
32651
32652

32653
32654
32655
32656
32657






32658
32659



32660
32661
32662
32663
32664
32665
32666
32667
32668
32669
32670
32671
32672
32673
32674
32675
32676
32677
32678
32679
32680
32681
32682
32683
32684
32685
32686
32687
32688
32689
32690
32691
32692
32693
32694
32695
32696
32697
32698
32699
32700
32701
32702
32703
32704
32705
32706
32707
32708
32709
32710
32711
32712
32713
32714
32715
32716
32717
};

/*
** Return the RID that is the "root" of the branch that contains
** check-in "rid".  Details depending on eType. If not found, rid is
** returned.
*/
static fsl_id_t fsl_start_of_branch(fsl_cx * f, fsl_id_t rid,
                                    enum fsl_stobr_type eType){
  fsl_db * db;
  fsl_stmt q = fsl_stmt_empty;
  int rc;
  fsl_id_t ans = rid;
  char * zBr = 0;
  rc = fsl_branch_of_rid(f, rid, true, &zBr);
  if(rc) return rc;
  db = fsl_cx_db_repo(f);

  assert(db);
  rc = fsl_db_prepare(db, &q,
    "SELECT pid, EXISTS(SELECT 1 FROM tagxref"
                       " WHERE tagid=%d AND tagtype>0"
                       "   AND value=%Q AND rid=plink.pid)"






    "  FROM plink"
    " WHERE cid=? AND isprim",



    FSL_TAGID_BRANCH, zBr
  );
  fsl_free(zBr);
  zBr = 0;
  if(rc){
    ans = -2;
    fsl_cx_uplift_db_error(f, db);
    MARKER(("Internal error: fsl_db_prepare() says: %s\n", fsl_rc_cstr(rc)));
    goto end;
  }
  do{
    fsl_stmt_reset(&q);
    fsl_stmt_bind_id(&q, 1, ans);
    rc = fsl_stmt_step(&q);
    if( rc!=FSL_RC_STEP_ROW ) break;
    if( eType==FSL_STOBR_FIRST_CI && fsl_stmt_g_int32(&q,1)==0 ){
      break;
    }
    ans = fsl_stmt_g_id(&q, 0);
  }while( fsl_stmt_g_int32(&q, 1)==1 && ans>0 );
  fsl_stmt_finalize(&q);
  end:
  if( ans>0 && eType==FSL_STOBR_YOAN ){
    rc = fsl_branch_of_rid(f, ans, true, &zBr);
    if(rc) goto oom;
    else{
      ans = fsl_youngest_ancestor_in_branch(f, rid, zBr);
      fsl_free(zBr);
    }
  }
  return ans;
  oom:
  if(!f->error.code){
    fsl_cx_err_set(f, FSL_RC_OOM, NULL);
  }/* Else assume the OOM is really a misleading
      side-effect of another failure. */
  return -1;
}

int fsl_sym_to_rid( fsl_cx * const f, char const * sym,
                    fsl_satype_e type, fsl_id_t * rv ){
  fsl_id_t rid = 0;
  fsl_id_t vid;
  fsl_size_t symLen;
  /* fsl_int_t i; */
  fsl_db * dbR = fsl_cx_db_repo(f);
  fsl_db * dbC = fsl_cx_db_ckout(f);
  bool startOfBranch = 0;
  int rc = 0;

  if(!f || !sym || !*sym || !rv) return FSL_RC_MISUSE;
  else if(!dbR) return FSL_RC_NOT_A_REPO;

  if(FSL_SATYPE_BRANCH_START==type){
    /* The original implementation takes a (char const *) for the
       type, and treats "b" (branch?) as a special case of
       FSL_SATYPE_CHECKIN, resets the type to "ci", then sets
       startOfBranch to 1. We introduced the FSL_SATYPE_BRANCH
34657
34658
34659
34660
34661
34662
34663
34664
34665
34666
34667
34668
34669
34670
34671
34672
34673
34674
34675
34676
34677
34678
34679
34680
34681
34682
34683
34684
34685
34686
34687
34688
34689
34690
34691
34692
34693
34694
34695
34696
34697
34698
34699
34700
34701
34702
34703
34704
34705
34706
34707
34708
34709


34710
34711
34712
34713
34714
34715
34716
    if(rid>0) goto gotit;
  }

  /* Deprecated time formats elided: local:..., utc:... */

  /* "tag:" + symbolic-name */
  if( memcmp(sym, "tag:", 4)==0 ){
    rid = fsl__morewt(f, sym+4, type);
    if(rid>0 && startOfBranch){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_FIRST_CI);
    }
    goto gotit;
  }

  /* root:TAG -> The origin of the branch */
  if( memcmp(sym, "root:", 5)==0 ){
    rc = fsl_sym_to_rid(f, sym+5, type, &rid);
    if(!rc && rid>0){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_ORIGIN);
    }
    goto gotit;
  }  

  /* start:TAG -> The first check-in on branch named TAG */
  if( strncmp(sym, "start:", 6)==0 ){
    rc = fsl_sym_to_rid(f, sym+6, type, &rid);
    if(!rc && rid>0){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_FIRST_CI);
    }
    goto gotit;
  }

  /* merge-in:TAG -> Most recent merge-in for the branch */
  if( memcmp(sym, "merge-in:", 9)==0 ){
    rc = fsl_sym_to_rid(f, sym+9, type, &rid);
    if(!rc){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_YOAN);
    }
    goto gotit;
  }  
  
  symLen = fsl_strlen(sym);
  /* SHA1/SHA3 hash or prefix */
  if( symLen>=4
      && symLen<=FSL_STRLEN_K256
      && fsl_validate16(sym, symLen) ){
    fsl_stmt q = fsl_stmt_empty;
    char zUuid[FSL_STRLEN_K256+1];
    memcpy(zUuid, sym, symLen);
    zUuid[symLen] = 0;
    fsl_canonical16(zUuid, symLen);
    rid = 0;
    /* Reminder to self: caching these queries would be cool. */


    if( FSL_SATYPE_ANY==type ){
      fsl_db_prepare(dbR, &q,
                       "SELECT rid FROM blob WHERE uuid GLOB '%s*'",
                       zUuid);
    }else{
      fsl_db_prepare(dbR, &q,
                     "SELECT blob.rid"







|















<
<
<
<
<
<
<
<
<




















|
>
>







32779
32780
32781
32782
32783
32784
32785
32786
32787
32788
32789
32790
32791
32792
32793
32794
32795
32796
32797
32798
32799
32800
32801









32802
32803
32804
32805
32806
32807
32808
32809
32810
32811
32812
32813
32814
32815
32816
32817
32818
32819
32820
32821
32822
32823
32824
32825
32826
32827
32828
32829
32830
32831
    if(rid>0) goto gotit;
  }

  /* Deprecated time formats elided: local:..., utc:... */

  /* "tag:" + symbolic-name */
  if( memcmp(sym, "tag:", 4)==0 ){
    rid = fsl_morewt(f, sym+4, type);
    if(rid>0 && startOfBranch){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_FIRST_CI);
    }
    goto gotit;
  }

  /* root:TAG -> The origin of the branch */
  if( memcmp(sym, "root:", 5)==0 ){
    rc = fsl_sym_to_rid(f, sym+5, type, &rid);
    if(!rc && rid>0){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_ORIGIN);
    }
    goto gotit;
  }  










  /* merge-in:TAG -> Most recent merge-in for the branch */
  if( memcmp(sym, "merge-in:", 9)==0 ){
    rc = fsl_sym_to_rid(f, sym+9, type, &rid);
    if(!rc){
      rid = fsl_start_of_branch(f, rid, FSL_STOBR_YOAN);
    }
    goto gotit;
  }  
  
  symLen = fsl_strlen(sym);
  /* SHA1/SHA3 hash or prefix */
  if( symLen>=4
      && symLen<=FSL_STRLEN_K256
      && fsl_validate16(sym, symLen) ){
    fsl_stmt q = fsl_stmt_empty;
    char zUuid[FSL_STRLEN_K256+1];
    memcpy(zUuid, sym, symLen);
    zUuid[symLen] = 0;
    fsl_canonical16(zUuid, symLen);
    rid = 0;
    /* Reminder to self: caching these queries would be cool but it
       can't work with the GLOBs.
    */
    if( FSL_SATYPE_ANY==type ){
      fsl_db_prepare(dbR, &q,
                       "SELECT rid FROM blob WHERE uuid GLOB '%s*'",
                       zUuid);
    }else{
      fsl_db_prepare(dbR, &q,
                     "SELECT blob.rid"
34746
34747
34748
34749
34750
34751
34752
34753
34754
34755
34756
34757
34758
34759
34760
34761
34762
34763
34764
34765
34766
34767
34768
34769
34770
                    " WHERE tag.tagname='sym-%q' "
                    "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
                    "   AND event.objid=tagxref.rid "
                    "   AND event.type GLOB '%q'",
                    sym, fsl_satype_event_cstr(type)
    );
  }else{
    rid = fsl__morewt(f, sym, type);
    //MARKER(("morewt(%s,%s) == %d\n", sym, fsl_satype_cstr(type), (int)rid));
  }

  if( rid>0 ){
    if(startOfBranch) rid = fsl_start_of_branch(f, rid,
                                                FSL_STOBR_FIRST_CI);
    goto gotit;
  }

  /* Undocumented: rid:### ==> validate that ### is a known rid */
  if(symLen>4 && 0==fsl_strncmp("rid:",sym,4)){
    int i;
    char const * oldSym = sym;
    sym += 4;
    for(i=0; fsl_isdigit(sym[i]); i++){}
    if( sym[i]==0 ){
      if( FSL_SATYPE_ANY==type ){







|









|







32861
32862
32863
32864
32865
32866
32867
32868
32869
32870
32871
32872
32873
32874
32875
32876
32877
32878
32879
32880
32881
32882
32883
32884
32885
                    " WHERE tag.tagname='sym-%q' "
                    "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
                    "   AND event.objid=tagxref.rid "
                    "   AND event.type GLOB '%q'",
                    sym, fsl_satype_event_cstr(type)
    );
  }else{
    rid = fsl_morewt(f, sym, type);
    //MARKER(("morewt(%s,%s) == %d\n", sym, fsl_satype_cstr(type), (int)rid));
  }

  if( rid>0 ){
    if(startOfBranch) rid = fsl_start_of_branch(f, rid,
                                                FSL_STOBR_FIRST_CI);
    goto gotit;
  }

  /* Undocumented: rid:### ==> rid */
  if(symLen>4 && 0==fsl_strncmp("rid:",sym,4)){
    int i;
    char const * oldSym = sym;
    sym += 4;
    for(i=0; fsl_isdigit(sym[i]); i++){}
    if( sym[i]==0 ){
      if( FSL_SATYPE_ANY==type ){
34818
34819
34820
34821
34822
34823
34824
34825
34826
34827
34828
34829
34830
34831
34832
34833
        assert(f->error.code);
        rv = -3;
      }
      return rv;
    }
}

int fsl_sym_to_uuid( fsl_cx * const f, char const * sym, fsl_satype_e type,
                     fsl_uuid_str * const rv, fsl_id_t * const rvId ){
  fsl_id_t rid = 0;
  fsl_db * dbR = fsl_needs_repo(f);
  fsl_uuid_str rvv = NULL;
  int rc = dbR
    ? fsl_sym_to_rid(f, sym, type, &rid)
    : FSL_RC_NOT_A_REPO;
  if(!rc){







|
|







32933
32934
32935
32936
32937
32938
32939
32940
32941
32942
32943
32944
32945
32946
32947
32948
        assert(f->error.code);
        rv = -3;
      }
      return rv;
    }
}

int fsl_sym_to_uuid( fsl_cx * f, char const * sym, fsl_satype_e type,
                     fsl_uuid_str * rv, fsl_id_t * rvId ){
  fsl_id_t rid = 0;
  fsl_db * dbR = fsl_needs_repo(f);
  fsl_uuid_str rvv = NULL;
  int rc = dbR
    ? fsl_sym_to_rid(f, sym, type, &rid)
    : FSL_RC_NOT_A_REPO;
  if(!rc){
35022
35023
35024
35025
35026
35027
35028
35029
35030
35031
35032
35033
35034
35035
35036
    case FSL_RC_STEP_ROW:
      rc = 0;
      *rv = fsl_stmt_g_id(q, 0);
      break;
    case 0:
      rc = 0;
      *rv = 0;
      __attribute__ ((fallthrough));
    default:
      fsl_cx_uplift_db_error(f, q->db);
      break;
  }
  fsl_stmt_reset(q);
  return rc;
}







<







33137
33138
33139
33140
33141
33142
33143

33144
33145
33146
33147
33148
33149
33150
    case FSL_RC_STEP_ROW:
      rc = 0;
      *rv = fsl_stmt_g_id(q, 0);
      break;
    case 0:
      rc = 0;
      *rv = 0;

    default:
      fsl_cx_uplift_db_error(f, q->db);
      break;
  }
  fsl_stmt_reset(q);
  return rc;
}
35311
35312
35313
35314
35315
35316
35317
35318
35319
35320
35321
35322
35323
35324
35325
    if(rc){
      rc = fsl_cx_err_set(f, rc, "Cannot unlink existing repo file: %s",
                          opt->filename);
      goto end2;
    }
  }
  rc = fsl__cx_attach_role(f, opt->filename, FSL_DBROLE_REPO, true);
  //MARKER(("attach role rc=%s\n", fsl_rc_cstr(rc)));
  if(rc){
    goto end2;
  }
  db = fsl_cx_db(f);
  if(!f->repo.user){
    f->repo.user = fsl_user_name_guess()
      /* Ignore OOM error here - we'll use 'root'







|







33425
33426
33427
33428
33429
33430
33431
33432
33433
33434
33435
33436
33437
33438
33439
    if(rc){
      rc = fsl_cx_err_set(f, rc, "Cannot unlink existing repo file: %s",
                          opt->filename);
      goto end2;
    }
  }
  rc = fsl__cx_attach_role(f, opt->filename, FSL_DBROLE_REPO, true);
  MARKER(("attach role rc=%s\n", fsl_rc_cstr(rc)));
  if(rc){
    goto end2;
  }
  db = fsl_cx_db(f);
  if(!f->repo.user){
    f->repo.user = fsl_user_name_guess()
      /* Ignore OOM error here - we'll use 'root'
36006
36007
36008
36009
36010
36011
36012
36013
36014
36015
36016
36017
36018
36019
36020
36021
36022
36023

36024
36025
36026
36027
36028
36029
36030
   * The original fingerprint algorithm used "quote(mtime)".  But this could
   * give slightly different answers depending on how the floating-point
   * hardware is configured.  For example, it gave different answers on
   * native Linux versus running under valgrind.
   */
  if(0==version){
    fsl_stmt_finalize(&q);
    fsl_buffer_append(sql,
                      "SELECT rcvid, quote(uid), quote(mtime), "
                      "quote(nonce), quote(ipaddr) "
                      "FROM rcvfrom ", -1);
  }else{
    assert(1==version);
    fsl_buffer_append(sql,
                      "SELECT rcvid, quote(uid), datetime(mtime), "
                      "quote(nonce), quote(ipaddr) "
                      "FROM rcvfrom ", -1);
  }

  rc = (rcvid>0)
    ? fsl_buffer_appendf(sql, "WHERE rcvid=%" FSL_ID_T_PFMT, rcvid)
    : fsl_buffer_append(sql, "ORDER BY rcvid DESC LIMIT 1", -1);
  if(rc) goto end;
  rc = fsl_db_prepare(db, &q, "%b", sql);
  if(rc) goto end;
  rc = fsl_stmt_step(&q);







|
|
|
|


|
|
|
|

>







34120
34121
34122
34123
34124
34125
34126
34127
34128
34129
34130
34131
34132
34133
34134
34135
34136
34137
34138
34139
34140
34141
34142
34143
34144
34145
   * The original fingerprint algorithm used "quote(mtime)".  But this could
   * give slightly different answers depending on how the floating-point
   * hardware is configured.  For example, it gave different answers on
   * native Linux versus running under valgrind.
   */
  if(0==version){
    fsl_stmt_finalize(&q);
    rc = fsl_buffer_append(sql,
                          "SELECT rcvid, quote(uid), quote(mtime), "
                          "quote(nonce), quote(ipaddr) "
                          "FROM rcvfrom ", -1);
  }else{
    assert(1==version);
    rc = fsl_buffer_append(sql,
                          "SELECT rcvid, quote(uid), datetime(mtime), "
                          "quote(nonce), quote(ipaddr) "
                          "FROM rcvfrom ", -1);
  }
  if(rc) goto end;
  rc = (rcvid>0)
    ? fsl_buffer_appendf(sql, "WHERE rcvid=%" FSL_ID_T_PFMT, rcvid)
    : fsl_buffer_append(sql, "ORDER BY rcvid DESC LIMIT 1", -1);
  if(rc) goto end;
  rc = fsl_db_prepare(db, &q, "%b", sql);
  if(rc) goto end;
  rc = fsl_stmt_step(&q);
36100
36101
36102
36103
36104
36105
36106
36107
36108

36109
36110
36111
36112
36113
36114
36115
      rc = fsl_rid_to_uuid2(f, manifestRid, bHash);
      if(rc) goto end;
      ridHash = (char *)bHash->mem;
    }else{
      ridHash = f->ckout.uuid;
    }
    assert(ridHash);
    fsl_buffer_append(pHash, ridHash, -1);
    rc = fsl_buffer_append(pHash, "\n", 1);

  }
  if(pTags){
    fsl_stmt q = fsl_stmt_empty;
    fsl_db * const db = fsl_cx_db_repo(f);
    assert(db && "We can't have a checkout w/o a repo.");
    str = fsl_db_g_text(db, NULL, "SELECT VALUE FROM tagxref "
                        "WHERE rid=%" FSL_ID_T_PFMT







|
|
>







34215
34216
34217
34218
34219
34220
34221
34222
34223
34224
34225
34226
34227
34228
34229
34230
34231
      rc = fsl_rid_to_uuid2(f, manifestRid, bHash);
      if(rc) goto end;
      ridHash = (char *)bHash->mem;
    }else{
      ridHash = f->ckout.uuid;
    }
    assert(ridHash);
    rc = fsl_buffer_append(pHash, ridHash, -1);
    if(!rc) rc = fsl_buffer_append(pHash, "\n", 1);
    if(rc) goto end;
  }
  if(pTags){
    fsl_stmt q = fsl_stmt_empty;
    fsl_db * const db = fsl_cx_db_repo(f);
    assert(db && "We can't have a checkout w/o a repo.");
    str = fsl_db_g_text(db, NULL, "SELECT VALUE FROM tagxref "
                        "WHERE rid=%" FSL_ID_T_PFMT
36123
36124
36125
36126
36127
36128
36129

36130
36131
36132

36133
36134
36135
36136

36137

36138
36139
36140
36141
36142
36143
36144
                        "  FROM tagxref, tag"
                        " WHERE tagxref.rid=%" FSL_ID_T_PFMT
                        "   AND tagxref.tagtype>0"
                        "   AND tag.tagid=tagxref.tagid"
                        "   AND tag.tagname GLOB 'sym-*'"
                        " /*%s()*/",
                        f->ckout.rid, __func__);

    while( 0==rc && FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
      const char *zName = fsl_stmt_g_text(&q, 0, NULL);
      rc = fsl_buffer_appendf(pTags, "tag %s\n", zName);

    }
    fsl_stmt_finalize(&q);
  }
  end:

  if(bHash) fsl__cx_scratchpad_yield(f, bHash);

  return rc;
}

/**
   Internal state for the rebuild process.
*/
struct FslRebuildState {







>
|


>




>
|
>







34239
34240
34241
34242
34243
34244
34245
34246
34247
34248
34249
34250
34251
34252
34253
34254
34255
34256
34257
34258
34259
34260
34261
34262
34263
34264
                        "  FROM tagxref, tag"
                        " WHERE tagxref.rid=%" FSL_ID_T_PFMT
                        "   AND tagxref.tagtype>0"
                        "   AND tag.tagid=tagxref.tagid"
                        "   AND tag.tagname GLOB 'sym-*'"
                        " /*%s()*/",
                        f->ckout.rid, __func__);
    if(rc) goto end;
    while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
      const char *zName = fsl_stmt_g_text(&q, 0, NULL);
      rc = fsl_buffer_appendf(pTags, "tag %s\n", zName);
      if(rc) break;
    }
    fsl_stmt_finalize(&q);
  }
  end:
  if(bHash){
    fsl__cx_scratchpad_yield(f, bHash);
  }
  return rc;
}

/**
   Internal state for the rebuild process.
*/
struct FslRebuildState {
36517
36518
36519
36520
36521
36522
36523
36524
36525
36526
36527
36528
36529
36530
36531
36532
36533
36534
36535
36536
36537
36538
  frs.opt = frs.step.opt = opt;
  rc = fsl__rebuild_update_schema(&frs);
  if(!rc) rc = fsl_buffer_reserve(sql, 1024 * 4);
  if(rc) goto end;

  fsl__cx_clear_mf_seen(f, false);
  /* DROP all tables which are not part of our One True Vision of the
     repo db...

     2022-07-31: we might want to stop doing this because: if fossil
     adds new tables, there may be a lag in getting them into
     libfossil and we don't necessarily want to nuke those. OTOH, all
     such tables would be transient/rebuildable state, so if we nuke
     them then a rebuild from fossil(1) would correct it.
 */
  rc = fsl_cx_prepare(f, &q,
     "SELECT name FROM %!Q.sqlite_schema /*scan*/"
     " WHERE type='table'"
     " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
                       "'config','shun','private','reportfmt',"
                       "'concealed','accesslog','modreq',"
                       "'purgeevent','purgeitem','unversioned',"







|
<
<
<
<
<
<
<







34637
34638
34639
34640
34641
34642
34643
34644







34645
34646
34647
34648
34649
34650
34651
  frs.opt = frs.step.opt = opt;
  rc = fsl__rebuild_update_schema(&frs);
  if(!rc) rc = fsl_buffer_reserve(sql, 1024 * 4);
  if(rc) goto end;

  fsl__cx_clear_mf_seen(f, false);
  /* DROP all tables which are not part of our One True Vision of the
     repo db... */







  rc = fsl_cx_prepare(f, &q,
     "SELECT name FROM %!Q.sqlite_schema /*scan*/"
     " WHERE type='table'"
     " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
                       "'config','shun','private','reportfmt',"
                       "'concealed','accesslog','modreq',"
                       "'purgeevent','purgeitem','unversioned',"
41325
41326
41327
41328
41329
41330
41331
41332
41333
41334
41335
41336
41337
41338
41339
41340
41341
41342
41343
41344
41345
41346
41347
41348
41349
41350
41351

/**
   SQL function to return the number of seconds since 1970.  This is
   the same as strftime('%s','now') but is more compact.
*/
static void fsl_db_now_udf(
  sqlite3_context *context,
  int argc __unused,
  sqlite3_value **argv __unused
){
  sqlite3_result_int64(context, (sqlite3_int64)time(0));
}

/**
   SQL function to convert a Julian Day to a Unix timestamp.
*/
static void fsl_db_j2u_udf(
  sqlite3_context *context,
  int argc __unused,
  sqlite3_value **argv __unused
){
  double const jd = (double)sqlite3_value_double(argv[0]);
  sqlite3_result_int64(context, (sqlite3_int64)fsl_julian_to_unix(jd));
}

/**
   SQL function FSL_CKOUT_DIR([bool includeTrailingSlash=1]) returns







|
|









|
|







39438
39439
39440
39441
39442
39443
39444
39445
39446
39447
39448
39449
39450
39451
39452
39453
39454
39455
39456
39457
39458
39459
39460
39461
39462
39463
39464

/**
   SQL function to return the number of seconds since 1970.  This is
   the same as strftime('%s','now') but is more compact.
*/
static void fsl_db_now_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  sqlite3_result_int64(context, (sqlite3_int64)time(0));
}

/**
   SQL function to convert a Julian Day to a Unix timestamp.
*/
static void fsl_db_j2u_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  double const jd = (double)sqlite3_value_double(argv[0]);
  sqlite3_result_int64(context, (sqlite3_int64)fsl_julian_to_unix(jd));
}

/**
   SQL function FSL_CKOUT_DIR([bool includeTrailingSlash=1]) returns
41384
41385
41386
41387
41388
41389
41390
41391
41392
41393
41394
41395
41396
41397
41398
                                     sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  fsl_time_t mtime = 0;
  int rc;
  fsl_id_t vid, fid;
  assert(f);
  assert(2<=argc);
  vid = (fsl_id_t)sqlite3_value_int(argv[0]);
  fid = (fsl_id_t)sqlite3_value_int(argv[1]);
  rc = fsl_mtime_of_manifest_file(f, vid, fid, &mtime);
  if( rc==0 ){
    sqlite3_result_int64(context, mtime);
  }else{
    sqlite3_result_error(context, "fsl_mtime_of_manifest_file() failed", -1); 







<







39497
39498
39499
39500
39501
39502
39503

39504
39505
39506
39507
39508
39509
39510
                                     sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  fsl_time_t mtime = 0;
  int rc;
  fsl_id_t vid, fid;
  assert(f);

  vid = (fsl_id_t)sqlite3_value_int(argv[0]);
  fid = (fsl_id_t)sqlite3_value_int(argv[1]);
  rc = fsl_mtime_of_manifest_file(f, vid, fid, &mtime);
  if( rc==0 ){
    sqlite3_result_int64(context, mtime);
  }else{
    sqlite3_result_error(context, "fsl_mtime_of_manifest_file() failed", -1); 
41550
41551
41552
41553
41554
41555
41556
41557
41558
41559
41560
41561
41562
41563
41564
41565

/*
   Implement the user() SQL function.  user() takes no arguments and
   returns the user ID of the current user.
*/
static void fsl_db_user_udf(
  sqlite3_context *context,
  int argc __unused,
  sqlite3_value **argv __unused
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  assert(f);
  if(f->repo.user){
    sqlite3_result_text(context, f->repo.user, -1, SQLITE_STATIC);
  }else{
    sqlite3_result_null(context);







|
|







39662
39663
39664
39665
39666
39667
39668
39669
39670
39671
39672
39673
39674
39675
39676
39677

/*
   Implement the user() SQL function.  user() takes no arguments and
   returns the user ID of the current user.
*/
static void fsl_db_user_udf(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  fsl_cx * f = (fsl_cx*)sqlite3_user_data(context);
  assert(f);
  if(f->repo.user){
    sqlite3_result_text(context, f->repo.user, -1, SQLITE_STATIC);
  }else{
    sqlite3_result_null(context);
41647
41648
41649
41650
41651
41652
41653
41654
41655
41656
41657
41658
41659
41660
41661
  p1 = (const char*)sqlite3_value_text(argv[0]);
  p2 = (const char*)sqlite3_value_text(argv[1]);
  if(!p1 || !p2){
    sqlite3_result_null(context);
    return;
  }
  int (*cmp)(char const *, char const *) =
    fsl_cx_is_case_sensitive(f, false) ? fsl_stricmp : fsl_strcmp;
  if(0==cmp(p1, p2)){
    sqlite3_result_int(context, 1);
    return;
  }
  b = fsl__cx_scratchpad(f);
  rc = fsl_buffer_appendf(b, "%s/", p2);
  if(rc) goto oom;







|







39759
39760
39761
39762
39763
39764
39765
39766
39767
39768
39769
39770
39771
39772
39773
  p1 = (const char*)sqlite3_value_text(argv[0]);
  p2 = (const char*)sqlite3_value_text(argv[1]);
  if(!p1 || !p2){
    sqlite3_result_null(context);
    return;
  }
  int (*cmp)(char const *, char const *) =
    f->cache.caseInsensitive ? fsl_stricmp : fsl_strcmp;
  if(0==cmp(p1, p2)){
    sqlite3_result_int(context, 1);
    return;
  }
  b = fsl__cx_scratchpad(f);
  rc = fsl_buffer_appendf(b, "%s/", p2);
  if(rc) goto oom;
41692
41693
41694
41695
41696
41697
41698
41699
41700
41701
41702
41703
41704
41705
41706
  sqlite3_value **argv
){
  fsl_cx * const f = (fsl_cx*)sqlite3_user_data(context);
  fsl_list * li = NULL;
  fsl_glob_category_e globType;
  char const * p1;
  char const * p2;
  assert(2<=argc);
  p2 = (const char*)sqlite3_value_text(argv[1])/*value to check*/;
  if(NULL==p2 || 0==p2[0]){
    sqlite3_result_int(context, 0);
    return;
  }
  p1 = (const char*)sqlite3_value_text(argv[0])/*glob set name*/;
  globType  = fsl_glob_name_to_category(p1);







<







39804
39805
39806
39807
39808
39809
39810

39811
39812
39813
39814
39815
39816
39817
  sqlite3_value **argv
){
  fsl_cx * const f = (fsl_cx*)sqlite3_user_data(context);
  fsl_list * li = NULL;
  fsl_glob_category_e globType;
  char const * p1;
  char const * p2;

  p2 = (const char*)sqlite3_value_text(argv[1])/*value to check*/;
  if(NULL==p2 || 0==p2[0]){
    sqlite3_result_int(context, 0);
    return;
  }
  p1 = (const char*)sqlite3_value_text(argv[0])/*glob set name*/;
  globType  = fsl_glob_name_to_category(p1);
41794
41795
41796
41797
41798
41799
41800
41801
41802
41803
41804
41805
41806
41807
41808
                          SQLITE_UTF8 | SQLITE_DETERMINISTIC,
                          /* noting that ^^^^^ it's only deterministic
                             for a given statement execution IF no SQL
                             triggers an effect which forces the globs to
                             reload. That "shouldn't ever happen." */
                          f, fsl_db_cx_glob_udf, 0, 0 );

  rc = fsl__foci_register(db);
#if 0
  /* functions registered in v1 by db.c:db_open(). */
  /* porting cgi() requires access to the HTTP/CGI
     layer. i.e. this belongs downstream. */
  sqlite3_create_function(dbh, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
  sqlite3_create_function(dbh, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
  re_add_sql_func(db) /* Requires the regex bits. */;







<







39905
39906
39907
39908
39909
39910
39911

39912
39913
39914
39915
39916
39917
39918
                          SQLITE_UTF8 | SQLITE_DETERMINISTIC,
                          /* noting that ^^^^^ it's only deterministic
                             for a given statement execution IF no SQL
                             triggers an effect which forces the globs to
                             reload. That "shouldn't ever happen." */
                          f, fsl_db_cx_glob_udf, 0, 0 );


#if 0
  /* functions registered in v1 by db.c:db_open(). */
  /* porting cgi() requires access to the HTTP/CGI
     layer. i.e. this belongs downstream. */
  sqlite3_create_function(dbh, "cgi", 1, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
  sqlite3_create_function(dbh, "cgi", 2, SQLITE_ANY, 0, db_sql_cgi, 0, 0);
  re_add_sql_func(db) /* Requires the regex bits. */;
41937
41938
41939
41940
41941
41942
41943
41944
41945
41946
41947
41948
41949
41950
41951

void fsl_filename_free(void *pOld){
#if defined(_WIN32)
  fsl_free(pOld);
#elif (defined(__APPLE__) && !defined(WITHOUT_ICONV)) || defined(__CYGWIN__)
  fsl_free(pOld);
#else
  (void)pOld;
  /* No-op on all other unix */
#endif
}

char *fsl_filename_to_utf8(const void *zFilename){
#if defined(_WIN32)
  int nByte = WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, 0, 0, 0, 0);







<







40047
40048
40049
40050
40051
40052
40053

40054
40055
40056
40057
40058
40059
40060

void fsl_filename_free(void *pOld){
#if defined(_WIN32)
  fsl_free(pOld);
#elif (defined(__APPLE__) && !defined(WITHOUT_ICONV)) || defined(__CYGWIN__)
  fsl_free(pOld);
#else

  /* No-op on all other unix */
#endif
}

char *fsl_filename_to_utf8(const void *zFilename){
#if defined(_WIN32)
  int nByte = WideCharToMultiByte(CP_UTF8, 0, zFilename, -1, 0, 0, 0, 0);
42014
42015
42016
42017
42018
42019
42020
42021
42022
42023
42024
42025
42026
42027
42028
42029
     been extended since this code was ported:

     void *fossil_utf8_to_path(const char *zUtf8, int isDir)

     That isDir param is only for Windows and its only purpose is to
     ensure that the translated path is not within 12 bytes of
     MAX_PATH. That same effect can be had by simply always assuming
     that bool is true and sacrificing those 12 bytes and that
     far-edge (literally) case.

     Also, the newer code jumps through many hoops which seem
     unimportant for fossil, e.g. handling UNC-style paths.

     Porting that latter bit over requires someone who can at least
     test whether it compiles.
  */







|
|







40123
40124
40125
40126
40127
40128
40129
40130
40131
40132
40133
40134
40135
40136
40137
40138
     been extended since this code was ported:

     void *fossil_utf8_to_path(const char *zUtf8, int isDir)

     That isDir param is only for Windows and its only purpose is to
     ensure that the translated path is not within 12 bytes of
     MAX_PATH. That same effect can be had by simply always assuming
     that bool is true and sacrificing those 12 bytes and that far-edge
     case.

     Also, the newer code jumps through many hoops which seem
     unimportant for fossil, e.g. handling UNC-style paths.

     Porting that latter bit over requires someone who can at least
     test whether it compiles.
  */
42172
42173
42174
42175
42176
42177
42178
42179
42180
42181
42182
42183
42184
42185
42186
  assert(dbC && "Must only be called when a checkout is opened.");
  assert(dbR && "Must only be called when a repo is opened.");
  if(!dbC) return FSL_RC_NOT_A_CKOUT;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  if(vid<=0) vid = f->ckout.rid;
  assert(vid>=0);

  rc = fsl_cx_transaction_begin(f);
  if(rc) return rc;
  alreadyHad = fsl_db_exists(dbC,
                             "SELECT 1 FROM vfile "
                             "WHERE vid=%" FSL_ID_T_PFMT,
                             vid);
  if(clearOtherVersions){
    /* Reminder to self: DO NOT clear vmerge here. Doing so will break







|







40281
40282
40283
40284
40285
40286
40287
40288
40289
40290
40291
40292
40293
40294
40295
  assert(dbC && "Must only be called when a checkout is opened.");
  assert(dbR && "Must only be called when a repo is opened.");
  if(!dbC) return FSL_RC_NOT_A_CKOUT;
  else if(!dbR) return FSL_RC_NOT_A_REPO;
  if(vid<=0) vid = f->ckout.rid;
  assert(vid>=0);

  rc = fsl_db_transaction_begin(dbC);
  if(rc) return rc;
  alreadyHad = fsl_db_exists(dbC,
                             "SELECT 1 FROM vfile "
                             "WHERE vid=%" FSL_ID_T_PFMT,
                             vid);
  if(clearOtherVersions){
    /* Reminder to self: DO NOT clear vmerge here. Doing so will break
42262
42263
42264
42265
42266
42267
42268
42269
42270
42271
42272
42273
42274
42275
42276
42277
     when changing the checkout. */
  if(!rc && vid>0){
    if(!alreadyHad){
      assert(d.rid>0);
    }
  }
  fsl_deck_finalize(&d);
  if(rc) fsl_cx_transaction_end(f, true);
  else rc = fsl_cx_transaction_end(f, false);
  if(rc && !f->error.code){
    if(dbC->error.code) fsl_cx_uplift_db_error(f, dbC);
    else if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR);
  }
  return rc;
}








|
|







40371
40372
40373
40374
40375
40376
40377
40378
40379
40380
40381
40382
40383
40384
40385
40386
     when changing the checkout. */
  if(!rc && vid>0){
    if(!alreadyHad){
      assert(d.rid>0);
    }
  }
  fsl_deck_finalize(&d);
  if(rc) fsl_db_transaction_rollback(dbC);
  else rc = fsl_db_transaction_commit(dbC);
  if(rc && !f->error.code){
    if(dbC->error.code) fsl_cx_uplift_db_error(f, dbC);
    else if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR);
  }
  return rc;
}

42564
42565
42566
42567
42568
42569
42570
42571
42572
42573
42574
42575
42576
42577
42578
  }
  fsl_stmt_cached_yield(stUpdate);
  fsl_stmt_finalize(&q);
  return rc;
}

int fsl__vfile_to_ckout(fsl_cx * const f, fsl_id_t vfileId,
                        int * wasWritten){
  int rc = 0;
  fsl_db * const db = fsl_needs_ckout(f);
  fsl_stmt q = fsl_stmt_empty;
  int counter = 0;
  fsl_buffer content = fsl_buffer_empty;
  char const * sql;
  fsl_id_t qArg;







|







40673
40674
40675
40676
40677
40678
40679
40680
40681
40682
40683
40684
40685
40686
40687
  }
  fsl_stmt_cached_yield(stUpdate);
  fsl_stmt_finalize(&q);
  return rc;
}

int fsl__vfile_to_ckout(fsl_cx * const f, fsl_id_t vfileId,
                       int * wasWritten){
  int rc = 0;
  fsl_db * const db = fsl_needs_ckout(f);
  fsl_stmt q = fsl_stmt_empty;
  int counter = 0;
  fsl_buffer content = fsl_buffer_empty;
  char const * sql;
  fsl_id_t qArg;
42703
42704
42705
42706
42707
42708
42709
42710
42711
42712
42713
42714
42715
42716
42717
42718
42719
42720
42721
42722
42723
42724
42725
42726
42727
42728
42729
  }    
  end:
  fsl_buffer_clear(&content);
  fsl_stmt_finalize(&q);
  return rc;
}

int fsl_vfile_pathname(fsl_cx * const f, fsl_id_t vfid, bool absolute, char **zOut){
  assert(f->ckout.dir);
  fsl_db * const db = fsl_cx_db_ckout(f);
  assert(db);
  int const rc = fsl_db_get_text(db, zOut, NULL,
                                 "SELECT %Q || pathname FROM vfile "
                                 "WHERE id=%"FSL_ID_T_PFMT,
                                 absolute ? f->ckout.dir : "",
                                 vfid);
  if(rc) fsl_cx_uplift_db_error(f, db);
  return rc;
}

#undef MARKER
/* end of file ./src/vfile.c */
/* start of file ./src/vpath.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt







<
<
<
<
<
<
<
<
<
<
<
<
<







40812
40813
40814
40815
40816
40817
40818













40819
40820
40821
40822
40823
40824
40825
  }    
  end:
  fsl_buffer_clear(&content);
  fsl_stmt_finalize(&q);
  return rc;
}














#undef MARKER
/* end of file ./src/vfile.c */
/* start of file ./src/vpath.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt
43389
43390
43391
43392
43393
43394
43395






43396
43397
43398
43399
43400
43401
43402
43403
43404
43405
43406
43407
43408
43409
43410
43411
43412
43413
43414
43415
43416
43417
43418
43419
43420
43421
43422
43423
43424
43425
43426
43427
43428
43429
43430
43431
43432
43433
43434
43435
43436
43437
43438
43439
43440
43441
43442
43443
43444

43445
43446
43447
43448
43449
43450
43451
43452
43453
43454
43455
43456
43457
43458
43459
43460
43461
43462
43463
43464
43465
43466
43467
43468
43469
43470
43471
43472
43473
43474
43475
43476
43477
43478
43479
43480
43481
43482
43483
43484
43485
43486
43487
43488
43489
43490
43491
43492
43493
43494
43495
43496
43497
43498
43499
43500
43501
43502
43503
43504
43505
43506


43507

43508
43509
43510
43511
43512
43513
43514
43515
43516
43517
43518
43519
43520
43521
43522
43523
43524
43525
43526
43527
43528
43529

43530
43531
43532
43533
43534
43535
43536
43537

43538
43539
43540
43541
43542
43543
43544
   This copy has been modified slightly, and expanded, for use
   with the libfossil project.
*/
#include <assert.h>
#include <zlib.h>
#include <stdlib.h> /* atoi() and friends */
#include <memory.h> /* memset() */







/*
   Write a 16- or 32-bit integer as little-endian into the given buffer.
*/
static void fzip__put16(char *z, int v){
  z[0] = v & 0xff;
  z[1] = (v>>8) & 0xff;
}
static void fzip__put32(char *z, int v){
  z[0] = v & 0xff;
  z[1] = (v>>8) & 0xff;
  z[2] = (v>>16) & 0xff;
  z[3] = (v>>24) & 0xff;
}

/**
    Set the date and time values from an ISO8601 date string.
 */
static void fzip__timestamp_from_str(fsl_zip_writer *z, const char *zDate){
  int y, m, d;
  int H, M, S;

  y = atoi(zDate);
  m = atoi(&zDate[5]);
  d = atoi(&zDate[8]);
  H = atoi(&zDate[11]);
  M = atoi(&zDate[14]);
  S = atoi(&zDate[17]);
  z->dosTime = (H<<11) + (M<<5) + (S>>1);
  z->dosDate = ((y-1980)<<9) + (m<<5) + d;
}

fsl_buffer const * fsl_zip_body( fsl_zip_writer const * const z ){
  return &z->body;
}

void fsl_zip_timestamp_set_julian(fsl_zip_writer * const z, double rDate){
  char buf[20] = {0};
  fsl_julian_to_iso8601(rDate, buf, 0);
  fzip__timestamp_from_str(z, buf);
  z->unixTime = (fsl_time_t)((rDate - 2440587.5)*86400.0);
}

void fsl_zip_timestamp_set_unix(fsl_zip_writer * const z, fsl_time_t epochTime){
  char buf[20] = {0};
  fsl_julian_to_iso8601(fsl_unix_to_julian(epochTime), buf, 0);
  fzip__timestamp_from_str(z, buf);
  z->unixTime = epochTime;
}


/**
    Adds all directories for the given file to the zip if they are not
    in there already. Returns 0 on success, non-0 on error (namely
    OOM).
 */
static int fzip__mkdir(fsl_zip_writer * const z, char const *zName);

/**
    Adds a file entry to zw's zip output. zName is the virtual name of
    the file or directory. If pSrc is NULL then it is assumed that we
    are creating a directory, otherwise the zip's entry is populated
    from pSrc. mPerms specify the fossil-specific permission flags
    from the fsl_fileperm_e enum. If doMkDirs is true then fzip__mkdir()
    is called to create the directory entries for zName, otherwise
    they are not.
 */
static int fzip__file_add(fsl_zip_writer * const zw, char const * zName,
                          fsl_buffer const * pSrc, int mPerm,
                          char doMkDirs){
  int rc = 0;
  z_stream stream;
  fsl_size_t nameLen;
  int toOut = 0;
  int iStart;
  unsigned int iCRC = 0;
  int nByte = 0;
  int nByteCompr = 0;
  int nBlob;                 /* Size of the blob */
  int iMethod;               /* Compression method. */
  int iMode = 0644;          /* Access permissions */
  char *z;
  char zHdr[30];
  char zExTime[13];
  char zBuf[100];
  char zOutBuf[/*historical: 100000*/ 1024 * 32];

  /* Fill in as much of the header as we know.
  */
  nBlob = pSrc ? (int)pSrc->used : 0;
  if( pSrc ){ /* a file entry */
    iMethod = pSrc->used ? 8 : 0 /* don't compress 0-byte files */;
    switch( mPerm ){
      case FSL_FILE_PERM_LINK:  iMode = 0120755;   break;
      case FSL_FILE_PERM_EXE:   iMode = 0100755;   break;
      default:         iMode = 0100644;   break;
    }
  }else{ /* a directory entry */
    iMethod = 0;
    iMode = 040755;
  }
  if(doMkDirs){
    rc = fzip__mkdir(zw, zName)
      /* This causes an extraneous run of fzip__mkdir(),
         but it is harmless other than the waste of search
         time */;
    if(rc) return rc;
  }

  if(zw->rootDir){
    fsl_buffer_reuse(&zw->scratch);
    rc = fsl_buffer_appendf(&zw->scratch, "%s%s", zw->rootDir, zName);


    if(rc) return rc;

    zName = fsl_buffer_cstr(&zw->scratch);
  }

  nameLen = fsl_strlen(zName);
  memset(zHdr, 0, sizeof(zHdr));
  fzip__put32(&zHdr[0], 0x04034b50);
  fzip__put16(&zHdr[4], 0x000a);
  fzip__put16(&zHdr[6], 0x0800);
  fzip__put16(&zHdr[8], iMethod);
  fzip__put16(&zHdr[10], zw->dosTime);
  fzip__put16(&zHdr[12], zw->dosDate);
  fzip__put16(&zHdr[26], nameLen);
  fzip__put16(&zHdr[28], 13);

  fzip__put16(&zExTime[0], 0x5455);
  fzip__put16(&zExTime[2], 9);
  zExTime[4] = 3;
  fzip__put32(&zExTime[5], zw->unixTime);
  fzip__put32(&zExTime[9], zw->unixTime);


  /* Write the header and filename. */

  iStart = (int)zw->body.used;
  fsl_buffer_append(&zw->body, zHdr, 30);
  fsl_buffer_append(&zw->body, zName, nameLen);
  fsl_buffer_append(&zw->body, zExTime, 13);

  if(zw->body.errCode) return zw->body.errCode;
  else if( nBlob>0 ){
    /* Write the compressed file.  Compute the CRC as we progress. */

    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;
    stream.opaque = 0;
    stream.avail_in = pSrc->used;
    stream.next_in = /* (unsigned char*) */pSrc->mem;
    stream.avail_out = sizeof(zOutBuf);
    stream.next_out = (unsigned char*)zOutBuf;







>
>
>
>
>
>




|



|









|














|





|






|


>






|






|



|
|
|





|









|
















|
|






|

>
>
|
>





|
|
|
|
|
|
|
|

|
|

|
|


|
>





<
|
|
>







41485
41486
41487
41488
41489
41490
41491
41492
41493
41494
41495
41496
41497
41498
41499
41500
41501
41502
41503
41504
41505
41506
41507
41508
41509
41510
41511
41512
41513
41514
41515
41516
41517
41518
41519
41520
41521
41522
41523
41524
41525
41526
41527
41528
41529
41530
41531
41532
41533
41534
41535
41536
41537
41538
41539
41540
41541
41542
41543
41544
41545
41546
41547
41548
41549
41550
41551
41552
41553
41554
41555
41556
41557
41558
41559
41560
41561
41562
41563
41564
41565
41566
41567
41568
41569
41570
41571
41572
41573
41574
41575
41576
41577
41578
41579
41580
41581
41582
41583
41584
41585
41586
41587
41588
41589
41590
41591
41592
41593
41594
41595
41596
41597
41598
41599
41600
41601
41602
41603
41604
41605
41606
41607
41608
41609
41610
41611
41612
41613
41614
41615
41616
41617
41618
41619
41620
41621
41622
41623
41624
41625
41626
41627
41628
41629
41630
41631
41632
41633
41634
41635
41636
41637
41638
41639
41640
41641

41642
41643
41644
41645
41646
41647
41648
41649
41650
41651
   This copy has been modified slightly, and expanded, for use
   with the libfossil project.
*/
#include <assert.h>
#include <zlib.h>
#include <stdlib.h> /* atoi() and friends */
#include <memory.h> /* memset() */

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


/*
   Write a 16- or 32-bit integer as little-endian into the given buffer.
*/
static void fzip_put16(char *z, int v){
  z[0] = v & 0xff;
  z[1] = (v>>8) & 0xff;
}
static void fzip_put32(char *z, int v){
  z[0] = v & 0xff;
  z[1] = (v>>8) & 0xff;
  z[2] = (v>>16) & 0xff;
  z[3] = (v>>24) & 0xff;
}

/**
    Set the date and time values from an ISO8601 date string.
 */
static void fzip_timestamp_from_str(fsl_zip_writer *z, const char *zDate){
  int y, m, d;
  int H, M, S;

  y = atoi(zDate);
  m = atoi(&zDate[5]);
  d = atoi(&zDate[8]);
  H = atoi(&zDate[11]);
  M = atoi(&zDate[14]);
  S = atoi(&zDate[17]);
  z->dosTime = (H<<11) + (M<<5) + (S>>1);
  z->dosDate = ((y-1980)<<9) + (m<<5) + d;
}

fsl_buffer const * fsl_zip_body( fsl_zip_writer const * const z ){
  return z ? &z->body : NULL;
}

void fsl_zip_timestamp_set_julian(fsl_zip_writer * const z, double rDate){
  char buf[20] = {0};
  fsl_julian_to_iso8601(rDate, buf, 0);
  fzip_timestamp_from_str(z, buf);
  z->unixTime = (fsl_time_t)((rDate - 2440587.5)*86400.0);
}

void fsl_zip_timestamp_set_unix(fsl_zip_writer * const z, fsl_time_t epochTime){
  char buf[20] = {0};
  fsl_julian_to_iso8601(fsl_unix_to_julian(epochTime), buf, 0);
  fzip_timestamp_from_str(z, buf);
  z->unixTime = epochTime;
}


/**
    Adds all directories for the given file to the zip if they are not
    in there already. Returns 0 on success, non-0 on error (namely
    OOM).
 */
static int fzip_mkdir(fsl_zip_writer * const z, char const *zName);

/**
    Adds a file entry to zw's zip output. zName is the virtual name of
    the file or directory. If pSrc is NULL then it is assumed that we
    are creating a directory, otherwise the zip's entry is populated
    from pSrc. mPerms specify the fossil-specific permission flags
    from the fsl_fileperm_e enum. If doMkDirs is true then fzip_mkdir()
    is called to create the directory entries for zName, otherwise
    they are not.
 */
static int fzip_file_add(fsl_zip_writer * const zw, char const * zName,
                         fsl_buffer const * pSrc, int mPerm,
                         char doMkDirs){
  int rc = 0;
  z_stream stream;
  fsl_size_t nameLen;
  int toOut = 0;
  int iStart;
  int iCRC = 0;
  int nByte = 0;
  int nByteCompr = 0;
  int nBlob;                 /* Size of the blob */
  int iMethod;               /* Compression method. */
  int iMode = 0644;          /* Access permissions */
  char *z;
  char zHdr[30];
  char zExTime[13];
  char zBuf[100];
  char zOutBuf[/*historical: 100000*/ 1024 * 16];

  /* Fill in as much of the header as we know.
  */
  nBlob = pSrc ? (int)pSrc->used : 0;
  if( pSrc ){ /* a file entry */
    iMethod = pSrc->used ? 8 : 0 /* don't compress 0-byte files */;
    switch( mPerm ){
      case FSL_FILE_PERM_LINK:  iMode = 0120755;   break;
      case FSL_FILE_PERM_EXE:   iMode = 0100755;   break;
      default:         iMode = 0100644;   break;
    }
  }else{ /* a directory entry */
    iMethod = 0;
    iMode = 040755;
  }
  if(doMkDirs){
    rc = fzip_mkdir(zw, zName)
      /* This causes an extraneous run of fzip_mkdir(),
         but it is harmless other than the waste of search
         time */;
    if(rc) return rc;
  }

  if(zw->rootDir){
    zw->scratch.used = 0;
    rc = fsl_buffer_appendf(&zw->scratch, "%s%s", zw->rootDir, zName);
    if(rc){
      assert(FSL_RC_OOM==rc);
      return rc;
    }
    zName = fsl_buffer_cstr(&zw->scratch);
  }

  nameLen = fsl_strlen(zName);
  memset(zHdr, 0, sizeof(zHdr));
  fzip_put32(&zHdr[0], 0x04034b50);
  fzip_put16(&zHdr[4], 0x000a);
  fzip_put16(&zHdr[6], 0x0800);
  fzip_put16(&zHdr[8], iMethod);
  fzip_put16(&zHdr[10], zw->dosTime);
  fzip_put16(&zHdr[12], zw->dosDate);
  fzip_put16(&zHdr[26], nameLen);
  fzip_put16(&zHdr[28], 13);

  fzip_put16(&zExTime[0], 0x5455);
  fzip_put16(&zExTime[2], 9);
  zExTime[4] = 3;
  fzip_put32(&zExTime[5], zw->unixTime);
  fzip_put32(&zExTime[9], zw->unixTime);


  /* Write the header and filename.
  */
  iStart = (int)zw->body.used;
  fsl_buffer_append(&zw->body, zHdr, 30);
  fsl_buffer_append(&zw->body, zName, nameLen);
  fsl_buffer_append(&zw->body, zExTime, 13);


  if( nBlob>0 ){
    /* Write the compressed file.  Compute the CRC as we progress.
    */
    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;
    stream.opaque = 0;
    stream.avail_in = pSrc->used;
    stream.next_in = /* (unsigned char*) */pSrc->mem;
    stream.avail_out = sizeof(zOutBuf);
    stream.next_out = (unsigned char*)zOutBuf;
43553
43554
43555
43556
43557
43558
43559
43560
43561
43562
43563
43564
43565
43566
43567
43568
43569
43570
43571
43572
43573
43574

43575
43576
43577
43578
43579
43580
43581
43582
43583
43584
43585
43586
43587
43588
43589
43590
43591
43592
43593
43594
43595
43596
43597

43598
43599
43600
43601
43602
43603
43604
43605
43606
43607
43608
43609
43610
43611
43612
43613
43614
43615
43616
43617
43618
43619
43620
43621
43622
43623
43624
43625
43626
43627
43628
43629
43630
43631
43632
43633
43634
43635
43636
43637
43638
    }
    do{
      stream.avail_out = sizeof(zOutBuf);
      stream.next_out = (unsigned char*)zOutBuf;
      deflate(&stream, Z_FINISH);
      toOut = sizeof(zOutBuf) - stream.avail_out;
      fsl_buffer_append(&zw->body, zOutBuf, toOut);
    }while( stream.avail_out==0 && 0==zw->body.errCode );
    if(zw->body.errCode) return zw->body.errCode;
    nByte = stream.total_in;
    nByteCompr = stream.total_out;
    deflateEnd(&stream);

    /* Go back and write the header, now that we know the compressed
       file size. */
    z = (char *)zw->body.mem + iStart;
    fzip__put32(&z[14], iCRC);
    fzip__put32(&z[18], nByteCompr);
    fzip__put32(&z[22], nByte);
  }

  /* Make an entry in the tables of contents */

  memset(zBuf, 0, sizeof(zBuf));
  fzip__put32(&zBuf[0], 0x02014b50);
  fzip__put16(&zBuf[4], 0x0317);
  fzip__put16(&zBuf[6], 0x000a);
  fzip__put16(&zBuf[8], 0x0800);
  fzip__put16(&zBuf[10], iMethod);
  fzip__put16(&zBuf[12], zw->dosTime);
  fzip__put16(&zBuf[14], zw->dosDate);
  fzip__put32(&zBuf[16], iCRC);
  fzip__put32(&zBuf[20], nByteCompr);
  fzip__put32(&zBuf[24], nByte);
  fzip__put16(&zBuf[28], nameLen);
  fzip__put16(&zBuf[30], 9);
  fzip__put16(&zBuf[32], 0);
  fzip__put16(&zBuf[34], 0);
  fzip__put16(&zBuf[36], 0);
  fzip__put32(&zBuf[38], ((unsigned)iMode)<<16);
  fzip__put32(&zBuf[42], iStart);
  fsl_buffer_append(&zw->toc, zBuf, 46);
  fsl_buffer_append(&zw->toc, zName, nameLen);
  fzip__put16(&zExTime[2], 5);
  fsl_buffer_append(&zw->toc, zExTime, 9);
  ++zw->entryCount;

  return zw->toc.errCode;
}

int fzip__mkdir(fsl_zip_writer * const z, char const *zName){
  fsl_size_t i;
  fsl_size_t j;
  int rc = 0;
  char const * dirName;
  fsl_size_t nDir = z->dirs.used;
  for(i=0; zName[i]; i++){
    if( zName[i]=='/' ){
      while(zName[i+1]=='/') ++i /* Skip extra slashes */;
      for(j=0; j<nDir; j++){
        /* See if we know this dir already... */
        dirName = (char const *)z->dirs.list[j];
        if( fsl_strncmp(zName, dirName, i)==0 ) break;
      }
      if( j>=nDir ){
        char * cp = fsl_strndup(zName, (fsl_int_t)i+1);
        rc = cp ? fsl_list_append(&z->dirs, cp) : FSL_RC_OOM;
        if(cp && rc){
          fsl_free(cp);
        }else{
          rc = fzip__file_add(z, cp, NULL, 0, 0);
        }
      }
    }
  }
  return rc;
}

int fsl_zip_file_add(fsl_zip_writer * const z, char const * zName,
                     fsl_buffer const * pSrc, int mPerm){
  return fzip__file_add(z, zName, pSrc, mPerm, 1);
}

int fsl_zip_root_set(fsl_zip_writer * const z, char const * zRoot ){
  if(!z) return FSL_RC_MISUSE;
  else if(zRoot && *zRoot && fsl_is_absolute_path(zRoot)){
    return FSL_RC_RANGE;
  }else{







|
<




|
|
|
|
|
|


|
>

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|


|


>
|


|



















|









|







41660
41661
41662
41663
41664
41665
41666
41667

41668
41669
41670
41671
41672
41673
41674
41675
41676
41677
41678
41679
41680
41681
41682
41683
41684
41685
41686
41687
41688
41689
41690
41691
41692
41693
41694
41695
41696
41697
41698
41699
41700
41701
41702
41703
41704
41705
41706
41707
41708
41709
41710
41711
41712
41713
41714
41715
41716
41717
41718
41719
41720
41721
41722
41723
41724
41725
41726
41727
41728
41729
41730
41731
41732
41733
41734
41735
41736
41737
41738
41739
41740
41741
41742
41743
41744
41745
41746
    }
    do{
      stream.avail_out = sizeof(zOutBuf);
      stream.next_out = (unsigned char*)zOutBuf;
      deflate(&stream, Z_FINISH);
      toOut = sizeof(zOutBuf) - stream.avail_out;
      fsl_buffer_append(&zw->body, zOutBuf, toOut);
    }while( stream.avail_out==0 );

    nByte = stream.total_in;
    nByteCompr = stream.total_out;
    deflateEnd(&stream);

    /* Go back and write the header, now that we know the compressed file size.
    */
    z = (char *)zw->body.mem + iStart/* &blob_buffer(&body)[iStart] */;
    fzip_put32(&z[14], iCRC);
    fzip_put32(&z[18], nByteCompr);
    fzip_put32(&z[22], nByte);
  }

  /* Make an entry in the tables of contents
  */
  memset(zBuf, 0, sizeof(zBuf));
  fzip_put32(&zBuf[0], 0x02014b50);
  fzip_put16(&zBuf[4], 0x0317);
  fzip_put16(&zBuf[6], 0x000a);
  fzip_put16(&zBuf[8], 0x0800);
  fzip_put16(&zBuf[10], iMethod);
  fzip_put16(&zBuf[12], zw->dosTime);
  fzip_put16(&zBuf[14], zw->dosDate);
  fzip_put32(&zBuf[16], iCRC);
  fzip_put32(&zBuf[20], nByteCompr);
  fzip_put32(&zBuf[24], nByte);
  fzip_put16(&zBuf[28], nameLen);
  fzip_put16(&zBuf[30], 9);
  fzip_put16(&zBuf[32], 0);
  fzip_put16(&zBuf[34], 0);
  fzip_put16(&zBuf[36], 0);
  fzip_put32(&zBuf[38], ((unsigned)iMode)<<16);
  fzip_put32(&zBuf[42], iStart);
  fsl_buffer_append(&zw->toc, zBuf, 46);
  fsl_buffer_append(&zw->toc, zName, nameLen);
  fzip_put16(&zExTime[2], 5);
  fsl_buffer_append(&zw->toc, zExTime, 9);
  ++zw->entryCount;

  return rc;
}

int fzip_mkdir(fsl_zip_writer * const z, char const *zName){
  fsl_size_t i;
  fsl_size_t j;
  int rc = 0;
  char const * dirName;
  fsl_size_t nDir = z->dirs.used;
  for(i=0; zName[i]; i++){
    if( zName[i]=='/' ){
      while(zName[i+1]=='/') ++i /* Skip extra slashes */;
      for(j=0; j<nDir; j++){
        /* See if we know this dir already... */
        dirName = (char const *)z->dirs.list[j];
        if( fsl_strncmp(zName, dirName, i)==0 ) break;
      }
      if( j>=nDir ){
        char * cp = fsl_strndup(zName, (fsl_int_t)i+1);
        rc = cp ? fsl_list_append(&z->dirs, cp) : FSL_RC_OOM;
        if(cp && rc){
          fsl_free(cp);
        }else{
          rc = fzip_file_add(z, cp, NULL, 0, 0);
        }
      }
    }
  }
  return rc;
}

int fsl_zip_file_add(fsl_zip_writer * const z, char const * zName,
                     fsl_buffer const * pSrc, int mPerm){
  return fzip_file_add(z, zName, pSrc, mPerm, 1);
}

int fsl_zip_root_set(fsl_zip_writer * const z, char const * zRoot ){
  if(!z) return FSL_RC_MISUSE;
  else if(zRoot && *zRoot && fsl_is_absolute_path(zRoot)){
    return FSL_RC_RANGE;
  }else{
43666
43667
43668
43669
43670
43671
43672
43673
43674
43675
43676
43677
43678
43679
43680
43681
43682
43683
43684
43685
43686
43687
43688
43689
43690
43691
43692
43693
43694
43695
43696
43697
43698
43699
43700
43701
43702
43703
43704

43705
43706
43707
43708
43709
43710
43711
43712
43713
43714
43715
43716
43717
43718
43719
43720
43721
43722
43723
43724
43725
43726
43727
43728
43729
43730
43731
43732
43733
43734
43735
43736
        n = fsl_file_simplify_name(cp, (fsl_int_t)n, 1);
        assert(n);
        assert('/'==cp[n-1]);
        cp[n-1] = 0;
        rc = fsl_is_simple_pathname(cp, 1);
        cp[n-1] = '/';
        rc = rc
          ? fzip__mkdir(z, cp)
          : FSL_RC_RANGE;
        z->rootDir = cp /* transfer ownership on error as well and let
                           normal downstream clean it up. */;
        return rc;
      }
    }
    return 0;
  }
}

static void fsl__zip_finalize(fsl_zip_writer * const z, bool alsoBody){
  if(z){
    fsl_buffer_clear(&z->toc);
    fsl_buffer_clear(&z->scratch);
    fsl_list_visit_free(&z->dirs, 1);
    assert(NULL==z->dirs.list);
    fsl_free(z->rootDir);
    if(alsoBody){
      fsl_buffer_clear(&z->body);
      *z = fsl_zip_writer_empty;
    }else{
      fsl_buffer cp = z->body;
      *z = fsl_zip_writer_empty;
      z->body = cp;
    }
  }
}

void fsl_zip_finalize(fsl_zip_writer * const z){
  fsl__zip_finalize(z, 1);
}


int fsl_zip_end( fsl_zip_writer * const z ){
  int rc;
  fsl_int_t iTocStart;
  fsl_int_t iTocEnd;
  char zBuf[30];

  iTocStart = (fsl_int_t)z->body.used;
  rc = fsl_buffer_append(&z->body, z->toc.mem, z->toc.used);
  if(rc) return rc;
  fsl_buffer_clear(&z->toc);
  iTocEnd = (fsl_int_t)z->body.used;

  memset(zBuf, 0, sizeof(zBuf));
  fzip__put32(&zBuf[0], 0x06054b50);
  fzip__put16(&zBuf[4], 0);
  fzip__put16(&zBuf[6], 0);
  fzip__put16(&zBuf[8], (int)z->entryCount);
  fzip__put16(&zBuf[10], (int)z->entryCount);
  fzip__put32(&zBuf[12], iTocEnd - iTocStart);
  fzip__put32(&zBuf[16], iTocStart);
  fzip__put16(&zBuf[20], 0);

  rc = fsl_buffer_append(&z->body, zBuf, 22);
  fsl__zip_finalize(z, 0);
  assert(z->body.used);
  return rc;
}

int fsl_zip_end_take( fsl_zip_writer * const z, fsl_buffer * dest ){
  if(!z) return FSL_RC_MISUSE;
  else{







|










|


















|

>














|
|
|
|
|
|
|
|


|







41774
41775
41776
41777
41778
41779
41780
41781
41782
41783
41784
41785
41786
41787
41788
41789
41790
41791
41792
41793
41794
41795
41796
41797
41798
41799
41800
41801
41802
41803
41804
41805
41806
41807
41808
41809
41810
41811
41812
41813
41814
41815
41816
41817
41818
41819
41820
41821
41822
41823
41824
41825
41826
41827
41828
41829
41830
41831
41832
41833
41834
41835
41836
41837
41838
41839
41840
41841
41842
41843
41844
41845
        n = fsl_file_simplify_name(cp, (fsl_int_t)n, 1);
        assert(n);
        assert('/'==cp[n-1]);
        cp[n-1] = 0;
        rc = fsl_is_simple_pathname(cp, 1);
        cp[n-1] = '/';
        rc = rc
          ? fzip_mkdir(z, cp)
          : FSL_RC_RANGE;
        z->rootDir = cp /* transfer ownership on error as well and let
                           normal downstream clean it up. */;
        return rc;
      }
    }
    return 0;
  }
}

static void fsl_zip_finalize_impl(fsl_zip_writer * const z, bool alsoBody){
  if(z){
    fsl_buffer_clear(&z->toc);
    fsl_buffer_clear(&z->scratch);
    fsl_list_visit_free(&z->dirs, 1);
    assert(NULL==z->dirs.list);
    fsl_free(z->rootDir);
    if(alsoBody){
      fsl_buffer_clear(&z->body);
      *z = fsl_zip_writer_empty;
    }else{
      fsl_buffer cp = z->body;
      *z = fsl_zip_writer_empty;
      z->body = cp;
    }
  }
}

void fsl_zip_finalize(fsl_zip_writer * const z){
  fsl_zip_finalize_impl(z, 1);
}


int fsl_zip_end( fsl_zip_writer * const z ){
  int rc;
  fsl_int_t iTocStart;
  fsl_int_t iTocEnd;
  char zBuf[30];

  iTocStart = (fsl_int_t)z->body.used;
  rc = fsl_buffer_append(&z->body, z->toc.mem, z->toc.used);
  if(rc) return rc;
  fsl_buffer_clear(&z->toc);
  iTocEnd = (fsl_int_t)z->body.used;

  memset(zBuf, 0, sizeof(zBuf));
  fzip_put32(&zBuf[0], 0x06054b50);
  fzip_put16(&zBuf[4], 0);
  fzip_put16(&zBuf[6], 0);
  fzip_put16(&zBuf[8], (int)z->entryCount);
  fzip_put16(&zBuf[10], (int)z->entryCount);
  fzip_put32(&zBuf[12], iTocEnd - iTocStart);
  fzip_put32(&zBuf[16], iTocStart);
  fzip_put16(&zBuf[20], 0);

  rc = fsl_buffer_append(&z->body, zBuf, 22);
  fsl_zip_finalize_impl(z, 0);
  assert(z->body.used);
  return rc;
}

int fsl_zip_end_take( fsl_zip_writer * const z, fsl_buffer * dest ){
  if(!z) return FSL_RC_MISUSE;
  else{
43760
43761
43762
43763
43764
43765
43766


43767
43768
43769
43770
43771
43772
43773
        rc = fsl_buffer_to_filename(&z->body, filename);
      }
    }
    fsl_zip_finalize( z );
    return rc;
  }
}



struct ZipState{
  fsl_cx * f;
  fsl_id_t vid;
  fsl_card_F_visitor_f progress;
  void * progressState;
  fsl_zip_writer z;







>
>







41869
41870
41871
41872
41873
41874
41875
41876
41877
41878
41879
41880
41881
41882
41883
41884
        rc = fsl_buffer_to_filename(&z->body, filename);
      }
    }
    fsl_zip_finalize( z );
    return rc;
  }
}



struct ZipState{
  fsl_cx * f;
  fsl_id_t vid;
  fsl_card_F_visitor_f progress;
  void * progressState;
  fsl_zip_writer z;
43781
43782
43783
43784
43785
43786
43787

43788
43789
43790
43791
43792
43793
43794
43795
};

static int fsl_card_F_visitor_zip(fsl_card_F const * fc,
                                   void * state){
  ZipState * zs = (ZipState *)state;
  fsl_id_t frid;
  int rc = 0;

  if(zs->progress){
    rc = (*zs->progress)(fc, zs->progressState);
    if(rc) return rc;
  }else if(FSL_FILE_PERM_LINK == fc->perm){
    return fsl_cx_err_set(zs->f, FSL_RC_NYI,
                          "Symlinks are not yet supported "
                          "in ZIP output.");
  }







>
|







41892
41893
41894
41895
41896
41897
41898
41899
41900
41901
41902
41903
41904
41905
41906
41907
};

static int fsl_card_F_visitor_zip(fsl_card_F const * fc,
                                   void * state){
  ZipState * zs = (ZipState *)state;
  fsl_id_t frid;
  int rc = 0;
  if(!fc->uuid) return 0 /* file was removed in this (delta) manifest */;
  else if(zs->progress){
    rc = (*zs->progress)(fc, zs->progressState);
    if(rc) return rc;
  }else if(FSL_FILE_PERM_LINK == fc->perm){
    return fsl_cx_err_set(zs->f, FSL_RC_NYI,
                          "Symlinks are not yet supported "
                          "in ZIP output.");
  }
43804
43805
43806
43807
43808
43809
43810
43811
43812
43813
43814
43815
43816
43817
43818
43819
43820
43821
43822

43823
43824
43825
43826
43827
43828
43829
    rc = fsl_mtime_of_manifest_file(zs->f, zs->vid, frid, &mTime);
    if(!rc){
      fsl_zip_timestamp_set_unix(&zs->z, mTime);
      zs->cbuf.used = 0;
      rc = fsl_content_get(zs->f, frid, &zs->cbuf);
      if(!rc){
        rc = fsl_zip_file_add(&zs->z, fc->name, &zs->cbuf,
                              fc->perm);
        if(rc){
          fsl_cx_err_set(zs->f, rc,
                         "Error %s adding file [%s] "
                         "to zip.", fsl_rc_cstr(rc),
                         fc->name);
        }
      }
    }
  }
  return rc;
}


int fsl_repo_zip_sym_to_filename( fsl_cx * const f, char const * sym,
                                  char const *  rootDir,
                                  char const * fileName,
                                  fsl_card_F_visitor_f progress,
                                  void * progressState ){
  int rc;







|











>







41916
41917
41918
41919
41920
41921
41922
41923
41924
41925
41926
41927
41928
41929
41930
41931
41932
41933
41934
41935
41936
41937
41938
41939
41940
41941
41942
    rc = fsl_mtime_of_manifest_file(zs->f, zs->vid, frid, &mTime);
    if(!rc){
      fsl_zip_timestamp_set_unix(&zs->z, mTime);
      zs->cbuf.used = 0;
      rc = fsl_content_get(zs->f, frid, &zs->cbuf);
      if(!rc){
        rc = fsl_zip_file_add(&zs->z, fc->name, &zs->cbuf,
                              FSL_FILE_PERM_REGULAR);
        if(rc){
          fsl_cx_err_set(zs->f, rc,
                         "Error %s adding file [%s] "
                         "to zip.", fsl_rc_cstr(rc),
                         fc->name);
        }
      }
    }
  }
  return rc;
}


int fsl_repo_zip_sym_to_filename( fsl_cx * const f, char const * sym,
                                  char const *  rootDir,
                                  char const * fileName,
                                  fsl_card_F_visitor_f progress,
                                  void * progressState ){
  int rc;
43899
43900
43901
43902
43903
43904
43905




43906
43907
43908
43909
43910
43911
43912
                   rc, fsl_rc_cstr(rc));
  }
  fsl_buffer_clear(&zs.cbuf);
  fsl_zip_finalize(&zs.z);
  fsl_deck_clean(&mf);
  return rc;
}




/* end of file ./src/zip.c */
/* start of file ./src/difftk_cstr.c */
/** @page page_difftk_cstr difftk.tcl

Binary form of file ./src/difftk.tcl.

*/







>
>
>
>







42012
42013
42014
42015
42016
42017
42018
42019
42020
42021
42022
42023
42024
42025
42026
42027
42028
42029
                   rc, fsl_rc_cstr(rc));
  }
  fsl_buffer_clear(&zs.cbuf);
  fsl_zip_finalize(&zs.z);
  fsl_deck_clean(&mf);
  return rc;
}



#undef MARKER
/* end of file ./src/zip.c */
/* start of file ./src/difftk_cstr.c */
/** @page page_difftk_cstr difftk.tcl

Binary form of file ./src/difftk.tcl.

*/

Changes to lib/libfossil.h.

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285

/**
   If true, the fsl_timer_xxx() family of functions might do something useful,
   otherwise they do not.
 */
#define FSL_CONFIG_ENABLE_TIMER 1

#if !defined(FSL_SWITCH_FALL_THROUGH)
#  if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7)
/*
  #define FSL_USING_GCC

  gcc v7+ treats implicit 'switch' fallthrough as a warning
  (i.e. error because we always build with -Wall -Werror -Wextra
  -pedantic). Because now it's apparently considered modern to warn
  for using perfectly valid features of the language. Holy cow, guys,
  what the hell were you thinking!?!?!?

  Similarly braindead, clang #defines __GNUC__.

  _Sigh_.
*/
#    define FSL_SWITCH_FALL_THROUGH __attribute__ ((fallthrough))
#  else
#    define FSL_SWITCH_FALL_THROUGH
#  endif
#endif
/* /FSL_SWITCH_FALL_THROUGH

   TODO: add support for the C++ attributes for doing this.
*/

#if !defined(__unused)
#define __unused
#endif


#endif
/* ORG_FOSSIL_SCM_FSL_CONFIG_H_INCLUDED */
/* end of file ./include/fossil-scm/config.h */
/* start of file ./include/fossil-scm/util.h */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







243
244
245
246
247
248
249





























250
251
252
253
254
255
256

/**
   If true, the fsl_timer_xxx() family of functions might do something useful,
   otherwise they do not.
 */
#define FSL_CONFIG_ENABLE_TIMER 1































#endif
/* ORG_FOSSIL_SCM_FSL_CONFIG_H_INCLUDED */
/* end of file ./include/fossil-scm/config.h */
/* start of file ./include/fossil-scm/util.h */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
   modification made by this routine. Thus the user may need to
   reset the cursor to 0 if he wishes to start consuming the buffer
   at its starting point. Subsequent calls to this function will
   increment the cursor by the number of bytes returned via *n.
   The buffer's "used" member is used to determine the logical end
   of input.

   If fsl_buffer_err() is true for the state argument, that code is
   returned without other side effects.

   Returns 0 on success and has no error conditions except for invalid
   arguments, which result in undefined beavhiour, or fsl_buffer_err()
   being true for the state argument. Results are undefined if any
   argument is NULL.

   Tip (and warning): sometimes a routine might have a const buffer
   handle which it would like to use in conjunction with this
   routine but cannot without violating constness. Here's a crude
   workaround:

   ```







<
<
<
|
|
<
|







736
737
738
739
740
741
742



743
744

745
746
747
748
749
750
751
752
   modification made by this routine. Thus the user may need to
   reset the cursor to 0 if he wishes to start consuming the buffer
   at its starting point. Subsequent calls to this function will
   increment the cursor by the number of bytes returned via *n.
   The buffer's "used" member is used to determine the logical end
   of input.




   Returns 0 on success and has no error conditions except for
   invalid arguments, which result in undefined beavhiour. Results

   are undefined if any argument is NULL.

   Tip (and warning): sometimes a routine might have a const buffer
   handle which it would like to use in conjunction with this
   routine but cannot without violating constness. Here's a crude
   workaround:

   ```
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
   @see fsl_buffer_append()
   @see fsl_buffer_appendf()
   @see fsl_buffer_cstr()
   @see fsl_buffer_size()
   @see fsl_buffer_capacity()
   @see fsl_buffer_clear()
   @see fsl_buffer_reuse()
   @see fsl_buffer_err()
   @see fsl_buffer_err_clear()
*/
struct fsl_buffer {
  /**
     The raw memory pointed to by this buffer. There are two ways of
     using this member:

     - If `this->capacity` is non-0 then the first `this->capacity`







<
<







879
880
881
882
883
884
885


886
887
888
889
890
891
892
   @see fsl_buffer_append()
   @see fsl_buffer_appendf()
   @see fsl_buffer_cstr()
   @see fsl_buffer_size()
   @see fsl_buffer_capacity()
   @see fsl_buffer_clear()
   @see fsl_buffer_reuse()


*/
struct fsl_buffer {
  /**
     The raw memory pointed to by this buffer. There are two ways of
     using this member:

     - If `this->capacity` is non-0 then the first `this->capacity`
969
970
971
972
973
974
975
976
977
978
979
980
981

982
983
984
985
986
987
988
989
990

991

992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
     precondition). Most APIs ensure that (used<capacity) is always
     true (as opposed to used<=capacity) because they add a
     trailing NUL byte which is not counted in the "used" length.
  */
  fsl_size_t used;
  /**
     Used by some routines to keep a cursor into this->mem.
  */
  fsl_size_t cursor;

  /**
     When any buffer-related routines encounter an error, they set
     this flag so that the error can be propagated. Many APIs also

     become no-ops when this is set, the intention being to simplify
     many common uses of this class, e.g.:

     ```
     fsl_buffer_append(b, ... );
     fsl_buffer_append(b, ... );
     fsl_buffer_append(b, ... );
     if(fsl_buffer_err(b)) ...
     ```



     The alternative (without this member) being to check for an OOM
     error after every append operation.
  */
  int errCode;
};

/** Empty-initialized fsl_buffer instance, intended for const-copy
    initialization. */
#define fsl_buffer_empty_m {NULL,0U,0U,0U,0}

/** Empty-initialized fsl_buffer instance, intended for copy
    initialization. */
FSL_EXPORT const fsl_buffer fsl_buffer_empty;

/**
   A container for storing generic error state. It is used to







<
<

<
<
<
>
|
<
|
<
<
<
<
<
<
>

>
|
<

|




|







934
935
936
937
938
939
940


941



942
943

944






945
946
947
948

949
950
951
952
953
954
955
956
957
958
959
960
961
962
     precondition). Most APIs ensure that (used<capacity) is always
     true (as opposed to used<=capacity) because they add a
     trailing NUL byte which is not counted in the "used" length.
  */
  fsl_size_t used;
  /**
     Used by some routines to keep a cursor into this->mem.






     TODO: factor this back out and let those cases keep their own
     state. This is only used by fsl_input_f_buffer() (and that

     function cannot be implemented unless we add the cursor here






     or add another layer of state type specifically for it).

     TODO: No, don't do ^^^^. It turns out that the merge algo
     wants this as well.

  */
  fsl_size_t cursor;
};

/** Empty-initialized fsl_buffer instance, intended for const-copy
    initialization. */
#define fsl_buffer_empty_m {NULL,0U,0U,0U}

/** Empty-initialized fsl_buffer instance, intended for copy
    initialization. */
FSL_EXPORT const fsl_buffer fsl_buffer_empty;

/**
   A container for storing generic error state. It is used to
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245

   z need not be NUL terminated - this function does not read past
   the first invalid byte. Thus is can be used on, e.g., full
   ISO8601-format strings. If z is NULL, 0 is returned.
*/
FSL_EXPORT int fsl_str_is_date2(const char *z);


/**
   Returns the current error code of the given buffer. Many buffer
   APIs become noops when this function returns non-0.
*/
FSL_EXPORT int fsl_buffer_err(fsl_buffer const * b);

/**
   Resets the error code of the given buffer to 0.
*/
FSL_EXPORT void fsl_buffer_err_clear(fsl_buffer * const b);

/**
   Reserves at least n bytes of capacity in buf. Returns 0 on
   success, FSL_RC_OOM if allocation fails, FSL_RC_MISUSE if !buf.

   If fsl_buffer_err() is true and n is not 0 then this function
   returns its value without other side effects, with one exception:
   if n is 0 then the error state is ignored.

   If b is an external buffer then:

   - If n is 0, this disassociates b->mem from b, effectively clearing
     the buffer's state. Else...

   - The buffer is materialized, transformed into a managed buffer.
     This happens even if n is less than b->used because this routine
     is always used in preparation for writing to the buffer.

   - If n>0 then the greater of (n, b->used) bytes of memory are
     allocated, b->used bytes are copied from b->mem (its external
     memory) to the new block, and b->mem is replaced with the new
     block. Afterwards, b->capacity will be non-0.

   This does not change b->used, nor will it shrink the buffer
   (reduce buf->capacity) unless n is 0, in which case it immediately:

   - frees b->mem (if b is a managed buffer)
   - sets b->capacity, buf->used, and b->cursor to 0
   - sets b->errCode to 0

   @see fsl_buffer_resize()
   @see fsl_buffer_materialize()
   @see fsl_buffer_clear()
*/
FSL_EXPORT int fsl_buffer_reserve( fsl_buffer * const b, fsl_size_t n );








<
<
<
<
<
<
<
<
<
<
<
<




<
<
<
<















|
<
|
|
<







1147
1148
1149
1150
1151
1152
1153












1154
1155
1156
1157




1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173

1174
1175

1176
1177
1178
1179
1180
1181
1182

   z need not be NUL terminated - this function does not read past
   the first invalid byte. Thus is can be used on, e.g., full
   ISO8601-format strings. If z is NULL, 0 is returned.
*/
FSL_EXPORT int fsl_str_is_date2(const char *z);













/**
   Reserves at least n bytes of capacity in buf. Returns 0 on
   success, FSL_RC_OOM if allocation fails, FSL_RC_MISUSE if !buf.





   If b is an external buffer then:

   - If n is 0, this disassociates b->mem from b, effectively clearing
     the buffer's state. Else...

   - The buffer is materialized, transformed into a managed buffer.
     This happens even if n is less than b->used because this routine
     is always used in preparation for writing to the buffer.

   - If n>0 then the greater of (n, b->used) bytes of memory are
     allocated, b->used bytes are copied from b->mem (its external
     memory) to the new block, and b->mem is replaced with the new
     block. Afterwards, b->capacity will be non-0.

   This does not change b->used, nor will it shrink the buffer
   (reduce buf->capacity) unless n is 0, in which case it immediately

   frees b->mem (if b is a managed buffer) and sets b->capacity
   and buf->used to 0.


   @see fsl_buffer_resize()
   @see fsl_buffer_materialize()
   @see fsl_buffer_clear()
*/
FSL_EXPORT int fsl_buffer_reserve( fsl_buffer * const b, fsl_size_t n );

1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352

   Results are undefined if mem is NULL, but n may be 0.

   Results are undefined if passed a completely uninitialized buffer
   object. _Always_ initialize new buffer objects by copying
   fsl_buffer_empty or (when appropriate) fsl_buffer_empty_m.

   This function resets b->errCode.

   @see fsl_buffer_materialize()
*/
FSL_EXPORT void fsl_buffer_external( fsl_buffer * const b, void const * mem, fsl_int_t n );

/**
   Convenience equivalent of fsl_buffer_reserve(b,0).
*/
FSL_EXPORT void fsl_buffer_clear( fsl_buffer * const b );

/**
   If b is a managed buffer, this resets b->used, b->cursor, and
   b->mem[0] (if b->mem is not NULL) to 0. If b is an external buffer,
   this clears all state from the buffer, behaving like
   fsl_buffer_clear() (making it available for reuse as a managed or
   external buffer).

   This does not (de)allocate memory, only changes the logical "used"
   size of the buffer. Returns its argument.

   This function resets b->errCode.

   Returns b.

   Achtung for fossil(1) porters: this function's semantics are much
   different from the fossil's blob_reset(). To get those semantics,
   use fsl_buffer_reserve(buf, 0) or its convenience form
   fsl_buffer_clear(). (This function _used_ to be called
   fsl_buffer_reset(), but it was renamed in the hope of avoiding
   related confusion.)
*/
FSL_EXPORT fsl_buffer * fsl_buffer_reuse( fsl_buffer * const b );

/**
   Similar to fsl_buffer_reserve() except that...

   - If fsl_buffer_err() is true, that result is returned with no other
     side effects.

   For managed buffers:

   - It does not free all memory when n==0. Instead it essentially
     makes the memory a length-0, NUL-terminated string.

   - It will try to shrink (realloc) buf's memory if (n<buf->capacity).

   - It sets buf->capacity to (n+1) and buf->used to n. This routine
     allocates one extra byte to ensure that buf is always
     NUL-terminated.

   - On success it always NUL-terminates the buffer at
   offset buf->used.

   For external buffers it behaves slightly differently:

   - If n==buf->used, this is a no-op and returns 0.







<
<



















<
<














<
<
<



|




|
|







1226
1227
1228
1229
1230
1231
1232


1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251


1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265



1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282

   Results are undefined if mem is NULL, but n may be 0.

   Results are undefined if passed a completely uninitialized buffer
   object. _Always_ initialize new buffer objects by copying
   fsl_buffer_empty or (when appropriate) fsl_buffer_empty_m.



   @see fsl_buffer_materialize()
*/
FSL_EXPORT void fsl_buffer_external( fsl_buffer * const b, void const * mem, fsl_int_t n );

/**
   Convenience equivalent of fsl_buffer_reserve(b,0).
*/
FSL_EXPORT void fsl_buffer_clear( fsl_buffer * const b );

/**
   If b is a managed buffer, this resets b->used, b->cursor, and
   b->mem[0] (if b->mem is not NULL) to 0. If b is an external buffer,
   this clears all state from the buffer, behaving like
   fsl_buffer_clear() (making it available for reuse as a managed or
   external buffer).

   This does not (de)allocate memory, only changes the logical "used"
   size of the buffer. Returns its argument.



   Returns b.

   Achtung for fossil(1) porters: this function's semantics are much
   different from the fossil's blob_reset(). To get those semantics,
   use fsl_buffer_reserve(buf, 0) or its convenience form
   fsl_buffer_clear(). (This function _used_ to be called
   fsl_buffer_reset(), but it was renamed in the hope of avoiding
   related confusion.)
*/
FSL_EXPORT fsl_buffer * fsl_buffer_reuse( fsl_buffer * const b );

/**
   Similar to fsl_buffer_reserve() except that...




   For managed buffers:

   - It does not free all memory when n==0. Instead it essentially
   makes the memory a length-0, NUL-terminated string.

   - It will try to shrink (realloc) buf's memory if (n<buf->capacity).

   - It sets buf->capacity to (n+1) and buf->used to n. This routine
   allocates one extra byte to ensure that buf is always
   NUL-terminated.

   - On success it always NUL-terminates the buffer at
   offset buf->used.

   For external buffers it behaves slightly differently:

   - If n==buf->used, this is a no-op and returns 0.
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
   @see fsl_buffer_reserve()
   @see fsl_buffer_materialize()
   @see fsl_buffer_clear()
*/
FSL_EXPORT int fsl_buffer_resize( fsl_buffer * const buf, fsl_size_t n );

/**
   Swaps the entire state of the left and right arguments. Results are
   undefined if either argument is NULL or points to uninitialized
   memory.
*/
FSL_EXPORT void fsl_buffer_swap( fsl_buffer * const left, fsl_buffer * const right );

/**
   Similar fsl_buffer_swap() but it also optionally frees one of
   the buffer's memories after swapping them. If clearWhich is
   negative then the left buffer (1st arg) is cleared _after_
   swapping (i.e., the NEW left hand side gets cleared). If
   clearWhich is greater than 0 then the right buffer (2nd arg) is







|



|







1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
   @see fsl_buffer_reserve()
   @see fsl_buffer_materialize()
   @see fsl_buffer_clear()
*/
FSL_EXPORT int fsl_buffer_resize( fsl_buffer * const buf, fsl_size_t n );

/**
   Swaps the contents of the left and right arguments. Results are
   undefined if either argument is NULL or points to uninitialized
   memory.
*/
FSL_EXPORT void fsl_buffer_swap( fsl_buffer * left, fsl_buffer * right );

/**
   Similar fsl_buffer_swap() but it also optionally frees one of
   the buffer's memories after swapping them. If clearWhich is
   negative then the left buffer (1st arg) is cleared _after_
   swapping (i.e., the NEW left hand side gets cleared). If
   clearWhich is greater than 0 then the right buffer (2nd arg) is
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
   ```
   fsl_buffer_swap_free( &b1, &b2, 1 );
   ```

   Swaps the contents of b1 and b2, then frees the contents
   of the right-side buffer (b2).
*/
FSL_EXPORT void fsl_buffer_swap_free( fsl_buffer * const left,
                                      fsl_buffer * const right,
                                      int clearWhich );  

/**
   Appends the first n bytes of src, plus a NUL byte, to b,
   expanding b as necessary and incrementing b->used by n. If n is
   less than 0 then the equivalent of fsl_strlen((char const*)src)
   is used to calculate the length.

   If fsl_buffer_err() is true, its result is returned without further
   side effects.

   If b is an external buffer, it is first transformed into a
   managed buffer.

   Results are undefined if b or src are NULL.

   If n is 0 (or negative and !*src), this function ensures that
   b->mem is not NULL and is NUL-terminated, so it may allocate







|
<








<
<
<







1327
1328
1329
1330
1331
1332
1333
1334

1335
1336
1337
1338
1339
1340
1341
1342



1343
1344
1345
1346
1347
1348
1349
   ```
   fsl_buffer_swap_free( &b1, &b2, 1 );
   ```

   Swaps the contents of b1 and b2, then frees the contents
   of the right-side buffer (b2).
*/
FSL_EXPORT void fsl_buffer_swap_free( fsl_buffer * left, fsl_buffer * right,

                                      int clearWhich );  

/**
   Appends the first n bytes of src, plus a NUL byte, to b,
   expanding b as necessary and incrementing b->used by n. If n is
   less than 0 then the equivalent of fsl_strlen((char const*)src)
   is used to calculate the length.




   If b is an external buffer, it is first transformed into a
   managed buffer.

   Results are undefined if b or src are NULL.

   If n is 0 (or negative and !*src), this function ensures that
   b->mem is not NULL and is NUL-terminated, so it may allocate
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539

/**
   Uses fsl_appendf() to append formatted output to the given buffer.
   Returns 0 on success and FSL_RC_OOM if an allocation fails while
   expanding dest. Results are undefined if either of the first two
   arguments are NULL.

   If fsl_buffer_err() is true, its result is returned without further
   side effects.

   @see fsl_buffer_append()
   @see fsl_buffer_reserve()
*/
FSL_EXPORT int fsl_buffer_appendf( fsl_buffer * const dest,
                                   char const * fmt, ... );

/** va_list counterpart to fsl_buffer_appendf(). */
FSL_EXPORT int fsl_buffer_appendfv( fsl_buffer * const dest,
                                    char const * fmt, va_list args );

/**
   Compresses the first pIn->used bytes of pIn to pOut. It is ok for
   pIn and pOut to be the same blob.

   If fsl_buffer_err() is true for either buffer, its result is
   returned without further side effects. The buffers are checked in
   the order of their parameter declaration.

   pOut must either be the same as pIn or else a properly
   initialized buffer. Any prior contents will be freed or their
   memory reused.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and
   FSL_RC_ERROR if the lower-level compression routines fail. If this
   function returns non-0, it does not update pOut's error state
   because the error will have happened on a temporary buffer.

   Use fsl_buffer_uncompress() to uncompress the data. The data is
   encoded with a big-endian, unsigned 32-bit length as the first four
   bytes (holding its uncomressed size), and then the data as
   compressed by zlib.

   TODO: if pOut!=pIn1 then re-use pOut's memory, if it has any.

   @see fsl_buffer_compress2()
   @see fsl_buffer_uncompress()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer * const pOut);

/**
   Compress the concatenation of a blobs pIn1 and pIn2 into pOut.

   pOut must be either empty (cleanly initialized or newly
   recycled) or must be the same as either pIn1 or pIn2.

   Results are undefined if any argument is NULL.

   If fsl_buffer_err() is true for any buffer, its result is
   returned without further side effects. The buffers are checked in
   the order of their parameter declaration.

   Returns 0 on success, FSL_RC_OOM on allocation error, and FSL_RC_ERROR
   if the lower-level compression routines fail.

   TODO: if pOut!=(pIn1 or pIn2) then re-use its memory, if it has any.

   @see fsl_buffer_compress()
   @see fsl_buffer_uncompress()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_compress2(fsl_buffer const *pIn1,
                                    fsl_buffer const *pIn2,
                                    fsl_buffer * const pOut);

/**
   Uncompress buffer pIn and store the result in pOut. It is ok for
   pIn and pOut to be the same buffer. Returns 0 on success. If
   pIn!=pOut then on error, depending on the type of error, pOut may
   have been partially written so the state of its contents are
   unspecified (but its state as a buffer object is still valid).

   If fsl_buffer_err() is true for either buffer, its result is
   returned without further side effects. The buffers are checked in
   the order of their parameter declaration.

   pOut must be either cleanly initialized/empty or the same object as
   pIn. If it has any current memory, it will be reused if it's
   large enough and it is not the same pointer as pIn.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and some
   other code if the lower-level decompression routines fail. On
   error, pOut->errCode is updated.

   Note that the decompression process, though computationally costly,
   is a no-op if pIn is not actually compressed.

   As a special case, if pIn==pOut and fsl_buffer_is_compressed() returns
   false for pIn then this is a no-op.








<
<
<














<
<
<
<






|
|
<
<












|









<
<
<
<











|








<
<
<
<






|
|
<







1362
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
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412




1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432




1433
1434
1435
1436
1437
1438
1439
1440

1441
1442
1443
1444
1445
1446
1447

/**
   Uses fsl_appendf() to append formatted output to the given buffer.
   Returns 0 on success and FSL_RC_OOM if an allocation fails while
   expanding dest. Results are undefined if either of the first two
   arguments are NULL.




   @see fsl_buffer_append()
   @see fsl_buffer_reserve()
*/
FSL_EXPORT int fsl_buffer_appendf( fsl_buffer * const dest,
                                   char const * fmt, ... );

/** va_list counterpart to fsl_buffer_appendf(). */
FSL_EXPORT int fsl_buffer_appendfv( fsl_buffer * const dest,
                                    char const * fmt, va_list args );

/**
   Compresses the first pIn->used bytes of pIn to pOut. It is ok for
   pIn and pOut to be the same blob.





   pOut must either be the same as pIn or else a properly
   initialized buffer. Any prior contents will be freed or their
   memory reused.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and FSL_RC_ERROR
   if the lower-level compression routines fail.



   Use fsl_buffer_uncompress() to uncompress the data. The data is
   encoded with a big-endian, unsigned 32-bit length as the first four
   bytes (holding its uncomressed size), and then the data as
   compressed by zlib.

   TODO: if pOut!=pIn1 then re-use pOut's memory, if it has any.

   @see fsl_buffer_compress2()
   @see fsl_buffer_uncompress()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer *pOut);

/**
   Compress the concatenation of a blobs pIn1 and pIn2 into pOut.

   pOut must be either empty (cleanly initialized or newly
   recycled) or must be the same as either pIn1 or pIn2.

   Results are undefined if any argument is NULL.





   Returns 0 on success, FSL_RC_OOM on allocation error, and FSL_RC_ERROR
   if the lower-level compression routines fail.

   TODO: if pOut!=(pIn1 or pIn2) then re-use its memory, if it has any.

   @see fsl_buffer_compress()
   @see fsl_buffer_uncompress()
   @see fsl_buffer_is_compressed()
*/
FSL_EXPORT int fsl_buffer_compress2(fsl_buffer const *pIn1,
                                    fsl_buffer const *pIn2,
                                    fsl_buffer *pOut);

/**
   Uncompress buffer pIn and store the result in pOut. It is ok for
   pIn and pOut to be the same buffer. Returns 0 on success. If
   pIn!=pOut then on error, depending on the type of error, pOut may
   have been partially written so the state of its contents are
   unspecified (but its state as a buffer object is still valid).





   pOut must be either cleanly initialized/empty or the same object as
   pIn. If it has any current memory, it will be reused if it's
   large enough and it is not the same pointer as pIn.

   Results are undefined if any argument is NULL.

   Returns 0 on success, FSL_RC_OOM on allocation error, and
   some other code if the lower-level decompression routines fail.


   Note that the decompression process, though computationally costly,
   is a no-op if pIn is not actually compressed.

   As a special case, if pIn==pOut and fsl_buffer_is_compressed() returns
   false for pIn then this is a no-op.

1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611

1612
1613
1614
1615
1616
1617
1618

/**
   The fsl_buffer counterpart of fsl_data_uncompressed_size().
*/
FSL_EXPORT fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b);

/**
   Equivalent to ((char const *)b->mem) except that if b->errCode is
   non-0, this returns NULL. The returned string is effectively
   b->used bytes long unless the user decides to apply his own
   conventions. Note that the buffer APIs generally assure that
   buffers are NUL-terminated, meaning that strings returned from this
   function can (for the vast majority of cases) assume that the
   returned string is NUL-terminated (with a string length of b->used
   _bytes_). It is, however, possible for client code to violate that
   convention via direct manipulation of the buffer or using
   non-NUL-terminated extranal buffers.

   Results are undefined if b is NULL.

   @see fsl_buffer_str()
   @see fsl_buffer_cstr2()
*/
FSL_EXPORT char const * fsl_buffer_cstr(fsl_buffer const * const b);

/**
   If b has any memory allocated to it and b->errCode is non-0, that
   memory is returned. If len is not NULL then *len is set to
   b->used. If b has no memory or b->errCode is non-0 then NULL is
   returned and *len (if len is not NULL) is set to 0.

   Results are undefined if b is NULL.

   @see fsl_buffer_str()
   @see fsl_buffer_cstr()
*/
FSL_EXPORT char const * fsl_buffer_cstr2(fsl_buffer const * const b, fsl_size_t * const len);

/**
   Equivalent to ((char *)b->mem) except that if b->errCode is non-0
   then NULL is returned. The returned memory is effectively b->used
   bytes long unless the user decides to apply their own conventions.


   Care must be taken to only write to the returned pointer for memory
   owned or write-proxied by this buffer. More specifically, results
   are undefined if b is an external buffer proxying const bytes. When
   in doubt about whether b is external, use fsl_buffer_materialize()
   to transform it to a managed buffer before using this routine,
   noting that any of the public fsl_buffer APIs which write to a







|
<
|
|







<
<






|
|
<
|
<
<







<
|
|
>







1478
1479
1480
1481
1482
1483
1484
1485

1486
1487
1488
1489
1490
1491
1492
1493
1494


1495
1496
1497
1498
1499
1500
1501
1502

1503


1504
1505
1506
1507
1508
1509
1510

1511
1512
1513
1514
1515
1516
1517
1518
1519
1520

/**
   The fsl_buffer counterpart of fsl_data_uncompressed_size().
*/
FSL_EXPORT fsl_int_t fsl_buffer_uncompressed_size(fsl_buffer const * b);

/**
   Equivalent to ((char const *)b->mem). The returned string is

   effectively b->used bytes long unless the user decides to apply his
   own conventions. Note that the buffer APIs generally assure that
   buffers are NUL-terminated, meaning that strings returned from this
   function can (for the vast majority of cases) assume that the
   returned string is NUL-terminated (with a string length of b->used
   _bytes_). It is, however, possible for client code to violate that
   convention via direct manipulation of the buffer or using
   non-NUL-terminated extranal buffers.



   @see fsl_buffer_str()
   @see fsl_buffer_cstr2()
*/
FSL_EXPORT char const * fsl_buffer_cstr(fsl_buffer const * const b);

/**
   If b has any memory allocated to it, that memory is returned. If
   len is not NULL then *len is set to b->used. If b has no memory

   then NULL is returned and *len (if len is not NULL) is set to 0.



   @see fsl_buffer_str()
   @see fsl_buffer_cstr()
*/
FSL_EXPORT char const * fsl_buffer_cstr2(fsl_buffer const * const b, fsl_size_t * const len);

/**

   Equivalent to ((char *)b->mem). The returned memory is effectively
   b->used bytes long unless the user decides to apply their own
   conventions.

   Care must be taken to only write to the returned pointer for memory
   owned or write-proxied by this buffer. More specifically, results
   are undefined if b is an external buffer proxying const bytes. When
   in doubt about whether b is external, use fsl_buffer_materialize()
   to transform it to a managed buffer before using this routine,
   noting that any of the public fsl_buffer APIs which write to a
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680

   When buffers of different length match on the first N bytes,
   where N is the shorter of the two buffers' lengths, it treats the
   shorter buffer as being "less than" the longer one.
*/
FSL_EXPORT int fsl_buffer_compare(fsl_buffer const * const lhs, fsl_buffer const * const rhs);

/**
   Compares b and the first nStr bytes of the given string. If nStr is
   negative, fsl_strlen() is used to calculate it. Returns true if the
   buffer and the string match, as per the rules of
   fsl_buffer_compare(), else returns false.
*/
FSL_EXPORT bool fsl_buffer_eq(fsl_buffer const * const b, char const * str,
                              fsl_int_t nStr);

/**
   Bitwise-compares the contents of b against the file named by
   zFile.  Returns 0 if they have the same size and contents, else
   non-zero.  This function has no way to report if zFile cannot be
   opened, and any error results in a non-0 return value. No
   interpretation/canonicalization of zFile is performed - it is
   used as-is.







<
<
<
<
<
<
<
<
<







1560
1561
1562
1563
1564
1565
1566









1567
1568
1569
1570
1571
1572
1573

   When buffers of different length match on the first N bytes,
   where N is the shorter of the two buffers' lengths, it treats the
   shorter buffer as being "less than" the longer one.
*/
FSL_EXPORT int fsl_buffer_compare(fsl_buffer const * const lhs, fsl_buffer const * const rhs);










/**
   Bitwise-compares the contents of b against the file named by
   zFile.  Returns 0 if they have the same size and contents, else
   non-zero.  This function has no way to report if zFile cannot be
   opened, and any error results in a non-0 return value. No
   interpretation/canonicalization of zFile is performed - it is
   used as-is.
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787


1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802

1803
1804
1805
1806
1807
1808
1809
   Apply the delta in pDelta to the original content pOriginal to
   generate the target content pTarget. All three pointers must point
   to properly initialized memory.

   If pTarget==pOriginal then this is a destructive operation,
   replacing the original's content with its new form.

   If fsl_buffer_err() is true for any buffer, its result is
   returned without further side effects. The buffers are checked in
   the order of their parameter declaration.

   Return 0 on success.

   @see fsl_buffer_delta_apply()
   @see fsl_delta_apply()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_buffer_delta_apply( fsl_buffer const * const pOriginal,
                                       fsl_buffer const * const pDelta,
                                       fsl_buffer * const pTarget);

/**
   Identical to fsl_buffer_delta_apply() except that if delta
   application fails then any error messages/codes are written to
   pErr if it is not NULL. It is rare that delta application fails
   (only if the inputs are invalid, e.g. do not belong together or
   are corrupt), but when it does, having error information can be
   useful.

   If fsl_buffer_err() is true for any buffer, its result is
   returned without further side effects. The buffers are checked in
   the order of their parameter declaration.

   @see fsl_buffer_delta_apply()
   @see fsl_delta_apply()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_buffer_delta_apply2( fsl_buffer const * const pOriginal,
                                        fsl_buffer const * const pDelta,
                                        fsl_buffer * const pTarget,
                                        fsl_error * const pErr);


/**
   Uses a fsl_input_f() function to buffer input into a fsl_buffer.

   dest must be a non-NULL, initialized (though possibly empty)
   fsl_buffer object. Its contents, if any, will be overwritten by
   this function, and any memory it holds might be re-used.

   If fsl_buffer_err() is true then this function returns its value
   without other side effects.

   The src function is called, and passed the state parameter, to
   fetch the input. If it returns non-0, this function returns that
   error code. src() is called, possibly repeatedly, until it
   reports that there is no more data.

   Whether or not this function succeeds, dest still owns any memory
   pointed to by dest->mem, and the client must eventually free it
   by calling fsl_buffer_reserve(dest,0).

   dest->mem might (and possibly will) be (re)allocated by this
   function, so any pointers to it held from before this call might
   be invalidated by this call.

   On error non-0 is returned and dest may be partially populated.

   Errors include:



   - Allocation error (FSL_RC_OOM), in which case dest->errCode is
     updated.

   - src() returns an error code, in which case dest-errCode is _not_
     modified.

   Whether or not the state parameter may be NULL depends on the src
   implementation requirements.

   On success dest will contain the contents read from the input
   source. dest->used will be the length of the read-in data, and
   dest->mem will point to the memory. dest->mem is automatically
   NUL-terminated if this function succeeds, but dest->used does not
   count that terminator. On error the state of dest->mem must be
   considered incomplete, and is not guaranteed to be NUL-terminated.


   Example usage:

   ```
   fsl_buffer buf = fsl_buffer_empty;
   int rc = fsl_buffer_fill_from( &buf,
     fsl_input_f_FILE,







<
<
<
<


















<
<
<
<

















<
<
<













|



>
>
|
<

|
<









|
>







1611
1612
1613
1614
1615
1616
1617




1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635




1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652



1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672

1673
1674

1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
   Apply the delta in pDelta to the original content pOriginal to
   generate the target content pTarget. All three pointers must point
   to properly initialized memory.

   If pTarget==pOriginal then this is a destructive operation,
   replacing the original's content with its new form.





   Return 0 on success.

   @see fsl_buffer_delta_apply()
   @see fsl_delta_apply()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_buffer_delta_apply( fsl_buffer const * const pOriginal,
                                       fsl_buffer const * const pDelta,
                                       fsl_buffer * const pTarget);

/**
   Identical to fsl_buffer_delta_apply() except that if delta
   application fails then any error messages/codes are written to
   pErr if it is not NULL. It is rare that delta application fails
   (only if the inputs are invalid, e.g. do not belong together or
   are corrupt), but when it does, having error information can be
   useful.





   @see fsl_buffer_delta_apply()
   @see fsl_delta_apply()
   @see fsl_delta_apply2()
*/
FSL_EXPORT int fsl_buffer_delta_apply2( fsl_buffer const * const pOriginal,
                                        fsl_buffer const * const pDelta,
                                        fsl_buffer * const pTarget,
                                        fsl_error * const pErr);


/**
   Uses a fsl_input_f() function to buffer input into a fsl_buffer.

   dest must be a non-NULL, initialized (though possibly empty)
   fsl_buffer object. Its contents, if any, will be overwritten by
   this function, and any memory it holds might be re-used.




   The src function is called, and passed the state parameter, to
   fetch the input. If it returns non-0, this function returns that
   error code. src() is called, possibly repeatedly, until it
   reports that there is no more data.

   Whether or not this function succeeds, dest still owns any memory
   pointed to by dest->mem, and the client must eventually free it
   by calling fsl_buffer_reserve(dest,0).

   dest->mem might (and possibly will) be (re)allocated by this
   function, so any pointers to it held from before this call might
   be invalidated by this call.

   On error non-0 is returned and dest may bge partially populated.

   Errors include:

   dest or src are NULL (FSL_RC_MISUSE)

   Allocation error (FSL_RC_OOM)


   src() returns an error code


   Whether or not the state parameter may be NULL depends on the src
   implementation requirements.

   On success dest will contain the contents read from the input
   source. dest->used will be the length of the read-in data, and
   dest->mem will point to the memory. dest->mem is automatically
   NUL-terminated if this function succeeds, but dest->used does not
   count that terminator. On error the state of dest->mem must be
   considered incomplete, and is not guaranteed to be
   NUL-terminated.

   Example usage:

   ```
   fsl_buffer buf = fsl_buffer_empty;
   int rc = fsl_buffer_fill_from( &buf,
     fsl_input_f_FILE,
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
FSL_EXPORT int fsl_buffer_fill_from_FILE( fsl_buffer * const dest,
                                          FILE * const src );

/**
   A wrapper for fsl_buffer_fill_from_FILE() which gets its input
   from the given file name.

   If fsl_buffer_err() is true then this function returns its value
   without other side effects.

   It uses fsl_fopen() to open the file, so it supports the name '-'
   as an alias for stdin.
*/
FSL_EXPORT int fsl_buffer_fill_from_filename( fsl_buffer * const dest,
                                              char const * filename );    

/**
   Writes the given buffer to the given filename. Returns 0 on success,
   FSL_RC_MISUSE if !b or !fname, FSL_RC_IO if opening or writing fails.
   
   If fsl_buffer_err() is true, that result is returned with no other
   side effects.

   Uses fsl_fopen() to open the file, so it supports the name '-'
   as an alias for stdout.
*/
FSL_EXPORT int fsl_buffer_to_filename( fsl_buffer const * const b,
                                       char const * fname );

/**
   Copy N lines of text from pFrom into pTo. The copy begins at the
   current pFrom->cursor position. pFrom->cursor is left pointing at
   the first character past the last `\n` copied. (Modification of the
   cursor is why pFrom is not const.)

   If fsl_buffer_err() is true for either buffer (checked in parameter
   order), that result is returned with no other side effects.

   If pTo==NULL then this routine simply skips over N lines.

   Returns 0 if it copies lines or does nothing (because N is 0 or
   pFrom's contents have been exhausted). Copying fewer lines than
   requested (because of EOF) is not an error. Returns non-0 only on
   allocation error. Results are undefined if pFrom is NULL or not
   properly initialized.

   @see fsl_buffer_stream_lines()
*/
FSL_EXPORT int fsl_buffer_copy_lines(fsl_buffer * const pTo, fsl_buffer * const pFrom,
                                     fsl_size_t N);

/**
   Works identically to fsl_buffer_copy_lines() except that it sends
   its output to the fTo output function. If fTo is NULL then it
   simply skips over N lines.

   If fsl_buffer_err() is true, that result is returned with no other
   side effects.

   @see fsl_buffer_copy_lines()
*/
FSL_EXPORT int fsl_buffer_stream_lines(fsl_output_f fTo, void * const toState,
                                       fsl_buffer * const pFrom,
                                       fsl_size_t N);









<
<
<









<
<
<













<
<
<


















<
<
<







1725
1726
1727
1728
1729
1730
1731



1732
1733
1734
1735
1736
1737
1738
1739
1740



1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753



1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771



1772
1773
1774
1775
1776
1777
1778
FSL_EXPORT int fsl_buffer_fill_from_FILE( fsl_buffer * const dest,
                                          FILE * const src );

/**
   A wrapper for fsl_buffer_fill_from_FILE() which gets its input
   from the given file name.




   It uses fsl_fopen() to open the file, so it supports the name '-'
   as an alias for stdin.
*/
FSL_EXPORT int fsl_buffer_fill_from_filename( fsl_buffer * const dest,
                                              char const * filename );    

/**
   Writes the given buffer to the given filename. Returns 0 on success,
   FSL_RC_MISUSE if !b or !fname, FSL_RC_IO if opening or writing fails.




   Uses fsl_fopen() to open the file, so it supports the name '-'
   as an alias for stdout.
*/
FSL_EXPORT int fsl_buffer_to_filename( fsl_buffer const * const b,
                                       char const * fname );

/**
   Copy N lines of text from pFrom into pTo. The copy begins at the
   current pFrom->cursor position. pFrom->cursor is left pointing at
   the first character past the last `\n` copied. (Modification of the
   cursor is why pFrom is not const.)




   If pTo==NULL then this routine simply skips over N lines.

   Returns 0 if it copies lines or does nothing (because N is 0 or
   pFrom's contents have been exhausted). Copying fewer lines than
   requested (because of EOF) is not an error. Returns non-0 only on
   allocation error. Results are undefined if pFrom is NULL or not
   properly initialized.

   @see fsl_buffer_stream_lines()
*/
FSL_EXPORT int fsl_buffer_copy_lines(fsl_buffer * const pTo, fsl_buffer * const pFrom,
                                     fsl_size_t N);

/**
   Works identically to fsl_buffer_copy_lines() except that it sends
   its output to the fTo output function. If fTo is NULL then it
   simply skips over N lines.




   @see fsl_buffer_copy_lines()
*/
FSL_EXPORT int fsl_buffer_stream_lines(fsl_output_f fTo, void * const toState,
                                       fsl_buffer * const pFrom,
                                       fsl_size_t N);


2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549

   This function does no filesystem-level validation of the the
   given path - only string evaluation.
*/
FSL_EXPORT int fsl_file_dirpart(char const * zFilename, fsl_int_t nLen,
                                fsl_buffer * const pOut, bool leaveSlash);

/**
   Return the tail of a NUL-terminated file pathname. The tail is the
   last component of the path.  For example, the tail of "/a/b/c.d" is
   "c.d". If the name ends in a slash, a pointer to its NUL terminator
   is returned.
*/
FSL_EXPORT const char *fsl_file_tail(const char *z);

/**
   Writes the absolute path name of the current directory to zBuf,
   which must be at least nBuf bytes long (nBuf includes the space
   for a trailing NUL terminator).

   Returns FSL_RC_RANGE if the name would be too long for nBuf,







<
<
<
<
<
<
<







2400
2401
2402
2403
2404
2405
2406







2407
2408
2409
2410
2411
2412
2413

   This function does no filesystem-level validation of the the
   given path - only string evaluation.
*/
FSL_EXPORT int fsl_file_dirpart(char const * zFilename, fsl_int_t nLen,
                                fsl_buffer * const pOut, bool leaveSlash);









/**
   Writes the absolute path name of the current directory to zBuf,
   which must be at least nBuf bytes long (nBuf includes the space
   for a trailing NUL terminator).

   Returns FSL_RC_RANGE if the name would be too long for nBuf,
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
   The returned type is (wchar_t*) on Windows and (char*)
   everywhere else.
*/
FSL_EXPORT void *fsl_utf8_to_filename(const char *zUtf8);


/**
   Deallocate pOld, which must be NULL or must have been allocated by
   fsl_filename_to_utf8(), fsl_utf8_to_filename(), fsl_getenv(), or
   another routine which explicitly documents this function as being
   the proper finalizer for its returned memory.
*/
FSL_EXPORT void fsl_filename_free(void *pOld);

/**
   Returns a (possible) copy of the environment variable with the
   given key, or NULL if no entry is found. The returned value must
   be passed to fsl_filename_free() to free it. ACHTUNG: DO NOT







|

|
|







2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
   The returned type is (wchar_t*) on Windows and (char*)
   everywhere else.
*/
FSL_EXPORT void *fsl_utf8_to_filename(const char *zUtf8);


/**
   Deallocate pOld, which must have been allocated by
   fsl_filename_to_utf8(), fsl_utf8_to_filename(), fsl_getenv(), or
   another routine which explicitly documents this function as
   being the proper finalizer for its returned memory.
*/
FSL_EXPORT void fsl_filename_free(void *pOld);

/**
   Returns a (possible) copy of the environment variable with the
   given key, or NULL if no entry is found. The returned value must
   be passed to fsl_filename_free() to free it. ACHTUNG: DO NOT
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
      will fail.

   If realLink is false or this is a Windows platform, a file is
   created named zLinkFile containing the string zTargetFile as its
   contents. If a file or symlink named zLinkFile already exists, it
   is removed before writing the new contents.

   In both cases the parent directories for zLinkFile are created, if
   needed, but that process will fail with FSL_RC_TYPE if any
   non-directory components with conflicting names are found in the
   to-be-mkdir'd path.

   Returns 0 on success or some lower-level result code if
   creation/writing of a directory, a symlink, or pseudo-symlink
   fails.
*/
FSL_EXPORT int fsl_symlink_create(char const *zTargetFile, char const * zLinkFile,
                                  bool realLink);

/**
   Reads symlink zFrom, as per fsl_symlink_read(), then creates a copy
   named zTo, as per fsl_symlink_create(). The first argument for the
   latter call is the contents of the result of fsl_symlink_read().
   The 2nd and 3rd arguments to fsl_symlink_create() are the 2nd and
   3rd arguments to this function.

   Returns 0 on success, FSL_RC_OOM on OOM, or any of various
   filesystem-related non-0 codes if reading or saving the link fails.
*/
FSL_EXPORT int fsl_symlink_copy(char const *zFrom, char const *zTo, bool realLink);

/**
   Uses fsl_getenv() to look for the environment variables
   (FOSSIL_USER, (Windows: USERNAME), (Unix: USER, LOGNAME)). If
   it finds one it returns a copy of that value, which must
   eventually be passed to fsl_free() to free it (NOT
   fsl_filename_free(), though fsl_getenv() requires that one). If
   it finds no match, or if copying the entry fails, it returns







|
|
|
<








<
<
<
<
<
<
<
<
<
<
<
<







3090
3091
3092
3093
3094
3095
3096
3097
3098
3099

3100
3101
3102
3103
3104
3105
3106
3107












3108
3109
3110
3111
3112
3113
3114
      will fail.

   If realLink is false or this is a Windows platform, a file is
   created named zLinkFile containing the string zTargetFile as its
   contents. If a file or symlink named zLinkFile already exists, it
   is removed before writing the new contents.

   In both cases, the parent directories for zLinkFile are created, if
   needed but that process will fail if any non-directory components
   with conflicting names are found in the to-be-mkdir'd path.


   Returns 0 on success or some lower-level result code if
   creation/writing of a directory, a symlink, or pseudo-symlink
   fails.
*/
FSL_EXPORT int fsl_symlink_create(char const *zTargetFile, char const * zLinkFile,
                                  bool realLink);













/**
   Uses fsl_getenv() to look for the environment variables
   (FOSSIL_USER, (Windows: USERNAME), (Unix: USER, LOGNAME)). If
   it finds one it returns a copy of that value, which must
   eventually be passed to fsl_free() to free it (NOT
   fsl_filename_free(), though fsl_getenv() requires that one). If
   it finds no match, or if copying the entry fails, it returns
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
   requireWriteAccess is ignored on others.

   - FSL_RC_TYPE if the home (as determined via inspection of the
   environment) is not a directory.

   - FSL_RC_OOM if a memory (re)allocation fails.
*/
FSL_EXPORT int fsl_find_home_dir( fsl_buffer * const tgt, bool requireWriteAccess );

/**
   Values for use with the fsl_fstat::type field.
*/
enum fsl_fstat_type_e {
/** Sentinel value for unknown/invalid filesystem entry types. */
FSL_FSTAT_TYPE_UNKNOWN = 0,







|







3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
   requireWriteAccess is ignored on others.

   - FSL_RC_TYPE if the home (as determined via inspection of the
   environment) is not a directory.

   - FSL_RC_OOM if a memory (re)allocation fails.
*/
FSL_EXPORT int fsl_find_home_dir( fsl_buffer * tgt, bool requireWriteAccess );

/**
   Values for use with the fsl_fstat::type field.
*/
enum fsl_fstat_type_e {
/** Sentinel value for unknown/invalid filesystem entry types. */
FSL_FSTAT_TYPE_UNKNOWN = 0,
3437
3438
3439
3440
3441
3442
3443

3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
   contain embedded NUL characters if either the zSrc or zOut files
   are binary.

   On success this function returns 0 and the length of the delta
   string, in bytes, excluding the final NUL terminator character,
   is written to *deltaSize.


   Returns FSL_RC_OOM if memory allocation fails during generation of
   the delta. Returns FSL_RC_RANGE if lenSrc or lenOut are "too
   big" (if they cause an overflow in the math).

   Results are undefined if any pointer is NULL.

   Output Format:

   The delta begins with a base64 number followed by a newline.
   This number is the number of bytes in the TARGET file.  Thus,
   given a delta file z, a program can compute the size of the
   output file simply by reading the first line and decoding the
   base-64 number found there.  The fsl_delta_applied_size()







>
|



<
<







3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299


3300
3301
3302
3303
3304
3305
3306
   contain embedded NUL characters if either the zSrc or zOut files
   are binary.

   On success this function returns 0 and the length of the delta
   string, in bytes, excluding the final NUL terminator character,
   is written to *deltaSize.

   Returns FSL_RC_MISUSE if any of the pointer arguments are NULL
   and FSL_RC_OOM if memory allocation fails during generation of
   the delta. Returns FSL_RC_RANGE if lenSrc or lenOut are "too
   big" (if they cause an overflow in the math).



   Output Format:

   The delta begins with a base64 number followed by a newline.
   This number is the number of bytes in the TARGET file.  Thus,
   given a delta file z, a program can compute the size of the
   output file simply by reading the first line and decoding the
   base-64 number found there.  The fsl_delta_applied_size()
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565

/**
   A fsl_delta_create() wrapper which uses the first two arguments
   as the original and "new" content versions to delta, and outputs
   the delta to the 3rd argument (overwriting any existing contents
   and re-using any memory it had allocated).

   If any of the buffers have the same address, FSL_RC_MISUSE is
   returned. If fsl_buffer_err() is true for the 3rd argument, that
   value is returned without side effects. If allocation of memory for
   the delta fails, FSL_RC_OOM is returned and delta's errCode is
   updated with that value.

   Results are undefined if any argument is NULL.

   Returns 0 on success.
*/
FSL_EXPORT int fsl_buffer_delta_create( fsl_buffer const * const src,
                                        fsl_buffer const * const newVers,
                                        fsl_buffer * const delta);

/**
   Apply a delta created using fsl_delta_create().

   The output buffer must be big enough to hold the whole output
   file and a NUL terminator at the end. The
   fsl_delta_applied_size() routine can be used to determine that







|
<
<
|
<
|
|




|
|







3389
3390
3391
3392
3393
3394
3395
3396


3397

3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412

/**
   A fsl_delta_create() wrapper which uses the first two arguments
   as the original and "new" content versions to delta, and outputs
   the delta to the 3rd argument (overwriting any existing contents
   and re-using any memory it had allocated).

   If the output buffer (delta) is the same as src or newVers,


   FSL_RC_MISUSE is returned, and results are undefined if delta

   indirectly refers to the same buffer as either src or newVers
   or if any argument is NULL.

   Returns 0 on success.
*/
FSL_EXPORT int fsl_buffer_delta_create( fsl_buffer const * const src,
                             fsl_buffer const * const newVers,
                             fsl_buffer * const delta);

/**
   Apply a delta created using fsl_delta_create().

   The output buffer must be big enough to hold the whole output
   file and a NUL terminator at the end. The
   fsl_delta_applied_size() routine can be used to determine that
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108

/**
   Convenience typedef.
*/
typedef struct fsl_dline fsl_dline;
/** Initialized-with-defaults fsl_dline structure, intended for
    const-copy initialization. */
#define fsl_dline_empty_m {NULL,0U,0U,0U,0U,0U,0U}
/** Initialized-with-defaults fsl_dline structure, intended for
    non-const copy initialization. */
extern const fsl_dline fsl_dline_empty;

/**
   Maximum number of change spans for fsl_dline_change.
*/







|







3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955

/**
   Convenience typedef.
*/
typedef struct fsl_dline fsl_dline;
/** Initialized-with-defaults fsl_dline structure, intended for
    const-copy initialization. */
#define fsl_dline_empty_m {NULL,0U,0U,0U,0U,0U}
/** Initialized-with-defaults fsl_dline structure, intended for
    non-const copy initialization. */
extern const fsl_dline fsl_dline_empty;

/**
   Maximum number of change spans for fsl_dline_change.
*/
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
  NULL/*opt*/,NULL/*typeID*/,                                         \
  NULL/*start()*/,NULL/*chunkHeader()*/,NULL/*skip()*/, NULL/*common()*/, \
  NULL/*insertion()*/,NULL/*deletion()*/, NULL/*replacement()*/, \
  NULL/*edit()*/, NULL/*finish()*/, NULL/*finally()*/,NULL/*finalize()*/, \
  false/*twoPass*/,0U/*passNumber*/, \
  NULL/*pimpl*/, 0U/*pimplFlags*/,0U/*implFlags*/,0U/*fileCount*/,         \
  0/*lnLHS*/,0/*lnRHS*/,                                              \
  {/*metrics*/0,0,0,0} \
}

/** Initialized-with-defaults fsl_dibu structure, intended for
    non-const copy initialization. */
extern const fsl_dibu fsl_dibu_empty;

/**







|







4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
  NULL/*opt*/,NULL/*typeID*/,                                         \
  NULL/*start()*/,NULL/*chunkHeader()*/,NULL/*skip()*/, NULL/*common()*/, \
  NULL/*insertion()*/,NULL/*deletion()*/, NULL/*replacement()*/, \
  NULL/*edit()*/, NULL/*finish()*/, NULL/*finally()*/,NULL/*finalize()*/, \
  false/*twoPass*/,0U/*passNumber*/, \
  NULL/*pimpl*/, 0U/*pimplFlags*/,0U/*implFlags*/,0U/*fileCount*/,         \
  0/*lnLHS*/,0/*lnRHS*/,                                              \
  {/*metric*/0,0,0} \
}

/** Initialized-with-defaults fsl_dibu structure, intended for
    non-const copy initialization. */
extern const fsl_dibu fsl_dibu_empty;

/**
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
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040
5041
/**
   Frees all memory associated with pf, but does not free pf.
   Is a no-op if pf is NULL.
*/
FSL_EXPORT void fsl_pathfinder_clear(fsl_pathfinder * const pf);

/**
   Equivalent to fsl_pathfinder_dir_add2() with -1 as a final


   argument.

   @see fsl_pathfinder_ext_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_dir_add(fsl_pathfinder * const pf,
                                      char const * const dir);

/**
   Adds the given directory to pf's search path. Returns 0 on
   success, FSL_RC_MISUSE if !pf or !dir (dir _may_ be an empty
   string), FSL_RC_OOM if copying the string or adding it to the
   list fails.

   If strLen is negative, fsl_strlen() is used to calculate the length
   of the string.

   @see fsl_pathfinder_ext_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_dir_add2(fsl_pathfinder * const pf, char const * const dir,
                                       fsl_int_t strLen);

/**
   Adds the given directory to pf's search extensions. Returns 0 on
   success, FSL_RC_MISUSE if !pf or !dir (dir _may_ be an empty
   string), FSL_RC_OOM if copying the string or adding it to the
   list fails.

   Note that the client is responsible for adding a "." to the
   extension, if needed, as this API does not apply any special
   meaning to any characters in a search extension. e.g. "-journal"
   and "~" are both perfectly valid extensions for this purpose.

   @see fsl_pathfinder_dir_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_ext_add2(fsl_pathfinder * const pf, char const * const ext,
                                       fsl_int_t strLen);

/**
   Equivalent to fsl_pathfinder_ext_add2() with -1 as a final
   argument.

   @see fsl_pathfinder_dir_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_ext_add(fsl_pathfinder * const pf,
                                      char const * const ext);

/**
   Splits a conventional path-separator-delimited string into tokens
   and adds each as either a directory (if isDirs is true) or an
   extension in the given fsl_pathfinder object. Returns 0 on success,
   FSL_RC_OOM on allocation error. pathLen is the length of the path
   string. If it's negative, fsl_strlen() is used to calculate it.

   See fsl_path_splitter for the semantics of the splitting.

   To change the delimiter characters, set tgt->separators before
   calling this.
*/
FSL_EXPORT int fsl_pathfinder_split( fsl_pathfinder * const tgt,
                                     bool isDirs,
                                     char const * path,
                                     fsl_int_t pathLen );

/**
   Searches for a file whose name can be constructed by some
   combination of pf's directory/suffix list and the given base
   name.

   It searches for files in the following manner:








|
>
>
|







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














<
<
<
<
<
<
<
<
<
<



<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824















4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838










4839
4840
4841

















4842
4843
4844
4845
4846
4847
4848
/**
   Frees all memory associated with pf, but does not free pf.
   Is a no-op if pf is NULL.
*/
FSL_EXPORT void fsl_pathfinder_clear(fsl_pathfinder * const pf);

/**
   Adds the given directory to pf's search path. Returns 0 on
   success, FSL_RC_MISUSE if !pf or !dir (dir _may_ be an empty
   string), FSL_RC_OOM if copying the string or adding it to the
   list fails.

   @see fsl_pathfinder_ext_add()
   @see fsl_pathfinder_search() 
*/
FSL_EXPORT int fsl_pathfinder_dir_add(fsl_pathfinder * const pf,
                                      char const * const dir);
















/**
   Adds the given directory to pf's search extensions. Returns 0 on
   success, FSL_RC_MISUSE if !pf or !dir (dir _may_ be an empty
   string), FSL_RC_OOM if copying the string or adding it to the
   list fails.

   Note that the client is responsible for adding a "." to the
   extension, if needed, as this API does not apply any special
   meaning to any characters in a search extension. e.g. "-journal"
   and "~" are both perfectly valid extensions for this purpose.

   @see fsl_pathfinder_dir_add()
   @see fsl_pathfinder_search() 
*/










FSL_EXPORT int fsl_pathfinder_ext_add(fsl_pathfinder * const pf,
                                      char const * const ext);


















/**
   Searches for a file whose name can be constructed by some
   combination of pf's directory/suffix list and the given base
   name.

   It searches for files in the following manner:

5049
5050
5051
5052
5053
5054
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065
5066
5067
5068
5069
5070
5071
5072
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
5088
5089
5090

5091
5092
5093
5094
5095
5096
5097
   extensions, so "~" and "-foo" are legal extensions). Check
   for a match.

   On success (a readable filesystem entry is found):

   - It returns 0.

   - If pOut is not NULL then `*pOut` is set to the path it found. The
     bytes of the returned string are only valid until the next search
     operation on pf, so copy them if you need them.  Note that the
     returned path is _not_ normalized via fsl_file_canonical_name()
     or similar, and it may very well return a relative path (if base
     or one of pf->dirs contains a relative path part). As a special
     case, if `base` is found as-is, without a lookup, `*pOut` is set
     to `base`, so has its lifetime.

   - If outLen is not NULL, *outLen will be set to the length of the
     returned string.

   On error:

   - Returns FSL_RC_MISUSE if !pf, !base, !*base.

   - Returns FSL_RC_OOM on allocation error (it uses a buffer to hold
     its path combinations and return value).

   - Returns FSL_RC_NOT_FOUND if it finds no entry.

   The host platform's customary path separator is used to separate
   directory/file parts ('\\' on Windows and '/' everywhere else).

   Note that it _is_ legal for pOut and outLen to both be NULL, in
   which case a return of 0 signals that an entry was found, but
   the client has no way of knowing what path it might be (unless,
   of course, he relies on internal details of the fsl_pathfinder
   API, which he most certainly should not do).

   Tip: if the client wants to be certain that this function will not
   allocate memory, simply use fsl_buffer_reserve() on pf->buf to
   reserve the desired amount of space in advance. As long as the
   search paths never surpass that length, this function will not need
   to allocate. (Until/unless the following TODO is implemented...)


   Potential TODO: use fsl_file_canonical_name() so that the search
   dirs themselves do not need to be entered using
   platform-specific separators. The main reason it's not done now
   is that it requires another allocation. The secondary reason is
   because it's sometimes useful to use relative paths in this
   context (based on usage in previous trees from which this code







|
|
|
|
|
|
|
<

|
|





|
|












|
|
|
|
|
>







4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869

4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
   extensions, so "~" and "-foo" are legal extensions). Check
   for a match.

   On success (a readable filesystem entry is found):

   - It returns 0.

   - If pOut is not NULL then *pOut is set to the path it
   found. The bytes of the returned string are only valid until the
   next search operation on pf, so copy them if you need them.
   Note that the returned path is _not_ normalized via
   fsl_file_canonical_name() or similar, and it may very well
   return a relative path (if base or one of pf->dirs contains a
   relative path part).


   - If outLen is not NULL, *outLen will be set to the
   length of the returned string. 

   On error:

   - Returns FSL_RC_MISUSE if !pf, !base, !*base.

   - Returns FSL_RC_OOM on allocation error (it uses a buffer to
   hold its path combinations and return value).

   - Returns FSL_RC_NOT_FOUND if it finds no entry.

   The host platform's customary path separator is used to separate
   directory/file parts ('\\' on Windows and '/' everywhere else).

   Note that it _is_ legal for pOut and outLen to both be NULL, in
   which case a return of 0 signals that an entry was found, but
   the client has no way of knowing what path it might be (unless,
   of course, he relies on internal details of the fsl_pathfinder
   API, which he most certainly should not do).

   Tip: if the client wants to be certain that this function will
   not allocate memory, simply use fsl_buffer_reserve() on pf->buf
   to reserve the desired amount of space in advance. As long as
   the search paths never extend that length, this function will
   not need to allocate. (Until/unless the following TODO is
   implemented...)

   Potential TODO: use fsl_file_canonical_name() so that the search
   dirs themselves do not need to be entered using
   platform-specific separators. The main reason it's not done now
   is that it requires another allocation. The secondary reason is
   because it's sometimes useful to use relative paths in this
   context (based on usage in previous trees from which this code
5346
5347
5348
5349
5350
5351
5352
5353
5354
5355
5356
5357
5358
5359
5360
5361
   (in the case of a heap-allocated instance) freed.
*/
FSL_EXPORT int fsl_zip_end_to_filename( fsl_zip_writer * const z,
                                        char const * filename );


/**
   Returns a pointer to z's ZIP content buffer. The contents are ONLY
   valid after fsl_zip_end() returns 0.

   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_file_add()
   @see fsl_zip_end()
   @see fsl_zip_end_take()
   @see fsl_zip_finalize()







|
|







5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
   (in the case of a heap-allocated instance) freed.
*/
FSL_EXPORT int fsl_zip_end_to_filename( fsl_zip_writer * const z,
                                        char const * filename );


/**
   Returns a pointer to z's ZIP content buffer. The contents
   are ONLY valid after fsl_zip_end() returns 0.

   @see fsl_zip_timestamp_set_julian()
   @see fsl_zip_timestamp_set_unix()
   @see fsl_zip_file_add()
   @see fsl_zip_end()
   @see fsl_zip_end_take()
   @see fsl_zip_finalize()
5742
5743
5744
5745
5746
5747
5748
5749
5750
5751
5752
5753
5754
5755
5756
5757
5758
                                 unsigned int * const conflictCount);

/**
   Appends the first n bytes of string z to buffer b in the form of
   TCL-format string literal. If n<0 then fsl_strlen() is used to
   determine the length. Returns 0 on success, FSL_RC_OOM on error.

   If fsl_buffer_err() is true for the given buffer, that code is
   returned without other side effects.

   If the 2nd argument is true, squiggly braces within the string are
   escaped, else they are not. Whether that's required or not depends
   on how the resulting TCL will be used. If it will be eval'd directly,
   it must be escaped. If it will be read as a file and tokenized, it
   needn't be.
 */
FSL_EXPORT int fsl_buffer_append_tcl_literal(fsl_buffer * const b,







<
<
<







5549
5550
5551
5552
5553
5554
5555



5556
5557
5558
5559
5560
5561
5562
                                 unsigned int * const conflictCount);

/**
   Appends the first n bytes of string z to buffer b in the form of
   TCL-format string literal. If n<0 then fsl_strlen() is used to
   determine the length. Returns 0 on success, FSL_RC_OOM on error.




   If the 2nd argument is true, squiggly braces within the string are
   escaped, else they are not. Whether that's required or not depends
   on how the resulting TCL will be used. If it will be eval'd directly,
   it must be escaped. If it will be read as a file and tokenized, it
   needn't be.
 */
FSL_EXPORT int fsl_buffer_append_tcl_literal(fsl_buffer * const b,
6132
6133
6134
6135
6136
6137
6138
6139
6140
6141
6142
6143
6144
6145
6146
6147
6148
6149
6150
6151
6152
6153
6154
6155
6156

/**
   A convenience from of fsl_strip_trailing_slashes() which strips
   trailing slashes from the given buffer and changes its b->used
   value to account for any stripping. Results are undefined if b is
   not properly initialized.
*/
FSL_EXPORT void fsl_buffer_strip_slashes(fsl_buffer * const b);

/**
   Appends each ID from the given bag to the given buffer using the given
   separator string. Returns FSL_RC_OOM on allocation error.

   If fsl_buffer_err() is true for the given buffer, that code is
   returned without other side effects.
*/
FSL_EXPORT int fsl_id_bag_to_buffer(fsl_id_bag const * bag,
                                    fsl_buffer * const b,
                                    char const * separator);

/**
   Flags for use with the fsl_looks family of functions.
*/
enum fsl_lookslike_e {
/* Nothing special was found. */







|




<
<
<

|
<







5936
5937
5938
5939
5940
5941
5942
5943
5944
5945
5946
5947



5948
5949

5950
5951
5952
5953
5954
5955
5956

/**
   A convenience from of fsl_strip_trailing_slashes() which strips
   trailing slashes from the given buffer and changes its b->used
   value to account for any stripping. Results are undefined if b is
   not properly initialized.
*/
FSL_EXPORT void fsl_buffer_strip_slashes(fsl_buffer * b);

/**
   Appends each ID from the given bag to the given buffer using the given
   separator string. Returns FSL_RC_OOM on allocation error.



*/
FSL_EXPORT int fsl_id_bag_to_buffer(fsl_id_bag const * bag, fsl_buffer * b,

                                    char const * separator);

/**
   Flags for use with the fsl_looks family of functions.
*/
enum fsl_lookslike_e {
/* Nothing special was found. */
6255
6256
6257
6258
6259
6260
6261
6262
6263
6264
6265
6266
6267
6268
6269
6270
6271
6272
6273
6274
6275
6276
6277
6278
6279
6280
6281
6282
6283
6284
6285
6286
6287
6288
6289
6290
6291
6292
6293
6294
6295
6296
6297
6298
6299
6300
6301
6302
6303
6304
6305
6306
6307
6308
6309
6310
6311
6312
6313
6314
6315
6316
6317
6318
6319
6320
6321
6322
6323
6324
6325
6326
6327
6328
6329
6330
6331
6332
6333
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
   Populates the first n bytes of tgt with random bytes. Note that n
   must be 31 bits or less (2GB). The exact source of randomness is
   not guaranteed by the API, but the implementation currently uses
   sqlite3_randomness().
*/
FSL_EXPORT void fsl_randomness(unsigned int n, void *tgt);


/**
   Given a filename and its length, this function returns a pointer to
   the last instance of a path separator character in that string,
   checking for both `/` and `\\`. If the length is negative,
   fsl_strlen() is used to calculate it. If no separator is found,
   NULL is returned. If it returns non-NULL, the pointer is guaranteed to
   live somewhere between the half-open range [str,str+slen).
*/
FSL_EXPORT char const * fsl_last_path_sep(char const * str, fsl_int_t slen );

/**
   A helper type for tokenizing conventional PATH-style strings.
   Initialize them with fsl_path_splitter_init() and iterate over them
   with fsl_path_splitter_next().
*/
struct fsl_path_splitter {
  /** Begining of the input range. */
  char const * begin;
  /** One-after-the-end of the input range. */
  char const * end;
  /** Position for the next token lookup. */
  char const * pos;
  /** List of token separator characters (ASCII only). */
  char const * separators;
};
typedef struct fsl_path_splitter fsl_path_splitter;
/** @def fsl_path_splitter_empty_m

   Default-initialized fsl_path_splitter instance, intended for const-copy
   initialization. On Windows builds its separators member is set to
   ";" and on other platforms it's set to ":;".
*/
#if FSL_PLATFORM_IS_WINDOWS
#  define fsl_path_splitter_empty_m {NULL,NULL,NULL,";"}
#else
#  define fsl_path_splitter_empty_m {NULL,NULL,NULL,":;"}
#endif

/**
   Default-initialized fsl_path_splitter instance, intended for
   copy initialization.

   @see fsl_path_splitter_empty_m
*/
FSL_EXPORT const fsl_path_splitter fsl_path_splitter_empty;

/**
   Wipes out pt's current state by copying fsl_path_splitter_empty over it
   and initializes pt to use the given path as its input. If len is 0
   or more then it must be the length of the string, in bytes. If len
   is less than 0, fsl_strlen() is used to determine the path's
   length.  (When dealing with inputs which are not NUL-terminated,
   it's critical that the user pass the correct non-negative length.)

   If the client wants to modify pt->separators, it must be done so
   *after* calling this.

   Use fsl_path_splitter_next() to iterate over the path entries.
*/
void fsl_path_splitter_init( fsl_path_splitter * const pt, char const * path,
                             fsl_int_t len );

/**
   Given a fsl_path_splitter which was formerly initialized using
   fsl_path_splitter_init(), this iterates over the next-available
   path component in the input, skipping over empty entries (leading,
   consecutive, or trailing separator characters).

   The separator characters are specified by pt->separators, which must
   be a NUL-terminated string of 1 or more characters.

   If a non-empty entry is found then:

   - *token is set to the first byte of the entry.

   - *len is assigned to the byte length of the entry.

   If no entry is found then:

   - *token, and *len are not modified.

   - FSL_RC_NOT_FOUND is returned if the end of the path was found
   while tokenizing.

   - FSL_RC_MISUSE is returned if pt->separators is NULL or empty or
   contains any non-ASCII characters.

   - FSL_RC_RANGE is returned if called after the previous case, or
   if the input object's path has a length of 0.

   In any non-0-return case, it's not a fatal error, it's simply
   information about why tokenization cannot continue, and can
   normally be ignored. After non-0 is returned, the tokenizer must be
   re-initialized if it is to be used again.

   Example:

   @code
   char const * t = 0;
   fsl_size_t tLen = 0;
   fsl_path_splitter pt = fsl_path_splitter_empty;
   fsl_path_splitter_init(&pt, path, pathLen);
   while(0==fsl_path_splitter_next(&pt, &t, &tLen)){
      // The next element is the tLen bytes of memory starting at t:
      printf("Path element: %.*s\n", (int)tLen, t);
   }
   @endcode
*/
int fsl_path_splitter_next( fsl_path_splitter * const pt, char const ** token,
                            fsl_size_t * const len );

/**
   Implements a cross-platform system(3) interface. It passes its
   argument, which must not be NULL or empty to the equivalent of
   system(). Returns 0 if that call fails, else it will return a
   FSL_RC code approximating the lower-level error code (noting that
   this system's man pages are a bit vague on the exact return
   semantics of system(3)).
*/
int fsl_system(const char *zOrigCmd);

#if 0
/**
   The UTF16 counterpart of fsl_looks_like_utf8(), with the addition that the
   2nd argument, if true, specifies that the 2nd argument is true then
   the contents of the buffer are byte-swapped for checking purposes.

   This is not validate that the blob is valid UTF16. It assumes that all







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







6055
6056
6057
6058
6059
6060
6061


























































































































6062
6063
6064
6065
6066
6067
6068
   Populates the first n bytes of tgt with random bytes. Note that n
   must be 31 bits or less (2GB). The exact source of randomness is
   not guaranteed by the API, but the implementation currently uses
   sqlite3_randomness().
*/
FSL_EXPORT void fsl_randomness(unsigned int n, void *tgt);



























































































































#if 0
/**
   The UTF16 counterpart of fsl_looks_like_utf8(), with the addition that the
   2nd argument, if true, specifies that the 2nd argument is true then
   the contents of the buffer are byte-swapped for checking purposes.

   This is not validate that the blob is valid UTF16. It assumes that all
6443
6444
6445
6446
6447
6448
6449
6450
6451
6452
6453
6454
6455
6456
6457
6458
6459
6460
6461
6462
6463
6464
6465
/**
  The fsl namespace is reserved for an eventual C++ wrapper for the API.
*/
namespace fsl {}
extern "C" {
#endif

/** @internal
   
    An internal helper macro to help localize OOM error reports (which
    are most often side effects of other issues, rather than being
    real OOM cases).
*/
#define FSL__WARN_OOM \
  fprintf(stderr,"OOM @ %s:%d\n", __FILE__, __LINE__)

/**
   @struct fsl_cx

   The main Fossil "context" type. This is the first argument to
   many Fossil library API routines, and holds all state related
   to a checkout and/or repository and/or global fossil configuration
   database(s).







<
<
<
<
<
<
<
<
<







6121
6122
6123
6124
6125
6126
6127









6128
6129
6130
6131
6132
6133
6134
/**
  The fsl namespace is reserved for an eventual C++ wrapper for the API.
*/
namespace fsl {}
extern "C" {
#endif










/**
   @struct fsl_cx

   The main Fossil "context" type. This is the first argument to
   many Fossil library API routines, and holds all state related
   to a checkout and/or repository and/or global fossil configuration
   database(s).
6693
6694
6695
6696
6697
6698
6699
6700
6701
6702
6703
6704
6705
6706
6707
6708
6709
6710
6711
6712
6713
6714
6715
6716
6717
6718
6719
6720
6721
6722
6723
6724
    a mix of hash types.

    Maintenance ACHTUNG: this enum's values must align with those from
    fossil(1) because their integer values are used in the
    `repo.config` table.
*/
enum fsl_hashpolicy_e {
/** Use only SHA1 hashes. */
FSL_HPOLICY_SHA1 = 0,
/** Accept SHA1 hashes but auto-promote to SHA3. */
FSL_HPOLICY_AUTO = 1,
/** Use SHA3 hashes. */
FSL_HPOLICY_SHA3 = 2,
/** Use SHA3 hashes exclusively. */
FSL_HPOLICY_SHA3_ONLY = 3,
/** With this policy, fsl_uuid_is_shunned() will always return true
    for SHA1 hashes, making it "impossible" to get SHA1-hashed content
    into the repository (for a given value of "impossible"). */
FSL_HPOLICY_SHUN_SHA1 = 4
};
typedef enum fsl_hashpolicy_e fsl_hashpolicy_e;

/**
   Most functions in this API which return an int type return error
   codes from the fsl_rc_e enum. None of these entries are
   (currently) guaranteed to have a specific value across library
   versions except for FSL_RC_OK, which is guaranteed to always be
   0 (and the API guarantees that no other code shall have a value
   of zero).

   The only reasons numbers are hard-coded to the values (or some of
   them) is to simplify debugging during development. Clients may use







|

|

|

|

|
|
<






|







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
    a mix of hash types.

    Maintenance ACHTUNG: this enum's values must align with those from
    fossil(1) because their integer values are used in the
    `repo.config` table.
*/
enum fsl_hashpolicy_e {
/* Use only SHA1 hashes. */
FSL_HPOLICY_SHA1 = 0,
/* Accept SHA1 hashes but auto-promote to SHA3. */
FSL_HPOLICY_AUTO = 1,
/* Use SHA3 hashes. */
FSL_HPOLICY_SHA3 = 2,
/* Use SHA3 hashes exclusively. */
FSL_HPOLICY_SHA3_ONLY = 3,
/* With this policy, fsl_uuid_is_shunned() will always return true for
   SHA1 hashes. */

FSL_HPOLICY_SHUN_SHA1 = 4
};
typedef enum fsl_hashpolicy_e fsl_hashpolicy_e;

/**
   Most functions in this API which return an int type return error
   codes from the fsl_rc_e enum.  None of these entries are
   (currently) guaranteed to have a specific value across library
   versions except for FSL_RC_OK, which is guaranteed to always be
   0 (and the API guarantees that no other code shall have a value
   of zero).

   The only reasons numbers are hard-coded to the values (or some of
   them) is to simplify debugging during development. Clients may use
6742
6743
6744
6745
6746
6747
6748
6749
6750
6751
6752
6753
6754
6755
6756
*/
FSL_RC_NYI = 101,
/**
   Out of memory. Indicates that a resource allocation request
   failed.
*/
FSL_RC_OOM = 102,
/**
  API misuse (invalid args)
*/
FSL_RC_MISUSE = 103,
/**
   Some range was violated (function argument, UTF character, etc.).
*/
FSL_RC_RANGE = 104,







|







6410
6411
6412
6413
6414
6415
6416
6417
6418
6419
6420
6421
6422
6423
6424
*/
FSL_RC_NYI = 101,
/**
   Out of memory. Indicates that a resource allocation request
   failed.
*/
FSL_RC_OOM = 102,
/*
  API misuse (invalid args)
*/
FSL_RC_MISUSE = 103,
/**
   Some range was violated (function argument, UTF character, etc.).
*/
FSL_RC_RANGE = 104,
6851
6852
6853
6854
6855
6856
6857
6858
6859
6860
6861
6862
6863
6864
6865
6866
6867
6868
6869
6870
FSL_RC_REPO_MISMATCH = 120,
/**
   Indicates that a checksum comparison failed, possibly indicating
   that corrupted or unexpected data was just read.
*/
FSL_RC_CHECKSUM_MISMATCH = 121,

/**
   Indicates a resource-locking error of some sort, normally a
   db lock.
*/
FSL_RC_LOCKED,

/**
   Indicates that a merge conflict, or some other context-dependent
   type of conflict, was detected.
*/
FSL_RC_CONFLICT,

/**







<
<
<
<
<
<







6519
6520
6521
6522
6523
6524
6525






6526
6527
6528
6529
6530
6531
6532
FSL_RC_REPO_MISMATCH = 120,
/**
   Indicates that a checksum comparison failed, possibly indicating
   that corrupted or unexpected data was just read.
*/
FSL_RC_CHECKSUM_MISMATCH = 121,







/**
   Indicates that a merge conflict, or some other context-dependent
   type of conflict, was detected.
*/
FSL_RC_CONFLICT,

/**
6971
6972
6973
6974
6975
6976
6977
6978
6979
6980
6981
6982
6983
6984
6985
6986
6987
6988
6989
6990
6991
6992
6993
6994
6995
6996
6997
6998

/**
   Intended to be used with fsl_cx_interrupt() by signal handlers
   and UI threads.
*/
FSL_RC_INTERRUPTED,

/**
   Intended to be used by operations which would cause what is
   presumably an unintended fork. Fossil does not have any issues with
   forking, but practice suggests that most forks (that is, checking
   in to a non-leaf version) are unintentional.
*/
FSL_RC_WOULD_FORK,

/**
   This is intended only for internal use with fsl__fatal(), to
   report conditions which "cannot possibly happen."
*/
FSL_RC_CANNOT_HAPPEN,

/**
   Must be the final entry in the enum. Used for creating client-side
   result codes which are guaranteed to live outside of this one's
   range.
*/
FSL_RC_end
};







<
<
<
<
<
<
<
<
<
<
<
<
<
<







6633
6634
6635
6636
6637
6638
6639














6640
6641
6642
6643
6644
6645
6646

/**
   Intended to be used with fsl_cx_interrupt() by signal handlers
   and UI threads.
*/
FSL_RC_INTERRUPTED,















/**
   Must be the final entry in the enum. Used for creating client-side
   result codes which are guaranteed to live outside of this one's
   range.
*/
FSL_RC_end
};
7021
7022
7023
7024
7025
7026
7027
7028
7029
7030
7031
7032
7033
7034
7035
7036
7037
7038
typedef enum fsl_fileperm_e fsl_fileperm_e;

/**
   Returns a "standard" string form for a fsl_rc_e code.  The string
   is primarily intended for debugging purposes.  The returned bytes
   are guaranteed to be static and NUL-terminated. They are not
   guaranteed to contain anything useful for any purposes other than
   debugging and tracking down problems. If passed a code which is
   not in the fsl_rc_e enum, it returns NULL.
*/
FSL_EXPORT char const * fsl_rc_cstr(int rc);

/**
   Returns the value of FSL_LIBRARY_VERSION used to compile the
   library. If this value differs from the value the caller was
   compiled with, Chaos might ensue.

   The API does not yet have any mechanism for determining







|
<

|







6669
6670
6671
6672
6673
6674
6675
6676

6677
6678
6679
6680
6681
6682
6683
6684
6685
typedef enum fsl_fileperm_e fsl_fileperm_e;

/**
   Returns a "standard" string form for a fsl_rc_e code.  The string
   is primarily intended for debugging purposes.  The returned bytes
   are guaranteed to be static and NUL-terminated. They are not
   guaranteed to contain anything useful for any purposes other than
   debugging and tracking down problems.

*/
FSL_EXPORT char const * fsl_rc_cstr(int);

/**
   Returns the value of FSL_LIBRARY_VERSION used to compile the
   library. If this value differs from the value the caller was
   compiled with, Chaos might ensue.

   The API does not yet have any mechanism for determining
7082
7083
7084
7085
7086
7087
7088
7089
7090
7091
7092
7093
7094
7095
7096
   A part of the configuration used by fsl_cx_init() and friends.
*/
struct fsl_cx_config {
  /**
     If true, all SQL which goes through the fossil engine
     will be traced to stdout.

     TODO: replace this with a FILE pointer or a fsl_outputer.
  */
  bool traceSql;
  /**
     If true, the fsl_print() SQL function will output its output to the
     fsl_output()-configured channel, else it is a no-op.
  */
  bool sqlPrint;







|







6729
6730
6731
6732
6733
6734
6735
6736
6737
6738
6739
6740
6741
6742
6743
   A part of the configuration used by fsl_cx_init() and friends.
*/
struct fsl_cx_config {
  /**
     If true, all SQL which goes through the fossil engine
     will be traced to stdout.

     TODO: replace this with a FILE pointer.
  */
  bool traceSql;
  /**
     If true, the fsl_print() SQL function will output its output to the
     fsl_output()-configured channel, else it is a no-op.
  */
  bool sqlPrint;
7206
7207
7208
7209
7210
7211
7212
7213
7214
7215
7216
7217
7218
7219
7220
7221
7222
7223
   @see fsl_cx_reset()
*/
FSL_EXPORT int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param );

/**
   Frees all memory associated with f, which must have been
   allocated/initialized using fsl_cx_malloc(), fsl_cx_init(), or
   equivalent, or created on the stack and properly initialized (via
   fsl_cx_init() or copy-constructed from fsl_cx_empty).  If it was
   allocated using fsl_cx_malloc(), this function frees f, else the
   memory is owned by someone else and it is not freed.

   This function triggers any finializers set for f's client state
   or output channel.

   This is a no-op if !f and is effectively a no-op if f has no
   state to destruct.
*/







|
|
<
<







6853
6854
6855
6856
6857
6858
6859
6860
6861


6862
6863
6864
6865
6866
6867
6868
   @see fsl_cx_reset()
*/
FSL_EXPORT int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param );

/**
   Frees all memory associated with f, which must have been
   allocated/initialized using fsl_cx_malloc(), fsl_cx_init(), or
   equivalent, or created on the stack and properly initialized
   (via fsl_cx_init() or copy-constructed from fsl_cx_empty).



   This function triggers any finializers set for f's client state
   or output channel.

   This is a no-op if !f and is effectively a no-op if f has no
   state to destruct.
*/
8287
8288
8289
8290
8291
8292
8293
8294
8295
8296
8297
8298
8299
8300
8301
8302
8303
8304
8305
8306
8307
8308
8309
8310
8311
8312
8313
8314
8315
8316
   f. Returns 0 on success or if no dbs are opened (noting that this
   does NOT close the separate global configuration db: see
   fsl_config_close()). Returns FSL_RC_MISUSE if the opened SCM
   db(s) have an opened transaction, but that behaviour may
   change in the future to force a rollback and close the database(s).
*/
FSL_EXPORT int fsl_close_scm_dbs(fsl_cx * const f);

/**
    Clears various internal caches and resets various
    internally-cached values related to repository and checkout
    dbs. This is intended primarily to be used when a db transaction
    is rolled back which might have introduced state into those caches
    which would be stale after a rollback (and it is internally used
    so - _do not_ call this after initiating a rollback!). It can also
    be used when shelling out to an external app like fossil(1) which
    might update the db or checkout.

    This "really should not" be called while a transaction is
    underway.
*/
FSL_EXPORT void fsl_cx_caches_reset(fsl_cx * const f);


#if 0
/**
   DO NOT USE - not yet tested and ready.

   Returns the result of either localtime(clock) or gmtime(clock),
   depending on f:







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







7932
7933
7934
7935
7936
7937
7938
















7939
7940
7941
7942
7943
7944
7945
   f. Returns 0 on success or if no dbs are opened (noting that this
   does NOT close the separate global configuration db: see
   fsl_config_close()). Returns FSL_RC_MISUSE if the opened SCM
   db(s) have an opened transaction, but that behaviour may
   change in the future to force a rollback and close the database(s).
*/
FSL_EXPORT int fsl_close_scm_dbs(fsl_cx * const f);

















#if 0
/**
   DO NOT USE - not yet tested and ready.

   Returns the result of either localtime(clock) or gmtime(clock),
   depending on f:
9767
9768
9769
9770
9771
9772
9773
9774

9775
9776
9777
9778
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
   or if no result is found, NULL is returned.
*/
FSL_EXPORT void * fsl_db_g_blob( fsl_db * const db, fsl_size_t * len,
                      char const * sql,
                      ... );
/**
   Similar to fsl_db_get_text() and fsl_db_get_blob(), but writes
   its result to tgt, appending its results to the given buffer.


   If asBlob is true then the underlying BLOB API is used to
   populate the buffer, else the underlying STRING/TEXT API is
   used.  For many purposes there will be no difference, but if you
   know you might have binary data, be sure to pass a true value
   for asBlob to avoid any potential encoding-related problems.

   Results are undefined if any pointer argument is NULL. Returns
   FSL_RC_MISUSE if the SQL is an empty string.
*/
FSL_EXPORT int fsl_db_get_buffer( fsl_db * const db, fsl_buffer * const tgt,
                                  bool asBlob, char const * sql,
                                  ... );

/**
   va_list counterpart of fsl_db_get_buffer().







|
>






<
<
<







9396
9397
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407
9408
9409
9410



9411
9412
9413
9414
9415
9416
9417
   or if no result is found, NULL is returned.
*/
FSL_EXPORT void * fsl_db_g_blob( fsl_db * const db, fsl_size_t * len,
                      char const * sql,
                      ... );
/**
   Similar to fsl_db_get_text() and fsl_db_get_blob(), but writes
   its result to tgt, overwriting (not appending to) any existing
   memory it might hold.

   If asBlob is true then the underlying BLOB API is used to
   populate the buffer, else the underlying STRING/TEXT API is
   used.  For many purposes there will be no difference, but if you
   know you might have binary data, be sure to pass a true value
   for asBlob to avoid any potential encoding-related problems.



*/
FSL_EXPORT int fsl_db_get_buffer( fsl_db * const db, fsl_buffer * const tgt,
                                  bool asBlob, char const * sql,
                                  ... );

/**
   va_list counterpart of fsl_db_get_buffer().
13831
13832
13833
13834
13835
13836
13837
13838
13839
13840
13841
13842
13843
13844
13845
13846
13847
13848
13849
13850
13851
13852
13853
13854
13855

13856
13857
13858
13859
13860
13861
13862
13863
13864
13865
13866
13867
13868
13869
13870
13871
13872
13873
13874
13875
13876
13877
13878
13879
13880
13881
13882
13883
13884


13885
13886
13887
13888
13889
13890
13891
13892
13893
13894
13895
13896
13897
13898
13899
13900
13901
13902
13903
13904
13905
13906
13907
   Resolves client-provided symbol as an artifact's db record ID.
   f must have an opened repository db, and some symbols can only
   be looked up if it has an opened checkout (see the list below).

   Returns 0 and sets *rv to the id if it finds an unambiguous
   match.

   Returns FSL_RC_MISUSE if !sym, !*sym, or !rv.

   Returns FSL_RC_NOT_A_REPO if f has no opened repository.

   Returns FSL_RC_AMBIGUOUS if sym is a partial UUID which matches
   multiple full UUIDs.

   Returns FSL_RC_NOT_FOUND if it cannot find anything.

   Symbols supported by this function:

   - SHA1/3 hash
   - SHA1/3 hash prefix of at least 4 characters
   - Symbolic Name, e.g. branch name
   - "tag:" + symbolic name
   - Date or date-time 
   - "date:" + Date or date-time
   - symbolic-name ":" date-time

   - "tip" means the most recent checkin, regardless of its branch
   - "rid:###" resolves to the hash of blob.rid ### if that RID is in
     the database

   The following additional forms are available in local checkouts:

   - "current"
   - "prev" or "previous"
   - "next"

   The following prefix may be applied to the above to modify how
   they are resolved:

   - "root:" prefix resolves to the checkin of the parent branch from
   which the record's branch divered. i.e. the version from which it
   was branched. In the trunk this will always resolve to the first
   checkin.

   - "start:" prefix resolves to the first checkin of the branch to
   which the given checkin belongs. This differs from "root:" by a
   single checkin: the "root:" point is the parent checkin of the
   "start:" point.

   - "merge-in:" TODO - document this once its implications are
   understood.

   If type is not FSL_SATYPE_ANY then it will only match artifacts of
   the specified type. In order to resolve arbitrary UUIDs, e.g.
   those of arbitrary blob content, type needs to be FSL_SATYPE_ANY.


*/
FSL_EXPORT int fsl_sym_to_rid( fsl_cx * const f, char const * sym,
                               fsl_satype_e type, fsl_id_t * const rv );

/**
   Similar to fsl_sym_to_rid() but on success it returns a UUID string
   by assigning it to *rv (if rv is not NULL). If rid is not NULL then
   on success the db record ID corresponding to the returned UUID is
   assigned to *rid. The caller must eventually free the returned
   string memory by passing it to fsl_free(). Returns 0 if it finds a
   match and one of any number of possible result codes on error, most
   notably FSL_RC_NOT_FOUND if no match is found.
*/
FSL_EXPORT int fsl_sym_to_uuid( fsl_cx * const f, char const * sym,
                                fsl_satype_e type, fsl_uuid_str * const rv,
                                fsl_id_t * const rid );


/**
   Searches f's repo database for the a blob with the given uuid
   (any unique UUID prefix). On success a positive record ID is
   returned. On error one of several unspecified negative values is
   returned. If no uuid match is found 0 is returned.







|












|




>
|

|















<
<
<
<
<



|
|
|
>
>


|







|
<

|
|
|







13458
13459
13460
13461
13462
13463
13464
13465
13466
13467
13468
13469
13470
13471
13472
13473
13474
13475
13476
13477
13478
13479
13480
13481
13482
13483
13484
13485
13486
13487
13488
13489
13490
13491
13492
13493
13494
13495
13496
13497
13498
13499
13500
13501





13502
13503
13504
13505
13506
13507
13508
13509
13510
13511
13512
13513
13514
13515
13516
13517
13518
13519
13520

13521
13522
13523
13524
13525
13526
13527
13528
13529
13530
13531
   Resolves client-provided symbol as an artifact's db record ID.
   f must have an opened repository db, and some symbols can only
   be looked up if it has an opened checkout (see the list below).

   Returns 0 and sets *rv to the id if it finds an unambiguous
   match.

   Returns FSL_RC_MISUSE if !f, !sym, !*sym, or !rv.

   Returns FSL_RC_NOT_A_REPO if f has no opened repository.

   Returns FSL_RC_AMBIGUOUS if sym is a partial UUID which matches
   multiple full UUIDs.

   Returns FSL_RC_NOT_FOUND if it cannot find anything.

   Symbols supported by this function:

   - SHA1/3 hash
   - SHA1/3 hash prefix of at least 4 characters
   - Symbolic Name
   - "tag:" + symbolic name
   - Date or date-time 
   - "date:" + Date or date-time
   - symbolic-name ":" date-time
   - "tip"

   - "rid:###" resolves to the hash of blob.rid ### if that RID is in
   the database

   The following additional forms are available in local checkouts:

   - "current"
   - "prev" or "previous"
   - "next"

   The following prefix may be applied to the above to modify how
   they are resolved:

   - "root:" prefix resolves to the checkin of the parent branch from
   which the record's branch divered. i.e. the version from which it
   was branched. In the trunk this will always resolve to the first
   checkin.






   - "merge-in:" TODO - document this once its implications are
   understood.

   If type is not FSL_SATYPE_ANY then it will only match artifacts
   of the specified type. In order to resolve arbitrary UUIDs, e.g.
   those of arbitrary blob content, type needs to be
   FSL_SATYPE_ANY.

*/
FSL_EXPORT int fsl_sym_to_rid( fsl_cx * const f, char const * sym,
                               fsl_satype_e type, fsl_id_t * rv );

/**
   Similar to fsl_sym_to_rid() but on success it returns a UUID string
   by assigning it to *rv (if rv is not NULL). If rid is not NULL then
   on success the db record ID corresponding to the returned UUID is
   assigned to *rid. The caller must eventually free the returned
   string memory by passing it to fsl_free(). Returns 0 if it finds a
   match and any number of result codes on error.

*/
FSL_EXPORT int fsl_sym_to_uuid( fsl_cx * f, char const * sym,
                                fsl_satype_e type, fsl_uuid_str * rv,
                                fsl_id_t * rid );


/**
   Searches f's repo database for the a blob with the given uuid
   (any unique UUID prefix). On success a positive record ID is
   returned. On error one of several unspecified negative values is
   returned. If no uuid match is found 0 is returned.
14560
14561
14562
14563
14564
14565
14566
14567
14568
14569
14570
14571
14572
14573
14574
  fsl_satype_e artifactType;
};
/** Convenience typedef. */
typedef struct fsl_rebuild_step fsl_rebuild_step;

/** Initialized-with-defaults fsl_rebuild_step structure, intended for
    const-copy initialization. */
#define fsl_rebuild_step_empty_m {NULL,NULL,0,0,-1,false,FSL_SATYPE_INVALID}

/** Initialized-with-defaults fsl_rebuild_step structure, intended for
    non-const copy initialization. */
FSL_EXPORT const fsl_rebuild_step fsl_rebuild_step_empty;

/**
   Callback for use with fsl_repo_rebuild() in order to report







|







14184
14185
14186
14187
14188
14189
14190
14191
14192
14193
14194
14195
14196
14197
14198
  fsl_satype_e artifactType;
};
/** Convenience typedef. */
typedef struct fsl_rebuild_step fsl_rebuild_step;

/** Initialized-with-defaults fsl_rebuild_step structure, intended for
    const-copy initialization. */
#define fsl_rebuild_step_empty_m {NULL,NULL,0,0,-1,false}

/** Initialized-with-defaults fsl_rebuild_step structure, intended for
    non-const copy initialization. */
FSL_EXPORT const fsl_rebuild_step fsl_rebuild_step_empty;

/**
   Callback for use with fsl_repo_rebuild() in order to report
14727
14728
14729
14730
14731
14732
14733
14734
14735
14736
14737
14738
14739
14740
14741
};

/** Convenience typedef. */
typedef struct fsl_cidiff_opt fsl_cidiff_opt;

/** Initialized-with-defaults fsl_cidiff_opt structure, intended for
    const-copy initialization. */
#define fsl_cidiff_opt_empty_m {0,0,0,0}

/** Initialized-with-defaults fsl_cidiff_opt structure, intended for
    non-const copy initialization. */
FSL_EXPORT const fsl_cidiff_opt fsl_cidiff_opt_empty;

/**
   Descriptors for the type of information being reported for each







|







14351
14352
14353
14354
14355
14356
14357
14358
14359
14360
14361
14362
14363
14364
14365
};

/** Convenience typedef. */
typedef struct fsl_cidiff_opt fsl_cidiff_opt;

/** Initialized-with-defaults fsl_cidiff_opt structure, intended for
    const-copy initialization. */
#define fsl_cidiff_opt_empty_m {0,0}

/** Initialized-with-defaults fsl_cidiff_opt structure, intended for
    non-const copy initialization. */
FSL_EXPORT const fsl_cidiff_opt fsl_cidiff_opt_empty;

/**
   Descriptors for the type of information being reported for each
17381
17382
17383
17384
17385
17386
17387
17388
17389
17390
17391
17392
17393
17394
17395
17396
17397
   fsl_is_simple_pathname()), and any number of codes for db-related
   errors.

   This function matches only vfile.pathname, not vfile.origname,
   because it is possible for a given name to be in both fields (in
   different records) at the same time.
*/
FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * const f, fsl_id_t vid,
                                         char const * zName,
                                         fsl_id_t * const vfid );

/**
   Searches the `vfile` table where `vfile.vid=vid` for a name which
   matches `zName` or all `vfile` entries found under a subdirectory
   named `zName` (with no trailing slash). `zName` must be relative to
   the checkout root. As a special case, if `zName` is `NULL`, empty,
   or `"."` then all files in `vfile` with the given `vid` are







|

|







17005
17006
17007
17008
17009
17010
17011
17012
17013
17014
17015
17016
17017
17018
17019
17020
17021
   fsl_is_simple_pathname()), and any number of codes for db-related
   errors.

   This function matches only vfile.pathname, not vfile.origname,
   because it is possible for a given name to be in both fields (in
   different records) at the same time.
*/
FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid,
                                         char const * zName,
                                         fsl_id_t * vfid );

/**
   Searches the `vfile` table where `vfile.vid=vid` for a name which
   matches `zName` or all `vfile` entries found under a subdirectory
   named `zName` (with no trailing slash). `zName` must be relative to
   the checkout root. As a special case, if `zName` is `NULL`, empty,
   or `"."` then all files in `vfile` with the given `vid` are
18017
18018
18019
18020
18021
18022
18023
18024
18025
18026
18027
18028
18029
18030
18031
18032
18033
18034
18035
18036
18037
18038
18039
18040
18041
18042
18043
18044
18045
18046
18047
18048
18049
18050
18051
18052
18053
18054
18055
18056
18057
18058
18059
18060
18061
18062
18063
18064
18065
18066
18067
18068
18069
18070
18071
18072
18073
18074
18075
18076
18077
18078
18079
18080
18081
18082
18083
18084
18085
18086
18087
18088
18089
18090
18091
18092
18093
18094
18095
18096
18097
18098
18099
18100
18101
18102
18103
18104
18105
18106
18107
18108
18109
18110
18111
18112
18113
18114
18115
18116
18117
18118
18119
18120
18121
18122
18123
18124
18125
18126
18127
18128
18129
18130
18131
18132
18133
18134
18135
18136
18137
18138
18139
18140
18141
18142
18143
18144
18145
18146
18147
18148
18149
18150
18151
18152
18153
18154
18155
18156
18157
18158
18159
18160
18161
18162
18163
18164
18165
18166
18167
18168
18169
18170
18171
18172
18173
18174
18175
18176
18177
18178
18179
18180
18181
18182
18183
18184
18185
18186
18187
18188
18189
18190
18191
18192
18193
18194
18195
18196
18197
18198
18199
18200
18201
18202
18203
18204
18205
18206
18207
18208
18209
18210
18211
18212
18213
18214
18215
18216
18217
18218
18219
18220
18221
18222
18223
18224
18225
18226
18227
18228
18229
18230
18231
18232
18233
18234
18235
18236
18237
18238
18239
18240
18241
18242
18243
18244
18245
18246
18247
   databases. The array is terminated by a NULL element.

   As of this writing, and for the foreseeable future, the list is
   comprised of only 3 elements, {".fslckout", "_FOSSIL_", NULL}, but
   the order of the non-NULL elements is unspecified by the interface.
*/
FSL_EXPORT char const ** fsl_ckout_dbnames(void);

/** Convenience typedef. */
typedef struct fsl_ckout_rename_opt fsl_ckout_rename_opt;
/**
   Callback type for use with fsl_ckout_rename(). It gets called once per
   iteration of a rename operation, along with the original options object
   for the rename and:

   - zSrcName is the checkout-relative original name of the file.
   - zDestname is the checkout-relative new name of the file.

   Both strings are owned by the calling operation and will be freed
   soon after this call returns. If the client needs them, they must
   make copies.

   It must returns 0 on success. If it returns non-0 then the current
   renaming op is cancelled (rolled back) and the result code is
   propagated back to the caller of fsl_ckout_rename().

   This gets called immediately after the associated db record is
   modified and before the filesystem move (if any) is performed.

   Design note: ideally the callback would be called after the
   filesystem move, but the way the moves are currently processed (as
   a batch after the db updates, noting that those updates may trigger
   variou error conditions) precludes that. Alternately, we could
   delay the callback until the file-move phase (noting that the
   file-move step is optional), and that might be a sensible change to
   make.
*/
typedef int (*fsl_ckout_rename_f)(fsl_cx *, fsl_ckout_rename_opt const *,
                                  char const * zSrcName, char const *zDestName);

/**
   Options object for use with fsl_ckout_rename().
*/
struct fsl_ckout_rename_opt {
  /**
     The source filename(s) or directory name(s) to rename. The
     contents of this list _must not_ be modified while
     fsl_ckout_rename() is running (e.g. via a callback function).
  */
  fsl_list const * src;
  /**
     The target filename or directory name to rename to.
  */
  char const * dest;
  /**
     If true, src and dest are resolved/normalized based on the current
     working directory, else they must be relative to the top of the
     checkout.
  */
  bool relativeToCwd;
  /**
     If true fsl_ckout_rename() will attempt to move files within the
     filesystem. If false, it will only do the db-side renaming.
  */
  bool doFsMv;
  /**
     If true, fsl_ckout_rename() will not perform any lasting
     operations but will "go through the motions" with the exception
     of actually attempting to move files on disk (if doFsMv is
     true). It will still report errors in this mode, with the
     exception that filesystem-level moving is not attempted so
     potential failures there cannot be detected.

     Achtung: in dry-run mode it will trigger a rollback of any
     pending transaction which is opened before fsl_ckout_rename() is
     called.
  */
  bool dryRun;
  /** Optional callback. May be NULL. */
  fsl_ckout_rename_f callback;
  /** Optional state for the callback. */
  void * callbackState;
};

/** Initialized-with-defaults fsl_ckout_rename_opt structure, intended for
    const-copy initialization. */
#define fsl_ckout_rename_opt_empty_m {NULL,NULL,true,false,false,NULL,NULL}

/** Initialized-with-defaults fsl_ckout_rename_opt structure, intended for
    non-const copy initialization. */
extern const fsl_ckout_rename_opt fsl_ckout_rename_opt_empty;

/**
   This routine renames one or more SCM-managed files matching all
   entries in opt->src to opt->dest within the current checkout. This
   updates the current checkout's vfile table with the changes, and
   optionally attempts to move the files within the filesystem, but
   does not commit anything, so the change may still be reverted later
   on.

   If an entry in opt->src matches an existing directory name within
   the SCM-managed file list, this function assumes that all files
   under that directory are to be moved.

   If opt->relativeToCwd is true then the destination name and all
   opt->src names are canonicalized based on the current working
   directory (see fsl_getcwd()), otherwise they are assumed to be
   relative to f's current checkout directory.

   If opt->dest refers to an _existing_ directory, when an opt->src
   entry refers to a directory then that whole directory, including the
   directory name part, is renamed. If opt->dest does not exist in the
   filesystem and if opt->src resolves to multiple inputs, opt->dest is
   assumed to be a new target directory but the renaming behavior of
   the opt->src files differs. Examples:

   - Case 1: opt->dest refers to an existing directory: moving
     directory foo/bar to baz will result in baz/bar.

   - Case 2: opt->dest does not refer to an existing directory: moving
   directory foo/bar to baz results in baz with the previous contents
   of foo/bar.

   Why? Because that's how fossil(1) does it.

   This function matches opt->src entries only against
   `vfile.pathname`, not `vfile.origname`.

   If opt->doFsMv is true then the final operation this function
   attempts is to rename source files to their new destination. It
   will only attempt this in cases where (A) the original file exists
   and (B) the destination file does _not_ exist. Any errors generated
   during this filesystem-level rename/move step _are ignored_ for two
   reasons: 1) fossil(1) does it that way and (2) after moving any
   files, trying to roll back and undo the filesystem-level operations
   would add a significant amount of complexity and new errror cases
   (what happens when a try-to-undo-rename fails?).

   Returns 0 on succcess. Potential errors include:

   - FSL_RC_OOM on allocation error.

   - FSL_RC_NOT_A_CKOUT if f has no opened checkout.

   - FSL_RC_MISUSE: opt->src contains multiple entries but the
   destination is not an _existing_ directory.

   - FSL_RC_ALREADY_EXISTS: cannot rename because the destination name
   is already in use by another record.

   - FSL_RC_TYPE: opt->dest refers to a non-directory and an opt->src
   entry refers to a directory.

   - Any potential DB-related errors.

   BUGS/Shortcomings:

   - If it fails moving files in the filesystem, it might have
   successfully already moved one or more source files, and those
   moves are not undone.

   - Depending on the case-sensitivity of the repo and the filesystem,
   it might (UNTESTED) fail to rename a file if the new name differs
   only in its case (e.g. `README.TXT` to `README.txt`). Those
   relavant code renames such vfile entries but leaves the filesystem
   entries intact because fossil(1) does it that way.

   - If it moves a whole directory full of files it leaves any
   original directories intact. Improving this is on the TODO list.
   Often, such directories have contents (e.g. compiled/generated/temp
   files) and cannot be removed.
*/
FSL_EXPORT int fsl_ckout_rename(fsl_cx * const f,
                                fsl_ckout_rename_opt const * opt);

/**
   Undoes a rename scheduled by fsl_ckout_rename(). zNewName is the
   current (renamed) name of the file.

   If relativeToCwd is true then zNewName is canonicalized based on
   the current working directory (see fsl_getcwd()), otherwise it is
   assumed to be relative to f's current checkout directory.

   On success, or if no such pending rename is found, it returns 0.
   If it actually performs a db-level rename then it sets
   `*didSomething` to true if didSomething is not NULL. It should
   arguably return FSL_RC_NOT_FOUND (or some such) if no matching
   rename is pending, but that currently feels overly-pedantic.

   If doFsMv is true and a db entry is renamed and a file with the
   new name exists in the filesystem then this function will
   _delete_ any file which has the original name and rename the zNewName
   file to its reverted name. If that fails, the operation as a
   whole will fail and the db changes will be rolled back. Note,
   however, that a file deleted by this operation cannot be recovered
   and that the deletion must be done before the rename is attempted.

   On error, returns any of a wide array of non-0 result codes, only
   two of which can come directly from this function: FSL_RC_OOM or
   FSL_RC_NOT_A_CKOUT. Any other non-zero result codes are propagated
   errors from lower-level code.
*/
FSL_EXPORT int fsl_ckout_rename_revert(fsl_cx * const f,
                                       char const * zNewName,
                                       bool relativeToCwd, bool doFsMv,
                                       bool *didSomething);

/**
    Fetches the vfile.pathname value for the given vfile.id
    entry. Results are undefined if f does not have an opened
    checkout. On success returns 0 and sets `*zOut` to the name,
    transfering ownership to the caller (who must eventually pass it
    to fsl_free()). If absolute is true then the returned name has the
    checkout dir prepended to it, else it is relative to the top of
    the repo.

    Potential TODO: this routine is in no way optimized for heavy use
    within an app, expecting to be only rarely used, e.g. in
    conjunction with fsl_ckout_rename()-like ops. If it sees any
    significant use it should be refactored to use a cached statement.
*/
FSL_EXPORT int fsl_vfile_pathname(fsl_cx * const f, fsl_id_t vfid,
                                  bool absolute, char **zOut);


#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */
/* end of file ./include/fossil-scm/checkout.h */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







17641
17642
17643
17644
17645
17646
17647

























































































































































































































17648
17649
17650
17651
17652
17653
17654
   databases. The array is terminated by a NULL element.

   As of this writing, and for the foreseeable future, the list is
   comprised of only 3 elements, {".fslckout", "_FOSSIL_", NULL}, but
   the order of the non-NULL elements is unspecified by the interface.
*/
FSL_EXPORT char const ** fsl_ckout_dbnames(void);


























































































































































































































#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */
/* end of file ./include/fossil-scm/checkout.h */
18619
18620
18621
18622
18623
18624
18625
18626
18627
18628
18629
18630
18631
18632
18633
18634
18635
18636
18637
18638
18639
18640
18641
18642
18643
18644
18645
18646
18647
18648
18649
18650
18651
18652
18653
18654
18655
18656
18657
18658
18659
18660
18661
18662
18663
18664
18665

18666
18667
18668
18669
18670
18671
18672
/**
   Fetches the preferred name of the "global" db file for the current
   user by assigning it to *zOut. Returns 0 on success, in which case
   *zOut is updated and non-0 on error, in which case *zOut is not
   modified.  On success, ownership of *zOut is transferred to the
   caller, who must eventually free it using fsl_free().

   Errors include:

   - FSL_RC_OOM on allocation error
   - FSL_RC_NOT_FOUND (Windows only) if no relevant environment
     variables are set from which to form the db path.

   The locations searched for the database file are
   platform-dependent...

   Unix-like systems are searched in the following order:

   1) If the FOSSIL_HOME environment var is set, use
   $FOSSIL_HOME/.fossil.

   2) If $HOME/.fossil already exists, use that.

   3) If XDG_CONFIG_HOME environment var is set, use
   $XDG_CONFIG_HOME/fossil.db.

   4) If $HOME/.config is a directory, use $HOME/.config/fossil.db

   5) Fall back to $HOME/.fossil (historical name).

   Windows:

   1) If $FOSSIL_HOME is set, use that directory.
   2) If $LOCALAPPDATA is set, use that directory.
   3) If $APPDATA is set, use that directory.
   4) If $USERPROFILE is set, use that directory.
   5) If $HOMEDRIVE and $HOMEPATH are set, use that directory.

   If none of the above are set, FSL_RC_NOT_FOUND is returned.

   The name `_fossil` is appended to the first of the above-listed
   locations which is set in the environment.

   All platforms:

   Except where listed above, this function does not check whether the
   file already exists or is a database.

*/
FSL_EXPORT int fsl_config_global_preferred_name(char ** zOut);

/**
   A variant of fsl_config_get_int32() which can search through multiple
   config sources in an order specified by the caller.








<
<
<
<
<
<

















<
|
|
<
<
<
<

<
|
<
<

<
|
<
<
>







18026
18027
18028
18029
18030
18031
18032






18033
18034
18035
18036
18037
18038
18039
18040
18041
18042
18043
18044
18045
18046
18047
18048
18049

18050
18051




18052

18053


18054

18055


18056
18057
18058
18059
18060
18061
18062
18063
/**
   Fetches the preferred name of the "global" db file for the current
   user by assigning it to *zOut. Returns 0 on success, in which case
   *zOut is updated and non-0 on error, in which case *zOut is not
   modified.  On success, ownership of *zOut is transferred to the
   caller, who must eventually free it using fsl_free().







   The locations searched for the database file are
   platform-dependent...

   Unix-like systems are searched in the following order:

   1) If the FOSSIL_HOME environment var is set, use
   $FOSSIL_HOME/.fossil.

   2) If $HOME/.fossil already exists, use that.

   3) If XDG_CONFIG_HOME environment var is set, use
   $XDG_CONFIG_HOME/fossil.db.

   4) If $HOME/.config is a directory, use $HOME/.config/fossil.db

   5) Fall back to $HOME/.fossil (historical name).


   Except where listed above, this function does not check whether the
   file already exists or is a database.






   Windows:




   - We need a Windows port of this routine. Currently it `#error`'s


   out at compile-time on Windows.
*/
FSL_EXPORT int fsl_config_global_preferred_name(char ** zOut);

/**
   A variant of fsl_config_get_int32() which can search through multiple
   config sources in an order specified by the caller.

19007
19008
19009
19010
19011
19012
19013
19014
19015
19016
19017
19018
19019
19020
19021
19022
19023
19024
19025
19026
19027
19028
19029
19030
19031
19032
19033
19034
19035
19036
19037
19038
19039
19040
19041
19042
19043
19044
19045
19046
*/


#if defined(__cplusplus)
extern "C" {
#endif

/** @internal
   
    An internal helper macro to help localize OOM error reports (which
    are most often side effects of other issues, rather than being
    real OOM cases). It's intended to be used like:

    ```
    void * someAllocedMemory = fsl_malloc(...); // any allocating routine
    if(!someAllocedMemory){
      FSL__WARN_OOM; rc = FSL_RC_OOM; goto end;
    }
    ```

    or:

    ```
    if(FSL_RC_OOM==fs){ FSL__WARN_OOM; }
    ```

    Note that this macro _may_ expand to encompass multiple commands
    so must never be used as the RHS of an `if` without squiggly
    braces surrounding it.
*/
#define FSL__WARN_OOM \
  fprintf(stderr,"OOM @ %s:%d\n", __FILE__, __LINE__)

typedef struct fsl__bccache fsl__bccache;
typedef struct fsl__bccache_line fsl__bccache_line;
typedef struct fsl__pq fsl__pq;
typedef struct fsl__pq_entry fsl__pq_entry;

/** @internal








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







18398
18399
18400
18401
18402
18403
18404


























18405
18406
18407
18408
18409
18410
18411
*/


#if defined(__cplusplus)
extern "C" {
#endif



























typedef struct fsl__bccache fsl__bccache;
typedef struct fsl__bccache_line fsl__bccache_line;
typedef struct fsl__pq fsl__pq;
typedef struct fsl__pq_entry fsl__pq_entry;

/** @internal

21356
21357
21358
21359
21360
21361
21362











21363
21364
21365
21366
21367
21368
21369
   approximate FSL_RC_xxx match but treats SQLITE_ROW and SQLITE_DONE
   as non-errors (result code 0). If non-0 is returned db's error
   state is updated with the current sqlite3_errmsg() string.
*/
int fsl__db_errcode(fsl_db * const db, int sqliteCode);

/** @internal












   Plug in fsl_cx-specific db functionality into the given db handle.
   This must only be passed the MAIN db handle for the context,
   immediately after opening that handle, before f->dbMain is
   assigned.

   This function has very limited applicability and various







>
>
>
>
>
>
>
>
>
>
>







20721
20722
20723
20724
20725
20726
20727
20728
20729
20730
20731
20732
20733
20734
20735
20736
20737
20738
20739
20740
20741
20742
20743
20744
20745
   approximate FSL_RC_xxx match but treats SQLITE_ROW and SQLITE_DONE
   as non-errors (result code 0). If non-0 is returned db's error
   state is updated with the current sqlite3_errmsg() string.
*/
int fsl__db_errcode(fsl_db * const db, int sqliteCode);

/** @internal

    Clears various internal caches and resets various
    internally-cached values related to a repository db, but the data
    cleared here are not associated directly with a db handle. This is
    intended primarily to be used when a db transaction is rolled back
    which might have introduced state into those caches which would be
    stale after a rollback.
*/
void fsl__cx_clear_repo_caches(fsl_cx * const f);

/** @internal

   Plug in fsl_cx-specific db functionality into the given db handle.
   This must only be passed the MAIN db handle for the context,
   immediately after opening that handle, before f->dbMain is
   assigned.

   This function has very limited applicability and various
21405
21406
21407
21408
21409
21410
21411
21412
21413
21414
21415
21416
21417
21418
21419
21420
21421
21422
21423
21424
21425
21426
21427
21428
21429
21430
21431
21432
21433
21434
21435
21436
21437
21438
21439
21440
21441
21442
21443

/** @internal

    Frees/clears the non-db state of f->ckout.
*/
void fsl__cx_ckout_clear(fsl_cx * const f);


/** @internal
   Register the "files of checkin" (fsl_foci) SQLite3 virtual table.
*/
int fsl__foci_register(fsl_db * const db);
/** @internal

    Maximum length of a line in a text file, in bytes. (2**15 = 32k)
*/
#define FSL__LINE_LENGTH_MASK_SZ  15

/** @internal

    Bitmask which, when AND-ed with a number, will result in the
    bottom FSL__LINE_LENGTH_MASK_SZ bits of that number.
*/
#define FSL__LINE_LENGTH_MASK     ((1<<FSL__LINE_LENGTH_MASK_SZ)-1)

/** @internal

    Internal impl of fsl_buffer_err(), implemented as a macro for
    efficiency's sake.
*/
#define fsl__buffer_err(B) (B)->errCode

#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED */
/* end of file ./include/fossil-scm/internal.h */
/* start of file ./include/fossil-scm/auth.h */







<
<
<
<
<













<
<
<
<
<
<
<







20781
20782
20783
20784
20785
20786
20787





20788
20789
20790
20791
20792
20793
20794
20795
20796
20797
20798
20799
20800







20801
20802
20803
20804
20805
20806
20807

/** @internal

    Frees/clears the non-db state of f->ckout.
*/
void fsl__cx_ckout_clear(fsl_cx * const f);






/** @internal

    Maximum length of a line in a text file, in bytes. (2**15 = 32k)
*/
#define FSL__LINE_LENGTH_MASK_SZ  15

/** @internal

    Bitmask which, when AND-ed with a number, will result in the
    bottom FSL__LINE_LENGTH_MASK_SZ bits of that number.
*/
#define FSL__LINE_LENGTH_MASK     ((1<<FSL__LINE_LENGTH_MASK_SZ)-1)








#if defined(__cplusplus)
} /*extern "C"*/
#endif
#endif
/* ORG_FOSSIL_SCM_FSL_INTERNAL_H_INCLUDED */
/* end of file ./include/fossil-scm/internal.h */
/* start of file ./include/fossil-scm/auth.h */
22430
22431
22432
22433
22434
22435
22436

22437
22438
22439
22440
22441
22442
22443
22444
22445
22446
22447
22448
22449
22450
22451
22452
22453
22454
22455
22456
22457
22458
22459
   fcli use.
*/
#define FCLI_V(pfexp) FCLI_VN(1,pfexp)

#if defined(__cplusplus)
extern "C" {
#endif


/**
   Result codes specific to the fcli API.
*/
enum fcli_rc_e {
/**
   For use with fcli_flag_callback_f() implementations to indicate
   that the flag processor should check for that flag again.
*/
FCLI_RC_FLAG_AGAIN = FSL_RC_end + 1,
/**
   Returned from fcli_dispatch_commands() when the string returned
   from fcli_next_arg() does not match any fcli_command in the list.
*/
FCLI_RC_NO_CMD,
/**
   Returned from fcli_setup() if flag processing invokes the help
   system. This is an indication that the app should exit immediately
   with a 0 result code.
*/
FCLI_RC_HELP
};








>











<
<
<
<
<







21794
21795
21796
21797
21798
21799
21800
21801
21802
21803
21804
21805
21806
21807
21808
21809
21810
21811
21812





21813
21814
21815
21816
21817
21818
21819
   fcli use.
*/
#define FCLI_V(pfexp) FCLI_VN(1,pfexp)

#if defined(__cplusplus)
extern "C" {
#endif


/**
   Result codes specific to the fcli API.
*/
enum fcli_rc_e {
/**
   For use with fcli_flag_callback_f() implementations to indicate
   that the flag processor should check for that flag again.
*/
FCLI_RC_FLAG_AGAIN = FSL_RC_end + 1,
/**





   Returned from fcli_setup() if flag processing invokes the help
   system. This is an indication that the app should exit immediately
   with a 0 result code.
*/
FCLI_RC_HELP
};

22846
22847
22848
22849
22850
22851
22852
22853
22854
22855
22856
22857
22858
22859
22860
22861
22862
22863
22864
22865
22866
       default implementation uses fsl_outputer_FILE to output to
       stdout.
    */
    fsl_outputer outputer;

  } config;
  /**
     Search paths.
  */
  struct {
    /** Search object for binaries. */
    fsl_pathfinder bins;
  } paths;
  /**
     For holding pre-this->f-init error state. Once this->f is
     initialized, all errors reported via fcli_err_set() are stored in
     that object's error state.
  */
  fsl_error err;
};
typedef struct fcli_t fcli_t;







<
<
<
<
<
<
<







22206
22207
22208
22209
22210
22211
22212







22213
22214
22215
22216
22217
22218
22219
       default implementation uses fsl_outputer_FILE to output to
       stdout.
    */
    fsl_outputer outputer;

  } config;
  /**







     For holding pre-this->f-init error state. Once this->f is
     initialized, all errors reported via fcli_err_set() are stored in
     that object's error state.
  */
  fsl_error err;
};
typedef struct fcli_t fcli_t;
23109
23110
23111
23112
23113
23114
23115
23116
23117
23118
23119
23120
23121
23122
23123
23124
23125
23126
23127

/**
   Convenience macro for using fcli_err_report2().
*/
#define fcli_err_report(CLEAR) fcli_err_report2((CLEAR), __FILE__, __LINE__)

/**
   Peeks at or takes the next argument from the CLI args. If the
   argument is true, it is removed from the args list. It is owned by
   fcli and will be freed when the app exits.

   Returns NULL if the args list is empty.
*/
FSL_EXPORT const char * fcli_next_arg(bool remove);

/**
   If fcli.argv contains what looks like any flag arguments, this
   updates the fossil error state and returns FSL_RC_MISUSE, else
   returns 0. If outputError is true and an unused flag is found







|
|

<
<







22462
22463
22464
22465
22466
22467
22468
22469
22470
22471


22472
22473
22474
22475
22476
22477
22478

/**
   Convenience macro for using fcli_err_report2().
*/
#define fcli_err_report(CLEAR) fcli_err_report2((CLEAR), __FILE__, __LINE__)

/**
   Peeks at or takes the next argument from the CLI args.  If the
   argument is true, it is removed from the args list.  It is owned by
   fcli and will be freed when the app exits.


*/
FSL_EXPORT const char * fcli_next_arg(bool remove);

/**
   If fcli.argv contains what looks like any flag arguments, this
   updates the fossil error state and returns FSL_RC_MISUSE, else
   returns 0. If outputError is true and an unused flag is found
23176
23177
23178
23179
23180
23181
23182
23183
23184
23185
23186
23187
23188
23189
23190
23191
23192
23193
23194
23195
23196
23197
23198
23199
23200
23201
23202
23203
23204
23205
23206
23207
23208
23209
23210
23211
23212
23213
23214
23215
23216
23217
};

/**
   Expects an array of fcli_commands which contain a trailing
   sentry entry with a NULL name and callback. It searches the list
   for a command matching fcli_next_arg(). If found, it
   removes that argument from the list, calls the callback, and
   returns its result. If no command is found FCLI_RC_NO_CMD is
   returned, the argument list is not modified, and the error state
   is updated with a description of the problem and a list of all
   command names in cmdList.

   The distinct FCLI return code aims to help clients distinguish
   between fcli_dispatch_commands() failure and failures propagated
   by successfully dispatched fcli_command fcli_command_f callbacks.

   If reportErrors is true then on error this function outputs
   the error result but it keeps the error state in place
   for the downstream use.

   As a special case: when a command matches the first argument and
   that object has a non-NULL flags member, this function checks the
   _next_ argument, and if it is "help" then this function passes
   that flags member to fcli_command_help() to output help, then
   returns 0.
*/
FSL_EXPORT int fcli_dispatch_commands( fcli_command const * cmdList,
                                       bool reportErrors);

/**
   Parse cmd->aliases for an alias (i.e., NUL-terminated string semantically
   equivalent to cmd->name) matching arg. Return 0 if a match is found, else 1.
   Note that cmd->aliases _must_ be double-NUL terminated.
*/
FSL_EXPORT int fcli_cmd_aliascmp( fcli_command const * cmd, char const * arg );

/**
   A minor helper function intended to be passed the pending result
   code of the main() routine. This function outputs any pending
   error state in fcli. Returns one of EXIT_SUCCESS if mainRc is 0
   and fcli had no pending error report, otherwise it returns
   EXIT_FAILURE. This function does not clean up fcli - that is







|




<
<
<
<















|
|

|







22527
22528
22529
22530
22531
22532
22533
22534
22535
22536
22537
22538




22539
22540
22541
22542
22543
22544
22545
22546
22547
22548
22549
22550
22551
22552
22553
22554
22555
22556
22557
22558
22559
22560
22561
22562
22563
22564
};

/**
   Expects an array of fcli_commands which contain a trailing
   sentry entry with a NULL name and callback. It searches the list
   for a command matching fcli_next_arg(). If found, it
   removes that argument from the list, calls the callback, and
   returns its result. If no command is found FSL_RC_NOT_FOUND is
   returned, the argument list is not modified, and the error state
   is updated with a description of the problem and a list of all
   command names in cmdList.





   If reportErrors is true then on error this function outputs
   the error result but it keeps the error state in place
   for the downstream use.

   As a special case: when a command matches the first argument and
   that object has a non-NULL flags member, this function checks the
   _next_ argument, and if it is "help" then this function passes
   that flags member to fcli_command_help() to output help, then
   returns 0.
*/
FSL_EXPORT int fcli_dispatch_commands( fcli_command const * cmdList,
                                       bool reportErrors);

/**
   Parse cmd->aliases for an alias (i.e., NUL-terminated string semantically
   equivalent to cmd->name) matching arg. Return true if found. Note that
   cmd->aliases _must_ be double-NUL terminated.
*/
FSL_EXPORT bool fcli_cmd_aliascmp( fcli_command const * cmd, char const * arg );

/**
   A minor helper function intended to be passed the pending result
   code of the main() routine. This function outputs any pending
   error state in fcli. Returns one of EXIT_SUCCESS if mainRc is 0
   and fcli had no pending error report, otherwise it returns
   EXIT_FAILURE. This function does not clean up fcli - that is
23432
23433
23434
23435
23436
23437
23438
23439
23440
23441
23442
23443
23444
23445
23446
23447
23448
23449
23450
23451
23452
23453
23454
23455
23456
23457
23458
23459
23460
23461
23462
23463
23464
23465

/**
   Returns the "tail" part of the argv[0] string which was passed to
   fcli_setup(), or NULL if neither of those have yet been called. The
   "tail" part is the part immediately after the final '/' or '\\'
   character.
*/
FSL_EXPORT char const * fcli_progname(void);

/**
   Searches the `$PATH` for a binary named fossil or fossil.exe.  If
   found, that string is returned. The bytes are guaranteed to be
   alive until either the app ends or someone modifies
   fcli.path.bins. If not found, it returns NULL.

   If errIfNotFound is true and no binary is found, it will update
   fcli's error state with a description of the problem.

   If errIfNotFound is false, reportPolicy is ignored, else it is
   interpreted as:

   - 0 = do not fcli_err_report() any errors and retain error state.
   - <0 = report and retain the error state.
   - >0 = report and clear error state.
*/
FSL_EXPORT char const * fcli_fossil_binary(bool errIfNotFound,
                                           int reportPolicy);

/**
   Color theme IDs for use with fcli_diff_colors.
 */
enum fcli_diff_colors_e{
/**
   Tells fcli_diff_colors() to NULL out the ANSI color state of its







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







22779
22780
22781
22782
22783
22784
22785
22786



















22787
22788
22789
22790
22791
22792
22793

/**
   Returns the "tail" part of the argv[0] string which was passed to
   fcli_setup(), or NULL if neither of those have yet been called. The
   "tail" part is the part immediately after the final '/' or '\\'
   character.
*/
FSL_EXPORT char const * fcli_progname();




















/**
   Color theme IDs for use with fcli_diff_colors.
 */
enum fcli_diff_colors_e{
/**
   Tells fcli_diff_colors() to NULL out the ANSI color state of its
23477
23478
23479
23480
23481
23482
23483
23484
23485
23486
23487
23488
23489
23490
23491
23492
23493
23494
23495
23496
23497
23498
23499
23500
23501
23502
23503
23504
23505
23506
23507
23508
23509
23510
23511
23512
23513
23514
23515
23516
23517
23518
23519
23520
23521
23522
23523
23524
23525
23526
23527
23528
23529
23530
23531
23532
23533
23534
23535
23536
23537
23538
23539
23540
23541
23542
23543
23544
23545
23546
23547
23548
23549
23550
23551
23552
23553
23554
23555
23556
23557
23558
23559
23560
23561
23562
23563
23564

23565
23566
23567
23568

23569
23570
23571
23572
23573
23574
23575
/**
   Populates the given fsl_dibu_opt::ansiColors state with values
   dependend on the second argument.
*/
FSL_EXPORT void fcli_diff_colors(fsl_dibu_opt * const tgt,
                                 fcli_diff_colors_e theme);


/**
   Flags for use with fcli_sync().
*/
enum fcli_sync_e {
/**
   Only perform one of the request operations if the fossil config
   setting "fcli.autosync" or "autosync" is set, preferring the
   former. "autosync" is fossil(1) standard flag for this, but clients
   may use "fcli.autosync" to trump that without changing fossil's
   behavior.
*/
FCLI_SYNC_AUTO     = 0x01,
/** Perform a pull (read-only) sync. */
FCLI_SYNC_PULL     = 0x02,
/** Perform a push (write-only) sync. */
FCLI_SYNC_PUSH     = 0x04,
/** Perform a full sync. */
FCLI_SYNC_FULL     = FCLI_SYNC_PULL | FCLI_SYNC_PUSH,
/** Perform a full sync, but only if autosync is enabled. */
FCLI_SYNC_FULLAUTO = FCLI_SYNC_AUTO | FCLI_SYNC_FULL,
/** Redirect all stdout output from the fsl_system() to /dev/null.
    This only works on non-Windows platforms. stderr output is
    retained. */
FCLI_SYNC_NO_STDOUT = 0x10,
/**
   Works like FCLI_SYNC_NO_STDOUT, but also suppresses
   stderr.
 */
FCLI_SYNC_NO_OUTPUT =    0x20
};

/**
   Uses fcli_fossil_binary() to find the fossil(1) binary and run a
   sync operation, optionally either or both of push and pull
   operations.

   Its argument may be a mask of any values from the fcli_sync_e
   enum.

   Returns 0 if it does nothing or if fossil exits with a zero code.
   Returns FSL_RC_NOT_A_REPO if no repo is opened. If the repo is
   opened but has no "syncwith:" entries in its config table, no sync
   is attempted and 0 is returned.

   If fossil cannot be found, returns FSL_RC_NOT_FOUND, else returns
   as documented for fsl_system().

   This function will fail with FSL_RC_LOCKED if a transaction is
   currently open. Synching cannot succeed if a transaction is opened
   because the repo is write-locked during that time.

   Though this routine does not generate any console output,
   the proxied binary will, which may corrupt screen state of
   any hypothetical curses-style libfossil clients.
*/
//FSL_EXPORT int fcli_sync( bool push, bool pull );

FSL_EXPORT int fcli_sync( int ops );


/** @internal

   This function is intented for use in development of libfossil. It
   dumps the current state of cached SQL statements to fcli_printf().

   Normally its output detail level is determined by
   fcli_is_verbose(), but if forceVerbose is true then it cranks the
   detail all the way up.
 */
FSL_EXPORT void fcli_dump_stmt_cache(bool forceVerbose);

/** @internal

    This pseudo-internal function causes fcli to dump any
    library-level caching metrics it knows about. This function exists
    primarily to keep fcli client code from having to access the
    related internal members directly. This is a no-op if fcli has
    not been initialized.
*/
FSL_EXPORT void fcli_dump_cache_metrics(void);


#if defined(__cplusplus)
} /*extern "C"*/
#endif


#endif
/* _ORG_FOSSIL_SCM_FCLI_H_INCLUDED_ */
/* end of file ./include/fossil-scm/cli.h */
/* start of file ./include/fossil-scm/deprecated.h */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















>




>







22805
22806
22807
22808
22809
22810
22811





























































22812
22813
22814
22815
22816
22817
22818
22819
22820
22821
22822
22823
22824
22825
22826
22827
22828
22829
22830
22831
22832
22833
22834
22835
22836
22837
22838
22839
22840
22841
22842
22843
22844
/**
   Populates the given fsl_dibu_opt::ansiColors state with values
   dependend on the second argument.
*/
FSL_EXPORT void fcli_diff_colors(fsl_dibu_opt * const tgt,
                                 fcli_diff_colors_e theme);






























































/** @internal

   This function is intented for use in development of libfossil. It
   dumps the current state of cached SQL statements to fcli_printf().

   Normally its output detail level is determined by
   fcli_is_verbose(), but if forceVerbose is true then it cranks the
   detail all the way up.
 */
FSL_EXPORT void fcli_dump_stmt_cache(bool forceVerbose);

/** @internal

    This pseudo-internal function causes fcli to dump any
    library-level caching metrics it knows about. This function exists
    primarily to keep fcli client code from having to access the
    related internal members directly. This is a no-op if fcli has
    not been initialized.
*/
FSL_EXPORT void fcli_dump_cache_metrics(void);


#if defined(__cplusplus)
} /*extern "C"*/
#endif


#endif
/* _ORG_FOSSIL_SCM_FCLI_H_INCLUDED_ */
/* end of file ./include/fossil-scm/cli.h */
/* start of file ./include/fossil-scm/deprecated.h */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

Added signify/fnc-11-release.pub.





>
>
1
2
untrusted comment: fnc 0.11 public key
RWQIBA4rVoFFDlh8+FWU6Oeg79hcWqdRXv1cTOzrwTeSxKiIUhMd5rRE

Deleted signify/fnc-13-release.pub.

1
2
untrusted comment: fnc 0.13 public key
RWRBkQNnoj1IBcgbH7YSJa2155xwSKh7+TGI3jW8KwraJ/GGgXtJPp0I
<
<




Changes to src/diff.c.

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
 */
#define SBS_LLINE 0	/* Left line number */
#define SBS_LTEXT 1	/* Left text */
#define SBS_MID   2	/* Middle separator column */
#define SBS_RLINE 3	/* Right line number */
#define SBS_RTEXT 4	/* Right text */

/* Return values for validate_scope_line() */
#define VSL_FALSE	0
#define VSL_TRUE	1
#define VSL_PRIVATE	2
#define VSL_PROTECT	3
#define VSL_PUBLIC	4

/*
 * ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code
 */
#define ANSI_COLOR_BLACK(BOLD)		((BOLD) ? "\x1b[30m" : "\x1b[30m")
#define ANSI_COLOR_RED(BOLD)		((BOLD) ? "\x1b[31;1m" : "\x1b[31m")
#define ANSI_COLOR_GREEN(BOLD)		((BOLD) ? "\x1b[32;1m" : "\x1b[32m")
#define ANSI_COLOR_YELLOW(BOLD)		((BOLD) ? "\x1b[33;1m" : "\x1b[33m")







<
<
<
<
<
<
<







55
56
57
58
59
60
61







62
63
64
65
66
67
68
 */
#define SBS_LLINE 0	/* Left line number */
#define SBS_LTEXT 1	/* Left text */
#define SBS_MID   2	/* Middle separator column */
#define SBS_RLINE 3	/* Right line number */
#define SBS_RTEXT 4	/* Right text */








/*
 * ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code
 */
#define ANSI_COLOR_BLACK(BOLD)		((BOLD) ? "\x1b[30m" : "\x1b[30m")
#define ANSI_COLOR_RED(BOLD)		((BOLD) ? "\x1b[31;1m" : "\x1b[31m")
#define ANSI_COLOR_GREEN(BOLD)		((BOLD) ? "\x1b[32;1m" : "\x1b[32m")
#define ANSI_COLOR_YELLOW(BOLD)		((BOLD) ? "\x1b[33;1m" : "\x1b[33m")
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#define ANSI_BG_CYAN(BOLD)		((BOLD) ? "\x1b[46;1m" : "\x1b[46m")
#define ANSI_BG_WHITE(BOLD)		((BOLD) ? "\x1b[47;1m" : "\x1b[47m")
#define ANSI_RESET_COLOR		"\x1b[39;49m"
#define ANSI_RESET_ALL			"\x1b[0m"
#define ANSI_RESET			ANSI_RESET_ALL
/* #define ANSI_BOLD     ";1m" */

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 {
		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;
};
static const struct diff_out_state diff_out_state_ctor =
    { NULL, NULL, NULL, 0U, 0, '\0', { NULL, NULL, NULL, 0U, 0U, 0U } };

struct sbsline {
	struct diff_out_state	*output;
	fsl_buffer		*cols[5];	/* Pointers to output columns */
	const char		*tag;		/* <span> tag */
	const char		*tag2;		/* <span> tag */
	int			 idx;		/* Write tag before idx */
	int			 end;		/* Close tag before end */
	int			 idx2;		/* Write tag2 before idx2 */
	int			 end2;		/* Close tag2 before end2 */
	int			 width;		/* Max column width in diff */
	bool			 esc;		/* Escape html characters */
	void			*regex;		/* Colour matching lines */
};

static int		 diff_blobs(fsl_buffer const *, fsl_buffer const *,
			    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,
			    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 *);
static int		 unidiff(fsl__diff_cx *, struct diff_out_state *,
			    void *, uint16_t, uint64_t);
static int		 unidiff_lineno(struct diff_out_state *, int, int, bool);
static int		 unidiff_txt( struct diff_out_state *const, char,
			    fsl_dline *, int, void *);
static int		 sbsdiff(fsl__diff_cx *, struct diff_out_state *,
			    void *, uint16_t, uint64_t);
static int		 max_sbs_width(fsl__diff_cx *, int *, unsigned short,
			    uint16_t);
static int		 sbsdiff_width(uint64_t);
static int		 sbsdiff_separator(struct sbsline *, int, int);
static int		 sbsdiff_lineno(struct sbsline *, int, int);
static void		 sbsdiff_shift_left(struct sbsline *, const char *);
static void		 sbsdiff_simplify_line(struct sbsline *, const char *);
static int		 sbsdiff_column(struct diff_out_state *,
			    fsl_buffer const *, int);
static int		 sbsdiff_txt(struct sbsline *, fsl_dline *, int);
static int		 sbsdiff_newline(struct sbsline *);
static int		 sbsdiff_space(struct sbsline *, int, int);
static int		 sbsdiff_marker(struct sbsline *, const char *,
			    const char *);
static int		 sbsdiff_close_gap(int *);
static unsigned char	*sbsdiff_align(fsl_dline *, int, fsl_dline *, int);
static int		 sbsdiff_write_change(struct sbsline *, fsl_dline *,
			    int, fsl_dline *, int);
int
fnc_diff_text_raw(fsl_buffer const *blob1, fsl_buffer const *blob2,
    int flags, int **out)
{
	return diff_blobs(blob1, blob2, NULL, NULL, NULL, NULL, 0, 0,
	    flags, out);
}

int
fnc_diff_text_to_buffer(fsl_buffer const *blob1, fsl_buffer const *blob2,
    fsl_buffer *out, enum line_type **lines, uint32_t *nlines, short context,
    short sbswidth, int flags)
{
	return (blob1 && blob2 && out) ?
	    diff_blobs(blob1, blob2, fsl_output_f_buffer, out, lines,
	    nlines, context, sbswidth, flags, NULL) : FSL_RC_MISUSE;
}

int
fnc_diff_text(fsl_buffer const *blob1, fsl_buffer const *blob2,
    fsl_output_f out, void *state, enum line_type **lines, uint32_t *nlines,
    short context, short sbswidth, int flags)
{
	return diff_blobs(blob1, blob2, out, state, lines, nlines,
	    context, sbswidth, flags, NULL);
}

/*

 * Diff two arbitrary blobs and either stream output to the third argument
 * or return an array of copy/delete/insert triples via the final argument.
 * The third XOR final argument must be set.
 *
 * If the third argument is not NULL:
 *   state     opaque state value passed to the third when emitting output
 *   context   number of context lines (negative values fallback to default)
 *   sbswidth  sbs diff width (0 = unidiff; negative values fallback to default)
 *
 * If the final argument is not NULL, it is assigned a pointer to the result
 * array of copy/delete/insert triples. Ownership is transfered to the caller,
 * who must eventually dispose of it with fsl_free().
 *
 * Return 0 on success, any number of other codes on error.
 */
static int
diff_blobs(fsl_buffer const *blob1, fsl_buffer const *blob2,
    fsl_output_f out, void *state, enum line_type **lines, uint32_t *nlines,
    /* void *regex, */ uint16_t context, short sbswidth, int flags,
    int **rawdata)
{
	fsl__diff_cx	c = fsl__diff_cx_empty;
	int		rc;








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




|









|





<
|

|




>















|
|







82
83
84
85
86
87
88











































































89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#define ANSI_BG_CYAN(BOLD)		((BOLD) ? "\x1b[46;1m" : "\x1b[46m")
#define ANSI_BG_WHITE(BOLD)		((BOLD) ? "\x1b[47;1m" : "\x1b[47m")
#define ANSI_RESET_COLOR		"\x1b[39;49m"
#define ANSI_RESET_ALL			"\x1b[0m"
#define ANSI_RESET			ANSI_RESET_ALL
/* #define ANSI_BOLD     ";1m" */












































































int
fnc_diff_text_raw(fsl_buffer const *blob1, fsl_buffer const *blob2,
    int flags, int **out)
{
	return fnc_diff_blobs(blob1, blob2, NULL, NULL, NULL, NULL, 0, 0,
	    flags, out);
}

int
fnc_diff_text_to_buffer(fsl_buffer const *blob1, fsl_buffer const *blob2,
    fsl_buffer *out, enum line_type **lines, uint32_t *nlines, short context,
    short sbswidth, int flags)
{
	return (blob1 && blob2 && out) ?
	    fnc_diff_blobs(blob1, blob2, fsl_output_f_buffer, out, lines,
	    nlines, context, sbswidth, flags, NULL) : FSL_RC_MISUSE;
}

int
fnc_diff_text(fsl_buffer const *blob1, fsl_buffer const *blob2,

    fsl_output_f out, void *state, short context, short sbswidth, int flags)
{
	return fnc_diff_blobs(blob1, blob2, out, state, NULL, NULL,
	    context, sbswidth, flags, NULL);
}

/*
 *
 * Diff two arbitrary blobs and either stream output to the third argument
 * or return an array of copy/delete/insert triples via the final argument.
 * The third XOR final argument must be set.
 *
 * If the third argument is not NULL:
 *   state     opaque state value passed to the third when emitting output
 *   context   number of context lines (negative values fallback to default)
 *   sbswidth  sbs diff width (0 = unidiff; negative values fallback to default)
 *
 * If the final argument is not NULL, it is assigned a pointer to the result
 * array of copy/delete/insert triples. Ownership is transfered to the caller,
 * who must eventually dispose of it with fsl_free().
 *
 * Return 0 on success, any number of other codes on error.
 */
int
fnc_diff_blobs(fsl_buffer const *blob1, fsl_buffer const *blob2,
    fsl_output_f out, void *state, enum line_type **lines, uint32_t *nlines,
    /* void *regex, */ uint16_t context, short sbswidth, int flags,
    int **rawdata)
{
	fsl__diff_cx	c = fsl__diff_cx_empty;
	int		rc;

272
273
274
275
276
277
278

279



280
281
282
283
284
285
286
287
288
289
290



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
		}
	}
	/* fsl__dump_triples(&c, __FILE__, __LINE__); */  /* DEBUG */
	if (!FLAG_CHK(flags, FNC_DIFF_NOOPT))
		fsl__diff_optimize(&c);
	/* fsl__dump_triples(&c, __FILE__, __LINE__); */  /* DEBUG */


	if (FLAG_CHK(flags, FNC_DIFF_SIDEBYSIDE)) {



		rc = max_sbs_width(&c, &flags, sbswidth, context);
		if (rc)
			goto end;
	}

	if (out) {
		/*
		 * Compute a unified or side-by-side diff.
		 * XXX Missing regex support.
		 */
		struct diff_out_state dos = diff_out_state_ctor;




		dos.out = out;
		dos.state = state;
		if (lines && nlines) {
			dos.lines = *lines;
			dos.nlines = *nlines;
		}
		dos.ansi = !!(FLAG_CHK(flags, FNC_DIFF_ANSI_COLOR));
		if (FLAG_CHK(flags, FNC_DIFF_PROTOTYPE))
			dos.proto.file = blob1;

		if (FLAG_CHK(flags, FNC_DIFF_SIDEBYSIDE))
			rc = sbsdiff(&c, &dos, NULL /*regex*/, context, flags);
		else
			rc = unidiff(&c, &dos, NULL /*regex*/, context, flags);

		if (lines && nlines) {
			*lines = dos.lines;  /* realloc'd */
			*nlines = dos.nlines;
		}
		fsl_free(dos.proto.sig);
	} else if (rawdata) {
		/* Return array of COPY/DELETE/INSERT triples. */
		*rawdata = c.aEdit;
		c.aEdit = NULL;
	}
end:
	fsl_free(c.aFrom);
	fsl_free(c.aTo);
	fsl_free(c.aEdit);
	return rc;
}

/*
 * Find the longest line in the file and, if longer than the
 * currently set sbs width, encode the length in *flags.
 */
static int
max_sbs_width(fsl__diff_cx *c, int *flags, unsigned short width, uint16_t ctxt)
{
	int		i, j, k;
	int		endl, endr, start; /* Start and end of l+r hunks */
	unsigned short	sz;		   /* Size of each line */

	j = endl = endr = start = 0;
	k = c->nEdit;

	if (width < 0)
		width = 80;

	/* Find minimal copy/delete/insert triples. */
	while (k > 2 && c->aEdit[k - 1] == 0 && c->aEdit[k - 2] == 0)
		k -= 3;

	/*
	 * c[0] = first line in the diff and we accumulate each c[n += 3] to
	 * find the start of the next change (after adding any insertions);
	 * that is, each copy element by itself does not correspond to a line.
	 */
	do {
		int e, e2, s;

		/*
		 * Compute number of lines in both the left and right hunk.
		 * Both hunks will have the _same_ start line, but may have
		 * distinct end lines.
		 */
		start += c->aEdit[j];		  /* Hunk start line */
		endl = endr = start;
		endl += MAX(c->aEdit[j + 1], 0);  /* End of left side hunk */
		endr += MAX(c->aEdit[j + 2], 0);  /* End of right side hunk */
		s = MAX(start - ctxt, 0);	  /* Include leading ctxt */
		e = MIN(endl + ctxt, c->nFrom);	  /* L include trailing ctxt */
		e2 = MIN(endr + ctxt, c->nTo);	  /* R include trailing ctxt */

		/*
		 * Now find the longest line. We'll make both the left and right
		 * columns the width of the longest line from either side.
		 * XXX Consider keeping max width of both sides to make each
		 * column only as wide as its longest line; we can set a min
		 * default in diffs with added/removed files.
		 */
		for (i = s; i < e || i < e2; ++i) {
			if (i < e) {
				/* Left side line length */
				sz = etcount(c->aFrom[i].z, c->aFrom[i].n);
				if (sz > width)
					width = sz;
			}
			if (i < e2) {
				/* Right side line length */
				sz = etcount(c->aTo[i].z, c->aTo[i].n);
				if (sz > width)
					width = sz;
			}
		}
		if (i >= c->nFrom && i >= c->nTo)
			break;

		/* Account for insertions before adding lines to next hunk. */
		start += c->aEdit[j + 2];
	} while ((j += 3) < k);

	*flags |= ((int)(width & 0xFF)) << 16;  /* Encode max width */

	return FSL_RC_OK;
}

/*
 * Convert str byte size n to actual column width by expanding tabs and
 * accounting for unicode continuation bytes. Return the result.
 */
unsigned short
etcount(const char *str, unsigned short n)
{
	unsigned short c = 0;

	if (n < 0)
		n = fsl_strlen(str);

	while (str && (str[c] != '\n' && str[c] != '\0')) {
		/* Expand tabs */
		if (str[c] == '\t')
			n += (8 - ((c - (c ? 1 : 0)) % 8)) - 1;
		if (UTF_CONT(str[c]))
			--n;
		++c;
	}

	return n;
}

/*
 * Convert mask of public fnc_diff_flag (32-bit) values to the Fossil-internal
 * 64-bit bitmask used by the DIFF_xxx macros because fossil(1) uses the macro
 * approach and a low-level encoding of data in the bitmask (e.g., the context
 * lines count). The public API hides the lower-level flags and allows the
 * internal API to take care of the encoding.
 */
#if 0
static uint64_t
fnc_diff_flags_convert(int mask)
{
	uint64_t rc = 0U;

#define DO(f)	if ((mask & f) == f) rc |= (((uint64_t)f) << 24)
	DO(FNC_DIFF_IGNORE_EOLWS);
	DO(FNC_DIFF_IGNORE_ALLWS);
	DO(FSL_DIFF2_LINE_NUMBERS);
	DO(FNC_DIFF_SIDEBYSIDE);
	DO(FNC_DIFF_NOTTOOBIG);
	DO(FNC_DIFF_STRIP_EOLCR);
	DO(FNC_DIFF_VERBOSE);
	DO(FNC_DIFF_BRIEF);
	DO(FNC_DIFF_HTML);
	DO(FNC_DIFF_NOOPT);
	DO(FNC_DIFF_INVERT);
#undef DO
	return rc;
}
#endif

static inline int
min(int a, int b)
{
	return a < b ? a : b;
}

/*
 * Return the number between 0 and 100 that is smaller the closer lline and
 * rline match. Return 0 for a perfect match. Return 100 if lline and rline
 * are completely different. The current algorithm is as follows:
 *
 *   1. Remove leading and trailing whitespace.
 *   2. Truncate both strings to at most 250 characters
 *   3. Find the length of the longest common subsequence
 *   4. Longer common subsequences yield lower scores.
 */
static int
match_dline(fsl_dline *lline, fsl_dline *rline)
{
	const char	*l, *r;		/* Left and right strings */
	int		 nleft, nright;	/* Bytes in l and r */
	int		 avg;		/* Average length of l and r */
	int		 i, j, k;	/* Loop counters */
	int		 best = 0;	/* Current longest match found */







>
|
>
>
>
|






|


|
>
>
>
|


<
|
|
<

<
<
<




<
<
|
|
<
<













|
<

|
|


|
|



|
<
<














|
|


|

|
|











|


|
|
|



|
|
|





|

|

|













<
<
<



|















<
|



















<

















|







190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

219
220

221



222
223
224
225


226
227


228
229
230
231
232
233
234
235
236
237
238
239
240
241

242
243
244
245
246
247
248
249
250
251
252


253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
		}
	}
	/* fsl__dump_triples(&c, __FILE__, __LINE__); */  /* DEBUG */
	if (!FLAG_CHK(flags, FNC_DIFF_NOOPT))
		fsl__diff_optimize(&c);
	/* fsl__dump_triples(&c, __FILE__, __LINE__); */  /* DEBUG */

	/* Encode longest line length in flags to set max sbs diff width */
	if ((!sbswidth && FLAG_CHK(flags, FNC_DIFF_SIDEBYSIDE)) ||
	    sbswidth < 0) {
		flags |= ((int)(sbswidth & 0xFFFF)) << 8;
		FLAG_SET(flags, FNC_DIFF_SIDEBYSIDE);
		rc = max_sbs_width(&c, &flags, context);
		if (rc)
			goto end;
	}

	if (out) {
		/*
		 * Compute a context or side-by-side diff.
		 * XXX Missing regex support.
		 */
		struct diff_out_state dos = diff_out_state_empty;
		if (FLAG_CHK(flags, FNC_DIFF_PROTOTYPE)) {
			memset(&dos.proto, 0, sizeof(dos.proto));
			dos.proto.file = blob1;
		}
		dos.out = out;
		dos.state = state;

		dos.lines = *lines;
		dos.nlines = *nlines;

		dos.ansi = !!(FLAG_CHK(flags, FNC_DIFF_ANSI_COLOR));



		if (FLAG_CHK(flags, FNC_DIFF_SIDEBYSIDE))
			rc = sbsdiff(&c, &dos, NULL /*regex*/, context, flags);
		else
			rc = unidiff(&c, &dos, NULL /*regex*/, context, flags);


		*lines = dos.lines;  /* realloc'd */
		*nlines = dos.nlines;


	} else if (rawdata) {
		/* Return array of COPY/DELETE/INSERT triples. */
		*rawdata = c.aEdit;
		c.aEdit = NULL;
	}
end:
	fsl_free(c.aFrom);
	fsl_free(c.aTo);
	fsl_free(c.aEdit);
	return rc;
}

/*
 * Find the longest line in the file and encode the length in *flags.

 */
int
max_sbs_width(fsl__diff_cx *c, int *flags, uint16_t ctxt)
{
	int		i, j, k;
	int		endl, endr, start; /* Start and end of l+r chunks */
	unsigned short	sz, sbswidth;	   /* Size of each line and max width */

	j = endl = endr = start = 0;
	k = c->nEdit;
	sbswidth = (*flags & FNC_DIFF_SIDEBYSIDE) ? sbsdiff_width(*flags) : 0;



	/* Find minimal copy/delete/insert triples. */
	while (k > 2 && c->aEdit[k - 1] == 0 && c->aEdit[k - 2] == 0)
		k -= 3;

	/*
	 * c[0] = first line in the diff and we accumulate each c[n += 3] to
	 * find the start of the next change (after adding any insertions);
	 * that is, each copy element by itself does not correspond to a line.
	 */
	do {
		int e, e2, s;

		/*
		 * Compute number of lines in both the left and right chunk.
		 * Both chunks will have the _same_ start line, but may have
		 * distinct end lines.
		 */
		start += c->aEdit[j];		  /* Chunk start line */
		endl = endr = start;
		endl += MAX(c->aEdit[j + 1], 0);  /* End of left side chunk */
		endr += MAX(c->aEdit[j + 2], 0);  /* End of right side chunk */
		s = MAX(start - ctxt, 0);	  /* Include leading ctxt */
		e = MIN(endl + ctxt, c->nFrom);	  /* L include trailing ctxt */
		e2 = MIN(endr + ctxt, c->nTo);	  /* R include trailing ctxt */

		/*
		 * Now find the longest line. We'll make both the left and right
		 * columns the width of the longest line from either side.
		 * XXX Consider keeping max width of both sides to make each
		 * column only as wide as its longest line; we can set a min
		 * default in diffs with added/removed files.
		 */
		for (i = s; sbswidth && (i < e || i < e2); ++i) {
			if (i < e) {
				/* Left side line length */
				sz = c->aFrom[i].n;
				if (sz > sbswidth)
					sbswidth = etcount(c->aFrom[i].z, sz);
			}
			if (i < e2) {
				/* Right side line length */
				sz = c->aTo[i].n;
				if (sz > sbswidth)
					sbswidth = etcount(c->aTo[i].z, sz);
			}
		}
		if (i >= c->nFrom && i >= c->nTo)
			break;

		/* Account for insertions before adding lines to next chunk. */
		start += c->aEdit[j + 2];
	} while ((j += 3) < k - 3);

	*flags |= ((int)(sbswidth & 0xFFF)) << 16;  /* Encode max width */

	return FSL_RC_OK;
}

/*
 * Convert str byte size n to actual column width by expanding tabs and
 * accounting for unicode continuation bytes. Return the result.
 */
unsigned short
etcount(const char *str, unsigned short n)
{
	unsigned short c = 0;




	while (str && (str[c] != '\n' && str[c] != '\0')) {
		/* Expand tabs */
		if (str[c] == '\t')
			n += 8 - (c % 8);
		if (UTF_CONT(str[c]))
			--n;
		++c;
	}

	return n;
}

/*
 * Convert mask of public fnc_diff_flag (32-bit) values to the Fossil-internal
 * 64-bit bitmask used by the DIFF_xxx macros because fossil(1) uses the macro
 * approach and a low-level encoding of data in the bitmask (e.g., the context
 * lines count). The public API hides the lower-level flags and allows the
 * internal API to take care of the encoding.
 */

uint64_t
fnc_diff_flags_convert(int mask)
{
	uint64_t rc = 0U;

#define DO(f)	if ((mask & f) == f) rc |= (((uint64_t)f) << 24)
	DO(FNC_DIFF_IGNORE_EOLWS);
	DO(FNC_DIFF_IGNORE_ALLWS);
	DO(FSL_DIFF2_LINE_NUMBERS);
	DO(FNC_DIFF_SIDEBYSIDE);
	DO(FNC_DIFF_NOTTOOBIG);
	DO(FNC_DIFF_STRIP_EOLCR);
	DO(FNC_DIFF_VERBOSE);
	DO(FNC_DIFF_BRIEF);
	DO(FNC_DIFF_HTML);
	DO(FNC_DIFF_NOOPT);
	DO(FNC_DIFF_INVERT);
#undef DO
	return rc;
}


static inline int
min(int a, int b)
{
	return a < b ? a : b;
}

/*
 * Return the number between 0 and 100 that is smaller the closer lline and
 * rline match. Return 0 for a perfect match. Return 100 if lline and rline
 * are completely different. The current algorithm is as follows:
 *
 *   1. Remove leading and trailing whitespace.
 *   2. Truncate both strings to at most 250 characters
 *   3. Find the length of the longest common subsequence
 *   4. Longer common subsequences yield lower scores.
 */
int
match_dline(fsl_dline *lline, fsl_dline *rline)
{
	const char	*l, *r;		/* Left and right strings */
	int		 nleft, nright;	/* Bytes in l and r */
	int		 avg;		/* Average length of l and r */
	int		 i, j, k;	/* Loop counters */
	int		 best = 0;	/* Current longest match found */
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
 *   lcs[1] = end of the common segment in left
 *   lcs[2] = start of the common segment in right
 *   lcs[3] = end of the common segment in right
 *
 * n.b. This computation is for display purposes only and does not have to be
 * optimal or exact.
 */
static bool
find_lcs(const char *left, int nleft, const char *right,
    int nright, int *lcs)
{
	const unsigned char	*l, *r;		/* Left and right strings */
	unsigned int		 probe;		/* Probe to compare target */
	unsigned int		 t[3];		/* 4-byte alignment targets */
	int			 ntargets;	/* Number of target points */







|







469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
 *   lcs[1] = end of the common segment in left
 *   lcs[2] = start of the common segment in right
 *   lcs[3] = end of the common segment in right
 *
 * n.b. This computation is for display purposes only and does not have to be
 * optimal or exact.
 */
bool
find_lcs(const char *left, int nleft, const char *right,
    int nright, int *lcs)
{
	const unsigned char	*l, *r;		/* Left and right strings */
	unsigned int		 probe;		/* Probe to compare target */
	unsigned int		 t[3];		/* 4-byte alignment targets */
	int			 ntargets;	/* Number of target points */
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
	}
	return rc;
}

/*
 * Send src to o->out(). If n is negative, use strlen() to determine length.
 */
static int
diff_out(struct diff_out_state *const o, void const *src, fsl_int_t n)
{
	return o->rc = n ?
	    o->out(o->state, src, n < 0 ? strlen((char const *)src) : (size_t)n)
	    : 0;
}

/*
 * 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;
	int rc = FSL_RC_OK;








|











|







540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
	}
	return rc;
}

/*
 * Send src to o->out(). If n is negative, use strlen() to determine length.
 */
int
diff_out(struct diff_out_state *const o, void const *src, fsl_int_t n)
{
	return o->rc = n ?
	    o->out(o->state, src, n < 0 ? strlen((char const *)src) : (size_t)n)
	    : 0;
}

/*
 * 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.
 */
int
buffer_copy_lines_from(fsl_buffer *const dst, const fsl_buffer *const src,
    fsl_size_t *offset, fsl_size_t seek, fsl_size_t n)
{
	const char *z = (const char *)src->mem;
	fsl_size_t idx = *offset, ln = 0, start = 0;
	int rc = FSL_RC_OK;

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
706
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
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
	if (dst)  /* trim trailing '\n' when copying */
		rc = fsl_buffer_append(dst, &src->mem[start], idx - start - 1);
	*offset = start;
	return rc;
}

/*
 * 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 (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;
		if (starts_with(str, "protected"))
			return VSL_PROTECT;
		if (starts_with(str, "public"))
			return VSL_PUBLIC;
	}

	return VSL_FALSE;
}

/*
 * 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)
{
	fsl_buffer	 buf = fsl_buffer_empty;

	char		*line = NULL;
	fsl_size_t	 offset;
	uint32_t	 last = dst->proto.lastline;
	int		 rc = FSL_RC_OK;

	dst->proto.lastline = pos;
	offset = dst->proto.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)
			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)";
			break;
		case VSL_PROTECT:
			if (!dst->proto.spec)
				dst->proto.spec = " (protected)";
			break;
		case VSL_PUBLIC:
			if (!dst->proto.spec)
				dst->proto.spec = " (public)";
			break;
		case VSL_TRUE:
			fsl_free(dst->proto.sig);
			dst->proto.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;


			return FSL_RC_OK;
		}


		/* No match, revert to last offset. */
		fsl_free(line);
		offset = dst->proto.offset;

		--pos;
	}

	return FSL_RC_OK;
}

/*
 * Render the diff triples array in cx->aEdit as a side-by-side diff in out.
 *   cx		Raw diff data
 *   out	Side-by-side diff representation
 *   regex	Show changes matching this regex
 *   context	Number of context lines
 *   flags	Flags controlling the diff
 */
static int
sbsdiff(fsl__diff_cx *cx, struct diff_out_state *dst, void *regex,
    uint16_t context, uint64_t flags)
{
	fsl_dline	*l, *r;		/* Left and right side of diff */
	fsl_buffer	 sbscols[5] = {
			    fsl_buffer_empty_m, fsl_buffer_empty_m,
			    fsl_buffer_empty_m, fsl_buffer_empty_m,
			    fsl_buffer_empty_m
			 };
	struct sbsline	  s;		/* Output line buffer */
	static int	  hunks = 0;	/* Number of hunks so far processed */
	int		  li, ri;	/* Index of next line in l[] and r[] */
	int		 *c;		/* copy/delete/insert triples */
	int		  ci;		/* Index into c[] */
	int		  nc;		/* number of c[] triples to process */
	int		  max_ci;	/* Maximum value for ci */
	int		  nleft, nright; /* Number of l and r lines to output */
	int		  ntotal;	/* Total number of lines to output */







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
|

|
|


>
|


<





|

<
<
|
>
>
>
>
>
|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
|
<
<
|
<
<
<
<
<
<
<
<
<
<
<
|
|
>
|
|
>
|
|
|
|
|
>
>
|
|
|
>

<

>


|
<










|










|







582
583
584
585
586
587
588



























589
590

591
592
593
594
595
596
597
598
599
600

601
602
603
604
605
606
607


608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629

630
631


632











633
634
635
636
637
638
639
640
641
642
643
644
645
646
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
	if (dst)  /* trim trailing '\n' when copying */
		rc = fsl_buffer_append(dst, &src->mem[start], idx - start - 1);
	*offset = start;
	return rc;
}

/*



























 * Scan the diffed file dst->proto->file from line pos preceding the start of
 * the current chunk for the enclosing function in which the change resides.

 * Return first match.
 */
char *
match_chunk_function(struct diff_out_state *const dst, uint32_t pos)
{
	fsl_buffer	 buf = fsl_buffer_empty;
	const char	*line;
	char		*spec = NULL;
	fsl_size_t	 offset;
	uint32_t	 last = dst->proto.lastline;


	dst->proto.lastline = pos;
	offset = dst->proto.offset;  /* Begin seek from last match */

	while (pos > 1 && pos > last) {
		buffer_copy_lines_from(&buf, dst->proto.file, &offset,
		    pos - dst->proto.lastmatch, 1);


		line = fsl_buffer_cstr(&buf);
		/*
		 * 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
		 */
		if (line) {
			if (fsl_isalpha(line[0]) || line[0] == '_' ||
			    line[0] == '$'){
				if (starts_with(line, "private:")) {
					if (!spec)
						spec = " (private)";
				} else if (starts_with(line, "protected:")) {
					if (!spec)
						spec = " (protected)";
				} else if (starts_with(line, "public:")) {
					if (!spec)
						spec = " (public)";
				} else {
					/*
					 * Don't exceed 80 cols: chunk header
					 * consumes ~25, so cap signature at 55.

					 */
					char *sig = fsl_mprintf("%s%s", line,


					    spec ? spec : "");











					fsl_free(dst->proto.signature);
					dst->proto.signature =
					    fsl_mprintf("%.55s", sig);
					/*
					 * It's expensive to seek from the start
					 * of the file for each chunk when
					 * diffing large files, so save offset
					 * and line index of this match.
					 */
					dst->proto.lastmatch = pos;
					dst->proto.offset = offset;
					fsl_free(sig);
					fsl_buffer_clear(&buf);
					return dst->proto.signature;
				}
			}
		}
		/* No match, revert to last offset. */

		offset = dst->proto.offset;
		fsl_buffer_clear(&buf);
		--pos;
	}
	return dst->proto.lastmatch > 0 ? dst->proto.signature : NULL;

}

/*
 * Render the diff triples array in cx->aEdit as a side-by-side diff in out.
 *   cx		Raw diff data
 *   out	Side-by-side diff representation
 *   regex	Show changes matching this regex
 *   context	Number of context lines
 *   flags	Flags controlling the diff
 */
int
sbsdiff(fsl__diff_cx *cx, struct diff_out_state *dst, void *regex,
    uint16_t context, uint64_t flags)
{
	fsl_dline	*l, *r;		/* Left and right side of diff */
	fsl_buffer	 sbscols[5] = {
			    fsl_buffer_empty_m, fsl_buffer_empty_m,
			    fsl_buffer_empty_m, fsl_buffer_empty_m,
			    fsl_buffer_empty_m
			 };
	struct sbsline	  s;		/* Output line buffer */
	static int	  chunks = 0;	/* Number of chunks so far processed */
	int		  li, ri;	/* Index of next line in l[] and r[] */
	int		 *c;		/* copy/delete/insert triples */
	int		  ci;		/* Index into c[] */
	int		  nc;		/* number of c[] triples to process */
	int		  max_ci;	/* Maximum value for ci */
	int		  nleft, nright; /* Number of l and r lines to output */
	int		  ntotal;	/* Total number of lines to output */
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
		for (i = 1; i < nc; ++i) {
			nleft += c[ci + i * 3];
			nright += c[ci + i * 3];
		}

		/* Draw separator between blocks except the first. */
		if (showsep) {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_SEPARATOR);
				if (rc)
					goto end;
			}
			if (s.esc) {
				char ln[10];
				fsl_snprintf(ln, sizeof(ln), "%d",
				    li + skip + 1);
				rc = sbsdiff_separator(&s, fsl_strlen(ln),
				    SBS_LLINE);
				if (rc)







<
|
<
|
|
<







776
777
778
779
780
781
782

783

784
785

786
787
788
789
790
791
792
		for (i = 1; i < nc; ++i) {
			nleft += c[ci + i * 3];
			nright += c[ci + i * 3];
		}

		/* Draw separator between blocks except the first. */
		if (showsep) {

			rc = add_line_type(lines, nlines, LINE_DIFF_SEPARATOR);

			if (rc)
				goto end;

			if (s.esc) {
				char ln[10];
				fsl_snprintf(ln, sizeof(ln), "%d",
				    li + skip + 1);
				rc = sbsdiff_separator(&s, fsl_strlen(ln),
				    SBS_LLINE);
				if (rc)
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947

948
949
950
951
952
953
954
955
956
957
958
959

960
961
962
963
964
965
966
967
968
969
			} else
				rc = diff_outf(s.output, "%.*c\n",
				    s.width * 2 + 16, '.');
			if (rc)
				goto end;
		}
		showsep = true;
		++hunks;
		if (s.esc)
			rc = fsl_buffer_appendf(s.cols[SBS_LLINE],
			    "<span class=\"fsl-diff-hunk-%d\"></span>",
			    hunks);

		/* Show the initial common area */
		li += skip;
		ri += skip;
		ntotal = c[ci] - skip;
		for (j = 0; j < ntotal; j++) {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_CONTEXT);
				if (rc)
					goto end;
			}
			s.idx = s.end = -1;
			rc = sbsdiff_lineno(&s, li + j, SBS_LLINE);

			if (rc)
				goto end;
			rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT);
			if (rc)
				goto end;
			rc = sbsdiff_marker(&s, "   ", "");
			if (rc)
				goto end;
			rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE);
			if (rc)
				goto end;
			rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT);

			if (rc)
				goto end;
		}
		li += ntotal;
		ri += ntotal;

		/* Show the differences */
		for (i = 0; i < nc; i++) {
			unsigned char *alignment;
			nleft = c[ci + i * 3 + 1];	/* Lines on left */







|


|
|





|
<
|
<
|
<
<
<
|
>
|
<
|
|
<
|
|
<
|
|
<
|
>
|
|
<







806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823

824

825



826
827
828

829
830

831
832

833
834

835
836
837
838

839
840
841
842
843
844
845
			} else
				rc = diff_outf(s.output, "%.*c\n",
				    s.width * 2 + 16, '.');
			if (rc)
				goto end;
		}
		showsep = true;
		++chunks;
		if (s.esc)
			rc = fsl_buffer_appendf(s.cols[SBS_LLINE],
			    "<span class=\"fsl-diff-chunk-%d\"></span>",
			    chunks);

		/* Show the initial common area */
		li += skip;
		ri += skip;
		ntotal = c[ci] - skip;
		for (j = 0; !rc && j < ntotal; j++) {

			rc = add_line_type(lines, nlines, LINE_DIFF_CONTEXT);

			if (!rc)



				rc = sbsdiff_lineno(&s, li + j, SBS_LLINE);
			s.idx = s.end = -1;
			if (!rc)

				rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT);
			if (!rc)

				rc = sbsdiff_marker(&s, "   ", "");
			if (!rc)

				rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE);
			if (!rc)

				rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT);
		}
		if (rc)
			goto end;

		li += ntotal;
		ri += ntotal;

		/* Show the differences */
		for (i = 0; i < nc; i++) {
			unsigned char *alignment;
			nleft = c[ci + i * 3 + 1];	/* Lines on left */
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018

			alignment = sbsdiff_align(&l[li], nleft, &r[ri],
			    nright);
			if (!alignment) {
				rc = FSL_RC_OOM;
				goto end;
			}
			for (j = 0; nleft + nright > 0; j++) {
				char tag[30] = "<span class=\"fsl-diff-";
				switch (alignment[j]) {
				case 1:
					/* Delete one line from the left */
					rc = sbsdiff_lineno(&s, li, SBS_LLINE);
					if (rc)
						goto end_align;
					s.idx = 0;
					fsl_strlcat(tag, "rm\">", sizeof(tag));
					s.tag = tag;
					s.end = LENGTH(&l[li]);
					rc = sbsdiff_txt(&s, &l[li], SBS_LTEXT);
					if (rc)
						goto end_align;
					if (lines && nlines) {
						rc = add_line_type(lines,
						    nlines, LINE_DIFF_MINUS);
						if (rc)
							goto end_align;
					}
					rc = sbsdiff_marker(&s, " <", "&lt;");
					if (rc)
						goto end_align;
					rc = sbsdiff_newline(&s);
					if (rc)
						goto end_align;
					assert(nleft > 0);







|












|
<
<


|
|
<







860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880


881
882
883
884

885
886
887
888
889
890
891

			alignment = sbsdiff_align(&l[li], nleft, &r[ri],
			    nright);
			if (!alignment) {
				rc = FSL_RC_OOM;
				goto end;
			}
			for (j = 0; !rc && nleft + nright > 0; j++) {
				char tag[30] = "<span class=\"fsl-diff-";
				switch (alignment[j]) {
				case 1:
					/* Delete one line from the left */
					rc = sbsdiff_lineno(&s, li, SBS_LLINE);
					if (rc)
						goto end_align;
					s.idx = 0;
					fsl_strlcat(tag, "rm\">", sizeof(tag));
					s.tag = tag;
					s.end = LENGTH(&l[li]);
					rc = sbsdiff_txt(&s, &l[li], SBS_LTEXT);
					if (!rc)


						rc = add_line_type(lines,
						    nlines, LINE_DIFF_MINUS);
					if (rc)
						goto end_align;

					rc = sbsdiff_marker(&s, " <", "&lt;");
					if (rc)
						goto end_align;
					rc = sbsdiff_newline(&s);
					if (rc)
						goto end_align;
					assert(nleft > 0);
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100

1101
1102
1103


1104
1105
1106
1107
1108

1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
					if (rc)
						goto end_align;
					s.idx = 0;
					fsl_strlcat(tag, "add\">", sizeof(tag));
					s.tag = tag;
					s.end = LENGTH(&r[ri]);
					rc = sbsdiff_txt(&s, &r[ri], SBS_RTEXT);
					if (rc)
						goto end_align;
					if (lines && nlines) {
						rc = add_line_type(lines,
						    nlines, LINE_DIFF_PLUS);
						if (rc)
							goto end_align;
					}
					assert(nright > 0);
					nright--;
					ri++;
					break;
				case 3:
					/* Left line changed into the right */
					rc = sbsdiff_write_change(&s, &l[li],
					    li, &r[ri], ri);
					if (rc)
						goto end_align;
					assert(nleft > 0 && nright > 0);
					nleft--;
					nright--;
					li++;
					ri++;
					break;
				default:
					/* Delete left and insert right */
					rc = sbsdiff_lineno(&s, li, SBS_LLINE);
					if (rc)
						goto end_align;
					s.idx = 0;
					fsl_strlcat(tag, "rm\">", sizeof(tag));
					s.tag = tag;
					s.end = LENGTH(&l[li]);
					rc = sbsdiff_txt(&s, &l[li], SBS_LTEXT);
					if (rc)
						goto end_align;
					rc = sbsdiff_marker(&s, " | ", "|");
					if (rc)
						goto end_align;
					if (lines && nlines) {
						rc = add_line_type(lines,
						    nlines, LINE_DIFF_EDIT);
						if (rc)
							goto end_align;
					}
					rc = sbsdiff_lineno(&s, ri, SBS_RLINE);
					if (rc)
						goto end_align;
					s.idx = 0;
					s.tag = "<span class=\"fsl-diff-add\">";
					s.end = LENGTH(&r[ri]);
					rc = sbsdiff_txt(&s, &r[ri], SBS_RTEXT);
					if (rc)
						goto end_align;
					nleft--;
					nright--;
					li++;
					ri++;
					break;
				}

			}
end_align:
			fsl_free(alignment);


			if (i < nc - 1) {
				ntotal = c[ci + i * 3 + 3];
				for (j = 0; j < ntotal; j++) {
					rc = sbsdiff_lineno(&s, li + j,
					    SBS_LLINE);

					if (rc)
						goto end;
					s.idx = s.end = -1;
					rc = sbsdiff_txt(&s, &l[li + j],
					    SBS_LTEXT);
					if (rc)
						goto end;
					if (lines && nlines) {
						rc = add_line_type(lines,
						    nlines, LINE_DIFF_CONTEXT);
						if (rc)
							goto end;
					}
					rc = sbsdiff_marker(&s, "   ", "");
					if (rc)
						goto end;
					rc = sbsdiff_lineno(&s, ri + j,
					    SBS_RLINE);
					if (rc)
						goto end;







|
<
<


|
|
<
















|












|
<
<


|
|
<















>



>
>


|


>


<


|
<
<


|
|
<







907
908
909
910
911
912
913
914


915
916
917
918

919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948


949
950
951
952

953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981

982
983
984


985
986
987
988

989
990
991
992
993
994
995
					if (rc)
						goto end_align;
					s.idx = 0;
					fsl_strlcat(tag, "add\">", sizeof(tag));
					s.tag = tag;
					s.end = LENGTH(&r[ri]);
					rc = sbsdiff_txt(&s, &r[ri], SBS_RTEXT);
					if (!rc)


						rc = add_line_type(lines,
						    nlines, LINE_DIFF_PLUS);
					if (rc)
						goto end_align;

					assert(nright > 0);
					nright--;
					ri++;
					break;
				case 3:
					/* Left line changed into the right */
					rc = sbsdiff_write_change(&s, &l[li],
					    li, &r[ri], ri);
					if (rc)
						goto end_align;
					assert(nleft > 0 && nright > 0);
					nleft--;
					nright--;
					li++;
					ri++;
					break;
				default: {
					/* Delete left and insert right */
					rc = sbsdiff_lineno(&s, li, SBS_LLINE);
					if (rc)
						goto end_align;
					s.idx = 0;
					fsl_strlcat(tag, "rm\">", sizeof(tag));
					s.tag = tag;
					s.end = LENGTH(&l[li]);
					rc = sbsdiff_txt(&s, &l[li], SBS_LTEXT);
					if (rc)
						goto end_align;
					rc = sbsdiff_marker(&s, " | ", "|");
					if (!rc)


						rc = add_line_type(lines,
						    nlines, LINE_DIFF_EDIT);
					if (rc)
						goto end_align;

					rc = sbsdiff_lineno(&s, ri, SBS_RLINE);
					if (rc)
						goto end_align;
					s.idx = 0;
					s.tag = "<span class=\"fsl-diff-add\">";
					s.end = LENGTH(&r[ri]);
					rc = sbsdiff_txt(&s, &r[ri], SBS_RTEXT);
					if (rc)
						goto end_align;
					nleft--;
					nright--;
					li++;
					ri++;
					break;
				}
				}
			}
end_align:
			fsl_free(alignment);
			if (rc)
				goto end;
			if (i < nc - 1) {
				ntotal = c[ci + i * 3 + 3];
				for (j = 0; !rc && j < ntotal; j++) {
					rc = sbsdiff_lineno(&s, li + j,
					    SBS_LLINE);
					s.idx = s.end = -1;
					if (rc)
						goto end;

					rc = sbsdiff_txt(&s, &l[li + j],
					    SBS_LTEXT);
					if (!rc)


						rc = add_line_type(lines,
						    nlines, LINE_DIFF_CONTEXT);
					if (rc)
						goto end;

					rc = sbsdiff_marker(&s, "   ", "");
					if (rc)
						goto end;
					rc = sbsdiff_lineno(&s, ri + j,
					    SBS_RLINE);
					if (rc)
						goto end;
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
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
		}

		/* Show the final common area */
		assert(nc == i);
		ntotal = c[ci + nc * 3];
		if (ntotal > context)
			ntotal = context;
		for (j = 0; j < ntotal; j++) {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_CONTEXT);
				if (rc)
					goto end;
			}
			s.idx = s.end = -1;
			rc = sbsdiff_lineno(&s, li + j, SBS_LLINE);

			if (rc)
				goto end;
			rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT);
			if (rc)
				goto end;
			rc = sbsdiff_marker(&s, "   ", "");
			if (rc)
				goto end;
			rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE);
			if (rc)
				goto end;
			rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT);
			if (rc)
				goto end;
		}
	}  /* diff triplet loop */

	assert(!rc);

	if (s.esc && (s.cols[SBS_LLINE]->used > 0)) {
		rc = diff_out(dst, "<table class=\"fsl-sbsdiff-cols\"><tr>\n",
		    -1);
		if (rc)
			goto end;
		for (i = SBS_LLINE; i <= SBS_RTEXT; i++) {
			rc = sbsdiff_column(dst, s.cols[i], i);
			if (rc)
				goto end;
		}
		rc = diff_out(dst, "</tr></table>\n", -1);
	}
end:
	for (i = 0; i < (int)nitems(sbscols); ++i)
		fsl_buffer_clear(&sbscols[i]);
	return rc;
}

/*
 * Render the diff triples array in cx->aEdit as a unified diff in out.
 *   cx		Raw diff data
 *   out	Side-by-side diff representation
 *   regex	Show changes matching this regex
 *   context	Number of context lines
 *   flags	Flags controlling the diff
 */
static int
unidiff(fsl__diff_cx *cx, struct diff_out_state *dst, void *regex,
    uint16_t context, uint64_t flags)
{
	fsl_dline	 *l, *r;	/* Left and right side of diff */
	static int	  hunks = 0;	/* Number of hunks so far processed */
	int		  li, ri;	/* Index of next line in l[] and r[] */
	int		 *c;		/* copy/delete/insert triples */
	int		  ci;		/* Index into c[] */
	int		  nc;		/* number of c[] triples to process */
	int		  max_ci;	/* Maximum value for ci */
	int		  nleft, nright; /* Number of l and r lines to output */
	int		  ntotal;	/* Total number of lines to output */







|
<
|
<
|
<
<
<
|
>
|
<
|
|
<
|
|
<
|
|
<
|










<
<
|

|
<
<
|















|




|







1004
1005
1006
1007
1008
1009
1010
1011

1012

1013



1014
1015
1016

1017
1018

1019
1020

1021
1022

1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033


1034
1035
1036


1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
		}

		/* Show the final common area */
		assert(nc == i);
		ntotal = c[ci + nc * 3];
		if (ntotal > context)
			ntotal = context;
		for (j = 0; !rc && j < ntotal; j++) {

			rc = add_line_type(lines, nlines, LINE_DIFF_CONTEXT);

			if (!rc)



				rc = sbsdiff_lineno(&s, li + j, SBS_LLINE);
			s.idx = s.end = -1;
			if (!rc)

				rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT);
			if (!rc)

				rc = sbsdiff_marker(&s, "   ", "");
			if (!rc)

				rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE);
			if (!rc)

				rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT);
			if (rc)
				goto end;
		}
	}  /* diff triplet loop */

	assert(!rc);

	if (s.esc && (s.cols[SBS_LLINE]->used > 0)) {
		rc = diff_out(dst, "<table class=\"fsl-sbsdiff-cols\"><tr>\n",
		    -1);


		for (i = SBS_LLINE; !rc && i <= SBS_RTEXT; i++)
			rc = sbsdiff_column(dst, s.cols[i], i);
		if (!rc)


			rc = diff_out(dst, "</tr></table>\n", -1);
	}
end:
	for (i = 0; i < (int)nitems(sbscols); ++i)
		fsl_buffer_clear(&sbscols[i]);
	return rc;
}

/*
 * Render the diff triples array in cx->aEdit as a unified diff in out.
 *   cx		Raw diff data
 *   out	Side-by-side diff representation
 *   regex	Show changes matching this regex
 *   context	Number of context lines
 *   flags	Flags controlling the diff
 */
int
unidiff(fsl__diff_cx *cx, struct diff_out_state *dst, void *regex,
    uint16_t context, uint64_t flags)
{
	fsl_dline	 *l, *r;	/* Left and right side of diff */
	static int	  chunks = 0;	/* Number of chunks so far processed */
	int		  li, ri;	/* Index of next line in l[] and r[] */
	int		 *c;		/* copy/delete/insert triples */
	int		  ci;		/* Index into c[] */
	int		  nc;		/* number of c[] triples to process */
	int		  max_ci;	/* Maximum value for ci */
	int		  nleft, nright; /* Number of l and r lines to output */
	int		  ntotal;	/* Total number of lines to output */
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350

1351
1352
1353

1354
1355
1356
1357
1358
1359
1360
1361
1362
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
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413

1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437

1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
1477
1478
1479

1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510

1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534

1535


1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
		}

		/*
		 * Show the header for this block, or if we are doing a modified
		 * unified diff that contains line numbers, show the separator
		 * from the previous block.
		 */
		++hunks;
		if (showln) {
			if (!showsep)
				showsep = 1;  /* Don't show a top divider */
			else if (html) {
				rc = diff_outf(dst,
				    "<span class=\"fsl-diff-hr\">%.*c</span>\n",
				    80, '.');
				if (rc)
					return rc;
			} else {
				if (lines && nlines) {
					rc = add_line_type(lines, nlines,
					    LINE_DIFF_SEPARATOR);
					if (rc)
						return rc;
				}
				rc = diff_outf(dst, "%.95c\n", '.');
				if (rc)
					return rc;
			}
			if (html) {
				rc = diff_outf(dst,
				    "<span class=\"fsl-diff-hunk-%d\"></span>",
				    hunks);
				if (rc)
					return rc;
			}
		} else {
			const char *ansi1 = "";
			const char *ansi2 = "";
			const char *ansi3 = "";

			if (html) {
				rc = diff_outf(dst,
				    "<span class=\"fsl-diff-lineno\">");
				if (rc)
					return rc;
			}
#if 0
			/* Turns out this just confuses the output */
			else if (dst->ansi) {
				ansi1 = ANSI_DIFF_RM(0);
				ansi2 = ANSI_DIFF_ADD(0);
				ansi3 = ANSI_RESET;
			}
#endif
			/*
			 * If the patch changes an empty file or results in
			 * an empty file, the block header must use 0,0 as
			 * position indicator and not 1,0. Otherwise, patch(1)
			 * would be confused and may reject the diff.
			 */

			rc = diff_outf(dst, "@@ %s-%d,%d %s+%d,%d%s @@",
			    ansi1, nleft ? li + skip + 1 : 0, nleft,
			    ansi2, nright ?  ri + skip + 1 : 0, nright, ansi3);

			if (rc)
				return rc;
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_HUNK);
				if (rc)
					return rc;
			}

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

			if (proto && li + skip > 1) {
				int n = 55;

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

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

		/* Show the initial common area */
		li += skip;
		ri += skip;
		ntotal = c[ci] - skip;
		for (j = 0; j < ntotal; j++) {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_CONTEXT);
				if (rc)
					return rc;
			}
			if (showln) {
				rc = unidiff_lineno(dst,
				    li + j + 1, ri + j + 1, html);
				if (rc)
					return rc;
			}
			rc = unidiff_txt(dst, ' ', &l[li + j], html, 0);

			if (rc)
				return rc;
		}
		li += ntotal;
		ri += ntotal;

		/* Show the differences */
		for (i = 0; i < nc; i++) {
			ntotal = c[ci + i * 3 + 1];
			for (j = 0; j < ntotal; j++) {
				if (lines && nlines) {
					rc = add_line_type(lines, nlines,
					    LINE_DIFF_MINUS);
					if (rc)
						return rc;
				}
				if (showln) {
					rc = unidiff_lineno(dst, li + j + 1, 0,
					    html);
					if (rc)
						return rc;
				}
				rc = unidiff_txt(dst, '-', &l[li + j],
				    html, regex);

				if (rc)
					return rc;
			}
			li += ntotal;
			ntotal = c[ci + i * 3 + 2];
			for (j = 0; j < ntotal; j++) {
				if (lines && nlines) {
					rc = add_line_type(lines, nlines,
					    LINE_DIFF_PLUS);
					if (rc)
						return rc;
				}
				if (showln) {
					rc = unidiff_lineno(dst, 0, ri + j + 1,
					    html);
					if (rc)
						return rc;
				}
				rc = unidiff_txt(dst, '+', &r[ri + j],
				    html, regex);

				if (rc)
					return rc;
			}
			ri += ntotal;
			if (i < nc - 1) {
				ntotal = c[ci + i * 3 + 3];
				for (j = 0; j < ntotal; j++) {
					if (lines && nlines) {
						rc = add_line_type(lines, nlines,
						    LINE_DIFF_CONTEXT);
						if (rc)
							return rc;
					}
					if (showln) {
						rc = unidiff_lineno(dst,
						    li + j + 1, ri + j + 1,
						    html);
						if (rc)
							return rc;
					}
					rc = unidiff_txt(dst, ' ',
					    &l[li + j], html, 0);

					if (rc)
						return rc;
				}
				ri += ntotal;
				li += ntotal;
			}
		}

		/* Show the final common area */
		assert(nc==i);
		ntotal = c[ci + nc * 3];
		if (ntotal > context)
			ntotal = context;
		for (j = 0; j < ntotal; j++) {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_CONTEXT);
				if (rc)
					return rc;
			}
			if (showln) {
				rc = unidiff_lineno(dst, li + j + 1, ri + j + 1,
				    html);
				if (rc)
					return rc;
			}
			rc = unidiff_txt(dst, ' ', &l[li + j], html, 0);
			if (rc)
				return rc;
		}
	}  /* _big_ for() loop */

	return rc;
}

/* Extract the number of context lines from flags. */
#if 0
static int
diff_context_lines(uint64_t flags)
{
	int n = flags >> 32;

	if (!n && !FLAG_CHK(flags, FNC_DIFF_CONTEXT_EX))
		n = 5;

	return n;
}
#endif

/*
 * Extract column width from flags for side-by-side diff.
 * Return appropriate default if no width is specified.
 */
static int
sbsdiff_width(uint64_t flags)
{

	int w = (flags & FNC_DIFF_WIDTH_MASK) / (FNC_DIFF_CONTEXT_MASK + 1);



	return w ? w : 80;
}

/* Append a separator line of length len to column col. */
static int
sbsdiff_separator(struct sbsline *dst, int len, int col)
{
	char ch = '.';

	if (len < 1) {
		len = 1;
		ch = ' ';
	}

	return fsl_buffer_appendf(dst->cols[col],
	    "<span class=\"fsl-diff-hr\">%.*c</span>\n", len, ch);
}

/*
 * fsl_output_f() implementation for use with diff_outf(). State must be a
 * struct diff_out_state *.
 */
static int
fnc_output_f_diff_out(void *state, void const *src, fsl_size_t n)
{
	struct diff_out_state *const dst = (struct diff_out_state *)state;

	return dst->rc = dst->out(dst->state, src, n);
}

static int
diff_outf(struct diff_out_state *dst, char const *fmt, ...)
{
	va_list va;

	va_start(va,fmt);
	fsl_appendfv(fnc_output_f_diff_out, dst, fmt, va);
	va_end(va);

	return dst->rc;
}

/* Append a column to the final output blob. */
static int
sbsdiff_column(struct diff_out_state *dst, fsl_buffer const *content, int col)
{
	return diff_outf(dst,
	    "<td><div class=\"fsl-diff-%s-col\">\n"
	    "<pre>\n"
	    "%b</pre>\n"
	    "</div></td>\n",







|



|



<
<
|
<
|
|
|
<
<
|
<
<

|

|
|
<
<
<

|
|
|
<
|


<
<
<











|


>
|
|
|
>
|
<
<
|
|
<
<

|
|
|
<
<
|
<
|
<
<
|
|
|
<
<
<
<
|
<
<
<

|
|
<
<
<
|
|
<
|
|
<





|
<
|
<
<
<
<
|


|
<
<
|
>
|
|
<






|
<
|
|
<
<
<
|


|
<
<
|
|
>
|
|
<


|
<
|
|
<
<
<
|


|
<
<
|
|
>
|
|
<



|
<
|
|
<
<
<
|



|
<
<
|
|
>
|
|
<










|
<
|
<
<
<
<
|


|
<
<
|
<
<


>




<
|


|






<


|
|

|


>
|
>
>

|



|

















|
|






|





|






|







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



1171
1172
1173
1174

1175
1176
1177



1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197


1198
1199


1200
1201
1202
1203


1204

1205


1206
1207
1208




1209



1210
1211
1212



1213
1214

1215
1216

1217
1218
1219
1220
1221
1222

1223




1224
1225
1226
1227


1228
1229
1230
1231

1232
1233
1234
1235
1236
1237
1238

1239
1240



1241
1242
1243
1244


1245
1246
1247
1248
1249

1250
1251
1252

1253
1254



1255
1256
1257
1258


1259
1260
1261
1262
1263

1264
1265
1266
1267

1268
1269



1270
1271
1272
1273
1274


1275
1276
1277
1278
1279

1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290

1291




1292
1293
1294
1295


1296


1297
1298
1299
1300
1301
1302
1303

1304
1305
1306
1307
1308
1309
1310
1311
1312
1313

1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
		}

		/*
		 * Show the header for this block, or if we are doing a modified
		 * unified diff that contains line numbers, show the separator
		 * from the previous block.
		 */
		++chunks;
		if (showln) {
			if (!showsep)
				showsep = 1;  /* Don't show a top divider */
			else if (html)
				rc = diff_outf(dst,
				    "<span class=\"fsl-diff-hr\">%.*c</span>\n",
				    80, '.');


			else {

				rc = add_line_type(lines, nlines,
				    LINE_DIFF_SEPARATOR);
				if (!rc)


					rc = diff_outf(dst, "%.95c\n", '.');


			}
			if (!rc && html)
				rc = diff_outf(dst,
				    "<span class=\"fsl-diff-chunk-%d\"></span>",
				    chunks);



		} else {
			char const *ansi1 = "";
			char const *ansi2 = "";
			char const *ansi3 = "";

			if (html)
				rc = diff_outf(dst,
				    "<span class=\"fsl-diff-lineno\">");



#if 0
			/* Turns out this just confuses the output */
			else if (dst->ansi) {
				ansi1 = ANSI_DIFF_RM(0);
				ansi2 = ANSI_DIFF_ADD(0);
				ansi3 = ANSI_RESET;
			}
#endif
			/*
			 * If the patch changes an empty file or results in
			 * an empty file, the block header must use 0,0 as
			 * position indicator and not 1,0. Otherwise, patch
			 * would be confused and may reject the diff.
			 */
			if (!rc) {
				rc = diff_outf(dst, "@@ %s-%d,%d %s+%d,%d%s @@",
				    ansi1, nleft ? li + skip + 1 : 0, nleft,
				    ansi2, nright ?  ri + skip + 1 : 0,
				    nright, ansi3);
				if (!rc)


					rc = add_line_type(lines, nlines,
					    LINE_DIFF_CHUNK);


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




				if (proto && li + skip > 1) {


					char *f = match_chunk_function(dst,
					    (li + skip) - 1);
					if (f != NULL)




						rc = diff_outf(dst, " %s", f);



				}
				if (!rc)
					rc = diff_out(dst, "\n", 1);



			}
		}

		if (rc)
			return rc;


		/* Show the initial common area */
		li += skip;
		ri += skip;
		ntotal = c[ci] - skip;
		for (j = 0; !rc && j < ntotal; j++) {

			rc = add_line_type(lines, nlines, LINE_DIFF_CONTEXT);




			if (!rc && showln)
				rc = unidiff_lineno(dst,
				    li + j + 1, ri + j + 1, html);
			if (!rc)


				rc = unidiff_txt(dst, ' ', &l[li + j], html, 0);
		}
		if (rc)
			return rc;

		li += ntotal;
		ri += ntotal;

		/* Show the differences */
		for (i = 0; i < nc; i++) {
			ntotal = c[ci + i * 3 + 1];
			for (j = 0; !rc && j < ntotal; j++) {

				rc = add_line_type(lines, nlines,
				    LINE_DIFF_MINUS);



				if (!rc && showln)
					rc = unidiff_lineno(dst, li + j + 1, 0,
					    html);
				if (!rc)


					rc = unidiff_txt(dst, '-', &l[li + j],
					    html, regex);
			}
			if (rc)
				return rc;

			li += ntotal;
			ntotal = c[ci + i * 3 + 2];
			for (j = 0; !rc && j < ntotal; j++) {

				rc = add_line_type(lines, nlines,
				    LINE_DIFF_PLUS);



				if (!rc && showln)
					rc = unidiff_lineno(dst, 0, ri + j + 1,
					    html);
				if (!rc)


					rc = unidiff_txt(dst, '+', &r[ri + j],
					    html, regex);
			}
			if (rc)
				return rc;

			ri += ntotal;
			if (i < nc - 1) {
				ntotal = c[ci + i * 3 + 3];
				for (j = 0; !rc && j < ntotal; j++) {

					rc = add_line_type(lines, nlines,
					    LINE_DIFF_CONTEXT);



					if (!rc && showln)
						rc = unidiff_lineno(dst,
						    li + j + 1, ri + j + 1,
						    html);
					if (!rc)


						rc = unidiff_txt(dst, ' ',
						    &l[li + j], html, 0);
				}
				if (rc)
					return rc;

				ri += ntotal;
				li += ntotal;
			}
		}

		/* Show the final common area */
		assert(nc==i);
		ntotal = c[ci + nc * 3];
		if (ntotal > context)
			ntotal = context;
		for (j = 0; !rc && j < ntotal; j++) {

			rc = add_line_type(lines, nlines, LINE_DIFF_CONTEXT);




			if (!rc && showln)
				rc = unidiff_lineno(dst, li + j + 1, ri + j + 1,
				    html);
			if (!rc)


				rc = unidiff_txt(dst, ' ', &l[li + j], html, 0);


		}
	}  /* _big_ for() loop */
	fsl_free(dst->proto.signature);
	return rc;
}

/* Extract the number of context lines from flags. */

int
diff_context_lines(uint64_t flags)
{
	int n = flags & FNC_DIFF_CONTEXT_MASK;

	if (!n && !FLAG_CHK(flags, FNC_DIFF_CONTEXT_EX))
		n = 5;

	return n;
}


/*
 * Extract column width for side-by-side diff from flags. Return appropriate
 * default if no width is specified.
 */
int
sbsdiff_width(uint64_t flags)
{
	int w = (flags & (FNC_DIFF_WIDTH_MASK - 0xf)) / FNC_DIFF_CONTEXT_MASK;

	if (!w)
		w = 80;

	return w;
}

/* Append a separator line of length len to column col. */
int
sbsdiff_separator(struct sbsline *dst, int len, int col)
{
	char ch = '.';

	if (len < 1) {
		len = 1;
		ch = ' ';
	}

	return fsl_buffer_appendf(dst->cols[col],
	    "<span class=\"fsl-diff-hr\">%.*c</span>\n", len, ch);
}

/*
 * fsl_output_f() implementation for use with diff_outf(). State must be a
 * struct diff_out_state *.
 */
int
fsl_output_f_diff_out(void *state, void const *src, fsl_size_t n)
{
	struct diff_out_state *const dst = (struct diff_out_state *)state;

	return dst->rc = dst->out(dst->state, src, n);
}

int
diff_outf(struct diff_out_state *dst, char const *fmt, ...)
{
	va_list va;

	va_start(va,fmt);
	fsl_appendfv(fsl_output_f_diff_out, dst, fmt, va);
	va_end(va);

	return dst->rc;
}

/* Append a column to the final output blob. */
int
sbsdiff_column(struct diff_out_state *dst, fsl_buffer const *content, int col)
{
	return diff_outf(dst,
	    "<td><div class=\"fsl-diff-%s-col\">\n"
	    "<pre>\n"
	    "%b</pre>\n"
	    "</div></td>\n",
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
 * Expand tabs to spaces, and add newlines if col is SBS_RTEXT. Translate HTML
 * characters if esc is true. Pad with spaces to dst->width bytes if col is
 * SBS_LTEXT and esc is false.
 *
 * This comment contains multibyte unicode characters (�, �, �) in order
 * to test the ability of the diff code to handle such characters.
 */
static int
sbsdiff_txt(struct sbsline *dst, fsl_dline *dline, int col)
{
	fsl_buffer	*o = dst->cols[col];
	const char	*str = dline->z;
	int		 n = dline->n;
	int		 i;	/* Number of input characters consumed */
	int		 pos;	/* Cursor position */







|







1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
 * Expand tabs to spaces, and add newlines if col is SBS_RTEXT. Translate HTML
 * characters if esc is true. Pad with spaces to dst->width bytes if col is
 * SBS_LTEXT and esc is false.
 *
 * This comment contains multibyte unicode characters (�, �, �) in order
 * to test the ability of the diff code to handle such characters.
 */
int
sbsdiff_txt(struct sbsline *dst, fsl_dline *dline, int col)
{
	fsl_buffer	*o = dst->cols[col];
	const char	*str = dline->z;
	int		 n = dline->n;
	int		 i;	/* Number of input characters consumed */
	int		 pos;	/* Cursor position */
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
}

/*
 * Append newlines to columns corresponding to sbs diff format.
 *   html: all columns
 *   text: right column only
 */
static int
sbsdiff_newline(struct sbsline *dst)
{
	int i, rc = FSL_RC_OK;

	for (i = dst->esc ? SBS_LLINE : SBS_RTEXT; !rc && i <= SBS_RTEXT; i++)
		rc = fsl_buffer_append(dst->cols[i], "\n", 1);

	return rc;
}

/* Append n spaces to column col in the sbs diff. */
static int
sbsdiff_space(struct sbsline *dst, int n, int col)
{
	return fsl_buffer_appendf(dst->cols[col], "%*s", n, "");
}

/* Append plaintext XOR html marker into the center column of the sbs diff. */
static int
sbsdiff_marker(struct sbsline *dst, const char *str, const char *html)
{
	return fsl_buffer_append(dst->cols[SBS_MID], dst->esc ? html : str, -1);
}

/* Append file line number ln to column col in the sbs diff. */
static int
sbsdiff_lineno(struct sbsline *dst, int ln, int col)
{
	int rc;

	if (dst->esc)
		rc = fsl_buffer_appendf(dst->cols[col], "%d", ln + 1);
	else {
		char lnno[8];
		fsl_snprintf(lnno, 8, "%5d ", ln + 1);
		rc = fsl_buffer_appendf(dst->cols[col], "%s ", lnno);
	}

	return rc;
}

/* Try to shift dst->idx as far as possible to the left. */
static void
sbsdiff_shift_left(struct sbsline *dst, const char *z)
{
	int i, j;

	while ((i = dst->idx) > 0 && z[i - 1] == z[i]) {
		for (j = i + 1; j < dst->end && z[j - 1] == z[j]; j++) {}
		if (j < dst->end)
			break;
		--dst->idx;
		--dst->end;
	}
}

/*
 * Simplify line at idx and idx2 in SBS diff output:
 *    -  If idx is a null-change then move idx2 into idx
 *    -  Make sure any null-changes are in canonical form.
 *    -  Make sure all changes are at character boundaries for multibyte chars.
 */
static void
sbsdiff_simplify_line(struct sbsline *dst, const char *z)
{
	if (dst->idx2 == dst->end2)
		dst->idx2 = dst->end2 = 0;
	else if (dst->idx2) {
		while (dst->idx2 > 0 && UTF_CONT(z[dst->idx2]))
			--dst->idx2;







|











|






|






|
















|



















|







1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
}

/*
 * Append newlines to columns corresponding to sbs diff format.
 *   html: all columns
 *   text: right column only
 */
int
sbsdiff_newline(struct sbsline *dst)
{
	int i, rc = FSL_RC_OK;

	for (i = dst->esc ? SBS_LLINE : SBS_RTEXT; !rc && i <= SBS_RTEXT; i++)
		rc = fsl_buffer_append(dst->cols[i], "\n", 1);

	return rc;
}

/* Append n spaces to column col in the sbs diff. */
int
sbsdiff_space(struct sbsline *dst, int n, int col)
{
	return fsl_buffer_appendf(dst->cols[col], "%*s", n, "");
}

/* Append plaintext XOR html marker into the center column of the sbs diff. */
int
sbsdiff_marker(struct sbsline *dst, const char *str, const char *html)
{
	return fsl_buffer_append(dst->cols[SBS_MID], dst->esc ? html : str, -1);
}

/* Append file line number ln to column col in the sbs diff. */
int
sbsdiff_lineno(struct sbsline *dst, int ln, int col)
{
	int rc;

	if (dst->esc)
		rc = fsl_buffer_appendf(dst->cols[col], "%d", ln + 1);
	else {
		char lnno[8];
		fsl_snprintf(lnno, 8, "%5d ", ln + 1);
		rc = fsl_buffer_appendf(dst->cols[col], "%s ", lnno);
	}

	return rc;
}

/* Try to shift dst->idx as far as possible to the left. */
void
sbsdiff_shift_left(struct sbsline *dst, const char *z)
{
	int i, j;

	while ((i = dst->idx) > 0 && z[i - 1] == z[i]) {
		for (j = i + 1; j < dst->end && z[j - 1] == z[j]; j++) {}
		if (j < dst->end)
			break;
		--dst->idx;
		--dst->end;
	}
}

/*
 * Simplify line at idx and idx2 in SBS diff output:
 *    -  If idx is a null-change then move idx2 into idx
 *    -  Make sure any null-changes are in canonical form.
 *    -  Make sure all changes are at character boundaries for multibyte chars.
 */
void
sbsdiff_simplify_line(struct sbsline *dst, const char *z)
{
	if (dst->idx2 == dst->end2)
		dst->idx2 = dst->end2 = 0;
	else if (dst->idx2) {
		while (dst->idx2 > 0 && UTF_CONT(z[dst->idx2]))
			--dst->idx2;
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
}

/*
 * c[] is an array of six integers: two copy/delete/insert triples for a
 * pair of adjacent differences. Return true if the gap between these two
 * differences is so small that they should be rendered as a single edit.
 */
static int
sbsdiff_close_gap(int *c)
{
	return c[3] <= 2 || c[3] <= (c[1] + c[2] + c[4] + c[5]) / 8;
}

/*
 * There is a change block in which nleft lines of text on the left are







|







1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
}

/*
 * c[] is an array of six integers: two copy/delete/insert triples for a
 * pair of adjacent differences. Return true if the gap between these two
 * differences is so small that they should be rendered as a single edit.
 */
int
sbsdiff_close_gap(int *c)
{
	return c[3] <= 2 || c[3] <= (c[1] + c[2] + c[4] + c[5]) / 8;
}

/*
 * There is a change block in which nleft lines of text on the left are
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
 * Insertion and deletion costs are 50. Match costs are between 0 and 100 where
 * 0 is a perfect match 100 is a complete mismatch.
 *   left	lines of text on the left
 *   nleft	number of lines on the left
 *   right	lines of text on the right
 *   nright	number of lines on the right
 */
static unsigned char *
sbsdiff_align(fsl_dline *left, int nleft, fsl_dline *right, int nright)
{
	int		 buf[100];	/* left[] stack if nright not too big */
	int		*row;		/* One row of the Wagner matrix */
	int		*ptr;		/* Space that needs to be freed */
	int		 nmatches;	/* Number of matches */
	int		 matchscore;	/* Match score */







|







1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
 * Insertion and deletion costs are 50. Match costs are between 0 and 100 where
 * 0 is a perfect match 100 is a complete mismatch.
 *   left	lines of text on the left
 *   nleft	number of lines on the left
 *   right	lines of text on the right
 *   nright	number of lines on the right
 */
unsigned char *
sbsdiff_align(fsl_dline *left, int nleft, fsl_dline *right, int nright)
{
	int		 buf[100];	/* left[] stack if nright not too big */
	int		*row;		/* One row of the Wagner matrix */
	int		*ptr;		/* Space that needs to be freed */
	int		 nmatches;	/* Number of matches */
	int		 matchscore;	/* Match score */
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
 * highlight to cover only those parts of the line that have changed.
 *   dst	The SBS output line
 *   left	Left line of the change
 *   llnno	Line number for the left line
 *   right	Right line of the change
 *   rlnno	Line number of the right line
 */
static int
sbsdiff_write_change(struct sbsline *dst, fsl_dline *left, int llnno,
    fsl_dline *right, int rlnno)
{
	static const char	  tag_rm[] = "<span class=\"fsl-diff-rm\">";
	static const char	  tag_add[] = "<span class=\"fsl-diff-add\">";
	static const char	  tag_chg[] = "<span class=\"fsl-diff-change\">";
	const char		 *ltxt;	/* Text of the left line */







|







1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
 * highlight to cover only those parts of the line that have changed.
 *   dst	The SBS output line
 *   left	Left line of the change
 *   llnno	Line number for the left line
 *   right	Right line of the change
 *   rlnno	Line number of the right line
 */
int
sbsdiff_write_change(struct sbsline *dst, fsl_dline *left, int llnno,
    fsl_dline *right, int rlnno)
{
	static const char	  tag_rm[] = "<span class=\"fsl-diff-rm\">";
	static const char	  tag_add[] = "<span class=\"fsl-diff-add\">";
	static const char	  tag_chg[] = "<span class=\"fsl-diff-change\">";
	const char		 *ltxt;	/* Text of the left line */
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032

2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043








2044
2045
2046

2047
2048
2049
2050
2051
2052
2053
2054
2055
2056

2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081



2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
				--nsfx;
		if (nsfx == leftsz || nsfx == rightsz)
			npfx = 0;
	}
	if (npfx + nsfx > shortest)
		npfx = shortest - nsfx;

	/* A single hunk of text inserted on the right */
	if (npfx + nsfx == leftsz) {
		rc = sbsdiff_lineno(dst, llnno, SBS_LLINE);
		if (rc)
			return rc;
		dst->idx2 = dst->end2 = 0;
		dst->idx = dst->end = -1;
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);
		if (rc)
			return rc;
		if (leftsz == rightsz && ltxt[leftsz] == rtxt[rightsz]) {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_CONTEXT);
				if (rc)
					return rc;
			}
			rc = sbsdiff_marker(dst, "   ", "");
			if (rc)
				return rc;

		} else {
			if (lines && nlines) {
				rc = add_line_type(lines, nlines,
				    LINE_DIFF_EDIT);
				if (rc)
					return rc;
			}
			rc = sbsdiff_marker(dst, " | ", "|");
			if (rc)
				return rc;
		}









		rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
		if (rc)

			return rc;
		dst->idx = npfx;
		dst->end = rightsz - nsfx;
		dst->tag = tag_add;
		rc = sbsdiff_txt(dst, right, SBS_RTEXT);
		return rc;
	}

	/* A single hunk of text deleted from the left */
	if (npfx + nsfx == rightsz) {

		rc = sbsdiff_lineno(dst, llnno, SBS_LLINE);
		if (rc)
			return rc;

		if (lines && nlines) {
			rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);
			if (rc)
				return rc;
		}

		dst->idx2 = dst->end2 = 0;
		dst->idx = npfx;
		dst->end = leftsz - nsfx;
		dst->tag = tag_rm;
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);
		if (rc)
			return rc;
		rc = sbsdiff_marker(dst, " | ", "|");
		if (rc)
			return rc;
		rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
		if (rc)
			return rc;
		dst->idx = dst->end = -1;
		rc = sbsdiff_txt(dst, right, SBS_RTEXT);



		return rc;
	}

	/*
	 * At this point we know that there is a hunk of text that has
	 * changed between the left and the right. Check to see if there
	 * is a large unchanged section in the middle of that changed block.
	 */
	nleft = leftsz - nsfx - npfx;
	nright = rightsz - nsfx - npfx;
	if (dst->esc && nleft >= 6 && nright >= 6 &&
	    find_lcs(&ltxt[npfx], nleft, &rtxt[npfx], nright, lcs)) {







|







<
<
|
<
|
<
|
<
<
|
<
<
>
|
<
|
<
|
<
<
|
<
<

>
>
>
>
>
>
>
>
|
<
<
>
|
<
<
<
<
<


|

>

|
<
<
<

|
|
<
<





|
<
|
|
<
|
|
<
|
|
>
>
>




|







1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810


1811

1812

1813


1814


1815
1816

1817

1818


1819


1820
1821
1822
1823
1824
1825
1826
1827
1828
1829


1830
1831





1832
1833
1834
1835
1836
1837
1838



1839
1840
1841


1842
1843
1844
1845
1846
1847

1848
1849

1850
1851

1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
				--nsfx;
		if (nsfx == leftsz || nsfx == rightsz)
			npfx = 0;
	}
	if (npfx + nsfx > shortest)
		npfx = shortest - nsfx;

	/* A single chunk of text inserted on the right */
	if (npfx + nsfx == leftsz) {
		rc = sbsdiff_lineno(dst, llnno, SBS_LLINE);
		if (rc)
			return rc;
		dst->idx2 = dst->end2 = 0;
		dst->idx = dst->end = -1;
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);


		if (!rc && leftsz == rightsz && ltxt[leftsz] == rtxt[rightsz]) {

			rc = add_line_type(lines, nlines, LINE_DIFF_CONTEXT);

			if (!rc)


				rc = sbsdiff_marker(dst, "   ", "");


		}
		else {

			rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);

			if (!rc)


				rc = sbsdiff_marker(dst, " | ", "|");


		}

		if (!rc) {
			rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
			if (!rc) {
				dst->idx = npfx;
				dst->end = rightsz - nsfx;
				dst->tag = tag_add;
				rc = sbsdiff_txt(dst, right, SBS_RTEXT);
			}


		}
		return rc;





	}

	/* A single chunk of text deleted from the left */
	if (npfx + nsfx == rightsz) {
		/* Text deleted from the left */
		rc = sbsdiff_lineno(dst, llnno, SBS_LLINE);
		if (!rc)



			rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);
		if (rc)
			return rc;


		dst->idx2 = dst->end2 = 0;
		dst->idx = npfx;
		dst->end = leftsz - nsfx;
		dst->tag = tag_rm;
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);
		if (!rc) {

			rc = sbsdiff_marker(dst, " | ", "|");
			if (!rc) {

				rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
				if (!rc) {

					dst->idx = dst->end = -1;
					sbsdiff_txt(dst, right, SBS_RTEXT);
				}
			}
		}
		return rc;
	}

	/*
	 * At this point we know that there is a chunk of text that has
	 * changed between the left and the right. Check to see if there
	 * is a large unchanged section in the middle of that changed block.
	 */
	nleft = leftsz - nsfx - npfx;
	nright = rightsz - nsfx - npfx;
	if (dst->esc && nleft >= 6 && nright >= 6 &&
	    find_lcs(&ltxt[npfx], nleft, &rtxt[npfx], nright, lcs)) {
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165




2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
		} else
			dst->tag = tag_chg;
		dst->idx2 = npfx + lcs[1];
		dst->end2 = leftsz - nsfx;
		dst->tag2 = lcs[3] == nright ? tag_rm : tag_chg;
		sbsdiff_simplify_line(dst, ltxt + npfx);
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);
		if (rc)
			return rc;

		if (lines && nlines) {
			rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);
			if (rc)
				return rc;
		}

		rc = sbsdiff_marker(dst, " | ", "|");
		if (rc)
			return rc;
		rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
		if (rc)
			return rc;

		dst->idx = npfx;
		dst->end = npfx + lcs[2];
		if (!lcs[0]) {
			sbsdiff_shift_left(dst, right->z);
			dst->tag = tag_add;
		} else
			dst->tag = tag_chg;
		dst->idx2 = npfx + lcs[3];
		dst->end2 = rightsz - nsfx;
		dst->tag2 = lcs[1]==nleft ? tag_add : tag_chg;
		sbsdiff_simplify_line(dst, rtxt + npfx);
		rc = sbsdiff_txt(dst, right, SBS_RTEXT);
		return rc;
	}

	/* If all else fails, show a single big change between left and right */
	rc = sbsdiff_lineno(dst, llnno, SBS_LLINE);
	if (rc)
		return rc;
	dst->idx2 = dst->end2 = 0;
	dst->idx = npfx;
	dst->end = leftsz - nsfx;
	dst->tag = tag_chg;
	rc = sbsdiff_txt(dst, left, SBS_LTEXT);
	if (rc)
		return rc;

	if (lines && nlines) {
		rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);
		if (rc)
			return rc;
	}

	rc = sbsdiff_marker(dst, " | ", "|");
	if (rc)
		return rc;
	rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
	if (rc)
		return rc;
	dst->end = rightsz - nsfx;
	rc = sbsdiff_txt(dst, right, SBS_RTEXT);




	return rc;
}

/*
 * Add two line numbers to the beginning of a unified diff output line.
 *   dst	Output destination
 *   lln	Line number corresponding to the line in the left (old) file
 *   rln	Line number corresponding to the line in the right (new) file
 *   html	Specify html formatted output
 * n.b. lln or rln can be zero to leave that number field blank.
 */
static int
unidiff_lineno(struct diff_out_state *dst, int lln, int rln, bool html)
{
	int rc = FSL_RC_OK;

	if (html) {
		rc = diff_out(dst, "<span class=\"fsl-diff-lineno\">", -1);
		if (rc)







|
<
<
<

|
<
<
<
|
|
<
|


<

















|
<
|
|
|
|
|
|
<
<
<
|
<
<
<
<
|
|
<
|
|
<
|
|
>
>
>
>











|







1877
1878
1879
1880
1881
1882
1883
1884



1885
1886



1887
1888

1889
1890
1891

1892
1893
1894
1895
1896
1897
1898
1899
1900
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
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
		} else
			dst->tag = tag_chg;
		dst->idx2 = npfx + lcs[1];
		dst->end2 = leftsz - nsfx;
		dst->tag2 = lcs[3] == nright ? tag_rm : tag_chg;
		sbsdiff_simplify_line(dst, ltxt + npfx);
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);
		if (!rc)



			rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);
		if (!rc)



			rc = sbsdiff_marker(dst, " | ", "|");
		if (!rc)

			rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
		if (rc)
			return rc;

		dst->idx = npfx;
		dst->end = npfx + lcs[2];
		if (!lcs[0]) {
			sbsdiff_shift_left(dst, right->z);
			dst->tag = tag_add;
		} else
			dst->tag = tag_chg;
		dst->idx2 = npfx + lcs[3];
		dst->end2 = rightsz - nsfx;
		dst->tag2 = lcs[1]==nleft ? tag_add : tag_chg;
		sbsdiff_simplify_line(dst, rtxt + npfx);
		rc = sbsdiff_txt(dst, right, SBS_RTEXT);
		return rc;
	}

	/* If all else fails, show a single big change between left and right */
	rc = sbsdiff_lineno(dst, llnno, SBS_LLINE);
	if (!rc) {

		dst->idx2 = dst->end2 = 0;
		dst->idx = npfx;
		dst->end = leftsz - nsfx;
		dst->tag = tag_chg;
		rc = sbsdiff_txt(dst, left, SBS_LTEXT);
		if (!rc) {



			rc = add_line_type(lines, nlines, LINE_DIFF_EDIT);




			rc = sbsdiff_marker(dst, " | ", "|");
			if (!rc) {

				rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE);
				if (!rc) {

					dst->end = rightsz - nsfx;
					sbsdiff_txt(dst, right, SBS_RTEXT);
				}
			}
		}
	}
	return rc;
}

/*
 * Add two line numbers to the beginning of a unified diff output line.
 *   dst	Output destination
 *   lln	Line number corresponding to the line in the left (old) file
 *   rln	Line number corresponding to the line in the right (new) file
 *   html	Specify html formatted output
 * n.b. lln or rln can be zero to leave that number field blank.
 */
int
unidiff_lineno(struct diff_out_state *dst, int lln, int rln, bool html)
{
	int rc = FSL_RC_OK;

	if (html) {
		rc = diff_out(dst, "<span class=\"fsl-diff-lineno\">", -1);
		if (rc)
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
 * Append a single line of unified diff text to dst.
 *   dst	Destination
 *   sign	Either a " " (context), "+" (added),  or "-" (removed) line
 *   line	The line to be output
 *   html	True if generating HTML, false for plain text
 *   regex	colourise only if line matches this regex
 */
static int
unidiff_txt(struct diff_out_state *const dst, char sign, fsl_dline *line,
    int html, void *regex)
{
	char const	*ansiccode;
	int		 rc = FSL_RC_OK;

	ansiccode = !dst->ansi ? NULL : ((sign == '+') ?







|







1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
 * Append a single line of unified diff text to dst.
 *   dst	Destination
 *   sign	Either a " " (context), "+" (added),  or "-" (removed) line
 *   line	The line to be output
 *   html	True if generating HTML, false for plain text
 *   regex	colourise only if line matches this regex
 */
int
unidiff_txt(struct diff_out_state *const dst, char sign, fsl_dline *line,
    int html, void *regex)
{
	char const	*ansiccode;
	int		 rc = FSL_RC_OK;

	ansiccode = !dst->ansi ? NULL : ((sign == '+') ?

Changes to src/fnc.1.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

249

250
251
252
253
254

255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273


274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399

400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445


446

447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588

589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
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
706
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
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829

830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877

878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953

954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013

1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114

1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate$
.Dt FNC 1
.Os
.Sh NAME
.Nm fnc
.Nd An interactive text-based user interface for Fossil
.Sh SYNOPSIS
.Nm
.Op Ar command
.Op Fl h | -help
.Nm
.Op Fl h | -help
.Op Fl v | -version
.Nm
.Cm stash Op Po Cm get Ns | Ns Cm pop Pc Oo Ar id Oc | Fl ChPx







.Nm
.Cm config
.Op Fl hu
.Op Fl -ls
.Op Fl R Ar path
.Op Ar setting Op Ar value
.Nm
.Cm timeline
.Op Fl Chz
.Op Fl b Ar branch
.Op Fl c Ar commit
.Op Fl f Ar glob
.Op Fl n Ar number
.Op Fl R Ar path
.Op Fl T Ar tag
.Op Fl t Ar type
.Op Fl u Ar user
.Op Ar path
.Nm
.Cm diff
.Op Fl bChilPqsWw
.Op Fl R Ar path
.Op Fl x Ar number
.Op Ar artifact1 Op Ar artifact2
.Op Ar path ...
.Nm
.Cm tree
.Op Fl Ch
.Op Fl c Ar commit
.Op Fl R Ar path
.Op Ar path
.Nm
.Cm blame
.Op Fl Ch
.Op Fl c Ar commit Op Fl r
.Op Fl n Ar number
.Op Fl R Ar path
.Ar path
.Nm
.Cm branch
.Op Fl Cchopr
.Op Fl a Ar date | Fl b Ar date
.Op Fl R Ar path
.Op Fl s Ar order
.Op Ar glob
.Nm
.Op Ar path
.Sh DESCRIPTION
.Nm
is an interactive text-based user interface for
.Xr fossil 1
repositories,
and supports multiple views to display repository data:
.Bl -tag -width Ds
.It Timeline view
Display commits from the repository's history in chronologically
descending order.

If no
.Ar command
or
.Ar arg
are specified, or just a
.Ar path
is passed,
.Nm
will default to displaying this view.
.It Diff view
Display changes introduced in the specified commit, or between two repository
artifacts.
.It Tree view
Display navigable tree reflecting the repository state as at the specified
commit.
.It Blame view
Display and annotate each line in the given file with the hyperlinked
historical commit that last modified the line.
.It Branch view
Display navigable list of all repository branches.
.El
.Pp
.Nm
provides both global and command-specific options and runtime key
bindings.
Global options are as follows:
.Bl -tag -width 6v
.It Fl h , -help
Display program help and usage information then exit.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm
invocation.
See command specific options for details.
.It Fl v , -version
Display program version then exit.
.El
.Pp



Global key bindings are as follows:
.Bl -tag -width Ds
.It Ic H, \&?, F1
Display runtime help.
.It Ic Tab
Switch focus between open views.
.It Ic f
Toggle the active view between fullscreen and splitscreen mode.
By default,
.Nm
will open nested views in a splitscreen if the terminal window is equal to or
greater than 110 columns wide.
.It Ic Q
Quit
.Nm .
.It Ic q
Quit the active view.
.El
.Pp
Commands available to
.Nm
are as follows:
.Bl -tag -width 4v
.Tg conf
.It Cm config Oo Fl h | -help Oc Oo Fl -ls Oc Oo Fl R | -repo Ar path Oc \
Oo Ar setting Oo Ar value | Fl -unset Oc Oc
.Dl Pq aliases: Cm conf , Cm set
Retrieve the current, or set a new,
.Ar value
for
.Ar setting
in the local repository.
When specified,
.Ar value
will become the new value for
.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
.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.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm Cm config
invocation.
.It Fl u , -unset
Clear the specified
.Ar setting .
.El
.Tg stash
.It Cm stash Oo Po Cm get Ns | Ns Cm pop Pc Oo Ar id Oc | \
Oo Fl C | -no-colour Oc Oo Fl h | -help Oc Oo Fl P | -no-prototype Oc \
Oo Fl x | -context Ar n Oc Oc
.Dl Pq aliases: Cm save , Cm sta
When run with neither the
.Cm get
nor
.Cm pop
subcommands,

.Nm Cm stash
will present an interactive view of the local changes on disk and iterate
each hunk in the diff, prompting the user to either stash or keep the current
change in the checkout.
Valid answers are as follows:
.Bl -column -offset 2s YXZ description
.Sy b Ta scroll back Ns \(ha
.Sy m Ta show more of the current hunk Ns \(ha
.Sy y Ta yes, stash the current hunk
.Sy n Ta no, do not stash the current hunk
.Sy a Ta yes, stash this hunk and all remaining hunks in the file
.Sy k Ta no, do not stash this hunk nor any remaining hunks in the file
.Sy A Ta yes, stash this hunk and all remaining hunks in the diff
.Sy K Ta no, do not stash this hunk nor any remaining hunks in the diff
.Sy \&? Ta display help dialog
.El
.Pp
\(haConditionally available when the current hunk occupies the previous
and/or following page.
.Pp
When all hunks have been selected,
.Nm
will prompt the user to enter a stash message.
If not provided, a default message of
.Qo
fnc stash HASH-PREFIX
.Qc ,
where
.Qq HASH-PREFIX
is an abbreviated SHA hash of the current checkout,
will be used.
At any time prior to the final hunk being selected

.Pq i.e., before the stash message prompt ,

the operation can be aborted by opening the help dialog and entering
.Qq Q .
This will discard all selections and leave the checkout state unchanged.
.Pp
Available subcommands for

.Nm Cm stash
are as follows:
.Bl -tag -width 00
.It Cm get Op Ar id
.Dl Pq aliases: Cm apply
Retrieve stash
.Ar id ,
or if not provided the most recent stash entry, and apply it to the current
checkout.
.It Cm pop Op Ar id
Remove stash
.Ar id ,
or if not provided the most recent stash entry, and apply it to the current
checkout.
.El
.Pp
Options only apply to
.Nm
.Cm stash


.Po i.e., neither the
.Cm get
nor
.Cm pop
subcommands
.Pc
and are as follows:
.Bl -tag -width Ds
.It Fl C , -no-colour
Disable coloured output, which is enabled by default on supported terminals.
.It Fl h , -help
Display
.Cm stash
command help and usage information then exit.
.It Fl P , -no-prototype
Disable hunk header display of which function or scope each change is in,
which is enabled by default.
The heuristic will produce reliable results for all C-like languages
.Pq e.g., C/C++, Java, Python, JavaScript, Rust ;
however, Lisps and non-source code
.Pq e.g., Markdown, reStructuredText
will return meaningless results.
.It Fl x , -context Ar n
Set
.Ar n
context lines to be shown in the interactive stash diff display such that
0 \*(Le n \*(Le 64.
By default, 5 context lines are shown.
Illegal values are a no-op.
.El
.Pp
.Tg log
.It Cm timeline Oo Fl C | -no-colour Oc Oo Fl T | -tag Ar tag Oc \
Oo Fl b | -branch Ar branch Oc Oo Fl c | -commit Ar commit Oc \
Oo Fl f | -filter Ar glob Oc Oo Fl h | -help Oc  Oo Fl n | -limit Ar n Oc \
Oo Fl R | -repo Ar path Oc Oo Fl t | -type Ar type Oc \
Oo Fl u | -username Ar user Oc Oo Fl z | -utc Oc \
Op Ar path
.Dl Pq aliases: Cm log , Cm tl
Display commit history of a repository.
If
.Ar path
is specified, only commits that modified the file(s) at this path will populate
the timeline.
The
.Ar path
may be absolute, relative to the current working directory, or relative to the
repository root.
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.
.Pp
If no command is explicitly specified, this command will be executed by
default.
.Pp
Options for
.Nm Cm timeline
are as follows:
.Bl -tag -width Ds
.It Fl b , -branch Ar branch
Display commits that are members of the specified
.Ar branch .
The expected argument is a glob of the symbolic name of a branch, with the most
recent branch to match being selected.
Pattern matching is case-insensitive unless
.Ar branch
has at least one uppercase character, in which case the search will be
case-sensitive.
By default,
.Nm
will display all commits irrespective of the branch on which they
reside.
.It Fl C , -no-colour
Disable colourised timeline, which is enabled by default on supported
terminals.
If this option is not used, colour can be toggled with the
.Sy c
timeline view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl c , -commit Ar commit
Open the timeline from the check-in identified by
.Ar commit .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit SHA1 or SHA3 hash.
When this option is not supplied,
.Nm
will open the timeline to the latest leaf on the repository tree.
For a complete list of valid arguments this option accepts, see
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Check-in Names".
.It Fl f , -filter Ar glob
Filter timeline by commits containing
.Ar glob
in any of the commit comment, user, or branch fields.
Pattern matching is case-insensitive unless
.Ar glob
has at least one uppercase character, in which case the search will be
case-sensitive.
Filtering can also be performed at runtime with the
.Sy F
timeline view key binding as documented below.
.It Fl h , -help
Display timeline command help and usage information then exit.
.It Fl n , -limit Ar n
Limit timeline to the
latest
.Ar n
commits.
By default,
.Nm
will load the entire history of the repository's local checkout.
Negative values are a no-op.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm Cm timeline
invocation.
When this option is used, the checkout-related Fossil special tags

.Qq current ,
.Qq prev ,
and
.Qq next
are invalid arguments to the
.Sy --commit
option.
When
.Sy --commit
is not specified,
.Nm
will default to populating the timeline from the latest commit.
.It Fl T , -tag Ar tag
Only display commits with T cards containing
.Ar tag .
The expected argument is a glob of a commit manifest's T card argument, with the
most recent tag to match being selected.
Pattern matching is case-insensitive unless
.Ar tag
has at least one uppercase character, in which case the search will be
case-sensitive.
By default,
.Nm
will indiscriminately display all commits irrespective of which T cards
are attached to the commit manifest.
.It Fl t , -type Ar type
Only display
.Ar type
commits.
Valid
.Ar type
values are as follows:
.Bl -column -offset 2s YXZ description
.Sy ci Ta check-in
.Sy w Ta wiki
.Sy t Ta ticket
.Sy e Ta technote
.Sy f Ta forum post
.Sy g Ta tag artifact
.El
.Pp
By default, when this option is not supplied,
.Nm
will indiscriminately load all commits irrespective of
.Ar type .
This is a repeatable flag


.Pq e.g., Nm Cm timeline Cm -t e -t t .

.It Fl u , -username Ar user
Only display commits authored by
.Ar user .
The search is case-insensitive by default unless
.Ar user
contains at least one uppercase character, in which case the search will be
case-sensitive.
.It Fl z , -utc
Use Coordinated Universal Time (UTC) rather than local time when
displaying commit dates and timestamps.
.El
.Pp
Key bindings for
.Nm Cm timeline
are as follows:
.Bl -tag -width Ds
.It Ic Arrow-down, j, >, \&.
Move selection cursor down the timeline.
.It Ic Arrow-up, k, <, \&,
Move selection cursor up the timeline.
.It Ic Arrow-right, l
Scroll the view two columns to the right in the buffer.
The comment field moves left on the screen.
.It Ic Arrow-left, h
Scroll the view two columns to the left in the buffer.
The comment field moves right on the screen.
.It Ic $
Scroll the view right to the end of the longest comment summary line on the
page.
.It Ic 0
Scroll the view left to the beginning of the line.
.It Ic C-f, Page-down
Scroll timeline view one page downwards in the buffer.
.It Ic C-b, Page-up
Scroll timeline view one page upwards in the buffer.
.It Ic C-d
Scroll timeline view half a page downwards in the buffer.
.It Ic C-u
Scroll timeline view half a page upwards in the buffer.
.It Ic G, End
Move selection cursor to the last commit on the timeline (i.e., oldest commit
in the repository).
.It Ic gg, Home
Move selection cursor to the first commit on the timeline (i.e., newest commit
in the repository).
.It Ic Enter
Open a
.Cm diff
view displaying the changeset of the currently selected commit.
.It Ic Space
(Un)tag the currently selected commit as the base commit for the next tagged
commit to be diffed against.
If another commit is already tagged, show the changes between it and the
selected commit.
.It Ic b
Open and populate branch view with all repository branches.
.It Ic C
Diff local changes on disk in the current checkout against the selected
commit.
.It Ic c
Toggle colourised timeline.
On supported terminals,
.Nm
will default to displaying the timeline in colour.
.It Ic F
Prompt to enter a search term to filter a new timeline view by commits with
comment, user, or branch fields that match the entered pattern.
If no commits match, a message is displayed on screen.
.It Ic t
Display the tree of the repository corresponding to the currently selected
commit.
.It Ic /
Prompt to enter a search term to begin searching for commits matching
the pattern provided.
The search term is an extended regular expression, which is cross-referenced
against a commit's comment, the username of its author, branch, and SHA1
or SHA3 hash.
See
.Xr re_format 7
for regular expression syntax.
.It Ic n
Find the next commit that matches the current search term.
The search will continue until either a match is found or the earliest commit
on the timeline is consumed.
.It Ic N
Find the previous commit that matches the current search term.
The search will continue until either a match is found or the latest commit
on the timeline is consumed.
.It Ic Backspace
Cancel the current search or timeline traversal
.Po
i.e.,
.Sy / ,
.Sy G ,
or
.Sy End
.Pc .
.El
.Tg di
.It Cm diff Oo Fl b | -brief Oc Oo Fl C | -no-colour Oc Oo Fl h | -help Oc \
Oo Fl i | -invert Oc Oo Fl l | -line-numbers Oc Oo Fl P | -no-prototype Oc \
Oo Fl q | -quiet Oc Oo Fl R | -repo Ar path Oc Oo Fl s | -sbs Oc \
Oo Fl W | -whitespace-eol Oc Oo Fl w | -whitespace Oc \
Oo Fl x | -context Ar n Oc Oo Ar artifact1 Oo Ar artifact2 Oc Oc Op Ar path ...
.Dl Pq alias: Cm di
Display the differences between two repository artifacts, or between the local
changes on disk and a given commit.
If neither
.Ar artifact1
nor
.Ar artifact2
are specified,
.Nm
will diff the local changes on disk against the version on which the current
checkout is based.
If only
.Ar artifact1
is specified,
.Nm
will diff the current checkout, including any local changes on disk, against
this version.
When both arguments are specified, the changes between these two versions will
be displayed.
If supplied,
.Nm
will filter diffs between commits by
.Ar path
so that only changes involving the file(s) identified are displayed.
Paths may be absolute, relative to the current working directory, or relative
to the repository root.
Both
.Ar artifact1
and
.Ar artifact2
must be of the same type, which is expected to be either a symbolic check-in
name, tag, (unique abbreviated prefix of) a commit or blob artifact SHA1
or SHA3 hash, or an ISO 8601 formatted date.
Both artifact arguments must be supplied when diffing blobs; any following
non-option arguments are invalid and will be ignored.
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.
.Pp
Options for
.Nm Cm diff
are as follows:
.Bl -tag -width Ds
.It Fl b , -brief
Display file index and hash lines only.
.It Fl C , -no-colour
Disable coloured diff output, which is enabled by default on supported
terminals.
If this option is not used, colour can be toggled with the
.Sy c
diff view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl h , -help
Display diff command help and usage information then exit.
.It Fl i , -invert
Invert the difference between artifacts when displaying the diff.
.It Fl l , -line-numbers
Display file line numbers in diff output.
As documented below, this option can be toggled with the
.Sy L
diff view key binding.
.It Fl P , -no-prototype
Disable hunk header display of which function or scope each change is in,
which is enabled by default.
The heuristic will produce reliable results for all C-like languages
.Pq e.g., C/C++, Java, Python, JavaScript, Rust ;
however, Lisps and non-source code
.Pq e.g., Markdown, reStructuredText
will return meaningless results.
Function prototype cannot be displayed in the hunk header with either
.Fl l|-line-numbers
or
.Fl s|-sbs
formatted diffs.
This option can be toggled at runtime with the
.Sy p
key binding as documented below.
.It Fl q , -quiet
Disable verbose output; that is, do not output complete content of newly added
or deleted files, which are displayed by default.
Verbosity can also be toggled with the
.Sy v
key binding as documented below.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm Cm diff
invocation.
When this option is used, both
.Ar artifact1
and
.Ar artifact2
are required, and the checkout-related Fossil special tags
.Qq current ,
.Qq prev ,
and
.Qq next
are invalid
.Sy artifact
operands.
.It Fl s , -sbs
Display a side-by-side formatted diff.
As documented below, this option can also be toggled at runtime with the
.Sy S
key binding.
.Po Mutually exclusive with
.Fl l , -line-numbers
.Pc
.It Fl W , -whitespace-eol
Ignore end-of-line whitespace-only changes when displaying the diff.
.It Fl w , -whitespace
Ignore whitespace-only changes when displaying the diff.
.It Fl x , -context Ar n
Set
.Ar n
context lines to be shown in the diff such that 0 \*(Le n \*(Le 64.
By default, 5 context lines are shown.
Illegal values are a no-op.
.El
.Pp
All the above options
.Po
sans
.Fl h
and
.Fl R
.Pc
can be made persistent as global or per-repo settings.
See
.Sx ENVIRONMENT
for details.
.Pp
Key bindings for
.Nm Cm diff
are as follows:
.Bl -tag -width Ds
.It Ic Arrow-down, j
Move the selection cursor down one line.
.It Ic Arrow-up, k
Move the selection cursor up one line.
.It Ic Arrow-right, l
Scroll the view two columns to the right in the buffer.
Diff output moves left on the screen.
.It Ic Arrow-left, h
Scroll the view two columns to the left in the buffer.
Diff output moves right on the screen.
.It Ic $
Scroll the view right to the end of the longest line in the diff.
.It Ic 0
Scroll the view left to the beginning of the line.
.It Ic C-e
Scroll view one line downwards in the buffer.
Diff output moves upwards on the screen.
.It Ic C-y
Scroll view one line upwards in the buffer.
Diff output moves downwards on the screen.




.It Ic C-f, Page-down, Space
Scroll diff view one page downwards in the buffer.
.It Ic C-b, Page-up
Scroll diff view one page upwards in the buffer.
.It Ic C-d
Scroll diff view half a page downwards in the buffer.
.It Ic C-u
Scroll diff view half a page upwards in the buffer.
.It Ic G, End
Scroll to the end of the view (i.e., last line of diff output).
.It Ic gg, Home
Scroll to the top of the view (i.e., first line of diff output).
.It Ic C-k, K, <, \&,
If the diff is derived from a
.Cm timeline
view, move up the timeline
to the previous (i.e., newer) commit and display its diff.
If the diff is derived from a
.Cm blame
view, display the commit diff of the previous line in the annotated file.
.It Ic C-j, J, >, \&.
If the diff is derived from a
.Cm timeline
view, move down the timeline
to the next (i.e., older) commit and display its diff.
If the diff is derived from a
.Cm blame
view, display the commit diff of the next line in the annotated file.
.It Ic C-p
Navigate to previous file in the diff.
.It Ic C-n
Navigate to next file in the diff.
.It Ic \&[
Navigate to previous hunk in the diff.
.It Ic \&]
Navigate to next hunk in the diff.
.It Ic \&-, \&_
Decrease the number of context lines shown in diff output.
.It Ic \&=, \&+
Increase the number of context lines shown in diff output.
.It Ic #
Toggle display of diff view line numbers.
.It Ic @
Open prompt to enter line number and navigate to that line in the view.
.It Ic b
Toggle brief diff mode by only displaying file index and hash lines.
.It Ic B
Open and populate branch view with all repository branches.
.It Ic c
Toggle coloured diff output.
On supported terminals,
.Nm
will default to displaying changes and diff metadata in colour.
.It Ic F
Open prompt to enter file number and navigate to that file in the diff.
.It Ic i
Toggle inversion of diff output.
.It Ic L
Toggle display of file line numbers in the diff.
.It Ic P
Write the currently viewed diff to a patch file.
.Nm
will prompt the user for a file path, which must be absolute or relative to
the current working directory.
If no path is input and the
.Sy return
key is entered, the patch will be written to the current working directory
using the first ten characters of the current artifact hash as the filename
with a
.Sy .patch
extension
.Pq e.g., Qq Pa 2870235eef.patch .
.It Ic p
In the diff hunk header, toggle display of which function each change is in;
for example:
.Sy @@ -2360,10 +2361,11 @@ draw_commits(struct fnc_view *view)
.It Ic S
Toggle display of a side-by-side formatted diff.
.It Ic v
Toggle verbosity of diff output.
By default,
.Nm
will display the entire content of newly added or deleted files.
.It Ic W
Toggle whether end-of-line whitespace changes are ignored when comparing lines
in the diff.
.It Ic w
Toggle whether whitespace-only changes are ignored when comparing lines in the
diff.
.It Ic /
Prompt to enter a search term to begin searching the diff output for
lines matching the pattern provided.
The search term is an extended regular expression, which is documented in
.Xr re_format 7 .
.It Ic n
Find the next line that matches the current search term.
.It Ic N
Find the previous line that matches the current search term.
.El
.Tg dir
.It Cm tree Oo Fl C | -no-colour Oc Oo Fl c | -commit Ar commit Oc \
Oo Fl h | -help Oc Oo Fl R | -repo Ar path Oc Op Ar path
.Dl Pq aliases: Cm dir , Cm tr
Display navigable, hierarchical tree of a repository.
If a
.Ar path
is specified, display tree nodes of this path.
The
.Ar path
may be absolute, relative to the current working directory, or relative to the
repository root.
With no options passed, the tree will reflect the state of the latest commit on
trunk.
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.
.Pp
Tree nodes are lexicographically ordered and may be postfixed with an identifier
corresponding to the mode of the file object on disk as returned by
.Xr lstat 2 :
.Bl -column -offset Ds YXZ description
.It / Ta directory
.It * Ta executable
.It @ Ta symbolic link
.El
.Pp
Nodes representing symbolic links are also annotated with the path of the
source file.
.Pp
Options for
.Nm Cm tree
are as follows:
.Bl -tag -width Ds
.It Fl C , -no-colour
Disable coloured output, which is enabled by default on supported terminals.
If this option is not used, colour can be toggled with the
.Sy c
tree view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl c , -commit Ar commit
The displayed tree will reflect the state of the repository as at the check-in
identified by
.Ar commit .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit SHA1 or SHA3 hash.
For a complete list of valid arguments this option accepts, see
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Check-in Names".
.It Fl h , -help
Display tree command help and usage information then exit.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm Cm tree
invocation.
When this option is used, the checkout-related Fossil special tags

.Qq current ,
.Qq prev ,
and
.Qq next
are invalid arguments to the
.Sy --commit
option.
.El
.Pp
Key bindings for
.Nm Cm tree
are as follows:
.Bl -tag -width Ds
.It Ic Enter, Arrow-right, l
Enter the currently selected directory, or open a
.Cm blame
view of the currently selected file.
.It Ic Backspace, Arrow-left, h
Move up a level to the parent directory.
This is a no-op when in the root tree.
.It Ic Arrow-down, j
Move selection cursor one node down the tree.
.It Ic Arrow-up, k
Move selection cursor one node up the tree.
.It Ic C-f, Page-down
Scroll tree view one page downwards in the buffer.
.It Ic C-b, Page-up
Scroll tree view one page upwards in the buffer.
.It Ic C-d
Scroll tree view half a page downwards in the buffer.
.It Ic C-u
Scroll tree view half a page upwards in the buffer.
.It Ic Home, gg
Move selection cursor to the first node in the tree.
.It Ic End, G
Move selection cursor to the last node in the tree.
.It Ic b
Open and populate branch view with all repository branches.
.It Ic c
Toggle coloured output.
On supported terminals,
.Nm
will default to displaying the tree in colour.
.It Ic d
Toggle ISO8601 modified timestamp display for each tree entry.
.It Ic i
Toggle SHA hash display for all file nodes displayed in the tree.
.It Ic t
Open
.Cm timeline
view for the currently selected tree node.
This will display the timeline of all commits that involve the versioned
file(s) corresponding to the selected node.
.It Ic /
Prompt to enter a search term to begin searching the tree for nodes matching the
entered pattern.
The search term is an extended regular expression, as documented in
.Xr re_format 7 ,
and is matched against the path of each tree node.
.It Ic n
Find the next tree node that matches the current search pattern.
.It Ic N
Find the previous tree node that matches the current search pattern.
.El
.Tg praise
.It Cm blame Oo Fl C | -no-colour Oc \
Oo Fl c | -commit Ar commit Oo Fl r | -reverse Oc Oc Oo Fl h | -help Oc \
Oo Fl n | -limit Ar n Oc Oo Fl R | -repo Ar path Oc Ar path
.Dl Pq aliases: Cm praise , Cm bl
Show commit attribution history for each line of the file at the specified
.Ar path ,
which may be absolute, relative to the current working directory, or relative to
the repository root.
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.
.Pp
Options for
.Nm Cm blame
are as follows:
.Bl -tag -width Ds
.It Fl C , -no-colour
Disable coloured output, which is enabled by default on supported terminals.
If this option is not used, colour can be toggled with the
.Sy c
blame view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl c , -commit Ar commit
Start blame of file at the specified
.Ar path
from the check-in identified by
.Ar commit .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit SHA1 or SHA3 hash.
When this option is not supplied,
.Nm
will blame the version of the file from the current checkout.
For a complete list of valid arguments this option accepts, see
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Check-in Names".
.It Fl h , -help
Display blame command help and usage information then exit.
.It Fl l , -line Ar lineno
Open annotated file at
.Ar lineno .
As documented below, once the file is loaded, the
.Sy @
key binding can be used to navigate to an arbitrary line in the file.
.It Fl n , -limit Ar n
Limit depth of blame history to
.Ar n
commits or seconds.
The latter is denoted by a postfixed 's' (e.g., 30s).
With this option,
.Nm
will traverse either as many commits as specified, or as possible in the
specified time limit.
By default,
.Nm
will traverse the entire historical record of the file, which can be expensive
for large files that span many commits.
Use this option for a faster, more targeted annotation.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm Cm blame
invocation.
When this option is used, the checkout-related Fossil special tags

.Qq current ,
.Qq prev ,
and
.Qq next
are invalid arguments to the
.Sy --commit
option.
When
.Sy --commit
is not specified,
.Nm
will default to blaming the version of the file found in the latest commit.
.It Fl r , -reverse
Reverse annotate the file starting from a historical commit and move forward in
time.
That is, rather than show the most recent change to each line, show the first
time each line was modified by a subsequent commit after the specified
.Ar commit .
(Requires \fB\-c\fP|\fB\-\-commit\fP.)
.El
.Pp
Key bindings for
.Nm Cm blame
are as follows:
.Bl -tag -width Ds
.It Ic Arrow-down, j
Move selection cursor down one line.
.It Ic Arrow-up, k
Move selection cursor up one line.
.It Ic Arrow-right, l
Scroll the view two columns to the right in the buffer.
File output moves left on the screen.
.It Ic Arrow-left, h
Scroll the view two columns to the left in the buffer.
File output moves right on the screen.
.It Ic $
Scroll the view right to the end of the longest line in the file.
.It Ic 0
Scroll the view left to the beginning of the line.
.It Ic C-f, Page-down
Scroll blame view one page downwards in the buffer.
.It Ic C-b, Page-up
Scroll blame view one page upwards in the buffer.
.It Ic C-d
Scroll blame view half a page downwards in the buffer.
.It Ic C-u
Scroll blame view half a page upwards in the buffer.
.It Ic Home, gg
Move selection cursor to the first line in the file.
.It Ic End, G
Move selection cursor to the last line in the file.
.It Ic Enter
Display the
.Cm diff
of the commit corresponding to the currently selected line.
.It Ic #
Toggle display of file line numbers.
.It Ic @
Open prompt to enter line number and navigate to that line in the file.
.It Ic B, Backspace
Reload the previous blamed version of the file.
.It Ic b
Blame the version of the file corresponding to the commit in the currently
selected line.
.It Ic c
Toggle coloured output.
On supported terminals,
.Nm
will default to displaying the blamed file in colour.
.It Ic p
Blame the version of the file corresponding to the parent of the commit in
the currently selected line.
.It Ic T
Open and populate branch view with all repository branches.
.It Ic /
Prompt to enter a search term to begin searching the file for tokens matching
the entered pattern.
The search term is an extended regular expression, as documented in
.Xr re_format 7 .
.It Ic N
Find the previous token that matches the current search pattern.
.It Ic n
Find the next token that matches the current search pattern.
.El
.Tg tag
.It Cm branch Oo Fl C | -no-colour Oc Oo Fl -after Ar date | \
Fl -before Ar date Oc Oo Fl h | -help Oc Oo Fl -open | Fl -closed Oc \
Oo Fl p | -no-private Oc Oo Fl R | -repo Ar path Oc Oo Fl r | -reverse Oc \
Oo Fl s | -sort Ar order Oc Op Ar glob
.Dl Pq aliases: Cm tag , Cm br
Display navigable list of repository branches.
If
.Ar glob
is specified, only display branches matching the pattern provided.
Pattern matching is case-insensitive unless
.Ar glob
contains at least one uppercase character, in which case the search will be
case-sensitive.
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.
.Pp
Branches are lexicographically ordered by default, and are prefixed with an
identifier corresponding to the branch state (i.e., open/closed).
The current and private branches are additionally annotated with a postfixed
identifier:
.Bl -column -offset Ds ABCDEFGHIJ description
.It +dev-foo Ta open
.It -rm-bar Ta closed
.It +trunk@ Ta current
.It +wip-baz* Ta private
.El
.Pp
All branches, irrespective of state or privacy, are displayed by default, but
can be filtered based on several characteristics.
.Pp
Options for
.Nm Cm branch
are as follows:
.Bl -tag -width Ds
.It Fl a , -after Ar date
Display only those branches with activity after the specified
.Ar date ,
which is expected to be either an ISO8601
.Po e.g.,







|








|
>
>
>
>
>
>
>








|











|






|





|






|








|







>
















|
|





|
<
|






|



<
|




>
>
>


|
|
|

|
|
|

|
|
|
|

|









|
|




|
<








|
|
<


|
<
|
|
<
<
<

|
>



|









|


|



|


|
|
<
|





>
|


|
<









|







|
|





|
<
|
>
|
>

|
|


>
|

|
|

<
<
|
<
|
<
<
|
<





>
>
|















|
|








<
|










|
|
<


|
<


|
<

|
>







|






|
|


|
<





<
|










|
|

|
|





|
|


<
|
















|


|
<
|
>






|
<








|
|


|
<






|
<















|
>
>
|
>













|


|

|

|
|
|
|
|
|
|


|

|

|

|

|

|


|


|



|

|
<
|
|

|


|
|
<


|

|
|
|


|

<
|
|
|
<


|
|
|
|
|
|
|

|










|
|
|
|
|


|
<






|
<




<
|
|
<



|
|
|
<




|
|
|
|
<

|
>




|


<
<


<
|










|
|



|
|
|




|




|




|
|





|


|
<
|












|
|
|












|
<









|
<




|


|

|

|
|
|
|
|
|
|

|

|
|
|
|
|
|
>
>
>
>
|

|

|

|

|

|

|







|







<
<
<
<
<
<
<
<
|

|

|

|

|
<
<

|
|
<


|

|

|

|



|
<





|
<
|
|


|

|
|
<


|
<
<
<


|

|
|

|

|






|
<

|
<


<
|
<
|

|
>
















|
















|
|







|


|
<
|
>










|


|



|
|
<
|

|

|

|

|

|

|

|

|

|
|
<


|

|
|
|


|
|
|
|

|
|


|

|






|



|
<

|
>




|

















|
|

|
|













<
|



|
<


|
|



|


|
<
|
>






|
<






<
|
|





|


|

|

|
|
|
|
|
|
|

|

|

|

|

|

|

|

|



|

|

|

|


|
|
<


|


|

|

|
|

|

|








|
<

|
|


|
<

|
>




|
|












|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

120
121
122
123
124
125
126
127
128
129
130

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
202
203
204
205
206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
221
222
223
224

225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

250
251
252
253
254
255
256
257
258
259
260
261
262
263
264


265

266


267

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

301
302
303
304
305
306
307
308
309
310
311
312
313

314
315
316

317
318
319

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341

342
343
344
345
346

347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392

393
394
395
396
397
398
399
400
401

402
403
404
405
406
407
408
409
410
411
412
413
414

415
416
417
418
419
420
421

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493

494
495
496
497
498
499
500
501

502
503
504
505
506
507
508
509
510
511
512

513
514
515

516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544

545
546
547
548
549
550
551

552
553
554
555

556
557

558
559
560
561
562
563

564
565
566
567
568
569
570
571

572
573
574
575
576
577
578
579
580
581


582
583

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
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
706
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
772
773
774
775
776
777
778
779
780
781
782
783
784

785
786

787
788

789

790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839

840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860

861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880

881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912

913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955

956
957
958
959
960

961
962
963
964
965
966
967
968
969
970
971

972
973
974
975
976
977
978
979
980

981
982
983
984
985
986

987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037

1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061

1062
1063
1064
1065
1066
1067

1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate$
.Dt FNC 1
.Os
.Sh NAME
.Nm fnc
.Nd Read-only ncurses-based Fossil repository browser
.Sh SYNOPSIS
.Nm
.Op Ar command
.Op Fl h | -help
.Nm
.Op Fl h | -help
.Op Fl v | -version
.Nm
.Cm stash
.Sm off
.Oo
.Cm get | pop
.Li |
.Fl ChPx
.Sm on
.Oc
.Nm
.Cm config
.Op Fl hu
.Op Fl -ls
.Op Fl R Ar path
.Op Ar setting Op Ar value
.Nm
.Cm timeline
.Op Fl Cz
.Op Fl b Ar branch
.Op Fl c Ar commit
.Op Fl f Ar glob
.Op Fl n Ar number
.Op Fl R Ar path
.Op Fl T Ar tag
.Op Fl t Ar type
.Op Fl u Ar user
.Op Ar path
.Nm
.Cm diff
.Op Fl CilPqsWw
.Op Fl R Ar path
.Op Fl x Ar number
.Op Ar artifact1 Op Ar artifact2
.Op Ar path ...
.Nm
.Cm tree
.Op Fl C
.Op Fl c Ar commit
.Op Fl R Ar path
.Op Ar path
.Nm
.Cm blame
.Op Fl C
.Op Fl c Ar commit Op Fl r
.Op Fl n Ar number
.Op Fl R Ar path
.Ar path
.Nm
.Cm branch
.Op Fl Ccopr
.Op Fl a Ar date | Fl b Ar date
.Op Fl R Ar path
.Op Fl s Ar order
.Op Ar glob
.Nm
.Op Ar path
.Sh DESCRIPTION
.Nm
is an interactive interface for
.Xr fossil 1
repositories,
and supports multiple views to display repository data:
.Bl -tag -width Ds
.It Timeline view
Display commits from the repository's history in chronologically
descending order.
.Br
If no
.Ar command
or
.Ar arg
are specified, or just a
.Ar path
is passed,
.Nm
will default to displaying this view.
.It Diff view
Display changes introduced in the specified commit, or between two repository
artifacts.
.It Tree view
Display navigable tree reflecting the repository state as at the specified
commit.
.It Blame view
Display and annotate each line in the given file with the hyperlinked historical
version that last modified the line.
.It Branch view
Display navigable list of all repository branches.
.El
.Pp
.Nm
provides both global and command-specific options and in-app key

bindings.  Global options are as follows:
.Bl -tag -width 6v
.It Fl h , -help
Display program help and usage information then exit.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Nm

session.  See command specific options for details.
.It Fl v , -version
Display program version then exit.
.El
.Pp
Note that any global options preceding a command name will be
interpreted as the command-specific variant if such an option exists.
.Pp
Global key bindings are as follows:
.Bl -tag -width Ds
.It Cm H, ?, F1
Display in-app help.
.It Cm Tab
Switch focus between open views.
.It Cm f
Toggle the active view between fullscreen and splitscreen mode.  Note
that
.Nm
will open nested views in splitscreen mode if the terminal window is
equal to or greater than 110 columns wide.
.It Cm Q
Immediatey quit
.Nm .
.It Cm q
Quit the active view.
.El
.Pp
Commands available to
.Nm
are as follows:
.Bl -tag -width 4v
.Tg conf
.It Cm config Oo Fl h | -help Oc Oo Fl -ls Oc Oo Fl R | -repo Ar path Oc \
Oo Ar setting Op Ar value | Fl -unset Oc
.Dl Pq aliases: Cm conf , Cm cfg , Cm settings , Cm set
Retrieve the current, or set a new,
.Ar value
for
.Ar setting
in the local repository.  When specified,

.Ar value
will become the new value for
.Ar setting ,
otherwise
.Nm
will display the current value of
.Ar setting .
With no arguments,
.Cm fnc config
will display a list of all configurable settings.  Alternatively, see

.Sx ENVIRONMENT
for a detailed list of available settings
used in the display or processing of data.  When no value is found for a given

setting in the local repository, environment variables will be searched.
If still not found, default values will be used.  Unless the



.Sy --repo
option is used, this command must be executed from within the repository
hierarchy; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
Options for
.Cm fnc 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.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Cm fnc config
invocation.
.It Fl u , -unset
Clear the specified
.Ar setting.
.El
.Tg stash
.It Cm stash Oo Cm get Ns | Ns Cm pop Ns | Ns Oo Fl C | -no-colour Oc \
Oo Fl h | -help Oc Oo Fl P | -no-prototype Oc Oo Fl x | -context Ar n Oc Oc

.Dl Pq aliases: Cm snapshot , Cm snap , Cm save , Cm sta
When run with neither the
.Cm get
nor
.Cm pop
subcommands,
.Nm
.Cm stash
will present an interactive view of the local changes on disk and iterate
each hunk in the diff, prompting the user to either stash or keep the current
change in the checkout. Valid answers are as follows:

.Bl -column -offset 2s YXZ description
.Sy b Ta scroll back Ns \(ha
.Sy m Ta show more of the current hunk Ns \(ha
.Sy y Ta yes, stash the current hunk
.Sy n Ta no, do not stash the current hunk
.Sy a Ta yes, stash this hunk and all remaining hunks in the file
.Sy k Ta no, do not stash this hunk nor any remaining hunks in the file
.Sy A Ta yes, stash this hunk and all remaining hunks in the diff
.Sy K Ta no, do not stash this hunk nor any remaining hunks in the diff
.Sy ? Ta display help dialog
.El
.Pp
\(haConditionally available when the current hunk occupies the previous
and/or following page.
.Pp
When all hunks have been selected,
.Nm
will prompt the user to enter a stash message.  If not provided, a default
message of
.Qo
fnc stash HASH-PREFIX
.Qc ,
where
.Qq HASH-PREFIX
is an abbreviated SHA UUID hash of the current checkout,

will be used.  At any time prior to the final hunk being selected
.Po
i.e., before the stash message prompt
.Pc ,
the operation can be aborted by opening the help dialog and entering
.Qq Q ,
which will discard all selections and leave the checkout state unchanged.
.Pp
Available subcommands for
.Nm
.Cm stash
are as follows:
.Bl -ohang -width Ds
.It Cm get
.Dl Pq aliases: Cm apply


Retrieve the most recent stash entry and apply it to the current checkout.

.It Cm pop


Remove the most recent stash entry and apply it to the current checkout.

.El
.Pp
Options only apply to
.Nm
.Cm stash
.Po
i.e.,
neither the
.Cm get
nor
.Cm pop
subcommands
.Pc
and are as follows:
.Bl -tag -width Ds
.It Fl C , -no-colour
Disable coloured output, which is enabled by default on supported terminals.
.It Fl h , -help
Display
.Cm stash
command help and usage information then exit.
.It Fl P , -no-prototype
Disable hunk header display of which function or scope each change is in,
which is enabled by default.  The heuristic will produce reliable results for
all C-like languages
.Pq e.g., C/C++, Java, Python, JavaScript, Rust ;
however, Lisps and non-source code
.Pq e.g., Markdown, reStructuredText
will return meaningless results.
.It Fl x , -context Ar n
Set
.Ar n
context lines to be shown in the interactive stash diff display such that

0 \*(Le n \*(Le 64.  By default, 5 context lines are shown.
Illegal values are a no-op.
.El
.Pp
.Tg log
.It Cm timeline Oo Fl C | -no-colour Oc Oo Fl T | -tag Ar tag Oc \
Oo Fl b | -branch Ar branch Oc Oo Fl c | -commit Ar commit Oc \
Oo Fl f | -filter Ar glob Oc Oo Fl h | -help Oc  Oo Fl n | -limit Ar n Oc \
Oo Fl R | -repo Ar path Oc Oo Fl t | -type Ar type Oc \
Oo Fl u | -username Ar user Oc Oo Fl z | -utc Oc \
Op Ar path
.Dl Pq aliases: Cm log , Cm tl , Cm time , Cm ti
Display commit history of a repository.  If

.Ar path
is specified, only commits that modified the file(s) at this path will populate
the timeline.  The

.Ar path
may be absolute, relative to the current working directory, or relative to the
repository root.  Unless the

.Sy --repo
option is used, this command must be executed from within the repository
hierarchy; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
.Pp
If no command is explicitly specified, this command will be executed by
default.
.Pp
Options for
.Cm fnc timeline
are as follows:
.Bl -tag -width Ds
.It Fl b , -branch Ar branch
Display commits that are members of the specified
.Ar branch .
The expected argument is a glob of the symbolic name of a branch, with the most
recent branch to match being selected.  Pattern matching is case-insensitive
unless
.Ar branch
has at least one uppercase character, in which case the search will be
case-sensitive.  By default,

.Nm
will display all commits irrespective of the branch on which they
reside.
.It Fl C , -no-colour
Disable colourised timeline, which is enabled by default on supported

terminals. If this option is not used, colour can be toggled with the
.Sy c
timeline view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl c , -commit Ar commit
Open the timeline from the check-in identified by
.Ar commit .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit UUID SHA1 or SHA3 hash. When this option is
not supplied,
.Nm
will open the timeline to the latest leaf on the repository tree.  For a
complete list of valid arguments this option accepts, see
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Check-in Names".
.It Fl f , -filter Ar glob
Filter timeline by commits containing
.Ar glob
in any of the commit comment, user, or branch fields.  Pattern matching is
case-insensitive unless
.Ar glob
has at least one uppercase character, in which case the search will be

case-sensitive.  Filtering can also be performed in-session with the
.Sy F
timeline view key binding as documented below.
.It Fl h , -help
Display timeline command help and usage information then exit.
.It Fl n , -limit Ar n
Limit timeline to the
latest
.Ar n
commits.
By default,
.Nm
will load the entire history of the repository's local checkout.
Negative values are a no-op.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Cm fnc timeline

invocation.  When this option is used, the checkout-related Fossil special
tags
.Qq current ,
.Qq prev ,
and
.Qq next
are invalid arguments to the
.Sy --commit
option.  When

.Sy --commit
is not specified,
.Nm
will default to populating the timeline from the latest commit.
.It Fl T , -tag Ar tag
Only display commits with T cards containing
.Ar tag .
The expected argument is a glob of a commit manifest's T card argument, with the
most recent tag to match being selected.  Pattern matching is case-insensitive
unless
.Ar tag
has at least one uppercase character, in which case the search will be
case-sensitive.  By default,

.Nm
will indiscriminately display all commits irrespective of which T cards
are attached to the commit manifest.
.It Fl t , -type Ar type
Only display
.Ar type
commits. Valid

.Ar type
values are as follows:
.Bl -column -offset 2s YXZ description
.Sy ci Ta check-in
.Sy w Ta wiki
.Sy t Ta ticket
.Sy e Ta technote
.Sy f Ta forum post
.Sy g Ta tag artifact
.El
.Pp
By default, when this option is not supplied,
.Nm
will indiscriminately load all commits irrespective of
.Ar type .
Note that this is a repeatable flag (e.g.,
.Nm
.Cm timeline
.Cm -t e -t t Ns
).
.It Fl u , -username Ar user
Only display commits authored by
.Ar user .
The search is case-insensitive by default unless
.Ar user
contains at least one uppercase character, in which case the search will be
case-sensitive.
.It Fl z , -utc
Use Coordinated Universal Time (UTC) rather than local time when
displaying commit dates and timestamps.
.El
.Pp
Key bindings for
.Cm fnc timeline
are as follows:
.Bl -tag -width Ds
.It Cm Arrow-down, j, >, \&.
Move selection cursor down the timeline.
.It Cm Arrow-up, k, <, \&,
Move selection cursor up the timeline.
.It Cm Arrow-right, l
Scroll the view two columns to the right in the buffer.  The comment field
moves left on the screen.
.It Cm Arrow-left, h
Scroll the view two columns to the left in the buffer.  The comment field
moves right on the screen.
.It Cm $
Scroll the view right to the end of the longest comment summary line on the
page.
.It Cm 0
Scroll the view left to the beginning of the line.
.It Cm C-f, Page-down
Scroll timeline view one page downwards in the buffer.
.It Cm C-b, Page-up
Scroll timeline view one page upwards in the buffer.
.It Cm C-d
Scroll timeline view half a page downwards in the buffer.
.It Cm C-u
Scroll timeline view half a page upwards in the buffer.
.It Cm G, End
Move selection cursor to the last commit on the timeline (i.e., oldest commit
in the repository).
.It Cm gg, Home
Move selection cursor to the first commit on the timeline (i.e., newest commit
in the repository).
.It Cm Enter
Open a
.Cm diff
view displaying the changeset of the currently selected commit.
.It Cm Space
(Un)tag the currently selected commit as the base commit for the next tagged
commit to be diffed against.  If another commit is already tagged, show the

changes between it and the selected commit.
.It Cm b
Open and populate branch view with all repository branches.
.It Cm C
Diff local changes on disk in the current checkout against the selected
commit.
.It Cm c
Toggle colourised timeline. On supported terminals,

.Nm
will default to displaying the timeline in colour.
.It Cm F
Prompt to enter a search term to filter a new timeline view by commits with
comment, user, or branch fields that match the entered pattern.  If no commits
match, a message is displayed on screen.
.It Cm t
Display the tree of the repository corresponding to the currently selected
commit.
.It Cm /
Prompt to enter a search term to begin searching for commits matching

the pattern provided.  The search term is an extended regular expression,
which is cross-referenced against a commit's comment, the username of
its author, branch, and UUID SHA1 or SHA3 hash.  See

.Xr re_format 7
for regular expression syntax.
.It Cm n
Find the next commit that matches the current search term.  The search
will continue until either a match is found or the earliest commit on
the timeline is consumed.
.It Cm N
Find the previous commit that matches the current search term.  The
search will continue until either a match is found or the latest commit
on the timeline is consumed.
.It Cm Backspace
Cancel the current search or timeline traversal
.Po
i.e.,
.Sy / ,
.Sy G ,
or
.Sy End
.Pc .
.El
.Tg di
.It Cm diff Oo Fl C | -no-colour Oc Oo Fl h | -help Oc Oo Fl i | -invert Oc \
Oo Fl l | -line-numbers Oc Oo Fl P | -no-prototype Oc Oo Fl q | -quiet Oc \
Oo Fl R | -repo Ar path Oc Oo Fl s | -sbs Oc Oo Fl W | -whitespace-eol Oc \
Oo Fl w | -whitespace Oc Oo Fl x | -context Ar n Oc \
Oo Ar artifact1 Oo Ar artifact2 Oc Oc Op Ar path ...
.Dl Pq alias: Cm di
Display the differences between two repository artifacts, or between the local
changes on disk and a given commit.  If neither

.Ar artifact1
nor
.Ar artifact2
are specified,
.Nm
will diff the local changes on disk against the version on which the current
checkout is based.  If only

.Ar artifact1
is specified,
.Nm
will diff the current checkout, including any local changes on disk, against

this version.  When both arguments are specified, the changes between these two
versions will be displayed.  If supplied,

.Nm
will filter diffs between commits by
.Ar path
so that only changes involving the file(s) identified are displayed.  Paths may
be absolute, relative to the current working directory, or relative to the
repository root.  Both

.Ar artifact1
and
.Ar artifact2
must be of the same type, which is expected to be either a symbolic check-in
name, tag, (unique abbreviated prefix of) a commit or blob artifact UUID SHA1
or SHA3 hash, or an ISO 8601 formatted date. Both artifact arguments must be
supplied when diffing blobs; any following non-option arguments are invalid
and will be ignored.  Unless the

.Sy --repo
option is used, this command must be executed from within the repository
hierarchy; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
.Pp
Options for
.Cm fnc diff
are as follows:
.Bl -tag -width Ds


.It Fl C , -no-colour
Disable coloured diff output, which is enabled by default on supported

terminals. If this option is not used, colour can be toggled with the
.Sy c
diff view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl h , -help
Display diff command help and usage information then exit.
.It Fl i , -invert
Invert the difference between artifacts when displaying the diff.
.It Fl l , -line-numbers
Display file line numbers in diff output.  As documented below, this option can
be toggled with the
.Sy L
diff view key binding.
.It Fl P , -no-prototype
Disable chunk header display of which function or scope each change is in,
which is enabled by default.  The heuristic will produce reliable results for
all C-like languages
.Pq e.g., C/C++, Java, Python, JavaScript, Rust ;
however, Lisps and non-source code
.Pq e.g., Markdown, reStructuredText
will return meaningless results.
Function prototype cannot be displayed in the chunk header with either
.Fl l|-line-numbers
or
.Fl s|-sbs
formatted diffs.
This option can be toggled in-session with the
.Sy p
key binding as documented below.
.It Fl q , -quiet
Disable verbose output; that is, do not output complete content of newly added
or deleted files, which are displayed by default.  Verbosity can also be
toggled with the
.Sy v
key binding as documented below.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Cm fnc diff

invocation.  When this option is used, both
.Ar artifact1
and
.Ar artifact2
are required, and the checkout-related Fossil special tags
.Qq current ,
.Qq prev ,
and
.Qq next
are invalid
.Sy artifact
operands.
.It Fl s , -sbs
Display a side-by-side formatted diff.  As documented below, this option can
also be toggled in-session with the
.Sy s
key binding.
.Po Mutually exclusive with
.Fl l , -line-numbers
.Pc
.It Fl W , -whitespace-eol
Ignore end-of-line whitespace-only changes when displaying the diff.
.It Fl w , -whitespace
Ignore whitespace-only changes when displaying the diff.
.It Fl x , -context Ar n
Set
.Ar n
context lines to be shown in the diff such that 0 \*(Le n \*(Le 64.
By default, 5 context lines are shown.  Illegal values are a no-op.

.El
.Pp
All the above options
.Po
sans
.Fl h
and
.Fl R
.Pc
can be made persistent as global or per-repo settings.  See

.Sx ENVIRONMENT
for details.
.Pp
Key bindings for
.Cm fnc diff
are as follows:
.Bl -tag -width Ds
.It Cm Arrow-down, j
Move the selection cursor down one line.
.It Cm Arrow-up, k
Move the selection cursor up one line.
.It Cm Arrow-right, l
Scroll the view two columns to the right in the buffer.  Diff output moves
left on the screen.
.It Cm Arrow-left, h
Scroll the view two columns to the left in the buffer.  Diff output moves
right on the screen.
.It Cm $
Scroll the view right to the end of the longest line in the diff.
.It Cm 0
Scroll the view left to the beginning of the line.
.It Cm C-e
Scroll view one line downwards in the buffer.  Diff output moves upwards on the
screen.
.It Cm C-y
Scroll view one line upwards in the buffer.  Diff output moves downwards on the
screen.
.It Cm C-n
Navigate to next file in the diff.
.It Cm C-p
Navigate to previous file in the diff.
.It Cm C-f, Page-down, Space
Scroll diff view one page downwards in the buffer.
.It Cm C-b, Page-up
Scroll diff view one page upwards in the buffer.
.It Cm C-d
Scroll diff view half a page downwards in the buffer.
.It Cm C-u
Scroll diff view half a page upwards in the buffer.
.It Cm G, End
Scroll to the end of the view (i.e., last line of diff output).
.It Cm gg, Home
Scroll to the top of the view (i.e., first line of diff output).
.It Cm C-k, K, <, \&,
If the diff is derived from a
.Cm timeline
view, move up the timeline
to the previous (i.e., newer) commit and display its diff.
If the diff is derived from a
.Cm blame
view, display the commit diff of the previous line in the annotated file.
.It Cm C-j, J, >, \&.
If the diff is derived from a
.Cm timeline
view, move down the timeline
to the next (i.e., older) commit and display its diff.
If the diff is derived from a
.Cm blame
view, display the commit diff of the next line in the annotated file.








.It Cm \&-, \&_
Decrease the number of context lines shown in diff output.
.It Cm \&=, \&+
Increase the number of context lines shown in diff output.
.It Cm #
Toggle display of diff view line numbers.
.It Cm @
Open prompt to enter line number and navigate to that line in the view.
.It Cm b


Open and populate branch view with all repository branches.
.It Cm c
Toggle coloured diff output. On supported terminals,

.Nm
will default to displaying changes and diff metadata in colour.
.It Cm F
Open prompt to enter file number and navigate to that file in the diff.
.It Cm i
Toggle inversion of diff output.
.It Cm L
Toggle display of file line numbers in the diff.
.It Cm P
Write the currently viewed diff to a patch file.
.Nm
will prompt the user for a file path, which must be absolute or relative to
the current working directory.  If no path is input and the

.Sy return
key is entered, the patch will be written to the current working directory
using the first ten characters of the current artifact hash as the filename
with a
.Sy .patch
extension.

.It Cm p
In the diff chunk header, toggle display of which function each change is in;
for example:
.Sy @@ -2360,10 +2361,11 @@ draw_commits(struct fnc_view *view)
.It Cm S
Toggle display of a side-by-side formatted diff.
.It Cm v
Toggle verbosity of diff output. By default,

.Nm
will display the entire content of newly added or deleted files.
.It Cm w



Toggle whether whitespace-only changes are ignored when comparing lines in the
diff.
.It Cm /
Prompt to enter a search term to begin searching the diff output for
lines matching the pattern provided.  The search term is an extended
regular expression, which is documented in
.Xr re_format 7 .
.It Cm n
Find the next line that matches the current search term.
.It Cm N
Find the previous line that matches the current search term.
.El
.Tg dir
.It Cm tree Oo Fl C | -no-colour Oc Oo Fl c | -commit Ar commit Oc \
Oo Fl h | -help Oc Oo Fl R | -repo Ar path Oc Op Ar path
.Dl Pq aliases: Cm dir , Cm tr
Display navigable, hierarchical tree of a repository.  If a

.Ar path
is specified, display tree nodes of this path.  The

.Ar path
may be absolute, relative to the current working directory, or relative to the

repository root.  With no options passed, the tree will reflect the state of the

latest commit on trunk.  Unless the
.Sy --repo
option is used, this command must be executed from within the repository
hierarchy; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
.Pp
Tree nodes are lexicographically ordered and may be postfixed with an identifier
corresponding to the mode of the file object on disk as returned by
.Xr lstat 2 :
.Bl -column -offset Ds YXZ description
.It / Ta directory
.It * Ta executable
.It @ Ta symbolic link
.El
.Pp
Nodes representing symbolic links are also annotated with the path of the
source file.
.Pp
Options for
.Cm fnc tree
are as follows:
.Bl -tag -width Ds
.It Fl C , -no-colour
Disable coloured output, which is enabled by default on supported terminals.
If this option is not used, colour can be toggled with the
.Sy c
tree view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl c , -commit Ar commit
The displayed tree will reflect the state of the repository as at the check-in
identified by
.Ar commit .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit UUID SHA1 or SHA3 hash.  For a complete list of valid
arguments this option accepts, see
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Check-in Names".
.It Fl h , -help
Display tree command help and usage information then exit.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Cm fnc tree

invocation.  When this option is used, the checkout-related Fossil special
tags
.Qq current ,
.Qq prev ,
and
.Qq next
are invalid arguments to the
.Sy --commit
option.
.El
.Pp
Key bindings for
.Cm fnc tree
are as follows:
.Bl -tag -width Ds
.It Cm Enter, Arrow-right, l
Enter the currently selected directory, or open a
.Cm blame
view of the currently selected file.
.It Cm Backspace, Arrow-left, h
Move up a level to the parent directory.  This is a no-op when in the root tree.

.It Cm Arrow-down, j
Move selection cursor one node down the tree.
.It Cm Arrow-up, k
Move selection cursor one node up the tree.
.It Cm C-f, Page-down
Scroll tree view one page downwards in the buffer.
.It Cm C-b, Page-up
Scroll tree view one page upwards in the buffer.
.It Cm C-d
Scroll tree view half a page downwards in the buffer.
.It Cm C-u
Scroll tree view half a page upwards in the buffer.
.It Cm Home, gg
Move selection cursor to the first node in the tree.
.It Cm End, G
Move selection cursor to the last node in the tree.
.It Cm b
Open and populate branch view with all repository branches.
.It Cm c
Toggle coloured output. On supported terminals,

.Nm
will default to displaying the tree in colour.
.It Cm d
Toggle ISO8601 modified timestamp display for each tree entry.
.It Cm i
Toggle SHA hash UUID display for all file nodes displayed in the tree.
.It Cm t
Open
.Cm timeline
view for the currently selected tree node.  This will display the timeline of
all commits that involve the versioned file(s) corresponding to the selected
node.
.It Cm /
Prompt to enter a search term to begin searching the tree for nodes matching the
entered pattern.  The search term is an extended regular expression, as
documented in
.Xr re_format 7 ,
and is matched against the path of each tree node.
.It Cm n
Find the next tree node that matches the current search pattern.
.It Cm N
Find the previous tree node that matches the current search pattern.
.El
.Tg praise
.It Cm blame Oo Fl C | -no-colour Oc \
Oo Fl c | -commit Ar commit Oo Fl r | -reverse Oc Oc Oo Fl h | -help Oc \
Oo Fl n | -limit Ar n Oc Oo Fl R | -repo Ar path Oc Ar path
.Dl Pq aliases: Cm praise , Cm annotate , Cm bl , Cm pr , Cm an
Show commit attribution history for each line of the file at the specified
.Ar path ,
which may be absolute, relative to the current working directory, or relative to
the repository root.  Unless the

.Sy --repo
option is used, this command must be executed from within the repository
hierarchy; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
.Pp
Options for
.Cm fnc blame
are as follows:
.Bl -tag -width Ds
.It Fl C , -no-colour
Disable coloured output, which is enabled by default on supported terminals.
If this option is not used, colour can be toggled with the
.Sy c
blame view key binding as documented below.
User-defined colours are also supported, see
.Sx ENVIRONMENT
for details.
.It Fl c , -commit Ar commit
Start blame of file at the specified
.Ar path
from the check-in identified by
.Ar commit .
The expected argument is either the name of a branch, which will resolve
to the latest commit on the given branch, or (a unique abbreviated
prefix of) a valid commit UUID SHA1 or SHA3 hash. When this option is
not supplied,
.Nm
will blame the version of the file from the current checkout.  For a complete
list of valid arguments this option accepts, see
.Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \
"Fossil's Check-in Names".
.It Fl h , -help
Display blame command help and usage information then exit.
.It Fl l , -line Ar lineno
Open annotated file at
.Ar lineno .
As documented below, once the file is loaded, the
.Sy @
key binding can be used to navigate to an arbitrary line in the file.
.It Fl n , -limit Ar n
Limit depth of blame history to
.Ar n

commits or seconds.  The latter is denoted by a postfixed 's' (e.g., 30s).
With this option,
.Nm
will traverse either as many commits as specified, or as possible in the
specified time limit.  By default,

.Nm
will traverse the entire historical record of the file, which can be expensive
for large files that span many commits. Use this option for a faster, more
targeted annotation.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Cm fnc blame

invocation.  When this option is used, the checkout-related Fossil special
tags
.Qq current ,
.Qq prev ,
and
.Qq next
are invalid arguments to the
.Sy --commit
option.  When

.Sy --commit
is not specified,
.Nm
will default to blaming the version of the file found in the latest commit.
.It Fl r , -reverse
Reverse annotate the file starting from a historical commit and move forward in

time. That is, rather than show the most recent change to each line, show the
first time each line was modified by a subsequent commit after the specified
.Ar commit .
(Requires \fB\-c\fP|\fB\-\-commit\fP.)
.El
.Pp
Key bindings for
.Cm fnc blame
are as follows:
.Bl -tag -width Ds
.It Cm Arrow-down, j
Move selection cursor down one line.
.It Cm Arrow-up, k
Move selection cursor up one line.
.It Cm Arrow-right, l
Scroll the view two columns to the right in the buffer.  File output moves
left on the screen.
.It Cm Arrow-left, h
Scroll the view two columns to the left in the buffer.  File output moves
right on the screen.
.It Cm $
Scroll the view right to the end of the longest line in the file.
.It Cm 0
Scroll the view left to the beginning of the line.
.It Cm C-f, Page-down
Scroll blame view one page downwards in the buffer.
.It Cm C-b, Page-up
Scroll blame view one page upwards in the buffer.
.It Cm C-d
Scroll blame view half a page downwards in the buffer.
.It Cm C-u
Scroll blame view half a page upwards in the buffer.
.It Cm Home, gg
Move selection cursor to the first line in the file.
.It Cm End, G
Move selection cursor to the last line in the file.
.It Cm Enter
Display the
.Cm diff
of the commit corresponding to the currently selected line.
.It Cm #
Toggle display of file line numbers.
.It Cm @
Open prompt to enter line number and navigate to that line in the file.
.It Cm B, Backspace
Reload the previous blamed version of the file.
.It Cm b
Blame the version of the file corresponding to the commit in the currently
selected line.
.It Cm c
Toggle coloured output. On supported terminals,

.Nm
will default to displaying the blamed file in colour.
.It Cm p
Blame the version of the file corresponding to the parent of the commit in
the currently selected line.
.It Cm T
Open and populate branch view with all repository branches.
.It Cm /
Prompt to enter a search term to begin searching the file for tokens matching
the entered pattern.  The search term is an extended regular expression, as
documented in
.Xr re_format 7 .
.It Cm N
Find the previous token that matches the current search pattern.
.It Cm n
Find the next token that matches the current search pattern.
.El
.Tg tag
.It Cm branch Oo Fl C | -no-colour Oc Oo Fl -after Ar date | \
Fl -before Ar date Oc Oo Fl h | -help Oc Oo Fl -open | Fl -closed Oc \
Oo Fl p | -no-private Oc Oo Fl R | -repo Ar path Oc Oo Fl r | -reverse Oc \
Oo Fl s | -sort Ar order Oc Op Ar glob
.Dl Pq aliases: Cm tag , Cm br
Display navigable list of repository branches. If

.Ar glob
is specified, only display branches matching the pattern provided.  Pattern
matching is case-insensitive unless
.Ar glob
contains at least one uppercase character, in which case the search will be
case-sensitive.  Unless the

.Sy --repo
option is used, this command must be executed from within the repository
hierarchy; that is,
.Nm
assumes a local checkout is open in or above the current working directory.
.Pp
Branches are lexicographically ordered by default, and are prefixed with an
identifier corresponding to the branch state (i.e., open/closed). The
current and private branches are additionally annotated with a postfixed
identifier:
.Bl -column -offset Ds ABCDEFGHIJ description
.It +dev-foo Ta open
.It -rm-bar Ta closed
.It +trunk@ Ta current
.It +wip-baz* Ta private
.El
.Pp
All branches, irrespective of state or privacy, are displayed by default, but
can be filtered based on several characteristics.
.Pp
Options for
.Cm fnc branch
are as follows:
.Bl -tag -width Ds
.It Fl a , -after Ar date
Display only those branches with activity after the specified
.Ar date ,
which is expected to be either an ISO8601
.Po e.g.,
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
Display only opened branches.
.It Fl p , -no-private
Do not show private branches, which are included in the list of displayed
branches by default.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository at the specified
.Ar path
for the current
.Nm Cm branch
invocation.
.It Fl r , -reverse
Reverse the order in which branches are displayed.
.It Fl s , -sort Ar order
Sort branches by
.Ar order .
Valid
.Ar order
values are as follows:
.Bl -column -offset 2s YXZ description
.Sy mru Ta most recently used
.Sy state Ta open/closed state
.El
.Pp
Branches are sorted in lexicographical order by default.
.El
.Pp
Key bindings for
.Nm Cm branch
are as follows:
.Bl -tag -width Ds
.It Ic Arrow-down, j
Move selection cursor down one branch.
.It Ic Arrow-up, k
Move selection cursor up one branch.
.It Ic C-f, Page-down
Scroll branch view one page downwards in the buffer.
.It Ic C-b, Page-up
Scroll branch view one page upwards in the buffer.
.It Ic C-d
Scroll branch view half a page downwards in the buffer.
.It Ic C-u
Scroll branch view half a page upwards in the buffer.
.It Ic Home, gg
Move selection cursor to the first branch in the list.
.It Ic End, G
Move selection cursor to the last branch in the list.
.It Ic Enter, Space
Display the
.Cm timeline
of the currently selected branch.
.It Ic c
Toggle coloured output.
On supported terminals,
.Nm
will default to displaying the branch list in colour.
.It Ic d
Toggle display of the date on which the branch last received changes.
.It Ic i
Toggle display of the SHA{1,3} hash that identifies branch, which is the hash
of the commit on the tip of said branch.
.It Ic s
Toggle sort order of currently displayed branches.
If branches are ordered lexicographically,
.Nm
will sort branches in most recently used order, otherwise branches will be
sorted by their open/closed state.
.It Ic t
Open the
.Cm tree
view of the currently selected branch.
.It Ic R, C-l
Reload the view with all repository branches, irrespective of which options
were used in this
.Nm Cm branch
invocation.
.It Ic /
Prompt to enter a search term to begin searching the list for branches matching
the entered pattern.
The search term is an extended regular expression, as documented in
.Xr re_format 7 .
.It Ic n
Find the next branch that matches the current search pattern.
.It Ic N
Find the previous branch that matches the current search pattern.
.El
.El
.Sh ENVIRONMENT
Depending on the available screen estate determined by the LINES and COLUMNS
environment variables,
.Nm
will display child views in either a horizontal or vertical split.
By default, if COLUMNS is \(>= 120, a child view will open in a vertical split
at least 80 columns wide.
Otherwise, the child view will open in a horizontal split that is approximately
60% of the terminal height.
This behaviour can be customised by configuring the following options as either
exported environment variables or with
.Nm Cm config
as documented above.
.Bl -tag -width FNC_VIEW_SPLIT_HEIGHT
.It Ev FNC_VIEW_SPLIT_MODE
Open child views in a horizontal or vertical split.
Value can be one of
.Sy auto ,
.Sy horizontal ,
or
.Sy vertical .
Default:
.Qq auto .
.It Ev FNC_VIEW_SPLIT_HEIGHT
Height of the child view when opening in a horizontal split.
Valid numeric values are 1 \(<=
.Sy n
< LINES.
Percentage values denoted with a postfixed
.Sq %
.Po
e.g.,
.Sy 55%
.Pc
are also valid.
Default:
.Qq 60% .
.It Ev FNC_VIEW_SPLIT_WIDTH
Minimum width of the child view when opening in a vertical split.
Currently a no-op.
Default:
.Qq 80 .
.El
.Pp
Similarly,
.Cm diff
options can be persistently applied to all diff views







|


|


















|


|

|

|

|

|

|

|

|

|



|
|
<


|

|


|
|
|



|



|


|

|

|
|

|

|







|
|
<
|
|
|
|




|
<







|
|

<
|









|
|







1129
1130
1131
1132
1133
1134
1135
1136
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
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182

1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221

1222
1223
1224
1225
1226
1227
1228
1229
1230

1231
1232
1233
1234
1235
1236
1237
1238
1239
1240

1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
Display only opened branches.
.It Fl p , -no-private
Do not show private branches, which are included in the list of displayed
branches by default.
.It Fl R , -repo Ar path
Use the
.Xr fossil 1
repository database at the specified
.Ar path
for the current
.Cm fnc branch
invocation.
.It Fl r , -reverse
Reverse the order in which branches are displayed.
.It Fl s , -sort Ar order
Sort branches by
.Ar order .
Valid
.Ar order
values are as follows:
.Bl -column -offset 2s YXZ description
.Sy mru Ta most recently used
.Sy state Ta open/closed state
.El
.Pp
Branches are sorted in lexicographical order by default.
.El
.Pp
Key bindings for
.Cm fnc branch
are as follows:
.Bl -tag -width Ds
.It Cm Arrow-down, j
Move selection cursor down one branch.
.It Cm Arrow-up, k
Move selection cursor up one branch.
.It Cm C-f, Page-down
Scroll branch view one page downwards in the buffer.
.It Cm C-b, Page-up
Scroll branch view one page upwards in the buffer.
.It Cm C-d
Scroll branch view half a page downwards in the buffer.
.It Cm C-u
Scroll branch view half a page upwards in the buffer.
.It Cm Home, gg
Move selection cursor to the first branch in the list.
.It Cm End, G
Move selection cursor to the last branch in the list.
.It Cm Enter, Space
Display the
.Cm timeline
of the currently selected branch.
.It Cm c
Toggle coloured output.  On supported terminals,

.Nm
will default to displaying the branch list in colour.
.It Cm d
Toggle display of the date on which the branch last received changes.
.It Cm i
Toggle display of the SHA{1,3} hash that identifies branch, which is the hash
of the commit on the tip of said branch.
.It Cm s
Toggle sort order of currently displayed branches.  If branches are ordered
lexicographically,
.Nm
will sort branches in most recently used order, otherwise branches will be
sorted by their open/closed state.
.It Cm t
Open the
.Cm tree
view of the currently selected branch.
.It Cm R, C-l
Reload the view with all repository branches, irrespective of which options
were used in this
.Cm fnc branch
invocation.
.It Cm /
Prompt to enter a search term to begin searching the list for branches matching
the entered pattern.  The search term is an extended regular expression, as
documented in
.Xr re_format 7 .
.It Cm n
Find the next branch that matches the current search pattern.
.It Cm N
Find the previous branch that matches the current search pattern.
.El
.El
.Sh ENVIRONMENT
Depending on the available screen estate determined by the LINES and COLUMNS
environment variables,
.Nm
will display child views in either a horizontal or vertical split.  By default,
if COLUMNS is \(>= 120, a child view will open in a vertical split at least 80

columns wide.  Otherwise, the child view will open in a horizontal split that
is approximately 60% of the terminal height.  This behaviour can be customised
by configuring the following options as either exported environment variables
or with
.Nm Cm config
as documented above.
.Bl -tag -width FNC_VIEW_SPLIT_HEIGHT
.It Ev FNC_VIEW_SPLIT_MODE
Open child views in a horizontal or vertical split. Value can be one of

.Sy auto ,
.Sy horizontal ,
or
.Sy vertical .
Default:
.Qq auto .
.It Ev FNC_VIEW_SPLIT_HEIGHT
Height of the child view when opening in a horizontal split. Valid numeric
values are 1 \(<=
.Sy n

< LINES.  Percentage values denoted with a postfixed
.Sq %
.Po
e.g.,
.Sy 55%
.Pc
are also valid.
Default:
.Qq 60% .
.It Ev FNC_VIEW_SPLIT_WIDTH
Minimum width of the child view when opening in a vertical split.  Currently a
no-op.
Default:
.Qq 80 .
.El
.Pp
Similarly,
.Cm diff
options can be persistently applied to all diff views
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
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
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499

1500
1501
by configuring the following options:
.Bl -tag -width FNC_DIFF_CONTEXT
.It Ev FNC_DIFF_FLAGS
String containing any or all of the available short form
.Cm diff
boolean flag options documented above
.Po
i.e., bCilPqsWw
.Pc .
If mutually exclusive options
.Qq l
and
.Qq s
are both specified, whichever is last will take precedence; for example,
.Qq lqs
will display side-by-side formatted diffs.
Default: NULL.
.It Ev FNC_DIFF_CONTEXT
Numeric value as per
the above documented
.Fl x|--context
option
.Po
i.e.,
0 \*(Le n \*(Le 64
.Pc
specifying the number of context lines.
Illegal values are a no-op.
Default: 5.
.El
.Pp
Any options passed to
.Nm Cm diff
will override the above settings.
.Pp
.Nm
displays coloured output by default in supported terminals.
Each colour object identified below can be defined by either exporting
environment variables
.Po e.g.,
.Cm export Ev FNC_COLOUR_COMMIT=red
.Pc ,
or with
.Nm Cm config
as documented above.
At startup,
.Nm
will search for user-defined colours in the following order:
.Bl -column " environment variables " description -offset Ds
.It 1. repository settings Ta Pa repo.fossil
.It 2. environment variables Ta Sy shell
.El
.Pp
If none are found, the default colour scheme will be displayed.
This enables setting per-project colours to visually distinguish the current
repository being viewed, and globally changing the colour scheme for all
repositories with no local settings configured.
Except where documented below, colours supported in

.Nm
are:
.Bl -column "black" "yellow" "magenta" "default" -offset indent-two
.It Qo black Qc Ta Qo green Qc Ta Qo blue Qc Ta Qo cyan Qc
.It Qo red Qc Ta Qo yellow Qc Ta Qo magenta Qc Ta Qo default Qc
.El
.Pp
Where
.Qq default
is the current foreground (i.e., text) colour in the terminal.
User-definable colour objects displayed in various
.Nm
views are as follows:
.Bl -tag -width FNC_COLOUR_BRANCH_PRIVATE
.It Ev FNC_COLOUR_COMMIT
The commit hash ID field displayed in the timeline, diff, tree, and blame views.
If not defined, the default value is
.Qq green .
.It Ev FNC_COLOUR_USER
The username field displayed in the timeline and diff views.
If not defined, the default value is
.Qq cyan .
.It Ev FNC_COLOUR_DATE
The date field displayed in the timeline and diff views.
If not defined, the default value is
.Qq yellow .
.It Ev FNC_COLOUR_DIFF_MINUS
Removed lines displayed in the diff view.
If not defined, the default value is
.Qq magenta .
.It Ev FNC_COLOUR_DIFF_PLUS
Added lines displayed in the diff view.
If not defined, the default value is
.Qq cyan .
.It Ev FNC_COLOUR_DIFF_HUNK
Hunk header lines
.Po e.g.,
.Li @@ -732,34 +747,40 @@
.Pc
displayed in the diff view.
If not defined, the default value is
.Qq yellow .
.It Ev FNC_COLOUR_DIFF_META
Metadata displayed in the diff view.
If not defined, the default value is
.Qq green .
.It Ev FNC_COLOUR_DIFF_TAGS
The tag field displayed in the diff view.
If not defined, the default value is
.Qq magenta .
.It Ev FNC_COLOUR_DIFF_SBS_EDIT
Changed
.Pq i.e., not added or removed
lines in the diff view when displaying side-by-side diffs.
If not defined, the default value is
.Qq red .
.It Ev FNC_COLOUR_TREE_DIR
Directory entries displayed in the tree view.
If not defined, the default value is
.Qq cyan .
.It Ev FNC_COLOUR_TREE_EXEC
Executable file entries displayed in the tree view.
If not defined, the default value is
.Qq green .
.It Ev FNC_COLOUR_TREE_LINK
Symbolic link entries displayed in the tree view.
If not defined, the default value is
.Qq magenta .
.It Ev FNC_COLOUR_BRANCH_OPEN
Open branches displayed in the branch view.
If not defined, the default value is
.Qq cyan .
.It Ev FNC_COLOUR_BRANCH_CLOSED
Closed branches displayed in the branch view.
If not defined, the default value is
.Qq magenta .
.It Ev FNC_COLOUR_BRANCH_CURRENT
The branch corresponding to the current checkout displayed in the branch view.
If not defined, the default value is
.Qq green .
.It Ev FNC_COLOUR_BRANCH_PRIVATE
Private branches displayed in the branch view.
If not defined, the default value is
.Qq yellow .
.It Ev FNC_COLOUR_HL_LINE
Selected line highlight in the diff view.
Value can be one of
.Sy auto
or
.Sy mono .
The former will invert the foreground colour of the selected line, while the
latter will use a monochromatic highlight.
If not defined, the default value is
.Qq auto .
.It Ev FNC_COLOUR_HL_SEARCH
Search term highlight in blame and diff view.
If not defined, the default value is
.Qq yellow .
.El
.Pp
To clear environment variables, issue
.Cm unset Ar ENVIRONMENT_VARIABLE
in the shell.
.Pp
.Nm
displays best with UTF-8, and will detect whether UTF-8 is enabled to determine
which characters to draw in certain views.
If UTF-8 is supported by your terminal but is currently disabled, it can be
enabled with
.Qq export LC_ALL=en_US.UTF-8 ;
If not available,
.Nm
will revert to ASCII.
Relatedly, some fonts may render certain characters poorly in the help screen;
.Li Monospace Regular ,
.Li JetBrains Mono ,
and
.Li Menlo
are known to render all characters well.
.Sh EXIT STATUS
.Ex -std fnc
.Sh SEE ALSO
.Xr fossil 1 ,
.Xr sqlite3 1 ,
.Xr re_format 7

.Sh AUTHORS
.An Mark Jamsek Aq Mt mark@jamsek.com







|







|
<









|
<








|
|
<

|



|
<




|


|
|
|
<
|
>



















|
|


|
|


|
<


|
<

|
|



|
<


|
<


|
<




|
|


|
|


|
|


|
|


|
|


|
|






|
|


|
<




|
<


|
|









|
|
<



|
|









<

>
|

1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284

1285
1286
1287
1288
1289
1290
1291
1292
1293
1294

1295
1296
1297
1298
1299
1300
1301
1302
1303
1304

1305
1306
1307
1308
1309
1310

1311
1312
1313
1314
1315
1316
1317
1318
1319
1320

1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350

1351
1352
1353

1354
1355
1356
1357
1358
1359
1360

1361
1362
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
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403

1404
1405
1406
1407
1408

1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423

1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437

1438
1439
1440
1441
by configuring the following options:
.Bl -tag -width FNC_DIFF_CONTEXT
.It Ev FNC_DIFF_FLAGS
String containing any or all of the available short form
.Cm diff
boolean flag options documented above
.Po
i.e., CilPqsWw
.Pc .
If mutually exclusive options
.Qq l
and
.Qq s
are both specified, whichever is last will take precedence; for example,
.Qq lqs
will display side-by-side formatted diffs.  Default: NULL.

.It Ev FNC_DIFF_CONTEXT
Numeric value as per
the above documented
.Fl x|--context
option
.Po
i.e.,
0 \*(Le n \*(Le 64
.Pc
specifying the number of context lines.  Illegal values are a no-op.

Default: 5.
.El
.Pp
Any options passed to
.Nm Cm diff
will override the above settings.
.Pp
.Nm
displays coloured output by default in supported terminals.  Each colour object
identified below can be defined by either exporting environment variables

.Po e.g.,
.Cm export FNC_COLOUR_COMMIT=red
.Pc ,
or with
.Nm Cm config
as documented above.  At startup,

.Nm
will search for user-defined colours in the following order:
.Bl -column " environment variables " description -offset Ds
.It 1. repository settings Ta Pa repo.fossil
.It 3. environment variables Ta Sy shell
.El
.Pp
If none are found, the default colour scheme will be displayed.  This enables
setting per-project colours to visually distinguish the current repository
being viewed, and globally changing the colour scheme for all repositories with

no local settings configured.  Except where documented below, colours supported
in
.Nm
are:
.Bl -column "black" "yellow" "magenta" "default" -offset indent-two
.It Qo black Qc Ta Qo green Qc Ta Qo blue Qc Ta Qo cyan Qc
.It Qo red Qc Ta Qo yellow Qc Ta Qo magenta Qc Ta Qo default Qc
.El
.Pp
Where
.Qq default
is the current foreground (i.e., text) colour in the terminal.
User-definable colour objects displayed in various
.Nm
views are as follows:
.Bl -tag -width FNC_COLOUR_BRANCH_PRIVATE
.It Ev FNC_COLOUR_COMMIT
The commit hash ID field displayed in the timeline, diff, tree, and blame views.
If not defined, the default value is
.Qq green .
.It Ev FNC_COLOUR_USER
The username field displayed in the timeline and diff views.  If not defined,
the default value is
.Qq cyan .
.It Ev FNC_COLOUR_DATE
The date field displayed in the timeline and diff views.  If not defined, the
default value is
.Qq yellow .
.It Ev FNC_COLOUR_DIFF_MINUS
Removed lines displayed in the diff view.  If not defined, the default value is

.Qq magenta .
.It Ev FNC_COLOUR_DIFF_PLUS
Added lines displayed in the diff view.  If not defined, the default value is

.Qq cyan .
.It Ev FNC_COLOUR_DIFF_CHUNK
Chunk header lines
.Po e.g.,
.Li @@ -732,34 +747,40 @@
.Pc
displayed in the diff view.  If not defined, the default value is

.Qq yellow .
.It Ev FNC_COLOUR_DIFF_META
Metadata displayed in the diff view.  If not defined, the default value is

.Qq green .
.It Ev FNC_COLOUR_DIFF_TAGS
The tag field displayed in the diff view.  If not defined, the default value is

.Qq magenta .
.It Ev FNC_COLOUR_DIFF_SBS_EDIT
Changed
.Pq i.e., not added or removed
lines in the diff view when displaying side-by-side diffs.  If not defined, the
default value is
.Qq red .
.It Ev FNC_COLOUR_TREE_DIR
Directory entries displayed in the tree view.  If not defined, the default value
is
.Qq cyan .
.It Ev FNC_COLOUR_TREE_EXEC
Executable file entries displayed in the tree view.  If not defined, the default
value is
.Qq green .
.It Ev FNC_COLOUR_TREE_LINK
Symbolic link entries displayed in the tree view.  If not defined, the default
value is
.Qq magenta .
.It Ev FNC_COLOUR_BRANCH_OPEN
Open branches displayed in the branch view.  If not defined, the default value
is
.Qq cyan .
.It Ev FNC_COLOUR_BRANCH_CLOSED
Closed branches displayed in the branch view.  If not defined, the default value
is
.Qq magenta .
.It Ev FNC_COLOUR_BRANCH_CURRENT
The branch corresponding to the current checkout displayed in the branch view.
If not defined, the default value is
.Qq green .
.It Ev FNC_COLOUR_BRANCH_PRIVATE
Private branches displayed in the branch view.  If not defined, the default
value is
.Qq yellow .
.It Ev FNC_COLOUR_HL_LINE
Selected line highlight in the diff view.  Value can be one of

.Sy auto
or
.Sy mono .
The former will invert the foreground colour of the selected line, while the
latter will use a monochromatic highlight.  If not defined, the default value is

.Qq auto .
.It Ev FNC_COLOUR_HL_SEARCH
Search term highlight in blame and diff view.  If not defined, the default
value is
.Qq yellow .
.El
.Pp
To clear environment variables, issue
.Cm unset Ar ENVIRONMENT_VARIABLE
in the shell.
.Pp
.Nm
displays best with UTF-8, and will detect whether UTF-8 is enabled to determine
which characters to draw in certain views.  If UTF-8 is supported by your
terminal but is currently disabled, it can be enabled with

.Qq export LC_ALL=en_US.UTF-8 ;
If not available,
.Nm
will revert to ASCII.  Relatedly, some fonts may render certain characters
poorly in the help screen;
.Li Monospace Regular ,
.Li JetBrains Mono ,
and
.Li Menlo
are known to render all characters well.
.Sh EXIT STATUS
.Ex -std fnc
.Sh SEE ALSO
.Xr fossil 1 ,

.Xr re_format 7
.Xr sqlite3 1
.Sh AUTHOR
.An Mark Jamsek Aq Mt mark@jamsek.com

Changes to src/fnc.c.

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <wchar.h>
#include <langinfo.h>

#include "libfossil.h"
#include "diff.h"

#define FNC_VERSION	VERSION  /* cf. Makefile */
#define FNC_HASH	HASH
#define FNC_DATE	DATE

/* User options: include/settings.h:29 */
#define STR_INFO(_) _(fnc_opt_name, FNC, USER_OPTIONS)
#define GEN_STRINGS(name, pfx, info) GEN_STR(name, pfx, info)
STR_INFO(GEN_STRINGS)

/* Utility macros. */







<
<







78
79
80
81
82
83
84


85
86
87
88
89
90
91
#include <wchar.h>
#include <langinfo.h>

#include "libfossil.h"
#include "diff.h"

#define FNC_VERSION	VERSION  /* cf. Makefile */



/* User options: include/settings.h:29 */
#define STR_INFO(_) _(fnc_opt_name, FNC, USER_OPTIONS)
#define GEN_STRINGS(name, pfx, info) GEN_STR(name, pfx, info)
STR_INFO(GEN_STRINGS)

/* Utility macros. */
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#define FLAG_SET(_f, _b)	((_f) |= (_b))
#define FLAG_CHK(_f, _b)	((_f) & (_b))
#define FLAG_TOG(_f, _b)	((_f) ^= (_b))
#define FLAG_CLR(_f, _b)	((_f) &= ~(_b))

/* Application macros. */
#define PRINT_VERSION	STRINGIFY(FNC_VERSION)
#define PRINT_HASH	STRINGIFY(FNC_HASH)
#define PRINT_DATE	STRINGIFY(FNC_DATE)
#define DEF_DIFF_CTX	5		/* Default diff context lines. */
#define MAX_DIFF_CTX	64		/* Max diff context lines. */
#define HSPLIT_SCALE	0.4		/* Default horizontal split scale. */
#define SPIN_INTERVAL	200		/* Status line progress indicator. */
#define LINENO_WIDTH	6		/* View lineno max column width. */
#define MAX_PCT_LEN	7		/* Line position upto max len 99.99% */
#define SPINNER		"\\|/-\0"







<
<







105
106
107
108
109
110
111


112
113
114
115
116
117
118
#define FLAG_SET(_f, _b)	((_f) |= (_b))
#define FLAG_CHK(_f, _b)	((_f) & (_b))
#define FLAG_TOG(_f, _b)	((_f) ^= (_b))
#define FLAG_CLR(_f, _b)	((_f) &= ~(_b))

/* Application macros. */
#define PRINT_VERSION	STRINGIFY(FNC_VERSION)


#define DEF_DIFF_CTX	5		/* Default diff context lines. */
#define MAX_DIFF_CTX	64		/* Max diff context lines. */
#define HSPLIT_SCALE	0.4		/* Default horizontal split scale. */
#define SPIN_INTERVAL	200		/* Status line progress indicator. */
#define LINENO_WIDTH	6		/* View lineno max column width. */
#define MAX_PCT_LEN	7		/* Line position upto max len 99.99% */
#define SPINNER		"\\|/-\0"
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#  define strtonum(s, min, max, o)	strtol(s, (char **)o, 10)
#  define inrange(n, min, max)		(((n) >= (min)) && ((n) <= (max)))
# endif /* HAVE_STRTONUM */
#else
#  define inrange(n, min, max) true
#endif /* OpenBSD */

/*
 * Bold intersects the colour space, which makes colours that use the intensity
 * bit (e.g., yellow) unavailable in VGA text mode; observed in xterm(1).
 */
#ifdef __OpenBSD__
# define FNC_HIGHLIGHT	A_REVERSE
#else
# define FNC_HIGHLIGHT	A_BOLD | A_REVERSE
#endif

#define PRINTFV(fmt, args) __attribute__((format (printf, fmt, args)))
#ifndef __dead
#define __dead	__attribute__((noreturn))
#endif

#ifndef __predict_true
# ifdef __has_builtin
#  if __has_builtin(__builtin_expect)
#   define __predict_true(_e)	__builtin_expect(((_e) != 0), 1)
#   define __predict_false(_e)	__builtin_expect(((_e) != 0), 0)
#  endif  /* __builtin_expect */
# endif  /* __has_builtin */
#endif  /* __predict_true */
#ifndef __predict_true
# define __predict_true(_e)	((_e) != 0)
# define __predict_false(_e)	((_e) != 0)
#endif  /* __predict_true */

#ifndef TAILQ_FOREACH_SAFE
/* Rewrite of OpenBSD 6.9 sys/queue.h for Linux builds. */
#define TAILQ_FOREACH_SAFE(var, head, field, tmp)			\
	for ((var) = ((head)->tqh_first);				\
		(var) != (NULL) && ((tmp) = TAILQ_NEXT(var, field), 1);	\
		(var) = (tmp))
#endif







<
<
<
<
<
<
<
<
<
<





<
<
<
<
<
<
<
<
<
<
<
<
<







140
141
142
143
144
145
146










147
148
149
150
151













152
153
154
155
156
157
158
#  define strtonum(s, min, max, o)	strtol(s, (char **)o, 10)
#  define inrange(n, min, max)		(((n) >= (min)) && ((n) <= (max)))
# endif /* HAVE_STRTONUM */
#else
#  define inrange(n, min, max) true
#endif /* OpenBSD */











#define PRINTFV(fmt, args) __attribute__((format (printf, fmt, args)))
#ifndef __dead
#define __dead	__attribute__((noreturn))
#endif














#ifndef TAILQ_FOREACH_SAFE
/* Rewrite of OpenBSD 6.9 sys/queue.h for Linux builds. */
#define TAILQ_FOREACH_SAFE(var, head, field, tmp)			\
	for ((var) = ((head)->tqh_first);				\
		(var) != (NULL) && ((tmp) = TAILQ_NEXT(var, field), 1);	\
		(var) = (tmp))
#endif
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
	bool		 ws;		/* Ignore whitespace-only changes. */
	bool		 eol;		/* Ignore eol whitespace-only changes */
	bool		 nocolour;	/* Disable colour in diff output. */
	bool		 verbose;	/* Disable verbose diff output. */
	bool		 invert;	/* Toggle inverted diff output. */
	bool		 showln;	/* Display line numbers in diff. */
	bool		 proto;		/* Display function prototype. */
	bool		 brief;		/* Only display file index lines. */

	/* Branch options. */
	const char	*before;	/* Last branch change before date. */
	const char	*after;		/* Last branch change after date. */
	const char	*sort;		/* Lexicographical, MRU, open/closed. */
	bool		 closed;	/* Show only closed branches. */
	bool		 open;		/* Show only open branches */
	bool		 noprivate;	/* Don't show private branches. */

	/* Config options. */
	bool		 lsconf;	/* List all defined settings. */
	bool		 unset;		/* Unset the specified setting. */

	/* Command line flags and help. */
	fcli_help_info	  fnc_help;			/* Global help. */
	fcli_cliflag	  cliflags_global[3];		/* Global options. */
	fcli_command	  cmd_args[8];			/* App commands. */
	fcli_cliflag	  cliflags_timeline[13];	/* Timeline options. */
	fcli_cliflag	  cliflags_diff[13];		/* Diff options. */
	fcli_cliflag	  cliflags_tree[5];		/* Tree options. */
	fcli_cliflag	  cliflags_blame[8];		/* Blame options. */
	fcli_cliflag	  cliflags_branch[11];		/* Branch options. */
	fcli_cliflag	  cliflags_config[5];		/* Config options. */
	fcli_cliflag	  cliflags_stash[5];		/* Stash options. */
} fnc_init = {
	NULL,		/* cmdarg copy of argv[1] to aid usage/error report. */







<


















|







241
242
243
244
245
246
247

248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
	bool		 ws;		/* Ignore whitespace-only changes. */
	bool		 eol;		/* Ignore eol whitespace-only changes */
	bool		 nocolour;	/* Disable colour in diff output. */
	bool		 verbose;	/* Disable verbose diff output. */
	bool		 invert;	/* Toggle inverted diff output. */
	bool		 showln;	/* Display line numbers in diff. */
	bool		 proto;		/* Display function prototype. */


	/* Branch options. */
	const char	*before;	/* Last branch change before date. */
	const char	*after;		/* Last branch change after date. */
	const char	*sort;		/* Lexicographical, MRU, open/closed. */
	bool		 closed;	/* Show only closed branches. */
	bool		 open;		/* Show only open branches */
	bool		 noprivate;	/* Don't show private branches. */

	/* Config options. */
	bool		 lsconf;	/* List all defined settings. */
	bool		 unset;		/* Unset the specified setting. */

	/* Command line flags and help. */
	fcli_help_info	  fnc_help;			/* Global help. */
	fcli_cliflag	  cliflags_global[3];		/* Global options. */
	fcli_command	  cmd_args[8];			/* App commands. */
	fcli_cliflag	  cliflags_timeline[13];	/* Timeline options. */
	fcli_cliflag	  cliflags_diff[12];		/* Diff options. */
	fcli_cliflag	  cliflags_tree[5];		/* Tree options. */
	fcli_cliflag	  cliflags_blame[8];		/* Blame options. */
	fcli_cliflag	  cliflags_branch[11];		/* Branch options. */
	fcli_cliflag	  cliflags_config[5];		/* Config options. */
	fcli_cliflag	  cliflags_stash[5];		/* Stash options. */
} fnc_init = {
	NULL,		/* cmdarg copy of argv[1] to aid usage/error report. */
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
	false,		/* ws defaults to acknowledge all whitespace. */
	false,		/* eol defaults to acknowledge eol whitespace. */
	false,		/* nocolour defaults to off (i.e., use diff colours). */
	true,		/* verbose defaults to on. */
	false,		/* invert diff defaults to off. */
	false,		/* showln in diff defaults to off. */
	true,		/* proto in diff hunk header defaults to on. */
	false,		/* brief diff defaults to off. */
	NULL,		/* before defaults to any time. */
	NULL,		/* after defaults to any time. */
	NULL,		/* sort by MRU or open/closed (dflt: lexicographical) */
	false,		/* closed only branches is off (defaults to all). */
	false,		/* open only branches is off by (defaults to all). */
	false,		/* noprivate is off (default to show private branch). */
	false,		/* do not list all defined settings by default. */







<







291
292
293
294
295
296
297

298
299
300
301
302
303
304
	false,		/* ws defaults to acknowledge all whitespace. */
	false,		/* eol defaults to acknowledge eol whitespace. */
	false,		/* nocolour defaults to off (i.e., use diff colours). */
	true,		/* verbose defaults to on. */
	false,		/* invert diff defaults to off. */
	false,		/* showln in diff defaults to off. */
	true,		/* proto in diff hunk header defaults to on. */

	NULL,		/* before defaults to any time. */
	NULL,		/* after defaults to any time. */
	NULL,		/* sort by MRU or open/closed (dflt: lexicographical) */
	false,		/* closed only branches is off (defaults to all). */
	false,		/* open only branches is off by (defaults to all). */
	false,		/* noprivate is off (default to show private branch). */
	false,		/* do not list all defined settings by default. */
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
	    "Display program help and usage then exit."),
	    FCLI_FLAG_BOOL("v", "version", &fnc_init.vflag,
	    "Display program version number and exit."),
	    fcli_cliflag_empty_m
	},

	{ /* cmd_args available app commands. */
	    {"timeline", "tl\0log\0",
	    "Show chronologically descending commit history of the repository.",
	    cmd_timeline, usage_timeline, fnc_init.cliflags_timeline},
	    {"diff", "di\0",
	    "Show changes to versioned files introduced with a given commit.",
	    cmd_diff, usage_diff, fnc_init.cliflags_diff},
	    {"tree", "tr\0dir\0",
	    "Show repository tree corresponding to a given commit",
	    cmd_tree, usage_tree, fnc_init.cliflags_tree},
	    {"blame", "bl\0praise\0",
	    "Show commit attribution history for each line of a file.",
	    cmd_blame, usage_blame, fnc_init.cliflags_blame},
	    {"branch", "br\0tag\0",
	    "Show navigable list of repository branches.",
	    cmd_branch, usage_branch, fnc_init.cliflags_branch},
	    {"config", "conf\0set\0",
	    "Configure or view currently available settings.",
	    cmd_config, usage_config, fnc_init.cliflags_config},
	    {"stash", "save\0sta\0",
	    "Interactively select hunks to stash from the diff of local "
	    "changes on\n  disk.",
	    cmd_stash, usage_stash, fnc_init.cliflags_stash},
	    {NULL, NULL, NULL, NULL, NULL}	/* Sentinel. */
	},

	{ /* cliflags_timeline timeline command related options. */







|








|





|


|







314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
	    "Display program help and usage then exit."),
	    FCLI_FLAG_BOOL("v", "version", &fnc_init.vflag,
	    "Display program version number and exit."),
	    fcli_cliflag_empty_m
	},

	{ /* cmd_args available app commands. */
	    {"timeline", "tl\0time\0ti\0log\0",
	    "Show chronologically descending commit history of the repository.",
	    cmd_timeline, usage_timeline, fnc_init.cliflags_timeline},
	    {"diff", "di\0",
	    "Show changes to versioned files introduced with a given commit.",
	    cmd_diff, usage_diff, fnc_init.cliflags_diff},
	    {"tree", "tr\0dir\0",
	    "Show repository tree corresponding to a given commit",
	    cmd_tree, usage_tree, fnc_init.cliflags_tree},
	    {"blame", "bl\0praise\0pr\0annotate\0an\0",
	    "Show commit attribution history for each line of a file.",
	    cmd_blame, usage_blame, fnc_init.cliflags_blame},
	    {"branch", "br\0tag\0",
	    "Show navigable list of repository branches.",
	    cmd_branch, usage_branch, fnc_init.cliflags_branch},
	    {"config", "conf\0cfg\0settings\0set\0",
	    "Configure or view currently available settings.",
	    cmd_config, usage_config, fnc_init.cliflags_config},
	    {"stash", "snapshot\0snap\0save\0sta\0",
	    "Interactively select hunks to stash from the diff of local "
	    "changes on\n  disk.",
	    cmd_stash, usage_stash, fnc_init.cliflags_stash},
	    {NULL, NULL, NULL, NULL, NULL}	/* Sentinel. */
	},

	{ /* cliflags_timeline timeline command related options. */
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
	    "Only display commits authored by <username>."),
	    FCLI_FLAG_BOOL("z", "utc", &fnc_init.utc,
	    "Use UTC (instead of local) time."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_timeline. */

	{ /* cliflags_diff diff command related options. */
	    FCLI_FLAG_BOOL("b", "brief", &fnc_init.brief,
	    "Display file index and hash lines only. Toggle with the 'b' key\n"
	    "    binding in diff view."),
	    FCLI_FLAG_BOOL("C", "no-colour", &fnc_init.nocolour,
	    "Disable coloured diff output, which is enabled by default on\n    "
	    "supported terminals. Colour can also be toggled with the 'c' "
	    "\n    key binding in diff view when this option is not used."),
	    FCLI_FLAG_BOOL("h", "help", NULL,
	    "Display diff command help and usage."),
	    FCLI_FLAG_BOOL("i", "invert", &fnc_init.invert,







<
<
<







389
390
391
392
393
394
395



396
397
398
399
400
401
402
	    "Only display commits authored by <username>."),
	    FCLI_FLAG_BOOL("z", "utc", &fnc_init.utc,
	    "Use UTC (instead of local) time."),
	    fcli_cliflag_empty_m
	}, /* End cliflags_timeline. */

	{ /* cliflags_diff diff command related options. */



	    FCLI_FLAG_BOOL("C", "no-colour", &fnc_init.nocolour,
	    "Disable coloured diff output, which is enabled by default on\n    "
	    "supported terminals. Colour can also be toggled with the 'c' "
	    "\n    key binding in diff view when this option is not used."),
	    FCLI_FLAG_BOOL("h", "help", NULL,
	    "Display diff command help and usage."),
	    FCLI_FLAG_BOOL("i", "invert", &fnc_init.invert,
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
	char			*branch;
	char			*type;
	enum fnc_diff_type	 diff_type;
};

struct fsl_file_artifact {
	fsl_card_F		*fc;
	fsl_ckout_change_e	 change;
};

TAILQ_HEAD(commit_tailhead, commit_entry);
struct commit_entry {
	TAILQ_ENTRY(commit_entry)	 entries;
	struct fnc_commit_artifact	*commit;
	int				 idx;







|







661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
	char			*branch;
	char			*type;
	enum fnc_diff_type	 diff_type;
};

struct fsl_file_artifact {
	fsl_card_F		*fc;
	enum fsl_ckout_change_e	 change;
};

TAILQ_HEAD(commit_tailhead, commit_entry);
struct commit_entry {
	TAILQ_ENTRY(commit_entry)	 entries;
	struct fnc_commit_artifact	*commit;
	int				 idx;
880
881
882
883
884
885
886

887
888

889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
	uint8_t				 context; /* MAX_DIFF_CTX lines = 64 */
	enum fnc_patch_rc		 rc;
	bool				 report;
};

struct stash_cx {
	struct patch_cx	 pcx;

	char		 patch[2][PATH_MAX]; /* stash & ckout patch filepath */
	unsigned char	*stash;	/* fnc_diff_view_state.hundex.lineno bitarray */

#define nbytes(nbits)	(((nbits) + 7) >> 3)
#define BIT_SET(_B, _i)	(_B[(_i / CHAR_BIT)] |=  (1 << (_i % CHAR_BIT)))
#define BIT_CLR(_B, _i)	(_B[(_i / CHAR_BIT)] &= ~(1 << (_i % CHAR_BIT)))
#define BIT_CHK(_B, _i)	(_B[(_i / CHAR_BIT)] &   (1 << (_i % CHAR_BIT)))
};

struct fnc_diff_view_state {
	struct fnc_view			*view;
	struct fnc_view			*parent_view;
	struct fnc_commit_artifact	*selected_entry;
	struct fnc_pathlist_head	*paths;
	struct stash_cx			 scx;
	fsl_buffer			 buf;
	struct fnc_colours		 colours;
	struct index			 index;   /* line indexes for files */
	struct index			 hundex;  /* line indexes for hunks */
	FILE				*f;
	fsl_uuid_str			 id1;
	fsl_uuid_str			 id2;
	int				 first_line_onscreen;
	int				 last_line_onscreen;
	int				 diff_flags;
	int				 context;







>

|
>

|
|
|










|
<







848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873

874
875
876
877
878
879
880
	uint8_t				 context; /* MAX_DIFF_CTX lines = 64 */
	enum fnc_patch_rc		 rc;
	bool				 report;
};

struct stash_cx {
	struct patch_cx	 pcx;
	struct index	 hunk;	   /* line indexes for each hunk in the diff */
	char		 patch[2][PATH_MAX]; /* stash & ckout patch filepath */
	unsigned char	*stash;	   /* bit array into this.hunk->lineno */
#define NBITS	(sizeof(unsigned char) * 8)
#define nbytes(nbits)	(((nbits) + 7) >> 3)
#define BIT_SET(_B, _i)	(_B[(_i / NBITS)] |=  (1 << (_i % NBITS)))
#define BIT_CLR(_B, _i)	(_B[(_i / NBITS)] &= ~(1 << (_i % NBITS)))
#define BIT_CHK(_B, _i)	(_B[(_i / NBITS)] &   (1 << (_i % NBITS)))
};

struct fnc_diff_view_state {
	struct fnc_view			*view;
	struct fnc_view			*parent_view;
	struct fnc_commit_artifact	*selected_entry;
	struct fnc_pathlist_head	*paths;
	struct stash_cx			 scx;
	fsl_buffer			 buf;
	struct fnc_colours		 colours;
	struct index			 index;

	FILE				*f;
	fsl_uuid_str			 id1;
	fsl_uuid_str			 id2;
	int				 first_line_onscreen;
	int				 last_line_onscreen;
	int				 diff_flags;
	int				 context;
955
956
957
958
959
960
961
962

963
964
965
966
967
968
969
	unsigned int	lineno;
	bool		annotated;
};

struct fnc_blame_cb_cx {
	struct fnc_view		*view;
	struct fnc_blame_line	*lines;
	fsl_uuid_cstr		 commit_id;

	int			 nlines;
	uint32_t		 maxlen;
	bool			*quit;
};

typedef int (*fnc_cancel_cb)(void *);








|
>







924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
	unsigned int	lineno;
	bool		annotated;
};

struct fnc_blame_cb_cx {
	struct fnc_view		*view;
	struct fnc_blame_line	*lines;
	fsl_uuid_str		 commit_id;
	fsl_uuid_str		 root_commit;
	int			 nlines;
	uint32_t		 maxlen;
	bool			*quit;
};

typedef int (*fnc_cancel_cb)(void *);

997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011

struct fnc_blame_view_state {
	struct fnc_blame		 blame;
	struct fnc_commit_id_queue	 blamed_commits;
	struct fnc_commit_qid		*blamed_commit;
	struct fnc_commit_artifact	*selected_entry;
	struct fnc_colours		 colours;
	fsl_uuid_cstr			 commit_id;
	const char			*lineno;
	char				*path;
	int				 first_line_onscreen;
	int				 last_line_onscreen;
	int				 selected_line;
	int				 matched_line;
	int				 spin_idx;







|







967
968
969
970
971
972
973
974
975
976
977
978
979
980
981

struct fnc_blame_view_state {
	struct fnc_blame		 blame;
	struct fnc_commit_id_queue	 blamed_commits;
	struct fnc_commit_qid		*blamed_commit;
	struct fnc_commit_artifact	*selected_entry;
	struct fnc_colours		 colours;
	fsl_uuid_str			 commit_id;
	const char			*lineno;
	char				*path;
	int				 first_line_onscreen;
	int				 last_line_onscreen;
	int				 selected_line;
	int				 matched_line;
	int				 spin_idx;
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
	void	(*grep_init)(struct fnc_view *);
	int	(*grep)(struct fnc_view *);
};

static volatile sig_atomic_t rec_sigwinch;
static volatile sig_atomic_t rec_sigpipe;
static volatile sig_atomic_t rec_sigcont;
static volatile sig_atomic_t rec_sigint;
static volatile sig_atomic_t rec_sigterm;

static void		 fnc_show_version(void);
static int		 init_curses(void);
static int		 fnc_set_signals(void);
static struct fnc_view	*view_open(int, int, int, int, enum fnc_view_id);
static int		 open_timeline_view(struct fnc_view *, fsl_id_t,
			    const char *, const char *);







<
<







1083
1084
1085
1086
1087
1088
1089


1090
1091
1092
1093
1094
1095
1096
	void	(*grep_init)(struct fnc_view *);
	int	(*grep)(struct fnc_view *);
};

static volatile sig_atomic_t rec_sigwinch;
static volatile sig_atomic_t rec_sigpipe;
static volatile sig_atomic_t rec_sigcont;



static void		 fnc_show_version(void);
static int		 init_curses(void);
static int		 fnc_set_signals(void);
static struct fnc_view	*view_open(int, int, int, int, enum fnc_view_id);
static int		 open_timeline_view(struct fnc_view *, fsl_id_t,
			    const char *, const char *);
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
static int		 make_stash_diff(struct fnc_diff_view_state *, char *);
static int		 write_commit_meta(struct fnc_diff_view_state *);
/* static int		 countlines(const char *); */
static int		 wrapline(char *, fsl_size_t,
			    struct fnc_diff_view_state *, off_t *);
static int		 add_line_offset(off_t **, size_t *, off_t);
static int		 diff_commit(struct fnc_diff_view_state *);
static bool		 path_to_diff(const struct fnc_pathlist_head *,
			    const fsl_card_F *, const fsl_card_F *);
static int		 diff_checkout(struct fnc_diff_view_state *);
static int		 write_diff_meta(struct fnc_diff_view_state *,
			    const char *, fsl_uuid_cstr, const char *,
			    fsl_uuid_cstr, const fsl_ckout_change_e);
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 *,
			    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);
static int		 prev_file(struct fnc_diff_view_state *);
static int		 next_file(struct fnc_diff_view_state *);
static int		 prev_hunk(struct fnc_diff_view_state *);
static int		 next_hunk(struct fnc_diff_view_state *);
static int		 request_tl_commits(struct fnc_view *);
static int		 reset_diff_view(struct fnc_view *, bool);
static int		 stash_get_rm_cb(fsl_ckout_unmanage_state const *);
static int		 stash_get_add_cb(fsl_ckout_manage_state const *,
			    bool *);
static int		 f__add_files_in_sfile(int *, int);
static int		 f__stash_get(uint32_t, bool);
static int		 fnc_stash(struct fnc_view *);
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);







<
<


|
|

|
|



|

|
<







<
<
<
<






|

<







1160
1161
1162
1163
1164
1165
1166


1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179

1180
1181
1182
1183
1184
1185
1186




1187
1188
1189
1190
1191
1192
1193
1194

1195
1196
1197
1198
1199
1200
1201
static int		 make_stash_diff(struct fnc_diff_view_state *, char *);
static int		 write_commit_meta(struct fnc_diff_view_state *);
/* static int		 countlines(const char *); */
static int		 wrapline(char *, fsl_size_t,
			    struct fnc_diff_view_state *, off_t *);
static int		 add_line_offset(off_t **, size_t *, off_t);
static int		 diff_commit(struct fnc_diff_view_state *);


static int		 diff_checkout(struct fnc_diff_view_state *);
static int		 write_diff_meta(struct fnc_diff_view_state *,
			    const char *, fsl_uuid_str, const char *,
			    fsl_uuid_str, enum fsl_ckout_change_e);
static int		 diff_file(struct fnc_diff_view_state *, fsl_buffer *,
			    const char *, const char *, fsl_uuid_str,
			    const char *, enum 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 *,
			    fsl_ckout_change_e);
static int		 show_diff(struct fnc_view *);
static int		 write_diff(struct fnc_view *, 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);




static int		 request_tl_commits(struct fnc_view *);
static int		 reset_diff_view(struct fnc_view *, bool);
static int		 stash_get_rm_cb(fsl_ckout_unmanage_state const *);
static int		 stash_get_add_cb(fsl_ckout_manage_state const *,
			    bool *);
static int		 f__add_files_in_sfile(int *, int);
static int		 f__stash_get(bool);
static int		 fnc_stash(struct fnc_view *);

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);
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312

1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
			    struct commit_entry *);
static void		 diff_grep_init(struct fnc_view *);
static int		 find_next_match(struct fnc_view *);
static void		 grep_set_view(struct fnc_view *, FILE **, off_t **,
			    size_t *, int **, int **, int **, int **,
			    uint8_t *);
static int		 view_close(struct fnc_view *);
static int		 map_ckout_path(char **);
static int		 valid_path(const char *, const char *);
static int		 init_timeline_view(struct fnc_view **, int, int,
			    fsl_id_t, const char *, const char *);
static bool		 path_is_child(const char *, const char *, size_t);
static int		 path_skip_common_ancestor(char **, const char *,
			    size_t, const char *, size_t);
static bool		 fnc_path_is_root_dir(const char *);
/* static bool		 fnc_path_is_cwd(const char *); */
static int		 fnc_pathlist_insert(struct fnc_pathlist_entry **,
			    struct fnc_pathlist_head *, const char *, void *);
static int		 fnc_path_cmp(const char *, const char *, size_t,
			    size_t);
static void		 fnc_pathlist_free(struct fnc_pathlist_head *);
static int		 browse_commit_tree(struct fnc_view **, int, int,
			    struct commit_entry *, const char *);
static int		 open_tree_view(struct fnc_view *, const char *,
			    fsl_id_t);
static int		 walk_tree_path(struct fnc_tree_view_state *,

			    const char *, uint16_t);
static int		 create_repository_tree(struct fnc_repository_tree **,
			    fsl_uuid_str *, fsl_id_t);
static int		 tree_builder(struct fnc_repository_tree *,
			    struct fnc_tree_object **, const char *);
/* static void		 delete_tree_node(struct fnc_tree_entry **, */
/*			    struct fnc_tree_entry *); */
static int		 link_tree_node(struct fnc_repository_tree *,
			    const char *, const char *, double);
static int		 show_tree_view(struct fnc_view *);
static int		 tree_input_handler(struct fnc_view **,
			    struct fnc_view *, int);
static int		 blame_tree_entry(struct fnc_view **, int, int,
			    struct fnc_tree_entry *, struct fnc_parent_trees *,
			    fsl_uuid_cstr);
static void		 tree_grep_init(struct fnc_view *);
static int		 tree_search_next(struct fnc_view *);
static int		 tree_entry_path(char **, struct fnc_parent_trees *,
			    struct fnc_tree_entry *);
static int		 draw_tree(struct fnc_view *, const char *);
static int		 blame_selected_file(struct fnc_view **,
			    struct fnc_view *);
static int		 timeline_tree_entry(struct fnc_view **, int,
			    struct fnc_tree_view_state *);
static void		 tree_scroll_up(struct fnc_tree_view_state *, int);
static int		 tree_scroll_down(struct fnc_view *, int);
static int		 visit_subtree(struct fnc_tree_view_state *,
			    struct fnc_tree_object *);
static int		 tree_entry_get_symlink_target(char **,
			    struct fnc_tree_entry *);
static int		 match_tree_entry(struct fnc_tree_entry *, regex_t *);
static void		 fnc_object_tree_close(struct fnc_tree_object *);
static void		 fnc_close_repo_tree(struct fnc_repository_tree *);
static int		 open_blame_view(struct fnc_view *, char *,
			    fsl_uuid_cstr, fsl_id_t, int, const char *);
static int		 run_blame(struct fnc_view *);
static int		 fnc_dump_buffer_to_file(off_t *, int *, off_t **,
			    FILE *, fsl_buffer *);
static int		 show_blame_view(struct fnc_view *);
static void		*blame_thread(void *);
static int		 blame_cb(void *, fsl_annotate_opt const * const,
			    fsl_annotate_step const * const);







|
<

















>
|













|



















|







1247
1248
1249
1250
1251
1252
1253
1254

1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
			    struct commit_entry *);
static void		 diff_grep_init(struct fnc_view *);
static int		 find_next_match(struct fnc_view *);
static void		 grep_set_view(struct fnc_view *, FILE **, off_t **,
			    size_t *, int **, int **, int **, int **,
			    uint8_t *);
static int		 view_close(struct fnc_view *);
static int		 map_repo_path(char **);

static int		 init_timeline_view(struct fnc_view **, int, int,
			    fsl_id_t, const char *, const char *);
static bool		 path_is_child(const char *, const char *, size_t);
static int		 path_skip_common_ancestor(char **, const char *,
			    size_t, const char *, size_t);
static bool		 fnc_path_is_root_dir(const char *);
/* static bool		 fnc_path_is_cwd(const char *); */
static int		 fnc_pathlist_insert(struct fnc_pathlist_entry **,
			    struct fnc_pathlist_head *, const char *, void *);
static int		 fnc_path_cmp(const char *, const char *, size_t,
			    size_t);
static void		 fnc_pathlist_free(struct fnc_pathlist_head *);
static int		 browse_commit_tree(struct fnc_view **, int, int,
			    struct commit_entry *, const char *);
static int		 open_tree_view(struct fnc_view *, const char *,
			    fsl_id_t);
static int		 walk_tree_path(struct fnc_tree_view_state *,
			    struct fnc_repository_tree *,
			    struct fnc_tree_object **, const char *);
static int		 create_repository_tree(struct fnc_repository_tree **,
			    fsl_uuid_str *, fsl_id_t);
static int		 tree_builder(struct fnc_repository_tree *,
			    struct fnc_tree_object **, const char *);
/* static void		 delete_tree_node(struct fnc_tree_entry **, */
/*			    struct fnc_tree_entry *); */
static int		 link_tree_node(struct fnc_repository_tree *,
			    const char *, const char *, double);
static int		 show_tree_view(struct fnc_view *);
static int		 tree_input_handler(struct fnc_view **,
			    struct fnc_view *, int);
static int		 blame_tree_entry(struct fnc_view **, int, int,
			    struct fnc_tree_entry *, struct fnc_parent_trees *,
			    fsl_uuid_str);
static void		 tree_grep_init(struct fnc_view *);
static int		 tree_search_next(struct fnc_view *);
static int		 tree_entry_path(char **, struct fnc_parent_trees *,
			    struct fnc_tree_entry *);
static int		 draw_tree(struct fnc_view *, const char *);
static int		 blame_selected_file(struct fnc_view **,
			    struct fnc_view *);
static int		 timeline_tree_entry(struct fnc_view **, int,
			    struct fnc_tree_view_state *);
static void		 tree_scroll_up(struct fnc_tree_view_state *, int);
static int		 tree_scroll_down(struct fnc_view *, int);
static int		 visit_subtree(struct fnc_tree_view_state *,
			    struct fnc_tree_object *);
static int		 tree_entry_get_symlink_target(char **,
			    struct fnc_tree_entry *);
static int		 match_tree_entry(struct fnc_tree_entry *, regex_t *);
static void		 fnc_object_tree_close(struct fnc_tree_object *);
static void		 fnc_close_repo_tree(struct fnc_repository_tree *);
static int		 open_blame_view(struct fnc_view *, char *,
			    fsl_uuid_str, fsl_id_t, int, const char *);
static int		 run_blame(struct fnc_view *);
static int		 fnc_dump_buffer_to_file(off_t *, int *, off_t **,
			    FILE *, fsl_buffer *);
static int		 show_blame_view(struct fnc_view *);
static void		*blame_thread(void *);
static int		 blame_cb(void *, fsl_annotate_opt const * const,
			    fsl_annotate_step const * const);
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
static void		 fnc_branch_close(struct fnc_branch *);
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		 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 *);
static void		 fnc_commit_artifact_close(struct fnc_commit_artifact*);
static int		 fsl_file_artifact_free(void *, void *);
static void		 sigwinch_handler(int);
static void		 sigpipe_handler(int);
static void		 sigcont_handler(int);
static void		 sigint_handler(int);
static void		 sigterm_handler(int);
static bool		 fatal_signal(void);
static int		 draw_lineno(struct fnc_view *, int, int, attr_t);
static bool		 gotoline(struct fnc_view *, int *, int *);
static int		 strtonumcheck(long *, const char *, const int,
			    const int);
static int		 fnc_prompt_input(struct fnc_view *, struct input *);
static int		 fnc_date_to_mtime(double *, const char *, int);
static int		 cook_input(char *, int, WINDOW *);







|













<
<
<







1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368



1369
1370
1371
1372
1373
1374
1375
static void		 fnc_branch_close(struct fnc_branch *);
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 *);
static void		 free_tags(struct fnc_tl_view_state *, bool);
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 *);
static void		 fnc_commit_artifact_close(struct fnc_commit_artifact*);
static int		 fsl_file_artifact_free(void *, void *);
static void		 sigwinch_handler(int);
static void		 sigpipe_handler(int);
static void		 sigcont_handler(int);



static int		 draw_lineno(struct fnc_view *, int, int, attr_t);
static bool		 gotoline(struct fnc_view *, int *, int *);
static int		 strtonumcheck(long *, const char *, const int,
			    const int);
static int		 fnc_prompt_input(struct fnc_view *, struct input *);
static int		 fnc_date_to_mtime(double *, const char *, int);
static int		 cook_input(char *, int, WINDOW *);
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
				    int);
static struct fnc_tree_entry	*find_tree_entry(struct fnc_tree_object *,
				    const char *, size_t);

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))) {
		rc = RC(FSL_RC_MISUSE, "invalid input device");







|
|
|







1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
				    int);
static struct fnc_tree_entry	*find_tree_entry(struct fnc_tree_object *,
				    const char *, size_t);

int
main(int argc, const char **argv)
{
	fcli_command	*cmd = NULL;
	char		*path = NULL;
	int		 rc = FSL_RC_OK;

	/*
	 * XXX Guard against misuse. Will have to take another approach once
	 * the test harness is finished as we pipe input for our tests cases.
	 */
	if (!isatty(fileno(stdin))) {
		rc = RC(FSL_RC_MISUSE, "invalid input device");
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517

1518
1519
1520
1521
1522
1523

1524



1525
1526
1527
1528

1529

1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544




1545
1546
1547
1548
1549
1550
1551
1552
	if (fnc_init.vflag) {
		fnc_show_version();
		goto end;
	} else if (fnc_init.hflag) {
		rc = FCLI_RC_HELP;
		goto end;
	}
	if (!fsl_cx_db_repo(fcli_cx())) {
		rc = RC(FSL_RC_MISUSE, "repository database required");
		goto end;
	}
#ifdef __OpenBSD__
	/*
	 * See pledge(2). This is the most restrictive set we can operate under.
	 * Look for any adverse impact & revise when implementing new features.
	 * stdio (close, sigaction); rpath (chdir getcwd lstat); wpath (getcwd);
	 * cpath (symlink); flock (open); tty (TIOCGWINSZ); unveil (unveil).
	 * XXX 'fnc stash' needs more perms, call pledge(2) from cmd_stash().
	 */
	if (!(!fsl_strcmp(fnc_init.cmdarg, "stash") ||
	    !fcli_cmd_aliascmp(&fnc_init.cmd_args[6], fnc_init.cmdarg)) &&
	    pledge("stdio rpath wpath cpath flock tty unveil", NULL) == -1) {
		rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "pledge");
		goto end;
	}
#endif
	rc = fcli_fingerprint_check(true);
	if (rc)
		goto end;

	if (fcli.argc) {
		rc = fcli_dispatch_commands(fnc_init.cmd_args, false);
		if (rc == FCLI_RC_NO_CMD && fcli.argc == 1) {
			/*
			 * Check if user entered fnc path/in/repo; if valid path
			 * is found, assume fnc timeline path/in/repo was meant.
			 */
			rc = map_ckout_path(&path);

			if (rc && rc != FSL_RC_NOT_FOUND)
				goto end;
			/* Path may have existed in the repo at some point. */
			RC_RESET(rc); /* for fcli_process_flags */
			cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];
			fnc_init.path = path;

		}



	} else
		cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];

	if (fcli.transient.helpRequested)

		goto end;


	if (cmd != NULL)
		rc = cmd->f(cmd);
end:
	fsl_free(path);
	!isendwin() ? endwin() : 0;  /* may have been called in cmd_stash() */
	if (rc) {
		if (rc == FSL_RC_BREAK) {
			const fsl_cx *const f = fcli_cx();
			const char *errstr;

			fsl_error_get(&f->error, &errstr, NULL);
			fsl_fprintf(stdout, "%s", errstr);
			RC_RESET(rc);  /* for fcli_end_of_main() */
		} else if (rc == FCLI_RC_HELP) {




			fnc_init.err = FCLI_RC_HELP;
			usage();
			/* NOT REACHED */
		}
	}
	putchar('\n');
	return fcli_end_of_main(rc);
}







<
<
<
<









|






|
<
|
|

|




|
>
|
|
|
|
|
|
>
|
>
>
>
|
<

|
>

>










<



|
>
>
>
>
|







1437
1438
1439
1440
1441
1442
1443




1444
1445
1446
1447
1448
1449
1450
1451
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
1477
1478
1479
1480
1481
1482

1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497

1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
	if (fnc_init.vflag) {
		fnc_show_version();
		goto end;
	} else if (fnc_init.hflag) {
		rc = FCLI_RC_HELP;
		goto end;
	}




#ifdef __OpenBSD__
	/*
	 * See pledge(2). This is the most restrictive set we can operate under.
	 * Look for any adverse impact & revise when implementing new features.
	 * stdio (close, sigaction); rpath (chdir getcwd lstat); wpath (getcwd);
	 * cpath (symlink); flock (open); tty (TIOCGWINSZ); unveil (unveil).
	 * XXX 'fnc stash' needs more perms, call pledge(2) from cmd_stash().
	 */
	if (!(!fsl_strcmp(fnc_init.cmdarg, "stash") ||
	    fcli_cmd_aliascmp(&fnc_init.cmd_args[6], fnc_init.cmdarg)) &&
	    pledge("stdio rpath wpath cpath flock tty unveil", NULL) == -1) {
		rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "pledge");
		goto end;
	}
#endif
	rc = fcli_fingerprint_check(true);
	if (argc == 1)

		cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];
	else if (!rc) {
		rc = fcli_dispatch_commands(fnc_init.cmd_args, false);
		if (rc == FSL_RC_NOT_FOUND && argc == 2) {
			/*
			 * Check if user entered fnc path/in/repo; if valid path
			 * is found, assume fnc timeline path/in/repo was meant.
			 */
			rc = map_repo_path(&path);
			if (rc == FSL_RC_UNKNOWN_RESOURCE || !path) {
				rc = RC(FSL_RC_NOT_FOUND,
				    "'%s' is not a valid command or path",
				    argv[1]);
			} else if (!rc) {
				cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];
				fnc_init.path = path;
				fcli_err_reset(); /* for fcli_process_flags */
			}
		}
	}
	if (rc)
		goto end;


	if (!fsl_cx_db_repo(fcli_cx())) {
		rc = RC(FSL_RC_MISUSE, "repository database required");
		goto end;
	}

	if (cmd != NULL)
		rc = cmd->f(cmd);
end:
	fsl_free(path);
	!isendwin() ? endwin() : 0;  /* may have been called in cmd_stash() */
	if (rc) {
		if (rc == FSL_RC_BREAK) {
			const fsl_cx *const f = fcli_cx();
			const char *errstr;

			fsl_error_get(&f->error, &errstr, NULL);
			fsl_fprintf(stdout, "%s", errstr);
			RC_RESET(rc);  /* for fcli_end_of_main() */
		} else if (rc == FSL_RC_UNKNOWN_RESOURCE) {
			/* file not found by map_repo_path() */
			fcli_err_set(FSL_RC_NOT_FOUND, "%s",
			    fsl_buffer_cstr(&fcli_error()->msg));
		} else {
			fnc_init.err = rc == FCLI_RC_HELP ? FSL_RC_OK : rc;
			usage();
			/* NOT REACHED */
		}
	}
	putchar('\n');
	return fcli_end_of_main(rc);
}
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
	}

	if (fnc_init.glob)
		glob = fsl_strdup(fnc_init.glob);
	if (fnc_init.path)
		path = fsl_strdup(fnc_init.path);
	else {
		rc = map_ckout_path(&path);
		if (rc) {
			if (rc != FSL_RC_NOT_FOUND && !fnc_init.sym)
				goto end;
			/* Path may have existed in the repo at some point. */
			RC_RESET(rc);
		}
	}

	rc = init_curses();
	if (rc)
		goto end;
	rc = init_unveil(((const char *[]){REPODB, CKOUTDIR, P_tmpdir,
	    gettzfile()}), ((const char *[]){"rw", "rwc", "rwc", "r"}), 4, true);







|
|
<
|
<
<
<







1539
1540
1541
1542
1543
1544
1545
1546
1547

1548



1549
1550
1551
1552
1553
1554
1555
	}

	if (fnc_init.glob)
		glob = fsl_strdup(fnc_init.glob);
	if (fnc_init.path)
		path = fsl_strdup(fnc_init.path);
	else {
		rc = map_repo_path(&path);
		if (rc)

			goto end;



	}

	rc = init_curses();
	if (rc)
		goto end;
	rc = init_unveil(((const char *[]){REPODB, CKOUTDIR, P_tmpdir,
	    gettzfile()}), ((const char *[]){"rw", "rwc", "rwc", "r"}), 4, true);
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647

1648

1649







1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668

1669
1670
1671
1672
1673
1674
1675
1676
1677



1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702


1703
1704
1705
1706
1707

1708
1709




1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726




1727
1728
1729
1730
1731
1732
1733
1734
1735
1736


1737
1738
1739








1740

1741
1742
1743
1744
















1745
1746
1747

1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
	if (!rc)
		rc = open_timeline_view(*view, rid, path, glob);

	return rc;
}

/*
 * Look for a work tree path in **argv. If found, canonicalise path as
 * relative to the root of the tree (e.g., ckoutdir/found/path), and assign to
 * a dynamically allocated string in *requested_path, which the caller must
 * free. As a special case, if path is the root of the tree, *requested_path
 * will be NULL. If path is not found, return FSL_RC_NOT_FOUND.
 */
static int
map_ckout_path(char **requested_path)
{
	fsl_cx		*const f = fcli_cx();
	fsl_buffer	 buf = fsl_buffer_empty;
	char		*ckoutdir = NULL, *path = NULL;
	const char	*canonpath = NULL, *ckoutdir0 = NULL;
	fsl_size_t	 len;
	int		 rc = 0;
	bool		 root;

	*requested_path = NULL;

	/* If no path argument is supplied, default to repository root. */
	canonpath = fcli_next_arg(true);

	if (canonpath == NULL)

		return rc;








	/*
	 * If no checkout (e.g., 'fnc timeline -R') copy the path verbatim to
	 * check its validity against a deck of F cards in open_timeline_view().
	 */
	ckoutdir0 = fsl_cx_ckout_dir_name(f, &len);
	if (!ckoutdir0) {
		path = fsl_strdup(canonpath);
		goto end;
	}

	path = realpath(canonpath, NULL);
	if (path == NULL && (errno == ENOENT || errno == ENOTDIR)) {
		/* Path is not on disk, assume it is relative to repo root. */
		rc = fsl_file_canonical_name2(ckoutdir0, canonpath, &buf, NULL);
		if (rc) {
			rc = RC(rc, "fsl_file_canonical_name2");
			goto end;
		}

		path = realpath(fsl_buffer_cstr(&buf), NULL);
		if (!path)
			rc = RC(FSL_RC_NOT_FOUND,
			    "path not found in checkout: %s", canonpath);
		/* Confirmed path is relative to repository root. */
		fsl_free(path);
		path = fsl_strdup(canonpath);
		if (!rc && path == NULL)
			rc = RC(FSL_RC_ERROR, "fsl_strdup");



		goto end;
	}

	/*
	 * Use the cwd as the virtual root to canonicalise the supplied path if
	 * it is either: (a) relative; or (b) the root of the current checkout.
	 * Otherwise, use the root of the current checkout.
	 */
	rc = fsl_cx_getcwd(f, &buf);
	if (rc)
		goto end;

	ckoutdir = fsl_mprintf("%.*s", len - 1, ckoutdir0);
	root = !fsl_strcmp(ckoutdir, fsl_buffer_cstr(&buf));
	fsl_buffer_reuse(&buf);
	rc = fsl_ckout_filename_check(f, (*canonpath == '.' || !root) ?
	    true : false, path, &buf);
	if (rc)
		goto end;

	fsl_free(path);
	path = fsl_buffer_take(&buf);
	if (*path == '\0' || (*path == '.' && !path[1])) {
		fsl_free(path);
		path = NULL;


		goto end;
	}

	fsl_buffer_reuse(&buf);
	rc = fsl_file_canonical_name2(f->ckout.dir, path, &buf, false);

	if (rc)
		goto end;





	fsl_free(path);
	path = fsl_buffer_take(&buf);
	if (access(path, F_OK) != 0) {
		rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS),
		    "path does not exist or inaccessible [%s]", path);
		goto end;
	}

	/*
	 * Now we have an absolute path, check again if it's the ckout
	 * dir; if so, clear it to signal an open_timeline_view() check.
	 */
	len = fsl_strlen(path);
	if (!fsl_strcmp(path, f->ckout.dir)) {
		fsl_free(path);
		path = NULL;




	} else if (len > f->ckout.dirLen && path_is_child(path,
	    f->ckout.dir, f->ckout.dirLen)) {
		char *child;

		/*
		 * Matched on-disk path within the repository; strip
		 * common prefix with repository root path.
		 */
		rc = path_skip_common_ancestor(&child, f->ckout.dir,
		    f->ckout.dirLen, path, len);


		fsl_free(path);
		if (!rc)
			path = child;








	}

end:
	/* Trim trailing slash if it exists. */
	if (path && path[fsl_strlen(path) - 1] == '/')
		path[fsl_strlen(path) - 1] = '\0';
















	*requested_path = path;

	fsl_buffer_clear(&buf);

	fsl_free(ckoutdir);
	return rc;
}

static bool
path_is_child(const char *child, const char *parent, size_t parentlen)
{
	if (parentlen == 0 || fnc_path_is_root_dir(parent))
		return true;

	if (fsl_strncmp(parent, child, parentlen) != 0)
		return false;
	if (child[parentlen - 1 /* Trailing slash */] != '/')
		return false;

	return true;
}

/*
 * NULL, "/", and "." all resolve to the repository root. The latter being a
 * special case, due to fsl_ckout_filename_check() resolving the current
 * checkout root directory to ".". For this reason, when path is intended to be
 * the current working directory for any directory other than the repository
 * root, callers must ensure path is either absolute or relative to the
 * respository root--not ".".
 */
static bool
fnc_path_is_root_dir(const char *path)
{
	if (!path)
		return true;
	while (*path == '/' || *path == '.')
		++path;
	return (*path == '\0');
}

static int
path_skip_common_ancestor(char **child, const char *parent_abspath,







|
|
|
|
<


|



|
|







|
>
|
>

>
>
>
>
>
>
>



















>

|
<
<
|
|
|
|
|
>
>
>


<








<

|

|
|


|
|
|
|
|
|
>
>
|
|
|
|
|
>
|
|
>
>
>
>
|
<
<
|
|
|
|
|
<
|
|
|
|
|
|
|
|
>
>
>
>
|
|
|
<
|
|
|
|
|
|
>
>
|
<

>
>
>
>
>
>
>
>
|
>
|

|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|

>



















<
|
|
|
|
|




<
<







1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587

1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636


1637
1638
1639
1640
1641
1642
1643
1644
1645
1646

1647
1648
1649
1650
1651
1652
1653
1654

1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682


1683
1684
1685
1686
1687

1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702

1703
1704
1705
1706
1707
1708
1709
1710
1711

1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765

1766
1767
1768
1769
1770
1771
1772
1773
1774


1775
1776
1777
1778
1779
1780
1781
	if (!rc)
		rc = open_timeline_view(*view, rid, path, glob);

	return rc;
}

/*
 * Look for an in-repository path in **argv. If found, canonicalise it as an
 * absolute path relative to the repository root (e.g., /ckoutdir/found/path),
 * and assign to a dynamically allocated string in *requested_path, which the
 * caller must dispose of with fsl_free or free(3).

 */
static int
map_repo_path(char **requested_path)
{
	fsl_cx		*const f = fcli_cx();
	fsl_buffer	 buf = fsl_buffer_empty;
	char		*canonpath = NULL, *ckoutdir = NULL, *path = NULL;
	const char	*ckoutdir0 = NULL;
	fsl_size_t	 len;
	int		 rc = 0;
	bool		 root;

	*requested_path = NULL;

	/* If no path argument is supplied, default to repository root. */
	if (!fcli_next_arg(false)) {
		*requested_path = fsl_strdup("/");
		if (*requested_path == NULL)
			return RC(FSL_RC_ERROR, "fsl_strdup");
		return rc;
	}

	canonpath = fsl_strdup(fcli_next_arg(true));
	if (canonpath == NULL) {
		rc = RC(FSL_RC_ERROR, "fsl_strdup");
		goto end;
	}

	/*
	 * If no checkout (e.g., 'fnc timeline -R') copy the path verbatim to
	 * check its validity against a deck of F cards in open_timeline_view().
	 */
	ckoutdir0 = fsl_cx_ckout_dir_name(f, &len);
	if (!ckoutdir0) {
		path = fsl_strdup(canonpath);
		goto end;
	}

	path = realpath(canonpath, NULL);
	if (path == NULL && (errno == ENOENT || errno == ENOTDIR)) {
		/* Path is not on disk, assume it is relative to repo root. */
		rc = fsl_file_canonical_name2(ckoutdir0, canonpath, &buf, NULL);
		if (rc) {
			rc = RC(rc, "fsl_file_canonical_name2");
			goto end;
		}
		fsl_free(path);
		path = realpath(fsl_buffer_cstr(&buf), NULL);
		if (path) {


			/* Confirmed path is relative to repository root. */
			fsl_free(path);
			path = fsl_strdup(canonpath);
			if (path == NULL)
				rc = RC(FSL_RC_ERROR, "fsl_strdup");
		} else
			rc = RC(FSL_RC_UNKNOWN_RESOURCE,
			    "'%s' not found in tree", canonpath);
		goto end;
	}

	/*
	 * Use the cwd as the virtual root to canonicalise the supplied path if
	 * it is either: (a) relative; or (b) the root of the current checkout.
	 * Otherwise, use the root of the current checkout.
	 */
	rc = fsl_cx_getcwd(f, &buf);
	if (rc)
		goto end;

	ckoutdir = fsl_mprintf("%.*s", len - 1, ckoutdir0);
	root = fsl_strcmp(ckoutdir, fsl_buffer_cstr(&buf)) == 0;
	fsl_buffer_reuse(&buf);
	rc = fsl_ckout_filename_check(f, (canonpath[0] == '.' || !root) ?
	    true : false, canonpath, &buf);
	if (rc)
		goto end;
	fsl_free(path);
	fsl_free(canonpath);
	canonpath = fsl_strdup(fsl_buffer_str(&buf));

	if (canonpath[0] == '\0') {
		path = fsl_strdup(canonpath);
		if (path == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			goto end;
		}
	} else {
		fsl_buffer_reuse(&buf);
		rc = fsl_file_canonical_name2(f->ckout.dir, canonpath, &buf,
		    false);
		if (rc)
			goto end;
		path = fsl_strdup(fsl_buffer_str(&buf));
		if (path == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			goto end;
		}


		if (access(path, F_OK) != 0) {
			rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS),
			    "path does not exist or inaccessible [%s]", path);
			goto end;
		}

		/*
		 * Now we have an absolute path, check again if it's the ckout
		 * dir; if so, clear it to signal an open_timeline_view() check.
		 */
		len = fsl_strlen(path);
		if (!fsl_strcmp(path, f->ckout.dir)) {
			fsl_free(path);
			path = fsl_strdup("");
			if (path == NULL) {
				rc = RC(FSL_RC_ERROR, "fsl_strdup");
				goto end;
			}
		} else if (len > f->ckout.dirLen && path_is_child(path,
		    f->ckout.dir, f->ckout.dirLen)) {
			char *child;

			/*
			 * Matched on-disk path within the repository; strip
			 * common prefix with repository root path.
			 */
			rc = path_skip_common_ancestor(&child, f->ckout.dir,
			    f->ckout.dirLen, path, len);
			if (rc)
				goto end;
			fsl_free(path);

			path = child;
		} else {
			/*
			 * Matched on-disk path outside the repository; treat
			 * as relative to repo root. (Though this should fail.)
			 */
			fsl_free(path);
			path = canonpath;
			canonpath = NULL;
		}
	}

	/* Trim trailing slash if it exists. */
	if (path[fsl_strlen(path) - 1] == '/')
		path[fsl_strlen(path) - 1] = '\0';

end:
	if (rc) {
		*requested_path = fsl_strdup(canonpath);
		fsl_free(path);
	} else {
		/* Make path absolute from repository root. */
		if (path[0] != '/' && (path[0] != '.' && path[1] != '/')) {
			char *abspath;
			if ((abspath = fsl_mprintf("/%s", path)) == NULL) {
				rc = RC(FSL_RC_ERROR, "fsl_mprintf");
			}
			fsl_free(path);
			path = abspath;
		}

		*requested_path = path;
	}
	fsl_buffer_clear(&buf);
	fsl_free(canonpath);
	fsl_free(ckoutdir);
	return rc;
}

static bool
path_is_child(const char *child, const char *parent, size_t parentlen)
{
	if (parentlen == 0 || fnc_path_is_root_dir(parent))
		return true;

	if (fsl_strncmp(parent, child, parentlen) != 0)
		return false;
	if (child[parentlen - 1 /* Trailing slash */] != '/')
		return false;

	return true;
}

/*

 * As a special case, due to fsl_ckout_filename_check() resolving the current
 * checkout directory to ".", this function returns true for ".". For this
 * reason, when path is intended to be the current working directory for any
 * directory other than the repository root, callers must ensure path is either
 * absolute or relative to the respository root--not ".".
 */
static bool
fnc_path_is_root_dir(const char *path)
{


	while (*path == '/' || *path == '.')
		++path;
	return (*path == '\0');
}

static int
path_skip_common_ancestor(char **child, const char *parent_abspath,
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
	return (path[0] == '.' && path[1] == '\0');
}
#endif

static int
init_curses(void)
{
	int rc;

	rc = fnc_set_signals();
	if (rc)
		return rc;

	initscr();
	cbreak();
	noecho();
	nonl();
	intrflush(stdscr, FALSE);
	keypad(stdscr, TRUE);
	raw();  /* Don't signal control characters, specifically C-y */
	curs_set(0);
	set_escdelay(0);  /* ESC should return immediately. */
#ifndef __linux__
	typeahead(-1);	/* Don't disrupt screen update operations. */
#endif

	if (!fnc_init.nocolour && has_colors()) {
		start_color();
		use_default_colors();
	}

	return FSL_RC_OK;
}

static int
fnc_set_signals(void)
{
	if (sigaction(SIGPIPE, &(struct sigaction){{sigpipe_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGPIPE)");
	if (sigaction(SIGWINCH, &(struct sigaction){{sigwinch_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGWINCH)");
	if (sigaction(SIGCONT, &(struct sigaction){{sigcont_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGCONT)");
	if (sigaction(SIGINT, &(struct sigaction){{sigint_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGINT)");
	if (sigaction(SIGTERM, &(struct sigaction){{sigterm_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGTERM)");


	return FSL_RC_OK;
}

static struct fnc_view *
view_open(int nlines, int ncols, int start_ln, int start_col,
    enum fnc_view_id vid)







<
<
<
<
<
<


















|

















<
<
<
<
<
<
<
<
<







1814
1815
1816
1817
1818
1819
1820






1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856









1857
1858
1859
1860
1861
1862
1863
	return (path[0] == '.' && path[1] == '\0');
}
#endif

static int
init_curses(void)
{






	initscr();
	cbreak();
	noecho();
	nonl();
	intrflush(stdscr, FALSE);
	keypad(stdscr, TRUE);
	raw();  /* Don't signal control characters, specifically C-y */
	curs_set(0);
	set_escdelay(0);  /* ESC should return immediately. */
#ifndef __linux__
	typeahead(-1);	/* Don't disrupt screen update operations. */
#endif

	if (!fnc_init.nocolour && has_colors()) {
		start_color();
		use_default_colors();
	}

	return fnc_set_signals();
}

static int
fnc_set_signals(void)
{
	if (sigaction(SIGPIPE, &(struct sigaction){{sigpipe_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGPIPE)");
	if (sigaction(SIGWINCH, &(struct sigaction){{sigwinch_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGWINCH)");
	if (sigaction(SIGCONT, &(struct sigaction){{sigcont_handler}}, NULL)
	    == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR),
		    "sigaction(SIGCONT)");










	return FSL_RC_OK;
}

static struct fnc_view *
view_open(int nlines, int ncols, int start_ln, int start_col,
    enum fnc_view_id vid)
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958

1959
1960
1961
1962












1963


1964

1965
1966
1967

1968
1969
1970
1971
1972
1973
1974
	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) {

		fsl_uuid_str id = NULL;

		if (rid)
			id = fsl_rid_to_uuid(f, rid);















		rc = valid_path(path, id ? id : "tip");

		fsl_free(id);
		if (rc)
			return rc;

	}

	if ((rc = pthread_cond_init(&s->thread_cx.commit_consumer, NULL))) {
		RC(fsl_errno_to_rc(rc, FSL_RC_ACCESS), "pthread_cond_init");
		goto end;
	}
	if ((rc = pthread_cond_init(&s->thread_cx.commit_producer, NULL))) {







|
|
|

|
>

|


>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
|
>

|
|
>







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
1966
1967
1968
1969
1970
1971
1972
	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 timeline -R repo.fossil path' case, check that path is a
	 * valid repository path in the repository tree as at either the
	 * latest check-in or the specified commit.
	 */
	if (s->curr_ckout_uuid == NULL && path[1]) {
		fsl_deck d = fsl_deck_empty;
		fsl_uuid_str id = NULL;
		bool ispath = false;
		if (rid)
			id = fsl_rid_to_uuid(f, rid);
		rc = fsl_deck_load_sym(f, &d, fnc_init.sym ? fnc_init.sym :
		    id ? id : "tip", FSL_SATYPE_CHECKIN);
		fsl_deck_F_rewind(&d);
		if (fsl_deck_F_search(&d, path + 1 /* Slash */) == NULL) {
			const fsl_card_F *cf;
			fsl_deck_F_next(&d, &cf);
			do {
				fsl_deck_F_next(&d, &cf);
				if (cf && !fsl_strncmp(path + 1 /* Slash */,
				    cf->name, fsl_strlen(path) - 1)) {
					ispath = true;
					break;
				}
			} while (cf);
		} else
			ispath = true;
		fsl_deck_finalize(&d);
		fsl_free(id);
		if (!ispath)
			return RC(FSL_RC_NOT_FOUND, "'%s' invalid path in [%s]",
			    path + 1, fnc_init.sym ? fnc_init.sym : "tip");
	}

	if ((rc = pthread_cond_init(&s->thread_cx.commit_consumer, NULL))) {
		RC(fsl_errno_to_rc(rc, FSL_RC_ACCESS), "pthread_cond_init");
		goto end;
	}
	if ((rc = pthread_cond_init(&s->thread_cx.commit_producer, NULL))) {
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107


2108
2109
2110
2111
2112
2113
2114

	if (startdate) {
		fsl_buffer_appendf(&sql, " AND event.mtime <= %s", startdate);
		fsl_free(startdate);
	}

	/*
	 * If path is not root (NULL, /, .), a versioned path in the repository
	 * has been requested, only retrieve commits involving path.
	 */
	if (path && path[1]) {
		fsl_buffer_appendf(&sql,
		    " AND EXISTS(SELECT 1 FROM mlink"
		    " WHERE mlink.mid = event.objid"
		    " AND mlink.fnid IN ");
		if (fsl_cx_is_case_sensitive(f, false))
			fsl_buffer_appendf(&sql,
			    "(SELECT fnid FROM filename"
			    " WHERE name = %Q OR name GLOB '%q/*')",
			    path, path);
		else
			fsl_buffer_appendf(&sql,
			    "(SELECT fnid FROM filename"
			    " WHERE name = %Q COLLATE nocase"
			    " OR lower(name) GLOB lower('%q/*'))", path, path);


		fsl_buffer_append(&sql, ")", 1);
	}

	fsl_buffer_appendf(&sql, " ORDER BY event.mtime DESC");

	if (fnc_init.nrecords.limit > 0)
		fsl_buffer_appendf(&sql, " LIMIT %d", fnc_init.nrecords.limit);







|
|

|




|



|
|



|
>
>







2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114

	if (startdate) {
		fsl_buffer_appendf(&sql, " AND event.mtime <= %s", startdate);
		fsl_free(startdate);
	}

	/*
	 * If path is not root ("/"), a versioned path in the repository has
	 * been requested, only retrieve commits involving path.
	 */
	if (path[1]) {
		fsl_buffer_appendf(&sql,
		    " AND EXISTS(SELECT 1 FROM mlink"
		    " WHERE mlink.mid = event.objid"
		    " AND mlink.fnid IN ");
		if (fsl_cx_is_case_sensitive(f,false)) {
			fsl_buffer_appendf(&sql,
			    "(SELECT fnid FROM filename"
			    " WHERE name = %Q OR name GLOB '%q/*')",
			    path + 1, path + 1);  /* Skip prepended slash. */
		} else {
			fsl_buffer_appendf(&sql,
			    "(SELECT fnid FROM filename"
			    " WHERE name = %Q COLLATE nocase"
			    " OR lower(name) GLOB lower('%q/*'))",
			    path + 1, path + 1);  /* Skip prepended slash. */
		}
		fsl_buffer_append(&sql, ")", 1);
	}

	fsl_buffer_appendf(&sql, " ORDER BY event.mtime DESC");

	if (fnc_init.nrecords.limit > 0)
		fsl_buffer_appendf(&sql, " LIMIT %d", fnc_init.nrecords.limit);
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
	case FSL_RC_STEP_ROW:
		rc = 0;
		break;
	case FSL_RC_STEP_ERROR:
		rc = RC(rc, "fsl_stmt_step");
		goto end;
	case FSL_RC_STEP_DONE:
		if (path == NULL)
			rc = RC(FSL_RC_NOT_FOUND, "no matching records");
		else {
			fcli_command *c = &fnc_init.cmd_args[FNC_VIEW_TIMELINE];
			const char *say = "invalid command or path: ";

			if (!fsl_strcmp(c->name, fnc_init.cmdarg) ||
			    !fcli_cmd_aliascmp(c, fnc_init.cmdarg))
				say = "path not found in repository: ";
			rc = RC(FSL_RC_NOT_FOUND, "%s%s", say, path);
		}
		goto end;
	}

	s->colour = !fnc_init.nocolour && has_colors();
	s->showmeta = true;
	s->thread_cx.rc = 0;
	s->thread_cx.db = db;







<
|
<
<
<
<
<
<
<
<
<







2130
2131
2132
2133
2134
2135
2136

2137









2138
2139
2140
2141
2142
2143
2144
	case FSL_RC_STEP_ROW:
		rc = 0;
		break;
	case FSL_RC_STEP_ERROR:
		rc = RC(rc, "fsl_stmt_step");
		goto end;
	case FSL_RC_STEP_DONE:

		rc = RC(FSL_RC_BREAK, "no matching records");









		goto end;
	}

	s->colour = !fnc_init.nocolour && has_colors();
	s->showmeta = true;
	s->thread_cx.rc = 0;
	s->thread_cx.db = db;
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
	TAILQ_INSERT_HEAD(&views, view, entries);

	view->active = true;
	rc = view->show(view);
	if (rc)
		return rc;

	while (!TAILQ_EMPTY(&views) && !done && !fatal_signal()) {
		rc = view_input(&new_view, &done, view, &views);
		if (rc)
			break;
		if (view->egress) {
			struct fnc_view *v, *prev = NULL;

			if (view_is_parent(view))







|







2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
	TAILQ_INSERT_HEAD(&views, view, entries);

	view->active = true;
	rc = view->show(view);
	if (rc)
		return rc;

	while (!TAILQ_EMPTY(&views) && !done && !rec_sigpipe) {
		rc = view_input(&new_view, &done, view, &views);
		if (rc)
			break;
		if (view->egress) {
			struct fnc_view *v, *prev = NULL;

			if (view_is_parent(view))
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
	int			 rc;
	bool			 done = false;

	rc = block_main_thread_signals();
	if (rc)
		return (void *)(intptr_t)rc;

	while (!done && !rc && !fatal_signal()) {
		switch (rc = build_commits(cx)) {
		case FSL_RC_STEP_DONE:
			done = true;
			/* FALL THROUGH */
		case FSL_RC_STEP_ROW:
			rc = 0;
			/* FALL THROUGH */







|







2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
	int			 rc;
	bool			 done = false;

	rc = block_main_thread_signals();
	if (rc)
		return (void *)(intptr_t)rc;

	while (!done && !rc && !rec_sigpipe) {
		switch (rc = build_commits(cx)) {
		case FSL_RC_STEP_DONE:
			done = true;
			/* FALL THROUGH */
		case FSL_RC_STEP_ROW:
			rc = 0;
			/* FALL THROUGH */
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
block_main_thread_signals(void)
{
	sigset_t set;

	if (sigemptyset(&set) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigemptyset");

	/* Bespoke handlers for SIGWINCH, SIGCONT, SIGINT, and SIGTERM. */
	if (sigaddset(&set, SIGWINCH) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");
	if (sigaddset(&set, SIGCONT) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");
	if (sigaddset(&set, SIGINT) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");
	if (sigaddset(&set, SIGTERM) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");

	/* ncurses handles SIGTSTP. */
	if (sigaddset(&set, SIGTSTP) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");

	if (pthread_sigmask(SIG_BLOCK, &set, NULL))







|



<
<
<
<







2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401




2402
2403
2404
2405
2406
2407
2408
block_main_thread_signals(void)
{
	sigset_t set;

	if (sigemptyset(&set) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigemptyset");

	/* Bespoke signal handlers for SIGWINCH and SIGCONT. */
	if (sigaddset(&set, SIGWINCH) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");
	if (sigaddset(&set, SIGCONT) == -1)




		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");

	/* ncurses handles SIGTSTP. */
	if (sigaddset(&set, SIGTSTP) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_MISUSE), "sigaddset");

	if (pthread_sigmask(SIG_BLOCK, &set, NULL))
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
	struct commit_entry		*entry = s->selected_entry;
	struct fnc_colour		*c = NULL;
	const char			*search_str = NULL;
	char				*headln = NULL, *idxstr = NULL;
	char				*branch = NULL, *type = NULL;
	char				*uuid = NULL;
	wchar_t				*wline;
	attr_t				 rx = 0;
	int				 ncommits = 0, rc = FSL_RC_OK, wlen = 0;
	int				 ncols_needed, maxlen = -1;

	if (s->selected_entry && !(view->searching != SEARCH_DONE &&
	    view->search_status == SEARCH_WAITING)) {
		uuid = fsl_strdup(s->selected_entry->commit->uuid);
		branch = fsl_strdup(s->selected_entry->commit->branch);







|







2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
	struct commit_entry		*entry = s->selected_entry;
	struct fnc_colour		*c = NULL;
	const char			*search_str = NULL;
	char				*headln = NULL, *idxstr = NULL;
	char				*branch = NULL, *type = NULL;
	char				*uuid = NULL;
	wchar_t				*wline;
	attr_t				 rx = A_BOLD;
	int				 ncommits = 0, rc = FSL_RC_OK, wlen = 0;
	int				 ncols_needed, maxlen = -1;

	if (s->selected_entry && !(view->searching != SEARCH_DONE &&
	    view->search_status == SEARCH_WAITING)) {
		uuid = fsl_strdup(s->selected_entry->commit->uuid);
		branch = fsl_strdup(s->selected_entry->commit->branch);
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
	 * for the space separator. Same applies if search_str is being shown.
	 */
	ncols_needed = fsl_strlen(type) + fsl_strlen(idxstr) + FSL_STRLEN_K256
	    + (!search_str && (!fsl_strcmp(type, "wiki") ||
	    !fsl_strcmp(type, "tag")  || !fsl_strcmp(type, "ticket") ||
	    (!branch && !fsl_strcmp(type, "technote"))) ? 0 : 1);
	/* If a path has been requested, display it in the headline. */
	if (s->path && s->path[1]) {
		if ((headln = fsl_mprintf("%s%c%.*s /%s%s", type ? type : "",
		    type ? ' ' : SPINNER[tcx->spin_idx], view->ncols <
		    ncols_needed ? view->ncols - (ncols_needed -
		    FSL_STRLEN_K256) : FSL_STRLEN_K256, uuid ? uuid :
		    "........................................",
		    s->path, idxstr)) == NULL) {
			rc = RC(FSL_RC_RANGE, "fsl_mprintf");
			headln = NULL;







|
|







2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
	 * for the space separator. Same applies if search_str is being shown.
	 */
	ncols_needed = fsl_strlen(type) + fsl_strlen(idxstr) + FSL_STRLEN_K256
	    + (!search_str && (!fsl_strcmp(type, "wiki") ||
	    !fsl_strcmp(type, "tag")  || !fsl_strcmp(type, "ticket") ||
	    (!branch && !fsl_strcmp(type, "technote"))) ? 0 : 1);
	/* If a path has been requested, display it in the headline. */
	if (s->path[1]) {
		if ((headln = fsl_mprintf("%s%c%.*s %s%s", type ? type : "",
		    type ? ' ' : SPINNER[tcx->spin_idx], view->ncols <
		    ncols_needed ? view->ncols - (ncols_needed -
		    FSL_STRLEN_K256) : FSL_STRLEN_K256, uuid ? uuid :
		    "........................................",
		    s->path, idxstr)) == NULL) {
			rc = RC(FSL_RC_RANGE, "fsl_mprintf");
			headln = NULL;
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
	rc = formatln(&wline, &wlen, headln, view->ncols, 0, false);
	if (rc)
		goto end;

	werase(view->window);

	if (screen_is_shared(view) || view->active)
		rx = FNC_HIGHLIGHT;
	if (s->colour)
		c = get_colour(&s->colours, FNC_COLOUR_COMMIT);
	if (c)
		rx |= COLOR_PAIR(c->scheme);
	wattron(view->window, rx);
	waddwstr(view->window, wline);
	while (wlen < view->ncols) {







|







2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
	rc = formatln(&wline, &wlen, headln, view->ncols, 0, false);
	if (rc)
		goto end;

	werase(view->window);

	if (screen_is_shared(view) || view->active)
		rx |= A_REVERSE;
	if (s->colour)
		c = get_colour(&s->colours, FNC_COLOUR_COMMIT);
	if (c)
		rx |= COLOR_PAIR(c->scheme);
	wattron(view->window, rx);
	waddwstr(view->window, wline);
	while (wlen < view->ncols) {
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
	    {""},
	    {""}, /* Diff */
	    {"  Space            ", "  ❬Space❭         "},
	    {"  #                ", "  ❬#❭             "},
	    {"  @                ", "  ❬@❭             "},
	    {"  C-e              ", "  ❬C-e❭           "},
	    {"  C-y              ", "  ❬C-y❭           "},
	    {"  C-p              ", "  ❬C-p❭           "},
	    {"  C-n              ", "  ❬C-n❭           "},
	    {"  [                ", "  ❬[             "},
	    {"  ]                ", "  ❬]❭             "},
	    {"  B                ", "  ❬B❭             "},
	    {"  b                ", "  ❬b❭             "},
	    {"  F                ", "  ❬F❭             "},
	    {"  i                ", "  ❬i❭             "},
	    {"  L                ", "  ❬L❭             "},
	    {"  P                ", "  ❬P❭             "},
	    {"  p                ", "  ❬p❭             "},
	    {"  S                ", "  ❬S❭             "},







<

|
<
<







3439
3440
3441
3442
3443
3444
3445

3446
3447


3448
3449
3450
3451
3452
3453
3454
	    {""},
	    {""}, /* Diff */
	    {"  Space            ", "  ❬Space❭         "},
	    {"  #                ", "  ❬#❭             "},
	    {"  @                ", "  ❬@❭             "},
	    {"  C-e              ", "  ❬C-e❭           "},
	    {"  C-y              ", "  ❬C-y❭           "},

	    {"  C-n              ", "  ❬C-n❭           "},
	    {"  C-p              ", "  ❬C-p❭           "},


	    {"  b                ", "  ❬b❭             "},
	    {"  F                ", "  ❬F❭             "},
	    {"  i                ", "  ❬i❭             "},
	    {"  L                ", "  ❬L❭             "},
	    {"  P                ", "  ❬P❭             "},
	    {"  p                ", "  ❬p❭             "},
	    {"  S                ", "  ❬S❭             "},
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
	    {""},
	    {""},
	    {0}
	};
	static const char *desc[] = {
	    "",
	    "Global",
	    "Open runtime help",
	    "Move selection cursor or page up one line",
	    "Move selection cursor or page down one line",
	    "Scroll view up one page",
	    "Scroll view down one page",
	    "Scroll view up one half page",
	    "Scroll view down one half page",
	    "Jump to first line or start of the view",







|







3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
	    {""},
	    {""},
	    {0}
	};
	static const char *desc[] = {
	    "",
	    "Global",
	    "Open in-app help",
	    "Move selection cursor or page up one line",
	    "Move selection cursor or page down one line",
	    "Scroll view up one page",
	    "Scroll view down one page",
	    "Scroll view up one half page",
	    "Scroll view down one half page",
	    "Jump to first line or start of the view",
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
	    "",
	    "Diff",
	    "Scroll down one page of diff output",
	    "Toggle display of diff view line numbers",
	    "Open prompt to enter line number and navigate to line",
	    "Scroll the view down in the buffer",
	    "Scroll the view up in the buffer",
	    "Navigate to previous file in the diff",
	    "Navigate to next file in the diff",
	    "Navigate to previous hunk in the diff",
	    "Navigate to next hunk in the diff",
	    "Open and populate branch view with all repository branches",
	    "Toggle brief diff display of file indexes and hashes only",
	    "Open prompt to enter file number and navigate to file",
	    "Toggle inversion of diff output",
	    "Toggle display of file line numbers",
	    "Prompt for path to write a patch of the currently viewed diff",
	    "Toggle display of function name in hunk header",
	    "Display side-by-side formatted diff",
	    "Toggle verbosity of diff output",







<

|
<

<







3529
3530
3531
3532
3533
3534
3535

3536
3537

3538

3539
3540
3541
3542
3543
3544
3545
	    "",
	    "Diff",
	    "Scroll down one page of diff output",
	    "Toggle display of diff view line numbers",
	    "Open prompt to enter line number and navigate to line",
	    "Scroll the view down in the buffer",
	    "Scroll the view up in the buffer",

	    "Navigate to next file in the diff",
	    "Navigate to previous file in the diff",

	    "Open and populate branch view with all repository branches",

	    "Open prompt to enter file number and navigate to file",
	    "Toggle inversion of diff output",
	    "Toggle display of file line numbers",
	    "Prompt for path to write a patch of the currently viewed diff",
	    "Toggle display of function name in hunk header",
	    "Display side-by-side formatted diff",
	    "Toggle verbosity of diff output",
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
		    || (stash == 1 && ln == 2) || (stash == 2 && 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],
		    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 */
	cury = curx = 0;







|







3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
		    || (stash == 1 && ln == 2) || (stash == 2 && 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],
		    keys[ln + 1] ? '\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 */
	cury = curx = 0;
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
			break;
		}
		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) {
			rc = sitrep(view, SR_ALL, "-- checkout db busy --");
			break;
		} else if (rc)
			return RC(rc, "fsl_ckout_changes_scan");







|







3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
			break;
		}
		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 in-session. Clear it and notify user.
		 */
		rc = fsl_ckout_changes_scan(f);
		if (rc == FSL_RC_DB) {
			rc = sitrep(view, SR_ALL, "-- checkout db busy --");
			break;
		} else if (rc)
			return RC(rc, "fsl_ckout_changes_scan");
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
	struct fnc_diff_view_state	*s = &view->state.diff;
	int				 rc = FSL_RC_OK;

	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;







|


<
<







4725
4726
4727
4728
4729
4730
4731
4732
4733
4734


4735
4736
4737
4738
4739
4740
4741
	struct fnc_diff_view_state	*s = &view->state.diff;
	int				 rc = FSL_RC_OK;

	set_diff_opt(s);

	s->index.n = 0;
	s->index.idx = 0;
	s->scx.hunk.n = 0;
	s->paths = paths;
	s->selected_entry = commit;


	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;
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
}

/*
 * Set diff options. Precedence is:
 *   1. CLI options passed to 'fnc diff' (see: fnc diff -h)
 *   2. global options set via envvars
 *      - FNC_DIFF_CONTEXT: n
 *      - FNC_DIFF_FLAGS: bCilPqsWw (see: fnc diff -h for all boolean flags)
 *      - FNC_COLOUR_HL_LINE: mono, auto
 *   3. repo options set via 'fnc set'
 *      - same as (2) global
 *   4. fnc default options
 * Input is validated; supplant bogus values with defaults.
 */
static void
set_diff_opt(struct fnc_diff_view_state *s)
{
	char	*opt;
	char	 ch;
	long	 ctx = DEF_DIFF_CTX;
	int	 i = 0;

	/* 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;
	fnc_init.brief ? FLAG_SET(s->diff_flags, FNC_DIFF_BRIEF) : 0;
	s->colour = !fnc_init.nocolour && has_colors();

	if (fnc_init.context.str)  /* fnc diff -x|--context */
		opt = fsl_strdup(fnc_init.context.str);
	else  /* fnc set option */
		opt = fnc_conf_getopt(FNC_DIFF_CONTEXT, false);
	if (opt)







|




















<







4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801

4802
4803
4804
4805
4806
4807
4808
}

/*
 * Set diff options. Precedence is:
 *   1. CLI options passed to 'fnc diff' (see: fnc diff -h)
 *   2. global options set via envvars
 *      - FNC_DIFF_CONTEXT: n
 *      - FNC_DIFF_FLAGS: CilPqsw (see: fnc diff -h for all boolean flags)
 *      - FNC_COLOUR_HL_LINE: mono, auto
 *   3. repo options set via 'fnc set'
 *      - same as (2) global
 *   4. fnc default options
 * Input is validated; supplant bogus values with defaults.
 */
static void
set_diff_opt(struct fnc_diff_view_state *s)
{
	char	*opt;
	char	 ch;
	long	 ctx = DEF_DIFF_CTX;
	int	 i = 0;

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

	s->colour = !fnc_init.nocolour && has_colors();

	if (fnc_init.context.str)  /* fnc diff -x|--context */
		opt = fsl_strdup(fnc_init.context.str);
	else  /* fnc set option */
		opt = fnc_conf_getopt(FNC_DIFF_CONTEXT, false);
	if (opt)
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858

	opt = fnc_conf_getopt(FNC_DIFF_FLAGS, false);
	while (opt && (ch = opt[i++])) {
		switch (ch) {
		case 'C':
			s->colour = false;
			break;
		case 'b':
			FLAG_SET(s->diff_flags, FNC_DIFF_BRIEF);
			break;
		case 'i':
			FLAG_SET(s->diff_flags, FNC_DIFF_INVERT);
			break;
		case 'l':
			if (FLAG_CHK(s->diff_flags, FNC_DIFF_SIDEBYSIDE))
				FLAG_CLR(s->diff_flags, FNC_DIFF_SIDEBYSIDE);
			FLAG_SET(s->diff_flags, FNC_DIFF_LINENO);







<
<
<







4819
4820
4821
4822
4823
4824
4825



4826
4827
4828
4829
4830
4831
4832

	opt = fnc_conf_getopt(FNC_DIFF_FLAGS, false);
	while (opt && (ch = opt[i++])) {
		switch (ch) {
		case 'C':
			s->colour = false;
			break;



		case 'i':
			FLAG_SET(s->diff_flags, FNC_DIFF_INVERT);
			break;
		case 'l':
			if (FLAG_CHK(s->diff_flags, FNC_DIFF_SIDEBYSIDE))
				FLAG_CLR(s->diff_flags, FNC_DIFF_SIDEBYSIDE);
			FLAG_SET(s->diff_flags, FNC_DIFF_LINENO);
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
	char	*line, *st0 = NULL, *st = NULL;
	off_t	 off = 0;
	uint32_t idx = 0;
	int	 rc = 0;

	s->maxx = 0;

	free_index(&s->hundex, false);
	free_index(&s->index, true);
	free(s->dlines);
	s->dlines = fsl_malloc(sizeof(enum line_type));
	if (s->dlines == NULL)
		return RC(FSL_RC_ERROR, "fsl_malloc");
	s->ndlines = 0;
	free(s->line_offsets);
	s->line_offsets = fsl_malloc(sizeof(off_t));







<
|







4877
4878
4879
4880
4881
4882
4883

4884
4885
4886
4887
4888
4889
4890
4891
	char	*line, *st0 = NULL, *st = NULL;
	off_t	 off = 0;
	uint32_t idx = 0;
	int	 rc = 0;

	s->maxx = 0;


	free_index(&s->index);
	free(s->dlines);
	s->dlines = fsl_malloc(sizeof(enum line_type));
	if (s->dlines == NULL)
		return RC(FSL_RC_ERROR, "fsl_malloc");
	s->ndlines = 0;
	free(s->line_offsets);
	s->line_offsets = fsl_malloc(sizeof(off_t));
4948
4949
4950
4951
4952
4953
4954























4955
4956
4957
4958
4959
4960
4961
	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;
























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

	/*







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
	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;

	/*
	 * Delay assigning diff headline labels (i.e., diff id1 id2) 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;
	}

	/*
5011
5012
5013
5014
5015
5016
5017
5018

5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
			lineno = s->nlines + (idx ? 1 : 0);
			s->index.lineno[idx++] = lineno;
		}
		off += n;
		rc = add_line_offset(&s->line_offsets, &s->nlines, off);
		if (rc)
			goto end;
		/* Save line offsets for each hunk. */

		if (s->nlines < s->ndlines - 1 &&
		    s->dlines[s->nlines] == LINE_DIFF_HUNK) {
			s->hundex.lineno = fsl_realloc(s->hundex.lineno,
			    (s->hundex.n + 1) * sizeof(size_t));
			if (s->hundex.lineno == NULL) {
				rc = RC(FSL_RC_ERROR, "realloc");
				goto end;
			}
			s->hundex.lineno[s->hundex.n++] = s->nlines;
		}
	}
	--s->nlines;  /* Don't count EOF '\n' */
end:
	fsl_free(st0);
	fsl_buffer_clear(&s->buf);
	if (s->f && fflush(s->f) != 0 && rc == 0)







|
>
|
|
|
|
|



|







5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
			lineno = s->nlines + (idx ? 1 : 0);
			s->index.lineno[idx++] = lineno;
		}
		off += n;
		rc = add_line_offset(&s->line_offsets, &s->nlines, off);
		if (rc)
			goto end;
		/* If in stash mode, save line offsets for each hunk. */
		if (s->diff_mode == STASH_INTERACTIVE &&
		    s->nlines < s->ndlines - 1 &&
		    s->dlines[s->nlines] == LINE_DIFF_CHUNK) {
			s->scx.hunk.lineno = fsl_realloc(s->scx.hunk.lineno,
			    (s->scx.hunk.n + 1) * sizeof(size_t));
			if (s->scx.hunk.lineno == NULL) {
				rc = RC(FSL_RC_ERROR, "realloc");
				goto end;
			}
			s->scx.hunk.lineno[s->scx.hunk.n++] = s->nlines;
		}
	}
	--s->nlines;  /* Don't count EOF '\n' */
end:
	fsl_free(st0);
	fsl_buffer_clear(&s->buf);
	if (s->f && fflush(s->f) != 0 && rc == 0)
5083
5084
5085
5086
5087
5088
5089
5090
5091
5092
5093
5094
5095
5096
5097
		/*
		 * Write all index headers irrespective of whether the file has
		 * any selected hunks, otherwise we end up with orphaned hunks.
		 */
		if (s->dlines[lineno] == LINE_DIFF_INDEX ||
		    s->dlines[lineno] == LINE_DIFF_META)
			drop = false;
		if (s->dlines[lineno++] == LINE_DIFF_HUNK) {
			/* Stash diff and hunk not marked for stash? Drop it. */
			if (s->stash == HUNK_STASH)
				drop = !BIT_CHK(s->scx.stash, idx) ?
				    true : false;
			else  /* ckout diff & hunk marked for stash? drop it */
				drop = BIT_CHK(s->scx.stash, idx) ?
				    true : false;







|







5080
5081
5082
5083
5084
5085
5086
5087
5088
5089
5090
5091
5092
5093
5094
		/*
		 * Write all index headers irrespective of whether the file has
		 * any selected hunks, otherwise we end up with orphaned hunks.
		 */
		if (s->dlines[lineno] == LINE_DIFF_INDEX ||
		    s->dlines[lineno] == LINE_DIFF_META)
			drop = false;
		if (s->dlines[lineno++] == LINE_DIFF_CHUNK) {
			/* Stash diff and hunk not marked for stash? Drop it. */
			if (s->stash == HUNK_STASH)
				drop = !BIT_CHK(s->scx.stash, idx) ?
				    true : false;
			else  /* ckout diff & hunk marked for stash? drop it */
				drop = BIT_CHK(s->scx.stash, idx) ?
				    true : false;
5453
5454
5455
5456
5457
5458
5459
5460
5461
5462


5463









5464
5465
5466
5467
5468
5469
5470
		fsl_deck_F_next(&d1, &fc1);
	}

	fsl_deck_F_next(&d2, &fc2);
	while (fc1 || fc2) {
		const fsl_card_F	*a = NULL, *b = NULL;
		fsl_ckout_change_e	 change = FSL_CKOUT_CHANGE_NONE;
		bool			 diff;

		diff = (!s->paths || TAILQ_EMPTY(s->paths)) ?


		    true : path_to_diff(s->paths, fc1, fc2);










		if (!fc1)	/* File added. */
			different = 1;
		else if (!fc2)	/* File deleted. */
			different = -1;
		else		/* Same filename in both versions. */
			different = fsl_strcmp(fc1->name, fc2->name);







|

|
>
>
|
>
>
>
>
>
>
>
>
>







5450
5451
5452
5453
5454
5455
5456
5457
5458
5459
5460
5461
5462
5463
5464
5465
5466
5467
5468
5469
5470
5471
5472
5473
5474
5475
5476
5477
5478
		fsl_deck_F_next(&d1, &fc1);
	}

	fsl_deck_F_next(&d2, &fc2);
	while (fc1 || fc2) {
		const fsl_card_F	*a = NULL, *b = NULL;
		fsl_ckout_change_e	 change = FSL_CKOUT_CHANGE_NONE;
		bool			 diff = true;

		if (s->paths != NULL && !TAILQ_EMPTY(s->paths)) {
			struct fnc_pathlist_entry *pe;
			diff = false;
			TAILQ_FOREACH(pe, s->paths, entry)
				if (!fsl_strcmp(pe->path, fc1->name) ||
				    !fsl_strcmp(pe->path, fc2->name) ||
				    !fsl_strncmp(pe->path, fc1->name,
				    pe->pathlen) || !fsl_strncmp(pe->path,
				    fc2->name, pe->pathlen)) {
					diff = true;
					break;
				}
		}

		if (!fc1)	/* File added. */
			different = 1;
		else if (!fc2)	/* File deleted. */
			different = -1;
		else		/* Same filename in both versions. */
			different = fsl_strcmp(fc1->name, fc2->name);
5509
5510
5511
5512
5513
5514
5515
5516
5517
5518
5519
5520
5521
5522
5523
5524
5525
5526
5527
5528
5529
5530
5531
5532
5533
5534
5535
5536
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
5550
5551
5552
	}
end:
	fsl_deck_finalize(&d1);
	fsl_deck_finalize(&d2);
	return rc;
}

/*
 * Iterate path list paths and return true if either:
 *   1. both fc1 and fc2 have the same filename and either their full path or
 *      parent dir(s) match a path in the list
 *   2. fc1 has been deleted and is a full or partial match as per (1)
 *   3. fc2 has been added and is a full or partial match as per (1)
 * Otherwise return false.
 */
static bool
path_to_diff(const struct fnc_pathlist_head *paths, const fsl_card_F *fc1,
    const fsl_card_F *fc2)
{
	struct fnc_pathlist_entry *pe;

	TAILQ_FOREACH(pe, paths, entry)
		/* path matches modified file */
		if ((!fsl_strcmp(fc1->name, fc2->name) &&
		    !fsl_strcmp(pe->path, fc1->name)) ||
		    /* path or parent dir matches deleted or modified fc1 */
		    (fsl_strcmp(fc1->name, fc2->name) <= 0 &&
		    !fsl_strncmp(pe->path, fc1->name, pe->pathlen)) ||
		    /* path or parent dir matches added or modified fc2 */
		    (fsl_strcmp(fc1->name, fc2->name) >= 0 &&
		    !fsl_strncmp(pe->path, fc2->name, pe->pathlen)))
			return true;

	/* file not in requested paths to diff */
	return false;
}

/*
 * Diff local changes on disk in the current checkout against either a previous
 * commit or, if no version has been supplied, the current checkout.
 *   buf  output buffer in which diff content is appended
 *   vid  repository database record id of the version to diff against
 * diff_flags, context, and sbs are the same parameters as diff_file_artifact()
 * nb. This routine is only called with 'fnc diff [hash]'; that is, one or







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







5517
5518
5519
5520
5521
5522
5523






























5524
5525
5526
5527
5528
5529
5530
	}
end:
	fsl_deck_finalize(&d1);
	fsl_deck_finalize(&d2);
	return rc;
}































/*
 * Diff local changes on disk in the current checkout against either a previous
 * commit or, if no version has been supplied, the current checkout.
 *   buf  output buffer in which diff content is appended
 *   vid  repository database record id of the version to diff against
 * diff_flags, context, and sbs are the same parameters as diff_file_artifact()
 * nb. This routine is only called with 'fnc diff [hash]'; that is, one or
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
5621
5622
5623
5624
	rc = fsl_cx_prepare(f, st, "%b", &sql);
	if (rc) {
		rc = RC(rc, "fsl_cx_prepare");
		goto yield;
	}

	while ((rc = fsl_stmt_step(st)) == FSL_RC_STEP_ROW) {
		const char		*path, *ogpath;
		int			 deleted, changed, added, fid, symlink;
		fsl_ckout_change_e	 change;
		bool			 diff = true;

		path = fsl_stmt_g_text(st, 0, NULL);
		ogpath = fsl_stmt_g_text(st, 1, NULL);
		deleted = fsl_stmt_g_int32(st, 2);
		changed = fsl_stmt_g_int32(st, 3);
		added = fsl_stmt_g_int32(st, 4);
		fid = fsl_stmt_g_int32(st, 5);







|
|
|
|







5585
5586
5587
5588
5589
5590
5591
5592
5593
5594
5595
5596
5597
5598
5599
5600
5601
5602
	rc = fsl_cx_prepare(f, st, "%b", &sql);
	if (rc) {
		rc = RC(rc, "fsl_cx_prepare");
		goto yield;
	}

	while ((rc = fsl_stmt_step(st)) == FSL_RC_STEP_ROW) {
		const char	*path, *ogpath;
		int		 deleted, changed, added, fid, symlink;
		enum		 fsl_ckout_change_e change;
		bool		 diff = true;

		path = fsl_stmt_g_text(st, 0, NULL);
		ogpath = fsl_stmt_g_text(st, 1, NULL);
		deleted = fsl_stmt_g_int32(st, 2);
		changed = fsl_stmt_g_int32(st, 3);
		added = fsl_stmt_g_int32(st, 4);
		fid = fsl_stmt_g_int32(st, 5);
5638
5639
5640
5641
5642
5643
5644
5645
5646
5647
5648
5649
5650
5651
5652
			change = FSL_CKOUT_CHANGE_ADDED;
		} else if (changed == 3) {
			fid = 0;
			change = FSL_CKOUT_CHANGE_MERGE_ADD;
		} else if (changed == 5) {
			fid = 0;
			change = FSL_CKOUT_CHANGE_INTEGRATE_ADD;
		} else if (ogpath && fsl_strcmp(ogpath, path))
			change = FSL_CKOUT_CHANGE_RENAMED;
		else
			change = FSL_CKOUT_CHANGE_MOD;

		/*
		 * For changed files of which this checkout is already aware,
		 * grab their hash to make comparisons. For removed files, if







|







5616
5617
5618
5619
5620
5621
5622
5623
5624
5625
5626
5627
5628
5629
5630
			change = FSL_CKOUT_CHANGE_ADDED;
		} else if (changed == 3) {
			fid = 0;
			change = FSL_CKOUT_CHANGE_MERGE_ADD;
		} else if (changed == 5) {
			fid = 0;
			change = FSL_CKOUT_CHANGE_INTEGRATE_ADD;
		} else if (fsl_strcmp(ogpath, path))
			change = FSL_CKOUT_CHANGE_RENAMED;
		else
			change = FSL_CKOUT_CHANGE_MOD;

		/*
		 * For changed files of which this checkout is already aware,
		 * grab their hash to make comparisons. For removed files, if
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777

5778

5779
5780
5781
5782
5783
5784

5785
5786
5787
5788
5789
5790

5791

5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809




5810
5811
5812
5813
5814
5815
5816
5817
5818
5819
5820
5821
5822
5823
5824
5825

5826

5827
5828
5829

5830
5831
5832


5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851
5852
5853
5854
5855
5856
5857
5858
5859
5860
5861
5862
5863
5864
5865
5866
5867
5868
5869
 *   zplus       file name of the file being diffed
 *   xplus       hex hash of the file named zplus
 *   diff_flags  bitwise flags to control the diff
 *   change      enum denoting the versioning change of the file
 */
static int
write_diff_meta(struct fnc_diff_view_state *s, const char *zminus,
    fsl_uuid_cstr xminus, const char *zplus, fsl_uuid_cstr xplus,
    const fsl_ckout_change_e change)
{
	const char	*index, *plus, *minus;
	int		 c, rc = FSL_RC_OK;
	enum line_type	 i;

	index = zplus ? zplus : (zminus ? zminus : NULL_DEVICE);

	switch (change) {
	case FSL_CKOUT_CHANGE_MERGE_ADD:

	case FSL_CKOUT_CHANGE_INTEGRATE_ADD:

	case FSL_CKOUT_CHANGE_ADDED:
		minus = NULL_DEVICE;
		plus = xplus;
		zminus = NULL_DEVICE;
		break;
	case FSL_CKOUT_CHANGE_MISSING:

	case FSL_CKOUT_CHANGE_REMOVED:
		minus = xminus;
		plus = NULL_DEVICE;
		zplus = NULL_DEVICE;
		break;
	case FSL_CKOUT_CHANGE_RENAMED:

	case FSL_CKOUT_CHANGE_MOD:

	default:
		minus = xminus;
		plus = xplus;
		break;
	}

	zminus = zminus ? zminus : zplus;

	if FLAG_CHK(s->diff_flags, FNC_DIFF_INVERT) {
		const char *tmp = minus;

		minus = plus;
		plus = tmp;
		tmp = zminus;
		zminus = zplus;
		zplus = tmp;
	}





	if (s->buf.used) {
		/*
		 * There're previous files in the diff--I don't like
		 * Git's contiguous lines between files--so add a new
		 * line before this file's 'Index: file/path' line.
		 */
		rc = add_line_type(&s->dlines, &s->ndlines, LINE_BLANK);
		if (!rc)
			rc = fsl_buffer_append(&s->buf, "\n", 1);
	}
	for (c = 0, i = 10; i < 14; ++c) {
		rc = add_line_type(&s->dlines, &s->ndlines, i);
		if (rc)
			return rc;
		i += (c > 3 || c % 2) ? 1 : 0;
	}

	rc = fsl_buffer_appendf(&s->buf, "Index: %s\n%.71c\n", index, '=');

	if (rc)
		return rc;
	rc = fsl_buffer_appendf(&s->buf, "hash - %s\nhash + %s\n", minus, plus);

	if (rc)
		return rc;
	rc = fsl_buffer_appendf(&s->buf, "--- %s\n+++ %s\n", zminus, zplus);



	return rc;
}

/*
 * The diff_file_artifact() counterpart that diffs actual files on disk rather
 * than file artifacts in the Fossil repository's blob table.
 *   buf      output buffer in which diff output will be appended
 *   bminus   blob containing content of the versioned file being diffed against
 *   zminus   filename of bminus
 *   xminus   hex UUID containing the SHA{1,3} hash of the file named zminus
 *   abspath  absolute path to the file on disk being diffed
 *   change   enum denoting the versioning change of the file
 * diff_flags, context, and sbs are the same parameters as diff_file_artifact()
 */
static int
diff_file(struct fnc_diff_view_state *s, fsl_buffer *bminus,
    const char *zminus, const char *zplus, fsl_uuid_cstr xminus,
    const char *abspath, const fsl_ckout_change_e change)
{
	fsl_cx		*const f = fcli_cx();
	fsl_buffer	 bplus = fsl_buffer_empty;
	fsl_buffer	 xplus = fsl_buffer_empty;
	int		 rc = 0;

	/*
	 * If it exists, read content of abspath to diff EXCEPT for the content
	 * of 'fossil rm FILE' files because they will either: (1) have the same
	 * content as the versioned file's blob in bminus or (2) have changes.
	 * As a result, the upcoming call to fnc_diff_text_to_buffer() _will_
	 * (1) produce an empty diff or (2) show the differences; neither are
	 * expected behaviour because the SCM has been instructed to remove the
	 * file; therefore, the diff should display the versioned file content
	 * as being entirely removed. With this check, fnc now contrasts the
	 * behaviour of fossil(1), which produces the abovementioned unexpected
	 * output described in (1) and (2).
	 */







|
|


|
<





>

>






>






>

>










<







>
>
>
>
|
|
|
|
|
|
|
|
|
|
|
|
<
<
|
|
>
|
>
|
<
|
>
|
<
|
>
>
















|
|
|










|







5738
5739
5740
5741
5742
5743
5744
5745
5746
5747
5748
5749

5750
5751
5752
5753
5754
5755
5756
5757
5758
5759
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783

5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806


5807
5808
5809
5810
5811
5812

5813
5814
5815

5816
5817
5818
5819
5820
5821
5822
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851
5852
5853
5854
5855
 *   zplus       file name of the file being diffed
 *   xplus       hex hash of the file named zplus
 *   diff_flags  bitwise flags to control the diff
 *   change      enum denoting the versioning change of the file
 */
static int
write_diff_meta(struct fnc_diff_view_state *s, const char *zminus,
    fsl_uuid_str xminus, const char *zplus, fsl_uuid_str xplus,
    enum fsl_ckout_change_e change)
{
	const char	*index, *plus, *minus;
	int		 rc = FSL_RC_OK;


	index = zplus ? zplus : (zminus ? zminus : NULL_DEVICE);

	switch (change) {
	case FSL_CKOUT_CHANGE_MERGE_ADD:
		/* FALL THROUGH */
	case FSL_CKOUT_CHANGE_INTEGRATE_ADD:
		/* FALL THROUGH */
	case FSL_CKOUT_CHANGE_ADDED:
		minus = NULL_DEVICE;
		plus = xplus;
		zminus = NULL_DEVICE;
		break;
	case FSL_CKOUT_CHANGE_MISSING:
		/* FALL THROUGH */
	case FSL_CKOUT_CHANGE_REMOVED:
		minus = xminus;
		plus = NULL_DEVICE;
		zplus = NULL_DEVICE;
		break;
	case FSL_CKOUT_CHANGE_RENAMED:
		/* FALL THROUGH */
	case FSL_CKOUT_CHANGE_MOD:
		/* FALL THROUGH */
	default:
		minus = xminus;
		plus = xplus;
		break;
	}

	zminus = zminus ? zminus : zplus;

	if FLAG_CHK(s->diff_flags, FNC_DIFF_INVERT) {
		const char *tmp = minus;

		minus = plus;
		plus = tmp;
		tmp = zminus;
		zminus = zplus;
		zplus = tmp;
	}

	if (!FLAG_CHK(s->diff_flags, FNC_DIFF_BRIEF)) {
		int c;
		enum line_type i;

		if (s->buf.used) {
			/*
			 * There're previous files in the diff--I don't like
			 * Git's contiguous lines between files--so add a new
			 * line before this file's 'Index: file/path' line.
			 */
			rc = add_line_type(&s->dlines, &s->ndlines, LINE_BLANK);
			if (!rc)
				rc = fsl_buffer_append(&s->buf, "\n", 1);
		}
		for (c = 0, i = 10; !rc && i < 14; ++c) {
			rc = add_line_type(&s->dlines, &s->ndlines, i);


			i += (c > 3 || c % 2) ? 1 : 0;
		}
		if (!rc)
			rc = fsl_buffer_appendf(&s->buf, "Index: %s\n%.71c\n",
			    index, '=');
		if (!rc)

			rc = fsl_buffer_appendf(&s->buf,
			    "hash - %s\nhash + %s\n", minus, plus);
		if (!rc)

			rc = fsl_buffer_appendf(&s->buf, "--- %s\n+++ %s\n",
			    zminus, zplus);
	}

	return rc;
}

/*
 * The diff_file_artifact() counterpart that diffs actual files on disk rather
 * than file artifacts in the Fossil repository's blob table.
 *   buf      output buffer in which diff output will be appended
 *   bminus   blob containing content of the versioned file being diffed against
 *   zminus   filename of bminus
 *   xminus   hex UUID containing the SHA{1,3} hash of the file named zminus
 *   abspath  absolute path to the file on disk being diffed
 *   change   enum denoting the versioning change of the file
 * diff_flags, context, and sbs are the same parameters as diff_file_artifact()
 */
static int
diff_file(struct fnc_diff_view_state *s, fsl_buffer *bminus, const char *zminus,
    const char *zplus, fsl_uuid_str xminus, const char *abspath,
    enum fsl_ckout_change_e change)
{
	fsl_cx		*const f = fcli_cx();
	fsl_buffer	 bplus = fsl_buffer_empty;
	fsl_buffer	 xplus = fsl_buffer_empty;
	int		 rc = 0;

	/*
	 * If it exists, read content of abspath to diff EXCEPT for the content
	 * of 'fossil rm FILE' files because they will either: (1) have the same
	 * content as the versioned file's blob in bminus or (2) have changes.
	 * As a result, the upcoming call to fsl_diff_text_to_buffer() _will_
	 * (1) produce an empty diff or (2) show the differences; neither are
	 * expected behaviour because the SCM has been instructed to remove the
	 * file; therefore, the diff should display the versioned file content
	 * as being entirely removed. With this check, fnc now contrasts the
	 * behaviour of fossil(1), which produces the abovementioned unexpected
	 * output described in (1) and (2).
	 */
5893
5894
5895
5896
5897
5898
5899

5900

5901
5902
5903
5904
5905
5906
5907
5908
5909
5910
5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
5925
5926
5927
5928
5929
5930
5931
5932
5933
5934
5935




5936
5937
5938
5939
5940
5941
5942
5943
5944
	case NULL_DEVICELEN:
		switch (fsl_config_get_int32(f, FSL_CONFDB_REPO,
		    FSL_HPOLICY_AUTO, "hash-policy")) {
		case FSL_HPOLICY_SHA1:
			rc = fsl_sha1sum_buffer(&bplus, &xplus);
			break;
		case FSL_HPOLICY_AUTO:

		case FSL_HPOLICY_SHA3:

		case FSL_HPOLICY_SHA3_ONLY:
			rc = fsl_sha3sum_buffer(&bplus, &xplus);
			break;
		}
		break;
	default:
		RC(FSL_RC_SIZE_MISMATCH, "invalid artifact uuid [%s]", xminus);
		goto end;
	}
	if (rc)
		goto end;

	/*
	 * XXX Edge case where the current checkout has a changed file that is
	 * now the same as it is in another version and the checkout is diffed
	 * against said version. diff_checkout() declares the file 'changed'
	 * because fsl_ckout_changes_scan() picks it up, but because it's the
	 * same as the other version the diff will be empty. As such, don't
	 * draw the index header UNLESS the file has been renamed.
	 */
	if (!fsl_uuidcmp(xminus, fsl_buffer_cstr(&xplus)) &&
	    change != FSL_CKOUT_CHANGE_RENAMED)
		goto end;

	if (s->buf.used) {
		s->index.offset = fsl_realloc(s->index.offset,
		    (s->index.n + 1) * sizeof(off_t));
		s->index.offset[s->index.n++] = s->buf.used;
	}
	rc = write_diff_meta(s, zminus, xminus, zplus, fsl_buffer_cstr(&xplus),
	    change);
	if (rc)
		goto end;

	if (!FLAG_CHK(s->diff_flags, FNC_DIFF_BRIEF) &&




	    (FLAG_CHK(s->diff_flags, FNC_DIFF_VERBOSE) ||
	    (bminus->used && bplus.used)))
		rc = fnc_diff_text_to_buffer(bminus, &bplus, &s->buf,
		    &s->dlines, &s->ndlines, s->context, s->sbs,
		    s->diff_flags);
end:
	fsl_buffer_clear(&bplus);
	fsl_buffer_clear(&xplus);
	return rc;







>

>












<
<
<
<
<
<
<
<
<
<
<
<





|




|
>
>
>
>
|
|







5879
5880
5881
5882
5883
5884
5885
5886
5887
5888
5889
5890
5891
5892
5893
5894
5895
5896
5897
5898
5899
5900












5901
5902
5903
5904
5905
5906
5907
5908
5909
5910
5911
5912
5913
5914
5915
5916
5917
5918
5919
5920
5921
5922
5923
5924
	case NULL_DEVICELEN:
		switch (fsl_config_get_int32(f, FSL_CONFDB_REPO,
		    FSL_HPOLICY_AUTO, "hash-policy")) {
		case FSL_HPOLICY_SHA1:
			rc = fsl_sha1sum_buffer(&bplus, &xplus);
			break;
		case FSL_HPOLICY_AUTO:
			/* FALL THROUGH */
		case FSL_HPOLICY_SHA3:
			/* FALL THROUGH */
		case FSL_HPOLICY_SHA3_ONLY:
			rc = fsl_sha3sum_buffer(&bplus, &xplus);
			break;
		}
		break;
	default:
		RC(FSL_RC_SIZE_MISMATCH, "invalid artifact uuid [%s]", xminus);
		goto end;
	}
	if (rc)
		goto end;













	if (s->buf.used) {
		s->index.offset = fsl_realloc(s->index.offset,
		    (s->index.n + 1) * sizeof(off_t));
		s->index.offset[s->index.n++] = s->buf.used;
	}
	rc = write_diff_meta(s, zminus, xminus, zplus, fsl_buffer_str(&xplus),
	    change);
	if (rc)
		goto end;

	if FLAG_CHK(s->diff_flags, FNC_DIFF_BRIEF) {
		rc = fsl_buffer_compare(bminus, &bplus);
		if (!rc)
			rc = fsl_buffer_appendf(&s->buf, "CHANGED -> %s\n",
			    zminus);
	} else if (FLAG_CHK(s->diff_flags, FNC_DIFF_VERBOSE) ||
	    (bminus->used && bplus.used))
		rc = fnc_diff_text_to_buffer(bminus, &bplus, &s->buf,
		    &s->dlines, &s->ndlines, s->context, s->sbs,
		    s->diff_flags);
end:
	fsl_buffer_clear(&bplus);
	fsl_buffer_clear(&xplus);
	return rc;
6065
6066
6067
6068
6069
6070
6071
6072
6073
6074
6075
6076
6077
6078
6079
 *   change      enum denoting the versioning change of the file
 *   diff_flags  bitwise flags to control the diff
 *   context     the number of context lines to surround changes
 *   sbs	 number of columns in which to display each side-by-side diff
 */
static int
diff_file_artifact(struct fnc_diff_view_state *s, fsl_id_t vid1,
    const fsl_card_F *a, const fsl_card_F *b, const fsl_ckout_change_e change)
{
	fsl_cx		*const f = fcli_cx();
	fsl_stmt	 stmt = fsl_stmt_empty;
	fsl_buffer	 fbuf1 = fsl_buffer_empty;
	fsl_buffer	 fbuf2 = fsl_buffer_empty;
	char		*zminus0 = NULL, *zplus0 = NULL;
	const char	*zplus = NULL, *zminus = NULL;







|







6045
6046
6047
6048
6049
6050
6051
6052
6053
6054
6055
6056
6057
6058
6059
 *   change      enum denoting the versioning change of the file
 *   diff_flags  bitwise flags to control the diff
 *   context     the number of context lines to surround changes
 *   sbs	 number of columns in which to display each side-by-side diff
 */
static int
diff_file_artifact(struct fnc_diff_view_state *s, fsl_id_t vid1,
    const fsl_card_F *a, const fsl_card_F *b, enum fsl_ckout_change_e change)
{
	fsl_cx		*const f = fcli_cx();
	fsl_stmt	 stmt = fsl_stmt_empty;
	fsl_buffer	 fbuf1 = fsl_buffer_empty;
	fsl_buffer	 fbuf2 = fsl_buffer_empty;
	char		*zminus0 = NULL, *zplus0 = NULL;
	const char	*zplus = NULL, *zminus = NULL;
6112
6113
6114
6115
6116
6117
6118
6119
6120
6121
6122
6123
6124
6125
6126
6127
6128
6129
6130
		else if (rc) {
			rc = RC(rc, "fsl_stmt_step");
			goto end;
		}
		xminus0 = fsl_rid_to_uuid(f, vid1);
		xminus = xminus0;
		fsl_stmt_finalize(&stmt);
		rc = fsl_content_get(f, vid1, &fbuf1);
		if (rc) {
			rc = RC(rc, "fsl_context_get(%s)", vid1);
			goto end;
		}
	}
	if (b) {
		rc = fsl_card_F_content(f, b, &fbuf2);
		if (rc)
			goto end;
		zplus = b->name;
		xplus = b->uuid;







|
<
<
<
<







6092
6093
6094
6095
6096
6097
6098
6099




6100
6101
6102
6103
6104
6105
6106
		else if (rc) {
			rc = RC(rc, "fsl_stmt_step");
			goto end;
		}
		xminus0 = fsl_rid_to_uuid(f, vid1);
		xminus = xminus0;
		fsl_stmt_finalize(&stmt);
		fsl_content_get(f, vid1, &fbuf1);




	}
	if (b) {
		rc = fsl_card_F_content(f, b, &fbuf2);
		if (rc)
			goto end;
		zplus = b->name;
		xplus = b->uuid;
6146
6147
6148
6149
6150
6151
6152
6153
6154
6155
6156
6157
6158
6159
6160
6161
6162
6163
6164
6165
6166
6167
6168
6169
6170
6171
6172
6173
6174
6175
6176
6177
6178
		else if (rc) {
			rc = RC(rc, "fsl_stmt_step");
			goto end;
		}
		xplus0 = fsl_rid_to_uuid(f, vid2);
		xplus = xplus0;
		fsl_stmt_finalize(&stmt);
		rc = fsl_content_get(f, vid2, &fbuf2);
		if (rc) {
			rc = RC(rc, "fsl_context_get(%s)", vid2);
			goto end;
		}
	}

	if (s->buf.used) {
		s->index.offset = fsl_realloc(s->index.offset,
		    (s->index.n + 1) * sizeof(off_t));
		s->index.offset[s->index.n++] = s->buf.used +
		    s->index.offset[0];
	}
	rc = write_diff_meta(s, zminus, xminus, zplus, xplus, change);
	if (rc)
		goto end;

	if (!FLAG_CHK(s->diff_flags, FNC_DIFF_BRIEF) &&
	    (FLAG_CHK(s->diff_flags, FNC_DIFF_VERBOSE) || (a && b)))
		rc = fnc_diff_text_to_buffer(&fbuf1, &fbuf2, &s->buf,
		    &s->dlines, &s->ndlines, s->context, s->sbs,
		    s->diff_flags);
	if (rc)
		RC(rc, "%s: fnc_diff_text_to_buffer\n"
		    " -> %s [%s]\n -> %s [%s]", fsl_rc_cstr(rc),
		    a ? a->name : NULL_DEVICE, a ? a->uuid : NULL_DEVICE,







|
<
<
<
<












<
|







6122
6123
6124
6125
6126
6127
6128
6129




6130
6131
6132
6133
6134
6135
6136
6137
6138
6139
6140
6141

6142
6143
6144
6145
6146
6147
6148
6149
		else if (rc) {
			rc = RC(rc, "fsl_stmt_step");
			goto end;
		}
		xplus0 = fsl_rid_to_uuid(f, vid2);
		xplus = xplus0;
		fsl_stmt_finalize(&stmt);
		fsl_content_get(f, vid2, &fbuf2);




	}

	if (s->buf.used) {
		s->index.offset = fsl_realloc(s->index.offset,
		    (s->index.n + 1) * sizeof(off_t));
		s->index.offset[s->index.n++] = s->buf.used +
		    s->index.offset[0];
	}
	rc = write_diff_meta(s, zminus, xminus, zplus, xplus, change);
	if (rc)
		goto end;


	if (FLAG_CHK(s->diff_flags, FNC_DIFF_VERBOSE) || (a && b))
		rc = fnc_diff_text_to_buffer(&fbuf1, &fbuf2, &s->buf,
		    &s->dlines, &s->ndlines, s->context, s->sbs,
		    s->diff_flags);
	if (rc)
		RC(rc, "%s: fnc_diff_text_to_buffer\n"
		    " -> %s [%s]\n -> %s [%s]", fsl_rc_cstr(rc),
		    a ? a->name : NULL_DEVICE, a ? a->uuid : NULL_DEVICE,
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
6274
6275
6276
6277
	return rc;
}

static int
show_diff(struct fnc_view *view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	char				*id2, *id1;
	enum fnc_diff_type		 diff = s->selected_entry->diff_type;

	/* Some diffs (e.g., technote, tag) have no parent hash to display. */
	id1 = s->id1 ? s->id1 : "/dev/null";




	/* If diffing the work tree, we have no hash to display for it. */







	id2 = !s->id2 || diff == FNC_DIFF_CKOUT ? "(checkout)" : s->id2;








	return write_diff(view, id1, id2);
}

static int
write_diff(struct fnc_view *view, const char *id1, const char *id2)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	regmatch_t			*regmatch = &view->regmatch;
	struct fnc_colour		*c = NULL;
	wchar_t				*wcstr;
	char				*line;
	static char			 pct[MAX_PCT_LEN];
	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];
	if (fseeko(s->f, line_offset, SEEK_SET))
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "fseeko");

	werase(view->window);






	ln = s->gtl ? s->gtl : s->lineno + s->selected_line;
	percent = 100.00 * ln / nlines;
	pctlen = snprintf(pct, MAX_PCT_LEN, "%.*lf%%",
	    percent > 99.99 ? 0 : 2, percent);
	if (pctlen < 0 || pctlen >= MAX_PCT_LEN)
		return RC(fsl_errno_to_rc(errno, FSL_RC_RANGE), "snprintf");


	line = fsl_mprintf("[%d/%d] diff %.40s %.40s", ln, nlines, id1, id2);
	if (line == NULL)
		return RC(FSL_RC_RANGE, "fsl_mprintf");

	rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0, false);
	fsl_free(line);
	line = NULL;

	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);
		if (linelen == -1) {
			if (feof(s->f)) {
				s->eof = true;
				break;







|
<


|
>
>

>
|
>
>
>
>
>
>
>
|
>
>
>
>
|
>
>
>
|



|






<



|
<
<












>
>
>
>
>
|
|
|
|
|
|
>

|
|
|
<
|
|
<
>
|
|

|
<
|
|
<
|
|
|
<
|
|
|
|
|
<
|

|
|
>


>







6158
6159
6160
6161
6162
6163
6164
6165

6166
6167
6168
6169
6170
6171
6172
6173
6174
6175
6176
6177
6178
6179
6180
6181
6182
6183
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
	return rc;
}

static int
show_diff(struct fnc_view *view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	char				*headln, *id2, *id1 = NULL;


	/* Some diffs (e.g., technote, tag) have no parent hash to display. */
	id1 = fsl_strdup(s->id1 ? s->id1 : "/dev/null");
	if (id1 == NULL)
		return RC(FSL_RC_ERROR, "fsl_strdup");

	/*
	 * If diffing the work tree, we have no hash to display for it.
	 * XXX Display "work tree" or "checkout" or "/dev/null" for clarity?
	 */
	id2 = fsl_strdup(s->id2 ? s->id2 : "");
	if (id2 == NULL) {
		fsl_free(id1);
		return RC(FSL_RC_ERROR, "fsl_strdup");
	}

	if ((headln = fsl_mprintf("diff %.40s %.40s", id1, id2)) == NULL) {
		fsl_free(id1);
		fsl_free(id2);
		return RC(FSL_RC_RANGE, "fsl_mprintf");
	}

	fsl_free(id1);
	fsl_free(id2);
	return write_diff(view, headln);
}

static int
write_diff(struct fnc_view *view, char *headln)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	regmatch_t			*regmatch = &view->regmatch;
	struct fnc_colour		*c = NULL;
	wchar_t				*wcstr;
	char				*line;

	size_t				 lidx, linesz = 0;
	ssize_t				 linelen;
	off_t				 line_offset;
	attr_t				 rx = A_BOLD;


	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];
	if (fseeko(s->f, line_offset, SEEK_SET))
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "fseeko");

	werase(view->window);

	if (headln) {
		static char	pct[MAX_PCT_LEN];
		double		percent;
		int		ln, pctlen;

		ln = s->gtl ? s->gtl : s->lineno + s->selected_line;
		percent = 100.00 * ln / nlines;
		pctlen = snprintf(pct, MAX_PCT_LEN, "%.*lf%%",
		    percent > 99.99 ? 0 : 2, percent);
		if (pctlen < 0 || pctlen >= MAX_PCT_LEN)
			return RC(fsl_errno_to_rc(errno, FSL_RC_RANGE),
			    "snprintf");

		line = fsl_mprintf("[%d/%d] %s", ln, nlines, headln);
		if (line == NULL)
			return RC(FSL_RC_RANGE, "fsl_mprintf");

		rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0, false);
		fsl_free(line);

		fsl_free(headln);
		if (rc)
			return rc;

		if (screen_is_shared(view) || view->active)

			rx |= A_REVERSE;
		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;
	line = NULL;
	while (max_lines > 0 && nprinted < max_lines) {
		col = wstrlen = 0;
		linelen = getline(&line, &linesz, s->f);
		if (linelen == -1) {
			if (feof(s->f)) {
				s->eof = true;
				break;
6287
6288
6289
6290
6291
6292
6293
6294
6295
6296
6297
6298
6299
6300
6301
6302
6303
6304
6305
6306
6307
6308
6309
		if (s->gtl)
			if (!gotoline(view, &s->lineno, &nprinted))
				continue;

		rx = 0;
		lidx = s->lineno - 1;
		if ((selected = nprinted == s->selected_line - 1))
			rx = FNC_HIGHLIGHT;
		if (s->showln)
			col = draw_lineno(view, nlines, s->lineno, rx);

		if (s->diff_mode == STASH_INTERACTIVE &&
		    s->hundex.lineno[s->hundex.idx] == lidx)
			rx = A_REVERSE;  /* highlight current hunk to stash */

		if (s->colour && s->ndlines > 0)
			c = get_colour(&s->colours,
			    s->dlines[MIN(s->ndlines - 1, lidx)]);
		if (c && !(selected && s->sline == SLINE_MONO))
			rx |= COLOR_PAIR(c->scheme);
		if (c || selected)
			wattron(view->window, rx);








|




|


|







6274
6275
6276
6277
6278
6279
6280
6281
6282
6283
6284
6285
6286
6287
6288
6289
6290
6291
6292
6293
6294
6295
6296
		if (s->gtl)
			if (!gotoline(view, &s->lineno, &nprinted))
				continue;

		rx = 0;
		lidx = s->lineno - 1;
		if ((selected = nprinted == s->selected_line - 1))
			rx = A_BOLD | A_REVERSE;
		if (s->showln)
			col = draw_lineno(view, nlines, s->lineno, rx);

		if (s->diff_mode == STASH_INTERACTIVE &&
		    s->scx.hunk.lineno[s->scx.hunk.idx] == lidx)
			rx = A_REVERSE;  /* highlight current hunk to stash */

		if (s->colour)
			c = get_colour(&s->colours,
			    s->dlines[MIN(s->ndlines - 1, lidx)]);
		if (c && !(selected && s->sline == SLINE_MONO))
			rx |= COLOR_PAIR(c->scheme);
		if (c || selected)
			wattron(view->window, rx);

6395
6396
6397
6398
6399
6400
6401
6402
6403
6404
6405
6406
6407
6408
6409
static int
draw_matched_line(struct fnc_view *view, const char *line, int *col, int limit,
    int offset, regmatch_t *regmatch, attr_t rx)
{
	wchar_t		*wstr = NULL;
	int		 rme, rms, skip = view->pos.col, wlen;
	int		 n, rc = FSL_RC_OK;
	attr_t		 hl = FNC_HIGHLIGHT;

	*col = n = 0;
	rms = regmatch->rm_so;
	rme = regmatch->rm_eo;

	rc = formatln(&wstr, &wlen, line, limit + view->pos.col,
	    offset, true);







|







6382
6383
6384
6385
6386
6387
6388
6389
6390
6391
6392
6393
6394
6395
6396
static int
draw_matched_line(struct fnc_view *view, const char *line, int *col, int limit,
    int offset, regmatch_t *regmatch, attr_t rx)
{
	wchar_t		*wstr = NULL;
	int		 rme, rms, skip = view->pos.col, wlen;
	int		 n, rc = FSL_RC_OK;
	attr_t		 hl = A_BOLD | A_REVERSE;

	*col = n = 0;
	rms = regmatch->rm_so;
	rme = regmatch->rm_eo;

	rc = formatln(&wstr, &wlen, line, limit + view->pos.col,
	    offset, true);
6519
6520
6521
6522
6523
6524
6525
6526
6527

6528



6529



6530
6531
6532
6533
6534
6535

6536



6537

6538
6539
6540
6541
6542
6543
6544
6545
			view->pos.col += 2;
		break;
	case KEY_LEFT:
	case 'h':
		view->pos.col -= MIN(view->pos.col, 2);
		break;
	case CTRL('p'):
		s->first_line_onscreen = prev_file(s);
		s->selected_line = 1;

		break;



	case CTRL('n'):



		s->first_line_onscreen = next_file(s);
		s->selected_line = 1;
		break;
	case '[':
		s->first_line_onscreen = prev_hunk(s);
		s->selected_line = 1;

		break;



	case ']':

		s->first_line_onscreen = next_hunk(s);
		s->selected_line = 1;
		break;
	case CTRL('e'):
		if (!s->eof) {
			++s->first_line_onscreen;
			if (s->lineno == nlines)
				--s->selected_line;







<
|
>
|
>
>
>
|
>
>
>
|


|
<
|
>
|
>
>
>
|
>
|







6506
6507
6508
6509
6510
6511
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
6538
6539
6540
6541
6542
			view->pos.col += 2;
		break;
	case KEY_LEFT:
	case 'h':
		view->pos.col -= MIN(view->pos.col, 2);
		break;
	case CTRL('p'):

		if (s->selected_entry->diff_type == FNC_DIFF_WIKI ||
		    !s->index.lineno)
			break;
		if (!((size_t)s->lineno > s->index.lineno[s->index.n - 1])) {
			if (s->index.idx == 0)
				s->index.idx = s->index.n - 1;
			else
				--s->index.idx;
		} else
			s->index.idx = s->index.n - 1;
		s->first_line_onscreen = s->index.lineno[s->index.idx];
		s->selected_line = 1;
		break;
	case CTRL('n'):

		if (s->selected_entry->diff_type == FNC_DIFF_WIKI ||
		    !s->index.lineno)
			break;
		if (!((size_t)s->lineno < s->index.lineno[0])) {
			if (++s->index.idx == s->index.n)
				s->index.idx = 0;
		} else
			s->index.idx = 0;
		s->first_line_onscreen = s->index.lineno[s->index.idx];
		s->selected_line = 1;
		break;
	case CTRL('e'):
		if (!s->eof) {
			++s->first_line_onscreen;
			if (s->lineno == nlines)
				--s->selected_line;
6672
6673
6674
6675
6676
6677
6678
6679
6680
6681
6682
6683
6684
6685
6686
		rc = fnc_prompt_input(view, &input);
		s->gtl = input.ret;
		break;
	}
	case '#':
		s->showln = !s->showln;
		break;
	case 'B': {
		int start_col = 0;
		if (view_is_parent(view))
			start_col = view_split_start_col(view->start_col);
		branch_view = view_open(view->nlines, view->ncols,
		    view->start_ln, start_col, FNC_VIEW_BRANCH);
		if (branch_view == NULL)
			return RC(FSL_RC_ERROR, "view_open");







|







6669
6670
6671
6672
6673
6674
6675
6676
6677
6678
6679
6680
6681
6682
6683
		rc = fnc_prompt_input(view, &input);
		s->gtl = input.ret;
		break;
	}
	case '#':
		s->showln = !s->showln;
		break;
	case 'b': {
		int start_col = 0;
		if (view_is_parent(view))
			start_col = view_split_start_col(view->start_col);
		branch_view = view_open(view->nlines, view->ncols,
		    view->start_ln, start_col, FNC_VIEW_BRANCH);
		if (branch_view == NULL)
			return RC(FSL_RC_ERROR, "view_open");
6702
6703
6704
6705
6706
6707
6708
6709
6710
6711
6712
6713
6714
6715
6716
6717
6718
6719
6720
6721
6722
6723
6724
6725
6726
6727
6728
6729
6730
6731
6732
6733
			*new_view = branch_view;
		break;
	}
	case 'P':
		s->patch = true;
		rc = create_diff(s);
		break;
	case 'b':
	case 'c':
	case 'i':
	case 'L':
	case 'p':
	case 'S':
	case 'v':
	case 'W':
	case 'w':
		if (ch == 'c')
			s->colour = !s->colour;
		/* biLpSvWw key maps don't apply to tag or ticket artifacts. */
		if (*s->selected_entry->type == 't' &&
		    (s->selected_entry->type[1] == 'a' ||
		     s->selected_entry->type[1] == 'i'))
			break;
		else if (ch == 'b')
			FLAG_TOG(s->diff_flags, FNC_DIFF_BRIEF);
		else if (ch == 'i')
			FLAG_TOG(s->diff_flags, FNC_DIFF_INVERT);
		else if (ch == 'L')
			FLAG_TOG(s->diff_flags, FNC_DIFF_LINENO);
		else if (ch == 'p')
			FLAG_TOG(s->diff_flags, FNC_DIFF_PROTOTYPE);
		else if (ch == 'S')







<










|




<
<







6699
6700
6701
6702
6703
6704
6705

6706
6707
6708
6709
6710
6711
6712
6713
6714
6715
6716
6717
6718
6719
6720


6721
6722
6723
6724
6725
6726
6727
			*new_view = branch_view;
		break;
	}
	case 'P':
		s->patch = true;
		rc = create_diff(s);
		break;

	case 'c':
	case 'i':
	case 'L':
	case 'p':
	case 'S':
	case 'v':
	case 'W':
	case 'w':
		if (ch == 'c')
			s->colour = !s->colour;
		/* LSipvWw key maps don't apply to tag or ticket artifacts. */
		if (*s->selected_entry->type == 't' &&
		    (s->selected_entry->type[1] == 'a' ||
		     s->selected_entry->type[1] == 'i'))
			break;


		else if (ch == 'i')
			FLAG_TOG(s->diff_flags, FNC_DIFF_INVERT);
		else if (ch == 'L')
			FLAG_TOG(s->diff_flags, FNC_DIFF_LINENO);
		else if (ch == 'p')
			FLAG_TOG(s->diff_flags, FNC_DIFF_PROTOTYPE);
		else if (ch == 'S')
6805
6806
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
6868
6869
6870
6871
6872
6873
6874
6875
6876
6877
6878
6879
6880
6881
6882
6883
6884
6885
6886
6887
6888
6889
6890
6891
6892
6893
6894
6895
6896
6897
6898
6899
6900
6901
6902
6903
6904
		break;
	}

	return rc;
}

static int
prev_file(struct fnc_diff_view_state *s)
{
	if (s->selected_entry->diff_type == FNC_DIFF_WIKI || !s->index.lineno)
		return s->first_line_onscreen;

	if (!((size_t)s->lineno > s->index.lineno[s->index.n - 1])) {
		if (s->index.idx == 0)
			s->index.idx = s->index.n - 1;
		else
			--s->index.idx;
	} else
		s->index.idx = s->index.n - 1;

	return s->index.lineno[s->index.idx];
}

static int
next_file(struct fnc_diff_view_state *s)
{
	if (s->selected_entry->diff_type == FNC_DIFF_WIKI || !s->index.lineno)
		return s->first_line_onscreen;

	if (!((size_t)s->lineno < s->index.lineno[0])) {
		if (++s->index.idx == s->index.n)
			s->index.idx = 0;
	} else
		s->index.idx = 0;

	return s->index.lineno[s->index.idx];
}

static int
prev_hunk(struct fnc_diff_view_state *s)
{
	struct index *h = &s->hundex;

	if (!h->lineno)
		return s->first_line_onscreen;

	if (!((size_t)s->lineno - 1 > h->lineno[h->idx])) {
		if (h->idx == 0 || (size_t)s->lineno < h->lineno[0])
			h->idx = h->n - 1;
		else
			while (h->idx > 0 &&
			    (size_t)s->lineno - 1 <= h->lineno[h->idx])
				--h->idx;
	} else
		while (h->idx < h->n - 1 &&
			    (size_t)s->lineno > h->lineno[h->idx + 1])
			++h->idx;

	return h->lineno[h->idx] + 1;
}

static int
next_hunk(struct fnc_diff_view_state *s)
{
	struct index *h = &s->hundex;

	if (!h->lineno)
		return s->first_line_onscreen;

	if (!((size_t)s->lineno - 1 < h->lineno[h->idx])) {
		if (h->idx == h->n - 1 ||
		    (size_t)s->lineno > h->lineno[h->n - 1])
			h->idx = 0;
		else
			while (h->idx < h->n - 1 &&
			    (size_t)s->lineno > h->lineno[h->idx])
				++h->idx;
	} else
		while (h->idx > 0 &&
		    (size_t)s->lineno - 1 < h->lineno[h->idx - 1])
			--h->idx;

	return h->lineno[h->idx] + 1;
}

static int
f__stash_get(uint32_t stashid, bool pop)
{
	fsl_cx	*const f = fcli_cx();
	fsl_db	*db = fsl_needs_ckout(f);
	fsl_stmt q = fsl_stmt_empty;
	int	 nadded, vid, rc0, rc = FSL_RC_OK;
	uint32_t cc = 0;

	vid = f->ckout.rid;

	rc = fsl_db_prepare(db, &q, "SELECT blob.rid, isRemoved, isExec,"
	    "  isLink, origname, newname, delta FROM stashfile, blob"
	    " WHERE stashid=%d AND blob.uuid=stashfile.hash UNION ALL"
	    "  SELECT 0, isRemoved, isExec, isLink, origname, newname, delta"







<
<
<
<
|
<
<
<
<
<
<
<
|
<
<
|
<
<
<
<
<
|
<
<
<
<
<
|
<
<
|
<
<
<
<
|
<
<

<
<
<
<
<
<
<
<
<
<
<
|
<
<
|
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<







6799
6800
6801
6802
6803
6804
6805




6806







6807


6808





6809





6810


6811




6812


6813











6814


6815




6816
















6817
6818









6819
6820
6821
6822
6823
6824
6825
		break;
	}

	return rc;
}

static int




f__stash_get(bool pop)







{


	fsl_cx	*const f = fcli_cx();





	fsl_db	*db = fsl_needs_ckout(f);





	fsl_stmt q = fsl_stmt_empty;


	int	 nadded, stashid, vid, rc0, rc = FSL_RC_OK;




	uint32_t cc = 0;














	stashid = fsl_db_g_int32(db, 0, "SELECT max(stashid) FROM stash");


	if (!stashid) {




		f_out(">> empty stash");
















		return rc;
	}










	vid = f->ckout.rid;

	rc = fsl_db_prepare(db, &q, "SELECT blob.rid, isRemoved, isExec,"
	    "  isLink, origname, newname, delta FROM stashfile, blob"
	    " WHERE stashid=%d AND blob.uuid=stashfile.hash UNION ALL"
	    "  SELECT 0, isRemoved, isExec, isLink, origname, newname, delta"
7053
7054
7055
7056
7057
7058
7059


7060
7061
7062
7063
7064
7065
7066
7067
7068
7069
7070
7071
7072
7073
7074
7075
7076
7077

7078

7079
7080
7081
7082
7083

7084
7085
7086
7087
7088
7089
7090
7091
7092
7093
7094
7095
7096
7097
7098
			fsl_buffer_clear(&a);
			fsl_buffer_clear(&b);
			fsl_buffer_clear(&out);
			fsl_buffer_clear(&disk);
			if (rc)
				goto clear_delta;
		}


		if (ogname && fsl_strcmp(ogname, name)) {
			rc = fsl_file_unlink(ogpath);
			if (rc) {
				rc = RC(rc, "fsl_file_unlink(%s)", ogpath);
				goto clear_delta;
			}
			rc = fsl_db_exec_multi(db,
			    "UPDATE vfile SET pathname='%q', origname='%q'"
			    " WHERE pathname='%q' %s AND vid=%d", name, ogname,
			    ogname, fsl_cx_filename_collation(f), vid);
		}
clear_delta:
		fsl_buffer_clear(&delta);
		fsl_free(path);
		fsl_free(ogpath);
		if (rc)
			goto end;
	}

	rc = f__add_files_in_sfile(&nadded, vid);

	if (rc)
		goto end;

	if (cc)
		f_out("\n>> merge conflict(s): %u\n", cc);

	if (pop) {
		rc = fsl_db_exec_multi(db, "DELETE FROM stash WHERE stashid=%d;"
		    "DELETE FROM stashfile WHERE stashid=%d;",
		    stashid, stashid);
	}
end:
	rc0 = fsl_stmt_finalize(&q);
	return rc ? rc : rc0;
}

static int
f__add_files_in_sfile(int *nadded, int vid)
{
	fsl_cx	*const f = fcli_cx();
	fsl_db	*db = fsl_needs_ckout(f);







>
>
|
|
|

<
<
|




<
<



|

>
|
>
|
<



>





<
<
|







6974
6975
6976
6977
6978
6979
6980
6981
6982
6983
6984
6985
6986


6987
6988
6989
6990
6991


6992
6993
6994
6995
6996
6997
6998
6999
7000

7001
7002
7003
7004
7005
7006
7007
7008
7009


7010
7011
7012
7013
7014
7015
7016
7017
			fsl_buffer_clear(&a);
			fsl_buffer_clear(&b);
			fsl_buffer_clear(&out);
			fsl_buffer_clear(&disk);
			if (rc)
				goto clear_delta;
		}
clear_delta:
		fsl_buffer_clear(&delta);
		if (!rc && fsl_strcmp(ogname, name)) {
			fsl_file_unlink(ogpath);
			if (rc)
				rc = RC(rc, "fsl_file_unlink(%s)", ogpath);


			rc = rc ? rc : fsl_db_exec_multi(db,
			    "UPDATE vfile SET pathname='%q', origname='%q'"
			    " WHERE pathname='%q' %s AND vid=%d", name, ogname,
			    ogname, fsl_cx_filename_collation(f), vid);
		}


		fsl_free(path);
		fsl_free(ogpath);
		if (rc)
			break;
	}
	if (!rc)
		rc = f__add_files_in_sfile(&nadded, vid);
	rc0 = fsl_stmt_finalize(&q);
	rc = rc ? rc : rc0;


	if (cc)
		f_out("\n>> merge conflict(s): %u\n", cc);

	if (pop) {
		rc = fsl_db_exec_multi(db, "DELETE FROM stash WHERE stashid=%d;"
		    "DELETE FROM stashfile WHERE stashid=%d;",
		    stashid, stashid);
	}


	return rc;
}

static int
f__add_files_in_sfile(int *nadded, int vid)
{
	fsl_cx	*const f = fcli_cx();
	fsl_db	*db = fsl_needs_ckout(f);
7141
7142
7143
7144
7145
7146
7147
7148
7149
7150
7151
7152
7153
7154
7155
7156
7157
7158
7159
7160
7161
7162
7163
7164
7165
7166
7167
7168
7169
7170
7171
7172
7173
7174
7175
7176
7177
7178
7179
7180
7181
7182
7183
7184
7185
7186
7187
7188
7189
7190
7191
7192




7193
7194
7195
7196
7197
7198
7199
7200
7201
7202
7203
7204
7205
7206
7207
7208
7209
7210
7211

7212
7213
7214
7215
7216
7217
7218
7219
7220
7221
7222
7223
7224
7225
7226
7227
7228
7229
7230
7231
7232
7233
7234
7235
7236
7237
7238
7239

7240
7241
7242
7243
7244
7245
7246
7247
7248
7249
7250
7251
7252

7253
7254
7255
7256

7257
7258
7259
7260
7261
7262
7263
{
	*include = true;
	f_out("[+] %s\n", cms->filename);
	return FSL_RC_OK;
}

/*
 * Interactive stash is a simple algorithm:
 *   1. let user select hunks to stash
 *   2. make two patch(1) files:
 *      2.1 diff of hunks selected to stash
 *      2.2 diff of hunks to be kept in the checkout
 *   3. revert checkout
 *   4. apply patch of hunks selected to stash
 *   5. stash changes
 *   6. revert checkout
 *   7. apply patch of hunks that were not selected to stash
 * This produces a checkout with only the changes that were not selected
 * to stash, achieving the same function as 'git add -p'. The user can
 * now test the code, commit, then run 'fnc stash pop' and repeat.
 */
static int
fnc_stash(struct fnc_view *view)
{
	struct fnc_diff_view_state	*s = &view->state.diff;
	struct stash_cx			*scx = &s->scx;
	struct input			 in;
	char				*msg = NULL, *prompt = NULL;
	int				 rc = FSL_RC_OK;

	scx->stash = alloc_bitstring(s->hundex.n);
	if (scx->stash == NULL)
		return RC(FSL_RC_ERROR, "alloc_bitstring");

	rc = select_hunks(view);  /* 1. get hunks to stash */
	if (rc)
		goto end;

	/* Use default stash msg of "fnc stash CKOUT-HASH" if not provided. */
	msg = fsl_mprintf("fnc stash %.11s", s->id2);
	if (msg == NULL) {
		rc = RC(FSL_RC_ERROR, "fsl_mprintf");
		goto end;
	}
	prompt = fsl_mprintf("stash message [%s]: ", msg);
	if (prompt == NULL) {
		rc = RC(FSL_RC_ERROR, "fsl_mprintf");
		goto end;
	}

	in = (struct input){NULL, prompt, INPUT_ALPHA, SR_CLREOL};
	rc = fnc_prompt_input(view, &in);




	if (rc) {
		rc = RC(rc, "fnc_prompt_input");
		goto end;
	}
	if (in.buf[0]) {
		fsl_free(msg);
		msg = fsl_mprintf("%s", in.buf);
		if (msg == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_mprintf");
			goto end;
		}
	}

	s->stash = HUNK_STASH;
	rc = create_diff(s);  /* 2.1 make patch of hunks selected to stash */
	if (rc)
		goto end;

	s->stash = HUNK_CKOUT;

	rc = create_diff(s);  /* 2.2 make patch of hunks to keep in ckout */
	if (rc)
		goto end;

	endwin();  /* restore tty so we can report progress to stdout */

	/* 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 With revert_ckout() finished, we can drop privileges further. */
	rc = init_unveil(((const char *[]){"/usr/bin/patch", "fossil"}),
	    ((const char *[]){"rx", "rx"}), 2, true);
	if (rc)
		goto end;

	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)
		goto end;

	scx->pcx.report = false; /* don't report changes kept in ckout */

	/* fnc_execp((const char *const []) */
	/*     {"fossil", "stash", "save", "-m", msg, (char *)NULL}, 10); */

	/* 5. stash changes */
	rc = f__stash_create(msg, s->selected_entry->rid);
	if (rc)
		goto end;

	rc = revert_ckout(false, false);  /* 6. revert checkout */
	if (rc)
		goto end;

	/* 7. apply patch of hunks that were not selected to stash */

	rc = fnc_patch(&scx->pcx, scx->patch[1]);
	rc = (rc == NO_PATCH ? FSL_RC_OK : rc);
end:
	fsl_free(scx->stash);

	fsl_free(msg);
	fsl_free(prompt);
	return rc;
}

/*
 * Allocate, zero, and return an unsigned char pointer of enough bytes







<
<
|
|
|
<
|
|
<
|
|






|
|
|
|
|

|

|

|





<
<
<
<

<
<
<
<
<


>
>
>
>




<
<
<
<
<
<
|
<
<
|
|
<
<
<
|
>
|





|














<
<
|
<
<
<

>
|
|
<
<
|
|
<
<
|
<
<
|
<
>
|



>







7060
7061
7062
7063
7064
7065
7066


7067
7068
7069

7070
7071

7072
7073
7074
7075
7076
7077
7078
7079
7080
7081
7082
7083
7084
7085
7086
7087
7088
7089
7090
7091
7092
7093
7094
7095




7096





7097
7098
7099
7100
7101
7102
7103
7104
7105
7106






7107


7108
7109



7110
7111
7112
7113
7114
7115
7116
7117
7118
7119
7120
7121
7122
7123
7124
7125
7126
7127
7128
7129
7130
7131
7132


7133



7134
7135
7136
7137


7138
7139


7140


7141

7142
7143
7144
7145
7146
7147
7148
7149
7150
7151
7152
7153
7154
{
	*include = true;
	f_out("[+] %s\n", cms->filename);
	return FSL_RC_OK;
}

/*


 * Get hunks selected to stash from the user and make two patch(1) files:
 * (1) diff of all hunks selected to stash; and (2) diff of all hunks to be
 * kept in the checkout. Then revert the checkout and use patch files to:

 *   1. apply patch of hunks selected to stash
 *   2. stash (and revert) checkout

 *   3. apply patch of hunks that were not selected to stash
 * This produces a ckout with only those changes that were not selected
 * to stash, achieving the same function as 'git add -p'. The user can
 * now test the code, commit, then run 'fnc stash pop' and repeat.
 */
static int
fnc_stash(struct fnc_view *view)
{
	struct fnc_diff_view_state	 *s = &view->state.diff;
	struct stash_cx			 *scx = &s->scx;
	struct input			  in;
	char				 *msg = NULL, *prompt = NULL;
	int				  rc = FSL_RC_OK;

	scx->stash = alloc_bitstring(scx->hunk.n);
	if (scx->stash == NULL)
		return RC(FSL_RC_ERROR, "calloc");

	rc = select_hunks(view);  /* get hunks to stash */
	if (rc)
		goto end;

	/* Use default stash msg of "fnc stash CKOUT-HASH" if not provided. */
	msg = fsl_mprintf("fnc stash %.11s", s->id2);




	prompt = fsl_mprintf("stash message [%s]: ", msg);





	in = (struct input){NULL, prompt, INPUT_ALPHA, SR_CLREOL};
	rc = fnc_prompt_input(view, &in);
	if (in.buf[0]) {
		fsl_free(msg);
		msg = fsl_mprintf("%s", in.buf);
	}
	if (rc) {
		rc = RC(rc, "fnc_prompt_input");
		goto end;
	}









	s->stash = HUNK_STASH;  /* make patch of hunks selected to stash */
	rc = create_diff(s);



	s->stash = HUNK_CKOUT;  /* make patch of hunks to keep in ckout */
	if (!rc)
		rc = create_diff(s);
	if (rc)
		goto end;

	endwin();  /* restore tty so we can report progress to stdout */

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

	/* XXX With revert_ckout() finished, we can drop privileges further. */
	rc = init_unveil(((const char *[]){"/usr/bin/patch", "fossil"}),
	    ((const char *[]){"rx", "rx"}), 2, true);
	if (rc)
		goto end;

	scx->pcx.context = s->context;
	scx->pcx.report = true;  /* report files with changes stashed */


	rc = fnc_patch(&scx->pcx, scx->patch[0]);  /* (1) apply stash patch */



	scx->pcx.report = false; /* don't report changes kept in ckout */
	if (!rc) {
		/* fnc_execp((const char *const []) */
		/*     {"fossil", "stash", "save", "-m", msg, (char *)NULL}, 10); */


		rc = f__stash_create(msg, fcli_cx()->ckout.rid);  /* (2) */
		if (!rc)


			rc = revert_ckout(false, false);


	}

	if (!rc)
		rc = fnc_patch(&scx->pcx, scx->patch[1]);  /* (3) ckout patch */
	rc = (rc == NO_PATCH ? FSL_RC_OK : rc);
end:
	fsl_free(scx->stash);
	fsl_free(scx->hunk.lineno);
	fsl_free(msg);
	fsl_free(prompt);
	return rc;
}

/*
 * Allocate, zero, and return an unsigned char pointer of enough bytes
7317
7318
7319
7320
7321
7322
7323
7324
7325
7326
7327
7328
7329
7330
7331
	struct input			 in;
	size_t				 last, nxt = 0, nf = 0;
	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"};

	/* Iterate hunks and prompt user to stash or keep in ckout. */
	while (!rc && *nh < hunks->n) {
		char	**ans = NULL;
		char	  prompt[64];







|







7208
7209
7210
7211
7212
7213
7214
7215
7216
7217
7218
7219
7220
7221
7222
	struct input			 in;
	size_t				 last, nxt = 0, nf = 0;
	uint32_t			*nh;
	int				 hl, rc = FSL_RC_OK;
	enum stash_opt			 choice = NO_CHOICE;
	bool				 lastfile = false;

	hunks = &s->scx.hunk;
	nh = &hunks->idx;
	in = (struct input){NULL, NULL, INPUT_ALPHA, SR_CLREOL, "X"};

	/* Iterate hunks and prompt user to stash or keep in ckout. */
	while (!rc && *nh < hunks->n) {
		char	**ans = NULL;
		char	  prompt[64];
9034
9035
9036
9037
9038
9039
9040

9041



9042
9043
9044
9045
9046
9047
9048
9049

	return rc;
}

static int
set_selected_commit(struct fnc_diff_view_state *s, struct commit_entry *entry)
{

	s->id2 = entry->commit->uuid;



	s->id1 = entry->commit->puuid;
	s->selected_entry = entry->commit;

	return 0;
}

static void
diff_grep_init(struct fnc_view *view)







>
|
>
>
>
|







8925
8926
8927
8928
8929
8930
8931
8932
8933
8934
8935
8936
8937
8938
8939
8940
8941
8942
8943
8944

	return rc;
}

static int
set_selected_commit(struct fnc_diff_view_state *s, struct commit_entry *entry)
{
	fsl_free(s->id2);
	s->id2 = fsl_strdup(entry->commit->uuid);
	if (s->id2 == NULL)
		return RC(FSL_RC_ERROR, "fsl_strdup");
	fsl_free(s->id1);
	s->id1 = entry->commit->puuid ? fsl_strdup(entry->commit->puuid) : NULL;
	s->selected_entry = entry->commit;

	return 0;
}

static void
diff_grep_init(struct fnc_view *view)
9188
9189
9190
9191
9192
9193
9194




9195
9196
9197
9198
9199
9200
9201
9202
9203
9204
9205
9206
9207
9208
9209
9210
9211
9212
9213
9214
9215
9216
9217
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->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);
	s->dlines = NULL;
	return rc;
}

static void
free_index(struct index *index, bool reset)
{
	if (reset)
		index->idx = 0;
	index->n = 0;
	fsl_free(index->lineno);
	index->lineno = NULL;
	fsl_free(index->offset);
	index->offset = NULL;
}








>
>
>
>




|
<






|

<
|







9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095
9096
9097
9098

9099
9100
9101
9102
9103
9104
9105
9106

9107
9108
9109
9110
9111
9112
9113
9114
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);

	fsl_free(s->dlines);
	s->dlines = NULL;
	return rc;
}

static void
free_index(struct index *index)
{

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

9336
9337
9338
9339
9340
9341
9342
9343
9344



9345
9346
9347
9348
9349
9350
9351
fcli_flag_type_arg_cb(fcli_cliflag const *v)
{
	struct artifact_types	*ft = &fnc_init.filter_types;
	const char		*t = *((const char **)v->flagValue);

	/* Valid types: ci, e, f, g, t, w */
	if (strlen(t) > 2 || (t[1] && (*t != 'c' || t[1] != 'i')) || (!t[1] &&
	    (*t != 'e' && *t != 'f' && *t != 'g' && *t != 't' && *t != 'w')))
		return RC(FSL_RC_TYPE, "invalid type: %s", t);




	ft->values = fsl_realloc(ft->values, (ft->nitems + 1) * sizeof(char *));
	ft->values[ft->nitems++] = t;

	return FCLI_RC_FLAG_AGAIN;
}








|
|
>
>
>







9233
9234
9235
9236
9237
9238
9239
9240
9241
9242
9243
9244
9245
9246
9247
9248
9249
9250
9251
fcli_flag_type_arg_cb(fcli_cliflag const *v)
{
	struct artifact_types	*ft = &fnc_init.filter_types;
	const char		*t = *((const char **)v->flagValue);

	/* Valid types: ci, e, f, g, t, w */
	if (strlen(t) > 2 || (t[1] && (*t != 'c' || t[1] != 'i')) || (!t[1] &&
	    (*t != 'e' && *t != 'f' && *t != 'g' && *t != 't' && *t != 'w'))) {
		fnc_init.err = RC(FSL_RC_TYPE, "invalid type: %s", t);
		usage();
		/* NOT REACHED */
	}

	ft->values = fsl_realloc(ft->values, (ft->nitems + 1) * sizeof(char *));
	ft->values[ft->nitems++] = t;

	return FCLI_RC_FLAG_AGAIN;
}

9377
9378
9379
9380
9381
9382
9383
9384
9385
9386
9387
9388
9389
9390
9391
9392
9393
9394
9395
9396
9397
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407
9408

static void
sigcont_handler(int sig)
{
	rec_sigcont = 1;
}

static void
sigint_handler(int signo)
{
	rec_sigint = 1;
}

static void
sigterm_handler(int signo)
{
	rec_sigterm = 1;
}

static bool
fatal_signal(void)
{
	return (rec_sigpipe || rec_sigint || rec_sigterm);
}

__dead static void
usage(void)
{
	/*
	 * It looks like the fsl_cx f member of the ::fcli singleton has
	 * already been cleaned up by the time this wrapper is called from
	 * fcli_help() after hijacking the process whenever the '--help'







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







9277
9278
9279
9280
9281
9282
9283


















9284
9285
9286
9287
9288
9289
9290

static void
sigcont_handler(int sig)
{
	rec_sigcont = 1;
}



















__dead static void
usage(void)
{
	/*
	 * It looks like the fsl_cx f member of the ::fcli singleton has
	 * already been cleaned up by the time this wrapper is called from
	 * fcli_help() after hijacking the process whenever the '--help'
9418
9419
9420
9421
9422
9423
9424
9425
9426
9427
9428
9429
9430
9431
9432
	!isendwin() ? endwin() : 0;

	/* If a command was passed on the CLI, output its corresponding help. */
	if (fnc_init.cmdarg)
		for (idx = 0; idx < nitems(fnc_init.cmd_args); ++idx) {
			fcli_command cmd = fnc_init.cmd_args[idx];
			if (!fsl_strcmp(fnc_init.cmdarg, cmd.name) ||
			    !fcli_cmd_aliascmp(&cmd, fnc_init.cmdarg)) {
				if (!fsl_strcmp(cmd.name, "stash")) {
					help_stash(&cmd);
				} else
					fcli_command_help(&cmd, true, true);
				exit(fcli_end_of_main(fnc_init.err));
			}
		}







|







9300
9301
9302
9303
9304
9305
9306
9307
9308
9309
9310
9311
9312
9313
9314
	!isendwin() ? endwin() : 0;

	/* If a command was passed on the CLI, output its corresponding help. */
	if (fnc_init.cmdarg)
		for (idx = 0; idx < nitems(fnc_init.cmd_args); ++idx) {
			fcli_command cmd = fnc_init.cmd_args[idx];
			if (!fsl_strcmp(fnc_init.cmdarg, cmd.name) ||
			    fcli_cmd_aliascmp(&cmd, fnc_init.cmdarg)) {
				if (!fsl_strcmp(cmd.name, "stash")) {
					help_stash(&cmd);
				} else
					fcli_command_help(&cmd, true, true);
				exit(fcli_end_of_main(fnc_init.err));
			}
		}
9451
9452
9453
9454
9455
9456
9457
9458
9459
9460
9461
9462
9463
9464
9465
9466
9467
9468
9469
	    fcli_progname(), fcli_progname());
}

static void
usage_diff(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " usage: %s diff  [-b|--brief][-C|--no-colour] [-h|--help] "
	    "[-i|--invert] [-l|--line-numbers] [-P|--no-prototype] "
	    "[-q|--quiet] [-R path] [-s|--sbs] [-W|--whitespace-eol] "
	    "[-w|--whitespace] [-x|--context n] [artifact1 [artifact2]] "
	    "[path ...]\n"
	    "  e.g.: %s diff --sbs d34db33f c0ff33 src/*.c\n\n",
	    fcli_progname(), fcli_progname());
}

static void
usage_tree(void)
{







|

|
|
<







9333
9334
9335
9336
9337
9338
9339
9340
9341
9342
9343

9344
9345
9346
9347
9348
9349
9350
	    fcli_progname(), fcli_progname());
}

static void
usage_diff(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " usage: %s diff [-C|--no-colour] [-R path] [-h|--help] "
	    "[-i|--invert] [-l|--line-numbers] [-P|--no-prototype] "
	    "[-q|--quiet] [-s|--sbs] [-W|--whitespace-eol] [-w|--whitespace] "
	    "[-x|--context n] [artifact1 [artifact2]] [path ...]\n"

	    "  e.g.: %s diff --sbs d34db33f c0ff33 src/*.c\n\n",
	    fcli_progname(), fcli_progname());
}

static void
usage_tree(void)
{
9505
9506
9507
9508
9509
9510
9511
9512
9513
9514
9515
9516
9517
9518
9519
9520
9521
9522
9523
9524
9525
9526
9527
9528
9529
9530
9531
9532
9533
9534
9535
9536
9537
9538
9539
9540
9541
9542
9543
9544
9545
9546
9547
9548
9549
9550
9551
9552
9553
9554
9555
9556
9557
9558
9559
9560
9561
9562
9563
9564
9565
9566
9567
9568
9569
9570
9571
9572
9573
9574
9575
9576
9577
9578
9579
9580
9581
9582
9583
9584
9585
9586
9587
9588
9589
9590
9591
9592
9593
9594
9595
9596
9597
9598
9599
9600
9601
9602
9603
9604
9605
9606
9607
9608
9609

9610
9611
9612



9613
9614
9615
9616
9617
9618
9619
9620
9621
9622
9623
9624
9625
9626
9627
9628
9629
9630
9631
9632
9633
9634
9635
9636
9637
	    fcli_progname(), fcli_progname());
}

static void
usage_stash(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " usage: %s stash [(get|pop) [<id>] | [-C|--no-colour] [-h|--help] "
	    "[-P|--no-prototype] [-x|--context n]]\n"
	    "  e.g.: %s stash -x 10\n\n", fcli_progname(), fcli_progname());
}

static void
help_stash(const fcli_command *cmd)
{
	fcli_command_help(cmd, false, true);
	f_out("[stash] subcommands:\n\n");
	f_out("  get [<id>]\n    "
	    "Apply the latest stash or stash <id> to the checkout.\n\n");
	f_out("  pop [<id>]\n    Remove the latest stash or stash <id> "
	    "and apply to the checkout.\n\n");
	usage_stash();
}

static int
fnc_stash_get(bool pop)
{
	fsl_cx		*const f = fcli_cx();
	fsl_db		*db = fsl_needs_ckout(f);
	char		*comment, *date;
	fsl_uuid_str	 hash;
	long		 stashid;
	int		 rc = FSL_RC_OK;

	if (fcli_next_arg(false)) {
		rc = strtonumcheck(&stashid, fcli_next_arg(true), 1, INT_MAX);
		if (rc)
			return rc;
	} else {
		stashid = fsl_db_g_int32(db, 0,
		    "SELECT max(stashid) FROM stash");
		if (!stashid) {
			f_out("empty stash");
			return FSL_RC_OK;
		}
	}

	if (!fsl_db_exists(db, "SELECT 1 FROM stash WHERE stashid=%d",
	    stashid)) {
		f_out("no such stash: %d", stashid);
		return FSL_RC_OK;
	}

	comment = fsl_db_g_text(db, NULL,
	    "SELECT comment FROM stash WHERE stashid=%d", stashid);
	date = fsl_db_g_text(db, NULL, "SELECT datetime(ctime) "
	    "FROM stash WHERE stashid=%d", stashid);
	hash = fsl_db_g_text(db, NULL,
	    "SELECT hash FROM stash WHERE stashid=%d", stashid);

	rc = f__stash_get(stashid, pop);
	if (rc)
		goto end;

	f_out("\n%s stash:\n%5d: [%.14s] from %s\n",
	    pop ? "Popped" : "Applied", stashid, hash, date);
	if (comment && *comment)
		f_out("        %s", comment);
end:
	fsl_free(comment);
	fsl_free(date);
	fsl_free(hash);
	return rc;

}

static int
cmd_stash(fcli_command const *argv)
{
	fsl_cx				*const f = fcli_cx();
	struct fnc_view			*view = NULL;
	struct fnc_commit_artifact	*commit = NULL;
	const char			*cmd;
	fsl_id_t			 rid;
	int				 rc = FSL_RC_OK;
	enum fnc_diff_type		 diff_type = FNC_DIFF_CKOUT;
	enum fnc_diff_mode		 diff_mode = STASH_INTERACTIVE;

#ifdef __OpenBSD__
	if (pledge("stdio rpath wpath cpath fattr flock tty unveil", NULL) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "pledge");
#endif

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		return rc;

	cmd = fcli_next_arg(true);
	if (cmd) {
		bool pop = false;

		if (fsl_strcmp(cmd, "get") && fsl_strcmp(cmd, "apply") &&
		    !(pop = !strcmp(cmd, "pop")))
			return RC(FSL_RC_NOT_FOUND,
			    "invalid stash subcommand: %s", cmd);


		return fnc_stash_get(pop);
	}




	fsl_ckout_version_info(f, &rid, NULL);
	rc = fsl_ckout_changes_scan(f);
	if (rc)
		return RC(rc, "fsl_ckout_changes_scan");
	if (!fsl_ckout_has_changes(f)) {
		fsl_fprintf(stdout, "No local changes.\n");
		return rc;
	}

	commit = calloc(1, sizeof(*commit));
	if (commit == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");
	commit->prid = rid;
	commit->rid = rid;
	commit->puuid = fsl_rid_to_uuid(f, rid);
	commit->uuid = fsl_strdup(commit->puuid);
	commit->type = fsl_strdup("checkin");
	commit->diff_type = diff_type;

	rc = init_curses();
	if (rc)
		goto end;
	/*
	 * XXX revert_ckout:fsl_ckout_revert()^ walks the tree from / to the







|









|
|
|


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<








<
|













|
|

|
|
<


>
|
<
|
>
>
>


|
<









|

|
|
|







9386
9387
9388
9389
9390
9391
9392
9393
9394
9395
9396
9397
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407




















































9408
9409
9410
9411
9412
9413
9414
9415

9416
9417
9418
9419
9420
9421
9422
9423
9424
9425
9426
9427
9428
9429
9430
9431
9432
9433
9434

9435
9436
9437
9438

9439
9440
9441
9442
9443
9444
9445

9446
9447
9448
9449
9450
9451
9452
9453
9454
9455
9456
9457
9458
9459
9460
9461
9462
9463
9464
9465
9466
	    fcli_progname(), fcli_progname());
}

static void
usage_stash(void)
{
	fsl_fprintf(fnc_init.err ? stderr : stdout,
	    " usage: %s stash [get|pop | [-C|--no-colour] [-h|--help] "
	    "[-P|--no-prototype] [-x|--context n]]\n"
	    "  e.g.: %s stash -x 10\n\n", fcli_progname(), fcli_progname());
}

static void
help_stash(const fcli_command *cmd)
{
	fcli_command_help(cmd, false, true);
	f_out("[stash] subcommands:\n\n");
	f_out("  get\n    "
	    "Apply the most recent stash changeset to the checkout.\n\n");
	f_out("  pop\n    Remove the most recent stash changeset, "
	    "and apply to the checkout.\n\n");
	usage_stash();




















































}

static int
cmd_stash(fcli_command const *argv)
{
	fsl_cx				*const f = fcli_cx();
	struct fnc_view			*view = NULL;
	struct fnc_commit_artifact	*commit = NULL;

	fsl_id_t			 prid = -1, rid = -1;
	int				 rc = FSL_RC_OK;
	enum fnc_diff_type		 diff_type = FNC_DIFF_CKOUT;
	enum fnc_diff_mode		 diff_mode = STASH_INTERACTIVE;

#ifdef __OpenBSD__
	if (pledge("stdio rpath wpath cpath fattr flock tty unveil", NULL) == -1)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "pledge");
#endif

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		return rc;

	if (fcli_next_arg(false)) {
		const char *cmd;
		bool pop = false;
		if (fsl_strcmp((cmd = fcli_next_arg(true)), "get") &&
		    fsl_strcmp(cmd, "apply") && !(pop = !fsl_strcmp(cmd, "pop")))

			return RC(FSL_RC_NOT_FOUND,
			    "invalid stash subcommand: %s", cmd);
		return f__stash_get(pop);
	}


	rc = fsl_sym_to_rid(f, "current", FSL_SATYPE_CHECKIN, &prid);
	if (rc || prid < 0)
		return RC(rc, "fsl_sym_to_rid");

	fsl_ckout_version_info(f, &rid, NULL);
	if ((rc = fsl_ckout_changes_scan(f)))

		return RC(rc, "fsl_ckout_changes_scan");
	if (!fsl_ckout_has_changes(f)) {
		fsl_fprintf(stdout, "No local changes.\n");
		return rc;
	}

	commit = calloc(1, sizeof(*commit));
	if (commit == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");
	commit->prid = prid;
	commit->rid = rid;
	commit->puuid = fsl_rid_to_uuid(f, prid);
	commit->uuid = fsl_rid_to_uuid(f, rid);
	commit->type = fsl_strdup("blob");
	commit->diff_type = diff_type;

	rc = init_curses();
	if (rc)
		goto end;
	/*
	 * XXX revert_ckout:fsl_ckout_revert()^ walks the tree from / to the
9648
9649
9650
9651
9652
9653
9654
9655
9656
9657
9658
9659
9660
9661
9662
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
	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);
	if (rc)
		goto end;
	rc = fnc_stash(view);
	if (rc)
		goto end;
	rc = fsl_vfile_changes_scan(f, rid, FSL_VFILE_CKSIG_HASH);

end:
	/*
	 * We must check for changes based on file content--not mtime--else
	 * the lib will report files as unchanged in some cases.
	 */

	if (commit)
		fnc_commit_artifact_close(commit);
	if (view)
		view_close(view);
	return rc;
}

static int
cmd_diff(fcli_command const *argv)
{
	fsl_cx				*const f = fcli_cx();
	struct fnc_view			*view;
	struct fnc_commit_artifact	*commit = NULL;
	struct fnc_pathlist_head	 paths;
	struct fnc_pathlist_entry	*pe;
	fsl_deck			 d = fsl_deck_empty;
	fsl_stmt			*q = NULL;
	const char			*artifact1 = NULL, *artifact2 = NULL;

	fsl_id_t			 prid = -1, rid = -1;
	int				 rc = FSL_RC_OK;
	unsigned short			 blob = 0;
	enum fnc_diff_type		 diff_type = FNC_DIFF_CKOUT;
	enum fnc_diff_mode		 diff_mode = DIFF_PLAIN;

	rc = fcli_process_flags(argv->flags);







|
<
<
|
|
<
|
<
<
<
>





>


















>







9477
9478
9479
9480
9481
9482
9483
9484


9485
9486

9487



9488
9489
9490
9491
9492
9493
9494
9495
9496
9497
9498
9499
9500
9501
9502
9503
9504
9505
9506
9507
9508
9509
9510
9511
9512
9513
9514
9515
9516
9517
9518
9519
9520
	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) {


		rc = show_diff(view);
		if (!rc)

			rc = fnc_stash(view);



	}
end:
	/*
	 * We must check for changes based on file content--not mtime--else
	 * the lib will report files as unchanged in some cases.
	 */
	fsl_vfile_changes_scan(f, f->ckout.rid, FSL_VFILE_CKSIG_HASH);
	if (commit)
		fnc_commit_artifact_close(commit);
	if (view)
		view_close(view);
	return rc;
}

static int
cmd_diff(fcli_command const *argv)
{
	fsl_cx				*const f = fcli_cx();
	struct fnc_view			*view;
	struct fnc_commit_artifact	*commit = NULL;
	struct fnc_pathlist_head	 paths;
	struct fnc_pathlist_entry	*pe;
	fsl_deck			 d = fsl_deck_empty;
	fsl_stmt			*q = NULL;
	const char			*artifact1 = NULL, *artifact2 = NULL;
	char				*path0 = NULL;
	fsl_id_t			 prid = -1, rid = -1;
	int				 rc = FSL_RC_OK;
	unsigned short			 blob = 0;
	enum fnc_diff_type		 diff_type = FNC_DIFF_CKOUT;
	enum fnc_diff_mode		 diff_mode = DIFF_PLAIN;

	rc = fcli_process_flags(argv->flags);
9716
9717
9718
9719
9720
9721
9722
9723
9724
9725
9726

9727
9728
9729
9730
9731
9732
9733
9734
9735


9736
9737
9738
9739
9740
9741

9742
9743
9744
9745
9746
9747
9748
9749
9750
9751
9752
9753
9754
9755
9756
9757
9758
9759
9760
9761
9762
9763
9764
9765

9766
9767
9768
9769

9770
9771
9772
9773
9774





9775
9776






9777
9778




9779

9780

9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
		if (!fsl_sym_to_rid(f, fcli_next_arg(false), FSL_SATYPE_ANY,
		    &rid)) {
			artifact2 = fcli_next_arg(true);
			diff_type = FNC_DIFF_COMMIT;
			if (!fsl_rid_is_a_checkin(f, rid))
				++blob;
		}
	} else if (fcli.argc > 0 &&
	    !fsl_sym_to_rid(f, fcli.argv[1], FSL_SATYPE_ANY, &prid))
		return RC(FSL_RC_NOT_FOUND, "invalid hash prefix [%s]",
		    fcli.argv[0]);

	if (fcli_error()->code == FSL_RC_NOT_FOUND)
		RC_RESET(rc);  /* If args aren't symbols, treat as paths. */
	if (blob == 2)
		diff_type = FNC_DIFF_BLOB;
	if (!artifact1 && diff_type != FNC_DIFF_BLOB) {
		artifact1 = "current";
		rc = fsl_sym_to_rid(f, artifact1, FSL_SATYPE_CHECKIN, &prid);
		if (rc || prid < 0)
			return RC(rc, "fsl_sym_to_rid");


	}
	if (!artifact2 && diff_type != FNC_DIFF_BLOB) {
		fsl_ckout_version_info(f, &rid, NULL);
		rc = fsl_ckout_changes_scan(f);
		if (rc)
			return RC(rc, "fsl_ckout_changes_scan");

		if (diff_type == FNC_DIFF_CKOUT && !fsl_ckout_has_changes(f)) {
			fsl_fprintf(stdout, "No local changes.\n");
			return rc;
		}
	}
	while (fcli_next_arg(false) && diff_type != FNC_DIFF_BLOB) {
		struct fnc_pathlist_entry	*ins;
		char				*path;

		rc = map_ckout_path(&path);
		if (rc) {
			if (rc != FSL_RC_NOT_FOUND) {
				if (!fsl_strcmp(artifact1, "current") &&
				    !artifact2) {
					rc = RC(rc, "invalid artifact hash: %s",
					    path);
				}
				goto end;
			}

			/* Path may be valid in tree of specified commit(s). */
			RC_RESET(rc);

			rc = valid_path(path, artifact1);

			if (rc) {
				if (rc != FSL_RC_UNKNOWN_RESOURCE || !artifact2)
					goto end;


				rc = valid_path(path, artifact2);
				if (rc == FSL_RC_UNKNOWN_RESOURCE)
					rc = RC(rc, "path not found in tree "
					    "[%s] or [%s]: %s",
					    artifact1, artifact2, path);





				if (rc)
					goto end;






			}
		}




		if (path == NULL)

			break;  /* work tree root */

		rc = fnc_pathlist_insert(&ins, &paths, path, NULL);
		if (rc || ins == NULL /* Duplicate path. */)
			fsl_free(path);
		if (rc)
			goto end;
	}

	if (diff_type != FNC_DIFF_BLOB && diff_type != FNC_DIFF_CKOUT) {
		q = fsl_stmt_malloc();
		rc = commit_builder(&commit, rid, q);







<
<
<
<
>







|
|
>
>



|
<

>
|





|
|
|
|

|







|

<
|
|
>
|
<
|
|
>
|
|
|
<
|
>
>
>
>
>


>
>
>
>
>
>
|
|
>
>
>
>
|
>
|
>
|

|







9542
9543
9544
9545
9546
9547
9548




9549
9550
9551
9552
9553
9554
9555
9556
9557
9558
9559
9560
9561
9562
9563
9564

9565
9566
9567
9568
9569
9570
9571
9572
9573
9574
9575
9576
9577
9578
9579
9580
9581
9582
9583
9584
9585
9586
9587

9588
9589
9590
9591

9592
9593
9594
9595
9596
9597

9598
9599
9600
9601
9602
9603
9604
9605
9606
9607
9608
9609
9610
9611
9612
9613
9614
9615
9616
9617
9618
9619
9620
9621
9622
9623
9624
9625
9626
9627
9628
9629
9630
9631
		if (!fsl_sym_to_rid(f, fcli_next_arg(false), FSL_SATYPE_ANY,
		    &rid)) {
			artifact2 = fcli_next_arg(true);
			diff_type = FNC_DIFF_COMMIT;
			if (!fsl_rid_is_a_checkin(f, rid))
				++blob;
		}




	}
	if (fcli_error()->code == FSL_RC_NOT_FOUND)
		RC_RESET(rc);  /* If args aren't symbols, treat as paths. */
	if (blob == 2)
		diff_type = FNC_DIFF_BLOB;
	if (!artifact1 && diff_type != FNC_DIFF_BLOB) {
		artifact1 = "current";
		rc = fsl_sym_to_rid(f, artifact1, FSL_SATYPE_CHECKIN, &prid);
		if (rc || prid < 0) {
			rc = RC(rc, "fsl_sym_to_rid");
			goto end;
		}
	}
	if (!artifact2 && diff_type != FNC_DIFF_BLOB) {
		fsl_ckout_version_info(f, &rid, NULL);
		if ((rc = fsl_ckout_changes_scan(f)))

			return RC(rc, "fsl_ckout_changes_scan");
		if (!fsl_strcmp(artifact1, "current") &&
		    !fsl_ckout_has_changes(f)) {
			fsl_fprintf(stdout, "No local changes.\n");
			return rc;
		}
	}
	while (fcli_next_arg(false) && diff_type != FNC_DIFF_BLOB) {
		struct fnc_pathlist_entry *ins;
		char *path, *path_to_diff;
		rc = map_repo_path(&path0);
		path = path0;
		if (rc) {
			if (rc != FSL_RC_UNKNOWN_RESOURCE) {
				if (!fsl_strcmp(artifact1, "current") &&
				    !artifact2) {
					rc = RC(rc, "invalid artifact hash: %s",
					    path);
				}
				goto end;
			}
			RC_RESET(rc);
			/* Path may be valid in tree of specified commit(s). */

			const fsl_card_F *cf = NULL;
			rc = fsl_deck_load_sym(f, &d, artifact1,
			    FSL_SATYPE_CHECKIN);
			if (rc)

				goto end;
			cf = fsl_deck_F_search(&d, path);
			if (cf == NULL) {
				if (!artifact2) {
					rc = RC(FSL_RC_UNKNOWN_RESOURCE,
					    "'%s' not found in tree [%s]", path,

					    artifact1);
					goto end;
				}
				fsl_deck_finalize(&d);
				rc = fsl_deck_load_sym(f, &d, artifact2,
				    FSL_SATYPE_CHECKIN);
				if (rc)
					goto end;
				cf = fsl_deck_F_search(&d, path);
				if (cf == NULL) {
					rc = RC(FSL_RC_NOT_FOUND,
					    "'%s' not found in trees [%s] [%s]",
					    path, artifact1, artifact2);
					goto end;
				}
			}
		} else
			while (path[0] == '/')
				++path;
		path_to_diff = fsl_strdup(path);
		if (path_to_diff == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			goto end;
		}
		rc = fnc_pathlist_insert(&ins, &paths, path_to_diff, NULL);
		if (rc || ins == NULL /* Duplicate path. */)
			fsl_free(path_to_diff);
		if (rc)
			goto end;
	}

	if (diff_type != FNC_DIFF_BLOB && diff_type != FNC_DIFF_CKOUT) {
		q = fsl_stmt_malloc();
		rc = commit_builder(&commit, rid, q);
9826
9827
9828
9829
9830
9831
9832

9833
9834
9835
9836
9837
9838
9839
9840
9841
9842
9843
9844
9845
9846
9847
9848
9849
9850
9851
9852
9853
9854
9855
9856
9857
9858
9859
9860
9861
9862
9863
9864
9865
9866
9867
9868
9869
9870
9871
9872
9873
9874
9875
9876
9877
9878
9879
9880
9881
9882
9883
9884
9885
9886
9887
9888
9889
9890
9891
		goto end;
	}

	rc = open_diff_view(view, commit, &paths, NULL, diff_mode);
	if (!rc)
		rc = view_loop(view);
end:

	fsl_deck_finalize(&d);
	fsl_stmt_finalize(q);
	if (commit)
		fnc_commit_artifact_close(commit);
	TAILQ_FOREACH(pe, &paths, entry)
		free((char *)pe->path);
	fnc_pathlist_free(&paths);
	return rc;
}

/*
 * Check that path is either a valid file or dir in the tree identified by sym.
 */
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;
	int			 rc;
	bool			 valid = false;

	rc = fsl_deck_load_sym(f, &d, sym, FSL_SATYPE_CHECKIN);
	if (rc)
		goto end;

	valid = !!(cf = fsl_deck_F_search(&d, path));
	if (valid)
		goto end;

	rc = fsl_deck_F_rewind(&d);
	if (rc)
		goto end;

	do {
		rc = fsl_deck_F_next(&d, &cf);
		if (rc)
			goto end;

		if (cf && !fsl_strncmp(path, cf->name, fsl_strlen(path) - 1))
			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);
	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)
{
	struct fnc_view	*tree_view;
	int		 rc = 0;








>










<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







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
		goto end;
	}

	rc = open_diff_view(view, commit, &paths, NULL, diff_mode);
	if (!rc)
		rc = view_loop(view);
end:
	fsl_free(path0);
	fsl_deck_finalize(&d);
	fsl_stmt_finalize(q);
	if (commit)
		fnc_commit_artifact_close(commit);
	TAILQ_FOREACH(pe, &paths, entry)
		free((char *)pe->path);
	fnc_pathlist_free(&paths);
	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)
{
	struct fnc_view	*tree_view;
	int		 rc = 0;

9911
9912
9913
9914
9915
9916
9917
9918
9919
9920
9921
9922
9923
9924
9925
9926
9927
9928
9929
9930
9931
9932
9933
9934
9935
9936
9937
9938
9939
9940
9941
9942
9943
9944
9945
9946
9947
9948

9949
9950
9951
9952
9953
9954
9955
	fsl_id_t	 rid;
	int		 rc = 0;

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		goto end;

	rc = map_ckout_path(&path);
	if (rc) {
		if (rc != FSL_RC_NOT_FOUND || !fnc_init.sym)
			goto end;

		/* Path may be valid in tree of specified commit. */
		RC_RESET(rc);
		rc = valid_path(path, fnc_init.sym);
		if (rc)
			goto end;
	}
	if (fnc_init.sym)
		rc = fsl_sym_to_rid(f, fnc_init.sym, FSL_SATYPE_ANY, &rid);
	else
		fsl_ckout_version_info(f, &rid, NULL);

	if (rc) {
		switch (rc) {
		case FSL_RC_AMBIGUOUS:
			RC(rc, "prefix too ambiguous [%s]",
			    fnc_init.sym);
			goto end;
		case FSL_RC_NOT_A_REPO:
			RC(rc, "%s tree needs a local checkout",
			    fcli_progname());
			goto end;
		case FSL_RC_NOT_FOUND:
			RC(rc, "invalid symbolic checkin name [%s]",
			    fnc_init.sym);
			goto end;
		case FSL_RC_MISUSE:

		default:
			goto end;
		}
	}

	/* In 'fnc tree -R repo.db [path]' case, use the latest checkin. */
	if (rid == 0) {







|
<
<
<
<
<
<
<
|
|
<




















>







9711
9712
9713
9714
9715
9716
9717
9718







9719
9720

9721
9722
9723
9724
9725
9726
9727
9728
9729
9730
9731
9732
9733
9734
9735
9736
9737
9738
9739
9740
9741
9742
9743
9744
9745
9746
9747
9748
	fsl_id_t	 rid;
	int		 rc = 0;

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		goto end;

	rc = map_repo_path(&path);







	if (rc)
		goto end;

	if (fnc_init.sym)
		rc = fsl_sym_to_rid(f, fnc_init.sym, FSL_SATYPE_ANY, &rid);
	else
		fsl_ckout_version_info(f, &rid, NULL);

	if (rc) {
		switch (rc) {
		case FSL_RC_AMBIGUOUS:
			RC(rc, "prefix too ambiguous [%s]",
			    fnc_init.sym);
			goto end;
		case FSL_RC_NOT_A_REPO:
			RC(rc, "%s tree needs a local checkout",
			    fcli_progname());
			goto end;
		case FSL_RC_NOT_FOUND:
			RC(rc, "invalid symbolic checkin name [%s]",
			    fnc_init.sym);
			goto end;
		case FSL_RC_MISUSE:
			/* FALL THROUGH */
		default:
			goto end;
		}
	}

	/* In 'fnc tree -R repo.db [path]' case, use the latest checkin. */
	if (rid == 0) {
10007
10008
10009
10010
10011
10012
10013
10014
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024
10025
10026
10027
10028
10029
10030
10031
10032
10033
10034
10035
10036
10037
10038
10039
10040
10041
10042
10043
	if (rc)
		goto end;

	/*
	 * Open the initial root level of the repository tree now. Subtrees
	 * opened during traversal are built and destroyed on demand.
	 */
	rc = tree_builder(s->repo, &s->root, NULL);
	if (rc)
		goto end;
	s->tree = s->root;
	/*
	 * If user has supplied a path arg (i.e., fnc tree path/in/repo), or
	 * has selected a commit from an 'fnc timeline path/in/repo' command,
	 * walk the path and open corresponding (sub)tree objects now.
	 */
	if (!fnc_path_is_root_dir(path)) {
		rc = walk_tree_path(s, path, view->nlines - 4);
		if (rc)
			goto end;
	}


	if ((s->tree_label = fsl_mprintf("checkin %s", s->commit_id)) == NULL) {
		rc = RC(FSL_RC_RANGE, "fsl_mprintf");
		goto end;
	}

	s->first_entry_onscreen = s->selected_entry = s->tree == s->root ?
	    &s->tree->entries[0] : NULL;

	if (s->colour) {
		STAILQ_INIT(&s->colours);
		rc = set_colours(&s->colours, FNC_VIEW_TREE);
		if (rc)
			goto end;
	}







|









|










|
|







9800
9801
9802
9803
9804
9805
9806
9807
9808
9809
9810
9811
9812
9813
9814
9815
9816
9817
9818
9819
9820
9821
9822
9823
9824
9825
9826
9827
9828
9829
9830
9831
9832
9833
9834
9835
9836
	if (rc)
		goto end;

	/*
	 * Open the initial root level of the repository tree now. Subtrees
	 * opened during traversal are built and destroyed on demand.
	 */
	rc = tree_builder(s->repo, &s->root, "/");
	if (rc)
		goto end;
	s->tree = s->root;
	/*
	 * If user has supplied a path arg (i.e., fnc tree path/in/repo), or
	 * has selected a commit from an 'fnc timeline path/in/repo' command,
	 * walk the path and open corresponding (sub)tree objects now.
	 */
	if (!fnc_path_is_root_dir(path)) {
		rc = walk_tree_path(s, s->repo, &s->root, path);
		if (rc)
			goto end;
	}


	if ((s->tree_label = fsl_mprintf("checkin %s", s->commit_id)) == NULL) {
		rc = RC(FSL_RC_RANGE, "fsl_mprintf");
		goto end;
	}

	s->first_entry_onscreen = &s->tree->entries[0];
	s->selected_entry = &s->tree->entries[0];

	if (s->colour) {
		STAILQ_INIT(&s->colours);
		rc = set_colours(&s->colours, FNC_VIEW_TREE);
		if (rc)
			goto end;
	}
10052
10053
10054
10055
10056
10057
10058
10059
10060
10061
10062
10063

10064
10065
10066
10067
10068
10069
10070
		close_tree_view(view);
	return rc;
}

/*
 * Decompose the supplied path into its constituent components, then build,
 * open and visit each subtree segment on the way to the requested entry.
 * Display max n entries per page, so if each visited dir is more than n
 * entries deep, scroll just enough to make it the last entry on screen.
 */
static int
walk_tree_path(struct fnc_tree_view_state *s, const char *path, uint16_t n)

{
	struct fnc_tree_object	*tree = NULL;
	const char		*p;
	char			*slash, *subpath = NULL;
	int			 rc = 0;

	/* Find each slash and open preceding directory segment as a tree. */







<
<


|
>







9845
9846
9847
9848
9849
9850
9851


9852
9853
9854
9855
9856
9857
9858
9859
9860
9861
9862
		close_tree_view(view);
	return rc;
}

/*
 * Decompose the supplied path into its constituent components, then build,
 * open and visit each subtree segment on the way to the requested entry.


 */
static int
walk_tree_path(struct fnc_tree_view_state *s, struct fnc_repository_tree *repo,
    struct fnc_tree_object **root, const char *path)
{
	struct fnc_tree_object	*tree = NULL;
	const char		*p;
	char			*slash, *subpath = NULL;
	int			 rc = 0;

	/* Find each slash and open preceding directory segment as a tree. */
10091
10092
10093
10094
10095
10096
10097
10098
10099
10100
10101
10102
10103
10104
10105
10106
10107
10108
10109
10110
10111
10112
10113
10114
10115
10116
10117
10118
10119
10120
10121
10122
10123
10124
10125
10126
10127
10128
10129
			rc = RC(FSL_RC_NOT_FOUND, "find_tree_entry(%s)",
			    te_name);
			fsl_free(te_name);
			break;
		}
		fsl_free(te_name);

		s->selected_entry = te;  /* dir matching provided path */

		/*
		 * If not in the root tree and the matching dir fits on the
		 * first page, the first displayed entry should be ".." (NULL),
		 * else scroll so the matching dir is the last entry on screen.
		 */
		s->first_entry_onscreen = &s->tree->entries[MAX(te->idx - n, 0)];
		if (__predict_true(!s->first_entry_onscreen->idx &&
		    s->selected_entry->idx < n && s->tree != s->root))
			s->first_entry_onscreen = NULL;
		if (!S_ISDIR(s->selected_entry->mode))
			break;	/* If a file, jump to this entry. */

		slash = strchr(p, '/');
		if (slash)
			subpath = fsl_strndup(path, slash - path);
		else
			subpath = fsl_strdup(path);
		if (subpath == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			break;
		}

		rc = tree_builder(s->repo, &tree, subpath);
		if (rc)
			break;
		rc = visit_subtree(s, tree);
		if (rc) {
			fnc_object_tree_close(tree);
			break;
		}







<
<
<
<
<
<
<
<
<
<
|













|







9883
9884
9885
9886
9887
9888
9889










9890
9891
9892
9893
9894
9895
9896
9897
9898
9899
9900
9901
9902
9903
9904
9905
9906
9907
9908
9909
9910
9911
			rc = RC(FSL_RC_NOT_FOUND, "find_tree_entry(%s)",
			    te_name);
			fsl_free(te_name);
			break;
		}
		fsl_free(te_name);











		s->first_entry_onscreen = s->selected_entry = te;
		if (!S_ISDIR(s->selected_entry->mode))
			break;	/* If a file, jump to this entry. */

		slash = strchr(p, '/');
		if (slash)
			subpath = fsl_strndup(path, slash - path);
		else
			subpath = fsl_strdup(path);
		if (subpath == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			break;
		}

		rc = tree_builder(repo, &tree, subpath + 1 /* Leading slash */);
		if (rc)
			break;
		rc = visit_subtree(s, tree);
		if (rc) {
			fnc_object_tree_close(tree);
			break;
		}
10218
10219
10220
10221
10222
10223
10224
10225
10226
10227
10228
10229
10230
10231

10232

10233
10234
10235
10236
10237
10238
10239
10240
10241
10242
10243
10244
10245
10246
10247
10248
10249
10250
10251
10252
10253
10254
10255
10256
10257
10258
10259
10260
10261
10262
10263
10264
	*tree = fsl_malloc(sizeof(**tree));
	if (*tree == NULL)
		return RC(FSL_RC_ERROR, "fsl_malloc");
	memset(*tree, 0, sizeof(**tree));

	/*
	 * Count how many elements will comprise the tree to be allocated.
	 * If dir is the root of the repository tree (i.e., NULL), only tree
	 * nodes (tn) with no parent_dir belong to this tree. Otherwise, tree
	 * nodes whose parent_dir matches dir will comprise the requested tree.
	 */
	for (tn = repo->head; tn; tn = tn->next)
		if ((!tn->parent_dir && !dir) ||
		    (tn->parent_dir && !fsl_strcmp(dir, tn->parent_dir->path)))

			++i;

	(*tree)->entries = calloc(i, sizeof(struct fnc_tree_entry));
	if ((*tree)->entries == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");
	/* Construct the tree to be displayed. */
	for (tn = repo->head, i = 0; tn; tn = tn->next) {
		if ((!tn->parent_dir && dir) ||
		    (tn->parent_dir && fsl_strcmp(dir, tn->parent_dir->path)))
			continue;
		te = &(*tree)->entries[i];
		te->mode = tn->mode;
		te->mtime = tn->mtime;
		te->basename = fsl_strdup(tn->basename);
		if (te->basename == NULL)
			return RC(FSL_RC_ERROR, "fsl_strdup");
		te->path = fsl_strdup(tn->path);
		if (te->path == NULL)
			return RC(FSL_RC_ERROR, "fsl_strdup");
		te->uuid = fsl_strdup(tn->uuid);
		if (te->uuid == NULL && !S_ISDIR(te->mode))
			return RC(FSL_RC_ERROR, "fsl_strdup");
		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;







|



|
|
|
>
|
>




|
|


















|







10000
10001
10002
10003
10004
10005
10006
10007
10008
10009
10010
10011
10012
10013
10014
10015
10016
10017
10018
10019
10020
10021
10022
10023
10024
10025
10026
10027
10028
10029
10030
10031
10032
10033
10034
10035
10036
10037
10038
10039
10040
10041
10042
10043
10044
10045
10046
10047
10048
	*tree = fsl_malloc(sizeof(**tree));
	if (*tree == NULL)
		return RC(FSL_RC_ERROR, "fsl_malloc");
	memset(*tree, 0, sizeof(**tree));

	/*
	 * Count how many elements will comprise the tree to be allocated.
	 * If dir is the root of the repository tree (i.e., "/"), only tree
	 * nodes (tn) with no parent_dir belong to this tree. Otherwise, tree
	 * nodes whose parent_dir matches dir will comprise the requested tree.
	 */
	for(tn = repo->head; tn; tn = tn->next) {
		if ((!tn->parent_dir && fsl_strcmp(dir, "/")) ||
		    (tn->parent_dir && fsl_strcmp(dir, tn->parent_dir->path)))
			continue;
		++i;
	}
	(*tree)->entries = calloc(i, sizeof(struct fnc_tree_entry));
	if ((*tree)->entries == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");
	/* Construct the tree to be displayed. */
	for(tn = repo->head, i = 0; tn; tn = tn->next) {
		if ((!tn->parent_dir && fsl_strcmp(dir, "/")) ||
		    (tn->parent_dir && fsl_strcmp(dir, tn->parent_dir->path)))
			continue;
		te = &(*tree)->entries[i];
		te->mode = tn->mode;
		te->mtime = tn->mtime;
		te->basename = fsl_strdup(tn->basename);
		if (te->basename == NULL)
			return RC(FSL_RC_ERROR, "fsl_strdup");
		te->path = fsl_strdup(tn->path);
		if (te->path == NULL)
			return RC(FSL_RC_ERROR, "fsl_strdup");
		te->uuid = fsl_strdup(tn->uuid);
		if (te->uuid == NULL && !S_ISDIR(te->mode))
			return RC(FSL_RC_ERROR, "fsl_strdup");
		te->idx = i++;
	}
	(*tree)->nentries = i;

	return 0;
}

#if 0
static void
delete_tree_node(struct fnc_tree_entry **head, struct fnc_tree_entry *del)
{
	struct fnc_tree_entry *temp = *head, *prev;
10427
10428
10429
10430
10431
10432
10433
10434
10435
10436
10437
10438
10439
10440
10441
10442
10443
10444
10445

10446
10447
10448
10449
10450
10451
10452
 * a timeline of all commits modifying path.
 */
static int
tree_entry_path(char **path, struct fnc_parent_trees *parents,
    struct fnc_tree_entry *te)
{
	struct fnc_parent_tree	*pt;
	size_t			 len = 1;  /* NUL */
	int			 rc = 0;

	TAILQ_FOREACH(pt, parents, entry)
		len += strlen(pt->selected_entry->basename) + 1 /* slash */;
	if (te)
		len += strlen(te->basename);

	*path = calloc(1, len);
	if (path == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");


	pt = TAILQ_LAST(parents, fnc_parent_trees);
	while (pt) {
		const char *name = pt->selected_entry->basename;
		if (strlcat(*path, name, len) >= len) {
			rc = RC(FSL_RC_RANGE, "strlcat(%s, %s, %lu)",
			    *path, name, len);
			goto end;







|











>







10211
10212
10213
10214
10215
10216
10217
10218
10219
10220
10221
10222
10223
10224
10225
10226
10227
10228
10229
10230
10231
10232
10233
10234
10235
10236
10237
 * a timeline of all commits modifying path.
 */
static int
tree_entry_path(char **path, struct fnc_parent_trees *parents,
    struct fnc_tree_entry *te)
{
	struct fnc_parent_tree	*pt;
	size_t			 len = 2;  /* Leading slash and NUL. */
	int			 rc = 0;

	TAILQ_FOREACH(pt, parents, entry)
		len += strlen(pt->selected_entry->basename) + 1 /* slash */;
	if (te)
		len += strlen(te->basename);

	*path = calloc(1, len);
	if (path == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");

	(*path)[0] = '/';  /* Make it absolute from the repository root. */
	pt = TAILQ_LAST(parents, fnc_parent_trees);
	while (pt) {
		const char *name = pt->selected_entry->basename;
		if (strlcat(*path, name, len) >= len) {
			rc = RC(FSL_RC_RANGE, "strlcat(%s, %s, %lu)",
			    *path, name, len);
			goto end;
10528
10529
10530
10531
10532
10533
10534
10535
10536
10537
10538
10539
10540
10541
10542
10543
10544
10545
10546
10547
10548
10549
10550
10551
10552
10553
10554
10555
10556
10557
10558
10559
10560
10561
10562
10563
10564
10565
10566

10567

10568

10569
10570
10571
10572
10573
10574
10575
	if (screen_is_shared(view))
		wattroff(view->window, A_REVERSE);
	fsl_free(wcstr);
	wcstr = NULL;
	if (--limit <= 0)
		return rc;

	/* Draw (sub)tree absolute repo path (prepend '/') so -1 from limit. */
	rc = formatln(&wcstr, &wstrlen, treepath, view->ncols - 1, 0, false);
	if (rc)
		return rc;
	wprintw(view->window, "/%ls", wcstr);	/* prepend slash */
	fsl_free(wcstr);
	wcstr = NULL;
	if (wstrlen < view->ncols)
		waddch(view->window, '\n');
	if (--limit <= 0)
		return rc;
	waddch(view->window, '\n');
	if (--limit <= 0)
		return rc;

	/* Write parent dir entry (i.e., "..") if top of the tree is in view. */
	n = 0;
	if (s->first_entry_onscreen == NULL) {
		te = &s->tree->entries[0];
		if (s->selected == 0) {
			wattr_on(view->window, A_REVERSE, NULL);
			s->selected_entry = NULL;
		}
		if (fsl_strlen(treepath)) {
			waddstr(view->window, "  ..\n");
			n = 1;
		}
		if (s->selected == 0)
			wattr_off(view->window, A_REVERSE, NULL);
		++s->ndisplayed;
		if (--limit <= 0)
			return rc;

	} else

		te = s->first_entry_onscreen;


	nentries = s->tree->nentries;
	for (idx = 0; idx < nentries; ++idx)	/* Find max hash length. */
		hashlen = MAX(fsl_strlen(s->tree->entries[idx].uuid), hashlen);
	/* Iterate and write tree nodes postfixed with path type identifier. */
	for (idx = te->idx; idx < nentries; ++idx) {
		char		*line = NULL, *idstr = NULL, *targetlnk = NULL;







|
|


|


|








<






<
|
<
<





>
|
>

>







10313
10314
10315
10316
10317
10318
10319
10320
10321
10322
10323
10324
10325
10326
10327
10328
10329
10330
10331
10332
10333
10334
10335

10336
10337
10338
10339
10340
10341

10342


10343
10344
10345
10346
10347
10348
10349
10350
10351
10352
10353
10354
10355
10356
10357
10358
10359
	if (screen_is_shared(view))
		wattroff(view->window, A_REVERSE);
	fsl_free(wcstr);
	wcstr = NULL;
	if (--limit <= 0)
		return rc;

	/* Write this (sub)tree's absolute repository path subheader. */
	rc = formatln(&wcstr, &wstrlen, treepath, view->ncols, 0, false);
	if (rc)
		return rc;
	waddwstr(view->window, wcstr);
	fsl_free(wcstr);
	wcstr = NULL;
	if (wstrlen < view->ncols - 1)
		waddch(view->window, '\n');
	if (--limit <= 0)
		return rc;
	waddch(view->window, '\n');
	if (--limit <= 0)
		return rc;

	/* Write parent dir entry (i.e., "..") if top of the tree is in view. */

	if (s->first_entry_onscreen == NULL) {
		te = &s->tree->entries[0];
		if (s->selected == 0) {
			wattr_on(view->window, A_REVERSE, NULL);
			s->selected_entry = NULL;
		}

		waddstr(view->window, "  ..\n");


		if (s->selected == 0)
			wattr_off(view->window, A_REVERSE, NULL);
		++s->ndisplayed;
		if (--limit <= 0)
			return rc;
		n = 1;
	} else {
		n = 0;
		te = s->first_entry_onscreen;
	}

	nentries = s->tree->nentries;
	for (idx = 0; idx < nentries; ++idx)	/* Find max hash length. */
		hashlen = MAX(fsl_strlen(s->tree->entries[idx].uuid), hashlen);
	/* Iterate and write tree nodes postfixed with path type identifier. */
	for (idx = te->idx; idx < nentries; ++idx) {
		char		*line = NULL, *idstr = NULL, *targetlnk = NULL;
11019
11020
11021
11022
11023
11024
11025
11026
11027
11028
11029
11030
11031
11032
11033
11034
11035
11036
11037
11038
11039
11040
11041
11042
11043
11044
11045
11046
11047
11048
11049
11050
11051
11052
11053
11054
11055
11056
11057
11058
11059
11060
11061
11062
11063
11064
11065
11066
11067
11068
11069
			next = get_tree_entry(s->tree, next->idx + 1);
		}
	}

	return FSL_RC_OK;
}

/*
 * Visit subtree by assigning the current tree, selected and first displayed
 * entries, and selected line index to a new parent tree node to be inserted
 * into the parents linked list. Then make subtree the current tree.
 */
static int
visit_subtree(struct fnc_tree_view_state *s, struct fnc_tree_object *subtree)
{
	struct fnc_parent_tree	*parent;

	parent = calloc(1, sizeof(*parent));
	if (parent == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");

	parent->tree = s->tree;
	parent->first_entry_onscreen = s->first_entry_onscreen;
	parent->selected_entry = s->selected_entry;
	/*
	 * If not the first page of entries (".." isn't visible), the line to
	 * select is the difference betwixt selected & first displayed entries.
	 */
	parent->selected = s->first_entry_onscreen ?
	    s->selected_entry->idx - s->first_entry_onscreen->idx :
	    s->selected_entry->idx + 1;

	TAILQ_INSERT_HEAD(&s->parents, parent, entry);
	s->tree = subtree;
	s->selected = 0;
	s->first_entry_onscreen = NULL;

	return FSL_RC_OK;
}

static int
blame_tree_entry(struct fnc_view **new_view, int start_col, int start_ln,
    struct fnc_tree_entry *te, struct fnc_parent_trees *parents,
    fsl_uuid_cstr commit_id)
{
	struct fnc_view	*blame_view;
	char		*path;
	int		 rc = 0;

	*new_view = NULL;








<
<
<
<
<












<
<
<
<
|
<
<
<





|





|







10803
10804
10805
10806
10807
10808
10809





10810
10811
10812
10813
10814
10815
10816
10817
10818
10819
10820
10821




10822



10823
10824
10825
10826
10827
10828
10829
10830
10831
10832
10833
10834
10835
10836
10837
10838
10839
10840
10841
			next = get_tree_entry(s->tree, next->idx + 1);
		}
	}

	return FSL_RC_OK;
}






static int
visit_subtree(struct fnc_tree_view_state *s, struct fnc_tree_object *subtree)
{
	struct fnc_parent_tree	*parent;

	parent = calloc(1, sizeof(*parent));
	if (parent == NULL)
		return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");

	parent->tree = s->tree;
	parent->first_entry_onscreen = s->first_entry_onscreen;
	parent->selected_entry = s->selected_entry;




	parent->selected = s->selected;



	TAILQ_INSERT_HEAD(&s->parents, parent, entry);
	s->tree = subtree;
	s->selected = 0;
	s->first_entry_onscreen = NULL;

	return 0;
}

static int
blame_tree_entry(struct fnc_view **new_view, int start_col, int start_ln,
    struct fnc_tree_entry *te, struct fnc_parent_trees *parents,
    fsl_uuid_str commit_id)
{
	struct fnc_view	*blame_view;
	char		*path;
	int		 rc = 0;

	*new_view = NULL;

11284
11285
11286
11287
11288
11289
11290
11291
11292
11293



11294
11295
11296
11297
11298
11299
11300

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		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);
	}

	setid = fnc_conf_str2enum(opt);
	if (!setid)
		return RC(FSL_RC_NOT_FOUND, "invalid setting: %s", opt);








|
|

>
>
>







11056
11057
11058
11059
11060
11061
11062
11063
11064
11065
11066
11067
11068
11069
11070
11071
11072
11073
11074
11075

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		return rc;

	opt = fcli_next_arg(true);
	if (opt == NULL || fnc_init.lsconf) {
		if (fnc_init.unset) {
			fnc_init.err = RC(FSL_RC_MISSING_INFO,
			    "-u|--unset requires <setting>");
			usage();
			/* NOT REACHED */
		}
		return fnc_conf_lsopt(fnc_init.lsconf ? false : true);
	}

	setid = fnc_conf_str2enum(opt);
	if (!setid)
		return RC(FSL_RC_NOT_FOUND, "invalid setting: %s", opt);

11425
11426
11427
11428
11429
11430
11431
11432
11433
11434
11435
11436
11437
11438
11439
		const int pairs_diff[][2] = {
		    {LINE_DIFF_META, init_colour(FNC_COLOUR_DIFF_META)},
		    {LINE_DIFF_USER, init_colour(FNC_COLOUR_USER)},
		    {LINE_DIFF_DATE, init_colour(FNC_COLOUR_DATE)},
		    {LINE_DIFF_TAGS, init_colour(FNC_COLOUR_DIFF_TAGS)},
		    {LINE_DIFF_MINUS, init_colour(FNC_COLOUR_DIFF_MINUS)},
		    {LINE_DIFF_PLUS, init_colour(FNC_COLOUR_DIFF_PLUS)},
		    {LINE_DIFF_HUNK, init_colour(FNC_COLOUR_DIFF_HUNK)},
		    {LINE_DIFF_EDIT, init_colour(FNC_COLOUR_DIFF_SBS_EDIT)}
		};
		rc = set_colour_scheme(s, pairs_diff, regexp_diff,
		    nitems(regexp_diff));
		break;
	}
	case FNC_VIEW_TREE: {







|







11200
11201
11202
11203
11204
11205
11206
11207
11208
11209
11210
11211
11212
11213
11214
		const int pairs_diff[][2] = {
		    {LINE_DIFF_META, init_colour(FNC_COLOUR_DIFF_META)},
		    {LINE_DIFF_USER, init_colour(FNC_COLOUR_USER)},
		    {LINE_DIFF_DATE, init_colour(FNC_COLOUR_DATE)},
		    {LINE_DIFF_TAGS, init_colour(FNC_COLOUR_DIFF_TAGS)},
		    {LINE_DIFF_MINUS, init_colour(FNC_COLOUR_DIFF_MINUS)},
		    {LINE_DIFF_PLUS, init_colour(FNC_COLOUR_DIFF_PLUS)},
		    {LINE_DIFF_CHUNK, init_colour(FNC_COLOUR_DIFF_CHUNK)},
		    {LINE_DIFF_EDIT, init_colour(FNC_COLOUR_DIFF_SBS_EDIT)}
		};
		rc = set_colour_scheme(s, pairs_diff, regexp_diff,
		    nitems(regexp_diff));
		break;
	}
	case FNC_VIEW_TREE: {
11613
11614
11615
11616
11617
11618
11619
11620
11621
11622
11623
11624
11625
11626
11627
		return COLOR_GREEN;
	case FNC_COLOUR_USER:
	case FNC_COLOUR_DIFF_PLUS:
	case FNC_COLOUR_TREE_DIR:
	case FNC_COLOUR_BRANCH_OPEN:
		return COLOR_CYAN;
	case FNC_COLOUR_DATE:
	case FNC_COLOUR_DIFF_HUNK:
	case FNC_COLOUR_BRANCH_PRIVATE:
		return COLOR_YELLOW;
	case FNC_COLOUR_DIFF_MINUS:
	case FNC_COLOUR_DIFF_TAGS:
	case FNC_COLOUR_TREE_LINK:
	case FNC_COLOUR_BRANCH_CLOSED:
		return COLOR_MAGENTA;







|







11388
11389
11390
11391
11392
11393
11394
11395
11396
11397
11398
11399
11400
11401
11402
		return COLOR_GREEN;
	case FNC_COLOUR_USER:
	case FNC_COLOUR_DIFF_PLUS:
	case FNC_COLOUR_TREE_DIR:
	case FNC_COLOUR_BRANCH_OPEN:
		return COLOR_CYAN;
	case FNC_COLOUR_DATE:
	case FNC_COLOUR_DIFF_CHUNK:
	case FNC_COLOUR_BRANCH_PRIVATE:
		return COLOR_YELLOW;
	case FNC_COLOUR_DIFF_MINUS:
	case FNC_COLOUR_DIFF_TAGS:
	case FNC_COLOUR_TREE_LINK:
	case FNC_COLOUR_BRANCH_CLOSED:
		return COLOR_MAGENTA;
11719
11720
11721
11722
11723
11724
11725
11726
11727
11728
11729
11730
11731
11732
11733
static int
cmd_blame(fcli_command const *argv)
{
	fsl_cx		*const f = fcli_cx();
	struct fnc_view	*view;
	char		*path = NULL;
	fsl_uuid_str	 commit_id = NULL;
	fsl_id_t	 oid = 0, rid = 0;
	long		 nlimit = 0;
	int		 rc = 0;

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		goto end;
	if (!fcli_next_arg(false)) {







|







11494
11495
11496
11497
11498
11499
11500
11501
11502
11503
11504
11505
11506
11507
11508
static int
cmd_blame(fcli_command const *argv)
{
	fsl_cx		*const f = fcli_cx();
	struct fnc_view	*view;
	char		*path = NULL;
	fsl_uuid_str	 commit_id = NULL;
	fsl_id_t	 tip = 0, rid = 0;
	long		 nlimit = 0;
	int		 rc = 0;

	rc = fcli_process_flags(argv->flags);
	if (rc || (rc = fcli_has_unused_flags(false)))
		goto end;
	if (!fcli_next_arg(false)) {
11753
11754
11755
11756
11757
11758
11759
11760
11761
11762

11763
11764
11765
11766
11767
11768
11769
11770
11771
11772
11773
11774
11775
11776
11777
11778
11779
11780
11781
11782
11783
11784
11785
11786
11787
11788
11789
11790
11791
11792
11793
11794
11795
11796
11797
11798
11799
11800
11801
11802
11803
11804
11805
11806
11807
11808
11809
11810
11811
11812
11813
11814
11815
11816
11817
11818
11819
11820
11821
11822
		if (fnc_init.reverse) {
			if (!fnc_init.sym) {
				rc = RC(FSL_RC_MISSING_INFO,
				    "%s blame --reverse requires --commit",
				    fcli_progname());
				goto end;
			}
			rc = fsl_sym_to_rid(f, fnc_init.sym,
			    FSL_SATYPE_CHECKIN, &oid);
		} else

			rc = fsl_sym_to_rid(f, fnc_init.sym,
			    FSL_SATYPE_CHECKIN, &rid);
		if (rc)
			goto end;
	} else if (!fnc_init.sym) {
		fsl_ckout_version_info(f, &rid, NULL);
		if (!rid)  /* -R|--repo option used */
			fsl_sym_to_rid(f, "tip", FSL_SATYPE_CHECKIN, &rid);
	}

	rc = map_ckout_path(&path);
	if (rc) {
		if (rc != FSL_RC_NOT_FOUND || !fnc_init.sym)
			goto end;
		/* Path may be valid in repository tree of specified commit. */
		RC_RESET(rc);
	}

	commit_id = fsl_rid_to_uuid(f, rid ? rid : f->ckout.rid);
	if (!path) {
		rc = RC(FSL_RC_MISSING_INFO,
		    "%s blame requires versioned file path", fcli_progname());
		goto end;
	}

	rc = init_curses();
	if (rc)
		goto end;
	rc = init_unveil(((const char *[]){REPODB, CKOUTDIR, P_tmpdir,
	    gettzfile()}), ((const char *[]){"rw", "rwc", "rwc", "r"}), 4, true);
	if (rc)
		goto end;

	view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME);
	if (view == NULL) {
		rc = RC(FSL_RC_ERROR, "view_open");
		goto end;
	}

	rc = open_blame_view(view, path, commit_id, oid, nlimit,
	    fnc_init.lineno);
	if (rc)
		goto end;
	rc = view_loop(view);
end:
	fsl_free(path);
	fsl_free(commit_id);
	return rc;
}

static int
open_blame_view(struct fnc_view *view, char *path, fsl_uuid_cstr commit_id,
    fsl_id_t oid, int nlimit, const char *lineno)
{
	struct fnc_blame_view_state	*s = &view->state.blame;
	int				 rc = 0;

	CONCAT(STAILQ, _INIT)(&s->blamed_commits);

	s->path = fsl_strdup(path);







|
|
|
>
|
<








|

|





|
|
|


















|











|
|







11528
11529
11530
11531
11532
11533
11534
11535
11536
11537
11538
11539

11540
11541
11542
11543
11544
11545
11546
11547
11548
11549
11550
11551
11552
11553
11554
11555
11556
11557
11558
11559
11560
11561
11562
11563
11564
11565
11566
11567
11568
11569
11570
11571
11572
11573
11574
11575
11576
11577
11578
11579
11580
11581
11582
11583
11584
11585
11586
11587
11588
11589
11590
11591
11592
11593
11594
11595
11596
11597
		if (fnc_init.reverse) {
			if (!fnc_init.sym) {
				rc = RC(FSL_RC_MISSING_INFO,
				    "%s blame --reverse requires --commit",
				    fcli_progname());
				goto end;
			}
			rc = fsl_sym_to_rid(f, "tip", FSL_SATYPE_CHECKIN, &tip);
			if (rc)
				goto end;
		}
		rc = fsl_sym_to_rid(f, fnc_init.sym, FSL_SATYPE_CHECKIN, &rid);

		if (rc)
			goto end;
	} else if (!fnc_init.sym) {
		fsl_ckout_version_info(f, &rid, NULL);
		if (!rid)  /* -R|--repo option used */
			fsl_sym_to_rid(f, "tip", FSL_SATYPE_CHECKIN, &rid);
	}

	rc = map_repo_path(&path);
	if (rc) {
		if (rc != FSL_RC_UNKNOWN_RESOURCE || !fnc_init.sym)
			goto end;
		/* Path may be valid in repository tree of specified commit. */
		RC_RESET(rc);
	}

	commit_id = fsl_rid_to_uuid(f, rid);
	if (rc || (path[0] == '/' && path[1] == '\0')) {
		rc = rc ? rc : RC(FSL_RC_MISSING_INFO,
		    "%s blame requires versioned file path", fcli_progname());
		goto end;
	}

	rc = init_curses();
	if (rc)
		goto end;
	rc = init_unveil(((const char *[]){REPODB, CKOUTDIR, P_tmpdir,
	    gettzfile()}), ((const char *[]){"rw", "rwc", "rwc", "r"}), 4, true);
	if (rc)
		goto end;

	view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME);
	if (view == NULL) {
		rc = RC(FSL_RC_ERROR, "view_open");
		goto end;
	}

	rc = open_blame_view(view, path, commit_id, tip, nlimit,
	    fnc_init.lineno);
	if (rc)
		goto end;
	rc = view_loop(view);
end:
	fsl_free(path);
	fsl_free(commit_id);
	return rc;
}

static int
open_blame_view(struct fnc_view *view, char *path, fsl_uuid_str commit_id,
    fsl_id_t tip, int nlimit, const char *lineno)
{
	struct fnc_blame_view_state	*s = &view->state.blame;
	int				 rc = 0;

	CONCAT(STAILQ, _INIT)(&s->blamed_commits);

	s->path = fsl_strdup(path);
11833
11834
11835
11836
11837
11838
11839
11840
11841
11842
11843
11844
11845
11846
11847
	    entry);
	memset(&s->blame, 0, sizeof(s->blame));
	s->first_line_onscreen = 1;
	s->last_line_onscreen = view->nlines;
	s->selected_line = 1;
	s->blame_complete = false;
	s->commit_id = commit_id;
	s->blame.origin = oid;
	s->blame.nlimit = nlimit;
	s->spin_idx = 0;
	s->colour = !fnc_init.nocolour && has_colors();
	s->lineno = lineno;

	if (s->colour) {
		STAILQ_INIT(&s->colours);







|







11608
11609
11610
11611
11612
11613
11614
11615
11616
11617
11618
11619
11620
11621
11622
	    entry);
	memset(&s->blame, 0, sizeof(s->blame));
	s->first_line_onscreen = 1;
	s->last_line_onscreen = view->nlines;
	s->selected_line = 1;
	s->blame_complete = false;
	s->commit_id = commit_id;
	s->blame.origin = tip;
	s->blame.nlimit = nlimit;
	s->spin_idx = 0;
	s->colour = !fnc_init.nocolour && has_colors();
	s->lineno = lineno;

	if (s->colour) {
		STAILQ_INIT(&s->colours);
11866
11867
11868
11869
11870
11871
11872

11873
11874







11875
11876
11877
11878
11879
11880
11881
11882
11883
11884
11885
11886
11887
11888
11889
11890
11891
	struct fnc_blame_view_state	*s = &view->state.blame;
	struct fnc_blame		*blame = &s->blame;
	fsl_deck			 d = fsl_deck_empty;
	fsl_buffer			 buf = fsl_buffer_empty;
	fsl_annotate_opt		*opt = NULL;
	const fsl_card_F		*cf;
	char				*filepath = NULL;

	int				 rc = 0;








	filepath = s->path;

	rc = fsl_deck_load_sym(f, &d, s->blamed_commit->id, FSL_SATYPE_CHECKIN);
	if (rc)
		goto end;

	cf = fsl_deck_F_search(&d, filepath);
	if (cf == NULL) {
		rc = RC(FSL_RC_UNKNOWN_RESOURCE, "path not found in [%s]: %s",
		    s->blamed_commit->id, filepath);
		goto end;
	}
	rc = fsl_card_F_content(f, cf, &buf);
	if (rc)
		goto end;
	if (fsl_looks_like_binary(&buf)) {
		rc = RC(FSL_RC_DIFF_BINARY, "cannot blame binary file");







>


>
>
>
>
>
>
>
|







|
|







11641
11642
11643
11644
11645
11646
11647
11648
11649
11650
11651
11652
11653
11654
11655
11656
11657
11658
11659
11660
11661
11662
11663
11664
11665
11666
11667
11668
11669
11670
11671
11672
11673
11674
	struct fnc_blame_view_state	*s = &view->state.blame;
	struct fnc_blame		*blame = &s->blame;
	fsl_deck			 d = fsl_deck_empty;
	fsl_buffer			 buf = fsl_buffer_empty;
	fsl_annotate_opt		*opt = NULL;
	const fsl_card_F		*cf;
	char				*filepath = NULL;
	char				*master = NULL, *root = NULL;
	int				 rc = 0;

	/*
	 * Trim prefixed '/' if path has been processed by map_repo_path(),
	 * which only occurs when the -c option has not been passed.
	 * XXX This slash trimming is cumbersome; we should not prefix a slash
	 * in map_repo_path() as we only want the slash for displaying an
	 * absolute-repository-relative path, so we should prefix it only then.
	 */
	filepath = s->path[0] != '/' ? s->path : s->path + 1;

	rc = fsl_deck_load_sym(f, &d, s->blamed_commit->id, FSL_SATYPE_CHECKIN);
	if (rc)
		goto end;

	cf = fsl_deck_F_search(&d, filepath);
	if (cf == NULL) {
		rc = RC(FSL_RC_NOT_FOUND, "'%s' not found in tree [%s]",
		    filepath, s->blamed_commit->id);
		goto end;
	}
	rc = fsl_card_F_content(f, cf, &buf);
	if (rc)
		goto end;
	if (fsl_looks_like_binary(&buf)) {
		rc = RC(FSL_RC_DIFF_BINARY, "cannot blame binary file");
11905
11906
11907
11908
11909
11910
11911
11912
11913
11914
11915
11916
11917
11918
11919
	opt = &blame->thread_cx.blame_opt;
	opt->filename = fsl_strdup(filepath);
	fcli_fax((char *)opt->filename);
	rc = fsl_sym_to_rid(f, s->blamed_commit->id, FSL_SATYPE_CHECKIN,
	    &opt->versionRid);
	if (rc)
		goto end;
	opt->originRid = blame->origin;    /* -c version when -r is passed */
	if (blame->nlimit < 0)
		opt->limitMs = abs(blame->nlimit) * 1000;
	else
		opt->limitVersions = blame->nlimit;
	opt->out = blame_cb;
	opt->outState = &blame->cb_cx;








|







11688
11689
11690
11691
11692
11693
11694
11695
11696
11697
11698
11699
11700
11701
11702
	opt = &blame->thread_cx.blame_opt;
	opt->filename = fsl_strdup(filepath);
	fcli_fax((char *)opt->filename);
	rc = fsl_sym_to_rid(f, s->blamed_commit->id, FSL_SATYPE_CHECKIN,
	    &opt->versionRid);
	if (rc)
		goto end;
	opt->originRid = blame->origin;    /* tip when -r is passed */
	if (blame->nlimit < 0)
		opt->limitMs = abs(blame->nlimit) * 1000;
	else
		opt->limitVersions = blame->nlimit;
	opt->out = blame_cb;
	opt->outState = &blame->cb_cx;

11939
11940
11941
11942
11943
11944
11945
















11946
11947
11948
11949
11950




11951
11952
11953
11954
11955
11956
11957
11958
11959
11960
11961
11962
11963
11964
11965
11966
11967


11968
11969
11970
11971
11972
11973
11974
	}

	blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
	if (blame->lines == NULL) {
		rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");
		goto end;
	}

















	blame->cb_cx.view = view;
	blame->cb_cx.lines = blame->lines;
	blame->cb_cx.nlines = blame->nlines;
	blame->cb_cx.commit_id = s->blamed_commit->id;




	blame->cb_cx.quit = &s->done;

	blame->thread_cx.path = s->path;
	blame->thread_cx.cb_cx = &blame->cb_cx;
	blame->thread_cx.complete = &s->blame_complete;
	blame->thread_cx.cancel_cb = cancel_blame;
	blame->thread_cx.cancel_cx = &s->done;
	s->blame_complete = false;

	if (s->first_line_onscreen + view->nlines - 1 > blame->nlines) {
		s->first_line_onscreen = 1;
		s->last_line_onscreen = view->nlines;
		s->selected_line = 1;
	}
	s->matched_line = 0;
	s->maxx = &blame->thread_cx.cb_cx->maxlen;
end:


	fsl_deck_finalize(&d);
	fsl_buffer_clear(&buf);
	if (rc)
		stop_blame(blame);
	return rc;
}








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




|
>
>
>
>

















>
>







11722
11723
11724
11725
11726
11727
11728
11729
11730
11731
11732
11733
11734
11735
11736
11737
11738
11739
11740
11741
11742
11743
11744
11745
11746
11747
11748
11749
11750
11751
11752
11753
11754
11755
11756
11757
11758
11759
11760
11761
11762
11763
11764
11765
11766
11767
11768
11769
11770
11771
11772
11773
11774
11775
11776
11777
11778
11779
	}

	blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
	if (blame->lines == NULL) {
		rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "calloc");
		goto end;
	}

	master = fsl_config_get_text(f, FSL_CONFDB_REPO, "main-branch", NULL);
	if (master == NULL) {
		master = fsl_strdup("trunk");
		if (master == NULL) {
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			goto end;
		}
	}
	root = fsl_mprintf("root:%s", master);
	rc = fsl_sym_to_uuid(f, root, FSL_SATYPE_CHECKIN,
	    &blame->cb_cx.root_commit, NULL);
	if (rc) {
		rc = RC(rc, "fsl_sym_to_uuid");
		goto end;
	}

	blame->cb_cx.view = view;
	blame->cb_cx.lines = blame->lines;
	blame->cb_cx.nlines = blame->nlines;
	blame->cb_cx.commit_id = fsl_strdup(s->blamed_commit->id);
	if (blame->cb_cx.commit_id == NULL) {
		rc = RC(FSL_RC_ERROR, "fsl_strdup");
		goto end;
	}
	blame->cb_cx.quit = &s->done;

	blame->thread_cx.path = s->path;
	blame->thread_cx.cb_cx = &blame->cb_cx;
	blame->thread_cx.complete = &s->blame_complete;
	blame->thread_cx.cancel_cb = cancel_blame;
	blame->thread_cx.cancel_cx = &s->done;
	s->blame_complete = false;

	if (s->first_line_onscreen + view->nlines - 1 > blame->nlines) {
		s->first_line_onscreen = 1;
		s->last_line_onscreen = view->nlines;
		s->selected_line = 1;
	}
	s->matched_line = 0;
	s->maxx = &blame->thread_cx.cb_cx->maxlen;
end:
	fsl_free(master);
	fsl_free(root);
	fsl_deck_finalize(&d);
	fsl_buffer_clear(&buf);
	if (rc)
		stop_blame(blame);
	return rc;
}

12153
12154
12155
12156
12157
12158
12159
12160
12161

12162

12163
12164
12165
12166
12167
12168
12169
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			goto end;
		}
		line->annotated = true;
	} else
		line->id = NULL;

	/* -r can return lines with no version (cf. fossil(1)). */
	if (opt->originRid && !line->id)

		line->annotated = false;


	line->lineno = step->lineNumber;
	cx->maxlen = MAX(step->lineLength, cx->maxlen);
	++cx->nlines;
end:
	rc = pthread_mutex_unlock(&fnc_mutex);
	if (rc)







|
|
>
|
>







11958
11959
11960
11961
11962
11963
11964
11965
11966
11967
11968
11969
11970
11971
11972
11973
11974
11975
11976
			rc = RC(FSL_RC_ERROR, "fsl_strdup");
			goto end;
		}
		line->annotated = true;
	} else
		line->id = NULL;

	/* -r can return lines with no version, so use root check-in. */
	if (opt->originRid && !line->id) {
		line->id = fsl_strdup(cx->root_commit);
		line->annotated = true;
	}

	line->lineno = step->lineNumber;
	cx->maxlen = MAX(step->lineLength, cx->maxlen);
	++cx->nlines;
end:
	rc = pthread_mutex_unlock(&fnc_mutex);
	if (rc)
12178
12179
12180
12181
12182
12183
12184
12185
12186
12187
12188
12189
12190
12191
12192
	struct fnc_blame_view_state	*s = &view->state.blame;
	struct fnc_blame		*blame = &s->blame;
	struct fnc_blame_line		*blame_line;
	regmatch_t			*regmatch = &view->regmatch;
	struct fnc_colour		*c = NULL;
	wchar_t				*wcstr;
	char				*line = NULL;
	fsl_uuid_cstr			 prev_id = NULL;
	ssize_t				 linelen;
	size_t				 linesz = 0;
	int				 col, width, lineno = 0, nprinted = 0;
	int				 rc = FSL_RC_OK;
	const int			 idfield = 11;  /* Prefix + space. */
	bool				 selected;








|







11985
11986
11987
11988
11989
11990
11991
11992
11993
11994
11995
11996
11997
11998
11999
	struct fnc_blame_view_state	*s = &view->state.blame;
	struct fnc_blame		*blame = &s->blame;
	struct fnc_blame_line		*blame_line;
	regmatch_t			*regmatch = &view->regmatch;
	struct fnc_colour		*c = NULL;
	wchar_t				*wcstr;
	char				*line = NULL;
	fsl_uuid_str			 prev_id = NULL;
	ssize_t				 linelen;
	size_t				 linesz = 0;
	int				 col, width, lineno = 0, nprinted = 0;
	int				 rc = FSL_RC_OK;
	const int			 idfield = 11;  /* Prefix + space. */
	bool				 selected;

12219
12220
12221
12222
12223
12224
12225
12226
12227
12228
12229
12230
12231
12232
12233
	if (screen_is_shared(view))
		wattroff(view->window, A_REVERSE);
	fsl_free(wcstr);
	wcstr = NULL;

	line = fsl_mprintf("[%d/%d] %s%s %c", s->gtl ? s->gtl :
	    MIN(blame->nlines, s->first_line_onscreen - 1 + s->selected_line),
	    blame->nlines, s->blame_complete ? "/" : "annotating... /",
	    s->path, s->blame_complete ? ' ' : SPINNER[s->spin_idx]);
	if (SPINNER[++s->spin_idx] == '\0')
		s->spin_idx = 0;
	rc = formatln(&wcstr, &width, line, view->ncols, 0, false);
	fsl_free(line);
	line = NULL;
	if (rc)







|







12026
12027
12028
12029
12030
12031
12032
12033
12034
12035
12036
12037
12038
12039
12040
	if (screen_is_shared(view))
		wattroff(view->window, A_REVERSE);
	fsl_free(wcstr);
	wcstr = NULL;

	line = fsl_mprintf("[%d/%d] %s%s %c", s->gtl ? s->gtl :
	    MIN(blame->nlines, s->first_line_onscreen - 1 + s->selected_line),
	    blame->nlines, s->blame_complete ? "" : "annotating... ",
	    s->path, s->blame_complete ? ' ' : SPINNER[s->spin_idx]);
	if (SPINNER[++s->spin_idx] == '\0')
		s->spin_idx = 0;
	rc = formatln(&wcstr, &width, line, view->ncols, 0, false);
	fsl_free(line);
	line = NULL;
	if (rc)
12255
12256
12257
12258
12259
12260
12261
12262
12263
12264
12265
12266
12267
12268
12269
		if (++lineno < s->first_line_onscreen)
			continue;
		if (s->gtl)
			if (!gotoline(view, &lineno, &nprinted))
				continue;

		if ((selected = nprinted == s->selected_line - 1)) {
			rx = FNC_HIGHLIGHT;
			wattron(view->window, rx);
		}

		if (blame->nlines > 0) {
			blame_line = &blame->lines[lineno - 1];
			if (blame_line->annotated && prev_id &&
			    !fsl_uuidcmp(prev_id, blame_line->id) &&







|







12062
12063
12064
12065
12066
12067
12068
12069
12070
12071
12072
12073
12074
12075
12076
		if (++lineno < s->first_line_onscreen)
			continue;
		if (s->gtl)
			if (!gotoline(view, &lineno, &nprinted))
				continue;

		if ((selected = nprinted == s->selected_line - 1)) {
			rx = A_BOLD | A_REVERSE;
			wattron(view->window, rx);
		}

		if (blame->nlines > 0) {
			blame_line = &blame->lines[lineno - 1];
			if (blame_line->annotated && prev_id &&
			    !fsl_uuidcmp(prev_id, blame_line->id) &&
12536
12537
12538
12539
12540
12541
12542
12543




12544





12545

12546
12547
12548




12549
12550
12551
12552
12553
12554
12555
12556
12557
12558
12559
12560
12561
12562
12563
12564
12565

12566
12567
12568
12569


12570
12571
12572
12573
12574
12575
12576
12577
12578
12579
12580
12581
12582
12583
12584
12585
12586


12587
12588
12589
12590
12591
12592
12593
			fsl_uuid_str	 pid = fsl_db_g_text(db, NULL,
			    "SELECT uuid FROM plink, blob WHERE plink.cid=%d "
			    "AND blob.rid=plink.pid AND plink.isprim", rid);
			if (pid == NULL)
				break;
			/* Check file exists in parent check-in. */
			rc = fsl_deck_load_sym(f, &d, pid, FSL_SATYPE_CHECKIN);
			if (!rc)




				rc = fsl_deck_F_rewind(&d);





			if (!rc && fsl_deck_F_search(&d, s->path) == NULL) {

				sitrep(view, SR_ALL ^ SR_RESET,
				    "-- /%s not in [%.12s] --", s->path, pid);
				rc = FSL_RC_BREAK;




			}
			if (!rc)
				rc = fnc_commit_qid_alloc(&s->blamed_commit,
				    pid);
			fsl_deck_finalize(&d);
			fsl_free(pid);
		} else {
			if (!fsl_uuidcmp(id, s->blamed_commit->id))
				break;
			rc = fnc_commit_qid_alloc(&s->blamed_commit, id);
		}
		if (rc)
			return rc == FSL_RC_BREAK ? FSL_RC_OK : rc;
		s->done = true;
		rc = stop_blame(&s->blame);
		s->done = false;
		if (!rc) {

			CONCAT(STAILQ, _INSERT_HEAD)(&s->blamed_commits,
			    s->blamed_commit, entry);
			rc = run_blame(view);
		}


		break;
	}
	case KEY_BACKSPACE:
	case 'B': {
		struct fnc_commit_qid *first;
		first = CONCAT(STAILQ, _FIRST)(&s->blamed_commits);
		if (!fsl_uuidcmp(first->id, s->commit_id))
			break;
		s->done = true;
		rc = stop_blame(&s->blame);
		s->done = false;
		if (rc)
			break;
		CONCAT(STAILQ, _REMOVE_HEAD)(&s->blamed_commits, entry);
		fnc_commit_qid_free(s->blamed_commit);
		s->blamed_commit = CONCAT(STAILQ, _FIRST)(&s->blamed_commits);
		rc = run_blame(view);


		break;
	}
	case 'T':
		if (view_is_parent(view))
			start_col = view_split_start_col(view->start_col);
		branch_view = view_open(view->nlines, view->ncols,
		    view->start_ln, start_col, FNC_VIEW_BRANCH);







|
>
>
>
>
|
>
>
>
>
>
|
>

|
<
>
>
>
>

<
|
|
<
|






|



|
>
|
|
|
<
>
>

















>
>







12343
12344
12345
12346
12347
12348
12349
12350
12351
12352
12353
12354
12355
12356
12357
12358
12359
12360
12361
12362
12363
12364

12365
12366
12367
12368
12369

12370
12371

12372
12373
12374
12375
12376
12377
12378
12379
12380
12381
12382
12383
12384
12385
12386
12387

12388
12389
12390
12391
12392
12393
12394
12395
12396
12397
12398
12399
12400
12401
12402
12403
12404
12405
12406
12407
12408
12409
12410
12411
12412
12413
12414
12415
			fsl_uuid_str	 pid = fsl_db_g_text(db, NULL,
			    "SELECT uuid FROM plink, blob WHERE plink.cid=%d "
			    "AND blob.rid=plink.pid AND plink.isprim", rid);
			if (pid == NULL)
				break;
			/* Check file exists in parent check-in. */
			rc = fsl_deck_load_sym(f, &d, pid, FSL_SATYPE_CHECKIN);
			if (rc) {
				fsl_deck_finalize(&d);
				fsl_free(pid);
				return RC(rc, "fsl_deck_load_sym");
			}
			rc = fsl_deck_F_rewind(&d);
			if (rc) {
				fsl_deck_finalize(&d);
				fsl_free(pid);
				return RC(rc, "fsl_deck_F_rewind");
			}
			if (fsl_deck_F_search(&d, s->path +
			    (fnc_init.sym ? 0 : 1)) == NULL) {
				sitrep(view, SR_ALL ^ SR_RESET,
				    "-- %s not in [%.12s] --",

				    s->path + (fnc_init.sym ? 0 : 1), pid);
				fsl_deck_finalize(&d);
				fsl_free(pid);
				break;
			}

			rc = fnc_commit_qid_alloc(&s->blamed_commit, pid);
			if (rc)

				return rc;
		} else {
			if (!fsl_uuidcmp(id, s->blamed_commit->id))
				break;
			rc = fnc_commit_qid_alloc(&s->blamed_commit, id);
		}
		if (rc)
			break;
		s->done = true;
		rc = stop_blame(&s->blame);
		s->done = false;
		if (rc)
			break;
		CONCAT(STAILQ, _INSERT_HEAD)(&s->blamed_commits,
		    s->blamed_commit, entry);
		rc = run_blame(view);

		if (rc)
			break;
		break;
	}
	case KEY_BACKSPACE:
	case 'B': {
		struct fnc_commit_qid *first;
		first = CONCAT(STAILQ, _FIRST)(&s->blamed_commits);
		if (!fsl_uuidcmp(first->id, s->commit_id))
			break;
		s->done = true;
		rc = stop_blame(&s->blame);
		s->done = false;
		if (rc)
			break;
		CONCAT(STAILQ, _REMOVE_HEAD)(&s->blamed_commits, entry);
		fnc_commit_qid_free(s->blamed_commit);
		s->blamed_commit = CONCAT(STAILQ, _FIRST)(&s->blamed_commits);
		rc = run_blame(view);
		if (rc)
			break;
		break;
	}
	case 'T':
		if (view_is_parent(view))
			start_col = view_split_start_col(view->start_col);
		branch_view = view_open(view->nlines, view->ncols,
		    view->start_ln, start_col, FNC_VIEW_BRANCH);
12669
12670
12671
12672
12673
12674
12675
12676

12677
12678
12679
12680
12681
12682
12683
		break;
	}
	case KEY_RESIZE:
		if (s->selected_line > view->nlines - 2) {
			s->selected_line = MIN(s->blame.nlines,
			    view->nlines - 2);
		}
		/* FALL THROUGH */

	default:
		break;
	}
	return rc;
}

static void







<
>







12491
12492
12493
12494
12495
12496
12497

12498
12499
12500
12501
12502
12503
12504
12505
		break;
	}
	case KEY_RESIZE:
		if (s->selected_line > view->nlines - 2) {
			s->selected_line = MIN(s->blame.nlines,
			    view->nlines - 2);
		}

		break;
	default:
		break;
	}
	return rc;
}

static void
12775
12776
12777
12778
12779
12780
12781




12782
12783
12784
12785
12786
12787
12788
	if (blame->lines) {
		for (idx = 0; idx < blame->nlines; ++idx)
			fsl_free(blame->lines[idx].id);
		fsl_free(blame->lines);
		blame->lines = NULL;
	}





	fsl_free(blame->line_offsets);

	return rc;
}

static int
cancel_blame(void *state)







>
>
>
>







12597
12598
12599
12600
12601
12602
12603
12604
12605
12606
12607
12608
12609
12610
12611
12612
12613
12614
	if (blame->lines) {
		for (idx = 0; idx < blame->nlines; ++idx)
			fsl_free(blame->lines[idx].id);
		fsl_free(blame->lines);
		blame->lines = NULL;
	}

	fsl_free(blame->cb_cx.root_commit);
	blame->cb_cx.root_commit = NULL;
	fsl_free(blame->cb_cx.commit_id);
	blame->cb_cx.commit_id = NULL;
	fsl_free(blame->line_offsets);

	return rc;
}

static int
cancel_blame(void *state)
13151
13152
13153
13154
13155
13156
13157
13158
13159
13160
13161
13162
13163
13164
13165
13166
13167
13168
13169
13170
13171
13172
13173
13174
13175
13176
13177
13178
13179
13180
13181
13182
13183
13184
13185
13186
{
	struct fnc_branch_view_state	*s = &view->state.branch;
	struct fnc_branchlist_entry	*be;
	struct fnc_colour		*c = NULL;
	char				*line = NULL;
	wchar_t				*wline;
	int				 limit, n, width, rc = 0;
	attr_t				 rx = 0;

	werase(view->window);
	s->ndisplayed = 0;

	limit = view->nlines;
	if (limit == 0)
		return rc;

	be = s->first_branch_onscreen;

	if ((line = fsl_mprintf("branches [%d/%d]", be->idx + s->selected + 1,
	    s->nbranches)) == NULL)
		return RC(FSL_RC_ERROR, "fsl_mprintf");

	rc = formatln(&wline, &width, line, view->ncols, 0, false);
	if (rc) {
		fsl_free(line);
		return rc;
	}
	if (screen_is_shared(view) || view->active)
		rx = FNC_HIGHLIGHT;
	if (s->colour)
		c = get_colour(&s->colours, FNC_COLOUR_BRANCH_CURRENT);
	if (c)
		rx |= COLOR_PAIR(c->scheme);
	wattron(view->window, rx);
	waddwstr(view->window, wline);
	while (width < view->ncols) {







|




















|







12977
12978
12979
12980
12981
12982
12983
12984
12985
12986
12987
12988
12989
12990
12991
12992
12993
12994
12995
12996
12997
12998
12999
13000
13001
13002
13003
13004
13005
13006
13007
13008
13009
13010
13011
13012
{
	struct fnc_branch_view_state	*s = &view->state.branch;
	struct fnc_branchlist_entry	*be;
	struct fnc_colour		*c = NULL;
	char				*line = NULL;
	wchar_t				*wline;
	int				 limit, n, width, rc = 0;
	attr_t				 rx = A_BOLD;

	werase(view->window);
	s->ndisplayed = 0;

	limit = view->nlines;
	if (limit == 0)
		return rc;

	be = s->first_branch_onscreen;

	if ((line = fsl_mprintf("branches [%d/%d]", be->idx + s->selected + 1,
	    s->nbranches)) == NULL)
		return RC(FSL_RC_ERROR, "fsl_mprintf");

	rc = formatln(&wline, &width, line, view->ncols, 0, false);
	if (rc) {
		fsl_free(line);
		return rc;
	}
	if (screen_is_shared(view) || view->active)
		rx |= A_REVERSE;
	if (s->colour)
		c = get_colour(&s->colours, FNC_COLOUR_BRANCH_CURRENT);
	if (c)
		rx |= COLOR_PAIR(c->scheme);
	wattron(view->window, rx);
	waddwstr(view->window, wline);
	while (width < view->ncols) {
13407
13408
13409
13410
13411
13412
13413
13414
13415
13416
13417
13418
13419
13420
13421
	if (rid < 0)
		return RC(rc, "fsl_uuid_to_rid");

	tree_view = view_open(0, 0, 0, start_col, FNC_VIEW_TREE);
	if (tree_view == NULL)
		return RC(FSL_RC_ERROR, "view_open");

	rc = open_tree_view(tree_view, NULL, rid);
	if (!rc)
		*new_view = tree_view;
	return rc;
}

static void
branch_scroll_up(struct fnc_branch_view_state *s, int maxscroll)







|







13233
13234
13235
13236
13237
13238
13239
13240
13241
13242
13243
13244
13245
13246
13247
	if (rid < 0)
		return RC(rc, "fsl_uuid_to_rid");

	tree_view = view_open(0, 0, 0, start_col, FNC_VIEW_TREE);
	if (tree_view == NULL)
		return RC(FSL_RC_ERROR, "view_open");

	rc = open_tree_view(tree_view, "/", rid);
	if (!rc)
		*new_view = tree_view;
	return rc;
}

static void
branch_scroll_up(struct fnc_branch_view_state *s, int maxscroll)
13689
13690
13691
13692
13693
13694
13695
13696
13697
13698
13699
13700
13701
13702
13703
13704
		free(pe);
	}
}

static void
fnc_show_version(void)
{
	printf("%s %s [%.10s] %.19s UTC", fcli_progname(), PRINT_VERSION,
	    PRINT_HASH, PRINT_DATE);
}

static int
strtonumcheck(long *ret, const char *nstr, const int min, const int max)
{
	const char	*ptr;
	long		 n;







|
<







13515
13516
13517
13518
13519
13520
13521
13522

13523
13524
13525
13526
13527
13528
13529
		free(pe);
	}
}

static void
fnc_show_version(void)
{
	printf("%s %s", fcli_progname(), PRINT_VERSION);

}

static int
strtonumcheck(long *ret, const char *nstr, const int min, const int max)
{
	const char	*ptr;
	long		 n;
13767
13768
13769
13770
13771
13772
13773
13774
13775
13776
13777
13778
13779
13780
13781
13782
13783
13784
13785
13786
13787
13788
13789
13790
13791
13792
13793
13794
13795
13796
13797
13798
13799
13800
13801

	return rc == ERR ? RC(FSL_RC_ERROR, "wgetnstr") : FSL_RC_OK;
}

static int PRINTFV(3, 4)
sitrep(struct fnc_view *view, int flags, const char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	/* vw_printw(view->window, msg, args); */
	wattr_on(view->window, A_BOLD, NULL);
	wmove(view->window, view->nlines - 1, 0);
	vw_printw(view->window, msg, args);
	if (FLAG_CHK(flags, SR_CLREOL))
		wclrtoeol(view->window);
	wattr_off(view->window, A_BOLD, NULL);
	va_end(args);
	if (FLAG_CHK(flags, SR_UPDATE)) {
		update_panels();
		doupdate();
	}
	if (FLAG_CHK(flags, SR_RESET))
		fcli_err_reset();
	if (FLAG_CHK(flags, SR_SLEEP))
		sleep(1);

	return FSL_RC_OK;
 }

/*
 * Attempt to parse string d, which must resemble either an ISO8601 formatted
 * date (e.g., 2021-10-10, 2020-01-01T10:10:10), disgregarding any trailing
 * garbage or space characters such that "2021-10-10x" or "2020-01-01 10:10:10"
 * will pass, or an _unambiguous_ DD/MM/YYYY or MM/DD/YYYY formatted date. Upon







|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|







13592
13593
13594
13595
13596
13597
13598
13599
13600
13601
13602
13603
13604
13605
13606
13607
13608
13609
13610
13611
13612
13613
13614
13615
13616
13617
13618
13619
13620
13621
13622
13623
13624
13625
13626

	return rc == ERR ? RC(FSL_RC_ERROR, "wgetnstr") : FSL_RC_OK;
}

static int PRINTFV(3, 4)
sitrep(struct fnc_view *view, int flags, const char *msg, ...)
{
       va_list args;

       va_start(args, msg);
       /* vw_printw(view->window, msg, args); */
       wattr_on(view->window, A_BOLD, NULL);
       wmove(view->window, view->nlines - 1, 0);
       vw_printw(view->window, msg, args);
       if (FLAG_CHK(flags, SR_CLREOL))
               wclrtoeol(view->window);
       wattr_off(view->window, A_BOLD, NULL);
       va_end(args);
       if (FLAG_CHK(flags, SR_UPDATE)) {
               update_panels();
               doupdate();
       }
       if (FLAG_CHK(flags, SR_RESET))
               fcli_err_reset();
       if (FLAG_CHK(flags, SR_SLEEP))
               sleep(1);

       return FSL_RC_OK;
 }

/*
 * Attempt to parse string d, which must resemble either an ISO8601 formatted
 * date (e.g., 2021-10-10, 2020-01-01T10:10:10), disgregarding any trailing
 * garbage or space characters such that "2021-10-10x" or "2020-01-01 10:10:10"
 * will pass, or an _unambiguous_ DD/MM/YYYY or MM/DD/YYYY formatted date. Upon
13934
13935
13936
13937
13938
13939
13940
13941
13942
13943
13944
13945
13946
13947
13948
static int
init_unveil(const char **paths, const char **perms, int n, bool disable)
{
#ifdef __OpenBSD__
	int i;

	for (i = 0; i < n; ++i) {
		if (paths[i] && unveil(paths[i], perms[i]) == -1)
			return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS),
			    "unveil(%s, \"%s\")", paths[i], perms[i]);
	}

	if (disable)
		if (unveil(NULL, NULL) == -1)
			return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS),







|







13759
13760
13761
13762
13763
13764
13765
13766
13767
13768
13769
13770
13771
13772
13773
static int
init_unveil(const char **paths, const char **perms, int n, bool disable)
{
#ifdef __OpenBSD__
	int i;

	for (i = 0; i < n; ++i) {
		if (unveil(paths[i], perms[i]) == -1)
			return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS),
			    "unveil(%s, \"%s\")", paths[i], perms[i]);
	}

	if (disable)
		if (unveil(NULL, NULL) == -1)
			return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS),