Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From 0.3 To 0.4
2021-10-31 13:18 | Bump version number: 0.5 (check-in: b977d0e1f4 user: mark tags: trunk) | |
2021-10-31 12:21 | CHANGES for 0.4 (check-in: 3cc00310ea user: mark tags: trunk, 0.4) | |
2021-10-31 11:23 | Improve documentation and amend incorrect path in README. (check-in: 22126a3f58 user: mark tags: trunk) | |
2021-10-17 07:16 | Bump version number: 0.4 (check-in: 20578f6ca1 user: mark tags: trunk) | |
2021-10-17 07:15 | Add CHANGES for 0.3 (check-in: e231a952cf user: mark tags: trunk, 0.3) | |
2021-10-16 17:32 | Update documentation with new blame command. (check-in: 96b3dbc641 user: mark tags: trunk) | |
Changes to CHANGES.md.
1 2 3 4 5 6 7 | **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 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | **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 - make tree command compatible with the `-R <repository>` option - add profiling (`--profile`) option to the build system - notify user of invalid blame _P_ key binding selections with ephemeral message - add coloured output and associated key binding/options to the timeline view - ensure the full diff is displayed for non-standard initial check-ins - initialise variables to squelch gcc-9.3 compiler warnings - add command line aliases to all currently available commands - add time-based annotation option to the blame command - fix colour conflicts between certain views - enhance diff interface with optional path arg to filter diffs between commits - improve diff view by displaying full context when diffing consecutive commits - make diff index parsing more robust when handling removed/missing files - ensure diff honours the repository's `allow-symlinks` setting - simplify help output when `-h|--help` is passed to command aliases - invert diff metadata when `-i|--invert` or the _i_ key binding is used - fix diff bug when the first commit is opened from the timeline view - enhance diff command to enable diff of arbitrary file blobs - 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 |
︙ | ︙ |
Changes to README.md.
1 2 | # README | | | | | | | | | | > | > > > | | | | | > > > > > | 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 | # README # fnc 0.4 ## A read-only ncurses browser for [Fossil][0] repositories in the terminal. `fnc` uses [libfossil][1] to create a [`fossil ui`][2] experience in the terminal. Tested and confirmed to run on the following x64 systems (additional platforms noted inline): 1. OpenBSD 6.8- and 6.9-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. # Build 1. clone the repository - `fossil clone https://fnc.bsdbox.org` 2. move into the repository checkout - `cd fnc` 3. run the configure script - `./configure` 4. build fnc - `make` 5. install the `fnc` binary (*requires privileges*) - `doas make install` 6. 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. To change the install path, use the `--prefix` option to `configure`; for example, replacing step 3 with `./configure --prefix=$HOME` will install the executable and man page into `~/bin` and `~/share/man`, respectively. Alternatively, cryptographically signed binaries for some of the abovementioned platforms are available to [download][3]. # Doc 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 ![diff split screen](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-splitscreen.png "fnc diff split screen") ![diff renamed file](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-full-file_renamed.png "fnc diff file renamed") ![diff added file](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-split-file_added.png "fnc diff file added") ![diff removed file](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-split-file-removed.png "fnc diff file removed") ![blame split screen](https://fnc.bsdbox.org/uv/resources/img/fnc-blame-splitscreen.png "fnc blame split screen") ![tree split screen](https://fnc.bsdbox.org/uv/resources/img/fnc-tree-splitscreen.png "fnc tree split screen") ![branch split screen](https://fnc.bsdbox.org/uv/resources/img/fnc-branch-splitscreen.png "fnc branch split screen") ![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") [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 |
︙ | ︙ |
Changes to auto.def.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # vim:se syn=tcl: # use cc cc-shared cc-lib wh-common options { no-debug=0 => "Disable debug build options." loud=0 => "Enables 'loud' build mode." no-compile-commands=0 => "Disable compile_commands.json support even if detected (possibly incorrectly)." } # autosetup interceps 'debug' and 'enable-debug' flags :/ # prefix:=[get-env HOME /usr/local] -> "Installation prefix." | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # vim:se syn=tcl: # use cc cc-shared cc-lib wh-common options { no-debug=0 => "Disable debug build options." loud=0 => "Enables 'loud' build mode." profile=0 => "Enables the -pg (profiling) compile/link flag if CC is gcc." no-compile-commands=0 => "Disable compile_commands.json support even if detected (possibly incorrectly)." } # autosetup interceps 'debug' and 'enable-debug' flags :/ # prefix:=[get-env HOME /usr/local] -> "Installation prefix." |
︙ | ︙ | |||
94 95 96 97 98 99 100 101 102 103 104 105 106 107 | wh-check-compile-commands no-compile-commands define CFLAGS $cFlags if {[wh-opt-bool-01 -v loud BUILD_QUIETLY]} { puts "Enabling quiet build mode. Use --loud to enable loud mode." } # Each generated Makefile requires an input file with a .in extension: wh-make-from-dot-in { config.make Makefile src/Makefile fnc/Makefile | > > > > > > > > | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | wh-check-compile-commands no-compile-commands define CFLAGS $cFlags if {[wh-opt-bool-01 -v loud BUILD_QUIETLY]} { puts "Enabling quiet build mode. Use --loud to enable loud mode." } msg-checking "gprof profiling? " if {[wh-check-profile-flag profile]} { msg-result "gcc detected: building with profiling option." # Add CC_PROFILE_FLAG to CFLAGS and LDFLAGS in config.make.in. } else { msg-result "no. Use --profile and gcc to enable." } # Each generated Makefile requires an input file with a .in extension: wh-make-from-dot-in { config.make Makefile src/Makefile fnc/Makefile |
︙ | ︙ |
Changes to autosetup/wh-common.tcl.
︙ | ︙ | |||
358 359 360 361 362 363 364 365 | } ######################################################################## # Uses [make-template] to creates makefile(-like) file $filename from # $filename.in but explicitly makes the output read-only, to avoid # inadvertent editing (who, me?). # # The argument may be a list of filenames. | > > > > > > | | | | > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | } ######################################################################## # Uses [make-template] to creates makefile(-like) file $filename from # $filename.in but explicitly makes the output read-only, to avoid # inadvertent editing (who, me?). # # The second argument is an optional boolean specifying whether to # `touch` the generates files. This can be used as a workaround for # cases where (A) autosetup does not update the file because it was # not really modified and (B) the file *really* needs to be updates to # the please build process. Pass any non-0 value to enable touching. # # The argument may be a list of filenames. proc wh-make-from-dot-in {filename {touch 0}} { foreach f $filename { catch { exec chmod u+w $f } make-template $f.in $f if {0 != $touch} { puts "Touching $f" catch { exec touch $f } } catch { exec chmod u-w $f } } } ######################################################################## # Checks for the boolean configure option named by $flagname. If set, # it checks if $CC seems to refer to gcc. If it does (or appears to) # then it defines CC_PROFILE_FLAG to "-pg" and returns 1, else it # defines CC_PROFILE_FLAG to "" and returns 0. # # Note that the resulting flag must be added to both CFLAGS and # LDFLAGS in order for binaries to be able to generate "gmon.out". In # order to avoid potential problems with escaping, space-containing # tokens, and interfering with autosetup's use of these vars, this # routine does not directly modify CFLAGS or LDFLAGS. proc wh-check-profile-flag {{flagname profile}} { if {[opt-bool $flagname]} { set CC [get-define CC] regsub {.*ccache *} $CC "" CC # ^^^ if CC="ccache gcc" then [exec] treats "ccache gcc" as a # single binary name and fails. So strip any leading ccache part # for this purpose. if { ![catch { exec $CC --version } msg]} { if {[string first gcc $CC] != -1} { define CC_PROFILE_FLAG "-pg" return 1 } } } define CC_PROFILE_FLAG "" return 0 } |
Changes to config.make.in.
1 2 3 4 5 6 7 8 9 | # Example of a typical Makefile template for autosetup # Tools. CC is standard. The rest are via cc-check-tools CC = @CC@ RANLIB = @RANLIB@ AR = @AR@ STRIP = @STRIP@ # FLAGS/LIBS | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # Example of a typical Makefile template for autosetup # Tools. CC is standard. The rest are via cc-check-tools CC = @CC@ RANLIB = @RANLIB@ AR = @AR@ STRIP = @STRIP@ # FLAGS/LIBS CFLAGS += @CFLAGS@ @CC_PROFILE_FLAG@ LDFLAGS += @LDFLAGS@ @CC_PROFILE_FLAG@ LDLIBS += @LIBS@ #LDLIBS += -lsqlite3 -lz LDFLAGS_MODULE_LOADER += @LDFLAGS_MODULE_LOADER@ # ??? HAVE_DLOPEN := @HAVE_DLOPEN@ HAVE_LIBDL := @HAVE_LIBDL@ HAVE_LIBLTDL := @HAVE_LIBLTDL@ |
︙ | ︙ |
Changes to fnc/fnc.1.
︙ | ︙ | |||
24 25 26 27 28 29 30 | .Op Ar command .Op Fl h | -help .Nm .Op Fl h | -help .Op Fl v | -version .Nm .Cm timeline | | | | | > > > > > > > > > > | > > | > | > | > > > | 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 | .Op Ar command .Op Fl h | -help .Nm .Op Fl h | -help .Op Fl v | -version .Nm .Cm timeline .Op Fl Cz .Op Fl T Ar tag .Op Fl b Ar branch .Op Fl c Ar commit .Op Fl n Ar number .Op Fl t Ar type .Op Fl u Ar user .Op Ar path .Nm .Cm diff .Op Fl Ciw .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 Ar path .Nm .Cm blame .Op Fl C .Op Fl c Ar commit Op Fl r .Op Fl n Ar number .Ar path .Nm .Cm branch .Op Fl Ccopr .Op Fl a Ar date | Fl b Ar date .Op Fl s Ar order .Op Ar glob .Nm .Op Ar path .Sh DESCRIPTION .Nm is an interactive read-only browser 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 specified absent a command name, and are as follows: .Bl -tag -width 6v |
︙ | ︙ | |||
98 99 100 101 102 103 104 | .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 | | > | | | < > > > > > > | 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 | .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 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 h | -help Oc Oo Fl n | -limit Ar n Oc Oo Fl t | -type Ar type Oc \ Oo Fl u | -username Ar user Oc Oo Fl z | -utc Oc Op Ar path .Dl (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. This command must be executed from within or below the top level directory of the repository; 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 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. .It Fl T , -tag Ar tag Only display commits with T cards containing .Ar tag . By default, .Nm will indiscriminately display all commits irrespective of which T cards are attached to the commit manifest. |
︙ | ︙ | |||
155 156 157 158 159 160 161 | 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 | | | | | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | 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 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. |
︙ | ︙ | |||
188 189 190 191 192 193 194 | .Sy f Ta forum post .El .Pp By default, when this option is not supplied, .Nm will indiscriminately load all commits irrespective of .Ar type . | | > > > > | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | .Sy f Ta forum post .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 . .It Fl z , -utc Use Coordinated Universal Time (UTC) rather than local time when displaying commit dates and timestamps. .El |
︙ | ︙ | |||
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 | .It Cm gg, Home Move selection cursor to the first commit on the timeline (i.e., newest commit in the repository). .It Cm Enter, Space Open a .Cm diff view displaying the changeset of the currently selected commit. .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. .El .It Cm diff Oo Fl C | -no-colour Oc Oo Fl h | -help Oc Oo Fl i | -invert \ Oc Oo Fl w | -whitespace Oc Oo Fl x | -context Ar n Oc \ | > > > > > | > | > > > > > > > > > > > > > > > > | > > > > | | | | > > | | | > | 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 | .It Cm gg, Home Move selection cursor to the first commit on the timeline (i.e., newest commit in the repository). .It Cm Enter, Space Open a .Cm diff view displaying the changeset of the currently selected commit. .It Cm c Toggle colourised timeline. On supported terminals, .Nm will default to displaying the timeline in colour. .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. .El .Tg di .It Cm diff Oo Fl C | -no-colour Oc Oo Fl h | -help Oc Oo Fl i | -invert \ 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 (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. .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. .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 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. By default, 5 context lines are shown. Negative values are a no-op. .El .Pp Key bindings for .Cm fnc diff are as follows: .Bl -tag -width Ds .It Cm c Toggle coloured diff output. On supported terminals, .Nm will default to displaying changes and diff metadata in colour. .It Cm i Toggle inversion of diff output. .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 Arrow-down, j Scroll down one line of diff output. .It Cm Arrow-up, k Scroll up one line of diff output. .It Cm Ctrl+f, Page-down, Space Scroll down one page of diff output. .It Cm Ctrl+b, Page-up |
︙ | ︙ | |||
321 322 323 324 325 326 327 328 329 | 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 .It Cm tree Oo Fl C | -no-colour Oc Oo Fl c | -commit Ar commit Oc \ Oo Fl h | -help Oc Op Ar path | > > | | 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 | 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 Op Ar path .Dl (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. This command must be executed from within or below the top level directory of the repository; that is, |
︙ | ︙ | |||
361 362 363 364 365 366 367 | tree view key binding as documented below. .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 | | | | | 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | tree view key binding as documented below. .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. .El .Pp Key bindings for .Cm fnc tree are as follows: |
︙ | ︙ | |||
392 393 394 395 396 397 398 | .It Cm Page-up, Ctrl+b Move selection cursor one page up the tree. .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 c | | | 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 | .It Cm Page-up, Ctrl+b Move selection cursor one page up the tree. .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 c Toggle coloured output. On supported terminals, .Nm will default to displaying the tree in colour. .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 |
︙ | ︙ | |||
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | .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 .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 Ar path 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. This command must be executed from within or below the top level directory of the repository; that is, .Nm assumes a local checkout is open in or above the current working directory. | > > | 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | .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 Ar path .Dl (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. This command must be executed from within or below the top level directory of the repository; that is, .Nm assumes a local checkout is open in or above the current working directory. |
︙ | ︙ | |||
444 445 446 447 448 449 450 | 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 | | | | | > > > | | | | 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 | 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 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 , -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 |
︙ | ︙ | |||
496 497 498 499 500 501 502 | selected line. .It Cm p Blame the version of the file corresponding to the parent of the commit in the currently selected line. .It Cm B, Backspace Reload the previous blamed version of the file. .It Cm c | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | selected line. .It Cm p Blame the version of the file corresponding to the parent of the commit in the currently selected line. .It Cm B, Backspace Reload the previous blamed version of the file. .It Cm c Toggle coloured output. On supported terminals, .Nm will default to displaying the blamed file in colour. .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 next token that matches the current search pattern. .It Cm N Find the previous 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 | -reverse Oc \ Oo Fl s | -sort Ar order Oc Op Ar glob .Dl (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 comparisons depend on the .Xr sqlite3 1 .B LIKE operator, which only folds case for the ASCII character set. This command must be executed from within or below the top level directory of the repository; 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 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 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 branch view key binding as documented below. .It Fl a , -after Ar date Display only those branches with activity after the specified .Ar date . .It Fl b , -before Ar date Display only those branches with activity before the specified .Ar date . .It Fl c , -close Display only closed branches. .It Fl h , -help Display branch command help and usage information then exit. .It Fl o , -open 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 , -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 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 Page-down, Ctrl+f Move selection cursor down one page. .It Cm Page-up, Ctrl+b Move selection cursor up one page. .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 t Open the .Cm tree view of the currently selected branch. .It Cm R, Ctrl+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 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 fnc/fnc.c.
︙ | ︙ | |||
63 64 65 66 67 68 69 | #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" | | > > > > | 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 | #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" #define FNC_VERSION 0.4 /* Utility macros. */ #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #if !defined(CTRL) #define CTRL(key) ((key) & 037) /* CTRL+<key> input. */ #endif #define nitems(a) (sizeof((a)) / sizeof((a)[0])) #define STRINGIFYOUT(s) #s #define STRINGIFY(s) STRINGIFYOUT(s) #define CONCATOUT(a, b) a ## b #define CONCAT(a, b) CONCATOUT(a, b) #define FILE_POSITION __FILE__ ":" STRINGIFY(__LINE__) #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 DIFF_DEF_CTXT 5 /* Default diff context lines. */ #define DIFF_MAX_CTXT 64 /* Max diff context lines. */ #define SPIN_INTERVAL 200 /* Status line progress indicator. */ #define SPINNER "\\|/-\0" |
︙ | ︙ | |||
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 | #endif /* __linux__ */ __dead static void usage(void); static void usage_timeline(void); static void usage_diff(void); static void usage_tree(void); static void usage_blame(void); static int fcli_flag_type_arg_cb(fcli_cliflag const *); static int cmd_timeline(fcli_command const *); static int cmd_diff(fcli_command const *); static int cmd_tree(fcli_command const *); static int cmd_blame(fcli_command const *); /* * Singleton initialising global configuration and state for app startup. */ static struct fnc_setup { /* Global options. */ const char *cmdarg; /* Retain argv[1] for use/err report. */ const char *sym; /* Open view from this symbolic name. */ const char *path; /* Optional path for timeline & tree. */ int err; /* Indicate fnc error state. */ bool hflag; /* Flag if --help is requested. */ bool vflag; /* Flag if --version is requested. */ /* Timeline options. */ struct artifact_types { const char **values; short nitems; } *filter_types; /* Only load commits of <type>. */ union { const char *zlimit; | > > > | | > > > | > > | < | > > | > > > > > > | | > | | > | | > | | > > > | < < < > > > > | 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 | #endif /* __linux__ */ __dead static void usage(void); static void usage_timeline(void); static void usage_diff(void); static void usage_tree(void); static void usage_blame(void); static void usage_branch(void); static int fcli_flag_type_arg_cb(fcli_cliflag const *); static int cmd_timeline(fcli_command const *); static int cmd_diff(fcli_command const *); static int cmd_tree(fcli_command const *); static int cmd_blame(fcli_command const *); static int cmd_branch(fcli_command const *); /* * Singleton initialising global configuration and state for app startup. */ static struct fnc_setup { /* Global options. */ const char *cmdarg; /* Retain argv[1] for use/err report. */ const char *sym; /* Open view from this symbolic name. */ const char *path; /* Optional path for timeline & tree. */ int err; /* Indicate fnc error state. */ bool hflag; /* Flag if --help is requested. */ bool vflag; /* Flag if --version is requested. */ bool reverse; /* Reverse branch sort or blame. */ /* Timeline options. */ struct artifact_types { const char **values; short nitems; } *filter_types; /* Only load commits of <type>. */ union { const char *zlimit; int limit; } nrecords; /* Number of commits to load. */ const char *filter_tag; /* Only load commits with <tag>. */ const char *filter_branch; /* Only load commits from <branch>. */ const char *filter_user; /* Only load commits from <user>. */ const char *filter_type; /* Placeholder for repeatable types. */ bool utc; /* Display UTC sans user local time. */ /* Diff options. */ const char *context; /* Number of context lines. */ bool ws; /* Ignore whitespace-only changes. */ bool nocolour; /* Disable colour in diff output. */ bool quiet; /* Disable verbose diff output. */ bool invert; /* Toggle inverted diff output. */ /* 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. */ /* Command line flags and help. */ fcli_help_info fnc_help; /* Global help. */ fcli_cliflag cliflags_global[3]; /* Global options. */ fcli_command cmd_args[6]; /* App commands. */ fcli_cliflag cliflags_timeline[11]; /* Timeline options. */ fcli_cliflag cliflags_diff[7]; /* Diff options. */ fcli_cliflag cliflags_tree[4]; /* Tree options. */ fcli_cliflag cliflags_blame[6]; /* Blame options. */ fcli_cliflag cliflags_branch[10]; /* Branch options. */ } fnc_init = { NULL, /* cmdarg copy of argv[1] to aid usage/error report. */ NULL, /* sym(bolic name) of commit to open defaults to tip. */ NULL, /* path for tree to open or timeline to find commits. */ 0, /* err fnc error state. */ false, /* hflag if --help is requested. */ false, /* vflag if --version is requested. */ false, /* reverse branch sort/annotation defaults to off. */ NULL, /* filter_types defaults to indiscriminate. */ {0}, /* nrecords defaults to all commits. */ NULL, /* filter_tag defaults to indiscriminate. */ NULL, /* filter_branch defaults to indiscriminate. */ NULL, /* filter_user defaults to indiscriminate. */ NULL, /* filter_type temporary placeholder. */ false, /* utc defaults to off (i.e., show user local time). */ NULL, /* context defaults to five context lines. */ false, /* ws defaults to acknowledge whitespace. */ false, /* nocolour defaults to off (i.e., use diff colours). */ false, /* quiet defaults to off (i.e., verbose diff is on). */ false, /* invert 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). */ { /* fnc_help global app help details. */ "A read-only ncurses browser for Fossil repositories in the " "terminal.", NULL, usage }, { /* cliflags_global global app options. */ FCLI_FLAG_BOOL("h", "help", &fnc_init.hflag, "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}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel. */ }, { /* cliflags_timeline timeline command related options. */ FCLI_FLAG_BOOL("C", "no-colour", &fnc_init.nocolour, "Disable colourised timeline, which is enabled by default on\n " "supported terminals. Colour can also be toggled with the 'c' " "\n key binding in timeline view when this option is not used."), FCLI_FLAG("T", "tag", "<tag>", &fnc_init.filter_tag, "Only display commits with T cards containing <tag>."), FCLI_FLAG("b", "branch", "<branch>", &fnc_init.filter_branch, "Only display commits that reside on the given <branch>."), FCLI_FLAG("c", "commit", "<commit>", &fnc_init.sym, "Open the timeline from <commit>. Common symbols are:\n" "\tSHA{1,3} hash\n" |
︙ | ︙ | |||
342 343 344 345 346 347 348 | "\tISO8601 timestamp\n" "\t{tip,current,prev,next}\n " "For a complete list of symbols see Fossil's Check-in Names:\n " "https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki"), FCLI_FLAG_BOOL("h", "help", NULL, "Display blame command help and usage."), FCLI_FLAG("n", "limit", "<n>", &fnc_init.nrecords.zlimit, | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | 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 | "\tISO8601 timestamp\n" "\t{tip,current,prev,next}\n " "For a complete list of symbols see Fossil's Check-in Names:\n " "https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki"), FCLI_FLAG_BOOL("h", "help", NULL, "Display blame command help and usage."), FCLI_FLAG("n", "limit", "<n>", &fnc_init.nrecords.zlimit, "Limit depth of blame history to <n> commits or seconds. Denote the" "\n latter by postfixing 's' (e.g., 30s). Useful for large files" " with\n extensive history. Persists for the duration of the " "session."), FCLI_FLAG_BOOL("r", "reverse", &fnc_init.reverse, "Reverse annotate the file starting from a historical commit. " "Rather\n than show the most recent change of each line, show " "the first time\n each line was modified after the specified " "commit. Requires -c|--commit."), fcli_cliflag_empty_m }, /* End cliflags_blame. */ { /* cliflags_branch branch command related options. */ FCLI_FLAG_BOOL("C", "no-colour", &fnc_init.nocolour, "Disable coloured output, which is enabled by default on supported" "\n terminals. Colour can also be toggled with the 'c' key " "binding when\n this option is not used."), FCLI_FLAG("a", "after", "<date>", &fnc_init.after, "Show branches with last activity occuring after <date>, which is\n" " expected to be either an ISO8601 (e.g., 2020-10-10) or " "unambiguous\n DD/MM/YYYY or MM/DD/YYYY formatted date."), FCLI_FLAG("b", "before", "<date>", &fnc_init.before, "Show branches with last activity occuring before <date>, which is" "\n expected to be either an ISO8601 (e.g., 2020-10-10) or " "unambiguous\n DD/MM/YYYY or MM/DD/YYYY formatted date."), FCLI_FLAG_BOOL("c", "closed", &fnc_init.closed, "Show closed branches only. Open and closed branches are listed by " "\n default."), FCLI_FLAG_BOOL("h", "help", NULL, "Display branch command help and usage."), FCLI_FLAG_BOOL("o", "open", &fnc_init.open, "Show open branches only. Open and closed branches are listed by " "\n default."), FCLI_FLAG_BOOL("p", "no-private", &fnc_init.noprivate, "Do not show private branches, which are otherwise included in the" "\n list of displayed branches by default."), FCLI_FLAG_BOOL("r", "reverse", &fnc_init.reverse, "Reverse the order in which branches are displayed."), FCLI_FLAG("s", "sort", "<order>", &fnc_init.sort, "Sort branches by <order>. Available options are:\n" "\tmru - most recently used\n" "\tstate - open/closed state\n " "Branches are sorted in lexicographical order by default."), fcli_cliflag_empty_m }, /* End cliflags_blame. */ }; enum fsl_list_object { FNC_ARTIFACT_OBJ, FNC_COLOUR_OBJ }; enum date_string { ISO8601_DATE_ONLY = 10, ISO8601_TIMESTAMP = 20 }; enum fnc_view_id { FNC_VIEW_TIMELINE, FNC_VIEW_DIFF, FNC_VIEW_TREE, FNC_VIEW_BLAME, FNC_VIEW_BRANCH }; enum fnc_search_mvmnt { SEARCH_DONE, SEARCH_FORWARD, SEARCH_REVERSE }; |
︙ | ︙ | |||
392 393 394 395 396 397 398 | FNC_DIFF_META = 1, FNC_DIFF_MINUS, FNC_DIFF_PLUS, FNC_DIFF_CHNK, FNC_TREE_LINK, FNC_TREE_DIR, FNC_TREE_EXEC, | | > > > > > > > > > > | | | | | | | | | | | | > | 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 | FNC_DIFF_META = 1, FNC_DIFF_MINUS, FNC_DIFF_PLUS, FNC_DIFF_CHNK, FNC_TREE_LINK, FNC_TREE_DIR, FNC_TREE_EXEC, FNC_COMMIT_ID, FNC_USER_STR, FNC_DATE_STR, FNC_TAGS_STR }; enum fnc_diff_type { FNC_DIFF_CKOUT, FNC_DIFF_COMMIT, FNC_DIFF_BLOB, FNC_DIFF_WIKI }; struct fnc_colour { regex_t regex; uint8_t scheme; }; struct fsl_list_state { enum fsl_list_object obj; }; struct fnc_commit_artifact { fsl_buffer wiki; fsl_buffer pwiki; fsl_list changeset; fsl_uuid_str uuid; fsl_uuid_str puuid; fsl_id_t rid; fsl_id_t prid; char *user; char *timestamp; char *comment; char *branch; char *type; enum fnc_diff_type diff_type; }; struct fsl_file_artifact { fsl_card_F *fc; enum fsl_ckout_change_e change; }; |
︙ | ︙ | |||
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 | int spin_idx; int ncommits_needed; /* * XXX Is there a more elegant solution to retrieving return codes from * thread functions while pinging between, but before we join, threads? */ int rc; bool endjmp; bool timeline_end; sig_atomic_t *quit; pthread_cond_t commit_consumer; pthread_cond_t commit_producer; }; struct fnc_tl_view_state { struct fnc_tl_thread_cx thread_cx; struct commit_queue commits; struct commit_entry *first_commit_onscreen; struct commit_entry *last_commit_onscreen; struct commit_entry *selected_commit; struct commit_entry *matched_commit; struct commit_entry *search_commit; const char *curr_ckout_uuid; char *path; /* Match commits involving path. */ int selected_idx; sig_atomic_t quit; pthread_t thread_id; }; struct fnc_diff_view_state { struct fnc_view *timeline_view; struct fnc_commit_artifact *selected_commit; fsl_buffer buf; fsl_list colours; FILE *f; fsl_uuid_str id1; fsl_uuid_str id2; int first_line_onscreen; int last_line_onscreen; | > > > > > > > > > > > > | 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 | int spin_idx; int ncommits_needed; /* * XXX Is there a more elegant solution to retrieving return codes from * thread functions while pinging between, but before we join, threads? */ int rc; bool tree_open; bool endjmp; bool timeline_end; sig_atomic_t *quit; pthread_cond_t commit_consumer; pthread_cond_t commit_producer; }; struct fnc_tl_view_state { struct fnc_tl_thread_cx thread_cx; struct commit_queue commits; struct commit_entry *first_commit_onscreen; struct commit_entry *last_commit_onscreen; struct commit_entry *selected_commit; struct commit_entry *matched_commit; struct commit_entry *search_commit; fsl_list colours; const char *curr_ckout_uuid; char *path; /* Match commits involving path. */ int selected_idx; sig_atomic_t quit; pthread_t thread_id; bool colour; }; struct fnc_pathlist_entry { TAILQ_ENTRY(fnc_pathlist_entry) entry; const char *path; size_t pathlen; void *data; /* XXX May want to save id, mode, etc. */ }; TAILQ_HEAD(fnc_pathlist_head, fnc_pathlist_entry); struct fnc_diff_view_state { struct fnc_view *timeline_view; struct fnc_commit_artifact *selected_commit; struct fnc_pathlist_head *paths; fsl_buffer buf; fsl_list colours; FILE *f; fsl_uuid_str id1; fsl_uuid_str id2; int first_line_onscreen; int last_line_onscreen; |
︙ | ︙ | |||
612 613 614 615 616 617 618 | struct fnc_blame_cb_cx cb_cx; FILE *f; /* Non-annotated copy of file */ struct fnc_blame_line *lines; off_t *line_offsets; off_t filesz; fsl_id_t origin; /* Tip rid for reverse blame */ int nlines; | | | 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 | struct fnc_blame_cb_cx cb_cx; FILE *f; /* Non-annotated copy of file */ struct fnc_blame_line *lines; off_t *line_offsets; off_t filesz; fsl_id_t origin; /* Tip rid for reverse blame */ int nlines; int nlimit; /* Limit depth traversal. */ pthread_t thread_id; }; CONCAT(STAILQ, _HEAD)(fnc_commit_id_queue, fnc_commit_qid); struct fnc_commit_qid { CONCAT(STAILQ, _ENTRY)(fnc_commit_qid) entry; fsl_uuid_str id; |
︙ | ︙ | |||
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 | int matched_line; int spin_idx; bool done; bool blame_complete; bool eof; bool colour; }; TAILQ_HEAD(view_tailhead, fnc_view); struct fnc_view { TAILQ_ENTRY(fnc_view) entries; WINDOW *window; PANEL *panel; struct fnc_view *parent; struct fnc_view *child; union { struct fnc_diff_view_state diff; struct fnc_tl_view_state timeline; struct fnc_tree_view_state tree; struct fnc_blame_view_state blame; } state; enum fnc_view_id vid; enum fnc_search_state search_status; enum fnc_search_mvmnt searching; int nlines; int ncols; int start_ln; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | int matched_line; int spin_idx; bool done; bool blame_complete; bool eof; bool colour; }; struct fnc_branch { char *name; char *date; fsl_uuid_str id; bool private; bool current; bool open; }; struct fnc_branchlist_entry { TAILQ_ENTRY(fnc_branchlist_entry) entries; struct fnc_branch *branch; int idx; }; TAILQ_HEAD(fnc_branchlist_head, fnc_branchlist_entry); struct fnc_branch_view_state { struct fnc_branchlist_head branches; struct fnc_branchlist_entry *first_branch_onscreen; struct fnc_branchlist_entry *last_branch_onscreen; struct fnc_branchlist_entry *matched_branch; struct fnc_branchlist_entry *selected_branch; const char *branch_glob; double dateline; int branch_flags; #define BRANCH_LS_CLOSED_ONLY 0x001 /* Show closed branches only. */ #define BRANCH_LS_OPEN_ONLY 0x002 /* Show open branches only. */ #define BRANCH_LS_OPEN_CLOSED 0x003 /* Show open & closed branches (dflt). */ #define BRANCH_LS_BITMASK 0x003 #define BRANCH_LS_NO_PRIVATE 0x004 /* Show public branches only. */ #define BRANCH_SORT_MTIME 0x008 /* Sort by activity. (default: name) */ #define BRANCH_SORT_STATUS 0x010 /* Sort by open/closed. */ #define BRANCH_SORT_REVERSE 0x020 /* Reverse sort order. */ int nbranches; int ndisplayed; int selected; int when; bool colour; bool show_date; bool show_id; }; TAILQ_HEAD(view_tailhead, fnc_view); struct fnc_view { TAILQ_ENTRY(fnc_view) entries; WINDOW *window; PANEL *panel; struct fnc_view *parent; struct fnc_view *child; union { struct fnc_diff_view_state diff; struct fnc_tl_view_state timeline; struct fnc_tree_view_state tree; struct fnc_blame_view_state blame; struct fnc_branch_view_state branch; } state; enum fnc_view_id vid; enum fnc_search_state search_status; enum fnc_search_mvmnt searching; int nlines; int ncols; int start_ln; |
︙ | ︙ | |||
702 703 704 705 706 707 708 | static void parse_emailaddr_username(char **); static int formatln(wchar_t **, int *, const char *, int, int); static int multibyte_to_wchar(const char *, wchar_t **, size_t *); static int write_commit_line(struct fnc_view *, struct fnc_commit_artifact *, int); static int view_input(struct fnc_view **, int *, struct fnc_view *, struct view_tailhead *); | | | | 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 | static void parse_emailaddr_username(char **); static int formatln(wchar_t **, int *, const char *, int, int); static int multibyte_to_wchar(const char *, wchar_t **, size_t *); static int write_commit_line(struct fnc_view *, struct fnc_commit_artifact *, int); static int view_input(struct fnc_view **, int *, struct fnc_view *, struct view_tailhead *); static int help(struct fnc_view *); static int padpopup(struct fnc_view *, int, int, FILE *, const char *); static void centerprint(WINDOW *, int, int, int, const char *, chtype); static int tl_input_handler(struct fnc_view **, struct fnc_view *, int); static int timeline_scroll_down(struct fnc_view *, int); static void timeline_scroll_up(struct fnc_tl_view_state *, int); |
︙ | ︙ | |||
725 726 727 728 729 730 731 | static int tl_search_next(struct fnc_view *); static bool find_commit_match(struct fnc_commit_artifact *, regex_t *); static int init_diff_commit(struct fnc_view **, int, struct fnc_commit_artifact *, struct fnc_view *); static int open_diff_view(struct fnc_view *, struct fnc_commit_artifact *, int, bool, bool, bool, | | > | | > | > > > > > > | 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 | static int tl_search_next(struct fnc_view *); static bool find_commit_match(struct fnc_commit_artifact *, regex_t *); static int init_diff_commit(struct fnc_view **, int, struct fnc_commit_artifact *, struct fnc_view *); static int open_diff_view(struct fnc_view *, struct fnc_commit_artifact *, int, bool, bool, bool, struct fnc_view *, bool, struct fnc_pathlist_head *); static void show_diff_status(struct fnc_view *); static int create_diff(struct fnc_diff_view_state *); static int create_changeset(struct fnc_commit_artifact *); static int write_commit_meta(struct fnc_diff_view_state *); static int wrapline(char *, fsl_size_t ncols_avail, struct fnc_diff_view_state *, off_t *); static int add_line_offset(off_t **, size_t *, off_t); static int diff_commit(fsl_buffer *, struct fnc_commit_artifact *, int, int, int, struct fnc_pathlist_head *); static int diff_checkout(fsl_buffer *, fsl_id_t, int, int, int, struct fnc_pathlist_head *); static int write_diff_meta(fsl_buffer *, const char *, fsl_uuid_str, const char *, fsl_uuid_str, int, enum fsl_ckout_change_e); static int diff_file(fsl_buffer *, fsl_buffer *, const char *, fsl_uuid_str, const char *, enum fsl_ckout_change_e, int, int, bool); static int diff_non_checkin(fsl_buffer *, struct fnc_commit_artifact *, int, int, int); static int diff_file_artifact(fsl_buffer *, fsl_id_t, const fsl_card_F *, fsl_id_t, const fsl_card_F *, fsl_ckout_change_e, int, int, int, enum fnc_diff_type); static int show_diff(struct fnc_view *); static int write_diff(struct fnc_view *, char *); static int match_line(const void *, const void *); static int write_matched_line(int *, const char *, int, int, WINDOW *, regmatch_t *); static void draw_vborder(struct fnc_view *); static int diff_input_handler(struct fnc_view **, struct fnc_view *, int); static int set_selected_commit(struct fnc_diff_view_state *, struct commit_entry *); static int diff_search_init(struct fnc_view *); static int diff_search_next(struct fnc_view *); static int view_close(struct fnc_view *); static int map_repo_path(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, 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 *); |
︙ | ︙ | |||
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 | int, int, int); static int fnc_commit_qid_alloc(struct fnc_commit_qid **, fsl_uuid_cstr); static int close_blame_view(struct fnc_view *); static int stop_blame(struct fnc_blame *); static int cancel_blame(void *); static void fnc_commit_qid_free(struct fnc_commit_qid *); 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 int view_resize(struct fnc_view *); static int screen_is_split(struct fnc_view *); static bool screen_is_shared(struct fnc_view *); 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_list_object_free(void *, void *); static void sigwinch_handler(int); static void sigpipe_handler(int); static void sigcont_handler(int); static int strtonumcheck(int *, const char *, const int, const int); static char *fnc_strsep (char **, const char *); #ifdef __linux__ static size_t fnc_strlcat(char *, const char *, size_t); static size_t fnc_strlcpy(char *, const char *, size_t); #endif static int set_colours(fsl_list *, enum fnc_view_id vid); static int match_colour(const void *, const void *); static bool fnc_home(struct fnc_view *); static struct fnc_colour *get_colour(fsl_list *, int); static struct fnc_tree_entry *get_tree_entry(struct fnc_tree_object *, 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; | > > > > > > > > > > > > > > > > > > > > > > > > > > < < | 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 | int, int, int); static int fnc_commit_qid_alloc(struct fnc_commit_qid **, fsl_uuid_cstr); static int close_blame_view(struct fnc_view *); static int stop_blame(struct fnc_blame *); static int cancel_blame(void *); static void fnc_commit_qid_free(struct fnc_commit_qid *); static int fnc_load_branches(struct fnc_branch_view_state *); static int create_tmp_branchlist_table(void); static int alloc_branch(struct fnc_branch **, const char *, double, bool, bool, bool); static int fnc_branchlist_insert(struct fnc_branchlist_entry **, struct fnc_branchlist_head *, struct fnc_branch *); static int open_branch_view(struct fnc_view *, int, const char *, double, int); static int show_branch_view(struct fnc_view *); static int branch_input_handler(struct fnc_view **, struct fnc_view *, int); static int tl_branch_entry(struct fnc_view **, int, struct fnc_branchlist_entry *); static int browse_branch_tree(struct fnc_view **, int, struct fnc_branchlist_entry *); static void branch_scroll_up(struct fnc_branch_view_state *, int); static void branch_scroll_down(struct fnc_branch_view_state *, int); static int branch_search_next(struct fnc_view *); static int branch_search_init(struct fnc_view *); static int match_branchlist_entry(struct fnc_branchlist_entry *, regex_t *); static int close_branch_view(struct fnc_view *); static void fnc_free_branches(struct fnc_branch_view_state *); static void fnc_branch_close(struct fnc_branch *); 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 int view_resize(struct fnc_view *); static int screen_is_split(struct fnc_view *); static bool screen_is_shared(struct fnc_view *); 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_list_object_free(void *, void *); static void sigwinch_handler(int); static void sigpipe_handler(int); static void sigcont_handler(int); static int strtonumcheck(int *, const char *, const int, const int); static int fnc_date_to_mtime(double *, const char *, int); static char *fnc_strsep (char **, const char *); #ifdef __linux__ static size_t fnc_strlcat(char *, const char *, size_t); static size_t fnc_strlcpy(char *, const char *, size_t); #endif static int set_colours(fsl_list *, enum fnc_view_id vid); static int match_colour(const void *, const void *); static bool fnc_home(struct fnc_view *); static struct fnc_colour *get_colour(fsl_list *, int); static struct fnc_tree_entry *get_tree_entry(struct fnc_tree_object *, 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 = 0; fnc_init.filter_types = (struct artifact_types *)fsl_malloc(sizeof(struct artifact_types)); fnc_init.filter_types->values = fsl_malloc(sizeof(char *)); fnc_init.filter_types->nitems = 0; |
︙ | ︙ | |||
895 896 897 898 899 900 901 | rc = fcli_fingerprint_check(true); if (rc) goto end; if (argc == 1) cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE]; | > | | | | | | | | > | | | | | | | | | | | | > < | < > > > > > > > > | < < | < > | 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 | rc = fcli_fingerprint_check(true); if (rc) goto end; if (argc == 1) cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE]; else { 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_NOT_FOUND || !path) { rc = RC(rc, "'%s' is not a valid command or path", argv[1]); fnc_init.err = rc; usage(); /* NOT REACHED */ } else if (rc) goto end; cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE]; fnc_init.path = path; fcli_err_reset(); /* cmd_timeline::fcli_process_flags */ } else if (rc) goto end; } if ((rc = fcli_has_unused_args(false))) { fnc_init.err = rc; usage(); /* NOT REACHED */ } if (!fsl_cx_db_repo(fcli_cx())) { rc = RC(FSL_RC_MISUSE, "%s", "repository database required"); goto end; } if (cmd != NULL) rc = cmd->f(cmd); end: fsl_free(path); endwin(); if (rc) { if (rc == FCLI_RC_HELP) rc = 0; else if (rc == FSL_RC_BREAK) { fsl_cx *f = fcli_cx(); const char *errstr; fsl_error_get(&f->error, &errstr, NULL); fsl_fprintf(stdout, "%s", errstr); fcli_err_reset(); /* For fcli_end_of_main() */ rc = 0; } else fsl_fprintf(stderr, "%s: %s %d\n", fcli_progname(), fsl_rc_cstr(rc), rc); } putchar('\n'); return fcli_end_of_main(rc); } static int cmd_timeline(fcli_command const *argv) { struct fnc_view *v; |
︙ | ︙ | |||
1488 1489 1490 1491 1492 1493 1494 | view->show = show_timeline_view; view->input = tl_input_handler; view->close = close_timeline_view; view->search_init = tl_search_init; view->search_next = tl_search_next; | > | > > > | > > > > > > > > > > > | > > > > > | 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 | view->show = show_timeline_view; view->input = tl_input_handler; view->close = close_timeline_view; view->search_init = tl_search_init; view->search_next = tl_search_next; s->thread_cx.q = fsl_stmt_malloc(); rc = fsl_db_prepare(db, s->thread_cx.q, "%b", &sql); if (rc) { rc = RC(rc, "%s", "fsl_db_prepare"); goto end; } rc = fsl_stmt_step(s->thread_cx.q); if (rc) { switch (rc) { case FSL_RC_STEP_ROW: rc = 0; break; case FSL_RC_STEP_ERROR: rc = RC(rc, "%s", "fsl_stmt_step"); goto end; case FSL_RC_STEP_DONE: rc = RC(FSL_RC_BREAK, "%s", "no matching records"); goto end; } } s->colour = !fnc_init.nocolour && has_colors(); s->thread_cx.rc = 0; s->thread_cx.db = db; s->thread_cx.spin_idx = 0; s->thread_cx.ncommits_needed = view->nlines - 1; s->thread_cx.commits = &s->commits; s->thread_cx.timeline_end = false; s->thread_cx.quit = &s->quit; s->thread_cx.first_commit_onscreen = &s->first_commit_onscreen; s->thread_cx.selected_commit = &s->selected_commit; s->thread_cx.searching = &view->searching; s->thread_cx.search_status = &view->search_status; s->thread_cx.regex = &view->regex; s->thread_cx.path = s->path; if (s->colour) set_colours(&s->colours, FNC_VIEW_TIMELINE); end: fsl_buffer_clear(&sql); if (rc) { close_timeline_view(view); if (db->error.code) rc = fsl_cx_uplift_db_error(f, db); } |
︙ | ︙ | |||
1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 | } static int build_commits(struct fnc_tl_thread_cx *cx) { int rc = 0; /* * Step through the given SQL query, passing each row to the commit * builder to build commits for the timeline. */ do { struct fnc_commit_artifact *commit = NULL; struct commit_entry *dup_entry, *entry; | > > > > > > > > > > > > > > > > | 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 | } static int build_commits(struct fnc_tl_thread_cx *cx) { int rc = 0; if (cx->tree_open) { /* * XXX If a tree has been opened with the 't' key binding, the * commit builder statement needs to be reset otherwise one of * the SQLite3 APIs down the fsl_stmt_step() call stack fails, * irrespective of whether fsl_db_prepare_cached() is called. */ fsl_size_t loaded = cx->q->rowCount; cx->tree_open = false; rc = fsl_stmt_reset(cx->q); if (rc) return RC(rc, "%s", "fsl_stmt_reset"); while (loaded--) if ((rc = fsl_stmt_step(cx->q)) != FSL_RC_STEP_ROW) return RC(rc, "%s", "fsl_stmt_step"); } /* * Step through the given SQL query, passing each row to the commit * builder to build commits for the timeline. */ do { struct fnc_commit_artifact *commit = NULL; struct commit_entry *dup_entry, *entry; |
︙ | ︙ | |||
1837 1838 1839 1840 1841 1842 1843 1844 1845 | { fsl_cx *f = fcli_cx(); fsl_db *db = fsl_needs_repo(f); struct fnc_commit_artifact *commit = NULL; fsl_buffer buf = fsl_buffer_empty; const char *comment, *prefix, *type; int rc = 0; if (rid) { | > | | > | 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 | { fsl_cx *f = fcli_cx(); fsl_db *db = fsl_needs_repo(f); struct fnc_commit_artifact *commit = NULL; fsl_buffer buf = fsl_buffer_empty; const char *comment, *prefix, *type; int rc = 0; enum fnc_diff_type diff_type = FNC_DIFF_WIKI; if (rid) { rc = fsl_db_prepare(db, q, "SELECT " /* 0 */"uuid, " /* 1 */"datetime(event.mtime%s), " /* 2 */"coalesce(euser, user), " /* 3 */"rid AS rid, " /* 4 */"event.type AS eventtype, " /* 5 */"(SELECT group_concat(substr(tagname,5), ',') " "FROM tag, tagxref WHERE tagname GLOB 'sym-*' " "AND tag.tagid=tagxref.tagid AND tagxref.rid=blob.rid " "AND tagxref.tagtype > 0) as tags, " /*6*/"coalesce(ecomment, comment) AS comment " "FROM event JOIN blob WHERE blob.rid=%d AND event.objid=%d", fnc_init.utc ? "" : ", 'localtime'", rid, rid); if (rc) return RC(FSL_RC_DB, "%s", "fsl_db_prepare"); fsl_stmt_step(q); } type = fsl_stmt_g_text(q, 4, NULL); comment = fsl_stmt_g_text(q, 6, NULL); prefix = NULL; switch (*type) { case 'c': type = "checkin"; diff_type = FNC_DIFF_COMMIT; break; case 'w': type = "wiki"; if (comment) { switch (*comment) { case '+': prefix = "Added: "; |
︙ | ︙ | |||
1914 1915 1916 1917 1918 1919 1920 | commit = calloc(1, sizeof(*commit)); if (commit == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); goto end; } | < > > < | 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 | commit = calloc(1, sizeof(*commit)); if (commit == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); goto end; } if (!rid && (rc = fsl_stmt_get_id(q, 3, &rid))) { rc = RC(rc, "%s", "fsl_stmt_get_id"); goto end; } /* Is there a more efficient way to get the parent? */ commit->puuid = fsl_db_g_text(db, NULL, "SELECT uuid FROM plink, blob WHERE plink.cid=%d " "AND blob.rid=plink.pid AND plink.isprim", rid); commit->prid = fsl_uuid_to_rid(f, commit->puuid); commit->uuid = fsl_strdup(fsl_stmt_g_text(q, 0, NULL)); commit->rid = rid; commit->type = fsl_strdup(type); commit->diff_type = diff_type; commit->timestamp = fsl_strdup(fsl_stmt_g_text(q, 1, NULL)); commit->user = fsl_strdup(fsl_stmt_g_text(q, 2, NULL)); commit->branch = fsl_strdup(fsl_stmt_g_text(q, 5, NULL)); commit->comment = fsl_strdup(comment ? fsl_buffer_str(&buf) : ""); fsl_buffer_clear(&buf); *ptr = commit; end: return rc; } static int signal_tl_thread(struct fnc_view *view, int wait) { struct fnc_tl_thread_cx *cx = &view->state.timeline.thread_cx; |
︙ | ︙ | |||
1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 | static int draw_commits(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; struct fnc_tl_thread_cx *tcx = &s->thread_cx; struct commit_entry *entry = s->selected_commit; const char *search_str = NULL; char *headln = NULL, *idxstr = NULL; char *branch = NULL, *type = NULL; char *uuid = NULL; wchar_t *wcstr; int ncommits = 0, rc = 0, wstrlen = 0; int ncols_needed, max_usrlen = -1; | > | 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 | static int draw_commits(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; struct fnc_tl_thread_cx *tcx = &s->thread_cx; struct commit_entry *entry = s->selected_commit; 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 *wcstr; int ncommits = 0, rc = 0, wstrlen = 0; int ncols_needed, max_usrlen = -1; |
︙ | ︙ | |||
2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 | if (rc) goto end; werase(view->window); if (screen_is_shared(view)) wstandout(view->window); waddwstr(view->window, wcstr); while (wstrlen < view->ncols) { waddch(view->window, ' '); ++wstrlen; } if (screen_is_shared(view)) wstandend(view->window); fsl_free(wcstr); | > > > > > > | 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 | if (rc) goto end; werase(view->window); if (screen_is_shared(view)) wstandout(view->window); if (s->colour) c = get_colour(&s->colours, FNC_COMMIT_ID); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddwstr(view->window, wcstr); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); while (wstrlen < view->ncols) { waddch(view->window, ' '); ++wstrlen; } if (screen_is_shared(view)) wstandend(view->window); fsl_free(wcstr); |
︙ | ︙ | |||
2110 2111 2112 2113 2114 2115 2116 | ncommits = 0; entry = s->first_commit_onscreen; s->last_commit_onscreen = s->first_commit_onscreen; while (entry) { if (ncommits >= view->nlines - 1) break; if (ncommits == s->selected_idx) | | | | 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 | ncommits = 0; entry = s->first_commit_onscreen; s->last_commit_onscreen = s->first_commit_onscreen; while (entry) { if (ncommits >= view->nlines - 1) break; if (ncommits == s->selected_idx) wattr_on(view->window, A_REVERSE, NULL); rc = write_commit_line(view, entry->commit, max_usrlen); if (ncommits == s->selected_idx) wattr_off(view->window, A_REVERSE, NULL); ++ncommits; s->last_commit_onscreen = entry; entry = TAILQ_NEXT(entry, entries); } draw_vborder(view); end: |
︙ | ︙ | |||
2267 2268 2269 2270 2271 2272 2273 | * * When < 110 columns, the (abbreviated 9-character) UUID will be elided. */ static int write_commit_line(struct fnc_view *view, struct fnc_commit_artifact *commit, int max_usrlen) { | > > | | > | | | > > > > > > > > > > > > > > > > > > > | 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 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 | * * When < 110 columns, the (abbreviated 9-character) UUID will be elided. */ static int write_commit_line(struct fnc_view *view, struct fnc_commit_artifact *commit, int max_usrlen) { struct fnc_tl_view_state *s = &view->state.timeline; struct fnc_colour *c = NULL; wchar_t *usr_wcstr = NULL, *wcomment = NULL; char *comment0 = NULL, *comment = NULL; char *date = NULL; char *eol = NULL, *pad = NULL, *user = NULL; size_t i = 0; int col_pos, ncols_avail, usrlen; int commentlen, rc = 0; /* Trim time component from timestamp for the date field. */ date = fsl_strdup(commit->timestamp); while (!fsl_isspace(date[i++])) {} date[i] = '\0'; col_pos = MIN(view->ncols, ISO8601_DATE_ONLY + 1); if (s->colour) c = get_colour(&s->colours, FNC_DATE_STR); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddnstr(view->window, date, col_pos); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (col_pos > view->ncols) goto end; /* If enough columns, write abbreviated commit hash. */ if (view->ncols >= 110) { if (s->colour) c = get_colour(&s->colours, FNC_COMMIT_ID); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); wprintw(view->window, "%.9s ", commit->uuid); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); col_pos += 10; if (col_pos > view->ncols) goto end; } /* * Parse username from emailaddr if needed, and postfix username * with as much whitespace as needed to fill two spaces beyond * the longest username on the screen. */ user = fsl_strdup(commit->user); if (user == NULL) goto end; if (strpbrk(user, "<@>") != NULL) parse_emailaddr_username(&user); rc = formatln(&usr_wcstr, &usrlen, user, view->ncols - col_pos, col_pos); if (rc) goto end; if (s->colour) c = get_colour(&s->colours, FNC_USER_STR); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddwstr(view->window, usr_wcstr); pad = fsl_mprintf("%*c", max_usrlen - usrlen + 2, ' '); waddstr(view->window, pad); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); col_pos += (max_usrlen + 2); if (col_pos > view->ncols) goto end; /* Only show comment up to the first newline character. */ comment0 = fsl_strdup(commit->comment); comment = comment0; |
︙ | ︙ | |||
2473 2474 2475 2476 2477 2478 2479 | rc = view->input(new, view, ch); break; } return rc; } | | > | | | | | > > > > > > > > | < > > > > | < | | | | < < < < | | < < < < | | < < < < | < < | | | | | | | > > > > > > > | > | < > > > | < > > > > > > | > > > > | > | | | | | | < < > | | < < | < < < | > > > < < | < > > > > | < > | < > > | | < > | > > | > > | > | | < < | < > > | < | < | < < < < | < | < | < < < < < < < | < < > > > > | > | > > > | > | | | > | < < < | < > | > > > | < > | | < < < > > | < > > | | | | | > | > > > | | | | | < < < < < < < < < < < < < < | | | | | | | | | < < | 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 | rc = view->input(new, view, ch); break; } return rc; } static int help(struct fnc_view *view) { FILE *help = NULL; char *title = NULL; static const char *keys[][2] = { {""}, {""}, /* Global */ {" H,?,F1 ", " ❬H❭❬?❭❬F1❭ "}, {" k,<Up> ", " ❬↑❭❬k❭ "}, {" j,<Down> ", " ❬↓❭❬j❭ "}, {" C-b,PgUp ", " ❬C-b❭❬PgUp❭ "}, {" C-f,PgDn ", " ❬C-f❭❬PgDn❭ "}, {" gg,Home ", " ❬gg❭❬Home❭ "}, {" G,End ", " ❬G❭❬End❭ "}, {" Tab ", " ❬TAB❭ "}, {" c ", " ❬c❭ "}, {" f ", " ❬f❭ "}, {" / ", " ❬/❭ "}, {" n ", " ❬n❭ "}, {" N ", " ❬N❭ "}, {" q ", " ❬q❭ "}, {" Q ", " ❬Q❭ "}, {""}, {""}, /* Timeline */ {" <,, ", " ❬<❭❬,❭ "}, {" >,. ", " ❬>❭❬.❭ "}, {" Enter,Space ", " ❬Enter❭❬Space❭ "}, {" t ", " ❬t❭ "}, {""}, {""}, /* Diff */ {" Space ", " ❬Space❭ "}, {" i ", " ❬i❭ "}, {" v ", " ❬v❭ "}, {" w ", " ❬w❭ "}, {" -,_ ", " ❬-❭❬_❭ "}, {" +,= ", " ❬+❭❬=❭ "}, {" C-k,K,<,, ", " ❬C-k❭❬K❭❬<❭❬,❭ "}, {" C-j,J,>,. ", " ❬C-j❭❬J❭❬>❭❬.❭ "}, {""}, {""}, /* Tree */ {" l,Enter,<Right> ", " ❬→❭❬l❭❬Enter❭ "}, {" h,<BS>,<Left> ", " ❬←❭❬h❭❬⌫❭ "}, {" i ", " ❬i❭ "}, {" t ", " ❬t❭ "}, {""}, {""}, /* Blame */ {" Space ", " ❬Space❭ "}, {" Enter ", " ❬Enter❭ "}, {" b ", " ❬b❭ "}, {" p ", " ❬p❭ "}, {" B ", " ❬B❭ "}, {""}, {""}, /* Branch */ {" Enter,Space ", " ❬Enter❭❬Space❭ "}, {" d ", " ❬d❭ "}, {" i ", " ❬i❭ "}, {" t ", " ❬t❭ "}, {" R,<C-l> ", " ❬R❭❬C-l❭ "}, {""}, {""}, {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 up one page", "Scroll down one page", "Jump to first line or start of the view", "Jump to last line or end of the view", "Switch focus between open views", "Toggle coloured output", "Toggle fullscreen", "Open prompt to enter search term (not available in this view)", "Find next line or token matching the current search term", "Find previous line or token matching the current search term", "Quit the active view", "Quit the program", "", "Timeline", "Move selection cursor up one commit", "Move selection cursor down one commit", "Open diff view of the selected commit", "Display a tree reflecting the state of the selected commit", "", "Diff", "Scroll down one page of diff output", "Toggle inversion of diff output", "Toggle verbosity of diff output", "Toggle ignore whitespace-only changes in diff", "Decrease the number of context lines", "Increase the number of context lines", "Display diff of next (newer) commit in the timeline", "Display diff of previous (older) commit in the timeline", "", "Tree", "Move into the selected directory", "Return to the parent directory", "Toggle display of file artifact SHA hashes", "Display timeline of all commits modifying the selected entry", "", "Blame", "Scroll down one page", "Display the diff of the commit corresponding to the selected line", "Blame the version of the file found in the selected line's commit", "Blame the version of the file found in the selected line's parent " "commit", "Reload the previous blamed version of the file", "", "Branch", "Display the timeline of the currently selected branch", "Toggle display of the date when the branch last received changes", "Toggle display of the SHA hash that identifies the branch", "Open a tree view of the currently selected branch", "Reload view with all repostory branches and no filters applied", "", " See fnc(1) for complete list of options and key bindings." }; int cs, ln, width = 0, rc = 0; cs = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0) ? 1 : 0; title = fsl_mprintf("%s %s Help\n", fcli_progname(), PRINT_VERSION); if (title == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); help = tmpfile(); if (help == NULL) return RC(FSL_RC_IO, "%s", "tmpfile"); /* * Format help text, and compute longest line and total number of * lines in text to be displayed to determine pad dimensions. */ width = fsl_strlen(title); for (ln = 0; keys[ln][0]; ++ln) { if (keys[ln][1]) { width = MAX((fsl_size_t)width, fsl_strlen(keys[ln][cs]) + fsl_strlen(desc[ln])); } fsl_fprintf(help, "%s%s%c", keys[ln][cs], desc[ln], keys[ln + 1] ? '\n' : 0); } rewind(help); rc = padpopup(view, width, ln, help, title); if (fclose(help) == EOF) rc = RC(fsl_errno_to_rc(errno, FSL_RC_IO), "%s", "fclose"); fsl_free(title); return rc; } /* * Create popup pad in which to write the supplied txt string and optional * title. The pad is contained within a window that is offset four columns in * and two lines down from the parent window. */ static int padpopup(struct fnc_view *view, int width, int height, FILE *txt, const char *title) { WINDOW *win, *content; char *line = NULL; ssize_t linelen; size_t linesz; int ch, cury, end, wy, wx, x0, y0; x0 = 4; /* Number of columns to border window. */ y0 = 2; /* Number of lines to border window. */ cury = 0; wx = getmaxx(view->window) - ((x0 + 1) * 2); /* Width of window. */ wy = getmaxy(view->window) - ((y0 + 1) * 2); /* Height of window */ ch = ERR; if ((win = newwin(wy, wx, y0, x0)) == 0) return RC(FSL_RC_ERROR, "%s", "newwin"); if ((content = newpad(height + 1, width + 1)) == 0) { delwin(win); return RC(FSL_RC_ERROR, "%s", "newpad"); } doupdate(); keypad(content, TRUE); /* Write text content to pad. */ if (title) centerprint(content, 0, 0, width, title, 0); while ((linelen = getline(&line, &linesz, txt)) != -1) waddstr(content, line); fsl_free(line); end = (getcury(content) - (wy - 3)); /* No. lines past end of pad. */ do { switch (ch) { case KEY_UP: case 'k': if (cury > 0) |
︙ | ︙ | |||
2712 2713 2714 2715 2716 2717 2718 | case 'G': cury = end; break; case ERR: default: break; } | | | | | | | | | > | > | | 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 | case 'G': cury = end; break; case ERR: default: break; } werase(win); box(win, 0, 0); wnoutrefresh(win); pnoutrefresh(content, cury, 0, y0 + 1, x0 + 1, wy, wx); doupdate(); } while ((ch = wgetch(content)) != 'q' && ch != KEY_ESCAPE && ch != ERR); /* Destroy window. */ werase(win); wrefresh(win); delwin(win); delwin(content); /* Restore fnc window content. */ touchwin(view->window); wnoutrefresh(view->window); doupdate(); return 0; } static void centerprint(WINDOW *win, int starty, int startx, int cols, const char *str, chtype colour) { int x, y; if (win == NULL) win = stdscr; |
︙ | ︙ | |||
2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 | return rc; view->child = diff_view; diff_view->parent = view; view->focus_child = true; } else *new_view = diff_view; break; case 't': if (s->selected_commit == NULL) break; if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); rc = browse_commit_tree(&tree_view, start_col, s->selected_commit, s->path); if (rc) break; view->active = false; tree_view->active = true; if (view_is_parent(view)) { rc = view_close_child(view); if (rc) return rc; view_set_child(view, tree_view); | > > > > > > > > > > > > > > > > > > > | 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 | return rc; view->child = diff_view; diff_view->parent = view; view->focus_child = true; } else *new_view = diff_view; break; case 'c': s->colour = !s->colour; break; case 't': if (s->selected_commit == NULL) break; if (!fsl_rid_is_a_checkin(fcli_cx(), s->selected_commit->commit->rid)) { wattr_on(view->window, A_BOLD, NULL); mvwaddstr(view->window, view->start_ln + view->nlines - 1, 0, "-- tree requires check-in artifact --"); wclrtoeol(view->window); wattr_off(view->window, A_BOLD, NULL); fcli_err_reset(); rc = 0; update_panels(); doupdate(); sleep(1); break; } if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); rc = browse_commit_tree(&tree_view, start_col, s->selected_commit, s->path); if (rc) break; s->thread_cx.tree_open = true; view->active = false; tree_view->active = true; if (view_is_parent(view)) { rc = view_close_child(view); if (rc) return rc; view_set_child(view, tree_view); |
︙ | ︙ | |||
3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 | return rc; } static int close_timeline_view(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; int rc = 0; rc = join_tl_thread(s); fnc_free_commits(&s->commits); regfree(&view->regex); fsl_free(s->path); s->path = NULL; return rc; } | > > > | 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 | return rc; } static int close_timeline_view(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; struct fsl_list_state st = { FNC_COLOUR_OBJ }; int rc = 0; rc = join_tl_thread(s); fsl_stmt_finalize(s->thread_cx.q); fnc_free_commits(&s->commits); fsl_list_clear(&s->colours, fsl_list_object_free, &st); regfree(&view->regex); fsl_free(s->path); s->path = NULL; return rc; } |
︙ | ︙ | |||
3369 3370 3371 3372 3373 3374 3375 | int rc = 0; diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF); if (diff_view == NULL) return RC(FSL_RC_ERROR, "%s", "view_open"); rc = open_diff_view(diff_view, commit, DIFF_DEF_CTXT, fnc_init.ws, | | | > > | 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 | int rc = 0; diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF); if (diff_view == NULL) return RC(FSL_RC_ERROR, "%s", "view_open"); rc = open_diff_view(diff_view, commit, DIFF_DEF_CTXT, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, timeline_view, true, NULL); if (!rc) *new_view = diff_view; return rc; } static int open_diff_view(struct fnc_view *view, struct fnc_commit_artifact *commit, int context, bool ignore_ws, bool invert, bool verbosity, struct fnc_view *timeline_view, bool showmeta, struct fnc_pathlist_head *paths) { struct fnc_diff_view_state *s = &view->state.diff; int rc = 0; s->paths = paths; s->selected_commit = commit; s->first_line_onscreen = 1; s->last_line_onscreen = view->nlines; s->current_line = 1; s->f = NULL; s->context = context; s->sbs = 0; |
︙ | ︙ | |||
3467 3468 3469 3470 3471 3472 3473 | } s->f = fout; /* * We'll diff artifacts of type "ci" (i.e., "checkin") separately, as * it's a different process to diff the others (wiki, technote, etc.). */ | | | | | < > > | | | > > | 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 | } s->f = fout; /* * We'll diff artifacts of type "ci" (i.e., "checkin") separately, as * it's a different process to diff the others (wiki, technote, etc.). */ if (s->selected_commit->diff_type == FNC_DIFF_COMMIT) rc = create_changeset(s->selected_commit); else if (s->selected_commit->diff_type == FNC_DIFF_BLOB) rc = diff_file_artifact(&s->buf, s->selected_commit->prid, NULL, s->selected_commit->rid, NULL, FSL_CKOUT_CHANGE_MOD, s->diff_flags, s->context, s->sbs, s->selected_commit->diff_type); else if (s->selected_commit->diff_type == FNC_DIFF_WIKI) rc = diff_non_checkin(&s->buf, s->selected_commit, s->diff_flags, s->context, s->sbs); 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_commit->puuid) { s->id1 = fsl_strdup(s->selected_commit->puuid); |
︙ | ︙ | |||
3510 3511 3512 3513 3514 3515 3516 | write_commit_meta(s); /* * Diff local changes on disk in the current checkout differently to * checked-in versions: the former compares on disk file content with * file artifacts; the latter compares file artifact blobs only. */ | | | | < | | | | 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 | write_commit_meta(s); /* * Diff local changes on disk in the current checkout differently to * checked-in versions: the former compares on disk file content with * file artifacts; the latter compares file artifact blobs only. */ if (s->selected_commit->diff_type == FNC_DIFF_COMMIT) diff_commit(&s->buf, s->selected_commit, s->diff_flags, s->context, s->sbs, s->paths); else if (s->selected_commit->diff_type == FNC_DIFF_CKOUT) diff_checkout(&s->buf, s->selected_commit->prid, s->diff_flags, s->context, s->sbs, s->paths); /* * Parse the diff buffer line-by-line to record byte offsets of each * line for scrolling and searching in diff view. */ st0 = fsl_strdup(fsl_buffer_str(&s->buf)); st = st0; |
︙ | ︙ | |||
3552 3553 3554 3555 3556 3557 3558 | } static int create_changeset(struct fnc_commit_artifact *commit) { fsl_cx *f = fcli_cx(); fsl_stmt *st = NULL; | < > | | | 3816 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 | } static int create_changeset(struct fnc_commit_artifact *commit) { fsl_cx *f = fcli_cx(); fsl_stmt *st = NULL; fsl_list changeset = fsl_list_empty; int rc = 0; st = fsl_stmt_malloc(); rc = fsl_cx_prepare(f, st, "SELECT name, mperm, " "(SELECT uuid FROM blob WHERE rid=mlink.pid), " "(SELECT uuid FROM blob WHERE rid=mlink.fid), " "(SELECT name FROM filename WHERE filename.fnid=mlink.pfnid) " "FROM mlink JOIN filename ON filename.fnid=mlink.fnid " "WHERE mlink.mid=%d AND NOT mlink.isaux " "AND (mlink.fid > 0 " "OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d)) " "ORDER BY name", commit->rid, commit->rid); if (rc) return RC(FSL_RC_DB, "%s", "fsl_cx_prepare"); while ((rc = fsl_stmt_step(st)) == FSL_RC_STEP_ROW) { struct fsl_file_artifact *fdiff = NULL; const char *path, *oldpath, *olduuid, *uuid; /* TODO: Parse file mode to display in commit changeset. */ /* int perm; */ |
︙ | ︙ | |||
3603 3604 3605 3606 3607 3608 3609 | fdiff->fc->uuid = fsl_strdup(uuid); fdiff->change = FSL_CKOUT_CHANGE_MOD; } fsl_list_append(&changeset, fdiff); } commit->changeset = changeset; | | | 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 | fdiff->fc->uuid = fsl_strdup(uuid); fdiff->change = FSL_CKOUT_CHANGE_MOD; } fsl_list_append(&changeset, fdiff); } commit->changeset = changeset; fsl_stmt_finalize(st); if (rc == FSL_RC_STEP_DONE) rc = 0; return rc; } |
︙ | ︙ | |||
3793 3794 3795 3796 3797 3798 3799 | * to dump the complete content of the added/deleted file if FSL_DIFF_VERBOSE is * set, otherwise only diff metatadata will be output. In case (3), if the * hash (UUID) of each F card is the same, there are no changes; if different, * both artifacts will be passed to diff_file_artifact() to be diffed. */ static int diff_commit(fsl_buffer *buf, struct fnc_commit_artifact *commit, int diff_flags, | | > | | < < < | | | | | | | | | | > | | > > > > > > > > > > > > > > > > | | > > | | > | 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 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 | * to dump the complete content of the added/deleted file if FSL_DIFF_VERBOSE is * set, otherwise only diff metatadata will be output. In case (3), if the * hash (UUID) of each F card is the same, there are no changes; if different, * both artifacts will be passed to diff_file_artifact() to be diffed. */ static int diff_commit(fsl_buffer *buf, struct fnc_commit_artifact *commit, int diff_flags, int context, int sbs, struct fnc_pathlist_head *paths) { fsl_cx *f = fcli_cx(); const fsl_card_F *fc1 = NULL; const fsl_card_F *fc2 = NULL; fsl_deck d1 = fsl_deck_empty; fsl_deck d2 = fsl_deck_empty; fsl_id_t id1; int different = 0, rc = 0; rc = fsl_deck_load_rid(f, &d2, commit->rid, FSL_SATYPE_CHECKIN); if (rc) goto end; rc = fsl_deck_F_rewind(&d2); if (rc) goto end; /* * For the one-and-only special case of repositories, such as the * canonical fnc, that do not have an "initial empty check-in", we * proceed with no parent version to diff against. */ if (commit->puuid) { rc = fsl_sym_to_rid(f, commit->puuid, FSL_SATYPE_CHECKIN, &id1); if (rc) goto end; rc = fsl_deck_load_rid(f, &d1, id1, FSL_SATYPE_CHECKIN); if (rc) goto end; rc = fsl_deck_F_rewind(&d1); if (rc) goto end; 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 (paths != NULL && !TAILQ_EMPTY(paths)) { struct fnc_pathlist_entry *pe; diff = false; TAILQ_FOREACH(pe, 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); if (different) { if (different > 0) { b = fc2; change = FSL_CKOUT_CHANGE_ADDED; fsl_deck_F_next(&d2, &fc2); } else if (different < 0) { a = fc1; change = FSL_CKOUT_CHANGE_REMOVED; fsl_deck_F_next(&d1, &fc1); } if (diff) rc = diff_file_artifact(buf, id1, a, commit->rid, b, change, diff_flags, context, sbs, commit->diff_type); } else if (!fsl_uuidcmp(fc1->uuid, fc2->uuid)) { /* No change */ fsl_deck_F_next(&d1, &fc1); fsl_deck_F_next(&d2, &fc2); } else { change = FSL_CKOUT_CHANGE_MOD; if (diff) rc = diff_file_artifact(buf, id1, fc1, commit->rid, fc2, change, diff_flags, context, sbs, commit->diff_type); fsl_deck_F_next(&d1, &fc1); fsl_deck_F_next(&d2, &fc2); } if (rc == FSL_RC_RANGE) { fsl_buffer_append(buf, "\nDiff has too many changes\n", -1); rc = 0; |
︙ | ︙ | |||
3893 3894 3895 3896 3897 3898 3899 | * 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 * zero args—not two—supplied to fnc's diff command line interface. */ static int diff_checkout(fsl_buffer *buf, fsl_id_t vid, int diff_flags, int context, | | < > | 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 | * 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 * zero args—not two—supplied to fnc's diff command line interface. */ static int diff_checkout(fsl_buffer *buf, fsl_id_t vid, int diff_flags, int context, int sbs, struct fnc_pathlist_head *paths) { fsl_cx *f = fcli_cx(); fsl_stmt *st = NULL; fsl_buffer sql, abspath, bminus; fsl_uuid_str xminus = NULL; fsl_id_t cid; int rc = 0; bool allow_symlinks; abspath = bminus = sql = fsl_buffer_empty; fsl_ckout_version_info(f, &cid, NULL); /* cid = fsl_config_get_id(f, FSL_CONFDB_CKOUT, 0, "checkout"); */ /* XXX Already done in cmd_diff(): Load vfile table with local state. */ /* rc = fsl_vfile_changes_scan(f, cid, */ /* FSL_VFILE_CKSIG_ENOTFILE & FSL_VFILE_CKSIG_KEEP_OTHERS); */ |
︙ | ︙ | |||
3947 3948 3949 3950 3951 3952 3953 | fsl_buffer_appendf(&sql, "SELECT pathname, deleted, chnged, " "rid == 0, rid, islink" " FROM vfile" " WHERE vid = %d" " AND (deleted OR chnged OR rid == 0)" " ORDER BY pathname", cid); } | > | > > > > | 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 | fsl_buffer_appendf(&sql, "SELECT pathname, deleted, chnged, " "rid == 0, rid, islink" " FROM vfile" " WHERE vid = %d" " AND (deleted OR chnged OR rid == 0)" " ORDER BY pathname", cid); } st = fsl_stmt_malloc(); rc = fsl_cx_prepare(f, st, "%b", &sql); if (rc) { rc = RC(rc, "%s", "fsl_cx_prepare"); goto yield; } while ((rc = fsl_stmt_step(st)) == FSL_RC_STEP_ROW) { const char *path; int deleted, changed, added, fid, symlink; enum fsl_ckout_change_e change; bool diff = true; path = fsl_stmt_g_text(st, 0, NULL); deleted = fsl_stmt_g_int32(st, 1); changed = fsl_stmt_g_int32(st, 2); added = fsl_stmt_g_int32(st, 3); fid = fsl_stmt_g_int32(st, 4); symlink = fsl_stmt_g_int32(st, 5); |
︙ | ︙ | |||
4019 4020 4021 4022 4023 4024 4025 | break; } } while (cf); fsl_deck_finalize(&d); } if (!xminus) xminus = fsl_strdup(NULL_DEVICE); | | > | > | > > | > > > > > > > > > > | | 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 | break; } } while (cf); fsl_deck_finalize(&d); } if (!xminus) xminus = fsl_strdup(NULL_DEVICE); allow_symlinks = fsl_config_get_bool(f, FSL_CONFDB_REPO, false, "allow-symlinks"); if (!symlink != !(fsl_is_symlink(fsl_buffer_cstr(&abspath)) && allow_symlinks)) { rc = write_diff_meta(buf, path, xminus, path, NULL_DEVICE, diff_flags, change); fsl_buffer_append(buf, "\nSymbolic links and regular " "files cannot be diffed\n", -1); if (rc) goto yield; continue; } if (fid > 0 && change != FSL_CKOUT_CHANGE_ADDED) { rc = fsl_content_get(f, fid, &bminus); if (rc) goto yield; } else fsl_buffer_clear(&bminus); if (paths != NULL && !TAILQ_EMPTY(paths)) { struct fnc_pathlist_entry *pe; diff = false; TAILQ_FOREACH(pe, paths, entry) if (!fsl_strncmp(pe->path, path, pe->pathlen) || !fsl_strcmp(pe->path, path)) { diff = true; break; } } if (diff) rc = diff_file(buf, &bminus, path, xminus, fsl_buffer_cstr(&abspath), change, diff_flags, context, sbs); fsl_buffer_reuse(&bminus); fsl_buffer_reuse(&abspath); fsl_free(xminus); xminus = NULL; |
︙ | ︙ | |||
4056 4057 4058 4059 4060 4061 4062 | rc = 0; fsl_cx_err_reset(f); } else if (rc) goto yield; } yield: | | | 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 | rc = 0; fsl_cx_err_reset(f); } else if (rc) goto yield; } yield: fsl_stmt_finalize(st); fsl_free(xminus); unload: fsl_vfile_unload_except(f, cid); fsl_buffer_clear(&abspath); fsl_buffer_clear(&bminus); fsl_buffer_clear(&sql); return rc; |
︙ | ︙ | |||
4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 | case FSL_CKOUT_CHANGE_MOD: /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } if ((diff_flags & (FSL_DIFF_SIDEBYSIDE | FSL_DIFF_BRIEF)) == 0) { rc = fsl_buffer_appendf(buf, "\nIndex: %s\n%.71c\n", index, '='); if (!rc) rc = fsl_buffer_appendf(buf, "hash - %s\nhash + %s\n", minus, plus); } | > > > > > > > > > | 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 | case FSL_CKOUT_CHANGE_MOD: /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } if (diff_flags & FSL_DIFF_INVERT) { const char *tmp = minus; minus = plus; plus = tmp; tmp = zminus; zminus = zplus; zplus = tmp; } if ((diff_flags & (FSL_DIFF_SIDEBYSIDE | FSL_DIFF_BRIEF)) == 0) { rc = fsl_buffer_appendf(buf, "\nIndex: %s\n%.71c\n", index, '='); if (!rc) rc = fsl_buffer_appendf(buf, "hash - %s\nhash + %s\n", minus, plus); } |
︙ | ︙ | |||
4145 4146 4147 4148 4149 4150 4151 | diff_file(fsl_buffer *buf, fsl_buffer *bminus, const char *zminus, fsl_uuid_str xminus, const char *abspath, enum fsl_ckout_change_e change, int diff_flags, int context, bool sbs) { fsl_cx *f = fcli_cx(); fsl_buffer bplus = fsl_buffer_empty; fsl_buffer xplus = fsl_buffer_empty; | | < < | | 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 | diff_file(fsl_buffer *buf, fsl_buffer *bminus, const char *zminus, fsl_uuid_str xminus, const char *abspath, enum fsl_ckout_change_e change, int diff_flags, int context, bool sbs) { fsl_cx *f = fcli_cx(); fsl_buffer bplus = fsl_buffer_empty; fsl_buffer xplus = fsl_buffer_empty; const char *zplus = NULL; int rc = 0; bool verbose; /* * 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). */ if (change != FSL_CKOUT_CHANGE_REMOVED) { rc = fsl_ckout_file_content(f, false, abspath, &bplus); if (rc) goto end; /* * To replicate fossil(1)'s behaviour—where a fossil rm'd file * will either show as an unchanged or edited rather than a * removed file with 'fossil diff -v' output—remove the above |
︙ | ︙ | |||
4352 4353 4354 4355 4356 4357 4358 | * 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(fsl_buffer *buf, fsl_id_t vid1, const fsl_card_F *a, fsl_id_t vid2, const fsl_card_F *b, enum fsl_ckout_change_e change, | | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 4660 4661 4662 4663 4664 4665 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 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 4769 | * 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(fsl_buffer *buf, fsl_id_t vid1, const fsl_card_F *a, fsl_id_t vid2, const fsl_card_F *b, enum fsl_ckout_change_e change, int diff_flags, int context, int sbs, enum fnc_diff_type diff_type) { fsl_cx *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; fsl_uuid_str xplus0 = NULL, xminus0 = NULL; fsl_uuid_str xplus = NULL, xminus = NULL; int rc = 0; bool verbose; assert(vid1 != vid2); assert(vid2 > 0 && "local checkout should be diffed with diff_checkout()"); fbuf2.used = fbuf1.used = 0; if (a) { rc = fsl_card_F_content(f, a, &fbuf1); if (rc) goto end; zminus = a->name; xminus = a->uuid; } else if (diff_type == FNC_DIFF_BLOB) { rc = fsl_cx_prepare(f, &stmt, "SELECT name FROM filename, mlink " "WHERE filename.fnid=mlink.fnid AND mlink.fid = %d", vid1); if (rc) { rc = RC(FSL_RC_DB, "%s %d", "fsl_cx_prepare", vid1); goto end; } rc = fsl_stmt_step(&stmt); if (rc == FSL_RC_STEP_ROW) { rc = 0; zminus0 = fsl_strdup(fsl_stmt_g_text(&stmt, 0, NULL)); zminus = zminus0; } else if (rc == FSL_RC_STEP_DONE) rc = 0; else if (rc) { rc = RC(rc, "%s", "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; } else if (diff_type == FNC_DIFF_BLOB) { rc = fsl_cx_prepare(f, &stmt, "SELECT name FROM filename, mlink " "WHERE filename.fnid=mlink.fnid AND mlink.fid = %d", vid2); if (rc) { rc = RC(FSL_RC_DB, "%s %d", "fsl_cx_prepare", vid2); goto end; } rc = fsl_stmt_step(&stmt); if (rc == FSL_RC_STEP_ROW) { rc = 0; zplus0 = fsl_strdup(fsl_stmt_g_text(&stmt, 0, NULL)); zplus = zplus0; } else if (rc == FSL_RC_STEP_DONE) rc = 0; else if (rc) { rc = RC(rc, "%s", "fsl_stmt_step"); goto end; } xplus0 = fsl_rid_to_uuid(f, vid2); xplus = xplus0; fsl_stmt_finalize(&stmt); fsl_content_get(f, vid2, &fbuf2); } rc = write_diff_meta(buf, zminus, xminus, zplus, xplus, diff_flags, change); verbose = (diff_flags & FSL_DIFF_VERBOSE) != 0 ? true : false; if (verbose || (a && b)) rc = fsl_diff_text_to_buffer(&fbuf1, &fbuf2, buf, context, sbs, diff_flags); if (rc) RC(rc, "%s: fsl_diff_text_to_buffer\n" " -> %s [%s]\n -> %s [%s]", fsl_rc_cstr(rc), a ? a->name : NULL_DEVICE, a ? a->uuid : NULL_DEVICE, b ? b->name : NULL_DEVICE, b ? b->uuid : NULL_DEVICE); end: fsl_free(zminus0); fsl_free(zplus0); fsl_free(xminus0); fsl_free(xplus0); fsl_buffer_clear(&fbuf1); fsl_buffer_clear(&fbuf2); return rc; } static int show_diff(struct fnc_view *view) |
︙ | ︙ | |||
5055 5056 5057 5058 5059 5060 5061 | size_t idx = 0; endwin(); /* 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) { | > | | | < < < | < < < < < | | > | > | | > | > > > > > > > > > > > > > > > | > > > > > > > | > | | > > > > > < > > > > > > > > > > > > > | > > | > > > | > > > > > > > > | > | > > | > > > > > > > > > < < < | > > > > > > > > > > | > > > > | | > > > | > > | > > > > | | > > > > > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | > > | | < > < < | | < < < < < < < < < < < < < | < | > > > > | > > > | 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 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 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 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 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 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 | size_t idx = 0; endwin(); /* 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)) { fcli_command_help(&cmd, true, true); exit(fcli_end_of_main(fnc_init.err)); } } /* Otherwise, output help/usage for all commands. */ fcli_command_help(fnc_init.cmd_args, true, false); fsl_fprintf(f, " note: %s " "with no args defaults to the timeline command.\n\n", fcli_progname()); exit(fcli_end_of_main(fnc_init.err)); } static void usage_timeline(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s timeline [-C|--no-colour] [-T tag] [-b branch] " "[-c commit] [-h|--help] [-n n] [-t type] [-u user] [-z|--utc] " "[path]\n" " e.g.: %s timeline --type ci -u jimmy src/frobnitz.c\n\n", fcli_progname(), fcli_progname()); } static void usage_diff(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s diff [-C|--no-colour] [-h|--help] [-i|--invert]" " [-q|--quiet] [-w|--whitespace] [-x|--context n] " "[artifact1 [artifact2]] [path ...]\n " "e.g.: %s diff --context 3 d34db33f c0ff33 src/*.c\n\n", fcli_progname(), fcli_progname()); } static void usage_tree(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s tree [-C|--no-colour] [-c commit] [-h|--help] [path]\n" " e.g.: %s tree -c d34dc0d3\n\n" , fcli_progname(), fcli_progname()); } static void usage_blame(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s blame [-C|--no-colour] [-c commit [-r]] [-h|--help] " "[-n n] path\n" " e.g.: %s blame -c d34db33f src/foo.c\n\n" , fcli_progname(), fcli_progname()); } static void usage_branch(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s branch [-C|--no-colour] [-a|--after date] " "[-b|--before date] [-c|--closed] [-h|--help] [-o|--open] " "[-p|--no-private] [-r|--reverse] [-s|--sort order] [glob]\n" " e.g.: %s branch -b 2020-10-10\n\n" , fcli_progname(), fcli_progname()); } static int cmd_diff(fcli_command const *argv) { fsl_cx *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 context = DIFF_DEF_CTXT, rc = 0; unsigned short blob = 0; enum fnc_diff_type diff_type; bool showmeta = false; rc = fcli_process_flags(argv->flags); if (rc || (rc = fcli_has_unused_flags(false))) return rc; TAILQ_INIT(&paths); /* * To provide an intuitive UI, use some magic. First, if there's an arg * and it's a symbolic checkin name, take as a checkin artifact. Repeat * for the next arg. If just one is a checkin, diff changes on disk * against it. If neither are checkins, diff changes on disk against the * current checkout. If both are checkins, diff against eachother. Treat * any non-symbol args as paths and try map to a valid repo path or F * card in the checkin(s) deck(s). It's tricky, but provides a smart UI: * fnc diff f1 f2 ... -> diff f{1,2,...} on disk against current ckout * fnc diff sym3 f1 -> diff f1 on disk against f1 found in checkin sym3 * fnc diff sym1 sym2 f1 f2 -> diff f{1,2} between checkins sym1 & sym2 */ if (!fsl_sym_to_rid(f, fcli_next_arg(false), FSL_SATYPE_ANY, &prid)) { artifact1 = fcli_next_arg(true); if (!fsl_rid_is_a_checkin(f, prid)) { if (fsl_rid_to_artifact_uuid(f, prid, FSL_SATYPE_ANY) != NULL) { rc = RC(FSL_RC_TYPE, "artifact [%s] not resolvable to a checkin", artifact1); goto end; } ++blob; } 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)) { if (fsl_rid_to_artifact_uuid(f, prid, FSL_SATYPE_ANY) != NULL) { rc = RC(FSL_RC_TYPE, "artifact [%s] " "not resolvable to a checkin", artifact2); goto end; } ++blob; } } } if (fcli_error()->code == FSL_RC_NOT_FOUND) { fcli_err_reset(); /* If args aren't symbols, treat as paths. */ rc = 0; } 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, "%s", "fsl_sym_to_rid"); goto end; } } if (!artifact2 && diff_type != FNC_DIFF_BLOB) { diff_type = FNC_DIFF_CKOUT; fsl_ckout_version_info(f, &rid, NULL); if ((rc = fsl_ckout_changes_scan(f))) return RC(rc, "%s", "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; while (path[0] == '/') ++path; if (rc) { if (rc != FSL_RC_NOT_FOUND || (!fsl_strcmp(artifact1, "current") && !artifact2)) goto end; rc = 0; fcli_err_reset(); /* 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_NOT_FOUND, "'%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; } } } path_to_diff = fsl_strdup(path); if (path_to_diff == NULL) { rc = RC(FSL_RC_ERROR, "%s", "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); if (rc) goto end; if (commit->prid == prid) showmeta = true; else { fsl_free(commit->puuid); commit->prid = prid; commit->puuid = fsl_rid_to_uuid(f, prid); } } else { commit = calloc(1, sizeof(*commit)); if (commit == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); goto end; } 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; if (fnc_init.context) { if ((rc = strtonumcheck(&context, fnc_init.context, INT_MIN, INT_MAX))) goto end; context = MIN(DIFF_MAX_CTXT, context); } view = view_open(0, 0, 0, 0, FNC_VIEW_DIFF); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } rc = open_diff_view(view, commit, context, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, NULL, showmeta, &paths); 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, struct commit_entry *entry, const char *path) { |
︙ | ︙ | |||
5255 5256 5257 5258 5259 5260 5261 | if (rc || (rc = fcli_has_unused_flags(false))) goto end; rc = map_repo_path(&path); if (rc) goto end; if (fnc_init.sym) | | | 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 | 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]", |
︙ | ︙ | |||
5280 5281 5282 5283 5284 5285 5286 | case FSL_RC_MISUSE: /* FALL THROUGH */ default: goto end; } } | > > > > > | | | 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 | case FSL_RC_MISUSE: /* FALL THROUGH */ default: goto end; } } /* In 'fnc tree -R repo.db [path]' case, use the latest checkin. */ if (rid == 0) { rc = fsl_sym_to_rid(f, "tip", FSL_SATYPE_CHECKIN, &rid); if (rc) goto end; } else if (!fsl_rid_is_a_checkin(f, rid)) { rc = RC(FSL_RC_TYPE, "%s tree requires check-in artifact", fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; |
︙ | ︙ | |||
5468 5469 5470 5471 5472 5473 5474 | return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); memset(ptr, 0, sizeof(struct fnc_repository_tree)); rc = fsl_deck_load_rid(fcli_cx(), &d, rid, FSL_SATYPE_CHECKIN); if (rc) return RC(rc, "fsl_deck_load_rid(%d) [%s]", rid, id); rc = fsl_deck_F_rewind(&d); | | > | > | | 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 | return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); memset(ptr, 0, sizeof(struct fnc_repository_tree)); rc = fsl_deck_load_rid(fcli_cx(), &d, rid, FSL_SATYPE_CHECKIN); if (rc) return RC(rc, "fsl_deck_load_rid(%d) [%s]", rid, id); rc = fsl_deck_F_rewind(&d); if (rc) goto end;; rc = fsl_deck_F_next(&d, &cf); if (rc) goto end;; while (cf) { char *filename = NULL, *uuid = NULL; fsl_time_t mtime; filename = fsl_strdup(cf->name); if (filename == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } uuid = fsl_strdup(cf->uuid); if (uuid == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } rc = fsl_mtime_of_F_card(f, rid, cf, &mtime); if (!rc) rc = link_tree_node(ptr, filename, uuid, mtime); fsl_free(filename); fsl_free(uuid); if (!rc) rc = fsl_deck_F_next(&d, &cf); if (rc) goto end; } |
︙ | ︙ | |||
5681 5682 5683 5684 5685 5686 5687 | /* Stat path for tree display features. */ rc = fsl_file_canonical_name2(f->ckout.dir, tn->path, &buf, false); if (rc) goto end; if (lstat(fsl_buffer_cstr(&buf), &s) == -1) { | > > > > | | | | > | | 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 | /* Stat path for tree display features. */ rc = fsl_file_canonical_name2(f->ckout.dir, tn->path, &buf, false); if (rc) goto end; if (lstat(fsl_buffer_cstr(&buf), &s) == -1) { if (errno == ENOENT) tn->mode = (!fsl_strcmp(tn->path, path) && tn->uuid) ? S_IFREG : S_IFDIR; else { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "lstat(%s)", fsl_buffer_cstr(&buf)); goto end; } } else tn->mode = s.st_mode; fsl_buffer_reuse(&buf); } while (parent_dir && parent_dir->parent_dir) { if (parent_dir->parent_dir->mtime < parent_dir->mtime) parent_dir->parent_dir->mtime = parent_dir->mtime; parent_dir = parent_dir->parent_dir; |
︙ | ︙ | |||
5812 5813 5814 5815 5816 5817 5818 | /* Write (highlighted) headline (if view is active in splitscreen). */ rc = formatln(&wcstr, &wstrlen, s->tree_label, view->ncols, 0); if (rc) return rc; if (screen_is_shared(view)) wstandout(view->window); if (s->colour) | | | 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 | /* Write (highlighted) headline (if view is active in splitscreen). */ rc = formatln(&wcstr, &wstrlen, s->tree_label, view->ncols, 0); if (rc) return rc; if (screen_is_shared(view)) wstandout(view->window); if (s->colour) c = get_colour(&s->colours, FNC_COMMIT_ID); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddwstr(view->window, wcstr); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (screen_is_shared(view)) wstandend(view->window); |
︙ | ︙ | |||
6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 | static int set_colours(fsl_list *s, enum fnc_view_id vid) { struct fnc_colour *colour; const char **regexp = NULL; const char *regexp_blame[] = {"^"}; const char *regexp_tree[] = {"@$", "/$", "\\*$", "^$"}; const char *regexp_diff[] = { "^((checkin|wiki|ticket|technote) " "[0-9a-f]|hash [+-] |\\[[+~>-]] |" | > | > > > | > > > > > | > > > > > > > | 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 | static int set_colours(fsl_list *s, enum fnc_view_id vid) { struct fnc_colour *colour; const char **regexp = NULL; const char *regexp_blame[] = {"^"}; const char *regexp_timeline[] = {"^$", "^$", "^$"}; const char *regexp_tree[] = {"@$", "/$", "\\*$", "^$"}; const char *regexp_diff[] = { "^((checkin|wiki|ticket|technote) " "[0-9a-f]|hash [+-] |\\[[+~>-]] |" "[+-]{3} )", "^user:", "^date:", "^tags:", "^-", "^\\+", "^@@" }; int pairs_diff[][2] = { {FNC_DIFF_META, COLOR_GREEN}, {FNC_USER_STR, COLOR_CYAN}, {FNC_DATE_STR, COLOR_YELLOW}, {FNC_TAGS_STR, COLOR_MAGENTA}, {FNC_DIFF_MINUS, COLOR_MAGENTA}, {FNC_DIFF_PLUS, COLOR_CYAN}, {FNC_DIFF_CHNK, COLOR_YELLOW} }; int pairs_tree[][2] = { {FNC_TREE_LINK, COLOR_MAGENTA}, {FNC_TREE_DIR, COLOR_CYAN}, {FNC_TREE_EXEC, COLOR_GREEN}, {FNC_COMMIT_ID, COLOR_GREEN} }; int pairs_timeline[][2] = { {FNC_COMMIT_ID, COLOR_GREEN}, {FNC_USER_STR, COLOR_CYAN}, {FNC_DATE_STR, COLOR_YELLOW} }; int pairs_blame[][2] = { {FNC_COMMIT_ID, COLOR_GREEN} }; int (*pairs)[2], rc = 0; fsl_size_t idx, n; switch (vid) { case FNC_VIEW_DIFF: n = nitems(regexp_diff); regexp = regexp_diff; pairs = pairs_diff; break; case FNC_VIEW_TREE: n = nitems(regexp_tree); regexp = regexp_tree; pairs = pairs_tree; break; case FNC_VIEW_TIMELINE: n = nitems(regexp_timeline); regexp = regexp_timeline; pairs = pairs_timeline; break; case FNC_VIEW_BLAME: n = nitems(regexp_blame); regexp = regexp_blame; pairs = pairs_blame; break; default: return RC(FSL_RC_TYPE, "%s", "invalid fnc_view_id"); |
︙ | ︙ | |||
6634 6635 6636 6637 6638 6639 6640 | cmd_blame(fcli_command const *argv) { fsl_cx *f = fcli_cx(); struct fnc_view *view; char *path = NULL; fsl_uuid_str commit_id = NULL; fsl_id_t tip = 0, rid = 0; | | | | > > > > > | > > > | 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 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 | cmd_blame(fcli_command const *argv) { fsl_cx *f = fcli_cx(); struct fnc_view *view; char *path = NULL; fsl_uuid_str commit_id = NULL; fsl_id_t tip = 0, rid = 0; int nlimit = 0, rc = 0; rc = fcli_process_flags(argv->flags); if (rc || (rc = fcli_has_unused_flags(false))) goto end; if (!fcli_next_arg(false)) { rc = RC(FSL_RC_MISSING_INFO, "%s blame requires versioned file path", fcli_progname()); goto end; } if (fnc_init.nrecords.zlimit) { char *n = (char *)fnc_init.nrecords.zlimit; bool timed; if (n[fsl_strlen(n) - 1] == 's') { n[fsl_strlen(n) - 1] = '\0'; timed = true; } if ((rc = strtonumcheck(&nlimit, n, INT_MIN, INT_MAX))) goto end; if (timed) nlimit *= -1; } if (fnc_init.sym || fnc_init.reverse) { if (fnc_init.reverse) { if (!fnc_init.sym) { rc = RC(FSL_RC_MISSING_INFO, "%s blame --reverse requires --commit", fcli_progname()); |
︙ | ︙ | |||
6692 6693 6694 6695 6696 6697 6698 | view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", view_open); goto end; } | | | | 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 | view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", view_open); goto end; } rc = open_blame_view(view, path, commit_id, tip, nlimit); 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) { struct fnc_blame_view_state *s = &view->state.blame; int rc = 0; CONCAT(STAILQ, _INIT)(&s->blamed_commits); s->path = fsl_strdup(path); |
︙ | ︙ | |||
6730 6731 6732 6733 6734 6735 6736 | 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; | | | 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 | 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(); if (s->colour) { rc = set_colours(&s->colours, FNC_VIEW_BLAME); if (rc) return rc; |
︙ | ︙ | |||
6801 6802 6803 6804 6805 6806 6807 6808 | } 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); opt->originRid = blame->origin; /* tip when -r is passed */ | > > > > > | | 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 | } 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; rc = fnc_dump_buffer_to_file(&blame->filesz, &blame->nlines, &blame->line_offsets, blame->f, &buf); if (rc) goto end; |
︙ | ︙ | |||
7115 7116 7117 7118 7119 7120 7121 | fsl_free(line); line = NULL; if (rc) return rc; if (screen_is_shared(view)) wstandout(view->window); if (s->colour) | | | 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637 7638 7639 | fsl_free(line); line = NULL; if (rc) return rc; if (screen_is_shared(view)) wstandout(view->window); if (s->colour) c = get_colour(&s->colours, FNC_COMMIT_ID); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddwstr(view->window, wcstr); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (screen_is_shared(view)) wstandend(view->window); |
︙ | ︙ | |||
7186 7187 7188 7189 7190 7191 7192 | idfield - 1); if (id_str == NULL) { fsl_free(line); return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); } if (s->colour) | | > | 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 | idfield - 1); if (id_str == NULL) { fsl_free(line); return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); } if (s->colour) c = get_colour(&s->colours, FNC_COMMIT_ID); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); wprintw(view->window, "%.*s", idfield - 1, id_str); if (c) wattr_off(view->window, |
︙ | ︙ | |||
7333 7334 7335 7336 7337 7338 7339 | "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) { | > > | < > > | < | | | > > > > > > > > > > | | | > > > > | 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 | "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, "%s", "fsl_deck_load_sym"); } rc = fsl_deck_F_rewind(&d); if (rc) { fsl_deck_finalize(&d); fsl_free(pid); return RC(rc, "%s", "fsl_deck_F_rewind"); } if (fsl_deck_F_search(&d, s->path + (fnc_init.sym ? 0 : 1)) == NULL) { char *m = fsl_mprintf("-- %s not in [%.12s] --", s->path + (fnc_init.sym ? 0 : 1), pid); if (m == NULL) rc = RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); wattr_on(view->window, A_BOLD, NULL); mvwaddstr(view->window, view->start_ln + view->nlines - 1, 0, m); wclrtoeol(view->window); wattr_off(view->window, A_BOLD, NULL); update_panels(); doupdate(); sleep(1); fsl_deck_finalize(&d); fsl_free(pid); fsl_free(m); 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); } |
︙ | ︙ | |||
7392 7393 7394 7395 7396 7397 7398 7399 | break; break; } case KEY_ENTER: case '\r': { fsl_cx *f = fcli_cx(); struct fnc_commit_artifact *commit = NULL; fsl_uuid_cstr id = NULL; | > < < < < < > | > | > | 7919 7920 7921 7922 7923 7924 7925 7926 7927 7928 7929 7930 7931 7932 7933 7934 7935 7936 7937 7938 7939 7940 7941 7942 7943 7944 7945 7946 7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 7961 | break; break; } case KEY_ENTER: case '\r': { fsl_cx *f = fcli_cx(); struct fnc_commit_artifact *commit = NULL; fsl_stmt *q = NULL; fsl_uuid_cstr id = NULL; id = get_selected_commit_id(s->blame.lines, s->blame.nlines, s->first_line_onscreen, s->selected_line); if (id == NULL) break; if (s->selected_commit) fnc_commit_artifact_close(s->selected_commit); if (rc) break; q = fsl_stmt_malloc(); rc = commit_builder(&commit, fsl_uuid_to_rid(f, id), q); fsl_stmt_finalize(q); if (rc) { fnc_commit_artifact_close(commit); break; } if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF); if (diff_view == NULL) { fnc_commit_artifact_close(commit); rc = RC(FSL_RC_ERROR, "%s", "view_open"); break; } rc = open_diff_view(diff_view, commit, DIFF_DEF_CTXT, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, NULL, true, NULL); s->selected_commit = commit; if (rc) { fnc_commit_artifact_close(commit); view_close(diff_view); break; } view->active = false; |
︙ | ︙ | |||
7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 | static void fnc_commit_qid_free(struct fnc_commit_qid *qid) { fsl_free(qid->id); fsl_free(qid); } static void fnc_show_version(void) { printf("%s %s\n", fcli_progname(), PRINT_VERSION); } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 8267 8268 8269 8270 8271 8272 8273 8274 8275 8276 8277 8278 8279 8280 8281 8282 8283 8284 8285 8286 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 8317 8318 8319 8320 8321 8322 8323 8324 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390 8391 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 8410 8411 8412 8413 8414 8415 8416 8417 8418 8419 8420 8421 8422 8423 8424 8425 8426 8427 8428 8429 8430 8431 8432 8433 8434 8435 8436 8437 8438 8439 8440 8441 8442 8443 8444 8445 8446 8447 8448 8449 8450 8451 8452 8453 8454 8455 8456 8457 8458 8459 8460 8461 8462 8463 8464 8465 8466 8467 8468 8469 8470 8471 8472 8473 8474 8475 8476 8477 8478 8479 8480 8481 8482 8483 8484 8485 8486 8487 8488 8489 8490 8491 8492 8493 8494 8495 8496 8497 8498 8499 8500 8501 8502 8503 8504 8505 8506 8507 8508 8509 8510 8511 8512 8513 8514 8515 8516 8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 8582 8583 8584 8585 8586 8587 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609 8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634 8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659 8660 8661 8662 8663 8664 8665 8666 8667 8668 8669 8670 8671 8672 8673 8674 8675 8676 8677 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 8691 8692 8693 8694 8695 8696 8697 8698 8699 8700 8701 8702 8703 8704 8705 8706 8707 8708 8709 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 8727 8728 8729 8730 8731 8732 8733 8734 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 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 8805 8806 8807 8808 8809 8810 8811 8812 8813 8814 8815 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899 8900 8901 8902 8903 8904 8905 8906 8907 8908 8909 8910 8911 8912 8913 8914 8915 8916 8917 8918 8919 8920 8921 8922 8923 8924 8925 8926 8927 8928 8929 8930 8931 8932 8933 8934 8935 8936 8937 8938 8939 8940 8941 8942 8943 8944 8945 8946 8947 8948 8949 8950 8951 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979 8980 8981 8982 8983 8984 8985 8986 8987 8988 8989 8990 8991 8992 8993 8994 8995 8996 8997 8998 8999 9000 9001 9002 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030 9031 9032 9033 9034 9035 9036 9037 9038 9039 9040 9041 9042 9043 9044 9045 9046 9047 9048 9049 9050 9051 9052 9053 9054 9055 9056 9057 9058 9059 9060 9061 9062 9063 9064 9065 9066 | static void fnc_commit_qid_free(struct fnc_commit_qid *qid) { fsl_free(qid->id); fsl_free(qid); } static int cmd_branch(fcli_command const *argv) { struct fnc_view *view; char *glob = NULL; double dateline; int branch_flags, rc = 0, when = 0; rc = fcli_process_flags(argv->flags); if (rc || (rc = fcli_has_unused_flags(false))) return rc; branch_flags = BRANCH_LS_OPEN_CLOSED; if (fnc_init.open && fnc_init.closed) return RC(FSL_RC_MISUSE, "%s", "--open and --close are mutually exclusive options"); else if (fnc_init.open) branch_flags = BRANCH_LS_OPEN_ONLY; else if (fnc_init.closed) branch_flags = BRANCH_LS_CLOSED_ONLY; if (fnc_init.sort) { if (!fsl_strcmp(fnc_init.sort, "mru")) FLAG_SET(branch_flags, BRANCH_SORT_MTIME); else if (!fsl_strcmp(fnc_init.sort, "state")) FLAG_SET(branch_flags, BRANCH_SORT_STATUS); else return RC(FSL_RC_MISUSE, "invalid sort order: %s", fnc_init.sort); } if (fnc_init.noprivate) FLAG_SET(branch_flags, BRANCH_LS_NO_PRIVATE); if (fnc_init.reverse) FLAG_SET(branch_flags, BRANCH_SORT_REVERSE); if (fnc_init.after && fnc_init.before) { return RC(FSL_RC_MISUSE, "%s", "--before and --after are mutually exclusive options"); } else if (fnc_init.after || fnc_init.before) { const char *d = NULL; d = fnc_init.after ? fnc_init.after : fnc_init.before; when = fnc_init.after ? 1 : -1; rc = fnc_date_to_mtime(&dateline, d, when); if (rc) return rc; } glob = fsl_strdup(fcli_next_arg(true)); init_curses(); view = view_open(0, 0, 0, 0, FNC_VIEW_BRANCH); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } rc = open_branch_view(view, branch_flags, glob, dateline, when); if (rc) goto end; rc = view_loop(view); end: fnc_free_branches(&view->state.branch); fsl_free(glob); return rc; } static int open_branch_view(struct fnc_view *view, int branch_flags, const char *glob, double dateline, int when) { struct fnc_branch_view_state *s = &view->state.branch; int rc = 0; s->selected_branch = 0; s->colour = !fnc_init.nocolour && has_colors(); s->branch_flags = branch_flags; s->branch_glob = glob; s->dateline = dateline; s->when = when; rc = fnc_load_branches(s); if (rc) return rc; if (s->colour) init_pair(FNC_COMMIT_ID, COLOR_GREEN, -1); view->show = show_branch_view; view->input = branch_input_handler; view->close = close_branch_view; view->search_init = branch_search_init; view->search_next = branch_search_next; return 0; } static int fnc_load_branches(struct fnc_branch_view_state *s) { fsl_cx *const f = fcli_cx(); fsl_buffer sql = fsl_buffer_empty; fsl_stmt *stmt = NULL; char *curr_branch = NULL; fsl_id_t ckoutrid; int rc = 0; rc = create_tmp_branchlist_table(); if (rc) goto end; TAILQ_INIT(&s->branches); s->nbranches = 0; switch (FLAG_CHK(s->branch_flags, BRANCH_LS_BITMASK)) { case BRANCH_LS_OPEN_CLOSED: rc = fsl_buffer_append(&sql, "SELECT name, isprivate, isclosed, mtime" " FROM tmp_brlist WHERE 1", -1); break; case BRANCH_LS_OPEN_ONLY: rc = fsl_buffer_append(&sql, "SELECT name, isprivate, isclosed, mtime" " FROM tmp_brlist WHERE NOT isclosed", -1); break; case BRANCH_LS_CLOSED_ONLY: rc = fsl_buffer_append(&sql, "SELECT name, isprivate, isclosed, mtime" " FROM tmp_brlist WHERE isclosed", -1); break; } if (rc) goto end; if (s->branch_glob) { char *like = fsl_mprintf("%%%%%s%%%%", s->branch_glob); rc = fsl_buffer_appendf(&sql, " AND name LIKE %Q", like); fsl_free(like); } if (FLAG_CHK(s->branch_flags, BRANCH_LS_NO_PRIVATE)) rc = fsl_buffer_append(&sql, " AND NOT isprivate", -1); if (FLAG_CHK(s->branch_flags, BRANCH_SORT_MTIME)) rc = fsl_buffer_append(&sql, " ORDER BY -mtime", -1); else if (FLAG_CHK(s->branch_flags, BRANCH_SORT_STATUS)) rc = fsl_buffer_append(&sql, " ORDER BY isclosed", -1); else rc = fsl_buffer_append(&sql, " ORDER BY name COLLATE nocase", -1); if (!rc && (FLAG_CHK(s->branch_flags, BRANCH_SORT_REVERSE))) rc = fsl_buffer_append(&sql," DESC", -1); stmt = fsl_stmt_malloc(); if (stmt == NULL) goto end; if (!rc) rc = fsl_cx_prepare(f, stmt, fsl_buffer_cstr(&sql)); fsl_ckout_version_info(f, &ckoutrid, NULL); curr_branch = fsl_db_g_text(fsl_needs_repo(f), NULL, "SELECT value FROM tagxref WHERE rid=%d AND tagid=%d", ckoutrid, 8); while (fsl_stmt_step(stmt) == FSL_RC_STEP_ROW) { struct fnc_branch *new_branch; struct fnc_branchlist_entry *be; const char *brname = fsl_stmt_g_text(stmt, 0, NULL); bool private = (curr_branch && fsl_stmt_g_int32(stmt, 1) == 1); bool open = fsl_stmt_g_int32(stmt, 2) == 0; double mtime = fsl_stmt_g_int64(stmt, 3); bool curr = curr_branch && !fsl_strcmp(curr_branch, brname); if ((s->when > 0 && mtime < s->dateline) || (s->when < 0 && mtime > s->dateline)) continue; alloc_branch(&new_branch, brname, mtime, open, private, curr); fnc_branchlist_insert(&be, &s->branches, new_branch); if (be) be->idx = s->nbranches++; } s->first_branch_onscreen = TAILQ_FIRST(&s->branches); if (!stmt->rowCount) rc = RC(FSL_RC_BREAK, "no matching records: %s", s->branch_glob); end: fsl_stmt_finalize(stmt); fsl_free(curr_branch); fsl_buffer_clear(&sql); return rc; } static int create_tmp_branchlist_table(void) { fsl_cx *const f = fcli_cx(); fsl_db *db = fsl_needs_ckout(f); static const char tmp_branchlist_table[] = "CREATE TEMP TABLE IF NOT EXISTS tmp_brlist AS " "SELECT tagxref.value AS name," " max(event.mtime) AS mtime," " EXISTS(SELECT 1 FROM tagxref AS tx WHERE tx.rid=tagxref.rid" " AND tx.tagid=(SELECT tagid FROM tag WHERE tagname='closed')" " AND tx.tagtype > 0) AS isclosed," " (SELECT tagxref.value FROM plink CROSS JOIN tagxref" " WHERE plink.pid=event.objid" " AND tagxref.rid=plink.cid" " AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='branch')" " AND tagtype>0) AS mergeto," " count(*) AS nckin," " (SELECT uuid FROM blob WHERE rid=tagxref.rid) AS ckin," " event.bgcolor AS bgclr," " EXISTS(SELECT 1 FROM private WHERE rid=tagxref.rid) AS isprivate " "FROM tagxref, tag, event " "WHERE tagxref.tagid=tag.tagid" " AND tagxref.tagtype>0" " AND tag.tagname='branch'" " AND event.objid=tagxref.rid " "GROUP BY 1;"; int rc = 0; if (!db) return RC(FSL_RC_NOT_A_CKOUT, "%s", "fsl_needs_ckout"); rc = fsl_db_exec(db, tmp_branchlist_table); return rc ? RC(fsl_cx_uplift_db_error2(f, db, rc), "%s", "fsl_db_exec") : rc; } static int alloc_branch(struct fnc_branch **branch, const char *name, double mtime, bool open, bool priv, bool curr) { fsl_uuid_str id = NULL; char iso8601[ISO8601_TIMESTAMP], *date = NULL; int rc = 0; *branch = calloc(1, sizeof(**branch)); if (*branch == NULL) return RC(FSL_RC_ERROR, "%s", "calloc"); rc = fsl_sym_to_uuid(fcli_cx(), name, FSL_SATYPE_ANY, &id, NULL); if (rc || id == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_sym_to_uuid"); fnc_branch_close(*branch); *branch = NULL; return rc; } fsl_julian_to_iso8601(mtime, iso8601, false); date = fsl_mprintf("%.*s", ISO8601_DATE_ONLY, iso8601); (*branch)->id = id; (*branch)->name = fsl_strdup(name); (*branch)->date = date; (*branch)->open = open; (*branch)->private = priv; (*branch)->current = curr; if ((*branch)->name == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); fnc_branch_close(*branch); *branch = NULL; } return rc; } static int fnc_branchlist_insert(struct fnc_branchlist_entry **newp, struct fnc_branchlist_head *branches, struct fnc_branch *branch) { struct fnc_branchlist_entry *new, *be; *newp = NULL; new = fsl_malloc(sizeof(*new)); if (new == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); new->branch = branch; *newp = new; be = TAILQ_LAST(branches, fnc_branchlist_head); if (!be) { /* Empty list; add first branch. */ TAILQ_INSERT_HEAD(branches, new, entries); return 0; } /* * Deduplicate (extremely unlikely or impossible?) entries on insert. * Don't force lexicographical order; we already retrieved the branch * names from the database using a query to obtain (a) lexicographical * or (b) user-specified sorted results (i.e., MRU or LRU). */ while (be) { if (!fsl_strcmp(be->branch->name, new->branch->name)) { /* Duplicate entry. */ fsl_free(new); *newp = NULL; return 0; } be = TAILQ_PREV(be, fnc_branchlist_head, entries); } /* No duplicates; add to end of list. */ TAILQ_INSERT_TAIL(branches, new, entries); return 0; } static int show_branch_view(struct fnc_view *view) { struct fnc_branch_view_state *s = &view->state.branch; struct fnc_branchlist_entry *be; char *line = NULL; wchar_t *wline; int limit, n, width, rc = 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, "%s", "fsl_mprintf"); rc = formatln(&wline, &width, line, view->ncols, 0); if (rc) { fsl_free(line); return rc; } if (screen_is_shared(view)) wstandout(view->window); waddwstr(view->window, wline); if (screen_is_shared(view)) wstandend(view->window); fsl_free(wline); wline = NULL; fsl_free(line); line = NULL; if (width < view->ncols - 1) waddch(view->window, '\n'); if (--limit <= 0) return rc; n = 0; while (be && limit > 0) { char *line = NULL; line = fsl_mprintf(" %c%s%s%s%s%s%s%c %s", be->branch->open ? '+' : '-', be->branch->name, be->branch->private ? "*" : "", be->branch->current ? "@" : "", s->show_id ? " [" : "", s->show_id ? be->branch->id : "", s->show_id ? "]" : "", s->show_date ? ':' : 0, s->show_date ? be->branch->date : 0); if (line == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); rc = formatln(&wline, &width, line, view->ncols, 0); if (rc) { fsl_free(line); return rc; } if (n == s->selected) { if (view->active) wattr_on(view->window, A_REVERSE, NULL); s->selected_branch = be; } if (s->colour) wattr_on(view->window, COLOR_PAIR(FNC_COMMIT_ID), NULL); waddwstr(view->window, wline); if (s->colour) wattr_off(view->window, COLOR_PAIR(FNC_COMMIT_ID), NULL); if (width < view->ncols - 1) waddch(view->window, '\n'); if (n == s->selected && view->active) wattr_off(view->window, A_REVERSE, NULL); fsl_free(line); fsl_free(wline); wline = NULL; ++n; ++s->ndisplayed; --limit; s->last_branch_onscreen = be; be = TAILQ_NEXT(be, entries); } draw_vborder(view); return rc; } static int branch_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch) { struct fnc_branch_view_state *s = &view->state.branch; struct fnc_view *timeline_view, *tree_view; struct fnc_branchlist_entry *be; int start_col = 0, n, rc = 0; switch (ch) { case 'c': s->colour = !s->colour; break; case 'd': s->show_date = !s->show_date; break; case 'i': s->show_id = !s->show_id; break; case KEY_ENTER: case '\r': case ' ': if (!s->selected_branch) break; if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); rc = tl_branch_entry(&timeline_view, start_col, s->selected_branch); view->active = false; timeline_view->active = true; if (view_is_parent(view)) { rc = view_close_child(view); if (rc) return rc; view_set_child(view, timeline_view); view->focus_child = true; } else *new_view = timeline_view; break; case 't': if (!s->selected_branch) break; if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); rc = browse_branch_tree(&tree_view, start_col, s->selected_branch); if (rc || tree_view == NULL) break; view->active = false; tree_view->active = true; if (view_is_parent(view)) { rc = view_close_child(view); if (rc) return rc; view_set_child(view, tree_view); view->focus_child = true; } else *new_view = tree_view; break; case 'g': if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: s->selected = 0; s->first_branch_onscreen = TAILQ_FIRST(&s->branches); break; case KEY_END: case 'G': s->selected = 0; be = TAILQ_LAST(&s->branches, fnc_branchlist_head); for (n = 0; n < view->nlines - 1; ++n) { if (be == NULL) break; s->first_branch_onscreen = be; be = TAILQ_PREV(be, fnc_branchlist_head, entries); } if (n > 0) s->selected = n - 1; break; case KEY_UP: case 'k': if (s->selected > 0) { --s->selected; break; } branch_scroll_up(s, 1); break; case KEY_DOWN: case 'j': if (s->selected < s->ndisplayed - 1) { ++s->selected; break; } if (TAILQ_NEXT(s->last_branch_onscreen, entries) == NULL) /* Reached last entry. */ break; branch_scroll_down(s, 1); break; case KEY_PPAGE: case CTRL('b'): if (s->first_branch_onscreen == TAILQ_FIRST(&s->branches)) s->selected = 0; branch_scroll_up(s, MAX(0, view->nlines - 1)); break; case KEY_NPAGE: case CTRL('f'): if (TAILQ_NEXT(s->last_branch_onscreen, entries) == NULL) { /* No more entries off-page; move cursor down. */ if (s->selected < s->ndisplayed - 1) s->selected = s->ndisplayed - 1; break; } branch_scroll_down(s, view->nlines - 1); break; case CTRL('l'): case 'R': fnc_free_branches(s); s->branch_glob = NULL; /* Shared pointer. */ s->when = 0; s->branch_flags = BRANCH_LS_OPEN_CLOSED; rc = fnc_load_branches(s); break; case KEY_RESIZE: if (view->nlines >= 2 && s->selected >= view->nlines - 1) s->selected = view->nlines - 2; break; default: break; } return rc; } static int tl_branch_entry(struct fnc_view **new_view, int start_col, struct fnc_branchlist_entry *be) { struct fnc_view *timeline_view; fsl_id_t rid; int rc = 0; *new_view = NULL; rid = fsl_uuid_to_rid(fcli_cx(), be->branch->id); if (rid < 0) return RC(rc, "%s", "fsl_uuid_to_rid"); timeline_view = view_open(0, 0, 0, start_col, FNC_VIEW_TIMELINE); if (timeline_view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } rc = open_timeline_view(timeline_view, rid, "/"); end: if (rc) view_close(timeline_view); else *new_view = timeline_view; return rc; } static int browse_branch_tree(struct fnc_view **new_view, int start_col, struct fnc_branchlist_entry *be) { struct fnc_view *tree_view; fsl_id_t rid; int rc = 0; *new_view = NULL; rid = fsl_uuid_to_rid(fcli_cx(), be->branch->id); if (rid < 0) return RC(rc, "%s", "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, "%s", "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) { struct fnc_branchlist_entry *be; int idx = 0; if (s->first_branch_onscreen == TAILQ_FIRST(&s->branches)) return; be = TAILQ_PREV(s->first_branch_onscreen, fnc_branchlist_head, entries); while (idx++ < maxscroll) { if (be == NULL) break; s->first_branch_onscreen = be; be = TAILQ_PREV(be, fnc_branchlist_head, entries); } } static void branch_scroll_down(struct fnc_branch_view_state *s, int maxscroll) { struct fnc_branchlist_entry *next, *last; int idx = 0; if (s->first_branch_onscreen) next = TAILQ_NEXT(s->first_branch_onscreen, entries); else next = TAILQ_FIRST(&s->branches); last = s->last_branch_onscreen; while (next && last && idx++ < maxscroll) { last = TAILQ_NEXT(last, entries); if (last) { s->first_branch_onscreen = next; next = TAILQ_NEXT(next, entries); } } } static int branch_search_init(struct fnc_view *view) { struct fnc_branch_view_state *s = &view->state.branch; s->matched_branch = NULL; return 0; } static int branch_search_next(struct fnc_view *view) { struct fnc_branch_view_state *s = &view->state.branch; struct fnc_branchlist_entry *be = NULL; if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (s->matched_branch) { if (view->searching == SEARCH_FORWARD) { if (s->selected_branch) be = TAILQ_NEXT(s->selected_branch, entries); else be = TAILQ_PREV(s->selected_branch, fnc_branchlist_head, entries); } else { if (s->selected_branch == NULL) be = TAILQ_LAST(&s->branches, fnc_branchlist_head); else be = TAILQ_PREV(s->selected_branch, fnc_branchlist_head, entries); } } else { if (view->searching == SEARCH_FORWARD) be = TAILQ_FIRST(&s->branches); else be = TAILQ_LAST(&s->branches, fnc_branchlist_head); } while (1) { if (be == NULL) { if (s->matched_branch == NULL) { view->search_status = SEARCH_CONTINUE; return 0; } if (view->searching == SEARCH_FORWARD) be = TAILQ_FIRST(&s->branches); else be = TAILQ_LAST(&s->branches, fnc_branchlist_head); } if (match_branchlist_entry(be, &view->regex)) { view->search_status = SEARCH_CONTINUE; s->matched_branch = be; break; } if (view->searching == SEARCH_FORWARD) be = TAILQ_NEXT(be, entries); else be = TAILQ_PREV(be, fnc_branchlist_head, entries); } if (s->matched_branch) { s->first_branch_onscreen = s->matched_branch; s->selected = 0; } return 0; } static int match_branchlist_entry(struct fnc_branchlist_entry *be, regex_t *regex) { regmatch_t regmatch; return regexec(regex, be->branch->name, 1, ®match, 0) == 0; } static int close_branch_view(struct fnc_view *view) { struct fnc_branch_view_state *s = &view->state.branch; fnc_free_branches(s); return 0; } static void fnc_free_branches(struct fnc_branch_view_state *s) { struct fnc_branchlist_entry *be; while (!TAILQ_EMPTY(&s->branches)) { be = TAILQ_FIRST(&s->branches); TAILQ_REMOVE(&s->branches, be, entries); fnc_branch_close(be->branch); fsl_free(be); } } static void fnc_branch_close(struct fnc_branch *branch) { fsl_free(branch->name); fsl_free(branch->date); fsl_free(branch->id); fsl_free(branch); } /* * Assign path to **inserted->path, with optional ->data assignment, and insert * in lexicographically sorted order into the doubly-linked list rooted at * *pathlist. If path is not unique, return without adding a duplicate entry. */ static int fnc_pathlist_insert(struct fnc_pathlist_entry **inserted, struct fnc_pathlist_head *pathlist, const char *path, void *data) { struct fnc_pathlist_entry *new, *pe; int rc = 0; if (inserted) *inserted = NULL; new = fsl_malloc(sizeof(*new)); if (new == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); new->path = path; new->pathlen = fsl_strlen(path); new->data = data; /* * Most likely, supplied paths will be sorted (e.g., fnc diff *.c), so * post-order traversal will be more efficient when inserting entries. */ pe = TAILQ_LAST(pathlist, fnc_pathlist_head); while (pe) { int cmp = fnc_path_cmp(pe->path, new->path, pe->pathlen, new->pathlen); if (cmp == 0) { fsl_free(new); /* Duplicate path; don't insert. */ return rc; } else if (cmp < 0) { TAILQ_INSERT_AFTER(pathlist, pe, new, entry); if (inserted) *inserted = new; return rc; } pe = TAILQ_PREV(pe, fnc_pathlist_head, entry); } TAILQ_INSERT_HEAD(pathlist, new, entry); if (inserted) *inserted = new; return rc; } static int fnc_path_cmp(const char *path1, const char *path2, size_t len1, size_t len2) { size_t minlen; size_t idx = 0; /* Trim any leading path separators. */ while (path1[0] == '/') { ++path1; --len1; } while (path2[0] == '/') { ++path2; --len2; } minlen = MIN(len1, len2); /* Skip common prefix. */ while (idx < minlen && path1[idx] == path2[idx]) ++idx; /* Are path lengths exactly equal (exluding path separators)? */ if (len1 == len2 && idx >= minlen) return 0; /* Trim any redundant trailing path seperators. */ while (path1[idx] == '/' && path1[idx + 1] == '/') ++path1; while (path2[idx] == '/' && path2[idx + 1] == '/') ++path2; /* Ignore trailing path separators. */ if (path1[idx] == '/' && path1[idx + 1] == '\0' && path2[idx] == '\0') return 0; if (path2[idx] == '/' && path2[idx + 1] == '\0' && path1[idx] == '\0') return 0; /* Order children in subdirectories directly after their parents. */ if (path1[idx] == '/' && path2[idx] == '\0') return 1; if (path2[idx] == '/' && path1[idx] == '\0') return -1; if (path1[idx] == '/' && path2[idx] != '\0') return -1; if (path2[idx] == '/' && path1[idx] != '\0') return 1; /* Character immediately after the common prefix determines order. */ return (unsigned char)path1[idx] < (unsigned char)path2[idx] ? -1 : 1; } static void fnc_pathlist_free(struct fnc_pathlist_head *pathlist) { struct fnc_pathlist_entry *pe; while ((pe = TAILQ_FIRST(pathlist)) != NULL) { TAILQ_REMOVE(pathlist, pe, entry); free(pe); } } static void fnc_show_version(void) { printf("%s %s\n", fcli_progname(), PRINT_VERSION); } |
︙ | ︙ | |||
7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 | return RC(FSL_RC_MISUSE, "<n> not a number: -n|--limit=%s [%s]", nstr, ptr); else if (ptr && *ptr != '\0') return RC(FSL_RC_MISUSE, "invalid char in <n>: -n|--limit=%s [%s]", nstr, ptr); *ret = n; return 0; } static char * fnc_strsep(char **ptr, const char *sep) { char *s, *token; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 9081 9082 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 9115 9116 9117 9118 9119 9120 9121 9122 9123 9124 9125 9126 9127 9128 9129 9130 9131 9132 9133 9134 9135 9136 9137 9138 | return RC(FSL_RC_MISUSE, "<n> not a number: -n|--limit=%s [%s]", nstr, ptr); else if (ptr && *ptr != '\0') return RC(FSL_RC_MISUSE, "invalid char in <n>: -n|--limit=%s [%s]", nstr, ptr); *ret = n; return 0; } /* * 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 * success, use when to determine which time component to add to the date (i.e., * 1 sec before or after midnight), and convert to an mtime suitable for * comparisons with repository mtime fields and assign to *ret. Upon failure, * the error state will be updated with an appropriate error message and code. */ static int fnc_date_to_mtime(double *ret, const char *d, int when) { struct tm t = {0, 0, 0, 0, 0, 0}; char iso8601[ISO8601_TIMESTAMP]; /* Fill the tm structure. */ if (strptime(d, "%Y-%m-%d", &t) == NULL) { /* If not YYYY-MM-DD, try MM/DD/YYYY and DD/MM/YYYY. */ if (strptime(d, "%D", &t) != NULL) { /* If MM/DD/YYYY, check if it could be DD/MM/YYYY too */ if (strptime(d, "%d/%m/%Y", &t) != NULL) return RC(FSL_RC_AMBIGUOUS, "ambiguous date [%s]", d); } else if (strptime(d, "%d/%m/%Y", &t) != NULL) { /* If DD/MM/YYYY, check if it could be MM/DD/YYYY too */ if (strptime(d, "%D", &t) != NULL) return RC(FSL_RC_AMBIGUOUS, "ambiguous date [%s]", d); } else return RC(FSL_RC_TYPE, "unable to parse date: %s", d); } /* Format tm into ISO8601 string then convert to mtime. */ if (when > 0) /* After date d. */ strftime(iso8601, ISO8601_TIMESTAMP, "%FT23:59:59", &t); else /* Before date d. */ strftime(iso8601, ISO8601_TIMESTAMP, "%FT00:00:01", &t); if (!fsl_iso8601_to_julian(iso8601, ret)) return RC(FSL_RC_ERROR, "fsl_iso8601_to_julian(%s)", iso8601); return 0; } static char * fnc_strsep(char **ptr, const char *sep) { char *s, *token; |
︙ | ︙ |
Deleted signify/fnc-02-release.pub.
|
| < < |
Added signify/fnc-04-release.pub.
> > | 1 2 | untrusted comment: fnc 0.4 public key RWRL2v5dIJ1toZNK6Y5jlEkT5uGlolQ2pS8tf2EilbX5eB6DQRYjl6AS |
Changes to src/libfossil.c.
1 | #include "libfossil.h" | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 | #include "libfossil.h" /* start of file 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 |
︙ | ︙ | |||
177 178 179 180 181 182 183 184 185 186 187 188 189 190 | fsl_confirm_response_empty_m; const fsl_error fsl_error_empty = fsl_error_empty_m; const fsl_checkin_queue_opt fsl_checkin_queue_opt_empty = fsl_checkin_queue_opt_empty_m; const fsl_fstat fsl_fstat_empty = fsl_fstat_empty_m; const fsl_list fsl_list_empty = fsl_list_empty_m; const fsl_mcache fsl_mcache_empty = fsl_mcache_empty_m; const fsl_outputer fsl_outputer_FILE = fsl_outputer_FILE_m; const fsl_outputer fsl_outputer_empty = fsl_outputer_empty_m; const fsl_pathfinder fsl_pathfinder_empty = fsl_pathfinder_empty_m; const fsl_pq fsl_pq_empty = fsl_pq_empty_m; const fsl_repo_create_opt fsl_repo_create_opt_empty = fsl_repo_create_opt_empty_m; const fsl_repo_extract_opt fsl_repo_extract_opt_empty = | > | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | fsl_confirm_response_empty_m; const fsl_error fsl_error_empty = fsl_error_empty_m; const fsl_checkin_queue_opt fsl_checkin_queue_opt_empty = fsl_checkin_queue_opt_empty_m; const fsl_fstat fsl_fstat_empty = fsl_fstat_empty_m; const fsl_list fsl_list_empty = fsl_list_empty_m; const fsl_mcache fsl_mcache_empty = fsl_mcache_empty_m; const fsl_merge_opt fsl_merge_opt_empty = fsl_merge_opt_empty_m; const fsl_outputer fsl_outputer_FILE = fsl_outputer_FILE_m; const fsl_outputer fsl_outputer_empty = fsl_outputer_empty_m; const fsl_pathfinder fsl_pathfinder_empty = fsl_pathfinder_empty_m; const fsl_pq fsl_pq_empty = fsl_pq_empty_m; const fsl_repo_create_opt fsl_repo_create_opt_empty = fsl_repo_create_opt_empty_m; const fsl_repo_extract_opt fsl_repo_extract_opt_empty = |
︙ | ︙ | |||
266 267 268 269 270 271 272 | case FSL_STRLEN_SHA1: case FSL_STRLEN_K256: return x; default: return 0; } } | | | | | | | 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 | case FSL_STRLEN_SHA1: case FSL_STRLEN_K256: return x; default: return 0; } } void fsl_error_clear( fsl_error * const err ){ if(err){ fsl_buffer_clear(&err->msg); *err = fsl_error_empty; } } void fsl_error_reset( fsl_error * const err ){ if(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 || (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(!err) return FSL_RC_MISUSE; else if(!code){ /* clear error state */ err->code = 0; err->msg.used = err->msg.cursor = 0; if(err->msg.mem){ err->msg.mem[0] = 0; |
︙ | ︙ | |||
327 328 329 330 331 332 333 | : fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1); if(rc) err->code = rc; } return rc ? rc : code; } } | | | > | 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 | : 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); rc = fsl_error_setv(err, code, fmt, args); va_end(args); return rc; } int fsl_error_get( fsl_error const * const err, char const ** str, fsl_size_t * const len ){ if(!err) return FSL_RC_MISUSE; else{ if(str) *str = err->msg.used ? (char const *)err->msg.mem : NULL; if(len) *len = err->msg.used; return err->code; |
︙ | ︙ | |||
885 886 887 888 889 890 891 | } #endif #endif /* FSL_CONFIG_ENABLE_TIMER */ } | | | | | | 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 | } #endif #endif /* FSL_CONFIG_ENABLE_TIMER */ } void fsl_timer_start(fsl_timer_state * const ft){ fsl_cpu_times( &ft->user, &ft->system ); } uint64_t fsl_timer_fetch(fsl_timer_state const * const t){ uint64_t eu = 0, es = 0; fsl_cpu_times( &eu, &es ); return (eu - t->user) + (es - t->system); } uint64_t fsl_timer_reset(fsl_timer_state * const t){ uint64_t const rc = fsl_timer_fetch(t); fsl_cpu_times( &t->user, &t->system ); return rc; } uint64_t fsl_timer_stop(fsl_timer_state * const t){ uint64_t const rc = fsl_timer_fetch(t); *t = fsl_timer_state_empty; return rc; } unsigned int fsl_rgb_encode( int r, int g, int b ){ return (unsigned int)(((r&0xFF)<<16) + ((g&0xFF)<<8) + (b&0xFF)); |
︙ | ︙ | |||
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 | struct AnnVers { char *zFUuid; /* File being analyzed */ char *zMUuid; /* Check-in containing the file */ char *zUser; /* Name of user who did the check-in */ double mtime; /* [event].[mtime] db entry */ } *aVers; /* For each check-in analyzed */ unsigned int naVers; /* # of entries allocated in this->aVers */ }; static const Annotator Annotator_empty = { fsl_diff_cx_empty_m, fsl_buffer_empty_m/*headVersion*/, NULL/*aOrig*/, 0U/*nOrig*/, 0U/*nVers*/, false/*bMoreToDo*/, 0/*origId*/, 0/*showId*/, NULL/*aVers*/, | > | > | 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 | struct AnnVers { char *zFUuid; /* File being analyzed */ char *zMUuid; /* Check-in containing the file */ char *zUser; /* Name of user who did the check-in */ double mtime; /* [event].[mtime] db entry */ } *aVers; /* For each check-in analyzed */ unsigned int naVers; /* # of entries allocated in this->aVers */ fsl_timer_state timer; }; static const Annotator Annotator_empty = { fsl_diff_cx_empty_m, fsl_buffer_empty_m/*headVersion*/, NULL/*aOrig*/, 0U/*nOrig*/, 0U/*nVers*/, false/*bMoreToDo*/, 0/*origId*/, 0/*showId*/, NULL/*aVers*/, 0U/*naVerse*/, fsl_timer_state_empty_m }; static void fsl__annotator_clean(Annotator * const a){ unsigned i; fsl__diff_cx_clean(&a->c); for(i = 0; i < a->nVers; ++i){ fsl_free(a->aVers[i].zFUuid); |
︙ | ︙ | |||
1312 1313 1314 1315 1316 1317 1318 | fsl_annotate_opt const * const opt){ int rc = FSL_RC_NYI; fsl_buffer step = fsl_buffer_empty /*previous revision*/; fsl_id_t cid = 0, fnid = 0; // , rid = 0; fsl_stmt q = fsl_stmt_empty; bool openedTransaction = false; fsl_db * const db = fsl_needs_repo(f); | < | 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 | fsl_annotate_opt const * const opt){ int rc = FSL_RC_NYI; fsl_buffer step = fsl_buffer_empty /*previous revision*/; fsl_id_t cid = 0, fnid = 0; // , rid = 0; fsl_stmt q = fsl_stmt_empty; bool openedTransaction = false; fsl_db * const db = fsl_needs_repo(f); if(!db) return FSL_RC_NOT_A_REPO; rc = fsl_cx_transaction_begin(f); if(rc) goto dberr; openedTransaction = true; fnid = fsl_db_g_id(db, 0, "SELECT fnid FROM filename WHERE name=%Q %s", |
︙ | ︙ | |||
1363 1364 1365 1366 1367 1368 1369 | " ORDER BY ancestor.generation;", fnid ); if(rc) goto dberr; while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ if(a->nVers>=3){ | | < | | > > > > | 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 | " ORDER BY ancestor.generation;", fnid ); if(rc) goto dberr; while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ if(a->nVers>=3){ /* Process at least 3 rows before imposing any limit. That is historical behaviour inherited from fossil(1). */ if(opt->limitMs>0 && fsl_timer_fetch(&a->timer)/1000 >= opt->limitMs){ a->bMoreToDo = true; break; }else if(opt->limitVersions>0 && a->nVers>=opt->limitVersions){ a->bMoreToDo = true; break; } } char * zTmp = 0; char const * zCol = 0; fsl_size_t nCol = 0; |
︙ | ︙ | |||
1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 | int rc; Annotator ann = Annotator_empty; unsigned int i; fsl_buffer * const scratch = fsl_cx_scratchpad(f); fsl_annotate_step aStep; assert(opt->out); rc = fsl__annotate_file(f, &ann, opt); if(rc) goto end; memset(&aStep,0,sizeof(fsl_annotate_step)); if(opt->dumpVersions){ struct AnnVers *av; for(av = ann.aVers, i = 0; | > | 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 | 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; for(av = ann.aVers, i = 0; |
︙ | ︙ | |||
1686 1687 1688 1689 1690 1691 1692 | etCHARX = 9, /* Characters. %c */ /* The rest are extensions, not normally found in printf() */ etCHARLIT = 10, /* Literal characters. %' */ #if !FSLPRINTF_OMIT_SQL etSQLESCAPE = 11, /* Strings with '\'' doubled. %q */ etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ | > | | 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 | etCHARX = 9, /* Characters. %c */ /* The rest are extensions, not normally found in printf() */ etCHARLIT = 10, /* Literal characters. %' */ #if !FSLPRINTF_OMIT_SQL etSQLESCAPE = 11, /* Strings with '\'' doubled. %q */ etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ etSQLESCAPE3 = 14, /* %!Q -> identifiers wrapped in \" with inner '\"' doubled */ etBLOBSQL = 13, /* %B -> Works like %Q, but requires a (fsl_buffer*) argument. */ #endif /* !FSLPRINTF_OMIT_SQL */ etPOINTER = 15, /* The %p conversion */ etORDINAL = 17, /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ #if ! FSLPRINTF_OMIT_HTML etHTML = 18, /* %h -> basic HTML escaping. */ |
︙ | ︙ | |||
1854 1855 1856 1857 1858 1859 1860 | {'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, #endif {'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0}, {'s'/*115*/, 0, FLAG_STRING, etSTRING, 0, 0 }, {'t'/*116*/, 0, FLAG_STRING, etURLENCODE, 0, 0 }, {'u'/*117*/, 10, 0, etRADIX, 0, 0 }, {'v'/*118*/, 0, 0, 0, 0, 0 }, | | > | 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 | {'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, #endif {'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0}, {'s'/*115*/, 0, FLAG_STRING, etSTRING, 0, 0 }, {'t'/*116*/, 0, FLAG_STRING, etURLENCODE, 0, 0 }, {'u'/*117*/, 10, 0, etRADIX, 0, 0 }, {'v'/*118*/, 0, 0, 0, 0, 0 }, #if 1 || FSLPRINTF_OMIT_SQL {'w'/*119*/, 0, 0, 0, 0, 0 }, #else /* This role is filled by %!Q. %w is not currently used/documented. */ {'w'/*119*/, 0, FLAG_STRING, etSQLESCAPE3, 0, 0 }, #endif {'x'/*120*/, 16, 0, etRADIX, 16, 1 }, {'y'/*121*/, 0, 0, 0, 0, 0 }, {'z'/*122*/, 0, FLAG_STRING, etDYNSTRING, 0, 0}, {'{'/*123*/, 0, 0, 0, 0, 0 }, {'|'/*124*/, 0, 0, 0, 0, 0 }, |
︙ | ︙ | |||
2159 2160 2161 2162 2163 2164 2165 | } #endif /* !FSLPRINTF_OMIT_HTML */ #if !FSLPRINTF_OMIT_SQL /** | | < | | | | | | | > > | < | | < < | > > | | | < < < < < < | | | | < < | | < | < | < < < | | < < < < | < < > | | 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 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 | } #endif /* !FSLPRINTF_OMIT_HTML */ #if !FSLPRINTF_OMIT_SQL /** Quotes the (char *) varg as an SQL string 'should' be quoted. The exact type of the conversion is specified by xtype, which must be one of etSQLESCAPE, etSQLESCAPE2, etSQLESCAPE3. Search this file for those constants to find the associated documentation. */ static int spech_sqlstring( int xtype, fsl_output_f pf, void * pfArg, unsigned int pfLen, void * varg ){ enum { BufLen = 512 }; char buf[BufLen]; unsigned int i = 0, j = 0; int ch; char const q = xtype==etSQLESCAPE3 ?'"':'\''; /* Quote character */ char const * escarg = (char const *) varg; bool const isnull = escarg==0; bool const needQuote = !isnull && (xtype==etSQLESCAPE2 || xtype==etSQLESCAPE3); if( isnull ){ escarg = (xtype==etSQLESCAPE2||xtype==etSQLESCAPE3) ? "NULL" : "(NULL)"; } if( needQuote ) buf[j++] = q; for(i=0; (ch=escarg[i])!=0 && i<pfLen; ++i){ buf[j++] = ch; if( ch==q ) buf[j++] = ch; if(j+2>=BufLen){ int const rc = pf( pfArg, &buf[0], j ); if(rc) return rc; j = 0; } } if( needQuote ) buf[j++] = q; buf[j] = 0; return j>0 ? pf( pfArg, &buf[0], j ) : 0; } #endif /* !FSLPRINTF_OMIT_SQL */ #if FSLPRINTF_ENABLE_JSON /* TODO? Move these UTF8 bits into the public API? */ /* |
︙ | ︙ | |||
3126 3127 3128 3129 3130 3131 3132 | } #endif /* FSLPRINTF_OMIT_HTML */ #if ! FSLPRINTF_OMIT_SQL case etBLOBSQL: case etSQLESCAPE: case etSQLESCAPE2: case etSQLESCAPE3: { | < | < | < < < > | | | > | | | | | 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 | } #endif /* FSLPRINTF_OMIT_HTML */ #if ! FSLPRINTF_OMIT_SQL case etBLOBSQL: case etSQLESCAPE: case etSQLESCAPE2: case etSQLESCAPE3: { if(flag_altform2 && etSQLESCAPE2==xtype){ xtype = etSQLESCAPE3; } if(etBLOBSQL==xtype){ fsl_buffer * const b = va_arg(ap,fsl_buffer*); bufpt = b ? fsl_buffer_str(b) : NULL; length = b ? (int)fsl_buffer_size(b) : 0; if(flag_altform2) xtype = etSQLESCAPE3; }else{ bufpt = va_arg(ap,char*); length = bufpt ? (int)strlen(bufpt) : 0; } pfrc = spech_sqlstring( xtype, pfAppend, pfAppendArg, (precision<length) ? precision : length, bufpt ); FSLPRINTF_CHECKERR; length = 0; } #endif /* !FSLPRINTF_OMIT_SQL */ }/* End switch over the format type */ /* The text of the conversion is pointed to by "bufpt" and is |
︙ | ︙ | |||
3441 3442 3443 3444 3445 3446 3447 | This file houses the code for the fsl_id_bag class. */ #include <assert.h> #include <string.h> /* memset() */ const fsl_id_bag fsl_id_bag_empty = fsl_id_bag_empty_m; | < < < | | | | | > > > | | | | | 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 | This file houses the code for the fsl_id_bag class. */ #include <assert.h> #include <string.h> /* memset() */ const fsl_id_bag fsl_id_bag_empty = fsl_id_bag_empty_m; void fsl_id_bag_clear(fsl_id_bag * const p){ fsl_free(p->list); *p = fsl_id_bag_empty; } /* The hash function */ #define fsl_id_bag_hash(i) (i*101) /* Change the size of the hash table on a bag so that it contains N slots Completely reconstruct the hash table from scratch. Deleted entries (indicated by a -1) are removed. When finished, p->entryCount==p->used and p->capacity==newSize. Returns on on success, FSL_RC_OOM on allocation error. */ static int fsl_id_bag_resize(fsl_id_bag * const p, fsl_size_t newSize){ fsl_size_t i; fsl_id_bag old; fsl_size_t nDel = 0; /* Number of deleted entries */ fsl_size_t nLive = 0; /* Number of live entries */ fsl_id_t * newList; assert( newSize > p->entryCount ); newList = (fsl_id_t*)fsl_malloc( sizeof(p->list[0])*newSize ); if(!newList) return FSL_RC_OOM; old = *p; p->list = newList; p->capacity = newSize; memset(p->list, 0, sizeof(p->list[0])*newSize ); for(i=0; i<old.capacity; i++){ fsl_id_t const e = old.list[i]; if( e>0 ){ unsigned h = fsl_id_bag_hash(e)%newSize; while( p->list[h] ){ h++; if( h==newSize ) h = 0; } p->list[h] = e; nLive++; }else if( e<0 ){ nDel++; } } assert( p->entryCount == nLive ); assert( p->used == nLive+nDel ); p->used = p->entryCount; fsl_id_bag_clear(&old); return 0; } void fsl_id_bag_reset(fsl_id_bag * const p){ p->entryCount = p->used = 0; if(p->list){ memset(p->list, 0, sizeof(p->list[0])*p->capacity); } } int fsl_id_bag_insert(fsl_id_bag * const p, fsl_id_t e){ fsl_size_t h; int rc = 0; assert( e>0 ); if( p->used+1 >= p->capacity/2 ){ fsl_size_t const n = p->capacity ? p->capacity*2 : 30; rc = fsl_id_bag_resize(p, n ); if(rc) return rc; } h = fsl_id_bag_hash(e)%p->capacity; while( p->list[h]>0 && p->list[h]!=e ){ h++; if( h>=p->capacity ) h = 0; } if( p->list[h]<=0 ){ if( p->list[h]==0 ) ++p->used; p->list[h] = e; ++p->entryCount; rc = 0; } return rc; } bool fsl_id_bag_contains(fsl_id_bag const * const p, fsl_id_t e){ fsl_size_t h; assert( e>0 ); if( p->capacity==0 || 0==p->used ){ return false; } assert(p->list); h = fsl_id_bag_hash(e)%p->capacity; while( p->list[h] && p->list[h]!=e ){ h++; if( h>=p->capacity ) h = 0 /*loop around to the start*/ ; } return p->list[h]==e; } bool fsl_id_bag_remove(fsl_id_bag * const p, fsl_id_t e){ fsl_size_t h; bool rv = false; assert( e>0 ); if( !p->capacity || !p->used ) return rv; assert(p->list); h = fsl_id_bag_hash(e)%p->capacity; while( p->list[h] && p->list[h]!=e ){ |
︙ | ︙ | |||
3576 3577 3578 3579 3580 3581 3582 | fsl_id_bag_resize(p, p->capacity/2) /* ignore realloc error and keep the old size. */; } } return rv; } | | | | | | 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 | fsl_id_bag_resize(p, p->capacity/2) /* ignore realloc error and keep the old size. */; } } return rv; } fsl_id_t fsl_id_bag_first(fsl_id_bag const * const p){ if( p->capacity==0 || 0==p->used ){ return 0; }else{ fsl_size_t i; for(i=0; i<p->capacity && p->list[i]<=0; ++i){} if( i<p->capacity ){ return p->list[i]; }else{ return 0; } } } fsl_id_t fsl_id_bag_next(fsl_id_bag const * const p, fsl_id_t e){ fsl_size_t h; assert( p->capacity>0 ); assert( e>0 ); assert(p->list); h = fsl_id_bag_hash(e)%p->capacity; while( p->list[h] && p->list[h]!=e ){ ++h; if( h>=p->capacity ) h = 0; } assert( p->list[h] ); h++; while( h<p->capacity && p->list[h]<=0 ){ h++; } return h<p->capacity ? p->list[h] : 0; } fsl_size_t fsl_id_bag_count(fsl_id_bag const * const p){ return p->entryCount; } void fsl_id_bag_swap(fsl_id_bag * const lhs, fsl_id_bag * const rhs){ fsl_id_bag x = *lhs; *lhs = *rhs; *rhs = x; } #undef fsl_id_bag_hash /* end of file bag.c */ |
︙ | ︙ | |||
3754 3755 3756 3757 3758 3759 3760 | int rc = 0; assert(b->capacity ? !!b->mem : !b->mem); assert(b->used <= b->capacity); if(len<0){ len = (fsl_int_t)fsl_strlen((char const *)data); } sz += len + 1/*NUL*/; | | | 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 | int rc = 0; assert(b->capacity ? !!b->mem : !b->mem); assert(b->used <= b->capacity); if(len<0){ len = (fsl_int_t)fsl_strlen((char const *)data); } sz += len + 1/*NUL*/; 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; |
︙ | ︙ | |||
4024 4025 4026 4027 4028 4029 4030 | }else{ fsl_buffer_reserve(&temp, 0); } return rc; } | | > | 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 | }else{ fsl_buffer_reserve(&temp, 0); } 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; |
︙ | ︙ | |||
4060 4061 4062 4063 4064 4065 4066 | if( !rc && dest->used ){ assert( dest->used < dest->capacity ); dest->mem[dest->used] = 0; } return rc; } | | | < | | | < | | | | | | | | | | | | | | | | | | < | 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 | if( !rc && dest->used ){ assert( dest->used < dest->capacity ); dest->mem[dest->used] = 0; } return rc; } int fsl_buffer_fill_from_FILE( fsl_buffer * const dest, 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; } |
︙ | ︙ | |||
4202 4203 4204 4205 4206 4207 4208 | 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; } | | > | | | | 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 | 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 len = fsl_strftime(buf, BufSize, format, timeptr); if(!len) return FSL_RC_RANGE; return fsl_buffer_append(b, buf, (fsl_int_t)len); } } 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 ){ |
︙ | ︙ | |||
4241 4242 4243 4244 4245 4246 4247 | if(!rc){ pFrom->cursor = i; } return rc; } | | > > | 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 | if(!rc){ pFrom->cursor = i; } return rc; } 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; |
︙ | ︙ | |||
4327 4328 4329 4330 4331 4332 4333 | } fsl_buffer_clear(&fc); #endif return rc; } } | | | > | 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 | } fsl_buffer_clear(&fc); #endif return rc; } } char * fsl_buffer_take(fsl_buffer * const b){ char * 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 |
︙ | ︙ | |||
4361 4362 4363 4364 4365 4366 4367 | } if(!b->used || c<0) b->cursor = 0; else if((fsl_size_t)c > b->used) b->cursor = b->used; else b->cursor = (fsl_size_t)c; return b->cursor; } | | | | 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 | } if(!b->used || c<0) b->cursor = 0; else if((fsl_size_t)c > b->used) b->cursor = b->used; else b->cursor = (fsl_size_t)c; return b->cursor; } fsl_size_t fsl_buffer_tell(fsl_buffer const * const b){ 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); |
︙ | ︙ | |||
5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 | static char const * errNoFilesMsg = "No files have changed. Cowardly refusing to commit."; static int const errNoFilesRc = FSL_RC_NOOP; fsl_deck * pBase = NULL /* baseline for delta generation purposes */; fsl_size_t szD = 0, szB = 0 /* see commentary below */; if(basedOnVid && deltaPolicy!=0){ /* Figure out a baseline for a delta manifest... */ rc = fsl_deck_load_rid(f, &dBase, basedOnVid, FSL_SATYPE_CHECKIN); RC; if(dBase.B.uuid){ /* dBase is a delta. Let's use its baseline for manifest | > | < | | | > > > > > > > > > | < > > | > > | 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 | static char const * errNoFilesMsg = "No files have changed. Cowardly refusing to commit."; static int const errNoFilesRc = FSL_RC_NOOP; fsl_deck * pBase = NULL /* baseline for delta generation purposes */; fsl_size_t szD = 0, szB = 0 /* see commentary below */; if(basedOnVid && deltaPolicy!=0){ /* Figure out a baseline for a delta manifest... */ fsl_uuid_str bUuid = NULL /* UUID for d's B-card */; rc = fsl_deck_load_rid(f, &dBase, basedOnVid, FSL_SATYPE_CHECKIN); RC; if(dBase.B.uuid){ /* dBase is a delta. Let's use its baseline for manifest generation. */ fsl_id_t const baseRid = fsl_uuid_to_rid(f, dBase.B.uuid); fsl_deck_finalize(&dBase); if(baseRid>0){ rc = fsl_deck_load_rid(f, &dBase, baseRid, FSL_SATYPE_CHECKIN); }else{ rc = fsl_cx_err_get(f, NULL, NULL); assert(0!=rc); } RC; }else{ /* dBase version is a suitable baseline. */ bUuid = fsl_rid_to_uuid(f, basedOnVid); if(!bUuid){ assert(f->error.code); rc = f->error.code; RC; } } /* MARKER(("Baseline = %d / %s\n", (int)pBase->rid, pBase->uuid)); */ assert(dBase.B.uuid || bUuid); rc = fsl_deck_B_set(d, dBase.B.uuid ? dBase.B.uuid : bUuid); fsl_free(bUuid); RC; pBase = &dBase; } rc = fsl_checkin_calc_F_cards2(f, d, pBase, basedOnVid, &szD, opt); /*MARKER(("szD=%d\n", (int)szD));*/ RC; if(basedOnVid && !szD){ rc = fsl_cx_err_set(f, errNoFilesRc, errNoFilesMsg); |
︙ | ︙ | |||
5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 | RC; } end: #undef RC fsl_stmt_finalize(&q); fsl_deck_finalize(&dBase); d->B.baseline = NULL /* if it was set, it was &dBase */; if(rc && !f->error.code){ if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR); else if(dbC->error.code) fsl_cx_uplift_db_error(f, dbC); else if(f->dbMain->error.code) fsl_cx_uplift_db_error(f, f->dbMain); } return rc; | > | 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 | RC; } end: #undef RC fsl_stmt_finalize(&q); fsl_deck_finalize(&dBase); assert(NULL==d->B.baseline || &dBase==d->B.baseline); d->B.baseline = NULL /* if it was set, it was &dBase */; if(rc && !f->error.code){ if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR); else if(dbC->error.code) fsl_cx_uplift_db_error(f, dbC); else if(f->dbMain->error.code) fsl_cx_uplift_db_error(f, f->dbMain); } return rc; |
︙ | ︙ | |||
5706 5707 5708 5709 5710 5711 5712 | } if(opt->calcRCard) f->flags |= FSL_CX_F_CALC_R_CARD; else f->flags &= ~FSL_CX_F_CALC_R_CARD; rc = fsl_deck_save( d, opt->isPrivate ); RC; assert(d->rid>0); | < | | 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 | } if(opt->calcRCard) f->flags |= FSL_CX_F_CALC_R_CARD; else f->flags &= ~FSL_CX_F_CALC_R_CARD; rc = fsl_deck_save( d, opt->isPrivate ); RC; assert(d->rid>0); /* Now get vfile back into shape. We do not do a vfile scan because that loses state like add/rm-queued files. */ rc = fsl_db_exec_multi(dbC, "DELETE FROM vfile WHERE vid<>" "%" FSL_ID_T_PFMT ";" "UPDATE vfile SET vid=%" FSL_ID_T_PFMT ";" "DELETE FROM vfile WHERE deleted AND " "fsl_is_enqueued(id); " "UPDATE vfile SET rid=mrid, mhash=NULL, " "chnged=0, deleted=0, origname=NULL " "WHERE fsl_is_enqueued(id)", vid, d->rid); if(!rc) rc = fsl_ckout_version_write(f, d->rid, NULL); RC; assert(d->f == f); rc = fsl_checkin_add_unsent(f, d->rid); RC; rc = fsl_ckout_clear_merge_state(f); RC; /* |
︙ | ︙ | |||
5800 5801 5802 5803 5804 5805 5806 | if(inTrans){ if(rc) fsl_db_transaction_rollback(dbR); else{ rc = fsl_db_transaction_commit(dbR); if(!rc){ if(newRid) *newRid = d->rid; if(newUuid){ | | | > < | > | 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 | if(inTrans){ if(rc) fsl_db_transaction_rollback(dbR); else{ rc = fsl_db_transaction_commit(dbR); if(!rc){ if(newRid) *newRid = d->rid; if(newUuid){ if(NULL==(*newUuid = fsl_rid_to_uuid(f, d->rid))){ rc = FSL_RC_OOM; } } } } } if(rc && !f->error.code){ if(f->dbMain->error.code) fsl_cx_uplift_db_error(f, f->dbMain); else f->error.code = rc; } fsl_checkin_discard(f); fsl_deck_finalize(d); return rc; } |
︙ | ︙ | |||
6590 6591 6592 6593 6594 6595 6596 | /** Create and attach ckout db... */ assert(!fsl_cx_db_ckout(f)); const char * dbName = opt->ckoutDbFile ? opt->ckoutDbFile : fsl_preferred_ckout_db_name(); fsl_cx_err_reset(f); | | | 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 | /** Create and attach ckout db... */ assert(!fsl_cx_db_ckout(f)); const char * dbName = opt->ckoutDbFile ? opt->ckoutDbFile : fsl_preferred_ckout_db_name(); fsl_cx_err_reset(f); int fsl_cx_attach_role(fsl_cx * const , const char *, fsl_dbrole_e) /* defined in cx.c */; rc = fsl_cx_attach_role(f, dbName, FSL_DBROLE_CKOUT); if(rc) goto end; fsl_db * const theDbC = fsl_cx_db_ckout(f); dbC = fsl_cx_db_for_role(f, FSL_DBROLE_CKOUT); assert(theDbC != dbC && "Not anymore."); assert(theDbC == f->dbMain); |
︙ | ︙ | |||
6713 6714 6715 6716 6717 6718 6719 | if(!rc && isModified) *isModified = mod; fsl_cx_scratchpad_yield(f, fname); if(hash) fsl_cx_scratchpad_yield(f, hash); return rc; } /** | | | | 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 | if(!rc && isModified) *isModified = mod; fsl_cx_scratchpad_yield(f, fname); if(hash) fsl_cx_scratchpad_yield(f, hash); return rc; } /** Infrastructure for fsl_repo_ckout(), fsl_ckout_update(), and fsl_ckout_merge(). */ typedef struct { /** The pre-checkout vfile.vid. 0 if no version was checked out. */ fsl_id_t originRid; fsl_repo_extract_opt const * eOpt; fsl_ckup_opt const * cOpt; |
︙ | ︙ | |||
7315 7316 7317 7318 7319 7320 7321 | /* Note that mtimes were set during extraction if cOpt->setMtime is true. */); if(rc) goto end; assert(f->ckout.rid==cOpt->checkinRid); assert(f->ckout.rid ? !!f->ckout.uuid : 1); } | < < < > > > > > | 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 | /* Note that mtimes were set during extraction if cOpt->setMtime is true. */); if(rc) goto end; assert(f->ckout.rid==cOpt->checkinRid); assert(f->ckout.rid ? !!f->ckout.uuid : 1); } if(!rc && prevRid!=0){ rc = fsl_repo_ckout_rm_list_fini(f, &rec); if(rc) goto end; } rc = fsl_ckout_manifest_write(f, -1, -1, -1, NULL); end: if(!rc){ rc = fsl_vfile_unload_except(f, cOpt->checkinRid); if(!rc) rc = fsl_ckout_clear_merge_state(f); } /* TODO: if "repo-cksum" config db setting is set, confirm R-card of cOpt->checkinRid against on-disk contents. */ if(cOpt->confirmer.callback){ fsl_cx_confirmer(f, &oldConfirm, NULL); } fsl_stmt_finalize(&rec.stChanged); fsl_stmt_finalize(&rec.stIsInVfile); fsl_stmt_finalize(&rec.stRidSize); fsl_cx_scratchpad_yield(f, rec.tgtDir); |
︙ | ︙ | |||
7432 7433 7434 7435 7436 7437 7438 | /* Compute file name changes on V->T. Record name changes in files that ** have changed locally. */ if( ckRid ){ uint32_t nChng = 0; fsl_id_t * aChng = 0; | | | | 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 | /* Compute file name changes on V->T. Record name changes in files that ** have changed locally. */ if( ckRid ){ uint32_t nChng = 0; fsl_id_t * aChng = 0; rc = fsl__find_filename_changes(f, ckRid, tid, true, &nChng, &aChng); if(rc){ assert(!aChng); assert(!nChng); goto end; } if( nChng ){ for(uint32_t i=0; i<nChng; ++i){ |
︙ | ︙ | |||
7573 7574 7575 7576 7577 7578 7579 | /** Missing features from fossil we still need for this include, but are not limited to: - file_unsafe_in_tree_path() (done, untested) - file_nondir_objects_on_path() (done, untested) - symlink_create() (done, untested) | < | 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 | /** Missing features from fossil we still need for this include, but are not limited to: - file_unsafe_in_tree_path() (done, untested) - file_nondir_objects_on_path() (done, untested) - symlink_create() (done, untested) - ... */ bFullPath = fsl_cx_scratchpad(f); bFullNewPath = fsl_cx_scratchpad(f); bFileUuid = fsl_cx_scratchpad(f); rc = fsl_buffer_append(bFullPath, f->ckout.dir, (fsl_int_t)f->ckout.dirLen); |
︙ | ︙ | |||
7662 7663 7664 7665 7666 7667 7668 | as FSL_CKUP_FCHANGE_ADDED. If they don't, use confirmer to ask the user what to do. */ }else{ //fsl_outputf(f, "ADD %s\n", zName); uState.fileChangeType = FSL_CKUP_FCHANGE_ADDED; } //if( !dryRunFlag && !internalUpdate ) undo_save(zName); | < | 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 | as FSL_CKUP_FCHANGE_ADDED. If they don't, use confirmer to ask the user what to do. */ }else{ //fsl_outputf(f, "ADD %s\n", zName); uState.fileChangeType = FSL_CKUP_FCHANGE_ADDED; } //if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !cuOpt->dryRun ){ rc = fsl_vfile_to_ckout(f, idt, &wasWritten); if(rc) goto end; } }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){ /* The file is unedited. Change it to the target version */ if( deleted ){ |
︙ | ︙ | |||
8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659 8660 8661 8662 8663 | SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ /* Only for debugging */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) /** Convenience form of FCLI_VN for level-3 verbosity. */ #define FCLI_V3(pfexp) FCLI_VN(3,pfexp) #define fcli_empty_m { \ NULL/*appHelp*/, \ NULL/*cliFlags*/, \ NULL/*f*/, \ NULL/*argv*/, \ 0/*argc*/, \ NULL/*appName*/, \ {/*clientFlags*/ \ "."/*checkoutDir*/, \ | > | < | 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 | 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() */ /* Only for debugging */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) /** Convenience form of FCLI_VN for level-3 verbosity. */ #define FCLI_V3(pfexp) FCLI_VN(3,pfexp) #define fcli_empty_m { \ NULL/*appHelp*/, \ NULL/*cliFlags*/, \ NULL/*f*/, \ NULL/*argv*/, \ 0/*argc*/, \ NULL/*appName*/, \ {/*clientFlags*/ \ "."/*checkoutDir*/, \ 0/*verbose*/ \ }, \ {/*transient*/ \ NULL/*repoDb*/, \ NULL/*userArg*/, \ 0/*helpRequested*/, \ false/*versionRequested*/\ }, \ |
︙ | ︙ | |||
8856 8857 8858 8859 8860 8861 8862 | 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)."), | < < | 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 | 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."), FCLI_FLAG(NULL,"checkout-dir","DIRECTORY", &fcli.clientFlags.checkoutDir, "Open the given directory as a checkout, instead of the current dir."), FCLI_FLAG_BOOL_X("V","verbose",NULL,fcli_flag_f_verbose, |
︙ | ︙ | |||
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 | if(rc) assert(fcli__error->msg.used); if(!rc){ rc = fcli_setup_common2(); } } return rc; } int fcli_setup(int argc, char const * const * argv ){ int rc = 0; if(fcli.cliFlags){ return fcli_setup2(argc, argv, fcli.cliFlags); } rc = fcli_setup_common1(true, argc, argv); if(!rc){ //f_out("fcli.transient.helpRequested=%d\n",fcli.transient.helpRequested); if(fcli.transient.helpRequested){ /* Do this last so that we can get the default user name and such for display in the help text. */ fcli_help(); rc = FCLI_RC_HELP; }else{ if( fcli_flag2(NULL, "no-checkout", NULL) ){ fcli.clientFlags.checkoutDir = NULL; } fcli_flag2(NULL,"user", &fcli.transient.userArg); fcli.config.traceSql = fcli_flag2(NULL,"trace-sql", NULL); | > > > > > > > > < | 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 9334 9335 | if(rc) assert(fcli__error->msg.used); if(!rc){ rc = fcli_setup_common2(); } } return rc; } int fcli_setup_v2(int argc, char const * const * argv, fcli_cliflag const * const cliFlags, fcli_help_info const * const helpInfo ){ fcli.cliFlags = cliFlags; fcli.appHelp = helpInfo; return fcli_setup(argc, argv); } int fcli_setup(int argc, char const * const * argv ){ int rc = 0; if(fcli.cliFlags){ return fcli_setup2(argc, argv, fcli.cliFlags); } rc = fcli_setup_common1(true, argc, argv); if(!rc){ //f_out("fcli.transient.helpRequested=%d\n",fcli.transient.helpRequested); if(fcli.transient.helpRequested){ /* Do this last so that we can get the default user name and such for display in the help text. */ fcli_help(); rc = FCLI_RC_HELP; }else{ if( fcli_flag2(NULL, "no-checkout", NULL) ){ fcli.clientFlags.checkoutDir = NULL; } fcli_flag2(NULL,"user", &fcli.transient.userArg); fcli.config.traceSql = fcli_flag2(NULL,"trace-sql", NULL); fcli_flag2("R", "repo", &fcli.transient.repoDbArg); rc = fcli_setup_common2(); } } return rc; } |
︙ | ︙ | |||
9542 9543 9544 9545 9546 9547 9548 | /* 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 | | | 9439 9440 9441 9442 9443 9444 9445 9446 9447 9448 9449 9450 9451 9452 9453 | /* 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){ |
︙ | ︙ | |||
9569 9570 9571 9572 9573 9574 9575 | } } break; } } if(helpState){ f_out("\n"); | | | 9466 9467 9468 9469 9470 9471 9472 9473 9474 9475 9476 9477 9478 9479 9480 | } } 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{ |
︙ | ︙ | |||
9596 9597 9598 9599 9600 9601 9602 | } if(rc && reportErrors){ fcli_err_report(0); } return rc; } | > > > > > > > > > > > | > > > > > > > > > > > > > > > > > | 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 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 | } 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){ f_out(" %s\n", c->briefDescription); } if(c->aliases){ fcli_help_show_aliases(c->aliases); }else{ f_out("\n"); } if(c->flags){ f_out("\n"); fcli_cliflag_help(c->flags); } if(showUsage && c->usage){ c->usage(); } if(onlyOne) break; } } void fcli_help_show_aliases(char const * aliases){ char const * alias = aliases; f_out(" (aliases: "); while ( *alias!=0 ){ f_out("%s%s", alias, *(strchr(alias, 0) + 1) ? ", " : ")\n"); alias = strchr(alias, 0) + 1; } } void fcli_fax(void * mem){ if(mem){ fsl_list_append( &FCliFree.list, mem ); } } |
︙ | ︙ | |||
9853 9854 9855 9856 9857 9858 9859 9860 9861 9862 9863 9864 9865 9866 | default: break; } tgt->ansiColor.insertion = zIns; tgt->ansiColor.edit = zEdit; tgt->ansiColor.deletion = zDel; tgt->ansiColor.reset = zReset; } #undef FCLI_V3 #undef fcli_empty_m #undef fcli__error #undef MARKER /* end of file cli.c */ /* start of file content.c */ | > > > > > > > > > > > > > > > | 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 | default: break; } tgt->ansiColor.insertion = zIns; tgt->ansiColor.edit = zEdit; tgt->ansiColor.deletion = zDel; tgt->ansiColor.reset = zReset; } void fcli_dump_stmt_cache(bool forceVerbose){ int i = 0; fsl_stmt * st; fsl_db * const db = fsl_cx_db(fcli_cx()); assert(db); for( st = db->cacheHead; st; st = st->next ) ++i; f_out("%s(): Cached fsl_stmt count: %d\n", __func__, i); if(i>0 && (forceVerbose || fcli_is_verbose()>1)){ for( i = 1, st = db->cacheHead; st; ++i, st = st->next ){ f_out("CACHED fsl_stmt #%d (%d hit(s)): %b\n", i, (int)st->cachedHits, &st->sql); } } } #undef FCLI_V3 #undef fcli_empty_m #undef fcli__error #undef MARKER /* end of file cli.c */ /* start of file content.c */ |
︙ | ︙ | |||
9944 9945 9946 9947 9948 9949 9950 | } return false; } int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ){ | | < | | 9884 9885 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 | } return false; } int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ){ fsl_db * const dbR = fsl_cx_db_repo(f); if(blobRid<=0) return FSL_RC_RANGE; else if(!dbR) return FSL_RC_NOT_A_REPO; else{ int rc; fsl_stmt * q = NULL; rc = fsl_db_prepare_cached( dbR, &q, "SELECT content, size FROM blob " "WHERE rid=?" |
︙ | ︙ | |||
9990 9991 9992 9993 9994 9995 9996 | fsl_cx_uplift_db_error(f, dbR); } return rc; } } | | | 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 | fsl_cx_uplift_db_error(f, dbR); } return rc; } } bool fsl_content_is_private(fsl_cx * const f, fsl_id_t rid){ fsl_stmt * s1 = NULL; fsl_db * db = fsl_cx_db_repo(f); int rc = db ? fsl_db_prepare_cached(db, &s1, "SELECT 1 FROM private " "WHERE rid=?" "/*%s()*/",__func__) |
︙ | ︙ | |||
10346 10347 10348 10349 10350 10351 10352 | } end: fsl_stmt_finalize(&q); fsl_buffer_clear(&bufChild); return rc; } | | > | 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 | } end: fsl_stmt_finalize(&q); fsl_buffer_clear(&bufChild); return rc; } int fsl_content_put_ex( fsl_cx * const f, fsl_buffer const * pBlob, fsl_uuid_cstr zUuid, fsl_id_t srcId, fsl_size_t uncompSize, bool isPrivate, fsl_id_t * outRid){ fsl_size_t size; fsl_id_t rid; |
︙ | ︙ | |||
10639 10640 10641 10642 10643 10644 10645 | fsl_buffer_clear(&hash); if(!uncompSize){ fsl_buffer_clear(&cmpr); }/* else cmpr.mem (if any) belongs to pBlob */ return rc; } | | | | 10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 | fsl_buffer_clear(&hash); if(!uncompSize){ fsl_buffer_clear(&cmpr); }/* else cmpr.mem (if any) belongs to pBlob */ return rc; } int fsl_content_put( fsl_cx * const f, fsl_buffer const * pBlob, fsl_id_t * newRid){ return fsl_content_put_ex(f, pBlob, NULL, 0, 0, 0, newRid); } int fsl_uuid_is_shunned(fsl_cx * const f, fsl_uuid_cstr zUuid){ fsl_db * db = fsl_cx_db_repo(f); if( !db || zUuid==0 || zUuid[0]==0 ) return 0; else if(FSL_HPOLICY_SHUN_SHA1==f->cxConfig.hashPolicy && FSL_STRLEN_SHA1==fsl_is_uuid(zUuid)){ return 1; } /* TODO? cached query */ |
︙ | ︙ | |||
10734 10735 10736 10737 10738 10739 10740 | else if(rc && !f->error.code){ fsl_cx_uplift_db_error(f, db); } } return rc; } | | | 10674 10675 10676 10677 10678 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 | else if(rc && !f->error.code){ fsl_cx_uplift_db_error(f, db); } } return rc; } int fsl_content_undeltify(fsl_cx * const f, fsl_id_t rid){ int rc; fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; fsl_id_t srcid = 0; fsl_buffer x = fsl_buffer_empty; fsl_stmt s = fsl_stmt_empty; if(!f) return FSL_RC_MISUSE; else if(!db) return FSL_RC_NOT_A_REPO; |
︙ | ︙ | |||
10962 10963 10964 10965 10966 10967 10968 | else fsl_db_transaction_rollback(db); if(rc && db->error.code && !f->error.code){ rc = fsl_cx_uplift_db_error(f, db); } return rc; } | | | 10902 10903 10904 10905 10906 10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 | else fsl_db_transaction_rollback(db); if(rc && db->error.code && !f->error.code){ rc = fsl_cx_uplift_db_error(f, db); } return rc; } int fsl_content_make_public(fsl_cx * const f, fsl_id_t rid){ int rc; fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; if(!f) return FSL_RC_MISUSE; else if(!db) return FSL_RC_NOT_A_REPO; rc = fsl_db_exec(db, "DELETE FROM private " "WHERE rid=%" FSL_ID_T_PFMT, rid); return rc ? fsl_cx_uplift_db_error(f, db) : 0; |
︙ | ︙ | |||
11278 11279 11280 11281 11282 11283 11284 11285 11286 11287 11288 11289 11290 11291 | break; } default: break; } return id; } #undef MARKER /* end of file content.c */ /* start of file config.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 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 11218 11219 11220 11221 11222 11223 11224 11225 11226 11227 11228 11229 11230 11231 11232 11233 11234 11235 11236 11237 11238 11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 11249 11250 11251 11252 11253 11254 11255 11256 11257 11258 11259 11260 11261 11262 | break; } default: break; } return id; } int fsl__shunned_remove(fsl_cx * const f){ fsl_stmt q = fsl_stmt_empty; int rc; assert(fsl_cx_db_repo(f)); rc = fsl_cx_exec_multi(f, "CREATE TEMP TABLE toshun(rid INTEGER PRIMARY KEY);" "INSERT INTO toshun SELECT rid FROM blob, shun WHERE blob.uuid=shun.uuid;" ); if(rc) goto end; rc = fsl_cx_prepare(f, &q, "SELECT rid FROM delta WHERE srcid IN toshun" ); while( 0==rc && FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){ rc = fsl_content_undeltify(f, fsl_stmt_g_id(&q, 0)); } fsl_stmt_finalize(&q); if(rc) goto end; rc = fsl_cx_exec_multi(f, "DELETE FROM delta WHERE rid IN toshun;" "DELETE FROM blob WHERE rid IN toshun;" "DROP TABLE toshun;" "DELETE FROM private " " WHERE NOT EXISTS (SELECT 1 FROM blob WHERE rid=private.rid);" ); end: fsl_stmt_finalize(&q); return rc; } #undef MARKER /* end of file content.c */ /* start of file config.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 |
︙ | ︙ | |||
12297 12298 12299 12300 12301 12302 12303 12304 12305 12306 12307 12308 | #if FSL_ENABLE_SQLITE_REGEXP /** Used for setup and teardown of sqlite3_auto_extension(). */ static volatile long sg_autoregctr = 0; #endif int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param ){ static fsl_cx_init_opt paramDefaults = fsl_cx_init_opt_default_m; int rc = 0; fsl_cx * f; | > > > > > > > > > > > > > > > > > > > > > | > | > | 12268 12269 12270 12271 12272 12273 12274 12275 12276 12277 12278 12279 12280 12281 12282 12283 12284 12285 12286 12287 12288 12289 12290 12291 12292 12293 12294 12295 12296 12297 12298 12299 12300 12301 12302 12303 12304 12305 12306 12307 12308 12309 12310 12311 12312 12313 12314 12315 12316 12317 12318 12319 12320 12321 12322 12323 12324 12325 12326 12327 12328 12329 | #if FSL_ENABLE_SQLITE_REGEXP /** Used for setup and teardown of sqlite3_auto_extension(). */ static volatile long sg_autoregctr = 0; #endif /** Clears (most) dynamic state in f, but does not free f and does not free "static" state (that set up by the init process). If closeDatabases is true then any databases managed by f are closed, else they are kept open. Client code will not normally need this - it is intended for a particular potential memory optimization case. If (and only if) closeDatabases is true then after calling this, f may be legally re-used as a target for fsl_cx_init(). This function does not trigger any finializers set for f's client state or output channel. Results are undefined if !f or f's memory has not been properly initialized. */ static void fsl_cx_reset( fsl_cx * const f, bool closeDatabases ); int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param ){ static fsl_cx_init_opt paramDefaults = fsl_cx_init_opt_default_m; int rc = 0; fsl_cx * f; extern int fsl_cx_install_timeline_crosslinkers(fsl_cx * const f) /*in deck.c*/; if(!tgt) return FSL_RC_MISUSE; else if(!param){ if(!paramDefaults.output.state){ paramDefaults.output.state = stdout; } param = ¶mDefaults; } if(*tgt){ void const * allocStamp = (*tgt)->allocStamp; fsl_cx_reset(*tgt, true) /* just to be safe */; f = *tgt; *f = fsl_cx_empty; f->allocStamp = allocStamp; }else{ f = fsl_cx_malloc(); if(!f) return FSL_RC_OOM; *tgt = f; } memset(&f->cache.mcache, 0, sizeof(f->cache.mcache)); f->output = param->output; f->cxConfig = param->config; enum { /* Because testing shows a lot of re-allocs via some of the lower-level stat()-related bits, we pre-allocate this many bytes into f->scratchpads.buf[]. Curiously, there is almost no |
︙ | ︙ | |||
12394 12395 12396 12397 12398 12399 12400 | /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; } | | | 12388 12389 12390 12391 12392 12393 12394 12395 12396 12397 12398 12399 12400 12401 12402 | /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; } static void fsl_cx_reset(fsl_cx * const f, bool closeDatabases){ fsl_checkin_discard(f); #define SFREE(X) fsl_free(X); X = NULL if(closeDatabases){ fsl_cx_close_dbs(f); /* Reminder: f->dbMem is NOT closed here: it's an internal detail, not public state. We could arguably close and reopen it here, |
︙ | ︙ | |||
12430 12431 12432 12433 12434 12435 12436 12437 12438 12439 12440 12441 12442 12443 12444 12445 12446 12447 12448 12449 12450 12451 12452 12453 12454 12455 | fsl_buffer_clear(&f->scratchpads.buf[i]); f->scratchpads.used[i] = false; } fsl_acache_clear(&f->cache.arty); fsl_id_bag_clear(&f->cache.leafCheck); fsl_id_bag_clear(&f->cache.toVerify); fsl_cx_clear_mf_seen(f); #define SLIST(L) fsl_list_visit_free(L, 1) #define GLOBL(X) SLIST(&f->cache.globs.X) GLOBL(ignore); GLOBL(binary); GLOBL(crnl); #undef GLOBL #undef SLIST fsl_cx_mcache_clear(f); f->cache = fsl_cx_empty.cache; } void fsl_cx_clear_mf_seen(fsl_cx * f){ fsl_id_bag_clear(&f->cache.mfSeen); } void fsl_cx_finalize( fsl_cx * f ){ void const * allocStamp = f ? f->allocStamp : NULL; if(!f) return; | > > > > < < < < < | | 12424 12425 12426 12427 12428 12429 12430 12431 12432 12433 12434 12435 12436 12437 12438 12439 12440 12441 12442 12443 12444 12445 12446 12447 12448 12449 12450 12451 12452 12453 12454 12455 12456 12457 12458 12459 12460 12461 12462 12463 12464 12465 12466 12467 | fsl_buffer_clear(&f->scratchpads.buf[i]); f->scratchpads.used[i] = false; } fsl_acache_clear(&f->cache.arty); fsl_id_bag_clear(&f->cache.leafCheck); fsl_id_bag_clear(&f->cache.toVerify); fsl_cx_clear_mf_seen(f); if(f->xlinkers.list){ fsl_free(f->xlinkers.list); f->xlinkers = fsl_xlinker_list_empty; } #define SLIST(L) fsl_list_visit_free(L, 1) #define GLOBL(X) SLIST(&f->cache.globs.X) GLOBL(ignore); GLOBL(binary); GLOBL(crnl); #undef GLOBL #undef SLIST fsl_cx_mcache_clear(f); f->cache = fsl_cx_empty.cache; } void fsl_cx_clear_mf_seen(fsl_cx * f){ fsl_id_bag_clear(&f->cache.mfSeen); } void fsl_cx_finalize( fsl_cx * f ){ void const * allocStamp = f ? f->allocStamp : NULL; if(!f) return; if(f->clientState.finalize.f){ f->clientState.finalize.f( f->clientState.finalize.state, f->clientState.state ); } f->clientState = fsl_state_empty; f->output = fsl_outputer_empty; fsl_cx_reset(f, true); fsl_db_close(&f->dbMem); *f = fsl_cx_empty; if(&fsl_cx_empty == allocStamp){ fsl_free(f); }else{ f->allocStamp = allocStamp; } |
︙ | ︙ | |||
12532 12533 12534 12535 12536 12537 12538 | int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len ){ return f ? fsl_error_get( &f->error, str, len ) : FSL_RC_MISUSE; } | | | 12525 12526 12527 12528 12529 12530 12531 12532 12533 12534 12535 12536 12537 12538 12539 | int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len ){ return f ? fsl_error_get( &f->error, str, len ) : FSL_RC_MISUSE; } fsl_id_t fsl_cx_last_insert_id(fsl_cx * const f){ return (f && f->dbMain && f->dbMain->dbh) ? fsl_db_last_insert_id(f->dbMain) : -1; } fsl_cx * fsl_cx_malloc(){ fsl_cx * rc = (fsl_cx *)fsl_malloc(sizeof(fsl_cx)); |
︙ | ︙ | |||
12687 12688 12689 12690 12691 12692 12693 | /** @internal Attaches the given db file to f with the given role. This function "should" be static but we need it in fsl_repo.c when creating a new repository. */ | | > > | | 12680 12681 12682 12683 12684 12685 12686 12687 12688 12689 12690 12691 12692 12693 12694 12695 12696 12697 12698 12699 12700 12701 12702 | /** @internal Attaches the given db file to f with the given role. This function "should" be static but we need it in fsl_repo.c when creating a new repository. */ int fsl_cx_attach_role(fsl_cx * const f, const char *zDbName, fsl_dbrole_e r){ char const * label = fsl_db_role_label(r); fsl_db * db = fsl_cx_db_for_role(f, r); char ** nameDest = NULL; int rc; if(!f->dbMain){ fsl_fatal(FSL_RC_MISUSE,"Internal API misuse: f->dbMain has " "not been set, so cannot attach role."); return FSL_RC_MISUSE; } else if(r & f->dbMain->role){ assert(!"Misuse: role is already attached."); return fsl_cx_err_set(f, FSL_RC_MISUSE, "Db role %s is already attached.", label); |
︙ | ︙ | |||
12753 12754 12755 12756 12757 12758 12759 | // (void*)db, label, db->filename)); f->dbMain->role |= r; } } return rc; } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | < < < < < < < < | < | | < < < < < < < < | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < | | 12748 12749 12750 12751 12752 12753 12754 12755 12756 12757 12758 12759 12760 12761 12762 12763 12764 12765 12766 12767 12768 12769 12770 12771 12772 12773 12774 12775 12776 12777 12778 12779 12780 12781 12782 12783 12784 12785 12786 12787 12788 12789 12790 12791 12792 12793 12794 12795 12796 12797 12798 12799 12800 12801 12802 12803 12804 12805 12806 12807 12808 12809 12810 12811 12812 12813 12814 12815 12816 12817 12818 12819 12820 12821 12822 | // (void*)db, label, db->filename)); f->dbMain->role |= r; } } return rc; } int fsl_config_close( fsl_cx * const f ){ if(!f) return FSL_RC_MISUSE; else{ int rc; fsl_db * const db = &f->config.db; if(f->dbMain && (FSL_DBROLE_CONFIG & f->dbMain->role)){ /* Config db is ATTACHed. */ rc = fsl_cx_detach_role(f, FSL_DBROLE_CONFIG); } else rc = FSL_RC_NOT_FOUND; assert(!db->dbh); fsl_db_clear_strings(db, 1); return rc; } } int fsl_repo_close( fsl_cx * const f ){ if(fsl_cx_transaction_level(f)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, "Cannot close repo with opened transaction."); }else{ int rc; fsl_db * const db = &f->repo.db; if(f->dbMain && (FSL_DBROLE_REPO & f->dbMain->role)){ /* Repo db is ATTACHed. */ if(FSL_DBROLE_CKOUT & f->dbMain->role){ rc = fsl_cx_err_set(f, FSL_RC_MISUSE, "Cannot close repo while checkout is " "opened."); }else{ assert(f->dbMain!=db); rc = fsl_cx_detach_role(f, FSL_DBROLE_REPO); } } else rc = FSL_RC_NOT_FOUND; assert(!db->dbh); fsl_db_clear_strings(db, true); return rc; } } int fsl_ckout_close( fsl_cx * const f ){ if(fsl_cx_transaction_level(f)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, "Cannot close checkout with opened transaction."); }else{ int rc; fsl_db * const db = &f->ckout.db; if(f->dbMain && (FSL_DBROLE_CKOUT & f->dbMain->role)){ /* Checkout db is ATTACHed. */ rc = fsl_cx_detach_role(f, FSL_DBROLE_CKOUT); fsl_repo_close(f) /* Because the repo is implicitly opened, we "should" implicitly close it. This is debatable but "probably almost always" desired. i can't currently envisage a reasonable use-case which requires closing the checkout but keeping the repo opened. The repo can always be re-opened by itself. */; }else{ rc = FSL_RC_NOT_FOUND; } fsl_free(f->ckout.uuid); f->ckout.uuid = NULL; f->ckout.rid = 0; assert(!db->dbh); fsl_db_clear_strings(db, true); |
︙ | ︙ | |||
13039 13040 13041 13042 13043 13044 13045 | } if( lsize%1024!=0 || lsize<4096 ){ return fsl_cx_err_set(f, FSL_RC_RANGE, "File's size is not correct for a " "checkout db: %s", zDbName); } | < | < < < | | | | > > > | | < < < < < < | > > > > > > > | < < < < < < < < | | | < > | < < < < | | < < < < | | | | | | | | < | | | > > > > > > > > > > > | 12837 12838 12839 12840 12841 12842 12843 12844 12845 12846 12847 12848 12849 12850 12851 12852 12853 12854 12855 12856 12857 12858 12859 12860 12861 12862 12863 12864 12865 12866 12867 12868 12869 12870 12871 12872 12873 12874 12875 12876 12877 12878 12879 12880 12881 12882 12883 12884 12885 12886 12887 12888 12889 12890 12891 12892 12893 12894 12895 12896 12897 12898 12899 12900 12901 12902 12903 | } if( lsize%1024!=0 || lsize<4096 ){ return fsl_cx_err_set(f, FSL_RC_RANGE, "File's size is not correct for a " "checkout db: %s", zDbName); } rc = fsl_cx_attach_role(f, zDbName, FSL_DBROLE_CKOUT); return rc; } int fsl_cx_execv( fsl_cx * const f, char const * sql, va_list args ){ int const rc = (f->dbMain && sql) ? fsl_db_execv(f->dbMain, sql, args) : FSL_RC_MISUSE; return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; } int fsl_cx_exec( fsl_cx * const f, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_cx_execv( f, sql, args ); va_end(args); return rc; } int fsl_cx_exec_multiv( fsl_cx * const f, char const * sql, va_list args ){ int const rc = (f->dbMain && sql) ? fsl_db_exec_multiv(f->dbMain, sql, args) : FSL_RC_MISUSE; return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; } int fsl_cx_exec_multi( fsl_cx * const f, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_cx_exec_multiv( f, sql, args ); va_end(args); return rc; } int fsl_cx_preparev( fsl_cx * const f, fsl_stmt * const tgt, char const * sql, va_list args ){ int const rc = (f->dbMain && tgt) ? fsl_db_preparev(f->dbMain, tgt, sql, args) : FSL_RC_MISUSE; return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; } int fsl_cx_prepare( fsl_cx * const f, fsl_stmt * const tgt, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_cx_preparev( f, tgt, sql, args ); va_end(args); return rc; } /** Passes the fsl_schema_config() SQL code through a new/truncated file named dbName. If the file exists before this call, it is unlink()ed and fails if that operation fails. |
︙ | ︙ | |||
13204 13205 13206 13207 13208 13209 13210 | assert(zRc); *zOut = zRc; } fsl_buffer_clear(&buf); return rc; } | | < < | 12996 12997 12998 12999 13000 13001 13002 13003 13004 13005 13006 13007 13008 13009 13010 13011 13012 13013 | 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; if(!f) return FSL_RC_MISUSE; else if(f->config.db.dbh){ fsl_config_close(f); } if(openDbName && *openDbName){ zDbName = openDbName; }else{ |
︙ | ︙ | |||
13240 13241 13242 13243 13244 13245 13246 | if( fsl_file_access(zDbName, W_OK) ){ rc = fsl_cx_err_set(f, FSL_RC_ACCESS, "Configuration database [%s] " "must be writeable.", zDbName); goto end; } #endif | < < | < < < < < < < < < < | 13030 13031 13032 13033 13034 13035 13036 13037 13038 13039 13040 13041 13042 13043 13044 | if( fsl_file_access(zDbName, W_OK) ){ rc = fsl_cx_err_set(f, FSL_RC_ACCESS, "Configuration database [%s] " "must be writeable.", zDbName); goto end; } #endif rc = fsl_cx_attach_role(f, zDbName, FSL_DBROLE_CONFIG); end: fsl_free(zPrefName); return rc; } static void fsl_cx_username_from_repo(fsl_cx * f){ fsl_db * dbR = fsl_cx_db_repo(f); |
︙ | ︙ | |||
13277 13278 13279 13280 13281 13282 13283 13284 13285 13286 13287 13288 13289 13290 | static int fsl_cx_load_glob_lists(fsl_cx * f){ int rc; rc = fsl_config_globs_load(f, &f->cache.globs.ignore, "ignore-glob"); if(!rc) rc = fsl_config_globs_load(f, &f->cache.globs.binary, "binary-glob"); if(!rc) rc = fsl_config_globs_load(f, &f->cache.globs.crnl, "crnl-glob"); return rc; } /** To be called after a repo or checkout/repo combination has been opened. This updates some internal cached info based on the checkout and/or repo. */ static int fsl_cx_after_open(fsl_cx * f){ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 13102 13103 13104 13105 13106 13107 13108 13109 | static int fsl_cx_load_glob_lists(fsl_cx * f){ int rc; rc = fsl_config_globs_load(f, &f->cache.globs.ignore, "ignore-glob"); if(!rc) rc = fsl_config_globs_load(f, &f->cache.globs.binary, "binary-glob"); if(!rc) rc = fsl_config_globs_load(f, &f->cache.globs.crnl, "crnl-glob"); return rc; } int fsl_cx_glob_list( fsl_cx * const f, fsl_glob_category_e gtype, fsl_list **tgt, bool reload ){ fsl_list * li = NULL; char const * reloadKey = NULL; switch(gtype){ case FSL_GLOBS_IGNORE: li = &f->cache.globs.ignore; reloadKey = "ignore-glob"; break; case FSL_GLOBS_CRNL: li = &f->cache.globs.crnl; reloadKey = "crnl-glob"; break; case FSL_GLOBS_BINARY: li = &f->cache.globs.binary; reloadKey = "binary-glob"; break; default: return FSL_RC_RANGE; } int rc = 0; if(reload){ assert(reloadKey); fsl_glob_list_clear(li); rc = fsl_config_globs_load(f, li, reloadKey); } if(0==rc) *tgt = li; return rc; } fsl_glob_category_e fsl_glob_name_to_category(char const * str){ if(str){ #define CHECK(PRE,E) \ if(*str==PRE[0] && \ (0==fsl_strcmp(PRE "-glob",str) \ || 0==fsl_strcmp(PRE,str))) return E; CHECK("ignore", FSL_GLOBS_IGNORE); CHECK("binary", FSL_GLOBS_BINARY); CHECK("crnl", FSL_GLOBS_CRNL); #undef CHECK } return FSL_GLOBS_INVALID; } /** To be called after a repo or checkout/repo combination has been opened. This updates some internal cached info based on the checkout and/or repo. */ static int fsl_cx_after_open(fsl_cx * f){ |
︙ | ︙ | |||
13305 13306 13307 13308 13309 13310 13311 | case FSL_HPOLICY_SHA1: p = FSL_HPOLICY_SHA1; break; case FSL_HPOLICY_SHUN_SHA1: p = FSL_HPOLICY_SHUN_SHA1; break; default: p = FSL_HPOLICY_AUTO; break; } f->cxConfig.hashPolicy = p; } | > | < | < | < | < | 13124 13125 13126 13127 13128 13129 13130 13131 13132 13133 13134 13135 13136 13137 13138 13139 13140 13141 13142 13143 13144 13145 13146 13147 13148 13149 13150 | case FSL_HPOLICY_SHA1: p = FSL_HPOLICY_SHA1; break; case FSL_HPOLICY_SHUN_SHA1: p = FSL_HPOLICY_SHUN_SHA1; break; default: p = FSL_HPOLICY_AUTO; break; } f->cxConfig.hashPolicy = p; } int fsl_repo_open( fsl_cx * const f, char const * repoDbFile /* , bool readOnlyCurrentlyIgnored */ ){ if(fsl_cx_db_repo(f)){ return fsl_cx_err_set(f, FSL_RC_ACCESS, "Context already has an opened repository."); }else{ int rc; if(0!=fsl_file_access( repoDbFile, F_OK )){ rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "Repository db [%s] not found or cannot be read.", repoDbFile); }else{ rc = fsl_cx_attach_role(f, repoDbFile, FSL_DBROLE_REPO); if(!rc && !(FSL_CX_F_IS_OPENING_CKOUT & f->flags)){ rc = fsl_cx_after_open(f); } if(!rc){ fsl_db * const db = fsl_cx_db_repo(f); fsl_cx_username_from_repo(f); f->cache.allowSymlinks = |
︙ | ︙ | |||
13468 13469 13470 13471 13472 13473 13474 | */ static int fsl_cx_ckout_version_set(fsl_cx *f, fsl_id_t rid, fsl_uuid_cstr uuid){ char * u = 0; assert(rid>=0); u = uuid ? fsl_strdup(uuid) | | | 13284 13285 13286 13287 13288 13289 13290 13291 13292 13293 13294 13295 13296 13297 13298 | */ static int fsl_cx_ckout_version_set(fsl_cx *f, fsl_id_t rid, fsl_uuid_cstr uuid){ char * u = 0; assert(rid>=0); u = uuid ? fsl_strdup(uuid) : (rid ? fsl_rid_to_uuid(f, rid) : NULL); if(rid && !u) return FSL_RC_OOM; f->ckout.rid = rid; fsl_free(f->ckout.uuid); f->ckout.uuid = u; fsl_ckout_mtime_set(f); return 0; } |
︙ | ︙ | |||
13519 13520 13521 13522 13523 13524 13525 | void fsl_ckout_version_info(fsl_cx * const f, fsl_id_t * const rid, fsl_uuid_cstr * const uuid ){ if(uuid) *uuid = f->ckout.uuid; if(rid) *rid = f->ckout.rid>=0 ? f->ckout.rid : 0; } int fsl_ckout_db_search( char const * dirName, bool checkParentDirs, | | | 13335 13336 13337 13338 13339 13340 13341 13342 13343 13344 13345 13346 13347 13348 13349 | void fsl_ckout_version_info(fsl_cx * const f, fsl_id_t * const rid, fsl_uuid_cstr * const uuid ){ if(uuid) *uuid = f->ckout.uuid; if(rid) *rid = f->ckout.rid>=0 ? f->ckout.rid : 0; } int fsl_ckout_db_search( char const * dirName, bool checkParentDirs, fsl_buffer * const pOut ){ int rc; fsl_int_t dLen = 0, i; enum { DbCount = 2 }; const char aDbName[DbCount][10] = { "_FOSSIL_", ".fslckout" }; fsl_buffer Buf = fsl_buffer_empty; fsl_buffer * buf = &Buf; buf->used = 0; |
︙ | ︙ | |||
13772 13773 13774 13775 13776 13777 13778 | for( i = 0; i < f->xlinkers.used; ++i ){ rv = f->xlinkers.list + i; if(0==fsl_strcmp(rv->name, name)) return rv; } return NULL; } | | | | 13588 13589 13590 13591 13592 13593 13594 13595 13596 13597 13598 13599 13600 13601 13602 13603 13604 13605 | for( i = 0; i < f->xlinkers.used; ++i ){ rv = f->xlinkers.list + i; if(0==fsl_strcmp(rv->name, name)) return rv; } return NULL; } int fsl_xlink_listener( fsl_cx * const f, char const * name, fsl_deck_xlink_f cb, void * cbState ){ fsl_xlinker * x; if(!*name) return FSL_RC_MISUSE; x = fsl_xlinker_by_name(f, name); if(x){ /* Replace existing entry */ x->f = cb; x->state = cbState; return 0; }else if(f->xlinkers.used <= f->xlinkers.capacity){ |
︙ | ︙ | |||
13913 13914 13915 13916 13917 13918 13919 | } char const * fsl_cx_filename_collation(fsl_cx const * f){ return f->cache.caseInsensitive ? "COLLATE nocase" : ""; } | | | | | 13729 13730 13731 13732 13733 13734 13735 13736 13737 13738 13739 13740 13741 13742 13743 13744 13745 13746 13747 13748 13749 13750 13751 13752 13753 13754 13755 13756 13757 13758 13759 13760 13761 13762 13763 13764 13765 13766 13767 13768 13769 13770 13771 13772 13773 13774 13775 13776 | } char const * fsl_cx_filename_collation(fsl_cx const * f){ return f->cache.caseInsensitive ? "COLLATE nocase" : ""; } void fsl_cx_content_buffer_yield(fsl_cx * const f){ enum { MaxSize = 1024 * 1024 * 2 }; assert(f); if(f->fileContent.capacity>MaxSize){ fsl_buffer_resize(&f->fileContent, MaxSize); assert(f->fileContent.capacity<=MaxSize+1); } fsl_buffer_reuse(&f->fileContent); } fsl_error const * fsl_cx_err_get_e(fsl_cx const * f){ return f ? &f->error : NULL; } int fsl_cx_close_dbs( fsl_cx * const f ){ if(fsl_cx_transaction_level(f)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, "Cannot close the databases when a " "transaction is pending."); } int rc = 0, rc1; rc1 = fsl_ckout_close(f); if(rc1) rc = rc1; rc1 = fsl_repo_close(f); if(rc1) rc = rc1; rc1 = fsl_config_close(f); if(rc1) rc = rc1; assert(!f->repo.db.dbh); assert(!f->ckout.db.dbh); assert(!f->config.db.dbh); return rc; } char const * fsl_cx_glob_matches( fsl_cx * const f, int gtype, char const * str ){ int i, count = 0; char const * rv = NULL; fsl_list const * lists[] = {0,0,0}; if(!f || !str || !*str) return NULL; if(gtype & FSL_GLOBS_IGNORE) lists[count++] = &f->cache.globs.ignore; if(gtype & FSL_GLOBS_CRNL) lists[count++] = &f->cache.globs.crnl; |
︙ | ︙ | |||
14394 14395 14396 14397 14398 14399 14400 | #undef MARKER #undef FSL_CX_NSCRATCH /* end of file cx.c */ /* start of file db.c */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* | | | | | | < | < < < > | | 14210 14211 14212 14213 14214 14215 14216 14217 14218 14219 14220 14221 14222 14223 14224 14225 14226 14227 14228 14229 14230 14231 14232 | #undef MARKER #undef FSL_CX_NSCRATCH /* end of file cx.c */ /* start of file db.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). ***************************************************************************** This file contains the fsl_db_xxx() and fsl_stmt_xxx() parts of the API. Maintenance reminders: When returning dynamically allocated memory to the client, it needs |
︙ | ︙ | |||
14429 14430 14431 14432 14433 14434 14435 | do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #if 0 /** fsl_list_visitor_f() impl which requires that obj be NULL or | | | | | | | < | | > > > > > > > > > > > > | | | < | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | < | | | | | 14242 14243 14244 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267 14268 14269 14270 14271 14272 14273 14274 14275 14276 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292 14293 14294 14295 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 14325 14326 14327 14328 14329 14330 14331 14332 14333 14334 14335 14336 14337 14338 14339 14340 14341 14342 14343 14344 14345 14346 14347 14348 14349 14350 14351 14352 14353 14354 14355 14356 14357 14358 14359 14360 14361 14362 14363 14364 14365 14366 14367 14368 14369 14370 14371 14372 14373 14374 14375 14376 14377 14378 14379 14380 14381 14382 14383 14384 14385 14386 14387 14388 14389 14390 14391 14392 14393 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 | do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #if 0 /** fsl_list_visitor_f() impl which requires that obj be NULL or a (fsl_stmt*), which it passed to fsl_stmt_finalize(). */ static int fsl_list_v_fsl_stmt_finalize(void * obj, void * visitorState ){ if(obj) fsl_stmt_finalize( (fsl_stmt*)obj ); return 0; } #endif void fsl_db_clear_strings(fsl_db * const db, bool alsoErrorState ){ fsl_free(db->filename); db->filename = NULL; fsl_free(db->name); db->name = NULL; if(alsoErrorState) fsl_error_clear(&db->error); } int fsl_db_err_get( fsl_db const * const db, char const ** msg, fsl_size_t * len ){ return db ? fsl_error_get(&db->error, msg, len) : FSL_RC_MISUSE; } fsl_db * fsl_stmt_db( fsl_stmt * const stmt ){ return stmt ? stmt->db : NULL; } /** Resets db->error state based on the given code and the current error string from the db driver. Returns FSL_RC_DB on success, some other non-0 value on error (most likely FSL_RC_OOM while allocating the error string - that's the only other error case as long as db is opened). Results are undefined if !db or db is not opened. */ static int fsl_err_from_db( fsl_db * const db, int dbCode ){ assert(db && db->dbh); db->error.msg.used =0 ; return fsl_error_set(&db->error, FSL_RC_DB, "Db error #%d: %s", dbCode, sqlite3_errmsg(db->dbh)); } char const * fsl_stmt_sql( fsl_stmt * const stmt, fsl_size_t * const len ){ return stmt ? fsl_buffer_cstr2(&stmt->sql, len) : NULL; } char const * fsl_db_filename(fsl_db const * db, fsl_size_t * len){ if(len && db->filename) *len = fsl_strlen(db->filename); return db->filename; } fsl_id_t fsl_db_last_insert_id(fsl_db * const db){ return (db && db->dbh) ? (fsl_id_t)sqlite3_last_insert_rowid(db->dbh) : -1; } /** Cleans up db->beforeCommit and its contents. */ static void fsl_db_cleanup_beforeCommit( fsl_db * const db ){ fsl_list_visit( &db->beforeCommit, -1, fsl_list_v_fsl_free, NULL ); fsl_list_reserve(&db->beforeCommit, 0); } /** Immediately cleans up all cached statements (if any) and returns the number of statements cleaned up. It is illegal to call this while any of the cached statements are actively being used (have not been fsl_stmt_cached_yield()ed), and doing so will lead to undefined results if the statement(s) in question are used after this function completes. @see fsl_db_prepare_cached() @see fsl_stmt_cached_yield() */ static fsl_size_t fsl_db_stmt_cache_clear(fsl_db * const db){ fsl_size_t rc = 0; if(db && db->cacheHead){ fsl_stmt * st; fsl_stmt * next = 0; for( st = db->cacheHead; st; st = next, ++rc ){ next = st->next; st->next = 0; fsl_stmt_finalize( st ); } db->cacheHead = 0; } return rc; } void fsl_db_close( fsl_db * const db ){ if(!db) return; void const * allocStamp = db->allocStamp; fsl_cx * const f = db->f; fsl_db_stmt_cache_clear(db); if(db->f && db->f->dbMain==db){ /* Horrible, horrible dependency, and only necessary if the fsl_cx API gets sloppy or repo/checkout/config DBs are otherwised closed improperly (i.e. not via the fsl_cx API). */ assert(0 != db->role); f->dbMain = NULL; } while(db->beginCount>0){ fsl_db_transaction_end(db, 1); } if(0!=db->openStatementCount){ MARKER(("WARNING: %d open statement(s) left on db [%s].\n", (int)db->openStatementCount, db->filename)); } if(db->dbh){ sqlite3_close(db->dbh); /* ignoring results in the style of "destructors may not throw". */ } fsl_db_clear_strings(db, 1); fsl_db_cleanup_beforeCommit(db); fsl_buffer_clear(&db->cachePrepBuf); *db = fsl_db_empty; if(&fsl_db_empty == allocStamp){ fsl_free( db ); }else{ db->allocStamp = allocStamp; db->f = f; } return; } void fsl_db_err_reset( fsl_db * const db ){ if(db && (db->error.code||db->error.msg.used)){ fsl_error_reset(&db->error); } } int fsl_db_attach(fsl_db * const db, const char *zDbName, const char *zLabel){ return (db && db->dbh && zDbName && *zDbName && zLabel && *zLabel) ? fsl_db_exec(db, "ATTACH DATABASE %Q AS %s", zDbName, zLabel) : FSL_RC_MISUSE; } int fsl_db_detach(fsl_db * const db, const char *zLabel){ return (db && db->dbh && zLabel && *zLabel) ? fsl_db_exec(db, "DETACH DATABASE %s /*%s()*/", zLabel, __func__) : FSL_RC_MISUSE; } char const * fsl_db_name(fsl_db const * const db){ return db ? db->name : NULL; } /** Returns the db name for the given role. */ const char * fsl_db_role_label(fsl_dbrole_e r){ |
︙ | ︙ | |||
14596 14597 14598 14599 14600 14601 14602 | case FSL_DBROLE_NONE: default: assert(!"cannot happen/not legal"); return NULL; } } | | | | | 14419 14420 14421 14422 14423 14424 14425 14426 14427 14428 14429 14430 14431 14432 14433 14434 14435 | case FSL_DBROLE_NONE: default: assert(!"cannot happen/not legal"); return NULL; } } char * fsl_db_julian_to_iso8601( fsl_db * const db, double j, bool msPrecision, bool localTime){ char * s = NULL; fsl_stmt * st = NULL; if(db && db->dbh && (j>=0.0)){ char const * sql; if(msPrecision){ sql = localTime ? "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',?, 'localtime')" |
︙ | ︙ | |||
14624 14625 14626 14627 14628 14629 14630 | } fsl_stmt_cached_yield(st); } } return s; } | | | 14447 14448 14449 14450 14451 14452 14453 14454 14455 14456 14457 14458 14459 14460 14461 | } fsl_stmt_cached_yield(st); } } return s; } char * fsl_db_unix_to_iso8601( fsl_db * const db, fsl_time_t t, bool localTime ){ char * s = NULL; fsl_stmt st = fsl_stmt_empty; if(db && db->dbh && (t>=0)){ char const * sql = localTime ? "SELECT datetime(?, 'unixepoch', 'localtime')/*%s()*/" : "SELECT datetime(?, 'unixepoch')/*%s()*/" ; |
︙ | ︙ | |||
14661 14662 14663 14664 14665 14666 14667 | /** Propagates our intent to "statically" prepare a given statement through various internal API calls. */ FSL_STMT_F_PREP_CACHE = 0x10 }; | | | 14484 14485 14486 14487 14488 14489 14490 14491 14492 14493 14494 14495 14496 14497 14498 | /** Propagates our intent to "statically" prepare a given statement through various internal API calls. */ FSL_STMT_F_PREP_CACHE = 0x10 }; int fsl_db_preparev( fsl_db * const db, fsl_stmt * const tgt, char const * sql, va_list args ){ if(!db || !tgt || !sql) return FSL_RC_MISUSE; else if(!db->dbh){ return fsl_error_set(&db->error, FSL_RC_NOT_FOUND, "Db is not opened."); }else if(!*sql){ return fsl_error_set(&db->error, FSL_RC_RANGE, "SQL is empty."); }else if(tgt->stmt){ return fsl_error_set(&db->error, FSL_RC_ALREADY_EXISTS, |
︙ | ︙ | |||
14743 14744 14745 14746 14747 14748 14749 | context. */ } return rc; } } | | > | | > > > > | > > > > > > > > > > > > > > | > | > < | | | | | 14566 14567 14568 14569 14570 14571 14572 14573 14574 14575 14576 14577 14578 14579 14580 14581 14582 14583 14584 14585 14586 14587 14588 14589 14590 14591 14592 14593 14594 14595 14596 14597 14598 14599 14600 14601 14602 14603 14604 14605 14606 14607 14608 14609 14610 14611 14612 14613 14614 14615 14616 14617 14618 14619 14620 14621 14622 14623 14624 14625 14626 14627 14628 14629 14630 14631 14632 14633 14634 14635 14636 14637 14638 14639 14640 14641 14642 14643 14644 14645 14646 14647 14648 14649 14650 14651 14652 14653 14654 14655 14656 14657 14658 14659 14660 14661 14662 14663 14664 14665 14666 14667 14668 14669 14670 14671 14672 14673 14674 14675 14676 14677 14678 14679 14680 14681 14682 14683 14684 14685 14686 14687 14688 14689 14690 14691 14692 14693 14694 14695 14696 14697 14698 14699 14700 14701 14702 14703 14704 14705 14706 | context. */ } return rc; } } int fsl_db_prepare( fsl_db * const db, fsl_stmt * const tgt, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_preparev( db, tgt, sql, args ); va_end(args); return rc; } int fsl_db_preparev_cached( fsl_db * const db, fsl_stmt ** rv, char const * sql, va_list args ){ int rc = 0; fsl_buffer * const buf = &db->cachePrepBuf; fsl_stmt * st = NULL; fsl_stmt * cs = NULL; if(!db || !rv || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; if(!buf->capacity && fsl_buffer_reserve(buf, 1024*2)){ return FSL_RC_OOM; } fsl_buffer_reuse(buf); rc = fsl_buffer_appendfv(buf, sql, args); if(rc) goto end; /** Hash buf's contents using a very primitive algo and stores the hash in buf->cursor. This is a blatant abuse of that member but we're otherwise not using it on these buffer instances. We use this to slightly speed up lookup of previously-cached entries and reduce the otherwise tremendous number of calls to fsl_buffer_compare() libfossil makes. */ for(fsl_size_t i = 0; i < buf->used; ++i){ //buf->cursor = (buf->cursor<<3) ^ buf->cursor ^ buf->mem[i]; buf->cursor = 31 * buf->cursor + (buf->mem[i] * 307); } for( cs = db->cacheHead; cs; cs = cs->next ){ if(cs->sql.cursor==buf->cursor/*hash value!*/ && buf->used==cs->sql.used && 0==fsl_buffer_compare(buf, &cs->sql)){ if(cs->flags & FSL_STMT_F_CACHE_HELD){ rc = fsl_error_set(&db->error, FSL_RC_ACCESS, "Cached statement is already in use. " "Do not use cached statements if recursion " "involving the statement is possible, and use " "fsl_stmt_cached_yield() to release them " "for further (re)use. SQL: %b", &cs->sql); goto end; } cs->flags |= FSL_STMT_F_CACHE_HELD; ++cs->cachedHits; *rv = cs; goto end; } } st = fsl_stmt_malloc(); if(!st){ rc = FSL_RC_OOM; goto end; } st->flags |= FSL_STMT_F_PREP_CACHE; rc = fsl_db_prepare( db, st, "%b", buf ); if(rc){ fsl_free(st); st = 0; }else{ st->sql.cursor = buf->cursor/*hash value!*/; st->next = db->cacheHead; db->cacheHead = st; st->flags = FSL_STMT_F_CACHE_HELD; *rv = st; } end: return rc; } int fsl_db_prepare_cached( fsl_db * db, fsl_stmt ** st, char const * sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_preparev_cached( db, st, sql, args ); va_end(args); return rc; } int fsl_stmt_cached_yield( fsl_stmt * const st ){ if(!st || !st->db || !st->stmt) return FSL_RC_MISUSE; else if(!(st->flags & FSL_STMT_F_CACHE_HELD)) { return fsl_error_set(&st->db->error, FSL_RC_MISUSE, "fsl_stmt_cached_yield() was passed a " "statement which is not marked as cached. " "SQL: %b", &st->sql); }else{ fsl_stmt_reset(st); st->flags &= ~FSL_STMT_F_CACHE_HELD; return 0; } } int fsl_db_before_commitv( fsl_db * const db, char const * const sql, va_list args ){ int rc = 0; char * cp = NULL; if(!db || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; cp = fsl_mprintfv(sql, args); if(cp){ rc = fsl_list_append(&db->beforeCommit, cp); if(rc) fsl_free(cp); }else{ rc = FSL_RC_OOM; } return rc; } int fsl_db_before_commit( fsl_db * const db, char const * const sql, ... ){ int rc; va_list args; va_start(args,sql); rc = fsl_db_before_commitv( db, sql, args ); va_end(args); return rc; } int fsl_stmt_finalize( fsl_stmt * const stmt ){ if(!stmt) return FSL_RC_MISUSE; else{ void const * allocStamp = stmt->allocStamp; fsl_db * db = stmt->db; if(db){ if(stmt->sql.mem){ /* ^^^ b/c that buffer is set at the same time |
︙ | ︙ | |||
14898 14899 14900 14901 14902 14903 14904 | }else{ stmt->allocStamp = allocStamp; } return 0; } } | | | | | < | 14741 14742 14743 14744 14745 14746 14747 14748 14749 14750 14751 14752 14753 14754 14755 14756 14757 14758 14759 14760 14761 14762 14763 14764 14765 14766 14767 14768 14769 | }else{ stmt->allocStamp = allocStamp; } return 0; } } int fsl_stmt_step( fsl_stmt * const stmt ){ if(!stmt || !stmt->stmt) return FSL_RC_MISUSE; else{ int const rc = sqlite3_step(stmt->stmt); assert(stmt->db); switch( rc ){ case SQLITE_ROW: ++stmt->rowCount; return FSL_RC_STEP_ROW; case SQLITE_DONE: return FSL_RC_STEP_DONE; default: return fsl_error_set(&stmt->db->error, FSL_RC_STEP_ERROR, "sqlite3_step(): sqlite error #%d: %s", rc, sqlite3_errmsg(stmt->db->dbh)); } } } int fsl_db_eachv( fsl_db * db, fsl_stmt_each_f callback, void * callbackState, char const * sql, va_list args ){ if(!db || !db->dbh || !callback || !sql) return FSL_RC_MISUSE; |
︙ | ︙ | |||
14971 14972 14973 14974 14975 14976 14977 | ? rc : ((FSL_RC_STEP_ERROR==strc) ? FSL_RC_DB : 0); } } | | | | | | | | 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 14823 14824 14825 14826 14827 14828 14829 14830 14831 14832 14833 14834 14835 14836 14837 14838 14839 14840 14841 14842 14843 14844 14845 14846 14847 14848 14849 14850 14851 14852 14853 14854 14855 14856 | ? rc : ((FSL_RC_STEP_ERROR==strc) ? FSL_RC_DB : 0); } } int fsl_stmt_reset2( fsl_stmt * const stmt, bool resetRowCounter ){ if(!stmt->stmt || !stmt->db) return FSL_RC_MISUSE; else{ int const rc = sqlite3_reset(stmt->stmt); if(resetRowCounter) stmt->rowCount = 0; assert(stmt->db); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_reset( fsl_stmt * const stmt ){ return fsl_stmt_reset2(stmt, 0); } int fsl_stmt_col_count( fsl_stmt const * const stmt ){ return (!stmt || !stmt->stmt) ? -1 : stmt->colCount ; } char const * fsl_stmt_col_name(fsl_stmt * const stmt, int index){ return (stmt && stmt->stmt && (index>=0 && index<stmt->colCount)) ? sqlite3_column_name(stmt->stmt, index) : NULL; } int fsl_stmt_param_count( fsl_stmt const * const stmt ){ return (!stmt || !stmt->stmt) ? -1 : stmt->paramCount; } int fsl_stmt_bind_fmtv( fsl_stmt * st, char const * fmt, va_list args ){ int rc = 0, ndx; |
︙ | ︙ | |||
15128 15129 15130 15131 15132 15133 15134 | } #define BIND_PARAM_CHECK \ if(!(stmt && stmt->stmt && stmt->db && stmt->db->dbh)) return FSL_RC_MISUSE; else #define BIND_PARAM_CHECK2 BIND_PARAM_CHECK \ if(ndx<1 || ndx>stmt->paramCount) return FSL_RC_RANGE; else | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 14970 14971 14972 14973 14974 14975 14976 14977 14978 14979 14980 14981 14982 14983 14984 14985 14986 14987 14988 14989 14990 14991 14992 14993 14994 14995 14996 14997 14998 14999 15000 15001 15002 15003 15004 15005 15006 15007 15008 15009 15010 15011 15012 15013 15014 15015 15016 15017 15018 15019 15020 15021 15022 15023 15024 15025 15026 15027 15028 15029 15030 15031 15032 15033 15034 15035 15036 15037 15038 15039 15040 15041 15042 15043 15044 15045 15046 15047 15048 15049 15050 15051 15052 15053 15054 15055 15056 15057 15058 15059 15060 15061 15062 15063 15064 15065 15066 15067 15068 15069 15070 15071 15072 15073 15074 15075 15076 15077 15078 15079 15080 15081 15082 15083 15084 15085 15086 15087 15088 15089 15090 15091 15092 15093 15094 15095 15096 15097 15098 15099 15100 15101 15102 15103 15104 15105 15106 15107 15108 15109 15110 15111 15112 15113 15114 15115 15116 15117 15118 15119 15120 15121 15122 15123 15124 15125 15126 15127 15128 15129 15130 15131 15132 15133 15134 15135 15136 15137 15138 15139 15140 15141 15142 15143 15144 15145 15146 15147 15148 15149 15150 15151 15152 15153 15154 15155 15156 15157 15158 15159 15160 15161 15162 15163 15164 15165 15166 15167 15168 15169 15170 15171 15172 15173 15174 15175 15176 15177 15178 15179 15180 15181 15182 15183 15184 15185 15186 15187 15188 15189 15190 15191 15192 15193 15194 15195 15196 15197 15198 15199 15200 15201 15202 15203 | } #define BIND_PARAM_CHECK \ if(!(stmt && stmt->stmt && stmt->db && stmt->db->dbh)) return FSL_RC_MISUSE; else #define BIND_PARAM_CHECK2 BIND_PARAM_CHECK \ if(ndx<1 || ndx>stmt->paramCount) return FSL_RC_RANGE; else int fsl_stmt_bind_null( fsl_stmt * const stmt, int ndx ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_null( stmt->stmt, ndx ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_int32( fsl_stmt * const stmt, int ndx, int32_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_int( stmt->stmt, ndx, (int)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_int64( fsl_stmt * const stmt, int ndx, int64_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_int64( stmt->stmt, ndx, (sqlite3_int64)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_id( fsl_stmt * const stmt, int ndx, fsl_id_t v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_int64( stmt->stmt, ndx, (sqlite3_int64)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_double( fsl_stmt * const stmt, int ndx, double v ){ BIND_PARAM_CHECK2 { int const rc = sqlite3_bind_double( stmt->stmt, ndx, (double)v ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_blob( fsl_stmt * const stmt, int ndx, void const * src, fsl_size_t len, bool makeCopy ){ BIND_PARAM_CHECK2 { int rc; rc = sqlite3_bind_blob( stmt->stmt, ndx, src, (int)len, makeCopy ? SQLITE_TRANSIENT : SQLITE_STATIC ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_text( fsl_stmt * const stmt, int ndx, char const * src, fsl_int_t len, bool makeCopy ){ BIND_PARAM_CHECK { int rc; if(len<0) len = fsl_strlen((char const *)src); rc = sqlite3_bind_text( stmt->stmt, ndx, src, len, makeCopy ? SQLITE_TRANSIENT : SQLITE_STATIC ); return rc ? fsl_err_from_db(stmt->db, rc) : 0; } } int fsl_stmt_bind_null_name( fsl_stmt * const stmt, char const * param ){ BIND_PARAM_CHECK{ return fsl_stmt_bind_null( stmt, sqlite3_bind_parameter_index( stmt->stmt, param) ); } } int fsl_stmt_bind_int32_name( fsl_stmt * const stmt, char const * param, int32_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_int32( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_int64_name( fsl_stmt * const stmt, char const * param, int64_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_int64( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_id_name( fsl_stmt * const stmt, char const * param, fsl_id_t v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_id( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_double_name( fsl_stmt * const stmt, char const * param, double v ){ BIND_PARAM_CHECK { return fsl_stmt_bind_double( stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v); } } int fsl_stmt_bind_text_name( fsl_stmt * const stmt, char const * param, char const * v, fsl_int_t n, bool makeCopy ){ BIND_PARAM_CHECK { return fsl_stmt_bind_text(stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v, n, makeCopy); } } int fsl_stmt_bind_blob_name( fsl_stmt * const stmt, char const * param, void const * v, fsl_int_t len, bool makeCopy ){ BIND_PARAM_CHECK { return fsl_stmt_bind_blob(stmt, sqlite3_bind_parameter_index( stmt->stmt, param), v, len, makeCopy); } } int fsl_stmt_param_index( fsl_stmt * const stmt, char const * const param){ return (stmt && stmt->stmt) ? sqlite3_bind_parameter_index( stmt->stmt, param) : -1; } #undef BIND_PARAM_CHECK #undef BIND_PARAM_CHECK2 #define GET_CHECK if(!stmt->colCount) return FSL_RC_MISUSE; \ else if((ndx<0) || (ndx>=stmt->colCount)) return FSL_RC_RANGE; else int fsl_stmt_get_int32( fsl_stmt * const stmt, int ndx, int32_t * v ){ GET_CHECK { if(v) *v = (int32_t)sqlite3_column_int(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_int64( fsl_stmt * const stmt, int ndx, int64_t * v ){ GET_CHECK { if(v) *v = (int64_t)sqlite3_column_int64(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_double( fsl_stmt * const stmt, int ndx, double * v ){ GET_CHECK { if(v) *v = (double)sqlite3_column_double(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_id( fsl_stmt * const stmt, int ndx, fsl_id_t * v ){ GET_CHECK { if(v) *v = (4==sizeof(fsl_id_t)) ? (fsl_id_t)sqlite3_column_int(stmt->stmt, ndx) : (fsl_id_t)sqlite3_column_int64(stmt->stmt, ndx); return 0; } } int fsl_stmt_get_text( fsl_stmt * const stmt, int ndx, char const **out, fsl_size_t * outLen ){ GET_CHECK { unsigned char const * t = (out || outLen) ? sqlite3_column_text(stmt->stmt, ndx) : NULL; if(out) *out = (char const *)t; if(outLen){ int const x = sqlite3_column_bytes(stmt->stmt, ndx); *outLen = (x>0) ? (fsl_size_t)x : 0; } return 0; } } int fsl_stmt_get_blob( fsl_stmt * const stmt, int ndx, void const **out, fsl_size_t * outLen ){ GET_CHECK { void const * t = (out || outLen) ? sqlite3_column_blob(stmt->stmt, ndx) : NULL; if(out) *out = t; if(outLen){ if(!t) *outLen = 0; else{ int sz = sqlite3_column_bytes(stmt->stmt, ndx); *outLen = (sz>=0) ? (fsl_size_t)sz : 0; } } return 0; } } #undef GET_CHECK fsl_id_t fsl_stmt_g_id( fsl_stmt * const stmt, int index ){ fsl_id_t rv = -1; fsl_stmt_get_id(stmt, index, &rv); return rv; } int32_t fsl_stmt_g_int32( fsl_stmt * const stmt, int index ){ int32_t rv = 0; fsl_stmt_get_int32(stmt, index, &rv); return rv; } int64_t fsl_stmt_g_int64( fsl_stmt * const stmt, int index ){ int64_t rv = 0; fsl_stmt_get_int64(stmt, index, &rv); return rv; } double fsl_stmt_g_double( fsl_stmt * const stmt, int index ){ double rv = 0; fsl_stmt_get_double(stmt, index, &rv); return rv; } char const * fsl_stmt_g_text( fsl_stmt * const stmt, int index, fsl_size_t * outLen ){ char const * rv = NULL; fsl_stmt_get_text(stmt, index, &rv, outLen); return rv; } |
︙ | ︙ | |||
15728 15729 15730 15731 15732 15733 15734 15735 15736 15737 15738 15739 15740 15741 | end: fsl_cx_scratchpad_yield(f, b); return; oom: sqlite3_result_error_nomem(context); goto end; } fsl_db * fsl_db_malloc(){ fsl_db * rc = (fsl_db *)fsl_malloc(sizeof(fsl_db)); if(rc){ *rc = fsl_db_empty; rc->allocStamp = &fsl_db_empty; } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 15570 15571 15572 15573 15574 15575 15576 15577 15578 15579 15580 15581 15582 15583 15584 15585 15586 15587 15588 15589 15590 15591 15592 15593 15594 15595 15596 15597 15598 15599 15600 15601 15602 15603 15604 15605 15606 15607 15608 15609 15610 15611 15612 15613 15614 15615 15616 15617 15618 15619 15620 15621 15622 15623 15624 15625 15626 15627 | end: fsl_cx_scratchpad_yield(f, b); return; oom: sqlite3_result_error_nomem(context); goto end; } /** F(glob-list-name, filename) Returns 1 if the 2nd argument matches any glob in the fossil glob list named by the first argument. The first argument must be a name resolvable via fsl_glob_name_to_category() or an error is triggered. The second value is intended to be a string, but NULL is accepted (but never matches anything). If no match is found, 0 is returned. An empty glob list never matches anything. */ static void fsl_db_cx_glob_udf( sqlite3_context *context, int argc, 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); if(FSL_GLOBS_INVALID==globType){ char buf[100] = {0}; buf[sizeof(buf)-1] = 0; fsl_snprintf(buf, (fsl_size_t)sizeof(buf)-1, "Unknown glob pattern name: %#.*s", 50, p1 ? p1 : "NULL"); sqlite3_result_error(context, buf, -1); return; } fsl_cx_glob_list(f, globType, &li, false); assert(li); sqlite3_result_int(context, fsl_glob_list_matches(li, p2) ? 1 : 0); } fsl_db * fsl_db_malloc(){ fsl_db * rc = (fsl_db *)fsl_malloc(sizeof(fsl_db)); if(rc){ *rc = fsl_db_empty; rc->allocStamp = &fsl_db_empty; } |
︙ | ︙ | |||
15951 15952 15953 15954 15955 15956 15957 15958 15959 15960 15961 15962 15963 15964 | sqlite3_create_function(dbh, "fsl_ckout_dir", -1, SQLITE_ANY /* | SQLITE_DETERMINISTIC ? */, f, fsl_db_cx_chkout_dir_udf,0,0 ); sqlite3_create_function(dbh, "fsl_match_vfile_or_dir", 2, SQLITE_ANY | SQLITE_DETERMINISTIC, f, fsl_db_match_vfile_or_dir,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); | > > > > > > > | 15837 15838 15839 15840 15841 15842 15843 15844 15845 15846 15847 15848 15849 15850 15851 15852 15853 15854 15855 15856 15857 | sqlite3_create_function(dbh, "fsl_ckout_dir", -1, SQLITE_ANY /* | SQLITE_DETERMINISTIC ? */, f, fsl_db_cx_chkout_dir_udf,0,0 ); sqlite3_create_function(dbh, "fsl_match_vfile_or_dir", 2, SQLITE_ANY | SQLITE_DETERMINISTIC, f, fsl_db_match_vfile_or_dir,0,0 ); sqlite3_create_function(dbh, "fsl_glob", 2, 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); |
︙ | ︙ | |||
15981 15982 15983 15984 15985 15986 15987 | } }else{ assert(db->dbh); } return rc; } | | | 15874 15875 15876 15877 15878 15879 15880 15881 15882 15883 15884 15885 15886 15887 15888 | } }else{ assert(db->dbh); } return rc; } int fsl_db_exec_multiv( fsl_db * const db, const char * sql, va_list args){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ fsl_buffer buf = fsl_buffer_empty; int rc = 0; char const * z; char const * zEnd = NULL; rc = fsl_buffer_appendfv( &buf, sql, args ); |
︙ | ︙ | |||
16014 16015 16016 16017 16018 16019 16020 | z = zEnd; } fsl_buffer_reserve(&buf, 0); return rc; } } | | | | | | | | | | | | | | | 15907 15908 15909 15910 15911 15912 15913 15914 15915 15916 15917 15918 15919 15920 15921 15922 15923 15924 15925 15926 15927 15928 15929 15930 15931 15932 15933 15934 15935 15936 15937 15938 15939 15940 15941 15942 15943 15944 15945 15946 15947 15948 15949 15950 15951 15952 15953 15954 15955 15956 15957 15958 15959 15960 15961 15962 15963 15964 15965 15966 15967 15968 15969 15970 15971 15972 15973 15974 15975 15976 15977 15978 15979 15980 15981 15982 15983 15984 15985 15986 15987 15988 15989 15990 15991 15992 15993 15994 15995 15996 15997 15998 15999 16000 16001 16002 16003 16004 16005 16006 16007 16008 16009 16010 16011 16012 16013 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 | z = zEnd; } fsl_buffer_reserve(&buf, 0); return rc; } } int fsl_db_exec_multi( fsl_db * const db, const char * sql, ...){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ int rc; va_list args; va_start(args,sql); rc = fsl_db_exec_multiv( db, sql, args ); va_end(args); return rc; } } int fsl_db_execv( fsl_db * const db, const char * sql, va_list args){ if(!db || !db->dbh || !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 ); */ while(FSL_RC_STEP_ROW == (rc=fsl_stmt_step(&st))){} fsl_stmt_finalize(&st); return (FSL_RC_STEP_ERROR==rc) ? FSL_RC_DB : 0; } } int fsl_db_exec( fsl_db * const db, const char * sql, ...){ if(!db || !db->dbh || !sql) return FSL_RC_MISUSE; else{ int rc; va_list args; va_start(args,sql); rc = fsl_db_execv( db, sql, args ); va_end(args); return rc; } } int fsl_db_changes_recent(fsl_db * const db){ return (db && db->dbh) ? sqlite3_changes(db->dbh) : 0; } int fsl_db_changes_total(fsl_db * const db){ return (db && db->dbh) ? sqlite3_total_changes(db->dbh) : 0; } /** Sets db->priorChanges to sqlite3_total_changes(db->dbh). */ static void fsl_db_reset_change_count(fsl_db * const db){ db->priorChanges = sqlite3_total_changes(db->dbh); } int fsl_db_transaction_begin(fsl_db * const db){ if(!db || !db->dbh) return FSL_RC_MISUSE; else { int rc = (0==db->beginCount) ? fsl_db_exec(db,"BEGIN TRANSACTION") : 0; if(!rc){ if(1 == ++db->beginCount){ fsl_db_reset_change_count(db); } } return rc; } } int fsl_db_transaction_level(fsl_db * const db){ return db->doRollback ? -db->beginCount : db->beginCount; } int fsl_db_transaction_commit(fsl_db * const db){ return (db && db->dbh) ? fsl_db_transaction_end(db, 0) : FSL_RC_MISUSE; } int fsl_db_transaction_rollback(fsl_db * const db){ return (db && db->dbh) ? fsl_db_transaction_end(db, 1) : FSL_RC_MISUSE; } int fsl_db_rollback_force( fsl_db * const db ){ if(!db || !db->dbh) return FSL_RC_MISUSE; else{ int rc; db->beginCount = 0; fsl_db_cleanup_beforeCommit(db); rc = fsl_db_exec(db, "ROLLBACK"); fsl_db_reset_change_count(db); return rc; } } int fsl_db_transaction_end(fsl_db * const db, bool doRollback){ int rc = 0; if(!db || !db->dbh) return FSL_RC_MISUSE; else if (db->beginCount<=0){ return fsl_error_set(&db->error, FSL_RC_RANGE, "No transaction is active."); } if(doRollback) ++db->doRollback |
︙ | ︙ | |||
16632 16633 16634 16635 16636 16637 16638 | /* TODO? use cached statement? So far not used often enough to justify it. */ fsl_db_get_double( db, &rc, "SELECT julianday(%Q)",str); } return rc; } | | | < < < | < < < < < < | | | | | 16525 16526 16527 16528 16529 16530 16531 16532 16533 16534 16535 16536 16537 16538 16539 16540 16541 16542 16543 16544 16545 16546 16547 16548 16549 16550 16551 16552 16553 16554 16555 16556 16557 16558 16559 16560 16561 16562 16563 16564 16565 16566 16567 16568 16569 16570 16571 16572 16573 16574 16575 16576 16577 16578 16579 16580 16581 16582 16583 16584 16585 16586 | /* TODO? use cached statement? So far not used often enough to justify it. */ fsl_db_get_double( db, &rc, "SELECT julianday(%Q)",str); } return rc; } 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; } } bool fsl_db_exists(fsl_db * const db, char const * sql, ... ){ bool rc; va_list args; va_start(args,sql); rc = fsl_db_existsv(db, sql, args); va_end(args); return rc; } bool fsl_db_table_exists(fsl_db * const db, fsl_dbrole_e whichDb, const char *zTable ){ const char *zDb = fsl_db_role_label( whichDb ); int rc = db->dbh ? sqlite3_table_column_metadata(db->dbh, zDb, zTable, 0, 0, 0, 0, 0, 0) : !SQLITE_OK; return rc==SQLITE_OK ? true : false; } bool fsl_db_table_has_column( fsl_db * const db, char const *zTableName, char const *zColName ){ fsl_stmt q = fsl_stmt_empty; int rc = 0; bool rv = 0; if(!zTableName || !*zTableName || !zColName || !*zColName) return false; rc = fsl_db_prepare(db, &q, "PRAGMA table_info(%Q)", zTableName ); if(!rc) while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ /* Columns: (cid, name, type, notnull, dflt_value, pk) */ fsl_size_t colLen = 0; char const * zCol = fsl_stmt_g_text(&q, 1, &colLen); if(0==fsl_strncmp(zColName, zCol, colLen)){ rv = true; break; } } fsl_stmt_finalize(&q); return rv; } |
︙ | ︙ | |||
16739 16740 16741 16742 16743 16744 16745 | } } fsl_stmt_finalize(&st); return rc; } } | | | | 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 | } } fsl_stmt_finalize(&st); return rc; } } int fsl_db_select_slist( fsl_db * const db, fsl_list * tgt, char const * fmt, ... ){ int rc; va_list va; va_start (va,fmt); rc = fsl_db_select_slistv(db, tgt, fmt, va); va_end(va); return rc; } void fsl_db_sqltrace_enable( fsl_db * const db, FILE * outStream ){ if(db && db->dbh){ sqlite3_trace(db->dbh, fsl_db_sql_trace, outStream); } } int fsl_db_init( fsl_error * err, char const * zFilename, |
︙ | ︙ | |||
16874 16875 16876 16877 16878 16879 16880 | If manifest caching is disabled for f, d is immediately finalized. */ static void fsl_cx_mcache_insert(fsl_cx *f, fsl_deck * d){ if(!(f->flags & FSL_CX_F_MANIFEST_CACHE)){ fsl_deck_finalize(d); return; } | | | 16758 16759 16760 16761 16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 | If manifest caching is disabled for f, d is immediately finalized. */ static void fsl_cx_mcache_insert(fsl_cx *f, fsl_deck * d){ if(!(f->flags & FSL_CX_F_MANIFEST_CACHE)){ fsl_deck_finalize(d); return; } static const unsigned cacheLen = (unsigned)(sizeof(fsl_mcache_empty.aAge) /sizeof(fsl_mcache_empty.aAge[0])); fsl_mcache * const mc = &f->cache.mcache; while( d ){ unsigned i; fsl_deck *pBaseline = d->B.baseline; d->B.baseline = 0; |
︙ | ︙ | |||
16923 16924 16925 16926 16927 16928 16929 | modified and false is returned. If manifest caching is disabled for f, false is immediately returned without causing side effects. */ static bool fsl_cx_mcache_search(fsl_cx * f, fsl_id_t rid, fsl_deck * tgt){ if(!(f->flags & FSL_CX_F_MANIFEST_CACHE)) return false; | | | 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 | modified and false is returned. If manifest caching is disabled for f, false is immediately returned without causing side effects. */ static bool fsl_cx_mcache_search(fsl_cx * f, fsl_id_t rid, fsl_deck * tgt){ if(!(f->flags & FSL_CX_F_MANIFEST_CACHE)) return false; static const unsigned cacheLen = (int)(sizeof(fsl_mcache_empty.aAge) /sizeof(fsl_mcache_empty.aAge[0])); unsigned i; assert(cacheLen == (unsigned)(sizeof(fsl_mcache_empty.decks) /sizeof(fsl_mcache_empty.decks[0]))); for(i=0; i<cacheLen; ++i){ |
︙ | ︙ | |||
17182 17183 17184 17185 17186 17187 17188 | #define SFREE(X) fsl_deck_clean_string(m, &m->X) #define SLIST(X) fsl_list_clear(&m->X, fsl_list_v_card_string_free, m) #define CBUF(X) fsl_buffer_clear(&m->X) static void fsl_deck_clean_string(fsl_deck *m, char **member){ fsl_deck_free_string(m, *member); *member = 0; } | | < | | | | | | | | | | | | | | | | | | | | | 17066 17067 17068 17069 17070 17071 17072 17073 17074 17075 17076 17077 17078 17079 17080 17081 17082 17083 17084 17085 17086 17087 17088 17089 17090 17091 17092 17093 17094 17095 17096 17097 17098 17099 17100 17101 17102 17103 17104 17105 17106 17107 17108 17109 17110 17111 17112 17113 17114 17115 17116 17117 17118 17119 17120 17121 17122 17123 17124 17125 17126 17127 17128 17129 17130 17131 17132 17133 17134 17135 17136 17137 17138 17139 17140 17141 17142 17143 17144 17145 17146 17147 17148 17149 17150 17151 17152 | #define SFREE(X) fsl_deck_clean_string(m, &m->X) #define SLIST(X) fsl_list_clear(&m->X, fsl_list_v_card_string_free, m) #define CBUF(X) fsl_buffer_clear(&m->X) static void fsl_deck_clean_string(fsl_deck *m, char **member){ fsl_deck_free_string(m, *member); *member = 0; } static void fsl_deck_clean_version(fsl_deck *const m){ m->rid = 0; } static void fsl_deck_clean_A(fsl_deck *const m){ SFREE(A.name); SFREE(A.tgt); SFREE(A.src); } static void fsl_deck_clean_B(fsl_deck * const m){ if(m->B.baseline){ assert(!m->B.baseline->B.uuid && "Baselines cannot have a B-card. API misuse?"); fsl_deck_finalize(m->B.baseline); m->B.baseline = NULL; } SFREE(B.uuid); } static void fsl_deck_clean_C(fsl_deck * const m){ fsl_deck_clean_string(m, &m->C); } static void fsl_deck_clean_E(fsl_deck * const m){ fsl_deck_clean_string(m, &m->E.uuid); m->E = fsl_deck_empty.E; } static void fsl_deck_clean_F(fsl_deck * const m){ if(m->F.list){ fsl_card_F_list_finalize(&m->F); m->F = fsl_deck_empty.F; } } static void fsl_deck_clean_G(fsl_deck * const m){ fsl_deck_clean_string(m, &m->G); } static void fsl_deck_clean_H(fsl_deck * const m){ fsl_deck_clean_string(m, &m->H); } static void fsl_deck_clean_I(fsl_deck * const m){ fsl_deck_clean_string(m, &m->I); } static void fsl_deck_clean_J(fsl_deck * const m, bool alsoListMem){ fsl_card_J_list_free(&m->J, alsoListMem); } static void fsl_deck_clean_K(fsl_deck * const m){ fsl_deck_clean_string(m, &m->K); } static void fsl_deck_clean_L(fsl_deck * const m){ fsl_deck_clean_string(m, &m->L); } static void fsl_deck_clean_M(fsl_deck * const m){ SLIST(M); } static void fsl_deck_clean_N(fsl_deck * const m){ fsl_deck_clean_string(m, &m->N); } static void fsl_deck_clean_P(fsl_deck * const m){ fsl_list_clear(&m->P, fsl_list_v_card_string_free, m); } static void fsl_deck_clean_Q(fsl_deck * const m){ fsl_list_clear(&m->Q, fsl_list_v_card_Q_free, NULL); } static void fsl_deck_clean_R(fsl_deck * const m){ fsl_deck_clean_string(m, &m->R); } static void fsl_deck_clean_T(fsl_deck * const m){ fsl_list_clear(&m->T, fsl_list_v_card_T_free, NULL); } static void fsl_deck_clean_U(fsl_deck * const m){ fsl_deck_clean_string(m, &m->U); } static void fsl_deck_clean_W(fsl_deck * const m){ CBUF(W); } void fsl_deck_clean2(fsl_deck * const m, fsl_buffer * const xferBuf){ if(!m) return; fsl_deck_clean_version(m); fsl_deck_clean_A(m); fsl_deck_clean_B(m); fsl_deck_clean_C(m); m->D = 0.0; fsl_deck_clean_E(m); |
︙ | ︙ | |||
17298 17299 17300 17301 17302 17303 17304 | m->f = f; } } #undef CBUF #undef SFREE #undef SLIST | | | | 17181 17182 17183 17184 17185 17186 17187 17188 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 | m->f = f; } } #undef CBUF #undef SFREE #undef SLIST void fsl_deck_clean(fsl_deck * const m){ fsl_deck_clean2(m, NULL); } void fsl_deck_finalize(fsl_deck * const m){ void const * allocStamp; if(!m) return; allocStamp = m->allocStamp; fsl_deck_clean(m); if(allocStamp == &fsl_deck_empty){ fsl_free(m); }else{ |
︙ | ︙ | |||
17454 17455 17456 17457 17458 17459 17460 | calculate and verify. Manifest #1 has an empty file list and an R-card with a constant (repo/manifest-independent) hash (d41d8cd98f00b204e9800998ecf8427e, the initial MD5 hash state). | | < < < | 17337 17338 17339 17340 17341 17342 17343 17344 17345 17346 17347 17348 17349 17350 17351 | calculate and verify. Manifest #1 has an empty file list and an R-card with a constant (repo/manifest-independent) hash (d41d8cd98f00b204e9800998ecf8427e, the initial MD5 hash state). R-card calculation is runtime-configurable option. */ NEED(D,d->D > 0); NEED(C,d->C); NEED(U,d->U); #if 0 /* It turns out that because the R-card is optional, we can have a legal manifest with no F-cards. */ |
︙ | ︙ | |||
17512 17513 17514 17515 17516 17517 17518 | case FSL_SATYPE_INVALID: default: assert(!"Invalid fsl_satype_e."); return 0; } #undef NEED } | < | 17392 17393 17394 17395 17396 17397 17398 17399 17400 17401 17402 17403 17404 17405 | case FSL_SATYPE_INVALID: default: assert(!"Invalid fsl_satype_e."); return 0; } #undef NEED } char const * fsl_satype_cstr(fsl_satype_e t){ switch(t){ #define C(X) case FSL_SATYPE_##X: return #X C(ANY); C(CHECKIN); C(CLUSTER); |
︙ | ︙ | |||
17572 17573 17574 17575 17576 17577 17578 | /** If the first n bytes of the given string contain any values <=32, returns FSL_RC_SYNTAX, else returns 0. mf->f's error state is updated no error. n<0 means to use fsl_strlen() to count the length. */ | | | 17451 17452 17453 17454 17455 17456 17457 17458 17459 17460 17461 17462 17463 17464 17465 | /** If the first n bytes of the given string contain any values <=32, returns FSL_RC_SYNTAX, else returns 0. mf->f's error state is updated no error. n<0 means to use fsl_strlen() to count the length. */ static int fsl_deck_strcheck_ctrl_chars(fsl_deck * const mf, char cardName, char const * v, fsl_int_t n){ const char * z = v; int rc = 0; if(v && n<0) n = fsl_strlen(v); for( ; v && z < v+n; ++z ){ if(*z <= 32){ rc = fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "Invalid character in %c-card.", cardName); |
︙ | ︙ | |||
17600 17601 17602 17603 17604 17605 17606 | SHA3, or MD5 hash value and it is validated against fsl_validate16(value,valLen), returning FSL_RC_SYNTAX if that check fails. In debug builds, the expected ranges are assert()ed. If value is NULL then it is removed from the card instead (semantically freed), *mfMember is set to NULL, and 0 is returned. */ | | | 17479 17480 17481 17482 17483 17484 17485 17486 17487 17488 17489 17490 17491 17492 17493 | SHA3, or MD5 hash value and it is validated against fsl_validate16(value,valLen), returning FSL_RC_SYNTAX if that check fails. In debug builds, the expected ranges are assert()ed. If value is NULL then it is removed from the card instead (semantically freed), *mfMember is set to NULL, and 0 is returned. */ static int fsl_deck_sethex_impl( fsl_deck * const mf, fsl_uuid_cstr value, char letter, fsl_size_t assertLen, char ** mfMember ){ assert(mf); assert( value ? (assertLen==FSL_STRLEN_SHA1 || assertLen==FSL_STRLEN_K256 || assertLen==FSL_STRLEN_MD5) |
︙ | ︙ | |||
17631 17632 17633 17634 17635 17636 17637 | return *mfMember ? 0 : FSL_RC_OOM; } } /** Implements fsl_set_set_XXX() where XXX is a fsl_buffer member of fsl_deck. */ | | | | 17510 17511 17512 17513 17514 17515 17516 17517 17518 17519 17520 17521 17522 17523 17524 17525 17526 17527 17528 17529 17530 17531 17532 17533 17534 17535 17536 17537 17538 17539 | return *mfMember ? 0 : FSL_RC_OOM; } } /** Implements fsl_set_set_XXX() where XXX is a fsl_buffer member of fsl_deck. */ static int fsl_deck_b_setuffer_impl( fsl_deck * const mf, char const * value, fsl_int_t valLen, char letter, fsl_buffer * buf){ assert(mf); if(!fsl_deck_check_type(mf,letter)) return mf->f->error.code; else if(valLen<0) valLen = (fsl_int_t)fsl_strlen(value); buf->used = 0; if(value && (valLen>0)){ return fsl_buffer_append( buf, value, valLen ); }else{ if(buf->mem) buf->mem[0] = 0; return 0; } } int fsl_deck_B_set( fsl_deck * const mf, fsl_uuid_cstr uuidBaseline){ if(!mf) return FSL_RC_MISUSE; else{ int const bLen = uuidBaseline ? fsl_is_uuid(uuidBaseline) : 0; if(uuidBaseline && !bLen){ return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "Invalid B-card value: %s", uuidBaseline); } |
︙ | ︙ | |||
17668 17669 17670 17671 17672 17673 17674 | } /** Internal impl for card setters which consist of a simple (char *) member. Replaces and frees any prior value. Passing NULL for the 4th argument unsets the given card (assigns NULL to it). */ | | | | | | | | | | | > > > > > > | > > > > > > > > | < < | > > > | > > > > > > > > > > > | | | 17547 17548 17549 17550 17551 17552 17553 17554 17555 17556 17557 17558 17559 17560 17561 17562 17563 17564 17565 17566 17567 17568 17569 17570 17571 17572 17573 17574 17575 17576 17577 17578 17579 17580 17581 17582 17583 17584 17585 17586 17587 17588 17589 17590 17591 17592 17593 17594 17595 17596 17597 17598 17599 17600 17601 17602 17603 17604 17605 17606 17607 17608 17609 17610 17611 17612 17613 17614 17615 17616 17617 17618 17619 17620 17621 17622 17623 17624 17625 17626 17627 17628 17629 17630 17631 17632 17633 17634 17635 17636 17637 17638 17639 17640 17641 17642 17643 17644 17645 17646 17647 17648 17649 17650 17651 17652 17653 17654 17655 17656 17657 17658 17659 17660 17661 17662 17663 17664 17665 17666 17667 17668 17669 17670 17671 17672 17673 17674 17675 17676 17677 17678 17679 17680 17681 17682 17683 17684 17685 17686 17687 17688 17689 17690 17691 17692 17693 17694 17695 17696 17697 17698 17699 17700 17701 17702 17703 | } /** Internal impl for card setters which consist of a simple (char *) member. Replaces and frees any prior value. Passing NULL for the 4th argument unsets the given card (assigns NULL to it). */ static int fsl_deck_set_string( fsl_deck * const mf, char letter, char ** member, char const * v, fsl_int_t n ){ if(!fsl_deck_check_type(mf, letter)) return mf->f->error.code; fsl_deck_free_string(mf, *member); *member = v ? fsl_strndup(v, n) : NULL; if(v && !*member) return FSL_RC_OOM; else return 0; } int fsl_deck_C_set( fsl_deck * const mf, char const * v, fsl_int_t n){ return fsl_deck_set_string( mf, 'C', &mf->C, v, n ); } int fsl_deck_G_set( fsl_deck * const mf, fsl_uuid_cstr uuid){ int const uLen = fsl_is_uuid(uuid); return uLen ? fsl_deck_sethex_impl(mf, uuid, 'G', uLen, &mf->G) : FSL_RC_SYNTAX; } int fsl_deck_H_set( fsl_deck * const mf, char const * v, fsl_int_t n){ if(v && mf->I) return FSL_RC_SYNTAX; return fsl_deck_set_string( mf, 'H', &mf->H, v, n ); } int fsl_deck_I_set( fsl_deck * const mf, fsl_uuid_cstr uuid){ if(uuid && mf->H) return FSL_RC_SYNTAX; int const uLen = uuid ? fsl_is_uuid(uuid) : 0; return fsl_deck_sethex_impl(mf, uuid, 'I', uLen, &mf->I); } int fsl_deck_J_add( fsl_deck * const mf, char isAppend, char const * field, char const * value){ if(!field) return FSL_RC_MISUSE; else if(!*field) return FSL_RC_SYNTAX; else if(!fsl_deck_check_type(mf,'J')) return mf->f->error.code; else{ int rc; fsl_card_J * cp = fsl_card_J_malloc(isAppend, field, value); if(!cp) rc = FSL_RC_OOM; else if( 0 != (rc = fsl_list_append(&mf->J, cp))){ fsl_card_J_free(cp); } return rc; } } int fsl_deck_K_set( fsl_deck * const mf, fsl_uuid_cstr uuid){ int const uLen = fsl_is_uuid(uuid); return uLen ? fsl_deck_sethex_impl(mf, uuid, 'K', uLen, &mf->K) : FSL_RC_SYNTAX; } int fsl_deck_L_set( fsl_deck * const mf, char const * v, fsl_int_t n){ return mf ? fsl_deck_set_string(mf, 'L', &mf->L, v, n) : FSL_RC_SYNTAX; } int fsl_deck_M_add( fsl_deck * const mf, char const *uuid){ int rc; char * dupe; int const uLen = uuid ? fsl_is_uuid(uuid) : 0; if(!uuid) return FSL_RC_MISUSE; else if(!fsl_deck_check_type(mf, 'M')) return mf->f->error.code; else if(!uLen) return FSL_RC_SYNTAX; dupe = fsl_strndup(uuid, uLen); if(!dupe) rc = FSL_RC_OOM; else{ rc = fsl_list_append( &mf->M, dupe ); if(rc){ fsl_free(dupe); } } return rc; } int fsl_deck_N_set( fsl_deck * const mf, char const * v, fsl_int_t n){ int rc = 0; if(v && n!=0){ if(n<0) n = fsl_strlen(v); rc = fsl_deck_strcheck_ctrl_chars(mf, 'N', v, n); } return rc ? rc : fsl_deck_set_string( mf, 'N', &mf->N, v, n ); } /** Adds either parentUuid or takeParentUuid to mf->P. ONE of those must e non-NULL and the other must be NULL. If takeParentUuid is not NULL then ownership of it is transfered to this function regardless of success or failure. */ static int fsl__deck_P_add_impl( fsl_deck * const mf, fsl_uuid_cstr parentUuid, fsl_uuid_str takeParentUuid){ if(!fsl_deck_check_type(mf, 'P')){ fsl_free(takeParentUuid); return mf->f->error.code; } int rc; char * dupe; fsl_uuid_cstr z = parentUuid ? parentUuid : takeParentUuid; assert(parentUuid ? !takeParentUuid : !!takeParentUuid); int const uLen = fsl_is_uuid(z); if(!uLen){ fsl_free(takeParentUuid); return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "Invalid UUID for P-card."); } dupe = takeParentUuid ? takeParentUuid : fsl_strndup(parentUuid, uLen); if(!dupe) rc = FSL_RC_OOM; else{ rc = fsl_list_append( &mf->P, dupe ); if(rc){ fsl_free(dupe); } } return rc; } int fsl_deck_P_add(fsl_deck * const mf, char const *parentUuid){ return fsl__deck_P_add_impl(mf, parentUuid, NULL); } int fsl_deck_P_add_rid( fsl_deck * const mf, fsl_id_t rid ){ fsl_uuid_str pU = fsl_rid_to_uuid(mf->f, rid); return pU ? fsl__deck_P_add_impl(mf, NULL, pU) : mf->f->error.code; } fsl_id_t fsl_deck_P_get_id(fsl_deck * const d, int index){ if(!d->f) return -1; else if(index>(int)d->P.used) return 0; else return fsl_uuid_to_rid(d->f, (char const *)d->P.list[index]); } int fsl_deck_Q_add( fsl_deck * const mf, int type, fsl_uuid_cstr target, fsl_uuid_cstr baseline ){ if(!target) return FSL_RC_MISUSE; else if(!fsl_deck_check_type(mf,'Q')) return mf->f->error.code; else if(!type || !fsl_is_uuid(target) || (baseline && !fsl_is_uuid(baseline))) return FSL_RC_SYNTAX; else{ |
︙ | ︙ | |||
17829 17830 17831 17832 17833 17834 17835 | if(FSL_CARD_F_LIST_NEEDS_SORT & li->flags){ qsort(li->list, li->used, sizeof(fsl_card_F), fsl_card_F_cmp ); li->flags &= ~FSL_CARD_F_LIST_NEEDS_SORT; } } | | | | | | 17734 17735 17736 17737 17738 17739 17740 17741 17742 17743 17744 17745 17746 17747 17748 17749 17750 17751 17752 17753 17754 17755 17756 17757 17758 17759 17760 17761 17762 17763 17764 17765 17766 17767 17768 17769 17770 17771 17772 17773 17774 17775 17776 17777 17778 17779 17780 | if(FSL_CARD_F_LIST_NEEDS_SORT & li->flags){ qsort(li->list, li->used, sizeof(fsl_card_F), fsl_card_F_cmp ); li->flags &= ~FSL_CARD_F_LIST_NEEDS_SORT; } } static void fsl_deck_F_sort(fsl_deck * const mf){ fsl_card_F_list_sort(&mf->F); } int fsl_card_F_compare_name( fsl_card_F const * const lhs, fsl_card_F const * const rhs){ return (lhs == rhs) ? 0 : fsl_card_F_cmp( lhs, rhs ); } int fsl_deck_R_set( fsl_deck * const mf, fsl_uuid_cstr md5){ return mf ? fsl_deck_sethex_impl(mf, md5, 'R', md5 ? FSL_STRLEN_MD5 : 0, &mf->R) : FSL_RC_MISUSE; } int fsl_deck_R_calc2(fsl_deck * const mf, char ** tgt){ fsl_cx * const f = mf->f; char const * theHash = 0; char hex[FSL_STRLEN_MD5+1]; if(!f) return FSL_RC_MISUSE; else if(!fsl_needs_repo(f)){ return FSL_RC_NOT_A_REPO; }else if(!fsl_deck_check_type(mf,'R')) { assert(mf->f->error.code); return mf->f->error.code; }else if(!mf->F.used){ theHash = FSL_MD5_INITIAL_HASH; /* fall through and set hash */ }else{ int rc = 0; fsl_card_F const * fc; fsl_id_t fileRid; fsl_buffer * const buf = &f->fileContent; unsigned char digest[16]; fsl_md5_cx md5 = fsl_md5_cx_empty; enum { NumBufSize = 40 }; char numBuf[NumBufSize] = {0}; assert(!buf->used && "Misuse of f->fileContent buffer."); rc = fsl_deck_F_rewind(mf); if(rc) goto end; |
︙ | ︙ | |||
17912 17913 17914 17915 17916 17917 17918 | rc = fsl_content_get(f, fileRid, buf); if(rc){ goto end; } numBuf[0] = 0; fsl_snprintf(numBuf, NumBufSize, " %"FSL_SIZE_T_PFMT"\n", | | | 17817 17818 17819 17820 17821 17822 17823 17824 17825 17826 17827 17828 17829 17830 17831 | rc = fsl_content_get(f, fileRid, buf); if(rc){ goto end; } numBuf[0] = 0; fsl_snprintf(numBuf, NumBufSize, " %"FSL_SIZE_T_PFMT"\n", buf->used); fsl_md5_update_cstr(&md5, numBuf, -1); fsl_md5_update_buffer(&md5, buf); } if(!rc){ fsl_md5_final(&md5, digest); fsl_md5_digest_to_base16(digest, hex); } |
︙ | ︙ | |||
17939 17940 17941 17942 17943 17944 17945 | }else{ char * x = fsl_strdup(theHash); if(x) *tgt = x; return x ? 0 : FSL_RC_OOM; } } | | | | 17844 17845 17846 17847 17848 17849 17850 17851 17852 17853 17854 17855 17856 17857 17858 17859 17860 17861 17862 17863 17864 17865 | }else{ char * x = fsl_strdup(theHash); if(x) *tgt = x; return x ? 0 : FSL_RC_OOM; } } int fsl_deck_R_calc(fsl_deck * const mf){ char R[FSL_STRLEN_MD5+1] = {0}; char * r = R; const int rc = fsl_deck_R_calc2(mf, &r); return rc ? rc : fsl_deck_R_set(mf, r); } int fsl_deck_T_add2( fsl_deck * const mf, fsl_card_T * t){ if(!t) return FSL_RC_MISUSE; else if(!fsl_deck_check_type(mf, 'T')){ return mf->f->error.code; }else if(FSL_SATYPE_CONTROL==mf->type && NULL==t->uuid){ return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "CONTROL artifacts may not have " "self-referential tags."); |
︙ | ︙ | |||
17978 17979 17980 17981 17982 17983 17984 | }else if(t->uuid && !fsl_is_uuid(t->uuid)){ return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "Invalid UUID in tag."); } return fsl_list_append(&mf->T, t); } | | | 17883 17884 17885 17886 17887 17888 17889 17890 17891 17892 17893 17894 17895 17896 17897 | }else if(t->uuid && !fsl_is_uuid(t->uuid)){ return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "Invalid UUID in tag."); } return fsl_list_append(&mf->T, t); } int fsl_deck_T_add( fsl_deck * const mf, fsl_tagtype_e tagType, char const * uuid, char const * name, char const * value){ if(!name) return FSL_RC_MISUSE; else if(!fsl_deck_check_type(mf, 'T')) return mf->f->error.code; else if(!*name || (uuid &&!fsl_is_uuid(uuid))) return FSL_RC_SYNTAX; else switch(tagType){ case FSL_TAGTYPE_CANCEL: |
︙ | ︙ | |||
18039 18040 18041 18042 18043 18044 18045 | }else{ rc = FSL_RC_OOM; } } return rc; } | | | | | 17944 17945 17946 17947 17948 17949 17950 17951 17952 17953 17954 17955 17956 17957 17958 17959 17960 17961 17962 17963 17964 17965 17966 | }else{ rc = FSL_RC_OOM; } } return rc; } int fsl_deck_U_set( fsl_deck * const mf, char const * v){ return fsl_deck_set_string( mf, 'U', &mf->U, v, -1 ); } int fsl_deck_W_set( fsl_deck * const mf, char const * v, fsl_int_t n){ return fsl_deck_b_setuffer_impl(mf, v, n, 'W', &mf->W); } int fsl_deck_A_set( fsl_deck * const mf, char const * name, char const * tgt, char const * uuidSrc ){ int const uLen = (uuidSrc && *uuidSrc) ? fsl_is_uuid(uuidSrc) : 0; if(!name || !tgt) return FSL_RC_MISUSE; else if(!fsl_deck_check_type(mf, 'A')) return mf->f->error.code; else if(!*tgt){ return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, |
︙ | ︙ | |||
18082 18083 18084 18085 18086 18087 18088 | /* Leave mf->A.tgt/name for downstream cleanup. */; } return rc; } } | | | | | 17987 17988 17989 17990 17991 17992 17993 17994 17995 17996 17997 17998 17999 18000 18001 18002 18003 18004 18005 18006 18007 18008 18009 18010 18011 18012 18013 18014 18015 18016 18017 18018 18019 18020 18021 18022 18023 18024 18025 18026 18027 18028 18029 | /* Leave mf->A.tgt/name for downstream cleanup. */; } return rc; } } int fsl_deck_D_set( fsl_deck * const mf, double date){ if(date<0) return FSL_RC_RANGE; else if(date>0 && !fsl_deck_check_type(mf, 'D')){ return mf->f->error.code; }else{ mf->D = date; return 0; } } int fsl_deck_E_set( fsl_deck * const mf, double date, char const * uuid){ int const uLen = uuid ? fsl_is_uuid(uuid) : 0; if(!mf || !uLen) return FSL_RC_MISUSE; else if(date<=0){ return fsl_cx_err_set(mf->f, FSL_RC_RANGE, "Invalid date value for E card."); }else if(!uLen){ return fsl_cx_err_set(mf->f, FSL_RC_RANGE, "Invalid UUID for E card."); } else{ mf->E.julian = date; fsl_deck_free_string(mf, mf->E.uuid); mf->E.uuid = fsl_strndup(uuid, uLen); return mf->E.uuid ? 0 : FSL_RC_OOM; } } int fsl_deck_F_add( fsl_deck * const mf, char const * name, char const * uuid, fsl_fileperm_e perms, char const * oldName){ int const uLen = uuid ? fsl_is_uuid(uuid) : 0; if(!mf || !name) return FSL_RC_MISUSE; else if(!uuid && !mf->B.uuid){ return fsl_cx_err_set(mf->f, FSL_RC_MISUSE, |
︙ | ︙ | |||
19557 19558 19559 19560 19561 19562 19563 | int fsl_deck_F_set( fsl_deck * d, char const * zName, char const * uuid, fsl_fileperm_e perms, char const * priorName){ uint32_t fcNdx = 0; fsl_card_F * fc = 0; | | | 19462 19463 19464 19465 19466 19467 19468 19469 19470 19471 19472 19473 19474 19475 19476 | int fsl_deck_F_set( fsl_deck * d, char const * zName, char const * uuid, fsl_fileperm_e perms, char const * priorName){ uint32_t fcNdx = 0; fsl_card_F * fc = 0; if(d->rid>0){ return fsl_cx_err_set(d->f, FSL_RC_MISUSE, "%s() cannot be applied to a saved deck.", __func__); }else if(!fsl_deck_check_type(d, 'F')){ return d->f->error.code; } fc = fsl_deck_F_seek_base(d, zName, &fcNdx); |
︙ | ︙ | |||
19613 19614 19615 19616 19617 19618 19619 | fsl_buffer const * src, fsl_fileperm_e perm, char const * priorName){ fsl_uuid_str zHash = 0; fsl_id_t rid = 0; fsl_id_t prevRid = 0; int rc = 0; | | | 19518 19519 19520 19521 19522 19523 19524 19525 19526 19527 19528 19529 19530 19531 19532 | fsl_buffer const * src, fsl_fileperm_e perm, char const * priorName){ fsl_uuid_str zHash = 0; fsl_id_t rid = 0; fsl_id_t prevRid = 0; int rc = 0; if(d->rid>0){ return fsl_cx_err_set(d->f, FSL_RC_MISUSE, "%s() cannot be applied to a saved deck.", __func__); }else if(!fsl_cx_transaction_level(d->f)){ return fsl_cx_err_set(d->f, FSL_RC_MISUSE, "%s() requires that a transaction is active.", __func__); |
︙ | ︙ | |||
19683 19684 19685 19686 19687 19688 19689 | case 'U': fsl_deck_clean_U(d); break; case 'W': fsl_deck_clean_W(d); break; default: break; } } } | | | > | > > > | > > > > > > > > | < > | 19588 19589 19590 19591 19592 19593 19594 19595 19596 19597 19598 19599 19600 19601 19602 19603 19604 19605 19606 19607 19608 19609 19610 19611 19612 19613 19614 19615 19616 19617 19618 19619 19620 19621 | case 'U': fsl_deck_clean_U(d); break; case 'W': fsl_deck_clean_W(d); break; default: break; } } } int fsl_deck_derive(fsl_deck * const d){ int rc = 0; if(d->rid<=0) return FSL_RC_MISUSE; assert(d->f); if(FSL_SATYPE_CHECKIN!=d->type) return FSL_RC_TYPE; fsl_deck_clean_P(d); { fsl_uuid_str pUuid = fsl_rid_to_uuid(d->f, d->rid); if(pUuid){ rc = fsl_list_append(&d->P, pUuid); if(rc){ assert(NULL==d->P.list); fsl_free(pUuid); } }else{ assert(d->f->error.code); rc = d->f->error.code; } if(rc) return rc; } d->rid = 0; fsl_deck_clean_cards(d, "ACDEGHIJKLMNQRTUW"); while(d->B.uuid){ /* This is a delta manifest. Convert this deck into a baseline by build a new, complete F-card list. */ fsl_card_F const * fc; fsl_card_F_list flist = fsl_card_F_list_empty; |
︙ | ︙ | |||
19810 19811 19812 19813 19814 19815 19816 | (1) Make this routine a no-op if pParent is a merge parent and the primary parent is a phantom. (2) Invoke this routine recursively for merge-parents if pParent is the primary parent. */ | | | 19727 19728 19729 19730 19731 19732 19733 19734 19735 19736 19737 19738 19739 19740 19741 | (1) Make this routine a no-op if pParent is a merge parent and the primary parent is a phantom. (2) Invoke this routine recursively for merge-parents if pParent is the primary parent. */ static int fsl_mlink_add( fsl_cx * const f, fsl_id_t pmid, fsl_deck /*const*/ * pParent, fsl_id_t cid, fsl_deck /*const*/ * pChild, bool isPrimary){ fsl_buffer otherContent = fsl_buffer_empty; fsl_id_t otherRid; fsl_size_t i = 0; int rc = 0; |
︙ | ︙ | |||
19844 19845 19846 19847 19848 19849 19850 | otherRid = cid; }else{ pParent = &dOther; otherRid = pmid; } if(otherRid && !fsl_cx_mcache_search(f, otherRid, &dOther)){ | | | 19761 19762 19763 19764 19765 19766 19767 19768 19769 19770 19771 19772 19773 19774 19775 | otherRid = cid; }else{ pParent = &dOther; otherRid = pmid; } if(otherRid && !fsl_cx_mcache_search(f, otherRid, &dOther)){ rc = fsl_content_get(f, otherRid, &otherContent); if(rc){ /* fossil(1) simply ignores errors here and returns. We'll ignore the phantom case because (1) erroring out here would be bad and (2) fossil does so. The exact implications of doing so are unclear, though. */ if(FSL_RC_PHANTOM==rc){ rc = 0; |
︙ | ︙ | |||
20818 20819 20820 20821 20822 20823 20824 | to do all updates to the event (timeline) table via these crosslinkers and perform the core, UI-agnostic, crosslinking bits in the internal fsl_deck_crosslink_XXX() functions. That should allow clients to override how the timeline is updated without requiring them to understand the rest of the required schema updates. */ | | | 20735 20736 20737 20738 20739 20740 20741 20742 20743 20744 20745 20746 20747 20748 20749 | to do all updates to the event (timeline) table via these crosslinkers and perform the core, UI-agnostic, crosslinking bits in the internal fsl_deck_crosslink_XXX() functions. That should allow clients to override how the timeline is updated without requiring them to understand the rest of the required schema updates. */ int fsl_cx_install_timeline_crosslinkers(fsl_cx * const f){ int rc; assert(!f->xlinkers.used); assert(!f->xlinkers.list); rc = fsl_xlink_listener(f, "fsl/attachment/timeline", fsl_deck_xlink_f_attachment, 0); if(!rc) rc = fsl_xlink_listener(f, "fsl/checkin/timeline", fsl_deck_xlink_f_checkin, 0); |
︙ | ︙ | |||
21137 21138 21139 21140 21141 21142 21143 | rc = fsl_cx_err_set(f, FSL_RC_NYI, "MISSING: a huge block of TICKET stuff from " "manifest_crosslink(). It requires infrastructure " "libfossil does not yet have."); return rc; } | | | | | 21054 21055 21056 21057 21058 21059 21060 21061 21062 21063 21064 21065 21066 21067 21068 21069 21070 21071 21072 21073 21074 21075 21076 21077 21078 21079 21080 21081 21082 21083 21084 21085 | rc = fsl_cx_err_set(f, FSL_RC_NYI, "MISSING: a huge block of TICKET stuff from " "manifest_crosslink(). It requires infrastructure " "libfossil does not yet have."); return rc; } int fsl_deck_crosslink_one( fsl_deck * const d ){ int rc; if(!d->f) return FSL_RC_MISUSE; rc = fsl_crosslink_begin(d->f); if(rc) return rc; rc = fsl_deck_crosslink(d); if(rc){ fsl_db_transaction_rollback(fsl_cx_db_repo(d->f)) /* Ignore result - keep existing error state */; d->f->cache.isCrosslinking = false; }else{ assert(fsl_db_transaction_level(fsl_cx_db_repo(d->f))); rc = fsl_crosslink_end(d->f); } return rc; } int fsl_deck_crosslink( fsl_deck /* const */ * const d ){ int rc = 0; fsl_cx * f = d->f; fsl_db * db = f ? fsl_needs_repo(f) : NULL; fsl_id_t parentid = 0; fsl_int_t const rid = d->rid; if(!f) return FSL_RC_MISUSE; else if(rid<=0){ |
︙ | ︙ | |||
21176 21177 21178 21179 21180 21181 21182 | return d->f->error.code; }else if(f->cache.xlinkClustersOnly && (FSL_SATYPE_CLUSTER!=d->type)){ /* is it okay to bypass the registered xlink listeners here? The use case called for by this is not yet implemented in libfossil. */ return 0; } | > > | | | | | | < < < | 21093 21094 21095 21096 21097 21098 21099 21100 21101 21102 21103 21104 21105 21106 21107 21108 21109 21110 21111 21112 21113 21114 | return d->f->error.code; }else if(f->cache.xlinkClustersOnly && (FSL_SATYPE_CLUSTER!=d->type)){ /* is it okay to bypass the registered xlink listeners here? The use case called for by this is not yet implemented in libfossil. */ return 0; } rc = fsl_db_transaction_begin(db); if(rc) goto end; if(FSL_SATYPE_CHECKIN==d->type && d->B.uuid && !d->B.baseline){ rc = fsl_deck_baseline_fetch(d); if(rc) goto end; assert(d->B.baseline); } switch(d->type){ case FSL_SATYPE_CHECKIN: rc = fsl_deck_crosslink_checkin(d, &parentid); break; case FSL_SATYPE_CLUSTER: rc = fsl_deck_crosslink_cluster(d); break; |
︙ | ︙ | |||
21242 21243 21244 21245 21246 21247 21248 | rc = xl->f( d, xl->state ); } if(rc){ assert(xl); if(!f->error.code){ fsl_cx_err_set(f, rc, "Crosslink callback handler " "'%s' failed with code %d (%s) for " | | | | 21158 21159 21160 21161 21162 21163 21164 21165 21166 21167 21168 21169 21170 21171 21172 21173 21174 | rc = xl->f( d, xl->state ); } if(rc){ assert(xl); if(!f->error.code){ fsl_cx_err_set(f, rc, "Crosslink callback handler " "'%s' failed with code %d (%s) for " "artifact RID #%" FSL_ID_T_PFMT ".", xl->name, rc, fsl_rc_cstr(rc), d->rid); } } }/*end crosslink callbacks*/ end: if(!rc){ rc = fsl_db_transaction_end(db, false); }else{ |
︙ | ︙ | |||
21454 21455 21456 21457 21458 21459 21460 | || z[n-34]!=' ' || !fsl_validate16((const char *)z+n-33, FSL_STRLEN_MD5)){ return 0; } return 1; } | | < | 21370 21371 21372 21373 21374 21375 21376 21377 21378 21379 21380 21381 21382 21383 21384 21385 21386 21387 21388 21389 21390 21391 21392 21393 21394 21395 21396 21397 21398 21399 21400 21401 21402 | || z[n-34]!=' ' || !fsl_validate16((const char *)z+n-33, FSL_STRLEN_MD5)){ return 0; } return 1; } int fsl_deck_parse2(fsl_deck * const d, fsl_buffer * const src, fsl_id_t rid){ #ifdef ERROR # undef ERROR #endif #define ERROR(RC,MSG) do{ rc = (RC); zMsg = (MSG); goto bailout; } while(0) #define SYNTAX(MSG) ERROR(rc ? rc : FSL_RC_SYNTAX,MSG) bool isRepeat = 0/* , hasSelfRefTag = 0 */; int rc = 0; fsl_src x = fsl_src_empty; char const * zMsg = NULL; fsl_id_bag * seen; char cType = 0, cPrevType = 0; unsigned char * z = src ? src->mem : NULL; fsl_size_t tokLen = 0; unsigned char * token; fsl_size_t n = z ? src->used : 0; unsigned char * uuid; double ts; int cardCount = 0; fsl_cx * f; fsl_error * err; int stealBuf = 0 /* gets incremented if we need to steal src->mem. */; unsigned nSelfTag = 0 /* number of T cards which refer to '*' (this artifact). */; unsigned nSimpleTag = 0 /* number of T cards with "+" prefix */; /* lettersSeen keeps track of the card letters we have seen so that |
︙ | ︙ | |||
21503 21504 21505 21506 21507 21508 21509 | : "Zero-length input"); } else if(rid<0){ return fsl_error_set(err, FSL_RC_RANGE, "Invalid (negative) RID %"FSL_ID_T_PFMT " for fsl_deck_parse()", rid); } | < | < | | | | | | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 21444 | : "Zero-length input"); } else if(rid<0){ return fsl_error_set(err, FSL_RC_RANGE, "Invalid (negative) RID %"FSL_ID_T_PFMT " for fsl_deck_parse()", rid); } seen = &f->cache.mfSeen; if((0==rid) || fsl_id_bag_contains(seen,rid)){ isRepeat = 1; }else{ isRepeat = 0; rc = fsl_id_bag_insert(seen, rid); if(rc){ assert(FSL_RC_OOM==rc); return rc; } } fsl_deck_clean(d); fsl_deck_init(f, d, FSL_SATYPE_ANY); /* Verify that the first few characters of the artifact look like a control artifact. */ if( !fsl_might_be_artifact(src) ){ ERROR(FSL_RC_SYNTAX, "Content does not look like " |
︙ | ︙ | |||
21574 21575 21576 21577 21578 21579 21580 21581 21582 21583 21584 21585 21586 21587 | z = (unsigned char *)zz; } /* Verify the Z card */ if( fsl_deck_verify_Z_card(z, n) < 0 ){ ERROR(FSL_RC_CONSISTENCY, "Z-card checksum mismatch"); } /* Reminder: parsing modifies the input (to simplify the tokenization/parsing). As of mid-201403, we recycle as much as possible from the source buffer and take over ownership _if_ we do so. | > > > | 21457 21458 21459 21460 21461 21462 21463 21464 21465 21466 21467 21468 21469 21470 21471 21472 21473 | z = (unsigned char *)zz; } /* Verify the Z card */ if( fsl_deck_verify_Z_card(z, n) < 0 ){ ERROR(FSL_RC_CONSISTENCY, "Z-card checksum mismatch"); } /* legacy: not yet clear if we need this: if( !isRepeat ) g.parseCnt[0]++; */ /* Reminder: parsing modifies the input (to simplify the tokenization/parsing). As of mid-201403, we recycle as much as possible from the source buffer and take over ownership _if_ we do so. |
︙ | ︙ | |||
22277 22278 22279 22280 22281 22282 22283 22284 22285 22286 22287 22288 22289 22290 | if(stealBuf>0){ /* We stashed something which points to src->mem, so we need to steal that memory. */ d->content = *src; *src = fsl_buffer_empty; } d->F.flags &= ~FSL_CARD_F_LIST_NEEDS_SORT/*we know all cards were read in order*/; return 0; bailout: if(stealBuf>0){ d->content = *src; *src = fsl_buffer_empty; | > | 22163 22164 22165 22166 22167 22168 22169 22170 22171 22172 22173 22174 22175 22176 22177 | if(stealBuf>0){ /* We stashed something which points to src->mem, so we need to steal that memory. */ d->content = *src; *src = fsl_buffer_empty; } d->rid = rid; d->F.flags &= ~FSL_CARD_F_LIST_NEEDS_SORT/*we know all cards were read in order*/; return 0; bailout: if(stealBuf>0){ d->content = *src; *src = fsl_buffer_empty; |
︙ | ︙ | |||
22301 22302 22303 22304 22305 22306 22307 | #undef TOKEN_EXISTS #undef TOKEN_UUID #undef TOKEN_MD5 #undef TOKEN #undef ERROR } | | | < > | | 22188 22189 22190 22191 22192 22193 22194 22195 22196 22197 22198 22199 22200 22201 22202 22203 22204 22205 22206 22207 22208 22209 22210 22211 22212 22213 22214 22215 22216 22217 22218 22219 22220 22221 | #undef TOKEN_EXISTS #undef TOKEN_UUID #undef TOKEN_MD5 #undef TOKEN #undef ERROR } int fsl_deck_parse(fsl_deck * const d, fsl_buffer * const src){ return fsl_deck_parse2(d, src, 0); } int fsl_deck_load_rid( fsl_cx * const f, fsl_deck * const d, fsl_id_t rid, fsl_satype_e type ){ fsl_buffer buf = fsl_buffer_empty; int rc = 0; if(0==rid) rid = f->ckout.rid; if(rid<0){ return fsl_cx_err_set(f, FSL_RC_RANGE, "Invalid RID for fsl_deck_load_rid(): " "%"FSL_ID_T_PFMT, rid); } fsl_deck_clean(d); d->f = f; if(fsl_cx_mcache_search(f, rid, d)){ assert(d->f); if(type!=FSL_SATYPE_ANY && type!=d->type){ rc = fsl_cx_err_set(f, FSL_RC_TYPE, "Unexpected match of RID #%" FSL_ID_T_PFMT " " "to a different artifact type (%d) " "than requested (%d).", d->type, type); fsl_cx_mcache_insert(f, d); assert(!d->f); }else{ |
︙ | ︙ | |||
22352 22353 22354 22355 22356 22357 22358 | it might be worth considering as a small optimization later on. */ d->type = type /* may help parsing fail more quickly if it's not the type we want.*/; #endif rc = fsl_deck_parse(d, &buf); if(!rc){ | < < < | | | > > | | 22239 22240 22241 22242 22243 22244 22245 22246 22247 22248 22249 22250 22251 22252 22253 22254 22255 22256 22257 22258 22259 22260 22261 22262 22263 22264 22265 22266 22267 22268 22269 22270 | it might be worth considering as a small optimization later on. */ d->type = type /* may help parsing fail more quickly if it's not the type we want.*/; #endif rc = fsl_deck_parse(d, &buf); if(!rc){ if( type!=FSL_SATYPE_ANY && d->type!=type ){ rc = fsl_cx_err_set(f, FSL_RC_TYPE, "RID %"FSL_ID_T_PFMT" is of type %s, " "but the caller requested type %s.", rid, fsl_satype_cstr(d->type), fsl_satype_cstr(type)); }else if(d->B.uuid ){ rc = fsl_cx_update_seen_delta_mf(f); } } end: if(0==rc) d->rid = rid; fsl_buffer_clear(&buf); return rc; } int fsl_deck_load_sym( fsl_cx * const f, fsl_deck * const d, char const * symbolicName, fsl_satype_e type ){ if(!symbolicName || !d) return FSL_RC_MISUSE; else{ fsl_id_t vid = 0; int rc = fsl_sym_to_rid(f, symbolicName, type, &vid); if(!rc){ assert(vid>0); |
︙ | ︙ | |||
22535 22536 22537 22538 22539 22540 22541 | } } return 0; } #undef FCARD } | | | | | | < | > | > | < > | > > | > > > > > > > > | | | | > | | | | | < < | | | < < < < < < < < < < < < < < < < < < < | < < | < < < < < < | | | | | | 22421 22422 22423 22424 22425 22426 22427 22428 22429 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 22460 22461 22462 22463 22464 22465 22466 22467 22468 22469 22470 22471 22472 22473 22474 22475 22476 22477 22478 22479 22480 22481 22482 22483 22484 22485 22486 22487 22488 22489 22490 22491 22492 22493 22494 22495 22496 22497 22498 22499 22500 22501 22502 22503 22504 22505 22506 22507 22508 22509 22510 22511 22512 22513 22514 22515 22516 | } } return 0; } #undef FCARD } int fsl_deck_save( fsl_deck * const d, bool isPrivate ){ int rc; fsl_cx * const f = d->f; fsl_db * const db = fsl_needs_repo(f); fsl_buffer * const buf = &d->f->fileContent; fsl_id_t newRid = 0; bool const oldPrivate = f->cache.markPrivate; if(!f || !d ) return FSL_RC_MISUSE; else if(!db) return FSL_RC_NOT_A_REPO; if(d->rid>0){ #if 1 return 0; #else return fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS, "Cannot re-save an existing deck, as that could " "lead to inconsistent data."); #endif } if(d->B.uuid && fsl_repo_forbids_delta_manifests(f)){ return fsl_cx_err_set(f, FSL_RC_ACCESS, "This deck is a delta manifest, but this " "repository has disallowed those via the " "forbid-delta-manifests config option."); } fsl_cx_err_reset(f); fsl_size_t const reserveSize = (1024 * 10) + (50 * d->T.used) + (50 * d->M.used) + (120 * d->F.used) + d->W.used; fsl_buffer_reuse(buf); rc = fsl_buffer_reserve(buf, reserveSize); if(0==rc) rc = fsl_deck_output(d, fsl_output_f_buffer, buf); if(rc){ fsl_buffer_reuse(buf); return rc; } rc = fsl_db_transaction_begin(db); if(rc){ fsl_buffer_reuse(buf); return rc; } if(0){ MARKER(("Saving deck:\n%s\n", fsl_buffer_cstr(buf))); } /* Starting here, don't return, use (goto end) instead. */ f->cache.markPrivate = isPrivate; { rc = fsl_content_put_ex(f, buf, NULL, 0, 0U, isPrivate, &newRid); if(rc) goto end; assert(newRid>0); } /* We need d->rid for crosslinking purposes, but will unset it on error because its value will no longer be in the db after rollback... */ d->rid = newRid; #if 0 /* Something to consider: has a parent, deltify the parent. The branch operation does this, but it is not yet clear whether that is a general pattern for manifests. */ if(d->P.used){ fsl_id_t pid; assert(FSL_SATYPE_CHECKIN == d->type); pid = fsl_uuid_to_rid(f, (char const *)d->P.list[0]); if(pid>0){ rc = fsl_content_deltify(f, pid, d->rid, 0); if(rc) goto end; } } #endif if(FSL_SATYPE_WIKI==d->type){ /* Analog to fossil's wiki.c:wiki_put(): */ /* MISSING: fossil's wiki.c:wiki_put() handles the moderation bits. */ if(d->P.used){ fsl_id_t const pid = fsl_deck_P_get_id(d, 0); |
︙ | ︙ | |||
22655 22656 22657 22658 22659 22660 22661 | rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "Did not find matching RID " "for P-card[0] (%s).", (char const *)d->P.list[0]); } goto end; } | < | 22524 22525 22526 22527 22528 22529 22530 22531 22532 22533 22534 22535 22536 22537 | rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "Did not find matching RID " "for P-card[0] (%s).", (char const *)d->P.list[0]); } goto end; } rc = fsl_content_deltify(f, pid, d->rid, 0); if(rc) goto end; } rc = fsl_db_exec_multi(db, "INSERT OR IGNORE INTO unsent " "VALUES(%"FSL_ID_T_PFMT");" "INSERT OR IGNORE INTO unclustered " |
︙ | ︙ | |||
22677 22678 22679 22680 22681 22682 22683 | rc = f->cache.isCrosslinking ? fsl_deck_crosslink(d) : fsl_deck_crosslink_one(d); end: f->cache.markPrivate = oldPrivate; | | < < < | < | | 22545 22546 22547 22548 22549 22550 22551 22552 22553 22554 22555 22556 22557 22558 22559 22560 22561 22562 22563 22564 22565 22566 22567 | rc = f->cache.isCrosslinking ? fsl_deck_crosslink(d) : fsl_deck_crosslink_one(d); end: f->cache.markPrivate = oldPrivate; if(!rc) rc = fsl_db_transaction_end(db, 0); else fsl_db_transaction_end(db, 1); if(rc){ d->rid = 0 /* this blob.rid will be lost after rollback */; if(!f->error.code && db->error.code){ rc = fsl_cx_uplift_db_error(f, db); } } fsl_buffer_reuse(buf); return rc; } int fsl_crosslink_end(fsl_cx * f){ int rc = 0; fsl_db * db = fsl_cx_db_repo(f); fsl_stmt q = fsl_stmt_empty; |
︙ | ︙ | |||
28982 28983 28984 28985 28986 28987 28988 | fsl_filename_free(zMbcs); return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0; } } | | | | | | | | | 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 | fsl_filename_free(zMbcs); return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0; } } void fsl_pathfinder_clear(fsl_pathfinder * const pf){ if(pf){ 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 = |
︙ | ︙ | |||
29475 29476 29477 29478 29479 29480 29481 | } z += i+1; } return rc; } | | > | | | | 29339 29340 29341 29342 29343 29344 29345 29346 29347 29348 29349 29350 29351 29352 29353 29354 29355 29356 29357 29358 29359 29360 29361 29362 29363 29364 29365 29366 29367 29368 29369 29370 29371 29372 29373 29374 29375 29376 29377 29378 29379 29380 29381 | } z += i+1; } return rc; } char const * fsl_glob_list_matches( fsl_list const * const globList, char const * zNeedle ){ if(!zNeedle || !*zNeedle || !globList->used) return NULL; else{ char const * glob; fsl_size_t i = 0; for( ; i < globList->used; ++i){ glob = (char const *)globList->list[i]; if( fsl_str_glob( glob, zNeedle ) ) return glob; } return NULL; } } int fsl_glob_list_append( fsl_list * tgt, char const * zGlob ){ if(!tgt || !zGlob || !*zGlob) return FSL_RC_MISUSE; else{ char * cp = fsl_strdup(zGlob); int rc = cp ? 0 : FSL_RC_OOM; if(!rc){ rc = fsl_list_append(tgt, cp); if(rc) fsl_free(cp); } return rc; } } void fsl_glob_list_clear( fsl_list * const globList ){ if(globList) fsl_list_visit_free(globList, 1); } /* end of file glob.c */ /* start of file io.c */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ |
︙ | ︙ | |||
29544 29545 29546 29547 29548 29549 29550 | */ static int fsl_output_f_fsl_output( void * state, void const * s, fsl_size_t n ){ return fsl_output( (fsl_cx *)state, s, n ); } | | | | < | | | < | < < | < < | > < < | 29409 29410 29411 29412 29413 29414 29415 29416 29417 29418 29419 29420 29421 29422 29423 29424 29425 29426 29427 29428 29429 29430 29431 29432 29433 29434 29435 29436 29437 29438 29439 29440 29441 29442 29443 29444 29445 29446 29447 29448 29449 29450 29451 29452 29453 29454 29455 29456 29457 29458 29459 29460 29461 29462 29463 29464 29465 29466 29467 29468 | */ static int fsl_output_f_fsl_output( void * state, void const * s, fsl_size_t n ){ return fsl_output( (fsl_cx *)state, s, n ); } int fsl_outputfv( fsl_cx * const f, char const * fmt, va_list args ){ if(!f || !fmt) return FSL_RC_MISUSE; else if(!*fmt) return FSL_RC_RANGE; return fsl_appendfv( fsl_output_f_fsl_output, f, fmt, args ); } int fsl_outputf( fsl_cx * const f, char const * fmt, ... ){ if(!f || !fmt) return FSL_RC_MISUSE; else if(!*fmt) return FSL_RC_RANGE; else{ int rc; va_list args; va_start(args,fmt); rc = fsl_outputfv( f, fmt, args ); va_end(args); return rc; } } int fsl_output( fsl_cx * const cx, void const * const src, fsl_size_t n ){ if(!n || !cx->output.out) return 0; else return cx->output.out( cx->output.state, src, n ); } int fsl_flush( fsl_cx * const f ){ return f->output.flush ? f->output.flush(f->output.state) : 0; } int fsl_flush_f_FILE(void * _FILE){ return fflush((FILE*)_FILE) ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0; } int fsl_output_f_FILE( void * state, void const * src, fsl_size_t n ){ if(!n) return 0; else return (1 == fwrite(src, n, 1, state ? (FILE*)state : stdout)) ? 0 : FSL_RC_IO; } int fsl_input_f_FILE( void * state, void * dest, fsl_size_t * n ){ if( !*n ) return FSL_RC_RANGE; 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 ){ |
︙ | ︙ | |||
29697 29698 29699 29700 29701 29702 29703 | " WHERE tagid=%d AND rid=plink.pid),'trunk')" " == coalesce((SELECT value FROM tagxref" " WHERE tagid=%d AND rid=plink.cid),'trunk')", FSL_TAGID_BRANCH, FSL_TAGID_BRANCH ); } | | | | | 29555 29556 29557 29558 29559 29560 29561 29562 29563 29564 29565 29566 29567 29568 29569 29570 29571 29572 29573 29574 29575 29576 29577 29578 29579 29580 29581 29582 29583 29584 29585 29586 | " WHERE tagid=%d AND rid=plink.pid),'trunk')" " == coalesce((SELECT value FROM tagxref" " WHERE tagid=%d AND rid=plink.cid),'trunk')", FSL_TAGID_BRANCH, FSL_TAGID_BRANCH ); } fsl_int_t fsl_count_nonbranch_children(fsl_cx * const f, fsl_id_t rid){ int32_t rv = 0; int rc; fsl_db * const db = fsl_cx_db_repo(f); if(!db || !db->dbh || (rid<=0)) return -1; rc = fsl_db_get_int32(db, &rv, "SELECT count(*) FROM plink " "WHERE pid=%"FSL_ID_T_PFMT" " "AND isprim " "AND coalesce((SELECT value FROM tagxref " "WHERE tagid=%d AND rid=plink.pid), 'trunk')" "=coalesce((SELECT value FROM tagxref " "WHERE tagid=%d AND rid=plink.cid), 'trunk')", rid, FSL_TAGID_BRANCH, FSL_TAGID_BRANCH); return rc ? -2 : rv; } bool fsl_rid_is_leaf(fsl_cx * const f, fsl_id_t rid){ int rv = -1; int rc; fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; fsl_stmt * st = NULL; if(!db || !db->dbh || (rid<=0)) return 0; rc = fsl_db_prepare_cached(db, &st, "SELECT 1 FROM plink " |
︙ | ︙ | |||
29754 29755 29756 29757 29758 29759 29760 29761 29762 29763 29764 29765 29766 29767 | break; } fsl_stmt_cached_yield(st); assert(0==rv || 1==rv); } return rc ? 0 : (rv==1); } int fsl_repo_leaf_check(fsl_cx * f, fsl_id_t rid){ fsl_db * const db = f ? fsl_cx_db_repo(f) : NULL; if(!db || !db->dbh) return FSL_RC_MISUSE; else if(rid<=0) return FSL_RC_RANGE; else { int rc = 0; | > > > > > > > > > | 29612 29613 29614 29615 29616 29617 29618 29619 29620 29621 29622 29623 29624 29625 29626 29627 29628 29629 29630 29631 29632 29633 29634 | break; } fsl_stmt_cached_yield(st); assert(0==rv || 1==rv); } return rc ? 0 : (rv==1); } bool fsl_rid_is_version(fsl_cx * const f, fsl_id_t rid){ fsl_db * const db = fsl_cx_db_repo(f); if(!db) return false; return 1==fsl_db_g_int32(db, 0, "SELECT 1 FROM event " "WHERE objid=%" FSL_ID_T_PFMT " AND type='ci'", rid); } int fsl_repo_leaf_check(fsl_cx * f, fsl_id_t rid){ fsl_db * const db = f ? fsl_cx_db_repo(f) : NULL; if(!db || !db->dbh) return FSL_RC_MISUSE; else if(rid<=0) return FSL_RC_RANGE; else { int rc = 0; |
︙ | ︙ | |||
31029 31030 31031 31032 31033 31034 31035 | assert((fsl_int_t)fsl_strlen(mergeMarker[1])==mmLen); assert((fsl_int_t)fsl_strlen(mergeMarker[2])==mmLen); assert((fsl_int_t)fsl_strlen(mergeMarker[3])==mmLen); } return mmLen; } | | > > > | | 30896 30897 30898 30899 30900 30901 30902 30903 30904 30905 30906 30907 30908 30909 30910 30911 30912 30913 30914 | assert((fsl_int_t)fsl_strlen(mergeMarker[1])==mmLen); assert((fsl_int_t)fsl_strlen(mergeMarker[2])==mmLen); assert((fsl_int_t)fsl_strlen(mergeMarker[3])==mmLen); } return mmLen; } int fsl_buffer_merge3(fsl_buffer * const pPivot, fsl_buffer * const pV1, fsl_buffer * const pV2, fsl_buffer * const pOut, unsigned int * const conflictCount){ int *aC1 = 0; /* Changes from pPivot to pV1 */ int *aC2 = 0; /* Changes from pPivot to pV2 */ int i1, i2; /* Index into aC1[] and aC2[] */ int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ int limit1, limit2; /* Sizes of aC1[] and aC2[] */ int rc = 0; unsigned int nConflict = 0; /* Number of merge conflicts seen so far */ |
︙ | ︙ | |||
32054 32055 32056 32057 32058 32059 32060 | }else{ fsl_free( rvv ); } } return rc; } | | | > | 31924 31925 31926 31927 31928 31929 31930 31931 31932 31933 31934 31935 31936 31937 31938 31939 31940 31941 31942 31943 31944 | }else{ fsl_free( rvv ); } } return rc; } fsl_id_t fsl_uuid_to_rid( fsl_cx * const f, char const * uuid ){ fsl_db * const db = fsl_needs_repo(f); fsl_size_t const uuidLen = (uuid && db) ? fsl_strlen(uuid) : 0; if(!uuid || !uuidLen) return -1; else if(!db){ /* f's error state has already been set */ assert(FSL_RC_NOT_A_REPO == f->error.code); return -2; } else if(!fsl_validate16(uuid, uuidLen)){ fsl_cx_err_set(f, FSL_RC_RANGE, "Invalid UUID (prefix): %s", uuid); return -3; } else if(uuidLen>FSL_STRLEN_K256){ |
︙ | ︙ | |||
32215 32216 32217 32218 32219 32220 32221 | *rv = fnid; }else if(db->error.code){ fsl_cx_uplift_db_error(f, db); } return rc; } | | > | 32086 32087 32088 32089 32090 32091 32092 32093 32094 32095 32096 32097 32098 32099 32100 32101 | *rv = fnid; }else if(db->error.code){ fsl_cx_uplift_db_error(f, db); } return rc; } int fsl_delta_src_id( fsl_cx * const f, fsl_id_t deltaRid, fsl_id_t * rv ){ fsl_db * const dbR = fsl_cx_db_repo(f); if(!rv) return FSL_RC_MISUSE; else if(deltaRid<=0) return FSL_RC_RANGE; else if(!dbR) return FSL_RC_NOT_A_REPO; else { int rc; fsl_stmt * q = NULL; |
︙ | ︙ | |||
32266 32267 32268 32269 32270 32271 32272 | : FSL_RC_RANGE; } void fsl_repo_verify_cancel( fsl_cx * f ){ fsl_id_bag_clear(&f->cache.toVerify); } | | | 32138 32139 32140 32141 32142 32143 32144 32145 32146 32147 32148 32149 32150 32151 32152 | : FSL_RC_RANGE; } void fsl_repo_verify_cancel( fsl_cx * f ){ fsl_id_bag_clear(&f->cache.toVerify); } int fsl_rid_to_uuid2(fsl_cx * const f, fsl_id_t rid, fsl_buffer *uuid){ fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; if(!f || !db || (rid<=0)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, "fsl_rid_to_uuid2() requires " "an opened repository and a " "positive RID value. rid=%" FSL_ID_T_PFMT, rid); |
︙ | ︙ | |||
32298 32299 32300 32301 32302 32303 32304 | rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "No blob found for rid %" FSL_ID_T_PFMT ".", rid); } } fsl_stmt_cached_yield(st); if(rc && !f->error.code){ | | | > > > | | | 32170 32171 32172 32173 32174 32175 32176 32177 32178 32179 32180 32181 32182 32183 32184 32185 32186 32187 32188 32189 32190 32191 32192 32193 32194 32195 32196 32197 32198 32199 32200 32201 | rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "No blob found for rid %" FSL_ID_T_PFMT ".", rid); } } fsl_stmt_cached_yield(st); if(rc && !f->error.code){ if(db->error.code){ fsl_cx_uplift_db_error(f, db); }else{ fsl_cx_err_set(f, rc, NULL); } } } return rc; } } fsl_uuid_str fsl_rid_to_uuid(fsl_cx * const f, fsl_id_t rid){ fsl_buffer uuid = fsl_buffer_empty; fsl_rid_to_uuid2(f, rid, &uuid); return fsl_buffer_take(&uuid); } fsl_uuid_str fsl_rid_to_artifact_uuid(fsl_cx * const f, fsl_id_t rid, fsl_satype_e type){ fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; if(!f || !db || (rid<=0)) return NULL; else{ char * rv = NULL; fsl_stmt * st = NULL; int rc; rc = fsl_db_prepare_cached(db, &st, |
︙ | ︙ | |||
33235 33236 33237 33238 33239 33240 33241 33242 33243 33244 33245 33246 33247 33248 | * * 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){ 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, | > | 33110 33111 33112 33113 33114 33115 33116 33117 33118 33119 33120 33121 33122 33123 33124 | * * 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, |
︙ | ︙ | |||
33500 33501 33502 33503 33504 33505 33506 | case FSL_SATYPE_TECHNOTE: return 'e'; default: assert(!"Internal misuse of fsl_satype_letter()"); return 0; } } | | | | 33376 33377 33378 33379 33380 33381 33382 33383 33384 33385 33386 33387 33388 33389 33390 33391 | case FSL_SATYPE_TECHNOTE: return 'e'; default: assert(!"Internal misuse of fsl_satype_letter()"); return 0; } } int fsl_search_doc_touch(fsl_cx * const f, fsl_satype_e saType, fsl_id_t rid, const char * docName){ if(!fsl_search_ndx_exists(f) || fsl_content_is_private(f, rid)) return 0; char zType[2] = {0,0}; zType[0] = fsl_satype_letter(saType); #if 0 /* See MARKER() call in the #else block */ assert(*zType); return *zType ? 0 : FSL_RC_MISUSE; |
︙ | ︙ | |||
37241 37242 37243 37244 37245 37246 37247 | rc = fsl_deck_F_add(&deck, fc->name, fc->uuid, fc->perm, fc->priorName); if(rc) goto end; } rc = fsl_deck_U_set(&deck, user); if(rc) goto end; | | | 37117 37118 37119 37120 37121 37122 37123 37124 37125 37126 37127 37128 37129 37130 37131 | rc = fsl_deck_F_add(&deck, fc->name, fc->uuid, fc->perm, fc->priorName); if(rc) goto end; } rc = fsl_deck_U_set(&deck, user); if(rc) goto end; rc = fsl_deck_P_add_rid(&deck, parent.rid); if(rc) goto end; if(opt->comment && *opt->comment){ rc = fsl_deck_C_set(&deck, opt->comment, -1); }else{ fsl_buffer c = fsl_buffer_empty; rc = fsl_buffer_appendf(&c, "Created branch [%s].", opt->name); |
︙ | ︙ | |||
37373 37374 37375 37376 37377 37378 37379 | */ /************************************************************************* This file implements ticket-related parts of the library. */ #include <assert.h> #include <string.h> /* memcmp() */ | | | < | | | > | < < < < < < | 37249 37250 37251 37252 37253 37254 37255 37256 37257 37258 37259 37260 37261 37262 37263 37264 37265 37266 37267 37268 37269 37270 37271 37272 37273 37274 37275 | */ /************************************************************************* This file implements ticket-related parts of the library. */ #include <assert.h> #include <string.h> /* memcmp() */ int fsl_cx_ticket_create_table(fsl_cx * const f){ fsl_db * const db = fsl_needs_repo(f); int rc; if(!db) return FSL_RC_NOT_A_REPO; rc = fsl_cx_exec_multi(f, "DROP TABLE IF EXISTS ticket;" "DROP TABLE IF EXISTS ticketchng;" ); if(!rc){ fsl_buffer * const buf = &f->fileContent; fsl_buffer_reuse(buf); rc = fsl_cx_schema_ticket(f, buf); if(!rc) rc = fsl_cx_exec_multi(f, "%b", buf); } return rc; } static int fsl_tkt_field_id(fsl_list const * jli, const char *zFieldName){ int i; fsl_card_J const * jc; |
︙ | ︙ | |||
37819 37820 37821 37822 37823 37824 37825 | /* Only for debugging */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) | | | > < | | > | > > | 37689 37690 37691 37692 37693 37694 37695 37696 37697 37698 37699 37700 37701 37702 37703 37704 37705 37706 37707 37708 37709 37710 37711 37712 37713 37714 37715 37716 37717 37718 37719 37720 37721 37722 37723 37724 37725 37726 37727 37728 37729 37730 37731 37732 37733 37734 37735 37736 37737 37738 37739 37740 37741 37742 37743 37744 37745 37746 37747 37748 37749 37750 37751 37752 37753 37754 37755 37756 37757 37758 37759 37760 37761 37762 37763 37764 37765 37766 | /* 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_vfile_load(fsl_cx * const f, fsl_id_t vid, bool clearOtherVersions, uint32_t * missingCount){ fsl_db * dbC = f ? fsl_needs_ckout(f) : NULL; fsl_db * dbR = dbC ? fsl_needs_repo(f) : NULL; fsl_deck d = fsl_deck_empty; fsl_stmt qIns = fsl_stmt_empty; fsl_stmt qRid = fsl_stmt_empty; int rc; bool alreadyHad; fsl_card_F const * fc; 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 merge tracking in the checkin process. */ rc = fsl_vfile_unload_except(f, vid); if(rc) goto end; } if(alreadyHad){ /* Already done. */ rc = 0; goto end; } assert(0==rc); if(0==vid){ /* This is either misuse or an empty/initial repo with no checkins. Let's assume the latter, since that's what triggered the addition of this check. */ goto end; } rc = fsl_deck_load_rid(f, &d, vid, FSL_SATYPE_CHECKIN); if(rc) goto end; assert(d.rid==vid); rc = fsl_deck_F_rewind(&d); if(rc) goto end; rc = fsl_db_prepare(dbC, &qIns, "INSERT INTO vfile" "(vid,isexe,islink,rid,mrid,pathname,mhash) " "VALUES(:vid,:isexe,:islink,:id,:id,:name,null)" /* 2021-10-15: why do we not set mtime here? */); if(rc) goto end; rc = fsl_db_prepare(dbR, &qRid, "SELECT rid,size FROM blob WHERE uuid=?"); if(rc) goto end; rc = fsl_stmt_bind_id_name(&qIns, ":vid", vid); while( !rc && !(rc=fsl_deck_F_next(&d, &fc)) && fc){ fsl_id_t rid; int64_t size; assert(fc->uuid && "We should never see F-card deletions " "via fsl_deck_F_next()"); if(fsl_uuid_is_shunned(f,fc->uuid)) continue; rc = fsl_stmt_bind_text(&qRid, 1, fc->uuid, -1, 0); if(rc) break; rc = fsl_stmt_step(&qRid); if(FSL_RC_STEP_ROW==rc){ rid = fsl_stmt_g_id(&qRid,0); size = fsl_stmt_g_int64(&qRid,1); |
︙ | ︙ | |||
37921 37922 37923 37924 37925 37926 37927 | fsl_stmt_finalize(&qIns); fsl_stmt_finalize(&qRid); /* Update f->ckout state and some db bits we need when changing the checkout. */ if(!rc && vid>0){ if(!alreadyHad){ assert(d.rid>0); | < | | | | 37794 37795 37796 37797 37798 37799 37800 37801 37802 37803 37804 37805 37806 37807 37808 37809 37810 37811 37812 37813 37814 37815 37816 37817 37818 37819 37820 37821 37822 37823 37824 37825 37826 37827 37828 37829 37830 37831 37832 37833 37834 37835 | fsl_stmt_finalize(&qIns); fsl_stmt_finalize(&qRid); /* Update f->ckout state and some db bits we need 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; } static int fsl_vfile_unload_impl(fsl_cx * const f, fsl_id_t vid, bool oneVersion){ fsl_db * const db = fsl_needs_ckout(f); if(!db) return FSL_RC_NOT_A_CKOUT; if(vid<=0) vid = f->ckout.rid; int const rc = fsl_db_exec(db, "DELETE FROM vfile " "WHERE vid%s%" FSL_ID_T_PFMT " /* %s() */", oneVersion ? "=" : "<>", vid, __func__); return rc ? fsl_cx_uplift_db_error2(f, db, rc) : 0; } int fsl_vfile_unload(fsl_cx * const f, fsl_id_t vid){ return fsl_vfile_unload_impl(f, vid, true); } int fsl_vfile_unload_except(fsl_cx * const f, fsl_id_t vid){ return fsl_vfile_unload_impl(f, vid, false); } /** Internal code de-duplifier for places which need to re-check a file's hash in order to be sure whether it was really modified. hashLen must be the length of the previous (db-side) hash |
︙ | ︙ | |||
37985 37986 37987 37988 37989 37990 37991 | rc = fsl_cx_err_set(f, rc, "Error %s while hashing file: %s", fsl_rc_cstr(rc), zName); } return rc; } | | | 37857 37858 37859 37860 37861 37862 37863 37864 37865 37866 37867 37868 37869 37870 37871 | rc = fsl_cx_err_set(f, rc, "Error %s while hashing file: %s", fsl_rc_cstr(rc), zName); } return rc; } int fsl_vfile_changes_scan(fsl_cx * const f, fsl_id_t vid, unsigned cksigFlags){ fsl_stmt * stUpdate = NULL; fsl_stmt q = fsl_stmt_empty; int rc = 0; fsl_db * const db = fsl_needs_ckout(f); fsl_fstat fst = fsl_fstat_empty; fsl_size_t rootLen; fsl_buffer * fileCksum = fsl_cx_scratchpad(f); |
︙ | ︙ | |||
38664 38665 38666 38667 38668 38669 38670 | fsl_id_t origName; /* Original name of file */ fsl_id_t curName; /* Current name of the file */ fsl_id_t newName; /* Name of file in next version */ NameChange *pNext; /* List of all name changes */ }; const NameChange NameChange_empty = {0,0,0,0}; | | | | 38536 38537 38538 38539 38540 38541 38542 38543 38544 38545 38546 38547 38548 38549 38550 38551 | fsl_id_t origName; /* Original name of file */ fsl_id_t curName; /* Current name of the file */ fsl_id_t newName; /* Name of file in next version */ NameChange *pNext; /* List of all name changes */ }; const NameChange NameChange_empty = {0,0,0,0}; int fsl__find_filename_changes( fsl_cx * const f, fsl_id_t iFrom, /* Ancestor check-in */ fsl_id_t iTo, /* Recent check-in */ bool revOK, /* OK to move backwards (child->parent) if true */ uint32_t *pnChng, /* Number of name changes along the path */ fsl_id_t **aiChng /* Name changes */ ){ fsl_vpath_node *p = 0; /* For looping over path from iFrom to iTo */ |
︙ | ︙ | |||
39528 39529 39530 39531 39532 39533 39534 | rc = fsl_deck_F_foreach( &mf, fsl_card_F_visitor_zip, &zs); if(!rc){ if(!zs.z.entryCount){ if(rootDir && *rootDir){ rc = fsl_zip_file_add( &zs.z, rootDir, NULL, FSL_FILE_PERM_REGULAR ); }else{ rc = fsl_cx_err_set(f, FSL_RC_RANGE, "Cowardly refusing to create " | | | | 39400 39401 39402 39403 39404 39405 39406 39407 39408 39409 39410 39411 39412 39413 39414 39415 | rc = fsl_deck_F_foreach( &mf, fsl_card_F_visitor_zip, &zs); if(!rc){ if(!zs.z.entryCount){ if(rootDir && *rootDir){ rc = fsl_zip_file_add( &zs.z, rootDir, NULL, FSL_FILE_PERM_REGULAR ); }else{ rc = fsl_cx_err_set(f, FSL_RC_RANGE, "Cowardly refusing to create " "empty ZIP file for repo version [%s].", sym); } if(rc) goto end; } } /** Always write he manifest files to the zip, regardless of |
︙ | ︙ |
Changes to src/libfossil.h.
︙ | ︙ | |||
131 132 133 134 135 136 137 | The warning does not apply to strongly-typed arguments, e.g. variables of the proper type, so long as the format specifier string matches the argument type. For example: | < > < > < > < > | 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 | The warning does not apply to strongly-typed arguments, e.g. variables of the proper type, so long as the format specifier string matches the argument type. For example: ``` fsl_size_t sz = 3; fsl_fprintf( stdout, "%"FSL_SIZE_T_PFMT" %"FSL_SIZE_T_PFMT\n", sz, // OK! 3 // BAD! See below... ); ``` The "fix" is to cast the literal 3 to a fsl_size_t resp. the type appropriate for the format specifier. That ensures that there is no (or much less ;) confusion when va_arg() extracts arguments from the variadic array. Reminders to self: ``` int i = 0; f_out(("#%d: %"FSL_ID_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_ID_T_PFMT"\n", ++i, 1, 2, 3)); f_out(("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n", ++i, (fsl_size_t)1, (fsl_id_t)2, (fsl_size_t)3)); // This one is the (generally) problematic case: f_out(("#%d: %"FSL_SIZE_T_PFMT" %"FSL_ID_T_PFMT" %"FSL_SIZE_T_PFMT"\n", ++i, 1, 2, 3)); ``` The above was Tested with gcc, clang, tcc on a 32-bit linux platform (it has not been problematic on 64-bit builds!). The above problem was reproduced on all compiler combinations i tried. Current code (20130824) seems to be behaving well as long as callers always cast to help variadic arg handling DTRT. */ |
︙ | ︙ | |||
242 243 244 245 246 247 248 | /* end of file ../include/fossil-scm/fossil-config.h */ /* start of file ../include/fossil-scm/fossil.h */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ #if !defined(ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED) #define ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED /* | | | | | | < < > > | < < | | | | | 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 | /* end of file ../include/fossil-scm/fossil-config.h */ /* start of file ../include/fossil-scm/fossil.h */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ #if !defined(ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED) #define ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED /* 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). */ /** @file fossil.h This file is the primary header for the public APIs. It includes various other header files. They are split into multiple headers primarily becuase my lower-end systems choke on syntax-highlighting them and browsing their (large) Doxygen output. */ /* fossil-config.h MUST be included first so we can set some portability flags and config-dependent typedefs! */ #endif /* ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED */ /* end of file ../include/fossil-scm/fossil.h */ /* start of file ../include/fossil-scm/fossil-util.h */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
︙ | ︙ | |||
516 517 518 519 520 521 522 | /** Arbitrary context-dependent state. */ void * state; /** Finalizer for this->state. If used, it should be called like: | < > < > | 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 | /** Arbitrary context-dependent state. */ void * state; /** Finalizer for this->state. If used, it should be called like: ``` this->finalize.f( this->finalize.state, this->state ); ``` After which this->state must be treated as if it has been free(3)'d. */ fsl_finalizer finalize; }; |
︙ | ︙ | |||
582 583 584 585 586 587 588 | /** fsl_output_f() implementation which requires state to be a (fsl_cx*) to which this routine simply redirects the output via fsl_output(). Is a no-op (returning 0) if !n. Returns FSL_RC_MISUSE if !state or !src. */ FSL_EXPORT int fsl_output_f_fsl_cx(void * state, void const * src, fsl_size_t n ); | < | 580 581 582 583 584 585 586 587 588 589 590 591 592 593 | /** fsl_output_f() implementation which requires state to be a (fsl_cx*) to which this routine simply redirects the output via fsl_output(). Is a no-op (returning 0) if !n. Returns FSL_RC_MISUSE if !state or !src. */ FSL_EXPORT int fsl_output_f_fsl_cx(void * state, void const * src, fsl_size_t n ); /** An interface which encapsulates data for managing an output destination, primarily intended for use with fsl_output(). Why abstract it to this level? So that we can do interesting things like output to buffers, files, sockets, etc., using the core output mechanism. e.g. so script bindings can send their output |
︙ | ︙ | |||
766 767 768 769 770 771 772 | 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 | | < > < > | 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 | 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: ``` fsl_buffer kludge = *originalConstBuffer; // normally this is dangerous! rc = some_func( fsl_input_f_buffer, &kludge, ... ); assert(kludge.mem==originalConstBuffer->mem); // See notes below. // DO NOT clean up the kludge buffer. Memory belongs to the original! ``` That is ONLY (ONLY! ONLY!! ONLY!!!) legal because this routine modifies only fsl_buffer::cursor. Such a workaround is STRICLY ILLEGAL if there is ANY CHANCE WHATSOEVER that the buffer's memory will be modified, in particular if it will be resized, and such use will eventually leak and/or corrupt memory. */ |
︙ | ︙ | |||
798 799 800 801 802 803 804 | bytes than requested. Returns the result of the last call to outF() or (only if reading fails) inF(). Returns FSL_RC_MISUSE if inF or ouF are NULL. Here is an example which basically does the same thing as the cat(1) command on Unix systems: | < > < > < > < | | | | | 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 | bytes than requested. Returns the result of the last call to outF() or (only if reading fails) inF(). Returns FSL_RC_MISUSE if inF or ouF are NULL. Here is an example which basically does the same thing as the cat(1) command on Unix systems: ``` fsl_stream( fsl_input_f_FILE, stdin, fsl_output_f_FILE, stdout ); ``` Or copy a FILE to a buffer: ``` fsl_buffer myBuf = fsl_buffer_empty; rc = fsl_stream( fsl_input_f_FILE, stdin, fsl_output_f_buffer, &myBuf ); // Note that on error myBuf might be partially populated. // Eventually clean up the buffer: fsl_buffer_clear(&myBuf); ``` */ FSL_EXPORT int fsl_stream( fsl_input_f inF, void * inState, fsl_output_f outF, void * outState ); /** Consumes two input streams looking for differences. It stops reading as soon as either or both streams run out of input or a byte-level difference is found. It consumes input in chunks of an unspecified size, and after this returns the input cursor of the streams is not well-defined. i.e. the cursor probably does not point to the exact position of the difference because this level of abstraction does not allow that unless we read byte by byte. Returns 0 if both streams emit the same amount of output and that ouput is bitwise identical, otherwise it returns non-0. */ FSL_EXPORT int fsl_stream_compare( fsl_input_f in1, void * in1State, fsl_input_f in2, void * in2State ); /** A general-purpose buffer class, analog to Fossil's Blob class, but it is not called fsl_blob to avoid confusion with DB-side blobs. Buffers are used extensively in fossil to do everything from reading files to compressing artifacts to creating dynamically-formatted strings. Because they are such a pervasive low-level type, and have such a simple structure, their members (unlike most other structs in this API) may be considered public and used directly by client code (as long as |
︙ | ︙ | |||
873 874 875 876 877 878 879 | - Use fsl_buffer_reuse() to keep memory around and reset the 'used' amount to 0. Most rountines which write to buffers will re-use that memory if they can. This example demonstrates the difference between 'used' and 'capacity' (error checking reduced to assert()ions for clarity): | < > < > | > > > > | 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 | - Use fsl_buffer_reuse() to keep memory around and reset the 'used' amount to 0. Most rountines which write to buffers will re-use that memory if they can. This example demonstrates the difference between 'used' and 'capacity' (error checking reduced to assert()ions for clarity): ``` fsl_buffer b = fsl_buffer_empty; int rc = fsl_buffer_reserve(&b, 20); assert(0==rc); assert(b.capacity>=20); // it may reserve more! assert(0==b.used); rc = fsl_buffer_append(&b, "abc", 3); assert(0==rc); assert(3==b.used); assert(0==b.mem[b.used]); // API always NUL-terminates ``` Potential TODO: add an allocator member which gets internally used for allocation of the buffer member. fossil(1) uses this approach, and swaps the allocator out as needed, to support a buffer pointing to memory it does not own, e.g. a slice of another buffer or to static memory, and then (re)allocate as necessary, e.g. to switch from static memory to dynamic. That may be useful in order to effectively port over some of the memory-intensive algos such as merging. That would not affect [much of] the public API, just how the buffer internally manages the memory. Certain API members would need to specify that the memory is not owned by the blob and needs to outlive the blob, though. We could potentially implement this same thing without a new member by using the convention that a `capacity` value of 0 when `mem` is!=00 means that the pointed-to memory is owned by someone else and must be copied before modification (effectively what fossil's approach does). @see fsl_buffer_reserve() @see fsl_buffer_append() @see fsl_buffer_appendf() @see fsl_buffer_cstr() @see fsl_buffer_size() @see fsl_buffer_capacity() |
︙ | ︙ | |||
936 937 938 939 940 941 942 | is always met, and any violation of it opens the code to undefined behaviour (which is okay, just don't ever break that 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; | < | 936 937 938 939 940 941 942 943 944 945 946 947 948 949 | is always met, and any violation of it opens the code to undefined behaviour (which is okay, just don't ever break that 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). |
︙ | ︙ | |||
976 977 978 979 980 981 982 | struct fsl_error { /** Error message text is stored in this->msg.mem. The usable text part is this->msg.used bytes long. */ fsl_buffer msg; /** | | > | > | > | 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 | struct fsl_error { /** Error message text is stored in this->msg.mem. The usable text part is this->msg.used bytes long. */ fsl_buffer msg; /** Error code, generally assumed to be a fsl_rc_e value. The "non-error" code is 0. */ int code; }; /** Empty-initialized fsl_error instance, intended for const-copy initialization. */ #define fsl_error_empty_m {fsl_buffer_empty_m,0} /** Empty-initialized fsl_error instance, intended for copy initialization. */ FSL_EXPORT const fsl_error fsl_error_empty; /** Populates err with the given code and formatted string, replacing any existing state. If fmt==NULL then fsl_rc_cstr(rc) is used to get the error string. |
︙ | ︙ | |||
1006 1007 1008 1009 1010 1011 1012 | As a special case, if code==FSL_RC_OOM then fmt is ignored to avoid a memory allocation (which would presumably fail). @see fsl_error_get() @see fsl_error_clear() @see fsl_error_move() */ | | | | | | 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 | As a special case, if code==FSL_RC_OOM then fmt is ignored to avoid a memory allocation (which would presumably fail). @see fsl_error_get() @see fsl_error_clear() @see fsl_error_move() */ FSL_EXPORT int fsl_error_set( fsl_error * const err, int code, char const * fmt, ... ); /** va_list counterpart to fsl_error_set(). */ FSL_EXPORT int fsl_error_setv( fsl_error * const err, int code, char const * fmt, va_list args ); /** Fetches the error state from err. If !err it returns FSL_RC_MISUSE without side-effects, else it returns err's current error code. If str is not NULL then *str will be assigned to the raw |
︙ | ︙ | |||
1035 1036 1037 1038 1039 1040 1041 | If len is not NULL then *len will be assigned to the length of the returned string (in bytes). @see fsl_error_set() @see fsl_error_clear() @see fsl_error_move() */ | | > | | | > | | 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 | If len is not NULL then *len will be assigned to the length of the returned string (in bytes). @see fsl_error_set() @see fsl_error_clear() @see fsl_error_move() */ FSL_EXPORT int fsl_error_get( fsl_error const * const err, char const ** str, fsl_size_t * const len ); /** Frees up any resources owned by err and sets its error code to 0, but does not free err. This is harmless no-op if !err or if err holds no dynamically allocated no memory. @see fsl_error_set() @see fsl_error_get() @see fsl_error_move() @see fsl_error_reset() */ FSL_EXPORT void fsl_error_clear( fsl_error * const err ); /** Sets err->code to 0 and resets its buffer, but keeps any err->msg memory around for later re-use. @see fsl_error_clear() */ FSL_EXPORT void fsl_error_reset( fsl_error * const err ); /** Copies the error state from src to dest. If dest contains state, it is cleared/recycled by this operation. Returns 0 on success, FSL_RC_MISUSE if either argument is NULL or if (src==dest), and FSL_RC_OOM if allocation of the message string fails. As a special case, if src->code==FSL_RC_OOM, then the code is copied but the message bytes (if any) are not (under the assumption that we have no more memory). */ FSL_EXPORT int fsl_error_copy( fsl_error const * const src, fsl_error * const dest ); /** Moves the error state from one fsl_error object to another, intended as an allocation optimization when propagating error state up the API. This "uplifts" an error from the 'from' object to the 'to' object. After this returns 'to' will contain the prior error state of 'from' and 'from' will contain the old error message memory of 'to'. 'from' will be re-set to the non-error state (its buffer memory is kept intact for later reuse, though). Results are undefined if either parameter is NULL or either is not properly initialized. i.e. neither may refer to uninitialized memory. Copying fsl_error_empty at declaration-time is a simple way to ensure that instances are cleanly initialized. */ FSL_EXPORT void fsl_error_move( fsl_error * const from, fsl_error * const to ); /** Returns the given Unix Epoch timestamp value as its approximate Julian Day value. Note that the calculation does not account for leap seconds. */ FSL_EXPORT double fsl_unix_to_julian( fsl_time_t unixEpoch ); |
︙ | ︙ | |||
1224 1225 1226 1227 1228 1229 1230 | clearWhich is greater than 0 then the right buffer (2nd arg) is cleared _after_ swapping (i.e. the NEW right hand side gets cleared). If clearWhich is 0, this function behaves identically to fsl_buffer_swap(). A couple examples should clear this up: | < > < > < > < > | 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 | clearWhich is greater than 0 then the right buffer (2nd arg) is cleared _after_ swapping (i.e. the NEW right hand side gets cleared). If clearWhich is 0, this function behaves identically to fsl_buffer_swap(). A couple examples should clear this up: ``` fsl_buffer_swap_free( &b1, &b2, -1 ); ``` Swaps the contents of b1 and b2, then frees the contents of the left-side buffer (b1). ``` 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 ); |
︙ | ︙ | |||
1561 1562 1563 1564 1565 1566 1567 | 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: | < > | | | < > < > < > | > | > | < | | > | > | | > | > | | 1565 1566 1567 1568 1569 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 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 | 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, stdin ); if( rc ){ fprintf(stderr,"Error %d (%s) while filling buffer.\n", rc, fsl_rc_cstr(rc)); fsl_buffer_reserve( &buf, 0 ); return ...; } ... use the buf->mem ... ... clean up the buffer ... fsl_buffer_reserve( &buf, 0 ); ``` To take over ownership of the buffer's memory, do: ``` void * mem = buf.mem; buf = fsl_buffer_empty; ``` In which case the memory must eventually be passed to fsl_free() to free it. */ FSL_EXPORT int fsl_buffer_fill_from( fsl_buffer * const dest, fsl_input_f src, void * const state ); /** A fsl_buffer_fill_from() proxy which overwrite's dest->mem with the contents of the given FILE handler (which must be opened for read access). Returns 0 on success, after which dest->mem contains dest->used bytes of content from the input source. On error dest may be partially filled. */ 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 * 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); /** Works like fsl_appendfv(), but appends all output to a dynamically-allocated string, expanding the string as necessary to collect all formatted data. The returned NUL-terminated string is owned by the caller and it must be cleaned up using |
︙ | ︙ | |||
1777 1778 1779 1780 1781 1782 1783 | /** Returns false if s is NULL or starts with any of (0 (NUL), '0' (ASCII character zero), 'f', 'n', "off"), case-insensitively, else it returns true. */ FSL_EXPORT bool fsl_str_bool( char const * s ); | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 | /** Returns false if s is NULL or starts with any of (0 (NUL), '0' (ASCII character zero), 'f', 'n', "off"), case-insensitively, else it returns true. */ FSL_EXPORT bool fsl_str_bool( char const * s ); /** _Almost_ equivalent to fopen(3) but: - expects name to be UTF8-encoded. - If name=="-", it returns one of stdin or stdout, depending on |
︙ | ︙ | |||
1902 1903 1904 1905 1906 1907 1908 | Most printf-style specifiers work as they do in standard printf() implementations. There might be some very minor differences, but the more common format specifiers work as most developers expect them to. In addition... Current (documented) printf extensions: | < < < < < | | | | | | | | | | | | | | | | | | | > > > | | | | | | | | | | 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 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 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 | Most printf-style specifiers work as they do in standard printf() implementations. There might be some very minor differences, but the more common format specifiers work as most developers expect them to. In addition... Current (documented) printf extensions: `%s` works like conventional printf `%s` except that any precision value can be modified via the '#' flag to counts in UTF8 characters instead of bytes! That is, if an `%#.10s` argument has a byte length of 20, a precision of 10, and contains only 8 UTF8, its precision will allow it to output all 8 characters, even though they total 20 bytes. The '#' flag works this way for both width and precision. `%z` works exactly like `%s`, but takes a non-const (char *) and deletes the string (using fsl_free()) after appending it to the output. `%h` (HTML) works like `%s` but (A) does not support the '#' flag and (B) converts certain characters (namely '<' and '&') to their HTML escaped equivalents. `%t` (URL encode) works like `%h` but converts certain characters into a representation suitable for use in an HTTP URL. (e.g. ' ' gets converted to `%20`) `%T` (URL decode) does the opposite of `%t` - it decodes URL-encoded strings and outputs their decoded form. ACHTUNG: fossil(1) interprets this the same as `%t` except that it leaves '/' characters unescaped (did that change at some point? This code originally derived from that one some years ago!). It is still to be determined whether we "really need" that behaviour (we don't really need either one, seeing as the library is not CGI-centric like fossil(1) is). `%r` requires an int and renders it in "ordinal form". That is, the number 1 converts to "1st" and 398 converts to "398th". `%q` quotes a string as required for SQL. That is, '\'' characters get doubled. It does NOT included the outer quotes and NULL values get replaced by the string "(NULL)" (without quotes). See `%Q`... `%Q` works like `%q`, but includes the outer '\'' characters and NULL pointers get output as the string literal "NULL" (without quotes), i.e. an SQL NULL. If modified with `%!Q` then it instead uses double quotes, the intent being for use with identifiers. In that form it still emits `NULL` without quotes, but it is not intended to be used with `NULL` values. `%S` works like `%.16s`. It is intended for fossil hashes. The '!' modifier removes the length limit, resulting in the whole hash (making this formatting option equivalent to `%s`). (Sidebar: in fossil(1) this length is runtime configurable but that requires storing that option in global state, which is not an option for this implementation.) `%/`: works mostly like `%s` but normalizes path-like strings by replacing backslashes with the One True Slash. `%b`: works like `%s` but takes its input from a (fsl_buffer const*) argument. It does not support the '#' flag. `%B`: works like `%Q` but takes its input from a (fsl_buffer const*) argument. `%F`: works like `%s` but runs the output through fsl_bytes_fossilize(). This requires dynamic memory allocation, so is less efficient than re-using a client-provided buffer with fsl_bytes_fossilize() if the client needs to fossilize more than one element. Does not support the '#' flag. `%j`: works like `%s` but JSON-encodes the string. It does not include the outer quotation marks by default, but using the '!' flag, i.e. `%!j`, causes those to be added. The length and precision flags are NOT supported for this format. Results are undefined if given input which is not legal UTF8. By default non-ASCII characters with values less than 0xffff are emitted as as literal characters (no escaping), but the '#' modifier flag will cause it to emit such characters in the `\u####` form. It always encodes characters above 0xFFFF as UTF16 surrogate pairs (as JSON requires). Invalid UTF8 characters may get converted to '?' or may produce invalid JSON output. As a special case, if the value is NULL pointer, it resolves to "null" without quotes (regardless of the '!' modifier). Some of these extensions may be disabled by setting certain macros |
︙ | ︙ | |||
2047 2048 2049 2050 2051 2052 2053 | Newly-allocated slots will be initialized with NULL pointers. Returns 0 on success, FSL_RC_MISUSE if !self, FSL_RC_OOM if reservation of new elements fails. The return value should be used like this: | < > < > | 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 | Newly-allocated slots will be initialized with NULL pointers. Returns 0 on success, FSL_RC_MISUSE if !self, FSL_RC_OOM if reservation of new elements fails. The return value should be used like this: ``` fsl_size_t const n = number of bytes to allocate; int const rc = fsl_list_reserve( myList, n ); if( rc ) { ... error ... } ``` @see fsl_list_clear() @see fsl_list_visit_free() */ FSL_EXPORT int fsl_list_reserve( fsl_list * self, fsl_size_t n ); /** |
︙ | ︙ | |||
2239 2240 2241 2242 2243 2244 2245 | zRoot is NULL or empty (and then only to get the current directory). This does not confirm whether the resulting file exists, nor that it is strictly a valid filename for the current filesystem. It simply transforms a potentially relative path into an absolute one. Example: | | > < > | 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 | zRoot is NULL or empty (and then only to get the current directory). This does not confirm whether the resulting file exists, nor that it is strictly a valid filename for the current filesystem. It simply transforms a potentially relative path into an absolute one. Example: ``` int rc; char const * zRoot = "/a/b/c"; char const * zName = "../foo.bar"; fsl_buffer buf = fsl_buffer_empty; rc = fsl_file_canonical_name2(zRoot, zName, &buf, 0); if(rc){ fsl_buffer_clear(&buf); return rc; } assert(0 == fsl_strcmp( "/a/b/foo.bar, fsl_buffer_cstr(&buf))); fsl_buffer_clear(&buf); ``` */ FSL_EXPORT int fsl_file_canonical_name2(const char *zRoot, const char *zOrigName, fsl_buffer *pOut, bool slash); /** Equivalent to fsl_file_canonical_name2(NULL, zOrigName, pOut, slash). |
︙ | ︙ | |||
2448 2449 2450 2451 2452 2453 2454 | ignored. Note that there is no separate "glob list" class. A "glob list" is simply a fsl_list whose list entries are glob-pattern strings owned by that list. Examples of a legal value for zPatternList: | | > < > | 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 | ignored. Note that there is no separate "glob list" class. A "glob list" is simply a fsl_list whose list entries are glob-pattern strings owned by that list. Examples of a legal value for zPatternList: ``` "*.c *.h, *.sh, '*.in'" ``` @see fsl_glob_list_append() @see fsl_glob_list_matches() @see fsl_glob_list_clear() */ FSL_EXPORT int fsl_glob_list_parse( fsl_list * tgt, char const * zPatternList ); |
︙ | ︙ | |||
2474 2475 2476 2477 2478 2479 2480 | @see fsl_glob_list_matches() @see fsl_glob_list_clear() */ FSL_EXPORT int fsl_glob_list_append( fsl_list * tgt, char const * zGlob ); /** Assumes globList is a list of (char [const] *) glob values and | | < > < > | > | | 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 | @see fsl_glob_list_matches() @see fsl_glob_list_clear() */ FSL_EXPORT int fsl_glob_list_append( fsl_list * tgt, char const * zGlob ); /** Assumes globList is a list of (char [const] *) glob values and tries to match each one against zNeedle using fsl_str_glob(). If any glob matches, it returns a pointer to the matched globList->list entry. If no matches are found, or if any argument is invalid, NULL is returned. The returned bytes are owned by globList and may be invalidated at its leisure. It is primarily intended to be used as a boolean, for example: ``` if( fsl_glob_list_matches(myGlobs, someFilename) ) { ... } ``` @see fsl_glob_list_parse() @see fsl_glob_list_append() @see fsl_glob_list_clear() */ FSL_EXPORT char const * fsl_glob_list_matches( fsl_list const * const globList, char const * zNeedle ); /** If globList is not NULL this is equivalent to fsl_list_visit_free(globList, 1), otherwise it is a no-op. Note that this does not free the globList object itself, just its underlying list entries and list memory. (In practice, lists are either allocated on the stack or as part of a higher-level structure, and not on the heap.) @see fsl_glob_list_parse() @see fsl_glob_list_append() @see fsl_glob_list_matches() */ FSL_EXPORT void fsl_glob_list_clear( fsl_list * const globList ); /** Returns true if the given letter is an ASCII alphabet character. */ FSL_EXPORT char fsl_isalpha(int c); |
︙ | ︙ | |||
3081 3082 3083 3084 3085 3086 3087 | - Returns FSL_RC_MISUSE if any of (zSrc, zOut, out) are NULL. - If out() returns non-0 at any time, delta generation is aborted and that code is returned. Example usage: | < > < > | 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 | - Returns FSL_RC_MISUSE if any of (zSrc, zOut, out) are NULL. - If out() returns non-0 at any time, delta generation is aborted and that code is returned. Example usage: ``` int rc = fsl_delta_create( v1, v1len, v2, v2len, fsl_output_f_FILE, stdout); ``` */ FSL_EXPORT int fsl_delta_create2( unsigned char const *zSrc, fsl_size_t lenSrc, unsigned char const *zOut, fsl_size_t lenOut, fsl_output_f out, void * outState); /** A fsl_delta_create() wrapper which uses the first two arguments |
︙ | ︙ | |||
3265 3266 3267 3268 3269 3270 3271 | /** Tries to convert the value of errNo, which is assumed to come from the global errno, to a fsl_rc_e code. If it can, it returns something approximating the errno value, else it returns dflt. Example usage: | < > < > | 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 | /** Tries to convert the value of errNo, which is assumed to come from the global errno, to a fsl_rc_e code. If it can, it returns something approximating the errno value, else it returns dflt. Example usage: ``` FILE * f = fsl_fopen("...", "..."); int rc = f ? 0 : fsl_errno_to_rc(errno, FSL_RC_IO); ... ``` Why require the caller to pass in errno, instead of accessing it directly from this function? To avoid the the off-chance that something changes errno between the call and the conversion (whether or not that's possible is as yet undetermined). It can also be used by clients to map to explicit errno values to fsl_rc_e values, e.g. fsl_errno_to_rc(EROFS,-1) returns |
︙ | ︙ | |||
3452 3453 3454 3455 3456 3457 3458 | fsl_output_f out, void * outState, short contextLines, short sbsWidth, int diffFlags ); /** Functionally equivalent to: | < > < > | 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 | fsl_output_f out, void * outState, short contextLines, short sbsWidth, int diffFlags ); /** Functionally equivalent to: ``` fsl_diff_text(pA, pB, fsl_output_f_buffer, pOut, contextLines, sbsWidth, diffFlags); ``` Except that it returns FSL_RC_MISUSE if !pOut. @see fsl_diff_v2() @deprecated Prefer fsl_diff_v2() for new code. */ FSL_EXPORT int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB, |
︙ | ︙ | |||
3898 3899 3900 3901 3902 3903 3904 | If this is not NULL, it is called one time at the start of each chunk of diff for a given file and is passed the line number of each half of the diff and the number of lines in that chunk for that half (including insertions and deletions). This is primarily intended for generating conventional unified diff chunk headers in the form: | < > < > | 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 | If this is not NULL, it is called one time at the start of each chunk of diff for a given file and is passed the line number of each half of the diff and the number of lines in that chunk for that half (including insertions and deletions). This is primarily intended for generating conventional unified diff chunk headers in the form: ``` @@ -A,B +C,D @@ ``` The inclusion of this method in an object might preclude certain other diff formatting changes which might otherwise apply. Notably, if the span between two diff chunks is smaller than the context lines count, the diff builder driver prefers to merge those two chunks together. That "readability optimization" is skipped when this method is set because this method may otherwise |
︙ | ︙ | |||
4107 4108 4109 4110 4111 4112 4113 | Note that it is legal for the names and hashes to be "falsy" (null, not set, or empty strings). The JSON array consists of integer opcodes with each opcode followed by zero or more arguments: | < > < > | 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 | Note that it is legal for the names and hashes to be "falsy" (null, not set, or empty strings). The JSON array consists of integer opcodes with each opcode followed by zero or more arguments: ``` Syntax Mnemonic Description ----------- -------- -------------------------- 0 END This is the end of the diff. 1 INTEGER SKIP Skip N lines from both files. 2 STRING COMMON The line STRING is in both files. 3 STRING INSERT The line STRING is in only the right file. 4 STRING DELETE The line STRING is in only the left file. 5 SUBARRAY EDIT One line is different on left and right. ``` The SUBARRAY is an array of 3*N+1 strings with N>=0. The triples represent common-text, left-text, and right-text. The last string in SUBARRAY is the common-suffix. Any string can be empty if it does not apply. */ FSL_DIFF_BUILDER_JSON1, |
︙ | ︙ | |||
4245 4246 4247 4248 4249 4250 4251 | /** If zDate is an ISO8601-format string, optionally with a .NNN fractional suffix, then this function returns true and sets *pOut (if pOut is not NULL) to the corresponding Julian value. If zDate is not an ISO8601-format string then this | | | 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 | /** If zDate is an ISO8601-format string, optionally with a .NNN fractional suffix, then this function returns true and sets *pOut (if pOut is not NULL) to the corresponding Julian value. If zDate is not an ISO8601-format string then this returns false and pOut is not modified. This function does NOT confirm that zDate ends with a NUL byte. i.e. if passed a valid date string which has trailing bytes after it then those are simply ignored. This is so that it can be used to read subsets of larger strings. Achtung: this calculation may, due to voodoo-level |
︙ | ︙ | |||
4356 4357 4358 4359 4360 4361 4362 | formatted representation to b. Returns 0 on success, non-0 on error. If any argument is NULL or !*format then FSL_RC_MISUSE is returned. FSL_RC_RANGE is returned if the underlying call to fsl_strftime() fails (which it will if the format string resolves to something "unususually long"). It returns FSL_RC_OOM if appending to b fails due to an allocation error. */ | | > > | | | | < > < > | 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 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 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 | formatted representation to b. Returns 0 on success, non-0 on error. If any argument is NULL or !*format then FSL_RC_MISUSE is returned. FSL_RC_RANGE is returned if the underlying call to fsl_strftime() fails (which it will if the format string resolves to something "unususually long"). It returns FSL_RC_OOM if appending to b fails due to an allocation error. */ FSL_EXPORT int fsl_buffer_strftime(fsl_buffer * const b, char const * format, const struct tm *timeptr); /** "whence" values for use with fsl_buffer_seek. */ enum fsl_buffer_seek_e { FSL_BUFFER_SEEK_SET = 1, FSL_BUFFER_SEEK_CUR = 2, FSL_BUFFER_SEEK_END = 3 }; typedef enum fsl_buffer_seek_e fsl_buffer_seek_e; /** "Seeks" b's internal cursor to a position specified by the given offset from either the current cursor position (FSL_BUFFER_SEEK_CUR), the start of the buffer (FSL_BUFFER_SEEK_SET), or the end (FSL_BUFFER_SEEK_END). If the cursor would be placed out of bounds, it will be placed at the start resp. end of the buffer. The "end" of a buffer is the value of its fsl_buffer::used member (i.e. its one-after-the-end). Returns the new position. Note that most buffer algorithms, e.g. fsl_buffer_append(), do not modify the cursor. Only certain special-case algorithms use it. @see fsl_buffer_tell() @see fsl_buffer_rewind() */ FSL_EXPORT fsl_size_t fsl_buffer_seek(fsl_buffer * const b, fsl_int_t offset, fsl_buffer_seek_e whence); /** Returns the buffer's current cursor position. @see fsl_buffer_rewind() @see fsl_buffer_seek() */ FSL_EXPORT fsl_size_t fsl_buffer_tell(fsl_buffer const * const b); /** Resets b's cursor to the beginning of the buffer. @see fsl_buffer_tell() @see fsl_buffer_seek() */ FSL_EXPORT void fsl_buffer_rewind(fsl_buffer * const b); /** The "Path Finder" class is a utility class for searching the filesystem for files matching a set of common prefixes and/or suffixes (i.e. directories and file extensions). Example usage: ``` fsl_pathfinder pf = fsl_pathfinder_empty; int rc; char const * found = NULL; rc = fsl_pathfinder_ext_add( &pf, ".doc" ); if(rc) { ...error... } // The following error checks are elided for readability: rc = fsl_pathfinder_ext_add( &pf, ".txt" ); rc = fsl_pathfinder_ext_add( &pf, ".wri" ); rc = fsl_pathfinder_dir_add( &pf, "." ); rc = fsl_pathfinder_dir_add( &pf, "/my/doc/dir" ); rc = fsl_pathfinder_dir_add( &pf, "/other/doc/dir" ); rc = fsl_pathfinder_search( &pf, "MyDoc", &found, NULL); if(0==rc){ assert(NULL!=found); } // Eventually clean up: fsl_pathfinder_clear(&pf); ``` @see fsl_pathfinder_dir_add() @see fsl_pathfinder_ext_add() @see fsl_pathfinder_clear() @see fsl_pathfinder_search() */ struct fsl_pathfinder { |
︙ | ︙ | |||
4461 4462 4463 4464 4465 4466 4467 | }; typedef struct fsl_pathfinder fsl_pathfinder; /** Initialized-with-defaults fsl_pathfinder instance, intended for const copy initialization. */ | | > > > | | > | > | 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 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 | }; typedef struct fsl_pathfinder fsl_pathfinder; /** Initialized-with-defaults fsl_pathfinder instance, intended for const copy initialization. */ #define fsl_pathfinder_empty_m {\ fsl_list_empty_m/*ext*/,\ fsl_list_empty_m/*dirs*/,\ fsl_buffer_empty_m/*buf*/} /** Initialized-with-defaults fsl_pathfinder instance, intended for copy initialization. */ FSL_EXPORT const fsl_pathfinder fsl_pathfinder_empty; /** 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: |
︙ | ︙ | |||
4571 4572 4573 4574 4575 4576 4577 | context (based on usage in previous trees from which this code derives). @see fsl_pathfinder_dir_add() @see fsl_pathfinder_ext_add() @see fsl_pathfinder_clear() */ | | > | > | 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 | context (based on usage in previous trees from which this code derives). @see fsl_pathfinder_dir_add() @see fsl_pathfinder_ext_add() @see fsl_pathfinder_clear() */ FSL_EXPORT int fsl_pathfinder_search(fsl_pathfinder * const pf, char const * const base, char const ** pOut, fsl_size_t * const outLen ); /** A utility class for creating ZIP-format archives. All members are internal details and must not be mucked about with by the client. See fsl_zip_file_add() for an example of how to use it. |
︙ | ︙ | |||
4669 4670 4671 4672 4673 4674 4675 | otherwise is injects any directory levels it needs to into the being-generated ZIP. Note that zRoot may contain multiple levels of directories, e.g. "foo/bar/baz", but it must be legal for use in a ZIP file. This routine copies zRoot's bytes, so they may be transient. | | | 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 | otherwise is injects any directory levels it needs to into the being-generated ZIP. Note that zRoot may contain multiple levels of directories, e.g. "foo/bar/baz", but it must be legal for use in a ZIP file. This routine copies zRoot's bytes, so they may be transient. Returns 0 on success, FSL_RC_MISUSE if !z, FSL_RC_OOM on allocation error. Returns FSL_RC_RANGE if zRoot is an absolute path or if zRoot cannot be normalized to a "simplified name" (as per fsl_is_simple_pathname(), with the note that this routine will pass a copy of zRoot through fsl_file_simplify_name() first). @see fsl_zip_finalize() |
︙ | ︙ | |||
4708 4709 4710 4711 4712 4713 4714 | - If a root directory has been set using fsl_zip_root_set() then that name, plus '/' (if the root does not end with one) gets prepended to all files added via this routine. An example of the ZIP-generation process: | < > | 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 | - If a root directory has been set using fsl_zip_root_set() then that name, plus '/' (if the root does not end with one) gets prepended to all files added via this routine. An example of the ZIP-generation process: ``` int rc; fsl_zip_writer z = fsl_zip_writer_empty; fsl_buffer buf = fsl_buffer_empty; fsl_buffer const * zipBody; // ...fill the buf buffer (not shown here)... |
︙ | ︙ | |||
4746 4747 4748 4749 4750 4751 4752 | rc = fsl_buffer_to_filename( zipBody, "my.zip" ); end: fsl_buffer_clear(&buf); // VERY important, once we're done with z: fsl_zip_finalize( &z ); if(rc){...we had an error...} | < > | 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 | rc = fsl_buffer_to_filename( zipBody, "my.zip" ); end: fsl_buffer_clear(&buf); // VERY important, once we're done with z: fsl_zip_finalize( &z ); if(rc){...we had an error...} ``` @see fsl_zip_timestamp_set_julian() @see fsl_zip_timestamp_set_unix() @see fsl_zip_end() @see fsl_zip_body() @see fsl_zip_finalize() */ |
︙ | ︙ | |||
4904 4905 4906 4907 4908 4909 4910 | Achtung: timer support is only enabled if the library is built with the FSL_CONFIG_ENABLE_TIMER macro set to a true value (it is on by default). @see fsl_timer_reset() @see fsl_timer_stop() */ | | | | | | 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 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 | Achtung: timer support is only enabled if the library is built with the FSL_CONFIG_ENABLE_TIMER macro set to a true value (it is on by default). @see fsl_timer_reset() @see fsl_timer_stop() */ FSL_EXPORT void fsl_timer_start(fsl_timer_state * const t); /** Returns the difference in _CPU_ times in microseconds since t was last passed to fsl_timer_start() or fsl_timer_reset(). It might return 0 due to system-level precision restrictions. Note that this is not useful for measuring wall times. */ FSL_EXPORT uint64_t fsl_timer_fetch(fsl_timer_state const * const t); /** Resets t to the current time and returns the number of microseconds since t was started or last reset. @see fsl_timer_start() @see fsl_timer_reset() */ FSL_EXPORT uint64_t fsl_timer_reset(fsl_timer_state * const t); /** Clears t's state and returns the difference (in uSec) between the last time t was started or reset, as per fsl_timer_fetch(). @see fsl_timer_start() @see fsl_timer_reset() */ FSL_EXPORT uint64_t fsl_timer_stop(fsl_timer_state * const t); /** For the given red/green/blue values (all in the range of 0 to 255, or truncated to be so!) this function returns the RGB encoded in the lower 24 bits of a single number. See fsl_gradient_color() for an explanation and example. |
︙ | ︙ | |||
4981 4982 4983 4984 4985 4986 4987 | - blue = (rc&0xFF) Or use fsl_rgb_decode() to break it into its component parts. It can be passed directly to a printf-like function, using the hex-integer format specifier, e.g.: | < > < > | 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 | - blue = (rc&0xFF) Or use fsl_rgb_decode() to break it into its component parts. It can be passed directly to a printf-like function, using the hex-integer format specifier, e.g.: ``` fsl_buffer_appendf(&myBuf, "#%06x", rc); ``` Tip: for a given HTML RRGGBB value, its C representation is identical: HTML \#F0D0A0 is 0xF0D0A0 in C. @see fsl_rgb_encode() @see fsl_rgb_decode() */ |
︙ | ︙ | |||
5087 5088 5089 5090 5091 5092 5093 | 0/*entryCount*/, 0/*capacity*/, \ 0/*used*/, NULL/*list*/ } /** Return the number of elements in the bag. */ | | | | | < > | | < | < > | | | | | | 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 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 | 0/*entryCount*/, 0/*capacity*/, \ 0/*used*/, NULL/*list*/ } /** Return the number of elements in the bag. */ FSL_EXPORT fsl_size_t fsl_id_bag_count(fsl_id_bag const * const p); /** Remove element e from the bag if it exists in the bag. If e is not in the bag, this is a no-op. Returns true if it removes an element, else false. e must be positive. Results are undefined if e<=0. */ FSL_EXPORT bool fsl_id_bag_remove(fsl_id_bag * const p, fsl_id_t e); /** Returns true if e is in the given bag. Returns false if it is not. It is illegal to pass an e value of 0, and that will trigger an assertion in debug builds. In non-debug builds, behaviour if passed 0 is undefined. */ FSL_EXPORT bool fsl_id_bag_contains(fsl_id_bag const * const p, fsl_id_t e); /** Insert element e into the bag if it is not there already. Returns 0 if it actually inserts something or if it already contains such an entry, and some other value on error (namely FSL_RC_OOM on allocation error). e must be positive or an assertion is triggered in debug builds. In non-debug builds, behaviour if passed 0 is undefined. */ FSL_EXPORT int fsl_id_bag_insert(fsl_id_bag * const p, fsl_id_t e); /** Returns the ID of the first element in the bag. Returns 0 if the bag is empty. Example usage: ``` fsl_id_t nid; for( nid = fsl_id_bag_first(&list); nid > 0; nid = fsl_id_bag_next(&list, nid)){ ...do something... } ``` */ FSL_EXPORT fsl_id_t fsl_id_bag_first(fsl_id_bag const * const p); /** Returns the next element in the bag after e. Return 0 if e is the last element in the bag. Any insert or removal from the bag might reorder the bag. It is illegal to pass this 0 (and will trigger an assertion in debug builds). For the first call, pass it the non-0 return value from fsl_id_bag_first(). For subsequent calls, pass the previous return value from this function. @see fsl_id_bag_first() */ FSL_EXPORT fsl_id_t fsl_id_bag_next(fsl_id_bag const * const p, fsl_id_t e); /** Swaps the contents of the given bags. */ FSL_EXPORT void fsl_id_bag_swap(fsl_id_bag * const lhs, fsl_id_bag * const rhs); /** Frees any memory owned by p, but does not free p. */ FSL_EXPORT void fsl_id_bag_clear(fsl_id_bag * const p); /** Resets p's internal list, effectively emptying it for re-use, but does not free its memory. Immediately after calling this fsl_id_bag_count() will return 0. */ FSL_EXPORT void fsl_id_bag_reset(fsl_id_bag * const p); /** Returns true if p contains a fossil-format merge conflict marker, else returns false. @see fsl_buffer_merge3() |
︙ | ︙ | |||
5194 5195 5196 5197 5198 5199 5200 | number of merge conflicts is written to *conflictCount. Returns 0 on success, FSL_RC_OOM on OOM, FSL_RC_TYPE if any input appears to be binary. @see fsl_buffer_contains_merge_marker() */ | | | > > > | 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 | number of merge conflicts is written to *conflictCount. Returns 0 on success, FSL_RC_OOM on OOM, FSL_RC_TYPE if any input appears to be binary. @see fsl_buffer_contains_merge_marker() */ FSL_EXPORT int fsl_buffer_merge3(fsl_buffer * const pPivot, fsl_buffer * const pV1, fsl_buffer * const pV2, fsl_buffer * const pOut, 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. */ FSL_EXPORT int fsl_buffer_append_tcl_literal(fsl_buffer * const b, |
︙ | ︙ | |||
5653 5654 5655 5656 5657 5658 5659 | - This function does not validate that the blob content is properly formed UTF-8. It assumes that all code points are the same size. It does not validate any code points. It makes no attempt to detect if any [invalid] switches between UTF-8 and other encodings occur. | | | | 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 | - This function does not validate that the blob content is properly formed UTF-8. It assumes that all code points are the same size. It does not validate any code points. It makes no attempt to detect if any [invalid] switches between UTF-8 and other encodings occur. - The only code points that this function cares about are the NUL character, carriage-return, and line-feed. */ FSL_EXPORT int fsl_looks_like_utf8(fsl_buffer const * const b, int stopFlags); /** Returns true if b's contents appear to contain anything other than valid UTF8. |
︙ | ︙ | |||
5736 5737 5738 5739 5740 5741 5742 | 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). | < | | 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 | 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). */ /** @file fossil-core.h This file declares the core SCM-related public APIs. */ #include <time.h> /* struct tm, time_t */ #if defined(__cplusplus) /** |
︙ | ︙ | |||
5763 5764 5765 5766 5767 5768 5769 | 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). An instance's lifetime looks something like this: | < > < > | | | | | < < > > | > > | > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | < | | | 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 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 | 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). An instance's lifetime looks something like this: ``` int rc; fsl_cx * f = NULL; // ALWAYS initialize to NULL or results are undefined rc = fsl_cx_init( &f, NULL ); assert(!rc); rc = fsl_repo_open( f, "myrepo.fsl" ); ...use the context, and clean up when done... fsl_cx_finalize(f); ``` The contents of an fsl_cx instance are strictly private, for use only by APIs in this library. Any client-side dependencies on them will lead to undefined behaviour at some point. Design note: this type is currently opaque to client code. Having it non-opaque also has advantages, though, and i'd generally prefer that (to allow client-side allocation and embedding in other structs). Binary compatibility concerns might eventually (once the API is declared "relatively stable") force us to keep it opaque. */ typedef struct fsl_cx fsl_cx; typedef struct fsl_cx_config fsl_cx_config; typedef struct fsl_db fsl_db; typedef struct fsl_cx_init_opt fsl_cx_init_opt; typedef struct fsl_stmt fsl_stmt; /** This enum defines type ID tags with which the API tags fsl_db instances so that the library can figure out which DB is which. This is primarily important for certain queries, which need to know whether they are accessing the repo or config db, for example. As of 2021-10, the fossil context db model has long been, and is very, very likely to remain: - When a context starts up, it creates a temporary db for use as its `main` db. - When a repo or checkout db are opened, they are `ATTACH`ed to the main db with the names `repo` resp. `ckout`. (Note that those differ from the names fossil(1) uses: `repository` and `localdb`.) A large amount of the library code is written _as if_ the library (roughly) followed fossil's model of using separate sqlite3 objects, even though it does not do so. The reason is historical: when starting the libfossil port, it was clear that fossil's internal connection juggling was going to be painful for the library, so the internal API was shaped to permit two separate approaches: Approach 1: the first db which gets opened (repo, checkout, or config) becomes the `main` db and all others get attached using their API-standard alias. That is _mostly_ what fossil does, but it leads to fossil sometimes having to "swap" connections by closing the db and re-opening it so that the db names are in a state which fits the current use. That model led to the creation of this enum. However, it also leads to unpredictable database names for purposes of queries, as we're never sure (at the client level or higher library-level APIs) which db is `main` and which has an alias. Approach 2: open a temp (or in-memory) db and attach all of the others to that using well-defined names. The latter has proven to work well enough that returning to the first approach seems _extremely_ unlikely at this point. Thus some newer library-level code behaves as if the latter model is in effect (which it is). The long and the short of the above diversion is that APIs like fsl_cx_db_repo() and fsl_cx_db_ckout() will always return the same database handle, but they can tell whether a given role has been attached or not and will fail in their documented ways when the role's corresponding database has not yet been attached. e.g. `fsl_cx_db_repo()` will return `NULL` if a repo database is not attached. @see fsl_db_role_label() @see fsl_cx_db_name_for_role() @see fsl_cx_db_file_for_role() */ enum fsl_dbrole_e { /** Sentinel "no role" value. */ FSL_DBROLE_NONE = 0, /** The global (per user) config db. Analog to fossil's "configdb". */ FSL_DBROLE_CONFIG = 0x01, /** The repository db. Analog to fossil's "repository". */ FSL_DBROLE_REPO = 0x02, /** The checkout db. Analog to fossil's "localdb". */ FSL_DBROLE_CKOUT = 0x04, /** Analog to fossil's "main", which is an alias for the first db opened. In this API a fsl_cx instance has a temporary/transient main db opened even if it does not have a repository, checkout, or config db opened. */ FSL_DBROLE_MAIN = 0x08, /** Refers to the "temp" database. This is only used by a very few APIs and is outright invalid for most. */ FSL_DBROLE_TEMP = 0x10 }; typedef enum fsl_dbrole_e fsl_dbrole_e; /** Bitmask values specifying "configuration sets". The values in this enum come directly from fossil(1), but they are not part of the db structure, so may be changed over time. It seems very unlikely that these will ever be used at the level of this library. They are a "porting artifact" and retained for the time being, but will very likely be removed. */ |
︙ | ︙ | |||
5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 | }; typedef enum fsl_configset_e fsl_configset_e; /** Runtime-configurable flags for a fsl_cx instance. */ enum fsl_cx_flags_e { FSL_CX_F_NONE = 0, /** Tells us whether or not we want to calculate R-cards by default. Historically they were initially required but eventually made optional due largely to their memory costs. */ FSL_CX_F_CALC_R_CARD = 0x01, /** When encounting artifact types in the crosslinking phase which the library does not currently support crosslinking for, skip over | > > > > | > | > > | 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 | }; typedef enum fsl_configset_e fsl_configset_e; /** Runtime-configurable flags for a fsl_cx instance. */ enum fsl_cx_flags_e { /** The "no flags" value. Guaranteed to be 0 and this is the only entry in this enum which is guaranteed to have a stable value. */ FSL_CX_F_NONE = 0, /** Tells us whether or not we want to calculate R-cards by default. Historically they were initially required but eventually made optional due largely to their memory costs. */ FSL_CX_F_CALC_R_CARD = 0x01, /** When encounting artifact types in the crosslinking phase which the library does not currently support crosslinking for, skip over them instead of generating an error. For day-to-day use this is, perhaps counter-intuitively, generally desirable. */ FSL_CX_F_SKIP_UNKNOWN_CROSSLINKS = 0x02, /** By default, fsl_reserved_fn_check() will fail if the given filename is reserved on Windows platforms because such filenames cannot be checked out on Windows. This flag removes that limitation. It should only be used, if at all, for repositories which will _never_ be used on Windows. */ FSL_CX_F_ALLOW_WINDOWS_RESERVED_NAMES = 0x04, /** If on (the default) then an internal cache will be used for artifact loading to speed up operations which do lots of that. Disabling this will save memory but will hurt performance badly for certain operations. */ FSL_CX_F_MANIFEST_CACHE = 0x08, /** Internal use only to prevent duplicate initialization of some bits. */ |
︙ | ︙ | |||
5929 5930 5931 5932 5933 5934 5935 | /** List of hash policy values. New repositories should generally use only SHA3 hashes, but older repos may contain SHA1 hashes (perhaps only SHA1), so we have to support those. Repositories may contain a mix of hash types. | | > > | | 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 | /** List of hash policy values. New repositories should generally use only SHA3 hashes, but older repos may contain SHA1 hashes (perhaps only SHA1), so we have to support those. Repositories may contain 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 fsl_rc_cstr() to get some human-readable (or programmer-readable) |
︙ | ︙ | |||
6285 6286 6287 6288 6289 6290 6291 | fsl_allocator allocator; }; typedef struct fsl_lib_configurable_t fsl_lib_configurable_t; FSL_EXPORT fsl_lib_configurable_t fsl_lib_configurable; /** A part of the configuration used by fsl_cx_init() and friends. | < | 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 | fsl_allocator allocator; }; typedef struct fsl_lib_configurable_t fsl_lib_configurable_t; FSL_EXPORT fsl_lib_configurable_t fsl_lib_configurable; /** 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 the fsl_output()-configured channel. */ bool traceSql; |
︙ | ︙ | |||
6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 | fsl_cx_config instance initialized with defaults, intended for copy-initialization. */ FSL_EXPORT const fsl_cx_config fsl_cx_config_empty; /** Parameters for fsl_cx_init(). */ struct fsl_cx_init_opt { /** The output channel for the Fossil instance. */ fsl_outputer output; /** | > > > | 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 | fsl_cx_config instance initialized with defaults, intended for copy-initialization. */ FSL_EXPORT const fsl_cx_config fsl_cx_config_empty; /** Parameters for fsl_cx_init(). Reminder to self: why are fsl_cx_config and fsl_cx_init_opt separate structs? TODO: look into consolidating them. */ struct fsl_cx_init_opt { /** The output channel for the Fossil instance. */ fsl_outputer output; /** |
︙ | ︙ | |||
6367 6368 6369 6370 6371 6372 6373 | */ FSL_EXPORT fsl_cx * fsl_cx_malloc(); /** Initializes a fsl_cx instance. tgt must be a pointer to NULL, e.g.: | < > < > | 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 | */ FSL_EXPORT fsl_cx * fsl_cx_malloc(); /** Initializes a fsl_cx instance. tgt must be a pointer to NULL, e.g.: ``` fsl_cxt * f = NULL; // NULL is important - see below int rc = fsl_cx_init( &f, NULL ); ``` It is very important that f be initialized to NULL _or_ to an instance which has been properly allocated and empty-initialized (e.g. via fsl_cx_malloc()). If *tgt is NULL, this routine allocates the context, else it assumes the caller did. If f points to unitialized memory then results are undefined. |
︙ | ︙ | |||
6408 6409 6410 6411 6412 6413 6414 | explained above. @see fsl_cx_finalize() @see fsl_cx_reset() */ FSL_EXPORT int fsl_cx_init( fsl_cx ** tgt, fsl_cx_init_opt const * param ); | < < < < < < < < < < < < < < < < < < < < | 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 | explained above. @see fsl_cx_finalize() @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 |
︙ | ︙ | |||
6558 6559 6560 6561 6562 6563 6564 | Results are undefined if db is NULL and f has no main db connection. */ FSL_EXPORT int fsl_cx_uplift_db_error2(fsl_cx * const f, fsl_db * db, int rc); /** Outputs the first n bytes of src to f's configured output | | < | | > > | > | | < > | | | < | > > > | 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 | Results are undefined if db is NULL and f has no main db connection. */ FSL_EXPORT int fsl_cx_uplift_db_error2(fsl_cx * const f, fsl_db * db, int rc); /** Outputs the first n bytes of src to f's configured output channel. Returns 0 on success, 0 (without side effects) if !n, else it returns the result of the underlying output call. This is a harmless no-op if f is configured with no output channel. Results are undefined if f or src are NULL. @see fsl_outputf() @see fsl_flush() */ FSL_EXPORT int fsl_output( fsl_cx * const f, void const * const src, fsl_size_t n ); /** Flushes f's output channel. Returns 0 on success. If the flush routine is NULL then this is a harmless no-op. Results are undefined if f is NULL. @see fsl_outputf() @see fsl_output() */ FSL_EXPORT int fsl_flush( fsl_cx * const f ); /** Uses fsl_appendf() to append formatted output to the channel configured for use with fsl_output(). Returns 0 on success, FSL_RC_MISUSE if !f or !fmt, FSL_RC_RANGE if !*fmt, and FSL_RC_IO if the underlying fsl_appendf() operation fails. Note, however, that due to the printf()-style return semantics of fsl_appendf(), it is not generically possible to distinguish a partially-successful (i.e. failed in the middle) write from success. e.g. if fmt contains a format specifier which performs memory allocation and that allocation fails, it is unlikely that this function will be able to be aware of that error. The only way to fix that is to change the return semantics of fsl_appendf() (and adjust any existing code which relies on them). @see fsl_output() @see fsl_flush() */ FSL_EXPORT int fsl_outputf( fsl_cx * const f, char const * fmt, ... ); /** va_list counterpart to fsl_outputf(). */ FSL_EXPORT int fsl_outputfv( fsl_cx * const f, char const * fmt, va_list args ); /** Opens the given db file name as f's repository. Returns 0 on success. On error it sets f's error state and returns that code unless the error was FSL_RC_MISUSE (which indicates invalid arguments and it does not set the error state). Returns FSL_RC_ACCESS if f already has an opened repo db (use fsl_repo_close() or fsl_ckout_close() to close it). Returns FSL_RC_NOT_FOUND if repoDbFile is not found, as this routine cannot create a new repository db. Results are undefined if any argument is NULL. When a repository is opened, the fossil-level user name associated with f (if any) is overwritten with the default user from the repo's login table (the one with uid=1). Thus fsl_cx_user_get() may return a value even if the client has not called fsl_cx_user_set(). |
︙ | ︙ | |||
6638 6639 6640 6641 6642 6643 6644 | - On success this re-sets several bits of f's configuration to match the repository-side settings. @see fsl_repo_create() @see fsl_repo_close() */ | | | | < | > > | | < > < > | 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 | - On success this re-sets several bits of f's configuration to match the repository-side settings. @see fsl_repo_create() @see fsl_repo_close() */ FSL_EXPORT int fsl_repo_open( fsl_cx * const f, char const * repoDbFile/*, char readOnlyCurrentlyIgnored*/ ); /** If fsl_repo_open_xxx() has been used to open a respository db, this call closes that db and returns 0. Returns FSL_RC_NOT_FOUND if f has not opened a repository (that can normally be ignored but is provided for completeness's sake). If a repository is opened "indirectly" via fsl_ckout_open_dir() then attempting to close it using this function will result in FSL_RC_MISUSE and f's error state will hold a description of the problem (the checkout must be closed before closing its repository). Such a repository will be closed implicitly when the checkout db is closed. @see fsl_repo_open() @see fsl_repo_create() */ FSL_EXPORT int fsl_repo_close( fsl_cx * const f ); /** Sets or clears (if userName is NULL or empty) the default repository user name for operations which require one. Returns 0 on success, FSL_RC_MISUSE if f is NULL, FSL_RC_OOM if copying of the userName fails. Example usage: ``` char * u = fsl_guess_user_name(); int rc = fsl_cx_user_set(f, u); fsl_free(u); ``` (Sorry about the extra string copy there, but adding a function which passes ownership of the name string seems like overkill.) */ FSL_EXPORT int fsl_cx_user_set( fsl_cx * f, char const * userName ); /** |
︙ | ︙ | |||
6823 6824 6825 6826 6827 6828 6829 | If initialization of the repository fails, this routine will attempt to remove its partially-initialize corpse from the filesystem but will ignore any errors encountered while doing so. Example usage: | < > | < > | 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 | If initialization of the repository fails, this routine will attempt to remove its partially-initialize corpse from the filesystem but will ignore any errors encountered while doing so. Example usage: ``` fsl_repo_create_opt opt = fsl_repo_create_opt_empty; int rc; opt.filename = "my.fossil"; // ... any other opt.xxx you want to set, e.g.: // opt.user = "fred"; // Assume fsl is a valid fsl_cx instance: rc = fsl_repo_create(fsl, &opt); if(rc) { ...error... } else { fsl_db * db = fsl_cx_db_repo(f); assert(db); // == the new repo db ... } ``` @see fsl_repo_open() @see fsl_repo_close() */ FSL_EXPORT int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt ); /** |
︙ | ︙ | |||
6859 6860 6861 6862 6863 6864 6865 | /** Tries to open a checked-out fossil repository db in the given directory. The (dirName, checkParentDirs) parameters are passed on as-is to fsl_ckout_db_search() to find a checkout db, so see that routine for how it searches. If this routine finds/opens a checkout, it also tries to open | | | > > | < < | | | | | | | | | | | | 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 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 | /** Tries to open a checked-out fossil repository db in the given directory. The (dirName, checkParentDirs) parameters are passed on as-is to fsl_ckout_db_search() to find a checkout db, so see that routine for how it searches. If this routine finds/opens a checkout, it also tries to open the repository database from which the checkout derives, and fails if it cannot. The library never allows a checkout to be opened without its corresponding repository partner because a checkout has hard dependencies on the repo's state. Returns 0 on success. If there is an error opening or validating the checkout or its repository db, f's error state will be updated. Error codes/conditions include: - FSL_RC_MISUSE if f is NULL. - FSL_RC_ACCESS if f already has and opened checkout. - FSL_RC_OOM if an allocation fails. - FSL_RC_NOT_FOUND if no checkout is foud or if a checkout's repository is not found. - FSL_RC_RANGE if dirname is not NULL but has a length of 0. - Various codes from fsl_getcwd() (if dirName is NULL). - Various codes if opening the associated repository DB fails. TODO: there's really nothing in the architecture which restricts a checkout db to being in the same directory as the checkout, except for some historical bits which "could" be refactored. It "might be interesting" to eventually provide a variant which opens a checkout db file directly. We have the infrastructure, just need some refactoring. We would need to add the working directory path to the checkout db's config (`vvar` table), but should otherwise require no trickery or incompatibilities with fossil(1). */ FSL_EXPORT int fsl_ckout_open_dir( fsl_cx * f, char const * dirName, bool checkParentDirs ); /** Searches the given directory (or the current directory if dirName is 0) for a fossil checkout database file named one of (_FOSSIL_, .fslckout). If it finds one, it returns 0 and appends the file's path to pOut if pOut is not 0. If neither is found AND if checkParentDirs is true an then it moves up the path one directory and tries again, until it hits the root of the dirPath (see below for a note/caveat). If dirName is NULL then it behaves as if it had been passed the absolute path of the current directory (as determined by fsl_getcwd()). This function does no normalization of dirName. Because of that... |
︙ | ︙ | |||
6933 6934 6935 6936 6937 6938 6939 | interpret this as a NULL string, i.e. the current directory.) - FSL_RC_OOM if allocation of a filename buffer fails. */ FSL_EXPORT int fsl_ckout_db_search( char const * dirName, bool checkParentDirs, | | | | | | | | 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 | interpret this as a NULL string, i.e. the current directory.) - FSL_RC_OOM if allocation of a filename buffer fails. */ FSL_EXPORT int fsl_ckout_db_search( char const * dirName, bool checkParentDirs, fsl_buffer * const pOut ); /** If fsl_ckout_open_dir() (or similar) has been used to open a checkout db, this call closes that db and returns 0. Returns FSL_RC_MISUSE if f has any transactions pending, FSL_RC_NOT_FOUND if f has not opened a checkout (which can safely be ignored and does not update f's error state). This also closes the repository which was implicitly opened for the checkout. */ FSL_EXPORT int fsl_ckout_close( fsl_cx * const f ); /** Attempts to close any opened databases (repo/checkout/config). This will fail if any transactions are pending. Any databases which are already closed are silently skipped. */ FSL_EXPORT int fsl_cx_close_dbs( fsl_cx * const f ); /** If f is not NULL and has a checkout db opened then this function returns its name. The bytes are valid until that checkout db connection is closed. If len is not NULL then *len is (on success) assigned to the length of the returned string, in bytes. The string is NUL-terminated, so fetching the length (by |
︙ | ︙ | |||
7155 7156 7157 7158 7159 7160 7161 | (i.e. sqlite3_open()'d) use of the config db. Comments in fossil(1) suggest that it is possible to lock the config db for other apps when it is attached to a long-running op by a fossil process. @see fsl_cx_db_config() @see fsl_config_close() */ | | | | > > | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 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 | (i.e. sqlite3_open()'d) use of the config db. Comments in fossil(1) suggest that it is possible to lock the config db for other apps when it is attached to a long-running op by a fossil process. @see fsl_cx_db_config() @see fsl_config_close() */ FSL_EXPORT int fsl_config_open( fsl_cx * const f, char const * dbName ); /** Closes/detaches the database connection opened by fsl_config_open(). Returns 0 on succes, FSL_RC_MISUSE if !f, FSL_RC_NOT_FOUND if no config db connection is opened/attached. @see fsl_cx_db_config() @see fsl_config_open() */ FSL_EXPORT int fsl_config_close( fsl_cx * const f ); /** If f has an opened/attached configuration db then its handle is returned, else 0 is returned. @see fsl_config_open() @see fsl_config_close() */ FSL_EXPORT fsl_db * fsl_cx_db_config( fsl_cx * const f ); /** Convenience form of fsl_db_prepare() which uses f's main db. Returns 0 on success. On preparation error, any db error state is uplifted from the db object to the fsl_cx object. Returns FSL_RC_MISUSE if f has no db opened (should never happen) or !sql, FSL_RC_RANGE if !*sql. */ FSL_EXPORT int fsl_cx_prepare( fsl_cx * const f, fsl_stmt * const tgt, char const * sql, ... ); /** va_list counterpart of fsl_cx_prepare(). */ FSL_EXPORT int fsl_cx_preparev( fsl_cx * const f, fsl_stmt * const tgt, char const * sql, va_list args ); /** Convenience form of fsl_db_exec() which uses f's main db handle. Returns 0 on success. On statement preparation or execution error, the db's error state is uplifted into f and that result is returned. Returns FSL_RC_MISUSE, without additional error information if f has no database (should not be able to happen and might assert) or sql is NULL. */ FSL_EXPORT int fsl_cx_exec( fsl_cx * const f, char const * sql, ... ); /** va_list counterpart of fsl_cx_exec(). */ FSL_EXPORT int fsl_cx_execv( fsl_cx * const f, char const * sql, va_list args ); /** The fsl_db_exec_multi() counterpart of fsl_cx_exec(). */ FSL_EXPORT int fsl_cx_exec_multi( fsl_cx * const f, char const * sql, ... ); /** va_list counterpart of fsl_cx_exec_multi(). */ FSL_EXPORT int fsl_cx_exec_multiv( fsl_cx * const f, char const * sql, va_list args ); /** Wrapper around fsl_db_last_insert_id() which uses f's main database. Returns -1 if !f or f has no opened db. @see fsl_cx_db() */ FSL_EXPORT fsl_id_t fsl_cx_last_insert_id(fsl_cx * const f); /** Works similarly to fsl_stat(), except that zName must refer to a path under f's current checkout directory. Note that this stats local files, not repository-level content. |
︙ | ︙ | |||
7400 7401 7402 7403 7404 7405 7406 | in the event table, and NULL is returned for them. All of the returned values can be used in comparison clauses in queries on the event table's 'type' field (but use GLOB instead of '=' so that the "*" returned by FSL_ATYPE_ANY can match!). For example, to get the comments from the most recent 5 commits: | < > < > | > > > > > > > > > | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 | in the event table, and NULL is returned for them. All of the returned values can be used in comparison clauses in queries on the event table's 'type' field (but use GLOB instead of '=' so that the "*" returned by FSL_ATYPE_ANY can match!). For example, to get the comments from the most recent 5 commits: ``` SELECT datetime(mtime), coalesce(ecomment,comment), user FROM event WHERE type='ci' ORDER BY mtime DESC LIMIT 5; ``` Where 'ci' in the SQL is the non-NULL return value from this function. When escaping this value via fsl_buffer_appendf() (or anything functionally similar), use the %%q/%%Q format specifiers to escape it. */ FSL_EXPORT char const * fsl_satype_event_cstr(fsl_satype_e t); /** A collection of bitmaskable values indicating categories of fossil-standard glob sets. These correspond to the following configurable settings: ignore-glob, crnl-glob, binary-glob */ enum fsl_glob_category_e{ /** Sentinel entry. */ FSL_GLOBS_INVALID = 0, /** Corresponds to the ignore-glob config setting. */ FSL_GLOBS_IGNORE = 0x01, /** Corresponds to the crnl-glob config setting. */ FSL_GLOBS_CRNL = 0x02, /** Corresponds to the binary-glob config setting. */ FSL_GLOBS_BINARY = 0x04, /** A superset of all config-level glob categories. */ FSL_GLOBS_ANY = 0xFF /* Potential TODO: add FSL_GLOBS_CURRENT_OP for use with SQL UDFs. The idea would be that SCM operations which could make use of op-specific glob lists, e.g., checkin/add/merge, could set a custom glob set as the current one and then access it via their SQL using `fsl_glob('_', ...)` or some such. */ }; typedef enum fsl_glob_category_e fsl_glob_category_e; /** Checks one or more of f's configurable glob lists to see if str matches one of them. If it finds a match, it returns a pointer to the matching glob (as per fsl_glob_list_matches()), the bytes of which are owned by f and may be invalidated via modification or reloading of the underlying glob list. In generally the return value can be used as a boolean - clients generally do not need to know exactly which glob matched. gtype specifies the glob list(s) to check in the form of a bitmask of fsl_glob_category_e values. Note that the order of the lists is unspecified, so if that is important for you then be sure that gtype only specifies one glob list (e.g. FSL_GLOBS_IGNORE) and call it again (e.g. passing FSL_GLOBS_BINARY) if you need to distinguish between those two cases. str must be a non-NULL, non-empty empty string. Returns NULL !str, !*str, gtype does not specify any known glob list(s), or no glob match is found. Performance is, abstractly speaking, horrible, because we're comparing arbitrarily long lists of glob patterns against an arbitrary string. That said, it's fast enough for our purposes. */ FSL_EXPORT char const * fsl_cx_glob_matches( fsl_cx * const f, int gtype, char const * str ); /** Converts a well-known fossil glob list configuration key to a fsl_glob_category_e value: - "ignore-glob" = FSL_GLOBS_IGNORE - "binary-glob" = FSL_GLOBS_BINARY - "crnl-glob" = FSL_GLOBS_CRNL - Anything else = FSL_GLOBS_INVALID To simplify this function's use via an SQL-accessible UDF, the `*-glob` names may be passed in without their `-glob` suffix, e.g. `"igore"` instead of `"ignore-glob"`. */ FSL_EXPORT fsl_glob_category_e fsl_glob_name_to_category(char const * str); /** Fetches f's glob list of the given category. If forceReload is true then the context will check whether the list has had any content added to its source since it was initially loaded. On success, returns 0 and assigns `*tgt` to the list (noting that it may be empty). On error `*tgt` is not modified. Returns FSL_RC_RANGE if gtype is not one of FSL_GLOBS_IGNORE, FSL_GLOBS_CRNL, or FSL_GLOBS_BINARY. Returns FSL_RC_OOM if there is an allocation error during list reloading. May return lower-level result codes from the filesystem or db layer if loading a given list fails. */ FSL_EXPORT int fsl_cx_glob_list( fsl_cx * const f, fsl_glob_category_e gtype, fsl_list ** tgt, bool forceReload ); /** Sets f's hash policy and returns the previous value. If f has a repository db open then the setting is stored there and any error in setting it is placed into f's error state but otherwise ignored for purposes of this call. If p is FSL_HPOLICY_AUTO *and* the current repository contains any |
︙ | ︙ | |||
7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 | */ #include "sqlite3.h" #if defined(__cplusplus) extern "C" { #endif /** Potential TODO. Maybe not needed - v1 uses only(?) 1 hook and we can do that w/o hooks. */ typedef int (*fsl_commit_hook_f)( void * state ); /** Potential TODO */ struct fsl_commit_hook { fsl_commit_hook_f hook; int sequence; void * state; }; #define fsl_commit_hook_empty_m {NULL,0,NULL} typedef struct fsl_commit_hook fsl_commit_hook; /* extern const fsl_commit_hook fsl_commit_hook_empty; */ /** Potential TODO. */ FSL_EXPORT int fsl_db_before_commit_hook( fsl_db * db, fsl_commit_hook_f f, int sequence, void * state ); | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > | | | | | < | | > > | 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 7832 7833 7834 7835 7836 7837 7838 7839 7840 7841 7842 7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 7891 7892 7893 7894 7895 7896 7897 7898 7899 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 | */ #include "sqlite3.h" #if defined(__cplusplus) extern "C" { #endif #if 0 /** Potential TODO. Maybe not needed - v1 uses only(?) 1 hook and we can do that w/o hooks. */ typedef int (*fsl_commit_hook_f)( void * state ); /** Potential TODO */ struct fsl_commit_hook { fsl_commit_hook_f hook; int sequence; void * state; }; #define fsl_commit_hook_empty_m {NULL,0,NULL} typedef struct fsl_commit_hook fsl_commit_hook; /* extern const fsl_commit_hook fsl_commit_hook_empty; */ /** Potential TODO. */ FSL_EXPORT int fsl_db_before_commit_hook( fsl_db * db, fsl_commit_hook_f f, int sequence, void * state ); #endif #if 0 /* We can't do this because it breaks when clients include both this header and sqlite3.h. Is there a solution which lets us _not_ include sqlite3.h from this file and also compiles when clients include both? */ #if !defined(SQLITE_OK) /** Placeholder for sqlite3/4 type. We currently use v3 but will almost certainly switch to v4 at some point. Before we can do that we need an upgrade/migration path. */ typedef struct sqlite3 sqlite3; #endif #endif /** Flags for use with fsl_db_open() and friends. */ enum fsl_open_flags_e { /** The "no flags" value. */ FSL_OPEN_F_NONE = 0, /** Flag for fsl_db_open() specifying that the db should be opened in read-only mode. */ FSL_OPEN_F_RO = 0x01, /** Flag for fsl_db_open() specifying that the db should be opened in read-write mode, but should not create the db if it does not already exist. */ FSL_OPEN_F_RW = 0x02, /** Flag for fsl_db_open() specifying that the db should be opened in read-write mode, creating the db if it does not already exist. */ FSL_OPEN_F_CREATE = 0x04, /** Shorthand for RW+CREATE flags. */ FSL_OPEN_F_RWC = FSL_OPEN_F_RW | FSL_OPEN_F_CREATE, /** Tells fsl_repo_open_xxx() to confirm that the db is a repository. */ FSL_OPEN_F_SCHEMA_VALIDATE = 0x20, /** Used by fsl_db_open() to to tell 1the underlying db connection to trace all SQL to stdout. This is often useful for testing, debugging, and learning about what's going on behind the scenes. */ FSL_OPEN_F_TRACE_SQL = 0x40 }; /** A level of indirection to "hide" the actual db driver implementation from the public API. Whether or not the API uses/will use sqlite3 or 4 is "officially unspecified." We currently use 3 because (A) it bootstraps development and testing by letting us use existing fossil repos for and (B) it reduces the number of potential problems when porting SQL-heavy code from the fossil(1) tree. Clients should try not to rely on the underlying db driver API, but may need it for some uses (e.g. binding custom SQL functions). Sidebar: at the time this port was initiated, sqlite4 was in an experimental stage to explore a new storage engine which, it was hoped, would speed up sqlite considerably. It ended up never leaving that stage because the performance gains of the storage engine did not justify such a significant upheaval. Even so, sqlite4 "might" come around sometime within the lifetime of this project, so it behooves us to abstract away this type from the public API. */ typedef sqlite3 fsl_dbh_t; /** Db handle wrapper class. Each instance wraps a single sqlite database handle. Fossil is built upon sqlite3, but this abstraction is intended to hide that, insofar as possible, from clients so as to simplify an eventual port from v3 to v4. Clients should avoid relying on the underlying db being sqlite (or at least not rely on a specific version), but may want to register custom functions with the driver (or perform similar low-level operations) and the option is left open for them to access that handle via the fsl_db::dbh member. In practice, library clients do not ever need to use the underlying sqlite API. @see fsl_db_open(); @see fsl_db_close(); @see fsl_stmt */ struct fsl_db { /** |
︙ | ︙ | |||
7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 | (2021-03-12) canonicalize db's which we fsl_db_open(), but not those which we ATTACH (which includes the repo and checkout dbs). We cannot reasonably canonicalize the repo db filename because it gets written into the checkout db so that the checkout knows where to find the repository. History has shown that that path needs to be stored exactly as a user entered it, which is often relative. */ char * filename; /** Holds the database name for use in creating queries. Might or might not be set/needed, depending on the context. */ char * name; /** Debugging/test counter. Closing a db with opened statements might assert() or trigger debug output when the db is closed. */ | > > > > | 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 7961 7962 7963 7964 7965 7966 7967 7968 7969 7970 7971 7972 7973 | (2021-03-12) canonicalize db's which we fsl_db_open(), but not those which we ATTACH (which includes the repo and checkout dbs). We cannot reasonably canonicalize the repo db filename because it gets written into the checkout db so that the checkout knows where to find the repository. History has shown that that path needs to be stored exactly as a user entered it, which is often relative. Memory is owned by this object. */ char * filename; /** Holds the database name for use in creating queries. Might or might not be set/needed, depending on the context. Memory is owned by this object. */ char * name; /** Debugging/test counter. Closing a db with opened statements might assert() or trigger debug output when the db is closed. */ |
︙ | ︙ | |||
7871 7872 7873 7874 7875 7876 7877 | (==fossil's infrastructure). @see fsl_db_before_commit() */ fsl_list beforeCommit; /** | > > > > > > | | | > | | | | | | | | | | | > | | | | | | | | | < | > | > < > < > < > < > < > < > | 8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038 8039 8040 8041 8042 8043 8044 8045 8046 8047 8048 8049 8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060 8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 8072 8073 8074 8075 8076 8077 8078 8079 8080 8081 8082 8083 8084 8085 8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 | (==fossil's infrastructure). @see fsl_db_before_commit() */ fsl_list beforeCommit; /** Internal buffer to reduce, potentially drasticlaly, reallocations caused via fsl_db_prepare_cached(). */ fsl_buffer cachePrepBuf; /** An internal cache of "static" queries - those which do not rely on call-time state unless that state can be bind()ed. Holds a linked list of (fsl_stmt*) instances, managed by the fsl_db_prepare_cached() and fsl_stmt_cached_yield() APIs. @see fsl_db_prepare_cached() */ fsl_stmt * cacheHead; /** A marker which tells fsl_db_close() whether or not fsl_db_malloc() allocated this instance (in which case fsl_db_close() will fsl_free() it) or not (in which case it does not fsl_free() it). */ void const * allocStamp; }; /** Empty-initialized fsl_db structure, intended for const-copy initialization. */ #define fsl_db_empty_m { \ NULL/*f*/, \ FSL_DBROLE_NONE, \ NULL/*dbh*/, \ fsl_error_empty_m /*error*/, \ NULL/*filename*/, \ NULL/*name*/, \ 0/*openStatementCount*/, \ 0/*beginCount*/, \ 0/*doRollback*/, \ 0/*priorChanges*/, \ fsl_list_empty_m/*beforeCommit*/, \ fsl_buffer_empty_m/*cachePrepBuf*/, \ NULL/*cacheHead*/, \ NULL/*allocStamp*/ \ } /** Empty-initialized fsl_db structure, intended for copy initialization. */ FSL_EXPORT const fsl_db fsl_db_empty; /** If db is not NULL then this function returns its name (the one used to open it). The bytes are valid until the db connection is closed or until someone mucks with db->filename. If len is not NULL then *len is (on success) assigned to the length of the returned string, in bytes. The string is NUL-terminated, so fetching the length (by passing a non-NULL 2nd parameter) is optional but sometimes useful to eliminate a downstream call to fsl_strlen(). Results are undefined if db is NULL or was improperly initialized. Will return NULL if db was properly initialized (via copying fsl_db_empty) but has not yet been opened. */ FSL_EXPORT char const * fsl_db_filename(fsl_db const * db, fsl_size_t * len); typedef sqlite3_stmt fsl_stmt_t; /** Represents a prepared statement handle. Intended usage: ``` fsl_stmt st = fsl_stmt_empty; int rc = fsl_db_prepare( db, &st, "..." ); if(rc){ // Error! assert(!st.stmt); // db->error might hold driver-level error details. }else{ // use st and eventually finalize it: fsl_stmt_finalize( &st ); } ``` Script binding implementations can largely avoid exposing the statement handle (and its related cleanup ordering requirements) to script code. They need to have some mechanism for binding values to SQL (or implement all the escaping themselves), but that can be done without exposing all of the statement class if desired. For example, here's some hypothetical script code: ``` var st = db.prepare(".... where i=:i and x=:x"); // st is-a Statement, but we need not add script bindings for // the whole Statement.bind() API. We can instead simplify that // to something like: try { st.exec( {i: 42, x: 3} ) // or, for a SELECT query: st.each({ bind{i:42, x:3}, rowType: 'array', // or 'object' callback: function(row,state,colNames){ print(row.join('\t')); }, state: {...callback function state...} }); } finally { st.finalize(); // It is critical that st gets finalized before its DB, and // that'shard to guaranty if we leave st to the garbage collector! } // see below for another (less messy) alternative ``` Ideally, script code should not have direct access to the Statement because managing lifetimes can be difficult in the face of flow-control changes caused by exceptions (as the above example demonstrates). Statements can be completely hidden from clients if the DB wrapper is written to support it. For example, in pseudo-JavaScript that might look like: ``` db.exec("...where i=? AND x=?", 42, 3); db.each({sql:"select ... where id<?", bind:[10], rowType: 'array', // or 'object' callback: function(row,state,colNames){ print(row.join('\t')); }, state: {...arbitrary state for the callback...} }); ``` */ struct fsl_stmt { /** The db which prepared this statement. */ fsl_db * db; |
︙ | ︙ | |||
8031 8032 8033 8034 8035 8036 8037 | fsl_stmt_step(). */ fsl_size_t rowCount; /** Internal state flags. */ | | > > > > > | | | | | | > | | | | 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 | fsl_stmt_step(). */ fsl_size_t rowCount; /** Internal state flags. */ short flags; /** Internal use only: counts the number of times this query has been resolved as cached via fsl_db_prepare_cached(). */ fsl_size_t cachedHits; /** For _internal_ use in creating linked lists. Clients _must_not_ modify this field. */ fsl_stmt * next; /** A marker which tells fsl_stmt_finalize() whether or not fsl_stmt_malloc() allocated this instance (in which case fsl_stmt_finalize() will fsl_free() it) or not (in which case it does not free() it). */ void const * allocStamp; }; /** Empty-initialized fsl_stmt instance, intended for use as an in-struct initializer. */ #define fsl_stmt_empty_m { \ NULL/*db*/, \ NULL/*stmt*/, \ fsl_buffer_empty_m/*sql*/, \ 0/*colCount*/, \ 0/*paramCount*/, \ 0/*rowCount*/, \ 0/*flags*/, \ 0/*cachedHits*/, \ NULL/*next*/, \ NULL/*allocStamp*/ \ } /** Empty-initialized fsl_stmt instance, intended for copy-constructing. */ FSL_EXPORT const fsl_stmt fsl_stmt_empty; |
︙ | ︙ | |||
8085 8086 8087 8088 8089 8090 8091 | /** If db is not NULL this behaves like fsl_error_get(), using the db's underlying error state. If !db then it returns FSL_RC_MISUSE. */ | | > | | 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 | /** If db is not NULL this behaves like fsl_error_get(), using the db's underlying error state. If !db then it returns FSL_RC_MISUSE. */ FSL_EXPORT int fsl_db_err_get( fsl_db const * const db, char const ** msg, fsl_size_t * len ); /** Resets any error state in db, but might keep the string memory allocated for later use. */ FSL_EXPORT void fsl_db_err_reset( fsl_db * const db ); /** Prepares an SQL statement for execution. On success it returns 0, populates tgt with the statement's state, and the caller is obligated to eventually pass tgt to fsl_stmt_finalize(). tgt must have been cleanly initialized, either via allocation via fsl_stmt_malloc() or by copy-constructing fsl_stmt_empty |
︙ | ︙ | |||
8131 8132 8133 8134 8135 8136 8137 | (fsl_stmt_col_count()==0) without having to know the contents of the query. - fsl_db_prepare_cached() can be used to cache often-used or expensive-to-prepare queries within the context of their parent db handle. */ | | > | > | 8292 8293 8294 8295 8296 8297 8298 8299 8300 8301 8302 8303 8304 8305 8306 8307 8308 8309 8310 8311 8312 8313 | (fsl_stmt_col_count()==0) without having to know the contents of the query. - fsl_db_prepare_cached() can be used to cache often-used or expensive-to-prepare queries within the context of their parent db handle. */ FSL_EXPORT int fsl_db_prepare( fsl_db * const db, fsl_stmt * const tgt, char const * sql, ... ); /** va_list counterpart of fsl_db_prepare(). */ FSL_EXPORT int fsl_db_preparev( fsl_db * const db, fsl_stmt * const tgt, char const * sql, va_list args ); /** A special-purpose variant of fsl_db_prepare() which caches statements based on their SQL code. This works very much like fsl_db_prepare() and friends except that it can return the same statement (via *st) multiple times (statements with identical SQL are considered equivalent for caching purposes). Clients |
︙ | ︙ | |||
8169 8170 8171 8172 8173 8174 8175 | Returns 0 on success, FSL_RC_MISUSE if any arguments are invalid. On error, *st is not written to. On other error's db->error might be updated with more useful information. See the Caveats section below for more details. Its intended usage looks like: | < > < > | 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 | Returns 0 on success, FSL_RC_MISUSE if any arguments are invalid. On error, *st is not written to. On other error's db->error might be updated with more useful information. See the Caveats section below for more details. Its intended usage looks like: ``` fsl_stmt * st = NULL; int rc = fsl_db_prepare_cached(myDb, &st, "SELECT ..."); if(rc) { assert(!st); ...error... } else { ...use it, and _be sure_ to yield it when done:... fsl_stmt_cached_yield(st); } ``` Though this function allows a formatted SQL string, caching is generally only useful with statements which have "static" SQL, i.e. no call-dependent values embedded within the SQL. It _can_, however, contain bind() placeholders which get reset for each use. Note that fsl_stmt_cached_yield() resets the statement, so most uses of cached statements do not require that the client |
︙ | ︙ | |||
8218 8219 8220 8221 8222 8223 8224 | queries in spots which would normally break them. The whole recursion problem is still theoretical at this point but could easily affect small, often-used queries without recursion. @see fsl_db_stmt_cache_clear() @see fsl_stmt_cached_yield() */ | | > | | | < < < < < < < < < < < < < | 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390 8391 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 8410 8411 8412 8413 8414 8415 8416 8417 8418 8419 8420 8421 | queries in spots which would normally break them. The whole recursion problem is still theoretical at this point but could easily affect small, often-used queries without recursion. @see fsl_db_stmt_cache_clear() @see fsl_stmt_cached_yield() */ FSL_EXPORT int fsl_db_prepare_cached( fsl_db * const db, fsl_stmt ** st, char const * sql, ... ); /** The va_list counterpart of fsl_db_prepare_cached(). */ FSL_EXPORT int fsl_db_preparev_cached( fsl_db * const db, fsl_stmt ** st, char const * sql, va_list args ); /** "Yields" a statement which was prepared with fsl_db_prepare_cached(), such that that routine can once again use/re-issue that statement. Statements prepared this way must be yielded in order to prevent that recursion causes difficult-to-track errors when a given cached statement is used concurrently in different code contexts. If st is not NULL then this also calls fsl_stmt_reset() on the statement (because that simplifies usage of cached statements). Returns 0 on success, FSL_RC_MISUSE if !st or if st does not appear to have been doled out from fsl_db_prepare_cached(). @see fsl_db_prepare_cached() @see fsl_db_stmt_cache_clear() */ FSL_EXPORT int fsl_stmt_cached_yield( fsl_stmt * const st ); /** A special-purposes utility which schedules SQL to be executed the next time fsl_db_transaction_end() commits a transaction for the given db. A commit or rollback will clear all before-commit SQL whether it executes them or not. This should not be used as a general-purpose trick, and is intended only for use in very |
︙ | ︙ | |||
8287 8288 8289 8290 8291 8292 8293 | preparation errors (which otherwise cause the the commit to fail). The down-side is that it prohibits the use of multi-statement pre-commit code. We have an implementation of this somewhere early on in the libfossil tree, but it was not integrated because of the inability to use multi-statement SQL with it. */ | | | | | 8438 8439 8440 8441 8442 8443 8444 8445 8446 8447 8448 8449 8450 8451 8452 8453 8454 8455 8456 8457 8458 8459 8460 8461 8462 8463 8464 8465 8466 8467 8468 | preparation errors (which otherwise cause the the commit to fail). The down-side is that it prohibits the use of multi-statement pre-commit code. We have an implementation of this somewhere early on in the libfossil tree, but it was not integrated because of the inability to use multi-statement SQL with it. */ FSL_EXPORT int fsl_db_before_commit( fsl_db * const db, char const * const sql, ... ); /** va_list counterpart to fsl_db_before_commit(). */ FSL_EXPORT int fsl_db_before_commitv( fsl_db * const db, char const * const sql, va_list args ); /** Frees memory associated with stmt but does not free stmt unless it was allocated by fsl_stmt_malloc() (these objects are normally stack-allocated, and such object must be initialized by copying fsl_stmt_empty so that this function knows whether or not to fsl_free() them). Returns FSL_RC_MISUSE if !stmt or it has already been finalized (but was not freed). */ FSL_EXPORT int fsl_stmt_finalize( fsl_stmt * const stmt ); /** "Steps" the given SQL cursor one time and returns one of the following: FSL_RC_STEP_ROW, FSL_RC_STEP_DONE, FSL_RC_STEP_ERROR. On a db error this will update the underlying db's error state. This function increments stmt->rowCount by 1 if it returns FSL_RC_STEP_ROW. |
︙ | ︙ | |||
8325 8326 8327 8328 8329 8330 8331 | UPDATE or INSERT). @see fsl_stmt_reset() @see fsl_stmt_reset2() @see fsl_stmt_each() */ | | | 8476 8477 8478 8479 8480 8481 8482 8483 8484 8485 8486 8487 8488 8489 8490 | UPDATE or INSERT). @see fsl_stmt_reset() @see fsl_stmt_reset2() @see fsl_stmt_each() */ FSL_EXPORT int fsl_stmt_step( fsl_stmt * const stmt ); /** A callback interface for use with fsl_stmt_each() and fsl_db_each(). It will be called one time for each row fetched, passed the statement object and the state parameter passed to fsl_stmt_each() resp. fsl_db_each(). If it returns non-0 then iteration stops and that code is returned UNLESS it returns |
︙ | ︙ | |||
8363 8364 8365 8366 8367 8368 8369 | applies no meaning to the callbackState parameter, which gets passed as-is to the callback. See fsl_stmt_each_f() for the semantics of the callback. Returns 0 on success. Returns FSL_RC_MISUSE if !stmt or !callback. */ | | | > | | | | | | > | | | | | | | | | | | | | | | | | | | > | | > | | | | | | | | > | | > | | > | | | | | < > < > < > < > | | | | | 8514 8515 8516 8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 8582 8583 8584 8585 8586 8587 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609 8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634 8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659 8660 8661 8662 8663 8664 8665 8666 8667 8668 8669 8670 8671 8672 8673 8674 8675 8676 8677 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 8691 8692 8693 8694 8695 8696 8697 8698 8699 8700 8701 8702 8703 8704 8705 8706 8707 8708 8709 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 8727 8728 8729 8730 8731 8732 8733 8734 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 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 8805 8806 8807 8808 8809 8810 8811 8812 8813 8814 8815 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 | applies no meaning to the callbackState parameter, which gets passed as-is to the callback. See fsl_stmt_each_f() for the semantics of the callback. Returns 0 on success. Returns FSL_RC_MISUSE if !stmt or !callback. */ FSL_EXPORT int fsl_stmt_each( fsl_stmt * const stmt, fsl_stmt_each_f callback, void * callbackState ); /** Resets the given statement, analog to sqlite3_reset(). Should be called one time between fsl_stmt_step() iterations when running multiple INSERTS, UPDATES, etc. via the same statement. If resetRowCounter is true then the statement's row counter (st->rowCount) is also reset to 0, else it is left unmodified. (Most use cases don't use the row counter.) Returns 0 on success, FSL_RC_MISUSE if stmt has not been prepared or has not been cleanly initialized via copying from fsl_stmt_empty or fsl_stmt_empty_m, FSL_RC_DB if the underlying reset fails (in which case the error state of the stmt->db handle is updated to contain the error information). @see fsl_stmt_db() @see fsl_stmt_reset() */ FSL_EXPORT int fsl_stmt_reset2( fsl_stmt * const stmt, bool resetRowCounter ); /** Equivalent to fsl_stmt_reset2(stmt, 0). */ FSL_EXPORT int fsl_stmt_reset( fsl_stmt * const stmt ); /** Returns the db handle which prepared the given statement, or NULL if !stmt or stmt has not been prepared. */ FSL_EXPORT fsl_db * fsl_stmt_db( fsl_stmt * const stmt ); /** Returns the SQL string used to prepare the given statement, or NULL if !stmt or stmt has not been prepared. If len is not NULL then *len is set to the length of the returned string (which is NUL-terminated). The returned bytes are owned by stmt and are invalidated when it is finalized. */ FSL_EXPORT char const * fsl_stmt_sql( fsl_stmt * const stmt, fsl_size_t * const len ); /** Returns the name of the given 0-based result column index, or NULL if !stmt, stmt is not prepared, or index is out out of range. The returned bytes are owned by the statement object and may be invalidated shortly after this is called, so the caller must copy the returned value if it needs to have any useful lifetime guarantees. It's a bit more complicated than this, but assume that any API calls involving the statement handle might invalidate the column name bytes. The API guarantees that the returned value is either NULL or NUL-terminated. @see fsl_stmt_param_count() @see fsl_stmt_col_count() */ FSL_EXPORT char const * fsl_stmt_col_name(fsl_stmt * const stmt, int index); /** Returns the result column count for the given statement, or -1 if !stmt or it has not been prepared. Note that this value is cached when the statement is created. Note that non-fetching queries (e.g. INSERT and UPDATE) have a column count of 0. Some non-SELECT constructs, e.g. PRAGMA table_info(tname), behave like SELECT and have a positive column count. @see fsl_stmt_param_count() @see fsl_stmt_col_name() */ FSL_EXPORT int fsl_stmt_col_count( fsl_stmt const * const stmt ); /** Returns the bound parameter count for the given statement, or -1 if !stmt or it has not been prepared. Note that this value is cached when the statement is created. @see fsl_stmt_col_count() @see fsl_stmt_col_name() */ FSL_EXPORT int fsl_stmt_param_count( fsl_stmt const * const stmt ); /** Returns the index of the given named parameter for the given statement, or -1 if !stmt or stmt is not prepared. */ FSL_EXPORT int fsl_stmt_param_index( fsl_stmt * const stmt, char const * const param); /** Binds NULL to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_null( fsl_stmt * const stmt, int index ); /** Equivalent to fsl_stmt_bind_null_name() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_null_name( fsl_stmt * const stmt, char const * param ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_int32( fsl_stmt * const stmt, int index, int32_t v ); /** Equivalent to fsl_stmt_bind_int32() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_int32_name( fsl_stmt * const stmt, char const * param, int32_t v ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_int64( fsl_stmt * const stmt, int index, int64_t v ); /** Equivalent to fsl_stmt_bind_int64() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_int64_name( fsl_stmt * const stmt, char const * param, int64_t v ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the Fossil context's error state on error. */ FSL_EXPORT int fsl_stmt_bind_double( fsl_stmt * const stmt, int index, double v ); /** Equivalent to fsl_stmt_bind_double() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_double_name( fsl_stmt * const stmt, char const * param, double v ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_id( fsl_stmt * const stmt, int index, fsl_id_t v ); /** Equivalent to fsl_stmt_bind_id() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_id_name( fsl_stmt * const stmt, char const * param, fsl_id_t v ); /** Binds the first n bytes of v as text to the given 1-based bound parameter column in the given statement. If makeCopy is true then the binding makes an copy of the data. Set makeCopy to false ONLY if you KNOW that the bytes will outlive the binding. Returns 0 on success. On error stmt's underlying db's error state is updated, hopefully with a useful error message. */ FSL_EXPORT int fsl_stmt_bind_text( fsl_stmt * const stmt, int index, char const * v, fsl_int_t n, bool makeCopy ); /** Equivalent to fsl_stmt_bind_text() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_text_name( fsl_stmt * const stmt, char const * param, char const * v, fsl_int_t n, bool makeCopy ); /** Binds the first n bytes of v as a blob to the given 1-based bound parameter column in the given statement. See fsl_stmt_bind_text() for the semantics of the makeCopy parameter and return value. */ FSL_EXPORT int fsl_stmt_bind_blob( fsl_stmt * const stmt, int index, void const * v, fsl_size_t len, bool makeCopy ); /** Equivalent to fsl_stmt_bind_blob() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_blob_name( fsl_stmt * const stmt, char const * param, void const * v, fsl_int_t len, bool makeCopy ); /** Gets an integer value from the given 0-based result set column, assigns *v to that value, and returns 0 on success. Returns FSL_RC_RANGE if index is out of range for stmt, FSL_RC_MISUSE if stmt has no result columns. */ FSL_EXPORT int fsl_stmt_get_int32( fsl_stmt * const stmt, int index, int32_t * v ); /** Gets an integer value from the given 0-based result set column, assigns *v to that value, and returns 0 on success. Returns FSL_RC_RANGE if index is out of range for stmt, FSL_RC_MISUSE if stmt has no result columns. */ FSL_EXPORT int fsl_stmt_get_int64( fsl_stmt * const stmt, int index, int64_t * v ); /** The fsl_id_t counterpart of fsl_stmt_get_int32(). Depending on the sizeof(fsl_id_t), it behaves as one of fsl_stmt_get_int32() or fsl_stmt_get_int64(). */ FSL_EXPORT int fsl_stmt_get_id( fsl_stmt * const stmt, int index, fsl_id_t * v ); /** Convenience form of fsl_stmt_get_id() which returns the value directly but cannot report errors. It returns -1 on error, but that is not unambiguously an error value. */ FSL_EXPORT fsl_id_t fsl_stmt_g_id( fsl_stmt * const stmt, int index ); /** Convenience form of fsl_stmt_get_int32() which returns the value directly but cannot report errors. It returns 0 on error, but that is not unambiguously an error. */ FSL_EXPORT int32_t fsl_stmt_g_int32( fsl_stmt * const stmt, int index ); /** Convenience form of fsl_stmt_get_int64() which returns the value directly but cannot report errors. It returns 0 on error, but that is not unambiguously an error. */ FSL_EXPORT int64_t fsl_stmt_g_int64( fsl_stmt * const stmt, int index ); /** Convenience form of fsl_stmt_get_double() which returns the value directly but cannot report errors. It returns 0 on error, but that is not unambiguously an error. */ FSL_EXPORT double fsl_stmt_g_double( fsl_stmt * const stmt, int index ); /** Convenience form of fsl_stmt_get_text() which returns the value directly but cannot report errors. It returns NULL on error, but that is not unambiguously an error because it also returns NULL if the column contains an SQL NULL value. If outLen is not NULL then it is set to the byte length of the returned string. */ FSL_EXPORT char const * fsl_stmt_g_text( fsl_stmt * const stmt, int index, fsl_size_t * outLen ); /** Gets double value from the given 0-based result set column, assigns *v to that value, and returns 0 on success. Returns FSL_RC_RANGE if index is out of range for stmt, FSL_RC_MISUSE if stmt has no result columns. */ FSL_EXPORT int fsl_stmt_get_double( fsl_stmt * const stmt, int index, double * v ); /** Gets a string value from the given 0-based result set column, assigns *out (if out is not NULL) to that value, assigns *outLen (if outLen is not NULL) to *out's length in bytes, and returns 0 on success. Ownership of the string memory is unchanged - it is owned by the statement and the caller should immediately copy it if it will be needed for much longer. Returns FSL_RC_RANGE if index is out of range for stmt, FSL_RC_MISUSE if stmt has no result columns. */ FSL_EXPORT int fsl_stmt_get_text( fsl_stmt * const stmt, int index, char const **out, fsl_size_t * outLen ); /** The Blob counterpart of fsl_stmt_get_text(). Identical to that function except that its output result (3rd paramter) type differs, and it fetches the data as a raw blob, without any sort of string interpretation. Returns FSL_RC_RANGE if index is out of range for stmt, FSL_RC_MISUSE if stmt has no result columns. */ FSL_EXPORT int fsl_stmt_get_blob( fsl_stmt * const stmt, int index, void const **out, fsl_size_t * outLen ); /** Executes multiple SQL statements, ignoring any results they might collect. Returns 0 on success, non-0 on error. On error db->error might be updated to report the problem. */ FSL_EXPORT int fsl_db_exec_multi( fsl_db * const db, const char * sql, ...); /** va_list counterpart of db_exec_multi(). */ FSL_EXPORT int fsl_db_exec_multiv( fsl_db * const db, const char * sql, va_list args); /** Executes a single SQL statement, skipping over any results it may have. Returns 0 on success. On error db's error state may be updated. */ FSL_EXPORT int fsl_db_exec( fsl_db * const db, char const * sql, ... ); /** va_list counterpart of fs_db_exec(). */ FSL_EXPORT int fsl_db_execv( fsl_db * const db, char const * sql, va_list args ); /** Begins a transaction on the given db. Nested transactions are not directly supported but the db handle keeps track of open/close counts, such that fsl_db_transaction_end() will not actually do anything until the transaction begin/end counter goes to 0. Returns FSL_RC_MISUSE if !db or the db is not connected, else the result of the underlying db call(s). Transactions are an easy way to implement "dry-run" mode for some types of applications. For example: ``` char dryRunMode = ...; fsl_db_transaction_begin(db); ...do your stuff... fsl_db_transaction_end(db, dryRunMode ? 1 : 0); ``` Here's a tip for propagating error codes when using transactions: ``` ... if(rc) fsl_db_transaction_end(db, 1); else rc = fsl_db_transaction_end(db, 0); ``` That ensures that we propagate rc in the face of a rollback but we also capture the rc for a commit (which might yet fail). Note that a rollback in and of itself is not an error (though it also might fail, that would be "highly unusual" and indicative of other problems), and we certainly don't want to overwrite that precious non-0 rc with a successful return result from a rollback (which would, in effect, hide the error from the client). */ FSL_EXPORT int fsl_db_transaction_begin(fsl_db * const db); /** Equivalent to fsl_db_transaction_end(db, 0). */ FSL_EXPORT int fsl_db_transaction_commit(fsl_db * const db); /** Equivalent to fsl_db_transaction_end(db, 1). */ FSL_EXPORT int fsl_db_transaction_rollback(fsl_db * const db); /** Forces a rollback of any pending transaction in db, regardless of the internal transaction begin/end counter. Returns FSL_RC_MISUSE if !db or db is not opened, else returns the value of the underlying ROLLBACK call. This also re-sets/frees any transaction-related state held by db (e.g. db->beforeCommit). Use with care, as this mucks about with db state in a way which is not all that pretty and it may confuse downstream code. Returns 0 on success. */ FSL_EXPORT int fsl_db_rollback_force(fsl_db * const db); /** Decrements the transaction counter incremented by fsl_db_transaction_begin() and commits or rolls back the transaction if the counter goes to 0. If doRollback is true then this rolls back (or schedules a |
︙ | ︙ | |||
8750 8751 8752 8753 8754 8755 8756 | Unfortunate low-level co-dependency: if db->f is not NULL and (db->role & FSL_DBROLE_REPO) then this function may perform extra repository-related post-processing on any commit, and checking the result code is particularly important for those cases. */ | | | | | | 8908 8909 8910 8911 8912 8913 8914 8915 8916 8917 8918 8919 8920 8921 8922 8923 8924 8925 8926 8927 8928 8929 8930 8931 8932 8933 8934 8935 8936 8937 8938 8939 8940 8941 8942 | Unfortunate low-level co-dependency: if db->f is not NULL and (db->role & FSL_DBROLE_REPO) then this function may perform extra repository-related post-processing on any commit, and checking the result code is particularly important for those cases. */ FSL_EXPORT int fsl_db_transaction_end(fsl_db * const db, bool doRollback); /** Returns the given db's current transaction depth. If the value is negative, its absolute value represents the depth but indicates that a rollback is pending. If it is positive, the transaction is still in a "good" state. If it is 0, no transaction is active. */ FSL_EXPORT int fsl_db_transaction_level(fsl_db * const db); /** Runs the given SQL query on the given db and returns true if the query returns any rows, else false. Returns 0 for any error as well. */ FSL_EXPORT bool fsl_db_exists(fsl_db * const db, char const * sql, ... ); /** va_list counterpart of fsl_db_exists(). */ FSL_EXPORT bool fsl_db_existsv(fsl_db * const db, char const * sql, va_list args ); /** Runs a fetch-style SQL query against DB and returns the first column of the first result row via *rv. If the query returns no rows, *rv is not modified. The intention is that the caller sets *rv to his preferred default (or sentinel) value before calling this. |
︙ | ︙ | |||
9013 9014 9015 9016 9017 9018 9019 | If localTime is true then the value is converted to the local time, otherwise it is not. @see fsl_db_unix_to_iso8601() @see fsl_julian_to_iso8601() @see fsl_iso8601_to_julian() */ | | | | > | 9171 9172 9173 9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184 9185 9186 9187 9188 9189 9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203 | If localTime is true then the value is converted to the local time, otherwise it is not. @see fsl_db_unix_to_iso8601() @see fsl_julian_to_iso8601() @see fsl_iso8601_to_julian() */ FSL_EXPORT char * fsl_db_julian_to_iso8601( fsl_db * const db, double j, bool msPrecision, bool localTime ); /** Returns the given Julian date value formatted as an ISO8601 string (with a fractional seconds part if msPrecision is true, else without it). Returns NULL if !db, db is not connected, j is less than 0, or on allocation error. The returned memory must eventually be freed using fsl_free(). If localTime is true then the value is converted to the local time, otherwise it is not. @see fsl_db_julian_to_iso8601() @see fsl_julian_to_iso8601() @see fsl_iso8601_to_julian() */ FSL_EXPORT char * fsl_db_unix_to_iso8601( fsl_db * const db, fsl_time_t j, bool localTime ); /** Returns the current time in Julian Date format. Returns a negative value if !db or db is not opened. */ FSL_EXPORT double fsl_db_julian_now(fsl_db * db); |
︙ | ︙ | |||
9107 9108 9109 9110 9111 9112 9113 | - If FSL_OPEN_F_SCHEMA_VALIDATE is set in openFlags then the db is validated to see if it has a fossil schema. If that validation fails, FSL_RC_REPO_NEEDS_REBUILD or FSL_RC_NOT_A_REPO will be returned and db's error state will be updated. db->f does not need to be set for that check to work. | < | | < | < < < < < < < < < < < < < < < < < < < < < < | | | < | > | | | | | > | | | 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 9279 9280 9281 9282 9283 9284 9285 9286 9287 9288 9289 9290 9291 9292 9293 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343 9344 9345 9346 9347 9348 | - If FSL_OPEN_F_SCHEMA_VALIDATE is set in openFlags then the db is validated to see if it has a fossil schema. If that validation fails, FSL_RC_REPO_NEEDS_REBUILD or FSL_RC_NOT_A_REPO will be returned and db's error state will be updated. db->f does not need to be set for that check to work. If db->f is not NULL when this function is called then a number of fossil-specific SQL-accessible functions are installed. See the file doc/db-udf.md in the libfossil source tree for complete docs. Note that functions in those docs described as "triggering a db error" will propagate that error, such that fsl_db_err_get() can report it to the client. @see fsl_db_close() @see fsl_db_prepare() @see fsl_db_malloc() */ FSL_EXPORT int fsl_db_open( fsl_db * const db, char const * dbFile, int openFlags ); /** Closes the given db handle and frees any resources owned by db. This is a no-op if db is NULL. If db was allocated using fsl_db_malloc() (as determined by examining db->allocStamp) then this routine also fsl_free()s it, otherwise it is assumed to either be on the stack or part of a larger struct and is not freed. If db has any pending transactions, they are rolled back by this function. */ FSL_EXPORT void fsl_db_close( fsl_db * const db ); /** If db is an opened db handle, this registers a debugging function with the db which traces all SQL to the given FILE handle (defaults to stdout if outStream is NULL). This mechanism is only intended for debugging and exploration of how Fossil works. Tracing is often as easy way to ensure that a given code block is getting run. As a special case, if db->f is not NULL _before_ it is is fsl_db_open()ed, then this function automatically gets installed if the SQL tracing option is enabled for that fsl_cx instance before the db is opened. This is a no-op if !db or db is not opened. */ FSL_EXPORT void fsl_db_sqltrace_enable( fsl_db * const db, FILE * outStream ); /** Returns the row ID of the most recent insertion, or -1 if !db, db is not connected, or 0 if no inserts have been performed. */ FSL_EXPORT fsl_id_t fsl_db_last_insert_id(fsl_db * const db); /** Returns non-0 (true) if the database (which must be open) table identified by zTableName has a column named zColName (case-sensitive), else returns 0. */ FSL_EXPORT bool fsl_db_table_has_column( fsl_db * const db, char const *zTableName, char const *zColName ); /** If a db name has been associated with db then it is returned, otherwise NULL is returned. A db has no name by default, but fsl_cx-used ones get their database name assigned to them (e.g. "main" for the main db). */ FSL_EXPORT char const * fsl_db_name(fsl_db const * const db); /** Returns a db name string for the given fsl_db_role value. The string is static, guaranteed to live as long as the app. It returns NULL (or asserts in debug builds) if passed FSL_DBROLE_NONE or some value out of range for the enum. |
︙ | ︙ | |||
9232 9233 9234 9235 9236 9237 9238 | The fsl_stmt counterpart of fsl_db_malloc(). See that function for when you might want to use this and a caveat involving the allocStamp member of the returned value. fsl_stmt_finalize() will free statements created with this function. */ FSL_EXPORT fsl_stmt * fsl_stmt_malloc(); | < | > | < < > < > | > | | | | | | < > | | | | | | | 9368 9369 9370 9371 9372 9373 9374 9375 9376 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 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 9467 9468 9469 9470 9471 9472 9473 9474 9475 9476 9477 9478 | The fsl_stmt counterpart of fsl_db_malloc(). See that function for when you might want to use this and a caveat involving the allocStamp member of the returned value. fsl_stmt_finalize() will free statements created with this function. */ FSL_EXPORT fsl_stmt * fsl_stmt_malloc(); /** ATTACHes the file zDbName to db using the databbase name zLabel. Returns 0 on success. Returns FSL_RC_MISUSE if any argument is NULL or any string argument starts with a NUL byte, else it returns the result of fsl_db_exec() which attaches the db. On db-level errors db's error state will be updated. */ FSL_EXPORT int fsl_db_attach(fsl_db * const db, const char *zDbName, const char *zLabel); /** The converse of fsl_db_detach(). Must be passed the same arguments which were passed as the 1st and 3rd arguments to fsl_db_attach(). Returns 0 on success, FSL_RC_MISUSE if !db, !zLabel, or !*zLabel, else it returns the result of the underlying fsl_db_exec() call. */ FSL_EXPORT int fsl_db_detach(fsl_db * const db, const char *zLabel); /** Expects fmt to be a SELECT-style query. For each row in the query, the first column is fetched as a string and appended to the tgt list. Returns 0 on success, FSL_RC_MISUSE if !db, !tgt, or !fmt, any number of potential FSL_RC_OOM or db-related errors. Results rows with a NULL value (resulting from an SQL NULL) are added to the list as NULL entries. Each entry appended to the list is a (char *) which must be freed using fsl_free(). To easiest way to clean up the list and its contents is: ``` fsl_list_visit_free(tgt,...); ``` On error the list may be partially populated. Complete example: ``` fsl_list li = fsl_list_empty; int rc = fsl_db_select_slist(db, &li, "SELECT uuid FROM blob WHERE rid<20"); if(!rc){ fsl_size_t i; for(i = 0;i < li.used; ++i){ char const * uuid = (char const *)li.list[i]; fsl_fprintf(stdout, "UUID: %s\n", uuid); } } fsl_list_visit_free(&li, 1); ``` Of course fsl_list_visit() may be used to traverse the list as well, as long as the visitor expects (char [const]*) list elements. */ FSL_EXPORT int fsl_db_select_slist( fsl_db * const db, fsl_list * tgt, char const * fmt, ... ); /** The va_list counterpart of fsl_db_select_slist(). */ FSL_EXPORT int fsl_db_select_slistv( fsl_db * const db, fsl_list * tgt, char const * fmt, va_list args ); /** Returns n bytes of random lower-case hexidecimal characters using the given db as its data source, plus a terminating NUL byte. The returned memory must eventually be freed using fsl_free(). Returns NULL if !db, !n, or on a db-level error. */ FSL_EXPORT char * fsl_db_random_hex(fsl_db * db, fsl_size_t n); /** Returns the "number of database rows that were changed or inserted or deleted by the most recently completed SQL statement" (to quote the underlying APIs). Returns 0 if !db or if db is not opened. See: https://sqlite.org/c3ref/changes.html */ FSL_EXPORT int fsl_db_changes_recent(fsl_db * const db); /** Returns "the number of row changes caused by INSERT, UPDATE or DELETE statements since the database connection was opened" (to quote the underlying APIs). Returns 0 if !db or if db is not opened. See; https://sqlite.org/c3ref/total_changes.html */ FSL_EXPORT int fsl_db_changes_total(fsl_db * const db); /** Initializes the given database file. zFilename is the name of the db file. It is created if needed, but any directory components are not created. zSchema is the base schema to install. The following arguments may be (char const *) SQL code, each of which gets run against the db after the main |
︙ | ︙ | |||
9369 9370 9371 9372 9373 9374 9375 | FSL_DBROLE_TEMP. Potential TODO: this is a bit of a wonky interface. Consider changing it to eliminate the role argument, which is only really needed if we have duplicate table names across attached dbs or if we internally mess up and write a table to the wrong db. */ | | | | 9505 9506 9507 9508 9509 9510 9511 9512 9513 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 | FSL_DBROLE_TEMP. Potential TODO: this is a bit of a wonky interface. Consider changing it to eliminate the role argument, which is only really needed if we have duplicate table names across attached dbs or if we internally mess up and write a table to the wrong db. */ FSL_EXPORT bool fsl_db_table_exists(fsl_db * const db, fsl_dbrole_e whichDb, const char *zTable); /** The elipsis counterpart of fsl_stmt_bind_fmtv(). */ FSL_EXPORT int fsl_stmt_bind_fmt( fsl_stmt * const st, char const * fmt, ... ); /** Binds a series of values using a formatting string. The string may contain the following characters, each of which refers to the next argument in the args list: |
︙ | ︙ | |||
9527 9528 9529 9530 9531 9532 9533 | /** The hash string of the initial MD5 state. Used as an optimization for some places where we need an MD5 but know it will not hash any data. Equivalent to what the md5sum command outputs for empty input: | < > < > < > < > | 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 9678 9679 9680 9681 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 | /** The hash string of the initial MD5 state. Used as an optimization for some places where we need an MD5 but know it will not hash any data. Equivalent to what the md5sum command outputs for empty input: ``` # md5sum < /dev/null d41d8cd98f00b204e9800998ecf8427e - ``` */ #define FSL_MD5_INITIAL_HASH "d41d8cd98f00b204e9800998ecf8427e" /** Holds state for MD5 calculations. It is intended to be used like this: ``` unsigned char digest[16]; char hex[FSL_STRLEN_MD5+1]; fsl_md5_cx cx = fsl_md5_cx_empty; // alternately: fsl_md5_init(&cx); ...call fsl_md5_update(&cx,...) any number of times to ...incrementally calculate the hash. fsl_md5_final(&cx, digest); // ends the calculation fsl_md5_digest_to_base16(digest, hex); // digest now contains the raw 16-byte MD5 digest. // hex now contains the 32-byte MD5 + a trailing NUL ``` */ struct fsl_md5_cx { int isInit; uint32_t buf[4]; uint32_t bits[2]; unsigned char in[64]; }; |
︙ | ︙ | |||
9682 9683 9684 9685 9686 9687 9688 | #if FSL_SHA1_HARDENED typedef void(*fsl_sha1h_collision_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); #endif /** Holds state for SHA1 calculations. It is intended to be used like this: | < > < > | 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 | #if FSL_SHA1_HARDENED typedef void(*fsl_sha1h_collision_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); #endif /** Holds state for SHA1 calculations. It is intended to be used like this: ``` unsigned char digest[20] char hex[FSL_STRLEN_SHA1+1]; fsl_sha1_cx cx = fsl_sha1_cx_empty; // alternately: fsl_sha1_init(&cx) ...call fsl_sha1_update(&cx,...) any number of times to ...incrementally calculate the hash. fsl_sha1_final(&cx, digest); // ends the calculation fsl_sha1_digest_to_base16(digest, hex); // digest now contains the raw 20-byte SHA1 digest. // hex now contains the 40-byte SHA1 + a trailing NUL ``` */ struct fsl_sha1_cx { #if FSL_SHA1_HARDENED uint64_t total; uint32_t ihv[5]; unsigned char buffer[64]; int bigendian; |
︙ | ︙ | |||
9863 9864 9865 9866 9867 9868 9869 | /** Type for holding SHA3 processing state. Each instance must be initialized with fsl_sha3_init(), populated with fsl_sha3_update(), and "sealed" with fsl_sha3_end(). Sample usage: | < > < > | 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018 10019 | /** Type for holding SHA3 processing state. Each instance must be initialized with fsl_sha3_init(), populated with fsl_sha3_update(), and "sealed" with fsl_sha3_end(). Sample usage: ``` fsl_sha3_cx cx; fsl_sha3_init(&cx, FSL_SHA3_DEFAULT); fsl_sha3_update(&cx, memory, lengthOfMemory); fsl_sha3_end(&cx); printf("Hash = %s\n", (char const *)cx.hex); ``` After fsl_sha3_end() is called cx.hex contains the hex-string forms of the digest. Note that fsl_sha3_update() may be called an arbitrary number of times to feed in chunks of memory (e.g. to stream in arbitrarily large data). */ struct fsl_sha3_cx { |
︙ | ︙ | |||
10284 10285 10286 10287 10288 10289 10290 | determined if a given card type is legal for a given value of this member. APIs which add/set cards use that to determine if the operation requested by the client is semantically legal. */ fsl_satype_e type; /** | | < < < < < < | 10420 10421 10422 10423 10424 10425 10426 10427 10428 10429 10430 10431 10432 10433 10434 10435 10436 10437 | determined if a given card type is legal for a given value of this member. APIs which add/set cards use that to determine if the operation requested by the client is semantically legal. */ fsl_satype_e type; /** DB repo.blob.rid value. Normally set by fsl_deck_load_rid(). */ fsl_id_t rid; /** The Fossil context responsible for this deck. Though this data type is normally, at least conceptually, free of any given fossil context, many related algorithms need a context in order to perform db- or caching-related work, as well as to simplify error message propagation. We store this as a struct member to keep all such algorithms from redundantly requiring both pieces of |
︙ | ︙ | |||
10500 10501 10502 10503 10504 10505 10506 | /** Initialized-with-defaults fsl_deck structure, intended for in-struct and const copy initialization. */ #define fsl_deck_empty_m { \ FSL_SATYPE_ANY /*type*/, \ 0/*rid*/, \ | < | 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 | /** Initialized-with-defaults fsl_deck structure, intended for in-struct and const copy initialization. */ #define fsl_deck_empty_m { \ FSL_SATYPE_ANY /*type*/, \ 0/*rid*/, \ NULL/*f*/, \ {/*A*/ NULL /* name */, \ NULL /* tgt */, \ NULL /* src */}, \ {/*B*/ NULL /*uuid*/, \ NULL /*baseline*/}, \ NULL /* C */, \ |
︙ | ︙ | |||
10550 10551 10552 10553 10554 10555 10556 | state, but does not free() deck. Is a no-op if deck is NULL. As a special case, the (allocStamp, f) members of deck are kept intact. @see fsl_deck_finalize() @see fsl_deck_malloc() @see fsl_deck_clean2() */ | | | | 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 10697 10698 10699 10700 10701 10702 10703 10704 10705 10706 10707 10708 | state, but does not free() deck. Is a no-op if deck is NULL. As a special case, the (allocStamp, f) members of deck are kept intact. @see fsl_deck_finalize() @see fsl_deck_malloc() @see fsl_deck_clean2() */ FSL_EXPORT void fsl_deck_clean(fsl_deck * const deck); /** A variant of fsl_deck_clean() which "returns" its content buffer for re-use by transferring, after ensuring proper cleanup of its internals, its own content buffer's bytes into the given target buffer. Note that decks created "manually" do not have any content buffer contents, but those loaded via fsl_deck_load_rid() do. This function will fsl_buffer_swap() the contents of the given buffer (if any) with its own buffer, clean up its newly-acquired memory (tgt's previous contents, if any), and fsl_buffer_reuse() the output buffer. If tgt is NULL, this behaves exactly like fsl_deck_clean(). */ FSL_EXPORT void fsl_deck_clean2(fsl_deck * const deck, fsl_buffer * const tgt); /** Frees all memory owned by deck (see fsl_deck_clean()). If deck was allocated using fsl_deck_malloc() then this function fsl_free()'s it, otherwise it does not free it. @see fsl_deck_malloc() |
︙ | ︙ | |||
10600 10601 10602 10603 10604 10605 10606 | wiki pages this is their normal name (e.g. "MyWikiPage"). For events and tickets it is their full 40-byte UUID. uuidSrc is the UUID of the attachment blob itself. If it is NULL or empty then this card indicates that the attachment will be "deleted" (insofar as anything is ever deleted in Fossil). */ | | | 10729 10730 10731 10732 10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 | wiki pages this is their normal name (e.g. "MyWikiPage"). For events and tickets it is their full 40-byte UUID. uuidSrc is the UUID of the attachment blob itself. If it is NULL or empty then this card indicates that the attachment will be "deleted" (insofar as anything is ever deleted in Fossil). */ FSL_EXPORT int fsl_deck_A_set( fsl_deck * const mf, char const * filename, char const * target, fsl_uuid_cstr uuidSrc); /** Sets or unsets (if uuidBaseline is NULL or empty) the B-card for the given manifest to a copy of the given UUID. Returns 0 on success, FSL_RC_MISUSE if !mf, FSL_RC_OOM on allocation |
︙ | ︙ | |||
10625 10626 10627 10628 10629 10630 10631 | fsl_card_is_legal(mf->type,...)). Sidebar: the ability to unset this card is unusual within this API, and is a requirement the library-internal delta manifest creation process. Most of the card-setting APIs, even when they are described as working like this one, do not accept NULL hash values. */ | | | | | | 10754 10755 10756 10757 10758 10759 10760 10761 10762 10763 10764 10765 10766 10767 10768 10769 10770 10771 10772 10773 10774 10775 10776 10777 10778 10779 10780 10781 10782 10783 10784 10785 10786 10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 | fsl_card_is_legal(mf->type,...)). Sidebar: the ability to unset this card is unusual within this API, and is a requirement the library-internal delta manifest creation process. Most of the card-setting APIs, even when they are described as working like this one, do not accept NULL hash values. */ FSL_EXPORT int fsl_deck_B_set( fsl_deck * const mf, fsl_uuid_cstr uuidBaseline); /** Semantically identical to fsl_deck_B_set() but sets the C-card and does not place a practical limit on the comment's length. comment must be the comment text for the change being applied. If the given length is negative, fsl_strlen() is used to determine its length. */ FSL_EXPORT int fsl_deck_C_set( fsl_deck * const mf, char const * comment, fsl_int_t cardLen); /** Sets mf's D-card as a Julian Date value. Returns FSL_RC_MISUSE if !mf, FSL_RC_RANGE if date is negative, FSL_RC_TYPE if a D-card is not valid for the given deck, else 0. Passing a value of 0 effectively unsets the card. */ FSL_EXPORT int fsl_deck_D_set( fsl_deck * const mf, double date); /** Sets the E-card in the given deck. date may not be negative - use fsl_db_julian_now() or fsl_julian_now() to get a default time if needed. Retursn FSL_RC_MISUSE if !mf or !uuid, FSL_RC_RANGE if date is not positive, FSL_RC_RANGE if uuid is not a valid UUID string. Note that the UUID for an event, unlike most other UUIDs, need not be calculated - it may be a random hex string, but it must pass the fsl_is_uuid() test. Use fsl_db_random_hex() to generate random UUIDs. When editing events, e.g. using the HTML UI, only the most recent event with the same UUID is shown. So when updating events, be sure to apply the same UUID to the edited copies before saving them. */ FSL_EXPORT int fsl_deck_E_set( fsl_deck * const mf, double date, fsl_uuid_cstr uuid); /** Adds a new F-card to the given deck. The uuid argument is required to be NULL or pass the fsl_is_uuid() test. The name must be a "simplified path name" (as determined by fsl_is_simple_pathname()), or FSL_RC_RANGE is returned. Note that a NULL uuid is only valid when constructing a delta manifest, and this routine will return |
︙ | ︙ | |||
10787 10788 10789 10790 10791 10792 10793 | - Moves d->uuid into d->P - Clears d->rid - Clears any other members which need to be (re)set by the new child/derived version. - It specifically keeps d->F intact OR creates a new one (see below). Returns 0 on success, FSL_RC_OOM on an allocation error, | | > | | | | | 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943 10944 10945 10946 10947 10948 10949 10950 10951 | - Moves d->uuid into d->P - Clears d->rid - Clears any other members which need to be (re)set by the new child/derived version. - It specifically keeps d->F intact OR creates a new one (see below). Returns 0 on success, FSL_RC_OOM on an allocation error, FSL_RC_MISUSE if d->rid<=0 (i.e. the deck has never been saved or was not loaded from the db. If d->type is not FSL_SATYPE_CHECKIN, FSL_RC_TYPE is returned. On error, d may be left in an inconsistent state and must not be used further except to pass it to fsl_deck_finalize(). The intention of this function is to simplify creation of decks which are to be used for creating checkins without requiring a checkin. To avoid certain corner cases, this function does not allow creation of delta manifests. If d has a B-card then it is a delta. This function clears its B-card and recreates the F-card list using the B-card's F-card list and any F-cards from the current delta. In other words, it creates a new baseline manifest. TODO: extend this to support other inheritable deck types, e.g. wiki, forum posts, and technotes. @see fsl_deck_F_set_content() */ FSL_EXPORT int fsl_deck_derive(fsl_deck * const d); /** Callback type for use with fsl_deck_F_foreach() and friends. Implementations must return 0 on success, FSL_RC_BREAK to abort looping without an error, and any other value on error. */ typedef int (*fsl_card_F_visitor_f)(fsl_card_F const * fc, |
︙ | ︙ | |||
10853 10854 10855 10856 10857 10858 10859 | On success 0 is returned and *f is assigned to the next F-card. If *f is NULL when returning 0 then the end of the list has been reached (fsl_deck_F_rewind() can be used to re-set it). Example usage: | < > < > | 10983 10984 10985 10986 10987 10988 10989 10990 10991 10992 10993 10994 10995 10996 10997 10998 10999 11000 11001 11002 | On success 0 is returned and *f is assigned to the next F-card. If *f is NULL when returning 0 then the end of the list has been reached (fsl_deck_F_rewind() can be used to re-set it). Example usage: ``` int rc; fsl_card_F const * fc = NULL; rc = fsl_deck_F_rewind(d); if(!rc) while( !(rc=fsl_deck_F_next(d, &fc)) && fc) {...} ``` Note that files which were deleted in a given version are not recorded in baseline manifests but are in deltas. To avoid inconsistencies, this routine does NOT include deleted files in its results, regardless of whether d is a baseline or delta. (It used to, but that turned out to be a design flaw.) |
︙ | ︙ | |||
10958 10959 10960 10961 10962 10963 10964 | FSL_EXPORT int fsl_card_F_content( fsl_cx * f, fsl_card_F const * fc, fsl_buffer * dest ); /** Sets the 'G' card on a forum-post deck to a copy of the given UUID. */ | | | | | | | | | | | | | > | > > > > > > | | | | | | | | | | 11088 11089 11090 11091 11092 11093 11094 11095 11096 11097 11098 11099 11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 11118 11119 11120 11121 11122 11123 11124 11125 11126 11127 11128 11129 11130 11131 11132 11133 11134 11135 11136 11137 11138 11139 11140 11141 11142 11143 11144 11145 11146 11147 11148 11149 11150 11151 11152 11153 11154 11155 11156 11157 11158 11159 11160 11161 11162 11163 11164 11165 11166 11167 11168 11169 11170 11171 11172 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186 11187 11188 11189 11190 11191 11192 11193 11194 11195 11196 11197 11198 11199 11200 11201 11202 11203 11204 11205 11206 11207 11208 11209 11210 11211 11212 11213 11214 11215 11216 11217 11218 11219 11220 11221 11222 11223 11224 11225 11226 11227 11228 11229 11230 11231 11232 11233 11234 11235 11236 11237 11238 11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 11249 11250 11251 | FSL_EXPORT int fsl_card_F_content( fsl_cx * f, fsl_card_F const * fc, fsl_buffer * dest ); /** Sets the 'G' card on a forum-post deck to a copy of the given UUID. */ FSL_EXPORT int fsl_deck_G_set( fsl_deck * const mf, fsl_uuid_cstr uuid); /** Sets the 'H' card on a forum-post deck to a copy of the given comment. If cardLen is negative then fsl_strlen() is used to calculate its length. */ FSL_EXPORT int fsl_deck_H_set( fsl_deck * const mf, char const * comment, fsl_int_t cardLen); /** Sets the 'I' card on a forum-post deck to a copy of the given UUID. */ FSL_EXPORT int fsl_deck_I_set( fsl_deck * const mf, fsl_uuid_cstr uuid); /** Adds a J-card to the given deck, setting/updating the given ticket property key to the given value. The key is required but the value is optional (may be NULL). If isAppend then the value is appended to any existing value, otherwise it replaces any existing value. It is currently unclear whether it is legal to include multiple J cards for the same key in the same control artifact, in particular if their isAppend values differ. Returns 0 on success, FSL_RC_MISUSE if !mf or !key, FSL_RC_RANGE if !*field, FSL_RC_TYPE if mf is of a type for which J cards are not legal (see fsl_card_is_legal()), FSL_RC_OOM on allocation error. */ FSL_EXPORT int fsl_deck_J_add( fsl_deck * const mf, char isAppend, char const * key, char const * value ); /** Semantically identical fsl_deck_B_set() but sets the K-card and does not accept a NULL value. uuid must be the UUID of the ticket this change is being applied to. */ FSL_EXPORT int fsl_deck_K_set( fsl_deck * const mf, fsl_uuid_cstr uuid); /** Semantically identical fsl_deck_B_set() but sets the L-card. title must be the wiki page title text of the wiki page this change is being applied to. */ FSL_EXPORT int fsl_deck_L_set( fsl_deck * const mf, char const *title, fsl_int_t len); /** Adds the given UUID as an M-card entry. Returns 0 on success, or: FSL_RC_MISUSE if !mf or !uuid FSL_RC_TYPE if fsl_deck_check_type(mf,'M') returns false. FSL_RC_RANGE if !fsl_is_uuid(uuid). FSL_RC_OOM if memory allocation fails while adding the entry. */ FSL_EXPORT int fsl_deck_M_add( fsl_deck * const mf, fsl_uuid_cstr uuid ); /** Semantically identical to fsl_deck_B_set() but sets the N card. mimeType must be the content mime type for comment text of the change being applied. */ FSL_EXPORT int fsl_deck_N_set( fsl_deck * const mf, char const *mimeType, fsl_int_t len); /** Adds the given UUID as a parent of the given change record. If len is less than 0 then fsl_strlen(parentUuid) is used to determine its length. Returns FSL_RC_MISUE if !*parentUuid. Returns FSL_RC_RANGE if parentUuid does not pass fsl_is_uuid(). Results are undefined if parentUuid is NULL. The first P-card added to a deck MUST be the UUID of its primary parent (one which was not involved in a merge operation). All others (from merges) are considered "non-primary." */ FSL_EXPORT int fsl_deck_P_add( fsl_deck * const mf, fsl_uuid_cstr parentUuid); /** A convenience wrapper around fsl_deck_P_add() which resolves the given RID to its UUID and passes it on to fsl_deck_P_add(). Returns non-0 on error. */ FSL_EXPORT int fsl_deck_P_add_rid( fsl_deck * const mf, fsl_id_t parentRid ); /** If d contains a P card with the given index, this returns the RID corresponding to the UUID at that index. Returns a negative value on error, 0 if there is no entry for that index (the index is out of bounds). */ FSL_EXPORT fsl_id_t fsl_deck_P_get_id(fsl_deck * const d, int index); /** Adds a Q-card record to the given deck. The type argument must be negative for a backed-out change, positive for a cherrypicked change. target must be a valid UUID string. If baseline is not NULL then it also must be a valid UUID. Returns 0 on success, non-0 on error. FSL_RC_MISUSE if !mf or !target, FSL_RC_RANGE if target/baseline are not valid UUID strings (baseline may be NULL). */ FSL_EXPORT int fsl_deck_Q_add( fsl_deck * const mf, int type, fsl_uuid_cstr target, fsl_uuid_cstr baseline ); /** Functionally identical to fsl_deck_B_set() except that it sets the R-card. Returns 0 on succes, FSL_RC_RANGE if md5 is not NULL or exactly FSL_STRLEN_MD5 bytes long (not including trailing NUL). If md5==NULL the current R value is cleared. It would be highly unusual to have to set the R-card manually, as its calculation is quite intricate/intensive. See fsl_deck_R_calc() and fsl_deck_unshuffle() for details */ FSL_EXPORT int fsl_deck_R_set( fsl_deck * const mf, char const *md5); /** Adds a new T-card (tag) entry to the given deck. If uuid is not NULL and fsl_is_uuid(uuid) returns false then this function returns FSL_RC_RANGE. If uuid is NULL then it is assumed to be the UUID of the currently-being-constructed artifact in which the tag is contained (which appears as the '*' character in generated artifacts). Returns 0 on success. Returns FSL_RC_MISUE if !mf or !name. Returns FSL_RC_TYPE (and update's mf's error state with a message) if the T card is not legal for mf (see fsl_card_is_legal()). Returns FSL_RC_RANGE if !*name, tagType is invalid, or if uuid is not NULL and fsl_is_uuid(uuid) return false. Returns FSL_RC_OOM if an allocation fails. */ FSL_EXPORT int fsl_deck_T_add( fsl_deck * const mf, fsl_tagtype_e tagType, fsl_uuid_cstr uuid, char const * name, char const * value); /** Adds the given tag instance to the given manifest. Returns 0 on success, FSL_RC_MISUSE if either argument is NULL, FSL_RC_OOM if appending the tag to the list fails. On success ownership of t is passed to mf. On error ownership is not modified. */ FSL_EXPORT int fsl_deck_T_add2( fsl_deck * const mf, fsl_card_T * t); /** A convenience form of fsl_deck_T_add() which adds two propagating tags to the given deck: "branch" with a value of branchName and "sym-branchName" with no value. Returns 0 on success. Returns FSL_RC_OOM on allocation error and |
︙ | ︙ | |||
11173 11174 11175 11176 11177 11178 11179 | FSL_EXPORT int fsl_deck_R_calc2(fsl_deck *d, char ** tgt); /** Semantically identical fsl_deck_B_set() but sets the U-card. userName must be the user who's name should be recorded for this change. */ | | | | 11310 11311 11312 11313 11314 11315 11316 11317 11318 11319 11320 11321 11322 11323 11324 11325 11326 11327 11328 11329 11330 11331 | FSL_EXPORT int fsl_deck_R_calc2(fsl_deck *d, char ** tgt); /** Semantically identical fsl_deck_B_set() but sets the U-card. userName must be the user who's name should be recorded for this change. */ FSL_EXPORT int fsl_deck_U_set( fsl_deck * const mf, char const *userName); /** Semantically identical fsl_deck_B_set() but sets the W-card. content must be the page content of the Wiki page or Event this change is being applied to. */ FSL_EXPORT int fsl_deck_W_set( fsl_deck * const mf, char const *content, fsl_int_t len); /** Must be called to initialize a newly-created/allocated deck instance. This function clears out all contents of the d parameter except for its (f, type, allocStamp) members, sets its (f, type) members, and leaves d->allocStamp intact. */ |
︙ | ︙ | |||
11330 11331 11332 11333 11334 11335 11336 | has any F-cards AND if the caller has not already set/calculated it AND if f's FSL_CX_F_CALC_R_CARD flag is set (it is on by default for historical reasons, but this may change at some point). Returns 0 on success, the usual non-0 suspects on error. | | | > > > > | > | | | < < < < | < | | | | | 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 | has any F-cards AND if the caller has not already set/calculated it AND if f's FSL_CX_F_CALC_R_CARD flag is set (it is on by default for historical reasons, but this may change at some point). Returns 0 on success, the usual non-0 suspects on error. - If d->rid is positive, it is assumed to refer to a valid `blob.rid` and this function returns 0 without side-effects. It does _not_ attempt to re-save such a record, as that could very possible result in a new hash (and would definitely do so if the deck's state had been modified in the slightest bit (literally "bit")). It should arguably return FSL_RC_ALREADY_EXISTS for this case, but does not do so for historical reasons. - PREVIOUSLY (prior to 2021-10-20), we assumed such a deck referred to a phantom which we would then save, but that behaviour seems to be misguided, as we only ever want to save newly-created decks, not decks created from existing content. On success, d->rid be set to the new record's RID. It will only be set on success because it would otherwise refer to a db record which get destroyed when the transaction rolls back. After saving, the deck will be cross-linked to update any relationships described by the deck. The save operation happens within a transaction, of course, and on any sort of error, db-side changes are rolled back. Note that it _is_ legal to start the transaction before calling this, which effectively makes this operation part of that transaction. This function will fail with FSL_RC_ACCESS if d is a delta manifest (has a B-card) d->f's forbid-delta-manifests configuration option is set to a truthy value. See fsl_repo_forbids_delta_manifests(). Maintenance reminder: this function also does a very small bit of artifact-type-specific processing. @see fsl_deck_output() @see fsl_content_put_ex() */ FSL_EXPORT int fsl_deck_save( fsl_deck * const d, bool isPrivate ); /** This starts a transaction (possibly nested) on the repository db and initializes some temporary db state needed for the crosslinking certain artifact types. It "should" (see below) be called at the start of the crosslinking process. Crosslinking *can* work without this but certain steps for certain (subject to |
︙ | ︙ | |||
11449 11450 11451 11452 11453 11454 11455 | from ::fsl_deck_empty is FSL_SATYPE_ANY, so normally clients do not need to set this (unless they want to, as a small optimization). On success it returns 0 and d will be updated with the state from the input artifact. (Ideally, outputing d via fsl_deck_output() will produce a lossless copy of the original.) | < < < < < < | 11586 11587 11588 11589 11590 11591 11592 11593 11594 11595 11596 11597 11598 11599 | from ::fsl_deck_empty is FSL_SATYPE_ANY, so normally clients do not need to set this (unless they want to, as a small optimization). On success it returns 0 and d will be updated with the state from the input artifact. (Ideally, outputing d via fsl_deck_output() will produce a lossless copy of the original.) On error, if there is error information to propagate beyond the result code then it is stored in d->f (if that is not NULL), else in d->error. Whether or not such error info is propagated depends on the type of error, but anything more trivial than invalid arguments will be noted there. |
︙ | ︙ | |||
11476 11477 11478 11479 11480 11481 11482 11483 | - FSL_RC_SYNTAX on syntax errors. - FSL_RC_CONSISTENCY if validation of a Z-card fails. - Any number of errors coming from the allocator, database, or fsl_deck APIs used here. */ | > > > > > > > | > > > > > > > > > > > > > | 11607 11608 11609 11610 11611 11612 11613 11614 11615 11616 11617 11618 11619 11620 11621 11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639 11640 11641 11642 | - FSL_RC_SYNTAX on syntax errors. - FSL_RC_CONSISTENCY if validation of a Z-card fails. - Any number of errors coming from the allocator, database, or fsl_deck APIs used here. ACHTUNG API CHANGE: prior to 2021-10-20, this routine set d->rid (and the now-removed d->uuid) based on the hash of the input buffer if a matching record could be found in the db. That proved to be a huge performance hit and was removed. @see fsl_deck_parse2() */ FSL_EXPORT int fsl_deck_parse(fsl_deck * const d, fsl_buffer * const src); /** This variant of fsl_deck_parse() works identically to that function except for the 3rd argument. If you happen to know the _correct_ RID for the deck being parsed, pass it as the rid argument, else pass 0. A negative value will result in a FSL_RC_RANGE error. This value is (or will be) only used as an optimization in other places. Passing a positive value has no effect on how the content is parsed or on the result - it only affects internal details/optimizations. */ FSL_EXPORT int fsl_deck_parse2(fsl_deck * const d, fsl_buffer * const src, fsl_id_t rid); /** Quickly determines whether the content held by the given buffer "might" be a structural artifact. It performs a fast sanity check for prominent features which can be checked either in O(1) or very short O(N) time (with a fixed N). If it returns false then the given buffer's contents are, with 100% certainty, *not* a |
︙ | ︙ | |||
11500 11501 11502 11503 11504 11505 11506 | Fossil artifact. If rid==0 the current checkout (if opened) is used. (Trivia: there can never be a checkout with rid==0 but rid==0 is sometimes valid for an new/empty repo devoid of commits). If type==FSL_SATYPE_ANY then it will allow any type of control artifact, else it returns FSL_RC_TYPE if the loaded artifact is of the wrong type. | | > > > > > > > > > | | | | 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 11701 11702 11703 11704 11705 11706 11707 11708 | Fossil artifact. If rid==0 the current checkout (if opened) is used. (Trivia: there can never be a checkout with rid==0 but rid==0 is sometimes valid for an new/empty repo devoid of commits). If type==FSL_SATYPE_ANY then it will allow any type of control artifact, else it returns FSL_RC_TYPE if the loaded artifact is of the wrong type. Returns 0 on success. Results are undefined if f or d are NULL. The potential error result codes include, but are not limited to: - FSL_RC_OOM - FSL_RC_RANGE if rid is negative or is 0 and no checkout is opened. - FSL_RC_TYPE if `type` is not `FSL_SATYPE_ANY` and the loaded result is of any artifact type other than `type`. d may be partially populated on error, and the caller must eventually pass it to fsl_deck_finalize() resp. fsl_deck_clean() regardless of success or error. This function "could" clean it up on error, but leaving it partially populated makes debugging easier. If the error was an artifact type mismatch then d will "probably" be properly populated but will not hold the type of artifact requested. It "should" otherwise be well-formed because parsing errors occur before the type check can happen, but parsing of invalid manifests might also trigger a FSL_RC_TYPE error of a different nature. The morale of the storage is: if this function returns non-0, assume d is useless and needs to be cleaned up. f's error state may be updated on error (for anything more serious than basic argument validation errors). On success this function sets d->rid to rid. @see fsl_deck_load_sym() */ FSL_EXPORT int fsl_deck_load_rid( fsl_cx * const f, fsl_deck * const d, fsl_id_t rid, fsl_satype_e type ); /** A convenience form of fsl_deck_load_rid() which uses fsl_sym_to_rid() to convert symbolicName into an artifact RID. See fsl_deck_load_rid() for the symantics of the first, second, and fourth arguments, as well as the return value. See fsl_sym_to_rid() for the allowable values of symbolicName. @see fsl_deck_load_rid() */ FSL_EXPORT int fsl_deck_load_sym( fsl_cx * const f, fsl_deck * const d, char const * symbolicName, fsl_satype_e type ); /** Loads the baseline manifest specified in d->B.uuid, if any and if necessary. Returns 0 on success. If d->B.baseline is already loaded or d->B.uuid is NULL (in which case there is no baseline), it |
︙ | ︙ | |||
11672 11673 11674 11675 11676 11677 11678 11679 11680 11681 11682 11683 11684 11685 | /** Adds the given function as a "crosslink callback" for the given Fossil context. The callback is called at the end of a successfull fsl_deck_crosslink() operation and provides a way for the client to perform their own work based on the app having crosslinked an artifact. Crosslinking happens when artifacts are saved or upon a rebuild operation. Crosslink callbacks are called at the end of the core crosslink steps, in the order they are registered, with the caveat that if a listener is overwritten by another with the same name, the new entry retains the older one's position in the list. The library may register its own before the client gets a chance to. | > > > | 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 11846 11847 11848 | /** Adds the given function as a "crosslink callback" for the given Fossil context. The callback is called at the end of a successfull fsl_deck_crosslink() operation and provides a way for the client to perform their own work based on the app having crosslinked an artifact. Crosslinking happens when artifacts are saved or upon a rebuild operation. This function returns 0 on success, non-0 on error. Behaviour is undefined if any of the first 3 arguments are NULL. Crosslink callbacks are called at the end of the core crosslink steps, in the order they are registered, with the caveat that if a listener is overwritten by another with the same name, the new entry retains the older one's position in the list. The library may register its own before the client gets a chance to. |
︙ | ︙ | |||
11755 11756 11757 11758 11759 11760 11761 | defaults, install their own crosslink listners which ammend the state applied by the default ones. e.g. add a listener which watches for checkin updates and replace the default-installed comment with one suitable for your application, leaving the rest of the db state in place. At its simplest, that looks more or less like the following code (inside a fsl_deck_xlink_f() callback): | < > < > | | 11918 11919 11920 11921 11922 11923 11924 11925 11926 11927 11928 11929 11930 11931 11932 11933 11934 11935 11936 11937 11938 11939 | defaults, install their own crosslink listners which ammend the state applied by the default ones. e.g. add a listener which watches for checkin updates and replace the default-installed comment with one suitable for your application, leaving the rest of the db state in place. At its simplest, that looks more or less like the following code (inside a fsl_deck_xlink_f() callback): ``` int rc = fsl_db_exec(fsl_cx_db_repo(deck->f), "UPDATE event SET comment=%Q " "WHERE objid=%"FSL_ID_T_PFMT, "the new comment.", deck->rid); ``` */ FSL_EXPORT int fsl_xlink_listener( fsl_cx * const f, char const * name, fsl_deck_xlink_f cb, void * cbState ); /** For the given blob.rid value, returns the blob.size value of that record via *rv. Returns 0 or higher on success, -1 if a phantom record is found, -2 if no entry is found, or a smaller |
︙ | ︙ | |||
11790 11791 11792 11793 11794 11795 11796 | uncompressed if they were stored in compressed form. This extracts a raw blob and does not apply any deltas - use fsl_content_get() to fully expand a delta-stored blob. Returns 0 on success. On error tgt might be partially updated, e.g. it might be populated with compressed data instead of uncompressed. On error tgt's contents should be recycled | | > > > | > | > | 11953 11954 11955 11956 11957 11958 11959 11960 11961 11962 11963 11964 11965 11966 11967 11968 11969 11970 11971 11972 11973 11974 11975 11976 11977 11978 | uncompressed if they were stored in compressed form. This extracts a raw blob and does not apply any deltas - use fsl_content_get() to fully expand a delta-stored blob. Returns 0 on success. On error tgt might be partially updated, e.g. it might be populated with compressed data instead of uncompressed. On error tgt's contents should be recycled (e.g. fsl_buffer_reuse()) or discarded (e.g. fsl_buffer_clear()) by the client. Returns FSL_RC_RANGE if blobRid<=0, FSL_RC_NOT_A_REPO if f has no repo opened, FSL_RC_OOM on allocation error, or potentially any number of other codes via the db layer. Results are undefined if any pointer argument is NULL. @see fsl_content_get() @see fsl_content_size() */ FSL_EXPORT int fsl_content_blob( fsl_cx * const f, fsl_id_t blobRid, fsl_buffer * const tgt ); /** Functionally similar to fsl_content_blob() but does a lot of work to ensure that the returned blob is expanded from its deltas, if any. The tgt buffer's memory, if any, will be replaced/reused if it has any. |
︙ | ︙ | |||
11840 11841 11842 11843 11844 11845 11846 | fsl_buffer * const tgt ); /** Returns true if the given rid is marked as PRIVATE in f's current repository. Returns false (0) on error or if the content is not marked as private. */ | | | | 12008 12009 12010 12011 12012 12013 12014 12015 12016 12017 12018 12019 12020 12021 12022 12023 12024 12025 12026 12027 12028 12029 12030 | fsl_buffer * const tgt ); /** Returns true if the given rid is marked as PRIVATE in f's current repository. Returns false (0) on error or if the content is not marked as private. */ FSL_EXPORT bool fsl_content_is_private(fsl_cx * const f, fsl_id_t rid); /** Marks the given rid public, if it was previously marked as private. Returns 0 on success, non-0 on error. Note that it is not possible to make public content private. */ FSL_EXPORT int fsl_content_make_public(fsl_cx * const f, fsl_id_t rid); /** Generic callback interface for visiting decks. The interface does not generically require that d survive after this call returns. Implementations must return 0 on success, non-0 on error. Some |
︙ | ︙ | |||
12015 12016 12017 12018 12019 12020 12021 | and appends them as new (char *) strings to tgt. On error tgt might be partially populated (but this will only happen on an OOM or serious system-level error). It is up to the caller free the entries added to the list. Some of the possibilities include: | < > < > | 12183 12184 12185 12186 12187 12188 12189 12190 12191 12192 12193 12194 12195 12196 12197 12198 12199 12200 12201 12202 12203 12204 | and appends them as new (char *) strings to tgt. On error tgt might be partially populated (but this will only happen on an OOM or serious system-level error). It is up to the caller free the entries added to the list. Some of the possibilities include: ``` fsl_list_visit( list, 0, fsl_list_v_fsl_free, NULL ); fsl_list_reserve(list,0); // Or: fsl_list_clear(list, fsl_list_v_fsl_free, NULL); // Or simply: fsl_list_visit_free( list, 1 ); ``` */ FSL_EXPORT int fsl_wiki_names_get( fsl_cx * f, fsl_list * tgt ); /** F-cards each represent one file entry in a Manifest Artifact (i.e., a checkin version). |
︙ | ︙ | |||
12536 12537 12538 12539 12540 12541 12542 | there are valid/interesting uses for such modification remains to be seen. If any are found, this copy behavior may change. */ FSL_EXPORT int fsl_repo_extract( fsl_cx * f, fsl_repo_extract_opt const * opt ); /** | | | | 12704 12705 12706 12707 12708 12709 12710 12711 12712 12713 12714 12715 12716 12717 12718 12719 12720 12721 12722 12723 12724 | there are valid/interesting uses for such modification remains to be seen. If any are found, this copy behavior may change. */ FSL_EXPORT int fsl_repo_extract( fsl_cx * f, fsl_repo_extract_opt const * opt ); /** Equivalent to fsl_tag_an_rid() except that it takes a symbolic artifact name in place of an artifact ID as the third argumemnt. This function passes symToTag to fsl_sym_to_rid(), and on success passes the rest of the parameters as-is to fsl_tag_an_rid(). See that function the semantics of the other arguments and the return value, as well as a description of the side effects. */ FSL_EXPORT int fsl_tag_sym( fsl_cx * f, fsl_tagtype_e tagType, char const * symToTag, char const * tagName, char const * tagValue, char const * userName, double mtime, fsl_id_t * newId ); |
︙ | ︙ | |||
12613 12614 12615 12616 12617 12618 12619 | A leaf, by the way, is a commit which has no children in the same branch. Sidebar: this function calculates whether the RID is a leaf, as opposed to checking the "static" (pre-calculated) list of leaves in the [leaf] table. */ | | > > > > > | | > | > | | 12781 12782 12783 12784 12785 12786 12787 12788 12789 12790 12791 12792 12793 12794 12795 12796 12797 12798 12799 12800 12801 12802 12803 12804 12805 12806 12807 12808 12809 12810 12811 12812 12813 12814 12815 12816 12817 12818 12819 12820 12821 12822 12823 12824 12825 12826 12827 12828 12829 12830 12831 12832 | A leaf, by the way, is a commit which has no children in the same branch. Sidebar: this function calculates whether the RID is a leaf, as opposed to checking the "static" (pre-calculated) list of leaves in the [leaf] table. */ FSL_EXPORT bool fsl_rid_is_leaf(fsl_cx * const f, fsl_id_t rid); /** Returns true if, according to f's current repo's [event] table, rid refers to a checkin, else false. */ FSL_EXPORT bool fsl_rid_is_version(fsl_cx * const f, fsl_id_t rid); /** Counts the number of primary non-branch children for the given check-in. A primary child is one where the parent is the primary parent, not a merge parent. A "leaf" is a node that has zero children of any kind. This routine counts only primary children. A non-branch child is one which is on the same branch as the parent. Returns a negative value on error. */ FSL_EXPORT fsl_int_t fsl_count_nonbranch_children(fsl_cx * const f, fsl_id_t rid); /** Looks for the delta table record where rid==deltaRid, and returns that record's srcid via *rv. Returns 0 on success, non-0 on error. If no record is found, *rv is set to 0 and 0 is returned (as opposed to FSL_RC_NOT_FOUND) because that generally simplifies the error checking. */ FSL_EXPORT int fsl_delta_src_id( fsl_cx * const f, fsl_id_t deltaRid, fsl_id_t * rv ); /** Return true if the given artifact ID should is listed in f's shun table, else false. */ FSL_EXPORT int fsl_uuid_is_shunned(fsl_cx * const f, fsl_uuid_cstr zUuid); /** Compute the "mtime" of the file given whose blob.rid is "fid" that is part of check-in "vid". The mtime will be the mtime on vid or some ancestor of vid where fid first appears. Note that fossil does not track the "real" mtimes of files, it only |
︙ | ︙ | |||
12842 12843 12844 12845 12846 12847 12848 | FSL_EXPORT int fsl_repo_import_blob( fsl_cx * f, fsl_input_f inFunc, void * inState, fsl_id_t * rid, fsl_uuid_str * uuid ); /** A convenience form of fsl_repo_import_blob(), equivalent to: | < > < > | 13017 13018 13019 13020 13021 13022 13023 13024 13025 13026 13027 13028 13029 13030 13031 13032 13033 | FSL_EXPORT int fsl_repo_import_blob( fsl_cx * f, fsl_input_f inFunc, void * inState, fsl_id_t * rid, fsl_uuid_str * uuid ); /** A convenience form of fsl_repo_import_blob(), equivalent to: ``` fsl_repo_import_blob(f, fsl_input_f_buffer, bIn, rid, uuid ) ``` except that (A) bIn is const in this call and non-const in the other form (due to cursor traversal requirements) and (B) it returns FSL_RC_MISUSE if bIn is NULL. */ FSL_EXPORT int fsl_repo_import_buffer( fsl_cx * f, fsl_buffer const * bIn, fsl_id_t * rid, fsl_uuid_str * uuid ); |
︙ | ︙ | |||
12936 12937 12938 12939 12940 12941 12942 | Error cases include: either argument is NULL, uuid does not appear to be a full or partial UUID (or is too long), uuid is ambiguous (try providing a longer one) This implementation is more efficient when given a full, valid UUID (one for which fsl_is_uuid() returns true). */ | | | | | 13111 13112 13113 13114 13115 13116 13117 13118 13119 13120 13121 13122 13123 13124 13125 13126 13127 13128 13129 13130 13131 13132 13133 13134 13135 13136 13137 13138 13139 13140 13141 13142 13143 13144 13145 | Error cases include: either argument is NULL, uuid does not appear to be a full or partial UUID (or is too long), uuid is ambiguous (try providing a longer one) This implementation is more efficient when given a full, valid UUID (one for which fsl_is_uuid() returns true). */ FSL_EXPORT fsl_id_t fsl_uuid_to_rid( fsl_cx * const f, char const * uuid ); /** The opposite of fsl_uuid_to_rid(), this returns the UUID string of the given blob record ID. Ownership of the string is passed to the caller and it must eventually be freed using fsl_free(). Returns NULL on error (invalid arguments or f has no repo opened) or if no blob record is found. If no record is found, f's error state is updated with an explanation of the problem. */ FSL_EXPORT fsl_uuid_str fsl_rid_to_uuid(fsl_cx * const f, fsl_id_t rid); /** Works like fsl_rid_to_uuid() but assigns the UUID to the given buffer, re-using its memory, if any. Returns 0 on success, FSL_RC_MISUSE if rid is not positive, FSL_RC_OOM on allocation error, and FSL_RC_NOT_FOUND if no blob entry matching the given rid is found. */ FSL_EXPORT int fsl_rid_to_uuid2(fsl_cx * const f, fsl_id_t rid, fsl_buffer *uuid); /** This works identically to fsl_rid_to_uuid() except that it will only resolve to a UUID if an artifact matching the given type has that UUID. If no entry is found, f's error state gets updated with a description of the problem. |
︙ | ︙ | |||
13432 13433 13434 13435 13436 13437 13438 | /** The maximum number of versions to search through. Note that fossil(1) offers the ability to limit the calculation based on processing time, e.g. to 1500ms. We may or may not add that in this library. */ | | > > > > > > > > > > > > > > > > | 13607 13608 13609 13610 13611 13612 13613 13614 13615 13616 13617 13618 13619 13620 13621 13622 13623 13624 13625 13626 13627 13628 13629 13630 13631 13632 13633 13634 13635 13636 13637 | /** The maximum number of versions to search through. Note that fossil(1) offers the ability to limit the calculation based on processing time, e.g. to 1500ms. We may or may not add that in this library. */ uint32_t limitVersions; /** An approximate number of milliseconds of processing time to limit the annotation to. Note that this is measured in CPU time, not "wall clock" time. This value is rough minimum approximation, and the annotation will stop at the first processing step after which this limit has been hit or surpassed. Even with this limit in place, the annotation engine may impose a minimum number of versions to step through before it enforces this limit. If both this and limitVersions are set to positive values, the first limit which is exceeded is applied. */ uint32_t limitMs; /** - 0 = do not ignore any spaces. - <0 = ignore trailing end-of-line spaces. - >1 = ignore all spaces */ int16_t spacePolicy; /** |
︙ | ︙ | |||
13477 13478 13479 13480 13481 13482 13483 | }; /** Initialized-with-defaults fsl_annotate_opt structure, intended for const-copy initialization. */ #define fsl_annotate_opt_empty_m {\ NULL/*filename*/, \ 0/*versionRid*/,0/*originRid*/, \ | > | | 13668 13669 13670 13671 13672 13673 13674 13675 13676 13677 13678 13679 13680 13681 13682 13683 | }; /** Initialized-with-defaults fsl_annotate_opt structure, intended for const-copy initialization. */ #define fsl_annotate_opt_empty_m {\ NULL/*filename*/, \ 0/*versionRid*/,0/*originRid*/, \ 0U/*limitVersions*/, 0U/*limitMs*/,\ 0/*spacePolicy*/, \ false/*praise*/, false/*fileVersions*/, \ false/*dumpVersions*/, \ NULL/*out*/, NULL/*outState*/ \ } /** Initialized-with-defaults fsl_annotate_opt structure, intended for non-const copy initialization. */ |
︙ | ︙ | |||
13760 13761 13762 13763 13764 13765 13766 | opt->counts when it adds, updates, or skips a file. On each call, it updated opt->counts without resetting it (as this function is typically called in a loop). This function does not modify any other entries of that object and it requires that the object not be modified (e.g. via opt->callback()) while it is recursively processing. To reset the counts between calls, if needed: | < > < > | 13952 13953 13954 13955 13956 13957 13958 13959 13960 13961 13962 13963 13964 13965 13966 13967 13968 | opt->counts when it adds, updates, or skips a file. On each call, it updated opt->counts without resetting it (as this function is typically called in a loop). This function does not modify any other entries of that object and it requires that the object not be modified (e.g. via opt->callback()) while it is recursively processing. To reset the counts between calls, if needed: ``` opt->counts = fsl_ckout_manage_opt_empty.counts; ``` Returns 0 on success, non-0 on error. Files queued for addition this way can be unqueued before they are committed using fsl_ckout_unmanage(). @see fsl_ckout_unmanage() |
︙ | ︙ | |||
14085 14086 14087 14088 14089 14090 14091 | all versions except for the one we're working with, but at least a couple of use cases call for having multiple versions in vfile at once. Many algorithms generally assume only a single checkin's worth of state is in vfile and can get confused if that is not the case. */ FSL_VFILE_CKSIG_KEEP_OTHERS = 0x008, | < | | 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292 14293 14294 14295 14296 | all versions except for the one we're working with, but at least a couple of use cases call for having multiple versions in vfile at once. Many algorithms generally assume only a single checkin's worth of state is in vfile and can get confused if that is not the case. */ FSL_VFILE_CKSIG_KEEP_OTHERS = 0x008, /** If set and fsl_vfile_changes_scan() is passed a version other than the pre-call checkout version, it will, when finished, write the given version in the "checkout" setting of the ckout.vvar table, effectively switching the checkout to that version. It does not do this by default because it is sometimes necessary to have two versions in the vfile table at once and the operation doing so needs to control which version number is the current checkout. */ FSL_VFILE_CKSIG_WRITE_CKOUT_VERSION = 0x010 }; /** |
︙ | ︙ | |||
14120 14121 14122 14123 14124 14125 14126 | Returns 0 on success, non-0 on error. BUG: this does not properly catch one particular corner-case change, where a file has been replaced by a same-named non-file (symlink or directory). */ | | | | 14311 14312 14313 14314 14315 14316 14317 14318 14319 14320 14321 14322 14323 14324 14325 14326 14327 14328 14329 14330 14331 14332 14333 14334 14335 14336 14337 | Returns 0 on success, non-0 on error. BUG: this does not properly catch one particular corner-case change, where a file has been replaced by a same-named non-file (symlink or directory). */ FSL_EXPORT int fsl_vfile_changes_scan(fsl_cx * const f, fsl_id_t vid, unsigned cksigFlags); /** If f has an opened checkout which has local changes noted in its checkout db state (the vfile table), returns true, else returns false. Note that this function does not do the filesystem scan to check for changes, but checks only the db state. Use fsl_vfile_changes_scan() to perform the actual scan (noting that library-side APIs which update that state may also record individual changes or automatically run a scan). */ FSL_EXPORT bool fsl_ckout_has_changes(fsl_cx * const f); /** Callback type for use with fsl_checkin_queue_opt for alerting a client about exactly which files get enqueued/dequeued via fsl_checkin_enqueue() and fsl_checkin_dequeue(). This function gets passed the checkout-relative name of the file |
︙ | ︙ | |||
14903 14904 14905 14906 14907 14908 14909 | database-side data will be consistent (the transaction is rolled back) but filesystem-side changes may not be. */ typedef int (*fsl_ckup_f)( fsl_ckup_state const * cState ); /** This enum lists the various types of individual file change states | | > | < | < | 15094 15095 15096 15097 15098 15099 15100 15101 15102 15103 15104 15105 15106 15107 15108 15109 15110 15111 15112 15113 15114 15115 15116 15117 15118 15119 15120 15121 15122 15123 15124 15125 15126 15127 15128 15129 | database-side data will be consistent (the transaction is rolled back) but filesystem-side changes may not be. */ typedef int (*fsl_ckup_f)( fsl_ckup_state const * cState ); /** This enum lists the various types of individual file change states which can happen during a checkout, update, or merge. */ enum fsl_ckup_fchange_e { /** Sentinel value. */ FSL_CKUP_FCHANGE_INVALID = -1, /** Was unchanged between the previous and updated-to version, so no change was made to the on-disk file. This is the only entry in the enum which is guaranteed to have a specific value: 0, so that it can be used as a boolean false. */ FSL_CKUP_FCHANGE_NONE = 0, /** Added to SCM in the updated-to version. */ FSL_CKUP_FCHANGE_ADDED, /** Added to SCM in the current checkout version and carried over into the updated-to version. */ FSL_CKUP_FCHANGE_ADD_PROPAGATED, /** Removed from SCM in the updated-to to version OR in the checked-out version but not yet committed. a.k.a. it became "unmanaged." Do we need to differentiate between those cases? */ FSL_CKUP_FCHANGE_RM, /** Removed from the checked-out version but not yet commited, so was carried over to the updated-to version. |
︙ | ︙ | |||
15010 15011 15012 15013 15014 15015 15016 | 2) For the update process, fCard->priorName will be NULL unless the file was renamed between the original and updated-to versions, in which case priorName will refer to the original version's name. */ fsl_repo_extract_state const * extractState; /** | | > | | | 15200 15201 15202 15203 15204 15205 15206 15207 15208 15209 15210 15211 15212 15213 15214 15215 15216 15217 15218 15219 15220 15221 15222 15223 15224 15225 15226 15227 15228 15229 15230 15231 | 2) For the update process, fCard->priorName will be NULL unless the file was renamed between the original and updated-to versions, in which case priorName will refer to the original version's name. */ fsl_repo_extract_state const * extractState; /** Optional client-dependent state for use in the fsl_ckup_f() callback. This is copies from the corresponding fsl_ckup_opt::callbackState member. */ void * callbackState; /** Vaguely describes the type of change the current call into the fsl_ckup_f() represents. The full range of values is not valid for all operations. Specifically: Checkout only uses: FSL_CKUP_FCHANGE_NONE FSL_CKUP_FCHANGE_UPDATED FSL_CKUP_FCHANGE_RM For update operations all (or most) values are potentially possible. If this has a value of FSL_CKUP_FCHANGE_RM, this->fileRmInfo will provide a bit more detail. */ fsl_ckup_fchange_e fileChangeType; |
︙ | ︙ | |||
15063 15064 15065 15066 15067 15068 15069 15070 15071 15072 15073 15074 15075 15076 15077 15078 15079 15080 15081 | this->fileRmInfo indicates that a file was removed, this might (depending on availability of the file in the filesystem at the time) be set to 0. When running in dry-run mode, this value may be 0, as we may not have a file in place which we can stat() to get it, nor a db entry from which to fetch it. */ fsl_time_t mtime; /** The size of the extracted file, in bytes. If the file was removed from the filesystem (or removal was at least attempted) then this is set to -1. */ fsl_int_t size; /** True if the current checkout/update is running in dry-run mode, | > > | < < | | | 15254 15255 15256 15257 15258 15259 15260 15261 15262 15263 15264 15265 15266 15267 15268 15269 15270 15271 15272 15273 15274 15275 15276 15277 15278 15279 15280 15281 15282 15283 15284 15285 15286 15287 15288 15289 | this->fileRmInfo indicates that a file was removed, this might (depending on availability of the file in the filesystem at the time) be set to 0. When running in dry-run mode, this value may be 0, as we may not have a file in place which we can stat() to get it, nor a db entry from which to fetch it. This option is ignored for merge operations. */ fsl_time_t mtime; /** The size of the extracted file, in bytes. If the file was removed from the filesystem (or removal was at least attempted) then this is set to -1. */ fsl_int_t size; /** True if the current checkout/update is running in dry-run mode, else false. See fsl_ckup_opt::dryRun for details. */ bool dryRun; }; /** Options for use with fsl_repo_ckout() and fsl_ckout_update(). i.e. for checkout and update. */ struct fsl_ckup_opt { /** The version of the repostitory to check out or update. This must be the blob.rid of a checkin artifact. */ fsl_id_t checkinRid; |
︙ | ︙ | |||
15150 15151 15152 15153 15154 15155 15156 | bool setMtime; /** A hint to fsl_repo_ckout() and fsl_ckout_update() about whether it needs to scan the checkout for changes. Set this to false ONLY if the calling code calls fsl_ckout_changes_scan() (or equivalent, e.g. fsl_vfile_changes_scan()) immediately before | | | | 15341 15342 15343 15344 15345 15346 15347 15348 15349 15350 15351 15352 15353 15354 15355 15356 | bool setMtime; /** A hint to fsl_repo_ckout() and fsl_ckout_update() about whether it needs to scan the checkout for changes. Set this to false ONLY if the calling code calls fsl_ckout_changes_scan() (or equivalent, e.g. fsl_vfile_changes_scan()) immediately before calling fsl_repo_ckout() or fsl_ckout_update(), as those require a non-stale changes scan in order to function properly. */ bool scanForChanges; /** If true, the extraction process will "go through the motions" but will not write any files to disk. It will perform I/O such as stat()'ing to see, e.g., if it would have needed to overwrite a |
︙ | ︙ | |||
15348 15349 15350 15351 15352 15353 15354 | manifest setting can be modified at any time, either in a versioned setting file or the repository db, and may be modified from outside the library. There's a tiny back-door for working around that: if m is NULL, the cache will be flushed and no other work will be performed. Thus the following approach can be used to force a fresh check for that setting: | < > < > | 15539 15540 15541 15542 15543 15544 15545 15546 15547 15548 15549 15550 15551 15552 15553 15554 15555 15556 | manifest setting can be modified at any time, either in a versioned setting file or the repository db, and may be modified from outside the library. There's a tiny back-door for working around that: if m is NULL, the cache will be flushed and no other work will be performed. Thus the following approach can be used to force a fresh check for that setting: ``` fsl_ckout_manifest_setting(f, NULL); // clears caches, does nothing else fsl_ckout_manifest_setting(f, &myInt); // loads/caches the setting ``` */ FSL_EXPORT void fsl_ckout_manifest_setting(fsl_cx *f, int *m); /** Might write out the files manifest, manifest.uuid, and/or manifest.tags for the current checkout to the the checkout's root directory. The 2nd-4th arguments are interpreted as follows: |
︙ | ︙ | |||
15599 15600 15601 15602 15603 15604 15605 | 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 ); /** | | | | | | | | | | | | > > | | | | | | 15790 15791 15792 15793 15794 15795 15796 15797 15798 15799 15800 15801 15802 15803 15804 15805 15806 15807 15808 15809 15810 15811 15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 15826 15827 15828 | 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 selected. For each entry it finds, it adds the `vfile.id` to `dest`. If `vid<=0` then the current checkout RID is used. If `changedOnly` is `true` then only entries which have been marked in the `vfile` table as having some sort of change are included, so if `true` then fsl_ckout_changes_scan() (or equivalent) must have been "recently" called to ensure that state is up to do. This routine only checks the `vfile` table for "is changed" state, it does not do filesystem-level checks on the files. This search honors the context-level case-sensitivity setting (see fsl_cx_case_sensitive_set()). Returns 0 on success. Not finding anything is not treated as an error, though we could arguably return `FSL_RC_NOT_FOUND` for the cases which use this function. In order to determine whether or not any results were actually found, compare `dest->entryCount` before and after calling this. 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. @see fsl_ckout_vfile_ids() */ FSL_EXPORT int fsl_filename_to_vfile_ids( fsl_cx * f, fsl_id_t vid, fsl_id_bag * dest, |
︙ | ︙ | |||
15698 15699 15700 15701 15702 15703 15704 | vfile table for longer than absolutely needed. They "really should" use fsl_vfile_unload() to clear out any version they load with this routine. @see fsl_vfile_unload() @see fsl_vfile_unload_except() */ | | | | | 15891 15892 15893 15894 15895 15896 15897 15898 15899 15900 15901 15902 15903 15904 15905 15906 15907 15908 15909 15910 15911 15912 15913 15914 15915 15916 15917 15918 15919 15920 15921 15922 15923 15924 15925 15926 15927 15928 15929 15930 15931 15932 15933 15934 15935 15936 15937 15938 | vfile table for longer than absolutely needed. They "really should" use fsl_vfile_unload() to clear out any version they load with this routine. @see fsl_vfile_unload() @see fsl_vfile_unload_except() */ FSL_EXPORT int fsl_vfile_load(fsl_cx * const f, fsl_id_t manifestRid, bool clearOtherVersions, uint32_t * missingCount); /** Clears out all entries in the current checkout's vfile table with the given vfile.vid value. If vid<=0 then the current checkout RID is used (which is never a good idea from client-side code!). ACHTUNG: never do this without understanding the consequences. It can ruin the current checkout state. Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout opened, FSL_RC_DB on any sort of db-related error (in which case f's error state is updated with a description of the problem). @see fsl_vfile_load() @see fsl_vfile_unload_except() */ FSL_EXPORT int fsl_vfile_unload(fsl_cx * const f, fsl_id_t vid); /** A counterpart of fsl_vfile_unload() which removes all vfile entries where vfile.vid is not the given vid. If vid is <=0 then the current checkout RID is used. Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout opened, FSL_RC_DB on any sort of db-related error (in which case f's error state is updated with a description of the problem). @see fsl_vfile_load() @see fsl_vfile_unload() */ FSL_EXPORT int fsl_vfile_unload_except(fsl_cx * const f, fsl_id_t vid); /** Performs a "fingerprint check" between f's current checkout and repository databases. Returns 0 if either there is no checkout, no mismatch, or it is impossible to determine because the checkout is missing a fingerprint (which is legal for "older" checkout |
︙ | ︙ | |||
15783 15784 15785 15786 15787 15788 15789 | content, but that behaviour may change in the future to reflect f's symlink preferences. */ FSL_EXPORT int fsl_ckout_file_content(fsl_cx * const f, bool relativeToCwd, char const * zName, fsl_buffer * const dest); | < | 15976 15977 15978 15979 15980 15981 15982 15983 15984 15985 15986 15987 15988 15989 | content, but that behaviour may change in the future to reflect f's symlink preferences. */ FSL_EXPORT int fsl_ckout_file_content(fsl_cx * const f, bool relativeToCwd, char const * zName, fsl_buffer * const dest); /** Fetches the timestamp of the given F-card's name against the filesystem and/or the most recent checkin in which it was modified (as reported by fsl_mtime_of_manifest()). vid is the checkin version to look at. If it's 0, the current checkout will be used. On success, returns 0 and: |
︙ | ︙ | |||
15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 | - FSL_RC_OOM. */ FSL_EXPORT int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * const fc, fsl_time_t * repoMtime, fsl_time_t * localMtime); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */ /* end of file ../include/fossil-scm/fossil-checkout.h */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 16004 16005 16006 16007 16008 16009 16010 16011 16012 16013 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 16035 16036 16037 16038 16039 16040 16041 16042 16043 16044 16045 16046 16047 16048 16049 16050 16051 16052 16053 16054 16055 16056 16057 16058 16059 16060 16061 16062 16063 16064 16065 16066 16067 16068 16069 16070 16071 16072 16073 16074 16075 16076 16077 16078 16079 16080 16081 16082 16083 16084 16085 16086 16087 16088 16089 16090 16091 16092 16093 16094 16095 16096 16097 16098 16099 16100 16101 16102 16103 16104 16105 16106 16107 16108 16109 16110 16111 16112 16113 16114 16115 16116 16117 16118 16119 16120 16121 16122 16123 16124 16125 16126 16127 16128 16129 16130 16131 16132 16133 16134 16135 16136 16137 16138 16139 16140 16141 16142 16143 16144 16145 16146 16147 16148 16149 16150 16151 16152 16153 16154 16155 16156 16157 16158 16159 16160 16161 16162 16163 16164 16165 16166 16167 16168 16169 16170 16171 16172 16173 16174 16175 16176 16177 16178 16179 16180 16181 16182 16183 16184 16185 16186 16187 16188 16189 16190 16191 16192 16193 16194 16195 16196 | - FSL_RC_OOM. */ FSL_EXPORT int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * const fc, fsl_time_t * repoMtime, fsl_time_t * localMtime); /** UNDER CONSTRUCTION! INCOMPLETE! */ struct fsl_merge_state { /** State to be passed to this->callback via the fsl_merge_state::callbackState member. */ void * callbackState; fsl_ckup_fchange_e fileChangeType; fsl_ckup_rm_state_e fileRmInfo; }; typedef struct fsl_merge_state fsl_merge_state; /** Callback type for use with fsl_merge_opt and fsl_merge(). TODO: figure out whether we can use fsl_ckout_state for this or whether we need a new type. Merge and update are quite similar, so the current thinking is that we can recycling this. */ typedef int (*fsl_merge_f)(fsl_merge_state const * const); /** Merge type enum for use with fsl_merge_opt::mergeType. */ enum fsl_merge_type_e { /** Indicates a normal merge. */ FSL_MERGE_TYPE_NORMAL, /** Indicates an "integrate" merge, which tells the next checkin operation to apply a "closed" tag to the checkin from which this merge is performed (effectively closing its branch). Certain merge-time state will force this merge type to behave like FSL_MERGE_TYPE_NORMAL: - If the being-merged-in content is marked as private. - If the being-merged-in content is not a leaf. */ FSL_MERGE_TYPE_INTEGRATE, /** Indicates a cherrypick merge, pulling in only the changes made to a specific checkin without otherwise inheriting its lineage. */ FSL_MERGE_TYPE_CHERRYPICK, /** Indicates a backout merge, a reverse cherrypick, backing out any changes which were added by this->checkinRid. */ FSL_MERGE_TYPE_BACKOUT }; typedef enum fsl_merge_type_e fsl_merge_type_e; /** UNDER CONSTRUCTION. Options for use with fsl_ckout_merge(). */ struct fsl_merge_opt { /** The version of the repostitory to merge into the current checkout. This must be the blob.rid of a checkin artifact. */ fsl_id_t mergeRid; /** The version of the most recent common ancestor. Must normally be 0. The default is calculated automatically based on this->mergeRid and this->mergeType. This corresponds to fossil(1)'s --baseline merge flag. */ fsl_id_t baselineRid; /** Specifies the merge type to perform. */ fsl_merge_type_e mergeType; /** Gets called once per merge-updated file, passed a fsl_ckup_state instance with information about the merged file and related metadata. May be NULL. */ fsl_merge_f callback; /** State to be passed to this->callback via the fsl_merge_state::callbackState member. */ void * callbackState; /** A hint to fsl_merge() about whether it needs to scan the checkout for changes. Set this to false ONLY if the calling code calls fsl_ckout_changes_scan() (or equivalent, e.g. fsl_vfile_changes_scan()) immediately before calling fsl_merge(), as that function require a non-stale changes scan in order to function properly. */ bool scanForChanges; /** If true, on merge conflict retain the temporary files used for mergin: `*-baseline`, `*-original`, and `*-merge`. By default these are removed because they're very rarely useful. */ bool keepMergeFiles; /** If true, the extraction process will "go through the motions" but will not write any files to disk. It will perform I/O such as stat()'ing to see, e.g., if it would have needed to overwrite a file. */ bool dryRun; /** This flag is not part of the public API and will be removed once the merge operation's development has settled down. */ unsigned short debug; /** TODO: - How to handle fossil's --binary GLOBPATTERN flag. Plain string or a glob list object or a stateful predicate function or... ? */ }; /** Convenience typedef. */ typedef struct fsl_merge_opt fsl_merge_opt; /** Initialized-with-defaults fsl_merge_opt structure, intended for const-copy initialization. */ #define fsl_merge_opt_empty_m \ {-1/*mergeRid*/,0/*baselineRid*/, \ FSL_MERGE_TYPE_NORMAL/*mergeType*/, \ NULL/*callback*/, NULL/*callbackState*/, \ true/*scanForChanges*/, false/*keepMergeFiles*/, \ false/*dryRun*/,0/*debug*/} /** Initialized-with-defaults fsl_merge_opt structure, intended for non-const copy initialization. */ extern const fsl_merge_opt fsl_merge_opt_empty; /** UNDER CONSTRUCTION and NOT YET IMPLEMENTED for the foreseeable future. Performs a "merge" operation on the current checkout, merging in version opt->mergeRid. If that version has already been merged, this call has no SCM-related side effects. Returns 0 on success, any number of non-0 codes on error, including, but not limited to: - FSL_RC_NOT_A_CKOUT if f has no opened checkout. - FSL_RC_OOM on allocation error. - FSL_RC_TYPE if opt->mergeRid does to refer to a checkin. - FSL_RC_PHANTOM if a file participating in the merge is a phantom. - FSL_RC_RANGE if the to-be-merged-in RID is the same as the current checkout RID or the same as the pivot/baseline of the merge. - FSL_RC_NOT_FOUND if no common ancestor can be found for use as a basis for the merge. - FSL_RC_MISUSE if the current checkout is empty (has an RID of 0). - Any number of DB- or I/O-related codes, as well as codes from underlying APIs such as fsl_vfile_changes_scan(). For most errors, f's error state will be updated with a description of the problem. Reminder: there are certain illegal combinations of merge state which may require adding new result codes for. e.g. a no-op merge. */ FSL_EXPORT int fsl_ckout_merge(fsl_cx * const f, fsl_merge_opt const * const opt); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */ /* end of file ../include/fossil-scm/fossil-checkout.h */ |
︙ | ︙ | |||
15887 15888 15889 15890 15891 15892 15893 | Note that the APIs which read and write versioned settings do not care whether those settings are valid for fossil(1). Reminder to self: SQL to find the checkins in which a versioned settings file was added or modified (ignoring renames and branches and whatnot). | < > < > | 16258 16259 16260 16261 16262 16263 16264 16265 16266 16267 16268 16269 16270 16271 16272 16273 16274 16275 16276 16277 16278 16279 16280 16281 | Note that the APIs which read and write versioned settings do not care whether those settings are valid for fossil(1). Reminder to self: SQL to find the checkins in which a versioned settings file was added or modified (ignoring renames and branches and whatnot). ``` select strftime('%Y-%m-%d %H:%M:%S',e.mtime) mtime, b.rid, b.uuid from mlink m, filename f, blob b, event e where f.fnid=m.fnid and m.mid=b.rid and b.rid=e.objid and f.name='.fossil-settings/ignore-glob' order by e.mtime desc ; ``` */ FSL_CONFDB_VERSIONABLE = 4 }; typedef enum fsl_confdb_e fsl_confdb_e; /** |
︙ | ︙ | |||
16332 16333 16334 16335 16336 16337 16338 | Returns the next node p's path. The returned node is owned by path may be invalidated by any APIs which manipulate path. Intended to be used like this: | < > < > | 16703 16704 16705 16706 16707 16708 16709 16710 16711 16712 16713 16714 16715 16716 16717 16718 16719 16720 16721 16722 16723 | Returns the next node p's path. The returned node is owned by path may be invalidated by any APIs which manipulate path. Intended to be used like this: ``` for( p = fsl_vpath_first(path) ; p ; p = fsl_vpath_next(p)){ ... } ``` */ FSL_EXPORT fsl_vpath_node * fsl_vpath_next(fsl_vpath_node *p); /** Returns p's path length. */ FSL_EXPORT int fsl_vpath_length(fsl_vpath const * p); |
︙ | ︙ | |||
16377 16378 16379 16380 16381 16382 16383 | On success, returns 0 and path->pStart will point to the beginning of the path (the iFrom node). If pStart is 0 then no path could be found but 0 is still returned. Elements of the path can be traversed like so: | < > < > < > < > | 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 16773 16774 16775 16776 16777 16778 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 | On success, returns 0 and path->pStart will point to the beginning of the path (the iFrom node). If pStart is 0 then no path could be found but 0 is still returned. Elements of the path can be traversed like so: ``` fsl_vpath path = fsl_vpath_empty; fsl_vpath_node * n = 0; int rc = fsl_vpath_shortest(f, &path, versionFrom, versionTo, 1, 0); if(rc) { ... error ... } for( n = fsl_vpath_first(&path); n; n = fsl_vpath_next(n) ){ ... } fsl_vpath_clear(&path); ``` On error, f's error state may be updated with a description of the problem. */ FSL_EXPORT int fsl_vpath_shortest( fsl_cx * const f, fsl_vpath * const path, fsl_id_t iFrom, fsl_id_t iTo, bool directOnly, bool oneWayOnly ); /** This variant of fsl_vpath_shortest() stores the shortest direct path from version iFrom to version iTo in the ANCESTOR temporary table using f's current repo db handle. That table gets created, if needed, else cleared by this call. The ANCESTOR temp table has the following interface: ``` rid INT UNIQUE generation INTEGER PRIMARY KEY ``` Where [rid] is a checkin version RID and [generation] is the 1-based number of steps from iFrom, including iFrom (so iFrom's own generation is 1). On error returns 0 and, if pSteps is not NULL, assigns *pSteps to the number of entries added to the ancestor table. On error, pSteps |
︙ | ︙ | |||
16736 16737 16738 16739 16740 16741 16742 | /* The fsl_cx class is documented in main public header. */ struct fsl_cx { /** A pointer to the "main" db handle. Exactly which db IS the main db is, because we have three DBs, not generally knowble. | | > | > | | | | | > > > > > > > > | 17107 17108 17109 17110 17111 17112 17113 17114 17115 17116 17117 17118 17119 17120 17121 17122 17123 17124 17125 17126 17127 17128 17129 17130 17131 17132 17133 17134 17135 17136 17137 17138 17139 17140 17141 17142 17143 17144 17145 | /* The fsl_cx class is documented in main public header. */ struct fsl_cx { /** A pointer to the "main" db handle. Exactly which db IS the main db is, because we have three DBs, not generally knowble. As of this writing (20141027, updated 20211018) the following applies: dbMain always points to &this->dbMem (a temp or ":memory:" (unspecified!) db opened by fsl_cx_init()), and the repo/ckout/config DBs get ATTACHed to that one. Their separate handles (this->{repo,ckout,config}.db) are used to store the name and file path to each one (even though they have no real db handle associated with them). Internal code should rely as little as possible on the actual arrangement of internal DB handles, and should use fsl_cx_db_repo(), fsl_cx_db_ckout(), and fsl_cx_db_config() to get a handle to the specific db they want. Currently they will always return NULL or the same handle, but that design decision might change at some point, so the public API treats them as separate entities. _That said_: at this point (2021-10-18), treating them as separate handles often proves to be annoying in their usage, and newer code will sometimes use (e.g.) fsl_cx_prepare() in lieu of explicitely using fsl_db_prepare() with the ostensibly db-specific handle when it knows that the required db is indeed attached. In other words: the internals, in some places, are starting to rely on this long-established convention of having a single sqlite3 object and multiple attached databases, _and that's okay_. */ fsl_db * dbMain; /** Marker which tells us whether fsl_cx_finalize() needs to fsl_free() this instance or not. */ |
︙ | ︙ | |||
16779 16780 16781 16782 16783 16784 16785 | fsl_db dbMem; /** Holds info directly related to a checkout database. */ struct { /** | | > | < < < > | > > < | | < | > | | > | | 17160 17161 17162 17163 17164 17165 17166 17167 17168 17169 17170 17171 17172 17173 17174 17175 17176 17177 17178 17179 17180 17181 17182 17183 17184 17185 17186 17187 17188 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 17200 17201 17202 17203 17204 17205 17206 17207 17208 17209 17210 17211 17212 17213 17214 17215 17216 17217 17218 17219 17220 17221 17222 17223 17224 17225 17226 17227 17228 17229 17230 17231 17232 17233 17234 17235 17236 | fsl_db dbMem; /** Holds info directly related to a checkout database. */ struct { /** Holds the filename of the current checkout db and possibly related state. Historically (very historically) it could also be the `main` db, but that is no longer the case. */ fsl_db db; /** The directory part of an opened checkout db. This is currently only set by fsl_ckout_open_dir(). It contains a trailing slash, largely because that simplifies porting fossil(1) code and appending filenames to this directory name to create absolute paths (a frequently-needed option). Useful for doing absolute-to-relative path conversions for checking file lists. */ char * dir; /** Optimization: fsl_strlen() of dir. Guaranteed to be set to dir's length if dir is not NULL. */ fsl_size_t dirLen; /** The rid of the current checkout. May be 0 for an empty repo/checkout. Must be negative if not yet known. */ fsl_id_t rid; /** The UUID of the current checkout. Only set if this->rid is positive. Owned by the containing fsl_cx object. */ fsl_uuid_str uuid; /** Julian mtime of the checkout version, as reported by the [event] table. */ double mtime; } ckout; /** Holds info directly related to a repo database. */ struct { /** Holds the filename of the current repo db and possibly related state. Historically (very historically) it could also be the `main` db, but that is no longer the case. */ fsl_db db; /** The default user name, for operations which need one. See fsl_cx_user_set(). */ char * user; } repo; /** Holds info directly related to a global config database. */ struct { /** Holds the filename of the current global config db and possibly related state. Historically (very historically) it could also be the `main` db, but that is no longer the case. */ fsl_db db; } config; /** State for incrementally proparing a checkin operation. */ |
︙ | ︙ | |||
16863 16864 16865 16866 16867 16868 16869 | The deck used for incrementally building certain parts of a checkin. */ fsl_deck mf; } ckin; /** | | > > > > > | > > > > | 17245 17246 17247 17248 17249 17250 17251 17252 17253 17254 17255 17256 17257 17258 17259 17260 17261 17262 17263 17264 17265 17266 17267 17268 17269 17270 17271 17272 17273 17274 17275 17276 17277 17278 17279 | The deck used for incrementally building certain parts of a checkin. */ fsl_deck mf; } ckin; /** Confirmation callback. Used by routines which may have to interactively ask a user to confirm things. */ fsl_confirmer confirmer; /** Output channel used by fsl_output() and friends. This was added primarily so that fossil client apps can share a single output channel which the user can swap out, e.g. to direct all output to a UI widget or a file. */ fsl_outputer output; /** Can be used to tie client-specific data to the context. Its finalizer is called when fsl_cx_finalize() cleans up. The library does not use this state. It is intended primarily for tying, e.g., scripting-engine information to the context, e.g. mapping a scripting engine context to this one for later use in fossil-side callbacks. */ fsl_state clientState; /** Holds error state. As a general rule, this information is updated only by routines which need to return more info than a simple integer error code. e.g. this is often used to hold |
︙ | ︙ | |||
16902 16903 16904 16905 16906 16907 16908 | two functions concurrently. */ fsl_buffer fileContent; /** Reuseable scratchpads for low-level file canonicalization buffering and whatnot. Not intended for huge content: use | | | < | 17293 17294 17295 17296 17297 17298 17299 17300 17301 17302 17303 17304 17305 17306 17307 17308 | two functions concurrently. */ fsl_buffer fileContent; /** Reuseable scratchpads for low-level file canonicalization buffering and whatnot. Not intended for huge content: use this->fileContent for that. This list should stay relatively short. @see fsl_cx_scratchpad() @see fsl_cx_scratchpad_yield() */ struct { /** Strictly-internal temporary buffers we intend to reuse many |
︙ | ︙ | |||
16941 16942 16943 16944 16945 16946 16947 | half a nanosecond, making it O(1) instead of O(small N) for the common case. */ short next; } scratchpads; /** | | | | 17331 17332 17333 17334 17335 17336 17337 17338 17339 17340 17341 17342 17343 17344 17345 17346 | half a nanosecond, making it O(1) instead of O(small N) for the common case. */ short next; } scratchpads; /** A bitwise copy of the config object passed to fsl_cx_init() (or some default). */ fsl_cx_config cxConfig; /** Flags, some (or one) of which is runtime-configurable by the client (see fsl_cx_flags_e). We can get rid of this and add the flags to the cache member along with the rest of them. |
︙ | ︙ | |||
16972 16973 16974 16975 16976 16977 16978 | */ bool caseInsensitive; /** If true, skip "dephantomization" of phantom blobs. This is a detail from fossil(1) with as-yet-undetermined utility. It's apparently only used during the remote-sync process, which this | | | > | 17362 17363 17364 17365 17366 17367 17368 17369 17370 17371 17372 17373 17374 17375 17376 17377 17378 17379 17380 17381 17382 17383 | */ bool caseInsensitive; /** If true, skip "dephantomization" of phantom blobs. This is a detail from fossil(1) with as-yet-undetermined utility. It's apparently only used during the remote-sync process, which this API does not (as of 2021-10) yet have. */ bool ignoreDephantomizations; /** Whether or not a running commit process should be marked as private. This flag is used for communicating this flag through multiple levels of API. */ bool markPrivate; /** True if fsl_crosslink_begin() has been called but fsl_crosslink_end() is still pending. */ |
︙ | ︙ | |||
17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 | security researcher, and we definitely must not default any symlink-related features to enabled/on. As of Feb. 2021, my personal preference, and very likely plan of attack, is to only treat SCM'd symlinks as if symlinks support is disabled. It's very unlikely that i will implement "real" symlink support but would, *solely* for compatibility with fossil(1), be convinced to permit such changes if someone else wants to implement them. */ short allowSymlinks; /** Indicates whether or not this repo has ever seen a delta manifest. If none has ever been seen then the repository will prefer to use baseline (non-delta) manifests. Once a delta is | > | 17408 17409 17410 17411 17412 17413 17414 17415 17416 17417 17418 17419 17420 17421 17422 | security researcher, and we definitely must not default any symlink-related features to enabled/on. As of Feb. 2021, my personal preference, and very likely plan of attack, is to only treat SCM'd symlinks as if symlinks support is disabled. It's very unlikely that i will implement "real" symlink support but would, *solely* for compatibility with fossil(1), be convinced to permit such changes if someone else wants to implement them. Patches are joyfully considered! */ short allowSymlinks; /** Indicates whether or not this repo has ever seen a delta manifest. If none has ever been seen then the repository will prefer to use baseline (non-delta) manifests. Once a delta is |
︙ | ︙ | |||
17042 17043 17044 17045 17046 17047 17048 | is written to it. */ short seenDeltaManifest; /** Records whether this repository has an FTS search index. <0=undetermined, 0=no, >0=yes. | | | 17434 17435 17436 17437 17438 17439 17440 17441 17442 17443 17444 17445 17446 17447 17448 | is written to it. */ short seenDeltaManifest; /** Records whether this repository has an FTS search index. <0=undetermined, 0=no, >0=yes. */ short searchIndexExists; /** Cache for the "manifest" config setting, as used by fsl_ckout_manifest_setting(), with the caveat that if the setting changes after it is cached, we won't necessarily see that here! |
︙ | ︙ | |||
17109 17110 17111 17112 17113 17114 17115 | */ fsl_mcache mcache; /** Holds various glob lists. That said... these features are actually app-level stuff which the library itself does not resp. should not enforce. We can keep track of these for users | | | 17501 17502 17503 17504 17505 17506 17507 17508 17509 17510 17511 17512 17513 17514 17515 | */ fsl_mcache mcache; /** Holds various glob lists. That said... these features are actually app-level stuff which the library itself does not resp. should not enforce. We can keep track of these for users but the library internals _generally_ have no business using them. _THAT_ said... these have enough generic utility that we can justify storing them and _optionally_ applying them. See fsl_checkin_opt for an example of where we do this. */ struct { |
︙ | ︙ | |||
17378 17379 17380 17381 17382 17383 17384 | Potential TODO: we don't really need the uncompSize param - we can deduce it, if needed, based on pBlob's content. We cannot, however, know the UUID of the decompressed content unless the client passes it in to us. @see fsl_content_put() */ | | > | > | | 17770 17771 17772 17773 17774 17775 17776 17777 17778 17779 17780 17781 17782 17783 17784 17785 17786 17787 17788 17789 17790 17791 17792 17793 17794 17795 17796 17797 17798 17799 17800 17801 17802 17803 17804 17805 17806 17807 17808 17809 17810 17811 17812 17813 17814 17815 17816 | Potential TODO: we don't really need the uncompSize param - we can deduce it, if needed, based on pBlob's content. We cannot, however, know the UUID of the decompressed content unless the client passes it in to us. @see fsl_content_put() */ FSL_EXPORT int fsl_content_put_ex( fsl_cx * const f, fsl_buffer const * pBlob, fsl_uuid_cstr zUuid, fsl_id_t srcId, fsl_size_t uncompSize, bool isPrivate, fsl_id_t * outRid); /** @internal Equivalent to fsl_content_put_ex(f,pBlob,NULL,0,0,0,newRid). This must only be used for saving raw (non-delta) content. @see fsl_content_put_ex() */ FSL_EXPORT int fsl_content_put( fsl_cx * const f, fsl_buffer const * pBlob, fsl_id_t * newRid); /** @internal If the given blob ID refers to deltified repo content, this routine undeltifies it and replaces its content with its expanded form. Returns 0 on success, FSL_RC_MISUSE if !f, FSL_RC_NOT_A_REPO if f has no opened repository, FSL_RC_RANGE if rid is not positive, and any number of other potential errors during the db and content operations. This function treats already unexpanded content as success. @see fsl_content_deltify() */ FSL_EXPORT int fsl_content_undeltify(fsl_cx * const f, fsl_id_t rid); /** @internal The converse of fsl_content_undeltify(), this replaces the storage of the given blob record so that it is a delta of srcid. |
︙ | ︙ | |||
17638 17639 17640 17641 17642 17643 17644 | /** @internal Clears and frees all (char*) members of db but leaves the rest intact. If alsoErrorState is true then the error state is also freed, else it is kept as well. */ | | | 18032 18033 18034 18035 18036 18037 18038 18039 18040 18041 18042 18043 18044 18045 18046 | /** @internal Clears and frees all (char*) members of db but leaves the rest intact. If alsoErrorState is true then the error state is also freed, else it is kept as well. */ FSL_EXPORT void fsl_db_clear_strings(fsl_db * const db, bool alsoErrorState ); /** @internal Returns 0 if db appears to have a current repository schema, 1 if it appears to have an out of date schema, and -1 if it appears to not be a repository. Results are undefined if db is NULL or not opened. |
︙ | ︙ | |||
17788 17789 17790 17791 17792 17793 17794 | /** @internal Creates the ticket and ticketchng tables in f's repository db, DROPPING them if they already exist. The schema comes from fsl_schema_ticket(). | | | | 18182 18183 18184 18185 18186 18187 18188 18189 18190 18191 18192 18193 18194 18195 18196 18197 18198 18199 18200 18201 | /** @internal Creates the ticket and ticketchng tables in f's repository db, DROPPING them if they already exist. The schema comes from fsl_schema_ticket(). TODO? Add a flag specifying whether to drop or keep existing copies. Returns 0 on success. */ FSL_EXPORT int fsl_cx_ticket_create_table(fsl_cx * const f); /** @internal Frees all J-card entries in the given list. li is assumed to be empty or contain (fsl_card_J*) instances. If alsoListMem is true then any memory owned |
︙ | ︙ | |||
17854 17855 17856 17857 17858 17859 17860 | important for card ordering in generated manifests. This routine expects to get passed (fsl_card_J**) (namely from fsl_list entries), and will not work on an array of J-cards. */ FSL_EXPORT int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ); | < < < < < < < < < < < < < < < | 18248 18249 18250 18251 18252 18253 18254 18255 18256 18257 18258 18259 18260 18261 | important for card ordering in generated manifests. This routine expects to get passed (fsl_card_J**) (namely from fsl_list entries), and will not work on an array of J-cards. */ FSL_EXPORT int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ); /** @internal This function updates the repo and/or global config databases with links between the dbs intended for various fossil-level bookkeeping and housecleaning. These links are not essential to fossil's functionality but assist in certain "global" operations. |
︙ | ︙ | |||
18013 18014 18015 18016 18017 18018 18019 | /** @internal Part of the fsl_cx::fileContent optimization. This sets f->fileContent.used to 0 and if its capacity is over a certain (unspecified, unconfigurable) size then it is trimmed to that size. */ | | | | > > | 18392 18393 18394 18395 18396 18397 18398 18399 18400 18401 18402 18403 18404 18405 18406 18407 18408 18409 18410 18411 18412 18413 18414 18415 18416 18417 18418 18419 18420 18421 18422 18423 18424 18425 18426 18427 18428 18429 18430 18431 18432 18433 18434 18435 18436 18437 18438 18439 18440 | /** @internal Part of the fsl_cx::fileContent optimization. This sets f->fileContent.used to 0 and if its capacity is over a certain (unspecified, unconfigurable) size then it is trimmed to that size. */ FSL_EXPORT void fsl_cx_content_buffer_yield(fsl_cx * const f); /** @internal Currently disabled (always returns 0) pending resolution of a "wha???" result from one of the underlying queries. Queues up the given artifact for a search index update. This is only intended to be called from crosslinking steps and similar content updates. Returns 0 on success. The final argument is intended only for wiki titles (the L-card of a wiki post). If the repository database has no search index or the given content is marked as private, this function returns 0 and makes no changes to the db. */ FSL_EXPORT int fsl_search_doc_touch(fsl_cx * const f, fsl_satype_e saType, fsl_id_t rid, const char * docName); /** @internal Performs the same job as fsl_diff_text() but produces the results in the low-level form of an array of "copy/delete/insert triples." This is primarily intended for internal use in other library-internal algorithms, not for client code. Note all FSL_DIFF_xxx flags apply to this form. Returns 0 on success, any number of non-0 codes on error. On success *outRaw will contain the resulting array, which must eventually be fsl_free()'d by the caller. On error *outRaw is not modified. @deprecated Use fsl_diff_v2_raw() instead. */ FSL_EXPORT int fsl_diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, int diffFlags, int ** outRaw); /** @internal If the given file name is a reserved filename (case-insensitive) on |
︙ | ︙ | |||
18178 18179 18180 18181 18182 18183 18184 | d. d->f must be set, d->rid must be set and valid and d's contents must accurately represent the stored manifest for the given rid. This is normally run just after the insertion of a new manifest, but is sometimes also run after reading a deck from the database (in order to rebuild all db relations and add/update the timeline entry). | | | | | 18559 18560 18561 18562 18563 18564 18565 18566 18567 18568 18569 18570 18571 18572 18573 18574 18575 18576 18577 18578 18579 18580 18581 18582 18583 18584 18585 18586 18587 18588 18589 18590 18591 18592 18593 18594 18595 18596 18597 18598 18599 18600 18601 | d. d->f must be set, d->rid must be set and valid and d's contents must accurately represent the stored manifest for the given rid. This is normally run just after the insertion of a new manifest, but is sometimes also run after reading a deck from the database (in order to rebuild all db relations and add/update the timeline entry). Returns 0 on succes, FSL_RC_MISUSE !d->f, FSL_RC_RANGE if d->rid<=0, FSL_RC_MISUSE (with more error info in f) if d does not contain all required cards for its d->type value. It may return various other codes from the many routines it delegates work to. Crosslinking of ticket artifacts is currently (2021-03) missing. Design note: d "really should" be const here but some internals (d->F.cursor and delayed baseline loading) prohibit it. @see fsl_deck_crosslink_one() */ FSL_EXPORT int fsl_deck_crosslink( fsl_deck /* const */ * const d ); /** @internal Run automatically by fsl_deck_save(), so it needn't normally be run aside from that, at least not from average client code. This is a convience form of crosslinking which must only be used when a single deck (and only a single deck) is to be crosslinked. This function wraps the crosslinking in fsl_crosslink_begin() and fsl_crosslink_end(), but otherwise behaves the same as fsl_deck_crosslink(). If crosslinking fails, any in-progress transaction will be flagged as failed. Returns 0 on success. */ FSL_EXPORT int fsl_deck_crosslink_one( fsl_deck * const d ); /** @internal Checks whether the given filename is "safe" for writing to within f's current checkout. zFilename must be in canonical form: only '/' directory separators. |
︙ | ︙ | |||
18287 18288 18289 18290 18291 18292 18293 | On error returns non-0, pnChng and aiChng are not modified, and f's error state might (depending on the error) contain a description of the problem. Space to hold *aiChng is obtained from fsl_malloc() and must be released by the caller. */ | | | | | | | | 18668 18669 18670 18671 18672 18673 18674 18675 18676 18677 18678 18679 18680 18681 18682 18683 18684 18685 18686 18687 | On error returns non-0, pnChng and aiChng are not modified, and f's error state might (depending on the error) contain a description of the problem. Space to hold *aiChng is obtained from fsl_malloc() and must be released by the caller. */ FSL_EXPORT int fsl__find_filename_changes(fsl_cx * const f, fsl_id_t iFrom, fsl_id_t iTo, bool revOK, uint32_t *pnChng, fsl_id_t **aiChng); /** Bitmask of file change types for use with fsl_is_locally_modified(). */ enum fsl_localmod_e { /** Sentinel value. */ |
︙ | ︙ | |||
18519 18520 18521 18522 18523 18524 18525 | Undocumented. For internal debugging only. */ void fsl__dump_triples(fsl_diff_cx const * const p, char const * zFile, int ln ); /** @internal | > > > > > > > > > | | 18900 18901 18902 18903 18904 18905 18906 18907 18908 18909 18910 18911 18912 18913 18914 18915 18916 18917 18918 18919 18920 18921 18922 18923 | Undocumented. For internal debugging only. */ void fsl__dump_triples(fsl_diff_cx const * const p, char const * zFile, int ln ); /** @internal Removes from the BLOB table all artifacts that are in the SHUN table. Returns 0 on success. Requires (asserts) that a repo is opened. */ int fsl__shunned_remove(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 */ #define FSL_LINE_LENGTH_MASK ((1<<FSL_LINE_LENGTH_MASK_SZ)-1) |
︙ | ︙ | |||
19119 19120 19121 19122 19123 19124 19125 | APIs will be necessary for most clients. The process of creating an artifact looks a lot like the following code example. We have elided error checking for readability purposes, but in fact this code has undefined behaviour if error codes are not checked and appropriately reacted to. | < > | > < > | 19509 19510 19511 19512 19513 19514 19515 19516 19517 19518 19519 19520 19521 19522 19523 19524 19525 19526 19527 19528 19529 19530 19531 19532 19533 19534 19535 19536 19537 19538 | APIs will be necessary for most clients. The process of creating an artifact looks a lot like the following code example. We have elided error checking for readability purposes, but in fact this code has undefined behaviour if error codes are not checked and appropriately reacted to. ``` fsl_deck deck = fsl_deck_empty; fsl_deck * d = &deck ; // for typing convenience. // Doxygen bug ^^^^^^^ requires space before semicolon! fsl_deck_init( fslCtx, d, FSL_SATYPE_CONTROL ); // must come first fsl_deck_D_set( d, fsl_julian_now() ); fsl_deck_U_set( d, "your-fossil-name", -1 ); fsl_deck_T_add( d, FSL_TAGTYPE_ADD, "...uuid being tagged...", "tag-name", "optional tag value"); ... // Now output it to stdout: fsl_deck_output( f, d, fsl_output_f_FILE, stdout ); // See also: fsl_deck_save(), which stores it in the db and // "crosslinks" it. fsl_deck_finalize(d); ``` The order the cards are added to the deck is irrelevant - they will be output in the order specified by the Fossil specs regardless of their insertion order. Each setter/adder function knows, based on the deck's type (set via fsl_deck_init()), whether the given card type is legal, and will return an error (probably FSL_RC_TYPE) if an attempt is made to add a card which is illegal |
︙ | ︙ | |||
19180 19181 19182 19183 19184 19185 19186 | Tip: implementing a "dry-run" mode for most fossil operations is trivial by starting a transaction before performing the operations. Many operations run in a transaction, but if the client starts one of his own they can "dry-run" any op by simply rolling back the transaction he started. Abstractly, that looks like this pseudocode: | < > < > | 19571 19572 19573 19574 19575 19576 19577 19578 19579 19580 19581 19582 19583 19584 19585 19586 19587 19588 19589 19590 19591 | Tip: implementing a "dry-run" mode for most fossil operations is trivial by starting a transaction before performing the operations. Many operations run in a transaction, but if the client starts one of his own they can "dry-run" any op by simply rolling back the transaction he started. Abstractly, that looks like this pseudocode: ``` db.begin(); fsl.something(); fsl.somethingElse(); if( dryRun ) db.rollback(); else db.commit(); ``` */ /** @page page_code_conventions Code Conventions Project and Code Conventions... Foreward: all of this more or less evolved organically or was |
︙ | ︙ | |||
19697 19698 19699 19700 19701 19702 19703 | /** @def FCLI_FLAG_xxx The various FCLI_FLAG_xxx macros are convenience-form initializers for fcli_cliflag instances for use in initializing a fcli_cliflag array. Example usage: | < > < > | 20088 20089 20090 20091 20092 20093 20094 20095 20096 20097 20098 20099 20100 20101 20102 20103 20104 20105 20106 20107 20108 20109 20110 20111 20112 20113 20114 20115 20116 | /** @def FCLI_FLAG_xxx The various FCLI_FLAG_xxx macros are convenience-form initializers for fcli_cliflag instances for use in initializing a fcli_cliflag array. Example usage: ``` bool flag1 = true, flag2 = false; int32_t flag3 = 0; const char * flag4 = 0; const fcli_cliflag cliFlags[] = { FCLI_FLAG_BOOL("x","xyz",&flag1,"Flag 1."), FCLI_FLAG_BOOL_INVERT(NULL,"yzx",&flag2,"Flag 2."), FCLI_FLAG_INT32("z",NULL,"value",&flag3,"Flag 3"), FCLI_FLAG("f","file","filename",&flag4, "Input file. May optionally be passed as the first " "non-flag argument."), fcli_cliflag_empty_m // list MUST end with this (or equivalent) }; fcli.cliFlags = cliFlags; ``` BE CAREFUL with the data types, as we're using (void*) to access data of an arbitrary type, the type being defined by a separate field in the fcli_cliflag object. */ #define FCLI_FLAG_xxx /* for doc purposes only */ //Members: |
︙ | ︙ | |||
19823 19824 19825 19826 19827 19828 19829 | FCLI_RC_FLAG_AGAIN will be traversed repeatedly before moving on to the next flag. This ordering may have side-effects on how the app sets up its flag handling. In particular, this arrangement makes the common cases easy to implement but makes certain more complicated situations effectively impossible to implement. For example: | < > < > | 20214 20215 20216 20217 20218 20219 20220 20221 20222 20223 20224 20225 20226 20227 20228 20229 20230 | FCLI_RC_FLAG_AGAIN will be traversed repeatedly before moving on to the next flag. This ordering may have side-effects on how the app sets up its flag handling. In particular, this arrangement makes the common cases easy to implement but makes certain more complicated situations effectively impossible to implement. For example: ``` -x=1 -y=2 -x=3 -y=4 ``` Assuming the definition of the -y flag is first in this array and has a callback which returns FCLI_RC_FLAG_AGAIN, the two -x flags will be processed before either of the -y flags. Thus it is impossible (using this method of traversal) to change the behavior of the -y flags based on the left-closest -x value. |
︙ | ︙ | |||
19888 19889 19890 19891 19892 19893 19894 | /** A verbosity level counter. Starts at 0 (no verbosity) and goes up for higher verbosity levels. Currently levels 1 and 2 are intended for app-level use and level 3 for library-level use. */ unsigned short verbose; | < < < < < < < | 20279 20280 20281 20282 20283 20284 20285 20286 20287 20288 20289 20290 20291 20292 | /** A verbosity level counter. Starts at 0 (no verbosity) and goes up for higher verbosity levels. Currently levels 1 and 2 are intended for app-level use and level 3 for library-level use. */ unsigned short verbose; } clientFlags; /** Transient settings and flags. These are bits which are used during (or very shortly after) fcli_setup() but have no effect if modified after that. */ struct { |
︙ | ︙ | |||
19994 19995 19996 19997 19998 19999 20000 | convention is that the app immediately returns the result of fcli_end_of_main(THE_RESULT_CODE) from main(). That function will treat FCLI_RC_HELP as a non-error and will report any error state pending in the fcli_cx() object. Example of intended basic usage: | < > | 20378 20379 20380 20381 20382 20383 20384 20385 20386 20387 20388 20389 20390 20391 20392 | convention is that the app immediately returns the result of fcli_end_of_main(THE_RESULT_CODE) from main(). That function will treat FCLI_RC_HELP as a non-error and will report any error state pending in the fcli_cx() object. Example of intended basic usage: ``` int main(int argc, char const * const * argv){ ... // Optional fcli_cliflag setup: fcli_cliflag const cliFlags[] = { ..., fcli_cliflag_empty_m }; |
︙ | ︙ | |||
20019 20020 20021 20022 20023 20024 20025 | if(rc) goto end; ... app logic ... end: return fcli_end_of_main(rc); } | < > > > > > > > > > > > > > > > > | 20403 20404 20405 20406 20407 20408 20409 20410 20411 20412 20413 20414 20415 20416 20417 20418 20419 20420 20421 20422 20423 20424 20425 20426 20427 20428 20429 20430 20431 20432 20433 20434 20435 20436 20437 | if(rc) goto end; ... app logic ... end: return fcli_end_of_main(rc); } ``` @see fcli_pre_setup() */ FSL_EXPORT int fcli_setup(int argc, char const * const * argv ); /** A convenience form of fcli_setup() which is equivalent to: ``` fcli.cliFlags = ...3rd argument...; fcli.appHelp = ...4th argument...; int rc = fcli_setup(argc, argv); ``` Either of the 3rd or 4th arguments may be NULL. */ FSL_EXPORT int fcli_setup_v2(int argc, char const * const * argv, fcli_cliflag const * const cliFlags, fcli_help_info const * const helpInfo ); /** The first time this is called, it swaps out libfossil's default allocator with a fail-fast one which abort()s on allocation error. This is normally called by fcli_setup(), but that also means that it's illegal to use fsl_malloc() and friends before calling that routine. If an application really needs to use fsl_malloc() before calling fcli_setup(), it must call this first in order to |
︙ | ︙ | |||
20107 20108 20109 20110 20111 20112 20113 | FSL_EXPORT bool fcli_flag(char const * opt, const char ** value); /** Works like fcli_flag() but tries two argument forms, in order. It is intended to be passed short and long forms, but can be passed two aliases or similar. It accepts NULL for either form. | < > < > | 20506 20507 20508 20509 20510 20511 20512 20513 20514 20515 20516 20517 20518 20519 20520 20521 20522 20523 20524 | FSL_EXPORT bool fcli_flag(char const * opt, const char ** value); /** Works like fcli_flag() but tries two argument forms, in order. It is intended to be passed short and long forms, but can be passed two aliases or similar. It accepts NULL for either form. ``` const char * v = NULL; fcli_flag2("n", "limit", &v); if(v) { ... } ``` */ FSL_EXPORT bool fcli_flag2(char const * opt1, char const * opt2, const char ** value); /** Works similarly to fcli_flag2(), but if no flag is found and value is not NULL then *value is assigned to the return value of |
︙ | ︙ | |||
20225 20226 20227 20228 20229 20230 20231 20232 20233 20234 20235 20236 20237 20238 20239 20240 20241 20242 | Describes a named callback command. @see fcli_dispatch_commands() */ struct fcli_command { /** The name of the command. */ char const * name; /** Brief description, for use in generating help text. */ char const * briefDescription; /** The callback for this command. */ fcli_command_f f; /** Must be NULL or an array compatible with fcli_process_flags(). Can be used from within this->f for command-specific flag dispatching, as well as help text generation. */ fcli_cliflag const * flags; }; | > > > > > > > | 20624 20625 20626 20627 20628 20629 20630 20631 20632 20633 20634 20635 20636 20637 20638 20639 20640 20641 20642 20643 20644 20645 20646 20647 20648 | Describes a named callback command. @see fcli_dispatch_commands() */ struct fcli_command { /** The name of the command. */ char const * name; /** NUL-delimited string containing optional aliases of the above command name. This string _must_ be double-NUL terminated (e.g., "alias\0\al\0"). */ char const * aliases; /** Brief description, for use in generating help text. */ char const * briefDescription; /** The callback for this command. */ fcli_command_f f; /** Optional usage callback for this command. */ void (*usage)(void); /** Must be NULL or an array compatible with fcli_process_flags(). Can be used from within this->f for command-specific flag dispatching, as well as help text generation. */ fcli_cliflag const * flags; }; |
︙ | ︙ | |||
20260 20261 20262 20263 20264 20265 20266 20267 20268 20269 20270 20271 20272 20273 20274 20275 20276 | _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); /** 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 handled via an atexit() handler. It is intended to be called once at the very end of main: | > > > > > > > < > < > | 20666 20667 20668 20669 20670 20671 20672 20673 20674 20675 20676 20677 20678 20679 20680 20681 20682 20683 20684 20685 20686 20687 20688 20689 20690 20691 20692 20693 20694 20695 20696 20697 20698 20699 20700 20701 20702 20703 20704 20705 | _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 handled via an atexit() handler. It is intended to be called once at the very end of main: ``` int main(){ int rc; ...set up fcli...assign rc... return fcli_end_of_main(rc); } ``` As a special case, if mainRc is FCLI_RC_HELP, it is assumed to be the result of the fcli --help flag handling, and is treated as if it were 0. @see fcli_error() @see fcli_err_set() |
︙ | ︙ | |||
20357 20358 20359 20360 20361 20362 20363 | /** Requires that cmd be an array of fcli_command objects with a trailing entry which has a NULL name. This function iterates over them and outputs help text based on each one's (name, briefDescription, flags) members. | > > > > | | > > > > > | 20770 20771 20772 20773 20774 20775 20776 20777 20778 20779 20780 20781 20782 20783 20784 20785 20786 20787 20788 20789 20790 20791 20792 20793 20794 20795 20796 | /** Requires that cmd be an array of fcli_command objects with a trailing entry which has a NULL name. This function iterates over them and outputs help text based on each one's (name, briefDescription, flags) members. If the 2nd argument is true AND the fcli_command->usage() callback has been set, it will be called for each object in the cmd array after outputting the fcli_command->flags help text. If the 3rd argument is true, only the help for the single given object is output, not any adjacent array members (if any). */ FSL_EXPORT void fcli_command_help(fcli_command const * cmd, bool showUsage, bool onlyOne); /** Pretty print fcli_command->aliases like: " (aliases: cmd, com)". */ FSL_EXPORT void fcli_help_show_aliases(char const * aliases); /** If fcli has a checkout opened, this dumps various info about it to its output channel. Returns 0 on success, FSL_RC_NOT_A_CKOUT if no checkout is opened, or some other non-0 code on error. If the useUtc argument is true, it uses UTC timestamps, else |
︙ | ︙ | |||
20467 20468 20469 20470 20471 20472 20473 | fsl_cx-level error state provides. e.g. it may provide a hint about how to recover. */ FSL_EXPORT int fcli_fingerprint_check(bool reportImmediately); /** Returns the "tail" part of the argv[0] string which was passed to | | | | | 20889 20890 20891 20892 20893 20894 20895 20896 20897 20898 20899 20900 20901 20902 20903 20904 20905 | fsl_cx-level error state provides. e.g. it may provide a hint about how to recover. */ FSL_EXPORT int fcli_fingerprint_check(bool reportImmediately); /** 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{ |
︙ | ︙ | |||
20497 20498 20499 20500 20501 20502 20503 20504 20505 20506 20507 20508 20509 20510 20511 | /** Populates the given fsl_diff_opt::ansiColors state with values dependend on the second argument. */ FSL_EXPORT void fcli_diff_colors(fsl_diff_opt * const tgt, fcli_diff_colors_e theme); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* _ORG_FOSSIL_SCM_FCLI_H_INCLUDED_ */ /* end of file ../include/fossil-scm/fossil-cli.h */ | > > > > > > > > > > > | 20919 20920 20921 20922 20923 20924 20925 20926 20927 20928 20929 20930 20931 20932 20933 20934 20935 20936 20937 20938 20939 20940 20941 20942 20943 20944 | /** Populates the given fsl_diff_opt::ansiColors state with values dependend on the second argument. */ FSL_EXPORT void fcli_diff_colors(fsl_diff_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 up the detail all the way. */ FSL_EXPORT void fcli_dump_stmt_cache(bool forceVerbose); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* _ORG_FOSSIL_SCM_FCLI_H_INCLUDED_ */ /* end of file ../include/fossil-scm/fossil-cli.h */ |