Fossil SCM
Enhance the "fossil grep" command with new options. Work in progress. Needs more testing.
Commit
1bf2f84843b063c61afc7f3ba95313dc5035d69d57dcad1266c4811fe1167b01
Parent
c06e0b2d0a201ae…
1 file changed
+99
-30
+99
-30
| --- src/regexp.c | ||
| +++ src/regexp.c | ||
| @@ -719,10 +719,11 @@ | ||
| 719 | 719 | |
| 720 | 720 | /* |
| 721 | 721 | ** Flags for grep_buffer() |
| 722 | 722 | */ |
| 723 | 723 | #define GREP_EXISTS 0x001 /* If any match, print only the name and stop */ |
| 724 | +#define GREP_QUIET 0x002 /* Return code only */ | |
| 724 | 725 | |
| 725 | 726 | /* |
| 726 | 727 | ** Run a "grep" over a text file |
| 727 | 728 | */ |
| 728 | 729 | static int grep_buffer( |
| @@ -737,14 +738,16 @@ | ||
| 737 | 738 | n = j - i; |
| 738 | 739 | ln++; |
| 739 | 740 | if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){ |
| 740 | 741 | cnt++; |
| 741 | 742 | if( flags & GREP_EXISTS ){ |
| 742 | - fossil_print("%S\n", zName); | |
| 743 | + if( (flags & GREP_QUIET)==0 ) fossil_print("%S\n", zName); | |
| 743 | 744 | break; |
| 744 | 745 | } |
| 745 | - fossil_print("%S:%d:%.*s\n", zName, ln, n, z+i); | |
| 746 | + if( (flags & GREP_QUIET)==0 ){ | |
| 747 | + fossil_print("%S:%d:%.*s\n", zName, ln, n, z+i); | |
| 748 | + } | |
| 746 | 749 | } |
| 747 | 750 | } |
| 748 | 751 | return cnt; |
| 749 | 752 | } |
| 750 | 753 | |
| @@ -787,61 +790,127 @@ | ||
| 787 | 790 | } |
| 788 | 791 | |
| 789 | 792 | /* |
| 790 | 793 | ** COMMAND: grep |
| 791 | 794 | ** |
| 792 | -** Usage: %fossil grep [OPTIONS] PATTERN FILENAME | |
| 795 | +** Usage: %fossil grep [OPTIONS] PATTERN FILENAME ... | |
| 793 | 796 | ** |
| 794 | 797 | ** Attempt to match the given POSIX extended regular expression PATTERN |
| 795 | 798 | ** over all historic versions of FILENAME. For details of the supported |
| 796 | 799 | ** RE dialect, see https://fossil-scm.org/fossil/doc/trunk/www/grep.md |
| 797 | 800 | ** |
| 798 | 801 | ** Options: |
| 799 | 802 | ** |
| 800 | -** -i|--ignore-case Ignore case | |
| 801 | -** -l|--files-with-matches List only checkin ID for versions that match | |
| 802 | -** -v|--verbose Show each file as it is analyzed | |
| 803 | +** -c|--count Suppress normal output; instead print a count | |
| 804 | +** of the number of matching files | |
| 805 | +** -H|--checkin-hash Show the check-in hash rather than | |
| 806 | +** file artifact hash for each match | |
| 807 | +** -i|--ignore-case Ignore case | |
| 808 | +** -l|--files-with-matches List only hash for each match | |
| 809 | +** --once Stop searching after the first match | |
| 810 | +** -s|--no-messages Suppress error messages about nonexistant | |
| 811 | +** or unreadable files | |
| 812 | +** -v|--invert-match Invert the sense of matching. Show only | |
| 813 | +** files that have no matches. Implies -l | |
| 814 | +** --verbose Show each file as it is analyzed | |
| 803 | 815 | */ |
| 804 | 816 | void re_grep_cmd(void){ |
| 805 | 817 | u32 flags = 0; |
| 806 | 818 | int bVerbose = 0; |
| 807 | 819 | ReCompiled *pRe; |
| 808 | 820 | const char *zErr; |
| 809 | 821 | int ignoreCase = 0; |
| 810 | 822 | Blob fullName; |
| 823 | + int ckinHash = 0; | |
| 824 | + int ii; | |
| 825 | + int nMatch = 0; | |
| 826 | + int bNoMsg; | |
| 827 | + int cntFlag; | |
| 828 | + int bOnce; | |
| 829 | + int bInvert; | |
| 830 | + int nSearch = 0; | |
| 811 | 831 | |
| 812 | 832 | if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1; |
| 813 | 833 | if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS; |
| 814 | - if( find_option("verbose","v",0)!=0 ) bVerbose = 1; | |
| 834 | + if( find_option("verbose",0,0)!=0 ) bVerbose = 1; | |
| 835 | + ckinHash = find_option("checkin-hash","H",0)!=0; | |
| 836 | + if( find_option("quiet","q",0) ) flags |= GREP_QUIET|GREP_EXISTS; | |
| 837 | + bNoMsg = find_option("no-messages","s",0)!=0; | |
| 838 | + bOnce = find_option("once",0,0)!=0; | |
| 839 | + bInvert = find_option("invert-match","v",0)!=0; | |
| 840 | + if( bInvert ){ | |
| 841 | + flags |= GREP_QUIET|GREP_EXISTS; | |
| 842 | + } | |
| 843 | + cntFlag = find_option("count","c",0)!=0; | |
| 844 | + if( cntFlag ){ | |
| 845 | + flags |= GREP_QUIET|GREP_EXISTS; | |
| 846 | + } | |
| 815 | 847 | db_find_and_open_repository(0, 0); |
| 816 | 848 | verify_all_options(); |
| 817 | 849 | if( g.argc<4 ){ |
| 818 | - usage("REGEXP FILENAME"); | |
| 850 | + usage("REGEXP FILENAME ..."); | |
| 819 | 851 | } |
| 820 | 852 | zErr = re_compile(&pRe, g.argv[2], ignoreCase); |
| 821 | 853 | if( zErr ) fossil_fatal("%s", zErr); |
| 822 | 854 | |
| 823 | - if( file_tree_name(g.argv[3], &fullName, 0, 0) ){ | |
| 824 | - int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", | |
| 825 | - blob_str(&fullName)); | |
| 826 | - if( fnid ){ | |
| 827 | - Stmt q; | |
| 828 | - add_content_sql_commands(g.db); | |
| 829 | - db_prepare(&q, | |
| 830 | - "SELECT content(ux), ux FROM (" | |
| 831 | - " SELECT blob.uuid AS ux, min(event.mtime) AS mx" | |
| 832 | - " FROM mlink, blob, event" | |
| 833 | - " WHERE mlink.mid=event.objid" | |
| 834 | - " AND mlink.fid=blob.rid" | |
| 835 | - " AND mlink.fnid=%d" | |
| 836 | - " GROUP BY blob.uuid" | |
| 837 | - ") ORDER BY mx DESC;", | |
| 838 | - fnid | |
| 839 | - ); | |
| 840 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 841 | - if( bVerbose ) fossil_print("%S:\n", db_column_text(&q,1)); | |
| 842 | - grep_buffer(pRe, db_column_text(&q,1), db_column_text(&q,0), flags); | |
| 843 | - } | |
| 844 | - db_finalize(&q); | |
| 855 | + add_content_sql_commands(g.db); | |
| 856 | + for(ii=3; ii<g.argc; ii++){ | |
| 857 | + const char *zTarget = g.argv[ii]; | |
| 858 | + if( nMatch ){ | |
| 859 | + if( (flags & GREP_QUIET)!=0 ) break; | |
| 860 | + if( bOnce ) break; | |
| 861 | + } | |
| 862 | + if( file_tree_name(zTarget, &fullName, 0, 1) ){ | |
| 863 | + int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", | |
| 864 | + blob_str(&fullName)); | |
| 865 | + if( !fnid ){ | |
| 866 | + if( bNoMsg ) continue; | |
| 867 | + if( file_size(zTarget, ExtFILE)<0 ){ | |
| 868 | + fossil_fatal("no such file: %s", zTarget); | |
| 869 | + } | |
| 870 | + fossil_fatal("not a managed file: %s", zTarget); | |
| 871 | + }else{ | |
| 872 | + Stmt q; | |
| 873 | + db_prepare(&q, | |
| 874 | + "SELECT content(ux), %w FROM (" | |
| 875 | + " SELECT A.uuid AS ux, B.uuid AS ckin, min(event.mtime) AS mx" | |
| 876 | + " FROM mlink, blob A, blob B, event" | |
| 877 | + " WHERE mlink.mid=event.objid" | |
| 878 | + " AND mlink.fid=A.rid" | |
| 879 | + " AND mlink.mid=B.rid" | |
| 880 | + " AND mlink.fnid=%d" | |
| 881 | + " GROUP BY A.uuid" | |
| 882 | + ") ORDER BY mx DESC;", | |
| 883 | + ckinHash ? "ckin" : "ux", | |
| 884 | + fnid | |
| 885 | + ); | |
| 886 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 887 | + const char *zHash = db_column_text(&q,1); | |
| 888 | + const char *zContent = db_column_text(&q,0); | |
| 889 | + if( bVerbose ) fossil_print("%S:\n", zHash); | |
| 890 | + nSearch++; | |
| 891 | + nMatch += grep_buffer(pRe, zHash, zContent, flags); | |
| 892 | + if( bInvert && cntFlag==0 ){ | |
| 893 | + if( nMatch==0 ){ | |
| 894 | + fossil_print("%S\n", zHash); | |
| 895 | + if( bOnce ) nMatch = 1; | |
| 896 | + }else{ | |
| 897 | + nMatch = 0; | |
| 898 | + } | |
| 899 | + } | |
| 900 | + if( nMatch ){ | |
| 901 | + if( (flags & GREP_QUIET)!=0 ) break; | |
| 902 | + if( bOnce ) break; | |
| 903 | + } | |
| 904 | + } | |
| 905 | + db_finalize(&q); | |
| 906 | + } | |
| 907 | + } | |
| 908 | + } | |
| 909 | + if( cntFlag ){ | |
| 910 | + if( bInvert ){ | |
| 911 | + fossil_print("%d\n", nSearch-nMatch); | |
| 912 | + }else{ | |
| 913 | + fossil_print("%d\n", nMatch); | |
| 845 | 914 | } |
| 846 | 915 | } |
| 847 | 916 | } |
| 848 | 917 |
| --- src/regexp.c | |
| +++ src/regexp.c | |
| @@ -719,10 +719,11 @@ | |
| 719 | |
| 720 | /* |
| 721 | ** Flags for grep_buffer() |
| 722 | */ |
| 723 | #define GREP_EXISTS 0x001 /* If any match, print only the name and stop */ |
| 724 | |
| 725 | /* |
| 726 | ** Run a "grep" over a text file |
| 727 | */ |
| 728 | static int grep_buffer( |
| @@ -737,14 +738,16 @@ | |
| 737 | n = j - i; |
| 738 | ln++; |
| 739 | if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){ |
| 740 | cnt++; |
| 741 | if( flags & GREP_EXISTS ){ |
| 742 | fossil_print("%S\n", zName); |
| 743 | break; |
| 744 | } |
| 745 | fossil_print("%S:%d:%.*s\n", zName, ln, n, z+i); |
| 746 | } |
| 747 | } |
| 748 | return cnt; |
| 749 | } |
| 750 | |
| @@ -787,61 +790,127 @@ | |
| 787 | } |
| 788 | |
| 789 | /* |
| 790 | ** COMMAND: grep |
| 791 | ** |
| 792 | ** Usage: %fossil grep [OPTIONS] PATTERN FILENAME |
| 793 | ** |
| 794 | ** Attempt to match the given POSIX extended regular expression PATTERN |
| 795 | ** over all historic versions of FILENAME. For details of the supported |
| 796 | ** RE dialect, see https://fossil-scm.org/fossil/doc/trunk/www/grep.md |
| 797 | ** |
| 798 | ** Options: |
| 799 | ** |
| 800 | ** -i|--ignore-case Ignore case |
| 801 | ** -l|--files-with-matches List only checkin ID for versions that match |
| 802 | ** -v|--verbose Show each file as it is analyzed |
| 803 | */ |
| 804 | void re_grep_cmd(void){ |
| 805 | u32 flags = 0; |
| 806 | int bVerbose = 0; |
| 807 | ReCompiled *pRe; |
| 808 | const char *zErr; |
| 809 | int ignoreCase = 0; |
| 810 | Blob fullName; |
| 811 | |
| 812 | if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1; |
| 813 | if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS; |
| 814 | if( find_option("verbose","v",0)!=0 ) bVerbose = 1; |
| 815 | db_find_and_open_repository(0, 0); |
| 816 | verify_all_options(); |
| 817 | if( g.argc<4 ){ |
| 818 | usage("REGEXP FILENAME"); |
| 819 | } |
| 820 | zErr = re_compile(&pRe, g.argv[2], ignoreCase); |
| 821 | if( zErr ) fossil_fatal("%s", zErr); |
| 822 | |
| 823 | if( file_tree_name(g.argv[3], &fullName, 0, 0) ){ |
| 824 | int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", |
| 825 | blob_str(&fullName)); |
| 826 | if( fnid ){ |
| 827 | Stmt q; |
| 828 | add_content_sql_commands(g.db); |
| 829 | db_prepare(&q, |
| 830 | "SELECT content(ux), ux FROM (" |
| 831 | " SELECT blob.uuid AS ux, min(event.mtime) AS mx" |
| 832 | " FROM mlink, blob, event" |
| 833 | " WHERE mlink.mid=event.objid" |
| 834 | " AND mlink.fid=blob.rid" |
| 835 | " AND mlink.fnid=%d" |
| 836 | " GROUP BY blob.uuid" |
| 837 | ") ORDER BY mx DESC;", |
| 838 | fnid |
| 839 | ); |
| 840 | while( db_step(&q)==SQLITE_ROW ){ |
| 841 | if( bVerbose ) fossil_print("%S:\n", db_column_text(&q,1)); |
| 842 | grep_buffer(pRe, db_column_text(&q,1), db_column_text(&q,0), flags); |
| 843 | } |
| 844 | db_finalize(&q); |
| 845 | } |
| 846 | } |
| 847 | } |
| 848 |
| --- src/regexp.c | |
| +++ src/regexp.c | |
| @@ -719,10 +719,11 @@ | |
| 719 | |
| 720 | /* |
| 721 | ** Flags for grep_buffer() |
| 722 | */ |
| 723 | #define GREP_EXISTS 0x001 /* If any match, print only the name and stop */ |
| 724 | #define GREP_QUIET 0x002 /* Return code only */ |
| 725 | |
| 726 | /* |
| 727 | ** Run a "grep" over a text file |
| 728 | */ |
| 729 | static int grep_buffer( |
| @@ -737,14 +738,16 @@ | |
| 738 | n = j - i; |
| 739 | ln++; |
| 740 | if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){ |
| 741 | cnt++; |
| 742 | if( flags & GREP_EXISTS ){ |
| 743 | if( (flags & GREP_QUIET)==0 ) fossil_print("%S\n", zName); |
| 744 | break; |
| 745 | } |
| 746 | if( (flags & GREP_QUIET)==0 ){ |
| 747 | fossil_print("%S:%d:%.*s\n", zName, ln, n, z+i); |
| 748 | } |
| 749 | } |
| 750 | } |
| 751 | return cnt; |
| 752 | } |
| 753 | |
| @@ -787,61 +790,127 @@ | |
| 790 | } |
| 791 | |
| 792 | /* |
| 793 | ** COMMAND: grep |
| 794 | ** |
| 795 | ** Usage: %fossil grep [OPTIONS] PATTERN FILENAME ... |
| 796 | ** |
| 797 | ** Attempt to match the given POSIX extended regular expression PATTERN |
| 798 | ** over all historic versions of FILENAME. For details of the supported |
| 799 | ** RE dialect, see https://fossil-scm.org/fossil/doc/trunk/www/grep.md |
| 800 | ** |
| 801 | ** Options: |
| 802 | ** |
| 803 | ** -c|--count Suppress normal output; instead print a count |
| 804 | ** of the number of matching files |
| 805 | ** -H|--checkin-hash Show the check-in hash rather than |
| 806 | ** file artifact hash for each match |
| 807 | ** -i|--ignore-case Ignore case |
| 808 | ** -l|--files-with-matches List only hash for each match |
| 809 | ** --once Stop searching after the first match |
| 810 | ** -s|--no-messages Suppress error messages about nonexistant |
| 811 | ** or unreadable files |
| 812 | ** -v|--invert-match Invert the sense of matching. Show only |
| 813 | ** files that have no matches. Implies -l |
| 814 | ** --verbose Show each file as it is analyzed |
| 815 | */ |
| 816 | void re_grep_cmd(void){ |
| 817 | u32 flags = 0; |
| 818 | int bVerbose = 0; |
| 819 | ReCompiled *pRe; |
| 820 | const char *zErr; |
| 821 | int ignoreCase = 0; |
| 822 | Blob fullName; |
| 823 | int ckinHash = 0; |
| 824 | int ii; |
| 825 | int nMatch = 0; |
| 826 | int bNoMsg; |
| 827 | int cntFlag; |
| 828 | int bOnce; |
| 829 | int bInvert; |
| 830 | int nSearch = 0; |
| 831 | |
| 832 | if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1; |
| 833 | if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS; |
| 834 | if( find_option("verbose",0,0)!=0 ) bVerbose = 1; |
| 835 | ckinHash = find_option("checkin-hash","H",0)!=0; |
| 836 | if( find_option("quiet","q",0) ) flags |= GREP_QUIET|GREP_EXISTS; |
| 837 | bNoMsg = find_option("no-messages","s",0)!=0; |
| 838 | bOnce = find_option("once",0,0)!=0; |
| 839 | bInvert = find_option("invert-match","v",0)!=0; |
| 840 | if( bInvert ){ |
| 841 | flags |= GREP_QUIET|GREP_EXISTS; |
| 842 | } |
| 843 | cntFlag = find_option("count","c",0)!=0; |
| 844 | if( cntFlag ){ |
| 845 | flags |= GREP_QUIET|GREP_EXISTS; |
| 846 | } |
| 847 | db_find_and_open_repository(0, 0); |
| 848 | verify_all_options(); |
| 849 | if( g.argc<4 ){ |
| 850 | usage("REGEXP FILENAME ..."); |
| 851 | } |
| 852 | zErr = re_compile(&pRe, g.argv[2], ignoreCase); |
| 853 | if( zErr ) fossil_fatal("%s", zErr); |
| 854 | |
| 855 | add_content_sql_commands(g.db); |
| 856 | for(ii=3; ii<g.argc; ii++){ |
| 857 | const char *zTarget = g.argv[ii]; |
| 858 | if( nMatch ){ |
| 859 | if( (flags & GREP_QUIET)!=0 ) break; |
| 860 | if( bOnce ) break; |
| 861 | } |
| 862 | if( file_tree_name(zTarget, &fullName, 0, 1) ){ |
| 863 | int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", |
| 864 | blob_str(&fullName)); |
| 865 | if( !fnid ){ |
| 866 | if( bNoMsg ) continue; |
| 867 | if( file_size(zTarget, ExtFILE)<0 ){ |
| 868 | fossil_fatal("no such file: %s", zTarget); |
| 869 | } |
| 870 | fossil_fatal("not a managed file: %s", zTarget); |
| 871 | }else{ |
| 872 | Stmt q; |
| 873 | db_prepare(&q, |
| 874 | "SELECT content(ux), %w FROM (" |
| 875 | " SELECT A.uuid AS ux, B.uuid AS ckin, min(event.mtime) AS mx" |
| 876 | " FROM mlink, blob A, blob B, event" |
| 877 | " WHERE mlink.mid=event.objid" |
| 878 | " AND mlink.fid=A.rid" |
| 879 | " AND mlink.mid=B.rid" |
| 880 | " AND mlink.fnid=%d" |
| 881 | " GROUP BY A.uuid" |
| 882 | ") ORDER BY mx DESC;", |
| 883 | ckinHash ? "ckin" : "ux", |
| 884 | fnid |
| 885 | ); |
| 886 | while( db_step(&q)==SQLITE_ROW ){ |
| 887 | const char *zHash = db_column_text(&q,1); |
| 888 | const char *zContent = db_column_text(&q,0); |
| 889 | if( bVerbose ) fossil_print("%S:\n", zHash); |
| 890 | nSearch++; |
| 891 | nMatch += grep_buffer(pRe, zHash, zContent, flags); |
| 892 | if( bInvert && cntFlag==0 ){ |
| 893 | if( nMatch==0 ){ |
| 894 | fossil_print("%S\n", zHash); |
| 895 | if( bOnce ) nMatch = 1; |
| 896 | }else{ |
| 897 | nMatch = 0; |
| 898 | } |
| 899 | } |
| 900 | if( nMatch ){ |
| 901 | if( (flags & GREP_QUIET)!=0 ) break; |
| 902 | if( bOnce ) break; |
| 903 | } |
| 904 | } |
| 905 | db_finalize(&q); |
| 906 | } |
| 907 | } |
| 908 | } |
| 909 | if( cntFlag ){ |
| 910 | if( bInvert ){ |
| 911 | fossil_print("%d\n", nSearch-nMatch); |
| 912 | }else{ |
| 913 | fossil_print("%d\n", nMatch); |
| 914 | } |
| 915 | } |
| 916 | } |
| 917 |