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