Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From 0.9 To 0.8
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 3 4 5 6 7 8 9 10 | **fnc 0.8** 2022-01-10 - fix vertical split - 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.7 ## 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 22 | # # FNC Common Build # # CONFIGURATION CC ?= cc PREFIX ?= /usr/local MANDIR ?= /share/man VERSION ?= 0.8 # FLAGS NEEDED TO BUILD SQLITE3 SQLITE_CFLAGS = ${CFLAGS} -Wall -Werror -Wno-sign-compare -pedantic -std=c99 \ -DNDEBUG=1 \ -DSQLITE_DQS=0 \ -DSQLITE_THREADSAFE=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 \ |
︙ | ︙ | |||
52 53 54 55 56 57 58 | 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 $@ | < < < | | | < | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 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/fnc.o: src/fnc.c include/settings.h fnc.bld.mk ${CC} ${FNC_CFLAGS} -c $< -o $@ src/fnc: src/fnc.o lib/libfossil.o lib/sqlite3.o fnc.bld.mk ${CC} -o $@ src/fnc.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 |
︙ | ︙ |
Deleted include/diff.h.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to include/settings.h.
︙ | ︙ | |||
32 33 34 35 36 37 38 | _(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), \ | < | 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), \ |
︙ | ︙ | |||
56 57 58 59 60 61 62 | _(pfx, MONO) #define INPUT_TYPE_ENUM(pfx, _) \ _(pfx, ALPHA), \ _(pfx, NUMERIC) #define LINE_TYPE_ENUM(pfx, _) \ | < < < < | 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(_) \ |
︙ | ︙ |
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; 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 * repoMtime, fsl_time_t * 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 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){ | > > | 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899 | 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){ fsl_buffer dir = fsl_buffer_empty; 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)); } } fsl_buffer_reserve(&dir, 0); 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){ |
︙ | ︙ | |||
14134 14135 14136 14137 14138 14139 14140 | 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? */ | | | 14136 14137 14138 14139 14140 14141 14142 14143 14144 14145 14146 14147 14148 14149 14150 | 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_ckout_close(f); } } end: fsl__cx_scratchpad_yield(f, buf); fsl__cx_scratchpad_yield(f, bufD); return rc; } |
︙ | ︙ | |||
23100 23101 23102 23103 23104 23105 23106 | f->cache.isCrosslinking = true; }else{ fsl_cx_transaction_end(f, true); } return rc; } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 23102 23103 23104 23105 23106 23107 23108 23109 23110 23111 23112 23113 23114 23115 | 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 */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
︙ | ︙ | |||
23830 23831 23832 23833 23834 23835 23836 | return fsl_delta_apply2(zSrc, lenSrc_, zDelta, lenDelta_, zOut, NULL); } #undef NHASH #undef DEBUG1 #undef DEBUG2 /* end of file ./src/delta.c */ | | | | < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 23754 23755 23756 23757 23758 23759 23760 23761 23762 23763 23764 23765 23766 23767 23768 23769 23770 23771 23772 23773 23774 23775 23776 23777 23778 23779 23780 23781 23782 23783 23784 23785 23786 23787 23788 23789 23790 23791 23792 23793 23794 23795 23796 23797 23798 23799 23800 23801 23802 23803 23804 23805 23806 23807 23808 23809 23810 23811 23812 23813 23814 23815 23816 23817 23818 23819 23820 23821 23822 23823 23824 23825 23826 23827 23828 23829 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 23876 23877 23878 23879 23880 23881 23882 23883 23884 23885 23886 23887 23888 23889 23890 23891 23892 23893 23894 23895 23896 23897 23898 23899 23900 23901 23902 23903 23904 23905 23906 23907 23908 23909 23910 23911 23912 23913 23914 23915 23916 23917 23918 23919 23920 23921 23922 23923 23924 23925 23926 23927 23928 23929 23930 23931 23932 23933 23934 23935 23936 23937 23938 23939 23940 23941 23942 23943 23944 23945 23946 23947 23948 23949 23950 23951 23952 23953 23954 23955 23956 23957 23958 23959 23960 23961 23962 23963 23964 23965 23966 23967 23968 23969 23970 23971 23972 23973 23974 23975 23976 23977 23978 23979 23980 23981 23982 23983 23984 23985 23986 23987 23988 23989 23990 23991 23992 23993 23994 23995 23996 23997 23998 23999 24000 24001 24002 24003 24004 24005 24006 24007 24008 24009 24010 24011 24012 24013 24014 24015 24016 24017 24018 24019 24020 24021 24022 24023 24024 24025 24026 24027 24028 24029 24030 24031 24032 24033 24034 24035 24036 24037 24038 24039 24040 24041 24042 24043 24044 24045 24046 24047 24048 24049 24050 24051 24052 24053 24054 24055 24056 24057 24058 24059 24060 24061 24062 24063 24064 24065 24066 24067 24068 24069 24070 24071 24072 24073 24074 24075 24076 24077 24078 24079 24080 24081 24082 24083 24084 24085 24086 24087 24088 24089 24090 24091 24092 24093 24094 24095 24096 24097 24098 24099 24100 24101 24102 24103 24104 24105 24106 24107 24108 24109 24110 24111 24112 24113 24114 24115 24116 24117 24118 24119 24120 24121 24122 24123 24124 24125 24126 24127 24128 24129 24130 24131 24132 24133 24134 24135 24136 24137 24138 24139 24140 24141 24142 24143 24144 24145 24146 24147 24148 24149 24150 24151 24152 24153 24154 24155 24156 24157 24158 24159 24160 24161 24162 24163 24164 24165 24166 24167 24168 24169 24170 24171 24172 24173 24174 24175 24176 24177 24178 24179 24180 24181 24182 24183 24184 24185 24186 24187 24188 24189 24190 24191 24192 24193 24194 24195 24196 24197 24198 24199 24200 24201 24202 24203 24204 24205 24206 24207 24208 24209 24210 24211 24212 24213 24214 24215 24216 24217 24218 24219 24220 24221 24222 24223 24224 24225 24226 24227 24228 24229 24230 24231 24232 24233 24234 24235 24236 24237 24238 24239 24240 24241 24242 24243 24244 24245 24246 24247 24248 24249 24250 24251 24252 24253 24254 24255 24256 24257 24258 24259 24260 24261 24262 24263 24264 24265 24266 24267 24268 24269 24270 24271 24272 24273 24274 24275 24276 24277 24278 24279 24280 24281 24282 24283 24284 24285 24286 24287 24288 24289 24290 24291 24292 24293 24294 24295 24296 24297 24298 24299 24300 24301 24302 24303 24304 24305 24306 24307 24308 24309 24310 24311 24312 24313 24314 24315 24316 24317 24318 24319 24320 24321 24322 24323 24324 24325 24326 24327 24328 24329 24330 24331 24332 24333 24334 24335 24336 24337 24338 24339 24340 24341 24342 24343 24344 24345 24346 24347 24348 24349 24350 24351 24352 24353 24354 24355 24356 24357 24358 24359 24360 24361 24362 24363 24364 24365 24366 24367 24368 24369 24370 24371 24372 24373 24374 24375 24376 24377 24378 24379 24380 24381 24382 24383 24384 24385 24386 24387 24388 24389 24390 24391 24392 24393 24394 24395 24396 24397 24398 24399 24400 24401 24402 24403 24404 24405 24406 24407 24408 24409 24410 24411 24412 24413 24414 24415 24416 24417 24418 24419 24420 24421 24422 24423 24424 24425 24426 24427 24428 24429 24430 24431 24432 24433 24434 24435 24436 24437 24438 24439 24440 24441 24442 24443 24444 24445 24446 24447 24448 24449 24450 24451 24452 24453 24454 24455 24456 24457 24458 24459 24460 24461 24462 24463 24464 24465 24466 24467 24468 24469 24470 24471 24472 24473 24474 24475 24476 24477 24478 24479 24480 24481 24482 24483 24484 24485 24486 24487 24488 24489 24490 24491 24492 24493 24494 24495 24496 24497 24498 24499 24500 24501 24502 24503 24504 24505 24506 24507 24508 24509 24510 24511 24512 24513 24514 24515 24516 24517 24518 24519 24520 24521 24522 24523 24524 24525 24526 24527 24528 24529 24530 24531 24532 24533 24534 24535 24536 24537 24538 24539 24540 24541 24542 24543 24544 24545 24546 24547 24548 24549 24550 24551 24552 24553 24554 24555 24556 24557 24558 24559 24560 24561 24562 24563 24564 24565 24566 24567 24568 24569 24570 24571 24572 24573 24574 24575 24576 24577 24578 24579 24580 24581 24582 24583 24584 24585 24586 24587 24588 24589 24590 24591 24592 24593 24594 24595 24596 24597 24598 24599 24600 24601 24602 24603 24604 24605 24606 24607 24608 24609 24610 24611 24612 24613 24614 24615 24616 24617 24618 24619 24620 24621 24622 24623 24624 24625 24626 24627 24628 24629 24630 24631 24632 24633 24634 24635 24636 24637 24638 24639 24640 24641 24642 24643 24644 24645 24646 24647 24648 24649 24650 24651 24652 24653 24654 24655 24656 24657 24658 24659 24660 24661 24662 24663 24664 24665 24666 24667 24668 24669 24670 24671 24672 24673 24674 24675 24676 24677 24678 24679 24680 24681 24682 24683 24684 24685 24686 24687 24688 24689 24690 24691 24692 24693 24694 24695 24696 24697 24698 24699 24700 24701 24702 24703 24704 24705 24706 24707 24708 24709 24710 24711 24712 24713 24714 24715 24716 24717 24718 24719 24720 24721 24722 24723 24724 24725 24726 24727 24728 24729 24730 24731 24732 24733 24734 24735 24736 24737 24738 24739 24740 24741 24742 24743 24744 24745 24746 24747 24748 24749 24750 24751 24752 24753 24754 24755 24756 24757 24758 24759 24760 24761 24762 24763 24764 24765 24766 24767 24768 24769 24770 24771 24772 24773 24774 24775 24776 24777 24778 24779 24780 24781 24782 24783 24784 24785 24786 24787 24788 24789 24790 24791 24792 24793 24794 24795 24796 24797 24798 24799 24800 24801 24802 24803 24804 24805 24806 24807 24808 24809 24810 24811 24812 24813 24814 24815 24816 24817 24818 24819 24820 24821 24822 24823 24824 24825 24826 24827 24828 24829 24830 24831 24832 24833 24834 24835 24836 24837 24838 24839 24840 24841 24842 24843 24844 24845 24846 24847 24848 24849 24850 24851 24852 24853 24854 24855 24856 24857 24858 24859 24860 24861 24862 24863 24864 24865 24866 24867 24868 24869 24870 24871 24872 24873 24874 24875 24876 24877 24878 24879 24880 24881 24882 24883 24884 24885 24886 24887 24888 24889 24890 24891 24892 24893 24894 24895 24896 24897 24898 24899 24900 24901 24902 24903 24904 24905 24906 24907 24908 24909 24910 24911 24912 24913 24914 24915 24916 24917 24918 24919 24920 24921 24922 24923 24924 24925 24926 24927 24928 24929 24930 24931 24932 24933 24934 24935 24936 24937 24938 24939 24940 24941 24942 24943 24944 24945 24946 24947 24948 24949 24950 24951 24952 24953 24954 24955 24956 24957 24958 24959 24960 24961 24962 24963 24964 24965 24966 24967 24968 24969 24970 24971 24972 24973 24974 24975 24976 24977 24978 24979 24980 24981 24982 24983 24984 24985 24986 24987 24988 24989 24990 24991 24992 24993 24994 24995 24996 24997 24998 24999 25000 25001 25002 25003 25004 25005 25006 25007 25008 25009 25010 25011 25012 25013 25014 25015 25016 25017 25018 25019 25020 25021 25022 25023 25024 25025 25026 25027 25028 25029 25030 25031 25032 25033 25034 25035 25036 25037 25038 25039 25040 25041 25042 25043 25044 25045 25046 25047 25048 25049 25050 25051 25052 25053 25054 25055 25056 25057 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 27063 27064 27065 27066 27067 27068 27069 27070 27071 27072 27073 27074 27075 27076 27077 27078 27079 27080 27081 27082 27083 27084 27085 27086 27087 27088 27089 27090 27091 27092 27093 27094 27095 27096 27097 27098 27099 27100 27101 27102 27103 27104 27105 27106 27107 27108 27109 27110 27111 27112 27113 27114 27115 27116 27117 27118 27119 27120 27121 27122 27123 27124 27125 27126 27127 27128 27129 27130 27131 27132 27133 27134 27135 27136 27137 27138 27139 27140 27141 27142 27143 27144 27145 27146 27147 27148 | 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/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-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ /************************************************************************ This file houses Fossil's diff-generation routines (as opposed to the delta-generation). */ #include <assert.h> #include <memory.h> #include <stdlib.h> #include <string.h> /* for memmove()/strlen() */ /** 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; } /** 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; } /** 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 ){ /* 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; } /* 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){ 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__); } #if !defined(FSL_OMIT_DEPRECATED) #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) typedef uint64_t u64; typedef void ReCompiled /* porting crutch. i would strongly prefer to replace the regex support with a stateful predicate callback. */; #define DIFF_CONTEXT_MASK ((u64)0x0000ffff) /* Lines of context. Default if 0 */ #define DIFF_WIDTH_MASK ((u64)0x00ff0000) /* side-by-side column width */ #define DIFF_IGNORE_EOLWS ((u64)0x01000000) /* Ignore end-of-line whitespace */ #define DIFF_IGNORE_ALLWS ((u64)0x03000000) /* Ignore all whitespace */ #define DIFF_SIDEBYSIDE ((u64)0x04000000) /* Generate a side-by-side diff */ #define DIFF_VERBOSE ((u64)0x08000000) /* Missing shown as empty files */ #define DIFF_BRIEF ((u64)0x10000000) /* Show filenames only */ #define DIFF_HTML ((u64)0x20000000) /* Render for HTML */ #define DIFF_LINENO ((u64)0x40000000) /* Show line numbers */ #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */ #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ /* 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"*/ /** Converts mask of public fsl_diff_flag_t (32-bit) values to the Fossil-internal 64-bit bitmask used by the DIFF_xxx macros. Why? (A) 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. */ static uint64_t fsl_diff_flags_convert( int mask ){ uint64_t rc = 0U; #define DO(F) if( (mask & F)==F ) rc |= (((u64)F) << 24) DO(FSL_DIFF2_IGNORE_EOLWS); DO(FSL_DIFF2_IGNORE_ALLWS); DO(FSL_DIFF2_LINE_NUMBERS); DO(FSL_DIFF_SIDEBYSIDE); DO(FSL_DIFF2_NOTTOOBIG); DO(FSL_DIFF2_STRIP_EOLCR); DO(FSL_DIFF_VERBOSE); DO(FSL_DIFF_BRIEF); DO(FSL_DIFF_HTML); DO(FSL_DIFF_NOOPT); DO(FSL_DIFF_INVERT); #undef DO return rc; } /** Holds output state for diff generation. */ struct DiffOutState { /** Output callback. */ fsl_output_f out; /** State for this->out(). */ void * oState; /** For propagating output errors. */ int rc; char ansiColor; }; typedef struct DiffOutState DiffOutState; static const DiffOutState DiffOutState_empty = { NULL/*out*/, NULL/*oState*/, 0/*rc*/, 0/*useAnsiColor*/ }; /** Internal helper. Sends src to o->out(). If n is negative, fsl_strlen() is used to determine the length. */ static int diff_out( DiffOutState * const o, void const * src, fsl_int_t n ){ return o->rc = n ? o->out(o->oState, src, n<0 ? fsl_strlen((char const *)src) : (fsl_size_t)n) : 0; } /** fsl_output_f() impl for use with diff_outf(). state must be a (DiffOutState*). */ static int fsl_output_f_diff_out( void * state, void const * src, fsl_size_t n ){ DiffOutState * const os = (DiffOutState *)state; return os->rc = os->out(os->oState, src, n); } static int diff_outf( DiffOutState * o, char const * fmt, ... ){ va_list va; va_start(va,fmt); fsl_appendfv(fsl_output_f_diff_out, o, fmt, va); va_end(va); return o->rc; } /* Append a single line of context-diff output to pOut. */ static int appendDiffLine( DiffOutState *const pOut, /* Where to write the line of output */ char cPrefix, /* One of " ", "+", or "-" */ fsl_dline *pLine, /* The line to be output */ int html, /* True if generating HTML. False for plain text */ ReCompiled *pRe /* Colorize only if line matches this Regex */ ){ int rc = 0; char const * ansiPrefix = !pOut->ansiColor ? NULL : (('+'==cPrefix) ? ANSI_DIFF_ADD(0) : (('-'==cPrefix) ? ANSI_DIFF_RM(0) : NULL)) ; if(ansiPrefix) rc = diff_out(pOut, ansiPrefix, -1 ); if(!rc) rc = diff_out(pOut, &cPrefix, 1); if(rc) return rc; else if( html ){ #if 0 if( pRe /*MISSING: && re_dline_match(pRe, pLine, 1)==0 */ ){ cPrefix = ' '; }else #endif if( cPrefix=='+' ){ rc = diff_out(pOut, "<span class=\"fsl-diff-add\">", -1); }else if( cPrefix=='-' ){ rc = diff_out(pOut, "<span class=\"fsl-diff-rm\">", -1); } if(!rc){ /* unsigned short n = pLine->n; */ /* while( n>0 && (pLine->z[n-1]=='\n' || pLine->z[n-1]=='\r') ) n--; */ rc = pOut->rc = fsl_htmlize(pOut->out, pOut->oState, pLine->z, pLine->n); if( !rc && cPrefix!=' ' ){ rc = diff_out(pOut, "</span>", -1); } } }else{ rc = diff_out(pOut, pLine->z, pLine->n); } if(!rc){ if(ansiPrefix){ rc = diff_out(pOut, ANSI_RESET, -1 ); } if(!rc) rc = diff_out(pOut, "\n", 1); } return rc; } /* Add two line numbers to the beginning of an output line for a context diff. One or the other of the two numbers might be zero, which means to leave that number field blank. The "html" parameter means to format the output for HTML. */ static int appendDiffLineno(DiffOutState *pOut, int lnA, int lnB, int html){ int rc = 0; if( html ){ rc = diff_out(pOut, "<span class=\"fsl-diff-lineno\">", -1); } if(!rc){ if( lnA>0 ){ rc = diff_outf(pOut, "%6d ", lnA); }else{ rc = diff_out(pOut, " ", 7); } } if(!rc){ if( lnB>0 ){ rc = diff_outf(pOut, "%6d ", lnB); }else{ rc = diff_out(pOut, " ", 8); } if( !rc && html ){ rc = diff_out(pOut, "</span>", -1); } } return rc; } /** Extract the number of lines of context from diffFlags. */ static int diff_context_lines(uint64_t diffFlags){ int n = diffFlags & DIFF_CONTEXT_MASK; if( n==0 && (diffFlags & DIFF_CONTEXT_EX)==0 ) n = 5; return n; } /* Extract the width of columns for side-by-side diff. Supply an appropriate default if no width is given. */ static int diff_width(uint64_t diffFlags){ int w = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1); if( w==0 ) w = 80; return w; } /* Given a raw diff p[] in which the p->aEdit[] array has been filled in, compute a context diff into pOut. */ static int contextDiff( fsl__diff_cx *p, /* The difference */ DiffOutState *pOut, /* Output a context diff to here */ ReCompiled *pRe, /* Only show changes that match this regex */ u64 diffFlags /* Flags controlling the diff format */ ){ fsl_dline *A; /* Left side of the diff */ fsl_dline *B; /* Right side of the diff */ int a = 0; /* Index of next line in A[] */ int b = 0; /* Index of next line in B[] */ int *R; /* Array of COPY/DELETE/INSERT triples */ int r; /* Index into R[] */ int nr; /* Number of COPY/DELETE/INSERT triples to process */ int mxr; /* Maximum value for r */ int na, nb; /* Number of lines shown from A and B */ int i, j; /* Loop counters */ int m; /* Number of lines to output */ int skip; /* Number of lines to skip */ static int nChunk = 0; /* Number of diff chunks seen so far */ int nContext; /* Number of lines of context */ int showLn; /* Show line numbers */ int html; /* Render as HTML */ int showDivider = 0; /* True to show the divider between diff blocks */ int rc = 0; nContext = diff_context_lines(diffFlags); showLn = (diffFlags & DIFF_LINENO)!=0; html = (diffFlags & DIFF_HTML)!=0; A = p->aFrom; B = p->aTo; R = p->aEdit; mxr = p->nEdit; while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } 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]<nContext*2; nr++){} /* printf("r=%d nr=%d\n", r, nr); */ /* 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 0 /* MISSING: re. i would prefer a predicate function, anyway. */ if( 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(pRe, &A[xa], R[r+i*3+1]); c2 = re_dline_match(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 /* For the current block comprising nr triples, figure out how many lines of A and B are to be displayed */ if( R[r]>nContext ){ na = nb = nContext; skip = R[r] - nContext; }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]>nContext ){ na += nContext; nb += nContext; }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]; } /* Show the header for this block, or if we are doing a modified context diff that contains line numbers, show the separator from the previous block. */ ++nChunk; if( showLn ){ if( !showDivider ){ /* Do not show a top divider */ showDivider = 1; }else if( html ){ rc = diff_outf(pOut, "<span class=\"fsl-diff-hr\">%.80c</span>\n", '.'); }else{ rc = diff_outf(pOut, "%.80c\n", '.'); } if( !rc && html ){ rc = diff_outf(pOut, "<span class=\"fsl-diff-chunk-%d\"></span>", nChunk); } }else{ char const * ansi1 = ""; char const * ansi2 = ""; char const * ansi3 = ""; if( html ) rc = diff_outf(pOut, "<span class=\"fsl-diff-lineno\">"); else if(0 && pOut->ansiColor){ /* Turns out this just confuses the output */ ansi1 = ANSI_DIFF_RM(0); ansi2 = ANSI_DIFF_ADD(0); ansi3 = ANSI_RESET; } /* * 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(pOut,"@@ %s-%d,%d %s+%d,%d%s @@", ansi1, na ? a+skip+1 : 0, na, ansi2, nb ? b+skip+1 : 0, nb, ansi3); if( !rc ){ if( html ) rc = diff_outf(pOut, "</span>"); if(!rc) rc = diff_out(pOut, "\n", 1); } } if(rc) return rc; /* Show the initial common area */ a += skip; b += skip; m = R[r] - skip; for(j=0; !rc && j<m; j++){ if( showLn ) rc = appendDiffLineno(pOut, a+j+1, b+j+1, html); if(!rc) rc = appendDiffLine(pOut, ' ', &A[a+j], html, 0); } if(rc) return rc; a += m; b += m; /* Show the differences */ for(i=0; i<nr; i++){ m = R[r+i*3+1]; for(j=0; !rc && j<m; j++){ if( showLn ) rc = appendDiffLineno(pOut, a+j+1, 0, html); if(!rc) rc = appendDiffLine(pOut, '-', &A[a+j], html, pRe); } if(rc) return rc; a += m; m = R[r+i*3+2]; for(j=0; !rc && j<m; j++){ if( showLn ) rc = appendDiffLineno(pOut, 0, b+j+1, html); if(!rc) rc = appendDiffLine(pOut, '+', &B[b+j], html, pRe); } if(rc) return rc; b += m; if( i<nr-1 ){ m = R[r+i*3+3]; for(j=0; !rc && j<m; j++){ if( showLn ) rc = appendDiffLineno(pOut, a+j+1, b+j+1, html); if(!rc) rc = appendDiffLine(pOut, ' ', &A[a+j], html, 0); } if(rc) return rc; b += m; a += m; } } /* Show the final common area */ assert( nr==i ); m = R[r+nr*3]; if( m>nContext ) m = nContext; for(j=0; !rc && j<m; j++){ if( showLn ) rc = appendDiffLineno(pOut, a+j+1, b+j+1, html); if(!rc) rc = appendDiffLine(pOut, ' ', &A[a+j], html, 0); } }/*big for() loop*/ return rc; } /* Status of a single output line */ typedef struct SbsLine SbsLine; struct SbsLine { fsl_buffer *apCols[5]; /* Array of pointers to output columns */ int width; /* Maximum width of a column in the output */ unsigned char escHtml; /* True to escape html characters */ int iStart; /* Write zStart prior to character iStart */ const char *zStart; /* A <span> tag */ int iEnd; /* Write </span> prior to character iEnd */ int iStart2; /* Write zStart2 prior to character iStart2 */ const char *zStart2; /* A <span> tag */ int iEnd2; /* Write </span> prior to character iEnd2 */ ReCompiled *pRe; /* Only colorize matching lines, if not NULL */ DiffOutState * pOut; }; /* Column indices for SbsLine.apCols[] */ #define SBS_LNA 0 /* Left line number */ #define SBS_TXTA 1 /* Left text */ #define SBS_MKR 2 /* Middle separator column */ #define SBS_LNB 3 /* Right line number */ #define SBS_TXTB 4 /* Right text */ /* Append newlines to all columns. */ static int sbsWriteNewlines(SbsLine *p){ int i; int rc = 0; for( i=p->escHtml ? SBS_LNA : SBS_TXTB; !rc && i<=SBS_TXTB; i++ ){ rc = fsl_buffer_append(p->apCols[i], "\n", 1); } return rc; } /* Append n spaces to the column. */ static int sbsWriteSpace(SbsLine *p, int n, int col){ return fsl_buffer_appendf(p->apCols[col], "%*s", n, ""); } /* Write the text of pLine into column iCol of p. If outputting HTML, write the full line. Otherwise, only write the width characters. Translate tabs into spaces. Add newlines if col is SBS_TXTB. Translate HTML characters if escHtml is true. Pad the rendering to width bytes if col is SBS_TXTA and escHtml is false. This comment contains multibyte unicode characters (�, �, �) in order to test the ability of the diff code to handle such characters. */ static int sbsWriteText(SbsLine *p, fsl_dline *pLine, int col){ fsl_buffer *pCol = p->apCols[col]; int rc = 0; int n = pLine->n; int i; /* Number of input characters consumed */ int k; /* Cursor position */ int needEndSpan = 0; const char *zIn = pLine->z; int w = p->width; int colorize = p->escHtml; #if 0 /* MISSING: re bits, but want to replace those with a predicate. */ if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){ colorize = 0; } #endif for(i=k=0; !rc && (p->escHtml || k<w) && i<n; i++, k++){ char c = zIn[i]; if( colorize ){ if( i==p->iStart ){ rc = fsl_buffer_append(pCol, p->zStart, -1); if(rc) break; needEndSpan = 1; if( p->iStart2 ){ p->iStart = p->iStart2; p->zStart = p->zStart2; p->iStart2 = 0; } }else if( i==p->iEnd ){ rc = fsl_buffer_append(pCol, "</span>", 7); if(rc) break; needEndSpan = 0; if( p->iEnd2 ){ p->iEnd = p->iEnd2; p->iEnd2 = 0; } } } if( c=='\t' && !p->escHtml ){ rc = fsl_buffer_append(pCol, " ", 1); while( !rc && (k&7)!=7 && (p->escHtml || k<w) ){ rc = fsl_buffer_append(pCol, " ", 1); k++; } }else if( c=='\r' || c=='\f' ){ rc = fsl_buffer_append(pCol, " ", 1); }else if( c=='<' && p->escHtml ){ rc = fsl_buffer_append(pCol, "<", 4); }else if( c=='&' && p->escHtml ){ rc = fsl_buffer_append(pCol, "&", 5); }else if( c=='>' && p->escHtml ){ rc = fsl_buffer_append(pCol, ">", 4); }else if( c=='"' && p->escHtml ){ rc = fsl_buffer_append(pCol, """, 6); }else{ rc = fsl_buffer_append(pCol, &zIn[i], 1); if( (c&0xc0)==0x80 ) k--; } } if( !rc && needEndSpan ){ rc = fsl_buffer_append(pCol, "</span>", 7); } if(!rc){ if( col==SBS_TXTB ){ rc = sbsWriteNewlines(p); }else if( !p->escHtml ){ rc = sbsWriteSpace(p, w-k, SBS_TXTA); } } return rc; } /* Append a column to the final output blob. */ static int sbsWriteColumn(DiffOutState *pOut, fsl_buffer const *pCol, int col){ return diff_outf(pOut, "<td><div class=\"fsl-diff-%s-col\">\n" "<pre>\n" "%b" "</pre>\n" "</div></td>\n", col % 3 ? (col == SBS_MKR ? "separator" : "text") : "lineno", pCol ); } /* Append a separator line to column iCol */ static int sbsWriteSep(SbsLine *p, int len, int col){ char ch = '.'; if( len<1 ){ len = 1; ch = ' '; } return fsl_buffer_appendf(p->apCols[col], "<span class=\"fsl-diff-hr\">%.*c</span>\n", len, ch); } /* Append the appropriate marker into the center column of the diff. */ static int sbsWriteMarker(SbsLine *p, const char *zTxt, const char *zHtml){ return fsl_buffer_append(p->apCols[SBS_MKR], p->escHtml ? zHtml : zTxt, -1); } /* Append a line number to the column. */ static int sbsWriteLineno(SbsLine *p, int ln, int col){ int rc; if( p->escHtml ){ rc = fsl_buffer_appendf(p->apCols[col], "%d", ln+1); }else{ char zLn[8]; fsl_snprintf(zLn, 8, "%5d ", ln+1); rc = fsl_buffer_appendf(p->apCols[col], "%s ", zLn); } return rc; } /* 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 1 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 textLCS( const char *zLeft, int nA, /* String on the left */ const char *zRight, int nB, /* String on the right */ int *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 */ int nt; /* Number of target points */ int ti[3]; /* Index for start of each 4-byte target */ unsigned int target[3]; /* 4-byte alignment targets */ unsigned int probe; /* probe to compare against target */ int iAS, iAE, iBS, iBE; /* Range of common segment */ int i, j; /* Loop counters */ int rc = 0; /* Result code. 1 for success */ if( nA<6 || nB<6 ) return 0; memset(aLCS, 0, sizeof(int)*4); ti[0] = i = nB/2-2; target[0] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; probe = 0; if( nB<16 ){ nt = 1; }else{ ti[1] = i = nB/4-2; target[1] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; ti[2] = i = (nB*3)/4-2; target[2] = (zB[i]<<24) | (zB[i+1]<<16) | (zB[i+2]<<8) | zB[i+3]; nt = 3; } probe = (zA[0]<<16) | (zA[1]<<8) | zA[2]; for(i=3; i<nA; i++){ probe = (probe<<8) | zA[i]; for(j=0; j<nt; j++){ if( probe==target[j] ){ iAS = i-3; iAE = i+1; iBS = ti[j]; iBE = ti[j]+4; while( iAE<nA && iBE<nB && zA[iAE]==zB[iBE] ){ iAE++; iBE++; } while( iAS>0 && iBS>0 && zA[iAS-1]==zB[iBS-1] ){ iAS--; iBS--; } if( iAE-iAS > aLCS[1] - aLCS[0] ){ aLCS[0] = iAS; aLCS[1] = iAE; aLCS[2] = iBS; aLCS[3] = iBE; rc = 1; } } } } return rc; } /* Try to shift iStart as far as possible to the left. */ static void sbsShiftLeft(SbsLine *p, const char *z){ int i, j; while( (i=p->iStart)>0 && z[i-1]==z[i] ){ for(j=i+1; j<p->iEnd && z[j-1]==z[j]; j++){} if( j<p->iEnd ) break; p->iStart--; p->iEnd--; } } /* Simplify iStart and iStart2: * If iStart is a null-change then move iStart2 into iStart * Make sure any null-changes are in canonoical form. * Make sure all changes are at character boundaries for multi-byte characters. */ static void sbsSimplifyLine(SbsLine *p, const char *z){ if( p->iStart2==p->iEnd2 ){ p->iStart2 = p->iEnd2 = 0; }else if( p->iStart2 ){ while( p->iStart2>0 && (z[p->iStart2]&0xc0)==0x80 ) p->iStart2--; while( (z[p->iEnd2]&0xc0)==0x80 ) p->iEnd2++; } if( p->iStart==p->iEnd ){ p->iStart = p->iStart2; p->iEnd = p->iEnd2; p->zStart = p->zStart2; p->iStart2 = 0; p->iEnd2 = 0; } if( p->iStart==p->iEnd ){ p->iStart = p->iEnd = -1; }else if( p->iStart>0 ){ while( p->iStart>0 && (z[p->iStart]&0xc0)==0x80 ) p->iStart--; while( (z[p->iEnd]&0xc0)==0x80 ) p->iEnd++; } } /* Write out lines that have been edited. Adjust the highlight to cover only those parts of the line that actually changed. */ static int sbsWriteLineChange( SbsLine *p, /* The SBS output line */ fsl_dline *pLeft, /* Left line of the change */ int lnLeft, /* Line number for the left line */ fsl_dline *pRight, /* Right line of the change */ int lnRight /* Line number of the right line */ ){ int rc = 0; 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 */ 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 */ int aLCS[4] = {0,0,0,0}; /* Bounds of common middle segment */ static const char zClassRm[] = "<span class=\"fsl-diff-rm\">"; static const char zClassAdd[] = "<span class=\"fsl-diff-add\">"; static const char zClassChng[] = "<span class=\"fsl-diff-change\">"; 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; } if( nPrefix+nSuffix > nShort ) nPrefix = nShort - nSuffix; /* A single chunk of text inserted on the right */ if( nPrefix+nSuffix==nLeft ){ rc = sbsWriteLineno(p, lnLeft, SBS_LNA); if(rc) return rc; p->iStart2 = p->iEnd2 = 0; p->iStart = p->iEnd = -1; rc = sbsWriteText(p, pLeft, SBS_TXTA); if( !rc && nLeft==nRight && zLeft[nLeft]==zRight[nRight] ){ rc = sbsWriteMarker(p, " ", ""); }else{ rc = sbsWriteMarker(p, " | ", "|"); } if(!rc){ rc = sbsWriteLineno(p, lnRight, SBS_LNB); if(!rc){ p->iStart = nPrefix; p->iEnd = nRight - nSuffix; p->zStart = zClassAdd; rc = sbsWriteText(p, pRight, SBS_TXTB); } } return rc; } /* A single chunk of text deleted from the left */ if( nPrefix+nSuffix==nRight ){ /* Text deleted from the left */ rc = sbsWriteLineno(p, lnLeft, SBS_LNA); if(!rc){ p->iStart2 = p->iEnd2 = 0; p->iStart = nPrefix; p->iEnd = nLeft - nSuffix; p->zStart = zClassRm; rc = sbsWriteText(p, pLeft, SBS_TXTA); if(!rc){ rc = sbsWriteMarker(p, " | ", "|"); if(!rc){ rc = sbsWriteLineno(p, lnRight, SBS_LNB); if(!rc){ p->iStart = p->iEnd = -1; sbsWriteText(p, pRight, SBS_TXTB); } } } } 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. */ nLeftDiff = nLeft - nSuffix - nPrefix; nRightDiff = nRight - nSuffix - nPrefix; if( p->escHtml && nLeftDiff >= 6 && nRightDiff >= 6 && textLCS(&zLeft[nPrefix], nLeftDiff, &zRight[nPrefix], nRightDiff, aLCS) ){ rc = sbsWriteLineno(p, lnLeft, SBS_LNA); if(rc) return rc; p->iStart = nPrefix; p->iEnd = nPrefix + aLCS[0]; if( aLCS[2]==0 ){ sbsShiftLeft(p, pLeft->z); p->zStart = zClassRm; }else{ p->zStart = zClassChng; } p->iStart2 = nPrefix + aLCS[1]; p->iEnd2 = nLeft - nSuffix; p->zStart2 = aLCS[3]==nRightDiff ? zClassRm : zClassChng; sbsSimplifyLine(p, zLeft+nPrefix); rc = sbsWriteText(p, pLeft, SBS_TXTA); if(!rc) rc = sbsWriteMarker(p, " | ", "|"); if(!rc) rc = sbsWriteLineno(p, lnRight, SBS_LNB); if(rc) return rc; p->iStart = nPrefix; p->iEnd = nPrefix + aLCS[2]; if( aLCS[0]==0 ){ sbsShiftLeft(p, pRight->z); p->zStart = zClassAdd; }else{ p->zStart = zClassChng; } p->iStart2 = nPrefix + aLCS[3]; p->iEnd2 = nRight - nSuffix; p->zStart2 = aLCS[1]==nLeftDiff ? zClassAdd : zClassChng; sbsSimplifyLine(p, zRight+nPrefix); rc = sbsWriteText(p, pRight, SBS_TXTB); return rc; } /* If all else fails, show a single big change between left and right */ rc = sbsWriteLineno(p, lnLeft, SBS_LNA); if(!rc){ p->iStart2 = p->iEnd2 = 0; p->iStart = nPrefix; p->iEnd = nLeft - nSuffix; p->zStart = zClassChng; rc = sbsWriteText(p, pLeft, SBS_TXTA); if(!rc){ rc = sbsWriteMarker(p, " | ", "|"); if(!rc){ rc = sbsWriteLineno(p, lnRight, SBS_LNB); if(!rc){ p->iEnd = nRight - nSuffix; sbsWriteText(p, pRight, SBS_TXTB); } } } } return rc; } /* 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) Find the length of the longest common subsequence (4) Longer common subsequences yield lower scores. */ static int match_dline(fsl_dline *pA, fsl_dline *pB){ const char *zA; /* Left string */ const char *zB; /* right string */ int nA; /* Bytes in zA[] */ int nB; /* Bytes in zB[] */ 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; zB = pB->z; nA = pA->n; nB = pB->n; while( nA>0 && fsl_isspace(zA[0]) ){ nA--; zA++; } while( nA>0 && fsl_isspace(zA[nA-1]) ){ nA--; } while( nB>0 && fsl_isspace(zB[0]) ){ nB--; zB++; } while( nB>0 && fsl_isspace(zB[nB-1]) ){ nB--; } if( nA>250 ) nA = 250; if( nB>250 ) nB = 250; avg = (nA+nB)/2; if( avg==0 ) return 0; if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0; memset(aFirst, 0, 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; } best = 0; for(i=1; i<=nA-best; i++){ c = (unsigned char)zA[i]; for(j=aFirst[c]; j>0 && j<nB-best; j = aNext[j]){ int limit = minInt(nA-i, nB-j); for(k=1; k<=limit && zA[k+i]==zB[k+j]; k++){} if( k>best ) best = k; } } score = (best>avg) ? 0 : (avg - best)*100/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; } /* 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 return 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. Values larger than three indicate better matches. The length of the returned array will be just large enough to cause all elements of pLeft and pRight 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. */ static unsigned char *sbsAlignment( fsl_dline *aLeft, int nLeft, /* Text on the left */ fsl_dline *aRight, int nRight /* Text on the right */ ){ int i, j, k; /* Loop counters */ int *a; /* One row of the Wagner matrix */ int *pToFree; /* Space that needs to be freed */ unsigned char *aM; /* Wagner result matrix */ int nMatch, iMatch; /* Number of matching lines and match score */ int mnLen; /* MIN(nLeft, nRight) */ int mxLen; /* MAX(nLeft, nRight) */ int aBuf[100]; /* Stack space for a[] if nRight not to big */ aM = (unsigned char *)fsl_malloc( (nLeft+1)*(nRight+1) ); if(!aM) return NULL; if( nLeft==0 ){ memset(aM, 2, nRight); return aM; } if( nRight==0 ){ memset(aM, 1, nLeft); return aM; } /* 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. */ mnLen = nLeft<nRight ? nLeft : nRight; if( nLeft*nRight>100000 ){ memset(aM, 4, mnLen); if( nLeft>mnLen ) memset(aM+mnLen, 1, nLeft-mnLen); if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen); return aM; } 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){ fsl_free(aM); return NULL; } } /* 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 score = match_dline(&aLeft[j-1], &aRight[i-1]); if( (score<=63 || (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); /* If: (1) the alignment is more than 25% longer than the longest side, and (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. */ mxLen = nLeft>nRight ? nLeft : nRight; if( i*4>mxLen*5 && (nMatch==0 || iMatch/nMatch>15) ){ memset(aM, 4, mnLen); if( nLeft>mnLen ) memset(aM+mnLen, 1, nLeft-mnLen); if( nRight>mnLen ) memset(aM+mnLen, 2, nRight-mnLen); } /* Return the result */ fsl_free(pToFree); return aM; } /* 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 smallGap(int *R){ return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8; } /* Given a diff context in which the aEdit[] array has been filled in, compute a side-by-side diff into pOut. */ static int sbsDiff( fsl__diff_cx *p, /* The computed diff */ DiffOutState *pOut, /* Write the results here */ ReCompiled *pRe, /* Only show changes that match this regex */ u64 diffFlags /* Flags controlling the diff */ ){ fsl_dline *A; /* Left side of the diff */ fsl_dline *B; /* Right side of the diff */ int rc = 0; int a = 0; /* Index of next line in A[] */ int b = 0; /* Index of next line in B[] */ int *R; /* Array of COPY/DELETE/INSERT triples */ int r; /* Index into R[] */ int nr; /* Number of COPY/DELETE/INSERT triples to process */ int mxr; /* Maximum value for r */ int i, j; /* Loop counters */ int m, ma, mb;/* Number of lines to output */ int skip; /* Number of lines to skip */ static int nChunk = 0; /* Number of chunks of diff output seen so far */ SbsLine s; /* Output line buffer */ int nContext; /* Lines of context above and below each change */ int showDivider = 0; /* True to show the divider */ fsl_buffer unesc = fsl_buffer_empty; fsl_buffer aCols[5] = { /* Array of column blobs */ fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m, fsl_buffer_empty_m }; memset(&s, 0, sizeof(s)); s.pOut = pOut; s.width = diff_width(diffFlags); nContext = diff_context_lines(diffFlags); s.escHtml = (diffFlags & DIFF_HTML)!=0; if( s.escHtml ){ for(i=SBS_LNA; i<=SBS_TXTB; i++){ s.apCols[i] = &aCols[i]; } }else{ for(i=SBS_LNA; i<=SBS_TXTB; i++){ s.apCols[i] = &unesc; } } s.pRe = pRe; s.iStart = -1; s.iStart2 = 0; s.iEnd = -1; A = p->aFrom; B = p->aTo; R = p->aEdit; mxr = p->nEdit; while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } 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]<nContext*2; nr++){} /* printf("r=%d nr=%d\n", r, nr); */ #if 0 /* MISSING: re/predicate bits. */ /* 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( 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(pRe, &A[xa], R[r+i*3+1]); c2 = re_dline_match(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 /* For the current block comprising nr triples, figure out how many lines to skip. */ if( R[r]>nContext ){ skip = R[r] - nContext; }else{ skip = 0; } /* Draw the separator between blocks */ if( showDivider ){ if( s.escHtml ){ char zLn[10]; fsl_snprintf(zLn, sizeof(zLn), "%d", a+skip+1); rc = sbsWriteSep(&s, strlen(zLn), SBS_LNA); if(!rc) rc = sbsWriteSep(&s, s.width, SBS_TXTA); if(!rc) rc = sbsWriteSep(&s, 0, SBS_MKR); if(!rc){ fsl_snprintf(zLn, sizeof(zLn), "%d", b+skip+1); rc = sbsWriteSep(&s, strlen(zLn), SBS_LNB); if(!rc) rc = sbsWriteSep(&s, s.width, SBS_TXTB); } }else{ diff_outf(pOut, "%.*c\n", s.width*2+16, '.'); } if(rc) goto end; } showDivider = 1; nChunk++; if( s.escHtml ){ rc = fsl_buffer_appendf(s.apCols[SBS_LNA], "<span class=\"fsl-diff-chunk-%d\"></span>", nChunk); } /* Show the initial common area */ a += skip; b += skip; m = R[r] - skip; for(j=0; !rc && j<m; j++){ rc = sbsWriteLineno(&s, a+j, SBS_LNA); if(rc) break; s.iStart = s.iEnd = -1; rc = sbsWriteText(&s, &A[a+j], SBS_TXTA); if(!rc) rc = sbsWriteMarker(&s, " ", ""); if(!rc) rc = sbsWriteLineno(&s, b+j, SBS_LNB); if(!rc) rc = sbsWriteText(&s, &B[b+j], SBS_TXTB); } if(rc) goto end; a += m; b += m; /* Show the differences */ for(i=0; i<nr; i++){ unsigned char *alignment; 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 the gap between the current diff and then next diff within the same block is not too great, then render them as if they are a single diff. */ while( i<nr-1 && smallGap(&R[r+i*3]) ){ i++; m = R[r+i*3]; ma += R[r+i*3+1] + m; mb += R[r+i*3+2] + m; } alignment = sbsAlignment(&A[a], ma, &B[b], mb); if(!alignment){ rc = FSL_RC_OOM; goto end; } for(j=0; !rc && ma+mb>0; j++){ if( alignment[j]==1 ){ /* Delete one line from the left */ rc = sbsWriteLineno(&s, a, SBS_LNA); if(rc) goto end_align; s.iStart = 0; s.zStart = "<span class=\"fsl-diff-rm\">"; s.iEnd = LENGTH(&A[a]); rc = sbsWriteText(&s, &A[a], SBS_TXTA); if(!rc) rc = sbsWriteMarker(&s, " <", "<"); if(!rc) rc = sbsWriteNewlines(&s); if(rc) goto end_align; assert( ma>0 ); ma--; a++; }else if( alignment[j]==3 ){ /* The left line is changed into the right line */ rc = sbsWriteLineChange(&s, &A[a], a, &B[b], b); if(rc) goto end_align; assert( ma>0 && mb>0 ); ma--; mb--; a++; b++; }else if( alignment[j]==2 ){ /* Insert one line on the right */ if( !s.escHtml ){ rc = sbsWriteSpace(&s, s.width + 7, SBS_TXTA); } if(!rc) rc = sbsWriteMarker(&s, " > ", ">"); if(!rc) rc = sbsWriteLineno(&s, b, SBS_LNB); if(rc) goto end_align; s.iStart = 0; s.zStart = "<span class=\"fsl-diff-add\">"; s.iEnd = LENGTH(&B[b]); rc = sbsWriteText(&s, &B[b], SBS_TXTB); if(rc) goto end_align; assert( mb>0 ); mb--; b++; }else{ /* Delete from the left and insert on the right */ rc = sbsWriteLineno(&s, a, SBS_LNA); if(rc) goto end_align; s.iStart = 0; s.zStart = "<span class=\"fsl-diff-rm\">"; s.iEnd = LENGTH(&A[a]); rc = sbsWriteText(&s, &A[a], SBS_TXTA); if(!rc) rc = sbsWriteMarker(&s, " | ", "|"); if(!rc) rc = sbsWriteLineno(&s, b, SBS_LNB); if(rc) goto end_align; s.iStart = 0; s.zStart = "<span class=\"fsl-diff-add\">"; s.iEnd = LENGTH(&B[b]); rc = sbsWriteText(&s, &B[b], SBS_TXTB); if(rc) goto end_align; ma--; mb--; a++; b++; } } end_align: fsl_free(alignment); if(rc) goto end; if( i<nr-1 ){ m = R[r+i*3+3]; for(j=0; !rc && j<m; j++){ rc = sbsWriteLineno(&s, a+j, SBS_LNA); s.iStart = s.iEnd = -1; if(!rc) rc = sbsWriteText(&s, &A[a+j], SBS_TXTA); if(!rc) rc = sbsWriteMarker(&s, " ", ""); if(!rc) rc = sbsWriteLineno(&s, b+j, SBS_LNB); if(!rc) rc = sbsWriteText(&s, &B[b+j], SBS_TXTB); } if(rc) goto end; b += m; a += m; } } /* Show the final common area */ assert( nr==i ); m = R[r+nr*3]; if( m>nContext ) m = nContext; for(j=0; !rc && j<m; j++){ rc = sbsWriteLineno(&s, a+j, SBS_LNA); s.iStart = s.iEnd = -1; if(!rc) rc = sbsWriteText(&s, &A[a+j], SBS_TXTA); if(!rc) rc = sbsWriteMarker(&s, " ", ""); if(!rc) rc = sbsWriteLineno(&s, b+j, SBS_LNB); if(!rc) rc = sbsWriteText(&s, &B[b+j], SBS_TXTB); } if(rc) goto end; } /* diff triplet loop */ assert(!rc); if( s.escHtml && (s.apCols[SBS_LNA]->used>0) ){ rc = diff_out(pOut, "<table class=\"fsl-sbsdiff-cols\"><tr>\n", -1); for(i=SBS_LNA; !rc && i<=SBS_TXTB; i++){ rc = sbsWriteColumn(pOut, s.apCols[i], i); } rc = diff_out(pOut, "</tr></table>\n", -1); }else if(unesc.used){ rc = pOut->out(pOut->oState, unesc.mem, unesc.used); } end: for( i = 0; i < (int)(sizeof(aCols)/sizeof(aCols[0])); ++i ){ fsl_buffer_clear(&aCols[i]); } fsl_buffer_clear(&unesc); return rc; } /** @internal Performs a text diff on two buffers, either streaming the output to the 3rd argument or returning the results as an array of copy/delete/insert triples via the final argument. ONE of the 3rd or final arguments must be set and the other must be NULL If the 3rd argument is not NULL: - The 4th argument is the opaque state value passed to the 3rd when emitting output. - contextLines specifies the number of lines of context for the diff. A negative contextLines value uses a default. - sbsWidth, if not 0, specifies a side-by-side diff width. A negative sbsWidth uses a default. A 0 sbsWidth indicates a unified-style diff output. If the final argument is not NULL then the result array of copy/delete/insert triples is assigned to *outRaw. Ownership is transfered to the caller, who must eventually pass it to fsl_free(). Returns 0 on success, any number of other codes on error. */ static int fsl_diff_text_impl( fsl_buffer const *pA, /* FROM file */ fsl_buffer const *pB, /* TO file */ fsl_output_f out, void * outState, /* ReCompiled *pRe, */ /* Only output changes where this Regexp matches */ short contextLines, short sbsWidth, int diffFlags_, /* FSL_DIFF_* flags */ int ** outRaw ){ int rc; fsl__diff_cx c = fsl__diff_cx_empty; uint64_t diffFlags = fsl_diff_flags_convert(diffFlags_) | DIFF_CONTEXT_EX /* to shoehorn newer 0-handling semantics into older (ported-in) code. */; if(!pA || !pB || (out && outRaw) || (!out && !outRaw)) return FSL_RC_MISUSE; else if(contextLines<0) contextLines = 5; else if(contextLines & ~FSL__LINE_LENGTH_MASK){ contextLines = (int)FSL__LINE_LENGTH_MASK; } diffFlags |= (FSL__LINE_LENGTH_MASK & contextLines); /* Encode SBS width... */ if(sbsWidth<0 || ((DIFF_SIDEBYSIDE & diffFlags) && !sbsWidth) ) sbsWidth = 80; if(sbsWidth) diffFlags |= DIFF_SIDEBYSIDE; diffFlags |= ((int)(sbsWidth & 0xFF))<<16; if( diffFlags & DIFF_INVERT ){ fsl_buffer const *pTemp = pA; pA = pB; pB = pTemp; } /* Prepare the input files */ if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_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)fsl_buffer_size(pA), (uint32_t*)&c.nFrom, &c.aFrom, diffFlags); if(rc) goto end; rc = fsl_break_into_dlines(fsl_buffer_cstr(pB), (fsl_int_t)fsl_buffer_size(pB), (uint32_t*)&c.nTo, &c.aTo, diffFlags); if(rc) goto end; /* Compute the difference */ rc = fsl__diff_all(&c); //fsl__dump_triples(&c, __FILE__, __LINE__); if(rc) goto end; if( (diffFlags & DIFF_NOTTOOBIG)!=0 ){ 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(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); */ goto end; } } //fsl__dump_triples(&c, __FILE__, __LINE__); if( (diffFlags & DIFF_NOOPT)==0 ){ fsl__diff_optimize(&c); } //fsl__dump_triples(&c, __FILE__, __LINE__); if( out ){ /* Compute a context or side-by-side diff */ /* MISSING: regex support */ DiffOutState dos = DiffOutState_empty; dos.out = out; dos.oState = outState; dos.ansiColor = !!(diffFlags_ & FSL_DIFF_ANSI_COLOR); if( diffFlags & DIFF_SIDEBYSIDE ){ rc = sbsDiff(&c, &dos, NULL/*pRe*/, diffFlags); }else{ rc = contextDiff(&c, &dos, NULL/*pRe*/, diffFlags); } }else if(outRaw){ /* If a context diff is not requested, then return the array of COPY/DELETE/INSERT triples. */ *outRaw = c.aEdit; c.aEdit = NULL; } end: fsl_free(c.aFrom); fsl_free(c.aTo); fsl_free(c.aEdit); return rc; } int fsl__diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, int diffFlags, int ** outRaw){ return fsl_diff_text_impl(p1, p2, NULL, NULL, 0, 0, diffFlags, outRaw); } int fsl_diff_text(fsl_buffer const *pA, fsl_buffer const *pB, fsl_output_f out, void * outState, short contextLines, short sbsWidth, int diffFlags ){ return fsl_diff_text_impl(pA, pB, out, outState, contextLines, sbsWidth, diffFlags, NULL ); } int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB, fsl_buffer * pOut, short contextLines, short sbsWidth, int diffFlags ){ return (pA && pB && pOut) ? fsl_diff_text_impl(pA, pB, fsl_output_f_buffer, pOut, contextLines, sbsWidth, diffFlags, NULL ) : FSL_RC_MISUSE; } #undef MARKER #undef LENGTH #undef DIFF_CONTEXT_MASK #undef DIFF_WIDTH_MASK #undef DIFF_IGNORE_EOLWS #undef DIFF_IGNORE_ALLWS #undef DIFF_SIDEBYSIDE #undef DIFF_VERBOSE #undef DIFF_BRIEF #undef DIFF_HTML #undef DIFF_LINENO #undef DIFF_NOOPT #undef DIFF_INVERT #undef DIFF_CONTEXT_EX #undef DIFF_NOTTOOBIG #undef DIFF_STRIP_EOLCR #undef SBS_LNA #undef SBS_TXTA #undef SBS_MKR #undef SBS_LNB #undef SBS_TXTB #undef ANSI_COLOR_BLACK #undef ANSI_COLOR_RED #undef ANSI_COLOR_GREEN #undef ANSI_COLOR_YELLOW #undef ANSI_COLOR_BLUE #undef ANSI_COLOR_MAGENTA #undef ANSI_COLOR_CYAN #undef ANSI_COLOR_WHITE #undef ANSI_BG_BLACK #undef ANSI_BG_RED #undef ANSI_BG_GREEN #undef ANSI_BG_YELLOW #undef ANSI_BG_BLUE #undef ANSI_BG_MAGENTA #undef ANSI_BG_CYAN #undef ANSI_BG_WHITE #undef ANSI_RESET_COLOR #undef ANSI_RESET_ALL #undef ANSI_RESET #undef ANSI_DIFF_ADD #undef ANSI_DIFF_MOD #undef ANSI_DIFF_RM #endif /*FSL_OMIT_DEPRECATED*/ /* end of file ./src/diff.c */ /* start of file ./src/diff2.c */ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt SPDX-License-Identifier: BSD-2-Clause-FreeBSD SPDX-FileCopyrightText: 2021 The Libfossil Authors SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ /** This file houses the "2nd generation" diff-generation APIs. 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 <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; 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; } /** 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. */ FSL_EXPORT bool fsl_count_lines(const char *z, fsl_int_t n, uint32_t * nOut ); 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; s = 0; if( diffFlags & FSL_DIFF2_IGNORE_EOLWS ){ while( k>0 && fsl_isspace(z[k-1]) ){ k--; } } if( (diffFlags & FSL_DIFF2_IGNORE_ALLWS) ==FSL_DIFF2_IGNORE_ALLWS ){ uint32_t numws = 0; while( s<k && fsl_isspace(z[s]) ){ ++s; } for(h=0, x=s; x<k; ++x){ char c = z[x]; if( fsl_isspace(c) ){ ++numws; }else{ h = (h^c)*9000000000000000041LL; } } k -= numws; }else{ uint32_t k2 = k & ~0x7; uint64_t m; for(h=0, x=s; 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].indent = s; 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){ unsigned short a = pA->indent, b = pB->indent; if( pA->h==pB->h ){ 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 && fsl_isspace(pA->z[a])) ++a; while( b<pB->n && fsl_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 && fsl_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( fsl_isspace(c) ){ iVal += 5; }else if( !fsl_isalnum(c) ){ iVal += 2; } c = zLeft[i+nGap-1]; if( fsl_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(const fsl_dline *pA, const fsl_dline *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; zB = pB->z; nA = pA->n; nB = pB->n; while( nA>0 && (unsigned char)zA[0]<=' ' ){ nA--; zA++; } while( nA>0 && (unsigned char)zA[nA-1]<=' ' ){ nA--; } while( nB>0 && (unsigned char)zB[0]<=' ' ){ nB--; zB++; } while( nB>0 && (unsigned char)zB[nB-1]<=' ' ){ nB--; } 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 = (best>=avg) ? 0 : (avg - best)*100/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; } /* ** 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 ** fossil_malloc(). (The caller needs to free the return value using ** fossil_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. */ static int diffBlockAlignment( const fsl_dline *aLeft, int nLeft, /* Text on the left */ const fsl_dline *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, use a divide and conquer algorithm that is ** O(NlogN). The result is not as precise, but this whole thing is an ** approximation anyhow, and the faster response time is an acceptable ** trade-off for reduced precision. */ if( nLeft*nRight>DIFF_ALIGN_MX && (pOpt->diffFlags & FSL_DIFF2_SLOW_SBS)==0 ){ const fsl_dline *aSmall; /* The smaller of aLeft and aRight */ const 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 */ unsigned int n1, n2; /* Number of entries in a1 and a2 */ int score, bestScore; /* Score and best score seen so far */ 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, &n1); if(!rc){ rc = diffBlockAlignment(aLeft+iDivLeft, nLeft-iDivLeft, aRight+iDivRight, nRight-iDivRight, pOpt, &a2, &n2); } if(rc) goto bail; void * a1Re = fsl_realloc(a1, n1+n2 ); if(!a1Re) goto bail; a1 = (unsigned char *)a1Re; memcpy(a1+n1,a2,n2); fsl_free(a2); a2 = 0; *pNResult = n1+n2; *pResult = a1; return 0; bail: assert(0!=rc); fsl_free( a1 ); fsl_free( a2 ); goto end; } /* 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 ){ const fsl_dline *A; /* Left side of the diff */ const 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); } 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; |
︙ | ︙ | |||
23930 23931 23932 23933 23934 23935 23936 | uint32_t lineCount[2]; /** The actual number of lines needed for rendering the file. */ uint32_t displayLines; }; typedef struct DiffCounter DiffCounter; | | | 27203 27204 27205 27206 27207 27208 27209 27210 27211 27212 27213 27214 27215 27216 27217 | 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,10,3,1,10},{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 |
︙ | ︙ | |||
24774 24775 24776 24777 24778 24779 24780 | #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; | < | | | 28047 28048 28049 28050 28051 28052 28053 28054 28055 28056 28057 28058 28059 28060 28061 28062 | #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; rc = fdb__out(b, "}\nset fossilcmd {}\n", -1); if(0==rc) fdb__out(b, fsl_difftk_cstr, -1); } return rc; } #undef BR_OPEN #undef BR_CLOSE |
︙ | ︙ | |||
25058 25059 25060 25061 25062 25063 25064 | if(NULL!=factory){ *pOut = factory(); rc = *pOut ? 0 : FSL_RC_OOM; } return rc; } | | > > > > > | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < > < < | | 28330 28331 28332 28333 28334 28335 28336 28337 28338 28339 28340 28341 28342 28343 28344 28345 28346 28347 28348 28349 28350 28351 28352 28353 28354 28355 | if(NULL!=factory){ *pOut = factory(); rc = *pOut ? 0 : FSL_RC_OOM; } return rc; } #undef DIFF_ALIGN_MX #undef DIFF_CANNOT_COMPUTE_BINARY #undef DIFF_CANNOT_COMPUTE_SYMLINK #undef DIFF_TOO_MANY_CHANGES #undef DIFF_WHITESPACE_ONLY #undef fsl_dline_empty_m #undef MARKER #undef DTCL_BUFFER #undef blob_to_utf8_no_bom #undef DICOSTATE #undef FSL_DIFF_SMALL_GAP /* end of file ./src/diff2.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 |
︙ | ︙ | |||
29904 29905 29906 29907 29908 29909 29910 | 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){ | | | 31197 31198 31199 31200 31201 31202 31203 31204 31205 31206 31207 31208 31209 31210 31211 | 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; 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*/ |
︙ | ︙ | |||
32532 32533 32534 32535 32536 32537 32538 | " WHERE tagid=%d AND tagxref.rid=ancestor.rid" " AND value=%Q AND tagtype>0)" " LIMIT 1", rid, rid, FSL_TAGID_BRANCH, zBranch ); } | | | 33825 33826 33827 33828 33829 33830 33831 33832 33833 33834 33835 33836 33837 33838 33839 | " 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_int_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); |
︙ | ︙ | |||
33397 33398 33399 33400 33401 33402 33403 | ? 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){ | | | 34690 34691 34692 34693 34694 34695 34696 34697 34698 34699 34700 34701 34702 34703 34704 | ? 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_ckout_close(f) /* Will fail if a transaction is active! */; switch(rc){ case 0: break; default: return rc; } |
︙ | ︙ | |||
33799 33800 33801 33802 33803 33804 33805 | fsl_stmt_finalize(&s); } return rc; } } /* UNTESTED */ | | | | | 35092 35093 35094 35095 35096 35097 35098 35099 35100 35101 35102 35103 35104 35105 35106 35107 35108 35109 35110 35111 35112 35113 35114 | fsl_stmt_finalize(&s); } return rc; } } /* UNTESTED */ char fsl_repo_is_readonly(fsl_cx const * f){ if(!f || !f->dbMain) return 0; 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) ? 1 : 0; } } int fsl__repo_record_filename(fsl_cx * const f){ fsl_db * dbR = fsl_needs_repo(f); fsl_db * dbC; fsl_db * dbConf; |
︙ | ︙ | |||
34913 34914 34915 34916 34917 34918 34919 | bool fsl_repo_forbids_delta_manifests(fsl_cx * const f){ return fsl_config_get_bool(f, FSL_CONFDB_REPO, false, "forbid-delta-manifests"); } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 36206 36207 36208 36209 36210 36211 36212 36213 36214 36215 36216 36217 36218 36219 | 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: */ /* Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net). |
︙ | ︙ | |||
42097 42098 42099 42100 42101 42102 42103 | 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, | | | 43306 43307 43308 43309 43310 43311 43312 43313 43314 43315 43316 43317 43318 43319 43320 | 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, 51, 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 interpration 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 | 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. /** Indent of the line. Only !=0 with certain options. */ unsigned short indent; /** 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 |
︙ | ︙ | |||
4080 4081 4082 4083 4084 4085 4086 | 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 | | | | | | 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 | 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. Builders are prohibited from modifying these but the diff driver will. 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 * 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 |
︙ | ︙ | |||
10717 10718 10719 10720 10721 10722 10723 | @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() | < | 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() |
︙ | ︙ | |||
12408 12409 12410 12411 12412 12413 12414 | 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. | < < < | | 12405 12406 12407 12408 12409 12410 12411 12412 12413 12414 12415 12416 12417 12418 12419 12420 | 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. */ typedef int (*fsl_deck_visitor_f)( fsl_cx * 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 |
︙ | ︙ | |||
13254 13255 13256 13257 13258 13259 13260 | /** 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 ); | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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. */ |
︙ | ︙ | |||
14308 14309 14310 14311 14312 14313 14314 | 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. */ | | | 14262 14263 14264 14265 14266 14267 14268 14269 14270 14271 14272 14273 14274 14275 14276 | 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_int_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 |
︙ | ︙ | |||
14703 14704 14705 14706 14707 14708 14709 | /** UNTESTED. Returns true if f has an opened repository database which is opened in read-only mode, else returns false. */ | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 14657 14658 14659 14660 14661 14662 14663 14664 14665 14666 14667 14668 14669 14670 14671 | /** UNTESTED. Returns true if f has an opened repository database which is opened in read-only mode, else returns false. */ FSL_EXPORT char fsl_repo_is_readonly(fsl_cx const * f); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_REPO_H_INCLUDED */ /* end of file ./include/fossil-scm/repo.h */ |
︙ | ︙ | |||
17221 17222 17223 17224 17225 17226 17227 | 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 | | < < < < | | | 17145 17146 17147 17148 17149 17150 17151 17152 17153 17154 17155 17156 17157 17158 17159 17160 17161 17162 17163 17164 17165 17166 | 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. - FSL_RC_OOM. */ FSL_EXPORT int fsl_card_F_ckout_mtime(fsl_cx * const f, fsl_id_t vid, fsl_card_F const * const fc, fsl_time_t * repoMtime, fsl_time_t * localMtime); /** 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. |
︙ | ︙ | |||
18635 18636 18637 18638 18639 18640 18641 | 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]; }; | < | < | < | | | < | 18555 18556 18557 18558 18559 18560 18561 18562 18563 18564 18565 18566 18567 18568 18569 18570 18571 18572 18573 18574 18575 18576 18577 18578 18579 18580 18581 18582 18583 18584 18585 18586 | 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]; }; /** Convenience typedef. */ typedef struct fsl__mcache fsl__mcache; /** 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}\ } /** 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. */ 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: |
︙ | ︙ | |||
20279 20280 20281 20282 20283 20284 20285 | 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); | | | 20195 20196 20197 20198 20199 20200 20201 20202 20203 20204 20205 20206 20207 20208 20209 | 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); /** 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(). |
︙ | ︙ | |||
20307 20308 20309 20310 20311 20312 20313 | 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); | | | | 20223 20224 20225 20226 20227 20228 20229 20230 20231 20232 20233 20234 20235 20236 20237 20238 20239 20240 | int fsl__find_filename_changes(fsl_cx * const f, fsl_id_t iFrom, fsl_id_t iTo, bool revOK, uint32_t *pnChng, fsl_id_t **aiChng); /** Bitmask of file change types for use with fsl__is_locally_modified(). */ enum fsl__localmod_e { /** Sentinel value. */ FSL__LOCALMOD_NONE = 0, /** Permissions changed. */ FSL__LOCALMOD_PERM = 0x01, |
︙ | ︙ | |||
20479 20480 20481 20482 20483 20484 20485 20486 20487 20488 20489 20490 20491 20492 | 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 | > | 20395 20396 20397 20398 20399 20400 20401 20402 20403 20404 20405 20406 20407 20408 20409 | 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 |
︙ | ︙ | |||
20509 20510 20511 20512 20513 20514 20515 | 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, | | | | 20426 20427 20428 20429 20430 20431 20432 20433 20434 20435 20436 20437 20438 20439 20440 20441 20442 | 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); /** 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[] |
︙ | ︙ | |||
20544 20545 20546 20547 20548 20549 20550 | /** 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); }; | | < | < | | | 20461 20462 20463 20464 20465 20466 20467 20468 20469 20470 20471 20472 20473 20474 20475 20476 20477 20478 20479 20480 20481 20482 20483 20484 20485 | /** 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); }; /** Convenience typeef. */ typedef struct fsl__diff_cx fsl__diff_cx; /** 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 \ } /** 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. |
︙ | ︙ | |||
20579 20580 20581 20582 20583 20584 20585 20586 20587 20588 | 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); | > > > < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 20494 20495 20496 20497 20498 20499 20500 20501 20502 20503 20504 20505 20506 20507 20508 20509 20510 20511 20512 20513 20514 20515 20516 20517 20518 20519 20520 20521 20522 20523 20524 20525 20526 20527 20528 20529 20530 | 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_optimize(fsl__diff_cx * const p); /** @internal */ void fsl__diff_cx_clean(fsl__diff_cx * const cx); /** @internal Undocumented. For internal debugging only. */ void fsl__dump_triples(fsl__diff_cx const * const p, char const * zFile, int ln ); /** @internal Removes from the BLOB table all artifacts that are in the SHUN table. Returns 0 on success. Requires (asserts) that a repo is opened. 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 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 |
︙ | ︙ | |||
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, | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 22768 22769 22770 22771 22772 22773 22774 22775 22776 22777 22778 22779 22780 22781 22782 22783 22784 22785 22786 22787 22788 22789 22790 22791 22792 22793 22794 22795 22796 22797 22798 22799 22800 22801 22802 22803 22804 22805 22806 22807 22808 22809 22810 22811 22812 22813 22814 22815 22816 22817 22818 22819 22820 22821 22822 22823 22824 22825 22826 22827 22828 22829 22830 22831 22832 22833 22834 22835 22836 22837 22838 22839 22840 22841 22842 22843 22844 22845 22846 22847 22848 22849 22850 22851 22852 22853 22854 22855 22856 22857 22858 22859 22860 22861 22862 22863 22864 22865 22866 22867 22868 22869 22870 22871 22872 22873 22874 22875 22876 22877 22878 22879 22880 22881 22882 22883 22884 22885 22886 22887 22888 22889 22890 22891 22892 22893 22894 22895 22896 22897 22898 22899 22900 22901 22902 22903 22904 22905 22906 22907 22908 22909 22910 22911 22912 22913 22914 22915 22916 22917 22918 22919 22920 22921 22922 22923 22924 22925 22926 22927 22928 22929 22930 22931 22932 22933 22934 | 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); /** Flags for use with text-diff generation APIs, e.g. fsl_diff_text(). Maintenance reminders: - These values are holy and must not be changed without also changing the corresponding code in diff.c. - Where these entries semantically overlap with their fsl_diff2_flag_e counterparts, they MUST have the same values because some internal APIs are used by both of the diff APIs. @deprecated Prefer fsl_diff2_flag_e and fsl_diff_v2() instead. */ enum fsl_diff_flag_e { /** Ignore end-of-line whitespace */ FSL_DIFF_IGNORE_EOLWS = 0x01, /** Ignore end-of-line whitespace */ FSL_DIFF_IGNORE_ALLWS = 0x03, /** Generate a side-by-side diff */ FSL_DIFF_SIDEBYSIDE = 0x04, /** Missing shown as empty files */ FSL_DIFF_VERBOSE = 0x08, /** Show filenames only. Not used in this impl! */ FSL_DIFF_BRIEF = 0x10, /** Render HTML. */ FSL_DIFF_HTML = 0x20, /** Show line numbers. */ FSL_DIFF_LINENO = 0x40, /** Suppress optimizations (debug). */ FSL_DIFF_NOOPT = 0x0100, /** Invert the diff (debug). */ FSL_DIFF_INVERT = 0x0200, /* ACHTUNG: do not use 0x0400 because of semantic collision with FSL_DIFF2_CONTEXT_ZERO */ /** Only display if not "too big." */ FSL_DIFF_NOTTOOBIG = 0x0800, /** Strip trailing CR */ FSL_DIFF_STRIP_EOLCR = 0x1000, /** This flag tells text-mode diff generation to add ANSI color sequences to some output. The colors are currently hard-coded and non-configurable. This has no effect for HTML output, and that flag trumps this one. It also currently only affects unified diffs, not side-by-side. Maintenance reminder: this one currently has no counterpart in fossil(1), is not tracked in the same way, and need not map to an internal flag value. */ FSL_DIFF_ANSI_COLOR = 0x2000 }; /** Generates a textual diff from two text inputs and writes it to the given output function. pA and pB are the buffers to diff. contextLines is the number of lines of context to output. This parameter has a built-in limit of 2^16, and values larger than that get truncated. A value of 0 is legal, in which case no surrounding context is provided. A negative value translates to some unspecified default value. sbsWidth specifies the width (in characters) of the side-by-side columns. If sbsWidth is not 0 then this function behaves as if diffFlags contains the FSL_DIFF_SIDEBYSIDE flag. If sbsWidth is negative, OR if diffFlags explicitly contains FSL_DIFF_SIDEBYSIDE and sbsWidth is 0, then some default width is used. This parameter has a built-in limit of 255, and values larger than that get truncated to 255. diffFlags is a mask of fsl_diff_flag_t values. Not all of the fsl_diff_flag_t flags are yet [sup]ported. The output is sent to out(outState,...). If out() returns non-0 during processing, processing stops and that result is returned to the caller of this function. Returns 0 on success, FSL_RC_OOM on allocation error, FSL_RC_MISUSE if any arguments are invalid, FSL_RC_TYPE if any of the content appears to be binary (contains embedded NUL bytes), FSL_RC_RANGE if some range is exceeded (e.g. the maximum number of input lines). None of (pA, pB, out) may be NULL. TODOs: - Add a predicate function for outputing only matching differences, analog to fossil(1)'s regex support (but more flexible). - Expose the raw diff-generation bits via the internal API to facilitate/enable the creation of custom diff formats. @see fsl_diff_v2() @deprecated Prefer fsl_diff_v2() for new code. */ int fsl_diff_text(fsl_buffer const *pA, fsl_buffer const *pB, fsl_output_f out, void * outState, short contextLines, short sbsWidth, int diffFlags ); /** Functionally equivalent to: ``` fsl_diff_text(pA, pB, fsl_output_f_buffer, pOut, contextLines, sbsWidth, diffFlags); ``` Except that it returns FSL_RC_MISUSE if !pOut. @see fsl_diff_v2() @deprecated Prefer fsl_diff_v2() for new code. */ int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB, fsl_buffer *pOut, short contextLines, short sbsWidth, int diffFlags ); /** 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 ); /** @internal Performs the same job as fsl_diff_text() but produces the results in the low-level form of an array of "copy/delete/insert triples." This is primarily intended for internal use in other library-internal algorithms, not for client code. Note all FSL_DIFF_xxx flags apply to this form. Returns 0 on success, any number of non-0 codes on error. On success *outRaw will contain the resulting array, which must eventually be fsl_free()'d by the caller. On error *outRaw is not modified. @deprecated Use fsl_diff_v2_raw() instead. */ int fsl__diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, int diffFlags, int ** outRaw); /** @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, |
︙ | ︙ |
Added signify/fnc-07-release.pub.
> > | 1 2 | untrusted comment: fnc 0.7 public key RWS7z+k9eoeF8OQp7R9VyUfKlxOODpUYCBM2v/YEbk8YXiSK1FzkIVJD |
Deleted signify/fnc-09-release.pub.
|
| < < |
Deleted src/diff.c.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
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 Ciw .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 | 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, Space Open a .Cm diff view displaying the changeset of the currently selected commit. .It Cm b Open and populate branch view with all repository branches. .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 |
︙ | ︙ | |||
408 409 410 411 412 413 414 | .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 \ | < | | | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | .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 R | -repo Ar path 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, |
︙ | ︙ | |||
463 464 465 466 467 468 469 | 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. | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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. |
︙ | ︙ | |||
576 577 578 579 580 581 582 | 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. | < < | < < < < < < | 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 | 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 Open prompt to enter line number and navigate to line. .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. |
︙ | ︙ | |||
780 781 782 783 784 785 786 | .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. | < < < < < < | 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 |
︙ | ︙ | |||
862 863 864 865 866 867 868 | 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. | < < > > | 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 | 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. .It Cm L Open prompt to enter line number and navigate to line. .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 |
︙ | ︙ | |||
957 958 959 960 961 962 963 | 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. | | | 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 | 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 , -close Display only closed branches. .It Fl h , -help Display branch command help and usage information then exit. .It Fl o , -open Display only opened branches. .It Fl p , -no-private Do not show private branches, which are included in the list of displayed |
︙ | ︙ | |||
1156 1157 1158 1159 1160 1161 1162 | .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 . | < < < < < < | 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 |
︙ | ︙ |
Changes to src/fnc.c.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /* * 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. */ /* | > | | < | | | > > > | > > > | | < < < < < < < < < < | < | | 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 | /* * Copyright (c) 2021, 2022 Mark Jamsek <mark@jamsek.com> * Copyright (c) 2013-2021 Stephan Beal <https://wanderinghorse.net> * 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. */ /* * This _POSIX_C_SOURCE bit really belongs in a config.h, but in the * name of expedience... */ #if defined __linux__ # if !defined(_XOPEN_SOURCE) # define _XOPEN_SOURCE 700 /* * _POSIX_C_SOURCE >= 199309L needed for sigaction(), sigemptyset() on Linux, * but glibc docs claim that _XOPEN_SOURCE>=700 has the same effect, PLUS * we need _XOPEN_SOURCE>=500 for ncurses wide-char APIs on linux. */ # endif # if !defined(_DEFAULT_SOURCE) # define _DEFAULT_SOURCE /* Needed for strsep() on glibc >= 2.19. */ # endif #endif #ifdef FCLI_USE_SIGACTION #define FCLI_USE_SIGACTION 0 /* We want C-c to exit. */ #endif #include <sys/queue.h> #include <sys/ioctl.h> |
︙ | ︙ | |||
70 71 72 73 74 75 76 | #include <libgen.h> #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" | | | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | #include <libgen.h> #include <regex.h> #include <signal.h> #include <wchar.h> #include <langinfo.h> #include "libfossil.h" #include "settings.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)) #if !defined(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 |
︙ | ︙ | |||
112 113 114 115 116 117 118 | #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 */ | < < < < < | | < < < | | 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | #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__ #ifndef HAVE_STRTONUM # define strtonum(s, min, max, o) strtol(s, (char **)o, 10) # endif /* HAVE_STRTONUM */ #endif #ifndef __dead #define __dead __attribute__((noreturn)) #endif #ifndef TAILQ_FOREACH_SAFE /* Rewrite of OpenBSD 6.9 sys/queue.h for Linux builds. */ |
︙ | ︙ | |||
202 203 204 205 206 207 208 | 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. */ | < < < < < | | < < < | 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 | 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. */ fcli_cliflag cliflags_diff[8]; /* Diff options. */ fcli_cliflag cliflags_tree[5]; /* Tree options. */ fcli_cliflag cliflags_blame[7]; /* 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, /* context defaults to five context lines. */ false, /* ws defaults to acknowledge whitespace. */ false, /* nocolour defaults to off (i.e., use diff colours). */ false, /* quiet defaults to off (i.e., verbose diff is on). */ false, /* invert diff defaults to off. */ NULL, /* before defaults to any time. */ NULL, /* after defaults to any time. */ NULL, /* sort by MRU or open/closed (dflt: lexicographical) */ false, /* closed only branches is off (defaults to all). */ false, /* open only branches is off by (defaults to all). */ false, /* noprivate is off (default to show private branch). */ false, /* do not list all defined settings by default. */ |
︙ | ︙ | |||
363 364 365 366 367 368 369 | "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."), | < < < < < < < | 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 |
︙ | ︙ | |||
431 432 433 434 435 436 437 | "\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."), | < < | 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" |
︙ | ︙ | |||
540 541 542 543 544 545 546 | FNC_DIFF_WIKI }; struct input { void *data; char *prompt; enum input_type type; | | < < < < | 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | FNC_DIFF_WIKI }; struct input { void *data; char *prompt; enum input_type type; bool clear; char buf[BUFSIZ]; long ret; }; struct fnc_colour { STAILQ_ENTRY(fnc_colour) entries; regex_t regex; |
︙ | ︙ | |||
644 645 646 647 648 649 650 | * 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; | | | | 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 | * 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_idx; }; 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_commit; 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; |
︙ | ︙ | |||
679 680 681 682 683 684 685 | }; 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; | | < < < < < < < | < | < < | 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 | }; struct fnc_tl_view_state { struct fnc_tl_thread_cx thread_cx; struct commit_queue commits; struct commit_entry *first_commit_onscreen; struct commit_entry *last_commit_onscreen; struct commit_entry *selected_commit; struct commit_entry *matched_commit; struct commit_entry *search_commit; struct fnc_colours colours; const char *curr_ckout_uuid; const char *glob; /* Match commits containing glob. */ char *path; /* Match commits involving path. */ int selected_idx; int nscrolled; sig_atomic_t quit; pthread_t thread_id; bool colour; }; struct fnc_pathlist_entry { TAILQ_ENTRY(fnc_pathlist_entry) entry; const char *path; size_t pathlen; void *data; /* XXX May want to save id, mode, etc. */ }; TAILQ_HEAD(fnc_pathlist_head, fnc_pathlist_entry); struct 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_commit; 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; size_t ncols; size_t nlines; enum line_attr sline; off_t *line_offsets; bool eof; bool colour; bool showmeta; bool showln; }; |
︙ | ︙ | |||
763 764 765 766 767 768 769 | 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; | | | 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 | 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_idx; bool colour; bool show_id; bool show_date; }; struct fnc_blame_line { fsl_uuid_str id; |
︙ | ︙ | |||
819 820 821 822 823 824 825 | 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; | | < | 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 | 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_commit; struct fnc_colours colours; fsl_uuid_str commit_id; char *path; int first_line_onscreen; int last_line_onscreen; int selected_line; int matched_line; int spin_idx; int gtl; |
︙ | ︙ | |||
859 860 861 862 863 864 865 | 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; | | | 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 | TAILQ_HEAD(fnc_branchlist_head, fnc_branchlist_entry); struct fnc_branch_view_state { struct fnc_branchlist_head branches; struct fnc_branchlist_entry *first_branch_onscreen; struct fnc_branchlist_entry *last_branch_onscreen; struct fnc_branchlist_entry *matched_branch; struct fnc_branchlist_entry *selected_branch; 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). */ |
︙ | ︙ | |||
882 883 884 885 886 887 888 | int when; bool colour; bool show_date; bool show_id; }; struct line { | | | | | | 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 | int when; bool colour; bool show_date; bool show_id; }; struct line { char buf[BUFSIZ]; int sz; enum line_type type; bool selected; }; struct position { int col; int line; int offset; }; |
︙ | ︙ | |||
954 955 956 957 958 959 960 | 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 **); | | | | < | < | | < | < < | | | > > > > | > | | | < | 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 | 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 *, int, int, size_t, bool); static size_t expand_tab(char *, size_t, 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 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 *); static int open_diff_view(struct fnc_view *, struct fnc_commit_artifact *, int, bool, bool, bool, struct fnc_view *, bool, struct fnc_pathlist_head *); static void show_diff_status(struct fnc_view *); static int create_diff(struct fnc_diff_view_state *); static int create_changeset(struct fnc_commit_artifact *); static int write_commit_meta(struct fnc_diff_view_state *); static int wrapline(char *, fsl_size_t ncols_avail, struct fnc_diff_view_state *, off_t *); static int add_line_offset(off_t **, size_t *, off_t); static int diff_commit(struct fnc_diff_view_state *); static int diff_checkout(struct fnc_diff_view_state *); static int write_diff_meta(fsl_buffer *, const char *, fsl_uuid_str, const char *, fsl_uuid_str, int, enum fsl_ckout_change_e); static int diff_file(struct fnc_diff_view_state *, fsl_buffer *, const char *, fsl_uuid_str, const char *, enum fsl_ckout_change_e); static int fnc_diff_builder(fsl_dibu **, fsl_uuid_cstr, fsl_uuid_cstr, const char *, const char *, int, int, fsl_buffer *); static void fnc_free_diff_builder(fsl_dibu *); static int diff_non_checkin(fsl_buffer *, struct fnc_commit_artifact *, int, int, int); 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 *, 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 **); 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); |
︙ | ︙ | |||
1093 1094 1095 1096 1097 1098 1099 | 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 *); | | | | 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 | 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_repository_tree(struct fnc_repository_tree *); static int open_blame_view(struct fnc_view *, char *, fsl_uuid_str, fsl_id_t, int); 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); |
︙ | ︙ | |||
1144 1145 1146 1147 1148 1149 1150 | 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 *); | < | > | < > < | | 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 | 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 *); static void fnc_print_msg(struct fnc_view *, const char *, bool, bool, bool); 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); #ifdef __OpenBSD__ static int init_unveil(const char *, const char *, bool); #endif 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 *); |
︙ | ︙ | |||
1331 1332 1333 1334 1335 1336 1337 | if (rc) goto end; } rc = init_curses(); if (rc) goto end; | > | > < < | | 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 | if (rc) goto end; } rc = init_curses(); if (rc) goto end; #ifdef __OpenBSD__ rc = init_unveil(fsl_cx_db_file_repo(f, NULL), fsl_cx_ckout_dir_name(f, NULL), false); if (rc) goto end; #endif rc = init_timeline_view(&v, 0, 0, rid, path, glob); if (!rc) rc = view_loop(v); end: fsl_free(glob); fsl_free(path); |
︙ | ︙ | |||
1704 1705 1706 1707 1708 1709 1710 | */ /* 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; | | | 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 | */ /* 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_idx = 0; */ /* Unnecessary? */ TAILQ_INIT(&s->commits.head); s->commits.ncommits = 0; if (rid) startdate = fsl_mprintf("(SELECT mtime FROM event " "WHERE objid=%d)", rid); |
︙ | ︙ | |||
1923 1924 1925 1926 1927 1928 1929 | 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(); | < | | 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 | 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; s->thread_cx.selected_commit = &s->selected_commit; s->thread_cx.searching = &view->searching; s->thread_cx.search_status = &view->search_status; s->thread_cx.regex = &view->regex; s->thread_cx.path = s->path; s->thread_cx.reset = true; if (s->colour) { |
︙ | ︙ | |||
2140 2141 2142 2143 2144 2145 2146 | 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); | | | 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 | 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_commit = *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); |
︙ | ︙ | |||
2410 2411 2412 2413 2414 2415 2416 | if (cx->eotl) break; if (view->mode == VIEW_SPLIT_HRZN) cx->reset = true; /* Wake timeline thread. */ | | < | < | | | | | | | | | 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 | if (cx->eotl) break; if (view->mode == VIEW_SPLIT_HRZN) cx->reset = true; /* Wake timeline thread. */ if ((rc = pthread_cond_signal(&cx->commit_consumer))) 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. */ if ((rc = pthread_cond_wait(&cx->commit_producer, &fnc_mutex))) 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_commit; struct fnc_colour *c = NULL; const char *search_str = NULL; char *headln = NULL, *idxstr = NULL; char *branch = NULL, *type = NULL; char *uuid = NULL; wchar_t *wcstr; attr_t rx = A_BOLD; int ncommits = 0, rc = 0, wstrlen = 0; int ncols_needed, max_usrlen = -1; if (s->selected_commit && !(view->searching != SEARCH_DONE && view->search_status == SEARCH_WAITING)) { uuid = fsl_strdup(s->selected_commit->commit->uuid); branch = fsl_strdup(s->selected_commit->commit->branch); type = fsl_strdup(s->selected_commit->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) { |
︙ | ︙ | |||
2525 2526 2527 2528 2529 2530 2531 | == NULL) { rc = RC(FSL_RC_RANGE, "%s", "fsl_mprintf"); headln = NULL; goto end; } if (SPINNER[++tcx->spin_idx] == '\0') tcx->spin_idx = 0; | | | | | | | | | > | | | | < | | < | 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 | == 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(&wcstr, &wstrlen, headln, view->ncols, 0, 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, wcstr); while (wstrlen < view->ncols) { waddch(view->window, ' '); ++wstrlen; } wattroff(view->window, rx); fsl_free(wcstr); 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 *usr_wcstr; char *user; int usrlen; 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(&usr_wcstr, &usrlen, user, view->ncols, 0, 0, false); if (max_usrlen < usrlen) max_usrlen = usrlen; fsl_free(usr_wcstr); 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_idx) wattr_on(view->window, A_REVERSE, NULL); rc = write_commit_line(view, entry->commit, max_usrlen); if (ncommits == s->selected_idx) wattr_off(view->window, A_REVERSE, NULL); ++ncommits; s->last_commit_onscreen = entry; entry = TAILQ_NEXT(entry, entries); } drawborder(view); |
︙ | ︙ | |||
2617 2618 2619 2620 2621 2622 2623 | usr = *username; usr[strcspn(usr, "@>")] = '\0'; *username = usr; } static int | | | | < | | | > | > > > | < | 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 | usr = *username; usr[strcspn(usr, "@>")] = '\0'; *username = usr; } static int formatln(wchar_t **ptr, int *wstrlen, const char *mbstr, int column_limit, int start_column, size_t skip, bool expand) { wchar_t *wline = NULL; static char exstr[BUFSIZ]; size_t i, sz, wlen; int cols = 0, rc = FSL_RC_OK; *ptr = NULL; *wstrlen = 0; sz = fsl_strlen(mbstr); if (expand) { sz = expand_tab(exstr, sizeof(exstr), mbstr, sz); mbstr = exstr; } if (skip) skip = MIN(skip, sz); rc = multibyte_to_wchar(mbstr + skip, &wline, &wlen); if (rc) return rc; if (wlen > 0 && wline[wlen - 1] == L'\n') { wline[wlen - 1] = L'\0'; wlen--; } |
︙ | ︙ | |||
2695 2696 2697 2698 2699 2700 2701 | /* * 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 | | < | | < < < | | | < | 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 | /* * 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 *dst, size_t dstlen, const char *src, int srclen) { size_t sz = 0; int idx = 0; while (sz < dstlen - 1 && idx < srclen && src[idx]) { const char c = *(src + idx); if (c == '\t') { size_t spaces = TABSIZE - (sz % TABSIZE); if (spaces + sz >= dstlen - 1) spaces = dstlen - sz - 1; memcpy(dst + sz, " ", spaces); sz += spaces; } else { dst[sz++] = src[idx]; } ++idx; } dst[sz] = '\0'; return sz; } static int multibyte_to_wchar(const char *src, wchar_t **dst, size_t *dstlen) { int rc = 0; |
︙ | ︙ | |||
2778 2779 2780 2781 2782 2783 2784 | * 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, | | | | | | | | | | | | | < | | | | | > | | | | | > | 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 | * 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 max_usrlen) { struct fnc_tl_view_state *s = &view->state.timeline; struct fnc_colour *c = NULL; wchar_t *usr_wcstr = NULL, *wcomment = NULL; char *comment0 = NULL, *comment = NULL; char *date = NULL; char *eol = NULL, *pad = NULL, *user = NULL; size_t i = 0; int col_pos, ncols_avail, usrlen; int commentlen, rc = 0; /* Trim time component from timestamp for the date field. */ date = fsl_strdup(commit->timestamp); while (!fsl_isspace(date[i++])) {} date[i] = '\0'; col_pos = MIN(view->ncols, ISO8601_DATE_ONLY + 1); if (s->colour) c = get_colour(&s->colours, FNC_COLOUR_DATE); if (c) wattr_on(view->window, COLOR_PAIR(c->scheme), NULL); waddnstr(view->window, date, col_pos); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); if (col_pos > view->ncols) goto end; /* If enough columns, write abbreviated commit hash. */ if (view->ncols >= 110) { if (s->colour) c = get_colour(&s->colours, FNC_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_pos += 10; if (col_pos > view->ncols) goto end; } /* * Parse username from emailaddr if needed, and postfix username * with as much whitespace as needed to fill two spaces beyond * the longest username on the screen. */ user = fsl_strdup(commit->user); if (user == NULL) goto end; if (strpbrk(user, "<@>") != NULL) parse_emailaddr_username(&user); rc = formatln(&usr_wcstr, &usrlen, user, view->ncols - col_pos, col_pos, 0, 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, usr_wcstr); pad = fsl_mprintf("%*c", max_usrlen - usrlen + 2, ' '); waddstr(view->window, pad); if (c) wattr_off(view->window, COLOR_PAIR(c->scheme), NULL); col_pos += (max_usrlen + 2); if (col_pos > view->ncols) goto end; /* Only show comment up to the first newline character. */ comment0 = fsl_strdup(commit->comment); comment = comment0; if (comment == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_strdup"); while (*comment == '\n') ++comment; eol = strchr(comment, '\n'); if (eol) *eol = '\0'; ncols_avail = view->ncols - col_pos; rc = formatln(&wcomment, &commentlen, comment, ncols_avail, col_pos, 0, false); if (rc) goto end; waddwstr(view->window, wcomment); col_pos += commentlen; while (col_pos < view->ncols) { waddch(view->window, ' '); ++col_pos; } end: fsl_free(date); fsl_free(user); fsl_free(usr_wcstr); fsl_free(pad); fsl_free(comment0); fsl_free(wcomment); return rc; } static int view_input(struct fnc_view **new, int *done, struct fnc_view *view, struct view_tailhead *views) { |
︙ | ︙ | |||
3082 3083 3084 3085 3086 3087 3088 | {" N ", " ❬N❭ "}, {" q ", " ❬q❭ "}, {" Q ", " ❬Q❭ "}, {""}, {""}, /* Timeline */ {" <,, ", " ❬<❭❬,❭ "}, {" >,. ", " ❬>❭❬.❭ "}, | | < < < < < < > | 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 | {" N ", " ❬N❭ "}, {" q ", " ❬q❭ "}, {" Q ", " ❬Q❭ "}, {""}, {""}, /* Timeline */ {" <,, ", " ❬<❭❬,❭ "}, {" >,. ", " ❬>❭❬.❭ "}, {" Enter,Space ", " ❬Enter❭❬Space❭ "}, {" b ", " ❬b❭ "}, {" 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❭ "}, {" 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❭❬←❭ "}, {" L ", " ❬L❭ "}, {" b ", " ❬b❭ "}, {" p ", " ❬p❭ "}, {" B ", " ❬B❭ "}, {" T ", " ❬T❭ "}, {""}, {""}, /* Branch */ {" Enter,Space ", " ❬Enter❭❬Space❭ "}, |
︙ | ︙ | |||
3172 3173 3174 3175 3176 3177 3178 | "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", | < < < | < < < > | 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 | "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", "Open prompt to enter line number and navigate to line", "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", "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", "Open prompt to enter line number and navigate to line", "Blame the version of the file found in the selected line's commit", "Blame the version of the file found in the selected line's parent " "commit", "Reload the previous blamed version of the file", "Open and populate branch view with all repository branches", "", "Branch", |
︙ | ︙ | |||
3406 3407 3408 3409 3410 3411 3412 | 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; | < < | 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; |
︙ | ︙ | |||
3449 3450 3451 3452 3453 3454 3455 | if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: move_tl_cursor_up(view, false, true); break; case KEY_RESIZE: | | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < | | | > > | | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 | if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: move_tl_cursor_up(view, false, true); break; case KEY_RESIZE: if (s->selected_idx > view->nlines - 2) s->selected_idx = view->nlines - 2; if (s->selected_idx > s->commits.ncommits - 1) s->selected_idx = 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 KEY_ENTER: case ' ': 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, true}; 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) { fnc_print_msg(view, "-- no matching commits --", true, true, true); fcli_err_reset(); rc = FSL_RC_OK; } break; } case 't': if (s->selected_commit == NULL) break; if (!fsl_rid_is_a_checkin(fcli_cx(), s->selected_commit->commit->rid)) fnc_print_msg(view, "-- tree requires check-in artifact --", true, true, true); else rc = request_view(new_view, view, FNC_VIEW_TREE); break; case 'q': s->quit = 1; break; default: break; } return rc; } 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_commit->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_idx < MIN(view->nlines - 2, s->commits.ncommits - 1)) ++s->selected_idx; 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_idx += MIN(s->last_commit_onscreen->idx - s->selected_commit->idx, page + 1); else /* Scroll the page. */ rc = timeline_scroll_down(view, MIN(page, s->commits.ncommits - s->selected_commit->idx - 1)); } else { rc = timeline_scroll_down(view, page); if (rc) return rc; if (first == s->first_commit_onscreen && s->selected_idx < MIN(view->nlines - 2, s->commits.ncommits - 1)) { /* End of timeline, no more commits; move cursor down */ s->selected_idx = MIN(s->commits.ncommits - 1, page); } /* * If we've overshot (necessarily possible with horizontal * splits), select the final commit. */ s->selected_idx = MIN(s->selected_idx, 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_idx = home ? 0 : MAX(0, s->selected_idx - page - 1); if (!page && !home && s->selected_idx > 0) --s->selected_idx; else timeline_scroll_up(s, home ? s->commits.ncommits : MAX(page, 1)); select_commit(s); return; } |
︙ | ︙ | |||
3696 3697 3698 3699 3700 3701 3702 | 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; | | | | | | | 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 | 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_commit == NULL) break; rc = init_diff_view(new_view, x, y, s->selected_commit->commit, view); 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_branch->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_commit, 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"); |
︙ | ︙ | |||
3749 3750 3751 3752 3753 3754 3755 | * 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) { | | | < | 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 | * 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); } 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) { |
︙ | ︙ | |||
3799 3800 3801 3802 3803 3804 3805 | int *selected; switch (view->vid) { case FNC_VIEW_TIMELINE: { struct fnc_tl_view_state *s = &view->state.timeline; scrolld = &timeline_scroll_down; header = 2; | | | | 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 | 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_idx; break; } case FNC_VIEW_TREE: { struct fnc_tree_view_state *s = &view->state.tree; scrolld = &tree_scroll_down; header = 4; selected = &s->selected_idx; break; } case FNC_VIEW_BRANCH: { struct fnc_branch_view_state *s = &view->state.branch; scrolld = &branch_scroll_down; header = 1; selected = &s->selected; |
︙ | ︙ | |||
3900 3901 3902 3903 3904 3905 3906 | select_commit(struct fnc_tl_view_state *s) { struct commit_entry *entry; int ncommits = 0; entry = s->first_commit_onscreen; while (entry) { | | | | 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 | 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_idx) { s->selected_commit = entry; break; } entry = TAILQ_NEXT(entry, entries); ++ncommits; } } |
︙ | ︙ | |||
4006 4007 4008 4009 4010 4011 4012 | fsl_free(height); return !rc && n && n < (lines - 2) ? lines - n : lines * HSPLIT_SCALE; } static int view_search_start(struct fnc_view *view) { | | | 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 | 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, true}; int rc = FSL_RC_OK; if (view->started_search) { regfree(&view->regex); view->searching = SEARCH_DONE; memset(&view->regmatch, 0, sizeof(view->regmatch)); } |
︙ | ︙ | |||
4140 4141 4142 4143 4144 4145 4146 | if (view->searching == SEARCH_FORWARD) entry = TAILQ_NEXT(entry, entries); else entry = TAILQ_PREV(entry, commit_tailhead, entries); } if (s->matched_commit) { | | | 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 | 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_commit->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))) |
︙ | ︙ | |||
4181 4182 4183 4184 4185 4186 4187 | static int view_close(struct fnc_view *view) { int rc = FSL_RC_OK; if (view->child) { | < < < > | 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 4084 4085 4086 4087 4088 4089 4090 4091 | 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); regfree(&view->regex); fsl_free(s->path); s->path = NULL; return rc; } /* static void */ |
︙ | ︙ | |||
4282 4283 4284 4285 4286 4287 4288 | --commits->ncommits; } } static void fnc_commit_artifact_close(struct fnc_commit_artifact *commit) { | < < < | < | | < | | > | < < | | | < < < | 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 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 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 | --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, struct fnc_commit_artifact *commit, struct fnc_view *timeline_view) { 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.ws, fnc_init.invert, !fnc_init.quiet, timeline_view, true, NULL); if (!rc) *new_view = diff_view; return rc; } static int open_diff_view(struct fnc_view *view, struct fnc_commit_artifact *commit, int context, bool ignore_ws, bool invert, bool verbosity, struct fnc_view *timeline_view, bool showmeta, struct fnc_pathlist_head *paths) { struct fnc_diff_view_state *s = &view->state.diff; 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->maxx = 0; s->paths = paths; s->selected_commit = 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; verbosity ? FLAG_SET(s->diff_flags, FSL_DIFF_VERBOSE) : 0; ignore_ws ? FLAG_SET(s->diff_flags, FSL_DIFF2_IGNORE_ALLWS) : 0; invert ? FLAG_SET(s->diff_flags, FSL_DIFF2_INVERT) : 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->ncols = view->ncols; rc = create_diff(s); if (rc) { if (s->colour) free_colours(&s->colours); return rc; } |
︙ | ︙ | |||
4415 4416 4417 4418 4419 4420 4421 | } static int create_diff(struct fnc_diff_view_state *s) { FILE *fout = NULL; char *line, *st0 = NULL, *st = NULL; | | < < < < < < < < < < < | < | | | < < | | > | | | | > | < | < | > > | | | | | | | | 4276 4277 4278 4279 4280 4281 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 | } static int create_diff(struct fnc_diff_view_state *s) { FILE *fout = NULL; char *line, *st0 = NULL, *st = NULL; off_t lnoff = 0; uint32_t idx = 0; int rc = 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; /* * We'll diff artifacts of type "ci" (i.e., "checkin") separately, as * it's a different process to diff the others (wiki, technote, etc.). */ if (s->selected_commit->diff_type == FNC_DIFF_COMMIT) rc = create_changeset(s->selected_commit); else if (s->selected_commit->diff_type == FNC_DIFF_BLOB) rc = diff_file_artifact(s, s->selected_commit->prid, NULL, NULL, FSL_CKOUT_CHANGE_MOD); else if (s->selected_commit->diff_type == FNC_DIFF_WIKI) rc = diff_non_checkin(&s->buf, s->selected_commit, s->diff_flags, s->context, s->sbs); if (rc) goto end; /* * Delay assigning diff headline labels (i.e., diff id1 id2) till now * because wiki parent commits are obtained in diff_non_checkin(). */ if (s->selected_commit->puuid) { fsl_free(s->id1); s->id1 = fsl_strdup(s->selected_commit->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_commit->uuid) { fsl_free(s->id2); s->id2 = fsl_strdup(s->selected_commit->uuid); if (s->id2 == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strdup"); goto end; } } else s->id2 = NULL; /* Local work tree. */ rc = add_line_offset(&s->line_offsets, &s->nlines, 0); if (rc) goto end; if (s->showmeta) write_commit_meta(s); /* * Diff local changes on disk in the current checkout differently to * checked-in versions: the former compares on disk file content with * file artifacts; the latter compares file artifact blobs only. */ if (s->selected_commit->diff_type == FNC_DIFF_COMMIT) diff_commit(s); else if (s->selected_commit->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; lnoff = (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((int)s->maxx, n); if (s->index.offset && idx < s->index.n && lnoff == s->index.offset[idx]) { lineno = s->nlines + (idx ? 1 : 0); s->index.lineno[idx++] = lineno; } lnoff += n; rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff); if (rc) goto end; } --s->nlines; /* Don't count EOF '\n' */ end: fsl_free(st0); fsl_buffer_clear(&s->buf); |
︙ | ︙ | |||
4562 4563 4564 4565 4566 4567 4568 | 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. */ | | | 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 | 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; |
︙ | ︙ | |||
4602 4603 4604 4605 4606 4607 4608 | } static int write_commit_meta(struct fnc_diff_view_state *s) { char *line = NULL, *st0 = NULL, *st = NULL; fsl_size_t linelen, idx = 0; | | < | < < | | | < < | < | | < < | < | | | < < | < | | < < | < | < < | < | | | < < < | | < | < < | < | | | | 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 4535 4536 | } 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 lnoff = 0; int n, rc = 0; if ((n = fprintf(s->f,"%s %s\n", s->selected_commit->type, s->selected_commit->uuid)) < 0) goto end; lnoff += n; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; if ((n = fprintf(s->f,"user: %s\n", s->selected_commit->user)) < 0) goto end; lnoff += n; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; if ((n = fprintf(s->f,"tags: %s\n", s->selected_commit->branch ? s->selected_commit->branch : "/dev/null")) < 0) goto end; lnoff += n; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; if ((n = fprintf(s->f,"date: %s\n", s->selected_commit->timestamp)) < 0) goto end; lnoff += n; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; fputc('\n', s->f); ++lnoff; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; st0 = fsl_strdup(s->selected_commit->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, &lnoff); if (rc) goto end; } else { if ((n = fprintf(s->f, "%s\n", line)) < 0) goto end; lnoff += n; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; } } fputc('\n', s->f); ++lnoff; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; if (s->selected_commit->diff_type == FNC_DIFF_WIKI) goto end; /* No changeset for wiki commits. */ for (idx = 0; idx < s->selected_commit->changeset.used; ++idx) { char *changeline; struct fsl_file_artifact *file_change; file_change = s->selected_commit->changeset.list[idx]; switch (file_change->change) { case FSL_CKOUT_CHANGE_MOD: changeline = "[~] "; break; case FSL_CKOUT_CHANGE_ADDED: changeline = "[+] "; |
︙ | ︙ | |||
4721 4722 4723 4724 4725 4726 4727 | default: changeline = "[!] "; break; } if ((n = fprintf(s->f, "%s%s\n", changeline, file_change->fc->name)) < 0) goto end; | | < < < | < | < < | < < | | < < < < < < < < < < < < | | | | | | < < < | | | | < < | > | < < < < < < < < < < < < < < < < < < < < | | | | | 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 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 | default: changeline = "[!] "; break; } if ((n = fprintf(s->f, "%s%s\n", changeline, file_change->fc->name)) < 0) goto end; lnoff += n; if ((rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff))) goto end; } /* Add blank line between end of changeset and diff. */ fputc('\n', s->f); ++lnoff; rc = add_line_offset(&s->line_offsets, &s->nlines, lnoff); s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(size_t)); s->index.offset[s->index.n++] = lnoff; end: free(st0); free(line); if (rc) { free(*&s->line_offsets); s->line_offsets = NULL; s->nlines = 0; } return rc; } /* * Wrap long lines at the terminal's available column width. The caller * must ensure the ncols_avail 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 ncols_avail limit. */ static int wrapline(char *line, fsl_size_t ncols_avail, struct fnc_diff_view_state *s, off_t *lnoff) { 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) >= ncols_avail) { fputc('\n', s->f); ++(*lnoff); rc = add_line_offset(&s->line_offsets, &s->nlines, *lnoff); if (rc) return rc; cursor = 0; } if ((n = fprintf(s->f, "%s ", word)) < 0) return rc; *lnoff += n; cursor += n; } fputc('\n', s->f); ++(*lnoff); if ((rc = add_line_offset(&s->line_offsets, &s->nlines, *lnoff))) return rc; return 0; } 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; } /* * 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 FSL_DIFF_VERBOSE is * set, otherwise only diff metatadata will be output. In case (3), if the * hash (UUID) of each F card is the same, there are no changes; if different, * both artifacts will be passed to diff_file_artifact() to be diffed. */ static int diff_commit(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_commit->rid, FSL_SATYPE_CHECKIN); if (rc) goto end; rc = fsl_deck_F_rewind(&d2); if (rc) goto end; /* * For the one-and-only special case of repositories, such as the * canonical fnc, that do not have an "initial empty check-in", we * proceed with no parent version to diff against. */ if (s->selected_commit->puuid) { rc = fsl_sym_to_rid(f, s->selected_commit->puuid, FSL_SATYPE_CHECKIN, &id1); if (rc) goto end; rc = fsl_deck_load_rid(f, &d1, id1, FSL_SATYPE_CHECKIN); if (rc) goto end; rc = fsl_deck_F_rewind(&d1); |
︙ | ︙ | |||
4984 4985 4986 4987 4988 4989 4990 | 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; | | > > > > > > | 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 | 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_commit->prid; fsl_ckout_version_info(f, &cid, NULL); /* cid = fsl_config_get_id(f, FSL_CONFDB_CKOUT, 0, "checkout"); */ /* XXX Already done in cmd_diff(): Load vfile table with local state. */ /* rc = fsl_vfile_changes_scan(f, cid, */ /* FSL_VFILE_CKSIG_ENOTFILE & FSL_VFILE_CKSIG_KEEP_OTHERS); */ /* if (rc) */ /* return RC(rc, "%s", "fsl_vfile_changes_scan"); */ /* * 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. */ |
︙ | ︙ | |||
5103 5104 5105 5106 5107 5108 5109 | } 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)) { | | | | 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 | } 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->buf, path, xminus, path, NULL_DEVICE, s->diff_flags, 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) { |
︙ | ︙ | |||
5171 5172 5173 5174 5175 5176 5177 | * 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 | | | > < | 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 | * 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(fsl_buffer *buf, const char *zminus, fsl_uuid_str xminus, const char *zplus, fsl_uuid_str xplus, int diff_flags, enum fsl_ckout_change_e change) { int rc = 0; const char *index, *plus, *minus; index = zplus ? zplus : (zminus ? zminus : NULL_DEVICE); switch (change) { case FSL_CKOUT_CHANGE_MERGE_ADD: /* FALL THROUGH */ case FSL_CKOUT_CHANGE_INTEGRATE_ADD: |
︙ | ︙ | |||
5207 5208 5209 5210 5211 5212 5213 | /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } | | | < < < < < < < | | | | < < < > > > | 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 | /* FALL THROUGH */ default: minus = xminus; plus = xplus; break; } if FLAG_CHK(diff_flags, FSL_DIFF2_INVERT) { const char *tmp = minus; minus = plus; plus = tmp; tmp = zminus; zminus = zplus; zplus = tmp; } if (!FLAG_CHK(diff_flags, (FSL_DIFF_SIDEBYSIDE | FSL_DIFF_BRIEF))) { rc = fsl_buffer_appendf(buf, "%sIndex: %s\n%.71c\n", buf->used ? "\n" : "", index, '='); if (!rc) rc = fsl_buffer_appendf(buf, "hash - %s\nhash + %s\n", minus, plus); } if (!rc && !FLAG_CHK(diff_flags, FSL_DIFF_BRIEF)) rc = fsl_buffer_appendf(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_dibu *diffbld = NULL; 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 |
︙ | ︙ | |||
5316 5317 5318 5319 5320 5321 5322 | default: RC(FSL_RC_SIZE_MISMATCH, "invalid artifact uuid [%s]", xminus); goto end; } if (rc) goto end; | < | | | > > > > | | < > | | | | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | < | | | | | | | < | | < | | | | | < | | > > > > > | > | 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 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 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 | default: RC(FSL_RC_SIZE_MISMATCH, "invalid artifact uuid [%s]", xminus); goto end; } if (rc) goto end; s->index.offset = fsl_realloc(s->index.offset, (s->index.n + 1) * sizeof(size_t)); s->index.offset[s->index.n++] = s->buf.used; rc = write_diff_meta(&s->buf, zminus, xminus, zplus, fsl_buffer_str(&xplus), s->diff_flags, change); if (rc) goto end; rc = fnc_diff_builder(&diffbld, xminus, fsl_buffer_str(&xplus), zminus, zplus, s->context, s->diff_flags, &s->buf); if (rc) goto end; if FLAG_CHK(s->diff_flags, FSL_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, FSL_DIFF_VERBOSE) || (bminus->used && bplus.used)) rc = fsl_diff_v2(bminus, &bplus, diffbld); end: fsl_buffer_clear(&bplus); fsl_buffer_clear(&xplus); fnc_free_diff_builder(diffbld); return rc; } static int fnc_diff_builder(fsl_dibu **ptr, fsl_uuid_cstr xminus, fsl_uuid_cstr xplus, const char *zminus, const char *zplus, int context, int diff_flags, fsl_buffer *buf) { fsl_dibu *diffbld = NULL; fsl_dibu_opt *diffopt = NULL; struct fsl_dibu_opt_ansi ansiopt = {"", "", "", ""}; int rc = FSL_RC_OK; *ptr = NULL; diffopt = fsl_malloc(sizeof(fsl_dibu_opt)); if (diffopt == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_malloc"); FLAG_SET(diff_flags, FSL_DIFF2_CONTEXT_ZERO); diffopt->ansiColor = ansiopt; diffopt->hashLHS = xminus; diffopt->hashRHS = xplus; diffopt->nameLHS = zminus; diffopt->nameRHS = zplus; diffopt->diffFlags = diff_flags; diffopt->contextLines = context; diffopt->out = fsl_output_f_buffer; diffopt->outState = buf; rc = fsl_dibu_factory(FSL_DIBU_UNIFIED_TEXT, &diffbld); if (!rc) { diffbld->opt = diffopt; diffbld->start = NULL; *ptr = diffbld; } else fsl_free(diffopt); return rc; } static void fnc_free_diff_builder(fsl_dibu *diffbld) { if (diffbld) { if (diffbld->opt) fsl_free(diffbld->opt); diffbld->finalize(diffbld); } } /* * 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(fsl_buffer *buf, struct fnc_commit_artifact *commit, int diff_flags, int context, int sbs) { fsl_cx *const f = fcli_cx(); fsl_dibu *diffbld = NULL; 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); if ((rc = fsl_deck_load_rid(f, d, commit->rid, FSL_SATYPE_ANY))) 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(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(buf, "Tag %d ", idx + 1); switch (ctl->type) { case FSL_TAGTYPE_CANCEL: fsl_buffer_append(buf, "[CANCEL]", -1); break; case FSL_TAGTYPE_ADD: fsl_buffer_append(buf, "[ADD]", -1); break; case FSL_TAGTYPE_PROPAGATING: fsl_buffer_append(buf, "[PROPAGATE]", -1); break; default: break; } if (ctl->uuid) fsl_buffer_appendf(buf, "\ncheckin %s", ctl->uuid); fsl_buffer_appendf(buf, "\n%s", ctl->name); if (!fsl_strcmp(ctl->name, "branch")) commit->branch = fsl_strdup(ctl->value); if (ctl->value) fsl_buffer_appendf(buf, " -> %s", ctl->value); fsl_buffer_append(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 (commit->puuid == NULL) { if (d->P.used > 0) commit->puuid = fsl_strdup(d->P.list[0]); else { fsl_buffer_copy(buf, &wiki); goto end; } } /* Diff the artifacts if a parent is found. */ rc = fsl_sym_to_rid(f, commit->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_builder(&diffbld, NULL, NULL, NULL, NULL, context, diff_flags, buf); if (rc) goto end; rc = fsl_diff_v2(&pwiki, &wiki, diffbld); if (rc) goto end; /* If a technote, provide the full content after its diff. */ if (d->type == FSL_SATYPE_TECHNOTE) fsl_buffer_appendf(buf, "\n---\n\n%s", wiki.mem); end: fsl_buffer_clear(&wiki); fsl_buffer_clear(&pwiki); fsl_deck_finalize(d); fnc_free_diff_builder(diffbld); 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 |
︙ | ︙ | |||
5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 | * 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; | > | | | 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 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 | * 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_dibu *diffbld = NULL; 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_commit->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_commit->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; } |
︙ | ︙ | |||
5523 5524 5525 5526 5527 5528 5529 | } if (b) { rc = fsl_card_F_content(f, b, &fbuf2); if (rc) goto end; zplus = b->name; xplus = b->uuid; | | | 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 | } if (b) { rc = fsl_card_F_content(f, b, &fbuf2); if (rc) goto end; zplus = b->name; xplus = b->uuid; } else if (s->selected_commit->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; } |
︙ | ︙ | |||
5550 5551 5552 5553 5554 5555 5556 | xplus = xplus0; fsl_stmt_finalize(&stmt); fsl_content_get(f, vid2, &fbuf2); } if (s->buf.used) { s->index.offset = fsl_realloc(s->index.offset, | | | < | > < | | > > | > | < > | 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 | 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(size_t)); s->index.offset[s->index.n++] = s->buf.used + s->index.offset[0]; } rc = write_diff_meta(&s->buf, zminus, xminus, zplus, xplus, s->diff_flags, change); if (rc) goto end; rc = fnc_diff_builder(&diffbld, xminus, xplus, zminus, zplus, s->context, s->diff_flags, &s->buf); if (rc) goto end; if (FLAG_CHK(s->diff_flags, FSL_DIFF_VERBOSE) || (a && b)) rc = fsl_diff_v2(&fbuf1, &fbuf2, diffbld); if (rc) RC(rc, "%s: fsl_diff_text_to_buffer\n" " -> %s [%s]\n -> %s [%s]", fsl_rc_cstr(rc), a ? a->name : NULL_DEVICE, a ? a->uuid : NULL_DEVICE, b ? b->name : NULL_DEVICE, b ? b->uuid : NULL_DEVICE); end: fsl_free(zminus0); fsl_free(zplus0); fsl_free(xminus0); fsl_free(xplus0); fsl_buffer_clear(&fbuf1); fsl_buffer_clear(&fbuf2); fnc_free_diff_builder(diffbld); return rc; } static int show_diff(struct fnc_view *view) { struct fnc_diff_view_state *s = &view->state.diff; |
︙ | ︙ | |||
5624 5625 5626 5627 5628 5629 5630 | 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; | | | 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 | 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 npad = 0, 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"); |
︙ | ︙ | |||
5647 5648 5649 5650 5651 5652 5653 | 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"); | | | 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 | 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, 0, false); fsl_free(line); fsl_free(headln); if (rc) return rc; if (screen_is_shared(view) || view->active) rx |= A_REVERSE; |
︙ | ︙ | |||
5673 5674 5675 5676 5677 5678 5679 | if (--max_lines < 1) return rc; } s->eof = false; line = NULL; while (max_lines > 0 && nprinted < max_lines) { | < | 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); |
︙ | ︙ | |||
5696 5697 5698 5699 5700 5701 5702 | if (!gotoline(view, &s->lineno, &nprinted)) continue; rx = 0; if ((selected = nprinted == s->selected_line - 1)) rx = A_BOLD | A_REVERSE; if (s->showln) | | < < | | | > > > > | | < | > > > | < < < < < | | 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 | if (!gotoline(view, &s->lineno, &nprinted)) continue; rx = 0; if ((selected = nprinted == s->selected_line - 1)) rx = A_BOLD | A_REVERSE; if (s->showln) npad = draw_lineno(view, nlines, s->lineno, rx); if (s->colour) c = 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, &wstrlen, npad, regmatch, rx); if (rc) { fsl_free(line); return rc; } } else { rc = formatln(&wcstr, &wstrlen, line, view->ncols - npad, npad, view->pos.col, true); if (rc) { fsl_free(line); return rc; } waddwstr(view->window, wcstr); fsl_free(wcstr); wcstr = NULL; } col = wstrlen + npad; while (col++ < view->ncols) waddch(view->window, ' '); if (c || selected) wattroff(view->window, rx); if (++nprinted == 1) s->first_line_onscreen = s->lineno; |
︙ | ︙ | |||
5793 5794 5795 5796 5797 5798 5799 | update_panels(); #endif if (update) doupdate(); } static int | | | | > | | | < < < < < | | | > > > > | > > > | > > | | < | < > > > > > > > > | < > > > | < | | > > | | < | | > > > > | > > | > > > | > | | < | 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 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 | update_panels(); #endif if (update) doupdate(); } static int draw_matched_line(struct fnc_view *view, int *col, int offset, regmatch_t *regmatch, attr_t rx) { wchar_t *wcstr; char *s; int rme, rms, skip = view->pos.col, wstrlen; int n = 0, ncols_avail = view->ncols - offset, rc = 0; attr_t hl = A_BOLD | A_REVERSE; *col = 0; rms = regmatch->rm_so; rme = regmatch->rm_eo; /* * Copy line up to string match and draw to screen. Trim preceding skip * chars if scrolled right. Don't copy if skip consumes substring. */ s = fsl_strndup(view->line.buf + skip, MAX(rms - skip, 0)); if (s == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_strndup"); rc = formatln(&wcstr, &wstrlen, s, ncols_avail, offset, 0, false); if (rc) { free(s); return rc; } waddwstr(view->window, wcstr); free(wcstr); free(s); ncols_avail -= wstrlen; *col += wstrlen; /* * 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 (ncols_avail > 0) { int len = rme - rms; if (skip > rms) { n = skip - rms; len = MAX(len - n, 0); } s = fsl_strndup(view->line.buf + rms + n, len); if (s == NULL) { rc = RC(FSL_RC_ERROR, "%s", "fsl_strndup"); free(s); return rc; } rc = formatln(&wcstr, &wstrlen, s, ncols_avail, offset, 0, false); if (rc) { free(s); return rc; } wattron(view->window, COLOR_PAIR(FNC_COLOUR_HL_SEARCH) | hl); waddwstr(view->window, wcstr); wattroff(view->window, COLOR_PAIR(FNC_COLOUR_HL_SEARCH) | hl); free(wcstr); free(s); ncols_avail -= wstrlen; *col += wstrlen; } /* * 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 (ncols_avail > 0 && view->line.sz > rme) { n = 0; if (skip > rme) n = MIN(skip - rme, view->line.sz - rme); rc = formatln(&wcstr, &wstrlen, view->line.buf + rme + n, ncols_avail, offset, 0, false); if (rc) return rc; wattron(view->window, rx); waddwstr(view->window, wcstr); free(wcstr); ncols_avail -= wstrlen; *col += wstrlen; while (--ncols_avail >= 0) { ++(*col); waddch(view->window, ' '); } wattroff(view->window, rx); } return rc; } static void drawborder(struct fnc_view *view) { const struct fnc_view *view_above; |
︙ | ︙ | |||
5910 5911 5912 5913 5914 5915 5916 | s->lineno = s->first_line_onscreen - 1 + s->selected_line; switch (ch) { case '0': view->pos.col = 0; break; case '$': | | | < | < | 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 | s->lineno = s->first_line_onscreen - 1 + s->selected_line; switch (ch) { case '0': view->pos.col = 0; break; case '$': view->pos.col = s->maxx - view->ncols / 2; 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_commit->diff_type == FNC_DIFF_WIKI) 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_commit->diff_type == FNC_DIFF_WIKI) 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]; |
︙ | ︙ | |||
6049 6050 6051 6052 6053 6054 6055 | /* * XXX Assume user would expect file navigation (C-n/p) to * reset after jumping home. */ s->index.idx = 0; break; case 'F': | | | | | | 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 | /* * 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_commit->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, true}; 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 'L': { struct input input = {(int []){1, nlines}, "line: ", INPUT_NUMERIC, true}; rc = fnc_prompt_input(view, &input); s->gtl = input.ret; break; } case '#': s->showln = !s->showln; break; |
︙ | ︙ | |||
6113 6114 6115 6116 6117 6118 6119 | view->focus_child = true; } else *new_view = branch_view; break; } case 'c': case 'i': | < < < < < < < < | | < < < < < < | | | | | 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 | 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; if (ch == 'i') FLAG_TOG(s->diff_flags, FSL_DIFF2_INVERT); if (ch == 'v') FLAG_TOG(s->diff_flags, FSL_DIFF_VERBOSE); if (ch == 'w') FLAG_TOG(s->diff_flags, FSL_DIFF2_IGNORE_ALLWS); rc = reset_diff_view(view, true); break; case '-': case '_': if (s->context > 0) { --s->context; rc = reset_diff_view(view, true); |
︙ | ︙ | |||
6166 6167 6168 6169 6170 6171 6172 | case CTRL('k'): case '<': case ',': case 'K': if (s->timeline_view == NULL) break; tlstate = &s->timeline_view->state.timeline; | | | | < | | < < < < < | < | < < < < < < | 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 | case CTRL('k'): case '<': case ',': case 'K': if (s->timeline_view == NULL) break; tlstate = &s->timeline_view->state.timeline; previous_selection = tlstate->selected_commit; if ((rc = tl_input_handler(NULL, s->timeline_view, tl_down ? KEY_DOWN : KEY_UP))) break; if (previous_selection == tlstate->selected_commit) break; if ((rc = set_selected_commit(s, tlstate->selected_commit))) 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); 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->last_line_onscreen = s->first_line_onscreen + view->nlines; s->matched_line = 0; return rc; } static int request_tl_commits(struct fnc_view *view) { |
︙ | ︙ | |||
6250 6251 6252 6253 6254 6255 6256 | { 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; | | | 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 | { 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_commit = entry->commit; return 0; } static void diff_grep_init(struct fnc_view *view) { |
︙ | ︙ | |||
6272 6273 6274 6275 6276 6277 6278 | { FILE *f = NULL; off_t *line_offsets = NULL; ssize_t linelen; size_t nlines = 0, linesz = 0; int *first, *last, *match, *selected; int lineno; | < | | | 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 | { FILE *f = NULL; off_t *line_offsets = NULL; ssize_t linelen; size_t nlines = 0, linesz = 0; int *first, *last, *match, *selected; int lineno; char *line = NULL; first = last = match = selected = NULL; grep_set_view(view, &f, &line_offsets, &nlines, &first, &last, &match, &selected); if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (*match) { |
︙ | ︙ | |||
6317 6318 6319 6320 6321 6322 6323 | 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"); } | > | > > > | | < > | < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 | 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, and save to * view->line so we don't have to expand when drawing matches. */ linelen = getline(&line, &linesz, f); view->line.sz = expand_tab(view->line.buf, sizeof(view->line.buf), line, linelen); if (linelen != -1 && regexec(&view->regex, view->line.buf, 1, &view->regmatch, 0) == 0) { view->search_status = SEARCH_CONTINUE; *match = lineno; while (view->pos.col > view->regmatch.rm_so) --view->pos.col; /* Scroll till on-screen. */ break; } if (view->searching == SEARCH_FORWARD) ++lineno; else --lineno; } fsl_free(line); /* * 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) { 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; } 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; } } 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); 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 fnc_resizeterm(void) { struct winsize size; int cols, lines; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) { |
︙ | ︙ | |||
6625 6626 6627 6628 6629 6630 6631 | } static void usage_diff(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s diff [-C|--no-colour] [-R path] [-h|--help] " | < | | | | | 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 | } static void usage_diff(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s diff [-C|--no-colour] [-R path] [-h|--help] " "[-i|--invert] [-q|--quiet] [-w|--whitespace] [-x|--context n] " "[artifact1 [artifact2]] [path ...]\n " "e.g.: %s diff --context 3 d34db33f c0ff33 src/*.c\n\n", fcli_progname(), fcli_progname()); } static void usage_tree(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s tree [-C|--no-colour] [-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] [-n n] path\n" " e.g.: %s blame -c d34db33f src/foo.c\n\n" , fcli_progname(), fcli_progname()); } static void usage_branch(void) { |
︙ | ︙ | |||
6669 6670 6671 6672 6673 6674 6675 | static void usage_config(void) { fsl_fprintf(fnc_init.err ? stderr : stdout, " usage: %s config [-R path] [-h|--help] [--ls] " "[setting [value|--unset]]\n" | | | 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 | 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_DIFF_COMMIT blue\n\n" , fcli_progname(), fcli_progname()); } static int cmd_diff(fcli_command const *argv) { fsl_cx *const f = fcli_cx(); |
︙ | ︙ | |||
6831 6832 6833 6834 6835 6836 6837 | commit->type = fsl_strdup("blob"); commit->diff_type = diff_type; } rc = init_curses(); if (rc) goto end; | > | > < < | | | < | 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 | commit->type = fsl_strdup("blob"); commit->diff_type = diff_type; } rc = init_curses(); if (rc) goto end; #ifdef __OpenBSD__ rc = init_unveil(fsl_cx_db_file_repo(f, NULL), fsl_cx_ckout_dir_name(f, NULL), false); if (rc) goto end; #endif 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.ws, fnc_init.invert, !fnc_init.quiet, NULL, showmeta, &paths); if (!rc) rc = view_loop(view); end: fsl_free(path0); fsl_deck_finalize(&d); fsl_stmt_finalize(q); if (commit) |
︙ | ︙ | |||
6944 6945 6946 6947 6948 6949 6950 | fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; | > | > < < | | 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 | fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; #ifdef __OpenBSD__ rc = init_unveil(fsl_cx_db_file_repo(f, NULL), fsl_cx_ckout_dir_name(f, NULL), false); if (rc) goto end; #endif view = view_open(0, 0, 0, 0, FNC_VIEW_TREE); if (view == NULL) { RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } |
︙ | ︙ | |||
7477 7478 7479 7480 7481 7482 7483 | s->ndisplayed = 0; werase(view->window); if (limit == 0) return rc; /* Write (highlighted) headline (if view is active in splitscreen). */ | | > | 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 | 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, 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) |
︙ | ︙ | |||
7501 7502 7503 7504 7505 7506 7507 | wattroff(view->window, A_REVERSE); fsl_free(wcstr); wcstr = NULL; if (--limit <= 0) return rc; /* Write this (sub)tree's absolute repository path subheader. */ | | | | | 7285 7286 7287 7288 7289 7290 7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 | 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, 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_idx == 0) { wattr_on(view->window, A_REVERSE, NULL); s->selected_entry = NULL; } waddstr(view->window, " ..\n"); if (s->selected_idx == 0) wattr_off(view->window, A_REVERSE, NULL); ++s->ndisplayed; if (--limit <= 0) return rc; n = 1; } else { n = 0; |
︙ | ︙ | |||
7602 7603 7604 7605 7606 7607 7608 | *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"); | | | | | 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 | *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, 0, false); if (rc) { fsl_free(line); break; } if (n == s->selected_idx) { 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_idx) wattr_off(view->window, A_REVERSE, NULL); fsl_free(line); fsl_free(wcstr); wcstr = NULL; ++n; ++s->ndisplayed; s->last_entry_onscreen = te; |
︙ | ︙ | |||
7760 7761 7762 7763 7764 7765 7766 | *new_view = timeline_view; break; case 'g': if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: | | | | | | | | | | | | | | 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 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 | *new_view = timeline_view; break; case 'g': if (!fnc_home(view)) break; /* FALL THROUGH */ case KEY_HOME: s->selected_idx = 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_idx = 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_idx = n - 1; break; case KEY_DOWN: case 'j': if (s->selected_idx < s->ndisplayed - 1) { ++s->selected_idx; 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_idx > 0) { --s->selected_idx; 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_idx < s->ndisplayed - 1) s->selected_idx += MIN(nscroll, s->ndisplayed - s->selected_idx - 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_idx -= MIN(s->selected_idx, nscroll); } else { if (s->first_entry_onscreen == NULL) s->selected_idx -= MIN(s->selected_idx, nscroll); } tree_scroll_up(s, nscroll); break; case KEY_BACKSPACE: case KEY_ENTER: case KEY_LEFT: case KEY_RIGHT: |
︙ | ︙ | |||
7860 7861 7862 7863 7864 7865 7866 | 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; | | | | | | 7644 7645 7646 7647 7648 7649 7650 7651 7652 7653 7654 7655 7656 7657 7658 7659 7660 7661 7662 7663 7664 7665 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 7677 7678 7679 7680 | 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_idx = parent->selected_idx; if (s->selected_idx > 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_idx >= view->nlines - 3) s->selected_idx = view->nlines - 4; break; default: break; } return rc; } |
︙ | ︙ | |||
7906 7907 7908 7909 7910 7911 7912 | 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)) | | | | 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 | 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)) fnc_print_msg(view, "-- cannot blame binary file --", false, true, true); else rc = request_view(new_view, view, FNC_VIEW_BLAME); end: fsl_buffer_clear(&buf); return rc; } |
︙ | ︙ | |||
8003 8004 8005 8006 8007 8008 8009 | 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; | | | | 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 | 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_idx = s->selected_idx; TAILQ_INSERT_HEAD(&s->parents, parent, entry); s->tree = subtree; s->selected_idx = 0; s->first_entry_onscreen = NULL; return 0; } static int blame_tree_entry(struct fnc_view **new_view, int start_col, int start_ln, |
︙ | ︙ | |||
8033 8034 8035 8036 8037 8038 8039 | 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; } | | | 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 | 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); if (rc) view_close(blame_view); else *new_view = blame_view; end: fsl_free(path); return rc; |
︙ | ︙ | |||
8116 8117 8118 8119 8120 8121 8122 | 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) | | | | 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 | 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 = idx - (parent ? - 1 : s->first_entry_onscreen->idx); else { s->first_entry_onscreen = s->matched_entry; s->selected_idx = 0; } } return rc; } static int |
︙ | ︙ | |||
8191 8192 8193 8194 8195 8196 8197 | } 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) | | | > > > > | > < | < | 7975 7976 7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989 7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038 8039 | } 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_repository_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_repository_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; #ifdef __OpenBSD__ const fsl_cx *const f = fcli_cx(); fsl_buffer buf = fsl_buffer_empty; fsl_file_dirpart(fsl_cx_db_file_repo(f, NULL), -1, &buf, false); rc = init_unveil(fsl_buffer_cstr(&buf), fsl_cx_ckout_dir_name(f, NULL), true); if (rc) return rc; #endif 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) { |
︙ | ︙ | |||
8375 8376 8377 8378 8379 8380 8381 | 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} )", | | < < < < < < | | | | | | | < | 8162 8163 8164 8165 8166 8167 8168 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 | 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:", "^-", "^\\+", "^@@" }; const int pairs_diff[][2] = { {FNC_COLOUR_DIFF_META, init_colour(FNC_COLOUR_DIFF_META)}, {FNC_COLOUR_USER, init_colour(FNC_COLOUR_USER)}, {FNC_COLOUR_DATE, init_colour(FNC_COLOUR_DATE)}, {FNC_COLOUR_DIFF_TAGS, init_colour(FNC_COLOUR_DIFF_TAGS)}, {FNC_COLOUR_DIFF_MINUS, init_colour(FNC_COLOUR_DIFF_MINUS)}, {FNC_COLOUR_DIFF_PLUS, init_colour(FNC_COLOUR_DIFF_PLUS)}, {FNC_COLOUR_DIFF_CHUNK, init_colour(FNC_COLOUR_DIFF_CHUNK)} }; rc = set_colour_scheme(s, pairs_diff, regexp_diff, nitems(regexp_diff)); break; } case FNC_VIEW_TREE: { static const char *regexp_tree[] = {"@ ->", "/$", "\\*$", "^$"}; |
︙ | ︙ | |||
8588 8589 8590 8591 8592 8593 8594 | 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; | < < | 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) |
︙ | ︙ | |||
8750 8751 8752 8753 8754 8755 8756 | "%s blame requires versioned file path", fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; | > | > < < | | < | | 8528 8529 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 | "%s blame requires versioned file path", fcli_progname()); goto end; } rc = init_curses(); if (rc) goto end; #ifdef __OpenBSD__ rc = init_unveil(fsl_cx_db_file_repo(f, NULL), fsl_cx_ckout_dir_name(f, NULL), false); if (rc) goto end; #endif view = view_open(0, 0, 0, 0, FNC_VIEW_BLAME); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", view_open); goto end; } rc = open_blame_view(view, path, commit_id, tip, nlimit); if (rc) goto end; rc = view_loop(view); end: fsl_free(path); fsl_free(commit_id); return rc; } static int open_blame_view(struct fnc_view *view, char *path, fsl_uuid_str commit_id, fsl_id_t tip, int nlimit) { struct fnc_blame_view_state *s = &view->state.blame; int rc = 0; CONCAT(STAILQ, _INIT)(&s->blamed_commits); s->path = fsl_strdup(path); |
︙ | ︙ | |||
8805 8806 8807 8808 8809 8810 8811 | 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(); | < | 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; } |
︙ | ︙ | |||
8902 8903 8904 8905 8906 8907 8908 | goto end; } /* Don't include EOF \n in blame line count. */ if (blame->line_offsets[blame->nlines - 1] == blame->filesz) --blame->nlines; | < < < < < < < < | 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 8691 | 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; } master = fsl_config_get_text(f, FSL_CONFDB_REPO, "main-branch", NULL); |
︙ | ︙ | |||
9184 9185 9186 9187 9188 9189 9190 | 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; | | > | | 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979 8980 8981 | 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 width, lineno = 0, nprinted = 0; int rc = FSL_RC_OK; int npad = 0; 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, 0, false); fsl_free(line); line = NULL; if (rc) return rc; if (screen_is_shared(view)) wattron(view->window, A_REVERSE); if (s->colour) |
︙ | ︙ | |||
9220 9221 9222 9223 9224 9225 9226 9227 | } 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; | > > | > | | < | 8989 8990 8991 8992 8993 8994 8995 8996 8997 8998 8999 9000 9001 9002 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 | } 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; if (width < view->ncols - 1) waddch(view->window, '\n'); line = fsl_mprintf("[%d/%d] %s%s%s %c", s->gtl ? s->gtl : MIN(blame->nlines, s->first_line_onscreen - 1 + s->selected_line), blame->nlines, s->blame_complete ? "" : "annotating... ", fnc_init.sym ? "/" : "", 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, 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) { attr_t rx = 0; linelen = getline(&line, &linesz, blame->f); if (linelen == -1) { if (feof(blame->f)) { s->eof = true; break; } |
︙ | ︙ | |||
9296 9297 9298 9299 9300 9301 9302 | fsl_free(id_str); prev_id = blame_line->id; } else { waddstr(view->window, ".........."); prev_id = NULL; } if (s->showln) | | < > | | > > > > > | | < | | > < < < | < < | | 9067 9068 9069 9070 9071 9072 9073 9074 9075 9076 9077 9078 9079 9080 9081 9082 9083 9084 9085 9086 9087 9088 9089 9090 9091 9092 9093 9094 9095 9096 9097 9098 9099 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 9110 9111 9112 9113 9114 9115 9116 9117 9118 9119 9120 9121 | fsl_free(id_str); prev_id = blame_line->id; } else { waddstr(view->window, ".........."); prev_id = NULL; } if (s->showln) npad = draw_lineno(view, blame->nlines, blame_line->lineno, rx); } else { waddstr(view->window, ".........."); prev_id = NULL; } if (selected) wattroff(view->window, rx); waddch(view->window, ' '); if (view->ncols <= idfield) { width = 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, &width, idfield + npad, regmatch, 0); if (rc) { fsl_free(line); return rc; } width += idfield; } else { rc = formatln(&wcstr, &width, line, view->ncols - idfield - npad, idfield + npad, view->pos.col, true); waddwstr(view->window, wcstr); fsl_free(wcstr); wcstr = NULL; width += idfield; } if (width + npad <= view->ncols - 1) waddch(view->window, '\n'); if (++nprinted == 1) s->first_line_onscreen = lineno; } fsl_free(line); s->last_line_onscreen = lineno; |
︙ | ︙ | |||
9440 9441 9442 9443 9444 9445 9446 | break; case KEY_LEFT: case 'h': view->pos.col -= MIN(view->pos.col, 2); break; case 'q': s->done = true; | | | | 9211 9212 9213 9214 9215 9216 9217 9218 9219 9220 9221 9222 9223 9224 9225 9226 | break; case KEY_LEFT: case 'h': view->pos.col -= MIN(view->pos.col, 2); break; case 'q': s->done = true; if (s->selected_commit) fnc_commit_artifact_close(s->selected_commit); break; case 'c': s->colour = !s->colour; break; case 'g': if (!fnc_home(view)) break; |
︙ | ︙ | |||
9514 9515 9516 9517 9518 9519 9520 | break; } if (s->first_line_onscreen > nscroll) s->first_line_onscreen -= nscroll; else s->first_line_onscreen = 1; break; | | | | 9285 9286 9287 9288 9289 9290 9291 9292 9293 9294 9295 9296 9297 9298 9299 9300 9301 | break; } if (s->first_line_onscreen > nscroll) s->first_line_onscreen -= nscroll; else s->first_line_onscreen = 1; break; case 'L': { struct input input = {(int []){1, s->blame.nlines}, "line: ", INPUT_NUMERIC, true}; rc = fnc_prompt_input(view, &input); s->gtl = input.ret; break; } case '#': s->showln = !s->showln; break; |
︙ | ︙ | |||
9561 9562 9563 9564 9565 9566 9567 | 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"); | | < | 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343 9344 9345 9346 | 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"); fnc_print_msg(view, m, true, true, true); fsl_deck_finalize(&d); fsl_free(pid); fsl_free(m); break; } rc = fnc_commit_qid_alloc(&s->blamed_commit, pid); if (rc) |
︙ | ︙ | |||
9644 9645 9646 9647 9648 9649 9650 | 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; | | | | | | | 9414 9415 9416 9417 9418 9419 9420 9421 9422 9423 9424 9425 9426 9427 9428 9429 9430 9431 9432 9433 9434 9435 9436 9437 9438 9439 9440 9441 9442 9443 9444 9445 9446 9447 9448 9449 9450 | fsl_stmt *q = NULL; fsl_uuid_cstr id = NULL; id = get_selected_commit_id(s->blame.lines, s->blame.nlines, s->first_line_onscreen, s->selected_line); if (id == NULL) break; if (s->selected_commit) fnc_commit_artifact_close(s->selected_commit); if (rc) break; q = fsl_stmt_malloc(); rc = commit_builder(&commit, fsl_uuid_to_rid(f, id), q); fsl_stmt_finalize(q); if (rc) { fnc_commit_artifact_close(commit); break; } if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); diff_view = view_open(0, 0, 0, start_col, FNC_VIEW_DIFF); if (diff_view == NULL) { fnc_commit_artifact_close(commit); rc = RC(FSL_RC_ERROR, "%s", "view_open"); break; } rc = open_diff_view(diff_view, commit, DEF_DIFF_CTX, fnc_init.ws, fnc_init.invert, !fnc_init.quiet, NULL, true, NULL); s->selected_commit = commit; if (rc) { fnc_commit_artifact_close(commit); view_close(diff_view); break; } view->active = false; diff_view->active = true; |
︙ | ︙ | |||
9887 9888 9889 9890 9891 9892 9893 | return rc; } glob = fsl_strdup(fcli_next_arg(true)); rc = init_curses(); if (rc) goto end; | > > | > < < | | 9657 9658 9659 9660 9661 9662 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 | return rc; } glob = fsl_strdup(fcli_next_arg(true)); rc = init_curses(); if (rc) goto end; #ifdef __OpenBSD__ const fsl_cx *const f = fcli_cx(); rc = init_unveil(fsl_cx_db_file_repo(f, NULL), fsl_cx_ckout_dir_name(f, NULL), false); if (rc) goto end; #endif view = view_open(0, 0, 0, 0, FNC_VIEW_BRANCH); if (view == NULL) { rc = RC(FSL_RC_ERROR, "%s", "view_open"); goto end; } |
︙ | ︙ | |||
9915 9916 9917 9918 9919 9920 9921 | 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; | | | 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 9700 | static int open_branch_view(struct fnc_view *view, int branch_flags, const char *glob, double dateline, int when) { struct fnc_branch_view_state *s = &view->state.branch; int rc = 0; s->selected_branch = 0; s->colour = !fnc_init.nocolour && has_colors(); s->branch_flags = branch_flags; s->branch_glob = glob; s->dateline = dateline; s->when = when; rc = fnc_load_branches(s); |
︙ | ︙ | |||
10195 10196 10197 10198 10199 10200 10201 | 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"); | | | 9966 9967 9968 9969 9970 9971 9972 9973 9974 9975 9976 9977 9978 9979 9980 | 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, 0, false); if (rc) { fsl_free(line); return rc; } if (screen_is_shared(view)) wattron(view->window, A_REVERSE); waddwstr(view->window, wline); |
︙ | ︙ | |||
10235 10236 10237 10238 10239 10240 10241 | 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); | | | | 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018 10019 10020 10021 10022 10023 10024 10025 10026 10027 10028 10029 | 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, 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_branch = 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) |
︙ | ︙ | |||
10292 10293 10294 10295 10296 10297 10298 | break; case 'i': s->show_id = !s->show_id; break; case KEY_ENTER: case '\r': case ' ': | | | | | 10063 10064 10065 10066 10067 10068 10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 10090 10091 10092 10093 10094 10095 10096 10097 10098 10099 10100 10101 10102 | break; case 'i': s->show_id = !s->show_id; break; case KEY_ENTER: case '\r': case ' ': if (!s->selected_branch) 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_branch) break; if (view_is_parent(view)) start_col = view_split_start_col(view->start_col); rc = browse_branch_tree(&tree_view, start_col, s->selected_branch); if (rc || tree_view == NULL) break; view->active = false; tree_view->active = true; if (view_is_parent(view)) { rc = view_close_child(view); if (rc) |
︙ | ︙ | |||
10500 10501 10502 10503 10504 10505 10506 | if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (s->matched_branch) { if (view->searching == SEARCH_FORWARD) { | | | | | | | 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 | if (view->searching == SEARCH_DONE) { view->search_status = SEARCH_CONTINUE; return 0; } if (s->matched_branch) { if (view->searching == SEARCH_FORWARD) { if (s->selected_branch) be = TAILQ_NEXT(s->selected_branch, entries); else be = TAILQ_PREV(s->selected_branch, fnc_branchlist_head, entries); } else { if (s->selected_branch == NULL) be = TAILQ_LAST(&s->branches, fnc_branchlist_head); else be = TAILQ_PREV(s->selected_branch, fnc_branchlist_head, entries); } } else { if (view->searching == SEARCH_FORWARD) be = TAILQ_FIRST(&s->branches); else be = TAILQ_LAST(&s->branches, fnc_branchlist_head); |
︙ | ︙ | |||
10720 10721 10722 10723 10724 10725 10726 | 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; | | < < < < | | > | | > | > | | | | | > > | | > | | | < < < < | 10491 10492 10493 10494 10495 10496 10497 10498 10499 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 10525 10526 10527 10528 10529 10530 10531 10532 10533 10534 10535 10536 10537 10538 10539 10540 10541 10542 10543 10544 10545 10546 10547 10548 10549 10550 10551 10552 10553 10554 10555 10556 10557 10558 10559 10560 10561 10562 10563 10564 10565 10566 10567 10568 10569 10570 10571 10572 10573 10574 10575 10576 10577 10578 10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 | 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; int n; ptr = NULL; errno = 0; n = strtonum(nstr, min, max, &ptr); if (errno == ERANGE) return RC(FSL_RC_RANGE, "<n> out of range: -n|--limit=%s [%s]", nstr, ptr); else if (errno != 0 || errno == EINVAL) return RC(FSL_RC_MISUSE, "<n> not a number: -n|--limit=%s [%s]", nstr, ptr); else if (ptr && *ptr != '\0') return RC(FSL_RC_MISUSE, "invalid char in <n>: -n|--limit=%s [%s]", nstr, ptr); *ret = n; return FSL_RC_OK; } static int fnc_prompt_input(struct fnc_view *view, struct input *input) { int rc = FSL_RC_OK; if (input->prompt) fnc_print_msg(view, input->prompt, input->clear, false, false); 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) fnc_print_msg(view, "-- numeric input only --", true, true, true); else if (rc == FSL_RC_RANGE || n < min || n > max) fnc_print_msg(view, "-- line outside file range --", true, true, true); else input->ret = n; rc = FSL_RC_OK; fcli_err_reset(); } 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 void fnc_print_msg(struct fnc_view *view, const char *msg, bool clear, bool update, bool zzz) { wattr_on(view->window, A_BOLD, NULL); mvwaddstr(view->window, view->nlines - 1, 0, msg); if (clear) wclrtoeol(view->window); wattr_off(view->window, A_BOLD, NULL); if (update) { update_panels(); doupdate(); } if (zzz) sleep(1); } /* * 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 |
︙ | ︙ | |||
10915 10916 10917 10918 10919 10920 10921 | if (*glob == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); } return 0; } | < < < < < < | < < < < < < < < < < < < < < < | < < < | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 10697 10698 10699 10700 10701 10702 10703 10704 10705 10706 10707 10708 10709 10710 10711 10712 10713 10714 10715 10716 10717 10718 10719 10720 10721 10722 10723 | if (*glob == NULL) return RC(FSL_RC_ERROR, "%s", "fsl_mprintf"); } return 0; } #ifdef __OpenBSD__ /* * 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 dir, and /tmp. */ static int init_unveil(const char *repodb, const char *ckoutdir, bool cfg) { /* w 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); /* w .fslckout for fsl_ckout_changes_scan() in cmd_diff(). */ if (unveil(ckoutdir, "rw") == -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"); return FSL_RC_OK; } #endif |