Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From 0.4 To 0.3
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 |
︙ | ︙ |
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 | # README # fnc 0.2a ## 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 x86 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 Alpha development notwithstanding, the `timeline` and `diff` commands are relatively stable; however, several known bugs exist—and 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 `~/man/man1`, respectively. Alternatively, cryptographically signed binaries for some of the abovementioned platforms are available to [download][3]. # Doc See `fnc --help` and the [fnc(1)][4] manual page. 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 `fossil patch create` patches are preferred, `diff -u` and `fossil diff` patches are also welcomed. Please ensure code complies with OpenBSD's KNF [style(9)][13], and any patch containing user-visible code addition, modification, or deletion (i.e., code that impacts user interfaces) should concomitantly include updating documentation affected by the change. # Screenshots ![diff split screen](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-splitscreen.png "fnc diff split screen") ![diff renamed file](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-full-file_renamed.png "fnc diff file renamed") ![diff added file](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-split-file_added.png "fnc diff file added") ![diff removed file](https://fnc.bsdbox.org/uv/resources/img/fnc-diff-split-file-removed.png "fnc diff file removed") ![timeline help](https://fnc.bsdbox.org/uv/resources/img/fnc-timeline-help.png "fnc timeline help") [0]: https://fossil-scm.org [1]: https://fossil.wanderinghorse.net/r/libfossil [2]: https://fossil-scm.org/home/help?cmd=ui [3]: https://fnc.bsdbox.org/uv/download.html [4]: https://fnc.bsdbox.org/uv/resources/doc/fnc.1.html |
︙ | ︙ |
Changes to auto.def.
1 2 3 4 5 6 7 | # 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." | < | 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." |
︙ | ︙ | |||
96 97 98 99 100 101 102 | 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." } | < < < < < < < < | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | 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 } |
︙ | ︙ |
Changes to autosetup/wh-common.tcl.
︙ | ︙ | |||
358 359 360 361 362 363 364 | } ######################################################################## # 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?). # | < < < < < < | | | | < < < < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | } ######################################################################## # 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. proc wh-make-from-dot-in {filename} { foreach f $filename { catch { exec chmod u+w $f } make-template $f.in $f catch { exec chmod u-w $f } } } |
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@ LDFLAGS += @LDFLAGS@ 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 | .Op Ar command .Op Fl h | -help .Nm .Op Fl h | -help .Op Fl v | -version .Nm .Cm timeline .Op Fl z .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 .Ar commit1 .Ar commit2 .Nm .Cm tree .Op Fl c Ar commit .Op Ar path .Nm .Cm blame .Op Fl c Ar commit Op Fl r .Op Fl n Ar number .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, .Nm will default to displaying this view. .It Diff view Display changes introduced in the specified commit. .It Tree view Display tree reflecting the repository state as at the specified commit. .It Blame view Display historical attribution of each line in the specified file. .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 |
︙ | ︙ | |||
115 116 117 118 119 120 121 | .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 | | < | | | < > < < < < < | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | .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 120 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 .It Cm timeline 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 .Pp 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 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. |
︙ | ︙ | |||
178 179 180 181 182 183 184 | 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 | | | | | 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | 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 trunk of the repository tree. Note that this option also accepts .Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \ "Fossil's Special Tags". .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. |
︙ | ︙ | |||
211 212 213 214 215 216 217 | .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 . | | < < < < | 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | .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. .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 |
︙ | ︙ | |||
246 247 248 249 250 251 252 | .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. | < < < < < | < | < < < < < < < < < < < < < < < < | < < < < | | | | < < | | | < | 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 | .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 \ Ar commit1 Ar commit2 Display the differences between two commits. Both .Ar commit1 and .Ar commit2 must be of the same type, which is expected to be either (a unique abbreviated prefix of) a commit artifact UUID SHA1 or SHA3 hash, a symbolic branch name, or an ISO 8601 formatted date. .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. .El .Pp Key bindings for .Cm fnc diff are as follows: .Bl -tag -width Ds .It Cm c Toggle coloured diff output. .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 display of whitespace-only changes in diff output. .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 |
︙ | ︙ | |||
377 378 379 380 381 382 383 | 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 | < < | | 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | 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 Display 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, |
︙ | ︙ | |||
419 420 421 422 423 424 425 | 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 | | | | | 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 | 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. Note that this option also accepts .Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \ "Fossil's Special Tags". .It Fl h , -help Display tree command help and usage information then exit. .El .Pp Key bindings for .Cm fnc tree are as follows: |
︙ | ︙ | |||
450 451 452 453 454 455 456 | .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 | | | 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 | .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. .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 |
︙ | ︙ | |||
472 473 474 475 476 477 478 | .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 | < < | 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. |
︙ | ︙ | |||
504 505 506 507 508 509 510 | 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 | | | | | < < < | | | | 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | 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. This option also accepts .Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \ "Fossil's Special Tags". .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. 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 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 |
︙ | ︙ | |||
559 560 561 562 563 564 565 | 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 | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 | 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. .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 .El .Sh EXIT STATUS .Ex -std fnc .Sh SEE ALSO .Xr fossil 1 , .Xr re_format 7 .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 | #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" #define FNC_VERSION 0.3a /* 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__) /* 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" |
︙ | ︙ | |||
141 142 143 144 145 146 147 | #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); | < < < | | < < < | < < | > | < < | < < < < < < | | < | | < | | < | | < < < | > > > < < < < | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | #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; 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. */ /* Blame options. */ bool reverse; /* Reverse annotation; requires sym. */ /* Command line flags and help. */ fcli_help_info fnc_help; /* Global help. */ fcli_cliflag cliflags_global[3]; /* Global options. */ fcli_command cmd_args[5]; /* App commands. */ void (*fnc_usage_cb[4])(void); /* Command usage. */ fcli_cliflag cliflags_timeline[10]; /* Timeline options. */ fcli_cliflag cliflags_diff[7]; /* Diff options. */ fcli_cliflag cliflags_tree[4]; /* Tree options. */ fcli_cliflag cliflags_blame[6]; /* Blame 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. */ 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. */ false, /* reverse annotation defaults to off. */ { /* 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", "Show chronologically descending commit history of " "the repository.", cmd_timeline, fnc_init.cliflags_timeline}, {"diff", "Show changes to versioned files introduced with a given " "commit.", cmd_diff, fnc_init.cliflags_diff}, {"tree", "Show repository tree corresponding to a given commit", cmd_tree, fnc_init.cliflags_tree}, {"blame", "Show commit attribution history for each line of a " "file.", cmd_blame, fnc_init.cliflags_blame}, {NULL,NULL,NULL, NULL} /* Sentinel. */ }, /* fnc_usage_cb individual command usage details. */ {&usage_timeline, &usage_diff, &usage_tree, &usage_blame}, { /* cliflags_timeline timeline command related options. */ 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" |
︙ | ︙ | |||
368 369 370 371 372 373 374 | "\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, | | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < | 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 | "\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. Useful for large " "files\n with extensive history."), 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. */ }; 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 }; enum fnc_search_mvmnt { SEARCH_DONE, SEARCH_FORWARD, SEARCH_REVERSE }; |
︙ | ︙ | |||
455 456 457 458 459 460 461 | FNC_DIFF_META = 1, FNC_DIFF_MINUS, FNC_DIFF_PLUS, FNC_DIFF_CHNK, FNC_TREE_LINK, FNC_TREE_DIR, FNC_TREE_EXEC, | | < < < < < < < < < < | | | | | | | | | | | | < | 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 | FNC_DIFF_META = 1, FNC_DIFF_MINUS, FNC_DIFF_PLUS, FNC_DIFF_CHNK, FNC_TREE_LINK, FNC_TREE_DIR, FNC_TREE_EXEC, FNC_COMMIT }; 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; }; struct fsl_file_artifact { fsl_card_F *fc; enum fsl_ckout_change_e change; }; |
︙ | ︙ | |||
587 588 589 590 591 592 593 | 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; | < < < < < < < < < < < < | 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; |
︙ | ︙ | |||
698 699 700 701 702 703 704 | 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; | | | 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 | 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 ndepth; /* 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; |
︙ | ︙ | |||
727 728 729 730 731 732 733 | int spin_idx; bool done; bool blame_complete; bool eof; bool colour; }; | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 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; |
︙ | ︙ | |||
831 832 833 834 835 836 837 | 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 *); | | | | 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 | 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 void help(struct fnc_view *); static void padpopup(struct fnc_view *, const char **, 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); |
︙ | ︙ | |||
854 855 856 857 858 859 860 | 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, | | < | | < | < < < < < < | 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 | 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); 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); static int diff_checkout(fsl_buffer *, fsl_id_t, int, int, int); 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); 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 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 *); |
︙ | ︙ | |||
962 963 964 965 966 967 968 | 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 *); | < < < < < < < < < < < < < < < < < < < < < < < < < < > > | 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 | 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; fsl_cx *f = NULL; fsl_error e = fsl_error_empty; /* DEBUG */ 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; |
︙ | ︙ | |||
1056 1057 1058 1059 1060 1061 1062 | rc = fcli_fingerprint_check(true); if (rc) goto end; if (argc == 1) cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE]; | < | | | | | | | | < | | | | | | | | | | | | < > | > < < < < < < < < | > > | < > | 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 | rc = fcli_fingerprint_check(true); if (rc) goto end; if (argc == 1) cmd = &fnc_init.cmd_args[FNC_VIEW_TIMELINE]; else if ((rc = fcli_dispatch_commands(fnc_init.cmd_args, false) == 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 */ } f = fcli_cx(); if (!fsl_cx_db_repo(f)) { rc = RC(FSL_RC_MISUSE, "%s", "repository database required"); goto end; } if (cmd != NULL) rc = cmd->f(cmd); end: fsl_free(path); endwin(); putchar('\n'); if (rc) { if (rc == FCLI_RC_HELP) rc = 0; else { fsl_error_set(&e, rc, NULL); fsl_fprintf(stderr, "%s: %s %d\n", fcli_progname(), fsl_rc_cstr(rc), rc); fsl_fprintf(stderr, "-> %s\n", e.msg.mem); } } return fcli_end_of_main(rc); } static int cmd_timeline(fcli_command const *argv) { struct fnc_view *v; |
︙ | ︙ | |||
1656 1657 1658 1659 1660 1661 1662 | 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; | < | < < < | < < < < < < < < < < < | < < < < < | 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 | 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; if ((rc = fsl_db_prepare_cached(db, &s->thread_cx.q, "%b", &sql))) goto end; fsl_stmt_step(s->thread_cx.q); 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; end: fsl_buffer_clear(&sql); if (rc) { close_timeline_view(view); if (db->error.code) rc = fsl_cx_uplift_db_error(f, db); } |
︙ | ︙ | |||
1953 1954 1955 1956 1957 1958 1959 | } static int build_commits(struct fnc_tl_thread_cx *cx) { int rc = 0; | < < < < < < < < < < < < < < < < | 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; |
︙ | ︙ | |||
2041 2042 2043 2044 2045 2046 2047 | { 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; | < | | < | 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 | { 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) { rc = fsl_db_prepare_cached(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_cached"); 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"; break; case 'w': type = "wiki"; if (comment) { switch (*comment) { case '+': prefix = "Added: "; |
︙ | ︙ | |||
2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 | } 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); | > < < > | 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 | } 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->uuid = fsl_strdup(fsl_stmt_g_text(q, 0, NULL)); commit->rid = rid; commit->type = fsl_strdup(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: fsl_stmt_cached_yield(q); return rc; } static int signal_tl_thread(struct fnc_view *view, int wait) { struct fnc_tl_thread_cx *cx = &view->state.timeline.thread_cx; |
︙ | ︙ | |||
2191 2192 2193 2194 2195 2196 2197 | 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; | < | 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; |
︙ | ︙ | |||
2275 2276 2277 2278 2279 2280 2281 | if (rc) goto end; werase(view->window); if (screen_is_shared(view)) wstandout(view->window); | < < < < < < | 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); |
︙ | ︙ | |||
2323 2324 2325 2326 2327 2328 2329 | 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) | | | | 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 | 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) wstandout(view->window); rc = write_commit_line(view, entry->commit, max_usrlen); if (ncommits == s->selected_idx) wstandend(view->window); ++ncommits; s->last_commit_onscreen = entry; entry = TAILQ_NEXT(entry, entries); } draw_vborder(view); end: |
︙ | ︙ | |||
2480 2481 2482 2483 2484 2485 2486 | * * 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) { | < < | | < | | | < < < < < < < < < < < < < < < < < < < | 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 | * * 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) { wchar_t *usr_wcstr = NULL, *wcomment = NULL; char *comment0 = NULL, *comment = NULL, *date = NULL; char *eol = NULL, *pad = NULL, *user = NULL; size_t i = 0; int col_pos, ncols_avail, usrlen, 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); waddnstr(view->window, date, col_pos); if (col_pos > view->ncols) goto end; /* If enough columns, write abbreviated commit hash. */ if (view->ncols >= 110) { wprintw(view->window, "%.9s ", commit->uuid); 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; waddwstr(view->window, usr_wcstr); pad = fsl_mprintf("%*c", max_usrlen - usrlen + 2, ' '); waddstr(view->window, pad); 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; |
︙ | ︙ | |||
2708 2709 2710 2711 2712 2713 2714 | rc = view->input(new, view, ch); break; } return rc; } | | < | | | | | > > > > > > | | | | | | < < < > | | > | < < | < < < < < | | > > > > | > > | | | | | | | > > > > > > | | > > > > > > | | > | | > > > | < < < < < < < > | < < < < < | < < > | | > | | < < < < < | < < < < < > | < | | > > > > | < > > > > > > > > | > > > | | | | | | | > > > > > > > > > > > | | > | | < < < | < < > > | | < | < < < < < | < < | > | < < < < < < < | < < < < < < < < < < < < < < | < < < < < < | | < | < < < | | | | | > > > > > > > > > > > > > > | | | | | | | | | > > | 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 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 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 | rc = view->input(new, view, ch); break; } return rc; } static void help(struct fnc_view *view) { static const char *progname = NULL; static const char *help[] = { "", "Global", " H,?,F1 Open in-app help", " f Toggle fullscreen", " Tab Switch focus between open views", " Q Quit the program", " q Quit the active view", "", "Timeline", " k,<Up>,<,, Move selection cursor up one commit", " j,<Down>,>,. Move selection cursor down one commit", " C-b,PgUp Scroll up one page", " C-f,PgDn Scroll down one page", " gg,Home Jump to first line in the current view", " G,End Jump to last line in the current view", " Enter,Space Open a diff view of the selected commit", " / Open prompt to enter timeline search", " n Find next commit matching the current search " "term", " N Find previous commit matching the current " "search term", "", "Diff", " k,<Up> Scroll up one line of diff output", " j,<Down> Scroll down one line of diff output", " C-b,PgUp Scroll up one page of diff output", " C-f,PgDn Scroll down one page of diff output", " gg,Home Scroll to the top of the diff view", " G,End Scroll to the end of the diff view", " c Toggle coloured diff output", " i Toggle inversion of diff output", " v Toggle verbosity of diff output", " w Toggle ignore whitespace-only changes in diff", " -,_ Decrease the number of context lines", " +,= Increase the number of context lines", " C-k,K,<,, Display diff of next commit in the timeline", " C-j,J,>,. Display diff of previous commit in the " "timeline", " / Open prompt to enter diff search", " n Find next line matching the current search " "term", " N Find previous line matching the current search" " term", "", "Tree", " k,<Up> Move selection cursor up one entry", " j,<Down> Move selection cursor down one entry", " C-b,PgUp Scroll up one page", " C-f,PgDn Scroll down one page", " gg,Home Jump to first entry in the tree", " G,End Jump to last entry in the tree", " l,Enter,<Right> Move into the selected directory", " h,<BS>,<Left> Return to the parent directory", " c Toggle coloured tree output", " i Toggle display of file artifact SHA hashes", " t Display timeline of all commits modifying the " "selected entry", " / Open prompt to enter tree search", " n Find next tree entry matching the current " "search term", " N Find previous tree entry matching the current " "search term", "", " See fnc(1) for complete list of options and key bindings.", 0}; static const char *help0[] = { "", "Global", " ❬H❭❬?❭❬F1❭ Open in-app help", " ❬f❭ Toggle fullscreen", " ❬TAB❭ Switch focus between open views", " ❬Q❭ Quit the program", " ❬q❭ Quit the active view", "", "Timeline", " ❬↑❭❬k❭❬<❭❬,❭ Move selection cursor up one commit", " ❬↓❭❬j❭❬>❭❬.❭ Move selection cursor down one commit", " ❬C-b❭❬PgUp❭ Scroll up one page", " ❬C-f❭❬PgDn❭ Scroll down one page", " ❬gg❭❬Home❭ Jump to first line in the current view", " ❬G❭❬End❭ Jump to last line in the current view", " ❬Enter❭❬Space❭ Open diff view of the selected commit", " ❬/❭ Open prompt to enter timeline search", " ❬n❭ Find next commit matching the current search " "term", " ❬N❭ Find previous commit matching the current search" " term", "", "Diff", " ❬↑❭❬k❭ Scroll up one line of diff output", " ❬↓❭❬j❭ Scroll down one line of diff output", " ❬C-b❭❬PgUp❭ Scroll up one page of diff output", " ❬C-f❭❬PgDn❭ Scroll down one page of diff output", " ❬gg❭❬Home❭ Scroll to the top of the diff view", " ❬G❭❬End❭ Scroll to the end of the diff view", " ❬c❭ Toggle coloured diff output", " ❬i❭ Toggle inversion of diff output", " ❬v❭ Toggle verbosity of diff output", " ❬w❭ Toggle ignore whitespace-only changes in diff", " ❬-❭❬_❭ Decrease the number of context lines", " ❬+❭❬=❭ Increase the number of context lines", " ❬C-k❭❬K❭❬<❭❬,❭ Display diff of next commit in the timeline", " ❬C-j❭❬J❭❬>❭❬.❭ Display diff of previous commit in the " "timeline", " ❬/❭ Open prompt to enter diff search", " ❬n❭ Find next line matching the current search term", " ❬N❭ Find previous line matching the current search " "term", "", "Tree", " ❬↑❭❬k❭ Move selection cursor up one entry", " ❬↓❭❬j❭ Move selection cursor down one entry", " ❬C-b❭❬PgUp❭ Scroll up one page", " ❬C-f❭❬PgDn❭ Scroll down one page", " ❬gg❭❬Home❭ Jump to first entry in the tree", " ❬G❭❬End❭ Jump to last entry in the tree", " ❬→❭❬l❭❬Enter❭ Move into the selected directory", " ❬←❭❬h❭❬⌫❭ Return to the parent directory", " ❬c❭ Toggle coloured tree output", " ❬i❭ Toggle display of file artifact SHA hashes", " ❬t❭ Display timeline of all commits modifying the " "selected entry", " ❬/❭ Open prompt to enter tree search", " ❬n❭ Find next tree entry matching the current search" " term", " ❬N❭ Find previous tree entry matching the current " "search term", "", " See fnc(1) for complete list of options and key bindings.", 0 }; const char *codeset = nl_langinfo(CODESET); progname = fsl_mprintf("%s %s Help\n", fcli_progname(), PRINT_VERSION); padpopup(view, !strcmp(codeset, "UTF-8") ? help0 : help, progname); } /* * 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 void padpopup(struct fnc_view *view, const char **txt, const char *title) { WINDOW *help, *content; int ch, cury, end, idx, len, py, px, wy, wx, x0, y0; x0 = 4; /* Number of columns to border help window. */ y0 = 2; /* Number of lines to border help window. */ cury = 0; wx = getmaxx(view->window) - ((x0 + 1) * 2); /* Width of help window. */ wy = getmaxy(view->window) - ((y0 + 1) * 2); /* Height of help window */ ch = ERR; /* * Compute longest line and total number of lines in text to be * displayed to determine pad dimensions. */ px = 0; /* Width of help pad (i.e., longest line in txt). */ for (idx = 0; txt[idx] != 0; ++idx) { len = fsl_strlen(txt[idx]); if (px < len) px = len; } py = idx; /* Height of help pad (i.e., number of lines in txt). */ if (title) px = MAX(fsl_strlen(title), (fsl_size_t)px); if ((help = newwin(wy, wx, y0, x0)) == 0) return; if ((content = newpad(py + 1, px + 1)) == 0) { delwin(help); return; } doupdate(); keypad(content, TRUE); /* Write text content to pad. */ if (title) centerprint(content, 0, 0, px, title, 0); for (idx = 0; idx < py; ++idx) { waddstr(content, txt[idx]); if ((idx + 1) < py) waddch(content, '\n'); } end = (getcury(content) - (wy - 3)); /* No. lines past end of pad. */ do { switch (ch) { case KEY_UP: case 'k': if (cury > 0) |
︙ | ︙ | |||
2948 2949 2950 2951 2952 2953 2954 | case 'G': cury = end; break; case ERR: default: break; } | | | | | | | | | < | < | | 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 | case 'G': cury = end; break; case ERR: default: break; } werase(help); box(help, 0, 0); wnoutrefresh(help); pnoutrefresh(content, cury, 0, y0 + 1, x0 + 1, wy, wx); doupdate(); } while ((ch = wgetch(content)) != 'q' && ch != KEY_ESCAPE && ch != ERR); /* Destroy help window. */ werase(help); wrefresh(help); delwin(help); delwin(content); /* Restore fnc window content. */ touchwin(view->window); wnoutrefresh(view->window); doupdate(); } void centerprint(WINDOW *win, int starty, int startx, int cols, const char *str, chtype colour) { int x, y; if (win == NULL) win = stdscr; |
︙ | ︙ | |||
3104 3105 3106 3107 3108 3109 3110 | return rc; view->child = diff_view; diff_view->parent = view; view->focus_child = true; } else *new_view = diff_view; break; | < < < < < < < < < < < < < < < < < < < | 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); |
︙ | ︙ | |||
3487 3488 3489 3490 3491 3492 3493 | return rc; } static int close_timeline_view(struct fnc_view *view) { struct fnc_tl_view_state *s = &view->state.timeline; | < < < | 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; } |
︙ | ︙ | |||
3629 3630 3631 3632 3633 3634 3635 | 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, | | | < < | 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 | 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); 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_diff_view_state *s = &view->state.diff; int rc = 0; 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; |
︙ | ︙ | |||
3729 3730 3731 3732 3733 3734 3735 | } 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.). */ | | | | | < < > | | | < < | 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 | } 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 (!fsl_strcmp(s->selected_commit->type, "checkin")) { rc = create_changeset(s->selected_commit); if (rc) { rc = RC(FSL_RC_DB, "%s", "create_changeset"); goto end; } } else diff_non_checkin(&s->buf, s->selected_commit, s->diff_flags, s->context, s->sbs); /* * 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); |
︙ | ︙ | |||
3775 3776 3777 3778 3779 3780 3781 | 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. */ | | | | > | | | | 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 | 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->rid == 0) diff_checkout(&s->buf, s->selected_commit->prid, s->diff_flags, s->context, s->sbs); else if (!fsl_strcmp(s->selected_commit->type, "checkin") && s->selected_commit->puuid != NULL) diff_commit(&s->buf, s->selected_commit, s->diff_flags, s->context, s->sbs); /* * 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; |
︙ | ︙ | |||
3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 | } 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; | > < | | | 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 | } static int create_changeset(struct fnc_commit_artifact *commit) { fsl_cx *f = fcli_cx(); fsl_stmt *st = NULL; fsl_db *db = fsl_cx_db_repo(f); fsl_list changeset = fsl_list_empty; int rc = 0; rc = fsl_db_prepare_cached(db, &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_db_prepare_cached"); 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; */ |
︙ | ︙ | |||
3867 3868 3869 3870 3871 3872 3873 | fdiff->fc->uuid = fsl_strdup(uuid); fdiff->change = FSL_CKOUT_CHANGE_MOD; } fsl_list_append(&changeset, fdiff); } commit->changeset = changeset; | | | 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 | fdiff->fc->uuid = fsl_strdup(uuid); fdiff->change = FSL_CKOUT_CHANGE_MOD; } fsl_list_append(&changeset, fdiff); } commit->changeset = changeset; fsl_stmt_cached_yield(st); if (rc == FSL_RC_STEP_DONE) rc = 0; return rc; } |
︙ | ︙ | |||
4057 4058 4059 4060 4061 4062 4063 | * 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, | | < | | > > > | | | | | | | | | | < | | < < < < < < < < < < < < < < < < | | < < | | < | 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 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 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 | * 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) { 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; /* * If this commit has no parent (initial empty checkin), there's * nothing to diff against. TODO: May be redundant with the * this->puuid != NULL check in the caller. */ if (d2.P.used == 0) goto end; 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; 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); } rc = diff_file_artifact(buf, id1, a, commit->rid, b, change, diff_flags, context, sbs); } 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; rc = diff_file_artifact(buf, id1, fc1, commit->rid, fc2, change, diff_flags, context, sbs); 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; |
︙ | ︙ | |||
4175 4176 4177 4178 4179 4180 4181 | * 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, | | > < | 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 | * 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) { fsl_cx *f = fcli_cx(); fsl_db *db = fsl_cx_db_ckout(f); fsl_stmt *st = NULL; fsl_buffer sql, abspath, bminus; fsl_uuid_str xminus = NULL; fsl_id_t cid; int rc = 0; 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); */ |
︙ | ︙ | |||
4229 4230 4231 4232 4233 4234 4235 | 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); } | < | < < < < | 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 | 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); } if ((rc = fsl_db_prepare_cached(db, &st, "%b", &sql))) 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; 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); |
︙ | ︙ | |||
4306 4307 4308 4309 4310 4311 4312 | break; } } while (cf); fsl_deck_finalize(&d); } if (!xminus) xminus = fsl_strdup(NULL_DEVICE); | | < | < | < < | < < < < < < < < < < | | 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 | break; } } while (cf); fsl_deck_finalize(&d); } if (!xminus) xminus = fsl_strdup(NULL_DEVICE); if (!symlink != !fsl_is_symlink(fsl_buffer_cstr(&abspath))) { 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); else fsl_buffer_clear(&bminus); if (!rc) 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; |
︙ | ︙ | |||
4357 4358 4359 4360 4361 4362 4363 | rc = 0; fsl_cx_err_reset(f); } else if (rc) goto yield; } yield: | | | 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 | rc = 0; fsl_cx_err_reset(f); } else if (rc) goto yield; } yield: fsl_stmt_cached_yield(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; |
︙ | ︙ | |||
4415 4416 4417 4418 4419 4420 4421 | /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } | < < < < < < < < < | 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 | /* 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); } if (!rc && (diff_flags & FSL_DIFF_BRIEF) == 0) |
︙ | ︙ | |||
4455 4456 4457 4458 4459 4460 4461 | 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; | | > > | | 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 | 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; 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 (fsl_file_size(abspath) < 0) zplus = NULL_DEVICE; else 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 |
︙ | ︙ | |||
4660 4661 4662 4663 4664 4665 4666 | * 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, | | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 | * 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) { fsl_cx *f = fcli_cx(); fsl_buffer fbuf1 = fsl_buffer_empty; fsl_buffer fbuf2 = fsl_buffer_empty; const char *zplus = NULL, *zminus = NULL; fsl_uuid_str xplus = NULL, xminus = NULL; int rc = 0; bool verbose; assert(vid1 != vid2); assert(vid1 > 0 && 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; } if (b) { rc = fsl_card_F_content(f, b, &fbuf2); if (rc) goto end; zplus = b->name; xplus = b->uuid; } 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_buffer_clear(&fbuf1); fsl_buffer_clear(&fbuf2); return rc; } static int show_diff(struct fnc_view *view) |
︙ | ︙ | |||
5416 5417 5418 5419 5420 5421 5422 | 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) { | < | | | > > > | > > > > > | | < | < | | | < < < < < < < < < < < < < < < < | < < < < < < < | < | | < < < < < < > < < < < < < < < < < < < | < < | < < < | < < < < < < < < | < | < < | < < < < < < < < < > > > | < < > | < < < | < < < < < < < | < | < < < < < < < < < < < | | < < < < < | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < | | < | < < | | < > > > | | > > > > > > > > > > > > > | > | < < < < | < < < | 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 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 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 | 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) { if (!fsl_strcmp(fnc_init.cmdarg, fnc_init.cmd_args[idx].name)) { fsl_fprintf(f, "[%s] command:\n\n usage:", fnc_init.cmd_args[idx].name); fnc_init.fnc_usage_cb[idx](); fcli_cliflag_help(fnc_init.cmd_args[idx].flags); exit(fcli_end_of_main(fnc_init.err)); } } /* Otherwise, output help/usage for all commands. */ fcli_command_help(fnc_init.cmd_args, false); fsl_fprintf(f, "[usage]\n\n"); usage_timeline(); usage_diff(); usage_tree(); usage_blame(); 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, " %s timeline [-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, " %s diff [-C|--no-colour] [-h|--help] [-i|--invert]" " [-q|--quiet] [-w|--whitespace] [-x|--context n] " "[commit ...]\n e.g.: %s diff --context 3 d34db33f c0ff33\n\n", fcli_progname(), fcli_progname()); } static void usage_tree(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " %s tree [-C|--no-colour] [-h|--help] [-c commit] [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, " %s blame [-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 int cmd_diff(fcli_command const *argv) { fsl_cx *f = fcli_cx(); struct fnc_view *view; struct fnc_commit_artifact *commit = NULL; const char *artifact1, *artifact2; fsl_id_t prid = -1, rid = -1; int context = DIFF_DEF_CTXT, rc = 0; rc = fcli_process_flags(argv->flags); if (rc || (rc = fcli_has_unused_flags(false))) return rc; /* * If only one artifact is supplied, use the local changes in the * current checkout to diff against it. If no artifacts are supplied, * diff any local changes on disk against the current checkout. */ if (fcli.argc == 2) { artifact1 = fcli_next_arg(true); artifact2 = fcli_next_arg(true); } else if (fcli.argc < 2) { artifact1 = "current"; rid = 0; if (fcli_next_arg(false)) artifact1 = fcli_next_arg(true); 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; } } else { /* fcli_* APIs should prevent getting here but just in case. */ usage_diff(); return RC(FSL_RC_MISUSE, "invalid args: %s", fcli.argv); } /* Find the corresponding rids for the versions we have; checkout = 0 */ rc = fsl_sym_to_rid(f, artifact1, FSL_SATYPE_ANY, &prid); if (rc || prid < 0) return RC(rc, "invalid artifact [%s]", artifact1); if (rid != 0) rc = fsl_sym_to_rid(f, artifact2, FSL_SATYPE_ANY, &rid); if (rc || rid < 0) return RC(rc, "invalid artifact [%s]", artifact2); commit = calloc(1, sizeof(*commit)); if (commit == NULL) return RC(FSL_RC_ERROR, "%s", "calloc fail"); if (rid == 0) fsl_ckout_version_info(f, NULL, (fsl_uuid_cstr *)commit->uuid); else commit->uuid = fsl_rid_to_uuid(f, rid); commit->puuid = fsl_rid_to_uuid(f, prid); commit->prid = prid; commit->rid = rid; /* * If either of the supplied versions are not checkin artifacts, * let the user know which operands aren't valid. */ if ((!fsl_rid_is_a_checkin(f, rid) && rid != 0) || !fsl_rid_is_a_checkin(f, prid)) return RC(FSL_RC_TYPE, "artifact(s) [%s] not resolvable to checkin(s)", (!fsl_rid_is_a_checkin(f, rid) && rid != 0) && !fsl_rid_is_a_checkin(f, prid) ? fsl_mprintf("%s / %s", artifact1, artifact2) : (!fsl_rid_is_a_checkin(f, rid) && rid != 0) ? artifact2 : artifact1); commit->type = fsl_strdup("checkin"); 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, false); if (!rc) rc = view_loop(view); end: fnc_commit_artifact_close(commit); return rc; } static int browse_commit_tree(struct fnc_view **new_view, int start_col, struct commit_entry *entry, const char *path) { |
︙ | ︙ | |||
5724 5725 5726 5727 5728 5729 5730 | if (rc || (rc = fcli_has_unused_flags(false))) goto end; rc = map_repo_path(&path); if (rc) goto end; if (fnc_init.sym) | | | 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 | 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_CHECKIN, &rid); else fsl_ckout_version_info(f, &rid, NULL); if (rc) { switch (rc) { case FSL_RC_AMBIGUOUS: RC(rc, "prefix too ambiguous [%s]", |
︙ | ︙ | |||
5749 5750 5751 5752 5753 5754 5755 | case FSL_RC_MISUSE: /* FALL THROUGH */ default: goto end; } } | < < < < < | | | 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 | case FSL_RC_MISUSE: /* FALL THROUGH */ default: goto end; } } if (!fsl_rid_is_a_checkin(f, rid)) { RC(FSL_RC_TYPE, "%s tree can only open a check-in", fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; |
︙ | ︙ | |||
5942 5943 5944 5945 5946 5947 5948 | 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); | | < | < | | 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 | 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) 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); 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; } |
︙ | ︙ | |||
6157 6158 6159 6160 6161 6162 6163 | /* 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) { | < < < < | | | | < | | 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 | /* 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) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "lstat(%s)", fsl_buffer_cstr(&buf)); goto end; } 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; |
︙ | ︙ | |||
6293 6294 6295 6296 6297 6298 6299 | /* 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) | | | 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 | /* 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); 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); |
︙ | ︙ | |||
7000 7001 7002 7003 7004 7005 7006 | static int set_colours(fsl_list *s, enum fnc_view_id vid) { struct fnc_colour *colour; const char **regexp = NULL; const char *regexp_blame[] = {"^"}; | < | < < < | < < < < < | < < < < < < < | 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 | 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 [+-] |\\[[+~>-]] |" "[+-]{3} )", "^-", "^\\+", "^@@" }; int pairs_diff[][2] = { {FNC_DIFF_META, COLOR_GREEN}, {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, COLOR_MAGENTA} }; int pairs_blame[][2] = {{FNC_COMMIT, COLOR_CYAN}}; 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_BLAME: n = nitems(regexp_blame); regexp = regexp_blame; pairs = pairs_blame; break; default: return RC(FSL_RC_TYPE, "%s", "invalid fnc_view_id"); |
︙ | ︙ | |||
7131 7132 7133 7134 7135 7136 7137 | 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; | | | | < < < < < | < < < | 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 | 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 ndepth = 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) if ((rc = strtonumcheck(&ndepth, fnc_init.nrecords.zlimit, INT_MIN, INT_MAX))) goto end; 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()); |
︙ | ︙ | |||
7197 7198 7199 7200 7201 7202 7203 | view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", view_open); goto end; } | | | | 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 | 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, ndepth); 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 ndepth) { struct fnc_blame_view_state *s = &view->state.blame; int rc = 0; CONCAT(STAILQ, _INIT)(&s->blamed_commits); s->path = fsl_strdup(path); |
︙ | ︙ | |||
7235 7236 7237 7238 7239 7240 7241 | 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; | | | 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 | 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.ndepth = ndepth; 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; |
︙ | ︙ | |||
7306 7307 7308 7309 7310 7311 7312 | } 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); | < < < < < | | 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 | } 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 */ opt->limit = blame->ndepth; 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; |
︙ | ︙ | |||
7625 7626 7627 7628 7629 7630 7631 | fsl_free(line); line = NULL; if (rc) return rc; if (screen_is_shared(view)) wstandout(view->window); if (s->colour) | | | 7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128 7129 | 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); 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); |
︙ | ︙ | |||
7696 7697 7698 7699 7700 7701 7702 | idfield - 1); if (id_str == NULL) { fsl_free(line); return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); } if (s->colour) | | < | 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 | 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); 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, |
︙ | ︙ | |||
7844 7845 7846 7847 7848 7849 7850 | "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) { | < < | > < < | > | < > | < < < < < < < < < < | | | < < < < | 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 | "SELECT uuid FROM plink, blob WHERE plink.cid=%d " "AND blob.rid=plink.pid AND plink.isprim", rid); if (pid == NULL) break; /* Check file exists in parent check-in. */ rc = fsl_deck_load_sym(f, &d, pid, FSL_SATYPE_CHECKIN); if (rc) { rc = RC(rc, "%s", "fsl_deck_load_sym"); goto cleanup; } rc = fsl_deck_F_rewind(&d); if (rc) { rc = RC(rc, "%s", "fsl_deck_F_rewind"); goto cleanup; } if (fsl_deck_F_search(&d, s->path + (fnc_init.sym ? 0 : 1)) == NULL) goto cleanup; /* File not in selected version */ rc = fnc_commit_qid_alloc(&s->blamed_commit, pid); cleanup: fsl_deck_finalize(&d); fsl_free(pid); if (rc) return rc; } else { if (!fsl_uuidcmp(id, s->blamed_commit->id)) break; rc = fnc_commit_qid_alloc(&s->blamed_commit, id); } |
︙ | ︙ | |||
7919 7920 7921 7922 7923 7924 7925 | break; break; } case KEY_ENTER: case '\r': { fsl_cx *f = fcli_cx(); struct fnc_commit_artifact *commit = NULL; | < > > > > > < | < | < | 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 | break; break; } case KEY_ENTER: case '\r': { fsl_cx *f = fcli_cx(); struct fnc_commit_artifact *commit = NULL; fsl_uuid_cstr id = NULL; char sym[FSL_UUID_STRLEN_MIN]; fsl_id_t rid; 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); memcpy(sym, id, FSL_UUID_STRLEN_MIN); sym[FSL_UUID_STRLEN_MIN - 1] = '\0'; rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_CHECKIN, &rid); if (rc) break; rc = commit_builder(&commit, rid, NULL); 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); s->selected_commit = commit; if (rc) { fnc_commit_artifact_close(commit); view_close(diff_view); break; } view->active = false; |
︙ | ︙ | |||
8207 8208 8209 8210 8211 8212 8213 | static void fnc_commit_qid_free(struct fnc_commit_qid *qid) { fsl_free(qid->id); fsl_free(qid); } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 | 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); } static int |
︙ | ︙ | |||
9081 9082 9083 9084 9085 9086 9087 | 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; | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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; |
︙ | ︙ |
Added signify/fnc-02-release.pub.
> > | 1 2 | untrusted comment: fnc 0.2 public key RWQbpVGH9jal6DtP0CjKTyQETetZS7nMoD76X9NVVji8MZ15XHX0IAHQ |
Deleted signify/fnc-04-release.pub.
|
| < < |
Changes to src/libfossil.c.
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 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | #include "libfossil.h" /* start of file fossil-ext_regexp.h */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** 2012-11-13 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ****************************************************************************** ** ** The code in this file implements a compact but reasonably ** efficient regular-expression matcher for posix extended regular ** expressions against UTF8 text. ** ** This file is an SQLite extension. It registers a single function ** named "regexp(A,B)" where A is the regular expression and B is the ** string to be matched. By registering this function, SQLite will also ** then implement the "B regexp A" operator. Note that with the function ** the regular expression comes first, but with the operator it comes ** second. ** ** The following regular expression syntax is supported: ** ** X* zero or more occurrences of X ** X+ one or more occurrences of X ** X? zero or one occurrences of X ** X{p,q} between p and q occurrences of X ** (X) match X ** X|Y X or Y ** ^X X occurring at the beginning of the string ** X$ X occurring at the end of the string ** . Match any single character ** \c Character c where c is one of \{}()[]|*+?. ** \c C-language escapes for c in afnrtv. ex: \t or \n ** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX ** \xXX Where XX is exactly 2 hex digits, unicode value XX ** [abc] Any single character from the set abc ** [^abc] Any single character not in the set abc ** [a-z] Any single character in the range a-z ** [^a-z] Any single character not in the range a-z ** \b Word boundary ** \w Word character. [A-Za-z0-9_] ** \W Non-word character ** \d Digit ** \D Non-digit ** \s Whitespace character ** \S Non-whitespace character ** ** A nondeterministic finite automaton (NFA) is used for matching, so the ** performance is bounded by O(N*M) where N is the size of the regular ** expression and M is the size of the input string. The matcher never ** exhibits exponential behavior. Note that the X{p,q} operator expands ** to p copies of X following by q-p copies of X? and that the size of the ** regular expression in the O(N*M) performance bound is computed after ** this expansion. */ #if !defined NET_FOSSIL_SCM_REGEXP_H_INCLUDED #define NET_FOSSIL_SCM_REGEXP_H_INCLUDED #ifdef __cplusplus extern "C" { #endif struct sqlite3_api_routines; /*(this file is derived from sqlite3 regexp.c; I wanted to expose the engine to also be used directly*/ typedef struct sqlite3_ReCompiled sqlite3_ReCompiled; /* ** Compile a textual regular expression in zIn[] into a compiled regular ** expression suitable for us by sqlite3re_match() and return a pointer to the ** compiled regular expression in *ppRe. Return NULL on success or an ** error message if something goes wrong. */ const char* sqlite3re_compile ( sqlite3_ReCompiled** ppRe, const char* zIn, int noCase ); /* Free and reclaim all the memory used by a previously compiled ** regular expression. Applications should invoke this routine once ** for every call to re_compile() to avoid memory leaks. */ void sqlite3re_free ( sqlite3_ReCompiled* pRe ); /* Run a compiled regular expression on the zero-terminated input ** string zIn[]. Return true on a match and false if there is no match. */ int sqlite3re_match ( sqlite3_ReCompiled* pRe, const unsigned char* zIn, int nIn ); /*registers the REGEXP extension function into a sqlite3 database instance */ int sqlite3_regexp_init ( sqlite3* db, char** pzErrMsg, const struct sqlite3_api_routines* pApi ); #ifdef __cplusplus } #endif #else #endif /*#ifndef NET_FOSSIL_SCM_REGEXP_H_INCLUDED*/ /* end of file fossil-ext_regexp.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 |
︙ | ︙ | |||
66 67 68 69 70 71 72 | 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; | < | 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 = |
︙ | ︙ | |||
156 157 158 159 160 161 162 | case FSL_STRLEN_SHA1: case FSL_STRLEN_K256: return x; default: return 0; } } | | | | | | | 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 | case FSL_STRLEN_SHA1: case FSL_STRLEN_K256: return x; default: return 0; } } void fsl_error_clear( fsl_error * err ){ if(err){ fsl_buffer_clear(&err->msg); *err = fsl_error_empty; } } void fsl_error_reset( fsl_error * 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 * src, fsl_error * 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 * lower, fsl_error * 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 * 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; |
︙ | ︙ | |||
217 218 219 220 221 222 223 | : fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1); if(rc) err->code = rc; } return rc ? rc : code; } } | | | < | 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 | : 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 * 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 * err, char const ** str, fsl_size_t * 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; |
︙ | ︙ | |||
776 777 778 779 780 781 782 | } #endif #endif /* FSL_CONFIG_ENABLE_TIMER */ } | | | | | | 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 | } #endif #endif /* FSL_CONFIG_ENABLE_TIMER */ } void fsl_timer_start(fsl_timer_state * ft){ fsl_cpu_times( &ft->user, &ft->system ); } uint64_t fsl_timer_fetch(fsl_timer_state 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 * 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 *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)); |
︙ | ︙ | |||
1049 1050 1051 1052 1053 1054 1055 | 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 */ | < | < | 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 | 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*/, 0U/*naVerse*/ }; 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); |
︙ | ︙ | |||
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", | > | 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 | 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", |
︙ | ︙ | |||
1255 1256 1257 1258 1259 1260 1261 | " ORDER BY ancestor.generation;", fnid ); if(rc) goto dberr; while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ if(a->nVers>=3){ | | > | | < < < < | 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 | " 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. Note that we do not impose a time-based limit here like fossil does, but may want to add that at some point.*/ if(opt->limit>0 && a->nVers>=opt->limit){ a->bMoreToDo = true; break; } } char * zTmp = 0; char const * zCol = 0; fsl_size_t nCol = 0; |
︙ | ︙ | |||
1401 1402 1403 1404 1405 1406 1407 | int rc; Annotator ann = Annotator_empty; unsigned int i; fsl_buffer * const scratch = fsl_cx_scratchpad(f); fsl_annotate_step aStep; assert(opt->out); | < | 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; |
︙ | ︙ | |||
1582 1583 1584 1585 1586 1587 1588 | 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 */ | < | | 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 | 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 = 16, /* %w -> Strings with '\"' 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. */ |
︙ | ︙ | |||
1751 1752 1753 1754 1755 1756 1757 | {'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 }, | | < | 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 | {'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 FSLPRINTF_OMIT_SQL {'w'/*119*/, 0, 0, 0, 0, 0 }, #else {'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 }, |
︙ | ︙ | |||
2057 2058 2059 2060 2061 2062 2063 | } #endif /* !FSLPRINTF_OMIT_HTML */ #if !FSLPRINTF_OMIT_SQL /** | | > | | | | | | | < < | > | | > > | < < | | | > > > > > > | | | | > > | | > | > | > > > | | > > > > | < > > | | 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 | } #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, or etSQLESCAPE3. Search this file for those constants to find the associated documentation. */ static int spech_sqlstring_main( int xtype, fsl_output_f pf, void * pfArg, unsigned int pfLen, void * varg ){ unsigned int i, n; int j, ch, isnull; int needQuote; char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ char const * escarg = (char const *) varg; char * bufpt = NULL; int rc; isnull = escarg==0; if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); for(i=n=0; (i<pfLen) && ((ch=escarg[i])!=0); ++i){ if( ch==q ) ++n; } needQuote = !isnull && xtype==etSQLESCAPE2; n += i + 1 + needQuote*2; /* FIXME: use a static buffer here instead of malloc()! Shame on you! */ bufpt = (char *)fsl_malloc( n ); if( ! bufpt ) return FSL_RC_OOM; j = 0; if( needQuote ) bufpt[j++] = q; for(i=0; (ch=escarg[i])!=0; i++){ bufpt[j++] = ch; if( ch==q ) bufpt[j++] = ch; } if( needQuote ) bufpt[j++] = q; bufpt[j] = 0; rc = pf( pfArg, bufpt, j ); fsl_free( bufpt ); return rc; } static int spech_sqlstring1( fsl_output_f pf, void * pfArg, unsigned int pfLen, void * varg ){ return spech_sqlstring_main( etSQLESCAPE, pf, pfArg, pfLen, varg ); } static int spech_sqlstring2( fsl_output_f pf, void * pfArg, unsigned int pfLen, void * varg ){ return spech_sqlstring_main( etSQLESCAPE2, pf, pfArg, pfLen, varg ); } static int spech_sqlstring3( fsl_output_f pf, void * pfArg, unsigned int pfLen, void * varg ){ return spech_sqlstring_main( etSQLESCAPE3, pf, pfArg, pfLen, varg ); } #endif /* !FSLPRINTF_OMIT_SQL */ #if FSLPRINTF_ENABLE_JSON /* TODO? Move these UTF8 bits into the public API? */ /* |
︙ | ︙ | |||
3006 3007 3008 3009 3010 3011 3012 | } #endif /* FSLPRINTF_OMIT_HTML */ #if ! FSLPRINTF_OMIT_SQL case etBLOBSQL: case etSQLESCAPE: case etSQLESCAPE2: case etSQLESCAPE3: { | > | > | < > > > | | | < | | | | | 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 | } #endif /* FSLPRINTF_OMIT_HTML */ #if ! FSLPRINTF_OMIT_SQL case etBLOBSQL: case etSQLESCAPE: case etSQLESCAPE2: case etSQLESCAPE3: { fsl_appendf_spec_handler spf = (xtype==etSQLESCAPE) ? spech_sqlstring1 : (((xtype==etSQLESCAPE2)||(etBLOBSQL==xtype)) ? spech_sqlstring2 : spech_sqlstring3 ); if(etBLOBSQL==xtype){ fsl_buffer * b = va_arg(ap,fsl_buffer*); bufpt = fsl_buffer_str(b); length = (int)fsl_buffer_size(b); }else{ bufpt = va_arg(ap,char*); length = bufpt ? strlen(bufpt) : 0; } pfrc = spf( 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 |
︙ | ︙ | |||
3318 3319 3320 3321 3322 3323 3324 | 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; | > > > | | | | | < < < | | | | | 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 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 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 | 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; /* Only for debugging */ #include <stdio.h> void fsl_id_bag_clear(fsl_id_bag *p){ if(p->list) 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 *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 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 *p){ p->entryCount = p->used = 0; } int fsl_id_bag_insert(fsl_id_bag *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 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 *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 *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 ){ |
︙ | ︙ | |||
3453 3454 3455 3456 3457 3458 3459 | fsl_id_bag_resize(p, p->capacity/2) /* ignore realloc error and keep the old size. */; } } return rv; } | | | | | | 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 | 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 *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 *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 *p){ return p->entryCount; } void fsl_id_bag_swap(fsl_id_bag *lhs, fsl_id_bag *rhs){ fsl_id_bag x = *lhs; *lhs = *rhs; *rhs = x; } #undef fsl_id_bag_hash /* end of file bag.c */ |
︙ | ︙ | |||
3631 3632 3633 3634 3635 3636 3637 | 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*/; | | | 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 | 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 = fsl_buffer_reserve( b, sz ); 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; |
︙ | ︙ | |||
3901 3902 3903 3904 3905 3906 3907 | }else{ fsl_buffer_reserve(&temp, 0); } return rc; } | | < | 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 | }else{ fsl_buffer_reserve(&temp, 0); } return rc; } int fsl_buffer_fill_from( fsl_buffer * dest, fsl_input_f src, void * 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; |
︙ | ︙ | |||
3938 3939 3940 3941 3942 3943 3944 | if( !rc && dest->used ){ assert( dest->used < dest->capacity ); dest->mem[dest->used] = 0; } return rc; } | | | > | | | > | | | | | | | | | | | | | | | | | | > | 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 | if( !rc && dest->used ){ assert( dest->used < dest->capacity ); dest->mem[dest->used] = 0; } return rc; } int fsl_buffer_fill_from_FILE( fsl_buffer * dest, FILE * src ){ return (!dest || !src) ? FSL_RC_MISUSE : fsl_buffer_fill_from( dest, fsl_input_f_FILE, src ); } int fsl_buffer_fill_from_filename( fsl_buffer * dest, char const * filename ){ if(!dest || !filename || !*filename) return FSL_RC_MISUSE; else{ 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; } |
︙ | ︙ | |||
4077 4078 4079 4080 4081 4082 4083 | 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; } | | < | | | | 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 | 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 * 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, len); } } int fsl_buffer_stream_lines(fsl_output_f fTo, void * toState, fsl_buffer *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 ){ |
︙ | ︙ | |||
4117 4118 4119 4120 4121 4122 4123 | if(!rc){ pFrom->cursor = i; } return rc; } | | < < | 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 | if(!rc){ pFrom->cursor = i; } return rc; } int fsl_buffer_copy_lines(fsl_buffer *pTo, fsl_buffer *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; |
︙ | ︙ | |||
4205 4206 4207 4208 4209 4210 4211 | } fsl_buffer_clear(&fc); #endif return rc; } } | | | < | 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 | } fsl_buffer_clear(&fc); #endif return rc; } } char * fsl_buffer_take(fsl_buffer *b){ char * z = (char *)b->mem; *b = fsl_buffer_empty; return z; } fsl_size_t fsl_buffer_seek(fsl_buffer * 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 |
︙ | ︙ | |||
4240 4241 4242 4243 4244 4245 4246 | } 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; } | | | | 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 | } 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 *b){ return b->cursor; } void fsl_buffer_rewind(fsl_buffer *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); |
︙ | ︙ | |||
5234 5235 5236 5237 5238 5239 5240 | 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... */ | < | > | | | < < < < < < < < < | < > < | < < | 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 | 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 generation. */ fsl_id_t const baseRid = fsl_uuid_to_rid(f, dBase.B.uuid); fsl_deck_finalize(&dBase); assert(baseRid>0); rc = fsl_deck_load_rid(f, &dBase, baseRid, FSL_SATYPE_CHECKIN); RC; }else{ /* dBase version is a suitable baseline. */ } pBase = &dBase; /* MARKER(("Baseline = %d / %s\n", (int)pBase->rid, pBase->uuid)); */ rc = fsl_deck_B_set(d, pBase->uuid); RC; } 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); |
︙ | ︙ | |||
5481 5482 5483 5484 5485 5486 5487 | RC; } end: #undef RC fsl_stmt_finalize(&q); fsl_deck_finalize(&dBase); | < | 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; |
︙ | ︙ | |||
5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 | } 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); | > | | 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 | } 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); assert(d->uuid); /* 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, d->uuid); RC; assert(d->f == f); rc = fsl_checkin_add_unsent(f, d->rid); RC; rc = fsl_ckout_clear_merge_state(f); RC; /* |
︙ | ︙ | |||
5691 5692 5693 5694 5695 5696 5697 | 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){ | | | < > | < | 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 | 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){ *newUuid = d->uuid; d->uuid = NULL /* transfer ownership */; } } } } if(rc && !f->error.code){ if(dbR->error.code) fsl_cx_uplift_db_error(f, dbR); else if(f->dbMain->error.code) fsl_cx_uplift_db_error(f, f->dbMain); } fsl_checkin_discard(f); fsl_deck_finalize(d); return rc; } |
︙ | ︙ | |||
6482 6483 6484 6485 6486 6487 6488 | /** 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); | | | 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 | /** 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 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); |
︙ | ︙ | |||
6605 6606 6607 6608 6609 6610 6611 | if(!rc && isModified) *isModified = mod; fsl_cx_scratchpad_yield(f, fname); if(hash) fsl_cx_scratchpad_yield(f, hash); return rc; } /** | | | | 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 | 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() and fsl_ckout_update(). */ 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; |
︙ | ︙ | |||
7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 | /* 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); | > > > < < < < < | 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 | /* 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); } /** TODO: Implement db fingerprint. */ 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); } 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); |
︙ | ︙ | |||
7326 7327 7328 7329 7330 7331 7332 | /* 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; | | | | 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 | /* 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_cx_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){ |
︙ | ︙ | |||
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); | > | 7573 7574 7575 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 | /** 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) - vfile_to_disk() (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); |
︙ | ︙ | |||
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 ){ | > | 7662 7663 7664 7665 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 | 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); //MARKER(("Here's where we would vfile_to_disk() %s\n", 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 ){ |
︙ | ︙ | |||
8530 8531 8532 8533 8534 8535 8536 | SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ | < | > | 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 | 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*/, \ 0/*verbose*/, \ false/*dryRun*/ \ }, \ {/*transient*/ \ NULL/*repoDb*/, \ NULL/*userArg*/, \ 0/*helpRequested*/, \ false/*versionRequested*/\ }, \ |
︙ | ︙ | |||
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, | > > | 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 | 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_BOOL(NULL,"dry-run",&fcli.clientFlags.dryRun, "Enable dry-run mode (not supported by all apps)."), 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, |
︙ | ︙ | |||
9295 9296 9297 9298 9299 9300 9301 | if(!rc){ rc = fcli_setup_common2(); } } return rc; } | < < < < < < < < > | 9405 9406 9407 9408 9409 9410 9411 9412 9413 9414 9415 9416 9417 9418 9419 9420 9421 9422 9423 9424 9425 9426 9427 9428 9429 9430 9431 9432 9433 9434 9435 9436 9437 9438 | 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); fcli.clientFlags.dryRun = fcli_flag2(NULL,"dry-run", NULL); fcli_flag2("R", "repo", &fcli.transient.repoDbArg); rc = fcli_setup_common2(); } } return rc; } |
︙ | ︙ | |||
9439 9440 9441 9442 9443 9444 9445 | /* 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 | | | 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 | /* 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)){ 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){ |
︙ | ︙ | |||
9466 9467 9468 9469 9470 9471 9472 | } } break; } } if(helpState){ f_out("\n"); | | | 9569 9570 9571 9572 9573 9574 9575 9576 9577 9578 9579 9580 9581 9582 9583 | } } break; } } if(helpState){ f_out("\n"); fcli_command_help(helpPos, 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{ |
︙ | ︙ | |||
9493 9494 9495 9496 9497 9498 9499 | } if(rc && reportErrors){ fcli_err_report(0); } return rc; } | < < < < < < < < < < < | < < < < < < < < < < < < < < < < < | 9596 9597 9598 9599 9600 9601 9602 9603 9604 9605 9606 9607 9608 9609 9610 9611 9612 9613 9614 9615 9616 9617 9618 9619 9620 9621 9622 9623 9624 | } if(rc && reportErrors){ fcli_err_report(0); } return rc; } void fcli_command_help(fcli_command const * cmd, 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->flags){ f_out("\n"); fcli_cliflag_help(c->flags); } if(onlyOne) break; } } void fcli_fax(void * mem){ if(mem){ fsl_list_append( &FCliFree.list, mem ); } } int fcli_ckout_show_info(bool useUtc){ |
︙ | ︙ | |||
9779 9780 9781 9782 9783 9784 9785 | } tgt->ansiColor.insertion = zIns; tgt->ansiColor.edit = zEdit; tgt->ansiColor.deletion = zDel; tgt->ansiColor.reset = zReset; } | < < < < < < < < < < < < < < < | 9854 9855 9856 9857 9858 9859 9860 9861 9862 9863 9864 9865 9866 9867 | } 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 */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
︙ | ︙ | |||
9884 9885 9886 9887 9888 9889 9890 | } return false; } int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ){ | | > | | 9944 9945 9946 9947 9948 9949 9950 9951 9952 9953 9954 9955 9956 9957 9958 9959 9960 | } return false; } int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ){ fsl_db * dbR = f ? fsl_cx_db_repo(f) : NULL; if(!f || !tgt) return FSL_RC_MISUSE; else 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=?" |
︙ | ︙ | |||
9929 9930 9931 9932 9933 9934 9935 | fsl_cx_uplift_db_error(f, dbR); } return rc; } } | | | 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 | fsl_cx_uplift_db_error(f, dbR); } return rc; } } bool fsl_content_is_private(fsl_cx * 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__) |
︙ | ︙ | |||
10285 10286 10287 10288 10289 10290 10291 | } end: fsl_stmt_finalize(&q); fsl_buffer_clear(&bufChild); return rc; } | | < | 10346 10347 10348 10349 10350 10351 10352 10353 10354 10355 10356 10357 10358 10359 10360 | } end: fsl_stmt_finalize(&q); fsl_buffer_clear(&bufChild); return rc; } int fsl_content_put_ex( fsl_cx * 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; |
︙ | ︙ | |||
10579 10580 10581 10582 10583 10584 10585 | fsl_buffer_clear(&hash); if(!uncompSize){ fsl_buffer_clear(&cmpr); }/* else cmpr.mem (if any) belongs to pBlob */ return rc; } | | | | 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 | 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 * 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 * 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 */ |
︙ | ︙ | |||
10674 10675 10676 10677 10678 10679 10680 | else if(rc && !f->error.code){ fsl_cx_uplift_db_error(f, db); } } return rc; } | | | 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746 10747 10748 | else if(rc && !f->error.code){ fsl_cx_uplift_db_error(f, db); } } return rc; } int fsl_content_undeltify(fsl_cx * 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; |
︙ | ︙ | |||
10902 10903 10904 10905 10906 10907 10908 | else fsl_db_transaction_rollback(db); if(rc && db->error.code && !f->error.code){ rc = fsl_cx_uplift_db_error(f, db); } return rc; } | | | 10962 10963 10964 10965 10966 10967 10968 10969 10970 10971 10972 10973 10974 10975 10976 | 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 * 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; |
︙ | ︙ | |||
11218 11219 11220 11221 11222 11223 11224 | break; } default: break; } return id; } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 |
︙ | ︙ | |||
12269 12270 12271 12272 12273 12274 12275 | #if FSL_ENABLE_SQLITE_REGEXP /** Used for setup and teardown of sqlite3_auto_extension(). */ static volatile long sg_autoregctr = 0; #endif | < < < < < < < < < < < < < < < < < < < < < | < | < | 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 12330 12331 12332 12333 12334 12335 | #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; extern int fsl_cx_install_timeline_crosslinkers(fsl_cx *f); 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, 1) /* 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; } 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 |
︙ | ︙ | |||
12388 12389 12390 12391 12392 12393 12394 | /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; } | | | 12394 12395 12396 12397 12398 12399 12400 12401 12402 12403 12404 12405 12406 12407 12408 | /sizeof(fsl_mcache_empty.aAge[0])); for(unsigned i = 0; i < cacheLen; ++i){ fsl_deck_finalize(&f->cache.mcache.decks[i]); } f->cache.mcache = fsl_mcache_empty; } void fsl_cx_reset(fsl_cx * 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, |
︙ | ︙ | |||
12424 12425 12426 12427 12428 12429 12430 | 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); | < < < < > > > > > | | 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 12468 12469 12470 12471 12472 12473 12474 | 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; if(f->xlinkers.list){ fsl_free(f->xlinkers.list); f->xlinkers = fsl_xlinker_list_empty; } 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, 1); fsl_db_close(&f->dbMem); *f = fsl_cx_empty; if(&fsl_cx_empty == allocStamp){ fsl_free(f); }else{ f->allocStamp = allocStamp; } |
︙ | ︙ | |||
12525 12526 12527 12528 12529 12530 12531 | 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; } | | | 12532 12533 12534 12535 12536 12537 12538 12539 12540 12541 12542 12543 12544 12545 12546 | 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 * 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)); |
︙ | ︙ | |||
12680 12681 12682 12683 12684 12685 12686 | /** @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. */ | | < < | | 12687 12688 12689 12690 12691 12692 12693 12694 12695 12696 12697 12698 12699 12700 12701 12702 12703 12704 12705 12706 12707 | /** @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 * 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){ assert(!"Misuse: f->dbMain has not been set: 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); |
︙ | ︙ | |||
12748 12749 12750 12751 12752 12753 12754 | // (void*)db, label, db->filename)); f->dbMain->role |= r; } } return rc; } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > > > > > > > > | > | | > > > > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | | 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 12823 12824 12825 12826 12827 12828 12829 12830 12831 12832 12833 12834 12835 12836 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 12904 12905 12906 12907 12908 12909 12910 12911 12912 12913 12914 12915 12916 12917 12918 12919 12920 12921 12922 12923 12924 12925 12926 12927 12928 12929 12930 12931 12932 12933 12934 12935 12936 12937 12938 12939 12940 12941 12942 12943 12944 12945 12946 12947 12948 12949 12950 12951 12952 12953 12954 12955 12956 12957 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 12970 12971 12972 12973 12974 12975 12976 12977 12978 12979 12980 12981 12982 12983 12984 12985 12986 12987 12988 12989 12990 12991 12992 12993 12994 12995 12996 12997 12998 12999 13000 13001 13002 13003 13004 13005 13006 13007 13008 13009 13010 13011 13012 13013 13014 13015 13016 13017 13018 13019 13020 13021 13022 13023 13024 | // (void*)db, label, db->filename)); f->dbMain->role |= r; } } return rc; } /* 2020-03-04: this function has, since the switch to using ATTACH for all major dbs, largely been supplanted by fsl_cx_attach_role(), but should we ever return to a multi-db-handle world, this implementation (or something close to it) is what we'll want. Analog to fossil's db_open_or_attach(). This function is still very much up for reconsideration. i'm not terribly happy with the "db roles" here - i'd prefer to have them each in their own struct, but understand that the roles may play a part in query generation, so i haven't yet ruled them out. On 20140724 we got a fix which allows us to publish concrete names for all dbs, regardless of which one is the "main", so much of the reason for that concern has been alleviated. In late October, 2014, Dave figured out that multi-attach leads to locking problems, so the innards are being rewritten to use a :memory: db as the permanent "main", and attaching the others. Opens or attaches the given db file. zDbName is the file name of the DB. role is the role of that db in the framework (that determines its db name in SQL). The first time this is called, f->dbMain is set to point to fsl_cx_db_for_role(f, role). If f->dbMain is already set then the db is attached using a role-dependent name and role is added to f->dbMain->role's bitmask. If f->dbMain is not open then it is opened here and its role is set to the given role. It is _also_ ATTACHED using its role name. This allows us to publish the names of all Fossil-managed db handles regardless of which one is "main". e.g. if repo is opened first, it is addressable from SQL as both "main" or "repo". The given db zDbName is the name of a database file. If f->dbMain is not opened then that db is assigned to the given db file, otherwise attach attach zDbName using the name zLabel. If pWasAttached is not NULL then it is set to a true value if the db gets attached, else a false value. It is only modified on success. It is an error to open the same db role more than once, and trying to do so results in a FSL_RC_ACCESS error (f's error state is updated). */ static int fsl_cx_db_main_open_or_attach( fsl_cx * f, const char *zDbName, /* const char *zLabel, */ fsl_dbrole_e role, int *pWasAttached){ int rc; int wasAttached = 0; fsl_db * db; assert(f); assert(zDbName && *zDbName); assert((FSL_DBROLE_CONFIG==role) || (FSL_DBROLE_CKOUT==role) || (FSL_DBROLE_REPO==role)); if(!f || !zDbName) return FSL_RC_MISUSE; else if((FSL_DBROLE_NONE==role) || !*zDbName) return FSL_RC_RANGE; switch(role){ case FSL_DBROLE_REPO: case FSL_DBROLE_CKOUT: case FSL_DBROLE_CONFIG: db = fsl_cx_db_for_role(f, role); break; default: assert(!"not possible"); db = NULL; /* We'll fall through and segfault in a moment... */ } assert(db); try_again: if(!f->dbMain) { assert(!"Not possible since moving to a :memory: main db."); /* This is the first db. It is now our main db. */ assert( FSL_DBROLE_NONE==db->role ); assert( NULL==db->dbh ); assert( role == FSL_DBROLE_CONFIG || role == FSL_DBROLE_REPO || role == FSL_DBROLE_CKOUT ); f->dbMain = db; db->f = f; rc = fsl_db_open( db, zDbName, FSL_OPEN_F_RW ); if(!rc){ db->role = role; assert(!db->name); #if 0 db->name = fsl_strdup( fsl_db_role_label(FSL_DBROLE_MAIN) ); if(!db->name) rc = FSL_RC_OOM; #else /* Many thanks to Simon Slavin, native resident of the sqlite3 mailing list, for this... we apply the db's "real" name via an ATTACH, meaning that all the client-side kludgery to get the proper DB name is now obsolete. */ rc = fsl_db_attach( db, zDbName, fsl_db_role_label(role) ); if(!rc){ db->name = fsl_strdup( fsl_db_role_label(role) ); if(!db->name) rc = FSL_RC_OOM; } #endif } /* MARKER(("db->role=%d\n",db->role)); */ /* g.db = fsl_db_open(zDbName); */ /* g.zMainDbType = zLabel; */ /* f->mainDbType = zLabel; */ /* f->dbMain.role = FSL_DBROLE_MAIN; */ }else{ /* Main db has already been opened. Attach this one to it... */ assert( (int)FSL_DBROLE_NONE!=f->dbMain->role ); assert( f == f->dbMain->f ); if(NULL == f->dbMain->dbh){ /* It was closed in the meantime. Re-assign dbMain... */ MARKER(("Untested: re-assigning f->dbMain after it has been closed.\n")); assert(!"Broken by addition of f->dbMem."); f->dbMain = NULL; goto try_again; } if((int)role == db->role){ return fsl_cx_err_set(f, FSL_RC_ACCESS, "Cannot open/attach db role " "#%d (%s) more than once.", (int)role, fsl_db_role_label(role)); } rc = fsl_cx_attach_role(f, zDbName, role); wasAttached = 1; } if(!rc){ if(pWasAttached) *pWasAttached = wasAttached; }else{ if(db->error.code){ fsl_cx_uplift_db_error(f, db); } if(db==f->dbMain) f->dbMain = NULL; fsl_db_close(db); } return rc; } int fsl_config_close( fsl_cx * f ){ if(!f) return FSL_RC_MISUSE; else{ int rc; fsl_db * db = &f->config.db; if(db->dbh){ /* Config is our main db. Close them all */ assert(!"Not possible since f->dbMem added."); assert(f->dbMain==db); fsl_ckout_close(f); fsl_repo_close(f); rc = fsl_db_close(db); f->dbMain = NULL; }else if(f->dbMain && (FSL_DBROLE_CONFIG & f->dbMain->role)){ /* Config db is ATTACHed. */ assert(f->dbMain!=db); 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 * 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 * db = &f->repo.db; if(db->dbh){ /* Repo is our main db. Close them all */ assert(!"Not possible since f->dbMem added."); assert(f->dbMain==db); fsl_config_close(f); fsl_ckout_close(f); rc = fsl_db_close(db); f->dbMain = NULL; }else 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 * 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 * db = &f->ckout.db; #if 0 fsl_repo_close(f) /* Ignore error - it might not be opened. Close it first because it was (if things went normally) attached to f->ckout.db resp. HOWEVER: we have a bug-in-waiting here, potentially. If repo becomes the main db for an attached checkout (which isn't current possibly because opening a checkout closes/(re)opens the repo) then we stomp on our db handle here. Hypothetically. If the repo is dbMain: a) we cannot have a checkout because fsl_repo_open() fails if a repo is already opened. b) fsl_repo_close() will clear f->dbMain, leaving the code below to return FSL_RC_NOT_FOUND, which would be misleading. But that's all hypothetical - best we make it impossible for the public API to have a checkout without a repo or a repo/checkout mismatch. */ ; #endif if(db->dbh){ /* checkout is our main db. Close them all. */ assert(!"Not possible since f->dbMem added."); assert(!f->repo.db.dbh); assert(f->dbMain==db); fsl_repo_close(f); fsl_config_close(f); rc = fsl_db_close(db); f->dbMain = NULL; } else if(f->dbMain && (FSL_DBROLE_CKOUT & f->dbMain->role)){ /* Checkout db is ATTACHed. */ assert(f->dbMain!=db); 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); |
︙ | ︙ | |||
12837 12838 12839 12840 12841 12842 12843 | } 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); } | > | > > > | | | | < < < | | > > > > > > | < < < < < < < | > > > > > > > > | | < | > | > > > > | | > > > > | | | | | | | | > | | | < < < < < < < < < < < | 13039 13040 13041 13042 13043 13044 13045 13046 13047 13048 13049 13050 13051 13052 13053 13054 13055 13056 13057 13058 13059 13060 13061 13062 13063 13064 13065 13066 13067 13068 13069 13070 13071 13072 13073 13074 13075 13076 13077 13078 13079 13080 13081 13082 13083 13084 13085 13086 13087 13088 13089 13090 13091 13092 13093 13094 13095 13096 13097 13098 13099 13100 13101 13102 13103 13104 13105 13106 13107 13108 13109 13110 13111 | } 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_db_main_open_or_attach(f, zDbName, FSL_DBROLE_CKOUT, NULL); return rc; #if 0 /* Historical bits: no longer needed(?). */ zVFileDef = fsl_db_text(0, "SELECT sql FROM %s.sqlite_master" " WHERE name=='vfile'", fsl_cx_db_name("localdb")); /* ==> "ckout" */ if( zVFileDef==0 ) return 0; /* If the "isexe" column is missing from the vfile table, then add it now. This code added on 2010-03-06. After all users have upgraded, this code can be safely deleted. */ if( !fsl_str_glob("* isexe *", zVFileDef) ){ fsl_db_multi_exec("ALTER TABLE vfile ADD COLUMN isexe BOOLEAN DEFAULT 0"); } /* If "islink"/"isLink" columns are missing from tables, then add them now. This code added on 2011-01-17 and 2011-08-27. After all users have upgraded, this code can be safely deleted. */ if( !fsl_str_glob("* islink *", zVFileDef) ){ db_multi_exec("ALTER TABLE vfile ADD COLUMN islink BOOLEAN DEFAULT 0"); if( db_local_table_exists_but_lacks_column("stashfile", "isLink") ){ db_multi_exec("ALTER TABLE stashfile ADD COLUMN isLink BOOL DEFAULT 0"); } if( db_local_table_exists_but_lacks_column("undo", "isLink") ){ db_multi_exec("ALTER TABLE undo ADD COLUMN isLink BOOLEAN DEFAULT 0"); } if( db_local_table_exists_but_lacks_column("undo_vfile", "islink") ){ db_multi_exec("ALTER TABLE undo_vfile ADD COLUMN islink BOOL DEFAULT 0"); } } #endif return 0; } int fsl_cx_prepare( fsl_cx *f, fsl_stmt * tgt, char const * sql, ... ){ if(!f || !f->dbMain) return FSL_RC_MISUSE; else{ int rc; va_list args; va_start(args,sql); rc = fsl_db_preparev( f->dbMain, tgt, sql, args ); va_end(args); return rc; } } int fsl_cx_preparev( fsl_cx *f, fsl_stmt * tgt, char const * sql, va_list args ){ return (f && f->dbMain && tgt) ? fsl_db_preparev(f->dbMain, tgt, sql, args) : FSL_RC_MISUSE; } /** 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. |
︙ | ︙ | |||
12996 12997 12998 12999 13000 13001 13002 | assert(zRc); *zOut = zRc; } fsl_buffer_clear(&buf); return rc; } | | > > | 13204 13205 13206 13207 13208 13209 13210 13211 13212 13213 13214 13215 13216 13217 13218 13219 13220 13221 13222 13223 | assert(zRc); *zOut = zRc; } fsl_buffer_clear(&buf); return rc; } int fsl_config_open( fsl_cx * f, char const * openDbName ){ int rc = 0; const char * zDbName = 0; char * zPrefName = 0; bool useAttach = false /* TODO? Move this into a parameter? Do we need the fossil behaviour? */; if(!f) return FSL_RC_MISUSE; else if(f->config.db.dbh){ fsl_config_close(f); } if(openDbName && *openDbName){ zDbName = openDbName; }else{ |
︙ | ︙ | |||
13030 13031 13032 13033 13034 13035 13036 | 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 | > > | > > > > > > > > > > | 13240 13241 13242 13243 13244 13245 13246 13247 13248 13249 13250 13251 13252 13253 13254 13255 13256 13257 13258 13259 13260 13261 13262 13263 13264 13265 13266 | 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 if( useAttach ){ rc = fsl_cx_attach_role(f, zDbName, FSL_DBROLE_CONFIG); }else{ rc = fsl_cx_db_main_open_or_attach(f, zDbName, FSL_DBROLE_CONFIG, NULL); /* rc = fsl_db_open(&f->config.db, zDbName, FSL_OPEN_F_RWC ); */ } if(!rc && !f->dbMain){ assert(!"Not possible(?) since addition of f->dbMem"); f->dbMain = &f->config.db; } end: fsl_free(zPrefName); return rc; } static void fsl_cx_username_from_repo(fsl_cx * f){ fsl_db * dbR = fsl_cx_db_repo(f); |
︙ | ︙ | |||
13055 13056 13057 13058 13059 13060 13061 | 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; } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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){ |
︙ | ︙ | |||
13124 13125 13126 13127 13128 13129 13130 | 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; } | < | > | > | > | > | 13305 13306 13307 13308 13309 13310 13311 13312 13313 13314 13315 13316 13317 13318 13319 13320 13321 13322 13323 13324 13325 13326 13327 13328 13329 13330 13331 13332 13333 13334 | 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 * f, char const * repoDbFile/* , bool readOnlyCurrentlyIgnored */ ){ if(!f || !repoDbFile || !*repoDbFile) return FSL_RC_MISUSE; else 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_db_main_open_or_attach(f, repoDbFile, FSL_DBROLE_REPO, NULL); 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 = |
︙ | ︙ | |||
13284 13285 13286 13287 13288 13289 13290 | */ 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) | | | 13468 13469 13470 13471 13472 13473 13474 13475 13476 13477 13478 13479 13480 13481 13482 | */ 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) : 0); 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; } |
︙ | ︙ | |||
13335 13336 13337 13338 13339 13340 13341 | 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, | | | 13519 13520 13521 13522 13523 13524 13525 13526 13527 13528 13529 13530 13531 13532 13533 | 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 * 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; |
︙ | ︙ | |||
13588 13589 13590 13591 13592 13593 13594 | for( i = 0; i < f->xlinkers.used; ++i ){ rv = f->xlinkers.list + i; if(0==fsl_strcmp(rv->name, name)) return rv; } return NULL; } | | | | 13772 13773 13774 13775 13776 13777 13778 13779 13780 13781 13782 13783 13784 13785 13786 13787 13788 13789 | 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 * f, char const * name, fsl_deck_xlink_f cb, void * cbState ){ fsl_xlinker * x; if(!f || !cb || !name || !*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){ |
︙ | ︙ | |||
13729 13730 13731 13732 13733 13734 13735 | } char const * fsl_cx_filename_collation(fsl_cx const * f){ return f->cache.caseInsensitive ? "COLLATE nocase" : ""; } | | | | | 13913 13914 13915 13916 13917 13918 13919 13920 13921 13922 13923 13924 13925 13926 13927 13928 13929 13930 13931 13932 13933 13934 13935 13936 13937 13938 13939 13940 13941 13942 13943 13944 13945 13946 13947 13948 13949 13950 13951 13952 13953 13954 13955 13956 13957 13958 13959 13960 | } char const * fsl_cx_filename_collation(fsl_cx const * f){ return f->cache.caseInsensitive ? "COLLATE nocase" : ""; } void fsl_cx_content_buffer_yield(fsl_cx * 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 * 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 * 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; |
︙ | ︙ | |||
14210 14211 14212 14213 14214 14215 14216 | #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: */ /* | | | | | | > | < > > > | | 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 14408 14409 14410 14411 14412 14413 14414 14415 14416 14417 14418 14419 | #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 Stephan Beal (https://wanderinghorse.net). This program is free software; you can redistribute it and/or modify it under the terms of the Simplified BSD License (also known as the "2-Clause License" or "FreeBSD License".) This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. ***************************************************************************** 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 |
︙ | ︙ | |||
14242 14243 14244 14245 14246 14247 14248 | 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 | | | | | | | > | | < < < < < < < < < < < < | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | < | | | | | | | | | | > | | | | | 14429 14430 14431 14432 14433 14434 14435 14436 14437 14438 14439 14440 14441 14442 14443 14444 14445 14446 14447 14448 14449 14450 14451 14452 14453 14454 14455 14456 14457 14458 14459 14460 14461 14462 14463 14464 14465 14466 14467 14468 14469 14470 14471 14472 14473 14474 14475 14476 14477 14478 14479 14480 14481 14482 14483 14484 14485 14486 14487 14488 14489 14490 14491 14492 14493 14494 14495 14496 14497 14498 14499 14500 14501 14502 14503 14504 14505 14506 14507 14508 14509 14510 14511 14512 14513 14514 14515 14516 14517 14518 14519 14520 14521 14522 14523 14524 14525 14526 14527 14528 14529 14530 14531 14532 14533 14534 14535 14536 14537 14538 14539 14540 14541 14542 14543 14544 14545 14546 14547 14548 14549 14550 14551 14552 14553 14554 14555 14556 14557 14558 14559 14560 14561 14562 14563 14564 14565 14566 14567 14568 14569 14570 14571 14572 14573 14574 14575 14576 14577 14578 14579 14580 14581 14582 14583 14584 | 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_st*), 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 * 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 * 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 * 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 * 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 * stmt, fsl_size_t * len ){ return stmt ? fsl_buffer_cstr2(&stmt->sql, len) : NULL; } char const * fsl_db_filename(fsl_db const * db, fsl_size_t * len){ if(!db) return NULL; if(len && db->filename) *len = fsl_strlen(db->filename); return db->filename; } fsl_id_t fsl_db_last_insert_id(fsl_db *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 * db ){ fsl_list_visit( &db->beforeCommit, -1, fsl_list_v_fsl_free, NULL ); fsl_list_reserve(&db->beforeCommit, 0); } fsl_size_t fsl_db_stmt_cache_clear(fsl_db * 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; } int fsl_db_close( fsl_db * db ){ if(!db) return FSL_RC_MISUSE; else{ void const * allocStamp = db->allocStamp; fsl_cx * 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); *db = fsl_db_empty; if(&fsl_db_empty == allocStamp){ fsl_free( db ); }else{ db->allocStamp = allocStamp; db->f = f; } return 0; } } void fsl_db_err_reset( fsl_db * db ){ if(db && (db->error.code||db->error.msg.used)){ fsl_error_reset(&db->error); } } int fsl_db_attach(fsl_db * 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 * 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 * db){ return db ? db->name : NULL; } /** Returns the db name for the given role. */ const char * fsl_db_role_label(fsl_dbrole_e r){ |
︙ | ︙ | |||
14419 14420 14421 14422 14423 14424 14425 | case FSL_DBROLE_NONE: default: assert(!"cannot happen/not legal"); return NULL; } } | | | | | 14596 14597 14598 14599 14600 14601 14602 14603 14604 14605 14606 14607 14608 14609 14610 14611 14612 | case FSL_DBROLE_NONE: default: assert(!"cannot happen/not legal"); return NULL; } } char * fsl_db_julian_to_iso8601( fsl_db * db, double j, char msPrecision, char 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')" |
︙ | ︙ | |||
14447 14448 14449 14450 14451 14452 14453 | } fsl_stmt_cached_yield(st); } } return s; } | | | 14624 14625 14626 14627 14628 14629 14630 14631 14632 14633 14634 14635 14636 14637 14638 | } fsl_stmt_cached_yield(st); } } return s; } char * fsl_db_unix_to_iso8601( fsl_db * db, fsl_time_t t, char 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()*/" ; |
︙ | ︙ | |||
14484 14485 14486 14487 14488 14489 14490 | /** Propagates our intent to "statically" prepare a given statement through various internal API calls. */ FSL_STMT_F_PREP_CACHE = 0x10 }; | | | 14661 14662 14663 14664 14665 14666 14667 14668 14669 14670 14671 14672 14673 14674 14675 | /** 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 *db, fsl_stmt * 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, |
︙ | ︙ | |||
14566 14567 14568 14569 14570 14571 14572 | context. */ } return rc; } } | | < | | < < < < | < < < < < < < < < < < < < < | < | < > | | | | | 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 14770 14771 14772 14773 14774 14775 14776 14777 14778 14779 14780 14781 14782 14783 14784 14785 14786 14787 14788 14789 14790 14791 14792 14793 14794 14795 14796 14797 14798 14799 14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 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 14857 14858 14859 14860 14861 14862 14863 | context. */ } return rc; } } int fsl_db_prepare( fsl_db *db, fsl_stmt * 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 * db, fsl_stmt ** rv, char const * sql, va_list args ){ int rc = 0; fsl_buffer buf = fsl_buffer_empty; fsl_stmt * st = NULL; fsl_stmt * cs = NULL; if(!db || !rv || !sql) return FSL_RC_MISUSE; else if(!*sql) return FSL_RC_RANGE; rc = fsl_buffer_appendfv( &buf, sql, args ); if(rc) goto end; for( cs = db->cacheHead; cs; cs = cs->next ){ if(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; *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->next = db->cacheHead; db->cacheHead = st; st->flags = FSL_STMT_F_CACHE_HELD; *rv = st; } end: fsl_buffer_clear(&buf); 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 * 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 * db, char 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 *db, char 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 * 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 |
︙ | ︙ | |||
14741 14742 14743 14744 14745 14746 14747 | }else{ stmt->allocStamp = allocStamp; } return 0; } } | | | | | > | 14898 14899 14900 14901 14902 14903 14904 14905 14906 14907 14908 14909 14910 14911 14912 14913 14914 14915 14916 14917 14918 14919 14920 14921 14922 14923 14924 14925 14926 14927 | }else{ stmt->allocStamp = allocStamp; } return 0; } } int fsl_stmt_step( fsl_stmt * 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: fsl_error_set(&stmt->db->error, FSL_RC_STEP_ERROR, "sqlite error #%d: %s", rc, sqlite3_errmsg(stmt->db->dbh)); return FSL_RC_STEP_ERROR; } } } 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; |
︙ | ︙ | |||
14813 14814 14815 14816 14817 14818 14819 | ? rc : ((FSL_RC_STEP_ERROR==strc) ? FSL_RC_DB : 0); } } | | | | | | | | 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 | ? rc : ((FSL_RC_STEP_ERROR==strc) ? FSL_RC_DB : 0); } } int fsl_stmt_reset2( fsl_stmt * stmt, bool resetRowCounter ){ if(!stmt || !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 * stmt ){ return fsl_stmt_reset2(stmt, 0); } int fsl_stmt_col_count( fsl_stmt const * stmt ){ return (!stmt || !stmt->stmt) ? -1 : stmt->colCount ; } char const * fsl_stmt_col_name(fsl_stmt * 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 * 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; |
︙ | ︙ | |||
14970 14971 14972 14973 14974 14975 14976 | } #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 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 15204 15205 15206 15207 15208 15209 15210 15211 15212 15213 15214 15215 15216 15217 15218 15219 15220 15221 15222 15223 15224 15225 15226 15227 15228 15229 15230 15231 15232 15233 15234 15235 15236 15237 15238 15239 15240 15241 15242 15243 15244 15245 15246 15247 15248 15249 15250 15251 15252 15253 15254 15255 15256 15257 15258 15259 15260 15261 15262 15263 15264 15265 15266 15267 15268 15269 15270 15271 15272 15273 15274 15275 15276 15277 15278 15279 15280 15281 15282 15283 15284 15285 15286 15287 15288 15289 15290 15291 15292 15293 15294 15295 15296 15297 15298 15299 15300 15301 15302 15303 15304 15305 15306 15307 15308 15309 15310 15311 15312 15313 15314 15315 15316 15317 15318 15319 15320 15321 15322 15323 15324 15325 15326 15327 15328 15329 15330 15331 15332 15333 15334 15335 15336 15337 15338 15339 15340 15341 15342 15343 15344 15345 15346 15347 15348 15349 15350 15351 15352 15353 15354 15355 15356 15357 15358 15359 15360 15361 | } #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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * stmt, char 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 || !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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * stmt, int index ){ int32_t rv = 0; fsl_stmt_get_int32(stmt, index, &rv); return rv; } int64_t fsl_stmt_g_int64( fsl_stmt * stmt, int index ){ int64_t rv = 0; fsl_stmt_get_int64(stmt, index, &rv); return rv; } double fsl_stmt_g_double( fsl_stmt * stmt, int index ){ double rv = 0; fsl_stmt_get_double(stmt, index, &rv); return rv; } char const * fsl_stmt_g_text( fsl_stmt * stmt, int index, fsl_size_t * outLen ){ char const * rv = NULL; fsl_stmt_get_text(stmt, index, &rv, outLen); return rv; } |
︙ | ︙ | |||
15570 15571 15572 15573 15574 15575 15576 | end: fsl_cx_scratchpad_yield(f, b); return; oom: sqlite3_result_error_nomem(context); goto end; } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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; } |
︙ | ︙ | |||
15837 15838 15839 15840 15841 15842 15843 | 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 ); | < < < < < < < | 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); |
︙ | ︙ | |||
15874 15875 15876 15877 15878 15879 15880 | } }else{ assert(db->dbh); } return rc; } | | | 15981 15982 15983 15984 15985 15986 15987 15988 15989 15990 15991 15992 15993 15994 15995 | } }else{ assert(db->dbh); } return rc; } int fsl_db_exec_multiv( fsl_db * 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 ); |
︙ | ︙ | |||
15907 15908 15909 15910 15911 15912 15913 | z = zEnd; } fsl_buffer_reserve(&buf, 0); return rc; } } | | | | | | | | | | | | | | | 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 16035 16036 16037 16038 16039 16040 16041 16042 16043 16044 16045 16046 16047 16048 16049 16050 16051 16052 16053 16054 16055 16056 16057 16058 16059 16060 16061 16062 16063 16064 16065 16066 16067 16068 16069 16070 16071 16072 16073 16074 16075 16076 16077 16078 16079 16080 16081 16082 16083 16084 16085 16086 16087 16088 16089 16090 16091 16092 16093 16094 16095 16096 16097 16098 16099 16100 16101 16102 16103 16104 16105 16106 16107 16108 16109 16110 16111 16112 16113 16114 16115 16116 16117 16118 16119 16120 16121 16122 16123 16124 16125 16126 16127 16128 16129 16130 | z = zEnd; } fsl_buffer_reserve(&buf, 0); return rc; } } int fsl_db_exec_multi( fsl_db * 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 * 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 * 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 * db){ return (db && db->dbh) ? sqlite3_changes(db->dbh) : 0; } int fsl_db_changes_total(fsl_db * 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 * db){ db->priorChanges = sqlite3_total_changes(db->dbh); } int fsl_db_transaction_begin(fsl_db * 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 * db){ return db->doRollback ? -db->beginCount : db->beginCount; } int fsl_db_transaction_commit(fsl_db * db){ return (db && db->dbh) ? fsl_db_transaction_end(db, 0) : FSL_RC_MISUSE; } int fsl_db_transaction_rollback(fsl_db * db){ return (db && db->dbh) ? fsl_db_transaction_end(db, 1) : FSL_RC_MISUSE; } int fsl_db_rollback_force( fsl_db * 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 * 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 |
︙ | ︙ | |||
16525 16526 16527 16528 16529 16530 16531 | /* 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; } | | | > > > | > > > > > > | | | | | 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 16648 16649 16650 16651 16652 16653 16654 16655 16656 16657 16658 16659 16660 16661 16662 16663 16664 16665 16666 16667 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687 16688 16689 16690 16691 16692 16693 16694 16695 16696 16697 16698 16699 16700 16701 16702 | /* 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 * 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 * 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; } /* ** Return TRUE if zTable exists. */ bool fsl_db_table_exists(fsl_db * 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; } /* Returns non-0 if the database (which must be open) table identified by zTableName has a column named zColName (case-sensitive), else returns 0. */ char fsl_db_table_has_column( fsl_db * db, char const *zTableName, char const *zColName ){ fsl_stmt q = fsl_stmt_empty; int rc = 0; char rv = 0; if(!db || !zTableName || !*zTableName || !zColName || !*zColName) return 0; 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 = 1; break; } } fsl_stmt_finalize(&q); return rv; } |
︙ | ︙ | |||
16623 16624 16625 16626 16627 16628 16629 | } } fsl_stmt_finalize(&st); return rc; } } | | | | 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 | } } fsl_stmt_finalize(&st); return rc; } } int fsl_db_select_slist( fsl_db * 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 * 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, |
︙ | ︙ | |||
16758 16759 16760 16761 16762 16763 16764 | 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; } | | | 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 16885 16886 16887 16888 | 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; } 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; |
︙ | ︙ | |||
16807 16808 16809 16810 16811 16812 16813 | 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; | | | 16923 16924 16925 16926 16927 16928 16929 16930 16931 16932 16933 16934 16935 16936 16937 | 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; 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){ |
︙ | ︙ | |||
17066 17067 17068 17069 17070 17071 17072 | #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; } | | > | | | | | | | | | | | | | | | | | | | | | 17182 17183 17184 17185 17186 17187 17188 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 17200 17201 17202 17203 17204 17205 17206 17207 17208 17209 17210 17211 17212 17213 17214 17215 17216 17217 17218 17219 17220 17221 17222 17223 17224 17225 17226 17227 17228 17229 17230 17231 17232 17233 17234 17235 17236 17237 17238 17239 17240 17241 17242 17243 17244 17245 17246 17247 17248 17249 17250 17251 17252 17253 17254 17255 17256 17257 17258 17259 17260 17261 17262 17263 17264 17265 17266 17267 17268 17269 | #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 *m){ fsl_deck_clean_string(m, &m->uuid); m->rid = 0; } static void fsl_deck_clean_A(fsl_deck *m){ SFREE(A.name); SFREE(A.tgt); SFREE(A.src); } static void fsl_deck_clean_B(fsl_deck *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 *m){ fsl_deck_clean_string(m, &m->C); } static void fsl_deck_clean_E(fsl_deck *m){ fsl_deck_clean_string(m, &m->E.uuid); m->E = fsl_deck_empty.E; } static void fsl_deck_clean_F(fsl_deck *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 *m){ fsl_deck_clean_string(m, &m->G); } static void fsl_deck_clean_H(fsl_deck *m){ fsl_deck_clean_string(m, &m->H); } static void fsl_deck_clean_I(fsl_deck *m){ fsl_deck_clean_string(m, &m->I); } static void fsl_deck_clean_J(fsl_deck *m, bool alsoListMem){ fsl_card_J_list_free(&m->J, alsoListMem); } static void fsl_deck_clean_K(fsl_deck *m){ fsl_deck_clean_string(m, &m->K); } static void fsl_deck_clean_L(fsl_deck *m){ fsl_deck_clean_string(m, &m->L); } static void fsl_deck_clean_M(fsl_deck *m){ SLIST(M); } static void fsl_deck_clean_N(fsl_deck *m){ fsl_deck_clean_string(m, &m->N); } static void fsl_deck_clean_P(fsl_deck *m){ fsl_list_clear(&m->P, fsl_list_v_card_string_free, m); } static void fsl_deck_clean_Q(fsl_deck *m){ fsl_list_clear(&m->Q, fsl_list_v_card_Q_free, NULL); } static void fsl_deck_clean_R(fsl_deck *m){ fsl_deck_clean_string(m, &m->R); } static void fsl_deck_clean_T(fsl_deck *m){ fsl_list_clear(&m->T, fsl_list_v_card_T_free, NULL); } static void fsl_deck_clean_U(fsl_deck *m){ fsl_deck_clean_string(m, &m->U); } static void fsl_deck_clean_W(fsl_deck *m){ CBUF(W); } void fsl_deck_clean2(fsl_deck *m, fsl_buffer *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); |
︙ | ︙ | |||
17181 17182 17183 17184 17185 17186 17187 | m->f = f; } } #undef CBUF #undef SFREE #undef SLIST | | | | 17298 17299 17300 17301 17302 17303 17304 17305 17306 17307 17308 17309 17310 17311 17312 17313 17314 17315 17316 | m->f = f; } } #undef CBUF #undef SFREE #undef SLIST void fsl_deck_clean(fsl_deck *m){ fsl_deck_clean2(m, NULL); } void fsl_deck_finalize(fsl_deck *m){ void const * allocStamp; if(!m) return; allocStamp = m->allocStamp; fsl_deck_clean(m); if(allocStamp == &fsl_deck_empty){ fsl_free(m); }else{ |
︙ | ︙ | |||
17337 17338 17339 17340 17341 17342 17343 | 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). | | > > > | 17454 17455 17456 17457 17458 17459 17460 17461 17462 17463 17464 17465 17466 17467 17468 17469 17470 17471 | 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. Rather than rely on d->f being set (so d->f->flags can tell us whether or not to calculate an R-card), we'll rely on downstream code to check for an R-card if needed. */ 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. */ |
︙ | ︙ | |||
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); | > | 17512 17513 17514 17515 17516 17517 17518 17519 17520 17521 17522 17523 17524 17525 17526 | 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); |
︙ | ︙ | |||
17451 17452 17453 17454 17455 17456 17457 | /** 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. */ | | | 17572 17573 17574 17575 17576 17577 17578 17579 17580 17581 17582 17583 17584 17585 17586 | /** 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 * 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); |
︙ | ︙ | |||
17479 17480 17481 17482 17483 17484 17485 | 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. */ | | | 17600 17601 17602 17603 17604 17605 17606 17607 17608 17609 17610 17611 17612 17613 17614 | 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 * 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) |
︙ | ︙ | |||
17510 17511 17512 17513 17514 17515 17516 | return *mfMember ? 0 : FSL_RC_OOM; } } /** Implements fsl_set_set_XXX() where XXX is a fsl_buffer member of fsl_deck. */ | | | | 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 | 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 * 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 * 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); } |
︙ | ︙ | |||
17547 17548 17549 17550 17551 17552 17553 | } /** 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). */ | | | | | | | | | | | < < < < < < | < < < < < < < < | > > | < < < | < < < < < < < < < < < | | | 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 17704 17705 17706 17707 17708 17709 17710 17711 17712 17713 17714 17715 17716 17717 17718 17719 17720 17721 17722 17723 17724 17725 17726 17727 17728 17729 17730 17731 17732 17733 17734 17735 17736 17737 17738 17739 17740 17741 17742 17743 17744 17745 17746 17747 17748 17749 17750 17751 17752 17753 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 17781 17782 17783 17784 17785 17786 17787 17788 17789 17790 17791 17792 17793 17794 17795 17796 17797 17798 | } /** 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 * 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 ); } int fsl_deck_P_add( fsl_deck * mf, char const *parentUuid){ int rc; char * dupe; int const uLen = parentUuid ? fsl_is_uuid(parentUuid) : 0; if(!mf || !parentUuid || !*parentUuid) return FSL_RC_MISUSE; else if(!fsl_deck_check_type(mf, 'P')) return mf->f->error.code; else if(!uLen){ return fsl_cx_err_set(mf->f, FSL_RC_SYNTAX, "Invalid UUID for P-card."); } dupe = 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; } fsl_id_t fsl_deck_P_get_id(fsl_deck * 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 * 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{ |
︙ | ︙ | |||
17734 17735 17736 17737 17738 17739 17740 | 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; } } | | | | | | 17829 17830 17831 17832 17833 17834 17835 17836 17837 17838 17839 17840 17841 17842 17843 17844 17845 17846 17847 17848 17849 17850 17851 17852 17853 17854 17855 17856 17857 17858 17859 17860 17861 17862 17863 17864 17865 17866 17867 17868 17869 17870 17871 17872 17873 17874 17875 | 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 * 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 * 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 *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 * 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; |
︙ | ︙ | |||
17817 17818 17819 17820 17821 17822 17823 | rc = fsl_content_get(f, fileRid, buf); if(rc){ goto end; } numBuf[0] = 0; fsl_snprintf(numBuf, NumBufSize, " %"FSL_SIZE_T_PFMT"\n", | | | 17912 17913 17914 17915 17916 17917 17918 17919 17920 17921 17922 17923 17924 17925 17926 | rc = fsl_content_get(f, fileRid, buf); if(rc){ goto end; } numBuf[0] = 0; fsl_snprintf(numBuf, NumBufSize, " %"FSL_SIZE_T_PFMT"\n", (fsl_size_t)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); } |
︙ | ︙ | |||
17844 17845 17846 17847 17848 17849 17850 | }else{ char * x = fsl_strdup(theHash); if(x) *tgt = x; return x ? 0 : FSL_RC_OOM; } } | | | | 17939 17940 17941 17942 17943 17944 17945 17946 17947 17948 17949 17950 17951 17952 17953 17954 17955 17956 17957 17958 17959 17960 | }else{ char * x = fsl_strdup(theHash); if(x) *tgt = x; return x ? 0 : FSL_RC_OOM; } } int fsl_deck_R_calc(fsl_deck * 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 * 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."); |
︙ | ︙ | |||
17883 17884 17885 17886 17887 17888 17889 | }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); } | | | 17978 17979 17980 17981 17982 17983 17984 17985 17986 17987 17988 17989 17990 17991 17992 | }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 * 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: |
︙ | ︙ | |||
17944 17945 17946 17947 17948 17949 17950 | }else{ rc = FSL_RC_OOM; } } return rc; } | | | | | 18039 18040 18041 18042 18043 18044 18045 18046 18047 18048 18049 18050 18051 18052 18053 18054 18055 18056 18057 18058 18059 18060 18061 | }else{ rc = FSL_RC_OOM; } } return rc; } int fsl_deck_U_set( fsl_deck * mf, char const * v){ return fsl_deck_set_string( mf, 'U', &mf->U, v, -1 ); } int fsl_deck_W_set( fsl_deck * 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 * 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, |
︙ | ︙ | |||
17987 17988 17989 17990 17991 17992 17993 | /* Leave mf->A.tgt/name for downstream cleanup. */; } return rc; } } | | | | | 18082 18083 18084 18085 18086 18087 18088 18089 18090 18091 18092 18093 18094 18095 18096 18097 18098 18099 18100 18101 18102 18103 18104 18105 18106 18107 18108 18109 18110 18111 18112 18113 18114 18115 18116 18117 18118 18119 18120 18121 18122 18123 18124 | /* Leave mf->A.tgt/name for downstream cleanup. */; } return rc; } } int fsl_deck_D_set( fsl_deck * 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 * 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 * 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, |
︙ | ︙ | |||
19462 19463 19464 19465 19466 19467 19468 | 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; | | | 19557 19558 19559 19560 19561 19562 19563 19564 19565 19566 19567 19568 19569 19570 19571 | 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->uuid || 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); |
︙ | ︙ | |||
19518 19519 19520 19521 19522 19523 19524 | 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; | | | 19613 19614 19615 19616 19617 19618 19619 19620 19621 19622 19623 19624 19625 19626 19627 | 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->uuid || 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__); |
︙ | ︙ | |||
19588 19589 19590 19591 19592 19593 19594 | case 'U': fsl_deck_clean_U(d); break; case 'W': fsl_deck_clean_W(d); break; default: break; } } } | | | < | < < < | < < < < < < < < | < > | 19683 19684 19685 19686 19687 19688 19689 19690 19691 19692 19693 19694 19695 19696 19697 19698 19699 19700 19701 19702 19703 19704 | case 'U': fsl_deck_clean_U(d); break; case 'W': fsl_deck_clean_W(d); break; default: break; } } } int fsl_deck_derive(fsl_deck * d){ int rc = 0; if(!d->uuid || d->rid<=0) return FSL_RC_MISUSE; else if(FSL_SATYPE_CHECKIN!=d->type) return FSL_RC_TYPE; fsl_deck_clean_P(d); rc = fsl_list_append(&d->P, d->uuid); if(rc) return rc; d->uuid = NULL; 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; |
︙ | ︙ | |||
19727 19728 19729 19730 19731 19732 19733 | (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. */ | | | 19810 19811 19812 19813 19814 19815 19816 19817 19818 19819 19820 19821 19822 19823 19824 | (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 * 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; |
︙ | ︙ | |||
19761 19762 19763 19764 19765 19766 19767 | otherRid = cid; }else{ pParent = &dOther; otherRid = pmid; } if(otherRid && !fsl_cx_mcache_search(f, otherRid, &dOther)){ | | | 19844 19845 19846 19847 19848 19849 19850 19851 19852 19853 19854 19855 19856 19857 19858 | otherRid = cid; }else{ pParent = &dOther; otherRid = pmid; } if(otherRid && !fsl_cx_mcache_search(f, otherRid, &dOther)){ rc = otherRid ? fsl_content_get(f, otherRid, &otherContent) : 0; 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; |
︙ | ︙ | |||
20735 20736 20737 20738 20739 20740 20741 | 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. */ | | | 20818 20819 20820 20821 20822 20823 20824 20825 20826 20827 20828 20829 20830 20831 20832 | 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 *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); |
︙ | ︙ | |||
21054 21055 21056 21057 21058 21059 21060 | 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; } | | | | > > > > > > > < < < < < < | 21137 21138 21139 21140 21141 21142 21143 21144 21145 21146 21147 21148 21149 21150 21151 21152 21153 21154 21155 21156 21157 21158 21159 21160 21161 21162 21163 21164 21165 21166 21167 21168 21169 21170 21171 21172 21173 21174 21175 21176 21177 21178 21179 21180 21181 21182 21183 21184 21185 21186 21187 21188 21189 21190 21191 21192 21193 21194 21195 21196 21197 21198 | 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 * d ){ int rc; if(!d || !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 */ * 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){ return fsl_cx_err_set(f, FSL_RC_RANGE, "Invalid RID for crosslink: %"FSL_ID_T_PFMT, rid); } else if(!db) return FSL_RC_NOT_A_REPO; else if(!fsl_deck_has_required_cards(d)){ assert(d->f->error.code); 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; } if(FSL_SATYPE_CHECKIN==d->type){ if(d->B.uuid && !d->B.baseline){ rc = fsl_deck_baseline_fetch(d); if(rc) goto end; assert(d->B.baseline); } } rc = fsl_db_transaction_begin(db); if(rc) goto end; 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; |
︙ | ︙ | |||
21158 21159 21160 21161 21162 21163 21164 | 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 " | | | | 21242 21243 21244 21245 21246 21247 21248 21249 21250 21251 21252 21253 21254 21255 21256 21257 21258 | 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 [%.12s].", xl->name, rc, fsl_rc_cstr(rc), d->uuid); } } }/*end crosslink callbacks*/ end: if(!rc){ rc = fsl_db_transaction_end(db, false); }else{ |
︙ | ︙ | |||
21370 21371 21372 21373 21374 21375 21376 | || z[n-34]!=' ' || !fsl_validate16((const char *)z+n-33, FSL_STRLEN_MD5)){ return 0; } return 1; } | | > | 21454 21455 21456 21457 21458 21459 21460 21461 21462 21463 21464 21465 21466 21467 21468 21469 21470 21471 21472 21473 21474 21475 21476 21477 21478 21479 21480 21481 21482 21483 21484 21485 21486 21487 | || z[n-34]!=' ' || !fsl_validate16((const char *)z+n-33, FSL_STRLEN_MD5)){ return 0; } return 1; } int fsl_deck_parse2(fsl_deck * d, fsl_buffer * 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_db * db; 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 |
︙ | ︙ | |||
21418 21419 21420 21421 21422 21423 21424 | : "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); } | > | > | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 21503 21504 21505 21506 21507 21508 21509 21510 21511 21512 21513 21514 21515 21516 21517 21518 21519 21520 21521 21522 21523 21524 21525 21526 21527 21528 21529 21530 21531 21532 21533 21534 21535 21536 21537 21538 21539 21540 21541 21542 21543 21544 21545 21546 21547 21548 21549 21550 21551 21552 21553 21554 21555 21556 21557 21558 21559 21560 21561 | : "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); } db = fsl_cx_db_repo(f); seen = f ? &f->cache.mfSeen : NULL; if(seen){ 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); /* We have to hash BEFORE parsing, as parsing modifies the input */ if(rid){ assert(rid>0); d->rid = rid; d->uuid = fsl_rid_to_uuid(f, rid); if(!d->uuid){ rc = f->error.code ? f->error.code : FSL_RC_OOM; goto end; } }else if(db){ rc = fsl_repo_blob_lookup(f, src, &d->rid, &d->uuid); if(FSL_RC_NOT_FOUND==rc){ /* Non-fatal */ rc = 0; } }else{ fsl_buffer hash = fsl_buffer_empty; rc = fsl_cx_hash_buffer(f, false, src, &hash); if(rc){ fsl_buffer_clear(&hash); }else{ d->uuid = fsl_buffer_str(&hash); } } if(rc) return rc; /* legacy: not yet clear if we need this: if( !isRepeat ) g.parseCnt[0]++; */ /* 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 " |
︙ | ︙ | |||
21458 21459 21460 21461 21462 21463 21464 | } /* Verify the Z card */ if( fsl_deck_verify_Z_card(z, n) < 0 ){ ERROR(FSL_RC_CONSISTENCY, "Z-card checksum mismatch"); } | < < < | 21575 21576 21577 21578 21579 21580 21581 21582 21583 21584 21585 21586 21587 21588 | } /* 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. */ |
︙ | ︙ | |||
22163 22164 22165 22166 22167 22168 22169 | 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; } | < | 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; |
︙ | ︙ | |||
22188 22189 22190 22191 22192 22193 22194 | #undef TOKEN_EXISTS #undef TOKEN_UUID #undef TOKEN_MD5 #undef TOKEN #undef ERROR } | | | > < | | 22301 22302 22303 22304 22305 22306 22307 22308 22309 22310 22311 22312 22313 22314 22315 22316 22317 22318 22319 22320 22321 22322 22323 22324 22325 22326 22327 22328 22329 22330 22331 22332 22333 22334 | #undef TOKEN_EXISTS #undef TOKEN_UUID #undef TOKEN_MD5 #undef TOKEN #undef ERROR } int fsl_deck_parse(fsl_deck * d, fsl_buffer * src){ return fsl_deck_parse2(d, src, 0); } int fsl_deck_load_rid( fsl_cx * f, fsl_deck * d, fsl_id_t rid, fsl_satype_e type ){ fsl_buffer buf = fsl_buffer_empty; int rc = 0; if(!f || !d) return FSL_RC_SYNTAX; 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); 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_ERROR, "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{ |
︙ | ︙ | |||
22239 22240 22241 22242 22243 22244 22245 22246 22247 22248 22249 22250 22251 22252 | 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)); | > > > | | | < < | | 22352 22353 22354 22355 22356 22357 22358 22359 22360 22361 22362 22363 22364 22365 22366 22367 22368 22369 22370 22371 22372 22373 22374 22375 22376 22377 22378 22379 22380 22381 22382 22383 22384 | 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){ assert(rid == d->rid); 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)); } } if( !rc && d->B.uuid ){ rc = fsl_cx_update_seen_delta_mf(f); } end: fsl_buffer_clear(&buf); return rc; } int fsl_deck_load_sym( fsl_cx * f, fsl_deck * 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); |
︙ | ︙ | |||
22421 22422 22423 22424 22425 22426 22427 | } } return 0; } #undef FCARD } | | | | | | > | < | < | > | < < < | < < < < < < < < | | | | < | | | | | > > | | | > > > > > > > > > > > > > > > > > > > | > > | > > > > > > | | | | | | 22535 22536 22537 22538 22539 22540 22541 22542 22543 22544 22545 22546 22547 22548 22549 22550 22551 22552 22553 22554 22555 22556 22557 22558 22559 22560 22561 22562 22563 22564 22565 22566 22567 22568 22569 22570 22571 22572 22573 22574 22575 22576 22577 22578 22579 22580 22581 22582 22583 22584 22585 22586 22587 22588 22589 22590 22591 22592 22593 22594 22595 22596 22597 22598 22599 22600 22601 22602 22603 22604 22605 22606 22607 22608 22609 22610 22611 22612 22613 22614 22615 22616 22617 22618 22619 22620 22621 22622 22623 22624 22625 22626 22627 22628 22629 22630 22631 22632 22633 22634 22635 22636 22637 22638 22639 22640 22641 22642 22643 22644 22645 22646 22647 | } } return 0; } #undef FCARD } int fsl_deck_save( fsl_deck * d, bool isPrivate ){ int rc; fsl_cx * f = d ? d->f : NULL; fsl_db * db = f ? fsl_needs_repo(f) : NULL; fsl_buffer buf = fsl_buffer_empty; fsl_id_t newRid = 0; bool const oldPrivate = f ? f->cache.markPrivate : 0; bool const isNew = d && !d->uuid; if(!f || !d ) return FSL_RC_MISUSE; else if(!db) return FSL_RC_NOT_A_REPO; else if( (d->rid>0 && !d->uuid) || (d->rid<=0 && d->uuid)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, "The input deck must have either rid _and_ UUID " "or neither of rid/UUID. A mixture is ambiguous."); }else 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); rc = fsl_deck_output(d, fsl_output_f_buffer, &buf); if(rc){ fsl_buffer_clear(&buf); return rc; } rc = fsl_db_transaction_begin(db); if(rc){ fsl_buffer_clear(&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, d->uuid, 0, 0U, isPrivate, &newRid); if(rc) goto end; assert(newRid>0); rc = fsl_cx_hash_buffer( f, false, &buf, &buf ); if(rc) goto end; /* We need d->uuid and d->rid for crosslinking purposes, but will unset them on error (if we set them) because their values will no longer be in the db after rollback... */ #if 0 /* practice shows that this is not needed/desired. There are cases where round-tripping a manifest results in a 1-millisecond difference in the D-card (Julian Day/double), which changes the has but otherwise has no difference on the meaning. That said, the way we use decks means that we really don't care (for purposes of this function) what their initial UUID is except for the purpose of fsl_content_put_ex(), above. */ if(d->uuid){ /* Compare original and resulting hashes. */ if(0 != fsl_uuidcmp(fsl_buffer_cstr(&buf), d->uuid)){ rc = fsl_cx_err_set(f, FSL_RC_CONSISTENCY, "Input deck's original UUID and resulting UUID " "do not match: [%s] vs [%b]", d->uuid, &buf); goto end; } /* ??? assert(d->rid==newRid); */ d->rid = newRid; }else #endif { fsl_free(d->uuid); d->uuid = fsl_buffer_str(&buf) /* transfer ownership */; buf = fsl_buffer_empty; d->rid = newRid; } #if 0 /* Something to consider: if d is new and 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(isNew && 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(isNew && (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); |
︙ | ︙ | |||
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 " | > | 22655 22656 22657 22658 22659 22660 22661 22662 22663 22664 22665 22666 22667 22668 22669 | 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 " |
︙ | ︙ | |||
22545 22546 22547 22548 22549 22550 22551 | rc = f->cache.isCrosslinking ? fsl_deck_crosslink(d) : fsl_deck_crosslink_one(d); end: f->cache.markPrivate = oldPrivate; | | > > > | > | | 22677 22678 22679 22680 22681 22682 22683 22684 22685 22686 22687 22688 22689 22690 22691 22692 22693 22694 22695 22696 22697 22698 22699 22700 22701 22702 22703 | 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){ if(isNew){ fsl_free(d->uuid); d->uuid = NULL; d->rid = 0; } if(!f->error.code && db->error.code){ rc = fsl_cx_uplift_db_error(f, db); } } fsl_buffer_clear(&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; |
︙ | ︙ | |||
28846 28847 28848 28849 28850 28851 28852 | fsl_filename_free(zMbcs); return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0; } } | | | | | | | | | 28982 28983 28984 28985 28986 28987 28988 28989 28990 28991 28992 28993 28994 28995 28996 28997 28998 28999 29000 29001 29002 29003 29004 29005 29006 29007 29008 29009 29010 29011 29012 29013 29014 29015 29016 29017 29018 29019 29020 29021 29022 29023 29024 29025 29026 29027 29028 29029 29030 29031 | fsl_filename_free(zMbcs); return rc ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0; } } void fsl_pathfinder_clear(fsl_pathfinder * 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 * 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 * pf, char const * dir){ return (pf && dir) ? fsl_pathfinder_add(&pf->dirs, dir) : FSL_RC_MISUSE; } int fsl_pathfinder_ext_add(fsl_pathfinder * pf, char const * ext){ return (pf && ext) ? fsl_pathfinder_add(&pf->ext, ext) : FSL_RC_MISUSE; } int fsl_pathfinder_search(fsl_pathfinder * pf, char const * base, char const ** pOut, fsl_size_t * 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 = |
︙ | ︙ | |||
29339 29340 29341 29342 29343 29344 29345 | } z += i+1; } return rc; } | | < | | | | 29475 29476 29477 29478 29479 29480 29481 29482 29483 29484 29485 29486 29487 29488 29489 29490 29491 29492 29493 29494 29495 29496 29497 29498 29499 29500 29501 29502 29503 29504 29505 29506 29507 29508 29509 29510 29511 29512 29513 29514 29515 29516 | } z += i+1; } return rc; } char const * fsl_glob_list_matches( fsl_list const * globList, char const * zHaystack ){ if(!globList || !zHaystack || !*zHaystack || !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, zHaystack ) ) 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 * 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: */ |
︙ | ︙ | |||
29409 29410 29411 29412 29413 29414 29415 | */ static int fsl_output_f_fsl_output( void * state, void const * s, fsl_size_t n ){ return fsl_output( (fsl_cx *)state, s, n ); } | | | | > | | | > | > > | > > | < > > | 29544 29545 29546 29547 29548 29549 29550 29551 29552 29553 29554 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 29587 29588 29589 29590 29591 29592 29593 29594 29595 29596 29597 29598 29599 29600 29601 29602 29603 29604 29605 29606 29607 29608 29609 29610 | */ 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 * 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 * 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 * cx, void const * src, fsl_size_t n ){ if(!cx || !src) return FSL_RC_MISUSE; else if(!n || !cx->output.out) return 0; else return cx->output.out( cx->output.state, src, n ); } int fsl_flush( fsl_cx * f ){ return f ? (f->output.flush ? f->output.flush(f->output.state) : 0) : FSL_RC_MISUSE; } int fsl_flush_f_FILE(void * _FILE){ return _FILE ? (fflush((FILE*)_FILE) ? fsl_errno_to_rc(errno, FSL_RC_IO) : 0) : FSL_RC_MISUSE; } int fsl_output_f_FILE( void * state, void const * src, fsl_size_t n ){ if(!state || !src) return FSL_RC_MISUSE; else 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 ){ FILE * f = (FILE*) state; if( !state || !dest || !n ) return FSL_RC_MISUSE; else if( !*n ) return FSL_RC_RANGE; *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 ){ |
︙ | ︙ | |||
29555 29556 29557 29558 29559 29560 29561 | " 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 ); } | | | | | 29697 29698 29699 29700 29701 29702 29703 29704 29705 29706 29707 29708 29709 29710 29711 29712 29713 29714 29715 29716 29717 29718 29719 29720 29721 29722 29723 29724 29725 29726 29727 29728 | " 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 * f, fsl_id_t rid){ int32_t rv = 0; int rc; fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; 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 * 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 " |
︙ | ︙ | |||
29613 29614 29615 29616 29617 29618 29619 | } fsl_stmt_cached_yield(st); assert(0==rv || 1==rv); } return rc ? 0 : (rv==1); } | < < < < < < < < < | 29755 29756 29757 29758 29759 29760 29761 29762 29763 29764 29765 29766 29767 29768 | } 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; bool isLeaf; |
︙ | ︙ | |||
30896 30897 30898 30899 30900 30901 30902 | 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; } | | < < < | | 31029 31030 31031 31032 31033 31034 31035 31036 31037 31038 31039 31040 31041 31042 31043 31044 | 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 *pPivot, fsl_buffer *pV1, fsl_buffer *pV2, fsl_buffer *pOut, unsigned int *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 */ |
︙ | ︙ | |||
31924 31925 31926 31927 31928 31929 31930 | }else{ fsl_free( rvv ); } } return rc; } | | | < | 32054 32055 32056 32057 32058 32059 32060 32061 32062 32063 32064 32065 32066 32067 32068 32069 32070 32071 32072 32073 | }else{ fsl_free( rvv ); } } return rc; } fsl_id_t fsl_uuid_to_rid( fsl_cx * f, char const * uuid ){ fsl_db * const db = fsl_needs_repo(f); fsl_size_t const uuidLen = (uuid && db) ? fsl_strlen(uuid) : 0; if(!f || !uuid || !uuidLen) return -1; else if(!db){ /* f's error state has already been set */ 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){ |
︙ | ︙ | |||
32086 32087 32088 32089 32090 32091 32092 | *rv = fnid; }else if(db->error.code){ fsl_cx_uplift_db_error(f, db); } return rc; } | | < | 32215 32216 32217 32218 32219 32220 32221 32222 32223 32224 32225 32226 32227 32228 32229 | *rv = fnid; }else if(db->error.code){ fsl_cx_uplift_db_error(f, db); } return rc; } int fsl_delta_src_id( fsl_cx * 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; |
︙ | ︙ | |||
32138 32139 32140 32141 32142 32143 32144 | : FSL_RC_RANGE; } void fsl_repo_verify_cancel( fsl_cx * f ){ fsl_id_bag_clear(&f->cache.toVerify); } | | | 32266 32267 32268 32269 32270 32271 32272 32273 32274 32275 32276 32277 32278 32279 32280 | : 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 * 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); |
︙ | ︙ | |||
32170 32171 32172 32173 32174 32175 32176 | 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){ | | | < < < | | | 32298 32299 32300 32301 32302 32303 32304 32305 32306 32307 32308 32309 32310 32311 32312 32313 32314 32315 32316 32317 32318 32319 32320 32321 32322 32323 32324 32325 32326 | 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){ assert(db->error.code); fsl_cx_uplift_db_error(f, db); } } return rc; } } fsl_uuid_str fsl_rid_to_uuid(fsl_cx * 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 * 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, |
︙ | ︙ | |||
33110 33111 33112 33113 33114 33115 33116 | * * 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){ | < | 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, |
︙ | ︙ | |||
33376 33377 33378 33379 33380 33381 33382 | case FSL_SATYPE_TECHNOTE: return 'e'; default: assert(!"Internal misuse of fsl_satype_letter()"); return 0; } } | | | | 33500 33501 33502 33503 33504 33505 33506 33507 33508 33509 33510 33511 33512 33513 33514 33515 | case FSL_SATYPE_TECHNOTE: return 'e'; default: assert(!"Internal misuse of fsl_satype_letter()"); return 0; } } int fsl_search_doc_touch(fsl_cx *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; |
︙ | ︙ | |||
37117 37118 37119 37120 37121 37122 37123 | 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; | | | 37241 37242 37243 37244 37245 37246 37247 37248 37249 37250 37251 37252 37253 37254 37255 | 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(&deck, parent.uuid); 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); |
︙ | ︙ | |||
37249 37250 37251 37252 37253 37254 37255 | */ /************************************************************************* This file implements ticket-related parts of the library. */ #include <assert.h> #include <string.h> /* memcmp() */ | | | > | | | < | > > > > > > | 37373 37374 37375 37376 37377 37378 37379 37380 37381 37382 37383 37384 37385 37386 37387 37388 37389 37390 37391 37392 37393 37394 37395 37396 37397 37398 37399 37400 37401 37402 37403 37404 37405 | */ /************************************************************************* This file implements ticket-related parts of the library. */ #include <assert.h> #include <string.h> /* memcmp() */ int fsl_cx_ticket_create_table(fsl_cx * f){ fsl_db * db = f ? fsl_needs_repo(f) : NULL; int rc; if(!f) return FSL_RC_MISUSE; else if(!db) return FSL_RC_NOT_A_REPO; rc = fsl_db_exec_multi(db, "DROP TABLE IF EXISTS ticket;" "DROP TABLE IF EXISTS ticketchng;" ); if(!rc){ fsl_buffer * buf = fsl_cx_scratchpad(f); rc = fsl_cx_schema_ticket(f, buf); if(!rc){ rc = fsl_db_exec_multi(db, "%b", buf); } fsl_cx_scratchpad_yield(f, buf); } if(rc && db->error.code && !f->error.code){ fsl_cx_uplift_db_error(f, db); } return rc; } static int fsl_tkt_field_id(fsl_list const * jli, const char *zFieldName){ int i; fsl_card_J const * jc; |
︙ | ︙ | |||
37689 37690 37691 37692 37693 37694 37695 | /* Only for debugging */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) | | | < > | | < | < < | 37819 37820 37821 37822 37823 37824 37825 37826 37827 37828 37829 37830 37831 37832 37833 37834 37835 37836 37837 37838 37839 37840 37841 37842 37843 37844 37845 37846 37847 37848 37849 37850 37851 37852 37853 37854 37855 37856 37857 37858 37859 37860 37861 37862 37863 37864 37865 37866 37867 37868 37869 37870 37871 37872 37873 37874 37875 37876 37877 37878 37879 37880 37881 37882 37883 37884 37885 37886 37887 37888 37889 37890 37891 37892 37893 | /* 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 * 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; } if(rc) goto end; 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)"); 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 couldn't get 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); |
︙ | ︙ | |||
37794 37795 37796 37797 37798 37799 37800 37801 37802 37803 37804 37805 37806 37807 37808 37809 37810 37811 37812 | 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; } | > | | | | 37921 37922 37923 37924 37925 37926 37927 37928 37929 37930 37931 37932 37933 37934 37935 37936 37937 37938 37939 37940 37941 37942 37943 37944 37945 37946 37947 37948 37949 37950 37951 37952 37953 37954 37955 37956 37957 37958 37959 37960 37961 37962 37963 | 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); assert(d.uuid); } } 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 * 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 * f, fsl_id_t vid){ return fsl_vfile_unload_impl(f, vid, true); } int fsl_vfile_unload_except(fsl_cx * 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 |
︙ | ︙ | |||
37857 37858 37859 37860 37861 37862 37863 | rc = fsl_cx_err_set(f, rc, "Error %s while hashing file: %s", fsl_rc_cstr(rc), zName); } return rc; } | | | 37985 37986 37987 37988 37989 37990 37991 37992 37993 37994 37995 37996 37997 37998 37999 | 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 * 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); |
︙ | ︙ | |||
38536 38537 38538 38539 38540 38541 38542 | 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}; | | | | 38664 38665 38666 38667 38668 38669 38670 38671 38672 38673 38674 38675 38676 38677 38678 38679 | 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_cx_find_filename_changes( fsl_cx * 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 */ |
︙ | ︙ | |||
39400 39401 39402 39403 39404 39405 39406 | 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 " | | | | 39528 39529 39530 39531 39532 39533 39534 39535 39536 39537 39538 39539 39540 39541 39542 39543 | 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].", 12, mf.uuid); } 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: @code fsl_size_t sz = 3; fsl_fprintf( stdout, "%"FSL_SIZE_T_PFMT" %"FSL_SIZE_T_PFMT\n", sz, // OK! 3 // BAD! See below... ); @endcode 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: @code 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)); @endcode 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 276 277 | /* 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 Stephan Beal (https://wanderinghorse.net). This program is free software; you can redistribute it and/or modify it under the terms of the Simplified BSD License (also known as the "2-Clause License" or "FreeBSD License".) This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. ***************************************************************************** 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 poor old netbook is beginning to choke on syntax-highlighting them and browsing their (large) Doxygen output. */ /* 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 -*- */ |
︙ | ︙ | |||
514 515 516 517 518 519 520 | /** Arbitrary context-dependent state. */ void * state; /** Finalizer for this->state. If used, it should be called like: | < > < > | 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 | /** Arbitrary context-dependent state. */ void * state; /** Finalizer for this->state. If used, it should be called like: @code this->finalize.f( this->finalize.state, this->state ); @endcode After which this->state must be treated as if it has been free(3)'d. */ fsl_finalizer finalize; }; |
︙ | ︙ | |||
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 | > | 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 | /** 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 |
︙ | ︙ | |||
763 764 765 766 767 768 769 | 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 | | < > < > | 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 | 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 withou violating constness. Here's a crude workaround: @code 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! @endcode 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. */ |
︙ | ︙ | |||
795 796 797 798 799 800 801 | 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: | < > < > < > > | | | | | 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 | 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: @code fsl_stream( fsl_input_f_FILE, stdin, fsl_output_f_FILE, stdout ); @endcode Or copy a FILE to a buffer: @code 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); @endcode */ 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 v1'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 |
︙ | ︙ | |||
869 870 871 872 873 874 875 | - 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): | < > < > | < < < < | 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): @code 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 @endcode 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. @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 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). | > | 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 | 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). |
︙ | ︙ | |||
975 976 977 978 979 980 981 | 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; /** | | < | < | < | 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 | 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. */ 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. |
︙ | ︙ | |||
1008 1009 1010 1011 1012 1013 1014 | 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() */ | | | | | | 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 | 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 * err, int code, char const * fmt, ... ); /** va_list counterpart to fsl_error_set(). */ FSL_EXPORT int fsl_error_setv( fsl_error * 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 |
︙ | ︙ | |||
1037 1038 1039 1040 1041 1042 1043 | 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() */ | | < | | | < | | 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 | 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 * err, char const ** str, fsl_size_t * 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 * 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 * 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 * src, fsl_error * 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 * from, fsl_error * 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 ); |
︙ | ︙ | |||
1228 1229 1230 1231 1232 1233 1234 | 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: | < > < > < > < > | 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 | 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: @code fsl_buffer_swap_free( &b1, &b2, -1 ); @endcode Swaps the contents of b1 and b2, then frees the contents of the left-side buffer (b1). @code fsl_buffer_swap_free( &b1, &b2, 1 ); @endcode 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 ); |
︙ | ︙ | |||
1565 1566 1567 1568 1569 1570 1571 | 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: | < > | | | < > < > < > | < | < | > | | < | < | | < | | < | 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 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 | 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: @code 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 ); @endcode To take over ownership of the buffer's memory, do: @code void * mem = buf.mem; buf = fsl_buffer_empty; @endcode In which case the memory must eventually be passed to fsl_free() to free it. */ FSL_EXPORT int fsl_buffer_fill_from( fsl_buffer * dest, fsl_input_f src, void * 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 * dest, FILE * 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 "-" as an alias for stdin. 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 * 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 *pTo, fsl_buffer *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 * toState, fsl_buffer *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 |
︙ | ︙ | |||
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 | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 | /** 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 ); /** 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 }; /** _Almost_ equivalent to fopen(3) but: - expects name to be UTF8-encoded. - If name=="-", it returns one of stdin or stdout, depending on |
︙ | ︙ | |||
1868 1869 1870 1871 1872 1873 1874 | 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: | > > > > > | | | | | | | | | | | | | | | | | | | < < < | | | | | | | | | | 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 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 1983 1984 1985 1986 1987 1988 1989 1990 1991 | 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: (If you are NOT reading this via doxygen-processed sources: the percent signs below are doubled for the sake of doxygen, and each pair refers to only a single percent sign in the format string.) %%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. %%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 |
︙ | ︙ | |||
2011 2012 2013 2014 2015 2016 2017 | 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: | < > < > | 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 | 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: @code fsl_size_t const n = number of bytes to allocate; int const rc = fsl_list_reserve( myList, n ); if( rc ) { ... error ... } @endcode @see fsl_list_clear() @see fsl_list_visit_free() */ FSL_EXPORT int fsl_list_reserve( fsl_list * self, fsl_size_t n ); /** |
︙ | ︙ | |||
2203 2204 2205 2206 2207 2208 2209 | 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: | | < < > | 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 | 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: @code 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); @endcode */ 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). |
︙ | ︙ | |||
2413 2414 2415 2416 2417 2418 2419 | 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: | | < < > | 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 | 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: @code "*.c *.h, *.sh, '*.in'" @endcode @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 ); |
︙ | ︙ | |||
2440 2441 2442 2443 2444 2445 2446 | @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 | | < > < > | < | | 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 | @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 zHaystack 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: @code if( fsl_glob_list_matches(myGlobs, someFilename) ) { ... } @endcode @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 * globList, char const * zHaystack ); /** 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 * globList ); /** Returns true if the given letter is an ASCII alphabet character. */ FSL_EXPORT char fsl_isalpha(int c); |
︙ | ︙ | |||
3048 3049 3050 3051 3052 3053 3054 | - 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: | < > < > | 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 | - 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: @code int rc = fsl_delta_create( v1, v1len, v2, v2len, fsl_output_f_FILE, stdout); @endcode */ 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 |
︙ | ︙ | |||
3232 3233 3234 3235 3236 3237 3238 | /** 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: | < > < > | 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 | /** 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: @code FILE * f = fsl_fopen("...", "..."); int rc = f ? 0 : fsl_errno_to_rc(errno, FSL_RC_IO); ... @endcode 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 |
︙ | ︙ | |||
3419 3420 3421 3422 3423 3424 3425 | fsl_output_f out, void * outState, short contextLines, short sbsWidth, int diffFlags ); /** Functionally equivalent to: | < > < > | 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 | fsl_output_f out, void * outState, short contextLines, short sbsWidth, int diffFlags ); /** Functionally equivalent to: @code: fsl_diff_text(pA, pB, fsl_output_f_buffer, pOut, contextLines, sbsWidth, diffFlags); @endcode 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, |
︙ | ︙ | |||
3865 3866 3867 3868 3869 3870 3871 | 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: | < > < > | 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 | 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: @code @@ -A,B +C,D @@ @endcode 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 |
︙ | ︙ | |||
4074 4075 4076 4077 4078 4079 4080 | 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: | < > < > | 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 | 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: @code 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. @endcode 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, |
︙ | ︙ | |||
4212 4213 4214 4215 4216 4217 4218 | /** 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 | | | 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 | /** 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 (0) 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 |
︙ | ︙ | |||
4323 4324 4325 4326 4327 4328 4329 | 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. */ | | < < | | | | < > < > | 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 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 | 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 * 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 * 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 *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 *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: @code 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); @endcode @see fsl_pathfinder_dir_add() @see fsl_pathfinder_ext_add() @see fsl_pathfinder_clear() @see fsl_pathfinder_search() */ struct fsl_pathfinder { |
︙ | ︙ | |||
4430 4431 4432 4433 4434 4435 4436 | }; typedef struct fsl_pathfinder fsl_pathfinder; /** Initialized-with-defaults fsl_pathfinder instance, intended for const copy initialization. */ | | < < < | | < | < | 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 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 | }; 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 * 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 * pf, char 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 * pf, char 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: |
︙ | ︙ | |||
4545 4546 4547 4548 4549 4550 4551 | 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() */ | | < | < | 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 | 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 * pf, char const * base, char const ** pOut, fsl_size_t * 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. |
︙ | ︙ | |||
4645 4646 4647 4648 4649 4650 4651 | 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. | | | 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 | 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_ERROR 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() |
︙ | ︙ | |||
4684 4685 4686 4687 4688 4689 4690 | - 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: | < > | 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 | - 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: @code 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)... |
︙ | ︙ | |||
4722 4723 4724 4725 4726 4727 4728 | 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...} | < > | 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 | 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...} @endcode @see fsl_zip_timestamp_set_julian() @see fsl_zip_timestamp_set_unix() @see fsl_zip_end() @see fsl_zip_body() @see fsl_zip_finalize() */ |
︙ | ︙ | |||
4880 4881 4882 4883 4884 4885 4886 | 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() */ | | | | | | 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 | 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 * 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 * 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 * 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 *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. |
︙ | ︙ | |||
4957 4958 4959 4960 4961 4962 4963 | - 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.: | < > < > | 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 | - 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.: @code fsl_buffer_appendf(&myBuf, "#%06x", rc); @endcode 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() */ |
︙ | ︙ | |||
5063 5064 5065 5066 5067 5068 5069 | 0/*entryCount*/, 0/*capacity*/, \ 0/*used*/, NULL/*list*/ } /** Return the number of elements in the bag. */ | | | | | < > | | > | < > | | | | | | 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 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 | 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 *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 *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 *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 *p, fsl_id_t e); /** Returns the ID of the first element in the bag. Returns 0 if the bag is empty. Example usage: @code fsl_id_t nid; for( nid = fsl_id_bag_first(&list); nid > 0; nid = fsl_id_bag_next(&list, nid)){ ...do something... } @endcode */ FSL_EXPORT fsl_id_t fsl_id_bag_first(fsl_id_bag 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 *p, fsl_id_t e); /** Swaps the contents of the given bags. */ FSL_EXPORT void fsl_id_bag_swap(fsl_id_bag *lhs, fsl_id_bag *rhs); /** Frees any memory owned by p, but does not free p. */ FSL_EXPORT void fsl_id_bag_clear(fsl_id_bag *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 *p); /** Returns true if p contains a fossil-format merge conflict marker, else returns false. @see fsl_buffer_merge3() |
︙ | ︙ | |||
5169 5170 5171 5172 5173 5174 5175 | 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() */ | | | < < < | 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 | 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 *pPivot, fsl_buffer *pV1, fsl_buffer *pV2, fsl_buffer *pOut, unsigned int *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, |
︙ | ︙ | |||
5631 5632 5633 5634 5635 5636 5637 | - 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. | | | | 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 | - 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. |
︙ | ︙ | |||
5714 5715 5716 5717 5718 5719 5720 5721 5722 | 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). */ | > | | 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 | 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) /** |
︙ | ︙ | |||
5740 5741 5742 5743 5744 5745 5746 | 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: | < > < > | | | | | > > < < | < < | < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | < | | > > | | 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 | 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: @code 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); @endcode 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 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. All that said, i'm not yet fully convinced that a straight port of the fossil model is the best option for how we internally manage DBs, so this is subject to eventual change or removal. @see fsl_db_role_label() @see fsl_cx_db_name_for_role() */ enum fsl_dbrole_e { /** Sentinel "no role" value. */ FSL_DBROLE_NONE = 0, /** Analog to fossil's "configdb". */ FSL_DBROLE_CONFIG = 0x01, /** Analog to fossil's "repository". */ FSL_DBROLE_REPO = 0x02, /** Analog to fossil's "localdb". */ FSL_DBROLE_CKOUT = 0x04, /** Analog to fossil's "main", which is basically an alias for the first db opened. This API opens an in-memory db to act as the main db and attaches the repo/checkout/config databases separately so that it has complete control over their names and internal relationships. */ 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. */ |
︙ | ︙ | |||
5896 5897 5898 5899 5900 5901 5902 | }; typedef enum fsl_configset_e fsl_configset_e; /** Runtime-configurable flags for a fsl_cx instance. */ enum fsl_cx_flags_e { | < < < < | < | < < | 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 | }; 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 them instead of generating an error. */ 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. */ FSL_CX_F_MANIFEST_CACHE = 0x08, /** Internal use only to prevent duplicate initialization of some bits. */ |
︙ | ︙ | |||
5953 5954 5955 5956 5957 5958 5959 | /** 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. | | < < | | 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 | /** 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. ACHTUNG: this enum's values must align with those from fossil(1). */ 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 Fossil 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) |
︙ | ︙ | |||
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; | > | 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 | 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; |
︙ | ︙ | |||
6348 6349 6350 6351 6352 6353 6354 | 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(). | < < < | 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; /** |
︙ | ︙ | |||
6395 6396 6397 6398 6399 6400 6401 | */ FSL_EXPORT fsl_cx * fsl_cx_malloc(); /** Initializes a fsl_cx instance. tgt must be a pointer to NULL, e.g.: | < > < > | 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 | */ FSL_EXPORT fsl_cx * fsl_cx_malloc(); /** Initializes a fsl_cx instance. tgt must be a pointer to NULL, e.g.: @code fsl_cxt * f = NULL; // NULL is important - see below int rc = fsl_cx_init( &f, NULL ); @endcode 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. |
︙ | ︙ | |||
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 | > > > > > > > > > > > > > > > > > > > > | 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 | 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 ); /** 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. It _does_ clear any user name set via fsl_cx_user_set(). Results are undefined if !f or f's memory has not been properly initialized. */ FSL_EXPORT void fsl_cx_reset( fsl_cx * f, bool closeDatabases ); /** 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 |
︙ | ︙ | |||
6566 6567 6568 6569 6570 6571 6572 | 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 | | > | | < < | < | | < > | | | > | < < < | 6558 6559 6560 6561 6562 6563 6564 6565 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 | 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, FSL_RC_MISUSE if (!f || !src), 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. @see fsl_outputf() @see fsl_flush() */ FSL_EXPORT int fsl_output( fsl_cx * f, void const * src, fsl_size_t n ); /** Flushes f's output channel. Returns 0 on success, FSL_RC_MISUSE if !f. If the flush routine is NULL then this is a harmless no-op. @see fsl_outputf() @see fsl_output() */ FSL_EXPORT int fsl_flush( fsl_cx * 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 * f, char const * fmt, ... ); /** va_list counterpart to fsl_outputf(). */ FSL_EXPORT int fsl_outputfv( fsl_cx * 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). Fails with FSL_RC_MISUSE if !f, !repoDbFile, !*repoDbFile. Returns FSL_RC_ACCESS if f already has an opened repo db. Returns FSL_RC_NOT_FOUND if repoDbFile is not found, as this routine cannot create a new repository db. 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(). It would be nice to have a parameter specifying that the repo |
︙ | ︙ | |||
6650 6651 6652 6653 6654 6655 6656 | - 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() */ | | | | > | < < | | < > < > | 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 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 | - 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 * f, char const * repoDbFile/*, char readOnlyCurrentlyIgnored*/ ); /** If fsl_repo_open_xxx() or fsl_ckout_open_dir() has been used to open a respository 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 repository. 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. 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 * 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: @code char * u = fsl_guess_user_name(); int rc = fsl_cx_user_set(f, u); fsl_free(u); @endcode (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 ); /** |
︙ | ︙ | |||
6836 6837 6838 6839 6840 6841 6842 | 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: | < > | < > | 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 | 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: @code 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 ... } @endcode @see fsl_repo_open() @see fsl_repo_close() */ FSL_EXPORT int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt ); /** |
︙ | ︙ | |||
6872 6873 6874 6875 6876 6877 6878 | /** 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 | | | < < | > > | | | | | | | | | | | | 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 | /** 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). 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, either because 0 was passed in for dirNameLen or because dirNameLen was negative and *dirName is a NUL byte. - 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, 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 (non-0) 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... |
︙ | ︙ | |||
6946 6947 6948 6949 6950 6951 6952 | 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, | | | | | | | | 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 | 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 * 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 * f ); /** Attempts to Closes 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 * 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 |
︙ | ︙ | |||
7168 7169 7170 7171 7172 7173 7174 | (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() */ | | | | < < | | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 | (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 * 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 * 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, FSL_RC_MISUSE if !f or !sql, FSL_RC_RANGE if !*sql. */ FSL_EXPORT int fsl_cx_prepare( fsl_cx *f, fsl_stmt * tgt, char const * sql, ... ); /** va_list counterpart of fsl_cx_prepare(). */ FSL_EXPORT int fsl_cx_preparev( fsl_cx *f, fsl_stmt * tgt, 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 *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. |
︙ | ︙ | |||
7444 7445 7446 7447 7448 7449 7450 | 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: | < > < > | < < < < < < < < < | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 | 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: @code SELECT datetime(mtime), coalesce(ecomment,comment), user FROM event WHERE type='ci' ORDER BY mtime DESC LIMIT 5; @endcode 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_t{ /** 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 }; typedef enum fsl_glob_category_t fsl_glob_category_t; /** 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_t 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 if !f, !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 * f, int gtype, char const * str ); /** 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 |
︙ | ︙ | |||
7780 7781 7782 7783 7784 7785 7786 | */ #include "sqlite3.h" #if defined(__cplusplus) extern "C" { #endif | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < | | | | | > | | < < | 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 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 | */ #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 ); #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 /** 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 v1 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). */ 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. @see fsl_db_open(); @see fsl_db_close(); @see fsl_stmt */ struct fsl_db { /** |
︙ | ︙ | |||
7949 7950 7951 7952 7953 7954 7955 | (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. | < < < < | 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. */ |
︙ | ︙ | |||
8016 8017 8018 8019 8020 8021 8022 | (==fossil's infrastructure). @see fsl_db_before_commit() */ fsl_list beforeCommit; /** | < < < < < < | | | < | | | | | | | | | | | < | | | | | | | | | > | < | < < > < > < > < > < > < > | 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 7911 7912 7913 7914 7915 7916 7917 7918 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 7962 7963 7964 7965 7966 7967 7968 7969 7970 7971 7972 7973 7974 7975 7976 7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989 7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 | (==fossil's infrastructure). @see fsl_db_before_commit() */ fsl_list beforeCommit; /** 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. @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 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*/, \ 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 helps improve efficiency be removing the need for a downstream call to fsl_strlen(). Returns NULL if !f or f has no checkout 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: @code 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 ); } @endcode 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: @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 @endcode 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: @code 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...} }); @endcode */ struct fsl_stmt { /** The db which prepared this statement. */ fsl_db * db; |
︙ | ︙ | |||
8185 8186 8187 8188 8189 8190 8191 | fsl_stmt_step(). */ fsl_size_t rowCount; /** Internal state flags. */ | | < < < < < | | | | | | < | | | | 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 | fsl_stmt_step(). */ fsl_size_t rowCount; /** Internal state flags. */ int flags; /** 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*/, \ 1/*flags*/, \ NULL/*next*/, \ NULL/*allocStamp*/ \ } /** Empty-initialized fsl_stmt instance, intended for copy-constructing. */ FSL_EXPORT const fsl_stmt fsl_stmt_empty; |
︙ | ︙ | |||
8245 8246 8247 8248 8249 8250 8251 | /** 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. */ | | < | | 8085 8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 | /** 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 * 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 * 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 |
︙ | ︙ | |||
8292 8293 8294 8295 8296 8297 8298 | (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. */ | | < | < | 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 | (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 *db, fsl_stmt * tgt, char const * sql, ... ); /** va_list counterpart of fsl_db_prepare(). */ FSL_EXPORT int fsl_db_preparev( fsl_db *db, fsl_stmt * 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 |
︙ | ︙ | |||
8332 8333 8334 8335 8336 8337 8338 | 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: | < > < > | 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 8186 8187 8188 8189 8190 8191 | 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: @code 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); } @endcode 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 |
︙ | ︙ | |||
8381 8382 8383 8384 8385 8386 8387 | 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() */ | | < | | | > > > > > > > > > > > > > | 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 | 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 * 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 * 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 * st ); /** Immediately cleans up all cached statements. 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() */ FSL_EXPORT fsl_size_t fsl_db_stmt_cache_clear(fsl_db * db); /** 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 |
︙ | ︙ | |||
8438 8439 8440 8441 8442 8443 8444 | 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. */ | | | | | 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 | 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 *db, char const * sql, ... ); /** va_list counterpart to fsl_db_before_commit(). */ FSL_EXPORT int fsl_db_before_commitv( fsl_db *db, char 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 * 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. |
︙ | ︙ | |||
8476 8477 8478 8479 8480 8481 8482 | UPDATE or INSERT). @see fsl_stmt_reset() @see fsl_stmt_reset2() @see fsl_stmt_each() */ | | | 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 | UPDATE or INSERT). @see fsl_stmt_reset() @see fsl_stmt_reset2() @see fsl_stmt_each() */ FSL_EXPORT int fsl_stmt_step( fsl_stmt * 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 |
︙ | ︙ | |||
8514 8515 8516 8517 8518 8519 8520 | 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. */ | | | < | | | | | | < | | | | | | | | | | | | | | | | | | | < | | < | | | | | | | | < | | < | | < | | | | | < > < > < > < > | | | | | 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 | applies no meaning to the callbackState parameter, which gets passed as-is to the callback. See fsl_stmt_each_f() for the semantics of the callback. Returns 0 on success. Returns FSL_RC_MISUSE if !stmt or !callback. */ FSL_EXPORT int fsl_stmt_each( fsl_stmt * stmt, fsl_stmt_each_f callback, void * callbackState ); /** Resets the given statement, analog to sqlite3_reset(). Should be called one time between fsl_stmt_step() iterations when running multiple INSERTS, UPDATES, etc. via the same statement. If resetRowCounter is true then the statement's row counter (st->rowCount) is also reset to 0, else it is left unmodified. (Most use cases don't use the row counter.) Returns 0 on success, FSL_RC_MISUSE if !stmt or stmt has not been prepared, FSL_RC_DB if the underlying reset fails (in which case the error state of the stmt->db handle is updated to contain the error information). @see fsl_stmt_db() @see fsl_stmt_reset() */ FSL_EXPORT int fsl_stmt_reset2( fsl_stmt * stmt, bool resetRowCounter ); /** Equivalent to fsl_stmt_reset2(stmt, 0). */ FSL_EXPORT int fsl_stmt_reset( fsl_stmt * stmt ); /** Returns the db handle which prepared the given statement, or NULL if !stmt or stmt has not been prepared. */ FSL_EXPORT fsl_db * fsl_stmt_db( fsl_stmt * stmt ); /** Returns the SQL string used to prepare the given statement, or NULL if !stmt or stmt has not been prepared. If len is not NULL then *len is set to the length of the returned string (which is NUL-terminated). The returned bytes are owned by stmt and are invalidated when it is finalized. */ FSL_EXPORT char const * fsl_stmt_sql( fsl_stmt * stmt, fsl_size_t * len ); /** Returns the name of the given 0-based result column index, or NULL if !stmt, stmt is not prepared, or index is out out of range. The returned bytes are owned by the statement object and may be invalidated shortly after this is called, so the caller must copy the returned value if it needs to have any useful lifetime guarantees. It's a bit more complicated than this, but assume that any API calls involving the statement handle might invalidate the column name bytes. The API guarantees that the returned value is either NULL or NUL-terminated. @see fsl_stmt_param_count() @see fsl_stmt_col_count() */ FSL_EXPORT char const * fsl_stmt_col_name(fsl_stmt * stmt, int index); /** Returns the result column count for the given statement, or -1 if !stmt or it has not been prepared. Note that this value is cached when the statement is created. Note that non-fetching queries (e.g. INSERT and UPDATE) have a column count of 0. Some non-SELECT constructs, e.g. PRAGMA table_info(tname), behave like SELECT and have a positive column count. @see fsl_stmt_param_count() @see fsl_stmt_col_name() */ FSL_EXPORT int fsl_stmt_col_count( fsl_stmt const * stmt ); /** Returns the bound parameter count for the given statement, or -1 if !stmt or it has not been prepared. Note that this value is cached when the statement is created. @see fsl_stmt_col_count() @see fsl_stmt_col_name() */ FSL_EXPORT int fsl_stmt_param_count( fsl_stmt const * stmt ); /** Returns the index of the given named parameter for the given statement, or -1 if !stmt or stmt is not prepared. */ FSL_EXPORT int fsl_stmt_param_index( fsl_stmt * stmt, char const * param); /** Binds NULL to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_null( fsl_stmt * stmt, int index ); /** Equivalent to fsl_stmt_bind_null_name() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_null_name( fsl_stmt * stmt, char const * param ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_int32( fsl_stmt * stmt, int index, int32_t v ); /** Equivalent to fsl_stmt_bind_int32() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_int32_name( fsl_stmt * stmt, char const * param, int32_t v ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_int64( fsl_stmt * stmt, int index, int64_t v ); /** Equivalent to fsl_stmt_bind_int64() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_int64_name( fsl_stmt * stmt, char const * param, int64_t v ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the Fossil context's error state on error. */ FSL_EXPORT int fsl_stmt_bind_double( fsl_stmt * stmt, int index, double v ); /** Equivalent to fsl_stmt_bind_double() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_double_name( fsl_stmt * stmt, char const * param, double v ); /** Binds v to the given 1-based parameter index. Returns 0 on succcess. Sets the DB's error state on error. */ FSL_EXPORT int fsl_stmt_bind_id( fsl_stmt * stmt, int index, fsl_id_t v ); /** Equivalent to fsl_stmt_bind_id() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_id_name( fsl_stmt * stmt, char const * param, fsl_id_t v ); /** Binds the first n bytes of v as text to the given 1-based bound parameter column in the given statement. If makeCopy is true then the binding makes an copy of the data. Set makeCopy to false ONLY if you KNOW that the bytes will outlive the binding. Returns 0 on success. On error stmt's underlying db's error state is updated, hopefully with a useful error message. */ FSL_EXPORT int fsl_stmt_bind_text( fsl_stmt * stmt, int index, char const * v, fsl_int_t n, bool makeCopy ); /** Equivalent to fsl_stmt_bind_text() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_text_name( fsl_stmt * stmt, char const * param, char const * v, fsl_int_t n, bool makeCopy ); /** Binds the first n bytes of v as a blob to the given 1-based bound parameter column in the given statement. See fsl_stmt_bind_text() for the semantics of the makeCopy parameter and return value. */ FSL_EXPORT int fsl_stmt_bind_blob( fsl_stmt * stmt, int index, void const * v, fsl_size_t len, bool makeCopy ); /** Equivalent to fsl_stmt_bind_blob() but binds to a named parameter. */ FSL_EXPORT int fsl_stmt_bind_blob_name( fsl_stmt * stmt, char const * param, void const * v, fsl_int_t len, bool makeCopy ); /** Gets an integer value from the given 0-based result set column, assigns *v to that value, and returns 0 on success. Returns FSL_RC_RANGE if index is out of range for stmt. */ FSL_EXPORT int fsl_stmt_get_int32( fsl_stmt * stmt, int index, int32_t * v ); /** Gets an integer value from the given 0-based result set column, assigns *v to that value, and returns 0 on success. Returns FSL_RC_RANGE if index is out of range for stmt. */ FSL_EXPORT int fsl_stmt_get_int64( fsl_stmt * stmt, int index, int64_t * v ); /** The fsl_id_t counterpart of fsl_stmt_get_int32(). Depending on the sizeof(fsl_id_t), it behaves as one of fsl_stmt_get_int32() or fsl_stmt_get_int64(). */ FSL_EXPORT int fsl_stmt_get_id( fsl_stmt * stmt, int index, fsl_id_t * v ); /** Convenience form of fsl_stmt_get_id() which returns the value directly but cannot report errors. It returns -1 on error, but that is not unambiguously an error value. */ FSL_EXPORT fsl_id_t fsl_stmt_g_id( fsl_stmt * stmt, int index ); /** Convenience form of fsl_stmt_get_int32() which returns the value directly but cannot report errors. It returns 0 on error, but that is not unambiguously an error. */ FSL_EXPORT int32_t fsl_stmt_g_int32( fsl_stmt * stmt, int index ); /** Convenience form of fsl_stmt_get_int64() which returns the value directly but cannot report errors. It returns 0 on error, but that is not unambiguously an error. */ FSL_EXPORT int64_t fsl_stmt_g_int64( fsl_stmt * stmt, int index ); /** Convenience form of fsl_stmt_get_double() which returns the value directly but cannot report errors. It returns 0 on error, but that is not unambiguously an error. */ FSL_EXPORT double fsl_stmt_g_double( fsl_stmt * stmt, int index ); /** Convenience form of fsl_stmt_get_text() which returns the value directly but cannot report errors. It returns NULL on error, but that is not unambiguously an error because it also returns NULL if the column contains an SQL NULL value. If outLen is not NULL then it is set to the byte length of the returned string. */ FSL_EXPORT char const * fsl_stmt_g_text( fsl_stmt * stmt, int index, fsl_size_t * outLen ); /** Gets double value from the given 0-based result set column, assigns *v to that value, and returns 0 on success. Returns FSL_RC_RANGE if index is out of range for stmt. */ FSL_EXPORT int fsl_stmt_get_double( fsl_stmt * stmt, int index, double * v ); /** Gets a string value from the given 0-based result set column, assigns *out (if out is not NULL) to that value, assigns *outLen (if outLen is not NULL) to *out's length in bytes, and returns 0 on success. Ownership of the string memory is unchanged - it is owned by the statement and the caller should immediately copy it if it will be needed for much longer. Returns FSL_RC_RANGE if index is out of range for stmt. */ FSL_EXPORT int fsl_stmt_get_text( fsl_stmt * stmt, int index, char const **out, fsl_size_t * outLen ); /** The Blob counterpart of fsl_stmt_get_text(). Identical to that function except that its output result (3rd paramter) type differs, and it fetches the data as a raw blob, without any sort of string interpretation. Returns FSL_RC_RANGE if index is out of range for stmt. */ FSL_EXPORT int fsl_stmt_get_blob( fsl_stmt * stmt, int index, void const **out, fsl_size_t * outLen ); /** Executes multiple SQL statements, ignoring any results they might collect. Returns 0 on success, non-0 on error. On error db->error might be updated to report the problem. */ FSL_EXPORT int fsl_db_exec_multi( fsl_db * db, const char * sql, ...); /** va_list counterpart of db_exec_multi(). */ FSL_EXPORT int fsl_db_exec_multiv( fsl_db * db, const char * sql, va_list args); /** Executes a single SQL statement, skipping over any results it may have. Returns 0 on success. On error db's error state may be updated. */ FSL_EXPORT int fsl_db_exec( fsl_db * db, char const * sql, ... ); /** va_list counterpart of fs_db_exec(). */ FSL_EXPORT int fsl_db_execv( fsl_db * db, char const * sql, va_list args ); /** Begins a transaction on the given db. Nested transactions are not directly supported but the db handle keeps track of open/close counts, such that fsl_db_transaction_end() will not actually do anything until the transaction begin/end counter goes to 0. Returns FSL_RC_MISUSE if !db or the db is not connected, else the result of the underlying db call(s). Transactions are an easy way to implement "dry-run" mode for some types of applications. For example: @code char dryRunMode = ...; fsl_db_transaction_begin(db); ...do your stuff... fsl_db_transaction_end(db, dryRunMode ? 1 : 0); @endcode Here's a tip for propagating error codes when using transactions: @code ... if(rc) fsl_db_transaction_end(db, 1); else rc = fsl_db_transaction_end(db, 0); @endcode That ensures that we propagate rc in the face of a rollback but we also capture the rc for a commit (which might yet fail). Note that a rollback in and of itself is not an error (though it also might fail, that would be "highly unusual" and indicative of other problems), and we certainly don't want to overwrite that precious non-0 rc with a successful return result from a rollback (which would, in effect, hide the error from the client). */ FSL_EXPORT int fsl_db_transaction_begin(fsl_db * db); /** Equivalent to fsl_db_transaction_end(db, 0). */ FSL_EXPORT int fsl_db_transaction_commit(fsl_db * db); /** Equivalent to fsl_db_transaction_end(db, 1). */ FSL_EXPORT int fsl_db_transaction_rollback(fsl_db * db); /** Forces a rollback of any pending transaction in db, regardless of the internal transaction begin/end counter. Returns FSL_RC_MISUSE if !db or db is not opened, else returns the value of the underlying ROLLBACK call. This also re-sets/frees any transaction-related state held by db (e.g. db->beforeCommit). Use with care, as this mucks about with db state in a way which is not all that pretty and it may confuse downstream code. Returns 0 on success. */ FSL_EXPORT int fsl_db_rollback_force(fsl_db * db); /** Decrements the transaction counter incremented by fsl_db_transaction_begin() and commits or rolls back the transaction if the counter goes to 0. If doRollback is true then this rolls back (or schedules a |
︙ | ︙ | |||
8908 8909 8910 8911 8912 8913 8914 | Unfortunate low-level co-dependency: if db->f is not NULL and (db->role & FSL_DBROLE_REPO) then this function may perform extra repository-related post-processing on any commit, and checking the result code is particularly important for those cases. */ | | | | | | 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 | Unfortunate low-level co-dependency: if db->f is not NULL and (db->role & FSL_DBROLE_REPO) then this function may perform extra repository-related post-processing on any commit, and checking the result code is particularly important for those cases. */ FSL_EXPORT int fsl_db_transaction_end(fsl_db * db, bool doRollback); /** Returns the given db's current transaction depth. If the value is negative, its absolute value represents the depth but indicates that a rollback is pending. If it is positive, the transaction is still in a "good" state. If it is 0, no transaction is active. */ FSL_EXPORT int fsl_db_transaction_level(fsl_db * db); /** Runs the given SQL query on the given db and returns true if the query returns any rows, else false. Returns 0 for any error as well. */ FSL_EXPORT bool fsl_db_exists(fsl_db * db, char const * sql, ... ); /** va_list counterpart of fsl_db_exists(). */ FSL_EXPORT bool fsl_db_existsv(fsl_db * db, char const * sql, va_list args ); /** Runs a fetch-style SQL query against DB and returns the first column of the first result row via *rv. If the query returns no rows, *rv is not modified. The intention is that the caller sets *rv to his preferred default (or sentinel) value before calling this. |
︙ | ︙ | |||
9171 9172 9173 9174 9175 9176 9177 | If localTime is true then the value is converted to the local time, otherwise it is not. @see fsl_db_unix_to_iso8601() @see fsl_julian_to_iso8601() @see fsl_iso8601_to_julian() */ | | | | < | 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 | If localTime is true then the value is converted to the local time, otherwise it is not. @see fsl_db_unix_to_iso8601() @see fsl_julian_to_iso8601() @see fsl_iso8601_to_julian() */ FSL_EXPORT char * fsl_db_julian_to_iso8601( fsl_db * db, double j, char msPrecision, char localTime ); /** Returns the given Julian date value formatted as an ISO8601 string (with a fractional seconds part if msPrecision is true, else without it). Returns NULL if !db, db is not connected, j is less than 0, or on allocation error. The returned memory must eventually be freed using fsl_free(). If localTime is true then the value is converted to the local time, otherwise it is not. @see fsl_db_julian_to_iso8601() @see fsl_julian_to_iso8601() @see fsl_iso8601_to_julian() */ FSL_EXPORT char * fsl_db_unix_to_iso8601( fsl_db * db, fsl_time_t j, char localTime ); /** Returns the current time in Julian Date format. Returns a negative value if !db or db is not opened. */ FSL_EXPORT double fsl_db_julian_now(fsl_db * db); |
︙ | ︙ | |||
9266 9267 9268 9269 9270 9271 9272 | - If FSL_OPEN_F_SCHEMA_VALIDATE is set in openFlags then the db is validated to see if it has a fossil schema. If that validation fails, FSL_RC_REPO_NEEDS_REBUILD or FSL_RC_NOT_A_REPO will be returned and db's error state will be updated. db->f does not need to be set for that check to work. | > | | > | > > > > > > > > > > > > > > > > > > > > > > | | | > | < | | | | | < | | | 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 9139 9140 9141 9142 9143 9144 9145 9146 9147 9148 9149 9150 9151 9152 9153 9154 9155 9156 9157 9158 9159 9160 9161 9162 9163 9164 9165 9166 9167 9168 9169 9170 9171 9172 9173 9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184 9185 9186 9187 9188 9189 9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203 9204 9205 9206 9207 9208 9209 9210 9211 9212 | - If FSL_OPEN_F_SCHEMA_VALIDATE is set in openFlags then the db is validated to see if it has a fossil schema. If that validation fails, FSL_RC_REPO_NEEDS_REBUILD or FSL_RC_NOT_A_REPO will be returned and db's error state will be updated. db->f does not need to be set for that check to work. The following SQL functions get registered with the db if db->f is not NULL when this function is called: - NOW() returns the current time as an integer, as per time(2). - FSL_USER() returns the current value of fsl_cx_user_get(), or NULL if that is not set. - FSL_CONTENT(INTEGER|STRING) returns the undeltified, uncompressed content for the blob record with the given ID (if the argument is an integer) or symbolic name (as per fsl_sym_to_rid()), as per fsl_content_get(). If the argument does not resolve to an in-repo blob, a db-level error is triggered. If passed an integer, no validation is done on its validity, but such checking can be enforced by instead passing the the ID as a string in the form "rid:ID". Both cases will result in an error if the RID is not found, but the error reporting is arguably slightly better for the "rid:ID" case. - FSL_SYM2RID(STRING) returns a blob RID for the given symbol, as per fsl_sym_to_rid(). Triggers an SQL error if fsl_sym_to_rid() fails. - FSL_DIRPART(STRING[, BOOL=0]) behaves like fsl_file_dirpart(), returning the result as a string unless it is empty, in which case the result is an SQL NULL. Note that functions described as "triggering a db error" will propagate that error, such that fsl_db_err_get() can report it to the client. @see fsl_db_close() @see fsl_db_prepare() @see fsl_db_malloc() */ FSL_EXPORT int fsl_db_open( fsl_db * db, char const * dbFile, int openFlags ); /** Closes the given db handle and frees any resources owned by db. Returns 0 on success. If db was allocated using fsl_db_malloc() (as determined by examining db->allocStamp) then this routine also fsl_free()s it, otherwise it is assumed to either be on the stack or part of a larger struct and is not freed. If db has any pending transactions, they are rolled back by this function. */ FSL_EXPORT int fsl_db_close( fsl_db * db ); /** If db is an opened db handle, this registers a debugging function with the db which traces all SQL to the given FILE handle (defaults to stdout if outStream is NULL). This mechanism is only intended for debugging and exploration of how Fossil works. Tracing is often as easy way to ensure that a given code block is getting run. As a special case, if db->f is not NULL _before_ it is is fsl_db_open()ed, then this function automatically gets installed if the SQL tracing option is enabled for that fsl_cx instance before the db is opened. This is a no-op if !db or db is not opened. */ FSL_EXPORT void fsl_db_sqltrace_enable( fsl_db * db, FILE * outStream ); /** Returns the row ID of the most recent insertion, or -1 if !db, db is not connected, or 0 if no inserts have been performed. */ FSL_EXPORT fsl_id_t fsl_db_last_insert_id(fsl_db *db); /** Returns non-0 (true) if the database (which must be open) table identified by zTableName has a column named zColName (case-sensitive), else returns 0. */ FSL_EXPORT char fsl_db_table_has_column( fsl_db * db, char const *zTableName, char const *zColName ); /** If a db name has been associated with db then it is returned, otherwise NULL is returned. A db has no name by default, but fsl_cx-used ones get their database name assigned to them (e.g. "main" for the main db). */ FSL_EXPORT char const * fsl_db_name(fsl_db const * db); /** Returns a db name string for the given fsl_db_role value. The string is static, guaranteed to live as long as the app. It returns NULL (or asserts in debug builds) if passed FSL_DBROLE_NONE or some value out of range for the enum. |
︙ | ︙ | |||
9367 9368 9369 9370 9371 9372 9373 9374 9375 9376 9377 9378 9379 9380 9381 | /** The fsl_stmt counterpart of fsl_db_malloc(). See that function for when you might want to use this and a caveat involving the allocStamp member of the returned value. fsl_stmt_finalize() will free statements created with this function. */ FSL_EXPORT fsl_stmt * fsl_stmt_malloc(); /** ATTACHes the file zDbName to db using the databbase name zLabel. Returns 0 on success. Returns FSL_RC_MISUSE if any argument is NULL or any string argument starts with a NUL byte, else it returns the result of fsl_db_exec() which attaches the db. On db-level errors db's error state will be updated. */ | > | < | > < > < > | < | | | | | | < > | | | | | | | 9231 9232 9233 9234 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 9249 9250 9251 9252 9253 9254 9255 9256 9257 9258 9259 9260 9261 9262 9263 9264 9265 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 9279 9280 9281 9282 9283 9284 9285 9286 9287 9288 9289 9290 9291 9292 9293 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 | /** The fsl_stmt counterpart of fsl_db_malloc(). See that function for when you might want to use this and a caveat involving the allocStamp member of the returned value. fsl_stmt_finalize() will free statements created with this function. */ FSL_EXPORT fsl_stmt * fsl_stmt_malloc(); /** ATTACHes the file zDbName to db using the databbase name zLabel. Returns 0 on success. Returns FSL_RC_MISUSE if any argument is NULL or any string argument starts with a NUL byte, else it returns the result of fsl_db_exec() which attaches the db. On db-level errors db's error state will be updated. */ FSL_EXPORT int fsl_db_attach(fsl_db * db, const char *zDbName, const char *zLabel); /** The converse of fsl_db_detach(). Must be passed the same arguments which were passed as the 1st and 3rd arguments to fsl_db_attach(). Returns 0 on success, FSL_RC_MISUSE if !db, !zLabel, or !*zLabel, else it returns the result of the underlying fsl_db_exec() call. */ FSL_EXPORT int fsl_db_detach(fsl_db * db, const char *zLabel); /** Expects fmt to be a SELECT-style query. For each row in the query, the first column is fetched as a string and appended to the tgt list. Returns 0 on success, FSL_RC_MISUSE if !db, !tgt, or !fmt, any number of potential FSL_RC_OOM or db-related errors. Results rows with a NULL value (resulting from an SQL NULL) are added to the list as NULL entries. Each entry appended to the list is a (char *) which must be freed using fsl_free(). To easiest way to clean up the list and its contents is: @code fsl_list_visit_free(tgt,...); @endcode On error the list may be partially populated. Complete example: @code fsl_list li = fsl_list_empty; int rc = fsl_db_select_slist(db, &li, "SELECT uuid FROM blob WHERE rid<20"); if(!rc){ fsl_size_t i; for(i = 0;i < li.used; ++i){ char const * uuid = (char const *)li.list[i]; fsl_fprintf(stdout, "UUID: %s\n", uuid); } } fsl_list_visit_free(&li, 1); @endcode Of course fsl_list_visit() may be used to traverse the list as well, as long as the visitor expects (char [const]*) list elements. */ FSL_EXPORT int fsl_db_select_slist( fsl_db * db, fsl_list * tgt, char const * fmt, ... ); /** The va_list counterpart of fsl_db_select_slist(). */ FSL_EXPORT int fsl_db_select_slistv( fsl_db * db, fsl_list * tgt, char const * fmt, va_list args ); /** Returns n bytes of random lower-case hexidecimal characters using the given db as its data source, plus a terminating NUL byte. The returned memory must eventually be freed using fsl_free(). Returns NULL if !db, !n, or on a db-level error. */ FSL_EXPORT char * fsl_db_random_hex(fsl_db * db, fsl_size_t n); /** Returns the "number of database rows that were changed or inserted or deleted by the most recently completed SQL statement" (to quote the underlying APIs). Returns 0 if !db or if db is not opened. See: https://sqlite.org/c3ref/changes.html */ FSL_EXPORT int fsl_db_changes_recent(fsl_db * db); /** Returns "the number of row changes caused by INSERT, UPDATE or DELETE statements since the database connection was opened" (to quote the underlying APIs). Returns 0 if !db or if db is not opened. See; https://sqlite.org/c3ref/total_changes.html */ FSL_EXPORT int fsl_db_changes_total(fsl_db * db); /** Initializes the given database file. zFilename is the name of the db file. It is created if needed, but any directory components are not created. zSchema is the base schema to install. The following arguments may be (char const *) SQL code, each of which gets run against the db after the main |
︙ | ︙ | |||
9505 9506 9507 9508 9509 9510 9511 | FSL_DBROLE_TEMP. Potential TODO: this is a bit of a wonky interface. Consider changing it to eliminate the role argument, which is only really needed if we have duplicate table names across attached dbs or if we internally mess up and write a table to the wrong db. */ | | | | 9369 9370 9371 9372 9373 9374 9375 9376 9377 9378 9379 9380 9381 9382 9383 9384 9385 9386 9387 9388 9389 | FSL_DBROLE_TEMP. Potential TODO: this is a bit of a wonky interface. Consider changing it to eliminate the role argument, which is only really needed if we have duplicate table names across attached dbs or if we internally mess up and write a table to the wrong db. */ FSL_EXPORT bool fsl_db_table_exists(fsl_db * db, fsl_dbrole_e whichDb, const char *zTable); /** The elipsis counterpart of fsl_stmt_bind_fmtv(). */ FSL_EXPORT int fsl_stmt_bind_fmt( fsl_stmt * st, char const * fmt, ... ); /** Binds a series of values using a formatting string. The string may contain the following characters, each of which refers to the next argument in the args list: |
︙ | ︙ | |||
9663 9664 9665 9666 9667 9668 9669 | /** The hash string of the initial MD5 state. Used as an optimization for some places where we need an MD5 but know it will not hash any data. Equivalent to what the md5sum command outputs for empty input: | < > < > < > < > | 9527 9528 9529 9530 9531 9532 9533 9534 9535 9536 9537 9538 9539 9540 9541 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 9557 9558 9559 9560 9561 9562 9563 | /** The hash string of the initial MD5 state. Used as an optimization for some places where we need an MD5 but know it will not hash any data. Equivalent to what the md5sum command outputs for empty input: @code # md5sum < /dev/null d41d8cd98f00b204e9800998ecf8427e - @endcode */ #define FSL_MD5_INITIAL_HASH "d41d8cd98f00b204e9800998ecf8427e" /** Holds state for MD5 calculations. It is intended to be used like this: @code unsigned char digest[16]; char hex[FSL_STRLEN_MD5+1]; fsl_md5_cx cx = fsl_md5_cx_empty; // alternately: fsl_md5_init(&cx); ...call fsl_md5_update(&cx,...) any number of times to ...incrementally calculate the hash. fsl_md5_final(&cx, digest); // ends the calculation fsl_md5_digest_to_base16(digest, hex); // digest now contains the raw 16-byte MD5 digest. // hex now contains the 32-byte MD5 + a trailing NUL @endcode */ struct fsl_md5_cx { int isInit; uint32_t buf[4]; uint32_t bits[2]; unsigned char in[64]; }; |
︙ | ︙ | |||
9818 9819 9820 9821 9822 9823 9824 | #if FSL_SHA1_HARDENED typedef void(*fsl_sha1h_collision_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); #endif /** Holds state for SHA1 calculations. It is intended to be used like this: | < > < > | 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 9700 9701 9702 9703 9704 9705 9706 9707 | #if FSL_SHA1_HARDENED typedef void(*fsl_sha1h_collision_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); #endif /** Holds state for SHA1 calculations. It is intended to be used like this: @code unsigned char digest[20] char hex[FSL_STRLEN_SHA1+1]; fsl_sha1_cx cx = fsl_sha1_cx_empty; // alternately: fsl_sha1_init(&cx) ...call fsl_sha1_update(&cx,...) any number of times to ...incrementally calculate the hash. fsl_sha1_final(&cx, digest); // ends the calculation fsl_sha1_digest_to_base16(digest, hex); // digest now contains the raw 20-byte SHA1 digest. // hex now contains the 40-byte SHA1 + a trailing NUL @endcode */ struct fsl_sha1_cx { #if FSL_SHA1_HARDENED uint64_t total; uint32_t ihv[5]; unsigned char buffer[64]; int bigendian; |
︙ | ︙ | |||
9999 10000 10001 10002 10003 10004 10005 | /** Type for holding SHA3 processing state. Each instance must be initialized with fsl_sha3_init(), populated with fsl_sha3_update(), and "sealed" with fsl_sha3_end(). Sample usage: | < > < > | 9863 9864 9865 9866 9867 9868 9869 9870 9871 9872 9873 9874 9875 9876 9877 9878 9879 9880 9881 9882 9883 | /** Type for holding SHA3 processing state. Each instance must be initialized with fsl_sha3_init(), populated with fsl_sha3_update(), and "sealed" with fsl_sha3_end(). Sample usage: @code fsl_sha3_cx cx; fsl_sha3_init(&cx, FSL_SHA3_DEFAULT); fsl_sha3_update(&cx, memory, lengthOfMemory); fsl_sha3_end(&cx); printf("Hash = %s\n", (char const *)cx.hex); @endcode After fsl_sha3_end() is called cx.hex contains the hex-string forms of the digest. Note that fsl_sha3_update() may be called an arbitrary number of times to feed in chunks of memory (e.g. to stream in arbitrarily large data). */ struct fsl_sha3_cx { |
︙ | ︙ | |||
10420 10421 10422 10423 10424 10425 10426 | determined if a given card type is legal for a given value of this member. APIs which add/set cards use that to determine if the operation requested by the client is semantically legal. */ fsl_satype_e type; /** | | > > > > > > | 10284 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 10306 10307 | determined if a given card type is legal for a given value of this member. APIs which add/set cards use that to determine if the operation requested by the client is semantically legal. */ fsl_satype_e type; /** DB repo.blob.rid value. Normally set by fsl_deck_parse(). */ fsl_id_t rid; /** Gets set by fsl_deck_parse() to the hash/UUID of the manifest it parsed. */ fsl_uuid_str uuid; /** The Fossil context responsible for this deck. Though this data type is normally, at least conceptually, free of any given fossil context, many related algorithms need a context in order to perform db- or caching-related work, as well as to simplify error message propagation. We store this as a struct member to keep all such algorithms from redundantly requiring both pieces of |
︙ | ︙ | |||
10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 | /** Initialized-with-defaults fsl_deck structure, intended for in-struct and const copy initialization. */ #define fsl_deck_empty_m { \ FSL_SATYPE_ANY /*type*/, \ 0/*rid*/, \ NULL/*f*/, \ {/*A*/ NULL /* name */, \ NULL /* tgt */, \ NULL /* src */}, \ {/*B*/ NULL /*uuid*/, \ NULL /*baseline*/}, \ NULL /* C */, \ | > | 10500 10501 10502 10503 10504 10505 10506 10507 10508 10509 10510 10511 10512 10513 10514 | /** Initialized-with-defaults fsl_deck structure, intended for in-struct and const copy initialization. */ #define fsl_deck_empty_m { \ FSL_SATYPE_ANY /*type*/, \ 0/*rid*/, \ NULL/*uuid*/, \ NULL/*f*/, \ {/*A*/ NULL /* name */, \ NULL /* tgt */, \ NULL /* src */}, \ {/*B*/ NULL /*uuid*/, \ NULL /*baseline*/}, \ NULL /* C */, \ |
︙ | ︙ | |||
10679 10680 10681 10682 10683 10684 10685 | state, but does not free() deck. Is a no-op if deck is NULL. As a special case, the (allocStamp, f) members of deck are kept intact. @see fsl_deck_finalize() @see fsl_deck_malloc() @see fsl_deck_clean2() */ | | | | 10550 10551 10552 10553 10554 10555 10556 10557 10558 10559 10560 10561 10562 10563 10564 10565 10566 10567 10568 10569 10570 10571 10572 10573 10574 10575 10576 10577 10578 10579 | state, but does not free() deck. Is a no-op if deck is NULL. As a special case, the (allocStamp, f) members of deck are kept intact. @see fsl_deck_finalize() @see fsl_deck_malloc() @see fsl_deck_clean2() */ FSL_EXPORT void fsl_deck_clean(fsl_deck *deck); /** A variant of fsl_deck_clean() which "returns" its content buffer for re-use by transferring, after ensuring proper cleanup of its internals, its own content buffer's bytes into the given target buffer. Note that decks created "manually" do not have any content buffer contents, but those loaded via fsl_deck_load_rid() do. This function will fsl_buffer_swap() the contents of the given buffer (if any) with its own buffer, clean up its newly-acquired memory (tgt's previous contents, if any), and fsl_buffer_reuse() the output buffer. If tgt is NULL, this behaves exactly like fsl_deck_clean(). */ FSL_EXPORT void fsl_deck_clean2(fsl_deck *deck, fsl_buffer *tgt); /** Frees all memory owned by deck (see fsl_deck_clean()). If deck was allocated using fsl_deck_malloc() then this function fsl_free()'s it, otherwise it does not free it. @see fsl_deck_malloc() |
︙ | ︙ | |||
10729 10730 10731 10732 10733 10734 10735 | wiki pages this is their normal name (e.g. "MyWikiPage"). For events and tickets it is their full 40-byte UUID. uuidSrc is the UUID of the attachment blob itself. If it is NULL or empty then this card indicates that the attachment will be "deleted" (insofar as anything is ever deleted in Fossil). */ | | | 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 | wiki pages this is their normal name (e.g. "MyWikiPage"). For events and tickets it is their full 40-byte UUID. uuidSrc is the UUID of the attachment blob itself. If it is NULL or empty then this card indicates that the attachment will be "deleted" (insofar as anything is ever deleted in Fossil). */ FSL_EXPORT int fsl_deck_A_set( fsl_deck * mf, char const * filename, char const * target, fsl_uuid_cstr uuidSrc); /** Sets or unsets (if uuidBaseline is NULL or empty) the B-card for the given manifest to a copy of the given UUID. Returns 0 on success, FSL_RC_MISUSE if !mf, FSL_RC_OOM on allocation |
︙ | ︙ | |||
10754 10755 10756 10757 10758 10759 10760 | fsl_card_is_legal(mf->type,...)). Sidebar: the ability to unset this card is unusual within this API, and is a requirement the library-internal delta manifest creation process. Most of the card-setting APIs, even when they are described as working like this one, do not accept NULL hash values. */ | | | | | | 10625 10626 10627 10628 10629 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 10658 10659 10660 10661 10662 10663 10664 10665 10666 10667 10668 10669 10670 10671 10672 10673 | fsl_card_is_legal(mf->type,...)). Sidebar: the ability to unset this card is unusual within this API, and is a requirement the library-internal delta manifest creation process. Most of the card-setting APIs, even when they are described as working like this one, do not accept NULL hash values. */ FSL_EXPORT int fsl_deck_B_set( fsl_deck * mf, fsl_uuid_cstr uuidBaseline); /** Semantically identical to fsl_deck_B_set() but sets the C-card and does not place a practical limit on the comment's length. comment must be the comment text for the change being applied. If the given length is negative, fsl_strlen() is used to determine its length. */ FSL_EXPORT int fsl_deck_C_set( fsl_deck * mf, char const * comment, fsl_int_t cardLen); /** Sets mf's D-card as a Julian Date value. Returns FSL_RC_MISUSE if !mf, FSL_RC_RANGE if date is negative, FSL_RC_TYPE if a D-card is not valid for the given deck, else 0. Passing a value of 0 effectively unsets the card. */ FSL_EXPORT int fsl_deck_D_set( fsl_deck * mf, double date); /** Sets the E-card in the given deck. date may not be negative - use fsl_db_julian_now() or fsl_julian_now() to get a default time if needed. Retursn FSL_RC_MISUSE if !mf or !uuid, FSL_RC_RANGE if date is not positive, FSL_RC_RANGE if uuid is not a valid UUID string. Note that the UUID for an event, unlike most other UUIDs, need not be calculated - it may be a random hex string, but it must pass the fsl_is_uuid() test. Use fsl_db_random_hex() to generate random UUIDs. When editing events, e.g. using the HTML UI, only the most recent event with the same UUID is shown. So when updating events, be sure to apply the same UUID to the edited copies before saving them. */ FSL_EXPORT int fsl_deck_E_set( fsl_deck * mf, double date, fsl_uuid_cstr uuid); /** Adds a new F-card to the given deck. The uuid argument is required to be NULL or pass the fsl_is_uuid() test. The name must be a "simplified path name" (as determined by fsl_is_simple_pathname()), or FSL_RC_RANGE is returned. Note that a NULL uuid is only valid when constructing a delta manifest, and this routine will return |
︙ | ︙ | |||
10916 10917 10918 10919 10920 10921 10922 | - Moves d->uuid into d->P - Clears d->rid - Clears any other members which need to be (re)set by the new child/derived version. - It specifically keeps d->F intact OR creates a new one (see below). Returns 0 on success, FSL_RC_OOM on an allocation error, | | < | | | | | 10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 10803 10804 10805 10806 10807 10808 10809 10810 10811 10812 10813 10814 10815 10816 10817 10818 10819 10820 10821 | - Moves d->uuid into d->P - Clears d->rid - Clears any other members which need to be (re)set by the new child/derived version. - It specifically keeps d->F intact OR creates a new one (see below). Returns 0 on success, FSL_RC_OOM on an allocation error, FSL_RC_MISUSE if d->uuid is NULL or d->rid<=0. If d->type is not FSL_SATYPE_CHECKIN, FSL_RC_TYPE is returned. On error, d may be left in an inconsistent state and must not be used further except to pass it to fsl_deck_finalize(). The intention of this function is to simplify creation of decks which are to be used for creating checkins without requiring a checkin. To avoid certain corner cases, this function does not allow creation of delta manifests. If d has a B-card then it is a delta. This function clears its B-card and recreates the F-card list using the B-card's F-card list and any F-cards from the current delta. In other words, it creates a new baseline manifest. TODO: extend this to support other inheritable deck types, e.g. wiki, forum posts, and technotes. @see fsl_deck_F_set_content() */ FSL_EXPORT int fsl_deck_derive(fsl_deck * d); /** Callback type for use with fsl_deck_F_foreach() and friends. Implementations must return 0 on success, FSL_RC_BREAK to abort looping without an error, and any other value on error. */ typedef int (*fsl_card_F_visitor_f)(fsl_card_F const * fc, |
︙ | ︙ | |||
10983 10984 10985 10986 10987 10988 10989 | On success 0 is returned and *f is assigned to the next F-card. If *f is NULL when returning 0 then the end of the list has been reached (fsl_deck_F_rewind() can be used to re-set it). Example usage: | < > < > | 10853 10854 10855 10856 10857 10858 10859 10860 10861 10862 10863 10864 10865 10866 10867 10868 10869 10870 10871 10872 | On success 0 is returned and *f is assigned to the next F-card. If *f is NULL when returning 0 then the end of the list has been reached (fsl_deck_F_rewind() can be used to re-set it). Example usage: @code int rc; fsl_card_F const * fc = NULL; rc = fsl_deck_F_rewind(d); if(!rc) while( !(rc=fsl_deck_F_next(d, &fc)) && fc) {...} @endcode Note that files which were deleted in a given version are not recorded in baseline manifests but are in deltas. To avoid inconsistencies, this routine does NOT include deleted files in its results, regardless of whether d is a baseline or delta. (It used to, but that turned out to be a design flaw.) |
︙ | ︙ | |||
11088 11089 11090 11091 11092 11093 11094 | FSL_EXPORT int fsl_card_F_content( fsl_cx * f, fsl_card_F const * fc, fsl_buffer * dest ); /** Sets the 'G' card on a forum-post deck to a copy of the given UUID. */ | | | | | | | | | | | | | < | < < < < < < | | | | | | | | | | 10958 10959 10960 10961 10962 10963 10964 10965 10966 10967 10968 10969 10970 10971 10972 10973 10974 10975 10976 10977 10978 10979 10980 10981 10982 10983 10984 10985 10986 10987 10988 10989 10990 10991 10992 10993 10994 10995 10996 10997 10998 10999 11000 11001 11002 11003 11004 11005 11006 11007 11008 11009 11010 11011 11012 11013 11014 11015 11016 11017 11018 11019 11020 11021 11022 11023 11024 11025 11026 11027 11028 11029 11030 11031 11032 11033 11034 11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047 11048 11049 11050 11051 11052 11053 11054 11055 11056 11057 11058 11059 11060 11061 11062 11063 11064 11065 11066 11067 11068 11069 11070 11071 11072 11073 11074 11075 11076 11077 11078 11079 11080 11081 11082 11083 11084 11085 11086 11087 11088 11089 11090 11091 11092 11093 11094 11095 11096 11097 11098 11099 11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 | FSL_EXPORT int fsl_card_F_content( fsl_cx * f, fsl_card_F const * fc, fsl_buffer * dest ); /** Sets the 'G' card on a forum-post deck to a copy of the given UUID. */ FSL_EXPORT int fsl_deck_G_set( fsl_deck * mf, fsl_uuid_cstr uuid); /** Sets the 'H' card on a forum-post deck to a copy of the given comment. If cardLen is negative then fsl_strlen() is used to calculate its length. */ FSL_EXPORT int fsl_deck_H_set( fsl_deck * mf, char const * comment, fsl_int_t cardLen); /** Sets the 'I' card on a forum-post deck to a copy of the given UUID. */ FSL_EXPORT int fsl_deck_I_set( fsl_deck * mf, fsl_uuid_cstr uuid); /** Adds a J-card to the given deck, setting/updating the given ticket property key to the given value. The key is required but the value is optional (may be NULL). If isAppend then the value is appended to any existing value, otherwise it replaces any existing value. It is currently unclear whether it is legal to include multiple J cards for the same key in the same control artifact, in particular if their isAppend values differ. Returns 0 on success, FSL_RC_MISUSE if !mf or !key, FSL_RC_RANGE if !*field, FSL_RC_TYPE if mf is of a type for which J cards are not legal (see fsl_card_is_legal()), FSL_RC_OOM on allocation error. */ FSL_EXPORT int fsl_deck_J_add( fsl_deck * mf, char isAppend, char const * key, char const * value ); /** Semantically identical fsl_deck_B_set() but sets the K-card and does not accept a NULL value. uuid must be the UUID of the ticket this change is being applied to. */ FSL_EXPORT int fsl_deck_K_set( fsl_deck * mf, fsl_uuid_cstr uuid); /** Semantically identical fsl_deck_B_set() but sets the L-card. title must be the wiki page title text of the wiki page this change is being applied to. */ FSL_EXPORT int fsl_deck_L_set( fsl_deck * mf, char const *title, fsl_int_t len); /** Adds the given UUID as an M-card entry. Returns 0 on success, or: FSL_RC_MISUSE if !mf or !uuid FSL_RC_TYPE if fsl_deck_check_type(mf,'M') returns false. FSL_RC_RANGE if !fsl_is_uuid(uuid). FSL_RC_OOM if memory allocation fails while adding the entry. */ FSL_EXPORT int fsl_deck_M_add( fsl_deck * mf, fsl_uuid_cstr uuid ); /** Semantically identical to fsl_deck_B_set() but sets the N card. mimeType must be the content mime type for comment text of the change being applied. */ FSL_EXPORT int fsl_deck_N_set( fsl_deck * mf, char const *mimeType, fsl_int_t len); /** Adds the given UUID as a parent of the given change record. If len is less than 0 then fsl_strlen(parentUuid) is used to determine its length. Returns FSL_RC_MISUE if !mf, !parentUuid, or !*parentUuid. Returns FSL_RC_RANGE if parentUuid is not 40 bytes long. The first P-card added to a deck MUST be the UUID of its primary parent (one which was not involved in a merge operation). All others (from merges) are considered "non-primary." */ FSL_EXPORT int fsl_deck_P_add( fsl_deck * mf, fsl_uuid_cstr parentUuid); /** If d contains a P card with the given index, this returns the RID corresponding to the UUID at that index. Returns a negative value on error, 0 if there is no for that index or the index is out of bounds. */ FSL_EXPORT fsl_id_t fsl_deck_P_get_id(fsl_deck * d, int index); /** Adds a Q-card record to the given deck. The type argument must be negative for a backed-out change, positive for a cherrypicked change. target must be a valid UUID string. If baseline is not NULL then it also must be a valid UUID. Returns 0 on success, non-0 on error. FSL_RC_MISUSE if !mf or !target, FSL_RC_RANGE if target/baseline are not valid UUID strings (baseline may be NULL). */ FSL_EXPORT int fsl_deck_Q_add( fsl_deck * mf, int type, fsl_uuid_cstr target, fsl_uuid_cstr baseline ); /** Functionally identical to fsl_deck_B_set() except that it sets the R-card. Returns 0 on succes, FSL_RC_RANGE if md5 is not NULL or exactly FSL_STRLEN_MD5 bytes long (not including trailing NUL). If md5==NULL the current R value is cleared. It would be highly unusual to have to set the R-card manually, as its calculation is quite intricate/intensive. See fsl_deck_R_calc() and fsl_deck_unshuffle() for details */ FSL_EXPORT int fsl_deck_R_set( fsl_deck * mf, char const *md5); /** Adds a new T-card (tag) entry to the given deck. If uuid is not NULL and fsl_is_uuid(uuid) returns false then this function returns FSL_RC_RANGE. If uuid is NULL then it is assumed to be the UUID of the currently-being-constructed artifact in which the tag is contained (which appears as the '*' character in generated artifacts). Returns 0 on success. Returns FSL_RC_MISUE if !mf or !name. Returns FSL_RC_TYPE (and update's mf's error state with a message) if the T card is not legal for mf (see fsl_card_is_legal()). Returns FSL_RC_RANGE if !*name, tagType is invalid, or if uuid is not NULL and fsl_is_uuid(uuid) return false. Returns FSL_RC_OOM if an allocation fails. */ FSL_EXPORT int fsl_deck_T_add( fsl_deck * mf, fsl_tagtype_e tagType, fsl_uuid_cstr uuid, char const * name, char const * value); /** Adds the given tag instance to the given manifest. Returns 0 on success, FSL_RC_MISUSE if either argument is NULL, FSL_RC_OOM if appending the tag to the list fails. On success ownership of t is passed to mf. On error ownership is not modified. */ FSL_EXPORT int fsl_deck_T_add2( fsl_deck * mf, fsl_card_T * t); /** A convenience form of fsl_deck_T_add() which adds two propagating tags to the given deck: "branch" with a value of branchName and "sym-branchName" with no value. Returns 0 on success. Returns FSL_RC_OOM on allocation error and |
︙ | ︙ | |||
11310 11311 11312 11313 11314 11315 11316 | FSL_EXPORT int fsl_deck_R_calc2(fsl_deck *d, char ** tgt); /** Semantically identical fsl_deck_B_set() but sets the U-card. userName must be the user who's name should be recorded for this change. */ | | | | 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186 11187 11188 11189 11190 11191 11192 11193 11194 | FSL_EXPORT int fsl_deck_R_calc2(fsl_deck *d, char ** tgt); /** Semantically identical fsl_deck_B_set() but sets the U-card. userName must be the user who's name should be recorded for this change. */ FSL_EXPORT int fsl_deck_U_set( fsl_deck * mf, char const *userName); /** Semantically identical fsl_deck_B_set() but sets the W-card. content must be the page content of the Wiki page or Event this change is being applied to. */ FSL_EXPORT int fsl_deck_W_set( fsl_deck * mf, char const *content, fsl_int_t len); /** Must be called to initialize a newly-created/allocated deck instance. This function clears out all contents of the d parameter except for its (f, type, allocStamp) members, sets its (f, type) members, and leaves d->allocStamp intact. */ |
︙ | ︙ | |||
11467 11468 11469 11470 11471 11472 11473 | has any F-cards AND if the caller has not already set/calculated it AND if f's FSL_CX_F_CALC_R_CARD flag is set (it is on by default for historical reasons, but this may change at some point). Returns 0 on success, the usual non-0 suspects on error. | | < < < | < | < | | | > > > > | > | | | | | 11330 11331 11332 11333 11334 11335 11336 11337 11338 11339 11340 11341 11342 11343 11344 11345 11346 11347 11348 11349 11350 11351 11352 11353 11354 11355 11356 11357 11358 11359 11360 11361 11362 11363 11364 11365 11366 11367 11368 11369 11370 11371 11372 11373 11374 11375 11376 11377 11378 11379 | has any F-cards AND if the caller has not already set/calculated it AND if f's FSL_CX_F_CALC_R_CARD flag is set (it is on by default for historical reasons, but this may change at some point). Returns 0 on success, the usual non-0 suspects on error. If d->rid and d->uuid are set when this is called, it is assumed that we are saving existing or phantom content, and in that case: - An existing phantom is populated with the new content. - If an existing record is found with a non-0 size then it is not modified but this is currently not treated as an error (for historical reasons, though one could argue that it should result in FSL_RC_ALREADY_EXISTS). If d->rid and d->uuid are not set when this is called then... on success, d->rid and d->uuid will contain the values held by their counterparts in the blob table. They will only be set on success because they would otherwise refer to db records which get destroyed when the transaction rolls back. After saving, the deck will be cross-linked to update any relationships described by the deck. The save operation happens within a transaction, of course, and on any sort of error, db-side changes are rolled back. Note that it _is_ legal to start the transaction before calling this, which effectively makes this operation part of that transaction. This function will fail with FSL_RC_ACCESS if d is a delta manifest (has a B-card) d->f's forbid-delta-manifests configuration option is set to a truthy value. See fsl_repo_forbids_delta_manifests(). Maintenance reminder: this function also does a small bit of artifact-type-specific processing. @see fsl_deck_output() @see fsl_content_put_ex() */ FSL_EXPORT int fsl_deck_save( fsl_deck * d, bool isPrivate ); /** This starts a transaction (possibly nested) on the repository db and initializes some temporary db state needed for the crosslinking certain artifact types. It "should" (see below) be called at the start of the crosslinking process. Crosslinking *can* work without this but certain steps for certain (subject to |
︙ | ︙ | |||
11586 11587 11588 11589 11590 11591 11592 11593 11594 11595 11596 11597 11598 11599 | from ::fsl_deck_empty is FSL_SATYPE_ANY, so normally clients do not need to set this (unless they want to, as a small optimization). On success it returns 0 and d will be updated with the state from the input artifact. (Ideally, outputing d via fsl_deck_output() will produce a lossless copy of the original.) On error, if there is error information to propagate beyond the result code then it is stored in d->f (if that is not NULL), else in d->error. Whether or not such error info is propagated depends on the type of error, but anything more trivial than invalid arguments will be noted there. | > > > > > > | 11449 11450 11451 11452 11453 11454 11455 11456 11457 11458 11459 11460 11461 11462 11463 11464 11465 11466 11467 11468 | from ::fsl_deck_empty is FSL_SATYPE_ANY, so normally clients do not need to set this (unless they want to, as a small optimization). On success it returns 0 and d will be updated with the state from the input artifact. (Ideally, outputing d via fsl_deck_output() will produce a lossless copy of the original.) d->uuid will be set to the SHA1 of the input artifact, ignoring any surrounding PGP signature for hashing purposes. If d->f has an opened repository db and the parsed artifact has a counterpart in the database (determined via a hash match) then d->rid is set to the record ID. On error, if there is error information to propagate beyond the result code then it is stored in d->f (if that is not NULL), else in d->error. Whether or not such error info is propagated depends on the type of error, but anything more trivial than invalid arguments will be noted there. |
︙ | ︙ | |||
11607 11608 11609 11610 11611 11612 11613 | - FSL_RC_SYNTAX on syntax errors. - FSL_RC_CONSISTENCY if validation of a Z-card fails. - Any number of errors coming from the allocator, database, or fsl_deck APIs used here. | < < < < < < < | < < < < < < < < < < < < < | 11476 11477 11478 11479 11480 11481 11482 11483 11484 11485 11486 11487 11488 11489 11490 11491 | - FSL_RC_SYNTAX on syntax errors. - FSL_RC_CONSISTENCY if validation of a Z-card fails. - Any number of errors coming from the allocator, database, or fsl_deck APIs used here. */ FSL_EXPORT int fsl_deck_parse(fsl_deck * d, fsl_buffer * src); /** Quickly determines whether the content held by the given buffer "might" be a structural artifact. It performs a fast sanity check for prominent features which can be checked either in O(1) or very short O(N) time (with a fixed N). If it returns false then the given buffer's contents are, with 100% certainty, *not* a |
︙ | ︙ | |||
11651 11652 11653 11654 11655 11656 11657 | Fossil artifact. If rid==0 the current checkout (if opened) is used. (Trivia: there can never be a checkout with rid==0 but rid==0 is sometimes valid for an new/empty repo devoid of commits). If type==FSL_SATYPE_ANY then it will allow any type of control artifact, else it returns FSL_RC_TYPE if the loaded artifact is of the wrong type. | | < < < < < < < < < | | | | 11500 11501 11502 11503 11504 11505 11506 11507 11508 11509 11510 11511 11512 11513 11514 11515 11516 11517 11518 11519 11520 11521 11522 11523 11524 11525 11526 11527 11528 11529 11530 11531 11532 11533 11534 11535 11536 11537 11538 11539 11540 11541 11542 11543 11544 11545 11546 11547 11548 | Fossil artifact. If rid==0 the current checkout (if opened) is used. (Trivia: there can never be a checkout with rid==0 but rid==0 is sometimes valid for an new/empty repo devoid of commits). If type==FSL_SATYPE_ANY then it will allow any type of control artifact, else it returns FSL_RC_TYPE if the loaded artifact is of the wrong type. Returns 0 on success. d may be partially populated on error, and the caller must eventually pass it to fsl_deck_finalize() resp. fsl_deck_clean() regardless of success or error. This function "could" clean it up on error, but leaving it partially populated makes debugging easier. If the error was an artifact type mismatch then d will "probably" be properly populated but will not hold the type of artifact requested. It "should" otherwise be well-formed because parsing errors occur before the type check can happen, but parsing of invalid manifests might also trigger a FSL_RC_TYPE error of a different nature. The morale of the storage is: if this function returns non-0, assume d is useless and needs to be cleaned up. f's error state may be updated on error (for anything more serious than basic argument validation errors). On success d->f is set to f. @see fsl_deck_load_sym() */ FSL_EXPORT int fsl_deck_load_rid( fsl_cx * f, fsl_deck * d, fsl_id_t rid, fsl_satype_e type ); /** A convenience form of fsl_deck_load_rid() which uses fsl_sym_to_rid() to convert symbolicName into an artifact RID. See fsl_deck_load_rid() for the symantics of the first, second, and fourth arguments, as well as the return value. See fsl_sym_to_rid() for the allowable values of symbolicName. @see fsl_deck_load_rid() */ FSL_EXPORT int fsl_deck_load_sym( fsl_cx * f, fsl_deck * d, char const * symbolicName, fsl_satype_e type ); /** Loads the baseline manifest specified in d->B.uuid, if any and if necessary. Returns 0 on success. If d->B.baseline is already loaded or d->B.uuid is NULL (in which case there is no baseline), it |
︙ | ︙ | |||
11833 11834 11835 11836 11837 11838 11839 | Adds the given function as a "crosslink callback" for the given Fossil context. The callback is called at the end of a successfull fsl_deck_crosslink() operation and provides a way for the client to perform their own work based on the app having crosslinked an artifact. Crosslinking happens when artifacts are saved or upon a rebuild operation. | < < < | 11673 11674 11675 11676 11677 11678 11679 11680 11681 11682 11683 11684 11685 11686 | Adds the given function as a "crosslink callback" for the given Fossil context. The callback is called at the end of a successfull fsl_deck_crosslink() operation and provides a way for the client to perform their own work based on the app having crosslinked an artifact. Crosslinking happens when artifacts are saved or upon a rebuild operation. Crosslink callbacks are called at the end of the core crosslink steps, in the order they are registered, with the caveat that if a listener is overwritten by another with the same name, the new entry retains the older one's position in the list. The library may register its own before the client gets a chance to. If _any_ crosslinking callback fails (returns non-0) then the |
︙ | ︙ | |||
11918 11919 11920 11921 11922 11923 11924 | defaults, install their own crosslink listners which ammend the state applied by the default ones. e.g. add a listener which watches for checkin updates and replace the default-installed comment with one suitable for your application, leaving the rest of the db state in place. At its simplest, that looks more or less like the following code (inside a fsl_deck_xlink_f() callback): | < > < > | | 11755 11756 11757 11758 11759 11760 11761 11762 11763 11764 11765 11766 11767 11768 11769 11770 11771 11772 11773 11774 11775 11776 | defaults, install their own crosslink listners which ammend the state applied by the default ones. e.g. add a listener which watches for checkin updates and replace the default-installed comment with one suitable for your application, leaving the rest of the db state in place. At its simplest, that looks more or less like the following code (inside a fsl_deck_xlink_f() callback): @code int rc = fsl_db_exec(fsl_cx_db_repo(deck->f), "UPDATE event SET comment=%Q " "WHERE objid=%"FSL_ID_T_PFMT, "the new comment.", deck->rid); @endcode */ FSL_EXPORT int fsl_xlink_listener( fsl_cx * f, char const * name, fsl_deck_xlink_f cb, void * cbState ); /** For the given blob.rid value, returns the blob.size value of that record via *rv. Returns 0 or higher on success, -1 if a phantom record is found, -2 if no entry is found, or a smaller |
︙ | ︙ | |||
11953 11954 11955 11956 11957 11958 11959 | uncompressed if they were stored in compressed form. This extracts a raw blob and does not apply any deltas - use fsl_content_get() to fully expand a delta-stored blob. Returns 0 on success. On error tgt might be partially updated, e.g. it might be populated with compressed data instead of uncompressed. On error tgt's contents should be recycled | | < < < | < | < | 11790 11791 11792 11793 11794 11795 11796 11797 11798 11799 11800 11801 11802 11803 11804 11805 11806 11807 11808 11809 11810 | uncompressed if they were stored in compressed form. This extracts a raw blob and does not apply any deltas - use fsl_content_get() to fully expand a delta-stored blob. Returns 0 on success. On error tgt might be partially updated, e.g. it might be populated with compressed data instead of uncompressed. On error tgt's contents should be recycled (e.g. fsl_buffer_reuse()) or discarded (e.g. fsl_buffer_clear()) by the client. @see fsl_content_get() @see fsl_content_size() */ FSL_EXPORT int fsl_content_blob( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ); /** Functionally similar to fsl_content_blob() but does a lot of work to ensure that the returned blob is expanded from its deltas, if any. The tgt buffer's memory, if any, will be replaced/reused if it has any. |
︙ | ︙ | |||
12008 12009 12010 12011 12012 12013 12014 | fsl_buffer * const tgt ); /** Returns true if the given rid is marked as PRIVATE in f's current repository. Returns false (0) on error or if the content is not marked as private. */ | | | | 11840 11841 11842 11843 11844 11845 11846 11847 11848 11849 11850 11851 11852 11853 11854 11855 11856 11857 11858 11859 11860 11861 11862 | fsl_buffer * const tgt ); /** Returns true if the given rid is marked as PRIVATE in f's current repository. Returns false (0) on error or if the content is not marked as private. */ FSL_EXPORT bool fsl_content_is_private(fsl_cx * f, fsl_id_t rid); /** Marks the given rid public, if it was previously marked as private. Returns 0 on success, non-0 on error. Note that it is not possible to make public content private. */ FSL_EXPORT int fsl_content_make_public(fsl_cx * f, fsl_id_t rid); /** Generic callback interface for visiting decks. The interface does not generically require that d survive after this call returns. Implementations must return 0 on success, non-0 on error. Some |
︙ | ︙ | |||
12183 12184 12185 12186 12187 12188 12189 | and appends them as new (char *) strings to tgt. On error tgt might be partially populated (but this will only happen on an OOM or serious system-level error). It is up to the caller free the entries added to the list. Some of the possibilities include: | < > < > | 12015 12016 12017 12018 12019 12020 12021 12022 12023 12024 12025 12026 12027 12028 12029 12030 12031 12032 12033 12034 12035 12036 | and appends them as new (char *) strings to tgt. On error tgt might be partially populated (but this will only happen on an OOM or serious system-level error). It is up to the caller free the entries added to the list. Some of the possibilities include: @code fsl_list_visit( list, 0, fsl_list_v_fsl_free, NULL ); fsl_list_reserve(list,0); // Or: fsl_list_clear(list, fsl_list_v_fsl_free, NULL); // Or simply: fsl_list_visit_free( list, 1 ); @endcode */ FSL_EXPORT int fsl_wiki_names_get( fsl_cx * f, fsl_list * tgt ); /** F-cards each represent one file entry in a Manifest Artifact (i.e., a checkin version). |
︙ | ︙ | |||
12704 12705 12706 12707 12708 12709 12710 | there are valid/interesting uses for such modification remains to be seen. If any are found, this copy behavior may change. */ FSL_EXPORT int fsl_repo_extract( fsl_cx * f, fsl_repo_extract_opt const * opt ); /** | | | | 12536 12537 12538 12539 12540 12541 12542 12543 12544 12545 12546 12547 12548 12549 12550 12551 12552 12553 12554 12555 12556 | there are valid/interesting uses for such modification remains to be seen. If any are found, this copy behavior may change. */ FSL_EXPORT int fsl_repo_extract( fsl_cx * f, fsl_repo_extract_opt const * opt ); /** Equivalent to fsl_tag_rid() except that it takes a symbolic artifact name in place of an artifact ID as the third argumemnt. This function passes symToTag to fsl_sym_to_rid(), and on success passes the rest of the parameters as-is to fsl_tag_rid(). See that function the semantics of the other arguments and the return value, as well as a description of the side effects. */ FSL_EXPORT int fsl_tag_sym( fsl_cx * f, fsl_tagtype_e tagType, char const * symToTag, char const * tagName, char const * tagValue, char const * userName, double mtime, fsl_id_t * newId ); |
︙ | ︙ | |||
12781 12782 12783 12784 12785 12786 12787 | A leaf, by the way, is a commit which has no children in the same branch. Sidebar: this function calculates whether the RID is a leaf, as opposed to checking the "static" (pre-calculated) list of leaves in the [leaf] table. */ | | < < < < < | | < | < | | 12613 12614 12615 12616 12617 12618 12619 12620 12621 12622 12623 12624 12625 12626 12627 12628 12629 12630 12631 12632 12633 12634 12635 12636 12637 12638 12639 12640 12641 12642 12643 12644 12645 12646 12647 12648 12649 12650 12651 12652 12653 12654 12655 12656 12657 | A leaf, by the way, is a commit which has no children in the same branch. Sidebar: this function calculates whether the RID is a leaf, as opposed to checking the "static" (pre-calculated) list of leaves in the [leaf] table. */ FSL_EXPORT bool fsl_rid_is_leaf(fsl_cx * f, fsl_id_t rid); /** Counts the number of primary non-branch children for the given check-in. A primary child is one where the parent is the primary parent, not a merge parent. A "leaf" is a node that has zero children of any kind. This routine counts only primary children. A non-branch child is one which is on the same branch as the parent. Returns a negative value on error. */ FSL_EXPORT fsl_int_t fsl_count_nonbranch_children(fsl_cx * f, fsl_id_t rid); /** Looks for the delta table record where rid==deltaRid, and returns that record's srcid via *rv. Returns 0 on success, non-0 on error. If no record is found, *rv is set to 0 and 0 is returned (as opposed to FSL_RC_NOT_FOUND) because that generally simplifies the error checking. */ FSL_EXPORT int fsl_delta_src_id( fsl_cx * f, fsl_id_t deltaRid, fsl_id_t * rv ); /** Return true if the given artifact ID should is listed in f's shun table, else false. */ FSL_EXPORT int fsl_uuid_is_shunned(fsl_cx * f, fsl_uuid_cstr zUuid); /** Compute the "mtime" of the file given whose blob.rid is "fid" that is part of check-in "vid". The mtime will be the mtime on vid or some ancestor of vid where fid first appears. Note that fossil does not track the "real" mtimes of files, it only |
︙ | ︙ | |||
13017 13018 13019 13020 13021 13022 13023 | FSL_EXPORT int fsl_repo_import_blob( fsl_cx * f, fsl_input_f inFunc, void * inState, fsl_id_t * rid, fsl_uuid_str * uuid ); /** A convenience form of fsl_repo_import_blob(), equivalent to: | < > < > | 12842 12843 12844 12845 12846 12847 12848 12849 12850 12851 12852 12853 12854 12855 12856 12857 12858 | FSL_EXPORT int fsl_repo_import_blob( fsl_cx * f, fsl_input_f inFunc, void * inState, fsl_id_t * rid, fsl_uuid_str * uuid ); /** A convenience form of fsl_repo_import_blob(), equivalent to: @code fsl_repo_import_blob(f, fsl_input_f_buffer, bIn, rid, uuid ) @endcode except that (A) bIn is const in this call and non-const in the other form (due to cursor traversal requirements) and (B) it returns FSL_RC_MISUSE if bIn is NULL. */ FSL_EXPORT int fsl_repo_import_buffer( fsl_cx * f, fsl_buffer const * bIn, fsl_id_t * rid, fsl_uuid_str * uuid ); |
︙ | ︙ | |||
13111 13112 13113 13114 13115 13116 13117 | Error cases include: either argument is NULL, uuid does not appear to be a full or partial UUID (or is too long), uuid is ambiguous (try providing a longer one) This implementation is more efficient when given a full, valid UUID (one for which fsl_is_uuid() returns true). */ | | | | | 12936 12937 12938 12939 12940 12941 12942 12943 12944 12945 12946 12947 12948 12949 12950 12951 12952 12953 12954 12955 12956 12957 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 12970 | Error cases include: either argument is NULL, uuid does not appear to be a full or partial UUID (or is too long), uuid is ambiguous (try providing a longer one) This implementation is more efficient when given a full, valid UUID (one for which fsl_is_uuid() returns true). */ FSL_EXPORT fsl_id_t fsl_uuid_to_rid( fsl_cx * f, char const * uuid ); /** The opposite of fsl_uuid_to_rid(), this returns the UUID string of the given blob record ID. Ownership of the string is passed to the caller and it must eventually be freed using fsl_free(). Returns NULL on error (invalid arguments or f has no repo opened) or if no blob record is found. If no record is found, f's error state is updated with an explanation of the problem. */ FSL_EXPORT fsl_uuid_str fsl_rid_to_uuid(fsl_cx * f, fsl_id_t rid); /** Works like fsl_rid_to_uuid() but assigns the UUID to the given buffer, re-using its memory, if any. Returns 0 on success, FSL_RC_MISUSE if rid is not positive, FSL_RC_OOM on allocation error, and FSL_RC_NOT_FOUND if no blob entry matching the given rid is found. */ FSL_EXPORT int fsl_rid_to_uuid2(fsl_cx * f, fsl_id_t rid, fsl_buffer *uuid); /** This works identically to fsl_rid_to_uuid() except that it will only resolve to a UUID if an artifact matching the given type has that UUID. If no entry is found, f's error state gets updated with a description of the problem. |
︙ | ︙ | |||
13607 13608 13609 13610 13611 13612 13613 | /** The maximum number of versions to search through. Note that fossil(1) offers the ability to limit the calculation based on processing time, e.g. to 1500ms. We may or may not add that in this library. */ | | < < < < < < < < < < < < < < < < | 13432 13433 13434 13435 13436 13437 13438 13439 13440 13441 13442 13443 13444 13445 13446 | /** The maximum number of versions to search through. Note that fossil(1) offers the ability to limit the calculation based on processing time, e.g. to 1500ms. We may or may not add that in this library. */ uint32_t limit; /** - 0 = do not ignore any spaces. - <0 = ignore trailing end-of-line spaces. - >1 = ignore all spaces */ int16_t spacePolicy; /** |
︙ | ︙ | |||
13668 13669 13670 13671 13672 13673 13674 | }; /** Initialized-with-defaults fsl_annotate_opt structure, intended for const-copy initialization. */ #define fsl_annotate_opt_empty_m {\ NULL/*filename*/, \ 0/*versionRid*/,0/*originRid*/, \ | < | | 13477 13478 13479 13480 13481 13482 13483 13484 13485 13486 13487 13488 13489 13490 13491 | }; /** Initialized-with-defaults fsl_annotate_opt structure, intended for const-copy initialization. */ #define fsl_annotate_opt_empty_m {\ NULL/*filename*/, \ 0/*versionRid*/,0/*originRid*/, \ 0U/*limit*/, 0/*spacePolicy*/, \ false/*praise*/, false/*fileVersions*/, \ false/*dumpVersions*/, \ NULL/*out*/, NULL/*outState*/ \ } /** Initialized-with-defaults fsl_annotate_opt structure, intended for non-const copy initialization. */ |
︙ | ︙ | |||
13952 13953 13954 13955 13956 13957 13958 | opt->counts when it adds, updates, or skips a file. On each call, it updated opt->counts without resetting it (as this function is typically called in a loop). This function does not modify any other entries of that object and it requires that the object not be modified (e.g. via opt->callback()) while it is recursively processing. To reset the counts between calls, if needed: | < > < > | 13760 13761 13762 13763 13764 13765 13766 13767 13768 13769 13770 13771 13772 13773 13774 13775 13776 | opt->counts when it adds, updates, or skips a file. On each call, it updated opt->counts without resetting it (as this function is typically called in a loop). This function does not modify any other entries of that object and it requires that the object not be modified (e.g. via opt->callback()) while it is recursively processing. To reset the counts between calls, if needed: @code opt->counts = fsl_ckout_manage_opt_empty.counts; @endcode Returns 0 on success, non-0 on error. Files queued for addition this way can be unqueued before they are committed using fsl_ckout_unmanage(). @see fsl_ckout_unmanage() |
︙ | ︙ | |||
14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 | all versions except for the one we're working with, but at least a couple of use cases call for having multiple versions in vfile at once. Many algorithms generally assume only a single checkin's worth of state is in vfile and can get confused if that is not the case. */ FSL_VFILE_CKSIG_KEEP_OTHERS = 0x008, /** If set and fsl_vfile_changes_scan() is passed a version other than the pre-call checkout version, it will, when finished, write the given version in the "checkout" setting of the ckout.vvar table, effectively switching the checkout to that version. It does not do | > | | 14085 14086 14087 14088 14089 14090 14091 14092 14093 14094 14095 14096 14097 14098 14099 14100 14101 14102 14103 14104 14105 | all versions except for the one we're working with, but at least a couple of use cases call for having multiple versions in vfile at once. Many algorithms generally assume only a single checkin's worth of state is in vfile and can get confused if that is not the case. */ FSL_VFILE_CKSIG_KEEP_OTHERS = 0x008, /** If set and fsl_vfile_changes_scan() is passed a version other than the pre-call checkout version, it will, when finished, write the given version in the "checkout" setting of the ckout.vvar table, effectively switching the checkout to that version. It does not do this be default because it is sometimes necessary to have two versions in the vfile table at once and the operation doing so needs to control which version number is the current checkout. */ FSL_VFILE_CKSIG_WRITE_CKOUT_VERSION = 0x010 }; /** |
︙ | ︙ | |||
14311 14312 14313 14314 14315 14316 14317 | Returns 0 on success, non-0 on error. BUG: this does not properly catch one particular corner-case change, where a file has been replaced by a same-named non-file (symlink or directory). */ | | | | 14120 14121 14122 14123 14124 14125 14126 14127 14128 14129 14130 14131 14132 14133 14134 14135 14136 14137 14138 14139 14140 14141 14142 14143 14144 14145 14146 | Returns 0 on success, non-0 on error. BUG: this does not properly catch one particular corner-case change, where a file has been replaced by a same-named non-file (symlink or directory). */ FSL_EXPORT int fsl_vfile_changes_scan(fsl_cx * f, fsl_id_t vid, unsigned cksigFlags); /** If f has an opened checkout which has local changes noted in its checkout db state (the vfile table), returns true, else returns false. Note that this function does not do the filesystem scan to check for changes, but checks only the db state. Use fsl_vfile_changes_scan() to perform the actual scan (noting that library-side APIs which update that state may also record individual changes or automatically run a scan). */ FSL_EXPORT bool fsl_ckout_has_changes(fsl_cx *f); /** Callback type for use with fsl_checkin_queue_opt for alerting a client about exactly which files get enqueued/dequeued via fsl_checkin_enqueue() and fsl_checkin_dequeue(). This function gets passed the checkout-relative name of the file |
︙ | ︙ | |||
15094 15095 15096 15097 15098 15099 15100 | database-side data will be consistent (the transaction is rolled back) but filesystem-side changes may not be. */ typedef int (*fsl_ckup_f)( fsl_ckup_state const * cState ); /** This enum lists the various types of individual file change states | | < | > | > | 14903 14904 14905 14906 14907 14908 14909 14910 14911 14912 14913 14914 14915 14916 14917 14918 14919 14920 14921 14922 14923 14924 14925 14926 14927 14928 14929 14930 14931 14932 14933 14934 14935 14936 14937 14938 14939 | database-side data will be consistent (the transaction is rolled back) but filesystem-side changes may not be. */ typedef int (*fsl_ckup_f)( fsl_ckup_state const * cState ); /** This enum lists the various types of individual file change states which can happen during a checkout or update. */ enum fsl_ckup_fchange_e { /** Sentinel value. */ FSL_CKUP_FCHANGE_INVALID = -1, /** Was unchanged between the previous and updated-to version, so no change was made to the on-disk file. This is the only entry in the enum which is guaranteed to have a specific value: 0, so that it can be used as a boolean false. */ FSL_CKUP_FCHANGE_NONE = 0, /** Added to SCM in the updated-to version. */ FSL_CKUP_FCHANGE_ADDED, /** Added to SCM in the current checkout version and carried over into the updated-to version. */ FSL_CKUP_FCHANGE_ADD_PROPAGATED, /** Removed from SCM in the updated-to to version OR in the checked-out version but not yet commited. a.k.a. it became "unmanaged." Do we need to differentiate between those cases? */ FSL_CKUP_FCHANGE_RM, /** Removed from the checked-out version but not yet commited, so was carried over to the updated-to version. |
︙ | ︙ | |||
15200 15201 15202 15203 15204 15205 15206 | 2) For the update process, fCard->priorName will be NULL unless the file was renamed between the original and updated-to versions, in which case priorName will refer to the original version's name. */ fsl_repo_extract_state const * extractState; /** | | < | | | 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 | 2) For the update process, fCard->priorName will be NULL unless the file was renamed between the original and updated-to versions, in which case priorName will refer to the original version's name. */ fsl_repo_extract_state const * extractState; /** Optional client-dependent state for use in the fsl_ckup_f() callback. */ void * callbackState; /** Vaguely describes the type of change the current call into the fsl_ckup_f() represents. The full range of values is not valid for all operations. Specifically: Checkout only uses: FSL_CKUP_FCHANGE_NONE FSL_CKUP_FCHANGE_UPDATED FSL_CKUP_FCHANGE_RM For update operatios all (or most) values are potentially possible. If this has a value of FSL_CKUP_FCHANGE_RM, this->fileRmInfo will provide a bit more detail. */ fsl_ckup_fchange_e fileChangeType; |
︙ | ︙ | |||
15254 15255 15256 15257 15258 15259 15260 | this->fileRmInfo indicates that a file was removed, this might (depending on availability of the file in the filesystem at the time) be set to 0. When running in dry-run mode, this value may be 0, as we may not have a file in place which we can stat() to get it, nor a db entry from which to fetch it. | < < | > > | | | 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 | this->fileRmInfo indicates that a file was removed, this might (depending on availability of the file in the filesystem at the time) be set to 0. When running in dry-run mode, this value may be 0, as we may not have a file in place which we can stat() to get it, nor a db entry from which to fetch it. */ fsl_time_t mtime; /** The size of the extracted file, in bytes. If the file was removed from the filesystem (or removal was at least attempted) then this is set to -1. */ fsl_int_t size; /** True if the current checkout/update is running in dry-run mode, else false. Dry-run mode has */ bool dryRun; }; /** UNDER CONSTRUCTION. Options for use with fsl_repo_ckout() and fsl_ckout_update(). i.e. for checkout and update operations. */ struct fsl_ckup_opt { /** The version of the repostitory to check out or update. This must be the blob.rid of a checkin artifact. */ fsl_id_t checkinRid; |
︙ | ︙ | |||
15341 15342 15343 15344 15345 15346 15347 | bool setMtime; /** A hint to fsl_repo_ckout() and fsl_ckout_update() about whether it needs to scan the checkout for changes. Set this to false ONLY if the calling code calls fsl_ckout_changes_scan() (or equivalent, e.g. fsl_vfile_changes_scan()) immediately before | | | | 15150 15151 15152 15153 15154 15155 15156 15157 15158 15159 15160 15161 15162 15163 15164 15165 | bool setMtime; /** A hint to fsl_repo_ckout() and fsl_ckout_update() about whether it needs to scan the checkout for changes. Set this to false ONLY if the calling code calls fsl_ckout_changes_scan() (or equivalent, e.g. fsl_vfile_changes_scan()) immediately before calling fsl_repo_ckout() or fsl_ckout_update(), as both require a non-stale changes scan in order to function properly. */ bool scanForChanges; /** If true, the extraction process will "go through the motions" but will not write any files to disk. It will perform I/O such as stat()'ing to see, e.g., if it would have needed to overwrite a |
︙ | ︙ | |||
15539 15540 15541 15542 15543 15544 15545 | manifest setting can be modified at any time, either in a versioned setting file or the repository db, and may be modified from outside the library. There's a tiny back-door for working around that: if m is NULL, the cache will be flushed and no other work will be performed. Thus the following approach can be used to force a fresh check for that setting: | < > < > | 15348 15349 15350 15351 15352 15353 15354 15355 15356 15357 15358 15359 15360 15361 15362 15363 15364 15365 | manifest setting can be modified at any time, either in a versioned setting file or the repository db, and may be modified from outside the library. There's a tiny back-door for working around that: if m is NULL, the cache will be flushed and no other work will be performed. Thus the following approach can be used to force a fresh check for that setting: @code fsl_ckout_manifest_setting(f, NULL); // clears caches, does nothing else fsl_ckout_manifest_setting(f, &myInt); // loads/caches the setting @endcode */ FSL_EXPORT void fsl_ckout_manifest_setting(fsl_cx *f, int *m); /** Might write out the files manifest, manifest.uuid, and/or manifest.tags for the current checkout to the the checkout's root directory. The 2nd-4th arguments are interpreted as follows: |
︙ | ︙ | |||
15790 15791 15792 15793 15794 15795 15796 | different records) at the same time. */ FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid, char const * zName, fsl_id_t * vfid ); /** | | | | | | | | | | | | < < | | | | | | 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 15628 15629 15630 15631 15632 15633 15634 15635 | different records) at the same time. */ FSL_EXPORT int fsl_filename_to_vfile_id( fsl_cx * f, fsl_id_t vid, char const * zName, fsl_id_t * vfid ); /** Searches the vfile table where vfile.vid=vid for a name which matches zName or all vfile entries found under a subdirectory named zName (with no trailing slash). zName must be relative to the checkout root. As a special case, if zName is NULL, empty, or "." then all files in vfile with the given vid are selected. For each entry it finds, it adds the vfile.id to dest. If vid<=0 then the current checkout RID is used. If changedOnly is true then only entries which have been marked in the vfile table as having some sort of change are included, so if true then fsl_ckout_changes_scan() (or equivalent) must have been "recently" called to ensure that state is up to do. This search honors the context-level case-sensitivity setting (see fsl_cx_case_sensitive_set()). Returns 0 on success. Not finding anything is not treated as an error, though we could arguably return FSL_RC_NOT_FOUND for the cases which use this function. In order to determine whether or not any results were found, compare dest->entryCount before and after calling this. This function matches only vfile.pathname, not vfile.origname, because it is possible for a given name to be in both fields (in different records) at the same time. @see fsl_ckout_vfile_ids() */ FSL_EXPORT int fsl_filename_to_vfile_ids( fsl_cx * f, fsl_id_t vid, fsl_id_bag * dest, |
︙ | ︙ | |||
15891 15892 15893 15894 15895 15896 15897 | vfile table for longer than absolutely needed. They "really should" use fsl_vfile_unload() to clear out any version they load with this routine. @see fsl_vfile_unload() @see fsl_vfile_unload_except() */ | | | | | 15698 15699 15700 15701 15702 15703 15704 15705 15706 15707 15708 15709 15710 15711 15712 15713 15714 15715 15716 15717 15718 15719 15720 15721 15722 15723 15724 15725 15726 15727 15728 15729 15730 15731 15732 15733 15734 15735 15736 15737 15738 15739 15740 15741 15742 15743 15744 15745 | vfile table for longer than absolutely needed. They "really should" use fsl_vfile_unload() to clear out any version they load with this routine. @see fsl_vfile_unload() @see fsl_vfile_unload_except() */ FSL_EXPORT int fsl_vfile_load(fsl_cx * f, fsl_id_t manifestRid, bool clearOtherVersions, uint32_t * missingCount); /** Clears out all entries in the current checkout's vfile table with the given vfile.vid value. If vid<=0 then the current checkout RID is used (which is never a good idea from client-side code!). ACHTUNG: never do this without understanding the consequences. It can ruin the current checkout state. Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout opened, FSL_RC_DB on any sort of db-related error (in which case f's error state is updated with a description of the problem). @see fsl_vfile_load() @see fsl_vfile_unload_except() */ FSL_EXPORT int fsl_vfile_unload(fsl_cx * f, fsl_id_t vid); /** A counterpart of fsl_vfile_unload() which removes all vfile entries where vfile.vid is not the given vid. If vid is <=0 then the current checkout RID is used. Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has no checkout opened, FSL_RC_DB on any sort of db-related error (in which case f's error state is updated with a description of the problem). @see fsl_vfile_load() @see fsl_vfile_unload() */ FSL_EXPORT int fsl_vfile_unload_except(fsl_cx * f, fsl_id_t vid); /** Performs a "fingerprint check" between f's current checkout and repository databases. Returns 0 if either there is no checkout, no mismatch, or it is impossible to determine because the checkout is missing a fingerprint (which is legal for "older" checkout |
︙ | ︙ | |||
15975 15976 15977 15978 15979 15980 15981 15982 15983 15984 15985 15986 15987 15988 | This function currently resolves symlinks on its way to the content, but that behaviour may change in the future to reflect f's symlink preferences. */ FSL_EXPORT int fsl_ckout_file_content(fsl_cx * const f, bool relativeToCwd, char const * zName, fsl_buffer * const dest); /** Fetches the timestamp of the given F-card's name against the filesystem and/or the most recent checkin in which it was modified (as reported by fsl_mtime_of_manifest()). vid is the checkin version to look at. If it's 0, the current checkout will be used. | > | 15782 15783 15784 15785 15786 15787 15788 15789 15790 15791 15792 15793 15794 15795 15796 | This function currently resolves symlinks on its way to the content, but that behaviour may change in the future to reflect f's symlink preferences. */ FSL_EXPORT int fsl_ckout_file_content(fsl_cx * const f, bool relativeToCwd, char const * zName, fsl_buffer * const dest); /** Fetches the timestamp of the given F-card's name against the filesystem and/or the most recent checkin in which it was modified (as reported by fsl_mtime_of_manifest()). vid is the checkin version to look at. If it's 0, the current checkout will be used. |
︙ | ︙ | |||
16004 16005 16006 16007 16008 16009 16010 | - FSL_RC_OOM. */ FSL_EXPORT int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * const fc, fsl_time_t * repoMtime, fsl_time_t * localMtime); | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 | - FSL_RC_OOM. */ FSL_EXPORT int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * const fc, fsl_time_t * repoMtime, fsl_time_t * localMtime); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */ /* end of file ../include/fossil-scm/fossil-checkout.h */ |
︙ | ︙ | |||
16258 16259 16260 16261 16262 16263 16264 | Note that the APIs which read and write versioned settings do not care whether those settings are valid for fossil(1). Reminder to self: SQL to find the checkins in which a versioned settings file was added or modified (ignoring renames and branches and whatnot). | < > < > | 15887 15888 15889 15890 15891 15892 15893 15894 15895 15896 15897 15898 15899 15900 15901 15902 15903 15904 15905 15906 15907 15908 15909 15910 | Note that the APIs which read and write versioned settings do not care whether those settings are valid for fossil(1). Reminder to self: SQL to find the checkins in which a versioned settings file was added or modified (ignoring renames and branches and whatnot). @code select strftime('%Y-%m-%d %H:%M:%S',e.mtime) mtime, b.rid, b.uuid from mlink m, filename f, blob b, event e where f.fnid=m.fnid and m.mid=b.rid and b.rid=e.objid and f.name='.fossil-settings/ignore-glob' order by e.mtime desc ; @endcode */ FSL_CONFDB_VERSIONABLE = 4 }; typedef enum fsl_confdb_e fsl_confdb_e; /** |
︙ | ︙ | |||
16703 16704 16705 16706 16707 16708 16709 | Returns the next node p's path. The returned node is owned by path may be invalidated by any APIs which manipulate path. Intended to be used like this: | < > < > | 16332 16333 16334 16335 16336 16337 16338 16339 16340 16341 16342 16343 16344 16345 16346 16347 16348 16349 16350 16351 16352 | Returns the next node p's path. The returned node is owned by path may be invalidated by any APIs which manipulate path. Intended to be used like this: @code for( p = fsl_vpath_first(path) ; p ; p = fsl_vpath_next(p)){ ... } @endcode */ FSL_EXPORT fsl_vpath_node * fsl_vpath_next(fsl_vpath_node *p); /** Returns p's path length. */ FSL_EXPORT int fsl_vpath_length(fsl_vpath const * p); |
︙ | ︙ | |||
16748 16749 16750 16751 16752 16753 16754 | On success, returns 0 and path->pStart will point to the beginning of the path (the iFrom node). If pStart is 0 then no path could be found but 0 is still returned. Elements of the path can be traversed like so: | < > < > < > < > | 16377 16378 16379 16380 16381 16382 16383 16384 16385 16386 16387 16388 16389 16390 16391 16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 16405 16406 16407 16408 16409 16410 16411 16412 16413 16414 16415 16416 16417 16418 16419 16420 | On success, returns 0 and path->pStart will point to the beginning of the path (the iFrom node). If pStart is 0 then no path could be found but 0 is still returned. Elements of the path can be traversed like so: @code fsl_vpath path = fsl_vpath_empty; fsl_vpath_node * n = 0; int rc = fsl_vpath_shortest(f, &path, versionFrom, versionTo, 1, 0); if(rc) { ... error ... } for( n = fsl_vpath_first(&path); n; n = fsl_vpath_next(n) ){ ... } fsl_vpath_clear(&path); @endcode On error, f's error state may be updated with a description of the problem. */ FSL_EXPORT int fsl_vpath_shortest( fsl_cx * const f, fsl_vpath * const path, fsl_id_t iFrom, fsl_id_t iTo, bool directOnly, bool oneWayOnly ); /** This variant of fsl_vpath_shortest() stores the shortest direct path from version iFrom to version iTo in the ANCESTOR temporary table using f's current repo db handle. That table gets created, if needed, else cleared by this call. The ANCESTOR temp table has the following interface: @code rid INT UNIQUE generation INTEGER PRIMARY KEY @endcode Where [rid] is a checkin version RID and [generation] is the 1-based number of steps from iFrom, including iFrom (so iFrom's own generation is 1). On error returns 0 and, if pSteps is not NULL, assigns *pSteps to the number of entries added to the ancestor table. On error, pSteps |
︙ | ︙ | |||
17107 17108 17109 17110 17111 17112 17113 | /* The fsl_cx class is documented in main public header. */ struct fsl_cx { /** A pointer to the "main" db handle. Exactly which db IS the main db is, because we have three DBs, not generally knowble. | | < | < | | | | | < < < < < < < < | 16736 16737 16738 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 16764 | /* The fsl_cx class is documented in main public header. */ struct fsl_cx { /** A pointer to the "main" db handle. Exactly which db IS the main db is, because we have three DBs, not generally knowble. As of this writing (20141027) the following applies: dbMain always points to &this->dbMem (a ":memory:" db opened by fsl_cx_init()), and the repo/ckout/config DBs get ATTACHed to that one. Their separate handles (this->{repo,ckout,config}.db) are used to store the name and file path to each one (even though they have no real db handle associated with them). Internal code should rely as little as possible on the actual arrangement of internal DB handles, and should use fsl_cx_db_repo(), fsl_cx_db_ckout(), and fsl_cx_db_config() to get a handle to the specific db they want. Currently they will always return NULL or the same handle, but that design decision might change at some point, so the public API treats them as separate entities. */ fsl_db * dbMain; /** Marker which tells us whether fsl_cx_finalize() needs to fsl_free() this instance or not. */ |
︙ | ︙ | |||
17160 17161 17162 17163 17164 17165 17166 | fsl_db dbMem; /** Holds info directly related to a checkout database. */ struct { /** | | < | > > > < | < < > | | > | < | | < | | 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 16832 16833 16834 16835 16836 16837 16838 16839 16840 16841 16842 16843 16844 16845 16846 16847 16848 16849 16850 16851 16852 16853 16854 | fsl_db dbMem; /** Holds info directly related to a checkout database. */ struct { /** Handle to the currently opened checkout database IF the checkout is the main db. */ fsl_db db; /** Possibly not needed, but useful for doing absolute-to-relative path conversions for checking file lists. The directory part of an opened checkout db. This is currently only set by fsl_ckout_open_dir(). It contains a trailing slash, largely because that simplifies porting fossil(1) code and appending filenames to this directory name to create absolute paths. */ char * dir; /** Optimization: fsl_strlen() of dir. Guaranteed to be set to dir's length if dir is not NULL. */ fsl_size_t dirLen; /** The rid of the current checkout. May be 0 for an empty repo/checkout. Must be negative if not yet known. */ fsl_id_t rid; /** The UUID of the current checkout. Only set if this->rid is positive. */ fsl_uuid_str uuid; /** Julian mtime of the checkout version, as reported by the [event] table. */ double mtime; } ckout; /** Holds info directly related to a repo database. */ struct { /** Handle to the currently opened repository database IF repo is the main db. */ fsl_db db; /** The default user name, for operations which need one. See fsl_cx_user_set(). */ char * user; } repo; /** Holds info directly related to a global config database. */ struct { /** Handle to the currently opened global config database IF config is the main db. */ fsl_db db; } config; /** State for incrementally proparing a checkin operation. */ |
︙ | ︙ | |||
17245 17246 17247 17248 17249 17250 17251 | The deck used for incrementally building certain parts of a checkin. */ fsl_deck mf; } ckin; /** | | < < < < < | < < < < | 16863 16864 16865 16866 16867 16868 16869 16870 16871 16872 16873 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 16885 16886 16887 16888 | The deck used for incrementally building certain parts of a checkin. */ fsl_deck mf; } ckin; /** Confirmation callback. */ fsl_confirmer confirmer; /** Output channel used by fsl_output() and friends. */ fsl_outputer output; /** Can be used to tie client-specific data to the context. Its finalizer is called when fsl_cx_finalize() cleans up. */ fsl_state clientState; /** Holds error state. As a general rule, this information is updated only by routines which need to return more info than a simple integer error code. e.g. this is often used to hold |
︙ | ︙ | |||
17293 17294 17295 17296 17297 17298 17299 | two functions concurrently. */ fsl_buffer fileContent; /** Reuseable scratchpads for low-level file canonicalization buffering and whatnot. Not intended for huge content: use | | | > | 16902 16903 16904 16905 16906 16907 16908 16909 16910 16911 16912 16913 16914 16915 16916 16917 16918 | two functions concurrently. */ fsl_buffer fileContent; /** Reuseable scratchpads for low-level file canonicalization buffering and whatnot. Not intended for huge content: use this->fileContent for that. This list must stay relatively short. As of this writing, most buffers ever active at once was 5, but 1-2 is more common. @see fsl_cx_scratchpad() @see fsl_cx_scratchpad_yield() */ struct { /** Strictly-internal temporary buffers we intend to reuse many |
︙ | ︙ | |||
17331 17332 17333 17334 17335 17336 17337 | half a nanosecond, making it O(1) instead of O(small N) for the common case. */ short next; } scratchpads; /** | | | | 16941 16942 16943 16944 16945 16946 16947 16948 16949 16950 16951 16952 16953 16954 16955 16956 | half a nanosecond, making it O(1) instead of O(small N) for the common case. */ short next; } scratchpads; /** A copy of the config object passed to fsl_cx_init() (or some default). */ fsl_cx_config cxConfig; /** Flags, some (or one) of which is runtime-configurable by the client (see fsl_cx_flags_e). We can get rid of this and add the flags to the cache member along with the rest of them. |
︙ | ︙ | |||
17362 17363 17364 17365 17366 17367 17368 | */ bool caseInsensitive; /** If true, skip "dephantomization" of phantom blobs. This is a detail from fossil(1) with as-yet-undetermined utility. It's apparently only used during the remote-sync process, which this | | | < | 16972 16973 16974 16975 16976 16977 16978 16979 16980 16981 16982 16983 16984 16985 16986 16987 16988 16989 16990 16991 16992 | */ bool caseInsensitive; /** If true, skip "dephantomization" of phantom blobs. This is a detail from fossil(1) with as-yet-undetermined utility. It's apparently only used during the remote-sync process, which this API does not (as of 2021-02) yet have. */ bool ignoreDephantomizations; /** Whether or not a running commit process should be marked as private. */ bool markPrivate; /** True if fsl_crosslink_begin() has been called but fsl_crosslink_end() is still pending. */ |
︙ | ︙ | |||
17408 17409 17410 17411 17412 17413 17414 | security researcher, and we definitely must not default any symlink-related features to enabled/on. As of Feb. 2021, my personal preference, and very likely plan of attack, is to only treat SCM'd symlinks as if symlinks support is disabled. It's very unlikely that i will implement "real" symlink support but would, *solely* for compatibility with fossil(1), be convinced to permit such changes if someone else wants to implement them. | < | 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 | security researcher, and we definitely must not default any symlink-related features to enabled/on. As of Feb. 2021, my personal preference, and very likely plan of attack, is to only treat SCM'd symlinks as if symlinks support is disabled. It's very unlikely that i will implement "real" symlink support but would, *solely* for compatibility with fossil(1), be convinced to permit such changes if someone else wants to implement them. */ short allowSymlinks; /** Indicates whether or not this repo has ever seen a delta manifest. If none has ever been seen then the repository will prefer to use baseline (non-delta) manifests. Once a delta is |
︙ | ︙ | |||
17434 17435 17436 17437 17438 17439 17440 | is written to it. */ short seenDeltaManifest; /** Records whether this repository has an FTS search index. <0=undetermined, 0=no, >0=yes. | | | 17042 17043 17044 17045 17046 17047 17048 17049 17050 17051 17052 17053 17054 17055 17056 | is written to it. */ short seenDeltaManifest; /** Records whether this repository has an FTS search index. <0=undetermined, 0=no, >0=yes. */ short searchIndexExists; /** Cache for the "manifest" config setting, as used by fsl_ckout_manifest_setting(), with the caveat that if the setting changes after it is cached, we won't necessarily see that here! |
︙ | ︙ | |||
17501 17502 17503 17504 17505 17506 17507 | */ fsl_mcache mcache; /** Holds various glob lists. That said... these features are actually app-level stuff which the library itself does not resp. should not enforce. We can keep track of these for users | | | 17109 17110 17111 17112 17113 17114 17115 17116 17117 17118 17119 17120 17121 17122 17123 | */ fsl_mcache mcache; /** Holds various glob lists. That said... these features are actually app-level stuff which the library itself does not resp. should not enforce. We can keep track of these for users but the library internals _generall_ have no business using them. _THAT_ said... these have enough generic utility that we can justify storing them and _optionally_ applying them. See fsl_checkin_opt for an example of where we do this. */ struct { |
︙ | ︙ | |||
17770 17771 17772 17773 17774 17775 17776 | Potential TODO: we don't really need the uncompSize param - we can deduce it, if needed, based on pBlob's content. We cannot, however, know the UUID of the decompressed content unless the client passes it in to us. @see fsl_content_put() */ | | < | < | | 17378 17379 17380 17381 17382 17383 17384 17385 17386 17387 17388 17389 17390 17391 17392 17393 17394 17395 17396 17397 17398 17399 17400 17401 17402 17403 17404 17405 17406 17407 17408 17409 17410 17411 17412 17413 17414 17415 17416 17417 17418 17419 17420 17421 17422 | Potential TODO: we don't really need the uncompSize param - we can deduce it, if needed, based on pBlob's content. We cannot, however, know the UUID of the decompressed content unless the client passes it in to us. @see fsl_content_put() */ FSL_EXPORT int fsl_content_put_ex( fsl_cx * f, fsl_buffer const * pBlob, fsl_uuid_cstr zUuid, fsl_id_t srcId, fsl_size_t uncompSize, bool isPrivate, fsl_id_t * outRid); /** @internal Equivalent to fsl_content_put_ex(f,pBlob,NULL,0,0,0,newRid). This must only be used for saving raw (non-delta) content. @see fsl_content_put_ex() */ FSL_EXPORT int fsl_content_put( fsl_cx * f, fsl_buffer const * pBlob, fsl_id_t * newRid); /** @internal If the given blob ID refers to deltified repo content, this routine undeltifies it and replaces its content with its expanded form. Returns 0 on success, FSL_RC_MISUSE if !f, FSL_RC_NOT_A_REPO if f has no opened repository, FSL_RC_RANGE if rid is not positive, and any number of other potential errors during the db and content operations. This function treats already unexpanded content as success. @see fsl_content_deltify() */ FSL_EXPORT int fsl_content_undeltify(fsl_cx * f, fsl_id_t rid); /** @internal The converse of fsl_content_undeltify(), this replaces the storage of the given blob record so that it is a delta of srcid. |
︙ | ︙ | |||
18032 18033 18034 18035 18036 18037 18038 | /** @internal Clears and frees all (char*) members of db but leaves the rest intact. If alsoErrorState is true then the error state is also freed, else it is kept as well. */ | | | 17638 17639 17640 17641 17642 17643 17644 17645 17646 17647 17648 17649 17650 17651 17652 | /** @internal Clears and frees all (char*) members of db but leaves the rest intact. If alsoErrorState is true then the error state is also freed, else it is kept as well. */ FSL_EXPORT void fsl_db_clear_strings(fsl_db * db, bool alsoErrorState ); /** @internal Returns 0 if db appears to have a current repository schema, 1 if it appears to have an out of date schema, and -1 if it appears to not be a repository. Results are undefined if db is NULL or not opened. |
︙ | ︙ | |||
18182 18183 18184 18185 18186 18187 18188 | /** @internal Creates the ticket and ticketchng tables in f's repository db, DROPPING them if they already exist. The schema comes from fsl_schema_ticket(). | | | | 17788 17789 17790 17791 17792 17793 17794 17795 17796 17797 17798 17799 17800 17801 17802 17803 17804 17805 17806 17807 | /** @internal Creates the ticket and ticketchng tables in f's repository db, DROPPING them if they already exist. The schema comes from fsl_schema_ticket(). FIXME: add a flag specifying whether to drop or keep existing copies. Returns 0 on success. */ FSL_EXPORT int fsl_cx_ticket_create_table(fsl_cx * f); /** @internal Frees all J-card entries in the given list. li is assumed to be empty or contain (fsl_card_J*) instances. If alsoListMem is true then any memory owned |
︙ | ︙ | |||
18248 18249 18250 18251 18252 18253 18254 18255 18256 18257 18258 18259 18260 18261 | important for card ordering in generated manifests. This routine expects to get passed (fsl_card_J**) (namely from fsl_list entries), and will not work on an array of J-cards. */ FSL_EXPORT int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ); /** @internal This function updates the repo and/or global config databases with links between the dbs intended for various fossil-level bookkeeping and housecleaning. These links are not essential to fossil's functionality but assist in certain "global" operations. | > > > > > > > > > > > > > > > | 17854 17855 17856 17857 17858 17859 17860 17861 17862 17863 17864 17865 17866 17867 17868 17869 17870 17871 17872 17873 17874 17875 17876 17877 17878 17879 17880 17881 17882 | important for card ordering in generated manifests. This routine expects to get passed (fsl_card_J**) (namely from fsl_list entries), and will not work on an array of J-cards. */ FSL_EXPORT int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ); /** @internal The internal version of fsl_deck_parse(). See that function for details regarding everything but the 3rd argument. If you happen to know the _correct_ RID for the deck being parsed, pass it as the rid argument, else pass 0. A negative value will result in a FSL_RC_RANGE error. This value is (or will be) only used as an optimization in other places and only if d->f is not NULL. Passing a positive value has no effect on how the content is parsed or on the result - it only affects internal details/optimizations. */ FSL_EXPORT int fsl_deck_parse2(fsl_deck * d, fsl_buffer * src, fsl_id_t rid); /** @internal This function updates the repo and/or global config databases with links between the dbs intended for various fossil-level bookkeeping and housecleaning. These links are not essential to fossil's functionality but assist in certain "global" operations. |
︙ | ︙ | |||
18392 18393 18394 18395 18396 18397 18398 | /** @internal Part of the fsl_cx::fileContent optimization. This sets f->fileContent.used to 0 and if its capacity is over a certain (unspecified, unconfigurable) size then it is trimmed to that size. */ | | | | < < | 18013 18014 18015 18016 18017 18018 18019 18020 18021 18022 18023 18024 18025 18026 18027 18028 18029 18030 18031 18032 18033 18034 18035 18036 18037 18038 18039 18040 18041 18042 18043 18044 18045 18046 18047 18048 18049 18050 18051 18052 18053 18054 18055 18056 18057 18058 18059 | /** @internal Part of the fsl_cx::fileContent optimization. This sets f->fileContent.used to 0 and if its capacity is over a certain (unspecified, unconfigurable) size then it is trimmed to that size. */ FSL_EXPORT void fsl_cx_content_buffer_yield(fsl_cx * f); /** @internal Currently disabled (always returns 0) pending resolution of a "wha???" result from one of the underlying queries. Queues up the given artifact for a search index update. This is only intended to be called from crosslinking steps and similar content updates. Returns 0 on success. The final argument is intended only for wiki titles (the L-card of a wiki post). If the repository database has no search index or the given content is marked as private, this function returns 0 and makes no changes to the db. */ FSL_EXPORT int fsl_search_doc_touch(fsl_cx *f, fsl_satype_e saType, fsl_id_t rid, const char * docName); /** @internal Performs the same job as fsl_diff_text() but produces the results in the low-level form of an array of "copy/delete/insert triples." This is primarily intended for internal use in other library-internal algorithms, not for client code. Note all FSL_DIFF_xxx flags apply to this form. Returns 0 on success, any number of non-0 codes on error. On success *outRaw will contain the resulting array, which must eventually be fsl_free()'d by the caller. On error *outRaw is not modified. */ FSL_EXPORT int fsl_diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, int diffFlags, int ** outRaw); /** @internal If the given file name is a reserved filename (case-insensitive) on |
︙ | ︙ | |||
18559 18560 18561 18562 18563 18564 18565 | d. d->f must be set, d->rid must be set and valid and d's contents must accurately represent the stored manifest for the given rid. This is normally run just after the insertion of a new manifest, but is sometimes also run after reading a deck from the database (in order to rebuild all db relations and add/update the timeline entry). | | | | | 18178 18179 18180 18181 18182 18183 18184 18185 18186 18187 18188 18189 18190 18191 18192 18193 18194 18195 18196 18197 18198 18199 18200 18201 18202 18203 18204 18205 18206 18207 18208 18209 18210 18211 18212 18213 18214 18215 18216 18217 18218 18219 18220 | d. d->f must be set, d->rid must be set and valid and d's contents must accurately represent the stored manifest for the given rid. This is normally run just after the insertion of a new manifest, but is sometimes also run after reading a deck from the database (in order to rebuild all db relations and add/update the timeline entry). Returns 0 on succes, FSL_RC_MISUSE if !d or !d->f, FSL_RC_RANGE if d->rid<=0, FSL_RC_MISUSE (with more error info in f) if d does not contain all required cards for its d->type value. It may return various other codes from the many routines it delegates work to. Crosslinking of ticket artifacts is currently (2021-03) missing. Design note: d "really should" be const here but some internals (d->F.cursor and delayed baseline loading) prohibit it. @see fsl_deck_crosslink_one() */ FSL_EXPORT int fsl_deck_crosslink( fsl_deck /* const */ * d ); /** @internal Run automatically by fsl_deck_save(), so it needn't normally be run aside from that, at least not from average client code. This is a convience form of crosslinking which must only be used when a single deck (and only a single deck) is to be crosslinked. This function wraps the crosslinking in fsl_crosslink_begin() and fsl_crosslink_end(), but otherwise behaves the same as fsl_deck_crosslink(). If crosslinking fails, any in-progress transaction will be flagged as failed. Returns 0 on success. */ FSL_EXPORT int fsl_deck_crosslink_one( fsl_deck * d ); /** @internal Checks whether the given filename is "safe" for writing to within f's current checkout. zFilename must be in canonical form: only '/' directory separators. |
︙ | ︙ | |||
18668 18669 18670 18671 18672 18673 18674 | On error returns non-0, pnChng and aiChng are not modified, and f's error state might (depending on the error) contain a description of the problem. Space to hold *aiChng is obtained from fsl_malloc() and must be released by the caller. */ | | | | | | | | 18287 18288 18289 18290 18291 18292 18293 18294 18295 18296 18297 18298 18299 18300 18301 18302 18303 18304 18305 18306 | On error returns non-0, pnChng and aiChng are not modified, and f's error state might (depending on the error) contain a description of the problem. Space to hold *aiChng is obtained from fsl_malloc() and must be released by the caller. */ FSL_EXPORT int fsl_cx_find_filename_changes(fsl_cx * f, fsl_id_t iFrom, fsl_id_t iTo, bool revOK, uint32_t *pnChng, fsl_id_t **aiChng); /** Bitmask of file change types for use with fsl_is_locally_modified(). */ enum fsl_localmod_e { /** Sentinel value. */ |
︙ | ︙ | |||
18900 18901 18902 18903 18904 18905 18906 | Undocumented. For internal debugging only. */ void fsl__dump_triples(fsl_diff_cx const * const p, char const * zFile, int ln ); /** @internal | < < < < < < < < < | | 18519 18520 18521 18522 18523 18524 18525 18526 18527 18528 18529 18530 18531 18532 18533 | Undocumented. For internal debugging only. */ void fsl__dump_triples(fsl_diff_cx const * const p, char const * zFile, int ln ); /** @internal Maximum length of a line in a text file, in bytes. (2**13 = 8192 bytes) */ #define FSL_LINE_LENGTH_MASK_SZ 15 /** @internal */ #define FSL_LINE_LENGTH_MASK ((1<<FSL_LINE_LENGTH_MASK_SZ)-1) |
︙ | ︙ | |||
19509 19510 19511 19512 19513 19514 19515 | APIs will be necessary for most clients. The process of creating an artifact looks a lot like the following code example. We have elided error checking for readability purposes, but in fact this code has undefined behaviour if error codes are not checked and appropriately reacted to. | < > | < < > | 19119 19120 19121 19122 19123 19124 19125 19126 19127 19128 19129 19130 19131 19132 19133 19134 19135 19136 19137 19138 19139 19140 19141 19142 19143 19144 19145 19146 19147 | APIs will be necessary for most clients. The process of creating an artifact looks a lot like the following code example. We have elided error checking for readability purposes, but in fact this code has undefined behaviour if error codes are not checked and appropriately reacted to. @code fsl_deck deck = fsl_deck_empty; fsl_deck * d = &deck; // for typing convenience fsl_deck_init( fslCtx, d, FSL_SATYPE_CONTROL ); // must come first fsl_deck_D_set( d, fsl_julian_now() ); fsl_deck_U_set( d, "your-fossil-name", -1 ); fsl_deck_T_add( d, FSL_TAGTYPE_ADD, "...uuid being tagged...", "tag-name", "optional tag value"); ... // Now output it to stdout: fsl_deck_output( f, d, fsl_output_f_FILE, stdout ); // See also: fsl_deck_save(), which stores it in the db and // "crosslinks" it. fsl_deck_finalize(d); @endcode The order the cards are added to the deck is irrelevant - they will be output in the order specified by the Fossil specs regardless of their insertion order. Each setter/adder function knows, based on the deck's type (set via fsl_deck_init()), whether the given card type is legal, and will return an error (probably FSL_RC_TYPE) if an attempt is made to add a card which is illegal |
︙ | ︙ | |||
19571 19572 19573 19574 19575 19576 19577 | Tip: implementing a "dry-run" mode for most fossil operations is trivial by starting a transaction before performing the operations. Many operations run in a transaction, but if the client starts one of his own they can "dry-run" any op by simply rolling back the transaction he started. Abstractly, that looks like this pseudocode: | < > < > | 19180 19181 19182 19183 19184 19185 19186 19187 19188 19189 19190 19191 19192 19193 19194 19195 19196 19197 19198 19199 19200 | Tip: implementing a "dry-run" mode for most fossil operations is trivial by starting a transaction before performing the operations. Many operations run in a transaction, but if the client starts one of his own they can "dry-run" any op by simply rolling back the transaction he started. Abstractly, that looks like this pseudocode: @code db.begin(); fsl.something(); fsl.somethingElse(); if( dryRun ) db.rollback(); else db.commit(); @endcode */ /** @page page_code_conventions Code Conventions Project and Code Conventions... Foreward: all of this more or less evolved organically or was |
︙ | ︙ | |||
20088 20089 20090 20091 20092 20093 20094 | /** @def FCLI_FLAG_xxx The various FCLI_FLAG_xxx macros are convenience-form initializers for fcli_cliflag instances for use in initializing a fcli_cliflag array. Example usage: | < > < > | 19697 19698 19699 19700 19701 19702 19703 19704 19705 19706 19707 19708 19709 19710 19711 19712 19713 19714 19715 19716 19717 19718 19719 19720 19721 19722 19723 19724 19725 | /** @def FCLI_FLAG_xxx The various FCLI_FLAG_xxx macros are convenience-form initializers for fcli_cliflag instances for use in initializing a fcli_cliflag array. Example usage: @code bool flag1 = true, flag2 = false; int32_t flag3 = 0; const char * flag4 = 0; const fcli_cliflag cliFlags[] = { FCLI_FLAG_BOOL("x","xyz",&flag1,"Flag 1."), FCLI_FLAG_BOOL_INVERT(NULL,"yzx",&flag2,"Flag 2."), FCLI_FLAG_INT32("z",NULL,"value",&flag3,"Flag 3"), FCLI_FLAG("f","file","filename",&flag4, "Input file. May optionally be passed as the first " "non-flag argument."), fcli_cliflag_empty_m // list MUST end with this (or equivalent) }; fcli.cliFlags = cliFlags; @endcode BE CAREFUL with the data types, as we're using (void*) to access data of an arbitrary type, the type being defined by a separate field in the fcli_cliflag object. */ #define FCLI_FLAG_xxx /* for doc purposes only */ //Members: |
︙ | ︙ | |||
20214 20215 20216 20217 20218 20219 20220 | FCLI_RC_FLAG_AGAIN will be traversed repeatedly before moving on to the next flag. This ordering may have side-effects on how the app sets up its flag handling. In particular, this arrangement makes the common cases easy to implement but makes certain more complicated situations effectively impossible to implement. For example: | < > < > | 19823 19824 19825 19826 19827 19828 19829 19830 19831 19832 19833 19834 19835 19836 19837 19838 19839 | FCLI_RC_FLAG_AGAIN will be traversed repeatedly before moving on to the next flag. This ordering may have side-effects on how the app sets up its flag handling. In particular, this arrangement makes the common cases easy to implement but makes certain more complicated situations effectively impossible to implement. For example: @code -x=1 -y=2 -x=3 -y=4 @endcode Assuming the definition of the -y flag is first in this array and has a callback which returns FCLI_RC_FLAG_AGAIN, the two -x flags will be processed before either of the -y flags. Thus it is impossible (using this method of traversal) to change the behavior of the -y flags based on the left-closest -x value. |
︙ | ︙ | |||
20279 20280 20281 20282 20283 20284 20285 20286 20287 20288 20289 20290 20291 20292 | /** A verbosity level counter. Starts at 0 (no verbosity) and goes up for higher verbosity levels. Currently levels 1 and 2 are intended for app-level use and level 3 for library-level use. */ unsigned short verbose; } clientFlags; /** Transient settings and flags. These are bits which are used during (or very shortly after) fcli_setup() but have no effect if modified after that. */ struct { | > > > > > > > | 19888 19889 19890 19891 19892 19893 19894 19895 19896 19897 19898 19899 19900 19901 19902 19903 19904 19905 19906 19907 19908 | /** A verbosity level counter. Starts at 0 (no verbosity) and goes up for higher verbosity levels. Currently levels 1 and 2 are intended for app-level use and level 3 for library-level use. */ unsigned short verbose; /** True if the -D|--dry-run flag is seen during initial arguments processing. ::fcli does not use this itself - it is intended as a convenience for applications. */ bool dryRun; } clientFlags; /** Transient settings and flags. These are bits which are used during (or very shortly after) fcli_setup() but have no effect if modified after that. */ struct { |
︙ | ︙ | |||
20378 20379 20380 20381 20382 20383 20384 | convention is that the app immediately returns the result of fcli_end_of_main(THE_RESULT_CODE) from main(). That function will treat FCLI_RC_HELP as a non-error and will report any error state pending in the fcli_cx() object. Example of intended basic usage: | < > | 19994 19995 19996 19997 19998 19999 20000 20001 20002 20003 20004 20005 20006 20007 20008 | convention is that the app immediately returns the result of fcli_end_of_main(THE_RESULT_CODE) from main(). That function will treat FCLI_RC_HELP as a non-error and will report any error state pending in the fcli_cx() object. Example of intended basic usage: @code int main(int argc, char const * const * argv){ ... // Optional fcli_cliflag setup: fcli_cliflag const cliFlags[] = { ..., fcli_cliflag_empty_m }; |
︙ | ︙ | |||
20403 20404 20405 20406 20407 20408 20409 | if(rc) goto end; ... app logic ... end: return fcli_end_of_main(rc); } | < > < < < < < < < < < < < < < < < | 20019 20020 20021 20022 20023 20024 20025 20026 20027 20028 20029 20030 20031 20032 20033 20034 20035 20036 20037 20038 | if(rc) goto end; ... app logic ... end: return fcli_end_of_main(rc); } @endcode @see fcli_pre_setup() */ FSL_EXPORT int fcli_setup(int argc, char const * const * argv ); /** The first time this is called, it swaps out libfossil's default allocator with a fail-fast one which abort()s on allocation error. This is normally called by fcli_setup(), but that also means that it's illegal to use fsl_malloc() and friends before calling that routine. If an application really needs to use fsl_malloc() before calling fcli_setup(), it must call this first in order to |
︙ | ︙ | |||
20506 20507 20508 20509 20510 20511 20512 | FSL_EXPORT bool fcli_flag(char const * opt, const char ** value); /** Works like fcli_flag() but tries two argument forms, in order. It is intended to be passed short and long forms, but can be passed two aliases or similar. It accepts NULL for either form. | < > < > | 20107 20108 20109 20110 20111 20112 20113 20114 20115 20116 20117 20118 20119 20120 20121 20122 20123 20124 20125 | FSL_EXPORT bool fcli_flag(char const * opt, const char ** value); /** Works like fcli_flag() but tries two argument forms, in order. It is intended to be passed short and long forms, but can be passed two aliases or similar. It accepts NULL for either form. @code const char * v = NULL; fcli_flag2("n", "limit", &v); if(v) { ... } @endcode */ FSL_EXPORT bool fcli_flag2(char const * opt1, char const * opt2, const char ** value); /** Works similarly to fcli_flag2(), but if no flag is found and value is not NULL then *value is assigned to the return value of |
︙ | ︙ | |||
20624 20625 20626 20627 20628 20629 20630 | Describes a named callback command. @see fcli_dispatch_commands() */ struct fcli_command { /** The name of the command. */ char const * name; | < < < < < < < | 20225 20226 20227 20228 20229 20230 20231 20232 20233 20234 20235 20236 20237 20238 20239 20240 20241 20242 | Describes a named callback command. @see fcli_dispatch_commands() */ struct fcli_command { /** The name of the command. */ char const * name; /** Brief description, for use in generating help text. */ char const * briefDescription; /** The callback for this command. */ fcli_command_f f; /** Must be NULL or an array compatible with fcli_process_flags(). Can be used from within this->f for command-specific flag dispatching, as well as help text generation. */ fcli_cliflag const * flags; }; |
︙ | ︙ | |||
20666 20667 20668 20669 20670 20671 20672 | _next_ argument, and if it is "help" then this function passes that flags member to fcli_command_help() to output help, then returns 0. */ FSL_EXPORT int fcli_dispatch_commands( fcli_command const * cmdList, bool reportErrors); | < < < < < < < < > < > | 20260 20261 20262 20263 20264 20265 20266 20267 20268 20269 20270 20271 20272 20273 20274 20275 20276 20277 20278 20279 20280 20281 20282 20283 20284 20285 20286 20287 20288 20289 20290 20291 20292 | _next_ argument, and if it is "help" then this function passes that flags member to fcli_command_help() to output help, then returns 0. */ FSL_EXPORT int fcli_dispatch_commands( fcli_command const * cmdList, bool reportErrors); /** A minor helper function intended to be passed the pending result code of the main() routine. This function outputs any pending error state in fcli. Returns one of EXIT_SUCCESS if mainRc is 0 and fcli had no pending error report, otherwise it returns EXIT_FAILURE. This function does not clean up fcli - that is handled via an atexit() handler. It is intended to be called once at the very end of main: @code int main(){ int rc; ...set up fcli...assign rc... return fcli_end_of_main(rc); } @endcode As a special case, if mainRc is FCLI_RC_HELP, it is assumed to be the result of the fcli --help flag handling, and is treated as if it were 0. @see fcli_error() @see fcli_err_set() |
︙ | ︙ | |||
20770 20771 20772 20773 20774 20775 20776 | /** Requires that cmd be an array of fcli_command objects with a trailing entry which has a NULL name. This function iterates over them and outputs help text based on each one's (name, briefDescription, flags) members. | < < < < | | < < < < < | 20357 20358 20359 20360 20361 20362 20363 20364 20365 20366 20367 20368 20369 20370 20371 20372 20373 20374 | /** Requires that cmd be an array of fcli_command objects with a trailing entry which has a NULL name. This function iterates over them and outputs help text based on each one's (name, briefDescription, flags) members. If the 2nd argument is true, only the help for the single given object is output, not any adjacent array members (if any). */ FSL_EXPORT void fcli_command_help(fcli_command const * cmd, bool onlyOne); /** If fcli has a checkout opened, this dumps various info about it to its output channel. Returns 0 on success, FSL_RC_NOT_A_CKOUT if no checkout is opened, or some other non-0 code on error. If the useUtc argument is true, it uses UTC timestamps, else |
︙ | ︙ | |||
20889 20890 20891 20892 20893 20894 20895 | fsl_cx-level error state provides. e.g. it may provide a hint about how to recover. */ FSL_EXPORT int fcli_fingerprint_check(bool reportImmediately); /** Returns the "tail" part of the argv[0] string which was passed to | | | | | 20467 20468 20469 20470 20471 20472 20473 20474 20475 20476 20477 20478 20479 20480 20481 20482 20483 | fsl_cx-level error state provides. e.g. it may provide a hint about how to recover. */ FSL_EXPORT int fcli_fingerprint_check(bool reportImmediately); /** Returns the "tail" part of the argv[0] string which was passed to fcli_setup() or fcli_setup2(), or NULL if neither of those have yet been called. The "tail" part is the part immediately after the final '/' or '\\' character. */ FSL_EXPORT char const * fcli_progname(); /** Color theme IDs for use with fcli_diff_colors. */ enum fcli_diff_colors_e{ |
︙ | ︙ | |||
20919 20920 20921 20922 20923 20924 20925 | /** Populates the given fsl_diff_opt::ansiColors state with values dependend on the second argument. */ FSL_EXPORT void fcli_diff_colors(fsl_diff_opt * const tgt, fcli_diff_colors_e theme); | < < < < < < < < < < < | 20497 20498 20499 20500 20501 20502 20503 20504 20505 20506 20507 20508 20509 20510 20511 | /** Populates the given fsl_diff_opt::ansiColors state with values dependend on the second argument. */ FSL_EXPORT void fcli_diff_colors(fsl_diff_opt * const tgt, fcli_diff_colors_e theme); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* _ORG_FOSSIL_SCM_FCLI_H_INCLUDED_ */ /* end of file ../include/fossil-scm/fossil-cli.h */ |