1 /*
2 * rsvndump - remote svn repository dump
3 * Copyright (C) 2008-2012 Jonas Gehring
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 *
19 * file: delta.c
20 * desc: The delta editor
21 */
22
23
24 #include <svn_delta.h>
25 #include <svn_io.h>
26 #include <svn_md5.h>
27 #include <svn_path.h>
28 #include <svn_pools.h>
29 #include <svn_props.h>
30 #include <svn_repos.h>
31
32 #include <apr_file_io.h>
33 #include <apr_hash.h>
34 #include <apr_md5.h>
35
36 #include "main.h"
37 #include "dump.h"
38 #include "log.h"
39 #include "logger.h"
40 #include "path_repo.h"
41 #include "property.h"
42 #include "rhash.h"
43 #include "session.h"
44 #include "utils.h"
45
46 #include "delta.h"
47
48 /* This is for compabibility for Subverison 1.4 */
49 #if (SVN_VER_MAJOR==1) && (SVN_VER_MINOR<=5)
50 #define SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM
51 #endif
52
53 #define ERRBUFFER_SIZE 512
54
55
56 /*---------------------------------------------------------------------------*/
57 /* Local data structures */
58 /*---------------------------------------------------------------------------*/
59
60
61 /* Copy info enumeration */
62 typedef enum {
63 CPI_NONE = 0x01,
64 CPI_COPY = 0x02,
65 CPI_FAILED = 0x04,
66 CPI_FAILED_OUTSIDE = 0x14 /* Outside of session prefix */
67 } cp_info_t;
68
69
70 /* Main delta editor baton */
71 typedef struct {
72 session_t *session;
73 dump_options_t *opts;
74 apr_array_header_t *logs;
75 log_revision_t *log_revision;
76 apr_pool_t *revision_pool;
77 apr_hash_t *dumped_entries;
78 svn_revnum_t local_revnum;
79 void *root_node;
80 path_repo_t *path_repo;
81 property_storage_t *prop_store;
82 } de_baton_t;
83
84
85 /* Node baton */
86 typedef struct {
87 de_baton_t *de_baton;
88 apr_pool_t *pool;
89 const char *path;
90 char *filename;
91 char *old_filename;
92 char *delta_filename;
93 char action;
94 svn_node_kind_t kind;
95 apr_hash_t *properties;
96 apr_hash_t *del_properties; /* Value is always 0x1 */
97 unsigned char md5sum[APR_MD5_DIGESTSIZE];
98 char *copyfrom_path;
99 svn_revnum_t copyfrom_revision;
100 svn_revnum_t copyfrom_rev_local;
101 cp_info_t cp_info;
102 char applied_delta;
103 char dump_needed;
104 char props_changed;
105 void *parent;
106 apr_array_header_t *children;
107 } de_node_baton_t;
108
109
110 /*---------------------------------------------------------------------------*/
111 /* Static variables */
112 /*---------------------------------------------------------------------------*/
113
114
115 /*
116 * If the dump output is not using deltas, we need to keep a local copy of
117 * every file in the repository. The delta_hash hash defines a mapping of
118 * repository paths to temporary files for this purpose. The md5_hash is
119 * used to store the md5-sums of the file contents.
120 */
121 static char hashes_created = 0;
122 static rhash_t *delta_hash = NULL;
123 static rhash_t *md5_hash = NULL;
124 #ifdef USE_TIMING
125 static float tm_de_apply_textdelta = 0.0f;
126 #endif
127
128
129 /*---------------------------------------------------------------------------*/
130 /* Static functions */
131 /*---------------------------------------------------------------------------*/
132
133
134 /* Prototypes */
135 static svn_error_t *delta_dump_node(de_node_baton_t *node);
136
137
138 /* Creates a new node baton */
delta_create_node(const char * path,de_node_baton_t * parent)139 static de_node_baton_t *delta_create_node(const char *path, de_node_baton_t *parent)
140 {
141 de_node_baton_t *node = apr_palloc(parent->pool, sizeof(de_node_baton_t));
142 node->pool = svn_pool_create(parent->pool);
143 node->path = apr_pstrdup(node->pool, path);
144 node->de_baton = parent->de_baton;
145 node->properties = apr_hash_make(node->pool);
146 node->del_properties = apr_hash_make(node->pool);
147 node->filename = NULL;
148 node->old_filename = NULL;
149 node->delta_filename = NULL;
150 node->cp_info = parent->cp_info;
151 node->copyfrom_path = NULL;
152 node->copyfrom_revision = 0;
153 node->applied_delta = 0;
154 node->dump_needed = 0;
155 node->props_changed = 0;
156 node->parent = parent;
157 node->children = apr_array_make(node->pool, 0, (sizeof(de_node_baton_t *)));
158 memset(node->md5sum, 0x00, sizeof(node->md5sum));
159
160 /* Register node in parent list */
161 APR_ARRAY_PUSH(parent->children, de_node_baton_t *) = node;
162
163 return node;
164 }
165
166
167 /* Creates a new node baton without a parent */
delta_create_node_no_parent(const char * path,de_baton_t * de_baton,apr_pool_t * pool)168 static de_node_baton_t *delta_create_node_no_parent(const char *path, de_baton_t *de_baton, apr_pool_t *pool)
169 {
170 de_node_baton_t *node = apr_palloc(pool, sizeof(de_node_baton_t));
171 node->pool = svn_pool_create(pool);
172 node->path = apr_pstrdup(node->pool, path);
173 node->de_baton = de_baton;
174 node->properties = apr_hash_make(node->pool);
175 node->del_properties = apr_hash_make(node->pool);
176 node->filename = NULL;
177 node->old_filename = NULL;
178 node->delta_filename = NULL;
179 node->cp_info = CPI_NONE;
180 node->copyfrom_path = NULL;
181 node->applied_delta = 0;
182 node->dump_needed = 0;
183 node->props_changed = 0;
184 node->parent = NULL;
185 node->children = apr_array_make(node->pool, 0, (sizeof(de_node_baton_t *)));
186 memset(node->md5sum, 0x00, sizeof(node->md5sum));
187
188 return node;
189 }
190
191
192 /* Marks a node as being dumped, i.e. set dump_needed to 0 */
delta_mark_node(de_node_baton_t * node)193 static void delta_mark_node(de_node_baton_t *node)
194 {
195 de_baton_t *de_baton = node->de_baton;
196 apr_hash_set(de_baton->dumped_entries, node->path, APR_HASH_KEY_STRING, node);
197 if (node->kind == svn_node_file) {
198 rhash_set(md5_hash, node->path, APR_HASH_KEY_STRING, node->md5sum, APR_MD5_DIGESTSIZE);
199 DEBUG_MSG("md5_hash += %s : %s\n", node->path, svn_md5_digest_to_cstring(node->md5sum, node->pool));
200 }
201 node->dump_needed = 0;
202
203 if (!(de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
204 if (node->cp_info == CPI_COPY) {
205 L1(_("COPIED ... done.\n"));
206 } else {
207 L1(_("done.\n"));
208 }
209 }
210 }
211
212
213 /* Checks if a node can be dumped as a copy */
delta_check_copy(de_node_baton_t * node)214 static char delta_check_copy(de_node_baton_t *node)
215 {
216 session_t *session = node->de_baton->session;
217 dump_options_t *opts = node->de_baton->opts;
218
219 /* Propagated copies should have been checked already */
220 if ((node->cp_info == CPI_COPY) && (node->copyfrom_path == NULL)) {
221 return 0;
222 }
223
224 /* If the parent could not be copied, this node won't be copied, too */
225 if (node->cp_info & CPI_FAILED) {
226 return 0;
227 }
228
229 /* Sanity check */
230 if (node->copyfrom_path == NULL) {
231 node->cp_info = CPI_NONE;
232 return 0;
233 }
234
235 /* Check if we can use the information we already have */
236 if ((strlen(session->prefix) == 0) && ((opts->start == 0) || (opts->flags & DF_INCREMENTAL))) {
237 node->copyfrom_rev_local = node->copyfrom_revision;
238 node->cp_info = CPI_COPY;
239 return 0;
240 }
241
242 /*
243 * Check if the source is reachable, i.e. can be found under
244 * the current session root and the target revision is within
245 * the selected revision range
246 */
247 if (((opts->flags & DF_INCREMENTAL) || (opts->start <= node->copyfrom_revision)) && !strncmp(session->prefix, node->copyfrom_path, strlen(session->prefix))) {
248 svn_revnum_t rev;
249
250 rev = delta_get_local_copyfrom_rev(node->copyfrom_revision, opts, node->de_baton->logs, node->de_baton->local_revnum);
251 if (rev > 0) {
252 node->copyfrom_rev_local = rev;
253 node->cp_info = CPI_COPY;
254 DEBUG_MSG("delta_check_copy: using local %ld\n", rev);
255 } else {
256 node->cp_info = CPI_FAILED;
257 DEBUG_MSG("delta_check_copy: no matching revision found\n");
258 }
259 } else {
260 /* Hm, this is bad. we have to ignore the copy operation and
261 simulate it by simple dumping the node as being added.
262 This will work fine for single files, but directories
263 must be dumped recursively. */
264 node->cp_info = CPI_FAILED_OUTSIDE;
265 DEBUG_MSG("delta_check_copy: resolving failed\n");
266 }
267
268 return 0;
269 }
270
271
272 /* Checks if a 'modify' or 'replace' action should be replaced by an 'add' action */
delta_check_action(de_node_baton_t * node)273 static svn_error_t *delta_check_action(de_node_baton_t *node)
274 {
275 char check;
276 const char *copyfrom_path, *relpath;
277 de_node_baton_t *parent;
278 de_baton_t *de_baton = node->de_baton;
279 session_t *session = de_baton->session;
280 dump_options_t *opts = node->de_baton->opts;
281
282 /* Easy case: 'add' and 'delete' won't need to be replaced */
283 if (node->action != 'R' && node->action != 'M') {
284 return SVN_NO_ERROR;
285 }
286
287 /* Check if we can use the information from the log */
288 if ((strlen(session->prefix) == 0) && ((opts->start == 0) || (opts->flags & DF_INCREMENTAL))) {
289 return SVN_NO_ERROR;
290 }
291
292 /* First, check the node's parents */
293 parent = node->parent;
294 while (parent) {
295 if (parent->cp_info == CPI_FAILED_OUTSIDE) {
296 /* This node is part of a manual copy replacement, so it
297 should be added only. */
298 node->action = 'A';
299 break;
300 } else if (parent->cp_info != CPI_COPY || parent->copyfrom_path == NULL) {
301 parent = parent->parent;
302 continue;
303 }
304
305 /* Found a successfully copied parent. If this node is *not* part of
306 the copy, it should be added only. */
307 copyfrom_path = delta_get_local_copyfrom_path(session->prefix, parent->copyfrom_path);
308 relpath = node->path + strlen(parent->path);
309 while (*relpath == '/') ++relpath;
310 check = path_repo_check_parent(de_baton->path_repo, copyfrom_path, relpath, parent->copyfrom_rev_local, node->pool);
311 DEBUG_MSG("delta_check_action(%s): parent copy check %s -> %s for %s at %ld [is %d]\n", node->path, copyfrom_path, parent->path, relpath, parent->copyfrom_rev_local, check);
312 if (check < 0) {
313 return svn_error_createf(1, NULL, _("Failed to check parent relationship at previous revision %ld"), parent->copyfrom_rev_local);
314 } else if (!check) {
315 node->action = 'A';
316 }
317 return SVN_NO_ERROR;
318 }
319
320 /* Next check: If the node is part of a failed copy operation
321 and wasn't present in the previous revision, change operation to 'add'. */
322 if ((node->cp_info & CPI_FAILED) && node->action == 'R') {
323 char *ppath = svn_path_dirname(node->path, node->pool);
324 const char *relpath = node->path + strlen(ppath);
325 while (*relpath == '/') ++relpath;
326 check = path_repo_check_parent(de_baton->path_repo, ppath, relpath, de_baton->local_revnum - 1, node->pool);
327 DEBUG_MSG("delta_check_action(%s): parent check: %s for %s at %ld [is %d]\n", node->path, ppath, relpath, de_baton->local_revnum - 1, check);
328 if (check < 0) {
329 return svn_error_createf(1, NULL, _("Failed to check parent relationship at previous revision %ld"), de_baton->local_revnum - 1);
330 } else if (!check) {
331 node->action = 'A';
332 }
333 }
334
335 /* Next check: modifications on previously non-existent items */
336 if (node->action == 'M' && de_baton->local_revnum > 0) {
337 check = path_repo_exists(de_baton->path_repo, node->path, de_baton->local_revnum - 1, node->pool);
338 if (check < 0) {
339 return svn_error_createf(1, NULL, _("Failed to check parent relationship at previous revision %ld"), de_baton->local_revnum - 1);
340 } else if (!check) {
341 node->action = 'A';
342 }
343 }
344
345 /* Not related to copying: When dumping a sub-directory, it can happen that
346 * the root node is being reported as added. */
347 if (!strcmp(node->path, "/") && node->action == 'A') {
348 node->action = 'M';
349 }
350
351 return SVN_NO_ERROR;
352 }
353
354
355 /* Propagates copy information from a parent to a child node */
delta_propagate_copy(de_node_baton_t * parent,de_node_baton_t * child)356 static svn_error_t *delta_propagate_copy(de_node_baton_t *parent, de_node_baton_t *child)
357 {
358 apr_pool_t *check_pool;
359 de_node_baton_t *copied_parent;
360 const char *child_relpath;
361 const char *path;
362 char *check_path;
363 char *base, *end;
364 svn_revnum_t revision;
365
366 /* Easy case first */
367 if (parent->cp_info != CPI_COPY) {
368 DEBUG_MSG("delta_propagate_copy(%s, %s): early out %d\n", parent->path, child->path, parent->cp_info);
369 child->cp_info = parent->cp_info;
370 return SVN_NO_ERROR;
371 }
372
373 /*
374 * The parent has already been copied. If the child has already been
375 * a child of the parent's copy source, it has been copied, too.
376 */
377
378 /* Build relative path for the child */
379 if (strncmp(parent->path, child->path, strlen(parent->path))) {
380 DEBUG_MSG("delta_propagate_copy(%s, %s): paths do not match!\n", parent->path, child->path);
381 return SVN_NO_ERROR;
382 }
383 child_relpath = child->path + strlen(parent->path);
384 while (*child_relpath == '/') {
385 ++child_relpath;
386 }
387
388 /* Determine copy information */
389 copied_parent = parent;
390 while (copied_parent && copied_parent->copyfrom_path == NULL) {
391 copied_parent = copied_parent->parent;
392 }
393 if (copied_parent == NULL) {
394 child->cp_info = parent->cp_info;
395 return SVN_NO_ERROR;
396 }
397
398 path = delta_get_local_copyfrom_path(parent->de_baton->session->prefix, copied_parent->copyfrom_path);
399 if (path == NULL) {
400 DEBUG_MSG("delta_propagate_copy(%s, %s): copyfrom_path (%s) is out of scope\n", copied_parent->copyfrom_path);
401 child->cp_info = CPI_NONE;
402 return SVN_NO_ERROR;
403 }
404
405 revision = delta_get_local_copyfrom_rev(copied_parent->copyfrom_revision, parent->de_baton->opts, parent->de_baton->logs, parent->de_baton->local_revnum);
406
407 /* Check the old parent relationship for every directory level */
408 child_relpath = child->path + strlen(copied_parent->path);
409 while (*child_relpath == '/') {
410 ++child_relpath;
411 }
412 check_path = apr_psprintf(child->pool, "%s/%s", path, child_relpath);
413 base = check_path;
414 end = check_path + strlen(check_path);
415
416 check_pool = svn_pool_create(child->pool);
417 while (1) {
418 char *tmp;
419 signed char check;
420
421 /* Extract directory and basename for current level */
422 if ((base = strchr(base, '/')) == NULL) {
423 break;
424 }
425 *base++ = '\0';
426 if ((tmp = strchr(base, '/')) == NULL) {
427 tmp = base + strlen(base);
428 }
429 *tmp = '\0';
430
431 check = path_repo_check_parent(parent->de_baton->path_repo, check_path, base, revision, check_pool);
432 if (check < 0) {
433 return svn_error_createf(1, NULL, _("Failed to propagate copy information"));
434 } else if (check) {
435 *(base-1) = '/';
436 if (tmp < end) {
437 *tmp = '/';
438 }
439 continue;
440 }
441
442 /* Failure */
443 DEBUG_MSG("path_repo: parent relation %s -> %s in %ld FAIL\n", path, child_relpath, revision);
444 child->cp_info = CPI_NONE;
445 svn_pool_clear(check_pool);
446 return SVN_NO_ERROR;
447 }
448
449 DEBUG_MSG("path_repo: parent relation %s -> %s in %ld OK\n", path, child_relpath, revision);
450 child->cp_info = CPI_COPY;
451 svn_pool_destroy(check_pool);
452 return SVN_NO_ERROR;
453 }
454
455
456 /* Deltifies a node, i.e. generates a svndiff that can be dumped */
delta_deltify_node(de_node_baton_t * node)457 static svn_error_t *delta_deltify_node(de_node_baton_t *node)
458 {
459 svn_txdelta_stream_t *stream;
460 svn_txdelta_window_handler_t handler;
461 void *handler_baton;
462 svn_stream_t *source, *target, *dest;
463 apr_file_t *source_file = NULL, *target_file = NULL, *dest_file = NULL;
464 apr_status_t status;
465 dump_options_t *opts = node->de_baton->opts;
466 apr_pool_t *pool = svn_pool_create(node->pool);
467 svn_error_t *err;
468
469 DEBUG_MSG("delta_deltify_node(%s): %s -> %s\n", node->path, node->old_filename, node->filename);
470
471 /* Open source and target */
472 status = apr_file_open(&target_file, node->filename, APR_READ, 0600, pool);
473 if (status) {
474 DEBUG_MSG("delta_deltify_node(%s): Error opening %s\n", node->path, node->filename);
475 return svn_error_wrap_apr(status, "Unable to open %s", node->filename);
476 }
477 target = svn_stream_from_aprfile2(target_file, FALSE, pool);
478 if (node->old_filename) {
479 status = apr_file_open(&source_file, node->old_filename, APR_READ, 0600, pool);
480 if (status) {
481 DEBUG_MSG("delta_deltify_node(%s): Error opening %s\n", node->path, node->old_filename);
482 return svn_error_wrap_apr(status, "Unable to open %s", node->old_filename);
483 }
484 source = svn_stream_from_aprfile2(source_file, FALSE, pool);
485 } else {
486 source = svn_stream_empty(pool);
487 }
488
489 /* Open temporary output file */
490 node->delta_filename = apr_psprintf(node->pool, "%s/df/XXXXXX", opts->temp_dir);
491 status = utils_mkstemp(&dest_file, node->delta_filename, pool);
492 if (status) {
493 DEBUG_MSG("delta_deltify_node(%s): Error creating temporary file in %s\n", node->path, opts->temp_dir);
494 return svn_error_wrap_apr(status, "Unable to create temporary file in %s", opts->temp_dir);
495 }
496 dest = svn_stream_from_aprfile2(dest_file, FALSE, pool);
497
498 DEBUG_MSG("delta_deltify_node(%s): writing to %s\n", node->path, node->delta_filename);
499
500 /* Produce delta in svndiff format */
501 svn_txdelta(&stream, source, target, pool);
502 svn_txdelta_to_svndiff2(&handler, &handler_baton, dest, 0, pool);
503
504 err = svn_txdelta_send_txstream(stream, handler, handler_baton, pool);
505 if (err) {
506 DEBUG_MSG("delta_delify_node(%s): Error creating svndiff\n", node->path);
507 return err;
508 }
509 svn_pool_destroy(pool);
510 return SVN_NO_ERROR;
511 }
512
513
514 /* Dumps the contents of a file to stdout */
delta_cat_file(apr_pool_t * pool,const char * path)515 static svn_error_t *delta_cat_file(apr_pool_t *pool, const char *path)
516 {
517 svn_error_t *err;
518 apr_status_t status;
519 apr_file_t *in_file = NULL;
520 svn_stream_t *in, *out;
521
522 status = apr_file_open(&in_file, path, APR_READ, 0600, pool);
523 if (status) {
524 apr_pool_t *epool = svn_pool_create(NULL);
525 char *errbuf = apr_palloc(epool, ERRBUFFER_SIZE);
526 return svn_error_create(status, NULL, apr_strerror(status, errbuf, ERRBUFFER_SIZE));
527 }
528 in = svn_stream_from_aprfile2(in_file, FALSE, pool);
529 if ((err = svn_stream_for_stdout(&out, pool))) {
530 svn_stream_close(in);
531 return err;
532 }
533
534 /* Write contents */
535 if ((err = svn_stream_copy(in, out, pool))) {
536 svn_stream_close(in);
537 return err;
538 }
539
540 err = svn_stream_close(in);
541 return SVN_NO_ERROR;
542 }
543
544
545 /* Dumps a node that has a 'replace' action */
delta_dump_replace(de_node_baton_t * node)546 static svn_error_t *delta_dump_replace(de_node_baton_t *node)
547 {
548 de_baton_t *de_baton = node->de_baton;
549 dump_options_t *opts = de_baton->opts;
550 const char *path = node->path;
551
552 /*
553 * A replacement implies deleting and adding the node
554 */
555
556 /* Dump the deletion */
557 if (opts->prefix != NULL) {
558 printf("%s: %s%s\n", SVN_REPOS_DUMPFILE_NODE_PATH, opts->prefix, path);
559 } else {
560 printf("%s: %s\n", SVN_REPOS_DUMPFILE_NODE_PATH, path);
561 }
562 printf("%s: delete\n", SVN_REPOS_DUMPFILE_NODE_ACTION);
563 printf("\n\n");
564
565 /* Don't use the copy information of the parent */
566 node->cp_info = CPI_NONE;
567 node->action = 'A';
568 return delta_dump_node(node);
569 }
570
571
572 /* Dumps a node */
delta_dump_node(de_node_baton_t * node)573 static svn_error_t *delta_dump_node(de_node_baton_t *node)
574 {
575 de_baton_t *de_baton = node->de_baton;
576 session_t *session = de_baton->session;
577 dump_options_t *opts = de_baton->opts;
578 const char *path = node->path;
579 unsigned long prop_len, content_len;
580 char dump_content = 0, dump_props = 0;
581 apr_hash_index_t *hi;
582 svn_error_t *err;
583
584 /* Check if the node needs to be dumped at all */
585 if (!node->dump_needed) {
586 DEBUG_MSG("delta_dump_node(%s): aborting: dump not needed\n", node->path);
587 return SVN_NO_ERROR;
588 }
589
590 /* Check if this is a dry run */
591 if (opts->flags & DF_INITIAL_DRY_RUN) {
592 node->dump_needed = 0;
593 DEBUG_MSG("delta_dump_node(%s): aborting: DF_INITIAL_DRY_RUN\n", node->path);
594 return SVN_NO_ERROR;
595 }
596
597 /* If the node is a directory and no properties have been changed,
598 we don't need to dump it */
599 if ((node->action == 'M') && (node->kind == svn_node_dir) && (!node->props_changed)) {
600 node->dump_needed = 0;
601 DEBUG_MSG("delta_dump_node(%s): aborting: directory && action == 'M' && !props_changed\n", node->path);
602 return SVN_NO_ERROR;
603 }
604
605 /* If the node's parent has been copied, we don't need to dump it if its contents haven't changed.
606 Addionally, make sure the node doesn't contain extra copyfrom information. */
607 if ((node->cp_info == CPI_COPY) && (node->action == 'A') && (node->copyfrom_path == NULL)) {
608 node->dump_needed = 0;
609 DEBUG_MSG("delta_dump_node(%s): aborting: cp_info == CPI_COPY && action == 'A'\n", node->path);
610 return SVN_NO_ERROR;
611 }
612
613 DEBUG_MSG("delta_dump_node(%s): started\n", node->path);
614
615 /* Check for potential copy. This is neede here because it might change the action. */
616 delta_check_copy(node);
617
618 /* Check whether the action is valid */
619 if ((err = delta_check_action(node))) {
620 return err;
621 }
622
623 if (node->action == 'R') {
624 /* Special handling for replacements */
625 DEBUG_MSG("delta_dump_node(%s): running delta_dump_replace()\n", node->path);
626 return delta_dump_replace(node);
627 }
628
629 /* Dump node path */
630 if (opts->prefix != NULL) {
631 printf("%s: %s%s\n", SVN_REPOS_DUMPFILE_NODE_PATH, opts->prefix, path);
632 } else {
633 printf("%s: %s\n", SVN_REPOS_DUMPFILE_NODE_PATH, path);
634 }
635
636 /* Dump node kind */
637 if (node->action != 'D') {
638 printf("%s: %s\n", SVN_REPOS_DUMPFILE_NODE_KIND, node->kind == svn_node_file ? "file" : "dir");
639 }
640
641 /* Dump action */
642 printf("%s: ", SVN_REPOS_DUMPFILE_NODE_ACTION);
643 switch (node->action) {
644 case 'M':
645 printf("change\n");
646 if (!(de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
647 L1(_(" * editing path : %s ... "), path);
648 }
649 break;
650
651 case 'A':
652 printf("add\n");
653 if (!(de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
654 L1(_(" * adding path : %s ... "), path);
655 }
656 break;
657
658 case 'D':
659 printf("delete\n");
660 if (!(de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
661 L1(_(" * deleting path : %s ... "), path);
662 }
663
664 /* We can finish early here */
665 DEBUG_MSG("delta_dump_node(%s): deleted -> finished\n", node->path);
666 goto finish;
667
668 case 'R':
669 printf("replace\n");
670 break;
671 }
672
673 /* Check if the node content needs to be dumped */
674 if (node->kind == svn_node_file && node->applied_delta) {
675 DEBUG_MSG("delta_dump_node(%s): dump_content = 1\n", node->path);
676 dump_content = 1;
677 }
678
679 /* Check if the node properties need to be dumped */
680 if ((node->props_changed) || (node->action == 'A')) {
681 DEBUG_MSG("delta_dump_node(%s): dump_props = 1\n", node->path);
682 dump_props = 1;
683 }
684
685 /* Output copy information if neccessary */
686 if (node->cp_info == CPI_COPY && node->copyfrom_path) {
687 const char *copyfrom_path = delta_get_local_copyfrom_path(session->prefix, node->copyfrom_path);
688
689 printf("%s: %ld\n", SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, node->copyfrom_rev_local);
690 if (opts->prefix != NULL) {
691 printf("%s: %s%s\n", SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, opts->prefix, copyfrom_path);
692 } else {
693 printf("%s: %s\n", SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, copyfrom_path);
694 }
695
696 /* Maybe we don't need to dump the contents */
697 if ((node->action == 'A') && (node->kind == svn_node_file)) {
698 unsigned char *prev_md5 = rhash_get(md5_hash, copyfrom_path, APR_HASH_KEY_STRING);
699 if (prev_md5 && !memcmp(node->md5sum, prev_md5, APR_MD5_DIGESTSIZE)) {
700 DEBUG_MSG("md5sum matches\n");
701 dump_content = 0;
702 } else {
703 #ifdef DEBUG
704 if (prev_md5) {
705 DEBUG_MSG("md5sum doesn't match: (%s != %s)\n", svn_md5_digest_to_cstring(node->md5sum, node->pool), svn_md5_digest_to_cstring(prev_md5, node->pool));
706 } else {
707 DEBUG_MSG("md5sum of %s not available\n", copyfrom_path);
708 }
709 #endif
710 dump_content = 1;
711 }
712 }
713
714 if (!dump_content && !node->props_changed) {
715 goto finish;
716 } else if (node->kind == svn_node_dir) {
717 dump_content = 0;
718 }
719 }
720
721 /* Deltify? */
722 if (dump_content && (opts->flags & DF_USE_DELTAS)) {
723 if ((err = delta_deltify_node(node))) {
724 return err;
725 }
726 }
727
728 #ifdef DUMP_DEBUG
729 /* Dump some extra debug info */
730 if (dump_content) {
731 printf("Debug-filename: %s\n", node->filename);
732 if (node->old_filename) {
733 printf("Debug-old-filename: %s\n", node->old_filename);
734 }
735 if (opts->flags & DF_USE_DELTAS) {
736 printf("Debug-delta-filename: %s\n", node->delta_filename);
737 }
738 }
739 #endif
740
741 /* Dump properties & content */
742 prop_len = 0;
743 content_len = 0;
744
745 /* Dump property size */
746 for (hi = apr_hash_first(node->pool, node->properties); hi; hi = apr_hash_next(hi)) {
747 const char *key;
748 svn_string_t *value;
749 apr_hash_this(hi, (const void **)&key, NULL, (void **)&value);
750 /* Don't dump the property if it has been deleted */
751 if (apr_hash_get(node->del_properties, key, APR_HASH_KEY_STRING) != NULL) {
752 continue;
753 }
754 prop_len += property_strlen(node->pool, key, value->data);
755 }
756 /* In dump format version 3, deleted properties should be dumped, too */
757 if (opts->dump_format == 3) {
758 for (hi = apr_hash_first(node->pool, node->del_properties); hi; hi = apr_hash_next(hi)) {
759 const char *key;
760 apr_hash_this(hi, (const void **)&key, NULL, NULL);
761 prop_len += property_del_strlen(node->pool, key);
762 }
763 }
764 if ((prop_len > 0)) {
765 dump_props = 1;
766 }
767 if (dump_props) {
768 if (opts->dump_format == 3) {
769 printf("%s: true\n", SVN_REPOS_DUMPFILE_PROP_DELTA);
770 }
771
772 prop_len += PROPS_END_LEN;
773 printf("%s: %lu\n", SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, prop_len);
774 }
775
776 /* Dump content size */
777 if (dump_content) {
778 char *fpath = (opts->flags & DF_USE_DELTAS) ? node->delta_filename : node->filename;
779 apr_finfo_t *info = apr_pcalloc(node->pool, sizeof(apr_finfo_t));
780 if (apr_stat(info, fpath, APR_FINFO_SIZE, node->pool) != APR_SUCCESS) {
781 DEBUG_MSG("delta_dump_node: FATAL: cannot stat %s\n", node->filename);
782 return svn_error_create(1, NULL, apr_psprintf(session->pool, "Cannot stat %s", node->filename));
783 }
784 content_len = (unsigned long)info->size;
785
786 if (opts->flags & DF_USE_DELTAS) {
787 printf("%s: true\n", SVN_REPOS_DUMPFILE_TEXT_DELTA);
788 }
789 printf("%s: %lu\n", SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH, content_len);
790
791 if (*node->md5sum != 0x00) {
792 printf("%s: %s\n", SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, svn_md5_digest_to_cstring(node->md5sum, node->pool));
793 }
794 }
795 printf("%s: %lu\n\n", SVN_REPOS_DUMPFILE_CONTENT_LENGTH, (unsigned long)prop_len+content_len);
796
797 /* Dump properties */
798 if (dump_props) {
799 for (hi = apr_hash_first(node->pool, node->properties); hi; hi = apr_hash_next(hi)) {
800 const char *key;
801 svn_string_t *value;
802 apr_hash_this(hi, (const void **)&key, NULL, (void **)&value);
803 /* Don't dump the property if it has been deleted */
804 if (apr_hash_get(node->del_properties, key, APR_HASH_KEY_STRING) != NULL) {
805 continue;
806 }
807 property_dump(key, value->data);
808 }
809 /* In dump format version 3, deleted properties should be dumped, too */
810 if (opts->dump_format == 3) {
811 for (hi = apr_hash_first(node->pool, node->del_properties); hi; hi = apr_hash_next(hi)) {
812 const char *key;
813 apr_hash_this(hi, (const void **)&key, NULL, NULL);
814 property_del_dump(key);
815 }
816 }
817 printf(PROPS_END);
818 }
819
820 /* Dump content */
821 if (dump_content) {
822 svn_error_t *err;
823 apr_pool_t *pool = svn_pool_create(node->pool);
824 const char *fpath = (opts->flags & DF_USE_DELTAS) ? node->delta_filename : node->filename;
825
826 fflush(stdout);
827 if ((err = delta_cat_file(pool, fpath))) {
828 return err;
829 }
830 fflush(stdout);
831
832 svn_pool_destroy(pool);
833 #ifndef DUMP_DEBUG
834 if (opts->flags & DF_USE_DELTAS) {
835 DEBUG_MSG("delta_dump_node(%s): Removing delta file %s\n", node->path, node->delta_filename);
836 if (apr_file_remove(node->delta_filename, node->pool) != APR_SUCCESS) {
837 DEBUG_MSG("delta_dump_node(%s): Cannot remove file %s\n", node->path, node->delta_filename);
838 }
839 }
840 #endif
841 }
842
843 finish:
844 printf("\n\n");
845 delta_mark_node(node);
846
847 /* Remove the old file if any - it's not needed any more */
848 #ifndef DUMP_DEBUG
849 if (node->old_filename) {
850 DEBUG_MSG("de_close_file(%s): Removing old file %s\n", node->path, node->old_filename);
851 if (apr_file_remove(node->old_filename, node->pool) != APR_SUCCESS) {
852 DEBUG_MSG("de_close_file(%s): Cannot remove old file %s\n", node->path, node->old_filename);
853 }
854 }
855 #endif
856
857 return SVN_NO_ERROR;
858 }
859
860
861 /* Dumps a node and all its children */
delta_dump_node_recursive(de_node_baton_t * node)862 static svn_error_t *delta_dump_node_recursive(de_node_baton_t *node)
863 {
864 int i;
865 svn_error_t *err;
866
867 /*
868 * The normal dumping order is parent prior to children. However,
869 * deletions are handled bottom-up, i.e. first the children and finally
870 * the parent.
871 */
872 if (node->action != 'D') {
873 if ((err = delta_dump_node(node))) {
874 return err;
875 }
876 }
877
878 DEBUG_MSG("delta_dump_node_recursive(%s): dumping %d children\n", node->path, node->children->nelts);
879 for (i = 0; i < node->children->nelts; i++) {
880 de_node_baton_t *child = APR_ARRAY_IDX(node->children, i, de_node_baton_t *);
881
882 /* Propagate copy information obtained while dumping the parent node */
883 if ((err = delta_propagate_copy(node, child))) {
884 return err;
885 }
886
887 if ((err = delta_dump_node_recursive(child))) {
888 return err;
889 }
890 }
891
892 if (node->action == 'D') {
893 if ((err = delta_dump_node(node))) {
894 return err;
895 }
896 }
897
898 return SVN_NO_ERROR;
899 }
900
901
902 /* Subversion delta editor callback */
de_set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * pool)903 static svn_error_t *de_set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool)
904 {
905 /* The revision header has already been dumped, so there's nothing to
906 do here */
907 return SVN_NO_ERROR;
908 }
909
910
911 /* Subversion delta editor callback */
de_open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** root_baton)912 static svn_error_t *de_open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *dir_pool, void **root_baton)
913 {
914 de_baton_t *de_baton = (de_baton_t *)edit_baton;
915 de_node_baton_t *node;
916 node = delta_create_node_no_parent("/", de_baton, de_baton->revision_pool);
917 node->action = 'M';
918 de_baton->root_node = node;
919
920 *root_baton = node;
921 #ifdef USE_TIMING
922 tm_de_apply_textdelta = 0.0f;
923 #endif
924 return SVN_NO_ERROR;
925 }
926
927
928 /* Subversion delta editor callback */
de_delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * pool)929 static svn_error_t *de_delete_entry(const char *path, svn_revnum_t revision, void *parent_baton, apr_pool_t *pool)
930 {
931 de_node_baton_t *node;
932 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
933 apr_hash_index_t *hi;
934 int pathlen;
935
936 path = session_obfuscate(parent->de_baton->session, pool, path);
937 DEBUG_MSG("de_delete_entry(%s@%ld)\n", path, revision);
938
939 /* We can dump this entry directly */
940 node = delta_create_node(path, parent);
941 node->kind = svn_node_none;
942 node->action = 'D';
943 node->dump_needed = 1;
944 #if 0
945 if ((err = delta_dump_node(node))) {
946 return err;
947 }
948 #endif
949
950 /* This node might be a directory, so clear the data of all children */
951 pathlen = strlen(node->path);
952 for (hi = rhash_first(pool, delta_hash); hi; hi = rhash_next(hi)) {
953 const char *npath;
954 char *filename;
955 rhash_this(hi, (const void **)&npath, NULL, (void **)&filename);
956 /* TODO: This is a small hack to make sure the node is a directory */
957 if (!strncmp(node->path, npath, pathlen) && (npath[pathlen] == '/')) {
958 #ifndef DUMP_DEBUG
959 DEBUG_MSG("de_delete_entry(%s): Removing file %s\n", node->path, filename);
960 if (apr_file_remove(filename, node->pool) != APR_SUCCESS) {
961 DEBUG_MSG("de_delete_entry(%s): Cannot remove file %s\n", node->path, filename);
962 }
963 #endif
964
965 /* Delete property data */
966 DEBUG_MSG("de_delete_entry(%s): removeing properties for %s\n", node->path, npath);
967 property_delete(node->de_baton->prop_store, npath, pool);
968
969 DEBUG_MSG("de_delete_entry(%s): deleting %s from delta_hash\n", node->path, npath);
970 rhash_set(delta_hash, npath, APR_HASH_KEY_STRING, NULL, 0);
971 }
972 }
973
974 for (hi = rhash_first(pool, md5_hash); hi; hi = rhash_next(hi)) {
975 const char *npath;
976 char *md5sum;
977 rhash_this(hi, (const void **)&npath, NULL, (void **)&md5sum);
978 if (!strncmp(node->path, npath, pathlen) && (npath[pathlen] == '/')) {
979 DEBUG_MSG("deleting %s from md5_hash\n", npath);
980 rhash_set(md5_hash, npath, APR_HASH_KEY_STRING, NULL, 0);
981 }
982 }
983
984 property_delete(node->de_baton->prop_store, node->path, pool);
985
986 /* This will delete all children, too */
987 if (!(node->de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
988 if (path_repo_delete(node->de_baton->path_repo, path, pool) != 0) {
989 return svn_error_createf(1, NULL, _("Unable to update local tree history"));
990 }
991 }
992 return SVN_NO_ERROR;
993 }
994
995
996 /* Subversion delta editor callback */
de_add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * dir_pool,void ** child_baton)997 static svn_error_t *de_add_directory(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_revision, apr_pool_t *dir_pool, void **child_baton)
998 {
999 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
1000 de_node_baton_t *node;
1001 svn_log_changed_path_t *log;
1002
1003 path = session_obfuscate(parent->de_baton->session, dir_pool, path);
1004 DEBUG_MSG("de_add_directory(%s), copy = %d\n", path, (int)parent->cp_info);
1005
1006 node = delta_create_node(path, parent);
1007 node->kind = svn_node_dir;
1008 node->dump_needed = 1;
1009
1010 log = apr_hash_get(node->de_baton->log_revision->changed_paths, path, APR_HASH_KEY_STRING);
1011
1012 /*
1013 * Although this function is named de_add_directory, it will also be called
1014 * for nodes that have been copied (or are located in a tree that has
1015 * just been copied). If this is true, we need to ask the log entry
1016 * to determine the correct action.
1017 */
1018 if (log == NULL) {
1019 /* Fallback */
1020 node->action = 'A';
1021 } else {
1022 node->action = log->action;
1023 /*
1024 * Check for copy. This needs to be done manually, since
1025 * svn_ra_do_diff does not supply any copy information to the
1026 * delta editor.
1027 */
1028 if (log->copyfrom_path != NULL) {
1029 DEBUG_MSG("copyfrom_path = %s\n", log->copyfrom_path);
1030 node->copyfrom_path = apr_pstrdup(node->pool, log->copyfrom_path);
1031 node->copyfrom_revision = log->copyfrom_rev;
1032 }
1033 /*
1034 * If the node is preset in the log, we must not use the copy
1035 * information of the parent node
1036 */
1037 if (!(node->cp_info & CPI_FAILED_OUTSIDE)) {
1038 node->cp_info = CPI_NONE;
1039 }
1040 }
1041
1042 if (!(node->de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
1043 if (path_repo_add(node->de_baton->path_repo, path, dir_pool) != 0) {
1044 return svn_error_createf(1, NULL, _("Unable to update local tree history"));
1045 }
1046 }
1047 *child_baton = node;
1048 return SVN_NO_ERROR;
1049 }
1050
1051
1052 /* Subversion delta editor callback */
de_open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** child_baton)1053 static svn_error_t *de_open_directory(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *dir_pool, void **child_baton)
1054 {
1055 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
1056 de_node_baton_t *node;
1057 int ret;
1058
1059 path = session_obfuscate(parent->de_baton->session, dir_pool, path);
1060 node = delta_create_node(path, parent);
1061 node->kind = svn_node_dir;
1062 node->action = 'M';
1063
1064 *child_baton = node;
1065
1066 /* Load properties (if any) */
1067 ret = property_load(parent->de_baton->prop_store, node->path, node->properties, node->pool);
1068 if (ret != 0) {
1069 return svn_error_createf(1, NULL, _("Unable to load properties for %s (%d)\n"), path, ret);
1070 }
1071 return SVN_NO_ERROR;
1072 }
1073
1074
1075 /* Subversion delta editor callback */
de_change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)1076 static svn_error_t *de_change_dir_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool)
1077 {
1078 de_node_baton_t *node = (de_node_baton_t *)dir_baton;
1079
1080 /* We're only interested in regular properties */
1081 if (svn_property_kind(NULL, name) != svn_prop_regular_kind) {
1082 return SVN_NO_ERROR;
1083 }
1084
1085 DEBUG_MSG("de_change_dir_prop(%s) %s = %s\n", ((de_node_baton_t *)dir_baton)->path, name, value ? value->data : NULL);
1086
1087 if (value != NULL) {
1088 apr_hash_set(node->properties, apr_pstrdup(node->pool, name), APR_HASH_KEY_STRING, svn_string_dup(value, node->pool));
1089 } else {
1090 apr_hash_set(node->del_properties, apr_pstrdup(node->pool, name), APR_HASH_KEY_STRING, (void *)0x1);
1091 }
1092 node->props_changed = 1;
1093
1094 node->dump_needed = 1;
1095 return SVN_NO_ERROR;
1096 }
1097
1098
1099 /* Subversion delta editor callback */
de_close_directory(void * dir_baton,apr_pool_t * pool)1100 static svn_error_t *de_close_directory(void *dir_baton, apr_pool_t *pool)
1101 {
1102 de_node_baton_t *node = (de_node_baton_t *)dir_baton;
1103 int ret;
1104
1105 DEBUG_MSG("de_close_directory(%s): dump_needed = %d\n", node->path, (int)node->dump_needed);
1106
1107 /* Save properties for next time */
1108 ret = property_store(node->de_baton->prop_store, node->path, node->properties, pool);
1109 if (ret != 0) {
1110 return svn_error_createf(1, NULL, _("Unable to store properties for %s (%d)\n"), node->path, ret);
1111 }
1112 return SVN_NO_ERROR;
1113 }
1114
1115
1116 /* Subversion delta editor callback */
de_absent_directory(const char * path,void * parent_baton,apr_pool_t * pool)1117 static svn_error_t *de_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool)
1118 {
1119 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
1120 path = session_obfuscate(parent->de_baton->session, pool, path);
1121 DEBUG_MSG("absent_directory(%s)\n", path);
1122 return SVN_NO_ERROR;
1123 }
1124
1125
1126 /* Subversion delta editor callback */
de_add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * file_pool,void ** file_baton)1127 static svn_error_t *de_add_file(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_revision, apr_pool_t *file_pool, void **file_baton)
1128 {
1129 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
1130 de_node_baton_t *node;
1131 svn_log_changed_path_t *log;
1132
1133 path = session_obfuscate(parent->de_baton->session, file_pool, path);
1134 DEBUG_MSG("de_add_file(%s), copy = %d\n", path, (int)parent->cp_info);
1135
1136 node = delta_create_node(path, parent);
1137 node->kind = svn_node_file;
1138 node->dump_needed = 1;
1139
1140 /* Get corresponding log entry */
1141 log = apr_hash_get(node->de_baton->log_revision->changed_paths, path, APR_HASH_KEY_STRING);
1142
1143 /*
1144 * Although this function is named de_add_file, it will also be called
1145 * for nodes that have been copied (or are located in a tree that has
1146 * just been copied). If this is true, we need to ask the log entry
1147 * to determine the correct action.
1148 */
1149 if (log == NULL) {
1150 /* Fallback */
1151 node->action = 'A';
1152 } else {
1153 node->action = log->action;
1154
1155 /*
1156 * Check for copy. This needs to be done manually, since
1157 * svn_ra_do_diff does not supply any copy information to the
1158 * delta editor.
1159 */
1160 if (log->copyfrom_path != NULL) {
1161 DEBUG_MSG("copyfrom_path = %s\n", log->copyfrom_path);
1162 node->copyfrom_path = apr_pstrdup(node->pool, log->copyfrom_path);
1163 node->copyfrom_revision = log->copyfrom_rev;
1164 }
1165
1166 /*
1167 * If the node is preset in the log, we must not use the copy
1168 * information of the parent node.
1169 */
1170 if (!(node->cp_info & CPI_FAILED)) {
1171 node->cp_info = CPI_NONE;
1172 }
1173 }
1174
1175 /*
1176 * If the node is part of a tree that is dumped instead of being copied,
1177 * this must be an add action
1178 */
1179 if (node->cp_info & CPI_FAILED) {
1180 node->action = 'A';
1181 }
1182
1183 if (!(node->de_baton->opts->flags & DF_INITIAL_DRY_RUN)) {
1184 if (path_repo_add(node->de_baton->path_repo, path, file_pool) != 0) {
1185 return svn_error_createf(1, NULL, _("Unable to update local tree history"));
1186 }
1187 }
1188 *file_baton = node;
1189 return SVN_NO_ERROR;
1190 }
1191
1192
1193 /* Subversion delta editor callback */
de_open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * file_pool,void ** file_baton)1194 static svn_error_t *de_open_file(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *file_pool, void **file_baton)
1195 {
1196 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
1197 de_node_baton_t *node;
1198 int ret;
1199
1200 path = session_obfuscate(parent->de_baton->session, file_pool, path);
1201 DEBUG_MSG("de_open_file(%s)\n", path);
1202 node = delta_create_node(path, parent);
1203 node->kind = svn_node_file;
1204 node->action = 'M';
1205
1206 *file_baton = node;
1207
1208 /* Load properties (if any) */
1209 ret = property_load(parent->de_baton->prop_store, node->path, node->properties, node->pool);
1210 if (ret != 0) {
1211 return svn_error_createf(1, NULL, _("Unable to load properties for %s (%d)\n"), path, ret);
1212 }
1213 return SVN_NO_ERROR;
1214 }
1215
1216
1217 /* Subversion delta editor callback */
de_apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)1218 static svn_error_t *de_apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton)
1219 {
1220 apr_file_t *src_file = NULL, *dest_file = NULL;
1221 apr_status_t status;
1222 svn_stream_t *src_stream, *dest_stream;
1223 de_node_baton_t *node = (de_node_baton_t *)file_baton;
1224 dump_options_t *opts = node->de_baton->opts;
1225 #ifdef USE_TIMING
1226 stopwatch_t watch = stopwatch_create();
1227 #endif
1228 char *filename;
1229
1230 DEBUG_MSG("de_apply_textdelta(%s)\n", node->path);
1231
1232 /* Create a new temporary file to write to */
1233 node->filename = apr_psprintf(node->pool, "%s/td/XXXXXX", opts->temp_dir);
1234 status = utils_mkstemp(&dest_file, node->filename, pool);
1235 if (status) {
1236 DEBUG_MSG("de_apply_textdelta(%s): Error creating temporary file in %s\n", node->path, opts->temp_dir);
1237 return svn_error_wrap_apr(status, "Unable to create temporary file in %s", opts->temp_dir);
1238 }
1239 dest_stream = svn_stream_from_aprfile2(dest_file, FALSE, pool);
1240
1241 /* Update the local copy */
1242 filename = rhash_get(delta_hash, node->path, APR_HASH_KEY_STRING);
1243 if (filename == NULL) {
1244 src_stream = svn_stream_empty(pool);
1245 } else {
1246 status = apr_file_open(&src_file, filename, APR_READ, 0600, pool);
1247 if (status) {
1248 DEBUG_MSG("de_apply_textdelta(%s): Error opening %s\n", node->path, filename);
1249 return svn_error_wrap_apr(status, "Unable to open %s", filename);
1250 }
1251 src_stream = svn_stream_from_aprfile2(src_file, FALSE, pool);
1252 }
1253
1254 svn_txdelta_apply(src_stream, dest_stream, node->md5sum, node->path, pool, handler, handler_baton);
1255
1256 node->old_filename = apr_pstrdup(node->pool, filename);
1257 rhash_set(delta_hash, node->path, APR_HASH_KEY_STRING, node->filename, RHASH_VAL_STRING);
1258
1259 DEBUG_MSG("applying delta: %s -> %s\n", node->old_filename, node->filename);
1260
1261 node->applied_delta = 1;
1262 node->dump_needed = 1;
1263
1264 #ifdef USE_TIMING
1265 tm_de_apply_textdelta += stopwatch_elapsed(&watch);
1266 #endif
1267 return SVN_NO_ERROR;
1268 }
1269
1270
1271 /* Subversion delta editor callback */
de_change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)1272 static svn_error_t *de_change_file_prop(void *file_baton, const char *name, const svn_string_t *value, apr_pool_t *pool)
1273 {
1274 de_node_baton_t *node = (de_node_baton_t *)file_baton;
1275
1276 /* We're only interested in regular properties */
1277 if (svn_property_kind(NULL, name) != svn_prop_regular_kind) {
1278 return SVN_NO_ERROR;
1279 }
1280
1281 DEBUG_MSG("de_change_file_prop(%s) %s = %s\n", ((de_node_baton_t *)file_baton)->path, name, value ? value->data : NULL);
1282
1283 if (value != NULL) {
1284 apr_hash_set(node->properties, apr_pstrdup(node->pool, name), APR_HASH_KEY_STRING, svn_string_dup(value, node->pool));
1285 } else {
1286 apr_hash_set(node->del_properties, apr_pstrdup(node->pool, name), APR_HASH_KEY_STRING, (void *)0x1);
1287 }
1288 node->props_changed = 1;
1289 node->dump_needed = 1;
1290
1291 return SVN_NO_ERROR;
1292 }
1293
1294
1295 /* Subversion delta editor callback */
de_close_file(void * file_baton,const char * text_checksum,apr_pool_t * pool)1296 static svn_error_t *de_close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool)
1297 {
1298 de_node_baton_t *node = (de_node_baton_t *)file_baton;
1299 int ret;
1300
1301 /* Save properties for next time */
1302 ret = property_store(node->de_baton->prop_store, node->path, node->properties, pool);
1303 if (ret != 0) {
1304 return svn_error_createf(1, NULL, _("Unable to store properties for %s (%d)\n"), node->path, ret);
1305 }
1306 return SVN_NO_ERROR;
1307 }
1308
1309
1310 /* Subversion delta editor callback */
de_absent_file(const char * path,void * parent_baton,apr_pool_t * pool)1311 static svn_error_t *de_absent_file(const char *path, void *parent_baton, apr_pool_t *pool)
1312 {
1313 de_node_baton_t *parent = (de_node_baton_t *)parent_baton;
1314 path = session_obfuscate(parent->de_baton->session, pool, path);
1315 DEBUG_MSG("absent_file(%s)\n", path);
1316 return SVN_NO_ERROR;
1317 }
1318
1319
1320 /* Subversion delta editor callback */
de_close_edit(void * edit_baton,apr_pool_t * pool)1321 static svn_error_t *de_close_edit(void *edit_baton, apr_pool_t *pool)
1322 {
1323 de_baton_t *de_baton = (de_baton_t *)edit_baton;
1324 apr_hash_index_t *hi;
1325 svn_error_t *err;
1326
1327 /* Recursively dump all nodes touched by this revision */
1328 if ((err = delta_dump_node_recursive((de_node_baton_t *)de_baton->root_node))) {
1329 return err;
1330 }
1331
1332 /*
1333 * There are probably some deleted nodes that haven't been dumped yet.
1334 * This will happen if nodes whose parent is a copy destination have been
1335 * deleted. In this case, the delta editor won't touch them.
1336 * However, if the parent has been copied from outside the session prefix,
1337 * it should have been added manually. We must not dump the deleted nodes then.
1338 */
1339 for (hi = apr_hash_first(pool, de_baton->log_revision->changed_paths); hi; hi = apr_hash_next(hi)) {
1340 const char *path;
1341 svn_log_changed_path_t *log;
1342 apr_hash_this(hi, (const void **)&path, NULL, (void **)&log);
1343 DEBUG_MSG("Checking %s (%c)\n", path, log->action);
1344 if (log->action == 'D') {
1345 char *filename, *parent, skip = 0;
1346
1347 /* We can unlink a possible temporary file now */
1348 filename = rhash_get(delta_hash, path, APR_HASH_KEY_STRING);
1349 if (filename) {
1350 #ifndef DUMP_DEBUG
1351 DEBUG_MSG("de_close_edit(): Removing %s\n", filename);
1352 if (apr_file_remove(filename, pool) != APR_SUCCESS) {
1353 DEBUG_MSG("de_close_edit(): Cannot remove file %s\n", filename);
1354 }
1355 #endif
1356 rhash_set(delta_hash, path, APR_HASH_KEY_STRING, NULL, 0);
1357 }
1358
1359 /* Already dumped? */
1360 if (apr_hash_get(de_baton->dumped_entries, path, APR_HASH_KEY_STRING) != NULL) {
1361 continue;
1362 }
1363
1364 /*
1365 * If this file is part of a deleted tree (that has been dumped as being
1366 * deleted) or part of a manually copied tree, we can safely ignore it.
1367 */
1368 parent = svn_path_dirname(path, pool);
1369 while (parent && *parent) {
1370 de_node_baton_t *pnode = apr_hash_get(de_baton->dumped_entries, parent, APR_HASH_KEY_STRING);
1371 if (pnode && pnode->action == 'D') {
1372 DEBUG_MSG("de_close_edit(): Parent %s of %s already deleted, ignoring\n", parent, path);
1373 skip = 1;
1374 break;
1375 }
1376 if (pnode && (pnode->cp_info & CPI_FAILED)) {
1377 DEBUG_MSG("de_close_edit(): Parent %s of %s is part of a failed copy, ignoring\n", parent, path);
1378 skip = 1;
1379 break;
1380 }
1381
1382 parent = svn_path_dirname(parent, pool);
1383 }
1384
1385 if (!skip) {
1386 de_node_baton_t *node = delta_create_node_no_parent(path, de_baton, de_baton->revision_pool);
1387 node->kind = svn_node_none; /* Does not matter for deleted nodes */
1388 node->action = log->action;
1389 node->dump_needed = 1;
1390
1391 DEBUG_MSG("Post-dumping %s\n", path);
1392 if ((err = delta_dump_node(node))) {
1393 return err;
1394 }
1395 }
1396 }
1397 }
1398
1399 #ifdef USE_TIMING
1400 DEBUG_MSG("apply_text_delta: %f seconds\n", tm_de_apply_textdelta);
1401 #endif
1402 return SVN_NO_ERROR;
1403 }
1404
1405
1406 /* Subversion delta editor callback */
de_abort_edit(void * edit_baton,apr_pool_t * pool)1407 static svn_error_t *de_abort_edit(void *edit_baton, apr_pool_t *pool)
1408 {
1409 #ifdef DEBUG
1410 DEBUG_MSG("abort_edit()\n");
1411 #endif
1412 return SVN_NO_ERROR;
1413 }
1414
1415
1416 /*---------------------------------------------------------------------------*/
1417 /* Global functions */
1418 /*---------------------------------------------------------------------------*/
1419
1420
1421 /* Determines the local copyfrom_path (returns NULL if it can't be reached) */
delta_get_local_copyfrom_path(const char * prefix,const char * path)1422 const char *delta_get_local_copyfrom_path(const char *prefix, const char *path)
1423 {
1424 int preflen = strlen(prefix);
1425
1426 if (strncmp(prefix, path, preflen)) {
1427 return NULL;
1428 }
1429
1430 while (*(path + preflen) == '/') {
1431 ++preflen;
1432 }
1433 return (path + preflen);
1434 }
1435
1436
1437 /* Determines the local copyfrom_revision number */
delta_get_local_copyfrom_rev(svn_revnum_t original,dump_options_t * opts,apr_array_header_t * logs,svn_revnum_t local_revnum)1438 svn_revnum_t delta_get_local_copyfrom_rev(svn_revnum_t original, dump_options_t *opts, apr_array_header_t *logs, svn_revnum_t local_revnum)
1439 {
1440 svn_revnum_t rev;
1441
1442 /* If we sync the revision numbers, the original one is correct */
1443 if (opts->flags & DF_KEEP_REVNUMS) {
1444 return original;
1445 }
1446
1447 DEBUG_MSG("local_revnum = %ld\n", local_revnum);
1448
1449 /*
1450 * Loop backwards through the revision list, starting at the previous
1451 * revision ,in order to find the best matching revision for the copy.
1452 * NOTE: This algorithm assumes that list indexes are equal to their
1453 * respective local revision numbers. This is ensured in dump()
1454 */
1455 rev = logs->nelts-1;
1456 while (--rev >= 0) {
1457 log_revision_t *logrev = &APR_ARRAY_IDX(logs, rev, log_revision_t);;
1458 DEBUG_MSG("node->copyfrom = %ld, logrev->revision = %ld, rev = %ld\n", original, logrev->revision, rev);
1459 if (original == logrev->revision) {
1460 /* This is ideal, there's an exact match */
1461 DEBUG_MSG("-> equal, using %ld\n", rev);
1462 break;
1463 } else if (logrev->revision < original) {
1464 /* The revision in question has not been dumped as we've just
1465 missed it. Therefore, simply use this revision (since
1466 node->copyfrom_revision has not been dumped, the node contents
1467 haven't changed between this revision and
1468 node->copyfrom_revision) */
1469 DEBUG_MSG("-> smaller , using %ld\n", rev);
1470 break;
1471 }
1472 }
1473
1474 return rev;
1475 }
1476
1477
1478 /* Sets up a delta editor for dumping a revision */
delta_setup_editor(delta_editor_info_t * info,log_revision_t * log_revision,svn_revnum_t local_revnum,svn_delta_editor_t ** editor,void ** editor_baton,apr_pool_t * pool)1479 void delta_setup_editor(delta_editor_info_t *info, log_revision_t *log_revision, svn_revnum_t local_revnum, svn_delta_editor_t **editor, void **editor_baton, apr_pool_t *pool)
1480 {
1481 de_baton_t *baton;
1482
1483 *editor = svn_delta_default_editor(pool);
1484 (*editor)->set_target_revision = de_set_target_revision;
1485 (*editor)->open_root = de_open_root;
1486 (*editor)->delete_entry = de_delete_entry;
1487 (*editor)->add_directory = de_add_directory;
1488 (*editor)->open_directory = de_open_directory;
1489 (*editor)->add_file = de_add_file;
1490 (*editor)->open_file = de_open_file;
1491 (*editor)->apply_textdelta = de_apply_textdelta;
1492 (*editor)->close_file = de_close_file;
1493 (*editor)->close_directory = de_close_directory;
1494 (*editor)->change_file_prop = de_change_file_prop;
1495 (*editor)->change_dir_prop = de_change_dir_prop;
1496 (*editor)->close_edit = de_close_edit;
1497 (*editor)->absent_directory = de_absent_directory;
1498 (*editor)->absent_file = de_absent_file;
1499 (*editor)->abort_edit = de_abort_edit;
1500
1501 baton = apr_palloc(pool, sizeof(de_baton_t));
1502 baton->session = info->session;
1503 baton->opts = info->options;
1504 baton->logs = info->logs;
1505 baton->log_revision = log_revision;
1506 baton->local_revnum = local_revnum;
1507 baton->revision_pool = svn_pool_create(pool);
1508 baton->dumped_entries = apr_hash_make(baton->revision_pool);
1509 baton->path_repo = info->path_repo;
1510 baton->prop_store = info->property_storage;
1511 *editor_baton = baton;
1512
1513 /* Create global hashes if needed */
1514 if (!hashes_created) {
1515 apr_pool_t *hash_pool = svn_pool_create(info->session->pool);
1516
1517 md5_hash = rhash_make(hash_pool);
1518 delta_hash = rhash_make(hash_pool);
1519
1520 hashes_created = 1;
1521 }
1522 }
1523
1524
1525 /* Cleans up global resources */
delta_cleanup()1526 void delta_cleanup()
1527 {
1528 if (hashes_created) {
1529 rhash_clear(md5_hash);
1530 rhash_clear(delta_hash);
1531
1532 hashes_created = 0;
1533 }
1534 }
1535