Fossil SCM
Add --user-override and --date-override options to the "fossil ticket add" command.
Commit
ea4acb5c5db5d15b86da6d785bc616c4ff300103
Parent
372879b3885d45e…
1 file changed
+309
-304
+309
-304
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -855,332 +855,337 @@ | ||
| 855 | 855 | ** COMMAND: ticket* |
| 856 | 856 | ** Usage: %fossil ticket SUBCOMMAND ... |
| 857 | 857 | ** |
| 858 | 858 | ** Run various subcommands to control tickets |
| 859 | 859 | ** |
| 860 | -** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options? | |
| 861 | -** | |
| 862 | -** options can be: | |
| 863 | -** ?-l|--limit LIMITCHAR? | |
| 864 | -** ?-q|--quote? | |
| 865 | -** ?-R|--repository FILE? | |
| 866 | -** | |
| 867 | -** Run the ticket report, identified by the report format title | |
| 868 | -** used in the gui. The data is written as flat file on stdout, | |
| 869 | -** using "," as separator. The separator "," can be changed using | |
| 870 | -** the -l or --limit option. | |
| 871 | -** | |
| 872 | -** If TICKETFILTER is given on the commandline, the query is | |
| 873 | -** limited with a new WHERE-condition. | |
| 874 | -** example: Report lists a column # with the uuid | |
| 875 | -** TICKETFILTER may be [#]='uuuuuuuuu' | |
| 876 | -** example: Report only lists rows with status not open | |
| 877 | -** TICKETFILTER: status != 'open' | |
| 878 | -** If the option -q|--quote is used, the tickets are encoded by | |
| 879 | -** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, | |
| 880 | -** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). | |
| 881 | -** Otherwise, the simplified encoding as on the show report raw | |
| 882 | -** page in the gui is used. This has no effect in JSON mode. | |
| 883 | -** | |
| 884 | -** Instead of the report title its possible to use the report | |
| 885 | -** number. Using the special report number 0 list all columns, | |
| 886 | -** defined in the ticket table. | |
| 887 | -** | |
| 888 | -** %fossil ticket list fields | |
| 889 | -** | |
| 890 | -** list all fields, defined for ticket in the fossil repository | |
| 891 | -** | |
| 892 | -** %fossil ticket list reports | |
| 893 | -** | |
| 894 | -** list all ticket reports, defined in the fossil repository | |
| 895 | -** | |
| 896 | -** %fossil ticket set TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? | |
| 897 | -** %fossil ticket change TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? | |
| 898 | -** | |
| 899 | -** change ticket identified by TICKETUUID and set the value of | |
| 900 | -** field FIELD to VALUE. Valid field descriptions are: | |
| 901 | -** status, type, severity, priority, resolution, | |
| 902 | -** foundin, private_contact, resolution, title or comment | |
| 903 | -** Field names given above are the ones, defined in a standard | |
| 904 | -** fossil environment. If you have added, deleted columns, you | |
| 905 | -** change the all your configured columns. | |
| 906 | -** If you use +FIELD, the VALUE Is appended to the field FIELD. | |
| 907 | -** You can use more than one field/value pair on the commandline. | |
| 908 | -** Using -q|--quote enables the special character decoding as | |
| 909 | -** in "ticket show". So it's possible, to set multiline text or | |
| 910 | -** text with special characters. | |
| 911 | -** | |
| 912 | -** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? | |
| 913 | -** | |
| 914 | -** like set, but create a new ticket with the given values. | |
| 915 | -** | |
| 916 | -** %fossil ticket history TICKETUUID | |
| 917 | -** | |
| 918 | -** Show the complete change history for the ticket | |
| 860 | +** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options? | |
| 861 | +** | |
| 862 | +** options can be: | |
| 863 | +** ?-l|--limit LIMITCHAR? | |
| 864 | +** ?-q|--quote? | |
| 865 | +** ?-R|--repository FILE? | |
| 866 | +** | |
| 867 | +** Run the ticket report, identified by the report format title | |
| 868 | +** used in the gui. The data is written as flat file on stdout, | |
| 869 | +** using "," as separator. The separator "," can be changed using | |
| 870 | +** the -l or --limit option. | |
| 871 | +** | |
| 872 | +** If TICKETFILTER is given on the commandline, the query is | |
| 873 | +** limited with a new WHERE-condition. | |
| 874 | +** example: Report lists a column # with the uuid | |
| 875 | +** TICKETFILTER may be [#]='uuuuuuuuu' | |
| 876 | +** example: Report only lists rows with status not open | |
| 877 | +** TICKETFILTER: status != 'open' | |
| 878 | +** If the option -q|--quote is used, the tickets are encoded by | |
| 879 | +** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, | |
| 880 | +** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). | |
| 881 | +** Otherwise, the simplified encoding as on the show report raw | |
| 882 | +** page in the gui is used. This has no effect in JSON mode. | |
| 883 | +** | |
| 884 | +** Instead of the report title its possible to use the report | |
| 885 | +** number. Using the special report number 0 list all columns, | |
| 886 | +** defined in the ticket table. | |
| 887 | +** | |
| 888 | +** %fossil ticket list fields | |
| 889 | +** | |
| 890 | +** list all fields, defined for ticket in the fossil repository | |
| 891 | +** | |
| 892 | +** %fossil ticket list reports | |
| 893 | +** | |
| 894 | +** list all ticket reports, defined in the fossil repository | |
| 895 | +** | |
| 896 | +** %fossil ticket set TICKETUUID (FIELD VALUE)+ ?-q|--quote? | |
| 897 | +** %fossil ticket change TICKETUUID (FIELD VALUE)+ ?-q|--quote? | |
| 898 | +** | |
| 899 | +** change ticket identified by TICKETUUID and set the value of | |
| 900 | +** field FIELD to VALUE. | |
| 901 | +** | |
| 902 | +** Field names as defined in the TICKET table. By default, these | |
| 903 | +** names include: type, status, subsystem, priority, severity, foundin, | |
| 904 | +** resolution, title, and comment, but other field names can be added | |
| 905 | +** or substituted in customized installations. | |
| 906 | +** | |
| 907 | +** If you use +FIELD, the VALUE Is appended to the field FIELD. | |
| 908 | +** You can use more than one field/value pair on the commandline. | |
| 909 | +** Using -q|--quote enables the special character decoding as | |
| 910 | +** in "ticket show". So it's possible, to set multiline text or | |
| 911 | +** text with special characters. | |
| 912 | +** | |
| 913 | +** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? | |
| 914 | +** | |
| 915 | +** like set, but create a new ticket with the given values. | |
| 916 | +** | |
| 917 | +** %fossil ticket history TICKETUUID | |
| 918 | +** | |
| 919 | +** Show the complete change history for the ticket | |
| 919 | 920 | ** |
| 920 | 921 | ** The values in set|add are not validated against the definitions |
| 921 | 922 | ** given in "Ticket Common Script". |
| 922 | 923 | */ |
| 923 | 924 | void ticket_cmd(void){ |
| 924 | 925 | int n; |
| 926 | + const char *zUser; | |
| 927 | + const char *zDate; | |
| 925 | 928 | |
| 926 | 929 | /* do some ints, we want to be inside a checkout */ |
| 927 | 930 | db_find_and_open_repository(0, 0); |
| 928 | 931 | user_select(); |
| 932 | + | |
| 933 | + zUser = find_option("user-override",0,1); | |
| 934 | + if( zUser==0 ) zUser = g.zLogin; | |
| 935 | + zDate = find_option("date-override",0,1); | |
| 936 | + if( zDate==0 ) zDate = "now"; | |
| 937 | + zDate = date_in_standard_format(zDate); | |
| 938 | + | |
| 929 | 939 | /* |
| 930 | 940 | ** Check that the user exists. |
| 931 | 941 | */ |
| 932 | - if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ | |
| 933 | - fossil_fatal("no such user: %s", g.zLogin); | |
| 942 | + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", zUser) ){ | |
| 943 | + fossil_fatal("no such user: %s", zUser); | |
| 934 | 944 | } |
| 935 | 945 | |
| 936 | 946 | if( g.argc<3 ){ |
| 937 | 947 | usage("add|fieldlist|set|show|history"); |
| 938 | - }else{ | |
| 939 | - n = strlen(g.argv[2]); | |
| 940 | - if( n==1 && g.argv[2][0]=='s' ){ | |
| 941 | - /* set/show cannot be distinguished, so show the usage */ | |
| 942 | - usage("add|fieldlist|set|show|history"); | |
| 943 | - }else if( strncmp(g.argv[2],"list",n)==0 ){ | |
| 944 | - if( g.argc==3 ){ | |
| 945 | - usage("list fields|reports"); | |
| 946 | - }else{ | |
| 947 | - n = strlen(g.argv[3]); | |
| 948 | - if( !strncmp(g.argv[3],"fields",n) ){ | |
| 949 | - /* simply show all field names */ | |
| 950 | - int i; | |
| 951 | - | |
| 952 | - /* read all available ticket fields */ | |
| 953 | - getAllTicketFields(); | |
| 954 | - for(i=0; i<nField; i++){ | |
| 955 | - printf("%s\n",azField[i]); | |
| 956 | - } | |
| 957 | - }else if( !strncmp(g.argv[3],"reports",n) ){ | |
| 958 | - rpt_list_reports(); | |
| 959 | - }else{ | |
| 960 | - fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); | |
| 961 | - } | |
| 962 | - } | |
| 963 | - }else{ | |
| 964 | - /* add a new ticket or set fields on existing tickets */ | |
| 965 | - tTktShowEncoding tktEncoding; | |
| 966 | - | |
| 967 | - tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; | |
| 968 | - | |
| 969 | - if( strncmp(g.argv[2],"show",n)==0 ){ | |
| 970 | - if( g.argc==3 ){ | |
| 971 | - usage("show REPORTNR"); | |
| 972 | - }else{ | |
| 973 | - const char *zRep = 0; | |
| 974 | - const char *zSep = 0; | |
| 975 | - const char *zFilterUuid = 0; | |
| 976 | - zSep = find_option("limit","l",1); | |
| 977 | - zRep = g.argv[3]; | |
| 978 | - if( !strcmp(zRep,"0") ){ | |
| 979 | - zRep = 0; | |
| 980 | - } | |
| 981 | - if( g.argc>4 ){ | |
| 982 | - zFilterUuid = g.argv[4]; | |
| 983 | - } | |
| 984 | - rptshow( zRep, zSep, zFilterUuid, tktEncoding ); | |
| 985 | - } | |
| 986 | - }else{ | |
| 987 | - /* add a new ticket or update an existing ticket */ | |
| 988 | - enum { set,add,history,err } eCmd = err; | |
| 989 | - int i = 0; | |
| 990 | - int rid; | |
| 991 | - const char *zTktUuid = 0; | |
| 992 | - Blob tktchng, cksum; | |
| 993 | - | |
| 994 | - /* get command type (set/add) and get uuid, if needed for set */ | |
| 995 | - if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || | |
| 996 | - strncmp(g.argv[2],"history",n)==0 ){ | |
| 997 | - if( strncmp(g.argv[2],"history",n)==0 ){ | |
| 998 | - eCmd = history; | |
| 999 | - }else{ | |
| 1000 | - eCmd = set; | |
| 1001 | - } | |
| 1002 | - if( g.argc==3 ){ | |
| 1003 | - usage("set TICKETUUID"); | |
| 1004 | - } | |
| 1005 | - zTktUuid = db_text(0, | |
| 1006 | - "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] | |
| 1007 | - ); | |
| 1008 | - if( !zTktUuid ){ | |
| 1009 | - fossil_fatal("unknown ticket: '%s'!",g.argv[3]); | |
| 1010 | - } | |
| 1011 | - i=4; | |
| 1012 | - }else if( strncmp(g.argv[2],"add",n)==0 ){ | |
| 1013 | - eCmd = add; | |
| 1014 | - i = 3; | |
| 1015 | - zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); | |
| 1016 | - } | |
| 1017 | - /* none of set/add, so show the usage! */ | |
| 1018 | - if( eCmd==err ){ | |
| 1019 | - usage("add|fieldlist|set|show|history"); | |
| 1020 | - } | |
| 1021 | - | |
| 1022 | - /* we just handle history separately here, does not get out */ | |
| 1023 | - if( eCmd==history ){ | |
| 1024 | - Stmt q; | |
| 1025 | - int tagid; | |
| 1026 | - | |
| 1027 | - if ( i != g.argc ){ | |
| 1028 | - fossil_fatal("no other parameters expected to %s!",g.argv[2]); | |
| 1029 | - } | |
| 1030 | - tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid); | |
| 1031 | - if( tagid==0 ){ | |
| 1032 | - fossil_fatal("no such ticket %h", zTktUuid); | |
| 1033 | - } | |
| 1034 | - db_prepare(&q, | |
| 1035 | - "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" | |
| 1036 | - " FROM event, blob" | |
| 1037 | - " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" | |
| 1038 | - " AND blob.rid=event.objid" | |
| 1039 | - " UNION " | |
| 1040 | - "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" | |
| 1041 | - " FROM attachment, blob" | |
| 1042 | - " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" | |
| 1043 | - " AND blob.rid=attachid" | |
| 1044 | - " ORDER BY 1 DESC", | |
| 1045 | - tagid, tagid | |
| 1046 | - ); | |
| 1047 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 1048 | - Manifest *pTicket; | |
| 1049 | - char zShort[12]; | |
| 1050 | - const char *zDate = db_column_text(&q, 0); | |
| 1051 | - int rid = db_column_int(&q, 1); | |
| 1052 | - const char *zChngUuid = db_column_text(&q, 2); | |
| 1053 | - const char *zFile = db_column_text(&q, 4); | |
| 1054 | - memcpy(zShort, zChngUuid, 10); | |
| 1055 | - zShort[10] = 0; | |
| 1056 | - if( zFile!=0 ){ | |
| 1057 | - const char *zSrc = db_column_text(&q, 3); | |
| 1058 | - const char *zUser = db_column_text(&q, 5); | |
| 1059 | - if( zSrc==0 || zSrc[0]==0 ){ | |
| 1060 | - fossil_print("Delete attachment %h\n", zFile); | |
| 1061 | - }else{ | |
| 1062 | - fossil_print("Add attachment %h\n", zFile); | |
| 1063 | - } | |
| 1064 | - fossil_print(" by %h on %h\n", zUser, zDate); | |
| 1065 | - }else{ | |
| 1066 | - pTicket = manifest_get(rid, CFTYPE_TICKET); | |
| 1067 | - if( pTicket ){ | |
| 1068 | - int i; | |
| 1069 | - | |
| 1070 | - fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate); | |
| 1071 | - for(i=0; i<pTicket->nField; i++){ | |
| 1072 | - Blob val; | |
| 1073 | - const char *z; | |
| 1074 | - z = pTicket->aField[i].zName; | |
| 1075 | - blob_set(&val, pTicket->aField[i].zValue); | |
| 1076 | - if( z[0]=='+' ){ | |
| 1077 | - fossil_print(" Append to "); | |
| 948 | + } | |
| 949 | + n = strlen(g.argv[2]); | |
| 950 | + if( n==1 && g.argv[2][0]=='s' ){ | |
| 951 | + /* set/show cannot be distinguished, so show the usage */ | |
| 952 | + usage("add|fieldlist|set|show|history"); | |
| 953 | + } | |
| 954 | + if( strncmp(g.argv[2],"list",n)==0 ){ | |
| 955 | + if( g.argc==3 ){ | |
| 956 | + usage("list fields|reports"); | |
| 957 | + }else{ | |
| 958 | + n = strlen(g.argv[3]); | |
| 959 | + if( !strncmp(g.argv[3],"fields",n) ){ | |
| 960 | + /* simply show all field names */ | |
| 961 | + int i; | |
| 962 | + | |
| 963 | + /* read all available ticket fields */ | |
| 964 | + getAllTicketFields(); | |
| 965 | + for(i=0; i<nField; i++){ | |
| 966 | + printf("%s\n",azField[i]); | |
| 967 | + } | |
| 968 | + }else if( !strncmp(g.argv[3],"reports",n) ){ | |
| 969 | + rpt_list_reports(); | |
| 970 | + }else{ | |
| 971 | + fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); | |
| 972 | + } | |
| 973 | + } | |
| 974 | + }else{ | |
| 975 | + /* add a new ticket or set fields on existing tickets */ | |
| 976 | + tTktShowEncoding tktEncoding; | |
| 977 | + | |
| 978 | + tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; | |
| 979 | + | |
| 980 | + if( strncmp(g.argv[2],"show",n)==0 ){ | |
| 981 | + if( g.argc==3 ){ | |
| 982 | + usage("show REPORTNR"); | |
| 983 | + }else{ | |
| 984 | + const char *zRep = 0; | |
| 985 | + const char *zSep = 0; | |
| 986 | + const char *zFilterUuid = 0; | |
| 987 | + zSep = find_option("limit","l",1); | |
| 988 | + zRep = g.argv[3]; | |
| 989 | + if( !strcmp(zRep,"0") ){ | |
| 990 | + zRep = 0; | |
| 991 | + } | |
| 992 | + if( g.argc>4 ){ | |
| 993 | + zFilterUuid = g.argv[4]; | |
| 994 | + } | |
| 995 | + rptshow( zRep, zSep, zFilterUuid, tktEncoding ); | |
| 996 | + } | |
| 997 | + }else{ | |
| 998 | + /* add a new ticket or update an existing ticket */ | |
| 999 | + enum { set,add,history,err } eCmd = err; | |
| 1000 | + int i = 0; | |
| 1001 | + int rid; | |
| 1002 | + const char *zTktUuid = 0; | |
| 1003 | + Blob tktchng, cksum; | |
| 1004 | + | |
| 1005 | + /* get command type (set/add) and get uuid, if needed for set */ | |
| 1006 | + if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || | |
| 1007 | + strncmp(g.argv[2],"history",n)==0 ){ | |
| 1008 | + if( strncmp(g.argv[2],"history",n)==0 ){ | |
| 1009 | + eCmd = history; | |
| 1010 | + }else{ | |
| 1011 | + eCmd = set; | |
| 1012 | + } | |
| 1013 | + if( g.argc==3 ){ | |
| 1014 | + usage("set TICKETUUID"); | |
| 1015 | + } | |
| 1016 | + zTktUuid = db_text(0, | |
| 1017 | + "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] | |
| 1018 | + ); | |
| 1019 | + if( !zTktUuid ){ | |
| 1020 | + fossil_fatal("unknown ticket: '%s'!",g.argv[3]); | |
| 1021 | + } | |
| 1022 | + i=4; | |
| 1023 | + }else if( strncmp(g.argv[2],"add",n)==0 ){ | |
| 1024 | + eCmd = add; | |
| 1025 | + i = 3; | |
| 1026 | + zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); | |
| 1027 | + } | |
| 1028 | + /* none of set/add, so show the usage! */ | |
| 1029 | + if( eCmd==err ){ | |
| 1030 | + usage("add|fieldlist|set|show|history"); | |
| 1031 | + } | |
| 1032 | + | |
| 1033 | + /* we just handle history separately here, does not get out */ | |
| 1034 | + if( eCmd==history ){ | |
| 1035 | + Stmt q; | |
| 1036 | + int tagid; | |
| 1037 | + | |
| 1038 | + if ( i != g.argc ){ | |
| 1039 | + fossil_fatal("no other parameters expected to %s!",g.argv[2]); | |
| 1040 | + } | |
| 1041 | + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid); | |
| 1042 | + if( tagid==0 ){ | |
| 1043 | + fossil_fatal("no such ticket %h", zTktUuid); | |
| 1044 | + } | |
| 1045 | + db_prepare(&q, | |
| 1046 | + "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" | |
| 1047 | + " FROM event, blob" | |
| 1048 | + " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" | |
| 1049 | + " AND blob.rid=event.objid" | |
| 1050 | + " UNION " | |
| 1051 | + "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" | |
| 1052 | + " FROM attachment, blob" | |
| 1053 | + " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" | |
| 1054 | + " AND blob.rid=attachid" | |
| 1055 | + " ORDER BY 1 DESC", | |
| 1056 | + tagid, tagid | |
| 1057 | + ); | |
| 1058 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 1059 | + Manifest *pTicket; | |
| 1060 | + char zShort[12]; | |
| 1061 | + const char *zDate = db_column_text(&q, 0); | |
| 1062 | + int rid = db_column_int(&q, 1); | |
| 1063 | + const char *zChngUuid = db_column_text(&q, 2); | |
| 1064 | + const char *zFile = db_column_text(&q, 4); | |
| 1065 | + memcpy(zShort, zChngUuid, 10); | |
| 1066 | + zShort[10] = 0; | |
| 1067 | + if( zFile!=0 ){ | |
| 1068 | + const char *zSrc = db_column_text(&q, 3); | |
| 1069 | + const char *zUser = db_column_text(&q, 5); | |
| 1070 | + if( zSrc==0 || zSrc[0]==0 ){ | |
| 1071 | + fossil_print("Delete attachment %h\n", zFile); | |
| 1072 | + }else{ | |
| 1073 | + fossil_print("Add attachment %h\n", zFile); | |
| 1074 | + } | |
| 1075 | + fossil_print(" by %h on %h\n", zUser, zDate); | |
| 1076 | + }else{ | |
| 1077 | + pTicket = manifest_get(rid, CFTYPE_TICKET); | |
| 1078 | + if( pTicket ){ | |
| 1079 | + int i; | |
| 1080 | + | |
| 1081 | + fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate); | |
| 1082 | + for(i=0; i<pTicket->nField; i++){ | |
| 1083 | + Blob val; | |
| 1084 | + const char *z; | |
| 1085 | + z = pTicket->aField[i].zName; | |
| 1086 | + blob_set(&val, pTicket->aField[i].zValue); | |
| 1087 | + if( z[0]=='+' ){ | |
| 1088 | + fossil_print(" Append to "); | |
| 1078 | 1089 | z++; |
| 1079 | 1090 | }else{ |
| 1080 | 1091 | fossil_print(" Change "); |
| 1081 | - } | |
| 1082 | - fossil_print("%h: ",z); | |
| 1083 | - if( blob_size(&val)>50 || contains_newline(&val)) { | |
| 1084 | - fossil_print("\n ",blob_str(&val)); | |
| 1085 | - comment_print(blob_str(&val),4,79); | |
| 1086 | - }else{ | |
| 1087 | - fossil_print("%s\n",blob_str(&val)); | |
| 1088 | - } | |
| 1089 | - blob_reset(&val); | |
| 1090 | - } | |
| 1091 | - } | |
| 1092 | - manifest_destroy(pTicket); | |
| 1093 | - } | |
| 1094 | - } | |
| 1095 | - db_finalize(&q); | |
| 1096 | - return; | |
| 1097 | - } | |
| 1098 | - /* read all given ticket field/value pairs from command line */ | |
| 1099 | - if( i==g.argc ){ | |
| 1100 | - fossil_fatal("empty %s command aborted!",g.argv[2]); | |
| 1101 | - } | |
| 1102 | - getAllTicketFields(); | |
| 1103 | - /* read commandline and assign fields in the azValue array */ | |
| 1104 | - while( i<g.argc ){ | |
| 1105 | - char *zFName; | |
| 1106 | - char *zFValue; | |
| 1107 | - int j; | |
| 1108 | - int append = 0; | |
| 1109 | - | |
| 1110 | - zFName = g.argv[i++]; | |
| 1111 | - if( i==g.argc ){ | |
| 1112 | - fossil_fatal("missing value for '%s'!",zFName); | |
| 1113 | - } | |
| 1114 | - zFValue = g.argv[i++]; | |
| 1115 | - if( tktEncoding == tktFossilize ){ | |
| 1116 | - zFValue=mprintf("%s",zFValue); | |
| 1117 | - defossilize(zFValue); | |
| 1118 | - } | |
| 1119 | - append = (zFName[0] == '+'); | |
| 1120 | - if (append){ | |
| 1121 | - zFName++; | |
| 1122 | - } | |
| 1123 | - j = fieldId(zFName); | |
| 1124 | - if( j == -1 ){ | |
| 1125 | - fossil_fatal("unknown field name '%s'!",zFName); | |
| 1126 | - }else{ | |
| 1127 | - if (append) { | |
| 1128 | - azAppend[j] = zFValue; | |
| 1129 | - } else { | |
| 1130 | - azValue[j] = zFValue; | |
| 1131 | - } | |
| 1132 | - } | |
| 1133 | - } | |
| 1134 | - | |
| 1135 | - /* now add the needed artifacts to the repository */ | |
| 1136 | - blob_zero(&tktchng); | |
| 1137 | - { /* add the time to the ticket manifest */ | |
| 1138 | - char *zDate; | |
| 1139 | - | |
| 1140 | - zDate = date_in_standard_format("now"); | |
| 1141 | - blob_appendf(&tktchng, "D %s\n", zDate); | |
| 1142 | - free(zDate); | |
| 1143 | - } | |
| 1144 | - /* append defined elements */ | |
| 1145 | - for(i=0; i<nField; i++){ | |
| 1146 | - char *zValue = 0; | |
| 1147 | - char *zPfx; | |
| 1148 | - | |
| 1149 | - if (azAppend[i] && azAppend[i][0] ){ | |
| 1150 | - zPfx = " +"; | |
| 1151 | - zValue = azAppend[i]; | |
| 1152 | - } else if( azValue[i] && azValue[i][0] ){ | |
| 1153 | - zPfx = " "; | |
| 1154 | - zValue = azValue[i]; | |
| 1155 | - } else { | |
| 1156 | - continue; | |
| 1157 | - } | |
| 1158 | - if( strncmp(azField[i], "private_", 8)==0 ){ | |
| 1159 | - zValue = db_conceal(zValue, strlen(zValue)); | |
| 1160 | - blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue); | |
| 1161 | - }else{ | |
| 1162 | - blob_appendf(&tktchng, "J%s%s %#F\n", zPfx, | |
| 1163 | - azField[i], strlen(zValue), zValue); | |
| 1164 | - } | |
| 1165 | - if( tktEncoding == tktFossilize ){ | |
| 1166 | - free(azValue[i]); | |
| 1167 | - } | |
| 1168 | - } | |
| 1169 | - blob_appendf(&tktchng, "K %s\n", zTktUuid); | |
| 1170 | - blob_appendf(&tktchng, "U %F\n", g.zLogin); | |
| 1171 | - md5sum_blob(&tktchng, &cksum); | |
| 1172 | - blob_appendf(&tktchng, "Z %b\n", &cksum); | |
| 1173 | - rid = content_put(&tktchng); | |
| 1174 | - if( rid==0 ){ | |
| 1175 | - fossil_panic("trouble committing ticket: %s", g.zErrMsg); | |
| 1176 | - } | |
| 1177 | - manifest_crosslink_begin(); | |
| 1178 | - manifest_crosslink(rid, &tktchng); | |
| 1179 | - manifest_crosslink_end(); | |
| 1180 | - assert( blob_is_reset(&tktchng) ); | |
| 1181 | - printf("ticket %s succeeded for UID %s\n", | |
| 1182 | - (eCmd==set?"set":"add"),zTktUuid); | |
| 1183 | - } | |
| 1092 | + } | |
| 1093 | + fossil_print("%h: ",z); | |
| 1094 | + if( blob_size(&val)>50 || contains_newline(&val)) { | |
| 1095 | + fossil_print("\n ",blob_str(&val)); | |
| 1096 | + comment_print(blob_str(&val),4,79); | |
| 1097 | + }else{ | |
| 1098 | + fossil_print("%s\n",blob_str(&val)); | |
| 1099 | + } | |
| 1100 | + blob_reset(&val); | |
| 1101 | + } | |
| 1102 | + } | |
| 1103 | + manifest_destroy(pTicket); | |
| 1104 | + } | |
| 1105 | + } | |
| 1106 | + db_finalize(&q); | |
| 1107 | + return; | |
| 1108 | + } | |
| 1109 | + /* read all given ticket field/value pairs from command line */ | |
| 1110 | + if( i==g.argc ){ | |
| 1111 | + fossil_fatal("empty %s command aborted!",g.argv[2]); | |
| 1112 | + } | |
| 1113 | + getAllTicketFields(); | |
| 1114 | + /* read commandline and assign fields in the azValue array */ | |
| 1115 | + while( i<g.argc ){ | |
| 1116 | + char *zFName; | |
| 1117 | + char *zFValue; | |
| 1118 | + int j; | |
| 1119 | + int append = 0; | |
| 1120 | + | |
| 1121 | + zFName = g.argv[i++]; | |
| 1122 | + if( i==g.argc ){ | |
| 1123 | + fossil_fatal("missing value for '%s'!",zFName); | |
| 1124 | + } | |
| 1125 | + zFValue = g.argv[i++]; | |
| 1126 | + if( tktEncoding == tktFossilize ){ | |
| 1127 | + zFValue=mprintf("%s",zFValue); | |
| 1128 | + defossilize(zFValue); | |
| 1129 | + } | |
| 1130 | + append = (zFName[0] == '+'); | |
| 1131 | + if (append){ | |
| 1132 | + zFName++; | |
| 1133 | + } | |
| 1134 | + j = fieldId(zFName); | |
| 1135 | + if( j == -1 ){ | |
| 1136 | + fossil_fatal("unknown field name '%s'!",zFName); | |
| 1137 | + }else{ | |
| 1138 | + if (append) { | |
| 1139 | + azAppend[j] = zFValue; | |
| 1140 | + } else { | |
| 1141 | + azValue[j] = zFValue; | |
| 1142 | + } | |
| 1143 | + } | |
| 1144 | + } | |
| 1145 | + | |
| 1146 | + /* now add the needed artifacts to the repository */ | |
| 1147 | + blob_zero(&tktchng); | |
| 1148 | + /* add the time to the ticket manifest */ | |
| 1149 | + blob_appendf(&tktchng, "D %s\n", zDate); | |
| 1150 | + /* append defined elements */ | |
| 1151 | + for(i=0; i<nField; i++){ | |
| 1152 | + char *zValue = 0; | |
| 1153 | + char *zPfx; | |
| 1154 | + | |
| 1155 | + if (azAppend[i] && azAppend[i][0] ){ | |
| 1156 | + zPfx = " +"; | |
| 1157 | + zValue = azAppend[i]; | |
| 1158 | + } else if( azValue[i] && azValue[i][0] ){ | |
| 1159 | + zPfx = " "; | |
| 1160 | + zValue = azValue[i]; | |
| 1161 | + } else { | |
| 1162 | + continue; | |
| 1163 | + } | |
| 1164 | + if( strncmp(azField[i], "private_", 8)==0 ){ | |
| 1165 | + zValue = db_conceal(zValue, strlen(zValue)); | |
| 1166 | + blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue); | |
| 1167 | + }else{ | |
| 1168 | + blob_appendf(&tktchng, "J%s%s %#F\n", zPfx, | |
| 1169 | + azField[i], strlen(zValue), zValue); | |
| 1170 | + } | |
| 1171 | + if( tktEncoding == tktFossilize ){ | |
| 1172 | + free(azValue[i]); | |
| 1173 | + } | |
| 1174 | + } | |
| 1175 | + blob_appendf(&tktchng, "K %s\n", zTktUuid); | |
| 1176 | + blob_appendf(&tktchng, "U %F\n", zUser); | |
| 1177 | + md5sum_blob(&tktchng, &cksum); | |
| 1178 | + blob_appendf(&tktchng, "Z %b\n", &cksum); | |
| 1179 | + rid = content_put(&tktchng); | |
| 1180 | + if( rid==0 ){ | |
| 1181 | + fossil_panic("trouble committing ticket: %s", g.zErrMsg); | |
| 1182 | + } | |
| 1183 | + manifest_crosslink_begin(); | |
| 1184 | + manifest_crosslink(rid, &tktchng); | |
| 1185 | + manifest_crosslink_end(); | |
| 1186 | + assert( blob_is_reset(&tktchng) ); | |
| 1187 | + printf("ticket %s succeeded for %s\n", | |
| 1188 | + (eCmd==set?"set":"add"),zTktUuid); | |
| 1184 | 1189 | } |
| 1185 | 1190 | } |
| 1186 | 1191 | } |
| 1187 | 1192 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -855,332 +855,337 @@ | |
| 855 | ** COMMAND: ticket* |
| 856 | ** Usage: %fossil ticket SUBCOMMAND ... |
| 857 | ** |
| 858 | ** Run various subcommands to control tickets |
| 859 | ** |
| 860 | ** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options? |
| 861 | ** |
| 862 | ** options can be: |
| 863 | ** ?-l|--limit LIMITCHAR? |
| 864 | ** ?-q|--quote? |
| 865 | ** ?-R|--repository FILE? |
| 866 | ** |
| 867 | ** Run the ticket report, identified by the report format title |
| 868 | ** used in the gui. The data is written as flat file on stdout, |
| 869 | ** using "," as separator. The separator "," can be changed using |
| 870 | ** the -l or --limit option. |
| 871 | ** |
| 872 | ** If TICKETFILTER is given on the commandline, the query is |
| 873 | ** limited with a new WHERE-condition. |
| 874 | ** example: Report lists a column # with the uuid |
| 875 | ** TICKETFILTER may be [#]='uuuuuuuuu' |
| 876 | ** example: Report only lists rows with status not open |
| 877 | ** TICKETFILTER: status != 'open' |
| 878 | ** If the option -q|--quote is used, the tickets are encoded by |
| 879 | ** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, |
| 880 | ** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). |
| 881 | ** Otherwise, the simplified encoding as on the show report raw |
| 882 | ** page in the gui is used. This has no effect in JSON mode. |
| 883 | ** |
| 884 | ** Instead of the report title its possible to use the report |
| 885 | ** number. Using the special report number 0 list all columns, |
| 886 | ** defined in the ticket table. |
| 887 | ** |
| 888 | ** %fossil ticket list fields |
| 889 | ** |
| 890 | ** list all fields, defined for ticket in the fossil repository |
| 891 | ** |
| 892 | ** %fossil ticket list reports |
| 893 | ** |
| 894 | ** list all ticket reports, defined in the fossil repository |
| 895 | ** |
| 896 | ** %fossil ticket set TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 897 | ** %fossil ticket change TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 898 | ** |
| 899 | ** change ticket identified by TICKETUUID and set the value of |
| 900 | ** field FIELD to VALUE. Valid field descriptions are: |
| 901 | ** status, type, severity, priority, resolution, |
| 902 | ** foundin, private_contact, resolution, title or comment |
| 903 | ** Field names given above are the ones, defined in a standard |
| 904 | ** fossil environment. If you have added, deleted columns, you |
| 905 | ** change the all your configured columns. |
| 906 | ** If you use +FIELD, the VALUE Is appended to the field FIELD. |
| 907 | ** You can use more than one field/value pair on the commandline. |
| 908 | ** Using -q|--quote enables the special character decoding as |
| 909 | ** in "ticket show". So it's possible, to set multiline text or |
| 910 | ** text with special characters. |
| 911 | ** |
| 912 | ** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 913 | ** |
| 914 | ** like set, but create a new ticket with the given values. |
| 915 | ** |
| 916 | ** %fossil ticket history TICKETUUID |
| 917 | ** |
| 918 | ** Show the complete change history for the ticket |
| 919 | ** |
| 920 | ** The values in set|add are not validated against the definitions |
| 921 | ** given in "Ticket Common Script". |
| 922 | */ |
| 923 | void ticket_cmd(void){ |
| 924 | int n; |
| 925 | |
| 926 | /* do some ints, we want to be inside a checkout */ |
| 927 | db_find_and_open_repository(0, 0); |
| 928 | user_select(); |
| 929 | /* |
| 930 | ** Check that the user exists. |
| 931 | */ |
| 932 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 933 | fossil_fatal("no such user: %s", g.zLogin); |
| 934 | } |
| 935 | |
| 936 | if( g.argc<3 ){ |
| 937 | usage("add|fieldlist|set|show|history"); |
| 938 | }else{ |
| 939 | n = strlen(g.argv[2]); |
| 940 | if( n==1 && g.argv[2][0]=='s' ){ |
| 941 | /* set/show cannot be distinguished, so show the usage */ |
| 942 | usage("add|fieldlist|set|show|history"); |
| 943 | }else if( strncmp(g.argv[2],"list",n)==0 ){ |
| 944 | if( g.argc==3 ){ |
| 945 | usage("list fields|reports"); |
| 946 | }else{ |
| 947 | n = strlen(g.argv[3]); |
| 948 | if( !strncmp(g.argv[3],"fields",n) ){ |
| 949 | /* simply show all field names */ |
| 950 | int i; |
| 951 | |
| 952 | /* read all available ticket fields */ |
| 953 | getAllTicketFields(); |
| 954 | for(i=0; i<nField; i++){ |
| 955 | printf("%s\n",azField[i]); |
| 956 | } |
| 957 | }else if( !strncmp(g.argv[3],"reports",n) ){ |
| 958 | rpt_list_reports(); |
| 959 | }else{ |
| 960 | fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); |
| 961 | } |
| 962 | } |
| 963 | }else{ |
| 964 | /* add a new ticket or set fields on existing tickets */ |
| 965 | tTktShowEncoding tktEncoding; |
| 966 | |
| 967 | tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; |
| 968 | |
| 969 | if( strncmp(g.argv[2],"show",n)==0 ){ |
| 970 | if( g.argc==3 ){ |
| 971 | usage("show REPORTNR"); |
| 972 | }else{ |
| 973 | const char *zRep = 0; |
| 974 | const char *zSep = 0; |
| 975 | const char *zFilterUuid = 0; |
| 976 | zSep = find_option("limit","l",1); |
| 977 | zRep = g.argv[3]; |
| 978 | if( !strcmp(zRep,"0") ){ |
| 979 | zRep = 0; |
| 980 | } |
| 981 | if( g.argc>4 ){ |
| 982 | zFilterUuid = g.argv[4]; |
| 983 | } |
| 984 | rptshow( zRep, zSep, zFilterUuid, tktEncoding ); |
| 985 | } |
| 986 | }else{ |
| 987 | /* add a new ticket or update an existing ticket */ |
| 988 | enum { set,add,history,err } eCmd = err; |
| 989 | int i = 0; |
| 990 | int rid; |
| 991 | const char *zTktUuid = 0; |
| 992 | Blob tktchng, cksum; |
| 993 | |
| 994 | /* get command type (set/add) and get uuid, if needed for set */ |
| 995 | if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || |
| 996 | strncmp(g.argv[2],"history",n)==0 ){ |
| 997 | if( strncmp(g.argv[2],"history",n)==0 ){ |
| 998 | eCmd = history; |
| 999 | }else{ |
| 1000 | eCmd = set; |
| 1001 | } |
| 1002 | if( g.argc==3 ){ |
| 1003 | usage("set TICKETUUID"); |
| 1004 | } |
| 1005 | zTktUuid = db_text(0, |
| 1006 | "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] |
| 1007 | ); |
| 1008 | if( !zTktUuid ){ |
| 1009 | fossil_fatal("unknown ticket: '%s'!",g.argv[3]); |
| 1010 | } |
| 1011 | i=4; |
| 1012 | }else if( strncmp(g.argv[2],"add",n)==0 ){ |
| 1013 | eCmd = add; |
| 1014 | i = 3; |
| 1015 | zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 1016 | } |
| 1017 | /* none of set/add, so show the usage! */ |
| 1018 | if( eCmd==err ){ |
| 1019 | usage("add|fieldlist|set|show|history"); |
| 1020 | } |
| 1021 | |
| 1022 | /* we just handle history separately here, does not get out */ |
| 1023 | if( eCmd==history ){ |
| 1024 | Stmt q; |
| 1025 | int tagid; |
| 1026 | |
| 1027 | if ( i != g.argc ){ |
| 1028 | fossil_fatal("no other parameters expected to %s!",g.argv[2]); |
| 1029 | } |
| 1030 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid); |
| 1031 | if( tagid==0 ){ |
| 1032 | fossil_fatal("no such ticket %h", zTktUuid); |
| 1033 | } |
| 1034 | db_prepare(&q, |
| 1035 | "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" |
| 1036 | " FROM event, blob" |
| 1037 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 1038 | " AND blob.rid=event.objid" |
| 1039 | " UNION " |
| 1040 | "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" |
| 1041 | " FROM attachment, blob" |
| 1042 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 1043 | " AND blob.rid=attachid" |
| 1044 | " ORDER BY 1 DESC", |
| 1045 | tagid, tagid |
| 1046 | ); |
| 1047 | while( db_step(&q)==SQLITE_ROW ){ |
| 1048 | Manifest *pTicket; |
| 1049 | char zShort[12]; |
| 1050 | const char *zDate = db_column_text(&q, 0); |
| 1051 | int rid = db_column_int(&q, 1); |
| 1052 | const char *zChngUuid = db_column_text(&q, 2); |
| 1053 | const char *zFile = db_column_text(&q, 4); |
| 1054 | memcpy(zShort, zChngUuid, 10); |
| 1055 | zShort[10] = 0; |
| 1056 | if( zFile!=0 ){ |
| 1057 | const char *zSrc = db_column_text(&q, 3); |
| 1058 | const char *zUser = db_column_text(&q, 5); |
| 1059 | if( zSrc==0 || zSrc[0]==0 ){ |
| 1060 | fossil_print("Delete attachment %h\n", zFile); |
| 1061 | }else{ |
| 1062 | fossil_print("Add attachment %h\n", zFile); |
| 1063 | } |
| 1064 | fossil_print(" by %h on %h\n", zUser, zDate); |
| 1065 | }else{ |
| 1066 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 1067 | if( pTicket ){ |
| 1068 | int i; |
| 1069 | |
| 1070 | fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate); |
| 1071 | for(i=0; i<pTicket->nField; i++){ |
| 1072 | Blob val; |
| 1073 | const char *z; |
| 1074 | z = pTicket->aField[i].zName; |
| 1075 | blob_set(&val, pTicket->aField[i].zValue); |
| 1076 | if( z[0]=='+' ){ |
| 1077 | fossil_print(" Append to "); |
| 1078 | z++; |
| 1079 | }else{ |
| 1080 | fossil_print(" Change "); |
| 1081 | } |
| 1082 | fossil_print("%h: ",z); |
| 1083 | if( blob_size(&val)>50 || contains_newline(&val)) { |
| 1084 | fossil_print("\n ",blob_str(&val)); |
| 1085 | comment_print(blob_str(&val),4,79); |
| 1086 | }else{ |
| 1087 | fossil_print("%s\n",blob_str(&val)); |
| 1088 | } |
| 1089 | blob_reset(&val); |
| 1090 | } |
| 1091 | } |
| 1092 | manifest_destroy(pTicket); |
| 1093 | } |
| 1094 | } |
| 1095 | db_finalize(&q); |
| 1096 | return; |
| 1097 | } |
| 1098 | /* read all given ticket field/value pairs from command line */ |
| 1099 | if( i==g.argc ){ |
| 1100 | fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 1101 | } |
| 1102 | getAllTicketFields(); |
| 1103 | /* read commandline and assign fields in the azValue array */ |
| 1104 | while( i<g.argc ){ |
| 1105 | char *zFName; |
| 1106 | char *zFValue; |
| 1107 | int j; |
| 1108 | int append = 0; |
| 1109 | |
| 1110 | zFName = g.argv[i++]; |
| 1111 | if( i==g.argc ){ |
| 1112 | fossil_fatal("missing value for '%s'!",zFName); |
| 1113 | } |
| 1114 | zFValue = g.argv[i++]; |
| 1115 | if( tktEncoding == tktFossilize ){ |
| 1116 | zFValue=mprintf("%s",zFValue); |
| 1117 | defossilize(zFValue); |
| 1118 | } |
| 1119 | append = (zFName[0] == '+'); |
| 1120 | if (append){ |
| 1121 | zFName++; |
| 1122 | } |
| 1123 | j = fieldId(zFName); |
| 1124 | if( j == -1 ){ |
| 1125 | fossil_fatal("unknown field name '%s'!",zFName); |
| 1126 | }else{ |
| 1127 | if (append) { |
| 1128 | azAppend[j] = zFValue; |
| 1129 | } else { |
| 1130 | azValue[j] = zFValue; |
| 1131 | } |
| 1132 | } |
| 1133 | } |
| 1134 | |
| 1135 | /* now add the needed artifacts to the repository */ |
| 1136 | blob_zero(&tktchng); |
| 1137 | { /* add the time to the ticket manifest */ |
| 1138 | char *zDate; |
| 1139 | |
| 1140 | zDate = date_in_standard_format("now"); |
| 1141 | blob_appendf(&tktchng, "D %s\n", zDate); |
| 1142 | free(zDate); |
| 1143 | } |
| 1144 | /* append defined elements */ |
| 1145 | for(i=0; i<nField; i++){ |
| 1146 | char *zValue = 0; |
| 1147 | char *zPfx; |
| 1148 | |
| 1149 | if (azAppend[i] && azAppend[i][0] ){ |
| 1150 | zPfx = " +"; |
| 1151 | zValue = azAppend[i]; |
| 1152 | } else if( azValue[i] && azValue[i][0] ){ |
| 1153 | zPfx = " "; |
| 1154 | zValue = azValue[i]; |
| 1155 | } else { |
| 1156 | continue; |
| 1157 | } |
| 1158 | if( strncmp(azField[i], "private_", 8)==0 ){ |
| 1159 | zValue = db_conceal(zValue, strlen(zValue)); |
| 1160 | blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue); |
| 1161 | }else{ |
| 1162 | blob_appendf(&tktchng, "J%s%s %#F\n", zPfx, |
| 1163 | azField[i], strlen(zValue), zValue); |
| 1164 | } |
| 1165 | if( tktEncoding == tktFossilize ){ |
| 1166 | free(azValue[i]); |
| 1167 | } |
| 1168 | } |
| 1169 | blob_appendf(&tktchng, "K %s\n", zTktUuid); |
| 1170 | blob_appendf(&tktchng, "U %F\n", g.zLogin); |
| 1171 | md5sum_blob(&tktchng, &cksum); |
| 1172 | blob_appendf(&tktchng, "Z %b\n", &cksum); |
| 1173 | rid = content_put(&tktchng); |
| 1174 | if( rid==0 ){ |
| 1175 | fossil_panic("trouble committing ticket: %s", g.zErrMsg); |
| 1176 | } |
| 1177 | manifest_crosslink_begin(); |
| 1178 | manifest_crosslink(rid, &tktchng); |
| 1179 | manifest_crosslink_end(); |
| 1180 | assert( blob_is_reset(&tktchng) ); |
| 1181 | printf("ticket %s succeeded for UID %s\n", |
| 1182 | (eCmd==set?"set":"add"),zTktUuid); |
| 1183 | } |
| 1184 | } |
| 1185 | } |
| 1186 | } |
| 1187 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -855,332 +855,337 @@ | |
| 855 | ** COMMAND: ticket* |
| 856 | ** Usage: %fossil ticket SUBCOMMAND ... |
| 857 | ** |
| 858 | ** Run various subcommands to control tickets |
| 859 | ** |
| 860 | ** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options? |
| 861 | ** |
| 862 | ** options can be: |
| 863 | ** ?-l|--limit LIMITCHAR? |
| 864 | ** ?-q|--quote? |
| 865 | ** ?-R|--repository FILE? |
| 866 | ** |
| 867 | ** Run the ticket report, identified by the report format title |
| 868 | ** used in the gui. The data is written as flat file on stdout, |
| 869 | ** using "," as separator. The separator "," can be changed using |
| 870 | ** the -l or --limit option. |
| 871 | ** |
| 872 | ** If TICKETFILTER is given on the commandline, the query is |
| 873 | ** limited with a new WHERE-condition. |
| 874 | ** example: Report lists a column # with the uuid |
| 875 | ** TICKETFILTER may be [#]='uuuuuuuuu' |
| 876 | ** example: Report only lists rows with status not open |
| 877 | ** TICKETFILTER: status != 'open' |
| 878 | ** If the option -q|--quote is used, the tickets are encoded by |
| 879 | ** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, |
| 880 | ** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). |
| 881 | ** Otherwise, the simplified encoding as on the show report raw |
| 882 | ** page in the gui is used. This has no effect in JSON mode. |
| 883 | ** |
| 884 | ** Instead of the report title its possible to use the report |
| 885 | ** number. Using the special report number 0 list all columns, |
| 886 | ** defined in the ticket table. |
| 887 | ** |
| 888 | ** %fossil ticket list fields |
| 889 | ** |
| 890 | ** list all fields, defined for ticket in the fossil repository |
| 891 | ** |
| 892 | ** %fossil ticket list reports |
| 893 | ** |
| 894 | ** list all ticket reports, defined in the fossil repository |
| 895 | ** |
| 896 | ** %fossil ticket set TICKETUUID (FIELD VALUE)+ ?-q|--quote? |
| 897 | ** %fossil ticket change TICKETUUID (FIELD VALUE)+ ?-q|--quote? |
| 898 | ** |
| 899 | ** change ticket identified by TICKETUUID and set the value of |
| 900 | ** field FIELD to VALUE. |
| 901 | ** |
| 902 | ** Field names as defined in the TICKET table. By default, these |
| 903 | ** names include: type, status, subsystem, priority, severity, foundin, |
| 904 | ** resolution, title, and comment, but other field names can be added |
| 905 | ** or substituted in customized installations. |
| 906 | ** |
| 907 | ** If you use +FIELD, the VALUE Is appended to the field FIELD. |
| 908 | ** You can use more than one field/value pair on the commandline. |
| 909 | ** Using -q|--quote enables the special character decoding as |
| 910 | ** in "ticket show". So it's possible, to set multiline text or |
| 911 | ** text with special characters. |
| 912 | ** |
| 913 | ** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 914 | ** |
| 915 | ** like set, but create a new ticket with the given values. |
| 916 | ** |
| 917 | ** %fossil ticket history TICKETUUID |
| 918 | ** |
| 919 | ** Show the complete change history for the ticket |
| 920 | ** |
| 921 | ** The values in set|add are not validated against the definitions |
| 922 | ** given in "Ticket Common Script". |
| 923 | */ |
| 924 | void ticket_cmd(void){ |
| 925 | int n; |
| 926 | const char *zUser; |
| 927 | const char *zDate; |
| 928 | |
| 929 | /* do some ints, we want to be inside a checkout */ |
| 930 | db_find_and_open_repository(0, 0); |
| 931 | user_select(); |
| 932 | |
| 933 | zUser = find_option("user-override",0,1); |
| 934 | if( zUser==0 ) zUser = g.zLogin; |
| 935 | zDate = find_option("date-override",0,1); |
| 936 | if( zDate==0 ) zDate = "now"; |
| 937 | zDate = date_in_standard_format(zDate); |
| 938 | |
| 939 | /* |
| 940 | ** Check that the user exists. |
| 941 | */ |
| 942 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", zUser) ){ |
| 943 | fossil_fatal("no such user: %s", zUser); |
| 944 | } |
| 945 | |
| 946 | if( g.argc<3 ){ |
| 947 | usage("add|fieldlist|set|show|history"); |
| 948 | } |
| 949 | n = strlen(g.argv[2]); |
| 950 | if( n==1 && g.argv[2][0]=='s' ){ |
| 951 | /* set/show cannot be distinguished, so show the usage */ |
| 952 | usage("add|fieldlist|set|show|history"); |
| 953 | } |
| 954 | if( strncmp(g.argv[2],"list",n)==0 ){ |
| 955 | if( g.argc==3 ){ |
| 956 | usage("list fields|reports"); |
| 957 | }else{ |
| 958 | n = strlen(g.argv[3]); |
| 959 | if( !strncmp(g.argv[3],"fields",n) ){ |
| 960 | /* simply show all field names */ |
| 961 | int i; |
| 962 | |
| 963 | /* read all available ticket fields */ |
| 964 | getAllTicketFields(); |
| 965 | for(i=0; i<nField; i++){ |
| 966 | printf("%s\n",azField[i]); |
| 967 | } |
| 968 | }else if( !strncmp(g.argv[3],"reports",n) ){ |
| 969 | rpt_list_reports(); |
| 970 | }else{ |
| 971 | fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); |
| 972 | } |
| 973 | } |
| 974 | }else{ |
| 975 | /* add a new ticket or set fields on existing tickets */ |
| 976 | tTktShowEncoding tktEncoding; |
| 977 | |
| 978 | tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; |
| 979 | |
| 980 | if( strncmp(g.argv[2],"show",n)==0 ){ |
| 981 | if( g.argc==3 ){ |
| 982 | usage("show REPORTNR"); |
| 983 | }else{ |
| 984 | const char *zRep = 0; |
| 985 | const char *zSep = 0; |
| 986 | const char *zFilterUuid = 0; |
| 987 | zSep = find_option("limit","l",1); |
| 988 | zRep = g.argv[3]; |
| 989 | if( !strcmp(zRep,"0") ){ |
| 990 | zRep = 0; |
| 991 | } |
| 992 | if( g.argc>4 ){ |
| 993 | zFilterUuid = g.argv[4]; |
| 994 | } |
| 995 | rptshow( zRep, zSep, zFilterUuid, tktEncoding ); |
| 996 | } |
| 997 | }else{ |
| 998 | /* add a new ticket or update an existing ticket */ |
| 999 | enum { set,add,history,err } eCmd = err; |
| 1000 | int i = 0; |
| 1001 | int rid; |
| 1002 | const char *zTktUuid = 0; |
| 1003 | Blob tktchng, cksum; |
| 1004 | |
| 1005 | /* get command type (set/add) and get uuid, if needed for set */ |
| 1006 | if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || |
| 1007 | strncmp(g.argv[2],"history",n)==0 ){ |
| 1008 | if( strncmp(g.argv[2],"history",n)==0 ){ |
| 1009 | eCmd = history; |
| 1010 | }else{ |
| 1011 | eCmd = set; |
| 1012 | } |
| 1013 | if( g.argc==3 ){ |
| 1014 | usage("set TICKETUUID"); |
| 1015 | } |
| 1016 | zTktUuid = db_text(0, |
| 1017 | "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] |
| 1018 | ); |
| 1019 | if( !zTktUuid ){ |
| 1020 | fossil_fatal("unknown ticket: '%s'!",g.argv[3]); |
| 1021 | } |
| 1022 | i=4; |
| 1023 | }else if( strncmp(g.argv[2],"add",n)==0 ){ |
| 1024 | eCmd = add; |
| 1025 | i = 3; |
| 1026 | zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 1027 | } |
| 1028 | /* none of set/add, so show the usage! */ |
| 1029 | if( eCmd==err ){ |
| 1030 | usage("add|fieldlist|set|show|history"); |
| 1031 | } |
| 1032 | |
| 1033 | /* we just handle history separately here, does not get out */ |
| 1034 | if( eCmd==history ){ |
| 1035 | Stmt q; |
| 1036 | int tagid; |
| 1037 | |
| 1038 | if ( i != g.argc ){ |
| 1039 | fossil_fatal("no other parameters expected to %s!",g.argv[2]); |
| 1040 | } |
| 1041 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid); |
| 1042 | if( tagid==0 ){ |
| 1043 | fossil_fatal("no such ticket %h", zTktUuid); |
| 1044 | } |
| 1045 | db_prepare(&q, |
| 1046 | "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" |
| 1047 | " FROM event, blob" |
| 1048 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 1049 | " AND blob.rid=event.objid" |
| 1050 | " UNION " |
| 1051 | "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" |
| 1052 | " FROM attachment, blob" |
| 1053 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 1054 | " AND blob.rid=attachid" |
| 1055 | " ORDER BY 1 DESC", |
| 1056 | tagid, tagid |
| 1057 | ); |
| 1058 | while( db_step(&q)==SQLITE_ROW ){ |
| 1059 | Manifest *pTicket; |
| 1060 | char zShort[12]; |
| 1061 | const char *zDate = db_column_text(&q, 0); |
| 1062 | int rid = db_column_int(&q, 1); |
| 1063 | const char *zChngUuid = db_column_text(&q, 2); |
| 1064 | const char *zFile = db_column_text(&q, 4); |
| 1065 | memcpy(zShort, zChngUuid, 10); |
| 1066 | zShort[10] = 0; |
| 1067 | if( zFile!=0 ){ |
| 1068 | const char *zSrc = db_column_text(&q, 3); |
| 1069 | const char *zUser = db_column_text(&q, 5); |
| 1070 | if( zSrc==0 || zSrc[0]==0 ){ |
| 1071 | fossil_print("Delete attachment %h\n", zFile); |
| 1072 | }else{ |
| 1073 | fossil_print("Add attachment %h\n", zFile); |
| 1074 | } |
| 1075 | fossil_print(" by %h on %h\n", zUser, zDate); |
| 1076 | }else{ |
| 1077 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 1078 | if( pTicket ){ |
| 1079 | int i; |
| 1080 | |
| 1081 | fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate); |
| 1082 | for(i=0; i<pTicket->nField; i++){ |
| 1083 | Blob val; |
| 1084 | const char *z; |
| 1085 | z = pTicket->aField[i].zName; |
| 1086 | blob_set(&val, pTicket->aField[i].zValue); |
| 1087 | if( z[0]=='+' ){ |
| 1088 | fossil_print(" Append to "); |
| 1089 | z++; |
| 1090 | }else{ |
| 1091 | fossil_print(" Change "); |
| 1092 | } |
| 1093 | fossil_print("%h: ",z); |
| 1094 | if( blob_size(&val)>50 || contains_newline(&val)) { |
| 1095 | fossil_print("\n ",blob_str(&val)); |
| 1096 | comment_print(blob_str(&val),4,79); |
| 1097 | }else{ |
| 1098 | fossil_print("%s\n",blob_str(&val)); |
| 1099 | } |
| 1100 | blob_reset(&val); |
| 1101 | } |
| 1102 | } |
| 1103 | manifest_destroy(pTicket); |
| 1104 | } |
| 1105 | } |
| 1106 | db_finalize(&q); |
| 1107 | return; |
| 1108 | } |
| 1109 | /* read all given ticket field/value pairs from command line */ |
| 1110 | if( i==g.argc ){ |
| 1111 | fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 1112 | } |
| 1113 | getAllTicketFields(); |
| 1114 | /* read commandline and assign fields in the azValue array */ |
| 1115 | while( i<g.argc ){ |
| 1116 | char *zFName; |
| 1117 | char *zFValue; |
| 1118 | int j; |
| 1119 | int append = 0; |
| 1120 | |
| 1121 | zFName = g.argv[i++]; |
| 1122 | if( i==g.argc ){ |
| 1123 | fossil_fatal("missing value for '%s'!",zFName); |
| 1124 | } |
| 1125 | zFValue = g.argv[i++]; |
| 1126 | if( tktEncoding == tktFossilize ){ |
| 1127 | zFValue=mprintf("%s",zFValue); |
| 1128 | defossilize(zFValue); |
| 1129 | } |
| 1130 | append = (zFName[0] == '+'); |
| 1131 | if (append){ |
| 1132 | zFName++; |
| 1133 | } |
| 1134 | j = fieldId(zFName); |
| 1135 | if( j == -1 ){ |
| 1136 | fossil_fatal("unknown field name '%s'!",zFName); |
| 1137 | }else{ |
| 1138 | if (append) { |
| 1139 | azAppend[j] = zFValue; |
| 1140 | } else { |
| 1141 | azValue[j] = zFValue; |
| 1142 | } |
| 1143 | } |
| 1144 | } |
| 1145 | |
| 1146 | /* now add the needed artifacts to the repository */ |
| 1147 | blob_zero(&tktchng); |
| 1148 | /* add the time to the ticket manifest */ |
| 1149 | blob_appendf(&tktchng, "D %s\n", zDate); |
| 1150 | /* append defined elements */ |
| 1151 | for(i=0; i<nField; i++){ |
| 1152 | char *zValue = 0; |
| 1153 | char *zPfx; |
| 1154 | |
| 1155 | if (azAppend[i] && azAppend[i][0] ){ |
| 1156 | zPfx = " +"; |
| 1157 | zValue = azAppend[i]; |
| 1158 | } else if( azValue[i] && azValue[i][0] ){ |
| 1159 | zPfx = " "; |
| 1160 | zValue = azValue[i]; |
| 1161 | } else { |
| 1162 | continue; |
| 1163 | } |
| 1164 | if( strncmp(azField[i], "private_", 8)==0 ){ |
| 1165 | zValue = db_conceal(zValue, strlen(zValue)); |
| 1166 | blob_appendf(&tktchng, "J%s%s %s\n", zPfx, azField[i], zValue); |
| 1167 | }else{ |
| 1168 | blob_appendf(&tktchng, "J%s%s %#F\n", zPfx, |
| 1169 | azField[i], strlen(zValue), zValue); |
| 1170 | } |
| 1171 | if( tktEncoding == tktFossilize ){ |
| 1172 | free(azValue[i]); |
| 1173 | } |
| 1174 | } |
| 1175 | blob_appendf(&tktchng, "K %s\n", zTktUuid); |
| 1176 | blob_appendf(&tktchng, "U %F\n", zUser); |
| 1177 | md5sum_blob(&tktchng, &cksum); |
| 1178 | blob_appendf(&tktchng, "Z %b\n", &cksum); |
| 1179 | rid = content_put(&tktchng); |
| 1180 | if( rid==0 ){ |
| 1181 | fossil_panic("trouble committing ticket: %s", g.zErrMsg); |
| 1182 | } |
| 1183 | manifest_crosslink_begin(); |
| 1184 | manifest_crosslink(rid, &tktchng); |
| 1185 | manifest_crosslink_end(); |
| 1186 | assert( blob_is_reset(&tktchng) ); |
| 1187 | printf("ticket %s succeeded for %s\n", |
| 1188 | (eCmd==set?"set":"add"),zTktUuid); |
| 1189 | } |
| 1190 | } |
| 1191 | } |
| 1192 |