| | @@ -2773,10 +2773,105 @@ |
| 2773 | 2773 | @ ↓</a> |
| 2774 | 2774 | } |
| 2775 | 2775 | document_emit_js(/*handles pikchrs rendered above*/); |
| 2776 | 2776 | style_finish_page(); |
| 2777 | 2777 | } |
| 2778 | + |
| 2779 | +/* |
| 2780 | +** Translate a timeline entry into the printable format by |
| 2781 | +** converting every %-substitutions as follows: |
| 2782 | +** |
| 2783 | +** %n newline |
| 2784 | +** %% a raw % |
| 2785 | +** %H commit hash |
| 2786 | +** %h abbreviated commit hash |
| 2787 | +** %a author name |
| 2788 | +** %d date |
| 2789 | +** %c comment (\n, \t replaced by space, \r deleted) |
| 2790 | +** %b branch |
| 2791 | +** %t tags |
| 2792 | +** %p phase (zero or more of: *CURRENT*, *MERGE*, *FORK*, |
| 2793 | +** *UNPUBLISHED*, *LEAF*, *BRANCH*) |
| 2794 | +** |
| 2795 | +** The returned string is obtained from fossil_malloc() and should |
| 2796 | +** be freed by the caller. |
| 2797 | +*/ |
| 2798 | +static char *timeline_entry_subst( |
| 2799 | + const char *zFormat, |
| 2800 | + int *nLine, |
| 2801 | + const char *zId, |
| 2802 | + const char *zDate, |
| 2803 | + const char *zUser, |
| 2804 | + const char *zCom, |
| 2805 | + const char *zBranch, |
| 2806 | + const char *zTags, |
| 2807 | + const char *zPhase |
| 2808 | +){ |
| 2809 | + Blob r, co; |
| 2810 | + int i, j; |
| 2811 | + blob_init(&r, 0, 0); |
| 2812 | + blob_init(&co, 0, 0); |
| 2813 | + |
| 2814 | + /* Replace LF and tab with space, delete CR */ |
| 2815 | + while( zCom[0] ){ |
| 2816 | + for(j=0; zCom[j] && zCom[j]!='\r' && zCom[j]!='\n' && zCom[j]!='\t'; j++){} |
| 2817 | + blob_append(&co, zCom, j); |
| 2818 | + if( zCom[j]==0 ) break; |
| 2819 | + if( zCom[j]!='\r') |
| 2820 | + blob_append(&co, " ", 1); |
| 2821 | + zCom += j+1; |
| 2822 | + } |
| 2823 | + blob_str(&co); |
| 2824 | + |
| 2825 | + *nLine = 1; |
| 2826 | + while( zFormat[0] ){ |
| 2827 | + for(i=0; zFormat[i] && zFormat[i]!='%'; i++){} |
| 2828 | + blob_append(&r, zFormat, i); |
| 2829 | + if( zFormat[i]==0 ) break; |
| 2830 | + if( zFormat[i+1]=='%' ){ |
| 2831 | + blob_append(&r, "%", 1); |
| 2832 | + zFormat += i+2; |
| 2833 | + }else if( zFormat[i+1]=='n' ){ |
| 2834 | + blob_append(&r, "\n", 1); |
| 2835 | + *nLine += 1; |
| 2836 | + zFormat += i+2; |
| 2837 | + }else if( zFormat[i+1]=='H' ){ |
| 2838 | + blob_append(&r, zId, -1); |
| 2839 | + zFormat += i+2; |
| 2840 | + }else if( zFormat[i+1]=='h' ){ |
| 2841 | + char *zFree = 0; |
| 2842 | + zFree = mprintf("%S", zId); |
| 2843 | + blob_append(&r, zFree, -1); |
| 2844 | + fossil_free(zFree); |
| 2845 | + zFormat += i+2; |
| 2846 | + }else if( zFormat[i+1]=='d' ){ |
| 2847 | + blob_append(&r, zDate, -1); |
| 2848 | + zFormat += i+2; |
| 2849 | + }else if( zFormat[i+1]=='a' ){ |
| 2850 | + blob_append(&r, zUser, -1); |
| 2851 | + zFormat += i+2; |
| 2852 | + }else if( zFormat[i+1]=='c' ){ |
| 2853 | + blob_append(&r, co.aData, -1); |
| 2854 | + zFormat += i+2; |
| 2855 | + }else if( zFormat[i+1]=='b' ){ |
| 2856 | + if( zBranch ) blob_append(&r, zBranch, -1); |
| 2857 | + zFormat += i+2; |
| 2858 | + }else if( zFormat[i+1]=='t' ){ |
| 2859 | + blob_append(&r, zTags, -1); |
| 2860 | + zFormat += i+2; |
| 2861 | + }else if( zFormat[i+1]=='p' ){ |
| 2862 | + blob_append(&r, zPhase, -1); |
| 2863 | + zFormat += i+2; |
| 2864 | + }else{ |
| 2865 | + blob_append(&r, zFormat+i, 1); |
| 2866 | + zFormat += i+1; |
| 2867 | + } |
| 2868 | + } |
| 2869 | + fossil_free(co.aData); |
| 2870 | + blob_str(&r); |
| 2871 | + return r.aData; |
| 2872 | +} |
| 2778 | 2873 | |
| 2779 | 2874 | /* |
| 2780 | 2875 | ** The input query q selects various records. Print a human-readable |
| 2781 | 2876 | ** summary of those records. |
| 2782 | 2877 | ** |
| | @@ -2791,18 +2886,21 @@ |
| 2791 | 2886 | ** The query should return these columns: |
| 2792 | 2887 | ** |
| 2793 | 2888 | ** 0. rid |
| 2794 | 2889 | ** 1. uuid |
| 2795 | 2890 | ** 2. Date/Time |
| 2796 | | -** 3. Comment string and user |
| 2891 | +** 3. Comment string, user, and tags |
| 2797 | 2892 | ** 4. Number of non-merge children |
| 2798 | 2893 | ** 5. Number of parents |
| 2799 | 2894 | ** 6. mtime |
| 2800 | 2895 | ** 7. branch |
| 2801 | 2896 | ** 8. event-type: 'ci', 'w', 't', 'f', and so forth. |
| 2897 | +** 9. comment |
| 2898 | +** 10. user |
| 2899 | +** 11. tags |
| 2802 | 2900 | */ |
| 2803 | | -void print_timeline(Stmt *q, int nLimit, int width, int verboseFlag){ |
| 2901 | +void print_timeline(Stmt *q, int nLimit, int width, const char *zFormat, int verboseFlag){ |
| 2804 | 2902 | int nAbsLimit = (nLimit >= 0) ? nLimit : -nLimit; |
| 2805 | 2903 | int nLine = 0; |
| 2806 | 2904 | int nEntry = 0; |
| 2807 | 2905 | char zPrevDate[20]; |
| 2808 | 2906 | const char *zCurrentUuid = 0; |
| | @@ -2821,11 +2919,15 @@ |
| 2821 | 2919 | const char *zId = db_column_text(q, 1); |
| 2822 | 2920 | const char *zDate = db_column_text(q, 2); |
| 2823 | 2921 | const char *zCom = db_column_text(q, 3); |
| 2824 | 2922 | int nChild = db_column_int(q, 4); |
| 2825 | 2923 | int nParent = db_column_int(q, 5); |
| 2924 | + const char *zBranch = db_column_text(q, 7); |
| 2826 | 2925 | const char *zType = db_column_text(q, 8); |
| 2926 | + const char *zComShort = db_column_text(q, 9); |
| 2927 | + const char *zUserShort = db_column_text(q, 10); |
| 2928 | + const char *zTags = db_column_text(q, 11); |
| 2827 | 2929 | char *zFree = 0; |
| 2828 | 2930 | int n = 0; |
| 2829 | 2931 | char zPrefix[80]; |
| 2830 | 2932 | |
| 2831 | 2933 | if( nAbsLimit!=0 ){ |
| | @@ -2835,17 +2937,18 @@ |
| 2835 | 2937 | }else if( nEntry>=nAbsLimit ){ |
| 2836 | 2938 | fossil_print("--- entry limit (%d) reached ---\n", nAbsLimit); |
| 2837 | 2939 | break; /* entry count limit hit, stop. */ |
| 2838 | 2940 | } |
| 2839 | 2941 | } |
| 2840 | | - if( fossil_strnicmp(zDate, zPrevDate, 10) ){ |
| 2942 | + if( zFormat == 0 && fossil_strnicmp(zDate, zPrevDate, 10) ){ |
| 2841 | 2943 | fossil_print("=== %.10s ===\n", zDate); |
| 2842 | 2944 | memcpy(zPrevDate, zDate, 10); |
| 2843 | 2945 | nLine++; /* record another line */ |
| 2844 | 2946 | } |
| 2845 | 2947 | if( zCom==0 ) zCom = ""; |
| 2846 | | - fossil_print("%.8s ", &zDate[11]); |
| 2948 | + if( zFormat == 0 ) |
| 2949 | + fossil_print("%.8s ", &zDate[11]); |
| 2847 | 2950 | zPrefix[0] = 0; |
| 2848 | 2951 | if( nParent>1 ){ |
| 2849 | 2952 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "*MERGE* "); |
| 2850 | 2953 | n = strlen(zPrefix); |
| 2851 | 2954 | } |
| | @@ -2877,12 +2980,27 @@ |
| 2877 | 2980 | zFree = mprintf("[%S] Edit to wiki page \"%s\"", zId, zCom+1); |
| 2878 | 2981 | } |
| 2879 | 2982 | }else{ |
| 2880 | 2983 | zFree = mprintf("[%S] %s%s", zId, zPrefix, zCom); |
| 2881 | 2984 | } |
| 2882 | | - /* record another X lines */ |
| 2883 | | - nLine += comment_print(zFree, zCom, 9, width, get_comment_format()); |
| 2985 | + |
| 2986 | + if( zFormat ){ |
| 2987 | + if( nChild==0 ){ |
| 2988 | + sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*LEAF* "); |
| 2989 | + } |
| 2990 | + char *zEntry; |
| 2991 | + int nEntryLine = 0; |
| 2992 | + zEntry = timeline_entry_subst(zFormat, &nEntryLine, zId, zDate, zUserShort, |
| 2993 | + zComShort, zBranch, zTags, zPrefix); |
| 2994 | + nLine += nEntryLine; |
| 2995 | + fossil_print("%s\n", zEntry); |
| 2996 | + fossil_free(zEntry); |
| 2997 | + } |
| 2998 | + else{ |
| 2999 | + /* record another X lines */ |
| 3000 | + nLine += comment_print(zFree, zCom, 9, width, get_comment_format()); |
| 3001 | + } |
| 2884 | 3002 | fossil_free(zFree); |
| 2885 | 3003 | |
| 2886 | 3004 | if(verboseFlag){ |
| 2887 | 3005 | if( !fchngQueryInit ){ |
| 2888 | 3006 | db_prepare(&fchngQuery, |
| | @@ -2911,10 +3029,13 @@ |
| 2911 | 3029 | } |
| 2912 | 3030 | nLine++; /* record another line */ |
| 2913 | 3031 | } |
| 2914 | 3032 | db_reset(&fchngQuery); |
| 2915 | 3033 | } |
| 3034 | + /* Except for "oneline", separate formatted entries by one empty line */ |
| 3035 | + if( zFormat && fossil_strcmp(zFormat, "%h %c")!=0 ) |
| 3036 | + fossil_print("\n"); |
| 2916 | 3037 | nEntry++; /* record another complete entry */ |
| 2917 | 3038 | } |
| 2918 | 3039 | if( rc==SQLITE_DONE ){ |
| 2919 | 3040 | /* Did the underlying query actually have all entries? */ |
| 2920 | 3041 | if( nAbsLimit==0 ){ |
| | @@ -2948,10 +3069,17 @@ |
| 2948 | 3069 | @ AS primPlinkCount, |
| 2949 | 3070 | @ (SELECT count(*) FROM plink WHERE cid=blob.rid) AS plinkCount, |
| 2950 | 3071 | @ event.mtime AS mtime, |
| 2951 | 3072 | @ tagxref.value AS branch, |
| 2952 | 3073 | @ event.type |
| 3074 | + @ , coalesce(ecomment,comment) AS comment0 |
| 3075 | + @ , coalesce(euser,user,'?') AS user0 |
| 3076 | + @ , (SELECT case when length(x)>0 then x else '' end |
| 3077 | + @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x |
| 3078 | + @ FROM tag, tagxref |
| 3079 | + @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid |
| 3080 | + @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0)) AS tags |
| 2953 | 3081 | @ FROM tag CROSS JOIN event CROSS JOIN blob |
| 2954 | 3082 | @ LEFT JOIN tagxref ON tagxref.tagid=tag.tagid |
| 2955 | 3083 | @ AND tagxref.tagtype>0 |
| 2956 | 3084 | @ AND tagxref.rid=blob.rid |
| 2957 | 3085 | @ WHERE blob.rid=event.objid |
| | @@ -2977,10 +3105,11 @@ |
| 2977 | 3105 | */ |
| 2978 | 3106 | static int fossil_is_julianday(const char *zDate){ |
| 2979 | 3107 | return db_int(0, "SELECT EXISTS (SELECT julianday(%Q) AS jd" |
| 2980 | 3108 | " WHERE jd IS NOT NULL)", zDate); |
| 2981 | 3109 | } |
| 3110 | + |
| 2982 | 3111 | |
| 2983 | 3112 | /* |
| 2984 | 3113 | ** COMMAND: timeline |
| 2985 | 3114 | ** |
| 2986 | 3115 | ** Usage: %fossil timeline ?WHEN? ?CHECKIN|DATETIME? ?OPTIONS? |
| | @@ -3023,10 +3152,27 @@ |
| 3023 | 3152 | ** etc.) after the check-in comment. |
| 3024 | 3153 | ** -W|--width N Width of lines (default is to auto-detect). N must be |
| 3025 | 3154 | ** either greater than 20 or it ust be zero 0 to |
| 3026 | 3155 | ** indicate no limit, resulting in a single line per |
| 3027 | 3156 | ** entry. |
| 3157 | +** -F|--format Entry format. Values "oneline", "medium", and "full" |
| 3158 | +** get mapped to the full options below. Otherwise a |
| 3159 | +** string which can contain these placeholders: |
| 3160 | +** %n newline |
| 3161 | +** %% a raw % |
| 3162 | +** %H commit hash |
| 3163 | +** %h abbreviated commit hash |
| 3164 | +** %a author name |
| 3165 | +** %d date |
| 3166 | +** %c comment (NL, TAB replaced by space, LF deleted) |
| 3167 | +** %b branch |
| 3168 | +** %t tags |
| 3169 | +** %p phase: zero or more of *CURRENT*, *MERGE*, |
| 3170 | +** *FORK*, *UNPUBLISHED*, *LEAF*, *BRANCH* |
| 3171 | +** --oneline Show only short hash and comment for each entry |
| 3172 | +** --medium Medium-verbose entry formatting |
| 3173 | +** --full Extra verbose entry formatting |
| 3028 | 3174 | ** -R REPO_FILE Specifies the repository db to use. Default is |
| 3029 | 3175 | ** the current checkout's repository. |
| 3030 | 3176 | */ |
| 3031 | 3177 | void timeline_cmd(void){ |
| 3032 | 3178 | Stmt q; |
| | @@ -3042,10 +3188,11 @@ |
| 3042 | 3188 | Blob uuid; |
| 3043 | 3189 | int mode = TIMELINE_MODE_NONE; |
| 3044 | 3190 | int verboseFlag = 0 ; |
| 3045 | 3191 | int iOffset; |
| 3046 | 3192 | const char *zFilePattern = 0; |
| 3193 | + const char *zFormat = 0; |
| 3047 | 3194 | Blob treeName; |
| 3048 | 3195 | int showSql = 0; |
| 3049 | 3196 | |
| 3050 | 3197 | verboseFlag = find_option("verbose","v", 0)!=0; |
| 3051 | 3198 | if( !verboseFlag){ |
| | @@ -3054,10 +3201,17 @@ |
| 3054 | 3201 | db_find_and_open_repository(0, 0); |
| 3055 | 3202 | zLimit = find_option("limit","n",1); |
| 3056 | 3203 | zWidth = find_option("width","W",1); |
| 3057 | 3204 | zType = find_option("type","t",1); |
| 3058 | 3205 | zFilePattern = find_option("path","p",1); |
| 3206 | + zFormat = find_option("format","F",1); |
| 3207 | + if( find_option("oneline",0,0)!= 0 || fossil_strcmp(zFormat,"oneline")==0 ) |
| 3208 | + zFormat = "%h %c"; |
| 3209 | + if( find_option("medium",0,0)!= 0 || fossil_strcmp(zFormat,"medium")==0 ) |
| 3210 | + zFormat = "Commit: %h%nDate: %d%nAuthor: %a%nComment: %c"; |
| 3211 | + if( find_option("full",0,0)!= 0 || fossil_strcmp(zFormat,"full")==0 ) |
| 3212 | + zFormat = "Commit: %H%nDate: %d%nAuthor: %a%nComment: %c%nBranch: %b%nTags: %t%nPrefix: %p"; |
| 3059 | 3213 | showSql = find_option("sql",0,0)!=0; |
| 3060 | 3214 | |
| 3061 | 3215 | if( !zLimit ){ |
| 3062 | 3216 | zLimit = find_option("count",0,1); |
| 3063 | 3217 | } |
| | @@ -3203,11 +3357,11 @@ |
| 3203 | 3357 | if( showSql ){ |
| 3204 | 3358 | fossil_print("%s\n", blob_str(&sql)); |
| 3205 | 3359 | } |
| 3206 | 3360 | db_prepare_blob(&q, &sql); |
| 3207 | 3361 | blob_reset(&sql); |
| 3208 | | - print_timeline(&q, n, width, verboseFlag); |
| 3362 | + print_timeline(&q, n, width, zFormat, verboseFlag); |
| 3209 | 3363 | db_finalize(&q); |
| 3210 | 3364 | } |
| 3211 | 3365 | |
| 3212 | 3366 | /* |
| 3213 | 3367 | ** WEBPAGE: thisdayinhistory |
| 3214 | 3368 | |