]>
Commit | Line | Data |
---|---|---|
21746aa3 DB |
1 | /* |
2 | * Parse and rearrange a svnadmin dump. | |
3 | * Create the dump with: | |
4 | * svnadmin dump --incremental -r<startrev>:<endrev> <repository> >outfile | |
5 | * | |
6 | * Licensed under a two-clause BSD-style license. | |
7 | * See LICENSE for details. | |
8 | */ | |
9 | ||
10 | #include "cache.h" | |
11 | #include "repo_tree.h" | |
12 | #include "fast_export.h" | |
13 | #include "line_buffer.h" | |
21746aa3 | 14 | #include "string_pool.h" |
dce33c9c | 15 | #include "strbuf.h" |
c5147722 | 16 | #include "svndump.h" |
21746aa3 | 17 | |
044ad290 DB |
18 | /* |
19 | * Compare start of string to literal of equal length; | |
20 | * must be guarded by length test. | |
21 | */ | |
22 | #define constcmp(s, ref) memcmp(s, ref, sizeof(ref) - 1) | |
23 | ||
21746aa3 DB |
24 | #define NODEACT_REPLACE 4 |
25 | #define NODEACT_DELETE 3 | |
26 | #define NODEACT_ADD 2 | |
27 | #define NODEACT_CHANGE 1 | |
28 | #define NODEACT_UNKNOWN 0 | |
29 | ||
30 | #define DUMP_CTX 0 | |
31 | #define REV_CTX 1 | |
32 | #define NODE_CTX 2 | |
33 | ||
34 | #define LENGTH_UNKNOWN (~0) | |
35 | #define DATE_RFC2822_LEN 31 | |
36 | ||
e5e45ca1 JN |
37 | static struct line_buffer input = LINE_BUFFER_INIT; |
38 | ||
21746aa3 | 39 | static struct { |
da3e2174 | 40 | uint32_t action, propLength, textLength, srcRev, type; |
21746aa3 | 41 | uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH]; |
1f05d07c | 42 | uint32_t text_delta, prop_delta; |
21746aa3 DB |
43 | } node_ctx; |
44 | ||
45 | static struct { | |
7c5817d3 | 46 | uint32_t revision; |
21746aa3 | 47 | unsigned long timestamp; |
7c5817d3 | 48 | struct strbuf log, author; |
21746aa3 DB |
49 | } rev_ctx; |
50 | ||
51 | static struct { | |
7c5817d3 DB |
52 | uint32_t version; |
53 | struct strbuf uuid, url; | |
21746aa3 DB |
54 | } dump_ctx; |
55 | ||
21746aa3 DB |
56 | static void reset_node_ctx(char *fname) |
57 | { | |
58 | node_ctx.type = 0; | |
59 | node_ctx.action = NODEACT_UNKNOWN; | |
60 | node_ctx.propLength = LENGTH_UNKNOWN; | |
61 | node_ctx.textLength = LENGTH_UNKNOWN; | |
62 | node_ctx.src[0] = ~0; | |
63 | node_ctx.srcRev = 0; | |
21746aa3 | 64 | pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname); |
1f05d07c DB |
65 | node_ctx.text_delta = 0; |
66 | node_ctx.prop_delta = 0; | |
21746aa3 DB |
67 | } |
68 | ||
69 | static void reset_rev_ctx(uint32_t revision) | |
70 | { | |
71 | rev_ctx.revision = revision; | |
72 | rev_ctx.timestamp = 0; | |
dce33c9c | 73 | strbuf_reset(&rev_ctx.log); |
7c5817d3 | 74 | strbuf_reset(&rev_ctx.author); |
21746aa3 DB |
75 | } |
76 | ||
7c5817d3 | 77 | static void reset_dump_ctx(const char *url) |
21746aa3 | 78 | { |
7c5817d3 DB |
79 | strbuf_reset(&dump_ctx.url); |
80 | if (url) | |
81 | strbuf_addstr(&dump_ctx.url, url); | |
b3e5bce1 | 82 | dump_ctx.version = 1; |
7c5817d3 | 83 | strbuf_reset(&dump_ctx.uuid); |
21746aa3 DB |
84 | } |
85 | ||
044ad290 | 86 | static void handle_property(const struct strbuf *key_buf, |
4c3169b0 | 87 | struct strbuf *val, |
6b01b676 | 88 | uint32_t *type_set) |
2a48afe1 | 89 | { |
044ad290 DB |
90 | const char *key = key_buf->buf; |
91 | size_t keylen = key_buf->len; | |
92 | ||
93 | switch (keylen + 1) { | |
94 | case sizeof("svn:log"): | |
95 | if (constcmp(key, "svn:log")) | |
96 | break; | |
6b01b676 DB |
97 | if (!val) |
98 | die("invalid dump: unsets svn:log"); | |
4c3169b0 | 99 | strbuf_swap(&rev_ctx.log, val); |
044ad290 DB |
100 | break; |
101 | case sizeof("svn:author"): | |
102 | if (constcmp(key, "svn:author")) | |
103 | break; | |
4c3169b0 JN |
104 | if (!val) |
105 | strbuf_reset(&rev_ctx.author); | |
106 | else | |
107 | strbuf_swap(&rev_ctx.author, val); | |
044ad290 DB |
108 | break; |
109 | case sizeof("svn:date"): | |
110 | if (constcmp(key, "svn:date")) | |
111 | break; | |
6b01b676 DB |
112 | if (!val) |
113 | die("invalid dump: unsets svn:date"); | |
4c3169b0 JN |
114 | if (parse_date_basic(val->buf, &rev_ctx.timestamp, NULL)) |
115 | warning("invalid timestamp: %s", val->buf); | |
044ad290 DB |
116 | break; |
117 | case sizeof("svn:executable"): | |
118 | case sizeof("svn:special"): | |
119 | if (keylen == strlen("svn:executable") && | |
120 | constcmp(key, "svn:executable")) | |
121 | break; | |
122 | if (keylen == strlen("svn:special") && | |
123 | constcmp(key, "svn:special")) | |
124 | break; | |
6b01b676 DB |
125 | if (*type_set) { |
126 | if (!val) | |
127 | return; | |
128 | die("invalid dump: sets type twice"); | |
129 | } | |
130 | if (!val) { | |
131 | node_ctx.type = REPO_MODE_BLB; | |
132 | return; | |
133 | } | |
134 | *type_set = 1; | |
044ad290 | 135 | node_ctx.type = keylen == strlen("svn:executable") ? |
6b01b676 DB |
136 | REPO_MODE_EXE : |
137 | REPO_MODE_LNK; | |
2a48afe1 | 138 | } |
21746aa3 DB |
139 | } |
140 | ||
c9d1c8ba JN |
141 | static void die_short_read(void) |
142 | { | |
143 | if (buffer_ferror(&input)) | |
144 | die_errno("error reading dump file"); | |
145 | die("invalid dump: unexpected end of file"); | |
146 | } | |
147 | ||
21746aa3 DB |
148 | static void read_props(void) |
149 | { | |
044ad290 | 150 | static struct strbuf key = STRBUF_INIT; |
e7d04ee1 | 151 | static struct strbuf val = STRBUF_INIT; |
6263c06d | 152 | const char *t; |
6b01b676 DB |
153 | /* |
154 | * NEEDSWORK: to support simple mode changes like | |
155 | * K 11 | |
156 | * svn:special | |
157 | * V 1 | |
158 | * * | |
159 | * D 14 | |
160 | * svn:executable | |
161 | * we keep track of whether a mode has been set and reset to | |
162 | * plain file only if not. We should be keeping track of the | |
163 | * symlink and executable bits separately instead. | |
164 | */ | |
165 | uint32_t type_set = 0; | |
e5e45ca1 | 166 | while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) { |
6263c06d | 167 | uint32_t len; |
6263c06d | 168 | const char type = t[0]; |
c9d1c8ba | 169 | int ch; |
6263c06d JN |
170 | |
171 | if (!type || t[1] != ' ') | |
172 | die("invalid property line: %s\n", t); | |
173 | len = atoi(&t[2]); | |
e7d04ee1 JN |
174 | strbuf_reset(&val); |
175 | buffer_read_binary(&input, &val, len); | |
176 | if (val.len < len) | |
c9d1c8ba JN |
177 | die_short_read(); |
178 | ||
179 | /* Discard trailing newline. */ | |
180 | ch = buffer_read_char(&input); | |
181 | if (ch == EOF) | |
182 | die_short_read(); | |
183 | if (ch != '\n') | |
e7d04ee1 | 184 | die("invalid dump: expected newline after %s", val.buf); |
6263c06d JN |
185 | |
186 | switch (type) { | |
187 | case 'K': | |
e7d04ee1 JN |
188 | strbuf_swap(&key, &val); |
189 | continue; | |
6b01b676 | 190 | case 'D': |
4c3169b0 | 191 | handle_property(&val, NULL, &type_set); |
e7d04ee1 | 192 | continue; |
6263c06d | 193 | case 'V': |
4c3169b0 | 194 | handle_property(&key, &val, &type_set); |
044ad290 | 195 | strbuf_reset(&key); |
6263c06d JN |
196 | continue; |
197 | default: | |
198 | die("invalid property line: %s\n", t); | |
21746aa3 DB |
199 | } |
200 | } | |
201 | } | |
202 | ||
203 | static void handle_node(void) | |
204 | { | |
1c7bb316 JN |
205 | uint32_t mark = 0; |
206 | const uint32_t type = node_ctx.type; | |
d6e81a03 | 207 | const int have_props = node_ctx.propLength != LENGTH_UNKNOWN; |
5a38b186 | 208 | const int have_text = node_ctx.textLength != LENGTH_UNKNOWN; |
21746aa3 | 209 | |
6b01b676 DB |
210 | if (node_ctx.text_delta) |
211 | die("text deltas not supported"); | |
5a38b186 | 212 | if (have_text) |
462e1f51 | 213 | mark = next_blob_mark(); |
21746aa3 | 214 | if (node_ctx.action == NODEACT_DELETE) { |
5a38b186 | 215 | if (have_text || have_props || node_ctx.srcRev) |
5af8fae2 JN |
216 | die("invalid dump: deletion node has " |
217 | "copyfrom info, text, or properties"); | |
9e113988 MW |
218 | repo_delete(node_ctx.dst); |
219 | return; | |
5af8fae2 | 220 | } |
6ee4a9be | 221 | if (node_ctx.action == NODEACT_REPLACE) { |
21746aa3 | 222 | repo_delete(node_ctx.dst); |
6ee4a9be JN |
223 | node_ctx.action = NODEACT_ADD; |
224 | } | |
1c7bb316 JN |
225 | if (node_ctx.srcRev) { |
226 | repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst); | |
414e569e JN |
227 | if (node_ctx.action == NODEACT_ADD) |
228 | node_ctx.action = NODEACT_CHANGE; | |
1c7bb316 | 229 | } |
5a38b186 | 230 | if (have_text && type == REPO_MODE_DIR) |
462e1f51 | 231 | die("invalid dump: directories cannot have text attached"); |
5a38b186 JN |
232 | |
233 | /* | |
234 | * Decide on the new content (mark) and mode (node_ctx.type). | |
235 | */ | |
9e8c5321 JN |
236 | if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) { |
237 | if (type != REPO_MODE_DIR) | |
238 | die("invalid dump: root of tree is not a regular file"); | |
239 | } else if (node_ctx.action == NODEACT_CHANGE) { | |
5a38b186 JN |
240 | uint32_t mode; |
241 | if (!have_text) | |
242 | mark = repo_read_path(node_ctx.dst); | |
e75316de | 243 | mode = repo_read_mode(node_ctx.dst); |
c7dbf35e JN |
244 | if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR) |
245 | die("invalid dump: cannot modify a directory into a file"); | |
246 | if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR) | |
247 | die("invalid dump: cannot modify a file into a directory"); | |
248 | node_ctx.type = mode; | |
21746aa3 | 249 | } else if (node_ctx.action == NODEACT_ADD) { |
5a38b186 | 250 | if (!have_text && type != REPO_MODE_DIR) |
c7dbf35e | 251 | die("invalid dump: adds node without text"); |
c7dbf35e | 252 | } else { |
414e569e | 253 | die("invalid dump: Node-path block lacks Node-action"); |
21746aa3 | 254 | } |
5a38b186 JN |
255 | |
256 | /* | |
257 | * Adjust mode to reflect properties. | |
258 | */ | |
1c7bb316 | 259 | if (have_props) { |
6b01b676 DB |
260 | if (!node_ctx.prop_delta) |
261 | node_ctx.type = type; | |
1c7bb316 JN |
262 | if (node_ctx.propLength) |
263 | read_props(); | |
21746aa3 | 264 | } |
5a38b186 JN |
265 | |
266 | /* | |
267 | * Save the result. | |
268 | */ | |
269 | repo_add(node_ctx.dst, node_ctx.type, mark); | |
270 | if (have_text) | |
a62bbf8f JN |
271 | fast_export_blob(node_ctx.type, mark, |
272 | node_ctx.textLength, &input); | |
21746aa3 DB |
273 | } |
274 | ||
275 | static void handle_revision(void) | |
276 | { | |
277 | if (rev_ctx.revision) | |
7c5817d3 | 278 | repo_commit(rev_ctx.revision, rev_ctx.author.buf, |
195b7ca6 | 279 | &rev_ctx.log, dump_ctx.uuid.buf, dump_ctx.url.buf, |
7c5817d3 | 280 | rev_ctx.timestamp); |
21746aa3 DB |
281 | } |
282 | ||
283 | void svndump_read(const char *url) | |
284 | { | |
285 | char *val; | |
286 | char *t; | |
287 | uint32_t active_ctx = DUMP_CTX; | |
288 | uint32_t len; | |
21746aa3 | 289 | |
7c5817d3 | 290 | reset_dump_ctx(url); |
e5e45ca1 | 291 | while ((t = buffer_read_line(&input))) { |
f1602054 | 292 | val = strchr(t, ':'); |
21746aa3 DB |
293 | if (!val) |
294 | continue; | |
f1602054 DB |
295 | val++; |
296 | if (*val != ' ') | |
297 | continue; | |
298 | val++; | |
21746aa3 | 299 | |
90c0a3cf DB |
300 | /* strlen(key) + 1 */ |
301 | switch (val - t - 1) { | |
302 | case sizeof("SVN-fs-dump-format-version"): | |
303 | if (constcmp(t, "SVN-fs-dump-format-version")) | |
304 | continue; | |
b3e5bce1 | 305 | dump_ctx.version = atoi(val); |
1f05d07c | 306 | if (dump_ctx.version > 3) |
a62bbf8f | 307 | die("expected svn dump format version <= 3, found %"PRIu32, |
b3e5bce1 | 308 | dump_ctx.version); |
90c0a3cf DB |
309 | break; |
310 | case sizeof("UUID"): | |
311 | if (constcmp(t, "UUID")) | |
312 | continue; | |
7c5817d3 DB |
313 | strbuf_reset(&dump_ctx.uuid); |
314 | strbuf_addstr(&dump_ctx.uuid, val); | |
90c0a3cf DB |
315 | break; |
316 | case sizeof("Revision-number"): | |
317 | if (constcmp(t, "Revision-number")) | |
318 | continue; | |
21746aa3 DB |
319 | if (active_ctx == NODE_CTX) |
320 | handle_node(); | |
321 | if (active_ctx != DUMP_CTX) | |
322 | handle_revision(); | |
323 | active_ctx = REV_CTX; | |
324 | reset_rev_ctx(atoi(val)); | |
90c0a3cf DB |
325 | break; |
326 | case sizeof("Node-path"): | |
327 | if (prefixcmp(t, "Node-")) | |
328 | continue; | |
329 | if (!constcmp(t + strlen("Node-"), "path")) { | |
330 | if (active_ctx == NODE_CTX) | |
331 | handle_node(); | |
332 | active_ctx = NODE_CTX; | |
333 | reset_node_ctx(val); | |
334 | break; | |
335 | } | |
336 | if (constcmp(t + strlen("Node-"), "kind")) | |
337 | continue; | |
21746aa3 DB |
338 | if (!strcmp(val, "dir")) |
339 | node_ctx.type = REPO_MODE_DIR; | |
340 | else if (!strcmp(val, "file")) | |
341 | node_ctx.type = REPO_MODE_BLB; | |
342 | else | |
343 | fprintf(stderr, "Unknown node-kind: %s\n", val); | |
90c0a3cf DB |
344 | break; |
345 | case sizeof("Node-action"): | |
346 | if (constcmp(t, "Node-action")) | |
347 | continue; | |
21746aa3 DB |
348 | if (!strcmp(val, "delete")) { |
349 | node_ctx.action = NODEACT_DELETE; | |
350 | } else if (!strcmp(val, "add")) { | |
351 | node_ctx.action = NODEACT_ADD; | |
352 | } else if (!strcmp(val, "change")) { | |
353 | node_ctx.action = NODEACT_CHANGE; | |
354 | } else if (!strcmp(val, "replace")) { | |
355 | node_ctx.action = NODEACT_REPLACE; | |
356 | } else { | |
357 | fprintf(stderr, "Unknown node-action: %s\n", val); | |
358 | node_ctx.action = NODEACT_UNKNOWN; | |
359 | } | |
90c0a3cf DB |
360 | break; |
361 | case sizeof("Node-copyfrom-path"): | |
362 | if (constcmp(t, "Node-copyfrom-path")) | |
363 | continue; | |
21746aa3 | 364 | pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val); |
90c0a3cf DB |
365 | break; |
366 | case sizeof("Node-copyfrom-rev"): | |
367 | if (constcmp(t, "Node-copyfrom-rev")) | |
368 | continue; | |
21746aa3 | 369 | node_ctx.srcRev = atoi(val); |
90c0a3cf DB |
370 | break; |
371 | case sizeof("Text-content-length"): | |
372 | if (!constcmp(t, "Text-content-length")) { | |
373 | node_ctx.textLength = atoi(val); | |
374 | break; | |
375 | } | |
376 | if (constcmp(t, "Prop-content-length")) | |
377 | continue; | |
21746aa3 | 378 | node_ctx.propLength = atoi(val); |
90c0a3cf DB |
379 | break; |
380 | case sizeof("Text-delta"): | |
381 | if (!constcmp(t, "Text-delta")) { | |
382 | node_ctx.text_delta = !strcmp(val, "true"); | |
383 | break; | |
384 | } | |
385 | if (constcmp(t, "Prop-delta")) | |
386 | continue; | |
1f05d07c | 387 | node_ctx.prop_delta = !strcmp(val, "true"); |
90c0a3cf DB |
388 | break; |
389 | case sizeof("Content-length"): | |
390 | if (constcmp(t, "Content-length")) | |
391 | continue; | |
21746aa3 | 392 | len = atoi(val); |
c9d1c8ba JN |
393 | t = buffer_read_line(&input); |
394 | if (!t) | |
395 | die_short_read(); | |
396 | if (*t) | |
397 | die("invalid dump: expected blank line after content length header"); | |
21746aa3 DB |
398 | if (active_ctx == REV_CTX) { |
399 | read_props(); | |
400 | } else if (active_ctx == NODE_CTX) { | |
401 | handle_node(); | |
402 | active_ctx = REV_CTX; | |
403 | } else { | |
5418d96d | 404 | fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len); |
c9d1c8ba JN |
405 | if (buffer_skip_bytes(&input, len) != len) |
406 | die_short_read(); | |
21746aa3 DB |
407 | } |
408 | } | |
409 | } | |
c9d1c8ba JN |
410 | if (buffer_ferror(&input)) |
411 | die_short_read(); | |
21746aa3 DB |
412 | if (active_ctx == NODE_CTX) |
413 | handle_node(); | |
414 | if (active_ctx != DUMP_CTX) | |
415 | handle_revision(); | |
416 | } | |
417 | ||
5c28a8b0 | 418 | int svndump_init(const char *filename) |
21746aa3 | 419 | { |
a62bbf8f | 420 | if (buffer_init(&input, filename)) |
5c28a8b0 | 421 | return error("cannot open %s: %s", filename, strerror(errno)); |
21746aa3 | 422 | repo_init(); |
7c5817d3 DB |
423 | strbuf_init(&dump_ctx.uuid, 4096); |
424 | strbuf_init(&dump_ctx.url, 4096); | |
dce33c9c | 425 | strbuf_init(&rev_ctx.log, 4096); |
7c5817d3 DB |
426 | strbuf_init(&rev_ctx.author, 4096); |
427 | reset_dump_ctx(NULL); | |
21746aa3 DB |
428 | reset_rev_ctx(0); |
429 | reset_node_ctx(NULL); | |
5c28a8b0 | 430 | return 0; |
21746aa3 DB |
431 | } |
432 | ||
433 | void svndump_deinit(void) | |
434 | { | |
21746aa3 | 435 | repo_reset(); |
7c5817d3 | 436 | reset_dump_ctx(NULL); |
21746aa3 DB |
437 | reset_rev_ctx(0); |
438 | reset_node_ctx(NULL); | |
dce33c9c | 439 | strbuf_release(&rev_ctx.log); |
e5e45ca1 | 440 | if (buffer_deinit(&input)) |
21746aa3 DB |
441 | fprintf(stderr, "Input error\n"); |
442 | if (ferror(stdout)) | |
443 | fprintf(stderr, "Output error\n"); | |
444 | } | |
445 | ||
446 | void svndump_reset(void) | |
447 | { | |
e5e45ca1 | 448 | buffer_reset(&input); |
21746aa3 | 449 | repo_reset(); |
7c5817d3 DB |
450 | strbuf_release(&dump_ctx.uuid); |
451 | strbuf_release(&dump_ctx.url); | |
452 | strbuf_release(&rev_ctx.log); | |
453 | strbuf_release(&rev_ctx.author); | |
21746aa3 | 454 | } |