Fossil

Diff
Login

Diff

Differences From Artifact [f65a4851d2]:

To Artifact [cf5abc2b02]:


37
38
39
40
41
42
43






























































44
45
46
47
48
49
50
51
52
53
54
55
56
57


58
59
60
61
62
63
64
65
66
67






68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

86
87
88
89
90
91
92
93

94
95
96
97
98
99
100


101
102

103
104


105
106
107
108
109
110
111
112
113
114
115

116
117
118
119
120
121
122
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126





127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

150
151
152
153
154
155
156
157

158
159
160
161
162
163


164
165


166
167

168
169
170
171
172
173
174
175
176
177
178
179

180
181
182
183
184
185
186
187







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+














+
+





-
-
-
-
-
+
+
+
+
+
+

















-
+







-
+





-
-
+
+
-
-
+

-
+
+










-
+







  zName = (const char*)sqlite3_value_text(argv[0]);
  if( zName==0 || (zName[0]=='-' && zName[1]==0) ) return;
  sz = blob_read_from_file(&x, zName, RepoFILE);
  sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT);
  blob_reset(&x);
}

/*
** mkdelta(X,Y)
**
** X is an numeric artifact id.  Y is a filename.
**
** Compute a compressed delta that carries X into Y.  Or return NULL
** if X is equal to Y.
*/
static void mkdeltaFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  const char *zFile;
  Blob x, y;
  int rid;
  char *aOut;
  int nOut;
  sqlite3_int64 sz;

  rid = sqlite3_value_int(argv[0]);
  if( !content_get(rid, &x) ){
    sqlite3_result_error(context, "mkdelta(X,Y): no content for X", -1);
    return;
  }
  zFile = (const char*)sqlite3_value_text(argv[1]);
  if( zFile==0 ){
    sqlite3_result_error(context, "mkdelta(X,Y): NULL Y argument", -1);
    blob_reset(&x);
    return;
  }
  sz = blob_read_from_file(&y, zFile, RepoFILE);
  if( sz<0 ){
    sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1);
    blob_reset(&x);
    return;
  }
  aOut = sqlite3_malloc64(sz+70);
  if( aOut==0 ){
    sqlite3_result_error_nomem(context);
    blob_reset(&y);
    blob_reset(&x);
    return;
  }
  if( blob_size(&x)==blob_size(&y)
   && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0
  ){
    blob_reset(&y);
    blob_reset(&x);
    return;
  }
  nOut = delta_create(blob_buffer(&x),blob_size(&x),
                      blob_buffer(&y),blob_size(&y), aOut);
  blob_reset(&x);
  blob_reset(&y);
  blob_init(&x, aOut, nOut);
  blob_compress(&x, &x);
  sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x),
                        SQLITE_TRANSIENT);
  blob_reset(&x);
}


/*
** Generate a binary patch file and store it into the file
** named zOut.
*/
void patch_create(const char *zOut){
  int vid;
  if( file_isdir(zOut, ExtFILE)!=0 ){
    fossil_fatal("patch file already exists: %s", zOut);
  }
  add_content_sql_commands(g.db);
  deltafunc_init(g.db);
  sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0,
                          readfileFunc, 0, 0);
  sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0,
                          mkdeltaFunc, 0, 0);
  db_multi_exec("ATTACH %Q AS patch;", zOut);
  db_multi_exec(
    "PRAGMA patch.journal_mode=OFF;\n"
    "PRAGMA patch.page_size=512;\n"
    "CREATE TABLE patch.chng(\n"
    "  fname TEXT,\n"   /* Filename */
    "  hash TEXT,\n"    /* Baseline hash.  NULL for new files. */
    "  isexe BOOL,\n"   /* True if executable */
    "  islink BOOL,\n"  /* True if is a symbolic link */
    "  delta BLOB\n"    /* Delta.  NULL if file deleted */
    "  pathname TEXT,\n" /* Filename */
    "  origname TEXT,\n" /* Name before rename.  NULL if not renamed */
    "  hash TEXT,\n"     /* Baseline hash.  NULL for new files. */
    "  isexe BOOL,\n"    /* True if executable */
    "  islink BOOL,\n"   /* True if is a symbolic link */
    "  delta BLOB\n"     /* Delta.  NULL if file deleted or unchanged */
    ");"
    "CREATE TABLE patch.cfg(\n"
    "  key TEXT,\n"
    "  value ANY\n"
    ");"
  );
  vid = db_lget_int("checkout", 0);
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_multi_exec(
    "INSERT INTO patch.cfg(key,value)"
    "SELECT 'baseline',uuid FROM blob WHERE rid=%d", vid);
  if( db_exists("SELECT 1 FROM vmerge") ){
    db_multi_exec("INSERT INTO patch.cfg(key,value)VALUES('merged',1);");
  }
  
  /* New files */
  db_multi_exec(
    "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)"
    "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)"
    "  SELECT pathname, NULL, isexe, islink,"
    "         compress(read_co_file(%Q||pathname))"
    "    FROM vfile WHERE rid==0;",
    g.zLocalRoot
  );
  /* Deleted files */
  db_multi_exec(
    "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)"
    "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)"
    "  SELECT pathname, NULL, 0, 0, NULL"
    "    FROM vfile WHERE deleted;"
  );
  /* Changed files */
  db_multi_exec(
    "INSERT INTO patch.chng(fname,hash,isexe,islink,delta)"
    "  SELECT pathname, blob.uuid, isexe, islink,"
    "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)"
    "  SELECT pathname, origname, blob.uuid, isexe, islink,"
    "         compress(delta_create(content(blob.uuid),"
                          "read_co_file(%Q||pathname)))"
            " mkdelta(blob.rid, %Q||pathname)"
    "    FROM vfile, blob"
    "   WHERE blob.rid=vfile.rid AND NOT deleted AND chnged;",
    "   WHERE blob.rid=vfile.rid"
    "     AND NOT deleted AND (chnged OR origname<>pathname);",
    g.zLocalRoot
  );
}

/*
** Attempt to load and validate a patchfile identified by the first
** argument.
*/
void patch_attach(const char *zIn){
  Stmt q;
  if( !file_isfile(zIn, zIn) ){
  if( !file_isfile(zIn, ExtFILE) ){
    fossil_fatal("no such file: %s", zIn);
  }
  if( g.db==0 ){
    sqlite3_open(":memory:", &g.db);
  }
  db_multi_exec("ATTACH %Q AS patch", zIn);
  db_prepare(&q, "PRAGMA patch.quick_check");
136
137
138
139
140
141
142
143
144






145
146


147

148
149
150

151




152
153



154

































155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175




176
177
178
179
180
181
182
183
184
185
186
187
188
189
190





191
192
193
194
195
196
197
198
199
200
201

202
203
204
205
206





207
208
209
210
211
212
213
201
202
203
204
205
206
207


208
209
210
211
212
213
214
215
216
217

218
219
220

221
222
223
224
225
226


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336







-
-
+
+
+
+
+
+


+
+
-
+


-
+

+
+
+
+
-
-
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




















-
+
+
+
+















+
+
+
+
+











+





+
+
+
+
+







  db_prepare(&q, "SELECT value FROM patch.cfg WHERE key='baseline'");
  if( db_step(&q)==SQLITE_ROW ){
    fossil_print("Patch against check-in %S\n", db_column_text(&q,0));
  }else{
    fossil_fatal("ERROR: Missing patch baseline");
  }
  db_finalize(&q);
  db_prepare(&q, "SELECT fname, hash IS NULL AS isnew, delta IS NULL AS isdel"
                 "  FROM patch.chng ORDER BY 1");
  db_prepare(&q,
    "SELECT pathname,"
          " hash IS NULL AND delta IS NOT NULL,"
          " delta IS NULL,"
          " origname"
    "  FROM patch.chng ORDER BY 1");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zClass = "CHANGED";
    const char *zName = db_column_text(&q,0);
    const char *zOrigName = db_column_text(&q, 3);
    if( db_column_int(&q, 1) ){
    if( db_column_int(&q, 1) && zOrigName==0 ){
      zClass = "NEW";
    }else if( db_column_int(&q, 2) ){
      zClass = "DELETED";
      zClass = zOrigName==0 ? "DELETED" : 0;
    }
    if( zOrigName!=0 && zOrigName[0]!=0 ){
      fossil_print("%-10s %s -> %s\n", "RENAME",zOrigName,zName);
    }
    if( zClass ){
    fossil_print("%-10s %s\n", zClass, db_column_text(&q,0));
  }
      fossil_print("%-10s %s\n", zClass, zName);
    }
  }
  db_finalize(&q);
}

/*
** Apply the patch currently attached as database "patch".
**
** First update the check-out to be at "baseline".  Then loop through
** and update all files.
*/
void patch_apply(void){
  Stmt q;
  Blob cmd;
  blob_init(&cmd, 0, 0);
  db_prepare(&q,
    "SELECT patch.cfg.value"
    "  FROM patch.cfg, localdb.vvar"
    " WHERE patch.cfg.key='baseline'"
    "   AND localdb.vvar.name='checkout-hash'"
    "   AND patch.cfg.key<>localdb.vvar.name"
  );
  if( db_step(&q)==SQLITE_ROW ){
    blob_append_escaped_arg(&cmd, g.nameOfExe);
    blob_appendf(&cmd, " update %s", db_column_text(&q, 0));
  }
  db_finalize(&q);
  if( blob_size(&cmd)>0 ){
    int rc = fossil_system(blob_str(&cmd));
    if( rc ){
      fossil_fatal("unable to update to the baseline check-out: %s",
                   blob_str(&cmd));
    }
  }
  blob_reset(&cmd);
  
}


/*
** COMMAND: patch
**
** Usage: %fossil patch SUBCOMMAND ?ARGS ..?
**
** This command is used to creates, view, and apply Fossil binary patches.
** A Fossil binary patch is a single (binary) file that captures all of the
** uncommitted changes of a check-out.  Use Fossil binary patches to transfer
** proposed or incomplete changes between machines for testing or analysis.
**
** > fossil patch create FILENAME
**
**       Create a new binary patch in FILENAME that captures all uncommitted
**       changes in the current check-out.
**
** > fossil patch apply FILENAME
**
**       Apply the changes in FILENAME to the current check-out.
**       Apply the changes in FILENAME to the current check-out. Options:
**
**           -f|--force     Apply the patch even though there are unsaved
**                          changes in the current check-out.
**
** > fossil patch diff [DIFF-FLAGS] FILENAME
**
**       View the changes specified by the binary patch FILENAME in a
**       human-readable format.  The usual diff flags apply.
**
** > fossil patch push REMOTE-CHECKOUT
**
**       Create a patch for the current check-out, transfer that patch to
**       a remote machine (using ssh) and apply the patch there.
**
** > fossil patch pull REMOTE-CHECKOUT
**
**       Create a patch on a remote check-out, transfer that patch to the
**       local machine (using ssh) and apply the patch in the local checkout.
**
** > fossil patch view FILENAME
**
**       View a summary of the the changes in the binary patch FILENAME.
**
*/
void patch_cmd(void){
  const char *zCmd;
  size_t n;
  if( g.argc<3 ){
    patch_usage:
    usage("apply|create|pull|push|view");
  }
  zCmd = g.argv[2];
  n = strlen(zCmd);
  if( strncmp(zCmd, "apply", n)==0 ){
    int forceFlag = find_option("force","f",0)!=0;
    db_must_be_within_tree();
    verify_all_options();
    if( g.argc!=4 ){
      usage("apply FILENAME");
    }
    if( !forceFlag && unsaved_changes(0) ){
      fossil_fatal("there are unsaved changes in the current checkout");
    }
    patch_attach(g.argv[3]);
    patch_apply();
  }else
  if( strncmp(zCmd, "create", n)==0 ){
    db_must_be_within_tree();
    verify_all_options();
    if( g.argc!=4 ){
      usage("create FILENAME");
    }