Fossil SCM

Rework the "fossil grep" command so that it shows both the file and check-in hash for matching files, and so that it can scan multiple files all at once.

drh 2019-11-30 13:53 trunk merge
Commit f5f4471323d44a82a73a506f4ecd277f7ff8a937547d9bc2a023f07243b82d3b
+119 -32
--- src/regexp.c
+++ src/regexp.c
@@ -719,10 +719,11 @@
719719
720720
/*
721721
** Flags for grep_buffer()
722722
*/
723723
#define GREP_EXISTS 0x001 /* If any match, print only the name and stop */
724
+#define GREP_QUIET 0x002 /* Return code only */
724725
725726
/*
726727
** Run a "grep" over a text file
727728
*/
728729
static int grep_buffer(
@@ -737,14 +738,19 @@
737738
n = j - i;
738739
ln++;
739740
if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){
740741
cnt++;
741742
if( flags & GREP_EXISTS ){
742
- fossil_print("%S\n", zName);
743
+ if( (flags & GREP_QUIET)==0 && zName ) fossil_print("%s\n", zName);
743744
break;
744745
}
745
- fossil_print("%S:%d:%.*s\n", zName, ln, n, z+i);
746
+ if( (flags & GREP_QUIET)==0 ){
747
+ if( cnt==1 && zName ){
748
+ fossil_print("== %s\n", zName);
749
+ }
750
+ fossil_print("%d:%.*s\n", ln, n, z+i);
751
+ }
746752
}
747753
}
748754
return cnt;
749755
}
750756
@@ -787,61 +793,142 @@
787793
}
788794
789795
/*
790796
** COMMAND: grep
791797
**
792
-** Usage: %fossil grep [OPTIONS] PATTERN FILENAME
798
+** Usage: %fossil grep [OPTIONS] PATTERN FILENAME ...
793799
**
794800
** 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
801
+** historic versions of FILENAME. The search begins with the most recent
802
+** version of the file and moves backwards in time. Multiple FILENAMEs can
803
+** be specified, in which case all named files are searched in reverse
804
+** chronological order.
805
+**
806
+** For details of the supported regular expression dialect, see
807
+** https://fossil-scm.org/fossil/doc/trunk/www/grep.md
797808
**
798809
** Options:
799810
**
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
811
+** -c|--count Suppress normal output; instead print a count
812
+** of the number of matching files
813
+** -i|--ignore-case Ignore case
814
+** -l|--files-with-matches List only hash for each match
815
+** --once Stop searching after the first match
816
+** -s|--no-messages Suppress error messages about nonexistant
817
+** or unreadable files
818
+** -v|--invert-match Invert the sense of matching. Show only
819
+** files that have no matches. Implies -l
820
+** --verbose Show each file as it is analyzed
803821
*/
804822
void re_grep_cmd(void){
805823
u32 flags = 0;
806824
int bVerbose = 0;
807825
ReCompiled *pRe;
808826
const char *zErr;
809827
int ignoreCase = 0;
810828
Blob fullName;
829
+ int ii;
830
+ int nMatch = 0;
831
+ int bNoMsg;
832
+ int cntFlag;
833
+ int bOnce;
834
+ int bInvert;
835
+ int nSearch = 0;
836
+ Stmt q;
837
+
811838
812839
if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1;
813840
if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS;
814
- if( find_option("verbose","v",0)!=0 ) bVerbose = 1;
841
+ if( find_option("verbose",0,0)!=0 ) bVerbose = 1;
842
+ if( find_option("quiet","q",0) ) flags |= GREP_QUIET|GREP_EXISTS;
843
+ bNoMsg = find_option("no-messages","s",0)!=0;
844
+ bOnce = find_option("once",0,0)!=0;
845
+ bInvert = find_option("invert-match","v",0)!=0;
846
+ if( bInvert ){
847
+ flags |= GREP_QUIET|GREP_EXISTS;
848
+ }
849
+ cntFlag = find_option("count","c",0)!=0;
850
+ if( cntFlag ){
851
+ flags |= GREP_QUIET|GREP_EXISTS;
852
+ }
815853
db_find_and_open_repository(0, 0);
816854
verify_all_options();
817855
if( g.argc<4 ){
818
- usage("REGEXP FILENAME");
856
+ usage("REGEXP FILENAME ...");
819857
}
820858
zErr = re_compile(&pRe, g.argv[2], ignoreCase);
821859
if( zErr ) fossil_fatal("%s", zErr);
822860
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);
861
+ add_content_sql_commands(g.db);
862
+ db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);");
863
+ for(ii=3; ii<g.argc; ii++){
864
+ const char *zTarget = g.argv[ii];
865
+ if( file_tree_name(zTarget, &fullName, 0, 1) ){
866
+ int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q",
867
+ blob_str(&fullName));
868
+ if( !fnid ){
869
+ if( bNoMsg ) continue;
870
+ if( file_size(zTarget, ExtFILE)<0 ){
871
+ fossil_fatal("no such file: %s", zTarget);
872
+ }
873
+ fossil_fatal("not a managed file: %s", zTarget);
874
+ }else{
875
+ db_multi_exec(
876
+ "INSERT INTO arglist(iname,fname,fnid) VALUES(%Q,%Q,%d)",
877
+ zTarget, blob_str(&fullName), fnid);
878
+ }
879
+ }
880
+ blob_reset(&fullName);
881
+ }
882
+ db_prepare(&q,
883
+ " SELECT"
884
+ " A.uuid," /* file hash */
885
+ " A.rid," /* file rid */
886
+ " B.uuid," /* check-in hash */
887
+ " datetime(min(event.mtime))," /* check-in time */
888
+ " arglist.iname" /* file name */
889
+ " FROM arglist, mlink, blob A, blob B, event"
890
+ " WHERE mlink.mid=event.objid"
891
+ " AND mlink.fid=A.rid"
892
+ " AND mlink.mid=B.rid"
893
+ " AND mlink.fnid=arglist.fnid"
894
+ " GROUP BY A.uuid"
895
+ " ORDER BY min(event.mtime) DESC;"
896
+ );
897
+ while( db_step(&q)==SQLITE_ROW ){
898
+ const char *zFileHash = db_column_text(&q,0);
899
+ int rid = db_column_int(&q,1);
900
+ const char *zCkinHash = db_column_text(&q,2);
901
+ const char *zDate = db_column_text(&q,3);
902
+ const char *zFN = db_column_text(&q,4);
903
+ char *zLabel;
904
+ Blob cx;
905
+ content_get(rid, &cx);
906
+ zLabel = mprintf("%.16s %s %S checkin %S", zDate, zFN,zFileHash,zCkinHash);
907
+ if( bVerbose ) fossil_print("Scanning: %s\n", zLabel);
908
+ nSearch++;
909
+ nMatch += grep_buffer(pRe, zLabel, blob_str(&cx), flags);
910
+ blob_reset(&cx);
911
+ if( bInvert && cntFlag==0 ){
912
+ if( nMatch==0 ){
913
+ fossil_print("== %s\n", zLabel);
914
+ if( bOnce ) nMatch = 1;
915
+ }else{
916
+ nMatch = 0;
917
+ }
918
+ }
919
+ fossil_free(zLabel);
920
+ if( nMatch ){
921
+ if( (flags & GREP_QUIET)!=0 ) break;
922
+ if( bOnce ) break;
923
+ }
924
+ }
925
+ db_finalize(&q);
926
+ re_free(pRe);
927
+ if( cntFlag ){
928
+ if( bInvert ){
929
+ fossil_print("%d\n", nSearch-nMatch);
930
+ }else{
931
+ fossil_print("%d\n", nMatch);
845932
}
846933
}
847934
}
848935
--- 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,19 @@
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 +793,142 @@
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,19 @@
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 && zName ) fossil_print("%s\n", zName);
744 break;
745 }
746 if( (flags & GREP_QUIET)==0 ){
747 if( cnt==1 && zName ){
748 fossil_print("== %s\n", zName);
749 }
750 fossil_print("%d:%.*s\n", ln, n, z+i);
751 }
752 }
753 }
754 return cnt;
755 }
756
@@ -787,61 +793,142 @@
793 }
794
795 /*
796 ** COMMAND: grep
797 **
798 ** Usage: %fossil grep [OPTIONS] PATTERN FILENAME ...
799 **
800 ** Attempt to match the given POSIX extended regular expression PATTERN
801 ** historic versions of FILENAME. The search begins with the most recent
802 ** version of the file and moves backwards in time. Multiple FILENAMEs can
803 ** be specified, in which case all named files are searched in reverse
804 ** chronological order.
805 **
806 ** For details of the supported regular expression dialect, see
807 ** https://fossil-scm.org/fossil/doc/trunk/www/grep.md
808 **
809 ** Options:
810 **
811 ** -c|--count Suppress normal output; instead print a count
812 ** of the number of matching files
813 ** -i|--ignore-case Ignore case
814 ** -l|--files-with-matches List only hash for each match
815 ** --once Stop searching after the first match
816 ** -s|--no-messages Suppress error messages about nonexistant
817 ** or unreadable files
818 ** -v|--invert-match Invert the sense of matching. Show only
819 ** files that have no matches. Implies -l
820 ** --verbose Show each file as it is analyzed
821 */
822 void re_grep_cmd(void){
823 u32 flags = 0;
824 int bVerbose = 0;
825 ReCompiled *pRe;
826 const char *zErr;
827 int ignoreCase = 0;
828 Blob fullName;
829 int ii;
830 int nMatch = 0;
831 int bNoMsg;
832 int cntFlag;
833 int bOnce;
834 int bInvert;
835 int nSearch = 0;
836 Stmt q;
837
838
839 if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1;
840 if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS;
841 if( find_option("verbose",0,0)!=0 ) bVerbose = 1;
842 if( find_option("quiet","q",0) ) flags |= GREP_QUIET|GREP_EXISTS;
843 bNoMsg = find_option("no-messages","s",0)!=0;
844 bOnce = find_option("once",0,0)!=0;
845 bInvert = find_option("invert-match","v",0)!=0;
846 if( bInvert ){
847 flags |= GREP_QUIET|GREP_EXISTS;
848 }
849 cntFlag = find_option("count","c",0)!=0;
850 if( cntFlag ){
851 flags |= GREP_QUIET|GREP_EXISTS;
852 }
853 db_find_and_open_repository(0, 0);
854 verify_all_options();
855 if( g.argc<4 ){
856 usage("REGEXP FILENAME ...");
857 }
858 zErr = re_compile(&pRe, g.argv[2], ignoreCase);
859 if( zErr ) fossil_fatal("%s", zErr);
860
861 add_content_sql_commands(g.db);
862 db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);");
863 for(ii=3; ii<g.argc; ii++){
864 const char *zTarget = g.argv[ii];
865 if( file_tree_name(zTarget, &fullName, 0, 1) ){
866 int fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q",
867 blob_str(&fullName));
868 if( !fnid ){
869 if( bNoMsg ) continue;
870 if( file_size(zTarget, ExtFILE)<0 ){
871 fossil_fatal("no such file: %s", zTarget);
872 }
873 fossil_fatal("not a managed file: %s", zTarget);
874 }else{
875 db_multi_exec(
876 "INSERT INTO arglist(iname,fname,fnid) VALUES(%Q,%Q,%d)",
877 zTarget, blob_str(&fullName), fnid);
878 }
879 }
880 blob_reset(&fullName);
881 }
882 db_prepare(&q,
883 " SELECT"
884 " A.uuid," /* file hash */
885 " A.rid," /* file rid */
886 " B.uuid," /* check-in hash */
887 " datetime(min(event.mtime))," /* check-in time */
888 " arglist.iname" /* file name */
889 " FROM arglist, mlink, blob A, blob B, event"
890 " WHERE mlink.mid=event.objid"
891 " AND mlink.fid=A.rid"
892 " AND mlink.mid=B.rid"
893 " AND mlink.fnid=arglist.fnid"
894 " GROUP BY A.uuid"
895 " ORDER BY min(event.mtime) DESC;"
896 );
897 while( db_step(&q)==SQLITE_ROW ){
898 const char *zFileHash = db_column_text(&q,0);
899 int rid = db_column_int(&q,1);
900 const char *zCkinHash = db_column_text(&q,2);
901 const char *zDate = db_column_text(&q,3);
902 const char *zFN = db_column_text(&q,4);
903 char *zLabel;
904 Blob cx;
905 content_get(rid, &cx);
906 zLabel = mprintf("%.16s %s %S checkin %S", zDate, zFN,zFileHash,zCkinHash);
907 if( bVerbose ) fossil_print("Scanning: %s\n", zLabel);
908 nSearch++;
909 nMatch += grep_buffer(pRe, zLabel, blob_str(&cx), flags);
910 blob_reset(&cx);
911 if( bInvert && cntFlag==0 ){
912 if( nMatch==0 ){
913 fossil_print("== %s\n", zLabel);
914 if( bOnce ) nMatch = 1;
915 }else{
916 nMatch = 0;
917 }
918 }
919 fossil_free(zLabel);
920 if( nMatch ){
921 if( (flags & GREP_QUIET)!=0 ) break;
922 if( bOnce ) break;
923 }
924 }
925 db_finalize(&q);
926 re_free(pRe);
927 if( cntFlag ){
928 if( bInvert ){
929 fossil_print("%d\n", nSearch-nMatch);
930 }else{
931 fossil_print("%d\n", nMatch);
932 }
933 }
934 }
935
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,13 @@
11
<title>Change Log</title>
22
3
+<a name='v2_11'></a>
4
+<h2>Changes for Version 2.11 (pending)</h2>
5
+
6
+ * Support Markdown in the default ticket configuration
7
+ * Rework the "fossil grep" command to be more useful
8
+
39
<a name='v2_10'></a>
410
<h2>Changes for Version 2.10 (2019-10-04)</h2>
511
612
* Added support for [./serverext.wiki|CGI-based Server Extensions].
713
* Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
814
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,13 @@
1 <title>Change Log</title>
2
 
 
 
 
 
 
3 <a name='v2_10'></a>
4 <h2>Changes for Version 2.10 (2019-10-04)</h2>
5
6 * Added support for [./serverext.wiki|CGI-based Server Extensions].
7 * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
8
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,13 @@
1 <title>Change Log</title>
2
3 <a name='v2_11'></a>
4 <h2>Changes for Version 2.11 (pending)</h2>
5
6 * Support Markdown in the default ticket configuration
7 * Rework the "fossil grep" command to be more useful
8
9 <a name='v2_10'></a>
10 <h2>Changes for Version 2.10 (2019-10-04)</h2>
11
12 * Added support for [./serverext.wiki|CGI-based Server Extensions].
13 * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
14
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,13 @@
11
<title>Change Log</title>
22
3
+<a name='v2_11'></a>
4
+<h2>Changes for Version 2.11 (pending)</h2>
5
+
6
+ * Support Markdown in the default ticket configuration
7
+ * Rework the "fossil grep" command to be more useful
8
+
39
<a name='v2_10'></a>
410
<h2>Changes for Version 2.10 (2019-10-04)</h2>
511
612
* Added support for [./serverext.wiki|CGI-based Server Extensions].
713
* Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
814
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,13 @@
1 <title>Change Log</title>
2
 
 
 
 
 
 
3 <a name='v2_10'></a>
4 <h2>Changes for Version 2.10 (2019-10-04)</h2>
5
6 * Added support for [./serverext.wiki|CGI-based Server Extensions].
7 * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
8
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,7 +1,13 @@
1 <title>Change Log</title>
2
3 <a name='v2_11'></a>
4 <h2>Changes for Version 2.11 (pending)</h2>
5
6 * Support Markdown in the default ticket configuration
7 * Rework the "fossil grep" command to be more useful
8
9 <a name='v2_10'></a>
10 <h2>Changes for Version 2.10 (2019-10-04)</h2>
11
12 * Added support for [./serverext.wiki|CGI-based Server Extensions].
13 * Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
14

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button