| | @@ -2727,10 +2727,106 @@ |
| 2727 | 2727 | @ ↓</a> |
| 2728 | 2728 | } |
| 2729 | 2729 | document_emit_js(/*handles pikchrs rendered above*/); |
| 2730 | 2730 | style_finish_page("timeline"); |
| 2731 | 2731 | } |
| 2732 | + |
| 2733 | +/* |
| 2734 | +** Translate a timeline entry into the printable format by |
| 2735 | +** converting every %-substitutions as follows: |
| 2736 | +** |
| 2737 | +** %n newline |
| 2738 | +** %% a raw % |
| 2739 | +** %H commit hash |
| 2740 | +** %h abbreviated commit hash |
| 2741 | +** %a author name |
| 2742 | +** %d date |
| 2743 | +** %c comment (\n, \t replaced by space, \r deleted) |
| 2744 | +** %b branch |
| 2745 | +** %t tags |
| 2746 | +** %p prefix (zero or more of: *CURRENT*, *MERGE*, *FORK*, |
| 2747 | +** *UNPUBLISHED*, *LEAF*, *BRANCH*) |
| 2748 | +** |
| 2749 | +** The returned string is obtained from fossil_malloc() and should |
| 2750 | +** be freed by the caller. |
| 2751 | +*/ |
| 2752 | +static char *timeline_entry_subst( |
| 2753 | + const char *zFormat, |
| 2754 | + int *nLine, |
| 2755 | + const char *zId, |
| 2756 | + const char *zDate, |
| 2757 | + const char *zUser, |
| 2758 | + const char *zCom, |
| 2759 | + const char *zBranch, |
| 2760 | + const char *zTags, |
| 2761 | + const char *zPrefix |
| 2762 | +){ |
| 2763 | + Blob co; |
| 2764 | + int j; |
| 2765 | + blob_init(&co, 0, 0); |
| 2766 | + *nLine = 1; |
| 2767 | + /* Replace LF and tab with space, delete CR */ |
| 2768 | + while( zCom[0] ){ |
| 2769 | + for(j=0; zCom[j] && zCom[j]!='\r' && zCom[j]!='\n' && zCom[j]!='\t'; j++){} |
| 2770 | + blob_append(&co, zCom, j); |
| 2771 | + if( zCom[j]==0 ) break; |
| 2772 | + if( zCom[j]!='\r') |
| 2773 | + blob_append(&co, " ", 1); |
| 2774 | + zCom += j+1; |
| 2775 | + } |
| 2776 | + blob_str(&co); |
| 2777 | + |
| 2778 | + Blob r; |
| 2779 | + int i; |
| 2780 | + blob_init(&r, 0, 0); |
| 2781 | + while( zFormat[0] ){ |
| 2782 | + for(i=0; zFormat[i] && zFormat[i]!='%'; i++){} |
| 2783 | + blob_append(&r, zFormat, i); |
| 2784 | + if( zFormat[i]==0 ) break; |
| 2785 | + if( zFormat[i+1]=='%' ){ |
| 2786 | + blob_append(&r, "%", 1); |
| 2787 | + zFormat += i+2; |
| 2788 | + }else if( zFormat[i+1]=='n' ){ |
| 2789 | + blob_append(&r, "\n", 1); |
| 2790 | + *nLine += 1; |
| 2791 | + zFormat += i+2; |
| 2792 | + }else if( zFormat[i+1]=='H' ){ |
| 2793 | + blob_append(&r, zId, -1); |
| 2794 | + zFormat += i+2; |
| 2795 | + }else if( zFormat[i+1]=='h' ){ |
| 2796 | + char *zFree = 0; |
| 2797 | + zFree = mprintf("%S", zId); |
| 2798 | + blob_append(&r, zFree, -1); |
| 2799 | + fossil_free(zFree); |
| 2800 | + zFormat += i+2; |
| 2801 | + }else if( zFormat[i+1]=='d' ){ |
| 2802 | + blob_append(&r, zDate, -1); |
| 2803 | + zFormat += i+2; |
| 2804 | + }else if( zFormat[i+1]=='a' ){ |
| 2805 | + blob_append(&r, zUser, -1); |
| 2806 | + zFormat += i+2; |
| 2807 | + }else if( zFormat[i+1]=='c' ){ |
| 2808 | + blob_append(&r, co.aData, -1); |
| 2809 | + zFormat += i+2; |
| 2810 | + }else if( zFormat[i+1]=='b' ){ |
| 2811 | + if( zBranch ) blob_append(&r, zBranch, -1); |
| 2812 | + zFormat += i+2; |
| 2813 | + }else if( zFormat[i+1]=='t' ){ |
| 2814 | + blob_append(&r, zTags, -1); |
| 2815 | + zFormat += i+2; |
| 2816 | + }else if( zFormat[i+1]=='p' ){ |
| 2817 | + blob_append(&r, zPrefix, -1); |
| 2818 | + zFormat += i+2; |
| 2819 | + }else{ |
| 2820 | + blob_append(&r, zFormat+i, 1); |
| 2821 | + zFormat += i+1; |
| 2822 | + } |
| 2823 | + } |
| 2824 | + fossil_free(co.aData); |
| 2825 | + blob_str(&r); |
| 2826 | + return r.aData; |
| 2827 | +} |
| 2732 | 2828 | |
| 2733 | 2829 | /* |
| 2734 | 2830 | ** The input query q selects various records. Print a human-readable |
| 2735 | 2831 | ** summary of those records. |
| 2736 | 2832 | ** |
| | @@ -2745,18 +2841,21 @@ |
| 2745 | 2841 | ** The query should return these columns: |
| 2746 | 2842 | ** |
| 2747 | 2843 | ** 0. rid |
| 2748 | 2844 | ** 1. uuid |
| 2749 | 2845 | ** 2. Date/Time |
| 2750 | | -** 3. Comment string and user |
| 2846 | +** 3. Comment string, user, and tags |
| 2751 | 2847 | ** 4. Number of non-merge children |
| 2752 | 2848 | ** 5. Number of parents |
| 2753 | 2849 | ** 6. mtime |
| 2754 | 2850 | ** 7. branch |
| 2755 | 2851 | ** 8. event-type: 'ci', 'w', 't', 'f', and so forth. |
| 2852 | +** 9. comment |
| 2853 | +** 10. user |
| 2854 | +** 11. tags |
| 2756 | 2855 | */ |
| 2757 | | -void print_timeline(Stmt *q, int nLimit, int width, int verboseFlag){ |
| 2856 | +void print_timeline(Stmt *q, int nLimit, int width, const char *zFormat, int verboseFlag){ |
| 2758 | 2857 | int nAbsLimit = (nLimit >= 0) ? nLimit : -nLimit; |
| 2759 | 2858 | int nLine = 0; |
| 2760 | 2859 | int nEntry = 0; |
| 2761 | 2860 | char zPrevDate[20]; |
| 2762 | 2861 | const char *zCurrentUuid = 0; |
| | @@ -2775,11 +2874,15 @@ |
| 2775 | 2874 | const char *zId = db_column_text(q, 1); |
| 2776 | 2875 | const char *zDate = db_column_text(q, 2); |
| 2777 | 2876 | const char *zCom = db_column_text(q, 3); |
| 2778 | 2877 | int nChild = db_column_int(q, 4); |
| 2779 | 2878 | int nParent = db_column_int(q, 5); |
| 2879 | + const char *zBranch = db_column_text(q, 7); |
| 2780 | 2880 | const char *zType = db_column_text(q, 8); |
| 2881 | + const char *zComShort = db_column_text(q, 9); |
| 2882 | + const char *zUserShort = db_column_text(q, 10); |
| 2883 | + const char *zTags = db_column_text(q, 11); |
| 2781 | 2884 | char *zFree = 0; |
| 2782 | 2885 | int n = 0; |
| 2783 | 2886 | char zPrefix[80]; |
| 2784 | 2887 | |
| 2785 | 2888 | if( nAbsLimit!=0 ){ |
| | @@ -2789,17 +2892,18 @@ |
| 2789 | 2892 | }else if( nEntry>=nAbsLimit ){ |
| 2790 | 2893 | fossil_print("--- entry limit (%d) reached ---\n", nAbsLimit); |
| 2791 | 2894 | break; /* entry count limit hit, stop. */ |
| 2792 | 2895 | } |
| 2793 | 2896 | } |
| 2794 | | - if( fossil_strnicmp(zDate, zPrevDate, 10) ){ |
| 2897 | + if( zFormat == 0 && fossil_strnicmp(zDate, zPrevDate, 10) ){ |
| 2795 | 2898 | fossil_print("=== %.10s ===\n", zDate); |
| 2796 | 2899 | memcpy(zPrevDate, zDate, 10); |
| 2797 | 2900 | nLine++; /* record another line */ |
| 2798 | 2901 | } |
| 2799 | 2902 | if( zCom==0 ) zCom = ""; |
| 2800 | | - fossil_print("%.8s ", &zDate[11]); |
| 2903 | + if( zFormat == 0 ) |
| 2904 | + fossil_print("%.8s ", &zDate[11]); |
| 2801 | 2905 | zPrefix[0] = 0; |
| 2802 | 2906 | if( nParent>1 ){ |
| 2803 | 2907 | sqlite3_snprintf(sizeof(zPrefix), zPrefix, "*MERGE* "); |
| 2804 | 2908 | n = strlen(zPrefix); |
| 2805 | 2909 | } |
| | @@ -2831,12 +2935,27 @@ |
| 2831 | 2935 | zFree = mprintf("[%S] Edit to wiki page \"%s\"", zId, zCom+1); |
| 2832 | 2936 | } |
| 2833 | 2937 | }else{ |
| 2834 | 2938 | zFree = mprintf("[%S] %s%s", zId, zPrefix, zCom); |
| 2835 | 2939 | } |
| 2836 | | - /* record another X lines */ |
| 2837 | | - nLine += comment_print(zFree, zCom, 9, width, get_comment_format()); |
| 2940 | + |
| 2941 | + if( zFormat ){ |
| 2942 | + if( nChild==0 ){ |
| 2943 | + sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*LEAF* "); |
| 2944 | + } |
| 2945 | + char *zEntry; |
| 2946 | + int nEntryLine = 0; |
| 2947 | + zEntry = timeline_entry_subst(zFormat, &nEntryLine, zId, zDate, zUserShort, |
| 2948 | + zComShort, zBranch, zTags, zPrefix); |
| 2949 | + nLine += nEntryLine; |
| 2950 | + fossil_print("%s\n", zEntry); |
| 2951 | + fossil_free(zEntry); |
| 2952 | + } |
| 2953 | + else{ |
| 2954 | + /* record another X lines */ |
| 2955 | + nLine += comment_print(zFree, zCom, 9, width, get_comment_format()); |
| 2956 | + } |
| 2838 | 2957 | fossil_free(zFree); |
| 2839 | 2958 | |
| 2840 | 2959 | if(verboseFlag){ |
| 2841 | 2960 | if( !fchngQueryInit ){ |
| 2842 | 2961 | db_prepare(&fchngQuery, |
| | @@ -2865,10 +2984,13 @@ |
| 2865 | 2984 | } |
| 2866 | 2985 | nLine++; /* record another line */ |
| 2867 | 2986 | } |
| 2868 | 2987 | db_reset(&fchngQuery); |
| 2869 | 2988 | } |
| 2989 | + /* Except for "oneline", separate formatted entries by one empty line */ |
| 2990 | + if( zFormat && fossil_strcmp(zFormat, "%h %c")!=0 ) |
| 2991 | + fossil_print("\n"); |
| 2870 | 2992 | nEntry++; /* record another complete entry */ |
| 2871 | 2993 | } |
| 2872 | 2994 | if( rc==SQLITE_DONE ){ |
| 2873 | 2995 | /* Did the underlying query actually have all entries? */ |
| 2874 | 2996 | if( nAbsLimit==0 ){ |
| | @@ -2902,10 +3024,17 @@ |
| 2902 | 3024 | @ AS primPlinkCount, |
| 2903 | 3025 | @ (SELECT count(*) FROM plink WHERE cid=blob.rid) AS plinkCount, |
| 2904 | 3026 | @ event.mtime AS mtime, |
| 2905 | 3027 | @ tagxref.value AS branch, |
| 2906 | 3028 | @ event.type |
| 3029 | + @ , coalesce(ecomment,comment) AS comment0 |
| 3030 | + @ , coalesce(euser,user,'?') AS user0 |
| 3031 | + @ , (SELECT case when length(x)>0 then x else '' end |
| 3032 | + @ FROM (SELECT group_concat(substr(tagname,5), ', ') AS x |
| 3033 | + @ FROM tag, tagxref |
| 3034 | + @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid |
| 3035 | + @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0)) AS tags |
| 2907 | 3036 | @ FROM tag CROSS JOIN event CROSS JOIN blob |
| 2908 | 3037 | @ LEFT JOIN tagxref ON tagxref.tagid=tag.tagid |
| 2909 | 3038 | @ AND tagxref.tagtype>0 |
| 2910 | 3039 | @ AND tagxref.rid=blob.rid |
| 2911 | 3040 | @ WHERE blob.rid=event.objid |
| | @@ -2931,10 +3060,11 @@ |
| 2931 | 3060 | */ |
| 2932 | 3061 | static int fossil_is_julianday(const char *zDate){ |
| 2933 | 3062 | return db_int(0, "SELECT EXISTS (SELECT julianday(%Q) AS jd" |
| 2934 | 3063 | " WHERE jd IS NOT NULL)", zDate); |
| 2935 | 3064 | } |
| 3065 | + |
| 2936 | 3066 | |
| 2937 | 3067 | /* |
| 2938 | 3068 | ** COMMAND: timeline |
| 2939 | 3069 | ** |
| 2940 | 3070 | ** Usage: %fossil timeline ?WHEN? ?CHECKIN|DATETIME? ?OPTIONS? |
| | @@ -2977,10 +3107,27 @@ |
| 2977 | 3107 | ** etc.) after the check-in comment. |
| 2978 | 3108 | ** -W|--width N Width of lines (default is to auto-detect). N must be |
| 2979 | 3109 | ** either greater than 20 or it ust be zero 0 to |
| 2980 | 3110 | ** indicate no limit, resulting in a single line per |
| 2981 | 3111 | ** entry. |
| 3112 | +** -F|--format Entry format. Values "oneline", "medium", and "full" |
| 3113 | +** get mapped to the full options below. Otherwise a |
| 3114 | +** string which can contain these placeholders: |
| 3115 | +** %n newline |
| 3116 | +** %% a raw % |
| 3117 | +** %H commit hash |
| 3118 | +** %h abbreviated commit hash |
| 3119 | +** %a author name |
| 3120 | +** %d date |
| 3121 | +** %c comment (NL, TAB replaced by space, LF deleted) |
| 3122 | +** %b branch |
| 3123 | +** %t tags |
| 3124 | +** %p zero or more of: *CURRENT*, *MERGE*, *FORK*, |
| 3125 | +** *UNPUBLISHED*, *LEAF*, *BRANCH* |
| 3126 | +** --oneline Show only short hash and comment for each entry |
| 3127 | +** --medium Medium-verbose entry formatting |
| 3128 | +** --full Extra verbose entry formatting |
| 2982 | 3129 | ** -R REPO_FILE Specifies the repository db to use. Default is |
| 2983 | 3130 | ** the current checkout's repository. |
| 2984 | 3131 | */ |
| 2985 | 3132 | void timeline_cmd(void){ |
| 2986 | 3133 | Stmt q; |
| | @@ -2996,10 +3143,11 @@ |
| 2996 | 3143 | Blob uuid; |
| 2997 | 3144 | int mode = TIMELINE_MODE_NONE; |
| 2998 | 3145 | int verboseFlag = 0 ; |
| 2999 | 3146 | int iOffset; |
| 3000 | 3147 | const char *zFilePattern = 0; |
| 3148 | + const char *zFormat = 0; |
| 3001 | 3149 | Blob treeName; |
| 3002 | 3150 | int showSql = 0; |
| 3003 | 3151 | |
| 3004 | 3152 | verboseFlag = find_option("verbose","v", 0)!=0; |
| 3005 | 3153 | if( !verboseFlag){ |
| | @@ -3008,10 +3156,17 @@ |
| 3008 | 3156 | db_find_and_open_repository(0, 0); |
| 3009 | 3157 | zLimit = find_option("limit","n",1); |
| 3010 | 3158 | zWidth = find_option("width","W",1); |
| 3011 | 3159 | zType = find_option("type","t",1); |
| 3012 | 3160 | zFilePattern = find_option("path","p",1); |
| 3161 | + zFormat = find_option("format","F",1); |
| 3162 | + if( find_option("oneline",0,0)!= 0 || fossil_strcmp(zFormat,"oneline")==0 ) |
| 3163 | + zFormat = "%h %c"; |
| 3164 | + if( find_option("medium",0,0)!= 0 || fossil_strcmp(zFormat,"medium")==0 ) |
| 3165 | + zFormat = "Commit: %h%nDate: %d%nAuthor: %a%nComment: %c"; |
| 3166 | + if( find_option("full",0,0)!= 0 || fossil_strcmp(zFormat,"full")==0 ) |
| 3167 | + zFormat = "Commit: %H%nDate: %d%nAuthor: %a%nComment: %c%nBranch: %b%nTags: %t%nPrefix: %p"; |
| 3013 | 3168 | showSql = find_option("sql",0,0)!=0; |
| 3014 | 3169 | |
| 3015 | 3170 | if( !zLimit ){ |
| 3016 | 3171 | zLimit = find_option("count",0,1); |
| 3017 | 3172 | } |
| | @@ -3157,11 +3312,11 @@ |
| 3157 | 3312 | if( showSql ){ |
| 3158 | 3313 | fossil_print("%s\n", blob_str(&sql)); |
| 3159 | 3314 | } |
| 3160 | 3315 | db_prepare_blob(&q, &sql); |
| 3161 | 3316 | blob_reset(&sql); |
| 3162 | | - print_timeline(&q, n, width, verboseFlag); |
| 3317 | + print_timeline(&q, n, width, zFormat, verboseFlag); |
| 3163 | 3318 | db_finalize(&q); |
| 3164 | 3319 | } |
| 3165 | 3320 | |
| 3166 | 3321 | /* |
| 3167 | 3322 | ** WEBPAGE: thisdayinhistory |
| 3168 | 3323 | |