Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_wc / log.c
blobb6eb3d9b6c10ff365331221f69871b3cb1a04e2f
1 /*
2 * log.c: handle the adm area's log file.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <string.h>
23 #include <apr_pools.h>
24 #include <apr_strings.h>
26 #include "svn_wc.h"
27 #include "svn_error.h"
28 #include "svn_string.h"
29 #include "svn_xml.h"
30 #include "svn_pools.h"
31 #include "svn_io.h"
32 #include "svn_path.h"
33 #include "svn_time.h"
34 #include "svn_iter.h"
36 #include "wc.h"
37 #include "log.h"
38 #include "props.h"
39 #include "adm_files.h"
40 #include "entries.h"
41 #include "lock.h"
42 #include "translate.h"
43 #include "questions.h"
45 #include "svn_private_config.h"
48 /*** Constant definitions for xml generation/parsing ***/
50 /* Note: every entry in the logfile is either idempotent or atomic.
51 * This allows us to remove the entire logfile when every entry in it
52 * has been completed -- if you crash in the middle of running a
53 * logfile, and then later are running over it again as part of the
54 * recovery, a given entry is "safe" in the sense that you can either
55 * tell it has already been done (in which case, ignore it) or you can
56 * do it again without ill effect.
58 * All log commands are self-closing tags with attributes.
62 /** Log actions. **/
64 /* Set some attributes on SVN_WC__LOG_ATTR_NAME's entry. Unmentioned
65 attributes are unaffected. */
66 #define SVN_WC__LOG_MODIFY_ENTRY "modify-entry"
68 /* Delete lock related fields from the entry SVN_WC__LOG_ATTR_NAME. */
69 #define SVN_WC__LOG_DELETE_LOCK "delete-lock"
71 /* Delete changelist field from the entry SVN_WC__LOG_ATTR_NAME. */
72 #define SVN_WC__LOG_DELETE_CHANGELIST "delete-changelist"
74 /* Delete the entry SVN_WC__LOG_ATTR_NAME. */
75 #define SVN_WC__LOG_DELETE_ENTRY "delete-entry"
77 /* Move file SVN_WC__LOG_ATTR_NAME to SVN_WC__LOG_ATTR_DEST. */
78 #define SVN_WC__LOG_MV "mv"
80 /* Copy file SVN_WC__LOG_ATTR_NAME to SVN_WC__LOG_ATTR_DEST. */
81 #define SVN_WC__LOG_CP "cp"
83 /* Copy file SVN_WC__LOG_ATTR_NAME to SVN_WC__LOG_ATTR_DEST, but
84 expand any keywords and use any eol-style defined by properties of
85 the DEST. */
86 #define SVN_WC__LOG_CP_AND_TRANSLATE "cp-and-translate"
88 /* Copy file SVN_WC__LOG_ATTR_NAME to SVN_WC__LOG_ATTR_DEST, but
89 contract any keywords and convert to LF eol, according to
90 properties of NAME. */
91 #define SVN_WC__LOG_CP_AND_DETRANSLATE "cp-and-detranslate"
93 /* Remove file SVN_WC__LOG_ATTR_NAME. */
94 #define SVN_WC__LOG_RM "rm"
96 /* Append file from SVN_WC__LOG_ATTR_NAME to SVN_WC__LOG_ATTR_DEST. */
97 #define SVN_WC__LOG_APPEND "append"
99 /* Make file SVN_WC__LOG_ATTR_NAME readonly */
100 #define SVN_WC__LOG_READONLY "readonly"
102 /* Make file SVN_WC__LOG_ATTR_NAME readonly if needs-lock property is set
103 and there is no lock token for the file in the working copy. */
104 #define SVN_WC__LOG_MAYBE_READONLY "maybe-readonly"
106 /* Make file SVN_WC__LOG_ATTR_NAME executable if the
107 executable property is set. */
108 #define SVN_WC__LOG_MAYBE_EXECUTABLE "maybe-executable"
110 /* Set SVN_WC__LOG_ATTR_NAME to have timestamp SVN_WC__LOG_ATTR_TIMESTAMP. */
111 #define SVN_WC__LOG_SET_TIMESTAMP "set-timestamp"
114 /* Handle closure after a commit completes successfully:
116 * If SVN/tmp/text-base/SVN_WC__LOG_ATTR_NAME exists, then
117 * compare SVN/tmp/text-base/SVN_WC__LOG_ATTR_NAME with working file
118 * if they're the same, use working file's timestamp
119 * else use SVN/tmp/text-base/SVN_WC__LOG_ATTR_NAME's timestamp
120 * set SVN_WC__LOG_ATTR_NAME's revision to N
122 #define SVN_WC__LOG_COMMITTED "committed"
124 /* On target SVN_WC__LOG_ATTR_NAME, set wc property
125 SVN_WC__LOG_ATTR_PROPNAME to value SVN_WC__LOG_ATTR_PROPVAL. If
126 SVN_WC__LOG_ATTR_PROPVAL is absent, then remove the property. */
127 #define SVN_WC__LOG_MODIFY_WCPROP "modify-wcprop"
130 /* DEPRECATED, left for compat with pre-v8 format working copies
132 A log command which runs svn_wc_merge2().
133 See its documentation for details.
135 Here is a map of entry-attributes to svn_wc_merge arguments:
137 SVN_WC__LOG_NAME : MERGE_TARGET
138 SVN_WC__LOG_ATTR_ARG_1 : LEFT
139 SVN_WC__LOG_ATTR_ARG_2 : RIGHT
140 SVN_WC__LOG_ATTR_ARG_3 : LEFT_LABEL
141 SVN_WC__LOG_ATTR_ARG_4 : RIGHT_LABEL
142 SVN_WC__LOG_ATTR_ARG_5 : TARGET_LABEL
144 Of course, the three paths should be *relative* to the directory in
145 which the log is running, as with all other log commands. (Usually
146 they're just basenames within loggy->path.)
148 #define SVN_WC__LOG_MERGE "merge"
150 /* Upgrade the WC format, both .svn/format and the format number in the
151 entries file to SVN_WC__LOG_ATTR_FORMAT. */
152 #define SVN_WC__LOG_UPGRADE_FORMAT "upgrade-format"
154 /** Log attributes. See the documentation above for log actions for
155 how these are used. **/
157 #define SVN_WC__LOG_ATTR_NAME "name"
158 #define SVN_WC__LOG_ATTR_DEST "dest"
159 #define SVN_WC__LOG_ATTR_REVISION "revision"
160 #define SVN_WC__LOG_ATTR_TIMESTAMP "timestamp"
161 #define SVN_WC__LOG_ATTR_PROPNAME "propname"
162 #define SVN_WC__LOG_ATTR_PROPVAL "propval"
164 /* This one is for SVN_WC__LOG_MERGE
165 and optionally SVN_WC__LOG_CP_AND_(DE)TRANSLATE to indicate special-only */
166 #define SVN_WC__LOG_ATTR_ARG_1 "arg1"
167 /* This one is for SVN_WC__LOG_MERGE
168 and optionally SVN_WC__LOG_CP_AND_(DE)TRANSLATE to indicate a versioned
169 path to take its translation properties from */
170 #define SVN_WC__LOG_ATTR_ARG_2 "arg2"
171 /* The rest are for SVN_WC__LOG_MERGE. Extend as necessary. */
172 #define SVN_WC__LOG_ATTR_ARG_3 "arg3"
173 #define SVN_WC__LOG_ATTR_ARG_4 "arg4"
174 #define SVN_WC__LOG_ATTR_ARG_5 "arg5"
175 /* For upgrade-format. */
176 #define SVN_WC__LOG_ATTR_FORMAT "format"
177 /* For modify-entry */
178 #define SVN_WC__LOG_ATTR_FORCE "force"
184 /*** Userdata for the callbacks. ***/
185 struct log_runner
187 apr_pool_t *pool;
188 svn_xml_parser_t *parser;
189 svn_boolean_t entries_modified;
190 svn_boolean_t wcprops_modified;
191 svn_boolean_t rerun;
192 svn_wc_adm_access_t *adm_access; /* the dir in which all this happens */
193 const char *diff3_cmd; /* external diff3 cmd, or null if none */
195 /* Which top-level log element we're on for this logfile. Some
196 callers care whether a failure happened on the first element or
197 on some later element (e.g., 'svn cleanup').
199 This is initialized to 0 when the log_runner is created, and
200 incremented every time start_handler() is called. */
201 int count;
206 /*** Forward declarations ***/
208 /* log runner forward declaration used in log_do_merge */
209 static svn_error_t *
210 run_log_from_memory(svn_wc_adm_access_t *adm_access,
211 const char *buf,
212 apr_size_t buf_len,
213 svn_boolean_t rerun,
214 const char *diff3_cmd,
215 apr_pool_t *pool);
221 /*** The XML handlers. ***/
223 /* Used by file_xfer_under_path(). */
224 enum svn_wc__xfer_action {
225 svn_wc__xfer_cp,
226 svn_wc__xfer_mv,
227 svn_wc__xfer_append,
228 svn_wc__xfer_cp_and_translate,
229 svn_wc__xfer_cp_and_detranslate
233 /* Perform some sort of copy-related ACTION on NAME and DEST:
235 svn_wc__xfer_cp: just do a copy of NAME to DEST.
236 svn_wc__xfer_mv: do a copy, then remove NAME.
237 svn_wc__xfer_append: append contents of NAME to DEST
238 svn_wc__xfer_cp_and_translate: copy NAME to DEST, doing any eol
239 and keyword expansion according to
240 the current property vals of VERSIONED
241 or, if that's NULL, those of DEST.
242 svn_wc__xfer_cp_and_detranslate: copy NAME to DEST, converting to LF
243 and contracting keywords according to
244 the current property vals of VERSIONED
245 or, if that's NULL, those of NAME.
247 When SPECIAL_ONLY is TRUE, only translate special,
248 not keywords and eol-style.
251 static svn_error_t *
252 file_xfer_under_path(svn_wc_adm_access_t *adm_access,
253 const char *name,
254 const char *dest,
255 const char *versioned,
256 enum svn_wc__xfer_action action,
257 svn_boolean_t special_only,
258 svn_boolean_t rerun,
259 apr_pool_t *pool)
261 svn_error_t *err;
262 const char *full_from_path, *full_dest_path, *full_versioned_path;
264 full_from_path = svn_path_join(svn_wc_adm_access_path(adm_access), name,
265 pool);
266 full_dest_path = svn_path_join(svn_wc_adm_access_path(adm_access), dest,
267 pool);
268 if (versioned)
269 full_versioned_path = svn_path_join(svn_wc_adm_access_path(adm_access),
270 versioned, pool);
271 else
272 full_versioned_path = NULL; /* Silence GCC uninitialised warning */
274 switch (action)
276 case svn_wc__xfer_append:
277 err = svn_io_append_file(full_from_path, full_dest_path, pool);
278 if (err)
280 if (! rerun || ! APR_STATUS_IS_ENOENT(err->apr_err))
281 return err;
282 svn_error_clear(err);
284 break;
286 case svn_wc__xfer_cp:
287 return svn_io_copy_file(full_from_path, full_dest_path, FALSE, pool);
289 case svn_wc__xfer_cp_and_translate:
291 svn_subst_eol_style_t style;
292 const char *eol;
293 apr_hash_t *keywords;
294 svn_boolean_t special;
296 if (! full_versioned_path)
297 full_versioned_path = full_dest_path;
299 err = svn_wc__get_eol_style(&style, &eol, full_versioned_path,
300 adm_access, pool);
301 if (! err)
302 err = svn_wc__get_keywords(&keywords, full_versioned_path,
303 adm_access, NULL, pool);
304 if (! err)
305 err = svn_wc__get_special(&special, full_versioned_path, adm_access,
306 pool);
308 if (! err)
309 err = svn_subst_copy_and_translate3
310 (full_from_path, full_dest_path,
311 eol, TRUE,
312 keywords, TRUE,
313 special,
314 pool);
316 if (err)
318 if (! rerun || ! APR_STATUS_IS_ENOENT(err->apr_err))
319 return err;
320 svn_error_clear(err);
323 SVN_ERR(svn_wc__maybe_set_read_only(NULL, full_dest_path,
324 adm_access, pool));
326 SVN_ERR(svn_wc__maybe_set_executable(NULL, full_dest_path,
327 adm_access, pool));
329 return SVN_NO_ERROR;
331 case svn_wc__xfer_cp_and_detranslate:
333 const char *tmp_file;
335 SVN_ERR(svn_wc_translated_file2
336 (&tmp_file,
337 full_from_path,
338 versioned ? full_versioned_path : full_from_path, adm_access,
339 SVN_WC_TRANSLATE_TO_NF
340 | SVN_WC_TRANSLATE_FORCE_COPY,
341 pool));
342 SVN_ERR(svn_io_file_rename(tmp_file, full_dest_path, pool));
344 return SVN_NO_ERROR;
347 case svn_wc__xfer_mv:
348 err = svn_io_file_rename(full_from_path,
349 full_dest_path, pool);
351 /* If we got an ENOENT, that's ok; the move has probably
352 already completed in an earlier run of this log. */
353 if (err)
355 if (! rerun || ! APR_STATUS_IS_ENOENT(err->apr_err))
356 return svn_error_quick_wrap(err, _("Can't move source to dest"));
357 svn_error_clear(err);
361 return SVN_NO_ERROR;
365 /* If new text was committed, then replace the text base for
366 * newly-committed file NAME in directory PATH with the new
367 * post-commit text base, which is waiting in the adm tmp area in
368 * detranslated form.
370 * If eol and/or keyword translation would cause the working file to
371 * change, then overwrite the working file with a translated copy of
372 * the new text base (but only if the translated copy differs from the
373 * current working file -- if they are the same, do nothing, to avoid
374 * clobbering timestamps unnecessarily).
376 * If the executable property is set, the set working file's
377 * executable.
379 * If the working file was re-translated or had executability set,
380 * then set OVERWROTE_WORKING to TRUE. If the working file isn't
381 * touched at all, then set to FALSE.
383 * Use POOL for any temporary allocation.
385 static svn_error_t *
386 install_committed_file(svn_boolean_t *overwrote_working,
387 svn_wc_adm_access_t *adm_access,
388 const char *name,
389 svn_boolean_t remove_executable,
390 svn_boolean_t remove_read_only,
391 apr_pool_t *pool)
393 const char *filepath;
394 const char *tmp_text_base;
395 svn_node_kind_t kind;
396 svn_boolean_t same, did_set;
397 const char *tmp_wfile;
398 svn_boolean_t special;
400 /* start off assuming that the working file isn't touched. */
401 *overwrote_working = FALSE;
403 filepath = svn_path_join(svn_wc_adm_access_path(adm_access), name, pool);
405 /* In the commit, newlines and keywords may have been
406 * canonicalized and/or contracted... Or they may not have
407 * been. It's kind of hard to know. Here's how we find out:
409 * 1. Make a translated tmp copy of the committed text base.
410 * Or, if no committed text base exists (the commit must have
411 * been a propchange only), make a translated tmp copy of the
412 * working file.
413 * 2. Compare the translated tmpfile to the working file.
414 * 3. If different, copy the tmpfile over working file.
416 * This means we only rewrite the working file if we absolutely
417 * have to, which is good because it avoids changing the file's
418 * timestamp unless necessary, so editors aren't tempted to
419 * reread the file if they don't really need to.
422 /* Is there a tmp_text_base that needs to be installed? */
423 tmp_text_base = svn_wc__text_base_path(filepath, 1, pool);
424 SVN_ERR(svn_io_check_path(tmp_text_base, &kind, pool));
427 const char *tmp = (kind == svn_node_file) ? tmp_text_base : filepath;
429 SVN_ERR(svn_wc_translated_file2(&tmp_wfile,
430 tmp,
431 filepath, adm_access,
432 SVN_WC_TRANSLATE_FROM_NF,
433 pool));
435 /* If the translation is a no-op, the text base and the working copy
436 * file contain the same content, because we use the same props here
437 * as were used to detranslate from working file to text base.
439 * In that case: don't replace the working file, but make sure
440 * it has the right executable and read_write attributes set.
443 SVN_ERR(svn_wc__get_special(&special, filepath, adm_access, pool));
444 if (! special && tmp != tmp_wfile)
445 SVN_ERR(svn_io_files_contents_same_p(&same, tmp_wfile,
446 filepath, pool));
447 else
448 same = TRUE;
451 if (! same)
453 SVN_ERR(svn_io_file_rename(tmp_wfile, filepath, pool));
454 *overwrote_working = TRUE;
457 if (remove_executable)
459 /* No need to chmod -x on a new file: new files don't have it. */
460 if (same)
461 SVN_ERR(svn_io_set_file_executable(filepath,
462 FALSE, /* chmod -x */
463 FALSE, pool));
464 *overwrote_working = TRUE; /* entry needs wc-file's timestamp */
466 else
468 /* Set the working file's execute bit if props dictate. */
469 SVN_ERR(svn_wc__maybe_set_executable(&did_set, filepath,
470 adm_access, pool));
471 if (did_set)
472 /* okay, so we didn't -overwrite- the working file, but we changed
473 its timestamp, which is the point of returning this flag. :-) */
474 *overwrote_working = TRUE;
477 if (remove_read_only)
479 /* No need to make a new file read_write: new files already are. */
480 if (same)
481 SVN_ERR(svn_io_set_file_read_write(filepath, FALSE, pool));
482 *overwrote_working = TRUE; /* entry needs wc-file's timestamp */
484 else
486 SVN_ERR(svn_wc__maybe_set_read_only(&did_set, filepath,
487 adm_access, pool));
488 if (did_set)
489 /* okay, so we didn't -overwrite- the working file, but we changed
490 its timestamp, which is the point of returning this flag. :-) */
491 *overwrote_working = TRUE;
494 /* Install the new text base if one is waiting. */
495 if (kind == svn_node_file) /* tmp_text_base exists */
496 SVN_ERR(svn_wc__sync_text_base(filepath, pool));
498 return SVN_NO_ERROR;
502 /* Sometimes, documentation would only confuse matters. */
503 static apr_status_t
504 pick_error_code(struct log_runner *loggy)
506 if (loggy->count <= 1)
507 return SVN_ERR_WC_BAD_ADM_LOG_START;
508 else
509 return SVN_ERR_WC_BAD_ADM_LOG;
512 /* Helper macro for erroring out while running a logfile.
514 This is implemented as a macro so that the error created has a useful
515 line number associated with it. */
516 #define SIGNAL_ERROR(loggy, err) \
517 svn_xml_signal_bailout \
518 (svn_error_createf(pick_error_code(loggy), err, \
519 _("In directory '%s'"), \
520 svn_path_local_style(svn_wc_adm_access_path \
521 (loggy->adm_access), \
522 loggy->pool)), \
523 loggy->parser)
527 /*** Dispatch on the xml opening tag. ***/
529 static svn_error_t *
530 log_do_merge(struct log_runner *loggy,
531 const char *name,
532 const char **atts)
534 const char *left, *right;
535 const char *left_label, *right_label, *target_label;
536 enum svn_wc_merge_outcome_t merge_outcome;
537 svn_stringbuf_t *log_accum = svn_stringbuf_create("", loggy->pool);
538 svn_error_t *err;
540 /* NAME is the basename of our merge_target. Pull out LEFT and RIGHT. */
541 left = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_1, atts);
542 if (! left)
543 return svn_error_createf(pick_error_code(loggy), NULL,
544 _("Missing 'left' attribute in '%s'"),
545 svn_path_local_style
546 (svn_wc_adm_access_path(loggy->adm_access),
547 loggy->pool));
548 right = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_2, atts);
549 if (! right)
550 return svn_error_createf(pick_error_code(loggy), NULL,
551 _("Missing 'right' attribute in '%s'"),
552 svn_path_local_style
553 (svn_wc_adm_access_path(loggy->adm_access),
554 loggy->pool));
556 /* Grab all three labels too. If non-existent, we'll end up passing
557 NULLs to svn_wc_merge, which is fine -- it will use default
558 labels. */
559 left_label = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_3, atts);
560 right_label = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_4, atts);
561 target_label = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_5, atts);
563 /* Convert the 3 basenames into full paths. */
564 left = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), left,
565 loggy->pool);
566 right = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), right,
567 loggy->pool);
568 name = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
569 loggy->pool);
571 /* Now do the merge with our full paths. */
572 err = svn_wc__merge_internal(&log_accum, &merge_outcome,
573 left, right, name, NULL, loggy->adm_access,
574 left_label, right_label, target_label,
575 FALSE, loggy->diff3_cmd, NULL, NULL,
576 NULL, NULL, loggy->pool);
577 if (err && loggy->rerun && APR_STATUS_IS_ENOENT(err->apr_err))
579 svn_error_clear(err);
580 return SVN_NO_ERROR;
582 else
583 if (err)
584 return err;
586 err = run_log_from_memory(loggy->adm_access,
587 log_accum->data, log_accum->len,
588 loggy->rerun, loggy->diff3_cmd, loggy->pool);
589 if (err && loggy->rerun && APR_STATUS_IS_ENOENT(err->apr_err))
591 svn_error_clear(err);
592 return SVN_NO_ERROR;
594 else
595 return err;
599 static svn_error_t *
600 log_do_file_xfer(struct log_runner *loggy,
601 const char *name,
602 enum svn_wc__xfer_action action,
603 const char **atts)
605 svn_error_t *err;
606 const char *dest = NULL;
607 const char *versioned;
608 svn_boolean_t special_only;
610 /* We have the name (src), and the destination is absolutely required. */
611 dest = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_DEST, atts);
612 special_only =
613 svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_1, atts) != NULL;
614 versioned =
615 svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_2, atts);
617 if (! dest)
618 return svn_error_createf(pick_error_code(loggy), NULL,
619 _("Missing 'dest' attribute in '%s'"),
620 svn_path_local_style
621 (svn_wc_adm_access_path(loggy->adm_access),
622 loggy->pool));
624 err = file_xfer_under_path(loggy->adm_access, name, dest, versioned,
625 action, special_only, loggy->rerun, loggy->pool);
626 if (err)
627 SIGNAL_ERROR(loggy, err);
629 return SVN_NO_ERROR;
632 /* Make file NAME in log's CWD readonly */
633 static svn_error_t *
634 log_do_file_readonly(struct log_runner *loggy,
635 const char *name)
637 svn_error_t *err;
638 const char *full_path
639 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
640 loggy->pool);
642 err = svn_io_set_file_read_only(full_path, FALSE, loggy->pool);
643 if (err && loggy->rerun && APR_STATUS_IS_ENOENT(err->apr_err))
645 svn_error_clear(err);
646 return SVN_NO_ERROR;
648 else
649 return err;
652 /* Maybe make file NAME in log's CWD executable */
653 static svn_error_t *
654 log_do_file_maybe_executable(struct log_runner *loggy,
655 const char *name)
657 const char *full_path
658 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
659 loggy->pool);
661 SVN_ERR(svn_wc__maybe_set_executable(NULL, full_path, loggy->adm_access,
662 loggy->pool));
664 return SVN_NO_ERROR;
667 /* Maybe make file NAME in log's CWD readonly */
668 static svn_error_t *
669 log_do_file_maybe_readonly(struct log_runner *loggy,
670 const char *name)
672 const char *full_path
673 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
674 loggy->pool);
676 SVN_ERR(svn_wc__maybe_set_read_only(NULL, full_path, loggy->adm_access,
677 loggy->pool));
679 return SVN_NO_ERROR;
682 /* Set file NAME in log's CWD to timestamp value in ATTS. */
683 static svn_error_t *
684 log_do_file_timestamp(struct log_runner *loggy,
685 const char *name,
686 const char **atts)
688 apr_time_t timestamp;
689 svn_node_kind_t kind;
690 const char *full_path
691 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
692 loggy->pool);
694 const char *timestamp_string
695 = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_TIMESTAMP, atts);
696 svn_boolean_t is_special;
698 if (! timestamp_string)
699 return svn_error_createf(pick_error_code(loggy), NULL,
700 _("Missing 'timestamp' attribute in '%s'"),
701 svn_path_local_style
702 (svn_wc_adm_access_path(loggy->adm_access),
703 loggy->pool));
705 /* Do not set the timestamp on special files. */
706 SVN_ERR(svn_io_check_special_path(full_path, &kind, &is_special,
707 loggy->pool));
709 if (! is_special)
711 SVN_ERR(svn_time_from_cstring(&timestamp, timestamp_string,
712 loggy->pool));
714 SVN_ERR(svn_io_set_file_affected_time(timestamp, full_path,
715 loggy->pool));
718 return SVN_NO_ERROR;
722 /* Remove file NAME in log's CWD. */
723 static svn_error_t *
724 log_do_rm(struct log_runner *loggy, const char *name)
726 const char *full_path
727 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access),
728 name, loggy->pool);
730 svn_error_t *err =
731 svn_io_remove_file(full_path, loggy->pool);
733 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
735 svn_error_clear(err);
736 return SVN_NO_ERROR;
738 else
739 return err;
745 static svn_error_t *
746 log_do_modify_entry(struct log_runner *loggy,
747 const char *name,
748 const char **atts)
750 svn_error_t *err;
751 apr_hash_t *ah = svn_xml_make_att_hash(atts, loggy->pool);
752 const char *tfile;
753 svn_wc_entry_t *entry;
754 apr_uint64_t modify_flags;
755 const char *valuestr;
757 if (loggy->rerun)
759 /* When committing a delete the entry might get removed, in
760 which case we don't want to reincarnate it. */
761 const svn_wc_entry_t *existing;
762 const char *path
763 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
764 loggy->pool);
765 SVN_ERR(svn_wc_entry(&existing, path, loggy->adm_access, TRUE,
766 loggy->pool));
767 if (! existing)
768 return SVN_NO_ERROR;
771 /* Convert the attributes into an entry structure. */
772 SVN_ERR(svn_wc__atts_to_entry(&entry, &modify_flags, ah, loggy->pool));
774 /* Make TFILE the path of the thing being modified. */
775 tfile = svn_path_join(svn_wc_adm_access_path(loggy->adm_access),
776 strcmp(name, SVN_WC_ENTRY_THIS_DIR) ? name : "",
777 loggy->pool);
779 /* Did the log command give us any timestamps? There are three
780 possible scenarios here. We must check both text_time
781 and prop_time for each of the three scenarios. */
783 /* TEXT_TIME: */
784 valuestr = apr_hash_get(ah, SVN_WC__ENTRY_ATTR_TEXT_TIME,
785 APR_HASH_KEY_STRING);
787 if ((modify_flags & SVN_WC__ENTRY_MODIFY_TEXT_TIME)
788 && (! strcmp(valuestr, SVN_WC__TIMESTAMP_WC)))
790 apr_time_t text_time;
792 err = svn_io_file_affected_time(&text_time, tfile, loggy->pool);
793 if (err)
794 return svn_error_createf
795 (pick_error_code(loggy), err,
796 _("Error getting 'affected time' on '%s'"),
797 svn_path_local_style(tfile, loggy->pool));
799 entry->text_time = text_time;
802 /* PROP_TIME: */
803 valuestr = apr_hash_get(ah, SVN_WC__ENTRY_ATTR_PROP_TIME,
804 APR_HASH_KEY_STRING);
806 if ((modify_flags & SVN_WC__ENTRY_MODIFY_PROP_TIME)
807 && (! strcmp(valuestr, SVN_WC__TIMESTAMP_WC)))
809 apr_time_t prop_time;
811 SVN_ERR(svn_wc__props_last_modified(&prop_time,
812 tfile, svn_wc__props_working,
813 loggy->adm_access, loggy->pool));
814 entry->prop_time = prop_time;
817 valuestr = apr_hash_get(ah, SVN_WC__ENTRY_ATTR_WORKING_SIZE,
818 APR_HASH_KEY_STRING);
819 if ((modify_flags & SVN_WC__ENTRY_MODIFY_WORKING_SIZE)
820 && (! strcmp(valuestr, SVN_WC__WORKING_SIZE_WC)))
822 apr_finfo_t finfo;
823 const svn_wc_entry_t *tfile_entry;
825 err = svn_wc_entry(&tfile_entry, tfile, loggy->adm_access,
826 FALSE, loggy->pool);
828 if (err)
829 SIGNAL_ERROR(loggy, err);
831 if (! tfile_entry)
832 return SVN_NO_ERROR;
834 err = svn_io_stat(&finfo, tfile, APR_FINFO_MIN | APR_FINFO_LINK,
835 loggy->pool);
836 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
838 svn_error_clear(err);
839 finfo.size = 0;
841 else if (err)
842 return svn_error_createf
843 (pick_error_code(loggy), NULL,
844 _("Error getting file size on '%s'"),
845 svn_path_local_style(tfile, loggy->pool));
847 entry->working_size = finfo.size;
851 /* Handle force flag. */
852 valuestr = apr_hash_get(ah, SVN_WC__LOG_ATTR_FORCE,
853 APR_HASH_KEY_STRING);
854 if (valuestr && strcmp(valuestr, "true") == 0)
855 modify_flags |= SVN_WC__ENTRY_MODIFY_FORCE;
857 /* Now write the new entry out */
858 err = svn_wc__entry_modify(loggy->adm_access, name,
859 entry, modify_flags, FALSE, loggy->pool);
860 if (err)
861 return svn_error_createf(pick_error_code(loggy), err,
862 _("Error modifying entry for '%s'"), name);
863 loggy->entries_modified = TRUE;
865 return SVN_NO_ERROR;
868 static svn_error_t *
869 log_do_delete_lock(struct log_runner *loggy,
870 const char *name)
872 svn_error_t *err;
873 svn_wc_entry_t entry;
875 entry.lock_token = entry.lock_comment = entry.lock_owner = NULL;
876 entry.lock_creation_date = 0;
878 /* Now write the new entry out */
879 err = svn_wc__entry_modify(loggy->adm_access, name,
880 &entry,
881 SVN_WC__ENTRY_MODIFY_LOCK_TOKEN
882 | SVN_WC__ENTRY_MODIFY_LOCK_OWNER
883 | SVN_WC__ENTRY_MODIFY_LOCK_COMMENT
884 | SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE,
885 FALSE, loggy->pool);
886 if (err)
887 return svn_error_createf(pick_error_code(loggy), err,
888 _("Error removing lock from entry for '%s'"),
889 name);
890 loggy->entries_modified = TRUE;
892 return SVN_NO_ERROR;
895 static svn_error_t *
896 log_do_delete_changelist(struct log_runner *loggy,
897 const char *name)
899 svn_error_t *err;
900 svn_wc_entry_t entry;
902 entry.changelist = NULL;
904 /* Now write the new entry out */
905 err = svn_wc__entry_modify(loggy->adm_access, name,
906 &entry,
907 SVN_WC__ENTRY_MODIFY_CHANGELIST,
908 FALSE, loggy->pool);
909 if (err)
910 return svn_error_createf(pick_error_code(loggy), err,
911 _("Error removing changelist from entry '%s'"),
912 name);
913 loggy->entries_modified = TRUE;
915 return SVN_NO_ERROR;
918 /* Ben sez: this log command is (at the moment) only executed by the
919 update editor. It attempts to forcefully remove working data. */
920 static svn_error_t *
921 log_do_delete_entry(struct log_runner *loggy, const char *name)
923 svn_wc_adm_access_t *adm_access;
924 const svn_wc_entry_t *entry;
925 svn_error_t *err = SVN_NO_ERROR;
926 const char *full_path
927 = svn_path_join(svn_wc_adm_access_path(loggy->adm_access), name,
928 loggy->pool);
930 /* Figure out if 'name' is a dir or a file */
931 SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, loggy->adm_access, full_path,
932 loggy->pool));
933 SVN_ERR(svn_wc_entry(&entry, full_path, adm_access, FALSE, loggy->pool));
935 if (! entry)
936 /* Hmm....this entry is already absent from the revision control
937 system. Chances are good that this item was removed via a
938 commit from this working copy. */
939 return SVN_NO_ERROR;
941 /* Remove the object from revision control -- whether it's a
942 single file or recursive directory removal. Attempt
943 attempt to destroy all working files & dirs too.
945 ### We pass NULL, NULL for cancel_func and cancel_baton below.
946 ### If they were available, it would be nice to use them. */
947 if (entry->kind == svn_node_dir)
949 svn_wc_adm_access_t *ignored;
951 /* If we get the right kind of error, it means the directory is
952 already missing, so all we need to do is delete its entry in
953 the parent directory. */
954 err = svn_wc_adm_retrieve(&ignored, adm_access, full_path, loggy->pool);
955 if (err)
957 if (err->apr_err == SVN_ERR_WC_NOT_LOCKED)
959 apr_hash_t *entries;
961 svn_error_clear(err);
962 err = SVN_NO_ERROR;
964 if (entry->schedule != svn_wc_schedule_add)
966 SVN_ERR(svn_wc_entries_read(&entries, loggy->adm_access,
967 TRUE, loggy->pool));
968 svn_wc__entry_remove(entries, name);
969 SVN_ERR(svn_wc__entries_write(entries, loggy->adm_access,
970 loggy->pool));
973 else
975 return err;
978 else
980 /* Deleting full_path requires that any children it has are
981 also locked (issue #3039). */
982 SVN_ERR(svn_wc__adm_extend_lock_to_tree(adm_access, loggy->pool));
983 err = svn_wc_remove_from_revision_control(adm_access,
984 SVN_WC_ENTRY_THIS_DIR,
985 TRUE, /* destroy */
986 FALSE, /* instant_error */
987 NULL, NULL,
988 loggy->pool);
991 else if (entry->kind == svn_node_file)
993 err = svn_wc_remove_from_revision_control(loggy->adm_access, name,
994 TRUE, /* destroy */
995 FALSE, /* instant_error */
996 NULL, NULL,
997 loggy->pool);
1000 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
1002 svn_error_clear(err);
1003 return SVN_NO_ERROR;
1005 else
1007 return err;
1011 static svn_error_t *
1012 remove_deleted_entry(void *baton, const void *key,
1013 apr_ssize_t klen, void *val, apr_pool_t *pool)
1015 struct log_runner *loggy = baton;
1016 const char *base_name;
1017 const char *pdir;
1018 const svn_wc_entry_t *cur_entry = val;
1019 svn_wc_adm_access_t *entry_access;
1021 /* Skip each entry that isn't scheduled for deletion. */
1022 if (cur_entry->schedule != svn_wc_schedule_delete)
1023 return SVN_NO_ERROR;
1025 /* Determine what arguments to hand to our removal function,
1026 and let BASE_NAME double as an "ok" flag to run that function. */
1027 base_name = NULL;
1028 if (cur_entry->kind == svn_node_file)
1030 pdir = svn_wc_adm_access_path(loggy->adm_access);
1031 base_name = apr_pstrdup(pool, key);
1032 entry_access = loggy->adm_access;
1034 else if (cur_entry->kind == svn_node_dir)
1036 pdir = svn_path_join(svn_wc_adm_access_path(loggy->adm_access),
1037 key, pool);
1038 base_name = SVN_WC_ENTRY_THIS_DIR;
1039 SVN_ERR(svn_wc_adm_retrieve(&entry_access, loggy->adm_access,
1040 pdir, pool));
1043 /* ### We pass NULL, NULL for cancel_func and cancel_baton below.
1044 ### If they were available, it would be nice to use them. */
1045 if (base_name)
1046 SVN_ERR(svn_wc_remove_from_revision_control
1047 (entry_access, base_name, FALSE, FALSE,
1048 NULL, NULL, pool));
1050 return SVN_NO_ERROR;
1053 /* Note: assuming that svn_wc__log_commit() is what created all of
1054 the <committed...> commands, the `name' attribute will either be a
1055 file or SVN_WC_ENTRY_THIS_DIR. */
1056 static svn_error_t *
1057 log_do_committed(struct log_runner *loggy,
1058 const char *name,
1059 const char **atts)
1061 svn_error_t *err;
1062 apr_pool_t *pool = loggy->pool;
1063 int is_this_dir = (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0);
1064 const char *rev = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_REVISION, atts);
1065 svn_boolean_t wc_root, overwrote_working = FALSE, remove_executable = FALSE;
1066 svn_boolean_t set_read_write = FALSE;
1067 const char *full_path;
1068 const char *pdir, *base_name;
1069 apr_hash_t *entries;
1070 const svn_wc_entry_t *orig_entry;
1071 svn_wc_entry_t *entry;
1072 apr_time_t text_time = 0; /* By default, don't override old stamp. */
1073 svn_wc_adm_access_t *adm_access;
1074 apr_finfo_t finfo;
1075 svn_boolean_t prop_mods;
1077 /* Determine the actual full path of the affected item. */
1078 if (! is_this_dir)
1079 full_path = svn_path_join(svn_wc_adm_access_path(loggy->adm_access),
1080 name, pool);
1081 else
1082 full_path = apr_pstrdup(pool, svn_wc_adm_access_path(loggy->adm_access));
1084 /*** Perform sanity checking operations ***/
1086 /* If no new post-commit revision was given us, bail with an error. */
1087 if (! rev)
1088 return svn_error_createf(pick_error_code(loggy), NULL,
1089 _("Missing 'revision' attribute for '%s'"),
1090 name);
1092 /* Read the entry for the affected item. If we can't find the
1093 entry, or if the entry states that our item is not either "this
1094 dir" or a file kind, perhaps this isn't really the entry our log
1095 creator was expecting. */
1096 SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, loggy->adm_access, full_path,
1097 pool));
1098 SVN_ERR(svn_wc_entry(&orig_entry, full_path, adm_access, TRUE, pool));
1100 /* Cannot rerun a commit of a delete since the entry gets changed
1101 too much; if it's got as far as being in state deleted=true, or
1102 if it has been removed, then the all the processing has been
1103 done. */
1104 if (loggy->rerun && (! orig_entry
1105 || (orig_entry->schedule == svn_wc_schedule_normal
1106 && orig_entry->deleted)))
1107 return SVN_NO_ERROR;
1109 if ((! orig_entry)
1110 || ((! is_this_dir) && (orig_entry->kind != svn_node_file)))
1111 return svn_error_createf
1112 (pick_error_code(loggy), NULL,
1113 _("Log command for directory '%s' is mislocated"), name);
1115 entry = svn_wc_entry_dup(orig_entry, pool);
1117 /*** Handle the committed deletion case ***/
1119 /* If the committed item was scheduled for deletion, it needs to
1120 now be removed from revision control. Once that is accomplished,
1121 we are finished handling this item. */
1122 if (entry->schedule == svn_wc_schedule_delete)
1124 svn_revnum_t new_rev = SVN_STR_TO_REV(rev);
1126 /* If we are suppose to delete "this dir", drop a 'killme' file
1127 into my own administrative dir as a signal for svn_wc__run_log()
1128 to blow away the administrative area after it is finished
1129 processing this logfile. */
1130 if (is_this_dir)
1132 /* Bump the revision number of this_dir anyway, so that it
1133 might be higher than its parent's revnum. If it's
1134 higher, then the process that sees KILLME and destroys
1135 the directory can also place a 'deleted' dir entry in the
1136 parent. */
1137 svn_wc_entry_t tmpentry;
1138 tmpentry.revision = new_rev;
1139 tmpentry.kind = svn_node_dir;
1141 SVN_ERR(svn_wc__entry_modify
1142 (loggy->adm_access, NULL, &tmpentry,
1143 SVN_WC__ENTRY_MODIFY_REVISION | SVN_WC__ENTRY_MODIFY_KIND,
1144 FALSE, pool));
1145 loggy->entries_modified = TRUE;
1147 /* Drop the 'killme' file. */
1148 err = svn_wc__make_killme(loggy->adm_access, entry->keep_local,
1149 pool);
1151 if (err)
1153 if (loggy->rerun && APR_STATUS_IS_EEXIST(err->apr_err))
1154 svn_error_clear(err);
1155 else
1156 return err;
1159 return SVN_NO_ERROR;
1162 /* Else, we're deleting a file, and we can safely remove files
1163 from revision control without screwing something else up.
1165 ### We pass NULL, NULL for cancel_func and cancel_baton below.
1166 ### If they were available, it would be nice to use them. */
1167 else
1169 const svn_wc_entry_t *parentry;
1170 svn_wc_entry_t tmp_entry;
1172 SVN_ERR(svn_wc_remove_from_revision_control(loggy->adm_access,
1173 name, FALSE, FALSE,
1174 NULL, NULL,
1175 pool));
1177 /* If the parent entry's working rev 'lags' behind new_rev... */
1178 SVN_ERR(svn_wc_entry(&parentry,
1179 svn_wc_adm_access_path(loggy->adm_access),
1180 loggy->adm_access,
1181 TRUE, pool));
1182 if (new_rev > parentry->revision)
1184 /* ...then the parent's revision is now officially a
1185 lie; therefore, it must remember the file as being
1186 'deleted' for a while. Create a new, uninteresting
1187 ghost entry: */
1188 tmp_entry.kind = svn_node_file;
1189 tmp_entry.deleted = TRUE;
1190 tmp_entry.revision = new_rev;
1191 SVN_ERR(svn_wc__entry_modify
1192 (loggy->adm_access, name, &tmp_entry,
1193 SVN_WC__ENTRY_MODIFY_REVISION
1194 | SVN_WC__ENTRY_MODIFY_KIND
1195 | SVN_WC__ENTRY_MODIFY_DELETED,
1196 FALSE, pool));
1197 loggy->entries_modified = TRUE;
1200 return SVN_NO_ERROR;
1205 /*** Mark the committed item committed-to-date ***/
1208 /* If "this dir" has been replaced (delete + add), all its
1209 immmediate children *must* be either scheduled for deletion (they
1210 were children of "this dir" during the "delete" phase of its
1211 replacement), added (they are new children of the replaced dir),
1212 or replaced (they are new children of the replace dir that have
1213 the same names as children that were present during the "delete"
1214 phase of the replacement).
1216 Children which are added or replaced will have been reported as
1217 individual commit targets, and thus will be re-visited by
1218 log_do_committed(). Children which were marked for deletion,
1219 however, need to be outright removed from revision control. */
1220 if ((entry->schedule == svn_wc_schedule_replace) && is_this_dir)
1222 /* Loop over all children entries, look for items scheduled for
1223 deletion. */
1224 SVN_ERR(svn_wc_entries_read(&entries, loggy->adm_access, TRUE, pool));
1225 SVN_ERR(svn_iter_apr_hash(NULL, entries,
1226 remove_deleted_entry, loggy, pool));
1229 SVN_ERR(svn_wc__has_prop_mods(&prop_mods,
1230 full_path, loggy->adm_access, pool));
1231 if (prop_mods)
1233 if (entry->kind == svn_node_file)
1235 /* Examine propchanges here before installing the new
1236 propbase. If the executable prop was -deleted-, then
1237 tell install_committed_file() so.
1239 The same applies to the needs-lock property. */
1240 int i;
1241 apr_array_header_t *propchanges;
1244 SVN_ERR(svn_wc_get_prop_diffs(&propchanges, NULL,
1245 full_path, loggy->adm_access, pool));
1246 for (i = 0; i < propchanges->nelts; i++)
1248 svn_prop_t *propchange
1249 = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
1251 if ((! strcmp(propchange->name, SVN_PROP_EXECUTABLE))
1252 && (propchange->value == NULL))
1253 remove_executable = TRUE;
1254 else if ((! strcmp(propchange->name, SVN_PROP_NEEDS_LOCK))
1255 && (propchange->value == NULL))
1256 set_read_write = TRUE;
1260 SVN_ERR(svn_wc__working_props_committed(full_path, loggy->adm_access,
1261 FALSE, pool));
1264 if (entry->kind == svn_node_file)
1266 /* Install the new file, which may involve expanding keywords.
1267 A copy of this file should have been dropped into our `tmp/text-base'
1268 directory during the commit process. Part of this process
1269 involves setting the textual timestamp for this entry. We'd like
1270 to just use the timestamp of the working file, but it is possible
1271 that at some point during the commit, the real working file might
1272 have changed again. If that has happened, we'll use the
1273 timestamp of the copy of this file in `tmp/text-base' (which
1274 by then will have moved to `text-base'. */
1276 if ((err = install_committed_file
1277 (&overwrote_working, loggy->adm_access, name,
1278 remove_executable, set_read_write, pool)))
1279 return svn_error_createf
1280 (pick_error_code(loggy), err,
1281 _("Error replacing text-base of '%s'"), name);
1283 if ((err = svn_io_stat(&finfo, full_path,
1284 APR_FINFO_MIN | APR_FINFO_LINK, pool)))
1285 return svn_error_createf(pick_error_code(loggy), err,
1286 _("Error getting 'affected time' of '%s'"),
1287 svn_path_local_style(full_path, pool));
1289 if (overwrote_working)
1290 text_time = finfo.mtime;
1291 else
1293 /* The working copy file hasn't been overwritten, meaning
1294 we need to decide which timestamp to use. */
1296 const char *basef;
1297 svn_boolean_t modified = FALSE;
1298 apr_finfo_t basef_finfo;
1300 /* If the working file was overwritten (due to re-translation)
1301 or touched (due to +x / -x), then use *that* textual
1302 timestamp instead. */
1303 basef = svn_wc__text_base_path(full_path, 0, pool);
1304 err = svn_io_stat(&basef_finfo, basef, APR_FINFO_MIN | APR_FINFO_LINK,
1305 pool);
1306 if (err)
1307 return svn_error_createf
1308 (pick_error_code(loggy), err,
1309 _("Error getting 'affected time' for '%s'"),
1310 svn_path_local_style(basef, pool));
1311 else
1313 /* Verify that the working file is the same as the base file
1314 by comparing file sizes, then timestamps and the contents
1315 after that. */
1317 /*###FIXME: if the file needs translation, don't compare
1318 file-sizes, just compare timestamps and do the rest of the
1319 hokey pokey. */
1320 modified = finfo.size != basef_finfo.size;
1321 if (finfo.mtime != basef_finfo.mtime && ! modified)
1323 err = svn_wc__versioned_file_modcheck(&modified, full_path,
1324 loggy->adm_access,
1325 basef, FALSE, pool);
1326 if (err)
1327 return svn_error_createf
1328 (pick_error_code(loggy), err,
1329 _("Error comparing '%s' and '%s'"),
1330 svn_path_local_style(full_path, pool),
1331 svn_path_local_style(basef, pool));
1333 /* If they are the same, use the working file's timestamp,
1334 else use the base file's timestamp. */
1335 text_time = modified ? basef_finfo.mtime : finfo.mtime;
1339 else
1340 finfo.size = 0;
1342 /* Files have been moved, and timestamps have been found. It is now
1343 time for The Big Entry Modification. */
1344 entry->revision = SVN_STR_TO_REV(rev);
1345 entry->kind = is_this_dir ? svn_node_dir : svn_node_file;
1346 entry->schedule = svn_wc_schedule_normal;
1347 entry->copied = FALSE;
1348 entry->deleted = FALSE;
1349 entry->text_time = text_time;
1350 entry->conflict_old = NULL;
1351 entry->conflict_new = NULL;
1352 entry->conflict_wrk = NULL;
1353 entry->prejfile = NULL;
1354 entry->copyfrom_url = NULL;
1355 entry->copyfrom_rev = SVN_INVALID_REVNUM;
1356 entry->has_prop_mods = FALSE;
1357 entry->working_size = finfo.size;
1358 if ((err = svn_wc__entry_modify(loggy->adm_access, name, entry,
1359 (SVN_WC__ENTRY_MODIFY_REVISION
1360 | SVN_WC__ENTRY_MODIFY_SCHEDULE
1361 | SVN_WC__ENTRY_MODIFY_COPIED
1362 | SVN_WC__ENTRY_MODIFY_DELETED
1363 | SVN_WC__ENTRY_MODIFY_COPYFROM_URL
1364 | SVN_WC__ENTRY_MODIFY_COPYFROM_REV
1365 | SVN_WC__ENTRY_MODIFY_CONFLICT_OLD
1366 | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW
1367 | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK
1368 | SVN_WC__ENTRY_MODIFY_PREJFILE
1369 | (text_time
1370 ? SVN_WC__ENTRY_MODIFY_TEXT_TIME
1371 : 0)
1372 | SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS
1373 | SVN_WC__ENTRY_MODIFY_WORKING_SIZE
1374 | SVN_WC__ENTRY_MODIFY_FORCE),
1375 FALSE, pool)))
1376 return svn_error_createf
1377 (pick_error_code(loggy), err,
1378 _("Error modifying entry of '%s'"), name);
1379 loggy->entries_modified = TRUE;
1381 /* If we aren't looking at "this dir" (meaning we are looking at a
1382 file), we are finished. From here on out, it's all about a
1383 directory's entry in its parent. */
1384 if (! is_this_dir)
1385 return SVN_NO_ERROR;
1387 /* For directories, we also have to reset the state in the parent's
1388 entry for this directory, unless the current directory is a `WC
1389 root' (meaning, our parent directory on disk is not our parent in
1390 Version Control Land), in which case we're all finished here. */
1391 SVN_ERR(svn_wc_is_wc_root(&wc_root,
1392 svn_wc_adm_access_path(loggy->adm_access),
1393 loggy->adm_access,
1394 pool));
1395 if (wc_root)
1396 return SVN_NO_ERROR;
1398 /* Make sure our entry exists in the parent. */
1400 svn_wc_adm_access_t *paccess;
1401 svn_boolean_t unassociated = FALSE;
1403 svn_path_split(svn_wc_adm_access_path(loggy->adm_access), &pdir,
1404 &base_name, pool);
1406 err = svn_wc_adm_retrieve(&paccess, loggy->adm_access, pdir, pool);
1407 if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED))
1409 svn_error_clear(err);
1410 SVN_ERR(svn_wc_adm_open3(&paccess, NULL, pdir, TRUE, 0,
1411 NULL, NULL, pool));
1412 unassociated = TRUE;
1414 else if (err)
1415 return err;
1417 SVN_ERR(svn_wc_entries_read(&entries, paccess, FALSE, pool));
1418 if (apr_hash_get(entries, base_name, APR_HASH_KEY_STRING))
1420 if ((err = svn_wc__entry_modify(paccess, base_name, entry,
1421 (SVN_WC__ENTRY_MODIFY_SCHEDULE
1422 | SVN_WC__ENTRY_MODIFY_COPIED
1423 | SVN_WC__ENTRY_MODIFY_DELETED
1424 | SVN_WC__ENTRY_MODIFY_FORCE),
1425 TRUE, pool)))
1426 return svn_error_createf(pick_error_code(loggy), err,
1427 _("Error modifying entry of '%s'"), name);
1430 if (unassociated)
1431 SVN_ERR(svn_wc_adm_close(paccess));
1434 return SVN_NO_ERROR;
1438 /* See documentation for SVN_WC__LOG_MODIFY_WCPROP. */
1439 static svn_error_t *
1440 log_do_modify_wcprop(struct log_runner *loggy,
1441 const char *name,
1442 const char **atts)
1444 svn_string_t value;
1445 const char *propname, *propval, *path;
1447 if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0)
1448 path = svn_wc_adm_access_path(loggy->adm_access);
1449 else
1450 path = svn_path_join(svn_wc_adm_access_path(loggy->adm_access),
1451 name, loggy->pool);
1453 propname = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_PROPNAME, atts);
1454 propval = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_PROPVAL, atts);
1456 if (propval)
1458 value.data = propval;
1459 value.len = strlen(propval);
1462 SVN_ERR(svn_wc__wcprop_set(propname, propval ? &value : NULL,
1463 path, loggy->adm_access, FALSE, loggy->pool));
1465 loggy->wcprops_modified = TRUE;
1467 return SVN_NO_ERROR;
1470 static svn_error_t *
1471 log_do_upgrade_format(struct log_runner *loggy,
1472 const char **atts)
1474 const char *fmtstr = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_FORMAT, atts);
1475 int fmt;
1476 const char *path = svn_wc__adm_path(svn_wc_adm_access_path(loggy->adm_access),
1477 FALSE, loggy->pool,
1478 SVN_WC__ADM_FORMAT, NULL);
1480 if (! fmtstr || (fmt = atoi(fmtstr)) == 0)
1481 return svn_error_create(pick_error_code(loggy), NULL,
1482 _("Invalid 'format' attribute"));
1484 /* Update the .svn/format file right away. */
1485 SVN_ERR(svn_io_write_version_file(path, fmt, loggy->pool));
1487 /* The nice thing is that, just by setting this flag, the entries file will
1488 be rewritten in the desired format. */
1489 loggy->entries_modified = TRUE;
1490 /* Reading the entries file will support old formats, even if this number
1491 is updated. */
1492 svn_wc__adm_set_wc_format(loggy->adm_access, fmt);
1494 return SVN_NO_ERROR;
1498 static void
1499 start_handler(void *userData, const char *eltname, const char **atts)
1501 svn_error_t *err = SVN_NO_ERROR;
1502 struct log_runner *loggy = userData;
1504 /* Most elements use the `name' attribute, so grab it now. */
1505 const char *name = svn_xml_get_attr_value(SVN_WC__LOG_ATTR_NAME, atts);
1507 /* Clear the per-log-item pool. */
1508 svn_pool_clear(loggy->pool);
1510 if (strcmp(eltname, "wc-log") == 0) /* ignore expat pacifier */
1511 return;
1512 else if (! name && strcmp(eltname, SVN_WC__LOG_UPGRADE_FORMAT) != 0)
1514 SIGNAL_ERROR
1515 (loggy, svn_error_createf
1516 (pick_error_code(loggy), NULL,
1517 _("Log entry missing 'name' attribute (entry '%s' "
1518 "for directory '%s')"),
1519 eltname,
1520 svn_path_local_style(svn_wc_adm_access_path(loggy->adm_access),
1521 loggy->pool)));
1522 return;
1525 /* Increment the top-level element count before processing any commands. */
1526 loggy->count += 1;
1528 /* Dispatch. */
1529 if (strcmp(eltname, SVN_WC__LOG_MODIFY_ENTRY) == 0) {
1530 err = log_do_modify_entry(loggy, name, atts);
1532 else if (strcmp(eltname, SVN_WC__LOG_DELETE_LOCK) == 0) {
1533 err = log_do_delete_lock(loggy, name);
1535 else if (strcmp(eltname, SVN_WC__LOG_DELETE_CHANGELIST) == 0) {
1536 err = log_do_delete_changelist(loggy, name);
1538 else if (strcmp(eltname, SVN_WC__LOG_DELETE_ENTRY) == 0) {
1539 err = log_do_delete_entry(loggy, name);
1541 else if (strcmp(eltname, SVN_WC__LOG_COMMITTED) == 0) {
1542 err = log_do_committed(loggy, name, atts);
1544 else if (strcmp(eltname, SVN_WC__LOG_MODIFY_WCPROP) == 0) {
1545 err = log_do_modify_wcprop(loggy, name, atts);
1547 else if (strcmp(eltname, SVN_WC__LOG_RM) == 0) {
1548 err = log_do_rm(loggy, name);
1550 else if (strcmp(eltname, SVN_WC__LOG_MERGE) == 0) {
1551 err = log_do_merge(loggy, name, atts);
1553 else if (strcmp(eltname, SVN_WC__LOG_MV) == 0) {
1554 err = log_do_file_xfer(loggy, name, svn_wc__xfer_mv, atts);
1556 else if (strcmp(eltname, SVN_WC__LOG_CP) == 0) {
1557 err = log_do_file_xfer(loggy, name, svn_wc__xfer_cp, atts);
1559 else if (strcmp(eltname, SVN_WC__LOG_CP_AND_TRANSLATE) == 0) {
1560 err = log_do_file_xfer(loggy, name,svn_wc__xfer_cp_and_translate, atts);
1562 else if (strcmp(eltname, SVN_WC__LOG_CP_AND_DETRANSLATE) == 0) {
1563 err = log_do_file_xfer(loggy, name,svn_wc__xfer_cp_and_detranslate, atts);
1565 else if (strcmp(eltname, SVN_WC__LOG_APPEND) == 0) {
1566 err = log_do_file_xfer(loggy, name, svn_wc__xfer_append, atts);
1568 else if (strcmp(eltname, SVN_WC__LOG_READONLY) == 0) {
1569 err = log_do_file_readonly(loggy, name);
1571 else if (strcmp(eltname, SVN_WC__LOG_MAYBE_READONLY) == 0) {
1572 err = log_do_file_maybe_readonly(loggy, name);
1574 else if (strcmp(eltname, SVN_WC__LOG_MAYBE_EXECUTABLE) == 0) {
1575 err = log_do_file_maybe_executable(loggy, name);
1577 else if (strcmp(eltname, SVN_WC__LOG_SET_TIMESTAMP) == 0) {
1578 err = log_do_file_timestamp(loggy, name, atts);
1580 else if (strcmp(eltname, SVN_WC__LOG_UPGRADE_FORMAT) == 0) {
1581 err = log_do_upgrade_format(loggy, atts);
1583 else
1585 SIGNAL_ERROR
1586 (loggy, svn_error_createf
1587 (pick_error_code(loggy), NULL,
1588 _("Unrecognized logfile element '%s' in '%s'"),
1589 eltname,
1590 svn_path_local_style(svn_wc_adm_access_path(loggy->adm_access),
1591 loggy->pool)));
1592 return;
1595 if (err)
1596 SIGNAL_ERROR
1597 (loggy, svn_error_createf
1598 (pick_error_code(loggy), err,
1599 _("Error processing command '%s' in '%s'"),
1600 eltname,
1601 svn_path_local_style(svn_wc_adm_access_path(loggy->adm_access),
1602 loggy->pool)));
1604 return;
1607 /* Process the "KILLME" file in ADM_ACCESS: remove the administrative area
1608 for ADM_ACCESS and its children, and, if ADM_ONLY is false, also remove
1609 the contents of the working copy (leaving only locally-modified files). */
1610 static svn_error_t *
1611 handle_killme(svn_wc_adm_access_t *adm_access,
1612 svn_boolean_t adm_only,
1613 svn_cancel_func_t cancel_func,
1614 void *cancel_baton,
1615 apr_pool_t *pool)
1617 const svn_wc_entry_t *thisdir_entry, *parent_entry;
1618 svn_wc_entry_t tmp_entry;
1619 svn_error_t *err;
1620 SVN_ERR(svn_wc_entry(&thisdir_entry,
1621 svn_wc_adm_access_path(adm_access), adm_access,
1622 FALSE, pool));
1624 /* Blow away the administrative directories, and possibly the working
1625 copy tree too. */
1626 err = svn_wc_remove_from_revision_control(adm_access,
1627 SVN_WC_ENTRY_THIS_DIR,
1628 !adm_only, /* destroy */
1629 FALSE, /* no instant err */
1630 cancel_func, cancel_baton,
1631 pool);
1632 if (err && err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD)
1633 return err;
1634 svn_error_clear(err);
1636 /* If revnum of this dir is greater than parent's revnum, then
1637 recreate 'deleted' entry in parent. */
1639 const char *parent, *bname;
1640 svn_wc_adm_access_t *parent_access;
1642 svn_path_split(svn_wc_adm_access_path(adm_access), &parent, &bname, pool);
1643 SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent, pool));
1644 SVN_ERR(svn_wc_entry(&parent_entry, parent, parent_access, FALSE, pool));
1646 if (thisdir_entry->revision > parent_entry->revision)
1648 tmp_entry.kind = svn_node_dir;
1649 tmp_entry.deleted = TRUE;
1650 tmp_entry.revision = thisdir_entry->revision;
1651 SVN_ERR(svn_wc__entry_modify(parent_access, bname, &tmp_entry,
1652 SVN_WC__ENTRY_MODIFY_REVISION
1653 | SVN_WC__ENTRY_MODIFY_KIND
1654 | SVN_WC__ENTRY_MODIFY_DELETED,
1655 TRUE, pool));
1658 return SVN_NO_ERROR;
1662 /*** Using the parser to run the log file. ***/
1664 /* Determine the log file that should be used for a given number. */
1665 const char *
1666 svn_wc__logfile_path(int log_number,
1667 apr_pool_t *pool)
1669 return apr_psprintf(pool, SVN_WC__ADM_LOG "%s",
1670 (log_number == 0) ? ""
1671 : apr_psprintf(pool, ".%d", log_number));
1674 /* Run a series of log-instructions from a memory block of length BUF_LEN
1675 at BUF. RERUN and DIFF3_CMD are passed in the log baton to the
1676 log runner callbacks.
1678 Allocations are done in POOL.
1680 static svn_error_t *
1681 run_log_from_memory(svn_wc_adm_access_t *adm_access,
1682 const char *buf,
1683 apr_size_t buf_len,
1684 svn_boolean_t rerun,
1685 const char *diff3_cmd,
1686 apr_pool_t *pool)
1688 struct log_runner *loggy;
1689 svn_xml_parser_t *parser;
1690 /* kff todo: use the tag-making functions here, now. */
1691 const char *log_start
1692 = "<wc-log xmlns=\"http://subversion.tigris.org/xmlns\">\n";
1693 const char *log_end
1694 = "</wc-log>\n";
1696 loggy = apr_pcalloc(pool, sizeof(*loggy));
1697 loggy->adm_access = adm_access;
1698 loggy->pool = svn_pool_create(pool);
1699 loggy->parser = svn_xml_make_parser(loggy, start_handler,
1700 NULL, NULL, pool);
1701 loggy->entries_modified = FALSE;
1702 loggy->wcprops_modified = FALSE;
1703 loggy->rerun = rerun;
1704 loggy->diff3_cmd = diff3_cmd;
1705 loggy->count = 0;
1707 parser = loggy->parser;
1708 /* Expat wants everything wrapped in a top-level form, so start with
1709 a ghost open tag. */
1710 SVN_ERR(svn_xml_parse(parser, log_start, strlen(log_start), 0));
1712 SVN_ERR(svn_xml_parse(parser, buf, buf_len, 0));
1714 /* Pacify Expat with a pointless closing element tag. */
1715 SVN_ERR(svn_xml_parse(parser, log_end, strlen(log_end), 1));
1717 return SVN_NO_ERROR;
1721 /* Run a sequence of log files. */
1722 static svn_error_t *
1723 run_log(svn_wc_adm_access_t *adm_access,
1724 svn_boolean_t rerun,
1725 const char *diff3_cmd,
1726 apr_pool_t *pool)
1728 svn_error_t *err, *err2;
1729 svn_xml_parser_t *parser;
1730 struct log_runner *loggy = apr_pcalloc(pool, sizeof(*loggy));
1731 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
1732 apr_size_t buf_len;
1733 apr_file_t *f = NULL;
1734 const char *logfile_path;
1735 int log_number;
1736 apr_pool_t *iterpool = svn_pool_create(pool);
1737 svn_boolean_t killme, kill_adm_only;
1739 /* kff todo: use the tag-making functions here, now. */
1740 const char *log_start
1741 = "<wc-log xmlns=\"http://subversion.tigris.org/xmlns\">\n";
1742 const char *log_end
1743 = "</wc-log>\n";
1745 /* #define RERUN_LOG_FILES to test that rerunning log files works */
1746 #ifdef RERUN_LOG_FILES
1747 int rerun_counter = 2;
1748 rerun:
1749 #endif
1751 parser = svn_xml_make_parser(loggy, start_handler, NULL, NULL, pool);
1752 loggy->adm_access = adm_access;
1753 loggy->pool = svn_pool_create(pool);
1754 loggy->parser = parser;
1755 loggy->entries_modified = FALSE;
1756 loggy->wcprops_modified = FALSE;
1757 loggy->rerun = rerun;
1758 loggy->diff3_cmd = diff3_cmd;
1759 loggy->count = 0;
1761 /* Expat wants everything wrapped in a top-level form, so start with
1762 a ghost open tag. */
1763 SVN_ERR(svn_xml_parse(parser, log_start, strlen(log_start), 0));
1765 for (log_number = 0; ; log_number++)
1767 svn_pool_clear(iterpool);
1768 logfile_path = svn_wc__logfile_path(log_number, iterpool);
1769 /* Parse the log file's contents. */
1770 err = svn_wc__open_adm_file(&f, svn_wc_adm_access_path(adm_access),
1771 logfile_path, APR_READ, iterpool);
1772 if (err)
1774 if (APR_STATUS_IS_ENOENT(err->apr_err))
1776 svn_error_clear(err);
1777 break;
1779 else
1781 SVN_ERR_W(err, _("Couldn't open log"));
1785 do {
1786 buf_len = SVN__STREAM_CHUNK_SIZE;
1788 err = svn_io_file_read(f, buf, &buf_len, iterpool);
1789 if (err && !APR_STATUS_IS_EOF(err->apr_err))
1790 return svn_error_createf
1791 (err->apr_err, err,
1792 _("Error reading administrative log file in '%s'"),
1793 svn_path_local_style(svn_wc_adm_access_path(adm_access),
1794 iterpool));
1796 err2 = svn_xml_parse(parser, buf, buf_len, 0);
1797 if (err2)
1799 svn_error_clear(err);
1800 SVN_ERR(err2);
1802 } while (! err);
1804 svn_error_clear(err);
1805 SVN_ERR(svn_io_file_close(f, iterpool));
1809 /* Pacify Expat with a pointless closing element tag. */
1810 SVN_ERR(svn_xml_parse(parser, log_end, strlen(log_end), 1));
1812 svn_xml_free_parser(parser);
1814 #ifdef RERUN_LOG_FILES
1815 rerun = TRUE;
1816 if (--rerun_counter)
1817 goto rerun;
1818 #endif
1820 if (loggy->entries_modified == TRUE)
1822 apr_hash_t *entries;
1823 SVN_ERR(svn_wc_entries_read(&entries, loggy->adm_access, TRUE, pool));
1824 SVN_ERR(svn_wc__entries_write(entries, loggy->adm_access, pool));
1826 if (loggy->wcprops_modified)
1827 SVN_ERR(svn_wc__props_flush(svn_wc_adm_access_path(adm_access),
1828 svn_wc__props_wcprop, loggy->adm_access, pool));
1830 /* Check for a 'killme' file in the administrative area. */
1831 SVN_ERR(svn_wc__check_killme(adm_access, &killme, &kill_adm_only, pool));
1832 if (killme)
1834 SVN_ERR(handle_killme(adm_access, kill_adm_only, NULL, NULL, pool));
1836 else
1838 for (log_number--; log_number >= 0; log_number--)
1840 svn_pool_clear(iterpool);
1841 logfile_path = svn_wc__logfile_path(log_number, iterpool);
1843 /* No 'killme'? Remove the logfile; its commands have been
1844 executed. */
1845 SVN_ERR(svn_wc__remove_adm_file(svn_wc_adm_access_path(adm_access),
1846 iterpool, logfile_path, NULL));
1850 svn_pool_destroy(iterpool);
1852 return SVN_NO_ERROR;
1855 svn_error_t *
1856 svn_wc__run_log(svn_wc_adm_access_t *adm_access,
1857 const char *diff3_cmd,
1858 apr_pool_t *pool)
1860 return run_log(adm_access, FALSE, diff3_cmd, pool);
1863 svn_error_t *
1864 svn_wc__rerun_log(svn_wc_adm_access_t *adm_access,
1865 const char *diff3_cmd,
1866 apr_pool_t *pool)
1868 return run_log(adm_access, TRUE, diff3_cmd, pool);
1873 /*** Log file generation helpers ***/
1875 /* Extend log_accum with log operations to do MOVE_COPY_OP to SRC_PATH and
1876 * DST_PATH, removing DST_PATH if no SRC_PATH exists when
1877 * REMOVE_DST_IF_NO_SRC is true.
1879 * Sets *DST_MODIFIED (if DST_MODIFIED isn't NULL) to indicate that the
1880 * destination path has been modified after running the log:
1881 * either MOVE_COPY_OP has been executed, or DST_PATH was removed.
1883 * SRC_PATH and DST_PATH are relative to ADM_ACCESS.
1885 static svn_error_t *
1886 loggy_move_copy_internal(svn_stringbuf_t **log_accum,
1887 svn_boolean_t *dst_modified,
1888 const char *move_copy_op,
1889 svn_boolean_t special_only,
1890 svn_wc_adm_access_t *adm_access,
1891 const char *src_path, const char *dst_path,
1892 svn_boolean_t remove_dst_if_no_src,
1893 apr_pool_t *pool)
1895 svn_node_kind_t kind;
1896 const char *full_src = svn_path_join(svn_wc_adm_access_path(adm_access),
1897 src_path, pool);
1898 const char *full_dst = svn_path_join(svn_wc_adm_access_path(adm_access),
1899 dst_path, pool);
1901 SVN_ERR(svn_io_check_path(full_src, &kind, pool));
1903 if (dst_modified)
1904 *dst_modified = FALSE;
1906 /* Does this file exist? */
1907 if (kind != svn_node_none)
1909 svn_xml_make_open_tag(log_accum, pool,
1910 svn_xml_self_closing,
1911 move_copy_op,
1912 SVN_WC__LOG_ATTR_NAME,
1913 src_path,
1914 SVN_WC__LOG_ATTR_DEST,
1915 dst_path,
1916 SVN_WC__LOG_ATTR_ARG_1,
1917 special_only ? "true" : NULL,
1918 NULL);
1919 if (dst_modified)
1920 *dst_modified = TRUE;
1922 /* File doesn't exists, the caller wants dst_path to be removed. */
1923 else if (kind == svn_node_none && remove_dst_if_no_src)
1925 SVN_ERR(svn_wc__loggy_remove(log_accum, adm_access, full_dst, pool));
1927 if (dst_modified)
1928 *dst_modified = TRUE;
1931 return SVN_NO_ERROR;
1937 static const char *
1938 loggy_path(const char *path,
1939 svn_wc_adm_access_t *adm_access)
1941 const char *adm_path = svn_wc_adm_access_path(adm_access);
1942 const char *local_path = svn_path_is_child(adm_path, path, NULL);
1944 if (! local_path && strcmp(path, adm_path) == 0)
1945 local_path = SVN_WC_ENTRY_THIS_DIR;
1947 return local_path;
1950 svn_error_t *
1951 svn_wc__loggy_append(svn_stringbuf_t **log_accum,
1952 svn_wc_adm_access_t *adm_access,
1953 const char *src, const char *dst,
1954 apr_pool_t *pool)
1956 svn_xml_make_open_tag(log_accum,
1957 pool,
1958 svn_xml_self_closing,
1959 SVN_WC__LOG_APPEND,
1960 SVN_WC__LOG_ATTR_NAME,
1961 loggy_path(src, adm_access),
1962 SVN_WC__LOG_ATTR_DEST,
1963 loggy_path(dst, adm_access),
1964 NULL);
1966 return SVN_NO_ERROR;
1970 svn_error_t *
1971 svn_wc__loggy_committed(svn_stringbuf_t **log_accum,
1972 svn_wc_adm_access_t *adm_access,
1973 const char *path, svn_revnum_t revnum,
1974 apr_pool_t *pool)
1976 svn_xml_make_open_tag(log_accum, pool, svn_xml_self_closing,
1977 SVN_WC__LOG_COMMITTED,
1978 SVN_WC__LOG_ATTR_NAME, loggy_path(path, adm_access),
1979 SVN_WC__LOG_ATTR_REVISION,
1980 apr_psprintf(pool, "%ld", revnum),
1981 NULL);
1983 return SVN_NO_ERROR;
1986 svn_error_t *
1987 svn_wc__loggy_copy(svn_stringbuf_t **log_accum,
1988 svn_boolean_t *dst_modified,
1989 svn_wc_adm_access_t *adm_access,
1990 svn_wc__copy_t copy_type,
1991 const char *src_path, const char *dst_path,
1992 svn_boolean_t remove_dst_if_no_src,
1993 apr_pool_t *pool)
1995 static const char *copy_op[] =
1997 SVN_WC__LOG_CP,
1998 SVN_WC__LOG_CP_AND_TRANSLATE,
1999 SVN_WC__LOG_CP_AND_TRANSLATE,
2000 SVN_WC__LOG_CP_AND_DETRANSLATE
2003 return loggy_move_copy_internal
2004 (log_accum, dst_modified,
2005 copy_op[copy_type], copy_type == svn_wc__copy_translate_special_only,
2006 adm_access,
2007 loggy_path(src_path, adm_access),
2008 loggy_path(dst_path, adm_access), remove_dst_if_no_src, pool);
2011 svn_error_t *
2012 svn_wc__loggy_translated_file(svn_stringbuf_t **log_accum,
2013 svn_wc_adm_access_t *adm_access,
2014 const char *dst,
2015 const char *src,
2016 const char *versioned,
2017 apr_pool_t *pool)
2019 svn_xml_make_open_tag
2020 (log_accum, pool, svn_xml_self_closing,
2021 SVN_WC__LOG_CP_AND_TRANSLATE,
2022 SVN_WC__LOG_ATTR_NAME, loggy_path(src, adm_access),
2023 SVN_WC__LOG_ATTR_DEST, loggy_path(dst, adm_access),
2024 SVN_WC__LOG_ATTR_ARG_2, loggy_path(versioned, adm_access),
2025 NULL);
2027 return SVN_NO_ERROR;
2030 svn_error_t *
2031 svn_wc__loggy_delete_entry(svn_stringbuf_t **log_accum,
2032 svn_wc_adm_access_t *adm_access,
2033 const char *path,
2034 apr_pool_t *pool)
2036 svn_xml_make_open_tag(log_accum, pool, svn_xml_self_closing,
2037 SVN_WC__LOG_DELETE_ENTRY,
2038 SVN_WC__LOG_ATTR_NAME, loggy_path(path, adm_access),
2039 NULL);
2041 return SVN_NO_ERROR;
2044 svn_error_t *
2045 svn_wc__loggy_delete_lock(svn_stringbuf_t **log_accum,
2046 svn_wc_adm_access_t *adm_access,
2047 const char *path,
2048 apr_pool_t *pool)
2050 svn_xml_make_open_tag(log_accum, pool, svn_xml_self_closing,
2051 SVN_WC__LOG_DELETE_LOCK,
2052 SVN_WC__LOG_ATTR_NAME, loggy_path(path, adm_access),
2053 NULL);
2055 return SVN_NO_ERROR;
2058 svn_error_t *
2059 svn_wc__loggy_delete_changelist(svn_stringbuf_t **log_accum,
2060 svn_wc_adm_access_t *adm_access,
2061 const char *path,
2062 apr_pool_t *pool)
2064 svn_xml_make_open_tag(log_accum, pool, svn_xml_self_closing,
2065 SVN_WC__LOG_DELETE_CHANGELIST,
2066 SVN_WC__LOG_ATTR_NAME, loggy_path(path, adm_access),
2067 NULL);
2069 return SVN_NO_ERROR;
2072 svn_error_t *
2073 svn_wc__loggy_entry_modify(svn_stringbuf_t **log_accum,
2074 svn_wc_adm_access_t *adm_access,
2075 const char *name,
2076 svn_wc_entry_t *entry,
2077 apr_uint64_t modify_flags,
2078 apr_pool_t *pool)
2080 apr_hash_t *prop_hash = apr_hash_make(pool);
2081 static const char *kind_str[] =
2082 { "none",
2083 SVN_WC__ENTRIES_ATTR_FILE_STR,
2084 SVN_WC__ENTRIES_ATTR_DIR_STR,
2085 "unknown",
2087 static const char *schedule_str[] =
2089 "", /* svn_wc_schedule_normal */
2090 SVN_WC__ENTRY_VALUE_ADD,
2091 SVN_WC__ENTRY_VALUE_DELETE,
2092 SVN_WC__ENTRY_VALUE_REPLACE,
2096 if (! modify_flags)
2097 return SVN_NO_ERROR;
2099 #define ADD_ENTRY_ATTR(attr_flag, attr_name, value) \
2100 if (modify_flags & (attr_flag)) \
2101 apr_hash_set(prop_hash, (attr_name), APR_HASH_KEY_STRING, value)
2103 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_REVISION,
2104 SVN_WC__ENTRY_ATTR_REVISION,
2105 apr_psprintf(pool, "%ld", entry->revision));
2107 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_URL,
2108 SVN_WC__ENTRY_ATTR_URL,
2109 entry->url);
2111 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_REPOS,
2112 SVN_WC__ENTRY_ATTR_REPOS,
2113 entry->repos);
2115 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_UUID,
2116 SVN_WC__ENTRY_ATTR_UUID,
2117 entry->uuid);
2119 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_KIND,
2120 SVN_WC__ENTRY_ATTR_KIND,
2121 kind_str[entry->kind]);
2123 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_SCHEDULE,
2124 SVN_WC__ENTRY_ATTR_SCHEDULE,
2125 schedule_str[entry->schedule]);
2127 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_COPIED,
2128 SVN_WC__ENTRY_ATTR_COPIED,
2129 entry->copied ? "true" : "false");
2131 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_DELETED,
2132 SVN_WC__ENTRY_ATTR_DELETED,
2133 entry->deleted ? "true" : "false");
2135 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_ABSENT,
2136 SVN_WC__ENTRY_ATTR_ABSENT,
2137 entry->absent ? "true" : "false");
2139 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_INCOMPLETE,
2140 SVN_WC__ENTRY_ATTR_INCOMPLETE,
2141 entry->incomplete ? "true" : "false");
2143 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_COPYFROM_URL,
2144 SVN_WC__ENTRY_ATTR_COPYFROM_URL,
2145 entry->copyfrom_url);
2147 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_COPYFROM_REV,
2148 SVN_WC__ENTRY_ATTR_COPYFROM_REV,
2149 apr_psprintf(pool, "%ld", entry->copyfrom_rev));
2151 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CONFLICT_OLD,
2152 SVN_WC__ENTRY_ATTR_CONFLICT_OLD,
2153 entry->conflict_old ? entry->conflict_old : "");
2155 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CONFLICT_NEW,
2156 SVN_WC__ENTRY_ATTR_CONFLICT_NEW,
2157 entry->conflict_new ? entry->conflict_new : "");
2159 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CONFLICT_WRK,
2160 SVN_WC__ENTRY_ATTR_CONFLICT_WRK,
2161 entry->conflict_wrk ? entry->conflict_wrk : "");
2163 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_PREJFILE,
2164 SVN_WC__ENTRY_ATTR_PREJFILE,
2165 entry->prejfile ? entry->prejfile : "");
2167 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_TEXT_TIME,
2168 SVN_WC__ENTRY_ATTR_TEXT_TIME,
2169 svn_time_to_cstring(entry->text_time, pool));
2171 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_PROP_TIME,
2172 SVN_WC__ENTRY_ATTR_PROP_TIME,
2173 svn_time_to_cstring(entry->prop_time, pool));
2175 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CHECKSUM,
2176 SVN_WC__ENTRY_ATTR_CHECKSUM,
2177 entry->checksum);
2179 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CMT_REV,
2180 SVN_WC__ENTRY_ATTR_CMT_REV,
2181 apr_psprintf(pool, "%ld", entry->cmt_rev));
2183 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CMT_DATE,
2184 SVN_WC__ENTRY_ATTR_CMT_DATE,
2185 svn_time_to_cstring(entry->cmt_date, pool));
2187 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CMT_AUTHOR,
2188 SVN_WC__ENTRY_ATTR_CMT_AUTHOR,
2189 entry->cmt_author);
2191 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_LOCK_TOKEN,
2192 SVN_WC__ENTRY_ATTR_LOCK_TOKEN,
2193 entry->lock_token);
2195 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_LOCK_OWNER,
2196 SVN_WC__ENTRY_ATTR_LOCK_OWNER,
2197 entry->lock_owner);
2199 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_LOCK_COMMENT,
2200 SVN_WC__ENTRY_ATTR_LOCK_COMMENT,
2201 entry->lock_comment);
2203 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE,
2204 SVN_WC__ENTRY_ATTR_LOCK_CREATION_DATE,
2205 svn_time_to_cstring(entry->lock_creation_date, pool));
2207 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_HAS_PROPS,
2208 SVN_WC__ENTRY_ATTR_HAS_PROPS,
2209 entry->has_props ? "true" : "false");
2211 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS,
2212 SVN_WC__ENTRY_ATTR_HAS_PROP_MODS,
2213 entry->has_prop_mods ? "true" : "false");
2215 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_CACHABLE_PROPS,
2216 SVN_WC__ENTRY_ATTR_CACHABLE_PROPS,
2217 entry->cachable_props);
2219 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_PRESENT_PROPS,
2220 SVN_WC__ENTRY_ATTR_PRESENT_PROPS,
2221 entry->present_props);
2223 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_WORKING_SIZE,
2224 SVN_WC__ENTRY_ATTR_WORKING_SIZE,
2225 apr_psprintf(pool, "%" APR_OFF_T_FMT,
2226 entry->working_size));
2227 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_FORCE,
2228 SVN_WC__LOG_ATTR_FORCE,
2229 "true");
2231 #undef ADD_ENTRY_ATTR
2233 if (apr_hash_count(prop_hash) == 0)
2234 return SVN_NO_ERROR;
2236 apr_hash_set(prop_hash, SVN_WC__LOG_ATTR_NAME,
2237 APR_HASH_KEY_STRING, loggy_path(name, adm_access));
2239 svn_xml_make_open_tag_hash(log_accum, pool,
2240 svn_xml_self_closing,
2241 SVN_WC__LOG_MODIFY_ENTRY,
2242 prop_hash);
2244 return SVN_NO_ERROR;
2248 svn_error_t *
2249 svn_wc__loggy_modify_wcprop(svn_stringbuf_t **log_accum,
2250 svn_wc_adm_access_t *adm_access,
2251 const char *path,
2252 const char *propname,
2253 const char *propval,
2254 apr_pool_t *pool)
2256 svn_xml_make_open_tag(log_accum, pool, svn_xml_self_closing,
2257 SVN_WC__LOG_MODIFY_WCPROP,
2258 SVN_WC__LOG_ATTR_NAME,
2259 loggy_path(path, adm_access),
2260 SVN_WC__LOG_ATTR_PROPNAME,
2261 propname,
2262 SVN_WC__LOG_ATTR_PROPVAL,
2263 propval,
2264 NULL);
2266 return SVN_NO_ERROR;
2269 svn_error_t *
2270 svn_wc__loggy_move(svn_stringbuf_t **log_accum,
2271 svn_boolean_t *dst_modified,
2272 svn_wc_adm_access_t *adm_access,
2273 const char *src_path, const char *dst_path,
2274 svn_boolean_t remove_dst_if_no_src,
2275 apr_pool_t *pool)
2277 return loggy_move_copy_internal(log_accum, dst_modified,
2278 SVN_WC__LOG_MV, FALSE, adm_access,
2279 loggy_path(src_path, adm_access),
2280 loggy_path(dst_path, adm_access),
2281 remove_dst_if_no_src, pool);
2284 svn_error_t *
2285 svn_wc__loggy_maybe_set_executable(svn_stringbuf_t **log_accum,
2286 svn_wc_adm_access_t *adm_access,
2287 const char *path,
2288 apr_pool_t *pool)
2290 svn_xml_make_open_tag(log_accum,
2291 pool,
2292 svn_xml_self_closing,
2293 SVN_WC__LOG_MAYBE_EXECUTABLE,
2294 SVN_WC__LOG_ATTR_NAME, loggy_path(path, adm_access),
2295 NULL);
2297 return SVN_NO_ERROR;
2300 svn_error_t *
2301 svn_wc__loggy_maybe_set_readonly(svn_stringbuf_t **log_accum,
2302 svn_wc_adm_access_t *adm_access,
2303 const char *path,
2304 apr_pool_t *pool)
2306 svn_xml_make_open_tag(log_accum,
2307 pool,
2308 svn_xml_self_closing,
2309 SVN_WC__LOG_MAYBE_READONLY,
2310 SVN_WC__LOG_ATTR_NAME,
2311 loggy_path(path, adm_access),
2312 NULL);
2314 return SVN_NO_ERROR;
2317 svn_error_t *
2318 svn_wc__loggy_set_entry_timestamp_from_wc(svn_stringbuf_t **log_accum,
2319 svn_wc_adm_access_t *adm_access,
2320 const char *path,
2321 const char *time_prop,
2322 apr_pool_t *pool)
2324 svn_xml_make_open_tag(log_accum,
2325 pool,
2326 svn_xml_self_closing,
2327 SVN_WC__LOG_MODIFY_ENTRY,
2328 SVN_WC__LOG_ATTR_NAME,
2329 loggy_path(path, adm_access),
2330 time_prop,
2331 SVN_WC__TIMESTAMP_WC,
2332 NULL);
2334 return SVN_NO_ERROR;
2337 svn_error_t *
2338 svn_wc__loggy_set_entry_working_size_from_wc(svn_stringbuf_t **log_accum,
2339 svn_wc_adm_access_t *adm_access,
2340 const char *path,
2341 apr_pool_t *pool)
2343 svn_xml_make_open_tag(log_accum,
2344 pool,
2345 svn_xml_self_closing,
2346 SVN_WC__LOG_MODIFY_ENTRY,
2347 SVN_WC__LOG_ATTR_NAME,
2348 loggy_path(path, adm_access),
2349 SVN_WC__ENTRY_ATTR_WORKING_SIZE,
2350 SVN_WC__TIMESTAMP_WC,
2351 NULL);
2353 return SVN_NO_ERROR;
2356 svn_error_t *
2357 svn_wc__loggy_set_readonly(svn_stringbuf_t **log_accum,
2358 svn_wc_adm_access_t *adm_access,
2359 const char *path,
2360 apr_pool_t *pool)
2362 svn_xml_make_open_tag(log_accum,
2363 pool,
2364 svn_xml_self_closing,
2365 SVN_WC__LOG_READONLY,
2366 SVN_WC__LOG_ATTR_NAME,
2367 loggy_path(path, adm_access),
2368 NULL);
2370 return SVN_NO_ERROR;
2373 svn_error_t *
2374 svn_wc__loggy_set_timestamp(svn_stringbuf_t **log_accum,
2375 svn_wc_adm_access_t *adm_access,
2376 const char *path,
2377 const char *timestr,
2378 apr_pool_t *pool)
2380 svn_xml_make_open_tag(log_accum,
2381 pool,
2382 svn_xml_self_closing,
2383 SVN_WC__LOG_SET_TIMESTAMP,
2384 SVN_WC__LOG_ATTR_NAME,
2385 loggy_path(path, adm_access),
2386 SVN_WC__LOG_ATTR_TIMESTAMP,
2387 timestr,
2388 NULL);
2390 return SVN_NO_ERROR;
2393 svn_error_t *
2394 svn_wc__loggy_remove(svn_stringbuf_t **log_accum,
2395 svn_wc_adm_access_t *adm_access,
2396 const char *path,
2397 apr_pool_t *pool)
2399 /* No need to check whether BASE_NAME exists: ENOENT is ignored
2400 by the log-runner */
2401 svn_xml_make_open_tag(log_accum, pool,
2402 svn_xml_self_closing,
2403 SVN_WC__LOG_RM,
2404 SVN_WC__LOG_ATTR_NAME,
2405 loggy_path(path, adm_access),
2406 NULL);
2408 return SVN_NO_ERROR;
2411 svn_error_t *
2412 svn_wc__loggy_upgrade_format(svn_stringbuf_t **log_accum,
2413 svn_wc_adm_access_t *adm_access,
2414 int format,
2415 apr_pool_t *pool)
2417 svn_xml_make_open_tag(log_accum, pool,
2418 svn_xml_self_closing,
2419 SVN_WC__LOG_UPGRADE_FORMAT,
2420 SVN_WC__LOG_ATTR_FORMAT,
2421 apr_itoa(pool, format),
2422 NULL);
2424 return SVN_NO_ERROR;
2429 /*** Helper to write log files ***/
2431 svn_error_t *
2432 svn_wc__write_log(svn_wc_adm_access_t *adm_access,
2433 int log_number, svn_stringbuf_t *log_content,
2434 apr_pool_t *pool)
2436 apr_file_t *log_file;
2437 const char *logfile_name = svn_wc__logfile_path(log_number, pool);
2438 const char *adm_path = svn_wc_adm_access_path(adm_access);
2440 SVN_ERR(svn_wc__open_adm_file(&log_file, adm_path, logfile_name,
2441 (APR_WRITE | APR_CREATE), pool));
2443 SVN_ERR_W(svn_io_file_write_full(log_file, log_content->data,
2444 log_content->len, NULL, pool),
2445 apr_psprintf(pool, _("Error writing log for '%s'"),
2446 svn_path_local_style(logfile_name, pool)));
2448 SVN_ERR(svn_wc__close_adm_file(log_file, adm_path, logfile_name,
2449 TRUE, pool));
2451 return SVN_NO_ERROR;
2455 /*** Recursively do log things. ***/
2457 svn_error_t *
2458 svn_wc_cleanup(const char *path,
2459 svn_wc_adm_access_t *optional_adm_access,
2460 const char *diff3_cmd,
2461 svn_cancel_func_t cancel_func,
2462 void *cancel_baton,
2463 apr_pool_t *pool)
2465 return svn_wc_cleanup2(path, diff3_cmd, cancel_func, cancel_baton, pool);
2468 svn_error_t *
2469 svn_wc_cleanup2(const char *path,
2470 const char *diff3_cmd,
2471 svn_cancel_func_t cancel_func,
2472 void *cancel_baton,
2473 apr_pool_t *pool)
2475 apr_hash_t *entries = NULL;
2476 apr_hash_index_t *hi;
2477 svn_node_kind_t kind;
2478 svn_wc_adm_access_t *adm_access;
2479 svn_boolean_t cleanup;
2480 int wc_format_version;
2481 apr_pool_t *subpool;
2482 svn_boolean_t killme, kill_adm_only;
2484 /* Check cancellation; note that this catches recursive calls too. */
2485 if (cancel_func)
2486 SVN_ERR(cancel_func(cancel_baton));
2488 SVN_ERR(svn_wc_check_wc(path, &wc_format_version, pool));
2490 /* a "version" of 0 means a non-wc directory */
2491 if (wc_format_version == 0)
2492 return svn_error_createf
2493 (SVN_ERR_WC_NOT_DIRECTORY, NULL,
2494 _("'%s' is not a working copy directory"),
2495 svn_path_local_style(path, pool));
2497 /* Lock this working copy directory, or steal an existing lock */
2498 SVN_ERR(svn_wc__adm_steal_write_lock(&adm_access, NULL, path, pool));
2500 /* Recurse on versioned elements first, oddly enough. */
2501 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
2502 subpool = svn_pool_create(pool);
2503 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
2505 const void *key;
2506 void *val;
2507 const svn_wc_entry_t *entry;
2508 const char *entry_path;
2510 svn_pool_clear(subpool);
2511 apr_hash_this(hi, &key, NULL, &val);
2512 entry = val;
2513 entry_path = svn_path_join(path, key, subpool);
2515 if (entry->kind == svn_node_dir
2516 && strcmp(key, SVN_WC_ENTRY_THIS_DIR) != 0)
2518 /* Sub-directories */
2519 SVN_ERR(svn_io_check_path(entry_path, &kind, subpool));
2520 if (kind == svn_node_dir)
2521 SVN_ERR(svn_wc_cleanup2(entry_path, diff3_cmd,
2522 cancel_func, cancel_baton, subpool));
2524 else
2526 /* "." and things that are not directories, check for mods to
2527 trigger the timestamp repair mechanism. Since this rewrites
2528 the entries file for each timestamp fixed it has the potential
2529 to be slow, perhaps we need something more sophisticated? */
2530 svn_boolean_t modified;
2531 SVN_ERR(svn_wc_props_modified_p(&modified, entry_path,
2532 adm_access, subpool));
2533 if (entry->kind == svn_node_file)
2534 SVN_ERR(svn_wc_text_modified_p(&modified, entry_path, FALSE,
2535 adm_access, subpool));
2538 svn_pool_destroy(subpool);
2540 SVN_ERR(svn_wc__check_killme(adm_access, &killme, &kill_adm_only, pool));
2542 if (killme)
2544 /* A KILLME indicates that the log has already been run */
2545 SVN_ERR(handle_killme(adm_access, kill_adm_only, cancel_func,
2546 cancel_baton, pool));
2548 else
2550 /* In an attempt to maintain consistency between the decisions made in
2551 this function, and those made in the access baton lock-removal code,
2552 we use the same test as the lock-removal code. */
2553 SVN_ERR(svn_wc__adm_is_cleanup_required(&cleanup, adm_access, pool));
2554 if (cleanup)
2555 SVN_ERR(svn_wc__rerun_log(adm_access, diff3_cmd, pool));
2558 /* Cleanup the tmp area of the admin subdir, if running the log has not
2559 removed it! The logs have been run, so anything left here has no hope
2560 of being useful. */
2561 if (svn_wc__adm_path_exists(path, 0, pool, NULL))
2562 SVN_ERR(svn_wc__adm_cleanup_tmp_area(adm_access, pool));
2564 SVN_ERR(svn_wc_adm_close(adm_access));
2566 return SVN_NO_ERROR;