|
1
|
/* |
|
2
|
** Copyright (c) 2007 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
** |
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file contains code used to check-in versions of the project |
|
19
|
** from the local repository. |
|
20
|
*/ |
|
21
|
#include "config.h" |
|
22
|
#include "checkin.h" |
|
23
|
#include <assert.h> |
|
24
|
|
|
25
|
/* |
|
26
|
** Change filter options. |
|
27
|
*/ |
|
28
|
enum { |
|
29
|
/* Zero-based bit indexes. */ |
|
30
|
CB_EDITED , CB_UPDATED , CB_CHANGED, CB_MISSING , CB_ADDED, CB_DELETED, |
|
31
|
CB_RENAMED, CB_CONFLICT, CB_META , CB_UNCHANGED, CB_EXTRA, CB_MERGE , |
|
32
|
CB_RELPATH, CB_CLASSIFY, CB_MTIME , CB_SIZE , CB_FATAL, CB_COMMENT, |
|
33
|
|
|
34
|
/* Bitmask values. */ |
|
35
|
C_EDITED = 1 << CB_EDITED, /* Edited, merged, and conflicted files. */ |
|
36
|
C_UPDATED = 1 << CB_UPDATED, /* Files updated by merge/integrate. */ |
|
37
|
C_CHANGED = 1 << CB_CHANGED, /* Treated the same as the above two. */ |
|
38
|
C_MISSING = 1 << CB_MISSING, /* Missing and non- files. */ |
|
39
|
C_ADDED = 1 << CB_ADDED, /* Added files. */ |
|
40
|
C_DELETED = 1 << CB_DELETED, /* Deleted files. */ |
|
41
|
C_RENAMED = 1 << CB_RENAMED, /* Renamed files. */ |
|
42
|
C_CONFLICT = 1 << CB_CONFLICT, /* Files having merge conflicts. */ |
|
43
|
C_META = 1 << CB_META, /* Files with metadata changes. */ |
|
44
|
C_UNCHANGED = 1 << CB_UNCHANGED, /* Unchanged files. */ |
|
45
|
C_EXTRA = 1 << CB_EXTRA, /* Unmanaged files. */ |
|
46
|
C_MERGE = 1 << CB_MERGE, /* Merge contributors. */ |
|
47
|
C_FILTER = C_EDITED | C_UPDATED | C_CHANGED | C_MISSING | C_ADDED |
|
48
|
| C_DELETED | C_RENAMED | C_CONFLICT | C_META | C_UNCHANGED |
|
49
|
| C_EXTRA | C_MERGE, /* All filter bits. */ |
|
50
|
C_ALL = C_FILTER & ~(C_EXTRA | C_MERGE),/* All managed files. */ |
|
51
|
C_DIFFER = C_FILTER & ~(C_UNCHANGED | C_MERGE),/* All differences. */ |
|
52
|
C_RELPATH = 1 << CB_RELPATH, /* Show relative paths. */ |
|
53
|
C_CLASSIFY = 1 << CB_CLASSIFY, /* Show file change types. */ |
|
54
|
C_DEFAULT = (C_ALL & ~C_UNCHANGED) | C_MERGE | C_CLASSIFY, |
|
55
|
C_MTIME = 1 << CB_MTIME, /* Show file modification time. */ |
|
56
|
C_SIZE = 1 << CB_SIZE, /* Show file size in bytes. */ |
|
57
|
C_FATAL = 1 << CB_FATAL, /* Fail on MISSING/NOT_A_FILE. */ |
|
58
|
C_COMMENT = 1 << CB_COMMENT, /* Precede each line with "# ". */ |
|
59
|
}; |
|
60
|
|
|
61
|
/* |
|
62
|
** Create a TEMP table named SFILE and add all unmanaged files named on |
|
63
|
** the command-line to that table. If directories are named, then add |
|
64
|
** all unmanaged files contained underneath those directories. If there |
|
65
|
** are no files or directories named on the command-line, then add all |
|
66
|
** unmanaged files anywhere in the check-out. |
|
67
|
** |
|
68
|
** This routine never follows symlinks. It always treats symlinks as |
|
69
|
** object unto themselves. |
|
70
|
*/ |
|
71
|
static void locate_unmanaged_files( |
|
72
|
int argc, /* Number of command-line arguments to examine */ |
|
73
|
char **argv, /* values of command-line arguments */ |
|
74
|
unsigned scanFlags, /* Zero or more SCAN_xxx flags */ |
|
75
|
Glob *pIgnore /* Do not add files that match this GLOB */ |
|
76
|
){ |
|
77
|
Blob name; /* Name of a candidate file or directory */ |
|
78
|
char *zName; /* Name of a candidate file or directory */ |
|
79
|
int isDir; /* 1 for a directory, 0 if doesn't exist, 2 for anything else */ |
|
80
|
int i; /* Loop counter */ |
|
81
|
int nRoot; /* length of g.zLocalRoot */ |
|
82
|
|
|
83
|
db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s," |
|
84
|
" mtime INTEGER, size INTEGER)", filename_collation()); |
|
85
|
nRoot = (int)strlen(g.zLocalRoot); |
|
86
|
if( argc==0 ){ |
|
87
|
blob_init(&name, g.zLocalRoot, nRoot - 1); |
|
88
|
vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE); |
|
89
|
blob_reset(&name); |
|
90
|
}else{ |
|
91
|
for(i=0; i<argc; i++){ |
|
92
|
file_canonical_name(argv[i], &name, 0); |
|
93
|
zName = blob_str(&name); |
|
94
|
isDir = file_isdir(zName, SymFILE); |
|
95
|
if( isDir==1 ){ |
|
96
|
vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE); |
|
97
|
}else if( isDir==0 ){ |
|
98
|
fossil_warning("not found: %s", &zName[nRoot]); |
|
99
|
}else if( file_access(zName, R_OK) ){ |
|
100
|
fossil_fatal("cannot open %s", &zName[nRoot]); |
|
101
|
}else{ |
|
102
|
/* Only add unmanaged file paths specified on the command line. */ |
|
103
|
db_multi_exec( |
|
104
|
"INSERT OR IGNORE INTO sfile(pathname)" |
|
105
|
" SELECT %Q WHERE NOT EXISTS" |
|
106
|
" (SELECT 1 FROM vfile WHERE pathname=%Q)", |
|
107
|
&zName[nRoot], &zName[nRoot] |
|
108
|
); |
|
109
|
} |
|
110
|
blob_reset(&name); |
|
111
|
} |
|
112
|
} |
|
113
|
} |
|
114
|
|
|
115
|
/* |
|
116
|
** Generate text describing all changes. |
|
117
|
** |
|
118
|
** We assume that vfile_check_signature has been run. |
|
119
|
*/ |
|
120
|
static void status_report( |
|
121
|
Blob *report, /* Append the status report here */ |
|
122
|
unsigned flags /* Filter and other configuration flags */ |
|
123
|
){ |
|
124
|
Stmt q; |
|
125
|
int nErr = 0; |
|
126
|
Blob rewrittenOrigName, rewrittenPathname; |
|
127
|
Blob sql = BLOB_INITIALIZER, where = BLOB_INITIALIZER; |
|
128
|
const char *zName; |
|
129
|
int i; |
|
130
|
|
|
131
|
/* Skip the file report if no files are requested at all. */ |
|
132
|
if( !(flags & (C_ALL | C_EXTRA)) ){ |
|
133
|
goto skipFiles; |
|
134
|
} |
|
135
|
|
|
136
|
/* Assemble the path-limiting WHERE clause, if any. */ |
|
137
|
blob_zero(&where); |
|
138
|
for(i=2; i<g.argc; i++){ |
|
139
|
Blob fname; |
|
140
|
file_tree_name(g.argv[i], &fname, 0, 1); |
|
141
|
zName = blob_str(&fname); |
|
142
|
if( fossil_strcmp(zName, ".")==0 ){ |
|
143
|
blob_reset(&where); |
|
144
|
break; |
|
145
|
} |
|
146
|
blob_append_sql(&where, |
|
147
|
" %s (pathname=%Q %s) " |
|
148
|
"OR (pathname>'%q/' %s AND pathname<'%q0' %s)", |
|
149
|
(blob_size(&where)>0) ? "OR" : "AND", zName, |
|
150
|
filename_collation(), zName, filename_collation(), |
|
151
|
zName, filename_collation() |
|
152
|
); |
|
153
|
} |
|
154
|
|
|
155
|
/* Obtain the list of managed files if appropriate. */ |
|
156
|
blob_zero(&sql); |
|
157
|
if( flags & C_ALL ){ |
|
158
|
/* Start with a list of all managed files. */ |
|
159
|
blob_append_sql(&sql, |
|
160
|
"SELECT pathname, %s as mtime, %s as size, deleted, chnged, rid," |
|
161
|
" coalesce(origname!=pathname,0) AS renamed, 1 AS managed," |
|
162
|
" origname" |
|
163
|
" FROM vfile LEFT JOIN blob USING (rid)" |
|
164
|
" WHERE is_selected(id)%s", |
|
165
|
flags & C_MTIME ? "datetime(checkin_mtime(:vid, rid), " |
|
166
|
"'unixepoch', toLocal())" : "''" /*safe-for-%s*/, |
|
167
|
flags & C_SIZE ? "coalesce(blob.size, 0)" : "0" /*safe-for-%s*/, |
|
168
|
blob_sql_text(&where)); |
|
169
|
|
|
170
|
/* Exclude unchanged files unless requested. */ |
|
171
|
if( !(flags & C_UNCHANGED) ){ |
|
172
|
blob_append_sql(&sql, |
|
173
|
" AND (chnged OR deleted OR rid=0 OR pathname!=origname)"); |
|
174
|
} |
|
175
|
} |
|
176
|
|
|
177
|
/* If C_EXTRA, add unmanaged files to the query result too. */ |
|
178
|
if( flags & C_EXTRA ){ |
|
179
|
if( blob_size(&sql) ){ |
|
180
|
blob_append_sql(&sql, " UNION ALL"); |
|
181
|
} |
|
182
|
blob_append_sql(&sql, |
|
183
|
" SELECT pathname, %s, %s, 0, 0, 0, 0, 0, NULL" |
|
184
|
" FROM sfile WHERE pathname NOT IN (%s)%s", |
|
185
|
flags & C_MTIME ? "datetime(mtime, 'unixepoch', toLocal())" : "''", |
|
186
|
flags & C_SIZE ? "size" : "0", |
|
187
|
fossil_all_reserved_names(0), blob_sql_text(&where)); |
|
188
|
} |
|
189
|
blob_reset(&where); |
|
190
|
|
|
191
|
/* Pre-create the "ok" temporary table so the checkin_mtime() SQL function |
|
192
|
* does not lead to SQLITE_ABORT_ROLLBACK during execution of the OP_OpenRead |
|
193
|
* SQLite opcode. checkin_mtime() calls mtime_of_manifest_file() which |
|
194
|
* creates a temporary table if it doesn't already exist, thus invalidating |
|
195
|
* the prepared statement in the middle of its execution. */ |
|
196
|
db_multi_exec("CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"); |
|
197
|
|
|
198
|
/* Append an ORDER BY clause then compile the query. */ |
|
199
|
blob_append_sql(&sql, " ORDER BY pathname"); |
|
200
|
db_prepare(&q, "%s", blob_sql_text(&sql)); |
|
201
|
blob_reset(&sql); |
|
202
|
|
|
203
|
/* Bind the check-out version ID to the query if needed. */ |
|
204
|
if( (flags & C_ALL) && (flags & C_MTIME) ){ |
|
205
|
db_bind_int(&q, ":vid", db_lget_int("checkout", 0)); |
|
206
|
} |
|
207
|
|
|
208
|
/* Execute the query and assemble the report. */ |
|
209
|
blob_zero(&rewrittenPathname); |
|
210
|
blob_zero(&rewrittenOrigName); |
|
211
|
while( db_step(&q)==SQLITE_ROW ){ |
|
212
|
const char *zPathname = db_column_text(&q, 0); |
|
213
|
const char *zClass = 0; |
|
214
|
int isManaged = db_column_int(&q, 7); |
|
215
|
const char *zMtime = db_column_text(&q, 1); |
|
216
|
int size = db_column_int(&q, 2); |
|
217
|
int isDeleted = db_column_int(&q, 3); |
|
218
|
int isChnged = db_column_int(&q, 4); |
|
219
|
int isNew = isManaged && !db_column_int(&q, 5); |
|
220
|
int isRenamed = db_column_int(&q, 6); |
|
221
|
const char *zOrigName = 0; |
|
222
|
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); |
|
223
|
int isMissing = !file_isfile_or_link(zFullName); |
|
224
|
|
|
225
|
/* Determine the file change classification, if any. */ |
|
226
|
if( isDeleted ){ |
|
227
|
if( flags & C_DELETED ){ |
|
228
|
zClass = "DELETED"; |
|
229
|
} |
|
230
|
}else if( isMissing ){ |
|
231
|
if( file_access(zFullName, F_OK)==0 ){ |
|
232
|
if( flags & C_MISSING ){ |
|
233
|
zClass = "NOT_A_FILE"; |
|
234
|
} |
|
235
|
if( flags & C_FATAL ){ |
|
236
|
fossil_warning("not a file: %s", zFullName); |
|
237
|
nErr++; |
|
238
|
} |
|
239
|
}else{ |
|
240
|
if( flags & C_MISSING ){ |
|
241
|
zClass = "MISSING"; |
|
242
|
} |
|
243
|
if( flags & C_FATAL ){ |
|
244
|
fossil_warning("missing file: %s", zFullName); |
|
245
|
nErr++; |
|
246
|
} |
|
247
|
} |
|
248
|
}else if( isNew ){ |
|
249
|
if( flags & C_ADDED ){ |
|
250
|
zClass = "ADDED"; |
|
251
|
} |
|
252
|
}else if( (flags & (C_UPDATED | C_CHANGED)) && isChnged==2 ){ |
|
253
|
zClass = "UPDATED_BY_MERGE"; |
|
254
|
}else if( (flags & C_ADDED) && isChnged==3 ){ |
|
255
|
zClass = "ADDED_BY_MERGE"; |
|
256
|
}else if( (flags & (C_UPDATED | C_CHANGED)) && isChnged==4 ){ |
|
257
|
zClass = "UPDATED_BY_INTEGRATE"; |
|
258
|
}else if( (flags & C_ADDED) && isChnged==5 ){ |
|
259
|
zClass = "ADDED_BY_INTEGRATE"; |
|
260
|
}else if( (flags & C_META) && isChnged==6 ){ |
|
261
|
zClass = "EXECUTABLE"; |
|
262
|
}else if( (flags & C_META) && isChnged==7 ){ |
|
263
|
zClass = "SYMLINK"; |
|
264
|
}else if( (flags & C_META) && isChnged==8 ){ |
|
265
|
zClass = "UNEXEC"; |
|
266
|
}else if( (flags & C_META) && isChnged==9 ){ |
|
267
|
zClass = "UNLINK"; |
|
268
|
}else if( (flags & C_CONFLICT) && isChnged && !file_islink(zFullName) |
|
269
|
&& file_contains_merge_marker(zFullName) ){ |
|
270
|
zClass = "CONFLICT"; |
|
271
|
}else if( (flags & (C_EDITED | C_CHANGED)) && isChnged |
|
272
|
&& (isChnged<2 || isChnged>9) ){ |
|
273
|
zClass = "EDITED"; |
|
274
|
}else if( (flags & C_UNCHANGED) && isManaged && !isNew |
|
275
|
&& !isChnged && !isRenamed ){ |
|
276
|
zClass = "UNCHANGED"; |
|
277
|
}else if( (flags & C_EXTRA) && !isManaged ){ |
|
278
|
zClass = "EXTRA"; |
|
279
|
} |
|
280
|
if( (flags & C_RENAMED) && isRenamed ){ |
|
281
|
zOrigName = db_column_text(&q,8); |
|
282
|
if( zClass==0 ){ |
|
283
|
zClass = "RENAMED"; |
|
284
|
} |
|
285
|
} |
|
286
|
|
|
287
|
/* Only report files for which a change classification was determined. */ |
|
288
|
if( zClass ){ |
|
289
|
if( flags & C_COMMENT ){ |
|
290
|
blob_append(report, "# ", 2); |
|
291
|
} |
|
292
|
if( flags & C_CLASSIFY ){ |
|
293
|
blob_appendf(report, "%-10s ", zClass); |
|
294
|
} |
|
295
|
if( flags & C_MTIME ){ |
|
296
|
blob_append(report, zMtime, -1); |
|
297
|
blob_append(report, " ", 2); |
|
298
|
} |
|
299
|
if( flags & C_SIZE ){ |
|
300
|
blob_appendf(report, "%7d ", size); |
|
301
|
} |
|
302
|
if( flags & C_RELPATH ){ |
|
303
|
/* If C_RELPATH, display paths relative to current directory. */ |
|
304
|
file_relative_name(zFullName, &rewrittenPathname, 0); |
|
305
|
zPathname = blob_str(&rewrittenPathname); |
|
306
|
if( zPathname[0]=='.' && zPathname[1]=='/' ){ |
|
307
|
zPathname += 2; /* no unnecessary ./ prefix */ |
|
308
|
} |
|
309
|
if( (flags & (C_FILTER ^ C_RENAMED)) && zOrigName ){ |
|
310
|
char *zOrigFullName = mprintf("%s%s", g.zLocalRoot, zOrigName); |
|
311
|
file_relative_name(zOrigFullName, &rewrittenOrigName, 0); |
|
312
|
zOrigName = blob_str(&rewrittenOrigName); |
|
313
|
fossil_free(zOrigFullName); |
|
314
|
if( zOrigName[0]=='.' && zOrigName[1]=='/' ){ |
|
315
|
zOrigName += 2; /* no unnecessary ./ prefix */ |
|
316
|
} |
|
317
|
} |
|
318
|
} |
|
319
|
if( (flags & (C_FILTER ^ C_RENAMED)) && zOrigName ){ |
|
320
|
blob_appendf(report, "%s -> ", zOrigName); |
|
321
|
} |
|
322
|
blob_appendf(report, "%s\n", zPathname); |
|
323
|
} |
|
324
|
free(zFullName); |
|
325
|
} |
|
326
|
blob_reset(&rewrittenPathname); |
|
327
|
blob_reset(&rewrittenOrigName); |
|
328
|
db_finalize(&q); |
|
329
|
|
|
330
|
/* If C_MERGE, put merge contributors at the end of the report. */ |
|
331
|
skipFiles: |
|
332
|
if( flags & C_MERGE ){ |
|
333
|
db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0" ); |
|
334
|
while( db_step(&q)==SQLITE_ROW ){ |
|
335
|
if( flags & C_COMMENT ){ |
|
336
|
blob_append(report, "# ", 2); |
|
337
|
} |
|
338
|
if( flags & C_CLASSIFY ){ |
|
339
|
const char *zClass; |
|
340
|
switch( db_column_int(&q, 1) ){ |
|
341
|
case -1: zClass = "CHERRYPICK" ; break; |
|
342
|
case -2: zClass = "BACKOUT" ; break; |
|
343
|
case -4: zClass = "INTEGRATE" ; break; |
|
344
|
default: zClass = "MERGED_WITH"; break; |
|
345
|
} |
|
346
|
blob_appendf(report, "%-10s ", zClass); |
|
347
|
} |
|
348
|
blob_append(report, db_column_text(&q, 0), -1); |
|
349
|
blob_append(report, "\n", 1); |
|
350
|
} |
|
351
|
db_finalize(&q); |
|
352
|
} |
|
353
|
if( nErr ){ |
|
354
|
fossil_fatal("aborting due to prior errors"); |
|
355
|
} |
|
356
|
} |
|
357
|
|
|
358
|
/* |
|
359
|
** Use the "relative-paths" setting and the --abs-paths and |
|
360
|
** --rel-paths command line options to determine whether the |
|
361
|
** status report should be shown relative to the current |
|
362
|
** working directory. |
|
363
|
*/ |
|
364
|
static int determine_cwd_relative_option() |
|
365
|
{ |
|
366
|
int relativePaths = db_get_boolean("relative-paths", 1); |
|
367
|
int absPathOption = find_option("abs-paths", 0, 0)!=0; |
|
368
|
int relPathOption = find_option("rel-paths", 0, 0)!=0; |
|
369
|
if( absPathOption ){ relativePaths = 0; } |
|
370
|
if( relPathOption ){ relativePaths = 1; } |
|
371
|
return relativePaths; |
|
372
|
} |
|
373
|
|
|
374
|
/* |
|
375
|
** COMMAND: changes |
|
376
|
** COMMAND: status |
|
377
|
** |
|
378
|
** Usage: %fossil changes|status ?OPTIONS? ?PATHS ...? |
|
379
|
** |
|
380
|
** Report the change status of files in the current check-out. If one or |
|
381
|
** more PATHS are specified, only changes among the named files and |
|
382
|
** directories are reported. Directories are searched recursively. |
|
383
|
** |
|
384
|
** The status command is similar to the changes command, except it lacks |
|
385
|
** several of the options supported by changes and it has its own header |
|
386
|
** and footer information. The header information is a subset of that |
|
387
|
** shown by the info command, and the footer shows if there are any forks. |
|
388
|
** Change type classification is always enabled for the status command. |
|
389
|
** |
|
390
|
** Each line of output is the name of a changed file, with paths shown |
|
391
|
** according to the "relative-paths" setting, unless overridden by the |
|
392
|
** --abs-paths or --rel-paths options. |
|
393
|
** |
|
394
|
** By default, all changed files are selected for display. This behavior |
|
395
|
** can be overridden by using one or more filter options (listed below), |
|
396
|
** in which case only files with the specified change type(s) are shown. |
|
397
|
** As a special case, the --no-merge option does not inhibit this default. |
|
398
|
** This default shows exactly the set of changes that would be checked- |
|
399
|
** in by the commit command. |
|
400
|
** |
|
401
|
** If no filter options are used, or if the --merge option is used, the |
|
402
|
** artifact hash of each merge contributor check-in version is displayed at |
|
403
|
** the end of the report. The --no-merge option is useful to display the |
|
404
|
** default set of changed files without the merge contributors. |
|
405
|
** |
|
406
|
** If change type classification is enabled, each output line starts with |
|
407
|
** a code describing the file's change type, e.g. EDITED or RENAMED. It |
|
408
|
** is enabled by default unless exactly one change type is selected. For |
|
409
|
** the purposes of determining the default, --changed counts as selecting |
|
410
|
** one change type. The default can be overridden by the --classify or |
|
411
|
** --no-classify options. |
|
412
|
** |
|
413
|
** --edited and --updated produce disjoint sets. --updated shows a file |
|
414
|
** only when it is identical to that of its merge contributor, and the |
|
415
|
** change type classification is UPDATED_BY_MERGE or UPDATED_BY_INTEGRATE. |
|
416
|
** If the file had to be merged with any other changes, it is considered |
|
417
|
** to be merged or conflicted and therefore will be shown by --edited, not |
|
418
|
** --updated, with types EDITED or CONFLICT. The --changed option can be |
|
419
|
** used to display the union of --edited and --updated. |
|
420
|
** |
|
421
|
** --differ is so named because it lists all the differences between the |
|
422
|
** checked-out version and the check-out directory. In addition to the |
|
423
|
** default changes (excluding --merge), it lists extra files which (if |
|
424
|
** ignore-glob is set correctly) may be worth adding. Prior to doing a |
|
425
|
** commit, it is good practice to check --differ to see not only which |
|
426
|
** changes would be committed but also if any files should be added. |
|
427
|
** |
|
428
|
** If both --merge and --no-merge are used, --no-merge has priority. The |
|
429
|
** same is true of --classify and --no-classify. |
|
430
|
** |
|
431
|
** The "fossil changes --extra" command is equivalent to "fossil extras". |
|
432
|
** |
|
433
|
** General options: |
|
434
|
** --abs-paths Display absolute pathnames |
|
435
|
** -b|--brief Show a single keyword for the status |
|
436
|
** --rel-paths Display pathnames relative to the current working |
|
437
|
** directory |
|
438
|
** --hash Verify file status using hashing rather than |
|
439
|
** relying on file mtimes |
|
440
|
** --case-sensitive BOOL Override case-sensitive setting |
|
441
|
** --dotfiles Include unmanaged files beginning with a dot |
|
442
|
** --ignore <CSG> Ignore unmanaged files matching CSG glob patterns |
|
443
|
** |
|
444
|
** Options specific to the changes command: |
|
445
|
** --header Identify the repository if report is non-empty |
|
446
|
** -v|--verbose Say "(none)" if the change report is empty |
|
447
|
** --classify Start each line with the file's change type |
|
448
|
** --no-classify Do not print file change types |
|
449
|
** |
|
450
|
** Filter options: |
|
451
|
** --edited Display edited, merged, and conflicted files |
|
452
|
** --updated Display files updated by merge/integrate |
|
453
|
** --changed Combination of the above two options |
|
454
|
** --missing Display missing files |
|
455
|
** --added Display added files |
|
456
|
** --deleted Display deleted files |
|
457
|
** --renamed Display renamed files |
|
458
|
** --conflict Display files having merge conflicts |
|
459
|
** --meta Display files with metadata changes |
|
460
|
** --unchanged Display unchanged files |
|
461
|
** --all Display all managed files, i.e. all of the above |
|
462
|
** --extra Display unmanaged files |
|
463
|
** --differ Display modified and extra files |
|
464
|
** --merge Display merge contributors |
|
465
|
** --no-merge Do not display merge contributors |
|
466
|
** |
|
467
|
** See also: [[extras]], [[ls]] |
|
468
|
*/ |
|
469
|
void status_cmd(void){ |
|
470
|
/* Affirmative and negative flag option tables. */ |
|
471
|
static const struct { |
|
472
|
const char *option; /* Flag name. */ |
|
473
|
unsigned mask; /* Flag bits. */ |
|
474
|
} flagDefs[] = { |
|
475
|
{"edited" , C_EDITED }, {"updated" , C_UPDATED }, |
|
476
|
{"changed" , C_CHANGED }, {"missing" , C_MISSING }, |
|
477
|
{"added" , C_ADDED }, {"deleted" , C_DELETED }, |
|
478
|
{"renamed" , C_RENAMED }, {"conflict" , C_CONFLICT }, |
|
479
|
{"meta" , C_META }, {"unchanged" , C_UNCHANGED}, |
|
480
|
{"all" , C_ALL }, {"extra" , C_EXTRA }, |
|
481
|
{"differ" , C_DIFFER }, {"merge" , C_MERGE }, |
|
482
|
{"classify", C_CLASSIFY}, |
|
483
|
}, noFlagDefs[] = { |
|
484
|
{"no-merge", C_MERGE }, {"no-classify", C_CLASSIFY }, |
|
485
|
}; |
|
486
|
|
|
487
|
Blob report = BLOB_INITIALIZER; |
|
488
|
enum {CHANGES, STATUS} command = *g.argv[1]=='s' ? STATUS : CHANGES; |
|
489
|
/* --sha1sum is an undocumented alias for --hash for backwards compatibility */ |
|
490
|
int useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
|
491
|
int showHdr = command==CHANGES && find_option("header", 0, 0); |
|
492
|
int verboseFlag = command==CHANGES && find_option("verbose", "v", 0); |
|
493
|
const char *zIgnoreFlag = find_option("ignore", 0, 1); |
|
494
|
unsigned scanFlags = 0; |
|
495
|
unsigned flags = 0; |
|
496
|
int vid, i; |
|
497
|
|
|
498
|
fossil_pledge("stdio rpath wpath cpath fattr id flock tty chown"); |
|
499
|
|
|
500
|
if( find_option("brief","b",0) ){ |
|
501
|
/* The --brief or -b option is special. It cannot be used with any |
|
502
|
** other options. It outputs a single keyword which indicates the |
|
503
|
** fossil status, for use by shell scripts. The output might be |
|
504
|
** one of: |
|
505
|
** |
|
506
|
** clean The current working directory is within an |
|
507
|
** unmodified fossil check-out. |
|
508
|
** |
|
509
|
** dirty The pwd is within a fossil check-out that has |
|
510
|
** uncommitted changes |
|
511
|
** |
|
512
|
** none The pwd is not within a fossil check-out. |
|
513
|
*/ |
|
514
|
int chnged; |
|
515
|
if( g.argc>2 ){ |
|
516
|
fossil_fatal("No other arguments or options may occur with --brief"); |
|
517
|
} |
|
518
|
if( db_open_local(0)==0 ){ |
|
519
|
fossil_print("none\n"); |
|
520
|
return; |
|
521
|
} |
|
522
|
vid = db_lget_int("checkout", 0); |
|
523
|
vfile_check_signature(vid, 0); |
|
524
|
chnged = db_int(0, |
|
525
|
"SELECT 1 FROM vfile" |
|
526
|
" WHERE vid=%d" |
|
527
|
" AND (chnged>0 OR deleted OR rid==0)", |
|
528
|
vid |
|
529
|
); |
|
530
|
if( chnged ){ |
|
531
|
fossil_print("dirty\n"); |
|
532
|
}else{ |
|
533
|
fossil_print("clean\n"); |
|
534
|
} |
|
535
|
return; |
|
536
|
} |
|
537
|
|
|
538
|
/* Load affirmative flag options. */ |
|
539
|
for( i=0; i<count(flagDefs); ++i ){ |
|
540
|
if( (command==CHANGES || !(flagDefs[i].mask & C_CLASSIFY)) |
|
541
|
&& find_option(flagDefs[i].option, 0, 0) ){ |
|
542
|
flags |= flagDefs[i].mask; |
|
543
|
} |
|
544
|
} |
|
545
|
|
|
546
|
/* If no filter options are specified, enable defaults. */ |
|
547
|
if( !(flags & C_FILTER) ){ |
|
548
|
flags |= C_DEFAULT; |
|
549
|
} |
|
550
|
|
|
551
|
/* If more than one filter is enabled, enable classification. This is tricky. |
|
552
|
* Having one filter means flags masked by C_FILTER is a power of two. If a |
|
553
|
* number masked by one less than itself is zero, it's either zero or a power |
|
554
|
* of two. It's already known to not be zero because of the above defaults. |
|
555
|
* Unlike --all, --changed is a single filter, i.e. it sets only one bit. |
|
556
|
* Also force classification for the status command. */ |
|
557
|
if( command==STATUS || (flags & (flags-1) & C_FILTER) ){ |
|
558
|
flags |= C_CLASSIFY; |
|
559
|
} |
|
560
|
|
|
561
|
/* Negative flag options override defaults applied above. */ |
|
562
|
for( i=0; i<count(noFlagDefs); ++i ){ |
|
563
|
if( (command==CHANGES || !(noFlagDefs[i].mask & C_CLASSIFY)) |
|
564
|
&& find_option(noFlagDefs[i].option, 0, 0) ){ |
|
565
|
flags &= ~noFlagDefs[i].mask; |
|
566
|
} |
|
567
|
} |
|
568
|
|
|
569
|
/* Confirm current working directory is within check-out. */ |
|
570
|
db_must_be_within_tree(); |
|
571
|
|
|
572
|
/* Get check-out version. */ |
|
573
|
vid = db_lget_int("checkout", 0); |
|
574
|
|
|
575
|
/* Relative path flag determination is done by a shared function. */ |
|
576
|
if( determine_cwd_relative_option() ){ |
|
577
|
flags |= C_RELPATH; |
|
578
|
} |
|
579
|
|
|
580
|
/* If --ignore is not specified, use the ignore-glob setting. */ |
|
581
|
if( !zIgnoreFlag ){ |
|
582
|
zIgnoreFlag = db_get("ignore-glob", 0); |
|
583
|
} |
|
584
|
|
|
585
|
/* Get the --dotfiles argument, or read it from the dotfiles setting. */ |
|
586
|
if( find_option("dotfiles", 0, 0) || db_get_boolean("dotfiles", 0) ){ |
|
587
|
scanFlags = SCAN_ALL; |
|
588
|
} |
|
589
|
|
|
590
|
/* We should be done with options. */ |
|
591
|
verify_all_options(); |
|
592
|
|
|
593
|
/* Check for changed files. */ |
|
594
|
vfile_check_signature(vid, useHash ? CKSIG_HASH : 0); |
|
595
|
|
|
596
|
/* Search for unmanaged files if requested. */ |
|
597
|
if( flags & C_EXTRA ){ |
|
598
|
Glob *pIgnore = glob_create(zIgnoreFlag); |
|
599
|
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); |
|
600
|
glob_free(pIgnore); |
|
601
|
} |
|
602
|
|
|
603
|
/* The status command prints general information before the change list. */ |
|
604
|
if( command==STATUS ){ |
|
605
|
fossil_print("repository: %s\n", db_repository_filename()); |
|
606
|
fossil_print("local-root: %s\n", g.zLocalRoot); |
|
607
|
if( g.zConfigDbName ){ |
|
608
|
fossil_print("config-db: %s\n", g.zConfigDbName); |
|
609
|
} |
|
610
|
if( vid ){ |
|
611
|
show_common_info(vid, "checkout:", 1, 1); |
|
612
|
} |
|
613
|
db_record_repository_filename(0); |
|
614
|
} |
|
615
|
|
|
616
|
/* Find and print all requested changes. */ |
|
617
|
blob_zero(&report); |
|
618
|
status_report(&report, flags); |
|
619
|
if( blob_size(&report) ){ |
|
620
|
if( showHdr ){ |
|
621
|
fossil_print( |
|
622
|
"Changes for %s at %s:\n", db_get("project-name", "<unnamed>"), |
|
623
|
g.zLocalRoot); |
|
624
|
} |
|
625
|
blob_write_to_file(&report, "-"); |
|
626
|
}else if( verboseFlag ){ |
|
627
|
fossil_print(" (none)\n"); |
|
628
|
} |
|
629
|
blob_reset(&report); |
|
630
|
|
|
631
|
/* The status command ends with warnings about ambiguous leaves (forks). */ |
|
632
|
if( command==STATUS ){ |
|
633
|
leaf_ambiguity_warning(vid, vid); |
|
634
|
} |
|
635
|
} |
|
636
|
|
|
637
|
/* zIn is a string that is guaranteed to be followed by \n. Return |
|
638
|
** a pointer to the next line after the \n. The returned value might |
|
639
|
** point to the \000 string terminator. |
|
640
|
*/ |
|
641
|
static const char *next_line(const char *zIn){ |
|
642
|
const char *z = strchr(zIn, '\n'); |
|
643
|
assert( z!=0 ); |
|
644
|
return z+1; |
|
645
|
} |
|
646
|
|
|
647
|
/* zIn is a non-empty list of filenames in sorted order and separated |
|
648
|
** by \n. There might be a cluster of lines that have the same n-character |
|
649
|
** prefix. Return a pointer to the start of the last line of that |
|
650
|
** cluster. The return value might be zIn if the first line of zIn is |
|
651
|
** unique in its first n character. |
|
652
|
*/ |
|
653
|
static const char *last_line(const char *zIn, int n){ |
|
654
|
const char *zLast = zIn; |
|
655
|
const char *z; |
|
656
|
while( 1 ){ |
|
657
|
z = next_line(zLast); |
|
658
|
if( z[0]==0 || (n>0 && strncmp(zIn, z, n)!=0) ) break; |
|
659
|
zLast = z; |
|
660
|
} |
|
661
|
return zLast; |
|
662
|
} |
|
663
|
|
|
664
|
/* |
|
665
|
** Print a section of a filelist hierarchy graph. This is a helper |
|
666
|
** routine for print_filelist_as_tree() below. |
|
667
|
*/ |
|
668
|
static const char *print_filelist_section( |
|
669
|
const char *zIn, /* List of filenames, separated by \n */ |
|
670
|
const char *zLast, /* Last filename in the list to print */ |
|
671
|
const char *zPrefix, /* Prefix so put before each output line */ |
|
672
|
int nDir /* Ignore this many characters of directory name */ |
|
673
|
){ |
|
674
|
/* Unicode box-drawing characters: U+251C, U+2514, U+2502 */ |
|
675
|
const char *zENTRY = "\342\224\234\342\224\200\342\224\200 "; |
|
676
|
const char *zLASTE = "\342\224\224\342\224\200\342\224\200 "; |
|
677
|
const char *zCONTU = "\342\224\202 "; |
|
678
|
const char *zBLANK = " "; |
|
679
|
|
|
680
|
while( zIn<=zLast ){ |
|
681
|
int i; |
|
682
|
for(i=nDir; zIn[i]!='\n' && zIn[i]!='/'; i++){} |
|
683
|
if( zIn[i]=='/' ){ |
|
684
|
char *zSubPrefix; |
|
685
|
const char *zSubLast = last_line(zIn, i+1); |
|
686
|
zSubPrefix = mprintf("%s%s", zPrefix, zSubLast==zLast ? zBLANK : zCONTU); |
|
687
|
fossil_print("%s%s%.*s\n", zPrefix, zSubLast==zLast ? zLASTE : zENTRY, |
|
688
|
i-nDir, &zIn[nDir]); |
|
689
|
zIn = print_filelist_section(zIn, zSubLast, zSubPrefix, i+1); |
|
690
|
fossil_free(zSubPrefix); |
|
691
|
}else{ |
|
692
|
fossil_print("%s%s%.*s\n", zPrefix, zIn==zLast ? zLASTE : zENTRY, |
|
693
|
i-nDir, &zIn[nDir]); |
|
694
|
zIn = next_line(zIn); |
|
695
|
} |
|
696
|
} |
|
697
|
return zIn; |
|
698
|
} |
|
699
|
|
|
700
|
/* |
|
701
|
** Input blob pList is a list of filenames, one filename per line, |
|
702
|
** in sorted order and with / directory separators. Output this list |
|
703
|
** as a tree in a manner similar to the "tree" command on Linux. |
|
704
|
*/ |
|
705
|
static void print_filelist_as_tree(Blob *pList){ |
|
706
|
char *zAll; |
|
707
|
const char *zLast; |
|
708
|
fossil_print("%s\n", g.zLocalRoot); |
|
709
|
zAll = blob_str(pList); |
|
710
|
if( zAll[0] ){ |
|
711
|
zLast = last_line(zAll, 0); |
|
712
|
print_filelist_section(zAll, zLast, "", 0); |
|
713
|
} |
|
714
|
} |
|
715
|
|
|
716
|
/* |
|
717
|
** Take care of -r version of ls command |
|
718
|
*/ |
|
719
|
void ls_cmd_rev( |
|
720
|
const char *zRev, /* Revision string given */ |
|
721
|
int verboseFlag, /* Verbose flag given */ |
|
722
|
int showAge, /* Age flag given */ |
|
723
|
int showFileHash, /* Show file hash flag given */ |
|
724
|
int showCkinHash, /* Show check-in hash flag given */ |
|
725
|
int showCkinInfo, /* Show check-in infos */ |
|
726
|
int timeOrder, /* Order by time flag given */ |
|
727
|
int treeFmt /* Show output in the tree format */ |
|
728
|
){ |
|
729
|
Stmt q; |
|
730
|
char *zOrderBy = "pathname COLLATE nocase"; |
|
731
|
char *zName; |
|
732
|
Blob where; |
|
733
|
int rid; |
|
734
|
int i; |
|
735
|
Blob out; |
|
736
|
|
|
737
|
/* Handle given file names */ |
|
738
|
blob_zero(&where); |
|
739
|
for(i=2; i<g.argc; i++){ |
|
740
|
Blob fname; |
|
741
|
file_tree_name(g.argv[i], &fname, 0, 1); |
|
742
|
zName = blob_str(&fname); |
|
743
|
if( fossil_strcmp(zName, ".")==0 ){ |
|
744
|
blob_reset(&where); |
|
745
|
break; |
|
746
|
} |
|
747
|
blob_append_sql(&where, |
|
748
|
" %s (pathname=%Q %s) " |
|
749
|
"OR (pathname>'%q/' %s AND pathname<'%q0' %s)", |
|
750
|
(blob_size(&where)>0) ? "OR" : "AND (", zName, |
|
751
|
filename_collation(), zName, filename_collation(), |
|
752
|
zName, filename_collation() |
|
753
|
); |
|
754
|
} |
|
755
|
if( blob_size(&where)>0 ){ |
|
756
|
blob_append_sql(&where, ")"); |
|
757
|
} |
|
758
|
|
|
759
|
rid = symbolic_name_to_rid(zRev, "ci"); |
|
760
|
if( rid==0 ){ |
|
761
|
fossil_fatal("not a valid check-in: %s", zRev); |
|
762
|
} |
|
763
|
|
|
764
|
if( timeOrder ){ |
|
765
|
zOrderBy = "mtime DESC"; |
|
766
|
} |
|
767
|
|
|
768
|
compute_fileage(rid,0); |
|
769
|
if( showCkinInfo ){ |
|
770
|
db_prepare(&q, |
|
771
|
"SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" |
|
772
|
" bfh.size, fileage.uuid, bch.uuid,\n" |
|
773
|
" coalesce(e.ecomment, e.comment), coalesce(e.euser, e.user)\n" |
|
774
|
" FROM fileage, blob bfh, blob bch, event e\n" |
|
775
|
" WHERE bfh.rid=fileage.fid AND bch.rid=fileage.mid\n" |
|
776
|
" AND e.objid = fileage.mid %s\n" |
|
777
|
" ORDER BY %s;", |
|
778
|
blob_sql_text(&where), |
|
779
|
zOrderBy /*safe-for-%s*/ |
|
780
|
); |
|
781
|
}else{ |
|
782
|
db_prepare(&q, |
|
783
|
"SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" |
|
784
|
" bfh.size, fileage.uuid %s\n" |
|
785
|
" FROM fileage, blob bfh %s\n" |
|
786
|
" WHERE bfh.rid=fileage.fid %s %s\n" |
|
787
|
" ORDER BY %s;", |
|
788
|
showCkinHash ? ", bch.uuid" : "", |
|
789
|
showCkinHash ? ", blob bch" : "", |
|
790
|
showCkinHash ? "\n AND bch.rid=fileage.mid" : "", |
|
791
|
blob_sql_text(&where), |
|
792
|
zOrderBy /*safe-for-%s*/ |
|
793
|
); |
|
794
|
} |
|
795
|
blob_reset(&where); |
|
796
|
if( treeFmt ) blob_init(&out, 0, 0); |
|
797
|
|
|
798
|
while( db_step(&q)==SQLITE_ROW ){ |
|
799
|
const char *zTime = db_column_text(&q,0); |
|
800
|
const char *zFile = db_column_text(&q,1); |
|
801
|
int size = db_column_int(&q,2); |
|
802
|
if( treeFmt ){ |
|
803
|
blob_appendf(&out, "%s\n", zFile); |
|
804
|
}else if( verboseFlag ){ |
|
805
|
if( showCkinInfo ){ |
|
806
|
const char *zUuidC = db_column_text(&q,4); |
|
807
|
const char *zComm = db_column_text(&q,5); |
|
808
|
const char *zUser = db_column_text(&q,6); |
|
809
|
fossil_print("%s [%S] %12s ", zTime, zUuidC, zUser); |
|
810
|
if( showCkinInfo==2 ) fossil_print("%-20.20s ", zComm); |
|
811
|
fossil_print("%s\n", zFile); |
|
812
|
}else if( showFileHash ){ |
|
813
|
const char *zUuidF = db_column_text(&q,3); |
|
814
|
fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidF, zFile); |
|
815
|
}else if( showCkinHash ){ |
|
816
|
const char *zUuidC = db_column_text(&q,4); |
|
817
|
fossil_print("%s %7d [%S] %s\n", zTime, size, zUuidC, zFile); |
|
818
|
}else{ |
|
819
|
fossil_print("%s %7d %s\n", zTime, size, zFile); |
|
820
|
} |
|
821
|
}else if( showAge ){ |
|
822
|
fossil_print("%s %s\n", zTime, zFile); |
|
823
|
}else{ |
|
824
|
fossil_print("%s\n", zFile); |
|
825
|
} |
|
826
|
} |
|
827
|
db_finalize(&q); |
|
828
|
if( treeFmt ){ |
|
829
|
print_filelist_as_tree(&out); |
|
830
|
blob_reset(&out); |
|
831
|
} |
|
832
|
} |
|
833
|
|
|
834
|
/* |
|
835
|
** COMMAND: ls |
|
836
|
** |
|
837
|
** Usage: %fossil ls ?OPTIONS? ?PATHS ...? |
|
838
|
** |
|
839
|
** List all files in the current check-out. If PATHS is included, only the |
|
840
|
** named files (or their children if directories) are shown. |
|
841
|
** |
|
842
|
** The ls command has grown by accretion, with multiple contributors, over |
|
843
|
** many years, and is hence a little confused. The ls command is essentially |
|
844
|
** two related commands in one, depending on whether or not the -r option |
|
845
|
** is given or implied. The -r option selects a specific check-in |
|
846
|
** version to list, in which case -R can be used to select the repository. |
|
847
|
** The fine behavior of the --age, -v, and -t options is altered by the -r |
|
848
|
** option as well, as explained below. The -h and -H options use an |
|
849
|
** implicit "-r current" option if no -r is specified. |
|
850
|
** |
|
851
|
** The --age option displays file commit times. Like -r, --age has the |
|
852
|
** side effect of making -t sort by commit time, not modification time. |
|
853
|
** |
|
854
|
** The -v option provides extra information about each file. Without -r, |
|
855
|
** -v displays the change status, in the manner of the [[changes]] command. |
|
856
|
** With -r, -v shows the commit time and size of the checked-in files. |
|
857
|
** The -h option also shows the file hash prefix. The -H option shows |
|
858
|
** the check-in hash prefix. The -v option added implicitly if either of the |
|
859
|
** -h or -H options is used. An implicit "-r current" is also added if |
|
860
|
** -h or -H are used and no -r is specified. |
|
861
|
** |
|
862
|
** The -t option changes the sort order. Without -t, files are sorted by |
|
863
|
** path and name (case insensitive sort if -r). If neither --age nor -r |
|
864
|
** are used, -t sorts by modification time, otherwise by commit time. |
|
865
|
** |
|
866
|
** Options: |
|
867
|
** --age Show when each file was committed |
|
868
|
** -h Show file hashes. Implies -v and -r |
|
869
|
** -H Show check-in hashes. Implies -v and -r |
|
870
|
** --hash Verify file status using hashing rather than |
|
871
|
** relying on file sizes and mtimes. Implies -v |
|
872
|
** -r VERSION The specific check-in to list |
|
873
|
** -R|--repository REPO Extract info from repository REPO |
|
874
|
** -t Sort output in time order |
|
875
|
** --tree Tree format |
|
876
|
** -v|--verbose Provide extra information about each file |
|
877
|
** |
|
878
|
** See also: [[changes]], [[extras]], [[status]], [[tree]] |
|
879
|
*/ |
|
880
|
void ls_cmd(void){ |
|
881
|
int vid; |
|
882
|
Stmt q; |
|
883
|
int verboseFlag; |
|
884
|
int showAge; |
|
885
|
int treeFmt; |
|
886
|
int timeOrder; |
|
887
|
char *zOrderBy = "pathname"; |
|
888
|
Blob where; |
|
889
|
int i; |
|
890
|
int useHash = 0; |
|
891
|
int showFHash = 0; /* Show file hash */ |
|
892
|
int showCHash = 0; /* Show check-in hash */ |
|
893
|
const char *zName; |
|
894
|
const char *zRev; |
|
895
|
|
|
896
|
verboseFlag = find_option("verbose","v", 0)!=0; |
|
897
|
if( !verboseFlag ){ |
|
898
|
verboseFlag = find_option("l","l", 0)!=0; /* deprecated */ |
|
899
|
} |
|
900
|
showAge = find_option("age",0,0)!=0; |
|
901
|
zRev = find_option("r","r",1); |
|
902
|
timeOrder = find_option("t","t",0)!=0; |
|
903
|
useHash = find_option("hash",0,0)!=0; |
|
904
|
if( useHash ) verboseFlag = 1; |
|
905
|
showFHash = find_option("h","h",0)!=0; |
|
906
|
showCHash = find_option("H","H",0)!=0; |
|
907
|
if( showFHash || showCHash ){ |
|
908
|
if( showFHash && showCHash ){ |
|
909
|
fossil_fatal("the \"ls\" command cannot use both -h and -H at once"); |
|
910
|
} |
|
911
|
verboseFlag = 1; |
|
912
|
if( zRev==0 ) zRev = "current"; |
|
913
|
} |
|
914
|
treeFmt = find_option("tree",0,0)!=0; |
|
915
|
if( treeFmt ){ |
|
916
|
if( zRev==0 ) zRev = "current"; |
|
917
|
} |
|
918
|
|
|
919
|
if( zRev!=0 ){ |
|
920
|
db_find_and_open_repository(0, 0); |
|
921
|
verify_all_options(); |
|
922
|
ls_cmd_rev(zRev, verboseFlag, showAge, showFHash, showCHash, 0, timeOrder, |
|
923
|
treeFmt); |
|
924
|
return; |
|
925
|
}else if( find_option("R",0,1)!=0 ){ |
|
926
|
fossil_fatal("the -r is required in addition to -R"); |
|
927
|
} |
|
928
|
|
|
929
|
db_must_be_within_tree(); |
|
930
|
vid = db_lget_int("checkout", 0); |
|
931
|
if( timeOrder ){ |
|
932
|
if( showAge ){ |
|
933
|
zOrderBy = mprintf("checkin_mtime(%d,rid) DESC", vid); |
|
934
|
}else{ |
|
935
|
zOrderBy = "mtime DESC"; |
|
936
|
} |
|
937
|
} |
|
938
|
verify_all_options(); |
|
939
|
blob_zero(&where); |
|
940
|
for(i=2; i<g.argc; i++){ |
|
941
|
Blob fname; |
|
942
|
file_tree_name(g.argv[i], &fname, 0, 1); |
|
943
|
zName = blob_str(&fname); |
|
944
|
if( fossil_strcmp(zName, ".")==0 ){ |
|
945
|
blob_reset(&where); |
|
946
|
break; |
|
947
|
} |
|
948
|
blob_append_sql(&where, |
|
949
|
" %s (pathname=%Q %s) " |
|
950
|
"OR (pathname>'%q/' %s AND pathname<'%q0' %s)", |
|
951
|
(blob_size(&where)>0) ? "OR" : "WHERE", zName, |
|
952
|
filename_collation(), zName, filename_collation(), |
|
953
|
zName, filename_collation() |
|
954
|
); |
|
955
|
} |
|
956
|
vfile_check_signature(vid, useHash ? CKSIG_HASH : 0); |
|
957
|
if( showAge ){ |
|
958
|
db_prepare(&q, |
|
959
|
"SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)," |
|
960
|
" datetime(checkin_mtime(%d,rid),'unixepoch',toLocal())" |
|
961
|
" FROM vfile %s" |
|
962
|
" ORDER BY %s", |
|
963
|
vid, blob_sql_text(&where), zOrderBy /*safe-for-%s*/ |
|
964
|
); |
|
965
|
}else{ |
|
966
|
db_prepare(&q, |
|
967
|
"SELECT pathname, deleted, rid, chnged," |
|
968
|
" coalesce(origname!=pathname,0), islink" |
|
969
|
" FROM vfile %s" |
|
970
|
" ORDER BY %s", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ |
|
971
|
); |
|
972
|
} |
|
973
|
blob_reset(&where); |
|
974
|
while( db_step(&q)==SQLITE_ROW ){ |
|
975
|
const char *zPathname = db_column_text(&q,0); |
|
976
|
int isDeleted = db_column_int(&q, 1); |
|
977
|
int isNew = db_column_int(&q,2)==0; |
|
978
|
int chnged = db_column_int(&q,3); |
|
979
|
int renamed = db_column_int(&q,4); |
|
980
|
int isLink = db_column_int(&q,5); |
|
981
|
char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); |
|
982
|
const char *type = ""; |
|
983
|
if( verboseFlag ){ |
|
984
|
if( isNew ){ |
|
985
|
type = "ADDED "; |
|
986
|
}else if( isDeleted ){ |
|
987
|
type = "DELETED "; |
|
988
|
}else if( !file_isfile_or_link(zFullName) ){ |
|
989
|
if( file_access(zFullName, F_OK)==0 ){ |
|
990
|
type = "NOT_A_FILE "; |
|
991
|
}else{ |
|
992
|
type = "MISSING "; |
|
993
|
} |
|
994
|
}else if( chnged ){ |
|
995
|
if( chnged==2 ){ |
|
996
|
type = "UPDATED_BY_MERGE "; |
|
997
|
}else if( chnged==3 ){ |
|
998
|
type = "ADDED_BY_MERGE "; |
|
999
|
}else if( chnged==4 ){ |
|
1000
|
type = "UPDATED_BY_INTEGRATE "; |
|
1001
|
}else if( chnged==5 ){ |
|
1002
|
type = "ADDED_BY_INTEGRATE "; |
|
1003
|
}else if( !isLink && file_contains_merge_marker(zFullName) ){ |
|
1004
|
type = "CONFLICT "; |
|
1005
|
}else{ |
|
1006
|
type = "EDITED "; |
|
1007
|
} |
|
1008
|
}else if( renamed ){ |
|
1009
|
type = "RENAMED "; |
|
1010
|
}else{ |
|
1011
|
type = "UNCHANGED "; |
|
1012
|
} |
|
1013
|
} |
|
1014
|
if( showAge ){ |
|
1015
|
fossil_print("%s%s %s\n", type, db_column_text(&q, 5), zPathname); |
|
1016
|
}else{ |
|
1017
|
fossil_print("%s%s\n", type, zPathname); |
|
1018
|
} |
|
1019
|
free(zFullName); |
|
1020
|
} |
|
1021
|
db_finalize(&q); |
|
1022
|
} |
|
1023
|
|
|
1024
|
/* |
|
1025
|
** COMMAND: tree |
|
1026
|
** |
|
1027
|
** Usage: %fossil tree ?OPTIONS? ?PATHS ...? |
|
1028
|
** |
|
1029
|
** List all files in the current check-out much like the "tree" |
|
1030
|
** command does. If PATHS is included, only the named files |
|
1031
|
** (or their children if directories) are shown. |
|
1032
|
** |
|
1033
|
** Options: |
|
1034
|
** -r VERSION The specific check-in to list |
|
1035
|
** -R|--repository REPO Extract info from repository REPO |
|
1036
|
** |
|
1037
|
** See also: [[ls]] |
|
1038
|
*/ |
|
1039
|
void tree_cmd(void){ |
|
1040
|
const char *zRev; |
|
1041
|
|
|
1042
|
zRev = find_option("r","r",1); |
|
1043
|
if( zRev==0 ) zRev = "current"; |
|
1044
|
db_find_and_open_repository(0, 0); |
|
1045
|
verify_all_options(); |
|
1046
|
ls_cmd_rev(zRev,0,0,0,0,0,0,1); |
|
1047
|
} |
|
1048
|
|
|
1049
|
/* |
|
1050
|
** COMMAND: extras |
|
1051
|
** |
|
1052
|
** Usage: %fossil extras ?OPTIONS? ?PATH1 ...? |
|
1053
|
** |
|
1054
|
** Print a list of all files in the source tree that are not part of the |
|
1055
|
** current check-out. See also the "clean" command. If paths are specified, |
|
1056
|
** only files in the given directories will be listed. |
|
1057
|
** |
|
1058
|
** Files and subdirectories whose names begin with "." are normally |
|
1059
|
** ignored but can be included by adding the --dotfiles option. |
|
1060
|
** |
|
1061
|
** Files whose names match any of the glob patterns in the "ignore-glob" |
|
1062
|
** setting are ignored. This setting can be overridden by the --ignore |
|
1063
|
** option, whose CSG argument is a comma-separated list of glob patterns. |
|
1064
|
** |
|
1065
|
** Pathnames are displayed according to the "relative-paths" setting, |
|
1066
|
** unless overridden by the --abs-paths or --rel-paths options. |
|
1067
|
** |
|
1068
|
** Options: |
|
1069
|
** --abs-paths Display absolute pathnames |
|
1070
|
** --case-sensitive BOOL Override case-sensitive setting |
|
1071
|
** --dotfiles Include files beginning with a dot (".") |
|
1072
|
** --header Identify the repository if there are extras |
|
1073
|
** --ignore CSG Ignore files matching patterns from the argument |
|
1074
|
** --rel-paths Display pathnames relative to the current working |
|
1075
|
** directory |
|
1076
|
** --tree Show output in the tree format |
|
1077
|
** |
|
1078
|
** See also: [[changes]], [[clean]], [[status]] |
|
1079
|
*/ |
|
1080
|
void extras_cmd(void){ |
|
1081
|
Blob report = BLOB_INITIALIZER; |
|
1082
|
const char *zIgnoreFlag = find_option("ignore",0,1); |
|
1083
|
unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0; |
|
1084
|
unsigned flags = C_EXTRA; |
|
1085
|
int showHdr = find_option("header",0,0)!=0; |
|
1086
|
int treeFmt = find_option("tree",0,0)!=0; |
|
1087
|
Glob *pIgnore; |
|
1088
|
|
|
1089
|
if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; |
|
1090
|
db_must_be_within_tree(); |
|
1091
|
|
|
1092
|
if( determine_cwd_relative_option() ){ |
|
1093
|
flags |= C_RELPATH; |
|
1094
|
} |
|
1095
|
|
|
1096
|
if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; |
|
1097
|
|
|
1098
|
if( treeFmt ){ |
|
1099
|
flags &= ~C_RELPATH; |
|
1100
|
} |
|
1101
|
|
|
1102
|
/* We should be done with options.. */ |
|
1103
|
verify_all_options(); |
|
1104
|
|
|
1105
|
if( zIgnoreFlag==0 ){ |
|
1106
|
zIgnoreFlag = db_get("ignore-glob", 0); |
|
1107
|
} |
|
1108
|
pIgnore = glob_create(zIgnoreFlag); |
|
1109
|
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); |
|
1110
|
glob_free(pIgnore); |
|
1111
|
|
|
1112
|
blob_zero(&report); |
|
1113
|
status_report(&report, flags); |
|
1114
|
if( blob_size(&report) ){ |
|
1115
|
if( showHdr ){ |
|
1116
|
fossil_print("Extras for %s at %s:\n", db_get("project-name","<unnamed>"), |
|
1117
|
g.zLocalRoot); |
|
1118
|
} |
|
1119
|
if( treeFmt ){ |
|
1120
|
print_filelist_as_tree(&report); |
|
1121
|
}else{ |
|
1122
|
blob_write_to_file(&report, "-"); |
|
1123
|
} |
|
1124
|
} |
|
1125
|
blob_reset(&report); |
|
1126
|
} |
|
1127
|
|
|
1128
|
/* |
|
1129
|
** COMMAND: clean |
|
1130
|
** |
|
1131
|
** Usage: %fossil clean ?OPTIONS? ?PATH ...? |
|
1132
|
** |
|
1133
|
** Delete all "extra" files in the source tree. "Extra" files are files |
|
1134
|
** that are not officially part of the check-out. If one or more PATH |
|
1135
|
** arguments appear, then only the files named, or files contained with |
|
1136
|
** directories named, will be removed. |
|
1137
|
** |
|
1138
|
** If the --prompt option is used, prompts are issued to confirm the |
|
1139
|
** permanent removal of each file. Otherwise, files are backed up to the |
|
1140
|
** undo buffer prior to removal, and prompts are issued only for files |
|
1141
|
** whose removal cannot be undone due to their large size or due to |
|
1142
|
** --disable-undo being used. |
|
1143
|
** |
|
1144
|
** The --force option treats all prompts as having been answered yes, |
|
1145
|
** whereas --no-prompt treats them as having been answered no. |
|
1146
|
** |
|
1147
|
** Files matching any glob pattern specified by the --clean option are |
|
1148
|
** deleted without prompting, and the removal cannot be undone. |
|
1149
|
** |
|
1150
|
** No file that matches glob patterns specified by --ignore or --keep will |
|
1151
|
** ever be deleted. Files and subdirectories whose names begin with "." |
|
1152
|
** are automatically ignored unless the --dotfiles option is used. |
|
1153
|
** |
|
1154
|
** The default values for --clean, --ignore, and --keep are determined by |
|
1155
|
** the (versionable) clean-glob, ignore-glob, and keep-glob settings. |
|
1156
|
** |
|
1157
|
** The --verily option ignores the keep-glob and ignore-glob settings and |
|
1158
|
** turns on --force, --emptydirs, --dotfiles, and --disable-undo. Use the |
|
1159
|
** --verily option when you really want to clean up everything. Extreme |
|
1160
|
** care should be exercised when using the --verily option. |
|
1161
|
** |
|
1162
|
** Options: |
|
1163
|
** --allckouts Check for empty directories within any check-outs |
|
1164
|
** that may be nested within the current one. This |
|
1165
|
** option should be used with great care because the |
|
1166
|
** empty-dirs setting (and other applicable settings) |
|
1167
|
** belonging to the other repositories, if any, will |
|
1168
|
** not be checked. |
|
1169
|
** --case-sensitive BOOL Override case-sensitive setting |
|
1170
|
** --dirsonly Only remove empty directories. No files will |
|
1171
|
** be removed. Using this option will automatically |
|
1172
|
** enable the --emptydirs option as well. |
|
1173
|
** --disable-undo WARNING: This option disables use of the undo |
|
1174
|
** mechanism for this clean operation and should be |
|
1175
|
** used with extreme caution. |
|
1176
|
** --dotfiles Include files beginning with a dot (".") |
|
1177
|
** --emptydirs Remove any empty directories that are not |
|
1178
|
** explicitly exempted via the empty-dirs setting |
|
1179
|
** or another applicable setting or command line |
|
1180
|
** argument. Matching files, if any, are removed |
|
1181
|
** prior to checking for any empty directories; |
|
1182
|
** therefore, directories that contain only files |
|
1183
|
** that were removed will be removed as well. |
|
1184
|
** -f|--force Remove files without prompting |
|
1185
|
** -i|--prompt Prompt before removing each file. This option |
|
1186
|
** implies the --disable-undo option. |
|
1187
|
** -x|--verily WARNING: Removes everything that is not a managed |
|
1188
|
** file or the repository itself. This option |
|
1189
|
** implies the --force, --emptydirs, --dotfiles, and |
|
1190
|
** --disable-undo options. Furthermore, it |
|
1191
|
** completely disregards the keep-glob |
|
1192
|
** and ignore-glob settings. However, it does honor |
|
1193
|
** the --ignore and --keep options. |
|
1194
|
** --clean CSG WARNING: Never prompt to delete any files matching |
|
1195
|
** this comma separated list of glob patterns. Also, |
|
1196
|
** deletions of any files matching this pattern list |
|
1197
|
** cannot be undone. |
|
1198
|
** --ignore CSG Ignore files matching patterns from the |
|
1199
|
** comma separated list of glob patterns |
|
1200
|
** --keep <CSG> Keep files matching this comma separated |
|
1201
|
** list of glob patterns |
|
1202
|
** -n|--dry-run Delete nothing, but display what would have been |
|
1203
|
** deleted |
|
1204
|
** --no-prompt Do not prompt the user for input and assume an |
|
1205
|
** answer of 'No' for every question |
|
1206
|
** --temp Remove only Fossil-generated temporary files |
|
1207
|
** -v|--verbose Show all files as they are removed |
|
1208
|
** |
|
1209
|
** See also: [[addremove]], [[extras]], [[status]] |
|
1210
|
*/ |
|
1211
|
void clean_cmd(void){ |
|
1212
|
int allFileFlag, allDirFlag, dryRunFlag, verboseFlag; |
|
1213
|
int emptyDirsFlag, dirsOnlyFlag; |
|
1214
|
int disableUndo, noPrompt; |
|
1215
|
int alwaysPrompt = 0; |
|
1216
|
unsigned scanFlags = 0; |
|
1217
|
int verilyFlag = 0; |
|
1218
|
const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag; |
|
1219
|
Glob *pIgnore, *pKeep, *pClean; |
|
1220
|
int nRoot; |
|
1221
|
|
|
1222
|
#ifndef UNDO_SIZE_LIMIT /* TODO: Setting? */ |
|
1223
|
#define UNDO_SIZE_LIMIT (10*1024*1024) /* 10MiB */ |
|
1224
|
#endif |
|
1225
|
|
|
1226
|
undo_capture_command_line(); |
|
1227
|
dryRunFlag = find_option("dry-run","n",0)!=0; |
|
1228
|
if( !dryRunFlag ){ |
|
1229
|
dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ |
|
1230
|
} |
|
1231
|
if( !dryRunFlag ){ |
|
1232
|
dryRunFlag = find_option("whatif",0,0)!=0; |
|
1233
|
} |
|
1234
|
disableUndo = find_option("disable-undo",0,0)!=0; |
|
1235
|
noPrompt = find_option("no-prompt",0,0)!=0; |
|
1236
|
alwaysPrompt = find_option("prompt","i",0)!=0; |
|
1237
|
allFileFlag = allDirFlag = find_option("force","f",0)!=0; |
|
1238
|
dirsOnlyFlag = find_option("dirsonly",0,0)!=0; |
|
1239
|
emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag; |
|
1240
|
if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; |
|
1241
|
if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; |
|
1242
|
if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED; |
|
1243
|
zIgnoreFlag = find_option("ignore",0,1); |
|
1244
|
verboseFlag = find_option("verbose","v",0)!=0; |
|
1245
|
zKeepFlag = find_option("keep",0,1); |
|
1246
|
zCleanFlag = find_option("clean",0,1); |
|
1247
|
db_must_be_within_tree(); |
|
1248
|
if( find_option("verily","x",0)!=0 ){ |
|
1249
|
verilyFlag = allFileFlag = allDirFlag = 1; |
|
1250
|
emptyDirsFlag = 1; |
|
1251
|
disableUndo = 1; |
|
1252
|
scanFlags |= SCAN_ALL; |
|
1253
|
zCleanFlag = 0; |
|
1254
|
} |
|
1255
|
if( zIgnoreFlag==0 && !verilyFlag ){ |
|
1256
|
zIgnoreFlag = db_get("ignore-glob", 0); |
|
1257
|
} |
|
1258
|
if( zKeepFlag==0 && !verilyFlag ){ |
|
1259
|
zKeepFlag = db_get("keep-glob", 0); |
|
1260
|
} |
|
1261
|
if( zCleanFlag==0 && !verilyFlag ){ |
|
1262
|
zCleanFlag = db_get("clean-glob", 0); |
|
1263
|
} |
|
1264
|
if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; |
|
1265
|
verify_all_options(); |
|
1266
|
pIgnore = glob_create(zIgnoreFlag); |
|
1267
|
pKeep = glob_create(zKeepFlag); |
|
1268
|
pClean = glob_create(zCleanFlag); |
|
1269
|
nRoot = (int)strlen(g.zLocalRoot); |
|
1270
|
if( !dirsOnlyFlag ){ |
|
1271
|
Stmt q; |
|
1272
|
Blob repo; |
|
1273
|
if( !dryRunFlag && !disableUndo ) undo_begin(); |
|
1274
|
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); |
|
1275
|
db_prepare(&q, |
|
1276
|
"SELECT %Q || pathname FROM sfile" |
|
1277
|
" WHERE pathname NOT IN (%s)" |
|
1278
|
" ORDER BY 1", |
|
1279
|
g.zLocalRoot, fossil_all_reserved_names(0) |
|
1280
|
); |
|
1281
|
if( file_tree_name(g.zRepositoryName, &repo, 0, 0) ){ |
|
1282
|
db_multi_exec("DELETE FROM sfile WHERE pathname=%B", &repo); |
|
1283
|
} |
|
1284
|
db_multi_exec("DELETE FROM sfile WHERE pathname IN" |
|
1285
|
" (SELECT pathname FROM vfile)"); |
|
1286
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1287
|
const char *zName = db_column_text(&q, 0); |
|
1288
|
if( glob_match(pKeep, zName+nRoot) ){ |
|
1289
|
if( verboseFlag ){ |
|
1290
|
fossil_print("KEPT file \"%s\" not removed (due to --keep" |
|
1291
|
" or \"keep-glob\")\n", zName+nRoot); |
|
1292
|
} |
|
1293
|
continue; |
|
1294
|
} |
|
1295
|
if( !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ |
|
1296
|
char *zPrompt = 0; |
|
1297
|
char cReply; |
|
1298
|
Blob ans = empty_blob; |
|
1299
|
int undoRc = UNDO_NONE; |
|
1300
|
if( alwaysPrompt ){ |
|
1301
|
zPrompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ", |
|
1302
|
zName+nRoot); |
|
1303
|
prompt_user(zPrompt, &ans); |
|
1304
|
fossil_free(zPrompt); |
|
1305
|
cReply = fossil_toupper(blob_str(&ans)[0]); |
|
1306
|
blob_reset(&ans); |
|
1307
|
if( cReply=='N' ) continue; |
|
1308
|
if( cReply=='A' ){ |
|
1309
|
allFileFlag = 1; |
|
1310
|
alwaysPrompt = 0; |
|
1311
|
}else{ |
|
1312
|
undoRc = UNDO_SAVED_OK; |
|
1313
|
} |
|
1314
|
}else if( !disableUndo ){ |
|
1315
|
undoRc = undo_maybe_save(zName+nRoot, UNDO_SIZE_LIMIT); |
|
1316
|
} |
|
1317
|
if( undoRc!=UNDO_SAVED_OK ){ |
|
1318
|
if( allFileFlag ){ |
|
1319
|
cReply = 'Y'; |
|
1320
|
}else if( !noPrompt ){ |
|
1321
|
Blob ans; |
|
1322
|
zPrompt = mprintf("\nWARNING: Deletion of this file will " |
|
1323
|
"not be undoable via the 'undo'\n" |
|
1324
|
" command because %s.\n\n" |
|
1325
|
"Remove unmanaged file \"%s\" (a=all/y/N)? ", |
|
1326
|
undo_save_message(undoRc), zName+nRoot); |
|
1327
|
prompt_user(zPrompt, &ans); |
|
1328
|
fossil_free(zPrompt); |
|
1329
|
cReply = blob_str(&ans)[0]; |
|
1330
|
blob_reset(&ans); |
|
1331
|
}else{ |
|
1332
|
cReply = 'N'; |
|
1333
|
} |
|
1334
|
if( cReply=='a' || cReply=='A' ){ |
|
1335
|
allFileFlag = 1; |
|
1336
|
}else if( cReply!='y' && cReply!='Y' ){ |
|
1337
|
continue; |
|
1338
|
} |
|
1339
|
} |
|
1340
|
} |
|
1341
|
if( dryRunFlag || file_delete(zName)==0 ){ |
|
1342
|
if( verboseFlag || dryRunFlag ){ |
|
1343
|
fossil_print("Removed unmanaged file: %s\n", zName+nRoot); |
|
1344
|
} |
|
1345
|
}else{ |
|
1346
|
fossil_print("Could not remove file: %s\n", zName+nRoot); |
|
1347
|
} |
|
1348
|
} |
|
1349
|
db_finalize(&q); |
|
1350
|
if( !dryRunFlag && !disableUndo ) undo_finish(); |
|
1351
|
} |
|
1352
|
if( emptyDirsFlag ){ |
|
1353
|
Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0)); |
|
1354
|
Stmt q; |
|
1355
|
Blob root; |
|
1356
|
blob_init(&root, g.zLocalRoot, nRoot - 1); |
|
1357
|
vfile_dir_scan(&root, blob_size(&root), scanFlags, pIgnore, |
|
1358
|
pEmptyDirs, RepoFILE); |
|
1359
|
blob_reset(&root); |
|
1360
|
db_prepare(&q, |
|
1361
|
"SELECT %Q || x FROM dscan_temp" |
|
1362
|
" WHERE x NOT IN (%s) AND y = 0" |
|
1363
|
" ORDER BY 1 DESC", |
|
1364
|
g.zLocalRoot, fossil_all_reserved_names(0) |
|
1365
|
); |
|
1366
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1367
|
const char *zName = db_column_text(&q, 0); |
|
1368
|
if( glob_match(pKeep, zName+nRoot) ){ |
|
1369
|
if( verboseFlag ){ |
|
1370
|
fossil_print("KEPT directory \"%s\" not removed (due to --keep" |
|
1371
|
" or \"keep-glob\")\n", zName+nRoot); |
|
1372
|
} |
|
1373
|
continue; |
|
1374
|
} |
|
1375
|
if( !allDirFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ |
|
1376
|
char cReply; |
|
1377
|
if( !noPrompt ){ |
|
1378
|
Blob ans; |
|
1379
|
char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ", |
|
1380
|
zName+nRoot); |
|
1381
|
prompt_user(prompt, &ans); |
|
1382
|
cReply = blob_str(&ans)[0]; |
|
1383
|
fossil_free(prompt); |
|
1384
|
blob_reset(&ans); |
|
1385
|
}else{ |
|
1386
|
cReply = 'N'; |
|
1387
|
} |
|
1388
|
if( cReply=='a' || cReply=='A' ){ |
|
1389
|
allDirFlag = 1; |
|
1390
|
}else if( cReply!='y' && cReply!='Y' ){ |
|
1391
|
continue; |
|
1392
|
} |
|
1393
|
} |
|
1394
|
if( dryRunFlag || file_rmdir(zName)==0 ){ |
|
1395
|
if( verboseFlag || dryRunFlag ){ |
|
1396
|
fossil_print("Removed unmanaged directory: %s\n", zName+nRoot); |
|
1397
|
} |
|
1398
|
}else if( verboseFlag ){ |
|
1399
|
fossil_print("Could not remove directory: %s\n", zName+nRoot); |
|
1400
|
} |
|
1401
|
} |
|
1402
|
db_finalize(&q); |
|
1403
|
glob_free(pEmptyDirs); |
|
1404
|
} |
|
1405
|
glob_free(pClean); |
|
1406
|
glob_free(pKeep); |
|
1407
|
glob_free(pIgnore); |
|
1408
|
} |
|
1409
|
|
|
1410
|
/* |
|
1411
|
** Prompt the user for a check-in or stash comment (given in pPrompt), |
|
1412
|
** gather the response, then return the response in pComment. |
|
1413
|
** |
|
1414
|
** Lines of the prompt that begin with # are discarded. Excess whitespace |
|
1415
|
** is removed from the reply. |
|
1416
|
** |
|
1417
|
** Appropriate encoding translations are made on windows. |
|
1418
|
*/ |
|
1419
|
void prompt_for_user_comment(Blob *pComment, Blob *pPrompt){ |
|
1420
|
const char *zEditor; |
|
1421
|
char *zCmd; |
|
1422
|
char *zFile; |
|
1423
|
Blob reply, line; |
|
1424
|
char *zComment; |
|
1425
|
int i; |
|
1426
|
|
|
1427
|
zEditor = fossil_text_editor(); |
|
1428
|
if( zEditor==0 ){ |
|
1429
|
if( blob_size(pPrompt)>0 ){ |
|
1430
|
blob_append(pPrompt, |
|
1431
|
"#\n" |
|
1432
|
"# Since no default text editor is set using EDITOR or VISUAL\n" |
|
1433
|
"# environment variables or the \"fossil set editor\" command,\n" |
|
1434
|
"# and because no comment was specified using the \"-m\" or \"-M\"\n" |
|
1435
|
"# command-line options, you will need to enter the comment below.\n" |
|
1436
|
"# Type \".\" on a line by itself when you are done:\n", -1); |
|
1437
|
} |
|
1438
|
zFile = mprintf("-"); |
|
1439
|
}else{ |
|
1440
|
Blob fname; |
|
1441
|
blob_zero(&fname); |
|
1442
|
if( g.zLocalRoot!=0 ){ |
|
1443
|
file_relative_name(g.zLocalRoot, &fname, 1); |
|
1444
|
zFile = db_text(0, "SELECT '%qci-comment-'||hex(randomblob(6))||'.txt'", |
|
1445
|
blob_str(&fname)); |
|
1446
|
}else{ |
|
1447
|
file_tempname(&fname, "ci-comment",0); |
|
1448
|
zFile = fossil_strdup(blob_str(&fname)); |
|
1449
|
} |
|
1450
|
blob_reset(&fname); |
|
1451
|
} |
|
1452
|
#if defined(_WIN32) |
|
1453
|
blob_add_cr(pPrompt); |
|
1454
|
#endif |
|
1455
|
if( blob_size(pPrompt)>0 ) blob_write_to_file(pPrompt, zFile); |
|
1456
|
if( zEditor ){ |
|
1457
|
char *z, *zEnd; |
|
1458
|
zCmd = mprintf("%s %$", zEditor, zFile); |
|
1459
|
fossil_print("%s\n", zCmd); |
|
1460
|
if( fossil_system(zCmd) ){ |
|
1461
|
fossil_fatal("editor aborted: \"%s\"", zCmd); |
|
1462
|
} |
|
1463
|
blob_read_from_file(&reply, zFile, ExtFILE); |
|
1464
|
z = blob_str(&reply); |
|
1465
|
zEnd = strstr(z, "##########"); |
|
1466
|
if( zEnd ){ |
|
1467
|
/* Truncate the reply at any sequence of 10 or more # characters. |
|
1468
|
** The diff for the -v option occurs after such a sequence. */ |
|
1469
|
blob_resize(&reply, (int)(zEnd - z)); |
|
1470
|
} |
|
1471
|
}else{ |
|
1472
|
char zIn[300]; |
|
1473
|
blob_zero(&reply); |
|
1474
|
while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ |
|
1475
|
if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ |
|
1476
|
break; |
|
1477
|
} |
|
1478
|
blob_append(&reply, zIn, -1); |
|
1479
|
} |
|
1480
|
} |
|
1481
|
blob_to_utf8_no_bom(&reply, 1); |
|
1482
|
blob_to_lf_only(&reply); |
|
1483
|
file_delete(zFile); |
|
1484
|
free(zFile); |
|
1485
|
blob_zero(pComment); |
|
1486
|
while( blob_line(&reply, &line) ){ |
|
1487
|
int i, n; |
|
1488
|
char *z; |
|
1489
|
n = blob_size(&line); |
|
1490
|
z = blob_buffer(&line); |
|
1491
|
for(i=0; i<n && fossil_isspace(z[i]); i++){} |
|
1492
|
if( i<n && z[i]=='#' ) continue; |
|
1493
|
if( i<n || blob_size(pComment)>0 ){ |
|
1494
|
blob_appendf(pComment, "%b", &line); |
|
1495
|
} |
|
1496
|
} |
|
1497
|
blob_reset(&reply); |
|
1498
|
zComment = blob_str(pComment); |
|
1499
|
i = strlen(zComment); |
|
1500
|
while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } |
|
1501
|
blob_resize(pComment, i); |
|
1502
|
} |
|
1503
|
|
|
1504
|
/* |
|
1505
|
** Prepare a commit comment. Let the user modify it using the |
|
1506
|
** editor specified in the global_config table or either |
|
1507
|
** the VISUAL or EDITOR environment variable. |
|
1508
|
** |
|
1509
|
** Store the final commit comment in pComment. pComment is assumed |
|
1510
|
** to be uninitialized - any prior content is overwritten. |
|
1511
|
** |
|
1512
|
** zInit is the text of the most recent failed attempt to check in |
|
1513
|
** this same change. Use zInit to reinitialize the check-in comment |
|
1514
|
** so that the user does not have to retype. |
|
1515
|
** |
|
1516
|
** zBranch is the name of a new branch that this check-in is forced into. |
|
1517
|
** zBranch might be NULL or an empty string if no forcing occurs. |
|
1518
|
** |
|
1519
|
** parent_rid is the recordid of the parent check-in. |
|
1520
|
*/ |
|
1521
|
static void prepare_commit_comment( |
|
1522
|
Blob *pComment, |
|
1523
|
char *zInit, |
|
1524
|
CheckinInfo *p, |
|
1525
|
int parent_rid, |
|
1526
|
int dryRunFlag |
|
1527
|
){ |
|
1528
|
Blob prompt; |
|
1529
|
int wikiFlags; |
|
1530
|
#if defined(_WIN32) || defined(__CYGWIN__) |
|
1531
|
int bomSize; |
|
1532
|
const unsigned char *bom = get_utf8_bom(&bomSize); |
|
1533
|
blob_init(&prompt, (const char *) bom, bomSize); |
|
1534
|
if( zInit && zInit[0]){ |
|
1535
|
blob_append(&prompt, zInit, -1); |
|
1536
|
} |
|
1537
|
#else |
|
1538
|
blob_init(&prompt, zInit, -1); |
|
1539
|
#endif |
|
1540
|
blob_append(&prompt, |
|
1541
|
"\n" |
|
1542
|
"# Enter the commit message. Formatting rules:\n" |
|
1543
|
"# * Lines beginning with # are ignored.\n", |
|
1544
|
-1 |
|
1545
|
); |
|
1546
|
wikiFlags = wiki_convert_flags(1); |
|
1547
|
if( wikiFlags & WIKI_LINKSONLY ){ |
|
1548
|
blob_append(&prompt,"# * Hyperlinks inside of [...]\n", -1); |
|
1549
|
if( wikiFlags & WIKI_NEWLINE ){ |
|
1550
|
blob_append(&prompt, |
|
1551
|
"# * Newlines are significant and are displayed as written\n", -1); |
|
1552
|
}else{ |
|
1553
|
blob_append(&prompt, |
|
1554
|
"# * Newlines are interpreted as ordinary spaces\n", |
|
1555
|
-1 |
|
1556
|
); |
|
1557
|
} |
|
1558
|
blob_append(&prompt, |
|
1559
|
"# * All other text will be displayed as written\n", -1); |
|
1560
|
}else{ |
|
1561
|
blob_append(&prompt, |
|
1562
|
"# * Hyperlinks: [target] or [target|display-text]\n" |
|
1563
|
"# * Blank lines cause a paragraph break\n" |
|
1564
|
"# * Other text rendered as if it were HTML\n", -1 |
|
1565
|
); |
|
1566
|
} |
|
1567
|
blob_append(&prompt, "#\n", 2); |
|
1568
|
|
|
1569
|
if( dryRunFlag ){ |
|
1570
|
blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes " |
|
1571
|
"will be made to the repository\n#\n"); |
|
1572
|
} |
|
1573
|
blob_appendf(&prompt, "# user: %s\n", |
|
1574
|
p->zUserOvrd ? p->zUserOvrd : login_name()); |
|
1575
|
if( p->zBranch && p->zBranch[0] ){ |
|
1576
|
blob_appendf(&prompt, "# tags: %s\n#\n", p->zBranch); |
|
1577
|
}else{ |
|
1578
|
char *zTags = info_tags_of_checkin(parent_rid, 1); |
|
1579
|
if( zTags || p->azTag ){ |
|
1580
|
blob_append(&prompt, "# tags: ", 8); |
|
1581
|
if(zTags){ |
|
1582
|
blob_appendf(&prompt, "%z%s", zTags, p->azTag ? ", " : ""); |
|
1583
|
} |
|
1584
|
if(p->azTag){ |
|
1585
|
int i = 0; |
|
1586
|
for( ; p->azTag[i]; ++i ){ |
|
1587
|
blob_appendf(&prompt, "%s%s", p->azTag[i], |
|
1588
|
p->azTag[i+1] ? ", " : ""); |
|
1589
|
} |
|
1590
|
} |
|
1591
|
blob_appendf(&prompt, "\n#\n"); |
|
1592
|
} |
|
1593
|
} |
|
1594
|
status_report(&prompt, C_DEFAULT | C_FATAL | C_COMMENT); |
|
1595
|
if( g.markPrivate ){ |
|
1596
|
blob_append(&prompt, |
|
1597
|
"# PRIVATE BRANCH: This check-in will be private and will not sync to\n" |
|
1598
|
"# repositories.\n" |
|
1599
|
"#\n", -1 |
|
1600
|
); |
|
1601
|
} |
|
1602
|
if( p->integrateFlag ){ |
|
1603
|
blob_append(&prompt, |
|
1604
|
"#\n" |
|
1605
|
"# All merged-in branches will be closed due to the --integrate flag\n" |
|
1606
|
"#\n", -1 |
|
1607
|
); |
|
1608
|
} |
|
1609
|
if( p->verboseFlag ){ |
|
1610
|
DiffConfig DCfg; |
|
1611
|
blob_appendf(&prompt, |
|
1612
|
"#\n%.78c\n" |
|
1613
|
"# The following diff is excluded from the commit message:\n#\n", |
|
1614
|
'#' |
|
1615
|
); |
|
1616
|
diff_options(&DCfg, 0, 1); |
|
1617
|
DCfg.diffFlags |= DIFF_VERBOSE; |
|
1618
|
if( g.aCommitFile ){ |
|
1619
|
Stmt q; |
|
1620
|
Blob sql = BLOB_INITIALIZER; |
|
1621
|
FileDirList *diffFiles; |
|
1622
|
int i; |
|
1623
|
for(i=0; g.aCommitFile[i]!=0; ++i){} |
|
1624
|
diffFiles = fossil_malloc_zero((i+1) * sizeof(*diffFiles)); |
|
1625
|
for(i=0; g.aCommitFile[i]!=0; ++i){ |
|
1626
|
blob_append_sql(&sql, |
|
1627
|
"SELECT pathname, deleted, rid " |
|
1628
|
"FROM vfile WHERE id=%d", |
|
1629
|
g.aCommitFile[i]); |
|
1630
|
db_prepare(&q, "%s", blob_sql_text(&sql)); |
|
1631
|
blob_reset(&sql); |
|
1632
|
assert( db_step(&q)==SQLITE_ROW ); |
|
1633
|
diffFiles[i].zName = fossil_strdup(db_column_text(&q, 0)); |
|
1634
|
DCfg.diffFlags &= (~DIFF_FILE_MASK); |
|
1635
|
if( db_column_int(&q, 1) ){ |
|
1636
|
DCfg.diffFlags |= DIFF_FILE_DELETED; |
|
1637
|
}else if( db_column_int(&q, 2)==0 ){ |
|
1638
|
DCfg.diffFlags |= DIFF_FILE_ADDED; |
|
1639
|
} |
|
1640
|
db_finalize(&q); |
|
1641
|
if( fossil_strcmp(diffFiles[i].zName, "." )==0 ){ |
|
1642
|
diffFiles[0].zName[0] = '.'; |
|
1643
|
diffFiles[0].zName[1] = 0; |
|
1644
|
break; |
|
1645
|
} |
|
1646
|
diffFiles[i].nName = strlen(diffFiles[i].zName); |
|
1647
|
diffFiles[i].nUsed = 0; |
|
1648
|
} |
|
1649
|
diff_version_to_checkout(0, &DCfg, diffFiles, &prompt); |
|
1650
|
for( i=0; diffFiles[i].zName; ++i ){ |
|
1651
|
fossil_free(diffFiles[i].zName); |
|
1652
|
} |
|
1653
|
fossil_free(diffFiles); |
|
1654
|
}else{ |
|
1655
|
diff_version_to_checkout(0, &DCfg, 0, &prompt); |
|
1656
|
} |
|
1657
|
} |
|
1658
|
prompt_for_user_comment(pComment, &prompt); |
|
1659
|
blob_reset(&prompt); |
|
1660
|
} |
|
1661
|
|
|
1662
|
/* |
|
1663
|
** Prepare text that describes a pending commit and write it into |
|
1664
|
** a file at the root of the check-in. Return the name of that file. |
|
1665
|
** |
|
1666
|
** Space to hold the returned filename is obtained from fossil_malloc() |
|
1667
|
** and should be freed by the caller. The caller should also unlink |
|
1668
|
** the file when it is done. |
|
1669
|
*/ |
|
1670
|
static char *prepare_commit_description_file( |
|
1671
|
CheckinInfo *p, /* Information about this commit */ |
|
1672
|
int parent_rid, /* parent check-in */ |
|
1673
|
Blob *pComment, /* Check-in comment */ |
|
1674
|
int dryRunFlag /* True for a dry-run only */ |
|
1675
|
){ |
|
1676
|
Blob *pDesc; |
|
1677
|
char *zTags; |
|
1678
|
char *zFilename; |
|
1679
|
const char *zMainBranch = db_main_branch(); |
|
1680
|
Blob desc; |
|
1681
|
blob_init(&desc, 0, 0); |
|
1682
|
pDesc = &desc; |
|
1683
|
blob_appendf(pDesc, "checkout %s\n", g.zLocalRoot); |
|
1684
|
blob_appendf(pDesc, "repository %s\n", g.zRepositoryName); |
|
1685
|
blob_appendf(pDesc, "user %s\n", |
|
1686
|
p->zUserOvrd ? p->zUserOvrd : login_name()); |
|
1687
|
blob_appendf(pDesc, "branch %s\n", |
|
1688
|
(p->zBranch && p->zBranch[0]) ? p->zBranch : zMainBranch); |
|
1689
|
zTags = info_tags_of_checkin(parent_rid, 1); |
|
1690
|
if( zTags || p->azTag ){ |
|
1691
|
blob_append(pDesc, "tags ", -1); |
|
1692
|
if(zTags){ |
|
1693
|
blob_appendf(pDesc, "%z%s", zTags, p->azTag ? ", " : ""); |
|
1694
|
} |
|
1695
|
if(p->azTag){ |
|
1696
|
int i = 0; |
|
1697
|
for( ; p->azTag[i]; ++i ){ |
|
1698
|
blob_appendf(pDesc, "%s%s", p->azTag[i], |
|
1699
|
p->azTag[i+1] ? ", " : ""); |
|
1700
|
} |
|
1701
|
} |
|
1702
|
blob_appendf(pDesc, "\n"); |
|
1703
|
} |
|
1704
|
status_report(pDesc, C_DEFAULT | C_FATAL); |
|
1705
|
if( g.markPrivate ){ |
|
1706
|
blob_append(pDesc, "private-branch\n", -1); |
|
1707
|
} |
|
1708
|
if( p->integrateFlag ){ |
|
1709
|
blob_append(pDesc, "integrate\n", -1); |
|
1710
|
} |
|
1711
|
if( pComment && blob_size(pComment)>0 ){ |
|
1712
|
blob_appendf(pDesc, "checkin-comment\n%s\n", blob_str(pComment)); |
|
1713
|
} |
|
1714
|
if( dryRunFlag ){ |
|
1715
|
zFilename = 0; |
|
1716
|
fossil_print("******* Commit Description *******\n%s" |
|
1717
|
"***** End Commit Description *****\n", |
|
1718
|
blob_str(pDesc)); |
|
1719
|
}else{ |
|
1720
|
unsigned int r[2]; |
|
1721
|
sqlite3_randomness(sizeof(r), r); |
|
1722
|
zFilename = mprintf("%scommit-description-%08x%08x.txt", |
|
1723
|
g.zLocalRoot, r[0], r[1]); |
|
1724
|
blob_write_to_file(pDesc, zFilename); |
|
1725
|
} |
|
1726
|
blob_reset(pDesc); |
|
1727
|
return zFilename; |
|
1728
|
} |
|
1729
|
|
|
1730
|
|
|
1731
|
/* |
|
1732
|
** Populate the Global.aCommitFile[] based on the command line arguments |
|
1733
|
** to a [commit] command. Global.aCommitFile is an array of integers |
|
1734
|
** sized at (N+1), where N is the number of arguments passed to [commit]. |
|
1735
|
** The contents are the [id] values from the vfile table corresponding |
|
1736
|
** to the filenames passed as arguments. |
|
1737
|
** |
|
1738
|
** The last element of aCommitFile[] is always 0 - indicating the end |
|
1739
|
** of the array. |
|
1740
|
** |
|
1741
|
** If there were no arguments passed to [commit], aCommitFile is not |
|
1742
|
** allocated and remains NULL. Other parts of the code interpret this |
|
1743
|
** to mean "all files". |
|
1744
|
** |
|
1745
|
** Returns 1 if there was a warning, 0 otherwise. |
|
1746
|
*/ |
|
1747
|
int select_commit_files(void){ |
|
1748
|
int result = 0; |
|
1749
|
assert( g.aCommitFile==0 ); |
|
1750
|
if( g.argc>2 ){ |
|
1751
|
int ii, jj=0; |
|
1752
|
Blob fname; |
|
1753
|
Stmt q; |
|
1754
|
Bag toCommit; |
|
1755
|
|
|
1756
|
blob_zero(&fname); |
|
1757
|
bag_init(&toCommit); |
|
1758
|
for(ii=2; ii<g.argc; ii++){ |
|
1759
|
int cnt = 0; |
|
1760
|
file_tree_name(g.argv[ii], &fname, 0, 1); |
|
1761
|
if( fossil_strcmp(blob_str(&fname),".")==0 ){ |
|
1762
|
bag_clear(&toCommit); |
|
1763
|
return result; |
|
1764
|
} |
|
1765
|
db_prepare(&q, |
|
1766
|
"SELECT id FROM vfile WHERE pathname=%Q %s" |
|
1767
|
" OR (pathname>'%q/' %s AND pathname<'%q0' %s)", |
|
1768
|
blob_str(&fname), filename_collation(), blob_str(&fname), |
|
1769
|
filename_collation(), blob_str(&fname), filename_collation()); |
|
1770
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1771
|
cnt++; |
|
1772
|
bag_insert(&toCommit, db_column_int(&q, 0)); |
|
1773
|
} |
|
1774
|
db_finalize(&q); |
|
1775
|
if( cnt==0 ){ |
|
1776
|
fossil_warning("fossil knows nothing about: %s", g.argv[ii]); |
|
1777
|
result = 1; |
|
1778
|
} |
|
1779
|
blob_reset(&fname); |
|
1780
|
} |
|
1781
|
g.aCommitFile = fossil_malloc( (bag_count(&toCommit)+1) * |
|
1782
|
sizeof(g.aCommitFile[0]) ); |
|
1783
|
for(ii=bag_first(&toCommit); ii>0; ii=bag_next(&toCommit, ii)){ |
|
1784
|
g.aCommitFile[jj++] = ii; |
|
1785
|
} |
|
1786
|
g.aCommitFile[jj] = 0; |
|
1787
|
bag_clear(&toCommit); |
|
1788
|
} |
|
1789
|
return result; |
|
1790
|
} |
|
1791
|
|
|
1792
|
/* |
|
1793
|
** Returns true if the check-in identified by the first parameter is |
|
1794
|
** older than the given (valid) date/time string, else returns false. |
|
1795
|
** Also returns true if rid does not refer to a check-in, but it is not |
|
1796
|
** intended to be used for that case. |
|
1797
|
*/ |
|
1798
|
int checkin_is_younger( |
|
1799
|
int rid, /* The record ID of the ancestor */ |
|
1800
|
const char *zDate /* Date & time of the current check-in */ |
|
1801
|
){ |
|
1802
|
return db_exists( |
|
1803
|
"SELECT 1 FROM event" |
|
1804
|
" WHERE datetime(mtime)>=%Q" |
|
1805
|
" AND type='ci' AND objid=%d", |
|
1806
|
zDate, rid |
|
1807
|
) ? 0 : 1; |
|
1808
|
} |
|
1809
|
|
|
1810
|
/* |
|
1811
|
** Make sure the current check-in with timestamp zDate is younger than its |
|
1812
|
** ancestor identified rid and zUuid. Throw a fatal error if not. |
|
1813
|
*/ |
|
1814
|
static void checkin_verify_younger( |
|
1815
|
int rid, /* The record ID of the ancestor */ |
|
1816
|
const char *zUuid, /* The artifact hash of the ancestor */ |
|
1817
|
const char *zDate /* Date & time of the current check-in */ |
|
1818
|
){ |
|
1819
|
#ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES |
|
1820
|
if(checkin_is_younger(rid,zDate)==0){ |
|
1821
|
fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" |
|
1822
|
" Use --allow-older to override.", zUuid, zDate); |
|
1823
|
} |
|
1824
|
#endif |
|
1825
|
} |
|
1826
|
|
|
1827
|
|
|
1828
|
|
|
1829
|
/* |
|
1830
|
** zDate should be a valid date string. Convert this string into the |
|
1831
|
** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date, |
|
1832
|
** print a fatal error and quit. |
|
1833
|
*/ |
|
1834
|
char *date_in_standard_format(const char *zInputDate){ |
|
1835
|
char *zDate; |
|
1836
|
if( g.perm.Setup && fossil_strcmp(zInputDate,"now")==0 ){ |
|
1837
|
zInputDate = PD("date_override","now"); |
|
1838
|
} |
|
1839
|
zDate = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", |
|
1840
|
zInputDate); |
|
1841
|
if( zDate[0]==0 ){ |
|
1842
|
fossil_fatal( |
|
1843
|
"unrecognized date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"", |
|
1844
|
zInputDate |
|
1845
|
); |
|
1846
|
} |
|
1847
|
return zDate; |
|
1848
|
} |
|
1849
|
|
|
1850
|
/* |
|
1851
|
** COMMAND: test-date-format |
|
1852
|
** |
|
1853
|
** Usage: %fossil test-date-format DATE-STRING... |
|
1854
|
** |
|
1855
|
** Convert the DATE-STRING into the standard format used in artifacts |
|
1856
|
** and display the result. |
|
1857
|
*/ |
|
1858
|
void test_date_format(void){ |
|
1859
|
int i; |
|
1860
|
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); |
|
1861
|
for(i=2; i<g.argc; i++){ |
|
1862
|
fossil_print("%s -> %s\n", g.argv[i], date_in_standard_format(g.argv[i])); |
|
1863
|
} |
|
1864
|
} |
|
1865
|
|
|
1866
|
#if INTERFACE |
|
1867
|
/* |
|
1868
|
** The following structure holds some of the information needed to construct a |
|
1869
|
** check-in manifest. |
|
1870
|
*/ |
|
1871
|
struct CheckinInfo { |
|
1872
|
Blob *pComment; /* Check-in comment text */ |
|
1873
|
const char *zMimetype; /* Mimetype of check-in command. May be NULL */ |
|
1874
|
int verifyDate; /* Verify that child is younger */ |
|
1875
|
int closeFlag; /* Close the branch being committed */ |
|
1876
|
int integrateFlag; /* Close merged-in branches */ |
|
1877
|
int verboseFlag; /* Show diff in editor for check-in comment */ |
|
1878
|
Blob *pCksum; /* Repository checksum. May be 0 */ |
|
1879
|
const char *zDateOvrd; /* Date override. If 0 then use 'now' */ |
|
1880
|
const char *zUserOvrd; /* User override. If 0 then use login_name() */ |
|
1881
|
const char *zBranch; /* Branch name. May be 0 */ |
|
1882
|
const char *zColor; /* One-time background color. May be 0 */ |
|
1883
|
const char *zBrClr; /* Persistent branch color. May be 0 */ |
|
1884
|
const char **azTag; /* Tags to apply to this check-in */ |
|
1885
|
}; |
|
1886
|
#endif /* INTERFACE */ |
|
1887
|
|
|
1888
|
/* |
|
1889
|
** Create a manifest. |
|
1890
|
*/ |
|
1891
|
static void create_manifest( |
|
1892
|
Blob *pOut, /* Write the manifest here */ |
|
1893
|
const char *zBaselineUuid, /* Hash of baseline, or zero */ |
|
1894
|
Manifest *pBaseline, /* Make it a delta manifest if not zero */ |
|
1895
|
int vid, /* BLOB.id for the parent check-in */ |
|
1896
|
CheckinInfo *p, /* Information about the check-in */ |
|
1897
|
int *pnFBcard /* OUT: Number of generated B- and F-cards */ |
|
1898
|
){ |
|
1899
|
char *zDate; /* Date of the check-in */ |
|
1900
|
char *zParentUuid = 0; /* Hash of parent check-in */ |
|
1901
|
Blob filename; /* A single filename */ |
|
1902
|
int nBasename; /* Size of base filename */ |
|
1903
|
Stmt q; /* Various queries */ |
|
1904
|
Blob mcksum; /* Manifest checksum */ |
|
1905
|
ManifestFile *pFile; /* File from the baseline */ |
|
1906
|
int nFBcard = 0; /* Number of B-cards and F-cards */ |
|
1907
|
int i; /* Loop counter */ |
|
1908
|
const char *zColor; /* Modified value of p->zColor */ |
|
1909
|
|
|
1910
|
assert( pBaseline==0 || pBaseline->zBaseline==0 ); |
|
1911
|
assert( pBaseline==0 || zBaselineUuid!=0 ); |
|
1912
|
blob_zero(pOut); |
|
1913
|
if( vid ){ |
|
1914
|
zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d AND " |
|
1915
|
"EXISTS(SELECT 1 FROM event WHERE event.type='ci' and event.objid=%d)", |
|
1916
|
vid, vid); |
|
1917
|
if( !zParentUuid ){ |
|
1918
|
fossil_fatal("Could not find a valid check-in for RID %d. " |
|
1919
|
"Possible check-out/repo mismatch.", vid); |
|
1920
|
} |
|
1921
|
} |
|
1922
|
if( pBaseline ){ |
|
1923
|
blob_appendf(pOut, "B %s\n", zBaselineUuid); |
|
1924
|
manifest_file_rewind(pBaseline); |
|
1925
|
pFile = manifest_file_next(pBaseline, 0); |
|
1926
|
nFBcard++; |
|
1927
|
}else{ |
|
1928
|
pFile = 0; |
|
1929
|
} |
|
1930
|
if( blob_size(p->pComment)!=0 ){ |
|
1931
|
blob_appendf(pOut, "C %F\n", blob_str(p->pComment)); |
|
1932
|
}else{ |
|
1933
|
blob_append(pOut, "C (no\\scomment)\n", 16); |
|
1934
|
} |
|
1935
|
zDate = date_in_standard_format(p->zDateOvrd ? p->zDateOvrd : "now"); |
|
1936
|
blob_appendf(pOut, "D %s\n", zDate); |
|
1937
|
zDate[10] = ' '; |
|
1938
|
db_prepare(&q, |
|
1939
|
"SELECT pathname, uuid, origname, blob.rid, isexe, islink," |
|
1940
|
" is_selected(vfile.id)" |
|
1941
|
" FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
|
1942
|
" WHERE (NOT deleted OR NOT is_selected(vfile.id))" |
|
1943
|
" AND vfile.vid=%d" |
|
1944
|
" ORDER BY if_selected(vfile.id, pathname, origname)", |
|
1945
|
vid); |
|
1946
|
blob_zero(&filename); |
|
1947
|
blob_appendf(&filename, "%s", g.zLocalRoot); |
|
1948
|
nBasename = blob_size(&filename); |
|
1949
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1950
|
const char *zName = db_column_text(&q, 0); |
|
1951
|
const char *zUuid = db_column_text(&q, 1); |
|
1952
|
const char *zOrig = db_column_text(&q, 2); |
|
1953
|
int frid = db_column_int(&q, 3); |
|
1954
|
int isExe = db_column_int(&q, 4); |
|
1955
|
int isLink = db_column_int(&q, 5); |
|
1956
|
int isSelected = db_column_int(&q, 6); |
|
1957
|
const char *zPerm; |
|
1958
|
int cmp; |
|
1959
|
|
|
1960
|
blob_resize(&filename, nBasename); |
|
1961
|
blob_append(&filename, zName, -1); |
|
1962
|
|
|
1963
|
#if !defined(_WIN32) |
|
1964
|
/* For unix, extract the "executable" and "symlink" permissions |
|
1965
|
** directly from the filesystem. On windows, permissions are |
|
1966
|
** unchanged from the original. However, only do this if the file |
|
1967
|
** itself is actually selected to be part of this check-in. |
|
1968
|
*/ |
|
1969
|
if( isSelected ){ |
|
1970
|
int mPerm; |
|
1971
|
|
|
1972
|
mPerm = file_perm(blob_str(&filename), RepoFILE); |
|
1973
|
isExe = ( mPerm==PERM_EXE ); |
|
1974
|
isLink = ( mPerm==PERM_LNK ); |
|
1975
|
} |
|
1976
|
#endif |
|
1977
|
if( isExe ){ |
|
1978
|
zPerm = " x"; |
|
1979
|
}else if( isLink ){ |
|
1980
|
zPerm = " l"; /* note: symlinks don't have executable bit on unix */ |
|
1981
|
}else{ |
|
1982
|
zPerm = ""; |
|
1983
|
} |
|
1984
|
if( !g.markPrivate ) content_make_public(frid); |
|
1985
|
while( pFile && fossil_strcmp(pFile->zName,zName)<0 ){ |
|
1986
|
blob_appendf(pOut, "F %F\n", pFile->zName); |
|
1987
|
pFile = manifest_file_next(pBaseline, 0); |
|
1988
|
nFBcard++; |
|
1989
|
} |
|
1990
|
cmp = 1; |
|
1991
|
if( pFile==0 |
|
1992
|
|| (cmp = fossil_strcmp(pFile->zName,zName))!=0 |
|
1993
|
|| fossil_strcmp(pFile->zUuid, zUuid)!=0 |
|
1994
|
){ |
|
1995
|
if( zOrig && !isSelected ){ zName = zOrig; zOrig = 0; } |
|
1996
|
if( zOrig==0 || fossil_strcmp(zOrig,zName)==0 ){ |
|
1997
|
blob_appendf(pOut, "F %F %s%s\n", zName, zUuid, zPerm); |
|
1998
|
}else{ |
|
1999
|
if( zPerm[0]==0 ){ zPerm = " w"; } |
|
2000
|
blob_appendf(pOut, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); |
|
2001
|
} |
|
2002
|
nFBcard++; |
|
2003
|
} |
|
2004
|
if( cmp==0 ) pFile = manifest_file_next(pBaseline,0); |
|
2005
|
} |
|
2006
|
blob_reset(&filename); |
|
2007
|
db_finalize(&q); |
|
2008
|
while( pFile ){ |
|
2009
|
blob_appendf(pOut, "F %F\n", pFile->zName); |
|
2010
|
pFile = manifest_file_next(pBaseline, 0); |
|
2011
|
nFBcard++; |
|
2012
|
} |
|
2013
|
if( p->zMimetype && p->zMimetype[0] ){ |
|
2014
|
blob_appendf(pOut, "N %F\n", p->zMimetype); |
|
2015
|
} |
|
2016
|
if( vid ){ |
|
2017
|
blob_appendf(pOut, "P %s", zParentUuid); |
|
2018
|
if( p->verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); |
|
2019
|
free(zParentUuid); |
|
2020
|
db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0 OR id<-2"); |
|
2021
|
while( db_step(&q)==SQLITE_ROW ){ |
|
2022
|
char *zMergeUuid; |
|
2023
|
int mid = db_column_int(&q, 0); |
|
2024
|
if( (!g.markPrivate && content_is_private(mid)) || (mid == vid) ){ |
|
2025
|
continue; |
|
2026
|
} |
|
2027
|
zMergeUuid = rid_to_uuid(mid); |
|
2028
|
if( zMergeUuid ){ |
|
2029
|
blob_appendf(pOut, " %s", zMergeUuid); |
|
2030
|
if( p->verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); |
|
2031
|
free(zMergeUuid); |
|
2032
|
} |
|
2033
|
} |
|
2034
|
db_finalize(&q); |
|
2035
|
blob_appendf(pOut, "\n"); |
|
2036
|
} |
|
2037
|
free(zDate); |
|
2038
|
|
|
2039
|
db_prepare(&q, |
|
2040
|
"SELECT CASE vmerge.id WHEN -1 THEN '+' ELSE '-' END || mhash, merge" |
|
2041
|
" FROM vmerge" |
|
2042
|
" WHERE (vmerge.id=-1 OR vmerge.id=-2)" |
|
2043
|
" ORDER BY 1"); |
|
2044
|
while( db_step(&q)==SQLITE_ROW ){ |
|
2045
|
const char *zCherrypickUuid = db_column_text(&q, 0); |
|
2046
|
int mid = db_column_int(&q, 1); |
|
2047
|
if( (!g.markPrivate && content_is_private(mid)) || (mid == vid) ) continue; |
|
2048
|
blob_appendf(pOut, "Q %s\n", zCherrypickUuid); |
|
2049
|
} |
|
2050
|
db_finalize(&q); |
|
2051
|
|
|
2052
|
if( p->pCksum ) blob_appendf(pOut, "R %b\n", p->pCksum); |
|
2053
|
zColor = p->zColor; |
|
2054
|
if( p->zBranch && p->zBranch[0] ){ |
|
2055
|
/* Set tags for the new branch */ |
|
2056
|
if( p->zBrClr && p->zBrClr[0] ){ |
|
2057
|
zColor = 0; |
|
2058
|
blob_appendf(pOut, "T *bgcolor * %F\n", p->zBrClr); |
|
2059
|
} |
|
2060
|
blob_appendf(pOut, "T *branch * %F\n", p->zBranch); |
|
2061
|
blob_appendf(pOut, "T *sym-%F *\n", p->zBranch); |
|
2062
|
} |
|
2063
|
if( zColor && zColor[0] ){ |
|
2064
|
/* One-time background color */ |
|
2065
|
blob_appendf(pOut, "T +bgcolor * %F\n", zColor); |
|
2066
|
} |
|
2067
|
if( p->closeFlag ){ |
|
2068
|
blob_appendf(pOut, "T +closed *\n"); |
|
2069
|
} |
|
2070
|
db_prepare(&q, "SELECT mhash,merge FROM vmerge" |
|
2071
|
" WHERE id %s ORDER BY 1", |
|
2072
|
p->integrateFlag ? "IN(0,-4)" : "=(-4)"); |
|
2073
|
while( db_step(&q)==SQLITE_ROW ){ |
|
2074
|
const char *zIntegrateUuid = db_column_text(&q, 0); |
|
2075
|
int rid = db_column_int(&q, 1); |
|
2076
|
if( is_a_leaf(rid) && !db_exists("SELECT 1 FROM tagxref " |
|
2077
|
" WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_CLOSED, rid)){ |
|
2078
|
#if 0 |
|
2079
|
/* Make sure the check-in manifest of the resulting merge child does not |
|
2080
|
** include a +close tag referring to the leaf check-in on a private |
|
2081
|
** branch, so as not to generate a missing artifact reference on |
|
2082
|
** repository clones without that private branch. The merge command |
|
2083
|
** should have dropped the --integrate option, at this point. */ |
|
2084
|
assert( !content_is_private(rid) ); |
|
2085
|
#endif |
|
2086
|
blob_appendf(pOut, "T +closed %s\n", zIntegrateUuid); |
|
2087
|
} |
|
2088
|
} |
|
2089
|
db_finalize(&q); |
|
2090
|
|
|
2091
|
if( p->azTag ){ |
|
2092
|
for(i=0; p->azTag[i]; i++){ |
|
2093
|
/* Add a symbolic tag to this check-in. The tag names have already |
|
2094
|
** been sorted and converted using the %F format */ |
|
2095
|
assert( i==0 || strcmp(p->azTag[i-1], p->azTag[i])<=0 ); |
|
2096
|
blob_appendf(pOut, "T +sym-%s *\n", p->azTag[i]); |
|
2097
|
} |
|
2098
|
} |
|
2099
|
if( p->zBranch && p->zBranch[0] ){ |
|
2100
|
/* For a new branch, cancel all prior propagating tags */ |
|
2101
|
db_prepare(&q, |
|
2102
|
"SELECT tagname FROM tagxref, tag" |
|
2103
|
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" |
|
2104
|
" AND tagtype==2 AND tagname GLOB 'sym-*'" |
|
2105
|
" AND tagname!='sym-'||%Q" |
|
2106
|
" ORDER BY tagname", |
|
2107
|
vid, p->zBranch); |
|
2108
|
while( db_step(&q)==SQLITE_ROW ){ |
|
2109
|
const char *zBrTag = db_column_text(&q, 0); |
|
2110
|
blob_appendf(pOut, "T -%F *\n", zBrTag); |
|
2111
|
} |
|
2112
|
db_finalize(&q); |
|
2113
|
} |
|
2114
|
blob_appendf(pOut, "U %F\n", p->zUserOvrd ? p->zUserOvrd : login_name()); |
|
2115
|
md5sum_blob(pOut, &mcksum); |
|
2116
|
blob_appendf(pOut, "Z %b\n", &mcksum); |
|
2117
|
if( pnFBcard ) *pnFBcard = nFBcard; |
|
2118
|
} |
|
2119
|
|
|
2120
|
/* |
|
2121
|
** Issue a warning and give the user an opportunity to abandon out |
|
2122
|
** if a Unicode (UTF-16) byte-order-mark (BOM) or a \r\n line ending |
|
2123
|
** is seen in a text file. |
|
2124
|
** |
|
2125
|
** Return 1 if the user pressed 'c'. In that case, the file will have |
|
2126
|
** been converted to UTF-8 (if it was UTF-16) with LF line-endings, |
|
2127
|
** and the original file will have been renamed to "<filename>-original". |
|
2128
|
*/ |
|
2129
|
static int commit_warning( |
|
2130
|
Blob *pContent, /* The content of the file being committed. */ |
|
2131
|
int crlfOk, /* Non-zero if CR/LF warnings should be disabled. */ |
|
2132
|
int binOk, /* Non-zero if binary warnings should be disabled. */ |
|
2133
|
int encodingOk, /* Non-zero if encoding warnings should be disabled. */ |
|
2134
|
int sizeOk, /* Non-zero if oversize warnings are disabled */ |
|
2135
|
int noPrompt, /* 0 to always prompt, 1 for 'N', 2 for 'Y'. */ |
|
2136
|
const char *zFilename, /* The full name of the file being committed. */ |
|
2137
|
Blob *pReason /* Reason for warning, if any (non-fatal only). */ |
|
2138
|
){ |
|
2139
|
int bReverse; /* UTF-16 byte order is reversed? */ |
|
2140
|
int fUnicode; /* return value of could_be_utf16() */ |
|
2141
|
int fBinary; /* does the blob content appear to be binary? */ |
|
2142
|
int lookFlags; /* output flags from looks_like_utf8/utf16() */ |
|
2143
|
int fHasAnyCr; /* the blob contains one or more CR chars */ |
|
2144
|
int fHasLoneCrOnly; /* all detected line endings are CR only */ |
|
2145
|
int fHasCrLfOnly; /* all detected line endings are CR/LF pairs */ |
|
2146
|
int fHasInvalidUtf8 = 0;/* contains invalid UTF-8 */ |
|
2147
|
int fHasNul; /* contains NUL chars? */ |
|
2148
|
int fHasLong; /* overly long line? */ |
|
2149
|
char *zMsg; /* Warning message */ |
|
2150
|
Blob fname; /* Relative pathname of the file */ |
|
2151
|
static int allOk = 0; /* Set to true to disable this routine */ |
|
2152
|
|
|
2153
|
if( allOk ) return 0; |
|
2154
|
if( sizeOk ){ |
|
2155
|
fUnicode = could_be_utf16(pContent, &bReverse); |
|
2156
|
if( fUnicode ){ |
|
2157
|
lookFlags = looks_like_utf16(pContent, bReverse, LOOK_NUL); |
|
2158
|
}else{ |
|
2159
|
lookFlags = looks_like_utf8(pContent, LOOK_NUL); |
|
2160
|
if( !(lookFlags & LOOK_BINARY) && invalid_utf8(pContent) ){ |
|
2161
|
fHasInvalidUtf8 = 1; |
|
2162
|
} |
|
2163
|
} |
|
2164
|
fHasAnyCr = (lookFlags & LOOK_CR); |
|
2165
|
fBinary = (lookFlags & LOOK_BINARY); |
|
2166
|
fHasLoneCrOnly = ((lookFlags & LOOK_EOL) == LOOK_LONE_CR); |
|
2167
|
fHasCrLfOnly = ((lookFlags & LOOK_EOL) == LOOK_CRLF); |
|
2168
|
fHasNul = (lookFlags & LOOK_NUL); |
|
2169
|
fHasLong = (lookFlags & LOOK_LONG); |
|
2170
|
}else{ |
|
2171
|
fUnicode = fHasAnyCr = fBinary = fHasInvalidUtf8 = 0; |
|
2172
|
fHasLoneCrOnly = fHasCrLfOnly = fHasNul = fHasLong = 0; |
|
2173
|
} |
|
2174
|
if( !sizeOk || fUnicode || fHasAnyCr || fBinary || fHasInvalidUtf8 ){ |
|
2175
|
const char *zWarning = 0; |
|
2176
|
const char *zDisable = 0; |
|
2177
|
const char *zConvert = "c=convert/"; |
|
2178
|
const char *zIn = "in"; |
|
2179
|
Blob ans; |
|
2180
|
char cReply; |
|
2181
|
|
|
2182
|
if( fBinary ){ |
|
2183
|
if( binOk ){ |
|
2184
|
return 0; /* We don't want binary warnings for this file. */ |
|
2185
|
} |
|
2186
|
if( !fHasNul && fHasLong ){ |
|
2187
|
zWarning = "long lines"; |
|
2188
|
zConvert = ""; /* We cannot convert overlong lines. */ |
|
2189
|
}else{ |
|
2190
|
zWarning = "binary data"; |
|
2191
|
zConvert = ""; /* We cannot convert binary files. */ |
|
2192
|
} |
|
2193
|
zDisable = "\"binary-glob\" setting"; |
|
2194
|
}else if( fUnicode && fHasAnyCr ){ |
|
2195
|
if( crlfOk && encodingOk ){ |
|
2196
|
return 0; /* We don't want CR/LF and Unicode warnings for this file. */ |
|
2197
|
} |
|
2198
|
if( fHasLoneCrOnly ){ |
|
2199
|
zWarning = "CR line endings and Unicode"; |
|
2200
|
}else if( fHasCrLfOnly ){ |
|
2201
|
zWarning = "CR/LF line endings and Unicode"; |
|
2202
|
}else{ |
|
2203
|
zWarning = "mixed line endings and Unicode"; |
|
2204
|
} |
|
2205
|
zDisable = "\"crlf-glob\" and \"encoding-glob\" settings"; |
|
2206
|
}else if( fHasInvalidUtf8 ){ |
|
2207
|
if( encodingOk ){ |
|
2208
|
return 0; /* We don't want encoding warnings for this file. */ |
|
2209
|
} |
|
2210
|
zWarning = "invalid UTF-8"; |
|
2211
|
zDisable = "\"encoding-glob\" setting"; |
|
2212
|
}else if( fHasAnyCr ){ |
|
2213
|
if( crlfOk ){ |
|
2214
|
return 0; /* We don't want CR/LF warnings for this file. */ |
|
2215
|
} |
|
2216
|
if( fHasLoneCrOnly ){ |
|
2217
|
zWarning = "CR line endings"; |
|
2218
|
}else if( fHasCrLfOnly ){ |
|
2219
|
zWarning = "CR/LF line endings"; |
|
2220
|
}else{ |
|
2221
|
zWarning = "mixed line endings"; |
|
2222
|
} |
|
2223
|
zDisable = "\"crlf-glob\" setting"; |
|
2224
|
}else if( !sizeOk ){ |
|
2225
|
zWarning = "oversize"; |
|
2226
|
zIn = "file"; |
|
2227
|
}else{ |
|
2228
|
if( encodingOk ){ |
|
2229
|
return 0; /* We don't want encoding warnings for this file. */ |
|
2230
|
} |
|
2231
|
zWarning = "Unicode"; |
|
2232
|
zDisable = "\"encoding-glob\" setting"; |
|
2233
|
} |
|
2234
|
file_relative_name(zFilename, &fname, 0); |
|
2235
|
if( !sizeOk ){ |
|
2236
|
zMsg = mprintf( |
|
2237
|
"%s is more than %,lld bytes in size.\n" |
|
2238
|
"Commit anyhow (a=all/y/N)? ", |
|
2239
|
blob_str(&fname), db_large_file_size()); |
|
2240
|
}else{ |
|
2241
|
zMsg = mprintf( |
|
2242
|
"%s contains %s. Use --no-warnings or the %s to" |
|
2243
|
" disable this warning.\n" |
|
2244
|
"Commit anyhow (a=all/%sy/N)? ", |
|
2245
|
blob_str(&fname), zWarning, zDisable, zConvert); |
|
2246
|
} |
|
2247
|
if( noPrompt==0 ){ |
|
2248
|
prompt_user(zMsg, &ans); |
|
2249
|
cReply = blob_str(&ans)[0]; |
|
2250
|
blob_reset(&ans); |
|
2251
|
}else if( noPrompt==2 ){ |
|
2252
|
cReply = 'Y'; |
|
2253
|
}else{ |
|
2254
|
cReply = 'N'; |
|
2255
|
} |
|
2256
|
fossil_free(zMsg); |
|
2257
|
if( cReply=='a' || cReply=='A' ){ |
|
2258
|
allOk = 1; |
|
2259
|
}else if( *zConvert && (cReply=='c' || cReply=='C') ){ |
|
2260
|
char *zOrig = file_newname(zFilename, "original", 1); |
|
2261
|
FILE *f; |
|
2262
|
blob_write_to_file(pContent, zOrig); |
|
2263
|
fossil_free(zOrig); |
|
2264
|
f = fossil_fopen(zFilename, "wb"); |
|
2265
|
if( f==0 ){ |
|
2266
|
fossil_warning("cannot open %s for writing", zFilename); |
|
2267
|
}else{ |
|
2268
|
if( fUnicode ){ |
|
2269
|
int bomSize; |
|
2270
|
const unsigned char *bom = get_utf8_bom(&bomSize); |
|
2271
|
fwrite(bom, 1, bomSize, f); |
|
2272
|
blob_to_utf8_no_bom(pContent, 0); |
|
2273
|
}else if( fHasInvalidUtf8 ){ |
|
2274
|
blob_cp1252_to_utf8(pContent); |
|
2275
|
} |
|
2276
|
if( fHasAnyCr ){ |
|
2277
|
blob_to_lf_only(pContent); |
|
2278
|
} |
|
2279
|
fwrite(blob_buffer(pContent), 1, blob_size(pContent), f); |
|
2280
|
fclose(f); |
|
2281
|
} |
|
2282
|
return 1; |
|
2283
|
}else if( cReply!='y' && cReply!='Y' ){ |
|
2284
|
fossil_fatal("Abandoning commit due to %s %s %s", |
|
2285
|
zWarning, zIn, blob_str(&fname)); |
|
2286
|
}else if( noPrompt==2 ){ |
|
2287
|
if( pReason ){ |
|
2288
|
blob_append(pReason, zWarning, -1); |
|
2289
|
} |
|
2290
|
return 1; |
|
2291
|
} |
|
2292
|
blob_reset(&fname); |
|
2293
|
} |
|
2294
|
return 0; |
|
2295
|
} |
|
2296
|
|
|
2297
|
/* |
|
2298
|
** COMMAND: test-commit-warning |
|
2299
|
** |
|
2300
|
** Usage: %fossil test-commit-warning ?OPTIONS? |
|
2301
|
** |
|
2302
|
** Check each file in the check-out, including unmodified ones, using all |
|
2303
|
** the pre-commit checks. |
|
2304
|
** |
|
2305
|
** Options: |
|
2306
|
** --no-settings Do not consider any glob settings. |
|
2307
|
** -v|--verbose Show per-file results for all pre-commit checks. |
|
2308
|
** |
|
2309
|
** See also: commit, extras |
|
2310
|
*/ |
|
2311
|
void test_commit_warning(void){ |
|
2312
|
int rc = 0; |
|
2313
|
int noSettings; |
|
2314
|
int verboseFlag; |
|
2315
|
i64 mxSize; |
|
2316
|
Stmt q; |
|
2317
|
noSettings = find_option("no-settings",0,0)!=0; |
|
2318
|
verboseFlag = find_option("verbose","v",0)!=0; |
|
2319
|
verify_all_options(); |
|
2320
|
db_must_be_within_tree(); |
|
2321
|
mxSize = db_large_file_size(); |
|
2322
|
db_prepare(&q, |
|
2323
|
"SELECT %Q || pathname, pathname, %s, %s, %s FROM vfile" |
|
2324
|
" WHERE NOT deleted", |
|
2325
|
g.zLocalRoot, |
|
2326
|
glob_expr("pathname", noSettings ? 0 : db_get("crlf-glob", |
|
2327
|
db_get("crnl-glob",""))), |
|
2328
|
glob_expr("pathname", noSettings ? 0 : db_get("binary-glob","")), |
|
2329
|
glob_expr("pathname", noSettings ? 0 : db_get("encoding-glob","")) |
|
2330
|
); |
|
2331
|
while( db_step(&q)==SQLITE_ROW ){ |
|
2332
|
const char *zFullname; |
|
2333
|
const char *zName; |
|
2334
|
Blob content; |
|
2335
|
Blob reason; |
|
2336
|
int crlfOk, binOk, encodingOk, sizeOk; |
|
2337
|
int fileRc; |
|
2338
|
|
|
2339
|
zFullname = db_column_text(&q, 0); |
|
2340
|
zName = db_column_text(&q, 1); |
|
2341
|
crlfOk = db_column_int(&q, 2); |
|
2342
|
binOk = db_column_int(&q, 3); |
|
2343
|
encodingOk = db_column_int(&q, 4); |
|
2344
|
sizeOk = mxSize<=0 || file_size(zFullname, ExtFILE)<=mxSize; |
|
2345
|
blob_zero(&content); |
|
2346
|
blob_read_from_file(&content, zFullname, RepoFILE); |
|
2347
|
blob_zero(&reason); |
|
2348
|
fileRc = commit_warning(&content, crlfOk, binOk, encodingOk, sizeOk, 2, |
|
2349
|
zFullname, &reason); |
|
2350
|
if( fileRc || verboseFlag ){ |
|
2351
|
fossil_print("%d\t%s\t%s\n", fileRc, zName, blob_str(&reason)); |
|
2352
|
} |
|
2353
|
blob_reset(&reason); |
|
2354
|
rc |= fileRc; |
|
2355
|
} |
|
2356
|
db_finalize(&q); |
|
2357
|
fossil_print("%d\n", rc); |
|
2358
|
} |
|
2359
|
|
|
2360
|
/* |
|
2361
|
** qsort() comparison routine for an array of pointers to strings. |
|
2362
|
*/ |
|
2363
|
static int tagCmp(const void *a, const void *b){ |
|
2364
|
char **pA = (char**)a; |
|
2365
|
char **pB = (char**)b; |
|
2366
|
return fossil_strcmp(pA[0], pB[0]); |
|
2367
|
} |
|
2368
|
|
|
2369
|
/* |
|
2370
|
** SETTING: verify-comments width=8 default=on |
|
2371
|
** |
|
2372
|
** This setting determines how much sanity checking, if any, the |
|
2373
|
** "fossil commit" and "fossil amend" commands do against check-in |
|
2374
|
** comments. Recognized values: |
|
2375
|
** |
|
2376
|
** on (Default) Check for bad syntax and/or broken hyperlinks |
|
2377
|
** in check-in comments and offer the user a chance to |
|
2378
|
** continue editing for interactive sessions, or simply |
|
2379
|
** abort the commit if the comment was entered using -m or -M |
|
2380
|
** |
|
2381
|
** off Do not do syntax checking of any kind |
|
2382
|
** |
|
2383
|
** preview Do all the same checks as "on" but also always preview the |
|
2384
|
** check-in comment to the user during interactive sessions |
|
2385
|
** even if no obvious errors are found, and provide an |
|
2386
|
** opportunity to accept or re-edit |
|
2387
|
*/ |
|
2388
|
|
|
2389
|
#if INTERFACE |
|
2390
|
#define COMCK_MARKUP 0x01 /* Check for mistakes */ |
|
2391
|
#define COMCK_PREVIEW 0x02 /* Always preview, even if no issues found */ |
|
2392
|
#endif /* INTERFACE */ |
|
2393
|
|
|
2394
|
/* |
|
2395
|
** Check for possible formatting errors in the comment string pComment. |
|
2396
|
** |
|
2397
|
** If issues are found, write an appropriate error notice, probably also |
|
2398
|
** including the complete text of the comment formatted to highlight the |
|
2399
|
** problem, to stdout and return non-zero. The return value is some |
|
2400
|
** combination of the COMCK_* flags, depending on what went wrong. |
|
2401
|
** |
|
2402
|
** If no issues are seen, do not output anything and return zero. |
|
2403
|
*/ |
|
2404
|
int verify_comment(Blob *pComment, int mFlags){ |
|
2405
|
Blob in, html; |
|
2406
|
int mResult; |
|
2407
|
int rc = mFlags & COMCK_PREVIEW; |
|
2408
|
int wFlags; |
|
2409
|
|
|
2410
|
if( mFlags==0 ) return 0; |
|
2411
|
blob_init(&in, blob_str(pComment), -1); |
|
2412
|
blob_init(&html, 0, 0); |
|
2413
|
wFlags = wiki_convert_flags(0); |
|
2414
|
wFlags &= ~WIKI_NOBADLINKS; |
|
2415
|
wFlags |= WIKI_MARK; |
|
2416
|
mResult = wiki_convert(&in, &html, wFlags); |
|
2417
|
if( mResult & RENDER_ANYERROR ) rc |= COMCK_MARKUP; |
|
2418
|
if( rc ){ |
|
2419
|
int htot = ((wFlags & WIKI_NEWLINE)!=0 ? 0 : HTOT_FLOW)|HTOT_TRIM; |
|
2420
|
Blob txt; |
|
2421
|
if( terminal_is_vt100() ) htot |= HTOT_VT100; |
|
2422
|
blob_init(&txt, 0, 0); |
|
2423
|
html_to_plaintext(blob_str(&html), &txt, htot); |
|
2424
|
if( rc & COMCK_MARKUP ){ |
|
2425
|
fossil_print("Possible format errors in the check-in comment:\n\n "); |
|
2426
|
}else{ |
|
2427
|
fossil_print("Preview of the check-in comment:\n\n "); |
|
2428
|
} |
|
2429
|
if( wFlags & WIKI_NEWLINE ){ |
|
2430
|
Blob line; |
|
2431
|
char *zIndent = ""; |
|
2432
|
while( blob_line(&txt, &line) ){ |
|
2433
|
fossil_print("%s%b", zIndent, &line); |
|
2434
|
zIndent = " "; |
|
2435
|
} |
|
2436
|
fossil_print("\n"); |
|
2437
|
}else{ |
|
2438
|
comment_print(blob_str(&txt), 0, 3, -1, get_comment_format()); |
|
2439
|
} |
|
2440
|
fossil_print("\n"); |
|
2441
|
fflush(stdout); |
|
2442
|
blob_reset(&txt); |
|
2443
|
} |
|
2444
|
blob_reset(&html); |
|
2445
|
blob_reset(&in); |
|
2446
|
return rc; |
|
2447
|
} |
|
2448
|
|
|
2449
|
/* |
|
2450
|
** COMMAND: ci# |
|
2451
|
** COMMAND: commit |
|
2452
|
** |
|
2453
|
** Usage: %fossil commit ?OPTIONS? ?FILE...? |
|
2454
|
** or: %fossil ci ?OPTIONS? ?FILE...? |
|
2455
|
** |
|
2456
|
** Create a new check-in containing all of the changes in the current |
|
2457
|
** check-out. All changes are committed unless some subset of files |
|
2458
|
** is specified on the command line, in which case only the named files |
|
2459
|
** become part of the new check-in. |
|
2460
|
** |
|
2461
|
** You will be prompted to enter a check-in comment unless the comment |
|
2462
|
** has been specified on the command-line using "-m" or "-M". The |
|
2463
|
** text editor used is determined by the "editor" setting, or by the |
|
2464
|
** "VISUAL" or "EDITOR" environment variables. Commit message text is |
|
2465
|
** interpreted as fossil-wiki format. Potentially misformatted check-in |
|
2466
|
** comment text is detected and reported unless the --no-verify-comment |
|
2467
|
** option is used. |
|
2468
|
** |
|
2469
|
** The --branch option followed by a branch name causes the new |
|
2470
|
** check-in to be placed in a newly-created branch with name specified. |
|
2471
|
** |
|
2472
|
** A check-in is not permitted to fork unless the --allow-fork option |
|
2473
|
** appears. An empty check-in (i.e. with nothing changed) is not |
|
2474
|
** allowed unless the --allow-empty option appears. A check-in may not |
|
2475
|
** be older than its ancestor unless the --allow-older option appears. |
|
2476
|
** If any files in the check-in appear to contain unresolved merge |
|
2477
|
** conflicts, the check-in will not be allowed unless the |
|
2478
|
** --allow-conflict option is present. In addition, the entire |
|
2479
|
** check-in process may be aborted if a file contains content that |
|
2480
|
** appears to be binary, Unicode text, or text with CR/LF line endings |
|
2481
|
** unless the interactive user chooses to proceed. If there is no |
|
2482
|
** interactive user or these warnings should be skipped for some other |
|
2483
|
** reason, the --no-warnings option may be used. A check-in is not |
|
2484
|
** allowed against a closed leaf. |
|
2485
|
** |
|
2486
|
** The --private option creates a private check-in that is never synced. |
|
2487
|
** Children of private check-ins are automatically private. |
|
2488
|
** |
|
2489
|
** The --tag option applies the symbolic tag name to the check-in. |
|
2490
|
** The --tag option can be repeated to assign multiple tags to a check-in. |
|
2491
|
** For example: "... --tag release --tag version-1.2.3 ..." |
|
2492
|
** |
|
2493
|
** Options: |
|
2494
|
** --allow-conflict Allow unresolved merge conflicts |
|
2495
|
** --allow-empty Allow a commit with no changes |
|
2496
|
** --allow-fork Allow the commit to fork |
|
2497
|
** --allow-older Allow a commit older than its ancestor |
|
2498
|
** --baseline Use a baseline manifest in the commit process |
|
2499
|
** --bgcolor COLOR Apply COLOR to this one check-in only |
|
2500
|
** --branch NEW-BRANCH-NAME Check in to this new branch |
|
2501
|
** --branchcolor COLOR Apply given COLOR to the branch |
|
2502
|
** --close Close the branch being committed |
|
2503
|
** --date-override DATETIME Make DATETIME the time of the check-in. |
|
2504
|
** Useful when importing historical check-ins |
|
2505
|
** from another version control system. |
|
2506
|
** --delta Use a delta manifest in the commit process |
|
2507
|
** --editor NAME Text editor to use for check-in comment. |
|
2508
|
** --hash Verify file status using hashing rather |
|
2509
|
** than relying on filesystem mtimes |
|
2510
|
** --if-changes Make this command a silent no-op if there |
|
2511
|
** are no changes |
|
2512
|
** --ignore-clock-skew If a clock skew is detected, ignore it and |
|
2513
|
** behave as if the user had entered 'yes' to |
|
2514
|
** the question of whether to proceed despite |
|
2515
|
** the skew. |
|
2516
|
** --ignore-oversize Do not warn the user about oversized files |
|
2517
|
** --integrate Close all merged-in branches |
|
2518
|
** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as the check-in comment |
|
2519
|
** -M|--message-file FILE Read the check-in comment from FILE |
|
2520
|
** -n|--dry-run Do not actually create a new check-in. Just |
|
2521
|
** show what would have happened. For debugging. |
|
2522
|
** -v|--verbose Show a diff in the commit message prompt |
|
2523
|
** --no-prompt This option disables prompting the user for |
|
2524
|
** input and assumes an answer of 'No' for every |
|
2525
|
** question. |
|
2526
|
** --no-warnings Omit all warnings about file contents |
|
2527
|
** --no-verify Do not run before-commit hooks |
|
2528
|
** --no-verify-comment Do not validate the check-in comment |
|
2529
|
** --nosign Do not attempt to sign this commit with gpg |
|
2530
|
** --nosync Do not auto-sync prior to committing |
|
2531
|
** --override-lock Allow a check-in even though parent is locked |
|
2532
|
** --private Never sync the resulting check-in and make |
|
2533
|
** all descendants private too. |
|
2534
|
** --proxy PROXY Use PROXY as http proxy during sync operation |
|
2535
|
** --tag TAG-NAME Add TAG-NAME to the check-in. May be repeated. |
|
2536
|
** --trace Debug tracing |
|
2537
|
** --user-override USER Record USER as the login that created the |
|
2538
|
** new check-in, rather that the current user. |
|
2539
|
** |
|
2540
|
** See also: [[branch]], [[changes]], [[update]], [[extras]], [[sync]] |
|
2541
|
*/ |
|
2542
|
void commit_cmd(void){ |
|
2543
|
int hasChanges; /* True if unsaved changes exist */ |
|
2544
|
int vid; /* blob-id of parent version */ |
|
2545
|
int nrid; /* blob-id of a modified file */ |
|
2546
|
int nvid; /* Blob-id of the new check-in */ |
|
2547
|
Blob comment; /* Check-in comment */ |
|
2548
|
const char *zComment; /* Check-in comment */ |
|
2549
|
Stmt q; /* Various queries */ |
|
2550
|
char *zUuid; /* Hash of the new check-in */ |
|
2551
|
int useHash = 0; /* True to verify file status using hashing */ |
|
2552
|
int noSign = 0; /* True to omit signing the manifest using GPG */ |
|
2553
|
int privateFlag = 0; /* True if the --private option is present */ |
|
2554
|
int privateParent = 0; /* True if the parent check-in is private */ |
|
2555
|
int isAMerge = 0; /* True if checking in a merge */ |
|
2556
|
int noWarningFlag = 0; /* True if skipping all warnings */ |
|
2557
|
int noVerify = 0; /* Do not run before-commit hooks */ |
|
2558
|
int bTrace = 0; /* Debug tracing */ |
|
2559
|
int noPrompt = 0; /* True if skipping all prompts */ |
|
2560
|
int forceFlag = 0; /* Undocumented: Disables all checks */ |
|
2561
|
int forceDelta = 0; /* Force a delta-manifest */ |
|
2562
|
int forceBaseline = 0; /* Force a baseline-manifest */ |
|
2563
|
int allowConflict = 0; /* Allow unresolve merge conflicts */ |
|
2564
|
int allowEmpty = 0; /* Allow a commit with no changes */ |
|
2565
|
int onlyIfChanges = 0; /* No-op if there are no changes */ |
|
2566
|
int allowFork = 0; /* Allow the commit to fork */ |
|
2567
|
int allowOlder = 0; /* Allow a commit older than its ancestor */ |
|
2568
|
int noVerifyCom = 0; /* Allow suspicious check-in comments */ |
|
2569
|
int useCksum; /* True if checksums should be computed and verified */ |
|
2570
|
int dryRunFlag; /* True for a test run. Debugging only */ |
|
2571
|
CheckinInfo sCiInfo; /* Information about this check-in */ |
|
2572
|
const char *zComFile; /* Read commit message from this file */ |
|
2573
|
int nTag = 0; /* Number of --tag arguments */ |
|
2574
|
const char *zTag; /* A single --tag argument */ |
|
2575
|
ManifestFile *pFile; /* File structure in the manifest */ |
|
2576
|
Manifest *pManifest; /* Manifest structure */ |
|
2577
|
Blob manifest; /* Manifest in baseline form */ |
|
2578
|
Blob cksum1, cksum2; /* Before and after commit checksums */ |
|
2579
|
Blob cksum1b; /* Checksum recorded in the manifest */ |
|
2580
|
int szD; /* Size of the delta manifest */ |
|
2581
|
int szB; /* Size of the baseline manifest */ |
|
2582
|
int nConflict = 0; /* Number of unresolved merge conflicts */ |
|
2583
|
int abortCommit = 0; /* Abort the commit due to text format conversions */ |
|
2584
|
Blob ans; /* Answer to continuation prompts */ |
|
2585
|
char cReply; /* First character of ans */ |
|
2586
|
int bRecheck = 0; /* Repeat fork and closed-branch checks*/ |
|
2587
|
int bIgnoreSkew = 0; /* --ignore-clock-skew flag */ |
|
2588
|
int mxSize; |
|
2589
|
char *zCurBranch = 0; /* The current branch name of checkout */ |
|
2590
|
char *zNewBranch = 0; /* The branch name after update */ |
|
2591
|
int ckComFlgs; /* Flags passed to verify_comment() */ |
|
2592
|
|
|
2593
|
memset(&sCiInfo, 0, sizeof(sCiInfo)); |
|
2594
|
url_proxy_options(); |
|
2595
|
/* --sha1sum is an undocumented alias for --hash for backwards compatibility */ |
|
2596
|
useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; |
|
2597
|
noSign = find_option("nosign",0,0)!=0; |
|
2598
|
if( find_option("nosync",0,0) ) g.fNoSync = 1; |
|
2599
|
privateFlag = find_option("private",0,0)!=0; |
|
2600
|
forceDelta = find_option("delta",0,0)!=0; |
|
2601
|
forceBaseline = find_option("baseline",0,0)!=0; |
|
2602
|
db_must_be_within_tree(); |
|
2603
|
if( db_get_boolean("dont-commit",0) ){ |
|
2604
|
fossil_fatal("committing is prohibited: the 'dont-commit' option is set"); |
|
2605
|
} |
|
2606
|
if( forceDelta ){ |
|
2607
|
if( forceBaseline ){ |
|
2608
|
fossil_fatal("cannot use --delta and --baseline together"); |
|
2609
|
} |
|
2610
|
if( db_get_boolean("forbid-delta-manifests",0) ){ |
|
2611
|
fossil_fatal("delta manifests are prohibited in this repository"); |
|
2612
|
} |
|
2613
|
} |
|
2614
|
dryRunFlag = find_option("dry-run","n",0)!=0; |
|
2615
|
if( !dryRunFlag ){ |
|
2616
|
dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ |
|
2617
|
} |
|
2618
|
zComment = find_option("comment","m",1); |
|
2619
|
forceFlag = find_option("force", "f", 0)!=0; |
|
2620
|
allowConflict = find_option("allow-conflict",0,0)!=0; |
|
2621
|
allowEmpty = find_option("allow-empty",0,0)!=0; |
|
2622
|
noVerifyCom = find_option("no-verify-comment",0,0)!=0; |
|
2623
|
onlyIfChanges = find_option("if-changes",0,0)!=0; |
|
2624
|
allowFork = find_option("allow-fork",0,0)!=0; |
|
2625
|
if( find_option("override-lock",0,0)!=0 ) allowFork = 1; |
|
2626
|
allowOlder = find_option("allow-older",0,0)!=0; |
|
2627
|
noPrompt = find_option("no-prompt", 0, 0)!=0; |
|
2628
|
noWarningFlag = find_option("no-warnings", 0, 0)!=0; |
|
2629
|
noVerify = find_option("no-verify",0,0)!=0; |
|
2630
|
bTrace = find_option("trace",0,0)!=0; |
|
2631
|
sCiInfo.zBranch = find_option("branch","b",1); |
|
2632
|
|
|
2633
|
/* NB: the --bgcolor and --branchcolor flags still work, but are |
|
2634
|
** now undocumented, to discourage their use. --mimetype has never |
|
2635
|
** been used for anything, so also leave it undocumented */ |
|
2636
|
sCiInfo.zColor = find_option("bgcolor",0,1); /* Deprecated, undocumented*/ |
|
2637
|
sCiInfo.zBrClr = find_option("branchcolor",0,1); /* Deprecated, undocumented*/ |
|
2638
|
sCiInfo.zMimetype = find_option("mimetype",0,1); /* Deprecated, undocumented*/ |
|
2639
|
|
|
2640
|
sCiInfo.closeFlag = find_option("close",0,0)!=0; |
|
2641
|
sCiInfo.integrateFlag = find_option("integrate",0,0)!=0; |
|
2642
|
sCiInfo.verboseFlag = find_option("verbose", "v", 0)!=0; |
|
2643
|
while( (zTag = find_option("tag",0,1))!=0 ){ |
|
2644
|
if( zTag[0]==0 ) continue; |
|
2645
|
sCiInfo.azTag = fossil_realloc((void*)sCiInfo.azTag, |
|
2646
|
sizeof(char*)*(nTag+2)); |
|
2647
|
sCiInfo.azTag[nTag++] = zTag; |
|
2648
|
sCiInfo.azTag[nTag] = 0; |
|
2649
|
} |
|
2650
|
zComFile = find_option("message-file", "M", 1); |
|
2651
|
sCiInfo.zDateOvrd = find_option("date-override",0,1); |
|
2652
|
sCiInfo.zUserOvrd = find_option("user-override",0,1); |
|
2653
|
noSign = db_get_boolean("omitsign", 0)|noSign; |
|
2654
|
if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } |
|
2655
|
useCksum = db_get_boolean("repo-cksum", 1); |
|
2656
|
bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0; |
|
2657
|
mxSize = db_large_file_size(); |
|
2658
|
if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0; |
|
2659
|
(void)fossil_text_editor(); |
|
2660
|
verify_all_options(); |
|
2661
|
|
|
2662
|
/* The --no-warnings flag and the --force flag each imply |
|
2663
|
** the --no-verify-comment flag */ |
|
2664
|
if( noWarningFlag || forceFlag ){ |
|
2665
|
noVerifyCom = 1; |
|
2666
|
} |
|
2667
|
|
|
2668
|
/* Get the ID of the parent manifest artifact */ |
|
2669
|
vid = db_lget_int("checkout", 0); |
|
2670
|
if( vid==0 ){ |
|
2671
|
useCksum = 1; |
|
2672
|
if( privateFlag==0 && sCiInfo.zBranch==0 ) { |
|
2673
|
sCiInfo.zBranch = db_main_branch(); |
|
2674
|
} |
|
2675
|
}else{ |
|
2676
|
privateParent = content_is_private(vid); |
|
2677
|
} |
|
2678
|
|
|
2679
|
user_select(); |
|
2680
|
/* |
|
2681
|
** Check that the user exists. |
|
2682
|
*/ |
|
2683
|
if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
|
2684
|
fossil_fatal("no such user: %s", g.zLogin); |
|
2685
|
} |
|
2686
|
|
|
2687
|
/* |
|
2688
|
** Detect if the branch name has changed from the parent check-in |
|
2689
|
** and prompt if necessary |
|
2690
|
**/ |
|
2691
|
zCurBranch = db_text(0, |
|
2692
|
" SELECT value FROM tagxref AS tx" |
|
2693
|
" WHERE rid=(SELECT pid" |
|
2694
|
" FROM tagxref LEFT JOIN event ON srcid=objid" |
|
2695
|
" LEFT JOIN plink ON rid=cid" |
|
2696
|
" WHERE rid=%d AND tagxref.tagid=%d" |
|
2697
|
" AND srcid!=origid" |
|
2698
|
" AND tagtype=2 AND coalesce(euser,user)!=%Q)" |
|
2699
|
" AND tx.tagid=%d", |
|
2700
|
vid, TAG_BRANCH, g.zLogin, TAG_BRANCH |
|
2701
|
); |
|
2702
|
if( zCurBranch!=0 && zCurBranch[0]!=0 |
|
2703
|
&& forceFlag==0 |
|
2704
|
&& noPrompt==0 |
|
2705
|
){ |
|
2706
|
zNewBranch = branch_of_rid(vid); |
|
2707
|
fossil_warning( |
|
2708
|
"WARNING: The parent check-in [%.10s] has been moved from branch\n" |
|
2709
|
" '%s' over to branch '%s'.", |
|
2710
|
rid_to_uuid(vid), zCurBranch, zNewBranch |
|
2711
|
); |
|
2712
|
prompt_user("Commit anyway? (y/N) ", &ans); |
|
2713
|
cReply = blob_str(&ans)[0]; |
|
2714
|
blob_reset(&ans); |
|
2715
|
if( cReply!='y' && cReply!='Y' ){ |
|
2716
|
fossil_fatal("Abandoning commit because branch has changed"); |
|
2717
|
} |
|
2718
|
fossil_free(zNewBranch); |
|
2719
|
fossil_free(zCurBranch); |
|
2720
|
zCurBranch = branch_of_rid(vid); |
|
2721
|
} |
|
2722
|
if( zCurBranch==0 ) zCurBranch = branch_of_rid(vid); |
|
2723
|
|
|
2724
|
/* Track the "private" status */ |
|
2725
|
g.markPrivate = privateFlag || privateParent; |
|
2726
|
if( privateFlag && !privateParent ){ |
|
2727
|
/* Apply default branch name ("private") and color ("orange") if not |
|
2728
|
** specified otherwise on the command-line, and if the parent is not |
|
2729
|
** already private. */ |
|
2730
|
if( sCiInfo.zBranch==0 ) sCiInfo.zBranch = "private"; |
|
2731
|
} |
|
2732
|
|
|
2733
|
/* Do not allow the creation of a new branch using an existing open |
|
2734
|
** branch name unless the --force flag is used */ |
|
2735
|
if( sCiInfo.zBranch!=0 |
|
2736
|
&& !forceFlag |
|
2737
|
&& fossil_strcmp(sCiInfo.zBranch,"private")!=0 |
|
2738
|
&& branch_is_open(sCiInfo.zBranch) |
|
2739
|
){ |
|
2740
|
fossil_fatal("an open branch named \"%s\" already exists - use --force" |
|
2741
|
" to override", sCiInfo.zBranch); |
|
2742
|
} |
|
2743
|
|
|
2744
|
/* Escape special characters in tags and put all tags in sorted order */ |
|
2745
|
if( nTag ){ |
|
2746
|
int i; |
|
2747
|
for(i=0; i<nTag; i++) sCiInfo.azTag[i] = mprintf("%F", sCiInfo.azTag[i]); |
|
2748
|
qsort((void*)sCiInfo.azTag, nTag, sizeof(sCiInfo.azTag[0]), tagCmp); |
|
2749
|
} |
|
2750
|
|
|
2751
|
hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0); |
|
2752
|
if( hasChanges==0 && onlyIfChanges ){ |
|
2753
|
/* "fossil commit --if-changes" is a no-op if there are no changes. */ |
|
2754
|
return; |
|
2755
|
} |
|
2756
|
|
|
2757
|
/* |
|
2758
|
** Autosync if autosync is enabled and this is not a private check-in. |
|
2759
|
*/ |
|
2760
|
if( !g.markPrivate ){ |
|
2761
|
int syncFlags = SYNC_PULL; |
|
2762
|
if( vid!=0 && !allowFork && !forceFlag ){ |
|
2763
|
syncFlags |= SYNC_CKIN_LOCK; |
|
2764
|
} |
|
2765
|
if( autosync_loop(syncFlags, 1, "commit") ){ |
|
2766
|
fossil_exit(1); |
|
2767
|
} |
|
2768
|
} |
|
2769
|
|
|
2770
|
/* So that older versions of Fossil (that do not understand delta- |
|
2771
|
** manifest) can continue to use this repository, and because |
|
2772
|
** delta manifests are usually a bad idea unless the repository |
|
2773
|
** has a really large number of files, do not create a new |
|
2774
|
** delta-manifest unless this repository already contains one or more |
|
2775
|
** delta-manifests, or unless the delta-manifest is explicitly requested |
|
2776
|
** by the --delta option. |
|
2777
|
** |
|
2778
|
** The forbid-delta-manifests setting prevents new delta manifests, |
|
2779
|
** even if the --delta option is used. |
|
2780
|
** |
|
2781
|
** If the remote repository sent an avoid-delta-manifests pragma on |
|
2782
|
** the autosync above, then also forbid delta manifests, even if the |
|
2783
|
** --delta option is specified. The remote repo will send the |
|
2784
|
** avoid-delta-manifests pragma if its "forbid-delta-manifests" |
|
2785
|
** setting is enabled. |
|
2786
|
*/ |
|
2787
|
if( !(forceDelta || db_get_boolean("seen-delta-manifest",0)) |
|
2788
|
|| db_get_boolean("forbid-delta-manifests",0) |
|
2789
|
|| g.bAvoidDeltaManifests |
|
2790
|
){ |
|
2791
|
forceBaseline = 1; |
|
2792
|
} |
|
2793
|
|
|
2794
|
/* Require confirmation to continue with the check-in if there is |
|
2795
|
** clock skew. This helps to prevent timewarps. |
|
2796
|
*/ |
|
2797
|
if( g.clockSkewSeen ){ |
|
2798
|
if( bIgnoreSkew!=0 ){ |
|
2799
|
cReply = 'y'; |
|
2800
|
fossil_warning("Clock skew ignored due to --ignore-clock-skew."); |
|
2801
|
}else if( !noPrompt ){ |
|
2802
|
prompt_user("continue in spite of time skew (y/N)? ", &ans); |
|
2803
|
cReply = blob_str(&ans)[0]; |
|
2804
|
blob_reset(&ans); |
|
2805
|
}else{ |
|
2806
|
fossil_print("Abandoning commit due to time skew\n"); |
|
2807
|
cReply = 'N'; |
|
2808
|
} |
|
2809
|
if( cReply!='y' && cReply!='Y' ){ |
|
2810
|
fossil_exit(1); |
|
2811
|
} |
|
2812
|
} |
|
2813
|
|
|
2814
|
/* There are two ways this command may be executed. If there are |
|
2815
|
** no arguments following the word "commit", then all modified files |
|
2816
|
** in the checked-out directory are committed. If one or more arguments |
|
2817
|
** follows "commit", then only those files are committed. |
|
2818
|
** |
|
2819
|
** After the following function call has returned, the Global.aCommitFile[] |
|
2820
|
** array is allocated to contain the "id" field from the vfile table |
|
2821
|
** for each file to be committed. Or, if aCommitFile is NULL, all files |
|
2822
|
** should be committed. |
|
2823
|
*/ |
|
2824
|
if( select_commit_files() ){ |
|
2825
|
if( !noPrompt ){ |
|
2826
|
prompt_user("continue (y/N)? ", &ans); |
|
2827
|
cReply = blob_str(&ans)[0]; |
|
2828
|
blob_reset(&ans); |
|
2829
|
}else{ |
|
2830
|
cReply = 'N'; |
|
2831
|
} |
|
2832
|
if( cReply!='y' && cReply!='Y' ){ |
|
2833
|
fossil_exit(1); |
|
2834
|
} |
|
2835
|
} |
|
2836
|
isAMerge = db_exists("SELECT 1 FROM vmerge WHERE id=0 OR id<-2"); |
|
2837
|
if( g.aCommitFile && isAMerge ){ |
|
2838
|
fossil_fatal("cannot do a partial commit of a merge"); |
|
2839
|
} |
|
2840
|
|
|
2841
|
/* Doing "fossil mv fileA fileB; fossil add fileA; fossil commit fileA" |
|
2842
|
** will generate a manifest that has two fileA entries, which is illegal. |
|
2843
|
** When you think about it, the sequence above makes no sense. So detect |
|
2844
|
** it and disallow it. Ticket [0ff64b0a5fc8]. |
|
2845
|
*/ |
|
2846
|
if( g.aCommitFile ){ |
|
2847
|
db_prepare(&q, |
|
2848
|
"SELECT v1.pathname, v2.pathname" |
|
2849
|
" FROM vfile AS v1, vfile AS v2" |
|
2850
|
" WHERE is_selected(v1.id)" |
|
2851
|
" AND v2.origname IS NOT NULL" |
|
2852
|
" AND v2.origname=v1.pathname" |
|
2853
|
" AND NOT is_selected(v2.id)"); |
|
2854
|
if( db_step(&q)==SQLITE_ROW ){ |
|
2855
|
const char *zFrom = db_column_text(&q, 0); |
|
2856
|
const char *zTo = db_column_text(&q, 1); |
|
2857
|
fossil_fatal("cannot do a partial commit of '%s' without '%s' because " |
|
2858
|
"'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo); |
|
2859
|
} |
|
2860
|
db_finalize(&q); |
|
2861
|
} |
|
2862
|
|
|
2863
|
db_begin_transaction(); |
|
2864
|
db_record_repository_filename(0); |
|
2865
|
if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){ |
|
2866
|
fossil_fatal("nothing has changed; use --allow-empty to override"); |
|
2867
|
} |
|
2868
|
|
|
2869
|
/* If none of the files that were named on the command line have |
|
2870
|
** been modified, bail out now unless the --allow-empty or --force |
|
2871
|
** flags is used. |
|
2872
|
*/ |
|
2873
|
if( g.aCommitFile |
|
2874
|
&& !allowEmpty |
|
2875
|
&& !forceFlag |
|
2876
|
&& !db_exists( |
|
2877
|
"SELECT 1 FROM vfile " |
|
2878
|
" WHERE is_selected(id)" |
|
2879
|
" AND (chnged OR deleted OR rid=0 OR pathname!=origname)") |
|
2880
|
){ |
|
2881
|
fossil_fatal("none of the selected files have changed; use " |
|
2882
|
"--allow-empty to override."); |
|
2883
|
} |
|
2884
|
|
|
2885
|
/* This loop checks for potential forks and for check-ins against a |
|
2886
|
** closed branch. The checks are repeated once after interactive |
|
2887
|
** check-in comment editing. |
|
2888
|
*/ |
|
2889
|
do{ |
|
2890
|
/* |
|
2891
|
** Do not allow a commit that will cause a fork unless the --allow-fork |
|
2892
|
** or --force flags is used, or unless this is a private check-in. |
|
2893
|
** The initial commit MUST have tags "trunk" and "sym-trunk". |
|
2894
|
*/ |
|
2895
|
if( sCiInfo.zBranch==0 |
|
2896
|
&& allowFork==0 |
|
2897
|
&& forceFlag==0 |
|
2898
|
&& g.markPrivate==0 |
|
2899
|
&& (vid==0 || !is_a_leaf(vid) || g.ckinLockFail) |
|
2900
|
){ |
|
2901
|
if( g.ckinLockFail ){ |
|
2902
|
fossil_fatal("Might fork due to a check-in race with user \"%s\"\n" |
|
2903
|
"Try \"update\" first, or --branch, or " |
|
2904
|
"use --override-lock", |
|
2905
|
g.ckinLockFail); |
|
2906
|
}else{ |
|
2907
|
fossil_fatal("Would fork. \"update\" first or use --branch or " |
|
2908
|
"--allow-fork."); |
|
2909
|
} |
|
2910
|
} |
|
2911
|
|
|
2912
|
/* |
|
2913
|
** Do not allow a commit against a closed leaf unless the commit |
|
2914
|
** ends up on a different branch. |
|
2915
|
*/ |
|
2916
|
if( |
|
2917
|
/* parent check-in has the "closed" tag... */ |
|
2918
|
leaf_is_closed(vid) |
|
2919
|
/* ... and the new check-in has no --branch option or the --branch |
|
2920
|
** option does not actually change the branch */ |
|
2921
|
&& (sCiInfo.zBranch==0 |
|
2922
|
|| db_exists("SELECT 1 FROM tagxref" |
|
2923
|
" WHERE tagid=%d AND rid=%d AND tagtype>0" |
|
2924
|
" AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch)) |
|
2925
|
){ |
|
2926
|
fossil_fatal("cannot commit against a closed leaf"); |
|
2927
|
} |
|
2928
|
|
|
2929
|
/* Require confirmation to continue with the check-in if the branch |
|
2930
|
** has changed and the committer did not provide the same branch |
|
2931
|
*/ |
|
2932
|
zNewBranch = branch_of_rid(vid); |
|
2933
|
if( fossil_strcmp(zCurBranch, zNewBranch)!=0 |
|
2934
|
&& fossil_strcmp(sCiInfo.zBranch, zNewBranch)!=0 |
|
2935
|
&& forceFlag==0 |
|
2936
|
&& noPrompt==0 |
|
2937
|
){ |
|
2938
|
fossil_warning("parent check-in [%.10s] branch changed from '%s' to '%s'", |
|
2939
|
rid_to_uuid(vid), zCurBranch, zNewBranch); |
|
2940
|
prompt_user("continue (y/N)? ", &ans); |
|
2941
|
cReply = blob_str(&ans)[0]; |
|
2942
|
blob_reset(&ans); |
|
2943
|
if( cReply!='y' && cReply!='Y' ){ |
|
2944
|
fossil_fatal("Abandoning commit because branch has changed"); |
|
2945
|
} |
|
2946
|
fossil_free(zCurBranch); |
|
2947
|
zCurBranch = branch_of_rid(vid); |
|
2948
|
} |
|
2949
|
fossil_free(zNewBranch); |
|
2950
|
|
|
2951
|
/* Always exit the loop on the second pass */ |
|
2952
|
if( bRecheck ) break; |
|
2953
|
|
|
2954
|
|
|
2955
|
/* Figure out how much comment verification is requested */ |
|
2956
|
if( noVerifyCom ){ |
|
2957
|
ckComFlgs = 0; |
|
2958
|
}else{ |
|
2959
|
const char *zVerComs = db_get("verify-comments","on"); |
|
2960
|
if( is_false(zVerComs) ){ |
|
2961
|
ckComFlgs = 0; |
|
2962
|
}else if( strcmp(zVerComs,"preview")==0 ){ |
|
2963
|
ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP; |
|
2964
|
}else{ |
|
2965
|
ckComFlgs = COMCK_MARKUP; |
|
2966
|
} |
|
2967
|
} |
|
2968
|
|
|
2969
|
/* Get the check-in comment. This might involve prompting the |
|
2970
|
** user for the check-in comment, in which case we should resync |
|
2971
|
** to renew the check-in lock and repeat the checks for conflicts. |
|
2972
|
*/ |
|
2973
|
if( zComment ){ |
|
2974
|
blob_zero(&comment); |
|
2975
|
blob_append(&comment, zComment, -1); |
|
2976
|
ckComFlgs &= ~COMCK_PREVIEW; |
|
2977
|
if( verify_comment(&comment, ckComFlgs) ){ |
|
2978
|
fossil_fatal("Commit aborted; " |
|
2979
|
"use --no-verify-comment to override"); |
|
2980
|
} |
|
2981
|
}else if( zComFile ){ |
|
2982
|
blob_zero(&comment); |
|
2983
|
blob_read_from_file(&comment, zComFile, ExtFILE); |
|
2984
|
blob_to_utf8_no_bom(&comment, 1); |
|
2985
|
ckComFlgs &= ~COMCK_PREVIEW; |
|
2986
|
if( verify_comment(&comment, ckComFlgs) ){ |
|
2987
|
fossil_fatal("Commit aborted; " |
|
2988
|
"use --no-verify-comment to override"); |
|
2989
|
} |
|
2990
|
}else if( !noPrompt ){ |
|
2991
|
while( 1/*exit-by-break*/ ){ |
|
2992
|
int rc; |
|
2993
|
char *zInit; |
|
2994
|
zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'"); |
|
2995
|
prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag); |
|
2996
|
db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); |
|
2997
|
if( (rc = verify_comment(&comment, ckComFlgs))!=0 ){ |
|
2998
|
if( rc==COMCK_PREVIEW ){ |
|
2999
|
prompt_user("Continue, abort, or edit? (C/a/e)? ", &ans); |
|
3000
|
}else{ |
|
3001
|
prompt_user("Edit, abort, or continue (E/a/c)? ", &ans); |
|
3002
|
} |
|
3003
|
cReply = blob_str(&ans)[0]; |
|
3004
|
cReply = fossil_tolower(cReply); |
|
3005
|
blob_reset(&ans); |
|
3006
|
if( cReply=='a' ){ |
|
3007
|
fossil_fatal("Commit aborted."); |
|
3008
|
} |
|
3009
|
if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){ |
|
3010
|
fossil_free(zInit); |
|
3011
|
continue; |
|
3012
|
} |
|
3013
|
} |
|
3014
|
if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){ |
|
3015
|
prompt_user("unchanged check-in comment. continue (y/N)? ", &ans); |
|
3016
|
cReply = blob_str(&ans)[0]; |
|
3017
|
blob_reset(&ans); |
|
3018
|
if( cReply!='y' && cReply!='Y' ){ |
|
3019
|
fossil_fatal("Commit aborted."); |
|
3020
|
} |
|
3021
|
} |
|
3022
|
fossil_free(zInit); |
|
3023
|
break; |
|
3024
|
} |
|
3025
|
|
|
3026
|
db_end_transaction(0); |
|
3027
|
db_begin_transaction(); |
|
3028
|
if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){ |
|
3029
|
/* Do another auto-pull, renewing the check-in lock. Then set |
|
3030
|
** bRecheck so that we loop back above to verify that the check-in |
|
3031
|
** is still not against a closed branch and still won't fork. */ |
|
3032
|
int syncFlags = SYNC_PULL|SYNC_CKIN_LOCK; |
|
3033
|
if( autosync_loop(syncFlags, 1, "commit") ){ |
|
3034
|
fossil_fatal("Auto-pull failed. Commit aborted."); |
|
3035
|
} |
|
3036
|
bRecheck = 1; |
|
3037
|
} |
|
3038
|
}else{ |
|
3039
|
blob_zero(&comment); |
|
3040
|
} |
|
3041
|
}while( bRecheck ); |
|
3042
|
|
|
3043
|
if( blob_size(&comment)==0 ){ |
|
3044
|
if( !dryRunFlag ){ |
|
3045
|
if( !noPrompt ){ |
|
3046
|
prompt_user("empty check-in comment. continue (y/N)? ", &ans); |
|
3047
|
cReply = blob_str(&ans)[0]; |
|
3048
|
blob_reset(&ans); |
|
3049
|
}else{ |
|
3050
|
cReply = 'N'; |
|
3051
|
} |
|
3052
|
if( cReply!='y' && cReply!='Y' ){ |
|
3053
|
fossil_fatal("Abandoning commit due to empty check-in comment\n"); |
|
3054
|
} |
|
3055
|
} |
|
3056
|
} |
|
3057
|
|
|
3058
|
if( !noVerify && hook_exists("before-commit") ){ |
|
3059
|
/* Run before-commit hooks */ |
|
3060
|
char *zAuxFile; |
|
3061
|
zAuxFile = prepare_commit_description_file( |
|
3062
|
&sCiInfo, vid, &comment, dryRunFlag); |
|
3063
|
if( zAuxFile ){ |
|
3064
|
int rc = hook_run("before-commit",zAuxFile,bTrace); |
|
3065
|
file_delete(zAuxFile); |
|
3066
|
fossil_free(zAuxFile); |
|
3067
|
if( rc ){ |
|
3068
|
fossil_fatal("Before-commit hook failed\n"); |
|
3069
|
} |
|
3070
|
} |
|
3071
|
} |
|
3072
|
|
|
3073
|
/* |
|
3074
|
** Step 1: Compute an aggregate MD5 checksum over the disk image |
|
3075
|
** of every file in vid. The file names are part of the checksum. |
|
3076
|
** The resulting checksum is the same as is expected on the R-card |
|
3077
|
** of a manifest. |
|
3078
|
*/ |
|
3079
|
if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1); |
|
3080
|
|
|
3081
|
/* Step 2: Insert records for all modified files into the blob |
|
3082
|
** table. If there were arguments passed to this command, only |
|
3083
|
** the identified files are inserted (if they have been modified). |
|
3084
|
*/ |
|
3085
|
db_prepare(&q, |
|
3086
|
"SELECT id, %Q || pathname, mrid, %s, %s, %s FROM vfile " |
|
3087
|
"WHERE chnged<>0 AND NOT deleted AND is_selected(id)", |
|
3088
|
g.zLocalRoot, |
|
3089
|
glob_expr("pathname", db_get("crlf-glob",db_get("crnl-glob",""))), |
|
3090
|
glob_expr("pathname", db_get("binary-glob","")), |
|
3091
|
glob_expr("pathname", db_get("encoding-glob","")) |
|
3092
|
); |
|
3093
|
while( db_step(&q)==SQLITE_ROW ){ |
|
3094
|
int id, rid; |
|
3095
|
const char *zFullname; |
|
3096
|
Blob content; |
|
3097
|
int crlfOk, binOk, encodingOk, sizeOk; |
|
3098
|
|
|
3099
|
id = db_column_int(&q, 0); |
|
3100
|
zFullname = db_column_text(&q, 1); |
|
3101
|
rid = db_column_int(&q, 2); |
|
3102
|
crlfOk = db_column_int(&q, 3); |
|
3103
|
binOk = db_column_int(&q, 4); |
|
3104
|
encodingOk = db_column_int(&q, 5); |
|
3105
|
sizeOk = mxSize<=0 || file_size(zFullname, ExtFILE)<=mxSize; |
|
3106
|
|
|
3107
|
blob_zero(&content); |
|
3108
|
blob_read_from_file(&content, zFullname, RepoFILE); |
|
3109
|
/* Do not emit any warnings when they are disabled. */ |
|
3110
|
if( !noWarningFlag ){ |
|
3111
|
abortCommit |= commit_warning(&content, crlfOk, binOk, |
|
3112
|
encodingOk, sizeOk, noPrompt, |
|
3113
|
zFullname, 0); |
|
3114
|
} |
|
3115
|
if( contains_merge_marker(&content) ){ |
|
3116
|
Blob fname; /* Relative pathname of the file */ |
|
3117
|
|
|
3118
|
nConflict++; |
|
3119
|
file_relative_name(zFullname, &fname, 0); |
|
3120
|
fossil_print("possible unresolved merge conflict in %s\n", |
|
3121
|
blob_str(&fname)); |
|
3122
|
blob_reset(&fname); |
|
3123
|
} |
|
3124
|
nrid = content_put(&content); |
|
3125
|
blob_reset(&content); |
|
3126
|
if( nrid!=rid ){ |
|
3127
|
if( rid>0 ){ |
|
3128
|
content_deltify(rid, &nrid, 1, 0); |
|
3129
|
} |
|
3130
|
db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d, mhash=NULL WHERE id=%d", |
|
3131
|
nrid,nrid,id); |
|
3132
|
db_add_unsent(nrid); |
|
3133
|
} |
|
3134
|
} |
|
3135
|
db_finalize(&q); |
|
3136
|
if( nConflict && !allowConflict ){ |
|
3137
|
fossil_fatal("abort due to unresolved merge conflicts; " |
|
3138
|
"use --allow-conflict to override"); |
|
3139
|
}else if( abortCommit ){ |
|
3140
|
fossil_fatal("one or more files were converted on your request; " |
|
3141
|
"please re-test before committing"); |
|
3142
|
} |
|
3143
|
|
|
3144
|
/* Create the new manifest */ |
|
3145
|
sCiInfo.pComment = &comment; |
|
3146
|
sCiInfo.pCksum = useCksum ? &cksum1 : 0; |
|
3147
|
sCiInfo.verifyDate = !allowOlder && !forceFlag; |
|
3148
|
if( forceDelta ){ |
|
3149
|
blob_zero(&manifest); |
|
3150
|
}else{ |
|
3151
|
create_manifest(&manifest, 0, 0, vid, &sCiInfo, &szB); |
|
3152
|
} |
|
3153
|
|
|
3154
|
/* See if a delta-manifest would be more appropriate */ |
|
3155
|
if( !forceBaseline ){ |
|
3156
|
const char *zBaselineUuid; |
|
3157
|
Manifest *pParent; |
|
3158
|
Manifest *pBaseline; |
|
3159
|
pParent = manifest_get(vid, CFTYPE_MANIFEST, 0); |
|
3160
|
if( pParent && pParent->zBaseline ){ |
|
3161
|
zBaselineUuid = pParent->zBaseline; |
|
3162
|
pBaseline = manifest_get_by_name(zBaselineUuid, 0); |
|
3163
|
}else{ |
|
3164
|
zBaselineUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); |
|
3165
|
pBaseline = pParent; |
|
3166
|
} |
|
3167
|
if( pBaseline ){ |
|
3168
|
Blob delta; |
|
3169
|
create_manifest(&delta, zBaselineUuid, pBaseline, vid, &sCiInfo, &szD); |
|
3170
|
/* |
|
3171
|
** At this point, two manifests have been constructed, either of |
|
3172
|
** which would work for this check-in. The first manifest (held |
|
3173
|
** in the "manifest" variable) is a baseline manifest and the second |
|
3174
|
** (held in variable named "delta") is a delta manifest. The |
|
3175
|
** question now is: which manifest should we use? |
|
3176
|
** |
|
3177
|
** Let B be the number of F-cards in the baseline manifest and |
|
3178
|
** let D be the number of F-cards in the delta manifest, plus one for |
|
3179
|
** the B-card. (B is held in the szB variable and D is held in the |
|
3180
|
** szD variable.) Assume that all delta manifests adds X new F-cards. |
|
3181
|
** Then to minimize the total number of F- and B-cards in the repository, |
|
3182
|
** we should use the delta manifest if and only if: |
|
3183
|
** |
|
3184
|
** D*D < B*X - X*X |
|
3185
|
** |
|
3186
|
** X is an unknown here, but for most repositories, we will not be |
|
3187
|
** far wrong if we assume X=3. |
|
3188
|
*/ |
|
3189
|
if( forceDelta || (szD*szD)<(szB*3-9) ){ |
|
3190
|
blob_reset(&manifest); |
|
3191
|
manifest = delta; |
|
3192
|
}else{ |
|
3193
|
blob_reset(&delta); |
|
3194
|
} |
|
3195
|
}else if( forceDelta ){ |
|
3196
|
fossil_fatal("unable to find a baseline-manifest for the delta"); |
|
3197
|
} |
|
3198
|
} |
|
3199
|
if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ |
|
3200
|
if( !noPrompt ){ |
|
3201
|
prompt_user("unable to sign manifest. continue (y/N)? ", &ans); |
|
3202
|
cReply = blob_str(&ans)[0]; |
|
3203
|
blob_reset(&ans); |
|
3204
|
}else{ |
|
3205
|
cReply = 'N'; |
|
3206
|
} |
|
3207
|
if( cReply!='y' && cReply!='Y' ){ |
|
3208
|
fossil_fatal("Abandoning commit due to manifest signing failure\n"); |
|
3209
|
} |
|
3210
|
} |
|
3211
|
|
|
3212
|
/* If the -n|--dry-run option is specified, output the manifest file |
|
3213
|
** and rollback the transaction. |
|
3214
|
*/ |
|
3215
|
if( dryRunFlag ){ |
|
3216
|
blob_write_to_file(&manifest, ""); |
|
3217
|
} |
|
3218
|
|
|
3219
|
nvid = content_put(&manifest); |
|
3220
|
if( nvid==0 ){ |
|
3221
|
fossil_fatal("trouble committing manifest: %s", g.zErrMsg); |
|
3222
|
} |
|
3223
|
db_add_unsent(nvid); |
|
3224
|
if( manifest_crosslink(nvid, &manifest, |
|
3225
|
dryRunFlag ? MC_NONE : MC_PERMIT_HOOKS)==0 ){ |
|
3226
|
fossil_fatal("%s", g.zErrMsg); |
|
3227
|
} |
|
3228
|
assert( blob_is_reset(&manifest) ); |
|
3229
|
content_deltify(vid, &nvid, 1, 0); |
|
3230
|
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); |
|
3231
|
|
|
3232
|
db_prepare(&q, "SELECT mhash,merge FROM vmerge WHERE id=-4"); |
|
3233
|
while( db_step(&q)==SQLITE_ROW ){ |
|
3234
|
const char *zIntegrateUuid = db_column_text(&q, 0); |
|
3235
|
if( is_a_leaf(db_column_int(&q, 1)) ){ |
|
3236
|
fossil_print("Closed: %s\n", zIntegrateUuid); |
|
3237
|
}else{ |
|
3238
|
fossil_print("Not_Closed: %s (not a leaf any more)\n", zIntegrateUuid); |
|
3239
|
} |
|
3240
|
} |
|
3241
|
db_finalize(&q); |
|
3242
|
|
|
3243
|
fossil_print("New_Version: %s\n", zUuid); |
|
3244
|
|
|
3245
|
/* Update the vfile and vmerge tables */ |
|
3246
|
db_multi_exec( |
|
3247
|
"DELETE FROM vfile WHERE (vid!=%d OR deleted) AND is_selected(id);" |
|
3248
|
"DELETE FROM vmerge;" |
|
3249
|
"UPDATE vfile SET vid=%d;" |
|
3250
|
"UPDATE vfile SET rid=mrid, mhash=NULL, chnged=0, deleted=0, origname=NULL" |
|
3251
|
" WHERE is_selected(id);" |
|
3252
|
, vid, nvid |
|
3253
|
); |
|
3254
|
db_set_checkout(nvid, !dryRunFlag); |
|
3255
|
|
|
3256
|
/* Update the isexe and islink columns of the vfile table */ |
|
3257
|
db_prepare(&q, |
|
3258
|
"UPDATE vfile SET isexe=:exec, islink=:link" |
|
3259
|
" WHERE vid=:vid AND pathname=:path AND (isexe!=:exec OR islink!=:link)" |
|
3260
|
); |
|
3261
|
db_bind_int(&q, ":vid", nvid); |
|
3262
|
pManifest = manifest_get(nvid, CFTYPE_MANIFEST, 0); |
|
3263
|
manifest_file_rewind(pManifest); |
|
3264
|
while( (pFile = manifest_file_next(pManifest, 0)) ){ |
|
3265
|
db_bind_int(&q, ":exec", pFile->zPerm && strstr(pFile->zPerm, "x")); |
|
3266
|
db_bind_int(&q, ":link", pFile->zPerm && strstr(pFile->zPerm, "l")); |
|
3267
|
db_bind_text(&q, ":path", pFile->zName); |
|
3268
|
db_step(&q); |
|
3269
|
db_reset(&q); |
|
3270
|
} |
|
3271
|
db_finalize(&q); |
|
3272
|
manifest_destroy(pManifest); |
|
3273
|
|
|
3274
|
if( useCksum ){ |
|
3275
|
/* Verify that the repository checksum matches the expected checksum |
|
3276
|
** calculated before the check-in started (and stored as the R record |
|
3277
|
** of the manifest file). |
|
3278
|
*/ |
|
3279
|
vfile_aggregate_checksum_repository(nvid, &cksum2); |
|
3280
|
if( blob_compare(&cksum1, &cksum2) ){ |
|
3281
|
vfile_compare_repository_to_disk(nvid); |
|
3282
|
fossil_fatal("working check-out does not match what would have ended " |
|
3283
|
"up in the repository: %b versus %b", |
|
3284
|
&cksum1, &cksum2); |
|
3285
|
} |
|
3286
|
|
|
3287
|
/* Verify that the manifest checksum matches the expected checksum */ |
|
3288
|
vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); |
|
3289
|
if( blob_compare(&cksum1, &cksum1b) ){ |
|
3290
|
fossil_fatal("manifest checksum self-test failed: " |
|
3291
|
"%b versus %b", &cksum1, &cksum1b); |
|
3292
|
} |
|
3293
|
if( blob_compare(&cksum1, &cksum2) ){ |
|
3294
|
fossil_fatal( |
|
3295
|
"working check-out does not match manifest after commit: " |
|
3296
|
"%b versus %b", &cksum1, &cksum2); |
|
3297
|
} |
|
3298
|
|
|
3299
|
/* Verify that the commit did not modify any disk images. */ |
|
3300
|
vfile_aggregate_checksum_disk(nvid, &cksum2); |
|
3301
|
if( blob_compare(&cksum1, &cksum2) ){ |
|
3302
|
fossil_fatal("working check-out before and after commit does not match"); |
|
3303
|
} |
|
3304
|
} |
|
3305
|
|
|
3306
|
/* Clear the undo/redo stack */ |
|
3307
|
undo_reset(); |
|
3308
|
|
|
3309
|
/* Commit */ |
|
3310
|
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); |
|
3311
|
db_multi_exec("PRAGMA repository.application_id=252006673;"); |
|
3312
|
db_multi_exec("PRAGMA localdb.application_id=252006674;"); |
|
3313
|
if( dryRunFlag ){ |
|
3314
|
db_end_transaction(1); |
|
3315
|
return; |
|
3316
|
} |
|
3317
|
db_end_transaction(0); |
|
3318
|
|
|
3319
|
if( !g.markPrivate ){ |
|
3320
|
int syncFlags = SYNC_PUSH | SYNC_PULL | SYNC_IFABLE; |
|
3321
|
autosync_loop(syncFlags, 0, "commit"); |
|
3322
|
} |
|
3323
|
if( count_nonbranch_children(vid)>1 ){ |
|
3324
|
fossil_print("**** warning: a fork has occurred *****\n"); |
|
3325
|
}else{ |
|
3326
|
leaf_ambiguity_warning(nvid,nvid); |
|
3327
|
} |
|
3328
|
} |
|
3329
|
|