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 * ====================================================================
23 #include <apr_pools.h>
24 #include <apr_strings.h>
27 #include "svn_error.h"
28 #include "svn_string.h"
30 #include "svn_pools.h"
39 #include "adm_files.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.
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
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. ***/
188 svn_xml_parser_t
*parser
;
189 svn_boolean_t entries_modified
;
190 svn_boolean_t wcprops_modified
;
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. */
206 /*** Forward declarations ***/
208 /* log runner forward declaration used in log_do_merge */
210 run_log_from_memory(svn_wc_adm_access_t
*adm_access
,
214 const char *diff3_cmd
,
221 /*** The XML handlers. ***/
223 /* Used by file_xfer_under_path(). */
224 enum svn_wc__xfer_action
{
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.
252 file_xfer_under_path(svn_wc_adm_access_t
*adm_access
,
255 const char *versioned
,
256 enum svn_wc__xfer_action action
,
257 svn_boolean_t special_only
,
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
,
266 full_dest_path
= svn_path_join(svn_wc_adm_access_path(adm_access
), dest
,
269 full_versioned_path
= svn_path_join(svn_wc_adm_access_path(adm_access
),
272 full_versioned_path
= NULL
; /* Silence GCC uninitialised warning */
276 case svn_wc__xfer_append
:
277 err
= svn_io_append_file(full_from_path
, full_dest_path
, pool
);
280 if (! rerun
|| ! APR_STATUS_IS_ENOENT(err
->apr_err
))
282 svn_error_clear(err
);
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
;
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
,
302 err
= svn_wc__get_keywords(&keywords
, full_versioned_path
,
303 adm_access
, NULL
, pool
);
305 err
= svn_wc__get_special(&special
, full_versioned_path
, adm_access
,
309 err
= svn_subst_copy_and_translate3
310 (full_from_path
, full_dest_path
,
318 if (! rerun
|| ! APR_STATUS_IS_ENOENT(err
->apr_err
))
320 svn_error_clear(err
);
323 SVN_ERR(svn_wc__maybe_set_read_only(NULL
, full_dest_path
,
326 SVN_ERR(svn_wc__maybe_set_executable(NULL
, full_dest_path
,
331 case svn_wc__xfer_cp_and_detranslate
:
333 const char *tmp_file
;
335 SVN_ERR(svn_wc_translated_file2
338 versioned
? full_versioned_path
: full_from_path
, adm_access
,
339 SVN_WC_TRANSLATE_TO_NF
340 | SVN_WC_TRANSLATE_FORCE_COPY
,
342 SVN_ERR(svn_io_file_rename(tmp_file
, full_dest_path
, pool
));
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. */
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
);
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
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
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.
386 install_committed_file(svn_boolean_t
*overwrote_working
,
387 svn_wc_adm_access_t
*adm_access
,
389 svn_boolean_t remove_executable
,
390 svn_boolean_t remove_read_only
,
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
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
,
431 filepath
, adm_access
,
432 SVN_WC_TRANSLATE_FROM_NF
,
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
,
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. */
461 SVN_ERR(svn_io_set_file_executable(filepath
,
462 FALSE
, /* chmod -x */
464 *overwrote_working
= TRUE
; /* entry needs wc-file's timestamp */
468 /* Set the working file's execute bit if props dictate. */
469 SVN_ERR(svn_wc__maybe_set_executable(&did_set
, filepath
,
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. */
481 SVN_ERR(svn_io_set_file_read_write(filepath
, FALSE
, pool
));
482 *overwrote_working
= TRUE
; /* entry needs wc-file's timestamp */
486 SVN_ERR(svn_wc__maybe_set_read_only(&did_set
, filepath
,
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
));
502 /* Sometimes, documentation would only confuse matters. */
504 pick_error_code(struct log_runner
*loggy
)
506 if (loggy
->count
<= 1)
507 return SVN_ERR_WC_BAD_ADM_LOG_START
;
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), \
527 /*** Dispatch on the xml opening tag. ***/
530 log_do_merge(struct log_runner
*loggy
,
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
);
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
);
543 return svn_error_createf(pick_error_code(loggy
), NULL
,
544 _("Missing 'left' attribute in '%s'"),
546 (svn_wc_adm_access_path(loggy
->adm_access
),
548 right
= svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_2
, atts
);
550 return svn_error_createf(pick_error_code(loggy
), NULL
,
551 _("Missing 'right' attribute in '%s'"),
553 (svn_wc_adm_access_path(loggy
->adm_access
),
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
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
,
566 right
= svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
), right
,
568 name
= svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
), name
,
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
);
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
);
600 log_do_file_xfer(struct log_runner
*loggy
,
602 enum svn_wc__xfer_action action
,
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
);
613 svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_1
, atts
) != NULL
;
615 svn_xml_get_attr_value(SVN_WC__LOG_ATTR_ARG_2
, atts
);
618 return svn_error_createf(pick_error_code(loggy
), NULL
,
619 _("Missing 'dest' attribute in '%s'"),
621 (svn_wc_adm_access_path(loggy
->adm_access
),
624 err
= file_xfer_under_path(loggy
->adm_access
, name
, dest
, versioned
,
625 action
, special_only
, loggy
->rerun
, loggy
->pool
);
627 SIGNAL_ERROR(loggy
, err
);
632 /* Make file NAME in log's CWD readonly */
634 log_do_file_readonly(struct log_runner
*loggy
,
638 const char *full_path
639 = svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
), name
,
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
);
652 /* Maybe make file NAME in log's CWD executable */
654 log_do_file_maybe_executable(struct log_runner
*loggy
,
657 const char *full_path
658 = svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
), name
,
661 SVN_ERR(svn_wc__maybe_set_executable(NULL
, full_path
, loggy
->adm_access
,
667 /* Maybe make file NAME in log's CWD readonly */
669 log_do_file_maybe_readonly(struct log_runner
*loggy
,
672 const char *full_path
673 = svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
), name
,
676 SVN_ERR(svn_wc__maybe_set_read_only(NULL
, full_path
, loggy
->adm_access
,
682 /* Set file NAME in log's CWD to timestamp value in ATTS. */
684 log_do_file_timestamp(struct log_runner
*loggy
,
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
,
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'"),
702 (svn_wc_adm_access_path(loggy
->adm_access
),
705 /* Do not set the timestamp on special files. */
706 SVN_ERR(svn_io_check_special_path(full_path
, &kind
, &is_special
,
711 SVN_ERR(svn_time_from_cstring(×tamp
, timestamp_string
,
714 SVN_ERR(svn_io_set_file_affected_time(timestamp
, full_path
,
722 /* Remove file NAME in log's CWD. */
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
),
731 svn_io_remove_file(full_path
, loggy
->pool
);
733 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
735 svn_error_clear(err
);
746 log_do_modify_entry(struct log_runner
*loggy
,
751 apr_hash_t
*ah
= svn_xml_make_att_hash(atts
, loggy
->pool
);
753 svn_wc_entry_t
*entry
;
754 apr_uint64_t modify_flags
;
755 const char *valuestr
;
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
;
763 = svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
), name
,
765 SVN_ERR(svn_wc_entry(&existing
, path
, loggy
->adm_access
, TRUE
,
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
: "",
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. */
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
);
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
;
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
)))
823 const svn_wc_entry_t
*tfile_entry
;
825 err
= svn_wc_entry(&tfile_entry
, tfile
, loggy
->adm_access
,
829 SIGNAL_ERROR(loggy
, err
);
834 err
= svn_io_stat(&finfo
, tfile
, APR_FINFO_MIN
| APR_FINFO_LINK
,
836 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
838 svn_error_clear(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
);
861 return svn_error_createf(pick_error_code(loggy
), err
,
862 _("Error modifying entry for '%s'"), name
);
863 loggy
->entries_modified
= TRUE
;
869 log_do_delete_lock(struct log_runner
*loggy
,
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
,
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
,
887 return svn_error_createf(pick_error_code(loggy
), err
,
888 _("Error removing lock from entry for '%s'"),
890 loggy
->entries_modified
= TRUE
;
896 log_do_delete_changelist(struct log_runner
*loggy
,
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
,
907 SVN_WC__ENTRY_MODIFY_CHANGELIST
,
910 return svn_error_createf(pick_error_code(loggy
), err
,
911 _("Error removing changelist from entry '%s'"),
913 loggy
->entries_modified
= TRUE
;
918 /* Ben sez: this log command is (at the moment) only executed by the
919 update editor. It attempts to forcefully remove working data. */
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
,
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
,
933 SVN_ERR(svn_wc_entry(&entry
, full_path
, adm_access
, FALSE
, loggy
->pool
));
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. */
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
);
957 if (err
->apr_err
== SVN_ERR_WC_NOT_LOCKED
)
961 svn_error_clear(err
);
964 if (entry
->schedule
!= svn_wc_schedule_add
)
966 SVN_ERR(svn_wc_entries_read(&entries
, loggy
->adm_access
,
968 svn_wc__entry_remove(entries
, name
);
969 SVN_ERR(svn_wc__entries_write(entries
, loggy
->adm_access
,
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
,
986 FALSE
, /* instant_error */
991 else if (entry
->kind
== svn_node_file
)
993 err
= svn_wc_remove_from_revision_control(loggy
->adm_access
, name
,
995 FALSE
, /* instant_error */
1000 if (err
&& err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
)
1002 svn_error_clear(err
);
1003 return SVN_NO_ERROR
;
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
;
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. */
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
),
1038 base_name
= SVN_WC_ENTRY_THIS_DIR
;
1039 SVN_ERR(svn_wc_adm_retrieve(&entry_access
, loggy
->adm_access
,
1043 /* ### We pass NULL, NULL for cancel_func and cancel_baton below.
1044 ### If they were available, it would be nice to use them. */
1046 SVN_ERR(svn_wc_remove_from_revision_control
1047 (entry_access
, base_name
, FALSE
, FALSE
,
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
,
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
;
1075 svn_boolean_t prop_mods
;
1077 /* Determine the actual full path of the affected item. */
1079 full_path
= svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
),
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. */
1088 return svn_error_createf(pick_error_code(loggy
), NULL
,
1089 _("Missing 'revision' attribute for '%s'"),
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
,
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
1104 if (loggy
->rerun
&& (! orig_entry
1105 || (orig_entry
->schedule
== svn_wc_schedule_normal
1106 && orig_entry
->deleted
)))
1107 return SVN_NO_ERROR
;
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. */
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
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
,
1145 loggy
->entries_modified
= TRUE
;
1147 /* Drop the 'killme' file. */
1148 err
= svn_wc__make_killme(loggy
->adm_access
, entry
->keep_local
,
1153 if (loggy
->rerun
&& APR_STATUS_IS_EEXIST(err
->apr_err
))
1154 svn_error_clear(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. */
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
,
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
),
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
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
,
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
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
));
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. */
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
,
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
;
1293 /* The working copy file hasn't been overwritten, meaning
1294 we need to decide which timestamp to use. */
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
,
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
));
1313 /* Verify that the working file is the same as the base file
1314 by comparing file sizes, then timestamps and the contents
1317 /*###FIXME: if the file needs translation, don't compare
1318 file-sizes, just compare timestamps and do the rest of the
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
,
1325 basef
, FALSE
, pool
);
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
;
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
1370 ? SVN_WC__ENTRY_MODIFY_TEXT_TIME
1372 | SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS
1373 | SVN_WC__ENTRY_MODIFY_WORKING_SIZE
1374 | SVN_WC__ENTRY_MODIFY_FORCE
),
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. */
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
),
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
,
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,
1412 unassociated
= TRUE
;
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
),
1426 return svn_error_createf(pick_error_code(loggy
), err
,
1427 _("Error modifying entry of '%s'"), name
);
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
,
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
);
1450 path
= svn_path_join(svn_wc_adm_access_path(loggy
->adm_access
),
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
);
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
,
1474 const char *fmtstr
= svn_xml_get_attr_value(SVN_WC__LOG_ATTR_FORMAT
, atts
);
1476 const char *path
= svn_wc__adm_path(svn_wc_adm_access_path(loggy
->adm_access
),
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
1492 svn_wc__adm_set_wc_format(loggy
->adm_access
, fmt
);
1494 return SVN_NO_ERROR
;
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 */
1512 else if (! name
&& strcmp(eltname
, SVN_WC__LOG_UPGRADE_FORMAT
) != 0)
1515 (loggy
, svn_error_createf
1516 (pick_error_code(loggy
), NULL
,
1517 _("Log entry missing 'name' attribute (entry '%s' "
1518 "for directory '%s')"),
1520 svn_path_local_style(svn_wc_adm_access_path(loggy
->adm_access
),
1525 /* Increment the top-level element count before processing any commands. */
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
);
1586 (loggy
, svn_error_createf
1587 (pick_error_code(loggy
), NULL
,
1588 _("Unrecognized logfile element '%s' in '%s'"),
1590 svn_path_local_style(svn_wc_adm_access_path(loggy
->adm_access
),
1597 (loggy
, svn_error_createf
1598 (pick_error_code(loggy
), err
,
1599 _("Error processing command '%s' in '%s'"),
1601 svn_path_local_style(svn_wc_adm_access_path(loggy
->adm_access
),
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
,
1617 const svn_wc_entry_t
*thisdir_entry
, *parent_entry
;
1618 svn_wc_entry_t tmp_entry
;
1620 SVN_ERR(svn_wc_entry(&thisdir_entry
,
1621 svn_wc_adm_access_path(adm_access
), adm_access
,
1624 /* Blow away the administrative directories, and possibly the working
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
,
1632 if (err
&& err
->apr_err
!= SVN_ERR_WC_LEFT_LOCAL_MOD
)
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
,
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. */
1666 svn_wc__logfile_path(int log_number
,
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
,
1684 svn_boolean_t rerun
,
1685 const char *diff3_cmd
,
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";
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
,
1701 loggy
->entries_modified
= FALSE
;
1702 loggy
->wcprops_modified
= FALSE
;
1703 loggy
->rerun
= rerun
;
1704 loggy
->diff3_cmd
= diff3_cmd
;
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
,
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
);
1733 apr_file_t
*f
= NULL
;
1734 const char *logfile_path
;
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";
1745 /* #define RERUN_LOG_FILES to test that rerunning log files works */
1746 #ifdef RERUN_LOG_FILES
1747 int rerun_counter
= 2;
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
;
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
);
1774 if (APR_STATUS_IS_ENOENT(err
->apr_err
))
1776 svn_error_clear(err
);
1781 SVN_ERR_W(err
, _("Couldn't open log"));
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
1792 _("Error reading administrative log file in '%s'"),
1793 svn_path_local_style(svn_wc_adm_access_path(adm_access
),
1796 err2
= svn_xml_parse(parser
, buf
, buf_len
, 0);
1799 svn_error_clear(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
1816 if (--rerun_counter
)
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
));
1834 SVN_ERR(handle_killme(adm_access
, kill_adm_only
, NULL
, NULL
, pool
));
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
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
;
1856 svn_wc__run_log(svn_wc_adm_access_t
*adm_access
,
1857 const char *diff3_cmd
,
1860 return run_log(adm_access
, FALSE
, diff3_cmd
, pool
);
1864 svn_wc__rerun_log(svn_wc_adm_access_t
*adm_access
,
1865 const char *diff3_cmd
,
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
,
1895 svn_node_kind_t kind
;
1896 const char *full_src
= svn_path_join(svn_wc_adm_access_path(adm_access
),
1898 const char *full_dst
= svn_path_join(svn_wc_adm_access_path(adm_access
),
1901 SVN_ERR(svn_io_check_path(full_src
, &kind
, pool
));
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
,
1912 SVN_WC__LOG_ATTR_NAME
,
1914 SVN_WC__LOG_ATTR_DEST
,
1916 SVN_WC__LOG_ATTR_ARG_1
,
1917 special_only
? "true" : NULL
,
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
));
1928 *dst_modified
= TRUE
;
1931 return SVN_NO_ERROR
;
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
;
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
,
1956 svn_xml_make_open_tag(log_accum
,
1958 svn_xml_self_closing
,
1960 SVN_WC__LOG_ATTR_NAME
,
1961 loggy_path(src
, adm_access
),
1962 SVN_WC__LOG_ATTR_DEST
,
1963 loggy_path(dst
, adm_access
),
1966 return SVN_NO_ERROR
;
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
,
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
),
1983 return SVN_NO_ERROR
;
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
,
1995 static const char *copy_op
[] =
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
,
2007 loggy_path(src_path
, adm_access
),
2008 loggy_path(dst_path
, adm_access
), remove_dst_if_no_src
, pool
);
2012 svn_wc__loggy_translated_file(svn_stringbuf_t
**log_accum
,
2013 svn_wc_adm_access_t
*adm_access
,
2016 const char *versioned
,
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
),
2027 return SVN_NO_ERROR
;
2031 svn_wc__loggy_delete_entry(svn_stringbuf_t
**log_accum
,
2032 svn_wc_adm_access_t
*adm_access
,
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
),
2041 return SVN_NO_ERROR
;
2045 svn_wc__loggy_delete_lock(svn_stringbuf_t
**log_accum
,
2046 svn_wc_adm_access_t
*adm_access
,
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
),
2055 return SVN_NO_ERROR
;
2059 svn_wc__loggy_delete_changelist(svn_stringbuf_t
**log_accum
,
2060 svn_wc_adm_access_t
*adm_access
,
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
),
2069 return SVN_NO_ERROR
;
2073 svn_wc__loggy_entry_modify(svn_stringbuf_t
**log_accum
,
2074 svn_wc_adm_access_t
*adm_access
,
2076 svn_wc_entry_t
*entry
,
2077 apr_uint64_t modify_flags
,
2080 apr_hash_t
*prop_hash
= apr_hash_make(pool
);
2081 static const char *kind_str
[] =
2083 SVN_WC__ENTRIES_ATTR_FILE_STR
,
2084 SVN_WC__ENTRIES_ATTR_DIR_STR
,
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
,
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
,
2111 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_REPOS
,
2112 SVN_WC__ENTRY_ATTR_REPOS
,
2115 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_UUID
,
2116 SVN_WC__ENTRY_ATTR_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
,
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
,
2191 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_LOCK_TOKEN
,
2192 SVN_WC__ENTRY_ATTR_LOCK_TOKEN
,
2195 ADD_ENTRY_ATTR(SVN_WC__ENTRY_MODIFY_LOCK_OWNER
,
2196 SVN_WC__ENTRY_ATTR_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
,
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
,
2244 return SVN_NO_ERROR
;
2249 svn_wc__loggy_modify_wcprop(svn_stringbuf_t
**log_accum
,
2250 svn_wc_adm_access_t
*adm_access
,
2252 const char *propname
,
2253 const char *propval
,
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
,
2262 SVN_WC__LOG_ATTR_PROPVAL
,
2266 return SVN_NO_ERROR
;
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
,
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
);
2285 svn_wc__loggy_maybe_set_executable(svn_stringbuf_t
**log_accum
,
2286 svn_wc_adm_access_t
*adm_access
,
2290 svn_xml_make_open_tag(log_accum
,
2292 svn_xml_self_closing
,
2293 SVN_WC__LOG_MAYBE_EXECUTABLE
,
2294 SVN_WC__LOG_ATTR_NAME
, loggy_path(path
, adm_access
),
2297 return SVN_NO_ERROR
;
2301 svn_wc__loggy_maybe_set_readonly(svn_stringbuf_t
**log_accum
,
2302 svn_wc_adm_access_t
*adm_access
,
2306 svn_xml_make_open_tag(log_accum
,
2308 svn_xml_self_closing
,
2309 SVN_WC__LOG_MAYBE_READONLY
,
2310 SVN_WC__LOG_ATTR_NAME
,
2311 loggy_path(path
, adm_access
),
2314 return SVN_NO_ERROR
;
2318 svn_wc__loggy_set_entry_timestamp_from_wc(svn_stringbuf_t
**log_accum
,
2319 svn_wc_adm_access_t
*adm_access
,
2321 const char *time_prop
,
2324 svn_xml_make_open_tag(log_accum
,
2326 svn_xml_self_closing
,
2327 SVN_WC__LOG_MODIFY_ENTRY
,
2328 SVN_WC__LOG_ATTR_NAME
,
2329 loggy_path(path
, adm_access
),
2331 SVN_WC__TIMESTAMP_WC
,
2334 return SVN_NO_ERROR
;
2338 svn_wc__loggy_set_entry_working_size_from_wc(svn_stringbuf_t
**log_accum
,
2339 svn_wc_adm_access_t
*adm_access
,
2343 svn_xml_make_open_tag(log_accum
,
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
,
2353 return SVN_NO_ERROR
;
2357 svn_wc__loggy_set_readonly(svn_stringbuf_t
**log_accum
,
2358 svn_wc_adm_access_t
*adm_access
,
2362 svn_xml_make_open_tag(log_accum
,
2364 svn_xml_self_closing
,
2365 SVN_WC__LOG_READONLY
,
2366 SVN_WC__LOG_ATTR_NAME
,
2367 loggy_path(path
, adm_access
),
2370 return SVN_NO_ERROR
;
2374 svn_wc__loggy_set_timestamp(svn_stringbuf_t
**log_accum
,
2375 svn_wc_adm_access_t
*adm_access
,
2377 const char *timestr
,
2380 svn_xml_make_open_tag(log_accum
,
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
,
2390 return SVN_NO_ERROR
;
2394 svn_wc__loggy_remove(svn_stringbuf_t
**log_accum
,
2395 svn_wc_adm_access_t
*adm_access
,
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
,
2404 SVN_WC__LOG_ATTR_NAME
,
2405 loggy_path(path
, adm_access
),
2408 return SVN_NO_ERROR
;
2412 svn_wc__loggy_upgrade_format(svn_stringbuf_t
**log_accum
,
2413 svn_wc_adm_access_t
*adm_access
,
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
),
2424 return SVN_NO_ERROR
;
2429 /*** Helper to write log files ***/
2432 svn_wc__write_log(svn_wc_adm_access_t
*adm_access
,
2433 int log_number
, svn_stringbuf_t
*log_content
,
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
,
2451 return SVN_NO_ERROR
;
2455 /*** Recursively do log things. ***/
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
,
2465 return svn_wc_cleanup2(path
, diff3_cmd
, cancel_func
, cancel_baton
, pool
);
2469 svn_wc_cleanup2(const char *path
,
2470 const char *diff3_cmd
,
2471 svn_cancel_func_t cancel_func
,
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. */
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
))
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
);
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
));
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
));
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
));
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
));
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
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
;