Fossil SCM
Add a path-tracing option to the timeline display.
Commit
63ac111d5baf713827bef1a5dc31fb9a34cf410f
Parent
1b7777b9eeb3b29…
2 files changed
+18
-4
+39
-1
+18
-4
| --- src/bisect.c | ||
| +++ src/bisect.c | ||
| @@ -21,13 +21,13 @@ | ||
| 21 | 21 | */ |
| 22 | 22 | #include "config.h" |
| 23 | 23 | #include "bisect.h" |
| 24 | 24 | #include <assert.h> |
| 25 | 25 | |
| 26 | +#if INTERFACE | |
| 26 | 27 | /* Nodes for the shortest path algorithm. |
| 27 | 28 | */ |
| 28 | -typedef struct BisectNode BisectNode; | |
| 29 | 29 | struct BisectNode { |
| 30 | 30 | int rid; /* ID for this node */ |
| 31 | 31 | int fromIsParent; /* True if pFrom is the parent of rid */ |
| 32 | 32 | BisectNode *pFrom; /* Node we came from */ |
| 33 | 33 | union { |
| @@ -34,10 +34,11 @@ | ||
| 34 | 34 | BisectNode *pPeer; /* List of nodes of the same generation */ |
| 35 | 35 | BisectNode *pTo; /* Next on path from beginning to end */ |
| 36 | 36 | } u; |
| 37 | 37 | BisectNode *pAll; /* List of all nodes */ |
| 38 | 38 | }; |
| 39 | +#endif | |
| 39 | 40 | |
| 40 | 41 | /* |
| 41 | 42 | ** Local variables for this module |
| 42 | 43 | */ |
| 43 | 44 | static struct { |
| @@ -71,11 +72,11 @@ | ||
| 71 | 72 | } |
| 72 | 73 | |
| 73 | 74 | /* |
| 74 | 75 | ** Reset memory used by the shortest path algorithm. |
| 75 | 76 | */ |
| 76 | -static void bisect_reset(void){ | |
| 77 | +void bisect_reset(void){ | |
| 77 | 78 | BisectNode *p; |
| 78 | 79 | while( bisect.pAll ){ |
| 79 | 80 | p = bisect.pAll; |
| 80 | 81 | bisect.pAll = p->pAll; |
| 81 | 82 | fossil_free(p); |
| @@ -91,11 +92,11 @@ | ||
| 91 | 92 | ** Compute the shortest path from iFrom to iTo |
| 92 | 93 | ** |
| 93 | 94 | ** If directOnly is true, then use only the "primary" links from parent to |
| 94 | 95 | ** child. In other words, ignore merges. |
| 95 | 96 | */ |
| 96 | -static BisectNode *bisect_shortest_path(int iFrom, int iTo, int directOnly){ | |
| 97 | +BisectNode *bisect_shortest_path(int iFrom, int iTo, int directOnly){ | |
| 97 | 98 | Stmt s; |
| 98 | 99 | BisectNode *pPrev; |
| 99 | 100 | BisectNode *p; |
| 100 | 101 | |
| 101 | 102 | bisect_reset(); |
| @@ -139,17 +140,18 @@ | ||
| 139 | 140 | } |
| 140 | 141 | |
| 141 | 142 | /* |
| 142 | 143 | ** Construct the path from bisect.pStart to bisect.pEnd in the u.pTo fields. |
| 143 | 144 | */ |
| 144 | -static void bisect_reverse_path(void){ | |
| 145 | +BisectNode *bisect_reverse_path(void){ | |
| 145 | 146 | BisectNode *p; |
| 146 | 147 | for(p=bisect.pEnd; p && p->pFrom; p = p->pFrom){ |
| 147 | 148 | p->pFrom->u.pTo = p; |
| 148 | 149 | } |
| 149 | 150 | bisect.pEnd->u.pTo = 0; |
| 150 | 151 | assert( p==bisect.pStart ); |
| 152 | + return p; | |
| 151 | 153 | } |
| 152 | 154 | |
| 153 | 155 | /* |
| 154 | 156 | ** COMMAND: test-shortest-path |
| 155 | 157 | ** |
| @@ -189,10 +191,22 @@ | ||
| 189 | 191 | }else{ |
| 190 | 192 | printf("\n"); |
| 191 | 193 | } |
| 192 | 194 | } |
| 193 | 195 | } |
| 196 | + | |
| 197 | +/* | |
| 198 | +** WEBPAGE: path | |
| 199 | +** | |
| 200 | +** example: /path?from=trunk&to=experimental&nomerge | |
| 201 | +** | |
| 202 | +** Show a timeline of all changes along a path between two versions. | |
| 203 | +*/ | |
| 204 | +void path_page(void){ | |
| 205 | + login_check_credentials(); | |
| 206 | + if( !g.okRead ){ login_needed(); return; } | |
| 207 | +} | |
| 194 | 208 | |
| 195 | 209 | /* |
| 196 | 210 | ** Find the shortest path between bad and good. |
| 197 | 211 | */ |
| 198 | 212 | static BisectNode *bisect_path(void){ |
| 199 | 213 |
| --- src/bisect.c | |
| +++ src/bisect.c | |
| @@ -21,13 +21,13 @@ | |
| 21 | */ |
| 22 | #include "config.h" |
| 23 | #include "bisect.h" |
| 24 | #include <assert.h> |
| 25 | |
| 26 | /* Nodes for the shortest path algorithm. |
| 27 | */ |
| 28 | typedef struct BisectNode BisectNode; |
| 29 | struct BisectNode { |
| 30 | int rid; /* ID for this node */ |
| 31 | int fromIsParent; /* True if pFrom is the parent of rid */ |
| 32 | BisectNode *pFrom; /* Node we came from */ |
| 33 | union { |
| @@ -34,10 +34,11 @@ | |
| 34 | BisectNode *pPeer; /* List of nodes of the same generation */ |
| 35 | BisectNode *pTo; /* Next on path from beginning to end */ |
| 36 | } u; |
| 37 | BisectNode *pAll; /* List of all nodes */ |
| 38 | }; |
| 39 | |
| 40 | /* |
| 41 | ** Local variables for this module |
| 42 | */ |
| 43 | static struct { |
| @@ -71,11 +72,11 @@ | |
| 71 | } |
| 72 | |
| 73 | /* |
| 74 | ** Reset memory used by the shortest path algorithm. |
| 75 | */ |
| 76 | static void bisect_reset(void){ |
| 77 | BisectNode *p; |
| 78 | while( bisect.pAll ){ |
| 79 | p = bisect.pAll; |
| 80 | bisect.pAll = p->pAll; |
| 81 | fossil_free(p); |
| @@ -91,11 +92,11 @@ | |
| 91 | ** Compute the shortest path from iFrom to iTo |
| 92 | ** |
| 93 | ** If directOnly is true, then use only the "primary" links from parent to |
| 94 | ** child. In other words, ignore merges. |
| 95 | */ |
| 96 | static BisectNode *bisect_shortest_path(int iFrom, int iTo, int directOnly){ |
| 97 | Stmt s; |
| 98 | BisectNode *pPrev; |
| 99 | BisectNode *p; |
| 100 | |
| 101 | bisect_reset(); |
| @@ -139,17 +140,18 @@ | |
| 139 | } |
| 140 | |
| 141 | /* |
| 142 | ** Construct the path from bisect.pStart to bisect.pEnd in the u.pTo fields. |
| 143 | */ |
| 144 | static void bisect_reverse_path(void){ |
| 145 | BisectNode *p; |
| 146 | for(p=bisect.pEnd; p && p->pFrom; p = p->pFrom){ |
| 147 | p->pFrom->u.pTo = p; |
| 148 | } |
| 149 | bisect.pEnd->u.pTo = 0; |
| 150 | assert( p==bisect.pStart ); |
| 151 | } |
| 152 | |
| 153 | /* |
| 154 | ** COMMAND: test-shortest-path |
| 155 | ** |
| @@ -189,10 +191,22 @@ | |
| 189 | }else{ |
| 190 | printf("\n"); |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | /* |
| 196 | ** Find the shortest path between bad and good. |
| 197 | */ |
| 198 | static BisectNode *bisect_path(void){ |
| 199 |
| --- src/bisect.c | |
| +++ src/bisect.c | |
| @@ -21,13 +21,13 @@ | |
| 21 | */ |
| 22 | #include "config.h" |
| 23 | #include "bisect.h" |
| 24 | #include <assert.h> |
| 25 | |
| 26 | #if INTERFACE |
| 27 | /* Nodes for the shortest path algorithm. |
| 28 | */ |
| 29 | struct BisectNode { |
| 30 | int rid; /* ID for this node */ |
| 31 | int fromIsParent; /* True if pFrom is the parent of rid */ |
| 32 | BisectNode *pFrom; /* Node we came from */ |
| 33 | union { |
| @@ -34,10 +34,11 @@ | |
| 34 | BisectNode *pPeer; /* List of nodes of the same generation */ |
| 35 | BisectNode *pTo; /* Next on path from beginning to end */ |
| 36 | } u; |
| 37 | BisectNode *pAll; /* List of all nodes */ |
| 38 | }; |
| 39 | #endif |
| 40 | |
| 41 | /* |
| 42 | ** Local variables for this module |
| 43 | */ |
| 44 | static struct { |
| @@ -71,11 +72,11 @@ | |
| 72 | } |
| 73 | |
| 74 | /* |
| 75 | ** Reset memory used by the shortest path algorithm. |
| 76 | */ |
| 77 | void bisect_reset(void){ |
| 78 | BisectNode *p; |
| 79 | while( bisect.pAll ){ |
| 80 | p = bisect.pAll; |
| 81 | bisect.pAll = p->pAll; |
| 82 | fossil_free(p); |
| @@ -91,11 +92,11 @@ | |
| 92 | ** Compute the shortest path from iFrom to iTo |
| 93 | ** |
| 94 | ** If directOnly is true, then use only the "primary" links from parent to |
| 95 | ** child. In other words, ignore merges. |
| 96 | */ |
| 97 | BisectNode *bisect_shortest_path(int iFrom, int iTo, int directOnly){ |
| 98 | Stmt s; |
| 99 | BisectNode *pPrev; |
| 100 | BisectNode *p; |
| 101 | |
| 102 | bisect_reset(); |
| @@ -139,17 +140,18 @@ | |
| 140 | } |
| 141 | |
| 142 | /* |
| 143 | ** Construct the path from bisect.pStart to bisect.pEnd in the u.pTo fields. |
| 144 | */ |
| 145 | BisectNode *bisect_reverse_path(void){ |
| 146 | BisectNode *p; |
| 147 | for(p=bisect.pEnd; p && p->pFrom; p = p->pFrom){ |
| 148 | p->pFrom->u.pTo = p; |
| 149 | } |
| 150 | bisect.pEnd->u.pTo = 0; |
| 151 | assert( p==bisect.pStart ); |
| 152 | return p; |
| 153 | } |
| 154 | |
| 155 | /* |
| 156 | ** COMMAND: test-shortest-path |
| 157 | ** |
| @@ -189,10 +191,22 @@ | |
| 191 | }else{ |
| 192 | printf("\n"); |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | /* |
| 198 | ** WEBPAGE: path |
| 199 | ** |
| 200 | ** example: /path?from=trunk&to=experimental&nomerge |
| 201 | ** |
| 202 | ** Show a timeline of all changes along a path between two versions. |
| 203 | */ |
| 204 | void path_page(void){ |
| 205 | login_check_credentials(); |
| 206 | if( !g.okRead ){ login_needed(); return; } |
| 207 | } |
| 208 | |
| 209 | /* |
| 210 | ** Find the shortest path between bad and good. |
| 211 | */ |
| 212 | static BisectNode *bisect_path(void){ |
| 213 |
+39
-1
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -726,10 +726,13 @@ | ||
| 726 | 726 | ** y=TYPE 'ci', 'w', 't', 'e' |
| 727 | 727 | ** s=TEXT string search (comment and brief) |
| 728 | 728 | ** ng Suppress the graph if present |
| 729 | 729 | ** nd Suppress "divider" lines |
| 730 | 730 | ** f=RID Show family (immediate parents and children) of RID |
| 731 | +** from=RID Path from... | |
| 732 | +** to=RID ... to this | |
| 733 | +** nomerge ... avoid merge links on the path | |
| 731 | 734 | ** |
| 732 | 735 | ** p= and d= can appear individually or together. If either p= or d= |
| 733 | 736 | ** appear, then u=, y=, a=, and b= are ignored. |
| 734 | 737 | ** |
| 735 | 738 | ** If a= and b= appear, only a= is used. If neither appear, the most |
| @@ -757,10 +760,13 @@ | ||
| 757 | 760 | int tagid; /* Tag ID */ |
| 758 | 761 | int tmFlags; /* Timeline flags */ |
| 759 | 762 | const char *zThisTag = 0; /* Suppress links to this tag */ |
| 760 | 763 | const char *zThisUser = 0; /* Suppress links to this user */ |
| 761 | 764 | HQuery url; /* URL for various branch links */ |
| 765 | + int from_rid = name_to_rid(P("from")); /* from= for path timelines */ | |
| 766 | + int to_rid = name_to_rid(P("to")); /* to= for path timelines */ | |
| 767 | + int noMerge = P("nomerge")!=0; /* Do not follow merge links */ | |
| 762 | 768 | |
| 763 | 769 | /* To view the timeline, must have permission to read project data. |
| 764 | 770 | */ |
| 765 | 771 | login_check_credentials(); |
| 766 | 772 | if( !g.okRead && !g.okRdTkt && !g.okRdWiki ){ login_needed(); return; } |
| @@ -789,11 +795,42 @@ | ||
| 789 | 795 | blob_zero(&desc); |
| 790 | 796 | blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); |
| 791 | 797 | blob_append(&sql, timeline_query_for_www(), -1); |
| 792 | 798 | url_initialize(&url, "timeline"); |
| 793 | 799 | if( !useDividers ) url_add_parameter(&url, "nd", 0); |
| 794 | - if( (p_rid || d_rid) && g.okRead ){ | |
| 800 | + if( from_rid && to_rid && g.okRead ){ | |
| 801 | + /* If from= and to= are present, display all nodes on a path connecting | |
| 802 | + ** the two */ | |
| 803 | + BisectNode *p; | |
| 804 | + const char *z; | |
| 805 | + | |
| 806 | + bisect_shortest_path(from_rid, to_rid, noMerge); | |
| 807 | + p = bisect_reverse_path(); | |
| 808 | + blob_append(&sql, " AND event.objid IN (0", -1); | |
| 809 | + while( p ){ | |
| 810 | + blob_appendf(&sql, ",%d", p->rid); | |
| 811 | + p = p->u.pTo; | |
| 812 | + } | |
| 813 | + blob_append(&sql, ")", -1); | |
| 814 | + bisect_reset(); | |
| 815 | + blob_append(&desc, "All nodes on the path from ", -1); | |
| 816 | + z = P("from"); | |
| 817 | + if( g.okHistory ){ | |
| 818 | + blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>", g.zTop, z, z); | |
| 819 | + }else{ | |
| 820 | + blob_appendf(&desc, "[%h]", z); | |
| 821 | + } | |
| 822 | + blob_append(&desc, " and ", -1); | |
| 823 | + z = P("to"); | |
| 824 | + if( g.okHistory ){ | |
| 825 | + blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>.", g.zTop, z, z); | |
| 826 | + }else{ | |
| 827 | + blob_appendf(&desc, "[%h].", z); | |
| 828 | + } | |
| 829 | + db_multi_exec("%s", blob_str(&sql)); | |
| 830 | + | |
| 831 | + }else if( (p_rid || d_rid) && g.okRead ){ | |
| 795 | 832 | /* If p= or d= is present, ignore all other parameters other than n= */ |
| 796 | 833 | char *zUuid; |
| 797 | 834 | int np, nd; |
| 798 | 835 | |
| 799 | 836 | if( p_rid && d_rid ){ |
| @@ -853,10 +890,11 @@ | ||
| 853 | 890 | g.zTop, zUuid, zUuid); |
| 854 | 891 | }else{ |
| 855 | 892 | blob_appendf(&desc, "[%.10s]", zUuid); |
| 856 | 893 | } |
| 857 | 894 | }else{ |
| 895 | + /* Otherwise, a timeline based on a span of time */ | |
| 858 | 896 | int n; |
| 859 | 897 | const char *zEType = "timeline item"; |
| 860 | 898 | char *zDate; |
| 861 | 899 | char *zNEntry = mprintf("%d", nEntry); |
| 862 | 900 | url_add_parameter(&url, "n", zNEntry); |
| 863 | 901 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -726,10 +726,13 @@ | |
| 726 | ** y=TYPE 'ci', 'w', 't', 'e' |
| 727 | ** s=TEXT string search (comment and brief) |
| 728 | ** ng Suppress the graph if present |
| 729 | ** nd Suppress "divider" lines |
| 730 | ** f=RID Show family (immediate parents and children) of RID |
| 731 | ** |
| 732 | ** p= and d= can appear individually or together. If either p= or d= |
| 733 | ** appear, then u=, y=, a=, and b= are ignored. |
| 734 | ** |
| 735 | ** If a= and b= appear, only a= is used. If neither appear, the most |
| @@ -757,10 +760,13 @@ | |
| 757 | int tagid; /* Tag ID */ |
| 758 | int tmFlags; /* Timeline flags */ |
| 759 | const char *zThisTag = 0; /* Suppress links to this tag */ |
| 760 | const char *zThisUser = 0; /* Suppress links to this user */ |
| 761 | HQuery url; /* URL for various branch links */ |
| 762 | |
| 763 | /* To view the timeline, must have permission to read project data. |
| 764 | */ |
| 765 | login_check_credentials(); |
| 766 | if( !g.okRead && !g.okRdTkt && !g.okRdWiki ){ login_needed(); return; } |
| @@ -789,11 +795,42 @@ | |
| 789 | blob_zero(&desc); |
| 790 | blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); |
| 791 | blob_append(&sql, timeline_query_for_www(), -1); |
| 792 | url_initialize(&url, "timeline"); |
| 793 | if( !useDividers ) url_add_parameter(&url, "nd", 0); |
| 794 | if( (p_rid || d_rid) && g.okRead ){ |
| 795 | /* If p= or d= is present, ignore all other parameters other than n= */ |
| 796 | char *zUuid; |
| 797 | int np, nd; |
| 798 | |
| 799 | if( p_rid && d_rid ){ |
| @@ -853,10 +890,11 @@ | |
| 853 | g.zTop, zUuid, zUuid); |
| 854 | }else{ |
| 855 | blob_appendf(&desc, "[%.10s]", zUuid); |
| 856 | } |
| 857 | }else{ |
| 858 | int n; |
| 859 | const char *zEType = "timeline item"; |
| 860 | char *zDate; |
| 861 | char *zNEntry = mprintf("%d", nEntry); |
| 862 | url_add_parameter(&url, "n", zNEntry); |
| 863 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -726,10 +726,13 @@ | |
| 726 | ** y=TYPE 'ci', 'w', 't', 'e' |
| 727 | ** s=TEXT string search (comment and brief) |
| 728 | ** ng Suppress the graph if present |
| 729 | ** nd Suppress "divider" lines |
| 730 | ** f=RID Show family (immediate parents and children) of RID |
| 731 | ** from=RID Path from... |
| 732 | ** to=RID ... to this |
| 733 | ** nomerge ... avoid merge links on the path |
| 734 | ** |
| 735 | ** p= and d= can appear individually or together. If either p= or d= |
| 736 | ** appear, then u=, y=, a=, and b= are ignored. |
| 737 | ** |
| 738 | ** If a= and b= appear, only a= is used. If neither appear, the most |
| @@ -757,10 +760,13 @@ | |
| 760 | int tagid; /* Tag ID */ |
| 761 | int tmFlags; /* Timeline flags */ |
| 762 | const char *zThisTag = 0; /* Suppress links to this tag */ |
| 763 | const char *zThisUser = 0; /* Suppress links to this user */ |
| 764 | HQuery url; /* URL for various branch links */ |
| 765 | int from_rid = name_to_rid(P("from")); /* from= for path timelines */ |
| 766 | int to_rid = name_to_rid(P("to")); /* to= for path timelines */ |
| 767 | int noMerge = P("nomerge")!=0; /* Do not follow merge links */ |
| 768 | |
| 769 | /* To view the timeline, must have permission to read project data. |
| 770 | */ |
| 771 | login_check_credentials(); |
| 772 | if( !g.okRead && !g.okRdTkt && !g.okRdWiki ){ login_needed(); return; } |
| @@ -789,11 +795,42 @@ | |
| 795 | blob_zero(&desc); |
| 796 | blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1); |
| 797 | blob_append(&sql, timeline_query_for_www(), -1); |
| 798 | url_initialize(&url, "timeline"); |
| 799 | if( !useDividers ) url_add_parameter(&url, "nd", 0); |
| 800 | if( from_rid && to_rid && g.okRead ){ |
| 801 | /* If from= and to= are present, display all nodes on a path connecting |
| 802 | ** the two */ |
| 803 | BisectNode *p; |
| 804 | const char *z; |
| 805 | |
| 806 | bisect_shortest_path(from_rid, to_rid, noMerge); |
| 807 | p = bisect_reverse_path(); |
| 808 | blob_append(&sql, " AND event.objid IN (0", -1); |
| 809 | while( p ){ |
| 810 | blob_appendf(&sql, ",%d", p->rid); |
| 811 | p = p->u.pTo; |
| 812 | } |
| 813 | blob_append(&sql, ")", -1); |
| 814 | bisect_reset(); |
| 815 | blob_append(&desc, "All nodes on the path from ", -1); |
| 816 | z = P("from"); |
| 817 | if( g.okHistory ){ |
| 818 | blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>", g.zTop, z, z); |
| 819 | }else{ |
| 820 | blob_appendf(&desc, "[%h]", z); |
| 821 | } |
| 822 | blob_append(&desc, " and ", -1); |
| 823 | z = P("to"); |
| 824 | if( g.okHistory ){ |
| 825 | blob_appendf(&desc, "<a href='%s/info/%h'>[%h]</a>.", g.zTop, z, z); |
| 826 | }else{ |
| 827 | blob_appendf(&desc, "[%h].", z); |
| 828 | } |
| 829 | db_multi_exec("%s", blob_str(&sql)); |
| 830 | |
| 831 | }else if( (p_rid || d_rid) && g.okRead ){ |
| 832 | /* If p= or d= is present, ignore all other parameters other than n= */ |
| 833 | char *zUuid; |
| 834 | int np, nd; |
| 835 | |
| 836 | if( p_rid && d_rid ){ |
| @@ -853,10 +890,11 @@ | |
| 890 | g.zTop, zUuid, zUuid); |
| 891 | }else{ |
| 892 | blob_appendf(&desc, "[%.10s]", zUuid); |
| 893 | } |
| 894 | }else{ |
| 895 | /* Otherwise, a timeline based on a span of time */ |
| 896 | int n; |
| 897 | const char *zEType = "timeline item"; |
| 898 | char *zDate; |
| 899 | char *zNEntry = mprintf("%d", nEntry); |
| 900 | url_add_parameter(&url, "n", zNEntry); |
| 901 |