| | @@ -830,5 +830,239 @@ |
| 830 | 830 | } |
| 831 | 831 | blob_reset(&val); |
| 832 | 832 | } |
| 833 | 833 | @ </ol> |
| 834 | 834 | } |
| 835 | + |
| 836 | +/* |
| 837 | +** COMMAND: ticket |
| 838 | +** Usage: %fossil ticket SUBCOMMAND ... |
| 839 | +** |
| 840 | +** Run various subcommands to control tickets |
| 841 | +** |
| 842 | +** %fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?options? |
| 843 | +** |
| 844 | +** options can be: |
| 845 | +** ?-l|--limit LIMITCHAR? |
| 846 | +** ?-q|--quote? |
| 847 | +** |
| 848 | +** Run the ticket report, identified by the report format title |
| 849 | +** used in the gui. The data is written as flat file on stdout, |
| 850 | +** using "," as separator. The seperator "," can be changed using |
| 851 | +** the -l or --limit option. |
| 852 | +** If TICKETFILTER is given on the commandline, the query is |
| 853 | +** limited with a new WHERE-condition. |
| 854 | +** example: Report lists a column # with the uuid |
| 855 | +** TICKETFILTER may be [#]='uuuuuuuuu' |
| 856 | +** example: Report only lists rows with status not open |
| 857 | +** TICKETFILTER: status != 'open' |
| 858 | +** If the option -q|--quote is used, the tickets are encoded by |
| 859 | +** quoting special chars(space -> \\s, tab -> \\t, newline -> \\n, |
| 860 | +** cr -> \\r, formfeed -> \\f, vtab -> \\v, nul -> \\0, \\ -> \\\\). |
| 861 | +** Otherwise, the simplified encoding as on the show report raw |
| 862 | +** page in the gui is used. |
| 863 | +** |
| 864 | +** Instead of the report title its possible to use the report |
| 865 | +** number. Using the special report number 0 list all columns, |
| 866 | +** defined in the ticket table. |
| 867 | +** |
| 868 | +** %fossil ticket list fields |
| 869 | +** |
| 870 | +** list all fields, defined for ticket in the fossil repository |
| 871 | +** |
| 872 | +** %fossil ticket list reports |
| 873 | +** |
| 874 | +** list all ticket reports, defined in the fossil repository |
| 875 | +** |
| 876 | +** %fossil ticket set TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 877 | +** %fossil ticket change TICKETUUID FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 878 | +** |
| 879 | +** change ticket identified by TICKETUUID and set the value of |
| 880 | +** field FIELD to VALUE. Valid field descriptions are: |
| 881 | +** status, type, severity, priority, resolution, |
| 882 | +** foundin, private_contact, resolution, title or comment |
| 883 | +** Field names given above are the ones, defined in a standard |
| 884 | +** fossil environment. If you have added, deleted columns, you |
| 885 | +** change the all your configured columns. |
| 886 | +** You can use more than one field/value pair on the commandline. |
| 887 | +** Using -q|--quote enables the special character decoding as |
| 888 | +** in "ticket show". So it's possible, to set multiline text or |
| 889 | +** text with special characters. |
| 890 | +** |
| 891 | +** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 892 | +** |
| 893 | +** like set, but create a new ticket with the given values. |
| 894 | +** |
| 895 | +** The values in set|add are not validated against the definitions |
| 896 | +** given in "Ticket Common Script". |
| 897 | +*/ |
| 898 | +void ticket_cmd(void){ |
| 899 | + int n; |
| 900 | + |
| 901 | + /* do some ints, we want to be inside a checkout */ |
| 902 | + db_must_be_within_tree(); |
| 903 | + db_find_and_open_repository(1); |
| 904 | + user_select(); |
| 905 | + /* |
| 906 | + ** Check that the user exists. |
| 907 | + */ |
| 908 | + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 909 | + fossil_fatal("no such user: %s", g.zLogin); |
| 910 | + } |
| 911 | + |
| 912 | + if( g.argc<3 ){ |
| 913 | + usage("add|fieldlist|set|show"); |
| 914 | + }else{ |
| 915 | + n = strlen(g.argv[2]); |
| 916 | + if( n==1 && g.argv[2][0]=='s' ){ |
| 917 | + /* set/show cannot be distinguished, so show the usage */ |
| 918 | + usage("add|fieldlist|set|show"); |
| 919 | + }else if( strncmp(g.argv[2],"list",n)==0 ){ |
| 920 | + if( g.argc==3 ){ |
| 921 | + usage("list fields|reports"); |
| 922 | + }else{ |
| 923 | + n = strlen(g.argv[3]); |
| 924 | + if( !strncmp(g.argv[3],"fields",n) ){ |
| 925 | + /* simply show all field names */ |
| 926 | + int i; |
| 927 | + |
| 928 | + /* read all available ticket fields */ |
| 929 | + getAllTicketFields(); |
| 930 | + for(i=0; i<nField; i++){ |
| 931 | + printf("%s\n",azField[i]); |
| 932 | + } |
| 933 | + }else if( !strncmp(g.argv[3],"reports",n) ){ |
| 934 | + rpt_list_reports(); |
| 935 | + }else{ |
| 936 | + fossil_fatal("unknown ticket list option '%s'!",g.argv[3]); |
| 937 | + } |
| 938 | + } |
| 939 | + }else{ |
| 940 | + /* add a new ticket or set fields on existing tickets */ |
| 941 | + tTktShowEncoding tktEncoding; |
| 942 | + |
| 943 | + tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab; |
| 944 | + |
| 945 | + if( strncmp(g.argv[2],"show",n)==0 ){ |
| 946 | + if( g.argc==3 ){ |
| 947 | + usage("show REPORTNR"); |
| 948 | + }else{ |
| 949 | + const char *zRep = 0; |
| 950 | + const char *zSep = 0; |
| 951 | + const char *zFilterUuid = 0; |
| 952 | + |
| 953 | + zSep = find_option("limit","l",1); |
| 954 | + zRep = g.argv[3]; |
| 955 | + if( !strcmp(zRep,"0") ){ |
| 956 | + zRep = 0; |
| 957 | + } |
| 958 | + if( g.argc>4 ){ |
| 959 | + zFilterUuid = g.argv[4]; |
| 960 | + } |
| 961 | + |
| 962 | + rptshow( zRep, zSep, zFilterUuid, tktEncoding ); |
| 963 | + |
| 964 | + } |
| 965 | + }else{ |
| 966 | + /* add a new ticket or update an existing ticket */ |
| 967 | + enum { set,add,err } eCmd = err; |
| 968 | + int i = 0; |
| 969 | + int rid; |
| 970 | + const char *zTktUuid = 0; |
| 971 | + Blob tktchng, cksum; |
| 972 | + |
| 973 | + /* get command type (set/add) and get uuid, if needed for set */ |
| 974 | + if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 ){ |
| 975 | + eCmd = set; |
| 976 | + if( g.argc==3 ){ |
| 977 | + usage("set TICKETUUID"); |
| 978 | + } |
| 979 | + zTktUuid = db_text(0, |
| 980 | + "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] |
| 981 | + ); |
| 982 | + if( !zTktUuid ){ |
| 983 | + fossil_fatal("unknown ticket: '%s'!",g.argv[3]); |
| 984 | + } |
| 985 | + i=4; |
| 986 | + }else if( strncmp(g.argv[2],"add",n)==0 ){ |
| 987 | + eCmd = add; |
| 988 | + i = 3; |
| 989 | + zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 990 | + } |
| 991 | + /* none of set/add, so show the usage! */ |
| 992 | + if( eCmd==err ){ |
| 993 | + usage("add|fieldlist|set|show"); |
| 994 | + } |
| 995 | + |
| 996 | + /* read all given ticket field/value pairs from command line */ |
| 997 | + if( i==g.argc ){ |
| 998 | + fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 999 | + } |
| 1000 | + getAllTicketFields(); |
| 1001 | + /* read commandline and assign fields in the azValue array */ |
| 1002 | + while( i<g.argc ){ |
| 1003 | + char *zFName; |
| 1004 | + char *zFValue; |
| 1005 | + int j; |
| 1006 | + |
| 1007 | + zFName = g.argv[i++]; |
| 1008 | + if( i==g.argc ){ |
| 1009 | + fossil_fatal("missing value for '%s'!",zFName); |
| 1010 | + } |
| 1011 | + zFValue = g.argv[i++]; |
| 1012 | + j = fieldId(zFName); |
| 1013 | + if( tktEncoding == tktFossilize ){ |
| 1014 | + zFValue=mprintf("%s",zFValue); |
| 1015 | + defossilize(zFValue); |
| 1016 | + } |
| 1017 | + if( j == -1 ){ |
| 1018 | + fossil_fatal("unknown field name '%s'!",zFName); |
| 1019 | + }else{ |
| 1020 | + azValue[j] = zFValue; |
| 1021 | + } |
| 1022 | + } |
| 1023 | + |
| 1024 | + /* now add the needed artifacts to the repository */ |
| 1025 | + blob_zero(&tktchng); |
| 1026 | + { /* add the time to the ticket manifest */ |
| 1027 | + char *zDate; |
| 1028 | + |
| 1029 | + zDate = db_text(0, "SELECT datetime('now')"); |
| 1030 | + zDate[10] = 'T'; |
| 1031 | + blob_appendf(&tktchng, "D %s\n", zDate); |
| 1032 | + free(zDate); |
| 1033 | + } |
| 1034 | + /* append defined elements */ |
| 1035 | + for(i=0; i<nField; i++){ |
| 1036 | + char *zValue; |
| 1037 | + |
| 1038 | + zValue = azValue[i]; |
| 1039 | + if( azValue[i] && azValue[i][0] ){ |
| 1040 | + if( strncmp(azField[i], "private_", 8)==0 ){ |
| 1041 | + zValue = db_conceal(zValue, strlen(zValue)); |
| 1042 | + blob_appendf(&tktchng, "J %s %s\n", azField[i], zValue); |
| 1043 | + }else{ |
| 1044 | + blob_appendf(&tktchng, "J %s %#F\n", |
| 1045 | + azField[i], strlen(zValue), zValue); |
| 1046 | + } |
| 1047 | + if( tktEncoding == tktFossilize ){ |
| 1048 | + free(azValue[i]); |
| 1049 | + } |
| 1050 | + } |
| 1051 | + } |
| 1052 | + blob_appendf(&tktchng, "K %s\n", zTktUuid); |
| 1053 | + blob_appendf(&tktchng, "U %F\n", g.zLogin); |
| 1054 | + md5sum_blob(&tktchng, &cksum); |
| 1055 | + blob_appendf(&tktchng, "Z %b\n", &cksum); |
| 1056 | + rid = content_put(&tktchng, 0, 0); |
| 1057 | + if( rid==0 ){ |
| 1058 | + fossil_panic("trouble committing ticket: %s", g.zErrMsg); |
| 1059 | + } |
| 1060 | + manifest_crosslink_begin(); |
| 1061 | + manifest_crosslink(rid, &tktchng); |
| 1062 | + manifest_crosslink_end(); |
| 1063 | + printf("ticket %s succeeded for UID %s\n", |
| 1064 | + (eCmd==set?"set":"add"),zTktUuid); |
| 1065 | + } |
| 1066 | + } |
| 1067 | + } |
| 1068 | +} |
| 835 | 1069 | |