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          [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. */ |