Fossil SCM

The "fossil merge-info --tcl FILE" command generates content for the merge.tcl GUI using MERGESTAT data. Incremental check-in.

drh 2024-12-03 19:20 merge-enhancements
Commit cbd24a2594c590a005aaa3e3c363545a73c0ace8d980dad7b2ce53545dacbd15
2 files changed +172 -2 +4 -3
+172 -2
--- src/merge.c
+++ src/merge.c
@@ -20,26 +20,196 @@
2020
*/
2121
#include "config.h"
2222
#include "merge.h"
2323
#include <assert.h>
2424
25
+
26
+/*
27
+** Bring up a Tcl/Tk GUI to show details of the most recent merge.
28
+*/
29
+static void merge_info_tk(int bDark, int nContext){
30
+ fossil_fatal("Not yet implemented");
31
+}
32
+
33
+/*
34
+** Generate a TCL list on standard output that can be fed into the
35
+** merge.tcl script to show the details of the most recent merge
36
+** command associated with file "zFName".
37
+**
38
+** When this routine is called, we know that the mergestat table
39
+** exists, but we do not know if zFName is mentioned in that table.
40
+*/
41
+static void merge_info_tcl(const char *zFName, int nContext){
42
+ char *zFile; /* Normalized filename */
43
+ char *zTreename; /* Name of the file in the tree */
44
+ Blob fname; /* Filename relative to root */
45
+ Stmt q; /* To query the MERGESTAT table */
46
+ MergeBuilder mb; /* The merge builder object */
47
+ Blob pivot,v1,v2,out; /* Blobs for holding content */
48
+ const char *zFN; /* A filename */
49
+ int rid; /* RID value */
50
+ int sz; /* File size value */
51
+
52
+ zFile = mprintf("%/", zFName);
53
+ file_tree_name(zFile, &fname, 0, 1);
54
+ zTreename = blob_str(&fname);
55
+ db_prepare(&q,
56
+ /* 0 1 2 3 4 5 6 7 */
57
+ "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
58
+ " FROM mergestat"
59
+ " WHERE fnp=%Q OR fnr=%Q",
60
+ zTreename, zTreename
61
+ );
62
+ if( db_step(&q)!=SQLITE_ROW ){
63
+ db_finalize(&q);
64
+ fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
65
+ return;
66
+ }
67
+ mergebuilder_init_tcl(&mb);
68
+ mb.nContext = nContext;
69
+
70
+ /* Set up the pivot */
71
+ zFN = db_column_text(&q, 0);
72
+ if( zFN==0 ){
73
+ /* No pivot because the file was added */
74
+ mb.zPivot = "(no baseline)";
75
+ blob_zero(&pivot);
76
+ }else{
77
+ mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
78
+ rid = db_column_int(&q, 1);
79
+ content_get(rid, &pivot);
80
+ }
81
+ mb.pPivot = &pivot;
82
+
83
+ /* Set up the merge-in as V1 */
84
+ zFN = db_column_text(&q, 5);
85
+ if( zFN==0 ){
86
+ /* File deleted in the merged-in branch */
87
+ mb.zV1 = "(deleted file)";
88
+ blob_zero(&v1);
89
+ }else{
90
+ mb.zV1 = mprintf("%s (merge-in)", file_tail(zFN));
91
+ rid = db_column_int(&q, 6);
92
+ content_get(rid, &v1);
93
+ }
94
+ mb.pV1 = &v1;
95
+
96
+ /* Set up the local content as V2 */
97
+ zFN = db_column_text(&q, 2);
98
+ if( zFN==0 ){
99
+ /* File added by merge */
100
+ mb.zV2 = "(no original)";
101
+ blob_zero(&v2);
102
+ }else{
103
+ mb.zV2 = mprintf("%s (local)", file_tail(zFN));
104
+ rid = db_column_int(&q, 3);
105
+ sz = db_column_int(&q, 4);
106
+ if( rid==0 && sz>0 ){
107
+ /* The origin file had been edited so we'll have to pull its
108
+ ** original content out of the undo buffer */
109
+ Stmt q2;
110
+ db_prepare(&q2,
111
+ "SELECT content FROM undo"
112
+ " WHERE pathname=%Q AND octet_length(content)=%d",
113
+ zFN, sz
114
+ );
115
+ blob_zero(&v2);
116
+ if( db_step(&q2)==SQLITE_ROW ){
117
+ db_column_blob(&q, 0, &v2);
118
+ }else{
119
+ mb.zV2 = "(local content missing)";
120
+ }
121
+ db_finalize(&q2);
122
+ }else{
123
+ /* The origin file was unchanged when the merge first occurred */
124
+ content_get(rid, &v2);
125
+ }
126
+ }
127
+ mb.pV2 = &v2;
128
+
129
+ /* Set up the output */
130
+ zFN = db_column_text(&q, 7);
131
+ if( zFN==0 ){
132
+ mb.zOut = "(Merge Result)";
133
+ }else{
134
+ mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
135
+ }
136
+ blob_zero(&out);
137
+ mb.pOut = &out;
138
+
139
+ merge_three_blobs(&mb);
140
+ blob_write_to_file(&out, "-");
141
+
142
+ mb.xDestroy(&mb);
143
+ blob_reset(&pivot);
144
+ blob_reset(&v1);
145
+ blob_reset(&v2);
146
+ blob_reset(&out);
147
+ db_finalize(&q);
148
+}
149
+
25150
/*
26151
** COMMAND: merge-info
152
+**
153
+** Usage: %fossil merge-info [OPTIONS]
27154
**
28155
** Display information about the most recent merge operation.
29156
**
30157
** Right now, this command basically just dumps the localdb.mergestat
31158
** table. The plan moving forward is that it can generate data for
32159
** a Tk-based GUI to show the details of the merge. This command is
33160
** a work-in-progress.
161
+**
162
+** Options:
163
+** -c|--context N Show N lines of context around each change,
164
+** with negative N meaning show all content. Only
165
+** meaningful in combination with --tcl or --tk.
166
+** --dark Use dark mode for the Tcl/Tk-based GUI
167
+** --tcl FILE Generate (to stdout) a TCL list containing
168
+** information needed to display the changes to
169
+** FILE caused by the most recent merge.
170
+** --tk Bring up a Tcl/Tk GUI that shows the changes
171
+** associated with the most recent merge.
172
+**
34173
*/
35174
void merge_info_cmd(void){
175
+ const char *zCnt;
176
+ const char *zTcl;
177
+ int bTk;
178
+ int bDark = 0;
179
+ int nContext;
36180
Stmt q;
37
- verify_all_options();
181
+
38182
db_must_be_within_tree();
39
-
183
+ zTcl = find_option("tcl", 0, 1);
184
+ bTk = find_option("tk", 0, 0)!=0;
185
+ zCnt = find_option("context", "c", 1);
186
+ bDark = find_option("dark", 0, 0)!=0;
187
+ verify_all_options();
188
+ if( g.argc>2 ){
189
+ usage("[OPTIONS]");
190
+ }
191
+ if( zCnt ){
192
+ nContext = atoi(zCnt);
193
+ if( nContext<0 ) nContext = 0xfffffff;
194
+ }else{
195
+ nContext = 6;
196
+ }
40197
if( !db_table_exists("localdb","mergestat") ){
198
+ if( zTcl ){
199
+ fossil_print("ERROR {no merge data available}\n");
200
+ }else{
201
+ fossil_print("No merge data is available\n");
202
+ }
203
+ return;
204
+ }
205
+ if( bTk ){
206
+ merge_info_tk(bDark, nContext);
207
+ return;
208
+ }
209
+ if( zTcl ){
210
+ merge_info_tcl(zTcl, nContext);
41211
return;
42212
}
43213
db_prepare(&q,
44214
/* 0 1 2 3 4 */
45215
"SELECT op, fn, fnr, nc, msg FROM mergestat ORDER BY coalesce(fnr,fn)"
46216
--- src/merge.c
+++ src/merge.c
@@ -20,26 +20,196 @@
20 */
21 #include "config.h"
22 #include "merge.h"
23 #include <assert.h>
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25 /*
26 ** COMMAND: merge-info
 
 
27 **
28 ** Display information about the most recent merge operation.
29 **
30 ** Right now, this command basically just dumps the localdb.mergestat
31 ** table. The plan moving forward is that it can generate data for
32 ** a Tk-based GUI to show the details of the merge. This command is
33 ** a work-in-progress.
 
 
 
 
 
 
 
 
 
 
 
 
34 */
35 void merge_info_cmd(void){
 
 
 
 
 
36 Stmt q;
37 verify_all_options();
38 db_must_be_within_tree();
39
 
 
 
 
 
 
 
 
 
 
 
 
 
40 if( !db_table_exists("localdb","mergestat") ){
 
 
 
 
 
 
 
 
 
 
 
 
 
41 return;
42 }
43 db_prepare(&q,
44 /* 0 1 2 3 4 */
45 "SELECT op, fn, fnr, nc, msg FROM mergestat ORDER BY coalesce(fnr,fn)"
46
--- src/merge.c
+++ src/merge.c
@@ -20,26 +20,196 @@
20 */
21 #include "config.h"
22 #include "merge.h"
23 #include <assert.h>
24
25
26 /*
27 ** Bring up a Tcl/Tk GUI to show details of the most recent merge.
28 */
29 static void merge_info_tk(int bDark, int nContext){
30 fossil_fatal("Not yet implemented");
31 }
32
33 /*
34 ** Generate a TCL list on standard output that can be fed into the
35 ** merge.tcl script to show the details of the most recent merge
36 ** command associated with file "zFName".
37 **
38 ** When this routine is called, we know that the mergestat table
39 ** exists, but we do not know if zFName is mentioned in that table.
40 */
41 static void merge_info_tcl(const char *zFName, int nContext){
42 char *zFile; /* Normalized filename */
43 char *zTreename; /* Name of the file in the tree */
44 Blob fname; /* Filename relative to root */
45 Stmt q; /* To query the MERGESTAT table */
46 MergeBuilder mb; /* The merge builder object */
47 Blob pivot,v1,v2,out; /* Blobs for holding content */
48 const char *zFN; /* A filename */
49 int rid; /* RID value */
50 int sz; /* File size value */
51
52 zFile = mprintf("%/", zFName);
53 file_tree_name(zFile, &fname, 0, 1);
54 zTreename = blob_str(&fname);
55 db_prepare(&q,
56 /* 0 1 2 3 4 5 6 7 */
57 "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
58 " FROM mergestat"
59 " WHERE fnp=%Q OR fnr=%Q",
60 zTreename, zTreename
61 );
62 if( db_step(&q)!=SQLITE_ROW ){
63 db_finalize(&q);
64 fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
65 return;
66 }
67 mergebuilder_init_tcl(&mb);
68 mb.nContext = nContext;
69
70 /* Set up the pivot */
71 zFN = db_column_text(&q, 0);
72 if( zFN==0 ){
73 /* No pivot because the file was added */
74 mb.zPivot = "(no baseline)";
75 blob_zero(&pivot);
76 }else{
77 mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
78 rid = db_column_int(&q, 1);
79 content_get(rid, &pivot);
80 }
81 mb.pPivot = &pivot;
82
83 /* Set up the merge-in as V1 */
84 zFN = db_column_text(&q, 5);
85 if( zFN==0 ){
86 /* File deleted in the merged-in branch */
87 mb.zV1 = "(deleted file)";
88 blob_zero(&v1);
89 }else{
90 mb.zV1 = mprintf("%s (merge-in)", file_tail(zFN));
91 rid = db_column_int(&q, 6);
92 content_get(rid, &v1);
93 }
94 mb.pV1 = &v1;
95
96 /* Set up the local content as V2 */
97 zFN = db_column_text(&q, 2);
98 if( zFN==0 ){
99 /* File added by merge */
100 mb.zV2 = "(no original)";
101 blob_zero(&v2);
102 }else{
103 mb.zV2 = mprintf("%s (local)", file_tail(zFN));
104 rid = db_column_int(&q, 3);
105 sz = db_column_int(&q, 4);
106 if( rid==0 && sz>0 ){
107 /* The origin file had been edited so we'll have to pull its
108 ** original content out of the undo buffer */
109 Stmt q2;
110 db_prepare(&q2,
111 "SELECT content FROM undo"
112 " WHERE pathname=%Q AND octet_length(content)=%d",
113 zFN, sz
114 );
115 blob_zero(&v2);
116 if( db_step(&q2)==SQLITE_ROW ){
117 db_column_blob(&q, 0, &v2);
118 }else{
119 mb.zV2 = "(local content missing)";
120 }
121 db_finalize(&q2);
122 }else{
123 /* The origin file was unchanged when the merge first occurred */
124 content_get(rid, &v2);
125 }
126 }
127 mb.pV2 = &v2;
128
129 /* Set up the output */
130 zFN = db_column_text(&q, 7);
131 if( zFN==0 ){
132 mb.zOut = "(Merge Result)";
133 }else{
134 mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
135 }
136 blob_zero(&out);
137 mb.pOut = &out;
138
139 merge_three_blobs(&mb);
140 blob_write_to_file(&out, "-");
141
142 mb.xDestroy(&mb);
143 blob_reset(&pivot);
144 blob_reset(&v1);
145 blob_reset(&v2);
146 blob_reset(&out);
147 db_finalize(&q);
148 }
149
150 /*
151 ** COMMAND: merge-info
152 **
153 ** Usage: %fossil merge-info [OPTIONS]
154 **
155 ** Display information about the most recent merge operation.
156 **
157 ** Right now, this command basically just dumps the localdb.mergestat
158 ** table. The plan moving forward is that it can generate data for
159 ** a Tk-based GUI to show the details of the merge. This command is
160 ** a work-in-progress.
161 **
162 ** Options:
163 ** -c|--context N Show N lines of context around each change,
164 ** with negative N meaning show all content. Only
165 ** meaningful in combination with --tcl or --tk.
166 ** --dark Use dark mode for the Tcl/Tk-based GUI
167 ** --tcl FILE Generate (to stdout) a TCL list containing
168 ** information needed to display the changes to
169 ** FILE caused by the most recent merge.
170 ** --tk Bring up a Tcl/Tk GUI that shows the changes
171 ** associated with the most recent merge.
172 **
173 */
174 void merge_info_cmd(void){
175 const char *zCnt;
176 const char *zTcl;
177 int bTk;
178 int bDark = 0;
179 int nContext;
180 Stmt q;
181
182 db_must_be_within_tree();
183 zTcl = find_option("tcl", 0, 1);
184 bTk = find_option("tk", 0, 0)!=0;
185 zCnt = find_option("context", "c", 1);
186 bDark = find_option("dark", 0, 0)!=0;
187 verify_all_options();
188 if( g.argc>2 ){
189 usage("[OPTIONS]");
190 }
191 if( zCnt ){
192 nContext = atoi(zCnt);
193 if( nContext<0 ) nContext = 0xfffffff;
194 }else{
195 nContext = 6;
196 }
197 if( !db_table_exists("localdb","mergestat") ){
198 if( zTcl ){
199 fossil_print("ERROR {no merge data available}\n");
200 }else{
201 fossil_print("No merge data is available\n");
202 }
203 return;
204 }
205 if( bTk ){
206 merge_info_tk(bDark, nContext);
207 return;
208 }
209 if( zTcl ){
210 merge_info_tcl(zTcl, nContext);
211 return;
212 }
213 db_prepare(&q,
214 /* 0 1 2 3 4 */
215 "SELECT op, fn, fnr, nc, msg FROM mergestat ORDER BY coalesce(fnr,fn)"
216
+4 -3
--- src/merge3.c
+++ src/merge3.c
@@ -128,18 +128,18 @@
128128
blob_append(pOut, mergeMarker[iMark], -1);
129129
if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
130130
ensure_line_end(pOut, useCrLf);
131131
}
132132
133
+#if INTERFACE
133134
/*
134135
** This is an abstract class for constructing a merge.
135136
** Subclasses of this object format the merge output in different ways.
136137
**
137138
** To subclass, create an instance of the MergeBuilder object and fill
138139
** in appropriate method implementations.
139140
*/
140
-typedef struct MergeBuilder MergeBuilder;
141141
struct MergeBuilder {
142142
void (*xStart)(MergeBuilder*);
143143
void (*xSame)(MergeBuilder*, unsigned int);
144144
void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
145145
void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
@@ -164,10 +164,11 @@
164164
unsigned int lnV1; /* Lines read from v1 */
165165
unsigned int lnV2; /* Lines read from v2 */
166166
unsigned int lnOut; /* Lines written to out */
167167
unsigned int nConflict; /* Number of conflicts seen */
168168
};
169
+#endif /* INTERFACE */
169170
170171
171172
/************************* Generic MergeBuilder ******************************/
172173
/* These are generic methods for MergeBuilder. They just output debugging
173174
** information. But some of them are useful as base methods for other useful
@@ -545,11 +546,11 @@
545546
}
546547
p->lnPivot += nPivot;
547548
p->lnV1 += nV1;
548549
p->lnV2 += nV2;
549550
}
550
-static void mergebuilder_init_tcl(MergeBuilder *p){
551
+void mergebuilder_init_tcl(MergeBuilder *p){
551552
mergebuilder_init(p);
552553
p->xStart = tclStart;
553554
p->xSame = tclSame;
554555
p->xChngV1 = tclChngV1;
555556
p->xChngV2 = tclChngV2;
@@ -618,11 +619,11 @@
618619
** The return is 0 upon complete success. If any input file is binary,
619620
** -1 is returned and pOut is unmodified. If there are merge
620621
** conflicts, the merge proceeds as best as it can and the number
621622
** of conflicts is returns
622623
*/
623
-static int merge_three_blobs(MergeBuilder *p){
624
+int merge_three_blobs(MergeBuilder *p){
624625
int *aC1; /* Changes from pPivot to pV1 */
625626
int *aC2; /* Changes from pPivot to pV2 */
626627
int i1, i2; /* Index into aC1[] and aC2[] */
627628
int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
628629
int limit1, limit2; /* Sizes of aC1[] and aC2[] */
629630
--- src/merge3.c
+++ src/merge3.c
@@ -128,18 +128,18 @@
128 blob_append(pOut, mergeMarker[iMark], -1);
129 if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
130 ensure_line_end(pOut, useCrLf);
131 }
132
 
133 /*
134 ** This is an abstract class for constructing a merge.
135 ** Subclasses of this object format the merge output in different ways.
136 **
137 ** To subclass, create an instance of the MergeBuilder object and fill
138 ** in appropriate method implementations.
139 */
140 typedef struct MergeBuilder MergeBuilder;
141 struct MergeBuilder {
142 void (*xStart)(MergeBuilder*);
143 void (*xSame)(MergeBuilder*, unsigned int);
144 void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
145 void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
@@ -164,10 +164,11 @@
164 unsigned int lnV1; /* Lines read from v1 */
165 unsigned int lnV2; /* Lines read from v2 */
166 unsigned int lnOut; /* Lines written to out */
167 unsigned int nConflict; /* Number of conflicts seen */
168 };
 
169
170
171 /************************* Generic MergeBuilder ******************************/
172 /* These are generic methods for MergeBuilder. They just output debugging
173 ** information. But some of them are useful as base methods for other useful
@@ -545,11 +546,11 @@
545 }
546 p->lnPivot += nPivot;
547 p->lnV1 += nV1;
548 p->lnV2 += nV2;
549 }
550 static void mergebuilder_init_tcl(MergeBuilder *p){
551 mergebuilder_init(p);
552 p->xStart = tclStart;
553 p->xSame = tclSame;
554 p->xChngV1 = tclChngV1;
555 p->xChngV2 = tclChngV2;
@@ -618,11 +619,11 @@
618 ** The return is 0 upon complete success. If any input file is binary,
619 ** -1 is returned and pOut is unmodified. If there are merge
620 ** conflicts, the merge proceeds as best as it can and the number
621 ** of conflicts is returns
622 */
623 static int merge_three_blobs(MergeBuilder *p){
624 int *aC1; /* Changes from pPivot to pV1 */
625 int *aC2; /* Changes from pPivot to pV2 */
626 int i1, i2; /* Index into aC1[] and aC2[] */
627 int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
628 int limit1, limit2; /* Sizes of aC1[] and aC2[] */
629
--- src/merge3.c
+++ src/merge3.c
@@ -128,18 +128,18 @@
128 blob_append(pOut, mergeMarker[iMark], -1);
129 if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
130 ensure_line_end(pOut, useCrLf);
131 }
132
133 #if INTERFACE
134 /*
135 ** This is an abstract class for constructing a merge.
136 ** Subclasses of this object format the merge output in different ways.
137 **
138 ** To subclass, create an instance of the MergeBuilder object and fill
139 ** in appropriate method implementations.
140 */
 
141 struct MergeBuilder {
142 void (*xStart)(MergeBuilder*);
143 void (*xSame)(MergeBuilder*, unsigned int);
144 void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
145 void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
@@ -164,10 +164,11 @@
164 unsigned int lnV1; /* Lines read from v1 */
165 unsigned int lnV2; /* Lines read from v2 */
166 unsigned int lnOut; /* Lines written to out */
167 unsigned int nConflict; /* Number of conflicts seen */
168 };
169 #endif /* INTERFACE */
170
171
172 /************************* Generic MergeBuilder ******************************/
173 /* These are generic methods for MergeBuilder. They just output debugging
174 ** information. But some of them are useful as base methods for other useful
@@ -545,11 +546,11 @@
546 }
547 p->lnPivot += nPivot;
548 p->lnV1 += nV1;
549 p->lnV2 += nV2;
550 }
551 void mergebuilder_init_tcl(MergeBuilder *p){
552 mergebuilder_init(p);
553 p->xStart = tclStart;
554 p->xSame = tclSame;
555 p->xChngV1 = tclChngV1;
556 p->xChngV2 = tclChngV2;
@@ -618,11 +619,11 @@
619 ** The return is 0 upon complete success. If any input file is binary,
620 ** -1 is returned and pOut is unmodified. If there are merge
621 ** conflicts, the merge proceeds as best as it can and the number
622 ** of conflicts is returns
623 */
624 int merge_three_blobs(MergeBuilder *p){
625 int *aC1; /* Changes from pPivot to pV1 */
626 int *aC2; /* Changes from pPivot to pV2 */
627 int i1, i2; /* Index into aC1[] and aC2[] */
628 int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
629 int limit1, limit2; /* Sizes of aC1[] and aC2[] */
630

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button