Fossil SCM
Add ability to show ticket history from command line. fossil ticket now takes a new history option, which prints the history of a ticket - somewhat like what the history button does on the ticket web GUI.
Commit
73e363ea96424d3fd036e145bc6cce593621bba5
Parent
ffa3b1eaf6c0627…
1 file changed
+87
-7
+87
-7
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -899,10 +899,14 @@ | ||
| 899 | 899 | ** |
| 900 | 900 | ** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 901 | 901 | ** |
| 902 | 902 | ** like set, but create a new ticket with the given values. |
| 903 | 903 | ** |
| 904 | +** %fossil ticket history TICKETUUID | |
| 905 | +** | |
| 906 | +** Show the complete change history for the ticket | |
| 907 | +** | |
| 904 | 908 | ** The values in set|add are not validated against the definitions |
| 905 | 909 | ** given in "Ticket Common Script". |
| 906 | 910 | */ |
| 907 | 911 | void ticket_cmd(void){ |
| 908 | 912 | int n; |
| @@ -916,16 +920,16 @@ | ||
| 916 | 920 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 917 | 921 | fossil_fatal("no such user: %s", g.zLogin); |
| 918 | 922 | } |
| 919 | 923 | |
| 920 | 924 | if( g.argc<3 ){ |
| 921 | - usage("add|fieldlist|set|show"); | |
| 925 | + usage("add|fieldlist|set|show|history"); | |
| 922 | 926 | }else{ |
| 923 | 927 | n = strlen(g.argv[2]); |
| 924 | 928 | if( n==1 && g.argv[2][0]=='s' ){ |
| 925 | 929 | /* set/show cannot be distinguished, so show the usage */ |
| 926 | - usage("add|fieldlist|set|show"); | |
| 930 | + usage("add|fieldlist|set|show|history"); | |
| 927 | 931 | }else if( strncmp(g.argv[2],"list",n)==0 ){ |
| 928 | 932 | if( g.argc==3 ){ |
| 929 | 933 | usage("list fields|reports"); |
| 930 | 934 | }else{ |
| 931 | 935 | n = strlen(g.argv[3]); |
| @@ -970,19 +974,24 @@ | ||
| 970 | 974 | rptshow( zRep, zSep, zFilterUuid, tktEncoding ); |
| 971 | 975 | |
| 972 | 976 | } |
| 973 | 977 | }else{ |
| 974 | 978 | /* add a new ticket or update an existing ticket */ |
| 975 | - enum { set,add,err } eCmd = err; | |
| 979 | + enum { set,add,history,err } eCmd = err; | |
| 976 | 980 | int i = 0; |
| 977 | 981 | int rid; |
| 978 | 982 | const char *zTktUuid = 0; |
| 979 | 983 | Blob tktchng, cksum; |
| 980 | 984 | |
| 981 | 985 | /* get command type (set/add) and get uuid, if needed for set */ |
| 982 | - if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 ){ | |
| 983 | - eCmd = set; | |
| 986 | + if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || | |
| 987 | + strncmp(g.argv[2],"history",n)==0 ){ | |
| 988 | + if( strncmp(g.argv[2],"history",n)==0 ){ | |
| 989 | + eCmd = history; | |
| 990 | + }else{ | |
| 991 | + eCmd = set; | |
| 992 | + } | |
| 984 | 993 | if( g.argc==3 ){ |
| 985 | 994 | usage("set TICKETUUID"); |
| 986 | 995 | } |
| 987 | 996 | zTktUuid = db_text(0, |
| 988 | 997 | "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] |
| @@ -996,13 +1005,84 @@ | ||
| 996 | 1005 | i = 3; |
| 997 | 1006 | zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 998 | 1007 | } |
| 999 | 1008 | /* none of set/add, so show the usage! */ |
| 1000 | 1009 | if( eCmd==err ){ |
| 1001 | - usage("add|fieldlist|set|show"); | |
| 1010 | + usage("add|fieldlist|set|show|history"); | |
| 1002 | 1011 | } |
| 1003 | - | |
| 1012 | + | |
| 1013 | + /* we just handle history separately here, does not get out */ | |
| 1014 | + if( eCmd==history ){ | |
| 1015 | + Stmt q; | |
| 1016 | + char *zTitle; | |
| 1017 | + int tagid; | |
| 1018 | + | |
| 1019 | + if ( i != g.argc ){ | |
| 1020 | + fossil_fatal("no other parameters expected to %s!",g.argv[2]); | |
| 1021 | + } | |
| 1022 | + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid); | |
| 1023 | + if( tagid==0 ){ | |
| 1024 | + fossil_fatal("no such ticket %h", zTktUuid); | |
| 1025 | + } | |
| 1026 | + db_prepare(&q, | |
| 1027 | + "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" | |
| 1028 | + " FROM event, blob" | |
| 1029 | + " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" | |
| 1030 | + " AND blob.rid=event.objid" | |
| 1031 | + " UNION " | |
| 1032 | + "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" | |
| 1033 | + " FROM attachment, blob" | |
| 1034 | + " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" | |
| 1035 | + " AND blob.rid=attachid" | |
| 1036 | + " ORDER BY 1 DESC", | |
| 1037 | + tagid, tagid | |
| 1038 | + ); | |
| 1039 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 1040 | + Manifest *pTicket; | |
| 1041 | + char zShort[12]; | |
| 1042 | + const char *zDate = db_column_text(&q, 0); | |
| 1043 | + int rid = db_column_int(&q, 1); | |
| 1044 | + const char *zChngUuid = db_column_text(&q, 2); | |
| 1045 | + const char *zFile = db_column_text(&q, 4); | |
| 1046 | + memcpy(zShort, zChngUuid, 10); | |
| 1047 | + zShort[10] = 0; | |
| 1048 | + if( zFile!=0 ){ | |
| 1049 | + const char *zSrc = db_column_text(&q, 3); | |
| 1050 | + const char *zUser = db_column_text(&q, 5); | |
| 1051 | + if( zSrc==0 || zSrc[0]==0 ){ | |
| 1052 | + fossil_print("Delete attachment %h\n", zFile); | |
| 1053 | + }else{ | |
| 1054 | + fossil_print("Add attachment %h\n", zFile); | |
| 1055 | + } | |
| 1056 | + fossil_print(" by %h on %h\n", zUser, zDate); | |
| 1057 | + }else{ | |
| 1058 | + pTicket = manifest_get(rid, CFTYPE_TICKET); | |
| 1059 | + if( pTicket ){ | |
| 1060 | + int i; | |
| 1061 | + | |
| 1062 | + fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate); | |
| 1063 | + for(i=0; i<pTicket->nField; i++){ | |
| 1064 | + Blob val; | |
| 1065 | + const char *z; | |
| 1066 | + z = pTicket->aField[i].zName; | |
| 1067 | + blob_set(&val, pTicket->aField[i].zValue); | |
| 1068 | + if( z[0]=='+' ){ | |
| 1069 | + fossil_print(" Appended to %h:\n ",&z[1]); | |
| 1070 | + comment_print(blob_str(&val),7,79); | |
| 1071 | + }else{ | |
| 1072 | + fossil_print(" Change %h to:\n ",z); | |
| 1073 | + comment_print(blob_str(&val),7,79); | |
| 1074 | + } | |
| 1075 | + blob_reset(&val); | |
| 1076 | + } | |
| 1077 | + } | |
| 1078 | + manifest_destroy(pTicket); | |
| 1079 | + } | |
| 1080 | + } | |
| 1081 | + db_finalize(&q); | |
| 1082 | + return; | |
| 1083 | + } | |
| 1004 | 1084 | /* read all given ticket field/value pairs from command line */ |
| 1005 | 1085 | if( i==g.argc ){ |
| 1006 | 1086 | fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 1007 | 1087 | } |
| 1008 | 1088 | getAllTicketFields(); |
| 1009 | 1089 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -899,10 +899,14 @@ | |
| 899 | ** |
| 900 | ** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 901 | ** |
| 902 | ** like set, but create a new ticket with the given values. |
| 903 | ** |
| 904 | ** The values in set|add are not validated against the definitions |
| 905 | ** given in "Ticket Common Script". |
| 906 | */ |
| 907 | void ticket_cmd(void){ |
| 908 | int n; |
| @@ -916,16 +920,16 @@ | |
| 916 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 917 | fossil_fatal("no such user: %s", g.zLogin); |
| 918 | } |
| 919 | |
| 920 | if( g.argc<3 ){ |
| 921 | usage("add|fieldlist|set|show"); |
| 922 | }else{ |
| 923 | n = strlen(g.argv[2]); |
| 924 | if( n==1 && g.argv[2][0]=='s' ){ |
| 925 | /* set/show cannot be distinguished, so show the usage */ |
| 926 | usage("add|fieldlist|set|show"); |
| 927 | }else if( strncmp(g.argv[2],"list",n)==0 ){ |
| 928 | if( g.argc==3 ){ |
| 929 | usage("list fields|reports"); |
| 930 | }else{ |
| 931 | n = strlen(g.argv[3]); |
| @@ -970,19 +974,24 @@ | |
| 970 | rptshow( zRep, zSep, zFilterUuid, tktEncoding ); |
| 971 | |
| 972 | } |
| 973 | }else{ |
| 974 | /* add a new ticket or update an existing ticket */ |
| 975 | enum { set,add,err } eCmd = err; |
| 976 | int i = 0; |
| 977 | int rid; |
| 978 | const char *zTktUuid = 0; |
| 979 | Blob tktchng, cksum; |
| 980 | |
| 981 | /* get command type (set/add) and get uuid, if needed for set */ |
| 982 | if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 ){ |
| 983 | eCmd = set; |
| 984 | if( g.argc==3 ){ |
| 985 | usage("set TICKETUUID"); |
| 986 | } |
| 987 | zTktUuid = db_text(0, |
| 988 | "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] |
| @@ -996,13 +1005,84 @@ | |
| 996 | i = 3; |
| 997 | zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 998 | } |
| 999 | /* none of set/add, so show the usage! */ |
| 1000 | if( eCmd==err ){ |
| 1001 | usage("add|fieldlist|set|show"); |
| 1002 | } |
| 1003 | |
| 1004 | /* read all given ticket field/value pairs from command line */ |
| 1005 | if( i==g.argc ){ |
| 1006 | fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 1007 | } |
| 1008 | getAllTicketFields(); |
| 1009 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -899,10 +899,14 @@ | |
| 899 | ** |
| 900 | ** %fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote? |
| 901 | ** |
| 902 | ** like set, but create a new ticket with the given values. |
| 903 | ** |
| 904 | ** %fossil ticket history TICKETUUID |
| 905 | ** |
| 906 | ** Show the complete change history for the ticket |
| 907 | ** |
| 908 | ** The values in set|add are not validated against the definitions |
| 909 | ** given in "Ticket Common Script". |
| 910 | */ |
| 911 | void ticket_cmd(void){ |
| 912 | int n; |
| @@ -916,16 +920,16 @@ | |
| 920 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 921 | fossil_fatal("no such user: %s", g.zLogin); |
| 922 | } |
| 923 | |
| 924 | if( g.argc<3 ){ |
| 925 | usage("add|fieldlist|set|show|history"); |
| 926 | }else{ |
| 927 | n = strlen(g.argv[2]); |
| 928 | if( n==1 && g.argv[2][0]=='s' ){ |
| 929 | /* set/show cannot be distinguished, so show the usage */ |
| 930 | usage("add|fieldlist|set|show|history"); |
| 931 | }else if( strncmp(g.argv[2],"list",n)==0 ){ |
| 932 | if( g.argc==3 ){ |
| 933 | usage("list fields|reports"); |
| 934 | }else{ |
| 935 | n = strlen(g.argv[3]); |
| @@ -970,19 +974,24 @@ | |
| 974 | rptshow( zRep, zSep, zFilterUuid, tktEncoding ); |
| 975 | |
| 976 | } |
| 977 | }else{ |
| 978 | /* add a new ticket or update an existing ticket */ |
| 979 | enum { set,add,history,err } eCmd = err; |
| 980 | int i = 0; |
| 981 | int rid; |
| 982 | const char *zTktUuid = 0; |
| 983 | Blob tktchng, cksum; |
| 984 | |
| 985 | /* get command type (set/add) and get uuid, if needed for set */ |
| 986 | if( strncmp(g.argv[2],"set",n)==0 || strncmp(g.argv[2],"change",n)==0 || |
| 987 | strncmp(g.argv[2],"history",n)==0 ){ |
| 988 | if( strncmp(g.argv[2],"history",n)==0 ){ |
| 989 | eCmd = history; |
| 990 | }else{ |
| 991 | eCmd = set; |
| 992 | } |
| 993 | if( g.argc==3 ){ |
| 994 | usage("set TICKETUUID"); |
| 995 | } |
| 996 | zTktUuid = db_text(0, |
| 997 | "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3] |
| @@ -996,13 +1005,84 @@ | |
| 1005 | i = 3; |
| 1006 | zTktUuid = db_text(0, "SELECT lower(hex(randomblob(20)))"); |
| 1007 | } |
| 1008 | /* none of set/add, so show the usage! */ |
| 1009 | if( eCmd==err ){ |
| 1010 | usage("add|fieldlist|set|show|history"); |
| 1011 | } |
| 1012 | |
| 1013 | /* we just handle history separately here, does not get out */ |
| 1014 | if( eCmd==history ){ |
| 1015 | Stmt q; |
| 1016 | char *zTitle; |
| 1017 | int tagid; |
| 1018 | |
| 1019 | if ( i != g.argc ){ |
| 1020 | fossil_fatal("no other parameters expected to %s!",g.argv[2]); |
| 1021 | } |
| 1022 | tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zTktUuid); |
| 1023 | if( tagid==0 ){ |
| 1024 | fossil_fatal("no such ticket %h", zTktUuid); |
| 1025 | } |
| 1026 | db_prepare(&q, |
| 1027 | "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL" |
| 1028 | " FROM event, blob" |
| 1029 | " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)" |
| 1030 | " AND blob.rid=event.objid" |
| 1031 | " UNION " |
| 1032 | "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user" |
| 1033 | " FROM attachment, blob" |
| 1034 | " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)" |
| 1035 | " AND blob.rid=attachid" |
| 1036 | " ORDER BY 1 DESC", |
| 1037 | tagid, tagid |
| 1038 | ); |
| 1039 | while( db_step(&q)==SQLITE_ROW ){ |
| 1040 | Manifest *pTicket; |
| 1041 | char zShort[12]; |
| 1042 | const char *zDate = db_column_text(&q, 0); |
| 1043 | int rid = db_column_int(&q, 1); |
| 1044 | const char *zChngUuid = db_column_text(&q, 2); |
| 1045 | const char *zFile = db_column_text(&q, 4); |
| 1046 | memcpy(zShort, zChngUuid, 10); |
| 1047 | zShort[10] = 0; |
| 1048 | if( zFile!=0 ){ |
| 1049 | const char *zSrc = db_column_text(&q, 3); |
| 1050 | const char *zUser = db_column_text(&q, 5); |
| 1051 | if( zSrc==0 || zSrc[0]==0 ){ |
| 1052 | fossil_print("Delete attachment %h\n", zFile); |
| 1053 | }else{ |
| 1054 | fossil_print("Add attachment %h\n", zFile); |
| 1055 | } |
| 1056 | fossil_print(" by %h on %h\n", zUser, zDate); |
| 1057 | }else{ |
| 1058 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 1059 | if( pTicket ){ |
| 1060 | int i; |
| 1061 | |
| 1062 | fossil_print("Ticket Change by %h on %h:\n", pTicket->zUser, zDate); |
| 1063 | for(i=0; i<pTicket->nField; i++){ |
| 1064 | Blob val; |
| 1065 | const char *z; |
| 1066 | z = pTicket->aField[i].zName; |
| 1067 | blob_set(&val, pTicket->aField[i].zValue); |
| 1068 | if( z[0]=='+' ){ |
| 1069 | fossil_print(" Appended to %h:\n ",&z[1]); |
| 1070 | comment_print(blob_str(&val),7,79); |
| 1071 | }else{ |
| 1072 | fossil_print(" Change %h to:\n ",z); |
| 1073 | comment_print(blob_str(&val),7,79); |
| 1074 | } |
| 1075 | blob_reset(&val); |
| 1076 | } |
| 1077 | } |
| 1078 | manifest_destroy(pTicket); |
| 1079 | } |
| 1080 | } |
| 1081 | db_finalize(&q); |
| 1082 | return; |
| 1083 | } |
| 1084 | /* read all given ticket field/value pairs from command line */ |
| 1085 | if( i==g.argc ){ |
| 1086 | fossil_fatal("empty %s command aborted!",g.argv[2]); |
| 1087 | } |
| 1088 | getAllTicketFields(); |
| 1089 |