Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From 0.8 To 0.9
2022-03-04 05:17 | Bump version number: 0.10 (check-in: a761c1f066 user: mark tags: trunk) | |
2022-03-04 05:14 | CHANGES for 0.9 (check-in: aef65fd1a9 user: mark tags: trunk, 0.9) | |
2022-03-04 04:53 | Plug small memleak when interactively changing diff format. (check-in: d64682655a user: mark tags: trunk) | |
2022-01-09 15:42 | Bump version number: 0.9 (check-in: 1291047898 user: mark tags: trunk) | |
2022-01-09 15:32 | CHANGES for 0.8 (check-in: ab629f6e73 user: mark tags: trunk, 0.8) | |
2022-01-09 15:29 | Fix vertical split regression and DB locking issue. (check-in: 250d2884e8 user: mark tags: trunk) | |
Changes to CHANGES.md.
1 2 | **fnc 0.8** 2022-01-10 | > > > > > > > > > > > > > > > > > > > > > > > > | | 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 | **fnc 0.9** 2022-03-04 - Add blame command `--line` option to open annotated file at the specified line - merge upstream libfossil changes that eliminate gcc compiler warnings - adopt libfossil diff v1 implementation into fnc tree to replace v2 API - refactor diff implementation to comport with code style - fix `--whitespace` option (`w` keymap) diffv2 regression from 0.7 [105123b40e] - implement diff `--line-numbers` opt (`L` keymap) to display file line numbers - implement display of enclosing function in diff chunk headers (enabled by default like CVS and Git, disabled with `--no-prototype` or `p` keymap); works with all C-like languages, but not Lisps or markup languages - upstream libfossil fix for segv bug when fsl_int_d and fsl_id_t differ in size - fix incorrect malloc producing undefined behaviour on 32-bit platforms - add unveil (c)reate permission to the checkout dir to fix some diff commands - change conflicting diff and blame view key maps - implement taggable timeline nodes to interactively diff arbitrary commits - implement `C` timeline keymap to diff selected commit against local changes - clean code of OS-dependent ifdefs by consolidating them in called functions - add support for landlock Linux security module in Linux builds - significant diff driver refactoring for finer granularity in view manipulation - implement `--sbs` (`S` keymap) to display side-by-side formatted diffs - add FNC_COLOUR_DIFF_SBS_EDIT option to set colour of edited lines in SBS diff - plug small memory leak when interactively changing diff format **fnc 0.8** 2022-01-10 - fix vertical split view init regression from 0.7 when terminal < 120 cols wide - fix DB lock when opening horizontal split that signals the timeline thread **fnc 0.7** 2022-01-09 - factor out common make(1) and gmake build bits - make build depend on make file - make all commands compatible with `-R|--repository` (i.e., no checkout needed) |
︙ | ︙ |
Changes to README.md.
1 2 | # README | | | 1 2 3 4 5 6 7 8 9 10 | # README # fnc 0.9 ## An interactive ncurses browser for [Fossil][0] repositories. `fnc` uses [libfossil][1] to create a [`fossil ui`][2] experience in the terminal. Tested and confirmed to run on the following amd64 systems (additional platforms |
︙ | ︙ |
Changes to fnc.bld.mk.
1 2 3 4 5 6 7 8 | # # FNC Common Build # # CONFIGURATION CC ?= cc PREFIX ?= /usr/local MANDIR ?= /share/man | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # # FNC Common Build # # CONFIGURATION CC ?= cc PREFIX ?= /usr/local MANDIR ?= /share/man VERSION ?= 0.9 # FLAGS NEEDED TO BUILD SQLITE3 SQLITE_CFLAGS = ${CFLAGS} -Wall -Werror -Wno-sign-compare -pedantic -std=c99 \ -DNDEBUG=1 \ -DSQLITE_DQS=0 \ -DSQLITE_DEFAULT_MEMSTATUS=0 \ -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \ -DSQLITE_LIKE_DOESNT_MATCH_BLOBS \ -DSQLITE_OMIT_DECLTYPE \ -DSQLITE_OMIT_PROGRESS_CALLBACK \ -DSQLITE_OMIT_SHARED_CACHE \ -DSQLITE_OMIT_LOAD_EXTENSION \ |
︙ | ︙ | |||
53 54 55 56 57 58 59 | lib/sqlite3.o: lib/sqlite3.c lib/sqlite3.h ${CC} ${SQLITE_CFLAGS} -c $< -o $@ lib/libfossil.o: lib/libfossil.c lib/libfossil.h ${CC} ${FOSSIL_CFLAGS} -c $< -o $@ | > > > | | | > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | lib/sqlite3.o: lib/sqlite3.c lib/sqlite3.h ${CC} ${SQLITE_CFLAGS} -c $< -o $@ lib/libfossil.o: lib/libfossil.c lib/libfossil.h ${CC} ${FOSSIL_CFLAGS} -c $< -o $@ src/diff.o: src/diff.c include/diff.h ${CC} ${FNC_CFLAGS} -c $< -o $@ src/fnc.o: src/fnc.c include/settings.h include/diff.h fnc.bld.mk ${CC} ${FNC_CFLAGS} -c $< -o $@ src/fnc: src/fnc.o src/diff.o lib/libfossil.o lib/sqlite3.o fnc.bld.mk ${CC} -o $@ src/fnc.o src/diff.o lib/libfossil.o lib/sqlite3.o \ ${FNC_LDFLAGS} install: install -s -m 0755 src/fnc ${PREFIX}/bin/fnc install -m 0644 src/fnc.1 ${PREFIX}${MANDIR}/man1/fnc.1 uninstall: rm -f ${PREFIX}/bin/fnc ${PREFIX}${MANDIR}/man1/fnc.1 |
︙ | ︙ |
Added include/diff.h.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | /* * Copyright (c) 2022 Mark Jamsek <mark@bsdbox.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "libfossil.h" #include "settings.h" enum fnc_diff_flag { FNC_DIFF_IGNORE_EOLWS = 0x01, FNC_DIFF_IGNORE_ALLWS = 0x03, FNC_DIFF_SIDEBYSIDE = 1 << 2, FNC_DIFF_VERBOSE = 1 << 3, FNC_DIFF_BRIEF = 1 << 4, FNC_DIFF_HTML = 1 << 5, FNC_DIFF_LINENO = 1 << 6, FNC_DIFF_NOOPT = 1 << 7, /* og. 0x0100 */ FNC_DIFF_INVERT = 1 << 8, /* og. 0x0200 */ FNC_DIFF_NOTTOOBIG = 1 << 9, /* og. 0x0800 */ FNC_DIFF_STRIP_EOLCR = 1 << 10, /* og. 0x1000 */ FNC_DIFF_ANSI_COLOR = 1 << 11, /* og. 0x2000 */ FNC_DIFF_PROTOTYPE = 1 << 12 #define FNC_DIFF_CONTEXT_EX (((uint64_t)0x04) << 32) /* Allow 0 context */ #define FNC_DIFF_CONTEXT_MASK ((uint64_t)0x0000ffff) /* Default context */ #define FNC_DIFF_WIDTH_MASK ((uint64_t)0x0fff0000) /* SBS column width */ }; struct diff_out_state { fsl_output_f out; /* Output callback */ void *state; /* State for this->out() */ enum line_type *lines; /* Diff line type (e.g., minus, plus) */ uint32_t *idx; /* Index into this->lines */ int rc; /* Error reporting */ char ansi; /* ANSI colour code */ struct { const fsl_buffer *file; /* Diffed file */ char *signature; /* Matching function */ uint32_t lastmatch; /* Match line index */ uint32_t lastline; /* Last line scanned */ fsl_size_t offset; /* Match byte offset */ } proto; }; static const struct diff_out_state diff_out_state_empty = { NULL, NULL, NULL, 0, 0, 0, { NULL, NULL, 0, 0, 0 } }; struct sbsline { struct diff_out_state *output; fsl_buffer *cols[5]; /* Pointers to output columns */ const char *tag; /* <span> tag */ const char *tag2; /* <span> tag */ int idx; /* Write tag before idx */ int end; /* Close tag before end */ int idx2; /* Write tag2 before idx2 */ int end2; /* Close tag2 before end2 */ int width; /* Max column width in diff */ bool esc; /* Escape html characters */ void *regex; /* Colour matching lines */ }; int fnc_diff_text_raw(fsl_buffer const *, fsl_buffer const *, int, int **); int fnc_diff_text_to_buffer(fsl_buffer const *, fsl_buffer const *, fsl_buffer *, enum line_type **, uint32_t *, short, short, int ); int fnc_diff_text(fsl_buffer const *, fsl_buffer const *, fsl_output_f, void *, short, short, int ); int fnc_diff_blobs(fsl_buffer const *, fsl_buffer const *, fsl_output_f, void *, enum line_type **, uint32_t *, uint16_t, short, int, int **); int fnc_output_f_diff_out(void *, void const *, fsl_size_t); int diff_outf(struct diff_out_state *, char const *, ... ); int diff_out(struct diff_out_state * const, void const *, fsl_int_t); char *match_chunk_function(struct diff_out_state *const, uint32_t); int buffer_copy_lines_from(fsl_buffer *const, const fsl_buffer *const, fsl_size_t *, fsl_size_t, fsl_size_t); uint64_t fnc_diff_flags_convert(int); int diff_context_lines(uint64_t); int match_dline(fsl_dline *, fsl_dline *); bool longest_common_subsequence(const char *z, int, const char *, int, int *); int unidiff(fsl__diff_cx *, struct diff_out_state *, void *, uint16_t, uint64_t); int unidiff_lineno(struct diff_out_state *, int, int, bool); int unidiff_txt( struct diff_out_state *const, char, fsl_dline *, int, void *); int sbsdiff(fsl__diff_cx *, struct diff_out_state *, void *, uint16_t, uint64_t); int alloc_lines_and_width(fsl__diff_cx *, int *, uint16_t, uint32_t, enum line_type **); unsigned short etcount(const char *str, unsigned short n); int sbsdiff_width(uint64_t); int sbsdiff_separator(struct sbsline *, int, int); int sbsdiff_lineno(struct sbsline *, int, int); void sbsdiff_shift_left(struct sbsline *, const char *); void sbsdiff_simplify_line(struct sbsline *, const char *); int sbsdiff_column(struct diff_out_state *, fsl_buffer const *, int); int sbsdiff_txt(struct sbsline *, fsl_dline *, int); int sbsdiff_newline(struct sbsline *); int sbsdiff_space(struct sbsline *, int, int); int sbsdiff_marker(struct sbsline *, const char *, const char *); int sbsdiff_close_gap(int *); unsigned char *sbsdiff_align(fsl_dline *, int, fsl_dline *, int); int sbsdiff_write_change(struct sbsline *, fsl_dline *, int, fsl_dline *, int); |
Changes to include/settings.h.
︙ | ︙ | |||
32 33 34 35 36 37 38 39 40 41 42 43 44 45 | _(pfx, COLOUR_USER), \ _(pfx, COLOUR_DATE), \ _(pfx, COLOUR_DIFF_META), \ _(pfx, COLOUR_DIFF_MINUS), \ _(pfx, COLOUR_DIFF_PLUS), \ _(pfx, COLOUR_DIFF_CHUNK), \ _(pfx, COLOUR_DIFF_TAGS), \ _(pfx, COLOUR_TREE_LINK), \ _(pfx, COLOUR_TREE_DIR), \ _(pfx, COLOUR_TREE_EXEC), \ _(pfx, COLOUR_BRANCH_OPEN), \ _(pfx, COLOUR_BRANCH_CLOSED), \ _(pfx, COLOUR_BRANCH_CURRENT), \ _(pfx, COLOUR_BRANCH_PRIVATE), \ | > | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | _(pfx, COLOUR_USER), \ _(pfx, COLOUR_DATE), \ _(pfx, COLOUR_DIFF_META), \ _(pfx, COLOUR_DIFF_MINUS), \ _(pfx, COLOUR_DIFF_PLUS), \ _(pfx, COLOUR_DIFF_CHUNK), \ _(pfx, COLOUR_DIFF_TAGS), \ _(pfx, COLOUR_DIFF_SBS_EDIT), \ _(pfx, COLOUR_TREE_LINK), \ _(pfx, COLOUR_TREE_DIR), \ _(pfx, COLOUR_TREE_EXEC), \ _(pfx, COLOUR_BRANCH_OPEN), \ _(pfx, COLOUR_BRANCH_CLOSED), \ _(pfx, COLOUR_BRANCH_CURRENT), \ _(pfx, COLOUR_BRANCH_PRIVATE), \ |
︙ | ︙ | |||
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 | _(pfx, MONO) #define INPUT_TYPE_ENUM(pfx, _) \ _(pfx, ALPHA), \ _(pfx, NUMERIC) #define LINE_TYPE_ENUM(pfx, _) \ _(pfx, TIMELINE_HEADER), \ _(pfx, TIMELINE_COMMIT), \ _(pfx, DIFF_HEADER), \ _(pfx, DIFF_ARTIFACT), \ _(pfx, DIFF_USER), \ _(pfx, DIFF_TAGS), \ _(pfx, DIFF_DATE), \ _(pfx, DIFF_COMMENT), \ _(pfx, DIFF_CHANGESET), \ _(pfx, DIFF_INDEX), \ _(pfx, DIFF_META), \ _(pfx, DIFF_MINUS), \ _(pfx, DIFF_PLUS), \ _(pfx, DIFF_CHUNK), \ #define VIEW_MODE_ENUM(pfx, _) \ _(pfx, NONE), \ _(pfx, VERT), \ _(pfx, HRZN) #define ENUM_INFO(_) \ | > > > > | 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 | _(pfx, MONO) #define INPUT_TYPE_ENUM(pfx, _) \ _(pfx, ALPHA), \ _(pfx, NUMERIC) #define LINE_TYPE_ENUM(pfx, _) \ _(pfx, BLANK), \ _(pfx, TIMELINE_HEADER), \ _(pfx, TIMELINE_COMMIT), \ _(pfx, DIFF_HEADER), \ _(pfx, DIFF_ARTIFACT), \ _(pfx, DIFF_USER), \ _(pfx, DIFF_TAGS), \ _(pfx, DIFF_DATE), \ _(pfx, DIFF_COMMENT), \ _(pfx, DIFF_CHANGESET), \ _(pfx, DIFF_INDEX), \ _(pfx, DIFF_META), \ _(pfx, DIFF_MINUS), \ _(pfx, DIFF_PLUS), \ _(pfx, DIFF_EDIT), \ _(pfx, DIFF_CONTEXT), \ _(pfx, DIFF_CHUNK), \ _(pfx, DIFF_SEPARATOR) #define VIEW_MODE_ENUM(pfx, _) \ _(pfx, NONE), \ _(pfx, VERT), \ _(pfx, HRZN) #define ENUM_INFO(_) \ |
︙ | ︙ |
Changes to lib/libfossil.c.
︙ | ︙ | |||
5940 5941 5942 5943 5944 5945 5946 | } } #endif else{ char const * zLocalRoot; char const * zFull; fsl_size_t nLocalRoot; | | | 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 | } } #endif else{ char const * zLocalRoot; char const * zFull; fsl_size_t nLocalRoot; fsl_size_t nFull = 0; fsl_buffer * const full = fsl__cx_scratchpad(f); int (*xCmp)(char const *, char const *,fsl_size_t); bool endsWithSlash; assert(f->ckout.dir); zLocalRoot = f->ckout.dir; assert(zLocalRoot); assert(*zLocalRoot); |
︙ | ︙ | |||
8711 8712 8713 8714 8715 8716 8717 | fsl__cx_scratchpad_yield(f, fname); return rc; } int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * fc, | | | | 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 | fsl__cx_scratchpad_yield(f, fname); return rc; } int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * fc, fsl_time_t * const repoMtime, fsl_time_t * const localMtime){ int rc = 0; fsl_id_t fid = 0; fsl_fstat fst = fsl_fstat_empty; if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT; if(0>=vid){ fsl_ckout_version_info(f, &vid, NULL); |
︙ | ︙ | |||
8870 8871 8872 8873 8874 8875 8876 | fsl_cx * f = fcli.f; assert(f); if(fcli.transient.repoDbArg){ FCLI_V3(("Trying to open repo db file [%s]...\n", fcli.transient.repoDbArg)); rc = fsl_repo_open( f, fcli.transient.repoDbArg ); } else if(fcli.clientFlags.checkoutDir){ | < < | 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 | fsl_cx * f = fcli.f; assert(f); if(fcli.transient.repoDbArg){ FCLI_V3(("Trying to open repo db file [%s]...\n", fcli.transient.repoDbArg)); rc = fsl_repo_open( f, fcli.transient.repoDbArg ); } else if(fcli.clientFlags.checkoutDir){ char const * dirName = fcli.clientFlags.checkoutDir; FCLI_V3(("Trying to open checkout from [%s]...\n", dirName)); rc = fsl_ckout_open_dir(f, dirName, true); FCLI_V3(("checkout open rc=%s\n", fsl_rc_cstr(rc))); /* if(FSL_RC_NOT_FOUND==rc) rc = FSL_RC_NOT_A_CKOUT; */ if(rc){ if(!fsl_cx_err_get(f,NULL,NULL)){ rc = fsl_cx_err_set(f, rc, "Opening of checkout under " "[%s] failed with code %d (%s).", dirName, rc, fsl_rc_cstr(rc)); } } if(rc) return rc; } if(!rc){ if(fcli.clientFlags.verbose>1){ fsl_db * dbC = fsl_cx_db_ckout(f); fsl_db * dbR = fsl_cx_db_repo(f); if(dbC){ |
︙ | ︙ | |||
14136 14137 14138 14139 14140 14141 14142 | assert('/' == f->ckout.dir[f->ckout.dirLen-1]); f->flags |= FSL_CX_F_IS_OPENING_CKOUT; rc = fsl_repo_open_for_ckout(f); f->flags &= ~FSL_CX_F_IS_OPENING_CKOUT; if(!rc) rc = fsl_cx_after_open(f); if(rc){ /* Is this sane? Is not doing it sane? */ | | | 14134 14135 14136 14137 14138 14139 14140 14141 14142 14143 14144 14145 14146 14147 14148 | assert('/' == f->ckout.dir[f->ckout.dirLen-1]); f->flags |= FSL_CX_F_IS_OPENING_CKOUT; rc = fsl_repo_open_for_ckout(f); f->flags &= ~FSL_CX_F_IS_OPENING_CKOUT; if(!rc) rc = fsl_cx_after_open(f); if(rc){ /* Is this sane? Is not doing it sane? */ fsl_close_scm_dbs(f); } } end: fsl__cx_scratchpad_yield(f, buf); fsl__cx_scratchpad_yield(f, bufD); return rc; } |
︙ | ︙ | |||
23101 23102 23103 23104 23105 23106 23107 23108 23109 23110 23111 23112 23113 23114 | if(0==rc){ f->cache.isCrosslinking = true; }else{ fsl_cx_transaction_end(f, true); } return rc; } #undef MARKER #undef AGE_FUDGE_WINDOW #undef AGE_ADJUST_INCREMENT #undef F_at /* end of file ./src/deck.c */ /* start of file ./src/delta.c */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 23099 23100 23101 23102 23103 23104 23105 23106 23107 23108 23109 23110 23111 23112 23113 23114 23115 23116 23117 23118 23119 23120 23121 23122 23123 23124 23125 23126 23127 23128 23129 23130 23131 23132 23133 23134 23135 23136 23137 23138 23139 23140 23141 23142 23143 23144 23145 23146 23147 23148 23149 23150 23151 23152 23153 23154 23155 23156 23157 23158 23159 23160 23161 23162 23163 23164 23165 23166 23167 23168 23169 23170 23171 23172 23173 23174 23175 23176 23177 23178 23179 23180 23181 23182 23183 23184 23185 23186 23187 23188 23189 23190 | if(0==rc){ f->cache.isCrosslinking = true; }else{ fsl_cx_transaction_end(f, true); } return rc; } int fsl_deck_foreach(fsl_cx * const f, fsl_satype_e type, fsl_deck_visitor_f visitor, void * visitorState){ int rc = 0; fsl_stmt q = fsl_stmt_empty; switch(type){ case FSL_SATYPE_CHECKIN: case FSL_SATYPE_FORUMPOST: case FSL_SATYPE_CONTROL: rc = fsl_cx_prepare(f, &q, "SELECT objid FROM event WHERE type=%Q", fsl_satype_event_cstr(type)); break; case FSL_SATYPE_WIKI: rc = fsl_cx_prepare(f, &q, "SELECT x.rid AS mrid FROM tag t, tagxref x " "WHERE x.tagid=t.tagid " "AND t.tagname LIKE 'wiki-%%' " "AND TYPEOF(x.value+0)='integer'" // ^^^^ only 'wiki-%' tags which are wiki pages "ORDER BY x.mtime DESC"); break; case FSL_SATYPE_TICKET: rc = fsl_cx_prepare(f, &q, "SELECT rid FROM tagxref WHERE tagid IN " "(SELECT tagid FROM tag WHERE tagname LIKE " "'tkt-%%' AND LENGTH(tagname)=44) " "ORDER BY mtime"); break; case FSL_SATYPE_TECHNOTE: rc = fsl_cx_prepare(f, &q, "SELECT x.rid AS mrid FROM tag t, tagxref x " "WHERE x.tagid=t.tagid " "AND t.tagname LIKE 'event-%%' " // ^^^^ only 'wiki-%' tags which are wiki pages "ORDER BY x.mtime DESC"); break; #if 0 case FSL_SATYPE_ANY: /* This case leads to confusion because ATTACHMENT records are recorded in the timeline as the underlying type of record to which the attachment is applied. e.g. an attachment on a wiki page is listed as a 'w' event. */ rc = fsl_cx_prepare(f, &q, "SELECT objid FROM event WHERE type IN" "(%Q, %Q, %Q, %Q, %Q) /*%s()*/", fsl_satype_event_cstr(FSL_SATYPE_CHECKIN), fsl_satype_event_cstr(FSL_SATYPE_TICKET), fsl_satype_event_cstr(FSL_SATYPE_FORUMPOST), fsl_satype_event_cstr(FSL_SATYPE_WIKI), fsl_satype_event_cstr(FSL_SATYPE_CONTROL), __func__); break; #endif default: return fsl_cx_err_set(f, FSL_RC_TYPE, "Artifact type [%s] is not currently " "supported by %s().", fsl_satype_cstr(type), __func__); } while(0==rc && (FSL_RC_STEP_ROW==fsl_stmt_step(&q))){ fsl_id_t const rid = fsl_stmt_g_id(&q, 0); fsl_deck d = fsl_deck_empty; rc = fsl_deck_load_rid(f, &d, rid, type); if(0==rc){ rc = visitor(f, &d, visitorState); } fsl_deck_finalize(&d); if(FSL_RC_BREAK==rc){ rc = 0; break; } } fsl_stmt_finalize(&q); return rc; } #undef MARKER #undef AGE_FUDGE_WINDOW #undef AGE_ADJUST_INCREMENT #undef F_at /* end of file ./src/deck.c */ /* start of file ./src/delta.c */ |
︙ | ︙ | |||
23754 23755 23756 23757 23758 23759 23760 | return fsl_delta_apply2(zSrc, lenSrc_, zDelta, lenDelta_, zOut, NULL); } #undef NHASH #undef DEBUG1 #undef DEBUG2 /* end of file ./src/delta.c */ | | | | > | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 23830 23831 23832 23833 23834 23835 23836 23837 23838 23839 23840 23841 23842 23843 23844 23845 23846 23847 23848 23849 23850 23851 23852 23853 23854 23855 23856 23857 23858 23859 23860 23861 23862 23863 23864 23865 23866 23867 23868 23869 23870 23871 23872 23873 23874 23875 | return fsl_delta_apply2(zSrc, lenSrc_, zDelta, lenDelta_, zOut, NULL); } #undef NHASH #undef DEBUG1 #undef DEBUG2 /* end of file ./src/delta.c */ /* start of file ./src/dibu.c */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 2022 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2013-2022 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ /************************************************************************ This file houses the "dibu" (Diff Builder) routines. */ #include <assert.h> #include <memory.h> #include <stdlib.h> #include <string.h> /* for memmove()/strlen() */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) const fsl_dibu_opt fsl_dibu_opt_empty = fsl_dibu_opt_empty_m; const fsl_dibu fsl_dibu_empty = fsl_dibu_empty_m; fsl_dibu * fsl_dibu_alloc(fsl_size_t extra){ fsl_dibu * rc = (fsl_dibu*)fsl_malloc(sizeof(fsl_dibu) + extra); if(rc){ *rc = fsl_dibu_empty; |
︙ | ︙ | |||
27203 27204 27205 27206 27207 27208 27209 | uint32_t lineCount[2]; /** The actual number of lines needed for rendering the file. */ uint32_t displayLines; }; typedef struct DiffCounter DiffCounter; | | | 23930 23931 23932 23933 23934 23935 23936 23937 23938 23939 23940 23941 23942 23943 23944 | uint32_t lineCount[2]; /** The actual number of lines needed for rendering the file. */ uint32_t displayLines; }; typedef struct DiffCounter DiffCounter; static const DiffCounter DiffCounter_empty = {{1,25,3,1,25},{0,0},0}; #define DICOSTATE(VNAME) DiffCounter * const VNAME = (DiffCounter *)b->pimpl static int maxColWidth(fsl_dibu const * const b, DiffCounter const * const sst, int mwIndex){ static const short minColWidth = 10/*b->opt.columnWidth values smaller than this are treated as |
︙ | ︙ | |||
28047 28048 28049 28050 28051 28052 28053 | #endif return rc; } static int fdb__tcl_finally(fsl_dibu * const b){ int rc = 0; if(FSL_DIBU_TCL_TK==(b->implFlags & FSL_DIBU_TCL_TK)){ extern char const * fsl_difftk_cstr; | > | | | 24774 24775 24776 24777 24778 24779 24780 24781 24782 24783 24784 24785 24786 24787 24788 24789 24790 | #endif return rc; } static int fdb__tcl_finally(fsl_dibu * const b){ int rc = 0; if(FSL_DIBU_TCL_TK==(b->implFlags & FSL_DIBU_TCL_TK)){ extern char const * fsl_difftk_cstr; if(!b->fileCount) rc = fdb__out(b,"set difftxt {\n",-1); if(0==rc) rc = fdb__out(b, "}\nset fossilcmd {}\n", -1); if(0==rc) rc = fdb__out(b, fsl_difftk_cstr, -1); } return rc; } #undef BR_OPEN #undef BR_CLOSE |
︙ | ︙ | |||
28330 28331 28332 28333 28334 28335 28336 | if(NULL!=factory){ *pOut = factory(); rc = *pOut ? 0 : FSL_RC_OOM; } return rc; } | | < < < < < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 25058 25059 25060 25061 25062 25063 25064 25065 25066 25067 25068 25069 25070 25071 25072 25073 25074 25075 25076 25077 25078 25079 25080 25081 25082 25083 25084 25085 25086 25087 25088 25089 25090 25091 25092 25093 25094 25095 25096 25097 25098 25099 25100 25101 25102 25103 25104 25105 25106 25107 25108 25109 25110 25111 25112 25113 25114 25115 25116 25117 25118 25119 25120 25121 25122 25123 25124 25125 25126 25127 25128 25129 25130 25131 25132 25133 25134 25135 25136 25137 25138 25139 25140 25141 25142 25143 25144 25145 25146 25147 25148 25149 25150 25151 25152 25153 25154 25155 25156 25157 25158 25159 25160 25161 25162 25163 25164 25165 25166 25167 25168 25169 25170 25171 25172 25173 25174 25175 25176 25177 25178 25179 25180 25181 25182 25183 25184 25185 25186 25187 25188 25189 25190 25191 25192 25193 25194 25195 25196 25197 25198 25199 25200 25201 25202 25203 25204 25205 25206 25207 25208 25209 25210 25211 25212 25213 25214 25215 25216 25217 25218 25219 25220 25221 25222 25223 25224 25225 25226 25227 25228 25229 25230 25231 25232 25233 25234 25235 25236 25237 25238 25239 25240 25241 25242 25243 25244 25245 25246 25247 25248 25249 25250 25251 25252 25253 25254 25255 25256 25257 25258 25259 25260 25261 25262 25263 25264 25265 25266 25267 25268 25269 25270 25271 25272 25273 25274 25275 25276 25277 25278 25279 25280 25281 25282 25283 25284 25285 25286 25287 25288 25289 25290 25291 25292 25293 25294 25295 25296 25297 25298 25299 25300 25301 25302 25303 25304 25305 25306 25307 25308 25309 25310 25311 25312 25313 25314 25315 25316 25317 25318 25319 25320 25321 25322 25323 25324 25325 25326 25327 25328 25329 25330 25331 25332 25333 25334 25335 25336 25337 25338 25339 25340 25341 25342 25343 25344 25345 25346 25347 25348 25349 25350 25351 25352 25353 25354 25355 25356 25357 25358 25359 25360 25361 25362 25363 25364 25365 25366 25367 25368 25369 25370 25371 25372 25373 25374 25375 25376 25377 25378 25379 25380 25381 25382 25383 25384 25385 25386 25387 25388 25389 25390 25391 25392 25393 25394 25395 25396 25397 25398 25399 25400 25401 25402 25403 25404 25405 25406 25407 25408 25409 25410 25411 25412 25413 25414 25415 25416 25417 25418 25419 25420 25421 25422 25423 25424 25425 25426 25427 25428 25429 25430 25431 25432 25433 25434 25435 25436 25437 25438 25439 25440 25441 25442 25443 25444 25445 25446 25447 25448 25449 25450 25451 25452 25453 25454 25455 25456 25457 25458 25459 25460 25461 25462 25463 25464 25465 25466 25467 25468 25469 25470 25471 25472 25473 25474 25475 25476 25477 25478 25479 25480 25481 25482 25483 25484 25485 25486 25487 25488 25489 25490 25491 25492 25493 25494 25495 25496 25497 25498 25499 25500 25501 25502 25503 25504 25505 25506 25507 25508 25509 25510 25511 25512 25513 25514 25515 25516 25517 25518 25519 25520 25521 25522 25523 25524 25525 25526 25527 25528 25529 25530 25531 25532 25533 25534 25535 25536 25537 25538 25539 25540 25541 25542 25543 25544 25545 25546 25547 25548 25549 25550 25551 25552 25553 25554 25555 25556 25557 25558 25559 25560 25561 25562 25563 25564 25565 25566 25567 25568 25569 25570 25571 25572 25573 25574 25575 25576 25577 25578 25579 25580 25581 25582 25583 25584 25585 25586 25587 25588 25589 25590 25591 25592 25593 25594 25595 25596 25597 25598 25599 25600 25601 25602 25603 25604 25605 25606 25607 25608 25609 25610 25611 25612 25613 25614 25615 25616 25617 25618 25619 25620 25621 25622 25623 25624 25625 25626 25627 25628 25629 25630 25631 25632 25633 25634 25635 25636 25637 25638 25639 25640 25641 25642 25643 25644 25645 25646 25647 25648 25649 25650 25651 25652 25653 25654 25655 25656 25657 25658 25659 25660 25661 25662 25663 25664 25665 25666 25667 25668 25669 25670 25671 25672 25673 25674 25675 25676 25677 25678 25679 25680 25681 25682 25683 25684 25685 25686 25687 25688 25689 25690 25691 25692 25693 25694 25695 25696 25697 25698 25699 25700 25701 25702 25703 25704 25705 25706 25707 25708 25709 25710 25711 25712 25713 25714 25715 25716 25717 25718 25719 25720 25721 25722 25723 25724 25725 25726 25727 25728 25729 25730 25731 25732 25733 25734 25735 25736 25737 25738 25739 25740 25741 25742 25743 25744 25745 25746 25747 25748 25749 25750 25751 25752 25753 25754 25755 25756 25757 25758 25759 25760 25761 25762 25763 25764 25765 25766 25767 25768 25769 25770 25771 25772 25773 25774 25775 25776 25777 25778 25779 25780 25781 25782 25783 25784 25785 25786 25787 25788 25789 25790 25791 25792 25793 25794 25795 25796 25797 25798 25799 25800 25801 25802 25803 25804 25805 25806 25807 25808 25809 25810 25811 25812 25813 25814 25815 25816 25817 25818 25819 25820 25821 25822 25823 25824 25825 25826 25827 25828 25829 25830 25831 25832 25833 25834 25835 25836 25837 25838 25839 25840 25841 25842 25843 25844 25845 25846 25847 25848 25849 25850 25851 25852 25853 25854 25855 25856 25857 25858 25859 25860 25861 25862 25863 25864 25865 25866 25867 25868 25869 25870 25871 25872 25873 25874 25875 25876 25877 25878 25879 25880 25881 25882 25883 25884 25885 25886 25887 25888 25889 25890 25891 25892 25893 25894 25895 25896 25897 25898 25899 25900 25901 25902 25903 25904 25905 25906 25907 25908 25909 25910 25911 25912 25913 25914 25915 25916 25917 25918 25919 25920 25921 25922 25923 25924 25925 25926 25927 25928 25929 25930 25931 25932 25933 25934 25935 25936 25937 25938 25939 25940 25941 25942 25943 25944 25945 25946 25947 25948 25949 25950 25951 25952 25953 25954 25955 25956 25957 25958 25959 25960 25961 25962 25963 25964 25965 25966 25967 25968 25969 25970 25971 25972 25973 25974 25975 25976 25977 25978 25979 25980 25981 25982 25983 25984 25985 25986 25987 25988 25989 25990 25991 25992 25993 25994 25995 25996 25997 25998 25999 26000 26001 26002 26003 26004 26005 26006 26007 26008 26009 26010 26011 26012 26013 26014 26015 26016 26017 26018 26019 26020 26021 26022 26023 26024 26025 26026 26027 26028 26029 26030 26031 26032 26033 26034 26035 26036 26037 26038 26039 26040 26041 26042 26043 26044 26045 26046 26047 26048 26049 26050 26051 26052 26053 26054 26055 26056 26057 26058 26059 26060 26061 26062 26063 26064 26065 26066 26067 26068 26069 26070 26071 26072 26073 26074 26075 26076 26077 26078 26079 26080 26081 26082 26083 26084 26085 26086 26087 26088 26089 26090 26091 26092 26093 26094 26095 26096 26097 26098 26099 26100 26101 26102 26103 26104 26105 26106 26107 26108 26109 26110 26111 26112 26113 26114 26115 26116 26117 26118 26119 26120 26121 26122 26123 26124 26125 26126 26127 26128 26129 26130 26131 26132 26133 26134 26135 26136 26137 26138 26139 26140 26141 26142 26143 26144 26145 26146 26147 26148 26149 26150 26151 26152 26153 26154 26155 26156 26157 26158 26159 26160 26161 26162 26163 26164 26165 26166 26167 26168 26169 26170 26171 26172 26173 26174 26175 26176 26177 26178 26179 26180 26181 26182 26183 26184 26185 26186 26187 26188 26189 26190 26191 26192 26193 26194 26195 26196 26197 26198 26199 26200 26201 26202 26203 26204 26205 26206 26207 26208 26209 26210 26211 26212 26213 26214 26215 26216 26217 26218 26219 26220 26221 26222 26223 26224 26225 26226 26227 26228 26229 26230 26231 26232 26233 26234 26235 26236 26237 26238 26239 26240 26241 26242 26243 26244 26245 26246 26247 26248 26249 26250 26251 26252 26253 26254 26255 26256 26257 26258 26259 26260 26261 26262 26263 26264 26265 26266 26267 26268 26269 26270 26271 26272 26273 26274 26275 26276 26277 26278 26279 26280 26281 26282 26283 26284 26285 26286 26287 26288 26289 26290 26291 26292 26293 26294 26295 26296 26297 26298 26299 26300 26301 26302 26303 26304 26305 26306 26307 26308 26309 26310 26311 26312 26313 26314 26315 26316 26317 26318 26319 26320 26321 26322 26323 26324 26325 26326 26327 26328 26329 26330 26331 26332 26333 26334 26335 26336 26337 26338 26339 26340 26341 26342 26343 26344 26345 26346 26347 26348 26349 26350 26351 26352 26353 26354 26355 26356 26357 26358 26359 26360 26361 26362 26363 26364 26365 26366 26367 26368 26369 26370 26371 26372 26373 26374 26375 26376 26377 26378 26379 26380 26381 26382 26383 26384 26385 26386 26387 26388 26389 26390 26391 26392 26393 26394 26395 26396 26397 26398 26399 26400 26401 26402 26403 26404 26405 26406 26407 26408 26409 26410 26411 26412 26413 26414 26415 26416 26417 26418 26419 26420 26421 26422 26423 26424 26425 26426 26427 26428 26429 26430 26431 26432 26433 26434 26435 26436 26437 26438 26439 26440 26441 26442 26443 26444 26445 26446 26447 26448 26449 26450 26451 26452 26453 26454 26455 26456 26457 26458 26459 26460 26461 26462 26463 26464 26465 26466 26467 26468 26469 26470 26471 26472 26473 26474 26475 26476 26477 26478 26479 26480 26481 26482 26483 26484 26485 26486 26487 26488 26489 26490 26491 26492 26493 26494 26495 26496 26497 26498 26499 26500 26501 26502 26503 26504 26505 26506 26507 26508 26509 26510 26511 26512 26513 26514 26515 26516 26517 26518 26519 26520 26521 26522 26523 26524 26525 26526 26527 26528 26529 26530 26531 26532 26533 26534 26535 26536 26537 26538 26539 26540 26541 26542 26543 26544 26545 26546 26547 26548 26549 26550 26551 26552 26553 26554 26555 26556 26557 26558 26559 26560 26561 26562 26563 26564 26565 26566 26567 26568 26569 26570 26571 26572 26573 26574 26575 26576 26577 26578 26579 26580 26581 26582 26583 26584 26585 26586 26587 26588 26589 26590 26591 26592 26593 26594 26595 26596 26597 26598 26599 26600 26601 26602 26603 26604 26605 26606 26607 26608 26609 26610 26611 26612 26613 26614 26615 26616 26617 26618 26619 26620 26621 26622 26623 26624 26625 26626 26627 26628 26629 26630 26631 26632 26633 26634 26635 26636 26637 26638 26639 26640 26641 26642 26643 26644 26645 26646 26647 26648 26649 26650 26651 26652 26653 26654 26655 26656 26657 26658 26659 26660 26661 26662 26663 26664 26665 26666 26667 26668 26669 26670 26671 26672 26673 26674 26675 26676 26677 26678 26679 26680 26681 26682 26683 26684 26685 26686 26687 26688 26689 26690 26691 26692 26693 26694 26695 26696 26697 26698 26699 26700 26701 26702 26703 26704 26705 26706 26707 26708 26709 26710 26711 26712 26713 26714 26715 26716 26717 26718 26719 26720 26721 26722 26723 26724 26725 26726 26727 26728 26729 26730 26731 26732 26733 26734 26735 26736 26737 26738 26739 26740 26741 26742 26743 26744 26745 26746 26747 26748 26749 26750 26751 26752 26753 26754 26755 26756 26757 26758 26759 26760 26761 26762 26763 26764 26765 26766 26767 26768 26769 26770 26771 26772 26773 26774 26775 26776 26777 26778 26779 26780 26781 26782 26783 26784 26785 26786 26787 26788 26789 26790 26791 26792 26793 26794 26795 26796 26797 26798 26799 26800 26801 26802 26803 26804 26805 26806 26807 26808 26809 26810 26811 26812 26813 26814 26815 26816 26817 26818 26819 26820 26821 26822 26823 26824 26825 26826 26827 26828 26829 26830 26831 26832 26833 26834 26835 26836 26837 26838 26839 26840 26841 26842 26843 26844 26845 26846 26847 26848 26849 26850 26851 26852 26853 26854 26855 26856 26857 26858 26859 26860 26861 26862 26863 26864 26865 26866 26867 26868 26869 26870 26871 26872 26873 26874 26875 26876 26877 26878 26879 26880 26881 26882 26883 26884 26885 26886 26887 26888 26889 26890 26891 26892 26893 26894 26895 26896 26897 26898 26899 26900 26901 26902 26903 26904 26905 26906 26907 26908 26909 26910 26911 26912 26913 26914 26915 26916 26917 26918 26919 26920 26921 26922 26923 26924 26925 26926 26927 26928 26929 26930 26931 26932 26933 26934 26935 26936 26937 26938 26939 26940 26941 26942 26943 26944 26945 26946 26947 26948 26949 26950 26951 26952 26953 26954 26955 26956 26957 26958 26959 26960 26961 26962 26963 26964 26965 26966 26967 26968 26969 26970 26971 26972 26973 26974 26975 26976 26977 26978 26979 26980 26981 26982 26983 26984 26985 26986 26987 26988 26989 26990 26991 26992 26993 26994 26995 26996 26997 26998 26999 27000 27001 27002 27003 27004 27005 27006 27007 27008 27009 27010 27011 27012 27013 27014 27015 27016 27017 27018 27019 27020 27021 27022 27023 27024 27025 27026 27027 27028 27029 27030 27031 27032 27033 27034 27035 27036 27037 27038 27039 27040 27041 27042 27043 27044 27045 27046 27047 27048 27049 27050 27051 27052 27053 27054 27055 27056 27057 27058 27059 27060 27061 27062 | if(NULL!=factory){ *pOut = factory(); rc = *pOut ? 0 : FSL_RC_OOM; } return rc; } #undef MARKER #undef DICOSTATE #undef DTCL_BUFFER /* end of file ./src/dibu.c */ /* start of file ./src/diff.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-2022 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2013-2022 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ /************************************************************************ This file houses Fossil's diff-generation routines (as opposed to the delta-generation). This code is a straight port of those algorithms from the Fossil SCM project, initially implemented by D. Richard Hipp, ported and the license re-assigned to this project with this consent. */ #include <assert.h> #include <memory.h> #include <stdlib.h> #include <string.h> /* for memmove()/strlen() */ #include <stdio.h> #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) const fsl_dline fsl_dline_empty = fsl_dline_empty_m; const fsl_dline_change fsl_dline_change_empty = fsl_dline_change_empty_m; const fsl__diff_cx fsl__diff_cx_empty = fsl__diff_cx_empty_m; void fsl__diff_cx_clean(fsl__diff_cx * const cx){ fsl_free(cx->aFrom); fsl_free(cx->aTo); fsl_free(cx->aEdit); cx->aFrom = cx->aTo = NULL; cx->aEdit = NULL; *cx = fsl__diff_cx_empty; } /* Fast isspace for use by diff */ static const char diffIsSpace[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; #define diff_isspace(X) (diffIsSpace[(unsigned char)(X)]) /** Length of a dline. */ #define LENGTH(X) ((X)->n) /** Minimum of two values */ static int minInt(int a, int b){ return a<b ? a : b; } /** @internal Compute the optimal longest common subsequence (LCS) using an exhaustive search. This version of the LCS is only used for shorter input strings since runtime is O(N*N) where N is the input string length. */ static void fsl__diff_optimal_lcs( fsl__diff_cx * const p, /* Two files being compared */ int iS1, int iE1, /* Range of lines in p->aFrom[] */ int iS2, int iE2, /* Range of lines in p->aTo[] */ int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ int *piSY, int *piEY /* Write p->aTo[] common segment here */ ){ int mxLength = 0; /* Length of longest common subsequence */ int i, j; /* Loop counters */ int k; /* Length of a candidate subsequence */ int iSXb = iS1; /* Best match so far */ int iSYb = iS2; /* Best match so far */ for(i=iS1; i<iE1-mxLength; i++){ for(j=iS2; j<iE2-mxLength; j++){ if( p->cmpLine(&p->aFrom[i], &p->aTo[j]) ) continue; if( mxLength && p->cmpLine(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){ continue; } k = 1; while( i+k<iE1 && j+k<iE2 && p->cmpLine(&p->aFrom[i+k],&p->aTo[j+k])==0 ){ k++; } if( k>mxLength ){ iSXb = i; iSYb = j; mxLength = k; } } } *piSX = iSXb; *piEX = iSXb + mxLength; *piSY = iSYb; *piEY = iSYb + mxLength; } /** Compare two blocks of text on lines iS1 through iE1-1 of the aFrom[] file and lines iS2 through iE2-1 of the aTo[] file. Locate a sequence of lines in these two blocks that are exactly the same. Return the bounds of the matching sequence. If there are two or more possible answers of the same length, the returned sequence should be the one closest to the center of the input range. Ideally, the common sequence should be the longest possible common sequence. However, an exact computation of LCS is O(N*N) which is way too slow for larger files. So this routine uses an O(N) heuristic approximation based on hashing that usually works about as well. But if the O(N) algorithm doesn't get a good solution and N is not too large, we fall back to an exact solution by calling fsl__diff_optimal_lcs(). */ static void fsl__diff_lcs( fsl__diff_cx * const p, /* Two files being compared */ int iS1, int iE1, /* Range of lines in p->aFrom[] */ int iS2, int iE2, /* Range of lines in p->aTo[] */ int *piSX, int *piEX, /* Write p->aFrom[] common segment here */ int *piSY, int *piEY /* Write p->aTo[] common segment here */ ){ int i, j, k; /* Loop counters */ int n; /* Loop limit */ fsl_dline *pA, *pB; /* Pointers to lines */ int iSX, iSY, iEX, iEY; /* Current match */ int skew = 0; /* How lopsided is the match */ int dist = 0; /* Distance of match from center */ int mid; /* Center of the chng */ int iSXb, iSYb, iEXb, iEYb; /* Best match so far */ int iSXp, iSYp, iEXp, iEYp; /* Previous match */ sqlite3_int64 bestScore; /* Best score so far */ sqlite3_int64 score; /* Score for current candidate LCS */ int span; /* combined width of the input sequences */ int cutoff = 4; /* Max hash chain entries to follow */ int nextCutoff = -1; /* Value of cutoff for next iteration */ span = (iE1 - iS1) + (iE2 - iS2); bestScore = -10000; score = 0; iSXb = iSXp = iS1; iEXb = iEXp = iS1; iSYb = iSYp = iS2; iEYb = iEYp = iS2; mid = (iE1 + iS1)/2; do{ nextCutoff = 0; for(i=iS1; i<iE1; i++){ int limit = 0; j = p->aTo[p->aFrom[i].h % p->nTo].iHash; while( j>0 && (j-1<iS2 || j>=iE2 || p->cmpLine(&p->aFrom[i], &p->aTo[j-1])) ){ if( limit++ > cutoff ){ j = 0; nextCutoff = cutoff*4; break; } j = p->aTo[j-1].iNext; } if( j==0 ) continue; assert( i>=iSXb && i>=iSXp ); if( i<iEXb && j>=iSYb && j<iEYb ) continue; if( i<iEXp && j>=iSYp && j<iEYp ) continue; iSX = i; iSY = j-1; pA = &p->aFrom[iSX-1]; pB = &p->aTo[iSY-1]; n = minInt(iSX-iS1, iSY-iS2); for(k=0; k<n && p->cmpLine(pA,pB)==0; k++, pA--, pB--){} iSX -= k; iSY -= k; iEX = i+1; iEY = j; pA = &p->aFrom[iEX]; pB = &p->aTo[iEY]; n = minInt(iE1-iEX, iE2-iEY); for(k=0; k<n && p->cmpLine(pA,pB)==0; k++, pA++, pB++){} iEX += k; iEY += k; skew = (iSX-iS1) - (iSY-iS2); if( skew<0 ) skew = -skew; dist = (iSX+iEX)/2 - mid; if( dist<0 ) dist = -dist; score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist); if( score>bestScore ){ bestScore = score; iSXb = iSX; iSYb = iSY; iEXb = iEX; iEYb = iEY; }else if( iEX>iEXp ){ iSXp = iSX; iSYp = iSY; iEXp = iEX; iEYp = iEY; } } }while( iSXb==iEXb && nextCutoff && (cutoff=nextCutoff)<=64 ); if( iSXb==iEXb && (sqlite3_int64)(iE1-iS1)*(iE2-iS2)<2500 ){ fsl__diff_optimal_lcs(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY); }else{ *piSX = iSXb; *piSY = iSYb; *piEX = iEXb; *piEY = iEYb; } } void fsl__dump_triples(fsl__diff_cx const * const p, char const * zFile, int ln ){ // Compare this with (fossil xdiff --raw) on the same inputs fprintf(stderr,"%s:%d: Compare this with (fossil xdiff --raw) on the same inputs:\n", zFile, ln); for(int i = 0; p->aEdit[i] || p->aEdit[i+1] || p->aEdit[i+2]; i+=3){ printf(" copy %6d delete %6d insert %6d\n", p->aEdit[i], p->aEdit[i+1], p->aEdit[i+2]); } } /** @internal Expand the size of p->aEdit array to hold at least nEdit elements. */ static int fsl__diff_expand_edit(fsl__diff_cx * const p, int nEdit){ void * re = fsl_realloc(p->aEdit, nEdit*sizeof(int)); if(!re) return FSL_RC_OOM; else{ p->aEdit = (int*)re; p->nEditAlloc = nEdit; return 0; } } /** Append a new COPY/DELETE/INSERT triple. Returns 0 on success, FSL_RC_OOM on OOM. */ static int appendTriple(fsl__diff_cx *p, int nCopy, int nDel, int nIns){ /* printf("APPEND %d/%d/%d\n", nCopy, nDel, nIns); */ if( p->nEdit>=3 ){ if( p->aEdit[p->nEdit-1]==0 ){ if( p->aEdit[p->nEdit-2]==0 ){ p->aEdit[p->nEdit-3] += nCopy; p->aEdit[p->nEdit-2] += nDel; p->aEdit[p->nEdit-1] += nIns; return 0; } if( nCopy==0 ){ p->aEdit[p->nEdit-2] += nDel; p->aEdit[p->nEdit-1] += nIns; return 0; } } if( nCopy==0 && nDel==0 ){ p->aEdit[p->nEdit-1] += nIns; return 0; } } if( p->nEdit+3>p->nEditAlloc ){ int const rc = fsl__diff_expand_edit(p, p->nEdit*2 + 15); if(rc) return rc; else if( p->aEdit==0 ) return 0; } p->aEdit[p->nEdit++] = nCopy; p->aEdit[p->nEdit++] = nDel; p->aEdit[p->nEdit++] = nIns; return 0; } /* ** A common subsequene between p->aFrom and p->aTo has been found. ** This routine tries to judge if the subsequence really is a valid ** match or rather is just an artifact of an indentation change. ** ** Return non-zero if the subsequence is valid. Return zero if the ** subsequence seems likely to be an editing artifact and should be ** ignored. ** ** This routine is a heuristic optimization intended to give more ** intuitive diff results following an indentation change it code that ** is formatted similarly to C/C++, Javascript, Go, TCL, and similar ** languages that use {...} for nesting. A correct diff is computed ** even if this routine always returns true (non-zero). But sometimes ** a more intuitive diff can result if this routine returns false. ** ** The subsequences consists of the rows iSX through iEX-1 (inclusive) ** in p->aFrom[]. The total sequences is iS1 through iE1-1 (inclusive) ** of p->aFrom[]. ** ** Example where this heuristic is useful, see the diff at ** https://www.sqlite.org/src/fdiff?v1=0e79dd15cbdb4f48&v2=33955a6fd874dd97 ** ** See also discussion at https://fossil-scm.org/forum/forumpost/9ba3284295 ** ** ALGORITHM (subject to change and refinement): ** ** 1. If the subsequence is larger than 1/7th of the original span, ** then consider it valid. --> return 1 ** ** 2. If the subsequence contains any charaters other than '}', '{", ** or whitespace, then consider it valid. --> return 1 ** ** 3. Otherwise, it is potentially an artifact of an indentation ** change. --> return 0 */ static bool likelyNotIndentChngArtifact( fsl__diff_cx const * const p, /* The complete diff context */ int iS1, /* Start of the main segment */ int iSX, /* Start of the subsequence */ int iEX, /* First row past the end of the subsequence */ int iE1 /* First row past the end of the main segment */ ){ int i, j; if( (iEX-iSX)*7 >= (iE1-iS1) ) return true; for(i=iSX; i<iEX; i++){ const char *z = p->aFrom[i].z; for(j=p->aFrom[i].n-1; j>=0; j--){ char c = z[j]; if( c!='}' && c!='{' && !diff_isspace(c) ) return true; } } return false; } /** Do a single step in the difference. Compute a sequence of copy/delete/insert steps that will convert lines iS1 through iE1-1 of the input into lines iS2 through iE2-1 of the output and write that sequence into the difference context. The algorithm is to find a block of common text near the middle of the two segments being diffed. Then recursively compute differences on the blocks before and after that common segment. Special cases apply if either input segment is empty or if the two segments have no text in common. */ static int diff_step(fsl__diff_cx *p, int iS1, int iE1, int iS2, int iE2){ int iSX, iEX, iSY, iEY; int rc = 0; if( iE1<=iS1 ){ /* The first segment is empty */ if( iE2>iS2 ){ rc = appendTriple(p, 0, 0, iE2-iS2); } return rc; } if( iE2<=iS2 ){ /* The second segment is empty */ return appendTriple(p, 0, iE1-iS1, 0); } /* Find the longest matching segment between the two sequences */ fsl__diff_lcs(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY); if( iEX>iSX+5 || (iEX>iSX && likelyNotIndentChngArtifact(p,iS1,iSX,iEX,iE1) )){ /* A common segment has been found. Recursively diff either side of the matching segment */ rc = diff_step(p, iS1, iSX, iS2, iSY); if(!rc){ if(iEX>iSX){ rc = appendTriple(p, iEX - iSX, 0, 0); } if(!rc) rc = diff_step(p, iEX, iE1, iEY, iE2); } }else{ /* The two segments have nothing in common. Delete the first then insert the second. */ rc = appendTriple(p, 0, iE1-iS1, iE2-iS2); } return rc; } int fsl__diff_all(fsl__diff_cx * const p){ int mnE, iS, iE1, iE2; int rc = 0; /* Carve off the common header and footer */ iE1 = p->nFrom; iE2 = p->nTo; while( iE1>0 && iE2>0 && p->cmpLine(&p->aFrom[iE1-1], &p->aTo[iE2-1])==0 ){ iE1--; iE2--; } mnE = iE1<iE2 ? iE1 : iE2; for(iS=0; iS<mnE && p->cmpLine(&p->aFrom[iS],&p->aTo[iS])==0; iS++){} /* do the difference */ if( iS>0 ){ rc = appendTriple(p, iS, 0, 0); if(rc) return rc; } rc = diff_step(p, iS, iE1, iS, iE2); //fsl__dump_triples(p, __FILE__, __LINE__); if(rc) return rc; else if( iE1<p->nFrom ){ rc = appendTriple(p, p->nFrom - iE1, 0, 0); if(rc) return rc; } /* Terminate the COPY/DELETE/INSERT triples with three zeros */ rc = fsl__diff_expand_edit(p, p->nEdit+3); if(0==rc){ if(p->aEdit ){ p->aEdit[p->nEdit++] = 0; p->aEdit[p->nEdit++] = 0; p->aEdit[p->nEdit++] = 0; //fsl__dump_triples(p, __FILE__, __LINE__); } } return rc; } void fsl__diff_optimize(fsl__diff_cx * const p){ int r; /* Index of current triple */ int lnFrom; /* Line number in p->aFrom */ int lnTo; /* Line number in p->aTo */ int cpy, del, ins; //fsl__dump_triples(p, __FILE__, __LINE__); lnFrom = lnTo = 0; for(r=0; r<p->nEdit; r += 3){ cpy = p->aEdit[r]; del = p->aEdit[r+1]; ins = p->aEdit[r+2]; lnFrom += cpy; lnTo += cpy; /* Shift insertions toward the beginning of the file */ while( cpy>0 && del==0 && ins>0 ){ fsl_dline *pTop = &p->aFrom[lnFrom-1]; /* Line before start of insert */ fsl_dline *pBtm = &p->aTo[lnTo+ins-1]; /* Last line inserted */ if( p->cmpLine(pTop, pBtm) ) break; if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break; lnFrom--; lnTo--; p->aEdit[r]--; p->aEdit[r+3]++; cpy--; } /* Shift insertions toward the end of the file */ while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){ fsl_dline *pTop = &p->aTo[lnTo]; /* First line inserted */ fsl_dline *pBtm = &p->aTo[lnTo+ins]; /* First line past end of insert */ if( p->cmpLine(pTop, pBtm) ) break; if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break; lnFrom++; lnTo++; p->aEdit[r]++; p->aEdit[r+3]--; cpy++; } /* Shift deletions toward the beginning of the file */ while( cpy>0 && del>0 && ins==0 ){ fsl_dline *pTop = &p->aFrom[lnFrom-1]; /* Line before start of delete */ fsl_dline *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */ if( p->cmpLine(pTop, pBtm) ) break; if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break; lnFrom--; lnTo--; p->aEdit[r]--; p->aEdit[r+3]++; cpy--; } /* Shift deletions toward the end of the file */ while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){ fsl_dline *pTop = &p->aFrom[lnFrom]; /* First line deleted */ fsl_dline *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */ if( p->cmpLine(pTop, pBtm) ) break; if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break; lnFrom++; lnTo++; p->aEdit[r]++; p->aEdit[r+3]--; cpy++; } lnFrom += del; lnTo += ins; } //fsl__dump_triples(p, __FILE__, __LINE__); } /** Counts the number of lines in the first n bytes of the given string. If n<0 then fsl_strlen() is used to count it. It includes the last line in the count even if it lacks the \n terminator. If an empty string is passed in, the number of lines is zero. For the purposes of this function, a string is considered empty if it contains no characters OR contains only NUL characters. If the input appears to be plain text it returns true and, if nOut is not NULL, writes the number of lines there. If the input appears to be binary, returns false and does not modify nOut. */ static bool fsl__count_lines(const char *z, fsl_int_t n, uint32_t * nOut ){ uint32_t nLine; const char *zNL, *z2; if(n<0) n = (fsl_int_t)fsl_strlen(z); for(nLine=0, z2=z; (zNL = strchr(z2,'\n'))!=0; z2=zNL+1, nLine++){} if( z2[0]!='\0' ){ nLine++; do{ z2++; }while( z2[0]!='\0' ); } if( n!=(fsl_int_t)(z2-z) ) return false; if( nOut ) *nOut = nLine; return true; } int fsl_break_into_dlines(const char *z, fsl_int_t n, uint32_t *pnLine, fsl_dline **pOut, uint64_t diffFlags){ uint32_t nLine, i, k, nn, s, x; uint64_t h, h2; fsl_dline *a = 0; const char *zNL; if(!z || !n){ *pnLine = 0; *pOut = NULL; return 0; } if( !fsl__count_lines(z, n, &nLine) ){ return FSL_RC_DIFF_BINARY; } assert( nLine>0 || z[0]=='\0' ); if(nLine>0){ a = fsl_malloc( sizeof(a[0])*nLine ); if(!a) return FSL_RC_OOM; memset(a, 0, sizeof(a[0])*nLine); }else{ *pnLine = 0; *pOut = a; return 0; } assert( a ); i = 0; do{ zNL = strchr(z,'\n'); if( zNL==0 ) zNL = z+n; nn = (uint32_t)(zNL - z); if( nn>FSL__LINE_LENGTH_MASK ){ fsl_free(a); *pOut = 0; *pnLine = 0; return FSL_RC_DIFF_BINARY; } a[i].z = z; k = nn; if( diffFlags & FSL_DIFF2_STRIP_EOLCR ){ if( k>0 && z[k-1]=='\r' ){ k--; } } a[i].n = k; if( diffFlags & FSL_DIFF2_IGNORE_EOLWS ){ while( k>0 && diff_isspace(z[k-1]) ){ k--; } } if( (diffFlags & FSL_DIFF2_IGNORE_ALLWS) ==FSL_DIFF2_IGNORE_ALLWS ){ uint32_t numws = 0; for(s=0; s<k && z[s]<=' '; s++){} a[i].indent = s; a[i].nw = k - s; for(h=0, x=s; x<k; ++x){ char c = z[x]; if( diff_isspace(c) ){ ++numws; }else{ h = (h^c)*9000000000000000041LL; } } k -= numws; }else{ uint32_t k2 = k & ~0x7; uint64_t m; for(h=x=s=0; x<k2; x += 8){ memcpy(&m, z+x, 8); h = (h^m)*9000000000000000041LL; } m = 0; memcpy(&m, z+x, k-k2); h ^= m; } a[i].h = h = ((h%281474976710597LL)<<FSL__LINE_LENGTH_MASK_SZ) | (k-s); h2 = h % nLine; a[i].iNext = a[h2].iHash; a[h2].iHash = i+1; z += nn+1; n -= nn+1; i++; }while( zNL[0]!='\0' && zNL[1]!='\0' ); assert( i==nLine ); *pnLine = nLine; *pOut = a; return 0; } int fsl_dline_cmp(const fsl_dline * const pA, const fsl_dline * const pB){ if( pA->h!=pB->h ) return 1; return memcmp(pA->z,pB->z, pA->h&FSL__LINE_LENGTH_MASK); } int fsl_dline_cmp_ignore_ws(const fsl_dline * const pA, const fsl_dline * const pB){ if( pA->h==pB->h ){ unsigned short a, b; if( memcmp(pA->z, pB->z, pA->h&FSL__LINE_LENGTH_MASK)==0 ) return 0; a = pA->indent; b = pB->indent; while( a<pA->n || b<pB->n ){ if( a<pA->n && b<pB->n && pA->z[a++] != pB->z[b++] ) return 1; while( a<pA->n && diff_isspace(pA->z[a])) ++a; while( b<pB->n && diff_isspace(pB->z[b])) ++b; } return pA->n-a != pB->n-b; } return 1; } /* ** The two text segments zLeft and zRight are known to be different on ** both ends, but they might have a common segment in the middle. If ** they do not have a common segment, return 0. If they do have a large ** common segment, return non-0 and before doing so set: ** ** aLCS[0] = start of the common segment in zLeft ** aLCS[1] = end of the common segment in zLeft ** aLCS[2] = start of the common segment in zLeft ** aLCS[3] = end of the common segment in zLeft ** ** This computation is for display purposes only and does not have to be ** optimal or exact. */ static int textLCS2( const char *zLeft, uint32_t nA, /* String on the left */ const char *zRight, uint32_t nB, /* String on the right */ uint32_t *aLCS /* Identify bounds of LCS here */ ){ const unsigned char *zA = (const unsigned char*)zLeft; /* left string */ const unsigned char *zB = (const unsigned char*)zRight; /* right string */ uint32_t i, j, k; /* Loop counters */ uint32_t lenBest = 0; /* Match length to beat */ for(i=0; i<nA-lenBest; i++){ unsigned char cA = zA[i]; if( (cA&0xc0)==0x80 ) continue; for(j=0; j<nB-lenBest; j++ ){ if( zB[j]==cA ){ for(k=1; j+k<nB && i+k<nA && zB[j+k]==zA[i+k]; k++){} while( (zB[j+k]&0xc0)==0x80 ){ k--; } if( k>lenBest ){ lenBest = k; aLCS[0] = i; aLCS[1] = i+k; aLCS[2] = j; aLCS[3] = j+k; } } } } return lenBest>0; } /* ** Find the smallest spans that are different between two text strings ** that are known to be different on both ends. Returns the number ** of entries in p->a which get populated. */ static unsigned short textLineChanges( const char *zLeft, uint32_t nA, /* String on the left */ const char *zRight, uint32_t nB, /* String on the right */ fsl_dline_change * const p /* Write results here */ ){ p->n = 1; p->a[0].iStart1 = 0; p->a[0].iLen1 = nA; p->a[0].iStart2 = 0; p->a[0].iLen2 = nB; p->a[0].isMin = 0; while( p->n<fsl_dline_change_max_spans-1 ){ int mxi = -1; int mxLen = -1; int x, i; uint32_t aLCS[4] = {0,0,0,0}; struct fsl_dline_change_span *a, *b; for(i=0; i<p->n; i++){ if( p->a[i].isMin ) continue; x = p->a[i].iLen1; if( p->a[i].iLen2<x ) x = p->a[i].iLen2; if( x>mxLen ){ mxLen = x; mxi = i; } } if( mxLen<6 ) break; x = textLCS2(zLeft + p->a[mxi].iStart1, p->a[mxi].iLen1, zRight + p->a[mxi].iStart2, p->a[mxi].iLen2, aLCS); if( x==0 ){ p->a[mxi].isMin = 1; continue; } a = p->a+mxi; b = a+1; if( mxi<p->n-1 ){ memmove(b+1, b, sizeof(*b)*(p->n-mxi-1)); } p->n++; b->iStart1 = a->iStart1 + aLCS[1]; b->iLen1 = a->iLen1 - aLCS[1]; a->iLen1 = aLCS[0]; b->iStart2 = a->iStart2 + aLCS[3]; b->iLen2 = a->iLen2 - aLCS[3]; a->iLen2 = aLCS[2]; b->isMin = 0; } return p->n; } /* ** Return true if the string starts with n spaces */ static int allSpaces(const char *z, int n){ int i; for(i=0; i<n && diff_isspace(z[i]); ++i){} return i==n; } /* ** Try to improve the human-readability of the fsl_dline_change p. ** ** (1) If the first change span shows a change of indentation, try to ** move that indentation change to the left margin. ** ** (2) Try to shift changes so that they begin or end with a space. */ static void improveReadability( const char *zA, /* Left line of the change */ const char *zB, /* Right line of the change */ fsl_dline_change * const p /* The fsl_dline_change to be adjusted */ ){ int j, n, len; if( p->n<1 ) return; /* (1) Attempt to move indentation changes to the left margin */ if( p->a[0].iLen1==0 && (len = p->a[0].iLen2)>0 && (j = p->a[0].iStart2)>0 && zB[0]==zB[j] && allSpaces(zB, j) ){ for(n=1; n<len && n<j && zB[j]==zB[j+n]; n++){} if( n<len ){ memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n); p->n++; p->a[0] = p->a[1]; p->a[1].iStart2 += n; p->a[1].iLen2 -= n; p->a[0].iLen2 = n; } p->a[0].iStart1 = 0; p->a[0].iStart2 = 0; }else if( p->a[0].iLen2==0 && (len = p->a[0].iLen1)>0 && (j = p->a[0].iStart1)>0 && zA[0]==zA[j] && allSpaces(zA, j) ){ for(n=1; n<len && n<j && zA[j]==zA[j+n]; n++){} if( n<len ){ memmove(&p->a[1], &p->a[0], sizeof(p->a[0])*p->n); p->n++; p->a[0] = p->a[1]; p->a[1].iStart1 += n; p->a[1].iLen1 -= n; p->a[0].iLen1 = n; } p->a[0].iStart1 = 0; p->a[0].iStart2 = 0; } /* (2) Try to shift changes so that they begin or end with a ** space. (TBD) */ } void fsl_dline_change_spans(const fsl_dline *pLeft, const fsl_dline *pRight, fsl_dline_change * const p){ /* fossil(1) counterpart ==> diff.c oneLineChange() */ int nLeft; /* Length of left line in bytes */ int nRight; /* Length of right line in bytes */ int nShort; /* Shortest of left and right */ int nPrefix; /* Length of common prefix */ int nSuffix; /* Length of common suffix */ int nCommon; /* Total byte length of suffix and prefix */ const char *zLeft; /* Text of the left line */ const char *zRight; /* Text of the right line */ int nLeftDiff; /* nLeft - nPrefix - nSuffix */ int nRightDiff; /* nRight - nPrefix - nSuffix */ nLeft = pLeft->n; zLeft = pLeft->z; nRight = pRight->n; zRight = pRight->z; nShort = nLeft<nRight ? nLeft : nRight; nPrefix = 0; while( nPrefix<nShort && zLeft[nPrefix]==zRight[nPrefix] ){ nPrefix++; } if( nPrefix<nShort ){ while( nPrefix>0 && (zLeft[nPrefix]&0xc0)==0x80 ) nPrefix--; } nSuffix = 0; if( nPrefix<nShort ){ while( nSuffix<nShort && zLeft[nLeft-nSuffix-1]==zRight[nRight-nSuffix-1] ){ nSuffix++; } if( nSuffix<nShort ){ while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--; } if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0; } nCommon = nPrefix + nSuffix; /* If the prefix and suffix overlap, that means that we are dealing with ** a pure insertion or deletion of text that can have multiple alignments. ** Try to find an alignment to begins and ends on whitespace, or on ** punctuation, rather than in the middle of a name or number. */ if( nCommon > nShort ){ int iBest = -1; int iBestVal = -1; int i; int nLong = nLeft<nRight ? nRight : nLeft; int nGap = nLong - nShort; for(i=nShort-nSuffix; i<=nPrefix; i++){ int iVal = 0; char c = zLeft[i]; if( diff_isspace(c) ){ iVal += 5; }else if( !fsl_isalnum(c) ){ iVal += 2; } c = zLeft[i+nGap-1]; if( diff_isspace(c) ){ iVal += 5; }else if( !fsl_isalnum(c) ){ iVal += 2; } if( iVal>iBestVal ){ iBestVal = iVal; iBest = i; } } nPrefix = iBest; nSuffix = nShort - nPrefix; nCommon = nPrefix + nSuffix; } /* A single chunk of text inserted */ if( nCommon==nLeft ){ p->n = 1; p->a[0].iStart1 = nPrefix; p->a[0].iLen1 = 0; p->a[0].iStart2 = nPrefix; p->a[0].iLen2 = nRight - nCommon; improveReadability(zLeft, zRight, p); return; } /* A single chunk of text deleted */ if( nCommon==nRight ){ p->n = 1; p->a[0].iStart1 = nPrefix; p->a[0].iLen1 = nLeft - nCommon; p->a[0].iStart2 = nPrefix; p->a[0].iLen2 = 0; improveReadability(zLeft, zRight, p); return; } /* At this point we know that there is a chunk of text that has ** changed between the left and the right. Check to see if there ** is a large unchanged section in the middle of that changed block. */ nLeftDiff = nLeft - nCommon; nRightDiff = nRight - nCommon; if( nLeftDiff >= 4 && nRightDiff >= 4 && textLineChanges(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, p)>1 ){ int i; for(i=0; i<p->n; i++){ p->a[i].iStart1 += nPrefix; p->a[i].iStart2 += nPrefix; } improveReadability(zLeft, zRight, p); return; } /* If all else fails, show a single big change between left and right */ p->n = 1; p->a[0].iStart1 = nPrefix; p->a[0].iLen1 = nLeft - nCommon; p->a[0].iStart2 = nPrefix; p->a[0].iLen2 = nRight - nCommon; improveReadability(zLeft, zRight, p); } /* ** The threshold at which diffBlockAlignment transitions from the ** O(N*N) Wagner minimum-edit-distance algorithm to a less process ** O(NlogN) divide-and-conquer approach. */ #define DIFF_ALIGN_MX 1225 /** FSL_DIFF_SMALL_GAP=0 is a temporary(? as of 2022-01-04) patch for a cosmetic-only (but unsightly) quirk of the diff engine where it produces a pair of identical DELETE/INSERT lines. Richard's preliminary solution for it is to remove the "small gap merging," but he notes (in fossil /chat) that he's not recommending this as "the" fix. PS: we colloquially know this as "the lineno diff" because it was first reported in a diff which resulted in: ``` - int lineno; + int lineno; ``` (No, there are no whitespace changes there.) To reiterate, though: this is not a "bug," in that it does not cause incorrect results when applying the resulting unfified-diff patches. It does, however, cause confusion for human users. Here are two inputs which, when diffed, expose the lineno behavior: #1: ``` struct fnc_diff_view_state { int first_line_onscreen; int last_line_onscreen; int diff_flags; int context; int sbs; int matched_line; int current_line; int lineno; size_t ncols; size_t nlines; off_t *line_offsets; bool eof; bool colour; bool showmeta; bool showln; }; ``` #2: ``` struct fnc_diff_view_state { int first_line_onscreen; int last_line_onscreen; int diff_flags; int context; int sbs; int matched_line; int selected_line; int lineno; int gtl; size_t ncols; size_t nlines; off_t *line_offsets; bool eof; bool colour; bool showmeta; bool showln; }; ``` Result without this patch: ``` Index: X.0 ================================================================== --- X.0 +++ X.1 @@ -3,15 +3,16 @@ int last_line_onscreen; int diff_flags; int context; int sbs; int matched_line; + int selected_line; - int current_line; - int lineno; + int lineno; + int gtl; size_t ncols; size_t nlines; off_t *line_offsets; bool eof; bool colour; bool showmeta; bool showln; }; ``` And with the patch: ``` Index: X.0 ================================================================== --- X.0 +++ X.1 @@ -3,15 +3,16 @@ int last_line_onscreen; int diff_flags; int context; int sbs; int matched_line; - int current_line; + int selected_line; int lineno; + int gtl; size_t ncols; size_t nlines; off_t *line_offsets; bool eof; bool colour; bool showmeta; bool showln; }; ``` */ #define FSL_DIFF_SMALL_GAP 0 #if FSL_DIFF_SMALL_GAP /* ** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a ** pair of adjacent differences. Return true if the gap between these ** two differences is so small that they should be rendered as a single ** edit. */ static int smallGap2(const int *R, int ma, int mb){ int m = R[3]; ma += R[4] + m; mb += R[5] + m; if( ma*mb>DIFF_ALIGN_MX ) return 0; return m<=2 || m<=(R[1]+R[2]+R[4]+R[5])/8; } #endif static unsigned short diff_opt_context_lines(fsl_dibu_opt const * opt){ const unsigned short dflt = 5; unsigned short n = opt ? opt->contextLines : dflt; if( !n && (opt->diffFlags & FSL_DIFF2_CONTEXT_ZERO)==0 ){ n = dflt; } return n; } /* ** Minimum of two values */ static int diffMin(int a, int b){ return a<b ? a : b; } /****************************************************************************/ /* ** Return the number between 0 and 100 that is smaller the closer pA and ** pB match. Return 0 for a perfect match. Return 100 if pA and pB are ** completely different. ** ** The current algorithm is as follows: ** ** (1) Remove leading and trailing whitespace. ** (2) Truncate both strings to at most 250 characters ** (3) If the two strings have a common prefix, measure that prefix ** (4) Find the length of the longest common subsequence that is ** at least 150% longer than the common prefix. ** (5) Longer common subsequences yield lower scores. */ static int match_dline2(fsl_dline * const pA, fsl_dline * const pB){ const char *zA; /* Left string */ const char *zB; /* right string */ int nA; /* Bytes in zA[] */ int nB; /* Bytes in zB[] */ int nMin; int nPrefix; int avg; /* Average length of A and B */ int i, j, k; /* Loop counters */ int best = 0; /* Longest match found so far */ int score; /* Final score. 0..100 */ unsigned char c; /* Character being examined */ unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */ unsigned char aNext[252]; /* aNext[i] = index in zB[] of next zB[i] char */ zA = pA->z; if( pA->nw==0 && pA->n ){ for(i=0; i<pA->n && diff_isspace(zA[i]); i++){} pA->indent = i; for(j=pA->n-1; j>i && diff_isspace(zA[j]); j--){} pA->nw = j - i + 1; } zA += pA->indent; nA = pA->nw; zB = pB->z; if( pB->nw==0 && pB->n ){ for(i=0; i<pB->n && diff_isspace(zB[i]); i++){} pB->indent = i; for(j=pB->n-1; j>i && diff_isspace(zB[j]); j--){} pB->nw = j - i + 1; } zB += pB->indent; nB = pB->nw; if( nA>250 ) nA = 250; if( nB>250 ) nB = 250; avg = (nA+nB)/2; if( avg==0 ) return 0; nMin = nA; if( nB<nMin ) nMin = nB; if( nMin==0 ) return 68; for(nPrefix=0; nPrefix<nMin && zA[nPrefix]==zB[nPrefix]; nPrefix++){} best = 0; if( nPrefix>5 && nPrefix>nMin/2 ){ best = nPrefix*3/2; if( best>=avg - 2 ) best = avg - 2; } if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0; memset(aFirst, 0xff, sizeof(aFirst)); zA--; zB--; /* Make both zA[] and zB[] 1-indexed */ for(i=nB; i>0; i--){ c = (unsigned char)zB[i]; aNext[i] = aFirst[c]; aFirst[c] = i; } for(i=1; i<=nA-best; i++){ c = (unsigned char)zA[i]; for(j=aFirst[c]; j<nB-best && memcmp(&zA[i],&zB[j],best)==0; j = aNext[j]){ int limit = diffMin(nA-i, nB-j); for(k=best; k<=limit && zA[k+i]==zB[k+j]; k++){} if( k>best ) best = k; } } score = 5 + ((best>=avg) ? 0 : (avg - best)*95/avg); #if 0 fprintf(stderr, "A: [%.*s]\nB: [%.*s]\nbest=%d avg=%d score=%d\n", nA, zA+1, nB, zB+1, best, avg, score); #endif /* Return the result */ return score; } // Forward decl for recursion's sake. static int diffBlockAlignment( fsl_dline * const aLeft, int nLeft, fsl_dline * const aRight, int nRight, fsl_dibu_opt const * pOpt, unsigned char **pResult, unsigned *pNResult ); /* ** Make a copy of a list of nLine DLine objects from one array to ** another. Hash the new array to ignore whitespace. */ static void diffDLineXfer( fsl_dline *aTo, const fsl_dline *aFrom, int nLine ){ int i, j, k; uint64_t h, h2; for(i=0; i<nLine; i++) aTo[i].iHash = 0; for(i=0; i<nLine; i++){ const char *z = aFrom[i].z; int n = aFrom[i].n; for(j=0; j<n && diff_isspace(z[j]); j++){} aTo[i].z = &z[j]; for(k=aFrom[i].n; k>j && diff_isspace(z[k-1]); k--){} aTo[i].n = n = k-j; aTo[i].indent = 0; aTo[i].nw = 0; for(h=0; j<k; j++){ char c = z[j]; if( !diff_isspace(c) ){ h = (h^c)*9000000000000000041LL; } } aTo[i].h = h = ((h%281474976710597LL)<<FSL__LINE_LENGTH_MASK_SZ) | n; h2 = h % nLine; aTo[i].iNext = aTo[h2].iHash; aTo[h2].iHash = i+1; } } /* ** For a difficult diff-block alignment that was originally for ** the default consider-all-whitespace algorithm, try to find the ** longest common subsequence between the two blocks that involves ** only whitespace changes. ** ** Result is stored in *pOut and must be eventually fsl_free()d. ** Returns 0 on success, setting *pOut to NULL if no good match is ** found. Returns FSL_RC_OOM on allocation error. */ static int diffBlockAlignmentIgnoreSpace( fsl_dline * const aLeft, int nLeft, /* Text on the left */ fsl_dline * const aRight, int nRight, /* Text on the right */ fsl_dibu_opt const *pOpt, /* Configuration options */ unsigned char ** pOut, /* OUTPUT: Result */ unsigned *pNResult /* OUTPUT: length of result */ ){ fsl__diff_cx dc; int iSX, iEX; /* Start and end of LCS on the left */ int iSY, iEY; /* Start and end of the LCS on the right */ unsigned char *a1, *a2; int n1, n2, nLCS, rc = 0; dc.aEdit = 0; dc.nEdit = 0; dc.nEditAlloc = 0; dc.nFrom = nLeft; dc.nTo = nRight; dc.cmpLine = fsl_dline_cmp_ignore_ws; dc.aFrom = fsl_malloc( sizeof(fsl_dline)*(nLeft+nRight) ); if(!dc.aFrom) return FSL_RC_OOM; dc.aTo = &dc.aFrom[dc.nFrom]; diffDLineXfer(dc.aFrom, aLeft, nLeft); diffDLineXfer(dc.aTo, aRight, nRight); fsl__diff_optimal_lcs(&dc,0,nLeft,0,nRight,&iSX,&iEX,&iSY,&iEY); fsl_free(dc.aFrom); nLCS = iEX - iSX; if( nLCS<5 ){ /* No good LCS was found */ *pOut = NULL; *pNResult = 0; return 0; } rc = diffBlockAlignment(aLeft,iSX,aRight,iSY, pOpt,&a1, (unsigned *)&n1); if(rc) return rc; rc = diffBlockAlignment(aLeft+iEX, nLeft-iEX, aRight+iEY, nRight-iEY, pOpt, &a2, (unsigned *)&n2); if(rc){ fsl_free(a1); return rc; }else{ unsigned char * x = (unsigned char *)fsl_realloc(a1, n1+nLCS+n2); if(NULL==x){ fsl_free(a1); fsl_free(a2); return FSL_RC_OOM; } a1 = x; } memcpy(a1+n1+nLCS,a2,n2); memset(a1+n1,3,nLCS); fsl_free(a2); *pNResult = (unsigned)(n1+n2+nLCS); *pOut = a1; return 0; } /* ** This is a helper route for diffBlockAlignment(). In this case, ** a very large block is encountered that might be too expensive to ** use the O(N*N) Wagner edit distance algorithm. So instead, this ** block implements a less-precise but faster O(N*logN) divide-and-conquer ** approach. ** ** Result is stored in *pOut and must be eventually fsl_free()d. ** Returns 0 on success. Returns FSL_RC_OOM on allocation error. */ static int diffBlockAlignmentDivideAndConquer( fsl_dline * const aLeft, int nLeft, /* Text on the left */ fsl_dline * const aRight, int nRight, /* Text on the right */ fsl_dibu_opt const *pOpt, /* Configuration options */ unsigned char ** pOut, /* OUTPUT: result */ unsigned *pNResult /* OUTPUT: length of result */ ){ fsl_dline *aSmall; /* The smaller of aLeft and aRight */ fsl_dline *aBig; /* The larger of aLeft and aRight */ int nSmall, nBig; /* Size of aSmall and aBig. nSmall<=nBig */ int iDivSmall, iDivBig; /* Divider point for aSmall and aBig */ int iDivLeft, iDivRight; /* Divider point for aLeft and aRight */ unsigned char *a1 = 0, *a2 = 0; /* Results of the alignments on two halves */ int n1, n2; /* Number of entries in a1 and a2 */ int score, bestScore; /* Score and best score seen so far */ int i; /* Loop counter */ int rc; if( nLeft>nRight ){ aSmall = aRight; nSmall = nRight; aBig = aLeft; nBig = nLeft; }else{ aSmall = aLeft; nSmall = nLeft; aBig = aRight; nBig = nRight; } iDivBig = nBig/2; iDivSmall = nSmall/2; bestScore = 10000; for(i=0; i<nSmall; i++){ score = match_dline2(aBig+iDivBig, aSmall+i) + abs(i-nSmall/2)*2; if( score<bestScore ){ bestScore = score; iDivSmall = i; } } if( aSmall==aRight ){ iDivRight = iDivSmall; iDivLeft = iDivBig; }else{ iDivRight = iDivBig; iDivLeft = iDivSmall; } rc = diffBlockAlignment(aLeft,iDivLeft,aRight,iDivRight, pOpt,&a1, (unsigned*)&n1); if(!rc){ rc = diffBlockAlignment(aLeft+iDivLeft, nLeft-iDivLeft, aRight+iDivRight, nRight-iDivRight, pOpt, &a2, (unsigned*)&n2); } if(rc){ fsl_free(a1); fsl_free(a2); }else{ unsigned char * x = (unsigned char *)fsl_realloc(a1, n1+n2); if(!x) rc = FSL_RC_OOM; else{ a1 = x; memcpy(a1+n1,a2,n2); *pNResult = (unsigned)(n1+n2); *pOut = a1; } fsl_free(a2); } return rc; } /* ** There is a change block in which nLeft lines of text on the left are ** converted into nRight lines of text on the right. This routine computes ** how the lines on the left line up with the lines on the right. ** ** The return value is a buffer of unsigned characters, obtained from ** fsl_malloc(). (The caller needs to free the `*pResult` value using ** fsl_free().) Entries in the returned array have values as follows: ** ** 1. Delete the next line of pLeft. ** 2. Insert the next line of pRight. ** 3. The next line of pLeft changes into the next line of pRight. ** 4. Delete one line from pLeft and add one line to pRight. ** ** The length of the returned array will be at most nLeft+nRight bytes. ** If the first bytes is 4, that means we could not compute reasonable ** alignment between the two blocks. ** ** Algorithm: Wagner's minimum edit-distance algorithm, modified by ** adding a cost to each match based on how well the two rows match ** each other. Insertion and deletion costs are 50. Match costs ** are between 0 and 100 where 0 is a perfect match 100 is a complete ** mismatch. */ int diffBlockAlignment( fsl_dline * const aLeft, int nLeft, /* Text on the left */ fsl_dline * const aRight, int nRight, /* Text on the right */ fsl_dibu_opt const * pOpt, /* Configuration options */ unsigned char **pResult, /* Raw result */ unsigned *pNResult /* OUTPUT: length of result */ ){ int i, j, k; /* Loop counters */ int *a = 0; /* One row of the Wagner matrix */ int *pToFree = 0; /* Space that needs to be freed */ unsigned char *aM = 0; /* Wagner result matrix */ int nMatch, iMatch; /* Number of matching lines and match score */ int aBuf[100]; /* Stack space for a[] if nRight not to big */ int rc = 0; if( nLeft==0 ){ aM = fsl_malloc( nRight + 2 ); if(!aM) return FSL_RC_OOM; memset(aM, 2, nRight); *pNResult = nRight; *pResult = aM; return 0; } if( nRight==0 ){ aM = fsl_malloc( nLeft + 2 ); if(!aM) return FSL_RC_OOM; memset(aM, 1, nLeft); *pNResult = nLeft; *pResult = aM; return 0; } /* For large alignments, try to use alternative algorithms that are ** faster than the O(N*N) Wagner edit distance. */ if( nLeft*nRight>DIFF_ALIGN_MX && (pOpt->diffFlags & FSL_DIFF2_SLOW_SBS)==0 ){ if( (pOpt->diffFlags & FSL_DIFF2_IGNORE_ALLWS)==0 ){ *pResult = NULL; rc = diffBlockAlignmentIgnoreSpace(aLeft, nLeft,aRight,nRight, pOpt, pResult, pNResult); if(rc || *pResult) return rc; } return diffBlockAlignmentDivideAndConquer(aLeft, nLeft,aRight, nRight, pOpt, pResult, pNResult); } /* If we reach this point, we will be doing an O(N*N) Wagner minimum ** edit distance to compute the alignment. */ if( nRight < (int)(sizeof(aBuf)/sizeof(aBuf[0]))-1 ){ pToFree = 0; a = aBuf; }else{ a = pToFree = fsl_malloc( sizeof(a[0])*(nRight+1) ); if(!a){ rc = FSL_RC_OOM; goto end; } } aM = fsl_malloc( (nLeft+1)*(nRight+1) ); if(!aM){ rc = FSL_RC_OOM; goto end; } /* Compute the best alignment */ for(i=0; i<=nRight; i++){ aM[i] = 2; a[i] = i*50; } aM[0] = 0; for(j=1; j<=nLeft; j++){ int p = a[0]; a[0] = p+50; aM[j*(nRight+1)] = 1; for(i=1; i<=nRight; i++){ int m = a[i-1]+50; int d = 2; if( m>a[i]+50 ){ m = a[i]+50; d = 1; } if( m>p ){ int const score = match_dline2(&aLeft[j-1], &aRight[i-1]); if( (score<=90 || (i<j+1 && i>j-1)) && m>p+score ){ m = p+score; d = 3 | score*4; } } p = a[i]; a[i] = m; aM[j*(nRight+1)+i] = d; } } /* Compute the lowest-cost path back through the matrix */ i = nRight; j = nLeft; k = (nRight+1)*(nLeft+1)-1; nMatch = iMatch = 0; while( i+j>0 ){ unsigned char c = aM[k]; if( c>=3 ){ assert( i>0 && j>0 ); i--; j--; nMatch++; iMatch += (c>>2); aM[k] = 3; }else if( c==2 ){ assert( i>0 ); i--; }else{ assert( j>0 ); j--; } k--; aM[k] = aM[j*(nRight+1)+i]; } k++; i = (nRight+1)*(nLeft+1) - k; memmove(aM, &aM[k], i); *pNResult = i; *pResult = aM; end: fsl_free(pToFree); return rc; } /* ** Format a diff using a fsl_dibu object */ static int fdb__format( fsl__diff_cx * const cx, fsl_dibu * const pBuilder ){ fsl_dline *A; /* Left side of the diff */ fsl_dline *B; /* Right side of the diff */ fsl_dibu_opt const * pOpt = pBuilder->opt; const int *R; /* Array of COPY/DELETE/INSERT triples */ unsigned int a; /* Index of next line in A[] */ unsigned int b; /* Index of next line in B[] */ unsigned int r; /* Index into R[] */ unsigned int nr; /* Number of COPY/DELETE/INSERT triples to process */ unsigned int mxr; /* Maximum value for r */ unsigned int na, nb; /* Number of lines shown from A and B */ unsigned int i, j; /* Loop counters */ unsigned int m, ma, mb;/* Number of lines to output */ signed int skip; /* Number of lines to skip */ unsigned int contextLines; /* Lines of context above and below each change */ unsigned short passNumber = 0; int rc = 0; #define RC if(rc) goto end #define METRIC(M) if(1==passNumber) ++pBuilder->metrics.M pass_again: contextLines = diff_opt_context_lines(pOpt); skip = 0; a = b = 0; A = cx->aFrom; B = cx->aTo; R = cx->aEdit; mxr = cx->nEdit; //MARKER(("contextLines=%u, nEdit = %d, mxr=%u\n", contextLines, cx->nEdit, mxr)); while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } pBuilder->lnLHS = pBuilder->lnRHS = 0; ++passNumber; if(pBuilder->start){ pBuilder->passNumber = passNumber; rc = pBuilder->start(pBuilder); RC; } for(r=0; r<mxr; r += 3*nr){ /* Figure out how many triples to show in a single block */ for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<(int)contextLines*2; nr++){} #if 0 /* MISSING: this "should" be replaced by a stateful predicate function, probably in the fsl_dibu_opt class. */ /* If there is a regex, skip this block (generate no diff output) ** if the regex matches or does not match both insert and delete. ** Only display the block if one side matches but the other side does ** not. */ if( pOpt->pRe ){ int hideBlock = 1; int xa = a, xb = b; for(i=0; hideBlock && i<nr; i++){ int c1, c2; xa += R[r+i*3]; xb += R[r+i*3]; c1 = re_dline_match(pOpt->pRe, &A[xa], R[r+i*3+1]); c2 = re_dline_match(pOpt->pRe, &B[xb], R[r+i*3+2]); hideBlock = c1==c2; xa += R[r+i*3+1]; xb += R[r+i*3+2]; } if( hideBlock ){ a = xa; b = xb; continue; } } #endif /* Figure out how many lines of A and B are to be displayed ** for this change block. */ if( R[r]>(int)contextLines ){ na = nb = contextLines; skip = R[r] - contextLines; }else{ na = nb = R[r]; skip = 0; } for(i=0; i<nr; i++){ na += R[r+i*3+1]; nb += R[r+i*3+2]; } if( R[r+nr*3]>(int)contextLines ){ na += contextLines; nb += contextLines; }else{ na += R[r+nr*3]; nb += R[r+nr*3]; } for(i=1; i<nr; i++){ na += R[r+i*3]; nb += R[r+i*3]; } //MARKER(("Chunk header... a=%u, b=%u, na=%u, nb=%u, skip=%d\n", a, b, na, nb, skip)); if(pBuilder->chunkHeader /* The following bit is a kludge to keep from injecting a chunk header between chunks which are directly adjacent. The problem, however, is that we cannot skip _reliably_ without also knowing how the next chunk aligns. If we skip it here, the _previous_ chunk header may well be telling the user a lie with regards to line numbers. Fossil itself does not have this issue because it generates these chunk headers directly in this routine, instead of in the diff builder, depending on a specific flag being set in builder->opt. Also (related), in that implementation, fossil will collapse chunks which are separated by less than the context distance into contiguous chunks (see below). Because we farm out the chunkHeader lines to the builder, we cannot reliably do that here. */ #if 0 && !skip #endif ){ rc = pBuilder->chunkHeader(pBuilder, (uint32_t)(na ? a+skip+1 : a+skip), (uint32_t)na, (uint32_t)(nb ? b+skip+1 : b+skip), (uint32_t)nb); RC; } /* Show the initial common area */ a += skip; b += skip; m = R[r] - skip; if( r ) skip -= contextLines; //MARKER(("Show the initial common... a=%u, b=%u, m=%u, r=%u, skip=%d\n", a, b, m, r, skip)); if( skip>0 ){ if( NULL==pBuilder->chunkHeader && skip<(int)contextLines ){ /* 2021-09-27: BUG: this is incompatible with unified diff format. The generated header lines say we're skipping X lines but we then end up including lines which that header says to skip. As a workaround, we'll only run this when pBuilder->chunkHeader is NULL, noting that fossil's diff builder interface does not have that method (and thus doesn't have this issue, instead generating chunk headers directly in this algorithm). Without this block, our "utxt" diff builder can mimic fossil's non-diff builder unified diff format, except that we add Index lines (feature or bug?). With this block, the header values output above are wrong. */ /* If the amount to skip is less that the context band, then ** go ahead and show the skip band as it is not worth eliding */ //MARKER(("skip %d < contextLines %d\n", skip, contextLines)); /* from fossil(1) from formatDiff() */ for(j=0; 0==rc && j<(unsigned)skip; j++){ //MARKER(("(A) COMMON\n")); rc = pBuilder->common(pBuilder, &A[a+j-skip]); } }else{ rc = pBuilder->skip(pBuilder, skip); } RC; } for(j=0; 0==rc && j<m; j++){ //MARKER(("(B) COMMON\n")); rc = pBuilder->common(pBuilder, &A[a+j]); } RC; a += m; b += m; //MARKER(("Show the differences... a=%d, b=%d, m=%d\n", a, b, m)); /* Show the differences */ for(i=0; i<nr; i++){ unsigned int nAlign; unsigned char *alignment = 0; ma = R[r+i*3+1]; /* Lines on left but not on right */ mb = R[r+i*3+2]; /* Lines on right but not on left */ #if FSL_DIFF_SMALL_GAP /* Try merging the current block with subsequent blocks, if the ** subsequent blocks are nearby and their result isn't too big. */ while( i<nr-1 && smallGap2(&R[r+i*3],ma,mb) ){ i++; m = R[r+i*3]; ma += R[r+i*3+1] + m; mb += R[r+i*3+2] + m; } #endif /* Try to find an alignment for the lines within this one block */ rc = diffBlockAlignment(&A[a], ma, &B[b], mb, pOpt, &alignment, &nAlign); RC; for(j=0; ma+mb>0; j++){ assert( j<nAlign ); switch( alignment[j] ){ case 1: { /* Delete one line from the left */ METRIC(deletions); rc = pBuilder->deletion(pBuilder, &A[a]); if(rc) goto bail; ma--; a++; break; } case 2: { /* Insert one line on the right */ METRIC(insertions); rc = pBuilder->insertion(pBuilder, &B[b]); if(rc) goto bail; assert( mb>0 ); mb--; b++; break; } case 3: { /* The left line is changed into the right line */ if( 0==cx->cmpLine(&A[a], &B[b]) ){ rc = pBuilder->common(pBuilder, &A[a]); }else{ METRIC(edits); rc = pBuilder->edit(pBuilder, &A[a], &B[b]); } if(rc) goto bail; assert( ma>0 && mb>0 ); ma--; mb--; a++; b++; break; } case 4: { /* Delete from left then separately insert on the right */ METRIC(replacements); rc = pBuilder->replacement(pBuilder, &A[a], &B[b]); if(rc) goto bail; ma--; a++; mb--; b++; break; } } } assert( nAlign==j ); fsl_free(alignment); if( i<nr-1 ){ m = R[r+i*3+3]; for(j=0; 0==rc && j<m; j++){ //MARKER(("D common\n")); rc = pBuilder->common(pBuilder, &A[a+j]); } RC; b += m; a += m; } continue; bail: assert(rc); fsl_free(alignment); goto end; } /* Show the final common area */ assert( nr==i ); m = R[r+nr*3]; if( m>contextLines ) m = contextLines; for(j=0; 0==rc && j<m && j<contextLines; j++){ //MARKER(("E common\n")); rc = pBuilder->common(pBuilder, &A[a+j]); } RC; } if( R[r]>(int)contextLines ){ rc = pBuilder->skip(pBuilder, R[r] - contextLines); } end: #undef RC #undef METRIC if(0==rc){ if(pBuilder->finish) pBuilder->finish(pBuilder); if(pBuilder->twoPass && 1==passNumber){ goto pass_again; } } return rc; } /* MISSING(?) fossil(1) converts the diff inputs into utf8 with no BOM. Whether we really want to do that here or rely on the caller to is up for debate. If we do it here, we have to make the inputs non-const, which seems "wrong" for a library API. */ #define blob_to_utf8_no_bom(A,B) (void)0 /** Performs a diff of version 1 (pA) and version 2 (pB). ONE of pBuilder or outRaw must be non-NULL. If pBuilder is not NULL, all output for the diff is emitted via pBuilder. If outRaw is not NULL then on success *outRaw is set to the array of diff triples, transfering ownership to the caller, who must eventually fsl_free() it. On error, *outRaw is not modified but pBuilder may have emitted partial output. That is not knowable for the general case. Ownership of pBuilder is not changed. If pBuilder is not NULL then pBuilder->opt must be non-NULL. */ static int fsl_diff2_text_impl(fsl_buffer const *pA, fsl_buffer const *pB, fsl_dibu * const pBuilder, fsl_dibu_opt const * const opt_, int ** outRaw){ int rc = 0; fsl__diff_cx c = fsl__diff_cx_empty; bool ignoreWs = false; int ansiOptCount = 0; fsl_dibu_opt opt = *opt_ /*we need a copy for the sake of the FSL_DIFF2_INVERT flag*/; if(!pA || !pB || (pBuilder && outRaw)) return FSL_RC_MISUSE; blob_to_utf8_no_bom(pA, 0); blob_to_utf8_no_bom(pB, 0); if( opt.diffFlags & FSL_DIFF2_INVERT ){ char const * z; fsl_buffer const *pTemp = pA; pA = pB; pB = pTemp; z = opt.hashRHS; opt.hashRHS = opt.hashLHS; opt.hashLHS = z; z = opt.nameRHS; opt.nameRHS = opt.nameLHS; opt.nameLHS = z; } #define AOPT(OPT) \ if(opt.ansiColor.OPT) ansiOptCount += (*opt.ansiColor.OPT) ? 1 : 0; \ else opt.ansiColor.OPT = "" AOPT(insertion); AOPT(edit); AOPT(deletion); #undef AOPT if(0==ansiOptCount){ opt.ansiColor.reset = ""; }else if(!opt.ansiColor.reset || !*opt.ansiColor.reset){ opt.ansiColor.reset = "\x1b[0m"; } ignoreWs = (opt.diffFlags & FSL_DIFF2_IGNORE_ALLWS)!=0; if(FSL_DIFF2_IGNORE_ALLWS==(opt.diffFlags & FSL_DIFF2_IGNORE_ALLWS)){ c.cmpLine = fsl_dline_cmp_ignore_ws; }else{ c.cmpLine = fsl_dline_cmp; } rc = fsl_break_into_dlines(fsl_buffer_cstr(pA), (fsl_int_t)pA->used, (uint32_t*)&c.nFrom, &c.aFrom, opt.diffFlags); if(rc) goto end; rc = fsl_break_into_dlines(fsl_buffer_cstr(pB), (fsl_int_t)pB->used, (uint32_t*)&c.nTo, &c.aTo, opt.diffFlags); if(rc) goto end; /* Compute the difference */ rc = fsl__diff_all(&c); if(rc) goto end; if( ignoreWs && c.nEdit==6 && c.aEdit[1]==0 && c.aEdit[2]==0 ){ rc = FSL_RC_DIFF_WS_ONLY; goto end; } if( (opt.diffFlags & FSL_DIFF2_NOTTOOBIG)!=0 ){ int i, m, n; int const * const a = c.aEdit; int const mx = c.nEdit; for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; } if( !n || n>10000 ){ rc = FSL_RC_RANGE; /* diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); */ goto end; } } //fsl__dump_triples(&c, __FILE__, __LINE__); if( (opt.diffFlags & FSL_DIFF2_NOOPT)==0 ){ fsl__diff_optimize(&c); } //fsl__dump_triples(&c, __FILE__, __LINE__); /** Reference: https://fossil-scm.org/home/file?ci=cae7036bb7f07c1b&name=src/diff.c&ln=2749-2804 Noting that: - That function's return value is this one's *outRaw - DIFF_NUMSTAT flag is not implemented. For that matter, flags which result in output going anywhere except for pBuilder->out are not implemented here, e.g. DIFF_RAW. That last point makes this impl tiny compared to the original! */ if(pBuilder){ fsl_dibu_opt const * oldOpt = pBuilder->opt; pBuilder->opt = &opt; rc = fdb__format(&c, pBuilder); pBuilder->opt = oldOpt; } end: if(0==rc && outRaw){ *outRaw = c.aEdit; c.aEdit = 0; } fsl__diff_cx_clean(&c); return rc; } int fsl_diff_v2(fsl_buffer const * pv1, fsl_buffer const * pv2, fsl_dibu * const pBuilder){ return fsl_diff2_text_impl(pv1, pv2, pBuilder, pBuilder->opt, NULL); } int fsl_diff_v2_raw(fsl_buffer const * pv1, fsl_buffer const * pv2, fsl_dibu_opt const * const opt, int **outRaw ){ return fsl_diff2_text_impl(pv1, pv2, NULL, opt ? opt : &fsl_dibu_opt_empty, outRaw); } #undef DIFF_ALIGN_MX #undef blob_to_utf8_no_bom #undef FSL_DIFF_SMALL_GAP #undef diff_isspace #undef LENGTH /* end of file ./src/diff.c */ /* start of file ./src/encode.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 |
︙ | ︙ | |||
31197 31198 31199 31200 31201 31202 31203 | if( pBomSize ) *pBomSize = bomSize; return fsl_buffer_size(b)<bomSize ? false : memcmp(z, bom, bomSize)==0; } bool fsl_invalid_utf8(fsl_buffer const * const b){ | | | 29904 29905 29906 29907 29908 29909 29910 29911 29912 29913 29914 29915 29916 29917 29918 | if( pBomSize ) *pBomSize = bomSize; return fsl_buffer_size(b)<bomSize ? false : memcmp(z, bom, bomSize)==0; } bool fsl_invalid_utf8(fsl_buffer const * const b){ fsl_size_t n = 0; const unsigned char *z = (unsigned char *) fsl_buffer_cstr2(b, &n); unsigned char c; /* lead byte to be handled. */ if( n==0 ) return false; /* Empty file -> OK */ c = *z; while( --n>0 ){ if( c>=0x80 ){ const unsigned char *def; /* pointer to range table*/ |
︙ | ︙ | |||
33825 33826 33827 33828 33829 33830 33831 | " WHERE tagid=%d AND tagxref.rid=ancestor.rid" " AND value=%Q AND tagtype>0)" " LIMIT 1", rid, rid, FSL_TAGID_BRANCH, zBranch ); } | | | 32532 32533 32534 32535 32536 32537 32538 32539 32540 32541 32542 32543 32544 32545 32546 | " WHERE tagid=%d AND tagxref.rid=ancestor.rid" " AND value=%Q AND tagtype>0)" " LIMIT 1", rid, rid, FSL_TAGID_BRANCH, zBranch ); } int fsl_branch_of_rid(fsl_cx * const f, fsl_id_t rid, bool doFallback, char ** zOut ){ char *zBr = 0; fsl_db * const db = fsl_cx_db_repo(f); fsl_stmt st = fsl_stmt_empty; int rc; if(!fsl_needs_repo(f)) return FSL_RC_NOT_A_REPO; assert(db); |
︙ | ︙ | |||
34690 34691 34692 34693 34694 34695 34696 | ? fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS, "File already exists and " "allowOverwrite is false: %s", opt->filename) : FSL_RC_ALREADY_EXISTS; } if(f){ | | | 33397 33398 33399 33400 33401 33402 33403 33404 33405 33406 33407 33408 33409 33410 33411 | ? fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS, "File already exists and " "allowOverwrite is false: %s", opt->filename) : FSL_RC_ALREADY_EXISTS; } if(f){ rc = fsl_close_scm_dbs(f) /* Will fail if a transaction is active! */; switch(rc){ case 0: break; default: return rc; } |
︙ | ︙ | |||
35092 35093 35094 35095 35096 35097 35098 | fsl_stmt_finalize(&s); } return rc; } } /* UNTESTED */ | | | | | 33799 33800 33801 33802 33803 33804 33805 33806 33807 33808 33809 33810 33811 33812 33813 33814 33815 33816 33817 33818 33819 33820 33821 | fsl_stmt_finalize(&s); } return rc; } } /* UNTESTED */ bool fsl_repo_is_readonly(fsl_cx const * f){ if(!f || !f->dbMain) return false; else{ int const roleId = f->ckout.db.dbh ? FSL_DBROLE_MAIN : FSL_DBROLE_REPO /* If CKOUT is attached, it is the main DB and REPO is ATTACHed. */ ; char const * zRole = fsl_db_role_name(roleId); assert(f->dbMain); return sqlite3_db_readonly(f->dbMain->dbh, zRole) ? true : false; } } int fsl__repo_record_filename(fsl_cx * const f){ fsl_db * dbR = fsl_needs_repo(f); fsl_db * dbC; fsl_db * dbConf; |
︙ | ︙ | |||
36205 36206 36207 36208 36209 36210 36211 36212 36213 36214 36215 36216 36217 36218 | } bool fsl_repo_forbids_delta_manifests(fsl_cx * const f){ return fsl_config_get_bool(f, FSL_CONFDB_REPO, false, "forbid-delta-manifests"); } #undef MARKER /* end of file ./src/repo.c */ /* start of file ./src/schema.c */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 34912 34913 34914 34915 34916 34917 34918 34919 34920 34921 34922 34923 34924 34925 34926 34927 34928 34929 34930 34931 34932 34933 34934 34935 34936 34937 34938 34939 34940 34941 34942 34943 34944 34945 34946 34947 34948 34949 34950 34951 34952 34953 34954 34955 34956 34957 34958 34959 34960 34961 34962 34963 34964 34965 34966 34967 34968 34969 34970 34971 34972 34973 34974 34975 34976 34977 34978 34979 34980 34981 34982 34983 34984 34985 34986 34987 34988 34989 34990 34991 34992 34993 34994 34995 34996 34997 34998 34999 35000 35001 35002 35003 35004 35005 35006 35007 35008 35009 | } bool fsl_repo_forbids_delta_manifests(fsl_cx * const f){ return fsl_config_get_bool(f, FSL_CONFDB_REPO, false, "forbid-delta-manifests"); } int fsl_tkt_id_to_rids(fsl_cx * const f, char const * tktId, fsl_id_t ** ridList){ fsl_db * const dbR = fsl_needs_repo(f); if(!dbR) return FSL_RC_NOT_A_REPO; fsl_stmt q = fsl_stmt_empty; int rc; fsl_id_t * rids = 0; int const isFullId = fsl_is_uuid(tktId); unsigned int n = 0; if(FSL_STRLEN_SHA1<isFullId){ return fsl_cx_err_set(f, FSL_RC_RANGE, "Ticket ID is not valid. Expecting <=%d bytes of " "lower-case hex values.", FSL_STRLEN_SHA1); } rc = fsl_cx_transaction_begin(f); if(rc) return rc; if(isFullId){ // Expect an exact match... assert(FSL_STRLEN_SHA1==isFullId); rc = fsl_cx_prepare(f, &q, "SELECT b.rid, b.uuid FROM blob b, tagxref x, tag t " "WHERE t.tagname = 'tkt-'||%Q " "AND t.tagid=x.tagid AND x.rid=b.rid " "ORDER BY x.mtime, x.rowid", tktId); }else{ /* Check for an ambiguous match of an ID prefix... */ int32_t const c = fsl_db_g_int32(dbR, -1, "SELECT COUNT(distinct tagname) FROM tag " "WHERE tagname GLOB 'tkt-'||%Q||'*'", tktId); if(c<0){ rc = fsl_cx_uplift_db_error(f, dbR); goto end; }else if(0==c){ n = 0; goto not_found; }else if(c>1){ rc = fsl_cx_err_set(f, FSL_RC_AMBIGUOUS, "Ticket ID prefix is ambiguous: %s", tktId); goto end; } rc = fsl_cx_prepare(f, &q, "SELECT b.rid, b.uuid FROM blob b, tagxref x, tag t " "WHERE t.tagname GLOB 'tkt-'||%Q||'*' " "AND t.tagid=x.tagid AND x.rid=b.rid " "ORDER BY x.mtime, x.rowid", tktId); } if(rc) return rc; // Count how many we have to allocate for... while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)) ++n; fsl_stmt_reset(&q); not_found: if(!n){ rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "No ticket found with ID%s %s.", isFullId ? "" : " prefix", tktId); goto end; } // Populate the result list... rids = (fsl_id_t*)fsl_malloc(sizeof(fsl_id_t)*(n+1)); if(!rids){ rc = FSL_RC_OOM; goto end; } unsigned i = 0; while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ assert(i<n); rids[i++] = fsl_stmt_g_id(&q, 0); } assert(i==n); rids[i] = 0; end: if(0==rc) *ridList = rids; else fsl_free(rids); fsl_stmt_finalize(&q); fsl_cx_transaction_end(f,false); return rc; } #undef MARKER /* end of file ./src/repo.c */ /* start of file ./src/schema.c */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* |
︙ | ︙ | |||
43306 43307 43308 43309 43310 43311 43312 | 32, 91, 115, 112, 108, 105, 116, 32, 91, 114, 101, 97, 100, 32, 36, 105, 110, 93, 32, 92, 110, 93, 10, 32, 32, 32, 32, 99, 108, 111, 115, 101, 32, 36, 105, 110, 10, 32, 32, 125, 10, 32, 32, 115, 101, 116, 32, 78, 32, 91, 108, 108, 101, 110, 103, 116, 104, 32, 36, 100, 105, 102, 102, 116, 120, 116, 93, 10, 32, 32, 115, 101, 116, 32, 105, 105, 32, 48, 10, 32, 32, 115, 101, 116, 32, 110, 68, 105, 102, 102, 115, 32, 48, 10, 32, 32, 115, 101, 116, 32, 110, 49, 32, 48, 10, 32, 32, 115, 101, 116, 32, 110, 50, 32, 48, 32, 32, 10, 32, 32, 97, 114, 114, 97, 121, 32, 115, 101, 116, 32, 119, 105, 100, 116, 104, 115, 32, 123, 116, 120, | | | 42097 42098 42099 42100 42101 42102 42103 42104 42105 42106 42107 42108 42109 42110 42111 | 32, 91, 115, 112, 108, 105, 116, 32, 91, 114, 101, 97, 100, 32, 36, 105, 110, 93, 32, 92, 110, 93, 10, 32, 32, 32, 32, 99, 108, 111, 115, 101, 32, 36, 105, 110, 10, 32, 32, 125, 10, 32, 32, 115, 101, 116, 32, 78, 32, 91, 108, 108, 101, 110, 103, 116, 104, 32, 36, 100, 105, 102, 102, 116, 120, 116, 93, 10, 32, 32, 115, 101, 116, 32, 105, 105, 32, 48, 10, 32, 32, 115, 101, 116, 32, 110, 68, 105, 102, 102, 115, 32, 48, 10, 32, 32, 115, 101, 116, 32, 110, 49, 32, 48, 10, 32, 32, 115, 101, 116, 32, 110, 50, 32, 48, 32, 32, 10, 32, 32, 97, 114, 114, 97, 121, 32, 115, 101, 116, 32, 119, 105, 100, 116, 104, 115, 32, 123, 116, 120, 116, 32, 51, 32, 108, 110, 32, 54, 32, 109, 107, 114, 32, 49, 125, 10, 32, 32, 119, 104, 105, 108, 101, 32, 123, 91, 115, 101, 116, 32, 108, 105, 110, 101, 32, 91, 103, 101, 116, 76, 105, 110, 101, 32, 36, 100, 105, 102, 102, 116, 120, 116, 32, 36, 78, 32, 105, 105, 93, 93, 32, 33, 61, 32, 45, 49, 125, 32, 123, 10, 32, 32, 32, 32, 115, 119, 105, 116, 99, 104, 32, 45, 45, 32, 91, 108, 105, 110, 100, 101, 120, 32, 36, 108, 105, 110, 101, 32, 48, 93, 32, 123, 10, 32, 32, 32, 32, 32, 32, 70, 73, 76, 69, 32, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 105, 110, 99, 114, 32, 110, 68, 105, 102, 102, 115, 10, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 114, 101, 97, 99, 104, 32, 119, 120, 32, 91, 108, 105, 115, 116, |
︙ | ︙ |
Changes to lib/libfossil.h.
︙ | ︙ | |||
2186 2187 2188 2189 2190 2191 2192 | /** @typedef typedef int (*fsl_list_visitor_f)(void * p, void * visitorState ) Generic visitor interface for fsl_list lists. Used by fsl_list_visit(). p is the pointer held by that list entry and visitorState is the 4th argument passed to fsl_list_visit(). Implementations must return 0 on success. Any other value causes | | | 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 | /** @typedef typedef int (*fsl_list_visitor_f)(void * p, void * visitorState ) Generic visitor interface for fsl_list lists. Used by fsl_list_visit(). p is the pointer held by that list entry and visitorState is the 4th argument passed to fsl_list_visit(). Implementations must return 0 on success. Any other value causes looping to stop and that value to be returned, but interpretation of the value is up to the caller (it might or might not be an error, depending on the context). Note that client code may use custom values, and is not strictly required to use FSL_RC_xxx values. HOWEVER... all of the libfossil APIs which take these as arguments may respond differently to some codes (most notable FSL_RC_BREAK, which they tend to treat as a stop-iteration-without-error result), so clients are strongly |
︙ | ︙ | |||
3919 3920 3921 3922 3923 3924 3925 | The text of the line. Owned by higher-level code. Not necessarily NUL-terminated: this->n holds its length. */ const char *z; /** Number of bytes of z which belong to this line. */ unsigned short n; // All members after this point are strictly for internal use only. | | > > | 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 | The text of the line. Owned by higher-level code. Not necessarily NUL-terminated: this->n holds its length. */ const char *z; /** Number of bytes of z which belong to this line. */ unsigned short n; // All members after this point are strictly for internal use only. /** Index of first non-space char. */ unsigned short indent; /** Number of bytes without leading/trailing space. */ unsigned short nw; /** Hash of the line. Lower X bits are the length. */ uint64_t h; /** 1+(Index of next line with same the same hash) */ unsigned int iNext; /** An array of fsl_dline elements serves two purposes. The fields above are one per line of input text. But each entry is also |
︙ | ︙ | |||
4078 4079 4080 4081 4082 4083 4084 | The internal APIs which drive each instance of this class guaranty that if any method of this class returns non-0 (an error code) then no futher methods will be called except for finalize(). */ struct fsl_dibu { /** Config info, owned by higher-level routines. Every diff builder | | | | | | 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 | The internal APIs which drive each instance of this class guaranty that if any method of this class returns non-0 (an error code) then no futher methods will be called except for finalize(). */ struct fsl_dibu { /** Config info, owned by higher-level routines. Every diff builder requires one of these but not all options are relevant for all builders. Note that the diff driver may make a bitwise copy of this object and use _that_ one for the actual diff generation. That is, methods of this class must never assume that this member's pointer refers to a specific object. (This leeway is necessary in order to implement diff inversion (swapping the LHS/RHS of a diff).) */ fsl_dibu_opt const * opt; /** Can optionally be set by factory functions to some internal opaque value, so that non-member routines specific to that type can determine whether any given builder is of the proper type. */ void const * typeID; /** If not NULL, this is called once per pass per diff to give the builder a chance to perform any bootstrapping initialization or header output. At the point this is called, this->cfg is assumed to have been filled out properly. Diff builder implementations which require dynamic resource allocation may perform it here or |
︙ | ︙ | |||
10715 10716 10717 10718 10719 10720 10721 10722 10723 10724 10725 10726 10727 10728 | @see fsl_deck_parse() @see fsl_deck_load_rid() @see fsl_deck_finalize() @see fsl_deck_clean() @see fsl_deck_save() @see fsl_deck_A_set() @see fsl_deck_B_set() @see fsl_deck_D_set() @see fsl_deck_E_set() @see fsl_deck_F_add() @see fsl_deck_J_add() @see fsl_deck_K_set() @see fsl_deck_L_set() @see fsl_deck_M_add() | > | 10717 10718 10719 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 | @see fsl_deck_parse() @see fsl_deck_load_rid() @see fsl_deck_finalize() @see fsl_deck_clean() @see fsl_deck_save() @see fsl_deck_A_set() @see fsl_deck_B_set() @see fsl_deck_C_set() @see fsl_deck_D_set() @see fsl_deck_E_set() @see fsl_deck_F_add() @see fsl_deck_J_add() @see fsl_deck_K_set() @see fsl_deck_L_set() @see fsl_deck_M_add() |
︙ | ︙ | |||
12405 12406 12407 12408 12409 12410 12411 12412 | Implementations must return 0 on success, non-0 on error. Some APIs using this interface may specify that FSL_RC_BREAK can be used to stop iteration over a loop without signaling an error. In such cases the APIs will translate FSL_RC_BREAK to 0 for result purposes, but will stop looping over whatever it is they are looping over. */ | > > > | | 12408 12409 12410 12411 12412 12413 12414 12415 12416 12417 12418 12419 12420 12421 12422 12423 12424 12425 12426 | Implementations must return 0 on success, non-0 on error. Some APIs using this interface may specify that FSL_RC_BREAK can be used to stop iteration over a loop without signaling an error. In such cases the APIs will translate FSL_RC_BREAK to 0 for result purposes, but will stop looping over whatever it is they are looping over. Note that the passed-in deck "should" be const but is not because iterating over a deck's F-cards requires non-const state. */ typedef int (*fsl_deck_visitor_f)( fsl_cx * const f, fsl_deck * const d, void * state ); /** For each unique wiki page name in f's repostory, this calls cb(), passing it the manifest of the most recent version of that page. The callback should return 0 on success, FSL_RC_BREAK to stop looping without an error, or any other non-0 code |
︙ | ︙ | |||
13248 13249 13250 13251 13252 13253 13254 13255 13256 13257 13258 13259 13260 13261 | /** Frees all memory owned by li and the F-cards it contains. Does not free the li pointer. */ FSL_EXPORT void fsl_card_F_list_finalize( fsl_card_F_list * li ); /** Holds options for use with fsl_branch_create(). */ struct fsl_branch_opt { /** The checkin RID from which the branch should originate. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 13254 13255 13256 13257 13258 13259 13260 13261 13262 13263 13264 13265 13266 13267 13268 13269 13270 13271 13272 13273 13274 13275 13276 13277 13278 13279 13280 13281 13282 13283 13284 13285 13286 13287 13288 13289 13290 13291 13292 13293 13294 13295 13296 13297 13298 13299 13300 13301 13302 13303 13304 13305 13306 13307 | /** Frees all memory owned by li and the F-cards it contains. Does not free the li pointer. */ FSL_EXPORT void fsl_card_F_list_finalize( fsl_card_F_list * li ); /** Iterates over all artifacts in f's current repository of the given type, passing each one to the given callback. If the callback returns non-0, iteration stops and that code is propagated back to the caller _unless_ the code is FSL_RC_BREAK, which means to stop iteration and return 0. The following artifact types are currently supported by this operation: FSL_SATYPE_CHECKIN, FSL_SATYPE_CONTROL, FSL_SATYPE_WIKI, FSL_SATYPE_TICKET, FSL_SATYPE_TECHNOTE, FSL_SATYPE_FORUMPOST (CLUSTER and ATTACHMENT are missing only because they're not(?) readily findable without parsing every blob in the blob table.) The decks are iterated over an an unspecified order except for: - WIKI and TECHNOTE entries are visited from newest to oldest. - TICKET entries are visited from oldest to newest to accommodate reconstructing their aggregate state in the callback. Each deck is finalized immediately after the callback returns, so the callback must not hold any pointers to the deck or its contents. Returns 0 on success or any number of error codes from lower levels, including, but not limited to: - FSL_RC_OOM on allocation error - FSL_RC_NOT_A_REPO if f has no repository opened. - FSL_RC_TYPE if the 2nd argument is invalid or if its type is not supported (e.g. FSL_SATYPE_CLUSTER). - FSL_RC_MISUSE if the 3rd argument is NULL. */ FSL_EXPORT int fsl_deck_foreach(fsl_cx * const f, fsl_satype_e type, fsl_deck_visitor_f visitor, void * visitorState); /** Holds options for use with fsl_branch_create(). */ struct fsl_branch_opt { /** The checkin RID from which the branch should originate. */ |
︙ | ︙ | |||
14262 14263 14264 14265 14266 14267 14268 | is false and no direct branch name is found then it sets `*zOut` to NULL. If doFallback is true then, on success, `*zOut` will always be set to some non-NULL value. On error `*zOut` is not modified. On error it may return FSL_RC_NOT_A_REPO, FSL_RC_OOM, or any number of db-side error codes. */ | | | 14308 14309 14310 14311 14312 14313 14314 14315 14316 14317 14318 14319 14320 14321 14322 | is false and no direct branch name is found then it sets `*zOut` to NULL. If doFallback is true then, on success, `*zOut` will always be set to some non-NULL value. On error `*zOut` is not modified. On error it may return FSL_RC_NOT_A_REPO, FSL_RC_OOM, or any number of db-side error codes. */ FSL_EXPORT int fsl_branch_of_rid(fsl_cx * const f, fsl_id_t rid, bool doFallback, char ** zOut ); /** Convenience typedef and obligatory forward declaration. */ typedef struct fsl_cidiff_state fsl_cidiff_state; /** Callback type for use with fsl_cidiff(). It must return 0 on |
︙ | ︙ | |||
14657 14658 14659 14660 14661 14662 14663 | /** UNTESTED. Returns true if f has an opened repository database which is opened in read-only mode, else returns false. */ | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 14703 14704 14705 14706 14707 14708 14709 14710 14711 14712 14713 14714 14715 14716 14717 14718 14719 14720 14721 14722 14723 14724 14725 14726 14727 14728 14729 14730 14731 14732 14733 14734 14735 14736 14737 14738 14739 14740 14741 14742 14743 14744 14745 14746 14747 | /** UNTESTED. Returns true if f has an opened repository database which is opened in read-only mode, else returns false. */ FSL_EXPORT bool fsl_repo_is_readonly(fsl_cx const * f); /** Searches for a matching blob records for a fossil ticket ID. (A ticket's ID is the value of its K-card.) The given ticket ID must be a full-length ticket ID (40 characters of lower-case hex) or an unambiguous prefix. If at least one match is found, it returns 0 and updates the 3rd argument: `*ridList` is assigned to an array of fsl_id_t (`blob.rid` values) terminated by an entry with the value 0. Ownership of the array is passed to the caller, who must eventually pass it to fsl_free(). The entries are ordered from oldest to newest (based on their `tagxref.mtime` value). On error it may return: - FSL_RC_OOM on allocation error. - FSL_RC_RANGE if the given ticket ID is longer than FSL_STRLEN_SHA1. - FSL_RC_AMBIGUOUS if the ticket ID prefix would match multiple distinct ticket IDs. This result is not possible if passed a full-length ticket ID. - FSL_RC_NOT_FOUND if no match is found. - FSL_RC_NOT_A_REPO if f has no repository opened. - Any number of db-related errors if something goes horribly wrong in the db layer. On error, the 3rd argument is not modified. */ FSL_EXPORT int fsl_tkt_id_to_rids(fsl_cx * const f, char const * tktId, fsl_id_t ** ridList); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_REPO_H_INCLUDED */ /* end of file ./include/fossil-scm/repo.h */ |
︙ | ︙ | |||
17145 17146 17147 17148 17149 17150 17151 | the checkout-local timestamp of the file. Returns non-0 on error. Some of the potential results include: - FSL_RC_NOT_A_CKOUT. - FSL_RC_NOT_FOUND if the filename cannot be resolved in the | | > > > > | | | 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 | the checkout-local timestamp of the file. Returns non-0 on error. Some of the potential results include: - FSL_RC_NOT_A_CKOUT. - FSL_RC_NOT_FOUND if the filename cannot be resolved in the requested version or cannot be stat()'d. This can happen if, e.g. fc's file has been renamed, but not yet checked in, in the current checkout. A _potential_ TODO is to check for a (`vfile.origname=fc->name`) record and use `vfile.pathname` for the stat() call. - 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 * const repoMtime, fsl_time_t * const localMtime); /** File change types for use with fsl_merge_state::fileChangeType. Terminology used in some of the descriptions: - (P) is the "pivot" - the common ancestor for the merge. |
︙ | ︙ | |||
18555 18556 18557 18558 18559 18560 18561 | Array sizes of 6 and 10 do not appreciably change the hit rate compared to 4, at least not for current (2021-11-18) uses. */ fsl_deck decks[4]; }; | > | > | > | | | > | 18635 18636 18637 18638 18639 18640 18641 18642 18643 18644 18645 18646 18647 18648 18649 18650 18651 18652 18653 18654 18655 18656 18657 18658 18659 18660 18661 18662 18663 18664 18665 18666 18667 18668 18669 18670 | Array sizes of 6 and 10 do not appreciably change the hit rate compared to 4, at least not for current (2021-11-18) uses. */ fsl_deck decks[4]; }; /** @internal Convenience typedef. */ typedef struct fsl__mcache fsl__mcache; /** @internal Initialized-with-defaults fsl__mcache structure, intended for const-copy initialization. */ #define fsl__mcache_empty_m { \ 0,0,0, \ {/*aAge*/0,0,0,0}, \ {fsl_deck_empty_m,fsl_deck_empty_m, \ fsl_deck_empty_m, fsl_deck_empty_m}\ } /**@internal Initialized-with-defaults fsl__mcache structure, intended for non-const copy initialization. */ extern const fsl__mcache fsl__mcache_empty; /* The fsl_cx class is documented in main public header. ALL of its members are to be considered private/internal. */ 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. The internal management of fsl_cx's db handles has changed a couple of times. As of 2022-01-01 the following applies: |
︙ | ︙ | |||
20195 20196 20197 20198 20199 20200 20201 | by only supporting them in the way fossil does for platforms which do not support symlinks. */ int fsl__ckout_symlink_create(fsl_cx * const f, char const *zTgtFile, char const * zLinkFile); | | | 20279 20280 20281 20282 20283 20284 20285 20286 20287 20288 20289 20290 20291 20292 20293 | by only supporting them in the way fossil does for platforms which do not support symlinks. */ int fsl__ckout_symlink_create(fsl_cx * const f, char const *zTgtFile, char const * zLinkFile); /** @internal Compute all file name changes that occur going from check-in iFrom to check-in iTo. Requires an opened repository. If revOK is true, the algorithm is free to move backwards in the chain. This is the opposite of the oneWayOnly parameter for fsl_vpath_shortest(). |
︙ | ︙ | |||
20223 20224 20225 20226 20227 20228 20229 | int fsl__find_filename_changes(fsl_cx * const f, fsl_id_t iFrom, fsl_id_t iTo, bool revOK, uint32_t *pnChng, fsl_id_t **aiChng); | | | | 20307 20308 20309 20310 20311 20312 20313 20314 20315 20316 20317 20318 20319 20320 20321 20322 20323 20324 | int fsl__find_filename_changes(fsl_cx * const f, fsl_id_t iFrom, fsl_id_t iTo, bool revOK, uint32_t *pnChng, fsl_id_t **aiChng); /** @internal Bitmask of file change types for use with fsl__is_locally_modified(). */ enum fsl__localmod_e { /** Sentinel value. */ FSL__LOCALMOD_NONE = 0, /** Permissions changed. */ FSL__LOCALMOD_PERM = 0x01, |
︙ | ︙ | |||
20396 20397 20398 20399 20400 20401 20402 | Returns 0 on success. On error it initiates (or propagates) a rollback for the current transaction. If called when a rollback is pending, it unsets the crosslink-is-running flag and returns the propagating result code. */ int fsl__crosslink_end(fsl_cx * const f, int resultCode); | < | 20480 20481 20482 20483 20484 20485 20486 20487 20488 20489 20490 20491 20492 20493 | Returns 0 on success. On error it initiates (or propagates) a rollback for the current transaction. If called when a rollback is pending, it unsets the crosslink-is-running flag and returns the propagating result code. */ int fsl__crosslink_end(fsl_cx * const f, int resultCode); /** @internal Searches the current repository database for a fingerprint and returns it as a string in *zOut. If rcvid<=0 then the fingerprint matches the last entry in the [rcvfrom] table, where "last" means highest-numbered rcvid (as |
︙ | ︙ | |||
20426 20427 20428 20429 20430 20431 20432 | fingerprint is not found. Version 0 was very short-lived and is not expected to be in many repositories which are accessed via this library. Practice has, however, revealed some. @see fsl_ckout_fingerprint_check() */ int fsl__repo_fingerprint_search(fsl_cx * const f, fsl_id_t rcvid, | | | | 20509 20510 20511 20512 20513 20514 20515 20516 20517 20518 20519 20520 20521 20522 20523 20524 20525 | fingerprint is not found. Version 0 was very short-lived and is not expected to be in many repositories which are accessed via this library. Practice has, however, revealed some. @see fsl_ckout_fingerprint_check() */ int fsl__repo_fingerprint_search(fsl_cx * const f, fsl_id_t rcvid, char ** zOut); /** @internal State for running a raw diff. @see fsl__diff_all() */ struct fsl__diff_cx { /** aEdit describes the raw diff. Each triple of integers in aEdit[] |
︙ | ︙ | |||
20461 20462 20463 20464 20465 20466 20467 | /** File content for the right side of the diff. */ fsl_dline *aTo; /** Number of lines in aTo[]. */ int nTo /*TODO unsigned*/; /** Predicate for comparing LHS/RHS lines for equivalence. */ int (*cmpLine)(const fsl_dline * const, const fsl_dline *const); }; | | > | > | | | 20544 20545 20546 20547 20548 20549 20550 20551 20552 20553 20554 20555 20556 20557 20558 20559 20560 20561 20562 20563 20564 20565 20566 20567 20568 20569 20570 | /** File content for the right side of the diff. */ fsl_dline *aTo; /** Number of lines in aTo[]. */ int nTo /*TODO unsigned*/; /** Predicate for comparing LHS/RHS lines for equivalence. */ int (*cmpLine)(const fsl_dline * const, const fsl_dline *const); }; /** @internal Convenience typeef. */ typedef struct fsl__diff_cx fsl__diff_cx; /** @internal Initialized-with-defaults fsl__diff_cx structure, intended for const-copy initialization. */ #define fsl__diff_cx_empty_m {\ NULL,0,0,NULL,0,NULL,0,fsl_dline_cmp \ } /** @internal Initialized-with-defaults fsl__diff_cx structure, intended for non-const copy initialization. */ extern const fsl__diff_cx fsl__diff_cx_empty; /** @internal Compute the differences between two files already loaded into the fsl__diff_cx structure. |
︙ | ︙ | |||
20494 20495 20496 20497 20498 20499 20500 20501 | Any common text at the beginning and end of the two files is removed before starting the divide-and-conquer algorithm. Returns 0 on succes, FSL_RC_OOM on an allocation error. */ int fsl__diff_all(fsl__diff_cx * const p); /** @internal */ | | | < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 20579 20580 20581 20582 20583 20584 20585 20586 20587 20588 20589 20590 20591 20592 20593 20594 20595 20596 20597 20598 20599 20600 20601 20602 20603 20604 20605 20606 20607 20608 20609 20610 20611 20612 20613 20614 20615 20616 20617 20618 20619 20620 20621 20622 20623 20624 20625 20626 20627 20628 20629 20630 20631 20632 20633 20634 20635 20636 20637 20638 20639 20640 20641 20642 20643 20644 20645 20646 20647 20648 20649 20650 20651 20652 20653 20654 20655 20656 | Any common text at the beginning and end of the two files is removed before starting the divide-and-conquer algorithm. Returns 0 on succes, FSL_RC_OOM on an allocation error. */ int fsl__diff_all(fsl__diff_cx * const p); /** @internal */ void fsl__diff_cx_clean(fsl__diff_cx * const cx); /** @internal Undocumented. For internal debugging only. The 2nd argument is intended to be __FILE__ and the 3rd is intended to be __LINE__. */ void fsl__dump_triples(fsl__diff_cx const * const p, char const * zFile, int ln ); /** @internal Removes from the BLOB table all artifacts that are in the SHUN table. Returns 0 on success. Requires (asserts) that a repo is opened. Note that this is not a simple DELETE operation, as it requires ensuring that all removed blobs have been undeltified first so that no stale delta records are left behind. */ int fsl__shunned_remove(fsl_cx * const f); /** @internal This function is, as of this writing, only exposed via a header file for the sake of fnc, which still relies on it after we moved the "v1" diff code out of this library and into fnc. Attempt to shift insertion or deletion blocks so that they begin and end on lines that are pure whitespace. In other words, try to transform this: ``` int func1(int x){ return x*10; +} + +int func2(int x){ + return x*20; } int func3(int x){ return x/5; } ``` Into one of these: ``` int func1(int x){ int func1(int x){ return x*10; return x*10; } } + +int func2(int x){ +int func2(int x){ + return x*20; + return x*20; +} +} + int func3(int x){ int func3(int x){ return x/5; return x/5; } } ``` */ void fsl__diff_optimize(fsl__diff_cx * const p); /** @internal This is a fossil-specific internal detail not needed by the more generic parts of the fsl_db API. It loops through all "cached" prepared statements for which stmt->role has been assigned a value which bitmasks as true against the given role and finalizes |
︙ | ︙ | |||
22768 22769 22770 22771 22772 22773 22774 | important to use the correct DB name when creating such constructs. Note that the role of FSL_DBROLE_TEMP is invalid here. */ char const * fsl_cx_db_name_for_role(fsl_cx const * const f, fsl_dbrole_e r, fsl_size_t * len); | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 22894 22895 22896 22897 22898 22899 22900 22901 22902 22903 22904 22905 22906 22907 22908 22909 22910 22911 22912 22913 22914 22915 22916 22917 | important to use the correct DB name when creating such constructs. Note that the role of FSL_DBROLE_TEMP is invalid here. */ char const * fsl_cx_db_name_for_role(fsl_cx const * const f, fsl_dbrole_e r, fsl_size_t * len); /** Equivalent to `fcli_setup_v2(argc,argv,fcli.cliFlags,fcli.appHelp)`. @see fcli_pre_setup() @see fcli_setup_v2() @see fcli_end_of_main() @deprecated Its signature will change to fcli_setup_v2()'s at some point. */ int fcli_setup(int argc, char const * const * argv ); /** @deprecated fsl_close_scm_dbs() As of 2021-01-01, this functions identically to fsl_close_scm_dbs(). Prior to that... If fsl_repo_open_xxx() has been used to open a respository db, |
︙ | ︙ |
Deleted signify/fnc-07-release.pub.
|
| < < |
Added signify/fnc-09-release.pub.
> > | 1 2 | untrusted comment: fnc 0.9 public key RWR/Unt0CGtNwq8vE59836HrU7Jt8ETbuxx2mnap+FakP2TCZLEcaS4j |
Added src/diff.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 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 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 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 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 | /* * Copyright (c) 2022 Mark Jamsek <mark@jamsek.com> * Copyright (c) 2013-2021 Stephan Beal, the Libfossil authors and contributors. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * This file contains the original libfossil diff implementation, published by * Stephan Beal, which has been superceded by libfossil diff 2.0. The code has * been imported with the author's blessing to hack on for fnc's opinionated * diff features. */ #include <assert.h> #include <memory.h> #include <stdlib.h> #include <string.h> /* memmove() */ #include "diff.h" #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) #define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) #define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) #define starts_with(_str, _pfx) (!fsl_strncmp(_str, _pfx, sizeof(_pfx) - 1)) #define LENGTH(_ln) ((_ln)->n) /* Length of a fsl_dline */ #define UTF_CONT(_ch) (((_ch) & 0xc0) == 0x80) #define FLAG_SET(f, b) ((f) |= (b)) #define FLAG_CHK(f, b) ((f) & (b)) #define FLAG_TOG(f, b) ((f) ^= (b)) #define FLAG_CLR(f, b) ((f) &= ~(b)) /* * Column indices for struct sbsline.cols[] */ #define SBS_LLINE 0 /* Left line number */ #define SBS_LTEXT 1 /* Left text */ #define SBS_MID 2 /* Middle separator column */ #define SBS_RLINE 3 /* Right line number */ #define SBS_RTEXT 4 /* Right text */ /* * ANSI escape codes: https://en.wikipedia.org/wiki/ANSI_escape_code */ #define ANSI_COLOR_BLACK(BOLD) ((BOLD) ? "\x1b[30m" : "\x1b[30m") #define ANSI_COLOR_RED(BOLD) ((BOLD) ? "\x1b[31;1m" : "\x1b[31m") #define ANSI_COLOR_GREEN(BOLD) ((BOLD) ? "\x1b[32;1m" : "\x1b[32m") #define ANSI_COLOR_YELLOW(BOLD) ((BOLD) ? "\x1b[33;1m" : "\x1b[33m") #define ANSI_COLOR_BLUE(BOLD) ((BOLD) ? "\x1b[34;1m" : "\x1b[34m") #define ANSI_COLOR_MAGENTA(BOLD) ((BOLD) ? "\x1b[35;1m" : "\x1b[35m") #define ANSI_COLOR_CYAN(BOLD) ((BOLD) ? "\x1b[36;1m" : "\x1b[36m") #define ANSI_COLOR_WHITE(BOLD) ((BOLD) ? "\x1b[37;1m" : "\x1b[37m") #define ANSI_DIFF_ADD(BOLD) ANSI_COLOR_GREEN(BOLD) #define ANSI_DIFF_RM(BOLD) ANSI_COLOR_RED(BOLD) #define ANSI_DIFF_MOD(BOLD) ANSI_COLOR_BLUE(BOLD) #define ANSI_BG_BLACK(BOLD) ((BOLD) ? "\x1b[40;1m" : "\x1b[40m") #define ANSI_BG_RED(BOLD) ((BOLD) ? "\x1b[41;1m" : "\x1b[41m") #define ANSI_BG_GREEN(BOLD) ((BOLD) ? "\x1b[42;1m" : "\x1b[42m") #define ANSI_BG_YELLOW(BOLD) ((BOLD) ? "\x1b[43;1m" : "\x1b[43m") #define ANSI_BG_BLUE(BOLD) ((BOLD) ? "\x1b[44;1m" : "\x1b[44m") #define ANSI_BG_MAGENTA(BOLD) ((BOLD) ? "\x1b[45;1m" : "\x1b[45m") #define ANSI_BG_CYAN(BOLD) ((BOLD) ? "\x1b[46;1m" : "\x1b[46m") #define ANSI_BG_WHITE(BOLD) ((BOLD) ? "\x1b[47;1m" : "\x1b[47m") #define ANSI_RESET_COLOR "\x1b[39;49m" #define ANSI_RESET_ALL "\x1b[0m" #define ANSI_RESET ANSI_RESET_ALL /* #define ANSI_BOLD ";1m" */ int fnc_diff_text_raw(fsl_buffer const *blob1, fsl_buffer const *blob2, int flags, int **out) { return fnc_diff_blobs(blob1, blob2, NULL, NULL, NULL, NULL, 0, 0, flags, out); } int fnc_diff_text_to_buffer(fsl_buffer const *blob1, fsl_buffer const *blob2, fsl_buffer *out, enum line_type **lines, uint32_t *nlines, short context, short sbswidth, int flags) { return (blob1 && blob2 && out) ? fnc_diff_blobs(blob1, blob2, fsl_output_f_buffer, out, lines, nlines, context, sbswidth, flags, NULL) : FSL_RC_MISUSE; } int fnc_diff_text(fsl_buffer const *blob1, fsl_buffer const *blob2, fsl_output_f out, void *state, short context, short sbswidth, int flags) { return fnc_diff_blobs(blob1, blob2, out, state, NULL, NULL, context, sbswidth, flags, NULL); } /* * * Diff two arbitrary blobs and either stream output to the third argument * or return an array of copy/delete/insert triples via the final argument. * The third XOR final argument must be set. * * If the third argument is not NULL: * state opaque state value passed to the third when emitting output * context number of context lines (negative values fallback to default) * sbswidth sbs diff width (0 = unidiff; negative values fallback to default) * * If the final argument is not NULL, it is assigned a pointer to the result * array of copy/delete/insert triples. Ownership is transfered to the caller, * who must eventually dispose of it with fsl_free(). * * Return 0 on success, any number of other codes on error. */ int fnc_diff_blobs(fsl_buffer const *blob1, fsl_buffer const *blob2, fsl_output_f out, void *state, enum line_type **lines, uint32_t *nlines, /* void *regex, */ uint16_t context, short sbswidth, int flags, int **rawdata) { fsl__diff_cx c = fsl__diff_cx_empty; int rc; if (!blob1 || !blob2 || (out && rawdata) || (!out && !rawdata)) return FSL_RC_MISUSE; if (context < 0) context = 5; else if (context & ~FSL__LINE_LENGTH_MASK) context = FSL__LINE_LENGTH_MASK; if (FLAG_CHK(flags, FNC_DIFF_INVERT)) { fsl_buffer const *tmp = blob1; blob1 = blob2; blob2 = tmp; } if ((flags & FNC_DIFF_IGNORE_ALLWS) == FNC_DIFF_IGNORE_ALLWS) c.cmpLine = fsl_dline_cmp_ignore_ws; else c.cmpLine = fsl_dline_cmp; /* Prepare input files. */ rc = fsl_break_into_dlines(fsl_buffer_cstr(blob1), (fsl_int_t)fsl_buffer_size(blob1), (uint32_t*)&c.nFrom, &c.aFrom, flags); if (rc) goto end; rc = fsl_break_into_dlines(fsl_buffer_cstr(blob2), (fsl_int_t)fsl_buffer_size(blob2), (uint32_t*)&c.nTo, &c.aTo, flags); if (rc) goto end; /* Compute the difference */ rc = fsl__diff_all(&c); /* fsl__dump_triples(&c, __FILE__, __LINE__); */ /* DEBUG */ if (rc) goto end; if (FLAG_CHK(flags, FNC_DIFF_NOTTOOBIG)) { int i, m, n; int *a = c.aEdit; int mx = c.nEdit; for (i = m = n = 0; i < mx; i += 3) { m += a[i]; n += a[i + 1] + a[i + 2]; } if (!n || n > 10000) { rc = FSL_RC_RANGE; /* diff_errmsg(out, DIFF_TOO_MANY_CHANGES, flags); */ goto end; } } /* fsl__dump_triples(&c, __FILE__, __LINE__); */ /* DEBUG */ if (!FLAG_CHK(flags, FNC_DIFF_NOOPT)) fsl__diff_optimize(&c); /* fsl__dump_triples(&c, __FILE__, __LINE__); */ /* DEBUG */ /* * For SBS diffs, compute total number of lines and set file column * width to the longest line in the diff and encode result in flags. * XXX I'm ambivalent about converting unified diffs to the line_type * interface as it's extra allocations for no immediate gain but creates * opportunities to do cool things; leave it till we want to do them? */ if ((!sbswidth && FLAG_CHK(flags, FNC_DIFF_SIDEBYSIDE)) || sbswidth < 0) { flags |= ((int)(sbswidth & 0xFFFF)) << 8; FLAG_SET(flags, FNC_DIFF_SIDEBYSIDE); rc = alloc_lines_and_width(&c, &flags, context, *nlines, lines); if (rc) goto end; } if (out) { /* * Compute a context or side-by-side diff. * XXX Missing regex support. */ struct diff_out_state dos = diff_out_state_empty; if (FLAG_CHK(flags, FNC_DIFF_PROTOTYPE)) { memset(&dos.proto, 0, sizeof(dos.proto)); dos.proto.file = blob1; } dos.out = out; dos.state = state; dos.lines = *lines; dos.idx = nlines; dos.ansi = !!(FLAG_CHK(flags, FNC_DIFF_ANSI_COLOR)); if (FLAG_CHK(flags, FNC_DIFF_SIDEBYSIDE)) rc = sbsdiff(&c, &dos, NULL /*regex*/, context, flags); else rc = unidiff(&c, &dos, NULL /*regex*/, context, flags); } else if (rawdata) { /* Return array of COPY/DELETE/INSERT triples. */ *rawdata = c.aEdit; c.aEdit = NULL; } end: fsl_free(c.aFrom); fsl_free(c.aTo); fsl_free(c.aEdit); return rc; } /* * Iterate copy elements in the c->aEdit 3-tuple to compute start and end lines, * including surrounding ctxt, of each chunk in the diff. Add result to nlines, * allocate the total and assign to *lines, which must be disposed of by the * caller. For SBS diffs, find the longest line and encode the length in *flags. */ int alloc_lines_and_width(fsl__diff_cx *c, int *flags, uint16_t ctxt, uint32_t nlines, enum line_type **lines) { int i, j, k; int endl, endr, start; /* Start and end of l+r chunks */ uint32_t n = 0; /* Number of lines in the diff */ unsigned short sz, sbswidth; /* Size of each line and max width */ j = endl = endr = start = 0; k = c->nEdit; sbswidth = (*flags & FNC_DIFF_SIDEBYSIDE) ? sbsdiff_width(*flags) : 0; /* Find minimal copy/delete/insert triples. */ while (k > 2 && c->aEdit[k - 1] == 0 && c->aEdit[k - 2] == 0) k -= 3; /* * c[0] = first line in the diff and we accumulate each c[n += 3] to * find the start of the next change (after adding any insertions); * that is, each copy element by itself does not correspond to a line. */ do { int e, e2, s; /* * Compute number of lines in both the left and right chunk. * Both chunks will have the _same_ start line, but may have * distinct end lines. */ start += c->aEdit[j]; /* Chunk start line */ endl = endr = start; endl += MAX(c->aEdit[j + 1], 0); /* End of left side chunk */ endr += MAX(c->aEdit[j + 2], 0); /* End of right side chunk */ s = MAX(start - ctxt, 0); /* Include leading ctxt */ e = MIN(endl + ctxt, c->nFrom); /* L include trailing ctxt */ e2 = MIN(endr + ctxt, c->nTo); /* R include trailing ctxt */ n += MAX(MAX(e, e2) - s + ctxt + 2, 0); /* Max lines in chunk */ /* * Now find the longest line. We'll make both the left and right * columns the width of the longest line from either side. * XXX Consider keeping max width of both sides to make each * column only as wide as its longest line; we can set a min * default in diffs with added/removed files. */ for (i = s; sbswidth && (i < e || i < e2); ++i) { if (i < e) { /* Left side line length */ sz = c->aFrom[i].n; if (sz > sbswidth) sbswidth = etcount(c->aFrom[i].z, sz); } if (i < e2) { /* Right side line length */ sz = c->aTo[i].n; if (sz > sbswidth) sbswidth = etcount(c->aTo[i].z, sz); } } if (i >= c->nFrom && i >= c->nTo) break; /* Account for insertions before adding lines to next chunk. */ start += c->aEdit[j + 2]; } while ((j += 3) < k - 3); *flags |= ((int)(sbswidth & 0xFFF)) << 16; /* Encode max width */ *lines = fsl_realloc(*lines, (nlines + n) * sizeof(enum line_type *)); memset((*lines + nlines), 0, sizeof(enum line_type *) * n); return *lines ? FSL_RC_OK : FSL_RC_ERROR; } /* * Convert str byte size n to actual column width by expanding tabs and * accounting for unicode continuation bytes. Return the result. */ unsigned short etcount(const char *str, unsigned short n) { unsigned short c = 0; while (str && str[c] != '\n') { /* Expand tabs */ if (str[c] == '\t') n += 8 - (c % 8); if (UTF_CONT(str[c])) --n; ++c; } return n; } /* * Convert mask of public fnc_diff_flag (32-bit) values to the Fossil-internal * 64-bit bitmask used by the DIFF_xxx macros because fossil(1) uses the macro * approach and a low-level encoding of data in the bitmask (e.g., the context * lines count). The public API hides the lower-level flags and allows the * internal API to take care of the encoding. */ uint64_t fnc_diff_flags_convert(int mask) { uint64_t rc = 0U; #define DO(f) if ((mask & f) == f) rc |= (((uint64_t)f) << 24) DO(FNC_DIFF_IGNORE_EOLWS); DO(FNC_DIFF_IGNORE_ALLWS); DO(FSL_DIFF2_LINE_NUMBERS); DO(FNC_DIFF_SIDEBYSIDE); DO(FNC_DIFF_NOTTOOBIG); DO(FNC_DIFF_STRIP_EOLCR); DO(FNC_DIFF_VERBOSE); DO(FNC_DIFF_BRIEF); DO(FNC_DIFF_HTML); DO(FNC_DIFF_NOOPT); DO(FNC_DIFF_INVERT); #undef DO return rc; } static inline int min(int a, int b) { return a < b ? a : b; } /* * Return the number between 0 and 100 that is smaller the closer lline and * rline match. Return 0 for a perfect match. Return 100 if lline and rline * are completely different. The current algorithm is as follows: * * 1. Remove leading and trailing whitespace. * 2. Truncate both strings to at most 250 characters * 3. Find the length of the longest common subsequence * 4. Longer common subsequences yield lower scores. */ int match_dline(fsl_dline *lline, fsl_dline *rline) { const char *l, *r; /* Left and right strings */ int nleft, nright; /* Bytes in l and r */ int avg; /* Average length of l and r */ int i, j, k; /* Loop counters */ int best = 0; /* Current longest match found */ int score; /* Final score (0-100) */ unsigned char c; /* Character being examined */ unsigned char idx1[256]; /* idx1[c]: r[] idx of first char c */ unsigned char idx2[256]; /* idx2[i]: r[] idx of next r[i] char */ l = lline->z; r = rline->z; nleft = lline->n; nright = rline->n; /* Consume leading and trailing whitespace of l string. */ while (nleft > 0 && fsl_isspace(l[0])) { nleft--; l++; } while (nleft > 0 && fsl_isspace(l[nleft - 1])) nleft--; /* Consume leading and trailing whitespace of r string. */ while (nright > 0 && fsl_isspace(r[0])) { nright--; r++; } while (nright > 0 && fsl_isspace(r[nright-1])) nright--; /* If needed, truncate strings to 250 chars, and find average length. */ nleft = min(nleft, 250); nright = min(nright, 250); avg = (nleft + nright) / 2; if (avg == 0) return 0; /* If equal, return max score. */ if (nleft == nright && !memcmp(l, r, nleft)) return 0; memset(idx1, 0, sizeof(idx1)); /* Make both l[] and r[] 1-indexed */ l--; r--; /* Populate character index. */ for (i = nright; i > 0; i--) { c = (unsigned char)r[i]; idx2[i] = idx1[c]; idx1[c] = i; } /* Find longest common subsequence. */ best = 0; for (i = 1; i <= nleft - best; i++) { c = (unsigned char)l[i]; for (j = idx1[c]; j > 0 && j < nright - best; j = idx2[j]) { int limit = min(nleft - i, nright - j); for (k = 1; k <= limit && l[k + i] == r[k + j]; k++){} if (k > best) best = k; } } score = (best > avg) ? 0 : (avg - best) * 100 / avg; #if 0 fprintf(stderr, "A: [%.*s]\nright: [%.*s]\nbest=%d avg=%d score=%d\n", nleft, l+1, nright, r+1, best, avg, score); #endif return score; /* Return the result */ } /* * The two text segments left and right are known to be different on both ends, * but they might have a common segment in the middle. If they do not have a * common segment, return false. If they do have a large common segment, * identify bounds and return true. * left String on the left * int nleft Bytes in left * right String on the right * int nright Bytes in right * lcs Identify bounds of LCS here * * Bounds are set as follows: * lcs[0] = start of the common segment in left * lcs[1] = end of the common segment in left * lcs[2] = start of the common segment in right * lcs[3] = end of the common segment in right * * n.b. This computation is for display purposes only and does not have to be * optimal or exact. */ bool find_lcs(const char *left, int nleft, const char *right, int nright, int *lcs) { const unsigned char *l, *r; /* Left and right strings */ unsigned int probe; /* Probe to compare target */ unsigned int t[3]; /* 4-byte alignment targets */ int ntargets; /* Number of target points */ int t_idx[3]; /* Index of each target */ int l_start, l_end, r_start, r_end; /* Range of common segment */ int i, j; bool rc = false; if (nleft < 6 || nright < 6) return rc; l = (const unsigned char*)left; r = (const unsigned char*)right; memset(lcs, 0, sizeof(int) * 4); t_idx[0] = i = nright / 2 - 2; t[0] = (r[i] << 24) | (r[i + 1] << 16) | (r[i + 2] << 8) | r[i + 3]; probe = 0; if (nright < 16) ntargets = 1; else { t_idx[1] = i = nright / 4 - 2; t[1] = (r[i] << 24) | (r[i + 1] << 16) | (r[i + 2] << 8) | r[i + 3]; t_idx[2] = i = (nright * 3) / 4 - 2; t[2] = (r[i] << 24) | (r[i + 1] << 16) | (r[i + 2] << 8) | r[i + 3]; ntargets = 3; } probe = (l[0] << 16) | (l[1] << 8) | l[2]; for (i = 3; i < nleft; i++) { probe = (probe << 8) | l[i]; for (j = 0; j < ntargets; j++) { if (probe == t[j]) { l_start = i - 3; l_end = i + 1; r_start = t_idx[j]; r_end = t_idx[j] + 4; while (l_end < nleft && r_end < nright && l[l_end] == r[r_end]) { l_end++; r_end++; } while (l_start > 0 && r_start > 0 && l[l_start - 1] == r[r_start - 1]) { l_start--; r_start--; } if (l_end - l_start > lcs[1] - lcs[0]) { lcs[0] = l_start; lcs[1] = l_end; lcs[2] = r_start; lcs[3] = r_end; rc = true; } } } } return rc; } /* * Send src to o->out(). If n is negative, use strlen() to determine length. */ int diff_out(struct diff_out_state *const o, void const *src, fsl_int_t n) { return o->rc = n ? o->out(o->state, src, n < 0 ? strlen((char const *)src) : (size_t)n) : 0; } /* * Starting from seek lines, copy n lines from src to dst. The seek starts * from offset bytes into src->mem. This routine does _not_ modify src. */ int buffer_copy_lines_from(fsl_buffer *const dst, const fsl_buffer *const src, fsl_size_t *offset, fsl_size_t seek, fsl_size_t n) { const char *z = (const char *)src->mem; fsl_size_t idx = *offset, ln = 0, start = 0; int rc = FSL_RC_OK; if (!n) return rc; while (idx < src->used) { if (z[idx] == '\n') { if (++ln == seek) start = idx + 1; /* skip leading '\n' */ if (ln == seek + n) { ++idx; break; } } ++idx; } if (dst) /* trim trailing when copying '\n' */ rc = fsl_buffer_append(dst, &src->mem[start], idx - start - 1); *offset = start; return rc; } /* * Scan the diffed file dst->proto->file from line pos preceding the start of * the current chunk for the enclosing function in which the change resides. * Return first match. */ char * match_chunk_function(struct diff_out_state *const dst, uint32_t pos) { fsl_buffer buf = fsl_buffer_empty; const char *line; char *spec = NULL; fsl_size_t offset; uint32_t last = dst->proto.lastline; dst->proto.lastline = pos; offset = dst->proto.offset; /* Begin seek from last match */ while (pos > 1 && pos > last) { buffer_copy_lines_from(&buf, dst->proto.file, &offset, pos - dst->proto.lastmatch, 1); line = fsl_buffer_cstr(&buf); /* * GNU C and MSVC allow '$' in identifier names. * https://gcc.gnu.org/onlinedocs/gcc/Dollar-Signs.html * https://docs.microsoft.com/en-us/cpp/cpp/identifiers-cpp */ if (line) { if (fsl_isalpha(line[0]) || line[0] == '_' || line[0] == '$'){ if (starts_with(line, "private:")) { if (!spec) spec = " (private)"; } else if (starts_with(line, "protected:")) { if (!spec) spec = " (protected)"; } else if (starts_with(line, "public:")) { if (!spec) spec = " (public)"; } else { /* * Don't exceed 80 cols: chunk header * consumes ~25, so cap signature at 55. */ char *sig = fsl_mprintf("%s%s", line, spec ? spec : ""); fsl_free(dst->proto.signature); dst->proto.signature = fsl_mprintf("%.55s", sig); /* * It's expensive to seek from the start * of the file for each chunk when * diffing large files, so save offset * and line index of this match. */ dst->proto.lastmatch = pos; dst->proto.offset = offset; fsl_free(sig); fsl_buffer_clear(&buf); return dst->proto.signature; } } } /* No match, revert to last offset. */ offset = dst->proto.offset; fsl_buffer_clear(&buf); --pos; } return dst->proto.lastmatch > 0 ? dst->proto.signature : NULL; } /* * Render the diff triples array in cx->aEdit as a side-by-side diff in out. * cx Raw diff data * out Side-by-side diff representation * regex Show changes matching this regex * context Number of context lines * flags Flags controlling the diff */ int sbsdiff(fsl__diff_cx *cx, struct diff_out_state *dst, void *regex, uint16_t context, uint64_t flags) { fsl_dline *l, *r; /* Left and right side of diff */ fsl_buffer sbscols[5] = { fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m }; struct sbsline s; /* Output line buffer */ static int chunks = 0; /* Number of chunks so far processed */ int li, ri; /* Index of next line in l[] and r[] */ int *c; /* copy/delete/insert triples */ int ci; /* Index into c[] */ int nc; /* number of c[] triples to process */ int max_ci; /* Maximum value for ci */ int nleft, nright; /* Number of l and r lines to output */ int ntotal; /* Total number of lines to output */ int skip; /* Number of lines to skip */ int i, j, rc = FSL_RC_OK; uint32_t *idx = dst->idx; enum line_type *lines = dst->lines; bool showsep = false; li = ri = 0; memset(&s, 0, sizeof(s)); s.output = dst; s.width = sbsdiff_width(flags); s.regex = regex; s.idx = -1; s.idx2 = 0; s.end = -1; max_ci = cx->nEdit; l = cx->aFrom; r = cx->aTo; c = cx->aEdit; s.esc = FLAG_CHK(flags, FNC_DIFF_HTML); if (s.esc) for (i = SBS_LLINE; i <= SBS_RTEXT; i++) s.cols[i] = &sbscols[i]; else for (i = SBS_LLINE; i <= SBS_RTEXT; i++) s.cols[i] = (fsl_buffer *)dst->state; while (max_ci > 2 && c[max_ci-1] == 0 && c[max_ci - 2] == 0) max_ci -= 3; for (ci = 0; ci < max_ci; ci += 3 * nc) { /* _huge_ loop */ /* Calculate how many triples to show in a single block */ for (nc = 1; c[ci + nc * 3] > 0 && c[ci + nc * 3] < context * 2; nc++) {} /* printf("ci=%d nc=%d\n", ci, nc); */ #if 0 /* * XXX Missing: re/predicate bits. * If there is a regex, skip this block (i.e., generate no diff * output) if the regex matches or does not match both insert * and delete. Only display the block if one side matches but * the other side does not. */ if (regex) { bool hidechange = true; int xa = li, xb = ri; for (i = 0; hidechange && i < nc; i++) { int c1, c2; xa += c[ci + i * 3]; xb += c[ci + i * 3]; c1 = re_dline_match(regex, &l[xa], c[ci + i * 3 + 1]); c2 = re_dline_match(regex, &r[xb], c[ci + i * 3 + 2]); hidechange = c1 == c2; xa += c[ci + i * 3 + 1]; xb += c[ci + i * 3 + 2]; } if (hidechange) { li = xa; ri = xb; continue; } } #endif /* * For the current block comprising nc triples, figure out * how many lines to skip. */ /* if (c[ci] > context) */ /* skip = c[ci] - context; */ /* else */ /* skip = 0; */ if (c[ci] > context) { nleft = nright = context; skip = c[ci] - context; } else { nleft = nright = c[ci]; skip = 0; } for (i = 0; i < nc; ++i) { nleft += c[ci + i * 3 + 1]; nright += c[ci + i * 3 + 2]; } if (c[ci + nc * 3] > context) { nleft += context; nright += context; } else { nleft += c[ci + nc * 3]; nright += c[ci + nc * 3]; } for (i = 1; i < nc; ++i) { nleft += c[ci + i * 3]; nright += c[ci + i * 3]; } /* Draw separator between blocks except the first. */ if (showsep) { lines[(*idx)++] = LINE_DIFF_SEPARATOR; if (s.esc) { char ln[10]; fsl_snprintf(ln, sizeof(ln), "%d", li + skip + 1); rc = sbsdiff_separator(&s, fsl_strlen(ln), SBS_LLINE); if (rc) goto end; rc = sbsdiff_separator(&s, s.width, SBS_LTEXT); if (!rc) rc = sbsdiff_separator(&s, 0, SBS_MID); if (rc) goto end; fsl_snprintf(ln, sizeof(ln), "%d", ri + skip + 1); rc = sbsdiff_separator(&s, fsl_strlen(ln), SBS_RLINE); if (rc) goto end; rc = sbsdiff_separator(&s, s.width, SBS_RTEXT); } else rc = diff_outf(s.output, "%.*c\n", s.width * 2 + 16, '.'); if (rc) goto end; } showsep = true; ++chunks; if (s.esc) rc = fsl_buffer_appendf(s.cols[SBS_LLINE], "<span class=\"fsl-diff-chunk-%d\"></span>", chunks); /* Show the initial common area */ li += skip; ri += skip; ntotal = c[ci] - skip; for (j = 0; !rc && j < ntotal; j++) { rc = sbsdiff_lineno(&s, li + j, SBS_LLINE); if (rc) break; s.idx = s.end = -1; rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT); if (!rc) rc = sbsdiff_marker(&s, " ", ""); if (!rc) rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE); if (!rc) rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT); lines[(*idx)++] = LINE_DIFF_CONTEXT; } if (rc) goto end; li += ntotal; ri += ntotal; /* Show the differences */ for (i = 0; i < nc; i++) { unsigned char *alignment; nleft = c[ci + i * 3 + 1]; /* Lines on left */ nright = c[ci + i * 3 + 2]; /* Lines on right */ /* * If the gap between the current change and the next * change within the same block is not too great, then * render them as if they are a single change. */ while (i < nc - 1 && sbsdiff_close_gap(&c[ci + i * 3])) { i++; ntotal = c[ci + i * 3]; nleft += c[ci + i * 3 + 1] + ntotal; nright += c[ci + i * 3 + 2] + ntotal; } alignment = sbsdiff_align(&l[li], nleft, &r[ri], nright); if (!alignment) { rc = FSL_RC_OOM; goto end; } for (j = 0; !rc && nleft + nright > 0; j++, (*idx)++) { char tag[30] = "<span class=\"fsl-diff-"; switch (alignment[j]) { case 1: /* Delete one line from the left */ rc = sbsdiff_lineno(&s, li, SBS_LLINE); if (rc) goto end_align; s.idx = 0; fsl_strlcat(tag, "rm\">", sizeof(tag)); s.tag = tag; s.end = LENGTH(&l[li]); lines[*idx] = LINE_DIFF_MINUS; rc = sbsdiff_txt(&s, &l[li], SBS_LTEXT); if (rc) goto end_align; rc = sbsdiff_marker(&s, " <", "<"); if (rc) goto end_align; rc = sbsdiff_newline(&s); if (rc) goto end_align; assert(nleft > 0); nleft--; li++; break; case 2: /* Insert one line on the right */ if (!s.esc) { rc = sbsdiff_space(&s, s.width + 7, SBS_LTEXT); if (rc) goto end_align; } rc = sbsdiff_marker(&s, " > ", ">"); if (rc) goto end_align; rc = sbsdiff_lineno(&s, ri, SBS_RLINE); if (rc) goto end_align; s.idx = 0; fsl_strlcat(tag, "add\">", sizeof(tag)); s.tag = tag; s.end = LENGTH(&r[ri]); lines[*idx] = LINE_DIFF_PLUS; rc = sbsdiff_txt(&s, &r[ri], SBS_RTEXT); if (rc) goto end_align; assert(nright > 0); nright--; ri++; break; case 3: /* Left line changed into the right */ rc = sbsdiff_write_change(&s, &l[li], li, &r[ri], ri); if (rc) goto end_align; assert(nleft > 0 && nright > 0); nleft--; nright--; li++; ri++; break; default: { /* Delete left and insert right */ rc = sbsdiff_lineno(&s, li, SBS_LLINE); if (rc) goto end_align; s.idx = 0; fsl_strlcat(tag, "rm\">", sizeof(tag)); s.tag = tag; s.end = LENGTH(&l[li]); rc = sbsdiff_txt(&s, &l[li], SBS_LTEXT); if (rc) goto end_align; lines[*idx] = LINE_DIFF_EDIT; rc = sbsdiff_marker(&s, " | ", "|"); if (rc) goto end_align; rc = sbsdiff_lineno(&s, ri, SBS_RLINE); if (rc) goto end_align; s.idx = 0; s.tag = "<span class=\"fsl-diff-add\">"; s.end = LENGTH(&r[ri]); rc = sbsdiff_txt(&s, &r[ri], SBS_RTEXT); if (rc) goto end_align; nleft--; nright--; li++; ri++; break; } } } end_align: fsl_free(alignment); if (rc) goto end; if (i < nc - 1) { ntotal = c[ci + i * 3 + 3]; for (j = 0; !rc && j < ntotal; j++, (*idx)++) { rc = sbsdiff_lineno(&s, li + j, SBS_LLINE); s.idx = s.end = -1; if (rc) goto end; lines[*idx] = LINE_DIFF_CONTEXT; rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT); if (rc) goto end; rc = sbsdiff_marker(&s, " ", ""); if (rc) goto end; rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE); if (rc) goto end; lines[*idx] = LINE_DIFF_CONTEXT; rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT); if (rc) goto end; } ri += ntotal; li += ntotal; } } /* Show the final common area */ assert(nc == i); ntotal = c[ci + nc * 3]; if (ntotal > context) ntotal = context; for (j = 0; !rc && j < ntotal; j++, (*idx)++) { rc = sbsdiff_lineno(&s, li + j, SBS_LLINE); s.idx = s.end = -1; lines[*idx] = LINE_DIFF_CONTEXT; if (!rc) rc = sbsdiff_txt(&s, &l[li + j], SBS_LTEXT); if (!rc) rc = sbsdiff_marker(&s, " ", ""); if (!rc) rc = sbsdiff_lineno(&s, ri + j, SBS_RLINE); lines[*idx] = LINE_DIFF_CONTEXT; if (!rc) rc = sbsdiff_txt(&s, &r[ri + j], SBS_RTEXT); if (rc) goto end; } } /* diff triplet loop */ assert(!rc); if (s.esc && (s.cols[SBS_LLINE]->used > 0)) { rc = diff_out(dst, "<table class=\"fsl-sbsdiff-cols\"><tr>\n", -1); for (i = SBS_LLINE; !rc && i <= SBS_RTEXT; i++) rc = sbsdiff_column(dst, s.cols[i], i); if (!rc) rc = diff_out(dst, "</tr></table>\n", -1); } end: for (i = 0; i < (int)nitems(sbscols); ++i) fsl_buffer_clear(&sbscols[i]); return rc; } /* * Render the diff triples array in cx->aEdit as a unified diff in out. * cx Raw diff data * out Side-by-side diff representation * regex Show changes matching this regex * context Number of context lines * flags Flags controlling the diff */ int unidiff(fsl__diff_cx *cx, struct diff_out_state *dst, void *regex, uint16_t context, uint64_t flags) { fsl_dline *l, *r; /* Left and right side of diff */ static int chunks = 0; /* Number of chunks so far processed */ int li, ri; /* Index of next line in l[] and r[] */ int *c; /* copy/delete/insert triples */ int ci; /* Index into c[] */ int nc; /* number of c[] triples to process */ int max_ci; /* Maximum value for ci */ int nleft, nright; /* Number of l and r lines to output */ int ntotal; /* Total number of lines to output */ int skip; /* Number of lines to skip */ int i, j, rc = FSL_RC_OK; bool html, proto, showln, showsep = false; proto = FLAG_CHK(flags, FNC_DIFF_PROTOTYPE); showln = FLAG_CHK(flags, FNC_DIFF_LINENO); html = FLAG_CHK(flags, FNC_DIFF_HTML); l = cx->aFrom; r = cx->aTo; c = cx->aEdit; max_ci = cx->nEdit; li = ri = 0; while (max_ci > 2 && c[max_ci - 1] == 0 && c[max_ci - 2] == 0) max_ci -= 3; for (ci = 0; ci < max_ci; ci += 3 * nc) { /* Figure out how many triples to show in a single block. */ for (nc = 1; c[ci + nc * 3] > 0 && c[ci + nc * 3] < context * 2; nc++) {} /* printf("ci=%d nc=%d\n", ci, nc); */ #if 0 /* * XXX Missing: re/predicate bits. * If there is a regex, skip this block (i.e., generate no diff * output) if the regex matches or does not match both insert * and delete. Only display the block if one side matches but * the other side does not. */ if (regex) { bool hidechange = true; int xa = li, xb = ri; for (i = 0; hidechange && i < nc; i++) { int c1, c2; xa += c[ci + i * 3]; xb += c[ci + i * 3]; c1 = re_dline_match(regex, &l[xa], c[ci + i * 3 + 1]); c2 = re_dline_match(regex, &r[xb], c[ci + i * 3 + 2]); hidechange = c1 == c2; xa += c[ci + i * 3 + 1]; xb += c[ci + i * 3 + 2]; } if( hidechange ) { li = xa; ri = xb; continue; } } #endif /* * For the current block comprising nc triples, figure out * how many lines of l and r are to be displayed. */ if (c[ci] > context) { nleft = nright = context; skip = c[ci] - context; } else { nleft = nright = c[ci]; skip = 0; } for (i = 0; i < nc; i++) { nleft += c[ci + i * 3 + 1]; nright += c[ci + i * 3 + 2]; } if (c[ci + nc * 3] > context) { nleft += context; nright += context; } else { nleft += c[ci + nc * 3]; nright += c[ci + nc * 3]; } for (i = 1; i < nc; i++) { nleft += c[ci + i * 3]; nright += c[ci + i * 3]; } /* * Show the header for this block, or if we are doing a modified * unified diff that contains line numbers, show the separator * from the previous block. */ ++chunks; if (showln) { if (!showsep) showsep = 1; /* Don't show a top divider */ else if (html) rc = diff_outf(dst, "<span class=\"fsl-diff-hr\">%.*c</span>\n", 80, '.'); else rc = diff_outf(dst, "%.95c\n", '.'); if (!rc && html) rc = diff_outf(dst, "<span class=\"fsl-diff-chunk-%d\"></span>", chunks); } else { char const *ansi1 = ""; char const *ansi2 = ""; char const *ansi3 = ""; if (html) rc = diff_outf(dst, "<span class=\"fsl-diff-lineno\">"); #if 0 /* Turns out this just confuses the output */ else if (dst->ansi) { ansi1 = ANSI_DIFF_RM(0); ansi2 = ANSI_DIFF_ADD(0); ansi3 = ANSI_RESET; } #endif /* * If the patch changes an empty file or results in * an empty file, the block header must use 0,0 as * position indicator and not 1,0. Otherwise, patch * would be confused and may reject the diff. */ if (!rc) rc = diff_outf(dst,"@@ %s-%d,%d %s+%d,%d%s @@", ansi1, nleft ? li+skip+1 : 0, nleft, ansi2, nright ? ri+skip+1 : 0, nright, ansi3); if (!rc) { if (html) rc = diff_outf(dst, "</span>"); if (proto && li + skip > 1) { char *f = match_chunk_function(dst, (li + skip) - 1); if (f != NULL) rc = diff_outf(dst, " %s", f); } if (!rc) rc = diff_out(dst, "\n", 1); } } if (rc) return rc; /* Show the initial common area */ li += skip; ri += skip; ntotal = c[ci] - skip; for (j = 0; !rc && j < ntotal; j++) { if (showln) rc = unidiff_lineno(dst, li + j + 1, ri + j + 1, html); if (!rc) rc = unidiff_txt(dst, ' ', &l[li + j], html, 0); } if (rc) return rc; li += ntotal; ri += ntotal; /* Show the differences */ for (i = 0; i < nc; i++) { ntotal = c[ci + i * 3 + 1]; for (j = 0; !rc && j < ntotal; j++) { if (showln) rc = unidiff_lineno(dst, li + j + 1, 0, html); if (!rc) rc = unidiff_txt(dst, '-', &l[li + j], html, regex); } if (rc) return rc; li += ntotal; ntotal = c[ci + i * 3 + 2]; for (j = 0; !rc && j < ntotal; j++) { if (showln) rc = unidiff_lineno(dst, 0, ri + j + 1, html); if (!rc) rc = unidiff_txt(dst, '+', &r[ri + j], html, regex); } if (rc) return rc; ri += ntotal; if (i < nc - 1) { ntotal = c[ci + i * 3 + 3]; for (j = 0; !rc && j < ntotal; j++) { if (showln) rc = unidiff_lineno(dst, li + j + 1, ri + j + 1, html); if (!rc) rc = unidiff_txt(dst, ' ', &l[li + j], html, 0); } if (rc) return rc; ri += ntotal; li += ntotal; } } /* Show the final common area */ assert(nc==i); ntotal = c[ci + nc * 3]; if (ntotal > context) ntotal = context; for (j = 0; !rc && j < ntotal; j++) { if (showln) rc = unidiff_lineno(dst, li + j + 1, ri + j + 1, html); if (!rc) rc = unidiff_txt(dst, ' ', &l[li + j], html, 0); } } /* _big_ for() loop */ fsl_free(dst->proto.signature); return rc; } /* Extract the number of context lines from flags. */ int diff_context_lines(uint64_t flags) { int n = flags & FNC_DIFF_CONTEXT_MASK; if (!n && !FLAG_CHK(flags, FNC_DIFF_CONTEXT_EX)) n = 5; return n; } /* * Extract column width for side-by-side diff from flags. Return appropriate * default if no width is specified. */ int sbsdiff_width(uint64_t flags) { int w = (flags & FNC_DIFF_WIDTH_MASK - 0xf) / FNC_DIFF_CONTEXT_MASK; if (!w) w = 80; return w; } /* Append a separator line of length len to column col. */ int sbsdiff_separator(struct sbsline *dst, int len, int col) { char ch = '.'; if (len < 1) { len = 1; ch = ' '; } return fsl_buffer_appendf(dst->cols[col], "<span class=\"fsl-diff-hr\">%.*c</span>\n", len, ch); } /* * fsl_output_f() implementation for use with diff_outf(). State must be a * struct diff_out_state *. */ int fsl_output_f_diff_out(void *state, void const *src, fsl_size_t n) { struct diff_out_state *const dst = (struct diff_out_state *)state; return dst->rc = dst->out(dst->state, src, n); } int diff_outf(struct diff_out_state *dst, char const *fmt, ...) { va_list va; va_start(va,fmt); fsl_appendfv(fsl_output_f_diff_out, dst, fmt, va); va_end(va); return dst->rc; } /* Append a column to the final output blob. */ int sbsdiff_column(struct diff_out_state *dst, fsl_buffer const *content, int col) { return diff_outf(dst, "<td><div class=\"fsl-diff-%s-col\">\n" "<pre>\n" "%b</pre>\n" "</div></td>\n", col % 3 ? (col == SBS_MID ? "separator" : "text") : "lineno", content); } /* * Write the text of dline into column col of SBS diff dst. If outputting HTML, * write the full line; otherwise, only write up to dst->width characters. * Expand tabs to spaces, and add newlines if col is SBS_RTEXT. Translate HTML * characters if esc is true. Pad with spaces to dst->width bytes if col is * SBS_LTEXT and esc is false. * * This comment contains multibyte unicode characters (�, �, �) in order * to test the ability of the diff code to handle such characters. */ int sbsdiff_txt(struct sbsline *dst, fsl_dline *dline, int col) { fsl_buffer *o = dst->cols[col]; const char *str = dline->z; int n = dline->n; int i; /* Number of input characters consumed */ int pos; /* Cursor position */ int w = dst->width; int rc = FSL_RC_OK; bool colourise = dst->esc; bool endspan = false; #if 0 /* * XXX Missing regex bits, but want to replace those with a predicate. */ if (colourise && dst->regex && !re_dline_match(dst->regex, dline, 1)) colourise = false; #endif for (i = pos = 0; !rc && (dst->esc || pos < w) && i < n; i++, pos++) { char c = str[i]; if (colourise) { if (i == dst->idx) { rc = fsl_buffer_append(o, dst->tag, -1); if (rc) break; endspan = true; if (dst->idx2) { dst->idx = dst->idx2; dst->tag = dst->tag2; dst->idx2 = 0; } } else if (i == dst->end) { rc = fsl_buffer_append(o, "</span>", 7); if (rc) break; endspan = false; if (dst->end2) { dst->end = dst->end2; dst->end2 = 0; } } } if (c == '\t' && !dst->esc) { rc = fsl_buffer_append(o, " ", 1); while (!rc && (pos & 7) != 7 && (dst->esc || pos < w)) { rc = fsl_buffer_append(o, " ", 1); ++pos; } } else if (c == '\r' || c == '\f') rc = fsl_buffer_append(o, " ", 1); else if (c == '<' && dst->esc) rc = fsl_buffer_append(o, "<", 4); else if (c == '&' && dst->esc) rc = fsl_buffer_append(o, "&", 5); else if (c == '>' && dst->esc) rc = fsl_buffer_append(o, ">", 4); else if (c == '"' && dst->esc) rc = fsl_buffer_append(o, """, 6); else { rc = fsl_buffer_append(o, &str[i], 1); if (UTF_CONT(c)) --pos; } } if (!rc && endspan) rc = fsl_buffer_append(o, "</span>", 7); if (!rc) { if (col == SBS_RTEXT) rc = sbsdiff_newline(dst); else if (!dst->esc) rc = sbsdiff_space(dst, w - pos, SBS_LTEXT); } return rc; } /* * Append newlines to columns corresponding to sbs diff format. * html: all columns * text: right column only */ int sbsdiff_newline(struct sbsline *dst) { int i, rc = FSL_RC_OK; for (i = dst->esc ? SBS_LLINE : SBS_RTEXT; !rc && i <= SBS_RTEXT; i++) rc = fsl_buffer_append(dst->cols[i], "\n", 1); return rc; } /* Append n spaces to column col in the sbs diff. */ int sbsdiff_space(struct sbsline *dst, int n, int col) { return fsl_buffer_appendf(dst->cols[col], "%*s", n, ""); } /* Append plaintext XOR html marker into the center column of the sbs diff. */ int sbsdiff_marker(struct sbsline *dst, const char *str, const char *html) { return fsl_buffer_append(dst->cols[SBS_MID], dst->esc ? html : str, -1); } /* Append file line number ln to column col in the sbs diff. */ int sbsdiff_lineno(struct sbsline *dst, int ln, int col) { int rc; if (dst->esc) rc = fsl_buffer_appendf(dst->cols[col], "%d", ln + 1); else { char lnno[8]; fsl_snprintf(lnno, 8, "%5d ", ln + 1); rc = fsl_buffer_appendf(dst->cols[col], "%s ", lnno); } return rc; } /* Try to shift dst->idx as far as possible to the left. */ void sbsdiff_shift_left(struct sbsline *dst, const char *z) { int i, j; while ((i = dst->idx) > 0 && z[i - 1] == z[i]) { for (j = i + 1; j < dst->end && z[j - 1] == z[j]; j++) {} if (j < dst->end) break; --dst->idx; --dst->end; } } /* * Simplify line at idx and idx2 in SBS diff output: * - If idx is a null-change then move idx2 into idx * - Make sure any null-changes are in canonical form. * - Make sure all changes are at character boundaries for multibyte chars. */ void sbsdiff_simplify_line(struct sbsline *dst, const char *z) { if (dst->idx2 == dst->end2) dst->idx2 = dst->end2 = 0; else if (dst->idx2) { while (dst->idx2 > 0 && UTF_CONT(z[dst->idx2])) --dst->idx2; while (UTF_CONT(z[dst->end2])) ++dst->end2; } if (dst->idx == dst->end) { dst->idx = dst->idx2; dst->end = dst->end2; dst->tag = dst->tag2; dst->idx2 = 0; dst->end2 = 0; } if (dst->idx == dst->end) dst->idx = dst->end = -1; else if (dst->idx > 0) { while (dst->idx > 0 && UTF_CONT(z[dst->idx])) --dst->idx; while (UTF_CONT(z[dst->end])) ++dst->end; } } /* * c[] is an array of six integers: two copy/delete/insert triples for a * pair of adjacent differences. Return true if the gap between these two * differences is so small that they should be rendered as a single edit. */ int sbsdiff_close_gap(int *c) { return c[3] <= 2 || c[3] <= (c[1] + c[2] + c[4] + c[5]) / 8; } /* * There is a change block in which nleft lines of text on the left are * converted into nright lines of text on the right. This routine computes how * the lines on the left line up with the lines on the right. * * The return value is a buffer of unsigned characters, obtained from * fsl_malloc(), which needs to be disposed of by the caller. Entries in the * returned array have values as follows: * 1 = Delete the next line of left. * 2 = Insert the next line of right. * 3 = The next line of left changes into the next line of right. * 4 = Delete one line from left and add one line to right. * * Values larger than three indicate better matches. * * The length of the returned array will be just large enough to cause all * elements of left and right to be consumed. * * Algorithm: Wagner's minimum edit-distance algorithm, modified by adding a * cost to each match based on how well the two rows match each other. * Insertion and deletion costs are 50. Match costs are between 0 and 100 where * 0 is a perfect match 100 is a complete mismatch. * left lines of text on the left * nleft number of lines on the left * right lines of text on the right * nright number of lines on the right */ unsigned char *sbsdiff_align(fsl_dline *left, int nleft, fsl_dline *right, int nright) { int buf[100]; /* left[] stack if nright not too big */ int *row; /* One row of the Wagner matrix */ int *ptr; /* Space that needs to be freed */ int nmatches; /* Number of matches */ int matchscore; /* Match score */ int minlen; /* MIN(nleft, nright) */ int maxlen; /* MAX(nleft, nright) */ int i, j, k; /* Loop counters */ unsigned char *matrix; /* Wagner result matrix */ matrix = (unsigned char *)fsl_malloc((nleft + 1) * (nright + 1)); if (!matrix) return NULL; if (!nleft) { memset(matrix, 2, nright); return matrix; } if (!nright) { memset(matrix, 1, nleft); return matrix; } /* * This algorithm is O(n^2). So if n is too big, bail out with a * simple (but stupid and ugly) result that doesn't take too long. */ minlen = min(nleft, nright); if (nleft * nright > 100000) { memset(matrix, 4, minlen); if (nleft > minlen) memset(matrix + minlen, 1, nleft - minlen); if (nright > minlen) memset(matrix + minlen, 2, nright - minlen); return matrix; } if (nright < (int)nitems(buf) - 1) { ptr = 0; row = buf; } else { row = ptr = fsl_malloc(sizeof(row[0]) * (nright + 1)); if (!row) { fsl_free(matrix); return NULL; } } /* Compute the best alignment */ for (i = 0; i <= nright; i++) { matrix[i] = 2; row[i] = i * 50; } matrix[0] = 0; for (j = 1; j <= nleft; j++) { int p = row[0]; row[0] = p + 50; matrix[j * (nright + 1)] = 1; for (i = 1; i <= nright; i++) { int nlines = row[i - 1] + 50; int d = 2; if (nlines > row[i] + 50) { nlines = row[i] + 50; d = 1; } if (nlines > p) { int score = match_dline(&left[j - 1], &right[i - 1]); if ((score <= 63 || (i < j + 1 && i > j - 1)) && nlines > p + score) { nlines = p + score; d = 3 | score * 4; } } p = row[i]; row[i] = nlines; matrix[j * (nright + 1) + i] = d; } } /* Compute the lowest-cost path back through the matrix. */ i = nright; j = nleft; k = (nright + 1) * (nleft + 1) - 1; nmatches = matchscore = 0; while (i + j > 0) { unsigned char c = matrix[k]; if (c >= 3) { assert(i > 0 && j > 0); --i; --j; ++nmatches; matchscore += (c >> 2); matrix[k] = 3; } else if (c == 2){ assert(i > 0); --i; } else { assert(j > 0); --j; } --k; matrix[k] = matrix[j * (nright + 1) + i]; } ++k; i = (nright + 1) * (nleft + 1) - k; memmove(matrix, &matrix[k], i); /* * If: * 1. the alignment is more than 25% longer than the longest side; & * 2. the average match cost exceeds 15 * Then this is probably an alignment that will be difficult for humans * to read. So instead, just show all of the right side inserted * followed by all of the left side deleted. * * The coefficients for conditions (1) and (2) above are determined by * experimentation. */ maxlen = nleft > nright ? nleft : nright; if (i * 4 > maxlen * 5 && (!nmatches || matchscore / nmatches > 15)) { memset(matrix, 4, minlen); if (nleft > minlen) memset(matrix + minlen, 1, nleft - minlen); if (nright > minlen) memset(matrix + minlen, 2, nright - minlen); } /* Return the result */ fsl_free(ptr); return matrix; } /* * Write and record line type of lines that have been edited to dst. Adjust * highlight to cover only those parts of the line that have changed. * dst The SBS output line * left Left line of the change * llnno Line number for the left line * right Right line of the change * rlnno Line number of the right line */ int sbsdiff_write_change(struct sbsline *dst, fsl_dline *left, int llnno, fsl_dline *right, int rlnno) { static const char tag_rm[] = "<span class=\"fsl-diff-rm\">"; static const char tag_add[] = "<span class=\"fsl-diff-add\">"; static const char tag_chg[] = "<span class=\"fsl-diff-change\">"; const char *ltxt; /* Text of the left line */ const char *rtxt; /* Text of the right line */ enum line_type *lines = dst->output->lines; uint32_t *idx = dst->output->idx; int lcs[4] = {0, 0, 0, 0}; /* Bounds of common middle segment */ int leftsz; /* Length of left line in bytes */ int rightsz; /* Length of right line in bytes */ int shortest; /* Shortest of left and right */ int npfx; /* Length of common prefix */ int nsfx; /* Length of common suffix */ int nleft; /* leftsz - npfx - nsfx */ int nright; /* rightsz - npfx - nsfx */ int rc = FSL_RC_OK; leftsz = left->n; ltxt = left->z; rightsz = right->n; rtxt = right->z; shortest = min(leftsz, rightsz); /* Count common prefix. */ npfx = 0; while (npfx < shortest && ltxt[npfx] == rtxt[npfx]) npfx++; /* Account for multibyte chars in prefix. */ if (npfx < shortest) while (npfx > 0 && UTF_CONT(ltxt[npfx])) npfx--; /* Count common suffix. */ nsfx = 0; if (npfx < shortest) { while (nsfx < shortest && ltxt[leftsz - nsfx - 1] == rtxt[rightsz - nsfx - 1]) ++nsfx; /* Account for multibyte chars in suffix. */ if (nsfx < shortest) while (nsfx > 0 && UTF_CONT(ltxt[leftsz - nsfx])) --nsfx; if (nsfx == leftsz || nsfx == rightsz) npfx = 0; } if (npfx + nsfx > shortest) npfx = shortest - nsfx; /* A single chunk of text inserted on the right */ if (npfx + nsfx == leftsz) { rc = sbsdiff_lineno(dst, llnno, SBS_LLINE); if (rc) return rc; dst->idx2 = dst->end2 = 0; dst->idx = dst->end = -1; rc = sbsdiff_txt(dst, left, SBS_LTEXT); if (!rc && leftsz == rightsz && ltxt[leftsz] == rtxt[rightsz]) { rc = sbsdiff_marker(dst, " ", ""); lines[*idx] = LINE_DIFF_CONTEXT; } else { rc = sbsdiff_marker(dst, " | ", "|"); lines[*idx] = LINE_DIFF_EDIT; } if (!rc) { rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE); if (!rc) { dst->idx = npfx; dst->end = rightsz - nsfx; dst->tag = tag_add; rc = sbsdiff_txt(dst, right, SBS_RTEXT); } } return rc; } /* A single chunk of text deleted from the left */ if (npfx + nsfx == rightsz) { lines[*idx] = LINE_DIFF_EDIT; /* Text deleted from the left */ rc = sbsdiff_lineno(dst, llnno, SBS_LLINE); if (rc) return rc; dst->idx2 = dst->end2 = 0; dst->idx = npfx; dst->end = leftsz - nsfx; dst->tag = tag_rm; rc = sbsdiff_txt(dst, left, SBS_LTEXT); if (!rc) { rc = sbsdiff_marker(dst, " | ", "|"); if (!rc) { rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE); if (!rc) { dst->idx = dst->end = -1; sbsdiff_txt(dst, right, SBS_RTEXT); } } } return rc; } /* * At this point we know that there is a chunk of text that has * changed between the left and the right. Check to see if there * is a large unchanged section in the middle of that changed block. */ nleft = leftsz - nsfx - npfx; nright = rightsz - nsfx - npfx; if (dst->esc && nleft >= 6 && nright >= 6 && find_lcs(<xt[npfx], nleft, &rtxt[npfx], nright, lcs)) { rc = sbsdiff_lineno(dst, llnno, SBS_LLINE); if (rc) return rc; dst->idx = npfx; dst->end = npfx + lcs[0]; if (lcs[2] == 0) { sbsdiff_shift_left(dst, left->z); dst->tag = tag_rm; } else dst->tag = tag_chg; dst->idx2 = npfx + lcs[1]; dst->end2 = leftsz - nsfx; dst->tag2 = lcs[3] == nright ? tag_rm : tag_chg; sbsdiff_simplify_line(dst, ltxt + npfx); lines[*idx] = LINE_DIFF_EDIT; rc = sbsdiff_txt(dst, left, SBS_LTEXT); if (!rc) rc = sbsdiff_marker(dst, " | ", "|"); if (!rc) rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE); if (rc) return rc; dst->idx = npfx; dst->end = npfx + lcs[2]; if (!lcs[0]) { sbsdiff_shift_left(dst, right->z); dst->tag = tag_add; } else dst->tag = tag_chg; dst->idx2 = npfx + lcs[3]; dst->end2 = rightsz - nsfx; dst->tag2 = lcs[1]==nleft ? tag_add : tag_chg; sbsdiff_simplify_line(dst, rtxt + npfx); rc = sbsdiff_txt(dst, right, SBS_RTEXT); return rc; } /* If all else fails, show a single big change between left and right */ rc = sbsdiff_lineno(dst, llnno, SBS_LLINE); if (!rc) { lines[*idx] = LINE_DIFF_EDIT; dst->idx2 = dst->end2 = 0; dst->idx = npfx; dst->end = leftsz - nsfx; dst->tag = tag_chg; rc = sbsdiff_txt(dst, left, SBS_LTEXT); if (!rc) { rc = sbsdiff_marker(dst, " | ", "|"); if (!rc) { rc = sbsdiff_lineno(dst, rlnno, SBS_RLINE); if (!rc) { dst->end = rightsz - nsfx; sbsdiff_txt(dst, right, SBS_RTEXT); } } } } return rc; } /* * Add two line numbers to the beginning of a unified diff output line. * dst Output destination * lln Line number corresponding to the line in the left (old) file * rln Line number corresponding to the line in the right (new) file * html Specify html formatted output * n.b. lln or rln can be zero to leave that number field blank. */ int unidiff_lineno(struct diff_out_state *dst, int lln, int rln, bool html) { int rc = FSL_RC_OK; if (html) { rc = diff_out(dst, "<span class=\"fsl-diff-lineno\">", -1); if (rc) return rc; } if (lln > 0) rc = diff_outf(dst, "%6d ", lln); else rc = diff_out(dst, " ", 7); if (!rc) { if (rln > 0) rc = diff_outf(dst, "%6d ", rln); else rc = diff_out(dst, " ", 8); if (!rc && html) rc = diff_out(dst, "</span>", -1); } return rc; } /* * Append a single line of unified diff text to dst. * dst Destination * sign Either a " " (context), "+" (added), or "-" (removed) line * line The line to be output * html True if generating HTML, false for plain text * regex colourise only if line matches this regex */ int unidiff_txt(struct diff_out_state *const dst, char sign, fsl_dline *line, int html, void *regex) { char const *ansiccode; int rc = FSL_RC_OK; ansiccode = !dst->ansi ? NULL : ((sign == '+') ? ANSI_DIFF_ADD(0) : ((sign == '-') ? ANSI_DIFF_RM(0) : NULL)); if (ansiccode) rc = diff_out(dst, ansiccode, -1); if (!rc) rc = diff_out(dst, &sign, 1); if (rc) return rc; if (html) { #if 0 /* XXX Missing regex implementation. */ if (regex && !re_dline_match(regex, line, 1)) sign = ' '; else #endif /* * XXX Shift below block left one tab while regex is if'd out. * ----8<----------------------------------------------------- */ if (sign == '+') rc = diff_out(dst, "<span class=\"fsl-diff-add\">", -1); else if (sign == '-') rc = diff_out(dst, "<span class=\"fsl-diff-rm\">", -1); if (!rc) { /* Trim trailing newline */ /* unsigned short n = line->n; */ /* while (n > 0 && (line->z[n - 1] == '\n' || */ /* line->z[n - 1] == '\r')) */ /* --n; */ rc = dst->rc = fsl_htmlize(dst->out, dst->state, line->z, line->n); if (!rc && sign != ' ') rc = diff_out(dst, "</span>", -1); } /* * XXX Shift above block left one tab while regex is if'd out. * ----------------------------------------------------->8---- */ } else rc = diff_out(dst, line->z, line->n); if (!rc) { if (ansiccode) rc = diff_out(dst, ANSI_RESET, -1); if (!rc) rc = diff_out(dst, "\n", 1); } return rc; } |
Changes to src/fnc.1.
︙ | ︙ | |||
42 43 44 45 46 47 48 | .Op Fl R Ar path .Op Fl T Ar tag .Op Fl t Ar type .Op Fl u Ar user .Op Ar path .Nm .Cm diff | | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | .Op Fl R Ar path .Op Fl T Ar tag .Op Fl t Ar type .Op Fl u Ar user .Op Ar path .Nm .Cm diff .Op Fl CilPqsw .Op Fl R Ar path .Op Fl x Ar number .Op Ar artifact1 Op Ar artifact2 .Op Ar path ... .Nm .Cm tree .Op Fl C |
︙ | ︙ | |||
366 367 368 369 370 371 372 | Scroll timeline view half a page upwards in the buffer. .It Cm G, End Move selection cursor to the last commit on the timeline (i.e., oldest commit in the repository). .It Cm gg, Home Move selection cursor to the first commit on the timeline (i.e., newest commit in the repository). | | > > > > > > > | 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | Scroll timeline view half a page upwards in the buffer. .It Cm G, End Move selection cursor to the last commit on the timeline (i.e., oldest commit in the repository). .It Cm gg, Home Move selection cursor to the first commit on the timeline (i.e., newest commit in the repository). .It Cm Enter Open a .Cm diff view displaying the changeset of the currently selected commit. .It Cm Space (Un)tag the currently selected commit as the base commit for the next tagged commit to be diffed against. If another commit is already tagged, show the changes between it and the selected commit. .It Cm b Open and populate branch view with all repository branches. .It Cm C Diff local changes on disk in the current checkout against the selected commit. .It Cm c Toggle colourised timeline. On supported terminals, .Nm will default to displaying the timeline in colour. .It Cm F Prompt to enter a search term to filter a new timeline view by commits with comment, user, or branch fields that match the entered pattern. If no commits |
︙ | ︙ | |||
401 402 403 404 405 406 407 | .It Cm N Find the previous commit that matches the current search term. The search will continue until either a match is found or the latest commit on the timeline is consumed. .El .Tg di .It Cm diff Oo Fl C | -no-colour Oc Oo Fl h | -help Oc Oo Fl i | -invert Oc \ | > | | | 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 | .It Cm N Find the previous commit that matches the current search term. The search will continue until either a match is found or the latest commit on the timeline is consumed. .El .Tg di .It Cm diff Oo Fl C | -no-colour Oc Oo Fl h | -help Oc Oo Fl i | -invert Oc \ Oo Fl l | -line-numbers Oc Oo Fl P | -no-prototype Oc Oo Fl q | -quiet Oc \ Oo Fl R | -repo Ar path Oc Oo Fl s | -sbs Oc Oo Fl w | -whitespace Oc \ Oo Fl x | -context Ar n Oc Oo Ar artifact1 Oo Ar artifact2 Oc Oc Op Ar path ... .Dl Pq alias: Cm di Display the differences between two repository artifacts, or between the local changes on disk and a given commit. If neither .Ar artifact1 nor .Ar artifact2 are specified, |
︙ | ︙ | |||
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 | User-defined colours are also supported, see .Sx ENVIRONMENT for details. .It Fl h , -help Display diff command help and usage information then exit. .It Fl i , -invert Invert the difference between artifacts when displaying the diff. .It Fl R , -repo Ar path Use the .Xr fossil 1 repository database at the specified .Ar path for the current .Cm fnc diff invocation. When this option is used, both .Ar artifact1 and .Ar artifact2 are required, and the checkout-related Fossil special tags .Qq current , .Qq prev , and .Qq next are invalid .Sy artifact operands. .It Fl w , -whitespace Ignore whitespace-only changes when displaying the diff. .It Fl x , -context Ar n Set .Ar n context lines to be shown in the diff. By default, 5 context lines are shown. Negative values are a no-op. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | User-defined colours are also supported, see .Sx ENVIRONMENT for details. .It Fl h , -help Display diff command help and usage information then exit. .It Fl i , -invert Invert the difference between artifacts when displaying the diff. .It Fl l , -line-numbers Display file line numbers in diff output. As documented below, this option can be toggled with the .Sy L diff view key binding. .It Fl P , -no-prototype Disable chunk header display of which function or scope each change is in, which is enabled by default. The heuristic will produce reliable results for all C-like languages .Pq e.g., Java, Python, JavaScript, Rust ; however, Lisps and non-source code .Pq e.g., Markdown, reStructuredText will return meaningless results. This option can be toggled in-session with the .Sy p key binding as documented below. .Po Mutually exclusive with .Fl l , -line-numbers .Pc .It Fl q , -quiet Disable verbose output; that is, do not output complete content of newly added or deleted files, which are displayed by default. Verbosity can also be toggled with the .Sy v key binding as documented below. .It Fl R , -repo Ar path Use the .Xr fossil 1 repository database at the specified .Ar path for the current .Cm fnc diff invocation. When this option is used, both .Ar artifact1 and .Ar artifact2 are required, and the checkout-related Fossil special tags .Qq current , .Qq prev , and .Qq next are invalid .Sy artifact operands. .It Fl s , -sbs Display a side-by-side formatted diff. As documented below, this option can also be toggled in-session with the .Sy s key binding. .It Fl w , -whitespace Ignore whitespace-only changes when displaying the diff. .It Fl x , -context Ar n Set .Ar n context lines to be shown in the diff. By default, 5 context lines are shown. Negative values are a no-op. |
︙ | ︙ | |||
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 | if the diff was accessed from the timeline.) .It Cm \&-, \&_ Decrease the number of context lines shown in diff output. .It Cm \&=, \&+ Increase the number of context lines shown in diff output. .It Cm # Toggle display of diff view line numbers. .It Cm b Open and populate branch view with all repository branches. .It Cm c Toggle coloured diff output. On supported terminals, .Nm will default to displaying changes and diff metadata in colour. .It Cm F Open prompt to enter file number and navigate to that file in the diff. .It Cm i Toggle inversion of diff output. .It Cm L | > > | > > > > > > | 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 | if the diff was accessed from the timeline.) .It Cm \&-, \&_ Decrease the number of context lines shown in diff output. .It Cm \&=, \&+ Increase the number of context lines shown in diff output. .It Cm # Toggle display of diff view line numbers. .It Cm @ Open prompt to enter line number and navigate to that line in the view. .It Cm b Open and populate branch view with all repository branches. .It Cm c Toggle coloured diff output. On supported terminals, .Nm will default to displaying changes and diff metadata in colour. .It Cm F Open prompt to enter file number and navigate to that file in the diff. .It Cm i Toggle inversion of diff output. .It Cm L Toggle display of file line numbers in the diff. .It Cm p In the diff chunk header, toggle display of which function each change is in; for example: .Sy @@ -2360,10 +2361,11 @@ draw_commits(struct fnc_view *view) .It Cm S Toggle display of a side-by-side formatted diff. .It Cm v Toggle verbosity of diff output. By default, .Nm will display the entire content of newly added or deleted files. .It Cm w Toggle whether whitespace-only changes are ignored when comparing lines in the diff. |
︙ | ︙ | |||
735 736 737 738 739 740 741 742 743 744 745 746 747 748 | .Nm will blame the version of the file from the current checkout. For a complete list of valid arguments this option accepts, see .Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \ "Fossil's Check-in Names". .It Fl h , -help Display blame command help and usage information then exit. .It Fl n , -limit Ar n Limit depth of blame history to .Ar n commits or seconds. The latter is denoted by a postfixed 's' (e.g., 30s). With this option, .Nm will traverse either as many commits as specified, or as possible in the | > > > > > > | 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 | .Nm will blame the version of the file from the current checkout. For a complete list of valid arguments this option accepts, see .Lk https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki \ "Fossil's Check-in Names". .It Fl h , -help Display blame command help and usage information then exit. .It Fl l , -line Ar lineno Open annotated file at .Ar lineno . As documented below, once the file is loaded, the .Sy @ key binding can be used to navigate to an arbitrary line in the file. .It Fl n , -limit Ar n Limit depth of blame history to .Ar n commits or seconds. The latter is denoted by a postfixed 's' (e.g., 30s). With this option, .Nm will traverse either as many commits as specified, or as possible in the |
︙ | ︙ | |||
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 | Move selection cursor to the last line in the file. .It Cm Enter Display the .Cm diff of the commit corresponding to the currently selected line. .It Cm # Toggle display of file line numbers. .It Cm B, Backspace Reload the previous blamed version of the file. .It Cm b Blame the version of the file corresponding to the commit in the currently selected line. .It Cm c Toggle coloured output. On supported terminals, .Nm will default to displaying the blamed file in colour. | > > < < | 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | Move selection cursor to the last line in the file. .It Cm Enter Display the .Cm diff of the commit corresponding to the currently selected line. .It Cm # Toggle display of file line numbers. .It Cm @ Open prompt to enter line number and navigate to that line in the file. .It Cm B, Backspace Reload the previous blamed version of the file. .It Cm b Blame the version of the file corresponding to the commit in the currently selected line. .It Cm c Toggle coloured output. On supported terminals, .Nm will default to displaying the blamed file in colour. .It Cm p Blame the version of the file corresponding to the parent of the commit in the currently selected line. .It Cm T Open and populate branch view with all repository branches. .It Cm / Prompt to enter a search term to begin searching the file for tokens matching |
︙ | ︙ | |||
906 907 908 909 910 911 912 | Disable coloured output, which is enabled by default on supported terminals. If this option is not used, colour can be toggled with the .Sy c branch view key binding as documented below. User-defined colours are also supported, see .Sx ENVIRONMENT for details. | | | 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 | Disable coloured output, which is enabled by default on supported terminals. If this option is not used, colour can be toggled with the .Sy c branch view key binding as documented below. User-defined colours are also supported, see .Sx ENVIRONMENT for details. .It Fl c , -closed Display only closed branches. .It Fl h , -help Display branch command help and usage information then exit. .It Fl o , -open Display only opened branches. .It Fl p , -no-private Do not show private branches, which are included in the list of displayed |
︙ | ︙ | |||
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 | .Qq yellow . .It Ev FNC_COLOUR_DIFF_META Metadata displayed in the diff view. If not defined, the default value is .Qq green . .It Ev FNC_COLOUR_DIFF_TAGS The tag field displayed in the diff view. If not defined, the default value is .Qq magenta . .It Ev FNC_COLOUR_TREE_DIR Directory entries displayed in the tree view. If not defined, the default value is .Qq cyan . .It Ev FNC_COLOUR_TREE_EXEC Executable file entries displayed in the tree view. If not defined, the default value is | > > > > > > | 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 | .Qq yellow . .It Ev FNC_COLOUR_DIFF_META Metadata displayed in the diff view. If not defined, the default value is .Qq green . .It Ev FNC_COLOUR_DIFF_TAGS The tag field displayed in the diff view. If not defined, the default value is .Qq magenta . .It Ev FNC_COLOUR_DIFF_SBS_EDIT Changed .Pq i.e., not added or removed lines in the diff view when displaying side-by-side diffs. If not defined, the default value is .Qq red . .It Ev FNC_COLOUR_TREE_DIR Directory entries displayed in the tree view. If not defined, the default value is .Qq cyan . .It Ev FNC_COLOUR_TREE_EXEC Executable file entries displayed in the tree view. If not defined, the default value is |
︙ | ︙ |
Changes to src/fnc.c.
1 2 | /* * Copyright (c) 2021, 2022 Mark Jamsek <mark@jamsek.com> | < | | > | | | < < < < < | | | > > > > | > > > > > | > | | 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 | /* * Copyright (c) 2021, 2022 Mark Jamsek <mark@jamsek.com> * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * _POSIX_C_SOURCE >= 199309L needed for sigaction() & sigemptyset() on Linux, * but glibc docs claim _XOPEN_SOURCE >= 700 has the same effect, plus we need * _XOPEN_SOURCE >= 500 for ncurses wchar APIs on linux. */ #ifdef __linux__ # ifndef _XOPEN_SOURCE # define _XOPEN_SOURCE 700 # endif /* _XOPEN_SOURCE */ # ifndef _DEFAULT_SOURCE # define _DEFAULT_SOURCE /* strsep() on glibc >= 2.19. */ # endif /* _DEFAULT_SOURCE */ # ifdef __has_include # if __has_include("linux/landlock.h") # define HAVE_LANDLOCK # include <linux/landlock.h> # include <linux/prctl.h> # include <sys/prctl.h> # include <sys/syscall.h> # include <fcntl.h> # include <libgen.h> # endif /* LANDLOCK_H */ # endif /* __has_include */ #endif /* __linux__ */ #ifdef FCLI_USE_SIGACTION #define FCLI_USE_SIGACTION 0 /* We want C-c to exit. */ #endif #include <sys/queue.h> #include <sys/ioctl.h> |
︙ | ︙ | |||
65 66 67 68 69 70 71 | #include <libgen.h> #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" | | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | #include <libgen.h> #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" #include "diff.h" #define FNC_VERSION VERSION /* cf. Makefile */ /* Utility macros. */ #define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) #define MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) #define ABS(_n) ((_n) >= 0 ? (_n) : -(_n)) #ifndef CTRL #define CTRL(key) ((key) & 037) /* CTRL+<key> input. */ #endif #define nitems(a) (sizeof((a)) / sizeof((a)[0])) #define ndigits(_d, _n) do { _d++; } while (_n /= 10) #define STRINGIFYOUT(s) #s #define STRINGIFY(s) STRINGIFYOUT(s) #define CONCATOUT(a, b) a ## b |
︙ | ︙ | |||
107 108 109 110 111 112 113 114 115 | #if DEBUG #define RC(r, fmt, ...) fcli_err_set(r, "%s::%s " fmt, \ __func__, FILE_POSITION, __VA_ARGS__) #else #define RC(r, fmt, ...) fcli_err_set(r, fmt, __VA_ARGS__) #endif /* DEBUG */ /* Portability macros. */ #ifndef __OpenBSD__ | > > > > > | | > > > | | 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 | #if DEBUG #define RC(r, fmt, ...) fcli_err_set(r, "%s::%s " fmt, \ __func__, FILE_POSITION, __VA_ARGS__) #else #define RC(r, fmt, ...) fcli_err_set(r, fmt, __VA_ARGS__) #endif /* DEBUG */ /* fossil(1) db-related paths. */ #define REPODB fsl_cx_db_file_repo(fcli_cx(), NULL) #define REPODIR getdirname(REPODB, -1, false) #define CKOUTDIR fsl_cx_ckout_dir_name(fcli_cx(), NULL) /* Portability macros. */ #ifndef __OpenBSD__ # ifndef HAVE_STRTONUM /* Use strtol and a range check to emulate strtonum. */ # define strtonum(s, min, max, o) strtol(s, (char **)o, 10) # define inrange(n, min, max) (((n) >= (min)) && ((n) <= (max))) # endif /* HAVE_STRTONUM */ #else # define inrange(n, min, max) true #endif /* OpenBSD */ #ifndef __dead #define __dead __attribute__((noreturn)) #endif #ifndef TAILQ_FOREACH_SAFE /* Rewrite of OpenBSD 6.9 sys/queue.h for Linux builds. */ |
︙ | ︙ | |||
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 | 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. */ const char *glob; /* Only load commits containing glob */ bool utc; /* Display UTC sans user local time. */ /* Diff options. */ const char *context; /* Number of context lines. */ bool ws; /* Ignore whitespace-only changes. */ bool nocolour; /* Disable colour in diff output. */ bool quiet; /* Disable verbose diff output. */ bool invert; /* Toggle inverted diff output. */ /* Branch options. */ const char *before; /* Last branch change before date. */ const char *after; /* Last branch change after date. */ const char *sort; /* Lexicographical, MRU, open/closed. */ bool closed; /* Show only closed branches. */ bool open; /* Show only open branches */ bool noprivate; /* Don't show private branches. */ /* Config options. */ bool lsconf; /* List all defined settings. */ bool unset; /* Unset the specified setting. */ /* Command line flags and help. */ fcli_help_info fnc_help; /* Global help. */ fcli_cliflag cliflags_global[3]; /* Global options. */ fcli_command cmd_args[7]; /* App commands. */ fcli_cliflag cliflags_timeline[13]; /* Timeline options. */ | > > > > > | | > > > | 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | 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. */ const char *glob; /* Only load commits containing glob */ bool utc; /* Display UTC sans user local time. */ /* Blame options. */ const char *lineno; /* Line to open blame view. */ /* Diff options. */ const char *context; /* Number of context lines. */ bool sbs; /* Display side-by-side diff. */ 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. */ bool showln; /* Display line numbers in diff. */ /* Branch options. */ const char *before; /* Last branch change before date. */ const char *after; /* Last branch change after date. */ const char *sort; /* Lexicographical, MRU, open/closed. */ bool closed; /* Show only closed branches. */ bool open; /* Show only open branches */ bool noprivate; /* Don't show private branches. */ /* Config options. */ bool lsconf; /* List all defined settings. */ bool unset; /* Unset the specified setting. */ /* Command line flags and help. */ fcli_help_info fnc_help; /* Global help. */ fcli_cliflag cliflags_global[3]; /* Global options. */ fcli_command cmd_args[7]; /* App commands. */ fcli_cliflag cliflags_timeline[13]; /* Timeline options. */ fcli_cliflag cliflags_diff[10]; /* Diff options. */ fcli_cliflag cliflags_tree[5]; /* Tree options. */ fcli_cliflag cliflags_blame[8]; /* Blame options. */ fcli_cliflag cliflags_branch[11]; /* Branch options. */ fcli_cliflag cliflags_config[5]; /* Config options. */ } fnc_init = { NULL, /* cmdarg copy of argv[1] to aid usage/error report. */ NULL, /* sym(bolic name) of commit to open defaults to tip. */ NULL, /* path for tree to open or timeline to find commits. */ 0, /* err fnc error state. */ false, /* hflag if --help is requested. */ false, /* vflag if --version is requested. */ false, /* reverse branch sort/annotation defaults to off. */ {NULL, 0}, /* 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 temp placeholder for filter_types cb. */ NULL, /* glob filter defaults to off; all commits are shown */ false, /* utc defaults to off (i.e., show user local time). */ NULL, /* lineno default: open blame at the first line. */ NULL, /* context defaults to five context lines. */ false, /* sbs diff defaults to false (show unified diff). */ 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, /* showln in diff defaults to off. */ NULL, /* before defaults to any time. */ NULL, /* after defaults to any time. */ NULL, /* sort by MRU or open/closed (dflt: lexicographical) */ false, /* closed only branches is off (defaults to all). */ false, /* open only branches is off by (defaults to all). */ false, /* noprivate is off (default to show private branch). */ false, /* do not list all defined settings by default. */ |
︙ | ︙ | |||
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | "supported terminals. Colour can also be toggled with the 'c' " "\n key binding in diff view when this option is not used."), FCLI_FLAG_BOOL("h", "help", NULL, "Display diff command help and usage."), FCLI_FLAG_BOOL("i", "invert", &fnc_init.invert, "Invert difference between artifacts. Inversion can also be " "toggled\n with the 'i' key binding in diff view."), FCLI_FLAG_BOOL("q", "quiet", &fnc_init.quiet, "Disable verbose diff output; that is, do not output complete" " content\n of newly added or deleted files. Verbosity can also" " be toggled with\n the 'v' key binding in diff view."), FCLI_FLAG_CSTR("R", "repo", "<path>", NULL, "Use the fossil(1) repository located at <path> for this diff\n " "invocation."), FCLI_FLAG_BOOL("w", "whitespace", &fnc_init.ws, "Ignore whitespace-only changes when displaying diff. This option " "can\n also be toggled with the 'w' key binding in diff view."), FCLI_FLAG("x", "context", "<n>", &fnc_init.context, "Show <n> context lines when displaying diff; <n> is capped at 64." "\n Negative values are a no-op."), fcli_cliflag_empty_m | > > > > > > > | 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | "supported terminals. Colour can also be toggled with the 'c' " "\n key binding in diff view when this option is not used."), FCLI_FLAG_BOOL("h", "help", NULL, "Display diff command help and usage."), FCLI_FLAG_BOOL("i", "invert", &fnc_init.invert, "Invert difference between artifacts. Inversion can also be " "toggled\n with the 'i' key binding in diff view."), FCLI_FLAG_BOOL("l", "line-numbers", &fnc_init.showln, "Show file line numbers in diff output. Line numbers can also be " "toggled\n with the 'L' key binding in diff view."), FCLI_FLAG_BOOL("q", "quiet", &fnc_init.quiet, "Disable verbose diff output; that is, do not output complete" " content\n of newly added or deleted files. Verbosity can also" " be toggled with\n the 'v' key binding in diff view."), FCLI_FLAG_CSTR("R", "repo", "<path>", NULL, "Use the fossil(1) repository located at <path> for this diff\n " "invocation."), FCLI_FLAG_BOOL("s", "sbs", &fnc_init.sbs, "Display a side-by-side, rather than the default unified, diff. " "This\n option can alse be toggled with the 'S' key binding in " "diff view."), FCLI_FLAG_BOOL("w", "whitespace", &fnc_init.ws, "Ignore whitespace-only changes when displaying diff. This option " "can\n also be toggled with the 'w' key binding in diff view."), FCLI_FLAG("x", "context", "<n>", &fnc_init.context, "Show <n> context lines when displaying diff; <n> is capped at 64." "\n Negative values are a no-op."), fcli_cliflag_empty_m |
︙ | ︙ | |||
403 404 405 406 407 408 409 410 411 412 413 414 415 416 | "\tISO8601 date\n" "\tISO8601 timestamp\n" "\t{tip,current,prev,next}\n " "For a complete list of symbols see Fossil's Check-in Names:\n " "https://fossil-scm.org/home/doc/trunk/www/checkin_names.wiki"), FCLI_FLAG_BOOL("h", "help", NULL, "Display blame command help and usage."), FCLI_FLAG("n", "limit", "<n>", &fnc_init.nrecords.zlimit, "Limit depth of blame history to <n> commits or seconds. Denote the" "\n latter by postfixing 's' (e.g., 30s). Useful for large files" " with\n extensive history. Persists for the duration of the " "session."), FCLI_FLAG_CSTR("R", "repo", "<path>", NULL, "Use the fossil(1) repository located at <path> for this blame\n" | > > | 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 | "\tISO8601 date\n" "\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("l", "line", "<lineno>", &fnc_init.lineno, "Open annotated file at <lineno>."), FCLI_FLAG("n", "limit", "<n>", &fnc_init.nrecords.zlimit, "Limit depth of blame history to <n> commits or seconds. Denote the" "\n latter by postfixing 's' (e.g., 30s). Useful for large files" " with\n extensive history. Persists for the duration of the " "session."), FCLI_FLAG_CSTR("R", "repo", "<path>", NULL, "Use the fossil(1) repository located at <path> for this blame\n" |
︙ | ︙ | |||
510 511 512 513 514 515 516 | FNC_DIFF_WIKI }; struct input { void *data; char *prompt; enum input_type type; | | > > > > | 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | FNC_DIFF_WIKI }; struct input { void *data; char *prompt; enum input_type type; int flags; #define SR_CLREOL 1 << 0 #define SR_UPDATE 1 << 1 #define SR_SLEEP 1 << 2 #define SR_RESET 1 << 3 char buf[BUFSIZ]; long ret; }; struct fnc_colour { STAILQ_ENTRY(fnc_colour) entries; regex_t regex; |
︙ | ︙ | |||
610 611 612 613 614 615 616 | * fnc_parent_tree(s) to be tracked. */ struct fnc_parent_tree { TAILQ_ENTRY(fnc_parent_tree) entry; struct fnc_tree_object *tree; struct fnc_tree_entry *first_entry_onscreen; struct fnc_tree_entry *selected_entry; | | | | 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 | * fnc_parent_tree(s) to be tracked. */ struct fnc_parent_tree { TAILQ_ENTRY(fnc_parent_tree) entry; struct fnc_tree_object *tree; struct fnc_tree_entry *first_entry_onscreen; struct fnc_tree_entry *selected_entry; int selected; }; pthread_mutex_t fnc_mutex = PTHREAD_MUTEX_INITIALIZER; struct fnc_tl_thread_cx { struct commit_queue *commits; struct commit_entry **first_commit_onscreen; struct commit_entry **selected_entry; fsl_db *db; fsl_stmt *q; regex_t *regex; char *path; /* Match commits involving path. */ enum fnc_search_state *search_status; enum fnc_search_mvmnt *searching; int spin_idx; |
︙ | ︙ | |||
645 646 647 648 649 650 651 | }; 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; | | > > > > > > > | > | > > | 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 | }; 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_entry; struct commit_entry *matched_commit; struct commit_entry *search_commit; struct fnc_colours colours; struct { fsl_uuid_str ogid; fsl_uuid_str id; char *type; fsl_id_t ogrid; fsl_id_t rid; } tagged; const char *curr_ckout_uuid; const char *glob; /* Match commits containing glob. */ char *path; /* Match commits involving path. */ int selected; int nscrolled; sig_atomic_t quit; pthread_t thread_id; bool colour; bool showmeta; }; struct fnc_pathlist_entry { TAILQ_ENTRY(fnc_pathlist_entry) entry; const char *path; size_t pathlen; void *data; /* XXX May want to save id, mode, etc. */ }; TAILQ_HEAD(fnc_pathlist_head, fnc_pathlist_entry); struct index { size_t *lineno; off_t *offset; uint32_t n; uint32_t idx; }; struct fnc_diff_view_state { struct fnc_view *timeline_view; struct fnc_commit_artifact *selected_entry; struct fnc_pathlist_head *paths; fsl_buffer buf; struct fnc_colours colours; struct index index; FILE *f; fsl_uuid_str id1; fsl_uuid_str id2; int first_line_onscreen; int last_line_onscreen; int diff_flags; int context; int sbs; int matched_line; int selected_line; int maxx; int lineno; int gtl; uint32_t ndlines; size_t ncols; size_t nlines; enum line_type *dlines; enum line_attr sline; off_t *line_offsets; bool eof; bool colour; bool showmeta; bool showln; }; |
︙ | ︙ | |||
719 720 721 722 723 724 725 | struct fnc_tree_entry *selected_entry; struct fnc_tree_entry *matched_entry; struct fnc_colours colours; char *tree_label; /* Headline string. */ fsl_uuid_str commit_id; fsl_id_t rid; int ndisplayed; | | | 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 | struct fnc_tree_entry *selected_entry; struct fnc_tree_entry *matched_entry; struct fnc_colours colours; char *tree_label; /* Headline string. */ fsl_uuid_str commit_id; fsl_id_t rid; int ndisplayed; int selected; bool colour; bool show_id; bool show_date; }; struct fnc_blame_line { fsl_uuid_str id; |
︙ | ︙ | |||
775 776 777 778 779 780 781 | fsl_uuid_str id; }; struct fnc_blame_view_state { struct fnc_blame blame; struct fnc_commit_id_queue blamed_commits; struct fnc_commit_qid *blamed_commit; | | > | 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 | fsl_uuid_str id; }; struct fnc_blame_view_state { struct fnc_blame blame; struct fnc_commit_id_queue blamed_commits; struct fnc_commit_qid *blamed_commit; struct fnc_commit_artifact *selected_entry; struct fnc_colours colours; fsl_uuid_str commit_id; const char *lineno; char *path; int first_line_onscreen; int last_line_onscreen; int selected_line; int matched_line; int spin_idx; int gtl; |
︙ | ︙ | |||
814 815 816 817 818 819 820 | TAILQ_HEAD(fnc_branchlist_head, fnc_branchlist_entry); struct fnc_branch_view_state { struct fnc_branchlist_head branches; struct fnc_branchlist_entry *first_branch_onscreen; struct fnc_branchlist_entry *last_branch_onscreen; struct fnc_branchlist_entry *matched_branch; | | | 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 | TAILQ_HEAD(fnc_branchlist_head, fnc_branchlist_entry); struct fnc_branch_view_state { struct fnc_branchlist_head branches; struct fnc_branchlist_entry *first_branch_onscreen; struct fnc_branchlist_entry *last_branch_onscreen; struct fnc_branchlist_entry *matched_branch; struct fnc_branchlist_entry *selected_entry; struct fnc_colours colours; const char *branch_glob; double dateline; int branch_flags; #define BRANCH_LS_CLOSED_ONLY 0x001 /* Show closed branches only. */ #define BRANCH_LS_OPEN_ONLY 0x002 /* Show open branches only. */ #define BRANCH_LS_OPEN_CLOSED 0x003 /* Show open & closed branches (dflt). */ |
︙ | ︙ | |||
837 838 839 840 841 842 843 | int when; bool colour; bool show_date; bool show_id; }; struct line { | | | | | | 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 | int when; bool colour; bool show_date; bool show_id; }; struct line { char *buf; int sz; enum line_type type; bool selected; }; struct position { int col; int line; int offset; }; |
︙ | ︙ | |||
909 910 911 912 913 914 915 | static int block_main_thread_signals(void); static int build_commits(struct fnc_tl_thread_cx *); static int commit_builder(struct fnc_commit_artifact **, fsl_id_t, fsl_stmt *); static int signal_tl_thread(struct fnc_view *, int); static int draw_commits(struct fnc_view *); static void parse_emailaddr_username(char **); | | | | > | > | | > | > > | | | < < < < | < | | | > | 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 | static int block_main_thread_signals(void); static int build_commits(struct fnc_tl_thread_cx *); static int commit_builder(struct fnc_commit_artifact **, fsl_id_t, fsl_stmt *); static int signal_tl_thread(struct fnc_view *, int); static int draw_commits(struct fnc_view *); static void parse_emailaddr_username(char **); static int formatln(wchar_t **, int *, const char *, size_t, int, bool); static size_t expand_tab(char **, const char *, int); static int multibyte_to_wchar(const char *, wchar_t **, size_t *); static int write_commit_line(struct fnc_view *, struct fnc_commit_artifact *, int); static int view_input(struct fnc_view **, int *, struct fnc_view *, struct view_tailhead *); static int cycle_view(struct fnc_view *); static int toggle_fullscreen(struct fnc_view **, struct fnc_view *); static int help(struct fnc_view *); static int padpopup(struct fnc_view *, int, int, FILE *, const char *); static void centerprint(WINDOW *, int, int, int, const char *, chtype); static int tl_input_handler(struct fnc_view **, struct fnc_view *, int); static int move_tl_cursor_down(struct fnc_view *, uint16_t); static void move_tl_cursor_up(struct fnc_view *, uint16_t, bool); static int timeline_scroll_down(struct fnc_view *, int); static void timeline_scroll_up(struct fnc_tl_view_state *, int); static bool tagged_commit(struct fnc_tl_view_state *); static void select_commit(struct fnc_tl_view_state *); static int request_view(struct fnc_view **, struct fnc_view *, enum fnc_view_id); static int init_view(struct fnc_view **, struct fnc_view *, enum fnc_view_id, int, int); static enum view_mode view_get_split(struct fnc_view *, int *, int *); static int split_view(struct fnc_view *, int *); static int offset_selected_line(struct fnc_view *); static int view_split_start_col(int); static int view_split_start_ln(int); static int make_splitscreen(struct fnc_view *); static int make_fullscreen(struct fnc_view *); static int view_search_start(struct fnc_view *); static void tl_grep_init(struct fnc_view *); static int tl_search_next(struct fnc_view *); static bool find_commit_match(struct fnc_commit_artifact *, regex_t *); static int init_diff_view(struct fnc_view **, int, int, struct fnc_commit_artifact *, struct fnc_view *, bool); static int open_diff_view(struct fnc_view *, struct fnc_commit_artifact *, int, bool, bool, bool, bool, bool, struct fnc_view *, bool, struct fnc_pathlist_head *); static void show_diff_status(struct fnc_view *); static int create_diff(struct fnc_diff_view_state *); static int create_changeset(struct fnc_commit_artifact *); static int write_commit_meta(struct fnc_diff_view_state *); static int countlines(const char *); static int wrapline(char *, fsl_size_t, struct fnc_diff_view_state *, off_t *); static int add_line_offset(off_t **, size_t *, off_t); static int add_line_type(enum line_type **, enum line_type, uint32_t *, uint32_t, bool); static int diff_commit(struct fnc_diff_view_state *); static int diff_checkout(struct fnc_diff_view_state *); static int write_diff_meta(struct fnc_diff_view_state *, const char *, fsl_uuid_str, const char *, fsl_uuid_str, enum fsl_ckout_change_e); static int diff_file(struct fnc_diff_view_state *, fsl_buffer *, const char *, fsl_uuid_str, const char *, enum fsl_ckout_change_e); static int diff_non_checkin(struct fnc_diff_view_state *); static int diff_file_artifact(struct fnc_diff_view_state *, fsl_id_t, const fsl_card_F *, const fsl_card_F *, fsl_ckout_change_e); static int show_diff(struct fnc_view *); static int write_diff(struct fnc_view *, char *); static int match_line(const char *, regex_t *, size_t, regmatch_t *); static int draw_matched_line(struct fnc_view *, const char *, int *, int, int, regmatch_t *, attr_t); static void drawborder(struct fnc_view *); static int diff_input_handler(struct fnc_view **, struct fnc_view *, int); static int request_tl_commits(struct fnc_view *); static int reset_diff_view(struct fnc_view *, bool); static int set_selected_commit(struct fnc_diff_view_state *, struct commit_entry *); static void diff_grep_init(struct fnc_view *); static int find_next_match(struct fnc_view *); static void grep_set_view(struct fnc_view *, FILE **, off_t **, size_t *, int **, int **, int **, int **, uint8_t *); static int view_close(struct fnc_view *); static int map_repo_path(char **); static int init_timeline_view(struct fnc_view **, int, int, fsl_id_t, const char *, const char *); static bool path_is_child(const char *, const char *, size_t); static int path_skip_common_ancestor(char **, const char *, size_t, const char *, size_t); |
︙ | ︙ | |||
1047 1048 1049 1050 1051 1052 1053 | static int tree_scroll_down(struct fnc_view *, int); static int visit_subtree(struct fnc_tree_view_state *, struct fnc_tree_object *); static int tree_entry_get_symlink_target(char **, struct fnc_tree_entry *); static int match_tree_entry(struct fnc_tree_entry *, regex_t *); static void fnc_object_tree_close(struct fnc_tree_object *); | | | | 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 | static int tree_scroll_down(struct fnc_view *, int); static int visit_subtree(struct fnc_tree_view_state *, struct fnc_tree_object *); static int tree_entry_get_symlink_target(char **, struct fnc_tree_entry *); static int match_tree_entry(struct fnc_tree_entry *, regex_t *); static void fnc_object_tree_close(struct fnc_tree_object *); static void fnc_close_repo_tree(struct fnc_repository_tree *); static int open_blame_view(struct fnc_view *, char *, fsl_uuid_str, fsl_id_t, int, const char *); static int run_blame(struct fnc_view *); static int fnc_dump_buffer_to_file(off_t *, int *, off_t **, FILE *, fsl_buffer *); static int show_blame_view(struct fnc_view *); static void *blame_thread(void *); static int blame_cb(void *, fsl_annotate_opt const * const, fsl_annotate_step const * const); |
︙ | ︙ | |||
1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 | static bool view_is_parent(struct fnc_view *); static void view_set_child(struct fnc_view *, struct fnc_view *); static int view_close_child(struct fnc_view *); static int close_tree_view(struct fnc_view *); static int close_timeline_view(struct fnc_view *); static int close_diff_view(struct fnc_view *); static void free_index(struct index *); static int view_resize(struct fnc_view *, enum view_mode); static bool screen_is_split(struct fnc_view *); static bool screen_is_shared(struct fnc_view *); static void updatescreen(WINDOW *, bool, bool); static void fnc_resizeterm(void); static int join_tl_thread(struct fnc_tl_view_state *); static void fnc_free_commits(struct commit_queue *); static void fnc_commit_artifact_close(struct fnc_commit_artifact*); static int fsl_file_artifact_free(void *, void *); static void sigwinch_handler(int); static void sigpipe_handler(int); static void sigcont_handler(int); static int draw_lineno(struct fnc_view *, int, int, attr_t); static bool gotoline(struct fnc_view *, int *, int *); static int strtonumcheck(long *, const char *, const int, const int); static int fnc_prompt_input(struct fnc_view *, struct input *); static int fnc_date_to_mtime(double *, const char *, int); static int cook_input(char *, int, WINDOW *); | > | < | < > > | | 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 | static bool view_is_parent(struct fnc_view *); static void view_set_child(struct fnc_view *, struct fnc_view *); static int view_close_child(struct fnc_view *); static int close_tree_view(struct fnc_view *); static int close_timeline_view(struct fnc_view *); static int close_diff_view(struct fnc_view *); static void free_index(struct index *); static void free_tags(struct fnc_tl_view_state *, bool); static int view_resize(struct fnc_view *, enum view_mode); static bool screen_is_split(struct fnc_view *); static bool screen_is_shared(struct fnc_view *); static void updatescreen(WINDOW *, bool, bool); static void fnc_resizeterm(void); static int join_tl_thread(struct fnc_tl_view_state *); static void fnc_free_commits(struct commit_queue *); static void fnc_commit_artifact_close(struct fnc_commit_artifact*); static int fsl_file_artifact_free(void *, void *); static void sigwinch_handler(int); static void sigpipe_handler(int); static void sigcont_handler(int); static int draw_lineno(struct fnc_view *, int, int, attr_t); static bool gotoline(struct fnc_view *, int *, int *); static int strtonumcheck(long *, const char *, const int, const int); static int fnc_prompt_input(struct fnc_view *, struct input *); static int fnc_date_to_mtime(double *, const char *, int); static int cook_input(char *, int, WINDOW *); static int sitrep(struct fnc_view *, const char *, int); static char *fnc_strsep (char **, const char *); static bool fnc_str_has_upper(const char *); static int fnc_make_sql_glob(char **, char **, const char *, bool); static int init_unveil(const char *, const char *, bool); static int init_landlock(const char **, const int); static const char *getdirname(const char *, fsl_int_t, bool); static int set_colours(struct fnc_colours *, enum fnc_view_id); static int set_colour_scheme(struct fnc_colours *, const int (*)[2], const char **, int); static int init_colour(enum fnc_opt_id); static int default_colour(enum fnc_opt_id); static void free_colours(struct fnc_colours *); static bool fnc_home(struct fnc_view *); |
︙ | ︙ | |||
1284 1285 1286 1287 1288 1289 1290 | if (rc) goto end; } rc = init_curses(); if (rc) goto end; | < | < > > | | 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 | if (rc) goto end; } rc = init_curses(); if (rc) goto end; rc = init_unveil(REPODB, CKOUTDIR, false); if (rc) goto end; rc = init_landlock((const char*[]){REPODIR, CKOUTDIR, P_tmpdir}, 3); if (rc) goto end; rc = init_timeline_view(&v, 0, 0, rid, path, glob); if (!rc) rc = view_loop(v); end: fsl_free(glob); fsl_free(path); |
︙ | ︙ | |||
1657 1658 1659 1660 1661 1662 1663 | */ /* if (path[1]) { */ /* rc = fsl_compute_ancestors(db, rid, 0, 0); */ /* if (rc) */ /* return RC(FSL_RC_DB, "%s", "fsl_compute_ancestors"); */ /* } */ s->thread_cx.q = NULL; | | | 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 | */ /* if (path[1]) { */ /* rc = fsl_compute_ancestors(db, rid, 0, 0); */ /* if (rc) */ /* return RC(FSL_RC_DB, "%s", "fsl_compute_ancestors"); */ /* } */ s->thread_cx.q = NULL; /* s->selected = 0; */ /* Unnecessary? */ TAILQ_INIT(&s->commits.head); s->commits.ncommits = 0; if (rid) startdate = fsl_mprintf("(SELECT mtime FROM event " "WHERE objid=%d)", rid); |
︙ | ︙ | |||
1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 | goto end; case FSL_RC_STEP_DONE: rc = RC(FSL_RC_BREAK, "%s", "no matching records"); goto end; } s->colour = !fnc_init.nocolour && has_colors(); s->thread_cx.rc = 0; s->thread_cx.db = db; s->thread_cx.spin_idx = 0; s->thread_cx.ncommits_needed = view->nlines - 1; s->thread_cx.commits = &s->commits; s->thread_cx.eotl = false; s->thread_cx.quit = &s->quit; s->thread_cx.first_commit_onscreen = &s->first_commit_onscreen; | > | | 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 | goto end; case FSL_RC_STEP_DONE: rc = RC(FSL_RC_BREAK, "%s", "no matching records"); goto end; } s->colour = !fnc_init.nocolour && has_colors(); s->showmeta = true; 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.eotl = false; s->thread_cx.quit = &s->quit; s->thread_cx.first_commit_onscreen = &s->first_commit_onscreen; s->thread_cx.selected_entry = &s->selected_entry; 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; s->thread_cx.reset = true; if (s->colour) { |
︙ | ︙ | |||
2092 2093 2094 2095 2096 2097 2098 | if ((rc = pthread_mutex_lock(&fnc_mutex))) { rc = RC(fsl_errno_to_rc(rc, FSL_RC_ACCESS), "%s", "pthread_mutex_lock"); break; } else if (*cx->first_commit_onscreen == NULL) { *cx->first_commit_onscreen = TAILQ_FIRST(&cx->commits->head); | | | 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 | if ((rc = pthread_mutex_lock(&fnc_mutex))) { rc = RC(fsl_errno_to_rc(rc, FSL_RC_ACCESS), "%s", "pthread_mutex_lock"); break; } else if (*cx->first_commit_onscreen == NULL) { *cx->first_commit_onscreen = TAILQ_FIRST(&cx->commits->head); *cx->selected_entry = *cx->first_commit_onscreen; } else if (*cx->quit) done = true; if ((rc = pthread_cond_signal(&cx->commit_producer))) { rc = RC(fsl_errno_to_rc(rc, FSL_RC_MISUSE), "%s", "pthread_cond_signal"); pthread_mutex_unlock(&fnc_mutex); |
︙ | ︙ | |||
2362 2363 2364 2365 2366 2367 2368 | if (cx->eotl) break; if (view->mode == VIEW_SPLIT_HRZN) cx->reset = true; /* Wake timeline thread. */ | | > | > | | | | | | | | | 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 | if (cx->eotl) break; if (view->mode == VIEW_SPLIT_HRZN) cx->reset = true; /* Wake timeline thread. */ rc = pthread_cond_signal(&cx->commit_consumer); if (rc) return RC(fsl_errno_to_rc(rc, FSL_RC_MISUSE), "%s", "pthread_cond_signal"); /* * Mutex will be released while view_loop().view_input() waits * in wgetch(), at which point the timeline thread will run. */ if (!wait) break; /* Show status update in timeline view. */ show_timeline_view(view); update_panels(); doupdate(); /* Wait while the next commit is being loaded. */ rc = pthread_cond_wait(&cx->commit_producer, &fnc_mutex); if (rc) return RC(fsl_errno_to_rc(rc, FSL_RC_ACCESS), "%s", "pthread_cond_wait"); /* Show status update in timeline view. */ show_timeline_view(view); update_panels(); doupdate(); } return cx->rc; } 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_entry; struct fnc_colour *c = NULL; const char *search_str = NULL; char *headln = NULL, *idxstr = NULL; char *branch = NULL, *type = NULL; char *uuid = NULL; wchar_t *wline; attr_t rx = A_BOLD; int ncommits = 0, rc = FSL_RC_OK, wlen = 0; int ncols_needed, maxlen = -1; if (s->selected_entry && !(view->searching != SEARCH_DONE && view->search_status == SEARCH_WAITING)) { uuid = fsl_strdup(s->selected_entry->commit->uuid); branch = fsl_strdup(s->selected_entry->commit->branch); type = fsl_strdup(s->selected_entry->commit->type); } if (tcx->ncommits_needed > 0 && !tcx->eotl) { if ((idxstr = fsl_mprintf(" [%d/%d] %s", entry ? entry->idx + 1 : 0, s->commits.ncommits, (view->searching && !view->search_status) ? "searching..." : "loading...")) == NULL) { |
︙ | ︙ | |||
2475 2476 2477 2478 2479 2480 2481 | == NULL) { rc = RC(FSL_RC_RANGE, "%s", "fsl_mprintf"); headln = NULL; goto end; } if (SPINNER[++tcx->spin_idx] == '\0') tcx->spin_idx = 0; | | | | | | | | | < | | | | > | | > | 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 | == NULL) { rc = RC(FSL_RC_RANGE, "%s", "fsl_mprintf"); headln = NULL; goto end; } if (SPINNER[++tcx->spin_idx] == '\0') tcx->spin_idx = 0; rc = formatln(&wline, &wlen, headln, view->ncols, 0, false); if (rc) goto end; werase(view->window); if (screen_is_shared(view) || view->active) rx |= A_REVERSE; if (s->colour) c = get_colour(&s->colours, FNC_COLOUR_COMMIT); if (c) rx |= COLOR_PAIR(c->scheme); wattron(view->window, rx); waddwstr(view->window, wline); while (wlen < view->ncols) { waddch(view->window, ' '); ++wlen; } wattroff(view->window, rx); fsl_free(wline); if (view->nlines <= 1) goto end; /* Parse commits to be written on screen for the longest username. */ entry = s->first_commit_onscreen; while (entry) { wchar_t *wstr; char *user; int wusrlen; if (ncommits >= view->nlines - 1) break; user = fsl_strdup(entry->commit->user); if (user == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } if (strpbrk(user, "<@>") != NULL) parse_emailaddr_username(&user); rc = formatln(&wstr, &wusrlen, user, view->ncols, 0, false); if (maxlen < wusrlen) maxlen = wusrlen; fsl_free(wstr); fsl_free(user); ++ncommits; entry = TAILQ_NEXT(entry, entries); } ncommits = 0; entry = s->first_commit_onscreen; s->last_commit_onscreen = s->first_commit_onscreen; while (entry) { if (ncommits >= MIN(view->nlines - 1, view->lines - 1)) break; if (ncommits == s->selected || entry->commit->rid == s->tagged.rid) wattr_on(view->window, A_REVERSE, NULL); rc = write_commit_line(view, entry->commit, maxlen); if (ncommits == s->selected || entry->commit->rid == s->tagged.rid) wattr_off(view->window, A_REVERSE, NULL); ++ncommits; s->last_commit_onscreen = entry; entry = TAILQ_NEXT(entry, entries); } drawborder(view); |
︙ | ︙ | |||
2566 2567 2568 2569 2570 2571 2572 | usr = *username; usr[strcspn(usr, "@>")] = '\0'; *username = usr; } static int | | | | > | | | < | < < < | > | 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 | usr = *username; usr[strcspn(usr, "@>")] = '\0'; *username = usr; } static int formatln(wchar_t **ptr, int *wstrlen, const char *mbstr, size_t column_limit, int start_column, bool expand) { wchar_t *wline = NULL; char *exstr = NULL; size_t i, sz, wlen; size_t cols = 0; int rc = FSL_RC_OK; *ptr = NULL; *wstrlen = 0; sz = fsl_strlen(mbstr); if (expand) expand_tab(&exstr, mbstr, sz); rc = multibyte_to_wchar(expand ? exstr : mbstr, &wline, &wlen); fsl_free(exstr); if (rc) return rc; if (wlen > 0 && wline[wlen - 1] == L'\n') { wline[wlen - 1] = L'\0'; wlen--; } |
︙ | ︙ | |||
2646 2647 2648 2649 2650 2651 2652 | /* * Copy the string src into the statically sized dst char array, and expand * any tab ('\t') characters found into the equivalent number of space (' ') * characters. Return number of bytes written to dst minus the terminating NUL. */ static size_t | | > | | > > > | | | > | 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 | /* * Copy the string src into the statically sized dst char array, and expand * any tab ('\t') characters found into the equivalent number of space (' ') * characters. Return number of bytes written to dst minus the terminating NUL. */ static size_t expand_tab(char **ptr, const char *src, int srclen) { char *dst; size_t n = srclen, sz = 0; int idx = 0; *ptr = NULL; dst = fsl_malloc((srclen + 1) * sizeof(char)); while (idx < srclen && src[idx]) { const char c = *(src + idx); if (c == '\t') { size_t spaces = TABSIZE - (sz % TABSIZE); n += spaces; dst = fsl_realloc(dst, n * sizeof(char)); memcpy(dst + sz, " ", spaces); sz += spaces; } else { dst[sz++] = src[idx]; } ++idx; } dst[sz] = '\0'; *ptr = dst; return sz; } static int multibyte_to_wchar(const char *src, wchar_t **dst, size_t *dstlen) { int rc = 0; |
︙ | ︙ | |||
2724 2725 2726 2727 2728 2729 2730 | * to limit commit comment summary lines to a maximum 50 characters, and most * plaintext-based conventions suggest not exceeding 72-80 characters. * * 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, | | | | | | | | | | | | | > | | | | | < | | | | | < | 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 | * to limit commit comment summary lines to a maximum 50 characters, and most * plaintext-based conventions suggest not exceeding 72-80 characters. * * 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 maxlen) { struct fnc_tl_view_state *s = &view->state.timeline; struct fnc_colour *c = NULL; wchar_t *wstr = NULL; char *comment0 = NULL, *comment = NULL; char *date = NULL; char *eol = NULL, *pad = NULL, *user = NULL; size_t i = 0; int col, limit, wlen; int rc = FSL_RC_OK; /* Trim time component from timestamp for the date field. */ date = fsl_strdup(commit->timestamp); while (!fsl_isspace(date[i++])) {} date[i] = '\0'; col = MIN(view->ncols, ISO8601_DATE_ONLY + 1); if (s->colour) c = get_colour(&s->colours, FNC_COLOUR_DATE); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddnstr(view->window, date, col); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (col > view->ncols) goto end; /* If enough columns, write abbreviated commit hash. */ if (view->ncols >= 110) { if (s->colour) c = get_colour(&s->colours, FNC_COLOUR_COMMIT); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); wprintw(view->window, "%.9s ", commit->uuid); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); col += 10; if (col > 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(&wstr, &wlen, user, view->ncols - col, col, false); if (rc) goto end; if (s->colour) c = get_colour(&s->colours, FNC_COLOUR_USER); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddwstr(view->window, wstr); fsl_free(wstr); pad = fsl_mprintf("%*c", maxlen - wlen + 2, ' '); waddstr(view->window, pad); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); col += (maxlen + 2); if (col > view->ncols) goto end; /* Only show comment up to the first newline character. */ comment0 = fsl_strdup(commit->comment); comment = comment0; if (comment == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); while (*comment == '\n') ++comment; eol = strchr(comment, '\n'); if (eol) *eol = '\0'; limit = view->ncols - col; rc = formatln(&wstr, &wlen, comment, limit, col, false); if (rc) goto end; waddwstr(view->window, wstr); col += wlen; while (col < view->ncols) { waddch(view->window, ' '); ++col; } end: fsl_free(date); fsl_free(user); fsl_free(wstr); fsl_free(pad); fsl_free(comment0); return rc; } static int view_input(struct fnc_view **new, int *done, struct fnc_view *view, struct view_tailhead *views) { |
︙ | ︙ | |||
3029 3030 3031 3032 3033 3034 3035 | {" N ", " ❬N❭ "}, {" q ", " ❬q❭ "}, {" Q ", " ❬Q❭ "}, {""}, {""}, /* Timeline */ {" <,, ", " ❬<❭❬,❭ "}, {" >,. ", " ❬>❭❬.❭ "}, | | > > > > > > < | 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 | {" N ", " ❬N❭ "}, {" q ", " ❬q❭ "}, {" Q ", " ❬Q❭ "}, {""}, {""}, /* Timeline */ {" <,, ", " ❬<❭❬,❭ "}, {" >,. ", " ❬>❭❬.❭ "}, {" Enter ", " ❬Enter❭ "}, {" Space ", " ❬Space❭ "}, {" b ", " ❬b❭ "}, {" C ", " ❬C❭ "}, {" F ", " ❬F❭ "}, {" t ", " ❬t❭ "}, {""}, {""}, /* Diff */ {" Space ", " ❬Space❭ "}, {" # ", " ❬#❭ "}, {" @ ", " ❬@❭ "}, {" $ ", " ❬$❭ "}, {" 0 ", " ❬0❭ "}, {" C-e ", " ❬C-e❭ "}, {" C-y ", " ❬C-y❭ "}, {" C-n ", " ❬C-n❭ "}, {" C-p ", " ❬C-p❭ "}, {" l<Right> ", " ❬l❭❬→❭ "}, {" h<Left> ", " ❬h❭❬←❭ "}, {" b ", " ❬b❭ "}, {" F ", " ❬F❭ "}, {" i ", " ❬i❭ "}, {" L ", " ❬L❭ "}, {" p ", " ❬p❭ "}, {" S ", " ❬S❭ "}, {" v ", " ❬v❭ "}, {" w ", " ❬w❭ "}, {" -,_ ", " ❬-❭❬_❭ "}, {" +,= ", " ❬+❭❬=❭ "}, {" C-k,K,<,, ", " ❬C-k❭❬K❭❬<❭❬,❭ "}, {" C-j,J,>,. ", " ❬C-j❭❬J❭❬>❭❬.❭ "}, {""}, {""}, /* Tree */ {" l,Enter,<Right> ", " ❬→❭❬l❭❬Enter❭ "}, {" h,<BS>,<Left> ", " ❬←❭❬h❭❬⌫❭ "}, {" b ", " ❬b❭ "}, {" d ", " ❬d❭ "}, {" i ", " ❬i❭ "}, {" t ", " ❬t❭ "}, {""}, {""}, /* Blame */ {" Space ", " ❬Space❭ "}, {" Enter ", " ❬Enter❭ "}, {" # ", " ❬#❭ "}, {" @ ", " ❬@❭ "}, {" $ ", " ❬$❭ "}, {" 0 ", " ❬0❭ "}, {" l<Right> ", " ❬l❭❬→❭ "}, {" h<Left> ", " ❬h❭❬←❭ "}, {" b ", " ❬b❭ "}, {" p ", " ❬p❭ "}, {" B ", " ❬B❭ "}, {" T ", " ❬T❭ "}, {""}, {""}, /* Branch */ {" Enter,Space ", " ❬Enter❭❬Space❭ "}, |
︙ | ︙ | |||
3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 | "Quit the active view", "Quit the program", "", "Timeline", "Move selection cursor up one commit", "Move selection cursor down one commit", "Open diff view of the selected commit", "Open and populate branch view with all repository branches", "Open prompt to enter term with which to filter new timeline view", "Display a tree reflecting the state of the selected commit", "", "Diff", "Scroll down one page of diff output", "Toggle display of diff view line numbers", "Scroll the view right to the end of the longest line", "Scroll the view left to the beginning of the line", "Scroll the view down in the buffer", "Scroll the view up in the buffer", "Navigate to next file in the diff", "Navigate to previous file in the diff", "Scroll the view right", "Scroll the view left", "Open and populate branch view with all repository branches", "Open prompt to enter file number and navigate to file", "Toggle inversion of diff output", | > > > | > > > < | 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 | "Quit the active view", "Quit the program", "", "Timeline", "Move selection cursor up one commit", "Move selection cursor down one commit", "Open diff view of the selected commit", "(Un)tag (or diff) the selected (against the tagged) commit", "Open and populate branch view with all repository branches", "Diff local changes in the checkout against selected commit", "Open prompt to enter term with which to filter new timeline view", "Display a tree reflecting the state of the selected commit", "", "Diff", "Scroll down one page of diff output", "Toggle display of diff view line numbers", "Open prompt to enter line number and navigate to line", "Scroll the view right to the end of the longest line", "Scroll the view left to the beginning of the line", "Scroll the view down in the buffer", "Scroll the view up in the buffer", "Navigate to next file in the diff", "Navigate to previous file in the diff", "Scroll the view right", "Scroll the view left", "Open and populate branch view with all repository branches", "Open prompt to enter file number and navigate to file", "Toggle inversion of diff output", "Toggle display of file line numbers", "Toggle display of function name in chunk header", "Display side-by-side formatted diff", "Toggle verbosity of diff output", "Toggle ignore whitespace-only changes in diff", "Decrease the number of context lines", "Increase the number of context lines", "Display diff of next (newer) commit in the timeline", "Display diff of previous (older) commit in the timeline", "", "Tree", "Move into the selected directory", "Return to the parent directory", "Open and populate branch view with all repository branches", "Toggle ISO8601 modified timestamp display for each tree entry", "Toggle display of file artifact SHA hash ID", "Display timeline of all commits modifying the selected entry", "", "Blame", "Scroll down one page", "Display the diff of the commit corresponding to the selected line", "Toggle display of file line numbers", "Open prompt to enter line number and navigate to line", "Scroll the view right to the end of the longest line", "Scroll the view left to the beginning of the line", "Scroll the view right", "Scroll the view left", "Blame the version of the file found in the selected line's commit", "Blame the version of the file found in the selected line's parent " "commit", "Reload the previous blamed version of the file", "Open and populate branch view with all repository branches", "", "Branch", |
︙ | ︙ | |||
3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 | static int tl_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch) { struct fnc_tl_view_state *s = &view->state.timeline; int rc = FSL_RC_OK; uint16_t nscroll = view->nlines - 2; switch (ch) { case KEY_DOWN: case 'j': case '.': case '>': rc = move_tl_cursor_down(view, 0); break; | > > | 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 | static int tl_input_handler(struct fnc_view **new_view, struct fnc_view *view, int ch) { struct fnc_tl_view_state *s = &view->state.timeline; int rc = FSL_RC_OK; uint16_t nscroll = view->nlines - 2; free_tags(s, true); switch (ch) { case KEY_DOWN: case 'j': case '.': case '>': rc = move_tl_cursor_down(view, 0); break; |
︙ | ︙ | |||
3384 3385 3386 3387 3388 3389 3390 | if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: move_tl_cursor_up(view, false, true); break; case KEY_RESIZE: | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > | | | < < | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | 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 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 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 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 | if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: move_tl_cursor_up(view, false, true); break; case KEY_RESIZE: if (s->selected > view->nlines - 2) s->selected = view->nlines - 2; if (s->selected > s->commits.ncommits - 1) s->selected = s->commits.ncommits - 1; select_commit(s); if (s->commits.ncommits < view->nlines - 1 && !s->thread_cx.eotl) { s->thread_cx.ncommits_needed += (view->nlines - 1) - s->commits.ncommits; rc = signal_tl_thread(view, 1); } break; case 'C': { fsl_cx *const f = fcli_cx(); /* * XXX This is not good but I can't think of an alternative * without patching libf: fsl_ckout_changes_scan() returns a * db lock error via fsl_vfile_changes_scan() when versioned * files are modified in-session. Clear it and notify user. */ rc = fsl_ckout_changes_scan(f); if (rc == FSL_RC_DB) { rc = sitrep(view, "-- checkout db busy --", SR_CLREOL | SR_UPDATE | SR_SLEEP | SR_RESET); break; } else if (rc) return RC(rc, "%s", "fsl_ckout_changes_scan"); if (!fsl_ckout_has_changes(f)) { sitrep(view, "-- no local changes --", SR_CLREOL | SR_UPDATE | SR_SLEEP); break; } s->selected_entry->commit->diff_type = FNC_DIFF_CKOUT; } /* FALL THROUGH */ case ' ': if (!tagged_commit(s)) break; /* FALL THROUGH */ case KEY_ENTER: case '\r': rc = request_view(new_view, view, FNC_VIEW_DIFF); break; case 'b': rc = request_view(new_view, view, FNC_VIEW_BRANCH); break; case 'c': s->colour = !s->colour; break; case 'F': { struct input input = {NULL, "filter: ", INPUT_ALPHA, SR_CLREOL}; rc = fnc_prompt_input(view, &input); if (rc) return rc; s->glob = input.buf; rc = request_view(new_view, view, FNC_VIEW_TIMELINE); if (rc == FSL_RC_BREAK) { rc = sitrep(view, "-- no matching commits --", SR_CLREOL | SR_UPDATE | SR_SLEEP | SR_RESET); } break; } case 't': if (s->selected_entry == NULL) break; if (!fsl_rid_is_a_checkin(fcli_cx(), s->selected_entry->commit->rid)) sitrep(view, "-- tree requires check-in artifact --", SR_CLREOL | SR_UPDATE | SR_SLEEP); else rc = request_view(new_view, view, FNC_VIEW_TREE); break; case 'q': s->quit = 1; break; default: break; } return rc; } static bool tagged_commit(struct fnc_tl_view_state *s) { /* If a commit is not already tagged, tag the selected commit. */ if (!s->tagged.rid) { /* Only tag checkin and wiki commits */ if (strcmp(s->selected_entry->commit->type, "checkin") && strcmp(s->selected_entry->commit->type, "wiki")) return false; s->tagged.rid = s->selected_entry->commit->rid; s->tagged.id = fsl_strdup(s->selected_entry->commit->uuid); s->tagged.type = fsl_strdup(s->selected_entry->commit->type); if (s->selected_entry->commit->diff_type != FNC_DIFF_CKOUT) return false; } else if (fsl_strcmp(s->selected_entry->commit->type, s->tagged.type)) return false; /* Can't diff different types */ else if (s->selected_entry->commit->rid == s->tagged.rid) { /* Untag currently tagged commit. */ fsl_free(s->tagged.id); s->tagged.id = NULL; fsl_free(s->tagged.type); s->tagged.type = NULL; s->tagged.rid = 0; return false; } /* A commit is tagged, diff selected commit against it. */ if (s->selected_entry->commit->prid != s->tagged.rid) s->showmeta = false; s->tagged.ogid = fsl_strdup(s->selected_entry->commit->puuid); s->tagged.ogrid = s->selected_entry->commit->prid; fsl_free(s->selected_entry->commit->puuid); s->selected_entry->commit->puuid = fsl_strdup(s->tagged.id); s->selected_entry->commit->prid = s->tagged.rid; s->tagged.rid = 0; /* Don't continue highlighting tagged commit. */ return true; } static int move_tl_cursor_down(struct fnc_view *view, uint16_t page) { struct fnc_tl_view_state *s = &view->state.timeline; struct commit_entry *first; int rc = FSL_RC_OK; first = s->first_commit_onscreen; if (first == NULL) return rc; if (s->thread_cx.eotl && s->selected_entry->idx >= s->commits.ncommits - 1) return rc; /* Last commit already selected. */ if (!page) { /* Still more commits on this page to scroll down. */ if (s->selected < MIN(view->nlines - 2, s->commits.ncommits - 1)) ++s->selected; else /* Last commit on screen is selected, need to scroll. */ rc = timeline_scroll_down(view, 1); } else if (s->thread_cx.eotl) { /* Last displayed commit is the end, jump to it. */ if (s->last_commit_onscreen->idx == s->commits.ncommits - 1) s->selected += MIN(s->last_commit_onscreen->idx - s->selected_entry->idx, page + 1); else /* Scroll the page. */ rc = timeline_scroll_down(view, MIN(page, s->commits.ncommits - s->selected_entry->idx - 1)); } else { rc = timeline_scroll_down(view, page); if (rc) return rc; if (first == s->first_commit_onscreen && s->selected < MIN(view->nlines - 2, s->commits.ncommits - 1)) { /* End of timeline, no more commits; move cursor down */ s->selected = MIN(s->commits.ncommits - 1, page); } /* * If we've overshot (necessarily possible with horizontal * splits), select the final commit. */ s->selected = MIN(s->selected, s->last_commit_onscreen->idx - s->first_commit_onscreen->idx); } if (!rc) select_commit(s); return rc; } static void move_tl_cursor_up(struct fnc_view *view, uint16_t page, bool home) { struct fnc_tl_view_state *s = &view->state.timeline; if (s->first_commit_onscreen == NULL) return; if ((page && TAILQ_FIRST(&s->commits.head) == s->first_commit_onscreen) || home) s->selected = home ? 0 : MAX(0, s->selected - page - 1); if (!page && !home && s->selected > 0) --s->selected; else timeline_scroll_up(s, home ? s->commits.ncommits : MAX(page, 1)); select_commit(s); return; } |
︙ | ︙ | |||
3569 3570 3571 3572 3573 3574 3575 | enum fnc_view_id request, int x, int y) { int rc = FSL_RC_OK; switch (request) { case FNC_VIEW_DIFF: { struct fnc_tl_view_state *s = &view->state.timeline; | | | | | | | 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 | enum fnc_view_id request, int x, int y) { int rc = FSL_RC_OK; switch (request) { case FNC_VIEW_DIFF: { struct fnc_tl_view_state *s = &view->state.timeline; if (s->selected_entry == NULL) break; rc = init_diff_view(new_view, x, y, s->selected_entry->commit, view, s->showmeta); break; } case FNC_VIEW_BLAME: { struct fnc_tree_view_state *s = &view->state.tree; rc = blame_tree_entry(new_view, x, y, s->selected_entry, &s->parents, s->commit_id); break; } case FNC_VIEW_TIMELINE: { const char *glob = NULL; int rid = 0; if (view->vid == FNC_VIEW_TIMELINE) glob = view->state.timeline.glob; else if (view->vid == FNC_VIEW_BRANCH) rid = fsl_uuid_to_rid(fcli_cx(), view->state.branch.selected_entry->branch->id); rc = init_timeline_view(new_view, x, y, rid, "/", glob); break; } case FNC_VIEW_TREE: { struct fnc_tl_view_state *s = &view->state.timeline; rc = browse_commit_tree(new_view, x, y, s->selected_entry, s->path); break; } case FNC_VIEW_BRANCH: { *new_view = view_open(0, 0, y, x, FNC_VIEW_BRANCH); if (*new_view == NULL) return RC(FSL_RC_ERROR, "%s", "view_open"); |
︙ | ︙ | |||
3622 3623 3624 3625 3626 3627 3628 | * or set to auto, determine vertical or horizontal split depending on screen * estate. If set to 'v' or 'h', assign start column or start line of the split * view to *start_col and *start_ln, respectively, and return split mode. */ static enum view_mode view_get_split(struct fnc_view *view, int *start_col, int *start_ln) { | | | > | 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 | * or set to auto, determine vertical or horizontal split depending on screen * estate. If set to 'v' or 'h', assign start column or start line of the split * view to *start_col and *start_ln, respectively, and return split mode. */ static enum view_mode view_get_split(struct fnc_view *view, int *start_col, int *start_ln) { char *mode = fnc_conf_getopt(FNC_VIEW_SPLIT_MODE, false); enum view_mode vm; if (!mode || mode[0] != 'h') { vm = VIEW_SPLIT_VERT; *start_col = view_split_start_col(view->start_col); } if (!*start_col && (!mode || mode[0] != 'v')) { vm = VIEW_SPLIT_HRZN; *start_ln = view_split_start_ln(view->lines); } fsl_free(mode); return vm; } /* Split view horizontally at *start_ln and offset view->state->selected line */ static int split_view(struct fnc_view *view, int *start_ln) { |
︙ | ︙ | |||
3671 3672 3673 3674 3675 3676 3677 | int *selected; switch (view->vid) { case FNC_VIEW_TIMELINE: { struct fnc_tl_view_state *s = &view->state.timeline; scrolld = &timeline_scroll_down; header = 2; | | | | 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 | int *selected; switch (view->vid) { case FNC_VIEW_TIMELINE: { struct fnc_tl_view_state *s = &view->state.timeline; scrolld = &timeline_scroll_down; header = 2; selected = &s->selected; break; } case FNC_VIEW_TREE: { struct fnc_tree_view_state *s = &view->state.tree; scrolld = &tree_scroll_down; header = 4; selected = &s->selected; break; } case FNC_VIEW_BRANCH: { struct fnc_branch_view_state *s = &view->state.branch; scrolld = &branch_scroll_down; header = 1; selected = &s->selected; |
︙ | ︙ | |||
3772 3773 3774 3775 3776 3777 3778 | select_commit(struct fnc_tl_view_state *s) { struct commit_entry *entry; int ncommits = 0; entry = s->first_commit_onscreen; while (entry) { | | | | 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 | select_commit(struct fnc_tl_view_state *s) { struct commit_entry *entry; int ncommits = 0; entry = s->first_commit_onscreen; while (entry) { if (ncommits == s->selected) { s->selected_entry = entry; break; } entry = TAILQ_NEXT(entry, entries); ++ncommits; } } |
︙ | ︙ | |||
3878 3879 3880 3881 3882 3883 3884 | fsl_free(height); return !rc && n && n < (lines - 2) ? lines - n : lines * HSPLIT_SCALE; } static int view_search_start(struct fnc_view *view) { | | | 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 | fsl_free(height); return !rc && n && n < (lines - 2) ? lines - n : lines * HSPLIT_SCALE; } static int view_search_start(struct fnc_view *view) { struct input input = {NULL, "/", INPUT_ALPHA, SR_CLREOL}; int rc = FSL_RC_OK; if (view->started_search) { regfree(&view->regex); view->searching = SEARCH_DONE; memset(&view->regmatch, 0, sizeof(view->regmatch)); } |
︙ | ︙ | |||
4012 4013 4014 4015 4016 4017 4018 | if (view->searching == SEARCH_FORWARD) entry = TAILQ_NEXT(entry, entries); else entry = TAILQ_PREV(entry, commit_tailhead, entries); } if (s->matched_commit) { | | | 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 | if (view->searching == SEARCH_FORWARD) entry = TAILQ_NEXT(entry, entries); else entry = TAILQ_PREV(entry, commit_tailhead, entries); } if (s->matched_commit) { int cur = s->selected_entry->idx; while (cur < s->matched_commit->idx) { if ((rc = tl_input_handler(NULL, view, KEY_DOWN))) return rc; ++cur; } while (cur > s->matched_commit->idx) { if ((rc = tl_input_handler(NULL, view, KEY_UP))) |
︙ | ︙ | |||
4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 | static int view_close(struct fnc_view *view) { int rc = FSL_RC_OK; if (view->child) { view_close(view->child); view->child = NULL; } if (view->close) rc = view->close(view); if (view->panel) del_panel(view->panel); if (view->window) delwin(view->window); fsl_free(view); 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); fsl_stmt_finalize(s->thread_cx.q); fnc_free_commits(&s->commits); free_colours(&s->colours); | > > > < | 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 | static int view_close(struct fnc_view *view) { int rc = FSL_RC_OK; if (view->child) { regfree(&view->child->regex); view_close(view->child); view->child = NULL; } regfree(&view->regex); if (view->close) rc = view->close(view); if (view->panel) del_panel(view->panel); if (view->window) delwin(view->window); fsl_free(view); 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); fsl_stmt_finalize(s->thread_cx.q); free_tags(s, true); /* Must be before fnc_free_commits() */ fnc_free_commits(&s->commits); free_colours(&s->colours); fsl_free(s->path); s->path = NULL; return rc; } /* static void */ |
︙ | ︙ | |||
4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 | --commits->ncommits; } } static void fnc_commit_artifact_close(struct fnc_commit_artifact *commit) { if (commit->branch) fsl_free(commit->branch); if (commit->comment) fsl_free(commit->comment); if (commit->timestamp) fsl_free(commit->timestamp); if (commit->type) fsl_free(commit->type); if (commit->user) fsl_free(commit->user); fsl_free(commit->uuid); fsl_free(commit->puuid); fsl_list_clear(&commit->changeset, fsl_file_artifact_free, NULL); fsl_list_reserve(&commit->changeset, 0); fsl_free(commit); } static int fsl_file_artifact_free(void *elem, void *state) { struct fsl_file_artifact *ffa = elem; fsl_free(ffa->fc->name); fsl_free(ffa->fc->uuid); fsl_free(ffa->fc->priorName); fsl_free(ffa->fc); fsl_free(ffa); return 0; } static int init_diff_view(struct fnc_view **new_view, int start_col, int start_ln, | > > > | > | | > | | < | > > | | | > > > | 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 | --commits->ncommits; } } static void fnc_commit_artifact_close(struct fnc_commit_artifact *commit) { if (!commit) return; if (commit->branch) fsl_free(commit->branch); if (commit->comment) fsl_free(commit->comment); if (commit->timestamp) fsl_free(commit->timestamp); if (commit->type) fsl_free(commit->type); if (commit->user) fsl_free(commit->user); fsl_free(commit->uuid); fsl_free(commit->puuid); fsl_list_clear(&commit->changeset, fsl_file_artifact_free, NULL); fsl_list_reserve(&commit->changeset, 0); fsl_free(commit); commit = NULL; } static int fsl_file_artifact_free(void *elem, void *state) { struct fsl_file_artifact *ffa = elem; fsl_free(ffa->fc->name); fsl_free(ffa->fc->uuid); fsl_free(ffa->fc->priorName); fsl_free(ffa->fc); fsl_free(ffa); return 0; } static int init_diff_view(struct fnc_view **new_view, int start_col, int start_ln, struct fnc_commit_artifact *commit, struct fnc_view *timeline_view, bool showmeta) { struct fnc_view *diff_view; int rc = 0; diff_view = view_open(0, 0, start_ln, start_col, FNC_VIEW_DIFF); if (diff_view == NULL) return RC(FSL_RC_ERROR, "%s", "view_open"); rc = open_diff_view(diff_view, commit, DEF_DIFF_CTX, fnc_init.sbs, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, fnc_init.showln, timeline_view, showmeta, NULL); if (!rc) *new_view = diff_view; return rc; } static int open_diff_view(struct fnc_view *view, struct fnc_commit_artifact *commit, int context, bool sbs, bool ignore_ws, bool invert, bool verbosity, bool showln, struct fnc_view *timeline_view, bool showmeta, struct fnc_pathlist_head *paths) { struct fnc_diff_view_state *s = &view->state.diff; char *opt; int rc = 0; opt = fnc_conf_getopt(FNC_COLOUR_HL_LINE, false); if (!fsl_stricmp(opt, "mono")) s->sline = SLINE_MONO; fsl_free(opt); s->index.n = 0; s->index.idx = 0; s->paths = paths; s->selected_entry = commit; s->first_line_onscreen = 1; s->last_line_onscreen = view->nlines; s->selected_line = 1; s->f = NULL; s->context = context; s->sbs = 0; FLAG_SET(s->diff_flags, FNC_DIFF_PROTOTYPE); sbs ? FLAG_SET(s->diff_flags, FNC_DIFF_SIDEBYSIDE) : 0; verbosity ? FLAG_SET(s->diff_flags, FNC_DIFF_VERBOSE) : 0; ignore_ws ? FLAG_SET(s->diff_flags, FNC_DIFF_IGNORE_ALLWS) : 0; invert ? FLAG_SET(s->diff_flags, FNC_DIFF_INVERT) : 0; showln ? FLAG_SET(s->diff_flags, FNC_DIFF_LINENO) : 0; s->timeline_view = timeline_view; s->colour = !fnc_init.nocolour && has_colors(); s->showmeta = showmeta; if (s->colour) { STAILQ_INIT(&s->colours); rc = set_colours(&s->colours, FNC_VIEW_DIFF); if (rc) return rc; } if (timeline_view && screen_is_split(view)) show_timeline_view(timeline_view); /* draw vborder */ show_diff_status(view); s->line_offsets = NULL; s->nlines = 0; s->dlines = NULL; s->ndlines = 0; s->ncols = view->ncols; rc = create_diff(s); if (rc) { if (s->colour) free_colours(&s->colours); return rc; } |
︙ | ︙ | |||
4276 4277 4278 4279 4280 4281 4282 | } static int create_diff(struct fnc_diff_view_state *s) { FILE *fout = NULL; char *line, *st0 = NULL, *st = NULL; | | > > > > > > > > > > > | > | | | > > | | < | | | | < | > | > | < < | | | | | | | | 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 | } static int create_diff(struct fnc_diff_view_state *s) { FILE *fout = NULL; char *line, *st0 = NULL, *st = NULL; off_t off = 0; uint32_t idx = 0; int rc = 0; s->maxx = 0; free(s->dlines); s->dlines = fsl_malloc(sizeof(enum line_type *)); if (s->dlines == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); s->ndlines = 0; free(s->line_offsets); s->line_offsets = fsl_malloc(sizeof(off_t)); if (s->line_offsets == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); s->nlines = 0; fout = tmpfile(); if (fout == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_IO), "%s", "tmpfile"); goto end; } if (s->f && fclose(s->f) == EOF) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_IO), "%s", "fclose"); goto end; } s->f = fout; rc = add_line_offset(&s->line_offsets, &s->nlines, 0); if (rc) goto end; /* * We'll diff artifacts of type "ci" (i.e., "checkin") separately, as * it's a different process to diff the others (wiki, technote, etc.). */ if (s->selected_entry->diff_type == FNC_DIFF_COMMIT && !s->selected_entry->changeset.used) rc = create_changeset(s->selected_entry); else if (s->selected_entry->diff_type == FNC_DIFF_BLOB) rc = diff_file_artifact(s, s->selected_entry->prid, NULL, NULL, FSL_CKOUT_CHANGE_MOD); if (!rc && s->showmeta) rc = write_commit_meta(s); if (!rc && s->selected_entry->diff_type == FNC_DIFF_WIKI) rc = diff_non_checkin(s); if (rc) goto end; /* * Delay assigning diff headline labels (i.e., diff id1 id2) till now * because wiki parent commits are obtained in diff_non_checkin(). */ if (s->selected_entry->puuid) { fsl_free(s->id1); s->id1 = fsl_strdup(s->selected_entry->puuid); if (s->id1 == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } } else s->id1 = NULL; /* Initial commit, tag, technote, etc. */ if (s->selected_entry->uuid) { fsl_free(s->id2); s->id2 = fsl_strdup(s->selected_entry->uuid); if (s->id2 == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } } else s->id2 = NULL; /* Local work tree. */ if (!s->showmeta) { s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(off_t)); s->index.offset[s->index.n++] = 0; } /* * 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_entry->diff_type == FNC_DIFF_COMMIT) diff_commit(s); else if (s->selected_entry->diff_type == FNC_DIFF_CKOUT) diff_checkout(s); /* * 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; off = (s->line_offsets)[s->nlines - 1]; s->index.lineno = fsl_malloc(s->index.n * sizeof(size_t)); while ((line = fnc_strsep(&st, "\n")) != NULL) { int lineno, n = fprintf(s->f, "%s\n", line); s->maxx = MAX(s->maxx, n); if (s->index.offset && idx < s->index.n && off == s->index.offset[idx]) { lineno = s->nlines + (idx ? 1 : 0); s->index.lineno[idx++] = lineno; } off += n; rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; } --s->nlines; /* Don't count EOF '\n' */ end: fsl_free(st0); fsl_buffer_clear(&s->buf); |
︙ | ︙ | |||
4411 4412 4413 4414 4415 4416 4417 | 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; */ path = fsl_stmt_g_text(st, 0, NULL); /* Current filename. */ | | | 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 | 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; */ path = fsl_stmt_g_text(st, 0, NULL); /* Current filename. */ /* perm = fsl_stmt_g_int32(st, 1); */ /* File permissions. */ olduuid = fsl_stmt_g_text(st, 2, NULL); /* UUID before change */ uuid = fsl_stmt_g_text(st, 3, NULL); /* UUID after change. */ oldpath = fsl_stmt_g_text(st, 4, NULL); /* Old name, if chngd */ fdiff = fsl_malloc(sizeof(struct fsl_file_artifact)); fdiff->fc = fsl_malloc(sizeof(fsl_card_F)); *fdiff->fc = fsl_card_F_empty; |
︙ | ︙ | |||
4451 4452 4453 4454 4455 4456 4457 | } static int write_commit_meta(struct fnc_diff_view_state *s) { char *line = NULL, *st0 = NULL, *st = NULL; fsl_size_t linelen, idx = 0; | | > | > > | | | > > | > | | > > | > | | | > > | > | | > > | > | > > | > | | | > > > | | > | > > | > | | | | 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 | } static int write_commit_meta(struct fnc_diff_view_state *s) { char *line = NULL, *st0 = NULL, *st = NULL; fsl_size_t linelen, idx = 0; off_t off = 0; int rc = FSL_RC_OK, n = 7; /* Min lines in commit meta */ n += countlines(s->selected_entry->comment); n += s->selected_entry->changeset.used; if ((n = fprintf(s->f,"%s %s\n", s->selected_entry->type, s->selected_entry->uuid)) < 0) goto end; off += n; rc = add_line_type(&s->dlines, LINE_DIFF_META, &s->ndlines, n, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; if ((n = fprintf(s->f,"user: %s\n", s->selected_entry->user)) < 0) goto end; off += n; rc = add_line_type(&s->dlines, LINE_DIFF_USER, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; if ((n = fprintf(s->f,"tags: %s\n", s->selected_entry->branch ? s->selected_entry->branch : "/dev/null")) < 0) goto end; off += n; rc = add_line_type(&s->dlines, LINE_DIFF_TAGS, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; if ((n = fprintf(s->f,"date: %s\n", s->selected_entry->timestamp)) < 0) goto end; off += n; rc = add_line_type(&s->dlines, LINE_DIFF_DATE, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; fputc('\n', s->f); ++off; rc = add_line_type(&s->dlines, LINE_BLANK, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; st0 = fsl_strdup(s->selected_entry->comment); st = st0; if (st == NULL) { RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } while ((line = fnc_strsep(&st, "\n")) != NULL) { linelen = fsl_strlen(line); if (linelen >= s->ncols) { rc = wrapline(line, s->ncols - LINENO_WIDTH, s, &off); if (rc) goto end; } else { if ((n = fprintf(s->f, "%s\n", line)) < 0) goto end; off += n; rc = add_line_type(&s->dlines, LINE_DIFF_COMMENT, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; } } fputc('\n', s->f); ++off; rc = add_line_type(&s->dlines, LINE_BLANK, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; if (s->selected_entry->diff_type == FNC_DIFF_WIKI) goto end; /* No changeset for wiki commits. */ for (idx = 0; idx < s->selected_entry->changeset.used; ++idx) { char *changeline; struct fsl_file_artifact *file_change; file_change = s->selected_entry->changeset.list[idx]; switch (file_change->change) { case FSL_CKOUT_CHANGE_MOD: changeline = "[~] "; break; case FSL_CKOUT_CHANGE_ADDED: changeline = "[+] "; |
︙ | ︙ | |||
4545 4546 4547 4548 4549 4550 4551 | default: changeline = "[!] "; break; } if ((n = fprintf(s->f, "%s%s\n", changeline, file_change->fc->name)) < 0) goto end; | | > > > | > | > > | > > | | > > > > > > > > > > > > | | | | | | > > > | | | | > > | < | > > > > > > > > > > > > > > > > > > > > | | | | | 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 | default: changeline = "[!] "; break; } if ((n = fprintf(s->f, "%s%s\n", changeline, file_change->fc->name)) < 0) goto end; off += n; rc = add_line_type(&s->dlines, LINE_DIFF_META, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; } /* Add blank line between end of changeset and diff. */ fputc('\n', s->f); ++off; rc = add_line_type(&s->dlines, LINE_BLANK, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, off); if (rc) goto end; s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(off_t)); s->index.offset[s->index.n++] = off; end: free(st0); free(line); if (rc) { free(*&s->line_offsets); s->line_offsets = NULL; s->nlines = 0; } return rc; } static int countlines(const char *str) { int n, idx; for (idx = 0, n = 1; str[idx]; ++idx) if (str[idx] == '\n') ++n; return str[idx - 1] == '\n' ? n : ++n; } /* * Wrap long lines at the terminal's available column width. The caller * must ensure the limit parameter has taken into account whether the * screen is currently split, and not mistakenly pass in the curses COLS macro * without deducting the parent panel's width. This function doesn't break * words, and will wrap at the end of the last word that can wholly fit within * the limit limit. */ static int wrapline(char *line, fsl_size_t limit, struct fnc_diff_view_state *s, off_t *off) { char *word; fsl_size_t wordlen, cursor = 0; int n = 0, rc = 0; while ((word = fnc_strsep(&line, " ")) != NULL) { wordlen = fsl_strlen(word); if ((cursor + wordlen) >= limit) { fputc('\n', s->f); ++(*off); rc = add_line_type(&s->dlines, LINE_DIFF_COMMENT, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, *off); if (rc) return rc; cursor = 0; } if ((n = fprintf(s->f, "%s ", word)) < 0) return rc; *off += n; cursor += n; } fputc('\n', s->f); ++(*off); rc = add_line_type(&s->dlines, LINE_DIFF_COMMENT, &s->ndlines, 0, true); if (!rc) rc = add_line_offset(&s->line_offsets, &s->nlines, *off); return rc; } static int add_line_offset(off_t **line_offsets, size_t *nlines, off_t off) { off_t *p; p = fsl_realloc(*line_offsets, (*nlines + 1) * sizeof(off_t)); if (p == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_realloc"); *line_offsets = p; (*line_offsets)[*nlines] = off; (*nlines)++; return 0; } static int add_line_type(enum line_type **lines, enum line_type type, uint32_t *nlines, uint32_t alloc, bool incr) { if (alloc) { enum line_type *p; p = fsl_realloc(*lines, (*nlines + alloc) * sizeof(enum line_type *)); if (p == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_realloc"); *lines = p; } (*lines)[*nlines] = type; if (incr) (*nlines)++; return FSL_RC_OK; } /* * Fill the buffer with the differences between commit->uuid and commit->puuid. * commit->rid (to load into deck d2) is the *this* version, and commit->puuid * (to be loaded into deck d1) is the version we diff against. Step through the * deck of F(ile) cards from both versions to determine: (1) if we have new * files added (i.e., no F card counterpart in d1); (2) files deleted (i.e., no * F card counterpart in d2); (3) or otherwise the same file (i.e., F card * exists in both d1 and d2). In cases (1) and (2), we call diff_file_artifact() * to dump the complete content of the added/deleted file if FNC_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(struct fnc_diff_view_state *s) { fsl_cx *const 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, s->selected_entry->rid, FSL_SATYPE_CHECKIN); if (rc) goto end; rc = fsl_deck_F_rewind(&d2); if (rc) goto end; /* * For the one-and-only special case of repositories, such as the * canonical fnc, that do not have an "initial empty check-in", we * proceed with no parent version to diff against. */ if (s->selected_entry->puuid) { rc = fsl_sym_to_rid(f, s->selected_entry->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); |
︙ | ︙ | |||
4764 4765 4766 4767 4768 4769 4770 | fsl_buffer sql, abspath, bminus; fsl_uuid_str xminus = NULL; fsl_id_t cid, vid; int rc = 0; bool allow_symlinks; abspath = bminus = sql = fsl_buffer_empty; | | < < < < < < | 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 | fsl_buffer sql, abspath, bminus; fsl_uuid_str xminus = NULL; fsl_id_t cid, vid; int rc = 0; bool allow_symlinks; abspath = bminus = sql = fsl_buffer_empty; vid = s->selected_entry->prid; fsl_ckout_version_info(f, &cid, NULL); /* * If a previous version is supplied, load its vfile state to query * changes. Otherwise query the current checkout state for changes. */ if (vid != cid) { /* Keep vfile ckout state; but unload vid when finished. */ |
︙ | ︙ | |||
4889 4890 4891 4892 4893 4894 4895 | } if (!xminus) xminus = fsl_strdup(NULL_DEVICE); allow_symlinks = fsl_config_get_bool(f, FSL_CONFDB_REPO, false, "allow-symlinks"); if (!symlink != !(fsl_is_symlink(fsl_buffer_cstr(&abspath)) && allow_symlinks)) { | | | | 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 | } if (!xminus) xminus = fsl_strdup(NULL_DEVICE); allow_symlinks = fsl_config_get_bool(f, FSL_CONFDB_REPO, false, "allow-symlinks"); if (!symlink != !(fsl_is_symlink(fsl_buffer_cstr(&abspath)) && allow_symlinks)) { rc = write_diff_meta(s, path, xminus, path, NULL_DEVICE, change); fsl_buffer_append(&s->buf, "\nSymbolic links cannot be diffed\n", -1); if (rc) goto yield; continue; } if (fid > 0 && change != FSL_CKOUT_CHANGE_ADDED) { |
︙ | ︙ | |||
4957 4958 4959 4960 4961 4962 4963 | * xminus hex hash of file named zminus * zplus file name of the file being diffed * xplus hex hash of the file named zplus * diff_flags bitwise flags to control the diff * change enum denoting the versioning change of the file */ static int | | | < > | 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 | * xminus hex hash of file named zminus * zplus file name of the file being diffed * xplus hex hash of the file named zplus * diff_flags bitwise flags to control the diff * change enum denoting the versioning change of the file */ static int write_diff_meta(struct fnc_diff_view_state *s, const char *zminus, fsl_uuid_str xminus, const char *zplus, fsl_uuid_str xplus, enum fsl_ckout_change_e change) { const char *index, *plus, *minus; int rc = FSL_RC_OK; index = zplus ? zplus : (zminus ? zminus : NULL_DEVICE); switch (change) { case FSL_CKOUT_CHANGE_MERGE_ADD: /* FALL THROUGH */ case FSL_CKOUT_CHANGE_INTEGRATE_ADD: |
︙ | ︙ | |||
4993 4994 4995 4996 4997 4998 4999 | /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } | | | > > > > > > > | | | | > > > < < < | 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 | /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } if FLAG_CHK(s->diff_flags, FNC_DIFF_INVERT) { const char *tmp = minus; minus = plus; plus = tmp; tmp = zminus; zminus = zplus; zplus = tmp; } if (!FLAG_CHK(s->diff_flags, FNC_DIFF_BRIEF)) { int c, i; for (c = 0, i = 10; !rc && i < 14; ++c) { rc = add_line_type(&s->dlines, (enum line_type)i, &s->ndlines, (!c ? 6 : 0), true); i += (c > 3 || c % 2) ? 1 : 0; } if (!rc) rc = fsl_buffer_appendf(&s->buf, "%sIndex: %s\n%.71c\n", s->buf.used ? "\n" : "", index, '='); if (!rc) rc = fsl_buffer_appendf(&s->buf, "hash - %s\nhash + %s\n", minus, plus); if (!rc) rc = fsl_buffer_appendf(&s->buf, "--- %s\n+++ %s\n", zminus, zplus); } return rc; } /* * The diff_file_artifact() counterpart that diffs actual files on disk rather * than file artifacts in the Fossil repository's blob table. * buf output buffer in which diff output will be appended * bminus blob containing content of the versioned file being diffed against * zminus filename of bminus * xminus hex UUID containing the SHA{1,3} hash of the file named zminus * abspath absolute path to the file on disk being diffed * change enum denoting the versioning change of the file * diff_flags, context, and sbs are the same parameters as diff_file_artifact() */ static int diff_file(struct fnc_diff_view_state *s, fsl_buffer *bminus, const char *zminus, fsl_uuid_str xminus, const char *abspath, enum fsl_ckout_change_e change) { fsl_cx *const f = fcli_cx(); fsl_buffer bplus = fsl_buffer_empty; fsl_buffer xplus = fsl_buffer_empty; const char *zplus = NULL; int rc = 0; /* * If it exists, read content of abspath to diff EXCEPT for the content |
︙ | ︙ | |||
5095 5096 5097 5098 5099 5100 5101 | default: RC(FSL_RC_SIZE_MISMATCH, "invalid artifact uuid [%s]", xminus); goto end; } if (rc) goto end; | > | | | > | | < < < < < | | | | > > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < | > | | | | | | | > | | > | | | | | > | | < < < < < | < | 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 | default: RC(FSL_RC_SIZE_MISMATCH, "invalid artifact uuid [%s]", xminus); goto end; } if (rc) goto end; if (s->buf.used) { s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(off_t)); s->index.offset[s->index.n++] = s->buf.used; } rc = write_diff_meta(s, zminus, xminus, zplus, fsl_buffer_str(&xplus), change); if (rc) goto end; if FLAG_CHK(s->diff_flags, FNC_DIFF_BRIEF) { rc = fsl_buffer_compare(bminus, &bplus); if (!rc) rc = fsl_buffer_appendf(&s->buf, "CHANGED -> %s\n", zminus); } else if (FLAG_CHK(s->diff_flags, FNC_DIFF_VERBOSE) || (bminus->used && bplus.used)) rc = fnc_diff_text_to_buffer(bminus, &bplus, &s->buf, &s->dlines, &s->ndlines, s->context, s->sbs, s->diff_flags); end: rc = add_line_type(&s->dlines, LINE_BLANK, &s->ndlines, 0, true); fsl_buffer_clear(&bplus); fsl_buffer_clear(&xplus); return rc; } /* * Parse the deck of non-checkin commits to present a 'fossil ui' equivalent * of the corresponding artifact when selected from the timeline. * TODO: Rename this horrible function name. */ static int diff_non_checkin(struct fnc_diff_view_state *s) { fsl_cx *const f = fcli_cx(); fsl_buffer wiki = fsl_buffer_empty; fsl_buffer pwiki = fsl_buffer_empty; fsl_id_t prid = 0; fsl_size_t idx; int rc = 0; fsl_deck *d = NULL; d = fsl_deck_malloc(); if (d == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_deck_malloc"); fsl_deck_init(f, d, FSL_SATYPE_ANY); rc = fsl_deck_load_rid(f, d, s->selected_entry->rid, FSL_SATYPE_ANY); if (rc) goto end; /* * Present ticket commits as a series of field: value tuples as per * the Fossil UI /info/UUID view. */ if (d->type == FSL_SATYPE_TICKET) { for (idx = 0; idx < d->J.used; ++idx) { fsl_card_J *ticket = d->J.list[idx]; bool icom = !fsl_strncmp(ticket->field, "icom", 4); fsl_buffer_appendf(&s->buf, "%d. %s:%s%s%c\n", idx + 1, ticket->field, icom ? "\n\n" : " ", ticket->value, icom ? '\n' : ' '); } goto end; } if (d->type == FSL_SATYPE_CONTROL) { for (idx = 0; idx < d->T.used; ++idx) { fsl_card_T *ctl = d->T.list[idx]; fsl_buffer_appendf(&s->buf, "Tag %d ", idx + 1); switch (ctl->type) { case FSL_TAGTYPE_CANCEL: fsl_buffer_append(&s->buf, "[CANCEL]", -1); break; case FSL_TAGTYPE_ADD: fsl_buffer_append(&s->buf, "[ADD]", -1); break; case FSL_TAGTYPE_PROPAGATING: fsl_buffer_append(&s->buf, "[PROPAGATE]", -1); break; default: break; } if (ctl->uuid) fsl_buffer_appendf(&s->buf, "\ncheckin %s", ctl->uuid); fsl_buffer_appendf(&s->buf, "\n%s", ctl->name); if (!fsl_strcmp(ctl->name, "branch")) s->selected_entry->branch = fsl_strdup(ctl->value); if (ctl->value) fsl_buffer_appendf(&s->buf, " -> %s", ctl->value); fsl_buffer_append(&s->buf, "\n\n", 2); } goto end; } /* * If neither a ticket nor control artifact, we assume it's a wiki, so * check if it has a parent commit to diff against. If not, append the * entire wiki card content. */ fsl_buffer_append(&wiki, d->W.mem, d->W.used); if (s->selected_entry->puuid == NULL) { if (d->P.used > 0) s->selected_entry->puuid = fsl_strdup(d->P.list[0]); else { fsl_buffer_copy(&s->buf, &wiki); goto end; } } /* Diff the artifacts if a parent is found. */ rc = fsl_sym_to_rid(f, s->selected_entry->puuid, FSL_SATYPE_ANY, &prid); if (rc) goto end; rc = fsl_deck_load_rid(f, d, prid, FSL_SATYPE_ANY); if (rc) goto end; fsl_buffer_append(&pwiki, d->W.mem, d->W.used); rc = fnc_diff_text_to_buffer(&pwiki, &wiki, &s->buf, &s->dlines, &s->ndlines, s->context, s->sbs, s->diff_flags); /* If a technote, provide the full content after its diff. */ if (d->type == FSL_SATYPE_TECHNOTE) fsl_buffer_appendf(&s->buf, "\n---\n\n%s", wiki.mem); end: fsl_buffer_clear(&wiki); fsl_buffer_clear(&pwiki); fsl_deck_finalize(d); return rc; } /* * Compute the differences between two repository file artifacts to produce the * set of changes necessary to convert one into the other. * buf output buffer in which diff output will be appended |
︙ | ︙ | |||
5305 5306 5307 5308 5309 5310 5311 | * sbs number of columns in which to display each side-by-side diff */ static int diff_file_artifact(struct fnc_diff_view_state *s, fsl_id_t vid1, const fsl_card_F *a, const fsl_card_F *b, enum fsl_ckout_change_e change) { fsl_cx *const f = fcli_cx(); | < | | | 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 | * sbs number of columns in which to display each side-by-side diff */ static int diff_file_artifact(struct fnc_diff_view_state *s, fsl_id_t vid1, const fsl_card_F *a, const fsl_card_F *b, enum fsl_ckout_change_e change) { fsl_cx *const f = fcli_cx(); fsl_stmt stmt = fsl_stmt_empty; fsl_buffer fbuf1 = fsl_buffer_empty; fsl_buffer fbuf2 = fsl_buffer_empty; char *zminus0 = NULL, *zplus0 = NULL; const char *zplus = NULL, *zminus = NULL; fsl_uuid_str xplus0 = NULL, xminus0 = NULL; fsl_uuid_str xplus = NULL, xminus = NULL; fsl_id_t vid2 = s->selected_entry->rid; int rc = 0; assert(vid1 != vid2); assert(vid2 > 0 && "local checkout should be diffed with diff_checkout()"); fbuf2.used = fbuf1.used = 0; if (a) { rc = fsl_card_F_content(f, a, &fbuf1); if (rc) goto end; zminus = a->name; xminus = a->uuid; } else if (s->selected_entry->diff_type == FNC_DIFF_BLOB) { rc = fsl_cx_prepare(f, &stmt, "SELECT name FROM filename, mlink " "WHERE filename.fnid=mlink.fnid AND mlink.fid = %d", vid1); if (rc) { rc = RC(FSL_RC_DB, "%s %d", "fsl_cx_prepare", vid1); goto end; } |
︙ | ︙ | |||
5358 5359 5360 5361 5362 5363 5364 | } if (b) { rc = fsl_card_F_content(f, b, &fbuf2); if (rc) goto end; zplus = b->name; xplus = b->uuid; | | | 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 | } if (b) { rc = fsl_card_F_content(f, b, &fbuf2); if (rc) goto end; zplus = b->name; xplus = b->uuid; } else if (s->selected_entry->diff_type == FNC_DIFF_BLOB) { rc = fsl_cx_prepare(f, &stmt, "SELECT name FROM filename, mlink " "WHERE filename.fnid=mlink.fnid AND mlink.fid = %d", vid2); if (rc) { rc = RC(FSL_RC_DB, "%s %d", "fsl_cx_prepare", vid2); goto end; } |
︙ | ︙ | |||
5385 5386 5387 5388 5389 5390 5391 | xplus = xplus0; fsl_stmt_finalize(&stmt); fsl_content_get(f, vid2, &fbuf2); } if (s->buf.used) { s->index.offset = fsl_realloc(s->index.offset, | | | > | < > | | < < | < | > < | 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 | xplus = xplus0; fsl_stmt_finalize(&stmt); fsl_content_get(f, vid2, &fbuf2); } if (s->buf.used) { s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(off_t)); s->index.offset[s->index.n++] = s->buf.used + s->index.offset[0]; } rc = write_diff_meta(s, zminus, xminus, zplus, xplus, change); if (rc) goto end; if (FLAG_CHK(s->diff_flags, FNC_DIFF_VERBOSE) || (a && b)) rc = fnc_diff_text_to_buffer(&fbuf1, &fbuf2, &s->buf, &s->dlines, &s->ndlines, s->context, s->sbs, s->diff_flags); if (rc) RC(rc, "%s: fnc_diff_text_to_buffer\n" " -> %s [%s]\n -> %s [%s]", fsl_rc_cstr(rc), a ? a->name : NULL_DEVICE, a ? a->uuid : NULL_DEVICE, b ? b->name : NULL_DEVICE, b ? b->uuid : NULL_DEVICE); end: rc = add_line_type(&s->dlines, LINE_BLANK, &s->ndlines, 0, true); fsl_free(zminus0); fsl_free(zplus0); fsl_free(xminus0); fsl_free(xplus0); fsl_buffer_clear(&fbuf1); fsl_buffer_clear(&fbuf2); return rc; } static int show_diff(struct fnc_view *view) { struct fnc_diff_view_state *s = &view->state.diff; |
︙ | ︙ | |||
5461 5462 5463 5464 5465 5466 5467 | char *line; size_t linesz = 0; ssize_t linelen; off_t line_offset; attr_t rx = A_BOLD; int col, wstrlen, max_lines = view->nlines; int nlines = s->nlines; | | | 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 | char *line; size_t linesz = 0; ssize_t linelen; off_t line_offset; attr_t rx = A_BOLD; int col, wstrlen, max_lines = view->nlines; int nlines = s->nlines; int nprinted = 0, rc = FSL_RC_OK; bool selected; s->lineno = s->first_line_onscreen - 1; line_offset = s->line_offsets[s->first_line_onscreen - 1]; if (fseeko(s->f, line_offset, SEEK_SET)) return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "fseeko"); |
︙ | ︙ | |||
5484 5485 5486 5487 5488 5489 5490 | pctlen = snprintf(pct, MAX_PCT_LEN, "%.*lf%%", percent > 99.99 ? 0 : 2, percent); if (pctlen < 0) return RC(FSL_RC_RANGE, "%s", "snprintf"); line = fsl_mprintf("[%d/%d] %s", ln, nlines, headln); if (line == NULL) return RC(FSL_RC_RANGE, "%s", "fsl_mprintf"); | | | 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 | pctlen = snprintf(pct, MAX_PCT_LEN, "%.*lf%%", percent > 99.99 ? 0 : 2, percent); if (pctlen < 0) return RC(FSL_RC_RANGE, "%s", "snprintf"); line = fsl_mprintf("[%d/%d] %s", ln, nlines, headln); if (line == NULL) return RC(FSL_RC_RANGE, "%s", "fsl_mprintf"); rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0, false); fsl_free(line); fsl_free(headln); if (rc) return rc; if (screen_is_shared(view) || view->active) rx |= A_REVERSE; |
︙ | ︙ | |||
5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 | if (--max_lines < 1) return rc; } s->eof = false; line = NULL; while (max_lines > 0 && nprinted < max_lines) { linelen = getline(&line, &linesz, s->f); if (linelen == -1) { if (feof(s->f)) { s->eof = true; break; } fsl_free(line); | > | 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 | if (--max_lines < 1) return rc; } s->eof = false; line = NULL; while (max_lines > 0 && nprinted < max_lines) { col = wstrlen = 0; linelen = getline(&line, &linesz, s->f); if (linelen == -1) { if (feof(s->f)) { s->eof = true; break; } fsl_free(line); |
︙ | ︙ | |||
5532 5533 5534 5535 5536 5537 5538 | if (!gotoline(view, &s->lineno, &nprinted)) continue; rx = 0; if ((selected = nprinted == s->selected_line - 1)) rx = A_BOLD | A_REVERSE; if (s->showln) | | > > | | | < < < < | | > | < < < | > > > > > | | 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 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 5734 5735 5736 5737 5738 5739 | if (!gotoline(view, &s->lineno, &nprinted)) continue; rx = 0; if ((selected = nprinted == s->selected_line - 1)) rx = A_BOLD | A_REVERSE; if (s->showln) col = draw_lineno(view, nlines, s->lineno, rx); if (s->colour) c = FLAG_CHK(s->diff_flags, FNC_DIFF_SIDEBYSIDE) ? get_colour(&s->colours, s->dlines[s->lineno - 1]) : match_colour(&s->colours, line); if (c && !(selected && s->sline == SLINE_MONO)) rx |= COLOR_PAIR(c->scheme); if (c || selected) wattron(view->window, rx); if (s->first_line_onscreen + nprinted == s->matched_line && regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) { rc = draw_matched_line(view, line, &wstrlen, view->ncols - col, 0, regmatch, rx); } else if (view->pos.col < (etcount(line, linelen) - 1)) { rc = formatln(&wcstr, &wstrlen, line, view->pos.col + view->ncols - col, 0, true); /* XXX Change above else if to else to use next line. */ /* if (view->pos.col < wstrlen) */ waddwstr(view->window, wcstr + view->pos.col); fsl_free(wcstr); wcstr = NULL; } if (rc) { fsl_free(line); return rc; } col += MAX(wstrlen - view->pos.col, 0); while (col++ < view->ncols) waddch(view->window, ' '); if (c || selected) wattroff(view->window, rx); if (++nprinted == 1) s->first_line_onscreen = s->lineno; |
︙ | ︙ | |||
5628 5629 5630 5631 5632 5633 5634 | update_panels(); #endif if (update) doupdate(); } static int | | | | < | | | > > > > > | | | < < < < | < < < | < < | | > | > < < < < < < < < | < < < > | > | | < < | | > | | < < < < | < < | < < < | < | | > | 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 | update_panels(); #endif if (update) doupdate(); } static int draw_matched_line(struct fnc_view *view, const char *line, int *col, int limit, int offset, regmatch_t *regmatch, attr_t rx) { wchar_t *wstr = NULL; int rme, rms, skip = view->pos.col, wlen; int n, rc = FSL_RC_OK; attr_t hl = A_BOLD | A_REVERSE; *col = n = 0; rms = regmatch->rm_so; rme = regmatch->rm_eo; rc = formatln(&wstr, &wlen, line, limit + view->pos.col, offset, true); if (rc) return rc; /* * Draw to screen all chars up to string match. Trim preceding skip * chars if scrolled right. Don't draw if skip consumes substring. */ n = MAX(rms - skip, 0); if (n) { waddnwstr(view->window, wstr + skip, n); limit -= n; *col += n; } /* * Copy string match and draw with highlight. If skip traverses string * match, trim n chars off the front. Don't copy if skip consumed match. */ if (limit > 0) { int len = rme - rms; n = 0; if (skip > rms) { n = skip - rms; len = MAX(len - n, 0); } if (len) { wattron(view->window, COLOR_PAIR(FNC_COLOUR_HL_SEARCH) | hl); waddnwstr(view->window, wstr + rms + n, len); wattroff(view->window, COLOR_PAIR(FNC_COLOUR_HL_SEARCH) | hl); limit -= len; *col += len; } } /* * Write rest of line if not yet at EOL. If skip passes the end of * string match, trim n chars off the front of substring. */ if (limit > 0 && skip < wlen) { n = 0; if (skip > rme) n = MIN(skip - rme, wlen - rme); wattron(view->window, rx); waddnwstr(view->window, wstr + rme + n, limit); /* *col += wlen - (rme + n) + view->pos.col; */ } *col = wlen; free(wstr); return rc; } static void drawborder(struct fnc_view *view) { const struct fnc_view *view_above; |
︙ | ︙ | |||
5767 5768 5769 5770 5771 5772 5773 | s->lineno = s->first_line_onscreen - 1 + s->selected_line; switch (ch) { case '0': view->pos.col = 0; break; case '$': | | | > | > | 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 | s->lineno = s->first_line_onscreen - 1 + s->selected_line; switch (ch) { case '0': view->pos.col = 0; break; case '$': view->pos.col = MAX(s->maxx - view->ncols / 2, 0); break; case KEY_RIGHT: case 'l': if (view->pos.col + view->ncols / 2 < s->maxx) view->pos.col += 2; break; case KEY_LEFT: case 'h': view->pos.col -= MIN(view->pos.col, 2); break; case CTRL('p'): if (s->selected_entry->diff_type == FNC_DIFF_WIKI || !s->index.lineno) break; if (!((size_t)s->lineno > s->index.lineno[s->index.n - 1])) { if (s->index.idx == 0) s->index.idx = s->index.n - 1; else --s->index.idx; } else s->index.idx = s->index.n - 1; s->first_line_onscreen = s->index.lineno[s->index.idx]; s->selected_line = 1; break; case CTRL('n'): if (s->selected_entry->diff_type == FNC_DIFF_WIKI || !s->index.lineno) break; if (!((size_t)s->lineno < s->index.lineno[0])) { if (++s->index.idx == s->index.n) s->index.idx = 0; } else s->index.idx = 0; s->first_line_onscreen = s->index.lineno[s->index.idx]; |
︙ | ︙ | |||
5904 5905 5906 5907 5908 5909 5910 | /* * XXX Assume user would expect file navigation (C-n/p) to * reset after jumping home. */ s->index.idx = 0; break; case 'F': | | | | | | 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 | /* * XXX Assume user would expect file navigation (C-n/p) to * reset after jumping home. */ s->index.idx = 0; break; case 'F': if (s->selected_entry->diff_type == FNC_DIFF_WIKI) break; fsl_buffer buf = fsl_buffer_empty; struct input input; char *end = fsl_mprintf(", ..., %d: ", s->index.n); size_t maxwidth = view->ncols - 12; uint32_t idx = 0; fsl_buffer_append(&buf, "File ", -1); while (++idx <= s->index.n && buf.used < maxwidth) fsl_buffer_appendf(&buf, "%d%s", idx, (idx + 1 < s->index.n ? buf.used + 6 < maxwidth ? ", " : end : (idx < s->index.n ? " or " : ": "))); input = (struct input){(int []){1, s->index.n}, fsl_buffer_str(&buf), INPUT_NUMERIC, SR_CLREOL}; rc = fnc_prompt_input(view, &input); if (input.ret) { s->index.idx = input.ret - 1; s->first_line_onscreen = s->index.lineno[s->index.idx]; s->selected_line = 1; } fsl_buffer_clear(&buf); fsl_free(end); break; case '@': { struct input input = {(int []){1, nlines}, "line: ", INPUT_NUMERIC, SR_CLREOL}; rc = fnc_prompt_input(view, &input); s->gtl = input.ret; break; } case '#': s->showln = !s->showln; break; |
︙ | ︙ | |||
5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 | view->focus_child = true; } else *new_view = branch_view; break; } case 'c': case 'i': case 'v': case 'w': if (ch == 'c') s->colour = !s->colour; | > > > > > > > > | | > > > > > > | | | | | 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 | view->focus_child = true; } else *new_view = branch_view; break; } case 'c': case 'i': case 'L': case 'p': case 'S': case 'v': case 'w': if (ch == 'c') s->colour = !s->colour; /* LSipvw key maps don't apply to tag or ticket artifacts. */ if (*s->selected_entry->type == 't' && (s->selected_entry->type[1] == 'a' || s->selected_entry->type[1] == 'i')) break; else if (ch == 'i') FLAG_TOG(s->diff_flags, FNC_DIFF_INVERT); else if (ch == 'L') FLAG_TOG(s->diff_flags, FNC_DIFF_LINENO); else if (ch == 'p') FLAG_TOG(s->diff_flags, FNC_DIFF_PROTOTYPE); else if (ch == 'S') FLAG_TOG(s->diff_flags, FNC_DIFF_SIDEBYSIDE); else if (ch == 'v') FLAG_TOG(s->diff_flags, FNC_DIFF_VERBOSE); else if (ch == 'w') FLAG_TOG(s->diff_flags, FNC_DIFF_IGNORE_ALLWS); rc = reset_diff_view(view, true); break; case '-': case '_': if (s->context > 0) { --s->context; rc = reset_diff_view(view, true); |
︙ | ︙ | |||
6007 6008 6009 6010 6011 6012 6013 | case CTRL('k'): case '<': case ',': case 'K': if (s->timeline_view == NULL) break; tlstate = &s->timeline_view->state.timeline; | | | | > | | > > > > < > > > > > > > > > | 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 | case CTRL('k'): case '<': case ',': case 'K': if (s->timeline_view == NULL) break; tlstate = &s->timeline_view->state.timeline; previous_selection = tlstate->selected_entry; rc = tl_input_handler(NULL, s->timeline_view, tl_down ? KEY_DOWN : KEY_UP); if (rc) break; if (previous_selection == tlstate->selected_entry) break; rc = set_selected_commit(s, tlstate->selected_entry); if (rc) break; s->selected_line = 1; reset_diff_view(view, false); break; default: break; } return rc; } static int reset_diff_view(struct fnc_view *view, bool stay) { struct fnc_diff_view_state *s = &view->state.diff; int n, rc = FSL_RC_OK; n = s->nlines; free_index(&s->index); fsl_free(s->dlines); s->dlines = NULL; s->ndlines = 0; show_diff_status(view); rc = create_diff(s); if (rc) return rc; if (stay) { float scale = (float)s->first_line_onscreen / n; s->first_line_onscreen = MAX(1, (int)(s->nlines * scale)); } else { s->first_line_onscreen = 1; view->pos.col = 0; } s->matched_line = 0; s->last_line_onscreen = MIN(s->nlines, (size_t)s->first_line_onscreen + view->nlines); s->selected_line = MIN(s->selected_line, s->last_line_onscreen - s->first_line_onscreen + 1); /* * If max width has reduced (i.e., user switched from SBS to unidiff), * and col position is beyond new max, move back to within line limits. */ view->pos.col = MAX(MIN(view->pos.col, s->maxx - view->ncols / 2), 0); return rc; } static int request_tl_commits(struct fnc_view *view) { |
︙ | ︙ | |||
6078 6079 6080 6081 6082 6083 6084 | { fsl_free(s->id2); s->id2 = fsl_strdup(entry->commit->uuid); if (s->id2 == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); fsl_free(s->id1); s->id1 = entry->commit->puuid ? fsl_strdup(entry->commit->puuid) : NULL; | | | 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 | { fsl_free(s->id2); s->id2 = fsl_strdup(entry->commit->uuid); if (s->id2 == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); fsl_free(s->id1); s->id1 = entry->commit->puuid ? fsl_strdup(entry->commit->puuid) : NULL; s->selected_entry = entry->commit; return 0; } static void diff_grep_init(struct fnc_view *view) { |
︙ | ︙ | |||
6100 6101 6102 6103 6104 6105 6106 | { FILE *f = NULL; off_t *line_offsets = NULL; ssize_t linelen; size_t nlines = 0, linesz = 0; int *first, *last, *match, *selected; int lineno; | > | | | 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 | { FILE *f = NULL; off_t *line_offsets = NULL; ssize_t linelen; size_t nlines = 0, linesz = 0; int *first, *last, *match, *selected; int lineno; uint8_t col = 0; char *exstr = NULL, *line = NULL; first = last = match = selected = NULL; grep_set_view(view, &f, &line_offsets, &nlines, &first, &last, &match, &selected, &col); if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (*match) { |
︙ | ︙ | |||
6144 6145 6146 6147 6148 6149 6150 | offset = line_offsets[lineno - 1]; if (fseeko(f, offset, SEEK_SET) != 0) { fsl_free(line); return RC(fsl_errno_to_rc(errno, FSL_RC_IO), "%s", "fseeko"); } | < | < < < | | > < | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 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 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 | offset = line_offsets[lineno - 1]; if (fseeko(f, offset, SEEK_SET) != 0) { fsl_free(line); return RC(fsl_errno_to_rc(errno, FSL_RC_IO), "%s", "fseeko"); } /* Expand tabs for accurate rm_so/rm_eo offsets. */ linelen = getline(&line, &linesz, f); expand_tab(&exstr, line, linelen); if (linelen != -1 && regexec(&view->regex, exstr, 1, &view->regmatch, 0) == 0) { int *pos = &view->pos.col; view->search_status = SEARCH_CONTINUE; *match = lineno; /* Scroll till on-screen. */ while (*pos > view->regmatch.rm_so) --(*pos); while (*pos + view->ncols < view->regmatch.rm_eo + col) ++(*pos); break; } fsl_free(exstr); exstr = NULL; if (view->searching == SEARCH_FORWARD) ++lineno; else --lineno; } fsl_free(line); fsl_free(exstr); /* * If match is on current screen, move to it and highlight; else, * scroll view till matching line is ~1/3rd from the top and highlight. */ if (*match) { if (*match >= *first && *match <= *last) *selected = *match - *first + 1; else { *first = MAX(*match - view->nlines / 3, 1); *selected = *match - *first + 1; } } return FSL_RC_OK; } static void grep_set_view(struct fnc_view *view, FILE **f, off_t **line_offsets, size_t *nlines, int **first, int **last, int **match, int **selected, uint8_t *startx) { if (view->vid == FNC_VIEW_DIFF) { struct fnc_diff_view_state *s = &view->state.diff; *f = s->f; *nlines = s->nlines; *line_offsets = s->line_offsets; *match = &s->matched_line; *first = &s->first_line_onscreen; *last = &s->last_line_onscreen; *selected = &s->selected_line; if (s->showln) { int d = s->nlines, n = 0; ndigits(n, d); *startx = n + 3; /* {ap,pre}pended ' ' + line sep */ } } else if (view->vid == FNC_VIEW_BLAME) { struct fnc_blame_view_state *s = &view->state.blame; *f = s->blame.f; *nlines = s->blame.nlines; *line_offsets = s->blame.line_offsets; *match = &s->matched_line; *first = &s->first_line_onscreen; *last = &s->last_line_onscreen; *selected = &s->selected_line; if (s->showln) { int d = s->blame.nlines, n = 0; ndigits(n, d); *startx = n + 3; /* {ap,pre}pended ' ' + line sep */ } *startx += 11; /* id field */ } } static int close_diff_view(struct fnc_view *view) { struct fnc_diff_view_state *s = &view->state.diff; int rc = 0; if (s->f && fclose(s->f) == EOF) rc = RC(fsl_errno_to_rc(errno, FSL_RC_IO), "%s", "fclose"); fsl_free(s->id1); s->id1 = NULL; fsl_free(s->id2); s->id2 = NULL; fsl_free(s->line_offsets); free_colours(&s->colours); s->line_offsets = NULL; s->nlines = 0; free_index(&s->index); fsl_free(s->dlines); s->dlines = NULL; return rc; } static void free_index(struct index *index) { index->idx = 0; index->n = 0; fsl_free(index->lineno); index->lineno = NULL; fsl_free(index->offset); index->offset = NULL; } static void free_tags(struct fnc_tl_view_state *s, bool restore) { if (!s->tagged.ogrid) return; /* Restore timeline commit queue entry. */ if (restore) { fsl_free(s->selected_entry->commit->puuid); s->selected_entry->commit->puuid = fsl_strdup(s->tagged.ogid); s->selected_entry->commit->prid = s->tagged.ogrid; s->selected_entry->commit->diff_type = FNC_DIFF_COMMIT; fsl_free(s->tagged.ogid); s->tagged.ogid = NULL; s->tagged.ogrid = 0; s->showmeta = true; } fsl_free(s->tagged.type); s->tagged.type = NULL; fsl_free(s->tagged.id); s->tagged.id = NULL; s->tagged.rid = 0; } static void fnc_resizeterm(void) { struct winsize size; int cols, lines; |
︙ | ︙ | |||
6410 6411 6412 6413 6414 6415 6416 | } static void usage_diff(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s diff [-C|--no-colour] [-R path] [-h|--help] " | > | | | | | 6625 6626 6627 6628 6629 6630 6631 6632 6633 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 | } static void usage_diff(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s diff [-C|--no-colour] [-R path] [-h|--help] " "[-i|--invert] [-l|--line-numbers] [-q|--quiet] [-s|--sbs] " "[-w|--whitespace] [-x|--context n] [artifact1 [artifact2]] " "[path ...]\n" " e.g.: %s diff --sbs d34db33f c0ff33 src/*.c\n\n", fcli_progname(), fcli_progname()); } static void usage_tree(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s tree [-C|--no-colour] [-R path] [-c commit] [-h|--help]" " [path]\n" " e.g.: %s tree -c d34dc0d3\n\n" , fcli_progname(), fcli_progname()); } static void usage_blame(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s blame [-C|--no-colour] [-R path] [-c commit [-r]] " "[-h|--help] [-l lineno] [-n n] path\n" " e.g.: %s blame -c d34db33f src/foo.c\n\n" , fcli_progname(), fcli_progname()); } static void usage_branch(void) { |
︙ | ︙ | |||
6453 6454 6455 6456 6457 6458 6459 | static void usage_config(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s config [-R path] [-h|--help] [--ls] " "[setting [value|--unset]]\n" | | | 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 | static void usage_config(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s config [-R path] [-h|--help] [--ls] " "[setting [value|--unset]]\n" " e.g.: %s config FNC_COLOUR_COMMIT blue\n\n" , fcli_progname(), fcli_progname()); } static int cmd_diff(fcli_command const *argv) { fsl_cx *const f = fcli_cx(); |
︙ | ︙ | |||
6615 6616 6617 6618 6619 6620 6621 | commit->type = fsl_strdup("blob"); commit->diff_type = diff_type; } rc = init_curses(); if (rc) goto end; | < | < > > | | | > | 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 | commit->type = fsl_strdup("blob"); commit->diff_type = diff_type; } rc = init_curses(); if (rc) goto end; rc = init_unveil(REPODB, CKOUTDIR, false); if (rc) goto end; rc = init_landlock((const char*[]){REPODIR, CKOUTDIR, P_tmpdir}, 3); if (rc) goto end; if (fnc_init.context) { if ((rc = strtonumcheck(&context, fnc_init.context, INT_MIN, INT_MAX))) goto end; context = MIN(MAX_DIFF_CTX, 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.sbs, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, fnc_init.showln, NULL, showmeta, &paths); if (!rc) rc = view_loop(view); end: fsl_free(path0); fsl_deck_finalize(&d); fsl_stmt_finalize(q); if (commit) |
︙ | ︙ | |||
6727 6728 6729 6730 6731 6732 6733 | fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; | < | < > > | | 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 | fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; rc = init_unveil(REPODB, CKOUTDIR, false); if (rc) goto end; rc = init_landlock((const char*[]){REPODIR, CKOUTDIR, P_tmpdir}, 3); if (rc) goto end; view = view_open(0, 0, 0, 0, FNC_VIEW_TREE); if (view == NULL) { RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } |
︙ | ︙ | |||
7260 7261 7262 7263 7264 7265 7266 | s->ndisplayed = 0; werase(view->window); if (limit == 0) return rc; /* Write (highlighted) headline (if view is active in splitscreen). */ | | < | 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 | s->ndisplayed = 0; werase(view->window); if (limit == 0) return rc; /* Write (highlighted) headline (if view is active in splitscreen). */ rc = formatln(&wcstr, &wstrlen, s->tree_label, view->ncols, 0, false); if (rc) return rc; if (screen_is_shared(view)) wattron(view->window, A_REVERSE); if (s->colour) c = get_colour(&s->colours, FNC_COLOUR_COMMIT); if (c) |
︙ | ︙ | |||
7285 7286 7287 7288 7289 7290 7291 | wattroff(view->window, A_REVERSE); fsl_free(wcstr); wcstr = NULL; if (--limit <= 0) return rc; /* Write this (sub)tree's absolute repository path subheader. */ | | | | | 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 | wattroff(view->window, A_REVERSE); fsl_free(wcstr); wcstr = NULL; if (--limit <= 0) return rc; /* Write this (sub)tree's absolute repository path subheader. */ rc = formatln(&wcstr, &wstrlen, treepath, view->ncols, 0, false); if (rc) return rc; waddwstr(view->window, wcstr); fsl_free(wcstr); wcstr = NULL; if (wstrlen < view->ncols - 1) waddch(view->window, '\n'); if (--limit <= 0) return rc; waddch(view->window, '\n'); if (--limit <= 0) return rc; /* Write parent dir entry (i.e., "..") if top of the tree is in view. */ if (s->first_entry_onscreen == NULL) { te = &s->tree->entries[0]; if (s->selected == 0) { wattr_on(view->window, A_REVERSE, NULL); s->selected_entry = NULL; } waddstr(view->window, " ..\n"); if (s->selected == 0) wattr_off(view->window, A_REVERSE, NULL); ++s->ndisplayed; if (--limit <= 0) return rc; n = 1; } else { n = 0; |
︙ | ︙ | |||
7386 7387 7388 7389 7390 7391 7392 | *iso8601 ? iso8601 : "", te->basename, modestr, targetlnk ? " -> ": "", targetlnk ? targetlnk : ""); fsl_free(idstr); fsl_free(targetlnk); if (rc || line == NULL) return RC(rc ? rc : FSL_RC_RANGE, "%s", rc ? "fsl_julian_to_iso8601" : "fsl_mprintf"); | | | | | 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 | *iso8601 ? iso8601 : "", te->basename, modestr, targetlnk ? " -> ": "", targetlnk ? targetlnk : ""); fsl_free(idstr); fsl_free(targetlnk); if (rc || line == NULL) return RC(rc ? rc : FSL_RC_RANGE, "%s", rc ? "fsl_julian_to_iso8601" : "fsl_mprintf"); rc = formatln(&wcstr, &wstrlen, line, view->ncols, 0, false); if (rc) { fsl_free(line); break; } if (n == s->selected) { wattr_on(view->window, A_REVERSE, NULL); s->selected_entry = te; } if (s->colour) c = match_colour(&s->colours, line); 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 (wstrlen < view->ncols) waddch(view->window, '\n'); if (n == s->selected) wattr_off(view->window, A_REVERSE, NULL); fsl_free(line); fsl_free(wcstr); wcstr = NULL; ++n; ++s->ndisplayed; s->last_entry_onscreen = te; |
︙ | ︙ | |||
7544 7545 7546 7547 7548 7549 7550 | *new_view = timeline_view; break; case 'g': if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: | | | | | | | | | | | | | | 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 7832 7833 7834 7835 7836 7837 7838 7839 7840 7841 7842 7843 7844 7845 | *new_view = timeline_view; break; case 'g': if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: s->selected = 0; if (s->tree == s->root) s->first_entry_onscreen = &s->tree->entries[0]; else s->first_entry_onscreen = NULL; break; case KEY_END: case 'G': s->selected = 0; te = &s->tree->entries[s->tree->nentries - 1]; for (n = 0; n < view->nlines - 3; ++n) { if (te == NULL) { if(s->tree != s->root) { s->first_entry_onscreen = NULL; ++n; } break; } s->first_entry_onscreen = te; te = get_tree_entry(s->tree, te->idx - 1); } if (n > 0) s->selected = n - 1; break; case KEY_DOWN: case 'j': if (s->selected < s->ndisplayed - 1) { ++s->selected; break; } if (get_tree_entry(s->tree, s->last_entry_onscreen->idx + 1) == NULL) break; /* Reached last entry. */ tree_scroll_down(view, 1); break; case KEY_UP: case 'k': if (s->selected > 0) { --s->selected; break; } tree_scroll_up(s, 1); break; case CTRL('d'): nscroll >>= 1; /* FALL THROUGH */ case KEY_NPAGE: case CTRL('f'): if (get_tree_entry(s->tree, s->last_entry_onscreen->idx + 1) == NULL) { /* * When the last entry on screen is the last node in the * tree move cursor to it instead of scrolling the view. */ if (s->selected < s->ndisplayed - 1) s->selected += MIN(nscroll, s->ndisplayed - s->selected - 1); break; } tree_scroll_down(view, nscroll); break; case CTRL('u'): nscroll >>= 1; /* FALL THROUGH */ case KEY_PPAGE: case CTRL('b'): if (s->tree == s->root) { if (&s->tree->entries[0] == s->first_entry_onscreen) s->selected -= MIN(s->selected, nscroll); } else { if (s->first_entry_onscreen == NULL) s->selected -= MIN(s->selected, nscroll); } tree_scroll_up(s, nscroll); break; case KEY_BACKSPACE: case KEY_ENTER: case KEY_LEFT: case KEY_RIGHT: |
︙ | ︙ | |||
7644 7645 7646 7647 7648 7649 7650 | parent = TAILQ_FIRST(&s->parents); TAILQ_REMOVE(&s->parents, parent, entry); fnc_object_tree_close(s->tree); s->tree = parent->tree; s->first_entry_onscreen = parent->first_entry_onscreen; s->selected_entry = parent->selected_entry; | | | | | | 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 7891 7892 7893 7894 7895 7896 | parent = TAILQ_FIRST(&s->parents); TAILQ_REMOVE(&s->parents, parent, entry); fnc_object_tree_close(s->tree); s->tree = parent->tree; s->first_entry_onscreen = parent->first_entry_onscreen; s->selected_entry = parent->selected_entry; s->selected = parent->selected; if (s->selected > view->nlines - 3) offset_selected_line(view); fsl_free(parent); } else if (s->selected_entry != NULL && S_ISDIR(s->selected_entry->mode)) { struct fnc_tree_object *subtree = NULL; rc = tree_builder(s->repo, &subtree, s->selected_entry->path); if (rc) break; rc = visit_subtree(s, subtree); if (rc) { fnc_object_tree_close(subtree); break; } } else if (s->selected_entry != NULL && S_ISREG(s->selected_entry->mode)) rc = blame_selected_file(new_view, view); break; case KEY_RESIZE: if (view->nlines >= 4 && s->selected >= view->nlines - 3) s->selected = view->nlines - 4; break; default: break; } return rc; } |
︙ | ︙ | |||
7690 7691 7692 7693 7694 7695 7696 | fid = fsl_uuid_to_rid(f, s->selected_entry->uuid); rc = fsl_content_get(f, fid, &buf); if (rc) goto end; if (fsl_looks_like_binary(&buf)) | | | | 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 7919 7920 7921 | fid = fsl_uuid_to_rid(f, s->selected_entry->uuid); rc = fsl_content_get(f, fid, &buf); if (rc) goto end; if (fsl_looks_like_binary(&buf)) sitrep(view, "-- cannot blame binary file --", SR_UPDATE | SR_SLEEP); else rc = request_view(new_view, view, FNC_VIEW_BLAME); end: fsl_buffer_clear(&buf); return rc; } |
︙ | ︙ | |||
7787 7788 7789 7790 7791 7792 7793 | parent = calloc(1, sizeof(*parent)); if (parent == NULL) return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); parent->tree = s->tree; parent->first_entry_onscreen = s->first_entry_onscreen; parent->selected_entry = s->selected_entry; | | | | 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 | parent = calloc(1, sizeof(*parent)); if (parent == NULL) return RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); parent->tree = s->tree; parent->first_entry_onscreen = s->first_entry_onscreen; parent->selected_entry = s->selected_entry; parent->selected = s->selected; TAILQ_INSERT_HEAD(&s->parents, parent, entry); s->tree = subtree; s->selected = 0; s->first_entry_onscreen = NULL; return 0; } static int blame_tree_entry(struct fnc_view **new_view, int start_col, int start_ln, |
︙ | ︙ | |||
7817 7818 7819 7820 7821 7822 7823 | blame_view = view_open(0, 0, start_ln, start_col, FNC_VIEW_BLAME); if (blame_view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } | | | 8033 8034 8035 8036 8037 8038 8039 8040 8041 8042 8043 8044 8045 8046 8047 | blame_view = view_open(0, 0, start_ln, start_col, FNC_VIEW_BLAME); if (blame_view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } rc = open_blame_view(blame_view, path, commit_id, 0, 0, NULL); if (rc) view_close(blame_view); else *new_view = blame_view; end: fsl_free(path); return rc; |
︙ | ︙ | |||
7900 7901 7902 7903 7904 7905 7906 | if (s->matched_entry) { int idx = s->matched_entry->idx; bool parent = !s->first_entry_onscreen; if (idx >= (parent ? 0 : s->first_entry_onscreen->idx) && idx <= s->last_entry_onscreen->idx) | | | | 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 | if (s->matched_entry) { int idx = s->matched_entry->idx; bool parent = !s->first_entry_onscreen; if (idx >= (parent ? 0 : s->first_entry_onscreen->idx) && idx <= s->last_entry_onscreen->idx) s->selected = idx - (parent ? - 1 : s->first_entry_onscreen->idx); else { s->first_entry_onscreen = s->matched_entry; s->selected = 0; } } return rc; } static int |
︙ | ︙ | |||
7975 7976 7977 7978 7979 7980 7981 | } if (s->tree != NULL && s->tree != s->root) fnc_object_tree_close(s->tree); if (s->root) fnc_object_tree_close(s->root); if (s->repo) | | | < < < < | < > | > | 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 | } if (s->tree != NULL && s->tree != s->root) fnc_object_tree_close(s->tree); if (s->root) fnc_object_tree_close(s->root); if (s->repo) fnc_close_repo_tree(s->repo); return 0; } static void fnc_object_tree_close(struct fnc_tree_object *tree) { int idx; for (idx = 0; idx < tree->nentries; ++idx) { fsl_free(tree->entries[idx].basename); fsl_free(tree->entries[idx].path); fsl_free(tree->entries[idx].uuid); } fsl_free(tree->entries); fsl_free(tree); } static void fnc_close_repo_tree(struct fnc_repository_tree *repo) { struct fnc_repo_tree_node *next, *tn; tn = repo->head; while (tn) { next = tn->next; fsl_free(tn); tn = next; } fsl_free(repo); } static int cmd_config(const fcli_command *argv) { const char *opt = NULL, *value = NULL; char *prev, *v; enum fnc_opt_id setid; int rc = FSL_RC_OK; rc = init_unveil(REPODIR, CKOUTDIR, true); if (rc) return rc; rc = init_landlock((const char*[]){REPODIR, CKOUTDIR, P_tmpdir}, 3); if (rc) return rc; rc = fcli_process_flags(argv->flags); if (rc || (rc = fcli_has_unused_flags(false))) return rc; opt = fcli_next_arg(true); if (opt == NULL || fnc_init.lsconf) { |
︙ | ︙ | |||
8162 8163 8164 8165 8166 8167 8168 | int rc = FSL_RC_OK; switch (vid) { case FNC_VIEW_DIFF: { static const char *regexp_diff[] = { "^((checkin|wiki|ticket|technote) " "[0-9a-f]|hash [+-] |\\[[+~>-]] |[+-]{3} )", | | > > > > > > | | | | | | | > | 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 | int rc = FSL_RC_OK; switch (vid) { case FNC_VIEW_DIFF: { static const char *regexp_diff[] = { "^((checkin|wiki|ticket|technote) " "[0-9a-f]|hash [+-] |\\[[+~>-]] |[+-]{3} )", "^user:", "^date:", "^tags:", "^-|^[0-9 ]+ -", "^\\+|^[0-9 ]+ \\+", "^@@", /* * XXX Ugly hack to fail matching _DIFF_SBS_EDIT early * until all diff modes use the new line_type interface. */ "a^" }; const int pairs_diff[][2] = { {LINE_DIFF_META, init_colour(FNC_COLOUR_DIFF_META)}, {LINE_DIFF_USER, init_colour(FNC_COLOUR_USER)}, {LINE_DIFF_DATE, init_colour(FNC_COLOUR_DATE)}, {LINE_DIFF_TAGS, init_colour(FNC_COLOUR_DIFF_TAGS)}, {LINE_DIFF_MINUS, init_colour(FNC_COLOUR_DIFF_MINUS)}, {LINE_DIFF_PLUS, init_colour(FNC_COLOUR_DIFF_PLUS)}, {LINE_DIFF_CHUNK, init_colour(FNC_COLOUR_DIFF_CHUNK)}, {LINE_DIFF_EDIT, init_colour(FNC_COLOUR_DIFF_SBS_EDIT)} }; rc = set_colour_scheme(s, pairs_diff, regexp_diff, nitems(regexp_diff)); break; } case FNC_VIEW_TREE: { static const char *regexp_tree[] = {"@ ->", "/$", "\\*$", "^$"}; |
︙ | ︙ | |||
8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 | case FNC_COLOUR_DIFF_MINUS: case FNC_COLOUR_DIFF_TAGS: case FNC_COLOUR_TREE_LINK: case FNC_COLOUR_BRANCH_CLOSED: return COLOR_MAGENTA; case FNC_COLOUR_HL_SEARCH: return COLOR_YELLOW; default: return -1; /* Terminal default foreground colour. */ } } static int fnc_conf_setopt(enum fnc_opt_id id, const char *val, bool unset) | > > | 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 | case FNC_COLOUR_DIFF_MINUS: case FNC_COLOUR_DIFF_TAGS: case FNC_COLOUR_TREE_LINK: case FNC_COLOUR_BRANCH_CLOSED: return COLOR_MAGENTA; case FNC_COLOUR_HL_SEARCH: return COLOR_YELLOW; case FNC_COLOUR_DIFF_SBS_EDIT: return COLOR_RED; default: return -1; /* Terminal default foreground colour. */ } } static int fnc_conf_setopt(enum fnc_opt_id id, const char *val, bool unset) |
︙ | ︙ | |||
8528 8529 8530 8531 8532 8533 8534 | "%s blame requires versioned file path", fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; | < | < > > | | > | | 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 8771 8772 8773 8774 8775 8776 8777 8778 8779 8780 8781 8782 8783 8784 8785 8786 8787 8788 8789 8790 | "%s blame requires versioned file path", fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; rc = init_unveil(REPODB, CKOUTDIR, false); if (rc) goto end; rc = init_landlock((const char*[]){REPODIR, CKOUTDIR, P_tmpdir}, 3); if (rc) goto end; view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", view_open); goto end; } rc = open_blame_view(view, path, commit_id, tip, nlimit, fnc_init.lineno); if (rc) goto end; rc = view_loop(view); end: fsl_free(path); fsl_free(commit_id); return rc; } static int open_blame_view(struct fnc_view *view, char *path, fsl_uuid_str commit_id, fsl_id_t tip, int nlimit, const char *lineno) { struct fnc_blame_view_state *s = &view->state.blame; int rc = 0; CONCAT(STAILQ, _INIT)(&s->blamed_commits); s->path = fsl_strdup(path); |
︙ | ︙ | |||
8582 8583 8584 8585 8586 8587 8588 8589 8590 8591 8592 8593 8594 8595 | s->selected_line = 1; s->blame_complete = false; s->commit_id = commit_id; s->blame.origin = tip; s->blame.nlimit = nlimit; s->spin_idx = 0; s->colour = !fnc_init.nocolour && has_colors(); if (s->colour) { STAILQ_INIT(&s->colours); rc = set_colours(&s->colours, FNC_VIEW_BLAME); if (rc) return rc; } | > | 8805 8806 8807 8808 8809 8810 8811 8812 8813 8814 8815 8816 8817 8818 8819 | s->selected_line = 1; s->blame_complete = false; s->commit_id = commit_id; s->blame.origin = tip; s->blame.nlimit = nlimit; s->spin_idx = 0; s->colour = !fnc_init.nocolour && has_colors(); s->lineno = lineno; if (s->colour) { STAILQ_INIT(&s->colours); rc = set_colours(&s->colours, FNC_VIEW_BLAME); if (rc) return rc; } |
︙ | ︙ | |||
8677 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 | s->blame_complete = true; goto end; } /* Don't include EOF \n in blame line count. */ if (blame->line_offsets[blame->nlines - 1] == blame->filesz) --blame->nlines; blame->lines = calloc(blame->nlines, sizeof(*blame->lines)); if (blame->lines == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); goto end; } | > > > > > > > > | 8901 8902 8903 8904 8905 8906 8907 8908 8909 8910 8911 8912 8913 8914 8915 8916 8917 8918 8919 8920 8921 8922 | s->blame_complete = true; goto end; } /* Don't include EOF \n in blame line count. */ if (blame->line_offsets[blame->nlines - 1] == blame->filesz) --blame->nlines; if (s->lineno) { long ln; rc = strtonumcheck(&ln, s->lineno, 1, blame->nlines); if (rc) goto end; s->gtl = ln; } blame->lines = calloc(blame->nlines, sizeof(*blame->lines)); if (blame->lines == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "calloc"); goto end; } |
︙ | ︙ | |||
8952 8953 8954 8955 8956 8957 8958 | regmatch_t *regmatch = &view->regmatch; struct fnc_colour *c = NULL; wchar_t *wcstr; char *line = NULL; fsl_uuid_str prev_id = NULL; ssize_t linelen; size_t linesz = 0; | | < | | 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 | regmatch_t *regmatch = &view->regmatch; struct fnc_colour *c = NULL; wchar_t *wcstr; char *line = NULL; fsl_uuid_str prev_id = NULL; ssize_t linelen; size_t linesz = 0; int col, width, lineno = 0, nprinted = 0; int rc = FSL_RC_OK; const int idfield = 11; /* Prefix + space. */ bool selected; rewind(blame->f); werase(view->window); if ((line = fsl_mprintf("checkin %s", s->blamed_commit->id)) == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ERROR), "%s", "fsl_mprintf"); return rc; } rc = formatln(&wcstr, &width, line, view->ncols, 0, false); fsl_free(line); line = NULL; if (rc) return rc; if (screen_is_shared(view)) wattron(view->window, A_REVERSE); if (s->colour) |
︙ | ︙ | |||
8989 8990 8991 8992 8993 8994 8995 | } if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (screen_is_shared(view)) wattroff(view->window, A_REVERSE); fsl_free(wcstr); wcstr = NULL; | < < | < | | > | 9220 9221 9222 9223 9224 9225 9226 9227 9228 9229 9230 9231 9232 9233 9234 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 9249 9250 9251 9252 9253 9254 | } if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (screen_is_shared(view)) wattroff(view->window, A_REVERSE); fsl_free(wcstr); wcstr = NULL; line = fsl_mprintf("[%d/%d] %s%s %c", s->gtl ? s->gtl : MIN(blame->nlines, s->first_line_onscreen - 1 + s->selected_line), blame->nlines, s->blame_complete ? "" : "annotating... ", s->path, s->blame_complete ? ' ' : SPINNER[s->spin_idx]); if (SPINNER[++s->spin_idx] == '\0') s->spin_idx = 0; rc = formatln(&wcstr, &width, line, view->ncols, 0, false); fsl_free(line); line = NULL; if (rc) return rc; waddwstr(view->window, wcstr); fsl_free(wcstr); wcstr = NULL; if (width < view->ncols - 1) waddch(view->window, '\n'); s->eof = false; while (nprinted < view->nlines - 2) { width = col = 0; attr_t rx = 0; linelen = getline(&line, &linesz, blame->f); if (linelen == -1) { if (feof(blame->f)) { s->eof = true; break; } |
︙ | ︙ | |||
9067 9068 9069 9070 9071 9072 9073 | fsl_free(id_str); prev_id = blame_line->id; } else { waddstr(view->window, ".........."); prev_id = NULL; } if (s->showln) | | > < | | < < < < < | | > | | < > > > | > > | | 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343 9344 9345 9346 9347 9348 9349 9350 | fsl_free(id_str); prev_id = blame_line->id; } else { waddstr(view->window, ".........."); prev_id = NULL; } if (s->showln) col = draw_lineno(view, blame->nlines, blame_line->lineno, rx); } else { waddstr(view->window, ".........."); prev_id = NULL; } col += idfield; if (selected) wattroff(view->window, rx); waddch(view->window, ' '); if (view->ncols <= idfield) { wcstr = wcsdup(L""); if (wcstr == NULL) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_RANGE), "%s", "wcsdup"); fsl_free(line); return rc; } } else if (s->first_line_onscreen + nprinted == s->matched_line && regmatch->rm_so >= 0 && regmatch->rm_so < regmatch->rm_eo) { rc = draw_matched_line(view, line, &width, view->ncols - col, 0, regmatch, 0); } else if (view->pos.col < etcount(line, linelen) - 1) { rc = formatln(&wcstr, &width, line, view->pos.col + view->ncols - col, 0, true); /* XXX Change above else if to else to use next line. */ /* if (view->pos.col < width) */ waddwstr(view->window, wcstr + view->pos.col); fsl_free(wcstr); wcstr = NULL; } if (rc) { fsl_free(line); return rc; } col += MAX(width - view->pos.col, 0); if (col < view->ncols) waddch(view->window, '\n'); if (++nprinted == 1) s->first_line_onscreen = lineno; } fsl_free(line); s->last_line_onscreen = lineno; |
︙ | ︙ | |||
9211 9212 9213 9214 9215 9216 9217 | break; case KEY_LEFT: case 'h': view->pos.col -= MIN(view->pos.col, 2); break; case 'q': s->done = true; | | | | 9440 9441 9442 9443 9444 9445 9446 9447 9448 9449 9450 9451 9452 9453 9454 9455 | break; case KEY_LEFT: case 'h': view->pos.col -= MIN(view->pos.col, 2); break; case 'q': s->done = true; if (s->selected_entry) fnc_commit_artifact_close(s->selected_entry); break; case 'c': s->colour = !s->colour; break; case 'g': if (!fnc_home(view)) break; |
︙ | ︙ | |||
9285 9286 9287 9288 9289 9290 9291 | break; } if (s->first_line_onscreen > nscroll) s->first_line_onscreen -= nscroll; else s->first_line_onscreen = 1; break; | | | | 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 9526 9527 9528 9529 9530 | break; } if (s->first_line_onscreen > nscroll) s->first_line_onscreen -= nscroll; else s->first_line_onscreen = 1; break; case '@': { struct input input = {(int []){1, s->blame.nlines}, "line: ", INPUT_NUMERIC, SR_CLREOL}; rc = fnc_prompt_input(view, &input); s->gtl = input.ret; break; } case '#': s->showln = !s->showln; break; |
︙ | ︙ | |||
9332 9333 9334 9335 9336 9337 9338 | if (fsl_deck_F_search(&d, s->path + (fnc_init.sym ? 0 : 1)) == NULL) { char *m = fsl_mprintf("-- %s not in [%.12s] --", s->path + (fnc_init.sym ? 0 : 1), pid); if (m == NULL) rc = RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); | | > | 9561 9562 9563 9564 9565 9566 9567 9568 9569 9570 9571 9572 9573 9574 9575 9576 | if (fsl_deck_F_search(&d, s->path + (fnc_init.sym ? 0 : 1)) == NULL) { char *m = fsl_mprintf("-- %s not in [%.12s] --", s->path + (fnc_init.sym ? 0 : 1), pid); if (m == NULL) rc = RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); sitrep(view, m, SR_CLREOL | SR_UPDATE | SR_SLEEP); fsl_deck_finalize(&d); fsl_free(pid); fsl_free(m); break; } rc = fnc_commit_qid_alloc(&s->blamed_commit, pid); if (rc) |
︙ | ︙ | |||
9414 9415 9416 9417 9418 9419 9420 | fsl_stmt *q = NULL; fsl_uuid_cstr id = NULL; id = get_selected_commit_id(s->blame.lines, s->blame.nlines, s->first_line_onscreen, s->selected_line); if (id == NULL) break; | | | | | | | 9644 9645 9646 9647 9648 9649 9650 9651 9652 9653 9654 9655 9656 9657 9658 9659 9660 9661 9662 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 9678 9679 9680 | fsl_stmt *q = NULL; fsl_uuid_cstr id = NULL; id = get_selected_commit_id(s->blame.lines, s->blame.nlines, s->first_line_onscreen, s->selected_line); if (id == NULL) break; if (s->selected_entry) fnc_commit_artifact_close(s->selected_entry); if (rc) break; q = fsl_stmt_malloc(); rc = commit_builder(&commit, fsl_uuid_to_rid(f, id), q); fsl_stmt_finalize(q); if (rc) { fnc_commit_artifact_close(commit); break; } if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF); if (diff_view == NULL) { fnc_commit_artifact_close(commit); rc = RC(FSL_RC_ERROR, "%s", "view_open"); break; } rc = open_diff_view(diff_view, commit, DEF_DIFF_CTX, fnc_init.sbs, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, fnc_init.showln, NULL, true, NULL); s->selected_entry = commit; if (rc) { fnc_commit_artifact_close(commit); view_close(diff_view); break; } view->active = false; diff_view->active = true; |
︙ | ︙ | |||
9657 9658 9659 9660 9661 9662 9663 | return rc; } glob = fsl_strdup(fcli_next_arg(true)); rc = init_curses(); if (rc) goto end; | < < | < > > | | 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 9901 9902 9903 9904 9905 9906 | return rc; } glob = fsl_strdup(fcli_next_arg(true)); rc = init_curses(); if (rc) goto end; rc = init_unveil(REPODB, CKOUTDIR, false); if (rc) goto end; rc = init_landlock((const char*[]){REPODIR, CKOUTDIR, P_tmpdir}, 3); if (rc) goto end; view = view_open(0, 0, 0, 0, FNC_VIEW_BRANCH); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } |
︙ | ︙ | |||
9686 9687 9688 9689 9690 9691 9692 | static int open_branch_view(struct fnc_view *view, int branch_flags, const char *glob, double dateline, int when) { struct fnc_branch_view_state *s = &view->state.branch; int rc = 0; | | | 9915 9916 9917 9918 9919 9920 9921 9922 9923 9924 9925 9926 9927 9928 9929 | static int open_branch_view(struct fnc_view *view, int branch_flags, const char *glob, double dateline, int when) { struct fnc_branch_view_state *s = &view->state.branch; int rc = 0; s->selected_entry = 0; s->colour = !fnc_init.nocolour && has_colors(); s->branch_flags = branch_flags; s->branch_glob = glob; s->dateline = dateline; s->when = when; rc = fnc_load_branches(s); |
︙ | ︙ | |||
9966 9967 9968 9969 9970 9971 9972 | be = s->first_branch_onscreen; if ((line = fsl_mprintf("branches [%d/%d]", be->idx + s->selected + 1, s->nbranches)) == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); | | | 10195 10196 10197 10198 10199 10200 10201 10202 10203 10204 10205 10206 10207 10208 10209 | be = s->first_branch_onscreen; if ((line = fsl_mprintf("branches [%d/%d]", be->idx + s->selected + 1, s->nbranches)) == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); rc = formatln(&wline, &width, line, view->ncols, 0, false); if (rc) { fsl_free(line); return rc; } if (screen_is_shared(view)) wattron(view->window, A_REVERSE); waddwstr(view->window, wline); |
︙ | ︙ | |||
10006 10007 10008 10009 10010 10011 10012 | s->show_date ? be->branch->date : 0); if (line == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); if (s->colour) c = match_colour(&s->colours, line); | | | | 10235 10236 10237 10238 10239 10240 10241 10242 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 | s->show_date ? be->branch->date : 0); if (line == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); if (s->colour) c = match_colour(&s->colours, line); rc = formatln(&wline, &width, line, view->ncols, 0, false); if (rc) { fsl_free(line); return rc; } if (n == s->selected) { if (view->active) wattr_on(view->window, A_REVERSE, NULL); s->selected_entry = be; } if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddwstr(view->window, wline); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (width < view->ncols - 1) |
︙ | ︙ | |||
10063 10064 10065 10066 10067 10068 10069 | break; case 'i': s->show_id = !s->show_id; break; case KEY_ENTER: case '\r': case ' ': | | | | | 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 10306 10307 10308 10309 10310 10311 10312 10313 10314 10315 10316 10317 10318 10319 10320 10321 10322 10323 10324 10325 10326 10327 10328 10329 10330 10331 | break; case 'i': s->show_id = !s->show_id; break; case KEY_ENTER: case '\r': case ' ': if (!s->selected_entry) break; rc = request_view(new_view, view, FNC_VIEW_TIMELINE); break; case 's': /* * Toggle branch list sort order (cf. branch --sort option): * lexicographical (default) -> most recently used -> state */ if (FLAG_CHK(s->branch_flags, BRANCH_SORT_MTIME)) { FLAG_CLR(s->branch_flags, BRANCH_SORT_MTIME); FLAG_SET(s->branch_flags, BRANCH_SORT_STATUS); } else if (FLAG_CHK(s->branch_flags, BRANCH_SORT_STATUS)) FLAG_CLR(s->branch_flags, BRANCH_SORT_STATUS); else FLAG_SET(s->branch_flags, BRANCH_SORT_MTIME); fnc_free_branches(&s->branches); rc = fnc_load_branches(s); break; case 't': if (!s->selected_entry) break; if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); rc = browse_branch_tree(&tree_view, start_col, s->selected_entry); if (rc || tree_view == NULL) break; view->active = false; tree_view->active = true; if (view_is_parent(view)) { rc = view_close_child(view); if (rc) |
︙ | ︙ | |||
10271 10272 10273 10274 10275 10276 10277 | if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (s->matched_branch) { if (view->searching == SEARCH_FORWARD) { | | | | | | | 10500 10501 10502 10503 10504 10505 10506 10507 10508 10509 10510 10511 10512 10513 10514 10515 10516 10517 10518 10519 10520 10521 10522 10523 10524 | if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (s->matched_branch) { if (view->searching == SEARCH_FORWARD) { if (s->selected_entry) be = TAILQ_NEXT(s->selected_entry, entries); else be = TAILQ_PREV(s->selected_entry, fnc_branchlist_head, entries); } else { if (s->selected_entry == NULL) be = TAILQ_LAST(&s->branches, fnc_branchlist_head); else be = TAILQ_PREV(s->selected_entry, fnc_branchlist_head, entries); } } else { if (view->searching == SEARCH_FORWARD) be = TAILQ_FIRST(&s->branches); else be = TAILQ_LAST(&s->branches, fnc_branchlist_head); |
︙ | ︙ | |||
10491 10492 10493 10494 10495 10496 10497 | printf("%s %s\n", fcli_progname(), PRINT_VERSION); } static int strtonumcheck(long *ret, const char *nstr, const int min, const int max) { const char *ptr; | | > > > > | | < | | < | < | | | | | < < | | < | | | > > > > | 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 10732 10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746 10747 10748 10749 10750 10751 10752 10753 10754 10755 10756 10757 10758 10759 10760 10761 10762 10763 10764 10765 10766 10767 10768 10769 10770 10771 10772 10773 10774 10775 10776 10777 10778 10779 10780 10781 10782 10783 10784 10785 10786 10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 10803 10804 10805 10806 10807 10808 10809 10810 10811 10812 10813 10814 10815 10816 10817 10818 10819 10820 | printf("%s %s\n", fcli_progname(), PRINT_VERSION); } static int strtonumcheck(long *ret, const char *nstr, const int min, const int max) { const char *ptr; long n; ptr = NULL; errno = 0; /* * Ubuntu strtol has weird errno and return semantics compared to Unix * implementations so we get a range error for "all_alpha_char" strings. */ n = strtonum(nstr, min, max, &ptr); if (errno == EINVAL) return RC(FSL_RC_MISUSE, "not a number: %s", nstr); else if (errno != 0 || errno == ERANGE || !inrange(n, min, max)) return RC(FSL_RC_RANGE, "out of range: %s", nstr); else if (ptr && *ptr != '\0') return RC(FSL_RC_MISUSE, "invalid char: %s", nstr); *ret = n; return FSL_RC_OK; } static int fnc_prompt_input(struct fnc_view *view, struct input *input) { int rc = FSL_RC_OK; if (input->prompt) sitrep(view, input->prompt, input->flags); rc = cook_input(input->buf, sizeof(input->buf), view->window); if (rc || !input->buf[0]) return rc; if (input->type == INPUT_NUMERIC) { long n = 0; int min = INT_MIN, max = INT_MAX; if (input->data) { min = *(int *)input->data; max = ((int *)input->data)[1]; } rc = strtonumcheck(&n, input->buf, min, max); if (rc == FSL_RC_MISUSE) rc = sitrep(view, "-- numeric input only --", SR_CLREOL | SR_UPDATE | SR_SLEEP | SR_RESET); else if (rc == FSL_RC_RANGE || n < min || n > max) rc = sitrep(view, "-- line outside range --", SR_CLREOL | SR_UPDATE | SR_SLEEP | SR_RESET); else input->ret = n; } return rc; } static int cook_input(char *ret, int sz, WINDOW *win) { int rc; nocbreak(); echo(); rc = wgetnstr(win, ret, sz); cbreak(); noecho(); raw(); return rc == ERR ? FSL_RC_ERROR : FSL_RC_OK; } static int sitrep(struct fnc_view *view, const char *msg, int flags) { wattr_on(view->window, A_BOLD, NULL); mvwaddstr(view->window, view->nlines - 1, 0, msg); if (FLAG_CHK(flags, SR_CLREOL)) wclrtoeol(view->window); wattr_off(view->window, A_BOLD, NULL); if (FLAG_CHK(flags, SR_UPDATE)) { update_panels(); doupdate(); } if (FLAG_CHK(flags, SR_RESET)) fcli_err_reset(); if (FLAG_CHK(flags, SR_SLEEP)) sleep(1); return FSL_RC_OK; } /* * Attempt to parse string d, which must resemble either an ISO8601 formatted * date (e.g., 2021-10-10, 2020-01-01T10:10:10), disgregarding any trailing * garbage or space characters such that "2021-10-10x" or "2020-01-01 10:10:10" * will pass, or an _unambiguous_ DD/MM/YYYY or MM/DD/YYYY formatted date. Upon |
︙ | ︙ | |||
10684 10685 10686 10687 10688 10689 10690 | if (*glob == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); } return 0; } | > > > > > > | > > > > > > > > > > > > > > > | > > > | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943 10944 10945 10946 10947 10948 10949 10950 10951 10952 10953 10954 10955 10956 10957 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 | if (*glob == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); } return 0; } static const char * getdirname(const char *path, fsl_int_t len, bool slash) { fsl_size_t n = (len > 0) ? (fsl_size_t)len : fsl_strlen(path); const char *p = path + n; static char ret[PATH_MAX]; if (!path || !*path || !len || !n) return NULL; while (--p >= path) if (*p == '/') { if (!slash) --p; break; } memset(ret, 0, PATH_MAX); memcpy(ret, path, p - path + 1); return ret; } /* * Read permissions for the below unveil() calls are self-evident; we need * to read the repository and ckout databases, and ckout dir for most all fnc * operations. Write and create permissions are briefly listed inline, but we * effectively veil the entire fs except the repo db, ckout, and /tmp dirs. * The create permissions for the repository and checkout dirs are (perhaps * unintuitively) needed as fossil(1) creates temporary journal files in both. */ static int init_unveil(const char *repodb, const char *ckoutdir, bool cfg) { #ifdef __OpenBSD__ /* wc repo db for 'fnc config' command: fnc_conf_setopt(). */ if (unveil(repodb, cfg ? "rwc" : "rw") == -1) return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "unveil(%s, \"rw\")", repodb); /* wc .fslckout for fsl_ckout_changes_scan() in cmd_diff(). */ if (unveil(ckoutdir, "rwc") == -1) return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "unveil(%s, \"rw\")", ckoutdir); /* rwc /tmp for tmpfile() in help(), create_diff(), and run_blame(). */ if (unveil(P_tmpdir, "rwc") == -1) return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "unveil(%s, \"rwc\")", P_tmpdir); if (unveil(NULL, NULL) == -1) return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "%s", "unveil"); #endif /* __OpenBSD__ */ return FSL_RC_OK; } #ifdef HAVE_LANDLOCK /* * Sans libc wrappers, use the following shims provided by Landlock authors. * https://www.kernel.org/doc/html/latest/userspace-api/landlock.html */ #ifndef landlock_create_ruleset static inline int landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, const size_t size, const __u32 flags) { return syscall(__NR_landlock_create_ruleset, attr, size, flags); } #endif #ifndef landlock_add_rule static inline int landlock_add_rule(const int rfd, const enum landlock_rule_type type, const void *const attr, const __u32 flags) { return syscall(__NR_landlock_add_rule, rfd, type, attr, flags); } #endif #ifndef landlock_restrict_self static inline int landlock_restrict_self(const int rfd, const __u32 flags) { return syscall(__NR_landlock_restrict_self, rfd, flags); } #endif #endif /* HAVE_LANDLOCK */ /* * Similar to unveil(), grant read and write permissisions to the repo and * ckout files, and create permissions to the ckout, repo, and tmp dirs. */ static int init_landlock(const char **paths, const int n) { int rc = FSL_RC_OK; #ifdef HAVE_LANDLOCK /* * Define default block list of _all_ possible operations. * XXX Due to the fail-open design, set all the bits to avoid following * Landlock for new ops to be added to this deny-by-default list. */ struct landlock_ruleset_attr attr = { .handled_access_fs = 0xffffffffffffffffULL }; /* Define allowed operations. */ struct landlock_path_beneath_attr path_beneath { .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_MAKE_REG } int rfd; rfd = landlock_create_ruleset(&attr, sizeof(attr), 0); if (rfd == -1) { /* Landlock is not supported or disabled by the kernel. */ if (errno == ENOSYS || errno == EOPNOTSUPP) return rc; return RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "landlock: %s", "failed to create ruleset"); } /* Iterate paths to grant fs permissions. */ for (i = 0; !rc && i < n; ++i) { path_beneath.parent_fd = open(paths[i], O_PATH | O_CLOEXEC); if (path_beneath.parent_fd == -1) rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "landlock: failed to open dir '%s'", paths[i]); else { if (landlock_add_rule(rfd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "landlock: %s", "failed to update ruleset"); close(path_beneath.parent_fd); } } if (!rc && prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "landlock: %s", "failed to restrict privileges"); if (!rc && landlock_restrict_self(rfd, 0)) { rc = RC(fsl_errno_to_rc(errno, FSL_RC_ACCESS), "landlock: %s", "failed to enforce ruleset"); } close(rfd); #endif /* HAVE_LANDLOCK */ return rc; } |