2 * props.c : routines dealing with properties in the working copy
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 * ====================================================================
24 #include <apr_pools.h>
26 #include <apr_tables.h>
27 #include <apr_file_io.h>
28 #include <apr_strings.h>
29 #include <apr_general.h>
30 #include "svn_types.h"
31 #include "svn_string.h"
32 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_props.h"
39 #include "svn_mergeinfo.h"
44 #include "private/svn_wc_private.h"
45 #include "private/svn_mergeinfo_private.h"
49 #include "adm_files.h"
52 #include "translate.h"
53 #include "questions.h"
56 #include "svn_private_config.h"
58 /*---------------------------------------------------------------------*/
60 /*** Deducing local changes to properties ***/
62 /*---------------------------------------------------------------------*/
64 /*** Reading/writing property hashes from disk ***/
66 /* The real functionality here is part of libsvn_subr, in hashdump.c.
67 But these are convenience routines for use in libsvn_wc. */
70 get_prop_path(const char **ppath
,
72 svn_wc__props_kind_t props_kind
,
73 svn_wc_adm_access_t
*adm_access
,
76 const svn_wc_entry_t
*entry
;
78 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
79 SVN_ERR(svn_wc__prop_path(ppath
, path
, entry
->kind
,
80 props_kind
, FALSE
, pool
));
85 /* If PROPFILE_PATH exists (and is a file), assume it's full of
86 properties and load this file into HASH. Otherwise, leave HASH
89 load_prop_file(const char *propfile_path
,
95 apr_file_t
*propfile
= NULL
;
97 err
= svn_io_file_open(&propfile
, propfile_path
,
98 APR_READ
| APR_BUFFERED
, APR_OS_DEFAULT
,
101 if (err
&& (APR_STATUS_IS_ENOENT(err
->apr_err
)
102 || APR_STATUS_IS_ENOTDIR(err
->apr_err
)))
104 svn_error_clear(err
);
110 SVN_ERR_W(svn_hash_read(hash
, propfile
, pool
),
111 apr_psprintf(pool
, _("Can't parse '%s'"),
112 svn_path_local_style(propfile_path
, pool
)));
114 SVN_ERR(svn_io_file_close(propfile
, pool
));
121 /* Given a HASH full of property name/values, write them to a file
122 located at PROPFILE_PATH. If WRITE_EMPTY is TRUE then writing
123 an emtpy property hash will result in an actual empty property
124 file on disk, otherwise an empty hash will result in no file
125 being written at all. */
127 save_prop_file(const char *propfile_path
,
129 svn_boolean_t write_empty
,
132 apr_file_t
*prop_tmp
;
134 SVN_ERR(svn_io_file_open(&prop_tmp
, propfile_path
,
135 (APR_WRITE
| APR_CREATE
| APR_TRUNCATE
137 APR_OS_DEFAULT
, pool
));
139 if (apr_hash_count(hash
) != 0 || write_empty
)
140 SVN_ERR_W(svn_hash_write(hash
, prop_tmp
, pool
),
142 _("Can't write property hash to '%s'"),
143 svn_path_local_style(propfile_path
, pool
)));
145 SVN_ERR(svn_io_file_close(prop_tmp
, pool
));
151 /*---------------------------------------------------------------------*/
155 /* Opens reject temporary file for FULL_PATH. */
157 open_reject_tmp_file(apr_file_t
**fp
, const char **reject_tmp_path
,
158 const char *full_path
,
159 svn_wc_adm_access_t
*adm_access
,
160 svn_boolean_t is_dir
, apr_pool_t
*pool
)
162 const char *tmp_path
;
164 /* Get path to /temporary/ local prop file */
165 SVN_ERR(svn_wc__prop_path(&tmp_path
, full_path
,
166 is_dir
? svn_node_dir
: svn_node_file
,
167 svn_wc__props_working
, TRUE
, pool
));
169 /* Reserve a .prej file based on it. */
170 SVN_ERR(svn_io_open_unique_file2(fp
, reject_tmp_path
, tmp_path
,
171 SVN_WC__PROP_REJ_EXT
,
172 svn_io_file_del_none
, pool
));
178 /* Assuming FP is a filehandle already open for appending, write
179 CONFLICT_DESCRIPTION to file, plus a trailing EOL sequence. */
181 append_prop_conflict(apr_file_t
*fp
,
182 const svn_string_t
*conflict_description
,
185 /* TODO: someday, perhaps prefix each conflict_description with a
186 timestamp or something? */
188 const char *native_text
=
189 svn_utf_cstring_from_utf8_fuzzy(conflict_description
->data
, pool
);
190 SVN_ERR(svn_io_file_write_full(fp
, native_text
, strlen(native_text
),
193 native_text
= svn_utf_cstring_from_utf8_fuzzy(APR_EOL_STR
, pool
);
194 SVN_ERR(svn_io_file_write_full(fp
, native_text
, strlen(native_text
),
201 /* Look up the entry NAME within ADM_ACCESS and see if it has a `current'
202 reject file describing a state of conflict. Set *REJECT_FILE to the
203 name of that file, or to NULL if no such file exists. */
205 get_existing_prop_reject_file(const char **reject_file
,
206 svn_wc_adm_access_t
*adm_access
,
210 const svn_wc_entry_t
*entry
;
212 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, FALSE
, pool
));
214 *reject_file
= entry
->prejfile
215 ? apr_pstrcat(pool
, svn_wc_adm_access_path(adm_access
), entry
->prejfile
)
220 /*---------------------------------------------------------------------*/
223 /* Build a space separated list of properties that are contained in
224 the hash PROPS and which we want to cache.
225 The string is allocated in POOL. */
227 build_present_props(apr_hash_t
*props
, apr_pool_t
*pool
)
229 apr_array_header_t
*cachable
;
230 svn_stringbuf_t
*present_props
= svn_stringbuf_create("", pool
);
233 if (apr_hash_count(props
) == 0)
234 return present_props
->data
;
236 cachable
= svn_cstring_split(SVN_WC__CACHABLE_PROPS
, " ", TRUE
, pool
);
237 for (i
= 0; i
< cachable
->nelts
; i
++)
239 const char *proptolookfor
= APR_ARRAY_IDX(cachable
, i
,
242 if (apr_hash_get(props
, proptolookfor
, APR_HASH_KEY_STRING
) != NULL
)
244 svn_stringbuf_appendcstr(present_props
, proptolookfor
);
245 svn_stringbuf_appendcstr(present_props
, " ");
249 /* Avoid returning a string with a trailing space. */
250 svn_stringbuf_chop(present_props
, 1);
251 return present_props
->data
;
254 /*** Loading regular properties. ***/
256 svn_wc__load_props(apr_hash_t
**base_props_p
,
257 apr_hash_t
**props_p
,
258 apr_hash_t
**revert_props_p
,
259 svn_wc_adm_access_t
*adm_access
,
263 svn_node_kind_t kind
;
264 svn_boolean_t has_propcaching
=
265 svn_wc__adm_wc_format(adm_access
) > SVN_WC__NO_PROPCACHING_VERSION
;
266 const svn_wc_entry_t
*entry
;
267 apr_hash_t
*base_props
= NULL
; /* Silence uninitialized warning. */
269 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
270 /* If there is no entry, we just return empty hashes, since the
271 property merging can use this function when there is no entry. */
275 *base_props_p
= apr_hash_make(pool
);
277 *props_p
= apr_hash_make(pool
);
279 *revert_props_p
= apr_hash_make(pool
);
284 /* We will need the base props if the user requested them, OR,
285 our WC has prop caching, the user requested working props and there are no
288 || (has_propcaching
&& ! entry
->has_prop_mods
&& entry
->has_props
))
290 const char *prop_base_path
;
292 SVN_ERR(svn_wc__prop_path(&prop_base_path
,
293 path
, kind
, svn_wc__props_base
, FALSE
, pool
));
294 base_props
= apr_hash_make(pool
);
295 SVN_ERR(load_prop_file(prop_base_path
, base_props
, pool
));
298 *base_props_p
= base_props
;
303 if (has_propcaching
&& ! entry
->has_prop_mods
&& entry
->has_props
)
304 *props_p
= apr_hash_copy(pool
, base_props
);
305 else if (! has_propcaching
|| entry
->has_props
)
307 const char *prop_path
;
309 SVN_ERR(svn_wc__prop_path(&prop_path
, path
, kind
,
310 svn_wc__props_working
, FALSE
, pool
));
311 *props_p
= apr_hash_make(pool
);
312 SVN_ERR(load_prop_file(prop_path
, *props_p
, pool
));
315 *props_p
= apr_hash_make(pool
);
320 *revert_props_p
= apr_hash_make(pool
);
322 if (entry
->schedule
== svn_wc_schedule_replace
325 const char *revert_prop_path
;
327 SVN_ERR(svn_wc__prop_path(&revert_prop_path
, path
, kind
,
328 svn_wc__props_revert
, FALSE
, pool
));
329 SVN_ERR(load_prop_file(revert_prop_path
, *revert_props_p
, pool
));
337 /*---------------------------------------------------------------------*/
339 /*** Installing new properties. ***/
341 svn_wc__install_props(svn_stringbuf_t
**log_accum
,
342 svn_wc_adm_access_t
*adm_access
,
344 apr_hash_t
*base_props
,
345 apr_hash_t
*working_props
,
346 svn_boolean_t write_base_props
,
349 const char *working_propfile_path
, *working_prop_tmp_path
;
350 apr_array_header_t
*prop_diffs
;
351 const svn_wc_entry_t
*entry
;
352 svn_wc_entry_t tmp_entry
;
353 svn_node_kind_t kind
;
354 svn_boolean_t has_propcaching
=
355 svn_wc__adm_wc_format(adm_access
) > SVN_WC__NO_PROPCACHING_VERSION
;
357 if (! svn_path_is_child(svn_wc_adm_access_path(adm_access
), path
, NULL
))
360 kind
= svn_node_file
;
362 /* Check if the props are modified. */
363 SVN_ERR(svn_prop_diffs(&prop_diffs
, working_props
, base_props
, pool
));
364 tmp_entry
.has_prop_mods
= (prop_diffs
->nelts
> 0);
365 tmp_entry
.has_props
= (apr_hash_count(working_props
) > 0);
366 tmp_entry
.cachable_props
= SVN_WC__CACHABLE_PROPS
;
367 tmp_entry
.present_props
= build_present_props(working_props
, pool
);
369 SVN_ERR(svn_wc__loggy_entry_modify(log_accum
, adm_access
,
371 SVN_WC__ENTRY_MODIFY_HAS_PROPS
372 | SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS
373 | SVN_WC__ENTRY_MODIFY_CACHABLE_PROPS
374 | SVN_WC__ENTRY_MODIFY_PRESENT_PROPS
,
378 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
382 /* Write our property hashes into temporary files. Notice that the
383 paths computed are ABSOLUTE pathnames, which is what our disk
385 SVN_ERR(svn_wc__prop_path(&working_propfile_path
, path
,
386 kind
, svn_wc__props_working
, FALSE
, pool
));
387 if (tmp_entry
.has_prop_mods
)
389 SVN_ERR(svn_wc__prop_path(&working_prop_tmp_path
, path
,
390 kind
, svn_wc__props_working
, TRUE
, pool
));
392 /* Write the working prop hash to path/.svn/tmp/props/name or
393 path/.svn/tmp/dir-props */
394 SVN_ERR(save_prop_file(working_prop_tmp_path
, working_props
,
397 /* Write log entry to move working tmp copy to real working area. */
398 SVN_ERR(svn_wc__loggy_move(log_accum
, NULL
,
400 working_prop_tmp_path
,
401 working_propfile_path
,
404 /* Make props read-only */
405 SVN_ERR(svn_wc__loggy_set_readonly(log_accum
, adm_access
,
406 working_propfile_path
, pool
));
410 /* No property modifications, remove the file instead. */
411 if (! has_propcaching
|| (entry
&& entry
->has_prop_mods
))
412 SVN_ERR(svn_wc__loggy_remove(log_accum
, adm_access
,
413 working_propfile_path
, pool
));
416 /* Repeat the above steps for the base properties if required. */
417 if (write_base_props
)
419 const char *base_propfile_path
, *base_prop_tmp_path
;
421 SVN_ERR(svn_wc__prop_path(&base_propfile_path
, path
,
422 kind
, svn_wc__props_base
, FALSE
, pool
));
424 if (apr_hash_count(base_props
) > 0)
426 SVN_ERR(svn_wc__prop_path(&base_prop_tmp_path
, path
,
427 kind
, svn_wc__props_base
, TRUE
, pool
));
429 SVN_ERR(save_prop_file(base_prop_tmp_path
, base_props
, FALSE
, pool
));
431 SVN_ERR(svn_wc__loggy_move(log_accum
, NULL
, adm_access
,
436 SVN_ERR(svn_wc__loggy_set_readonly(log_accum
, adm_access
,
437 base_propfile_path
, pool
));
441 if (! has_propcaching
|| (entry
&& entry
->has_props
))
442 SVN_ERR(svn_wc__loggy_remove(log_accum
, adm_access
,
443 base_propfile_path
, pool
));
453 svn_wc__working_props_committed(const char *path
,
454 svn_wc_adm_access_t
*adm_access
,
455 svn_boolean_t sync_entries
,
460 const svn_wc_entry_t
*entry
;
461 svn_wc_entry_t mod_entry
;
462 svn_wc_adm_access_t
*mod_access
;
465 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
467 SVN_ERR(svn_wc__prop_path(&working
, path
, entry
->kind
,
468 svn_wc__props_working
, FALSE
, pool
));
469 SVN_ERR(svn_wc__prop_path(&base
, path
, entry
->kind
,
470 svn_wc__props_base
, FALSE
, pool
));
472 /* svn_io_file_rename() retains a read-only bit, so there's no
473 need to explicitly set it. */
474 SVN_ERR(svn_io_file_rename(working
, base
, pool
));
476 SVN_ERR(svn_wc_adm_probe_retrieve(&mod_access
, adm_access
, path
, pool
));
477 mod_entry
.has_prop_mods
= FALSE
;
478 SVN_ERR(svn_wc__entry_modify(mod_access
, entry
->name
, &mod_entry
,
479 SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS
,
480 sync_entries
, pool
));
487 svn_wc__props_last_modified(apr_time_t
*mod_time
,
489 svn_wc__props_kind_t props_kind
,
490 svn_wc_adm_access_t
*adm_access
,
494 const char *props_file
;
496 SVN_ERR(get_prop_path(&props_file
, path
, props_kind
, adm_access
, pool
));
498 err
= svn_io_file_affected_time(mod_time
, props_file
, pool
);
499 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
501 svn_error_clear(err
);
507 _("Error getting 'affected time' on '%s'"),
508 svn_path_local_style(props_file
, pool
)));
514 remove_file_if_present(const char *file
, apr_pool_t
*pool
)
518 /* Try to remove the file. */
519 err
= svn_io_remove_file(file
, pool
);
521 /* Ignore file not found error. */
522 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
524 svn_error_clear(err
);
532 /* If wcprops are stored in a single file in this working copy, read that file
533 and store it in the cache of ADM_ACCESS. Use POOL for temporary
536 read_wcprops(svn_wc_adm_access_t
*adm_access
, apr_pool_t
*pool
)
539 apr_pool_t
*cache_pool
= svn_wc_adm_access_pool(adm_access
);
540 apr_hash_t
*all_wcprops
;
541 apr_hash_t
*proplist
;
542 svn_stream_t
*stream
;
545 /* If the WC format is too old, there is nothing to cache. */
546 if (svn_wc__adm_wc_format(adm_access
) <= SVN_WC__WCPROPS_MANY_FILES_VERSION
)
549 all_wcprops
= apr_hash_make(cache_pool
);
551 err
= svn_wc__open_adm_file(&file
, svn_wc_adm_access_path(adm_access
),
552 SVN_WC__ADM_ALL_WCPROPS
,
553 APR_READ
| APR_BUFFERED
, pool
);
555 /* A non-existent file means there are no props. */
556 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
558 svn_error_clear(err
);
559 svn_wc__adm_access_set_wcprops(adm_access
, all_wcprops
);
564 stream
= svn_stream_from_aprfile2(file
, TRUE
, pool
);
566 /* Read the proplist for THIS_DIR. */
567 proplist
= apr_hash_make(cache_pool
);
568 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
, cache_pool
));
569 apr_hash_set(all_wcprops
, SVN_WC_ENTRY_THIS_DIR
, APR_HASH_KEY_STRING
,
572 /* And now, the children. */
575 svn_stringbuf_t
*line
;
577 SVN_ERR(svn_stream_readline(stream
, &line
, "\n", &eof
, cache_pool
));
581 return svn_error_createf
582 (SVN_ERR_WC_CORRUPT
, NULL
,
583 _("Missing end of line in wcprops file for '%s'"),
584 svn_path_local_style(svn_wc_adm_access_path(adm_access
), pool
));
587 proplist
= apr_hash_make(cache_pool
);
588 SVN_ERR(svn_hash_read2(proplist
, stream
, SVN_HASH_TERMINATOR
,
590 apr_hash_set(all_wcprops
, line
->data
, APR_HASH_KEY_STRING
, proplist
);
593 svn_wc__adm_access_set_wcprops(adm_access
, all_wcprops
);
595 SVN_ERR(svn_wc__close_adm_file(file
, svn_wc_adm_access_path(adm_access
),
596 SVN_WC__ADM_ALL_WCPROPS
, FALSE
, pool
));
602 write_wcprops(svn_wc_adm_access_t
*adm_access
, apr_pool_t
*pool
)
604 apr_hash_t
*wcprops
= svn_wc__adm_access_wcprops(adm_access
);
606 svn_stream_t
*stream
;
607 apr_hash_t
*proplist
;
608 apr_hash_index_t
*hi
;
609 apr_pool_t
*subpool
= svn_pool_create(pool
);
610 svn_boolean_t any_props
= FALSE
;
612 /* If there are no cached wcprops, there is nothing to do. */
616 /* Check if there are any properties at all. */
617 for (hi
= apr_hash_first(pool
, wcprops
); hi
&& ! any_props
;
618 hi
= apr_hash_next(hi
))
622 apr_hash_this(hi
, NULL
, NULL
, &val
);
624 if (apr_hash_count(proplist
) > 0)
628 /* If there are no props, remove the file. */
633 err
= svn_wc__remove_adm_file(svn_wc_adm_access_path(adm_access
), pool
,
634 SVN_WC__ADM_ALL_WCPROPS
, NULL
);
635 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
637 svn_error_clear(err
);
644 SVN_ERR(svn_wc__open_adm_file(&file
, svn_wc_adm_access_path(adm_access
),
645 SVN_WC__ADM_ALL_WCPROPS
,
646 APR_WRITE
| APR_BUFFERED
, pool
));
647 stream
= svn_stream_from_aprfile2(file
, TRUE
, pool
);
649 /* First, the props for this_dir. */
650 proplist
= apr_hash_get(wcprops
, SVN_WC_ENTRY_THIS_DIR
, APR_HASH_KEY_STRING
);
652 proplist
= apr_hash_make(subpool
);
653 SVN_ERR(svn_hash_write2(proplist
, stream
, SVN_HASH_TERMINATOR
, subpool
));
655 /* Write children. */
656 for (hi
= apr_hash_first(pool
, wcprops
); hi
; hi
= apr_hash_next(hi
))
662 apr_hash_this(hi
, &key
, NULL
, &val
);
666 /* We already wrote this_dir, and writing empty hashes makes me
668 if (strcmp(SVN_WC_ENTRY_THIS_DIR
, name
) == 0
669 || apr_hash_count(proplist
) == 0)
672 svn_pool_clear(subpool
);
674 SVN_ERR(svn_stream_printf(stream
, subpool
, "%s\n", name
));
675 SVN_ERR(svn_hash_write2(proplist
, stream
, SVN_HASH_TERMINATOR
, subpool
));
678 SVN_ERR(svn_wc__close_adm_file(file
, svn_wc_adm_access_path(adm_access
),
679 SVN_WC__ADM_ALL_WCPROPS
, TRUE
, pool
));
686 svn_wc__props_flush(const char *path
,
687 svn_wc__props_kind_t props_kind
,
688 svn_wc_adm_access_t
*adm_access
,
691 if (props_kind
!= svn_wc__props_wcprop
)
695 svn_wc_adm_access_t
*prop_access
;
697 SVN_ERR(svn_wc_adm_probe_retrieve(&prop_access
, adm_access
,
699 SVN_ERR(write_wcprops(prop_access
, pool
));
707 remove_wcprops(svn_wc_adm_access_t
*adm_access
,
711 apr_hash_t
*all_wcprops
= svn_wc__adm_access_wcprops(adm_access
);
712 svn_boolean_t write_needed
= FALSE
;
716 /* There is no point in reading the props just to determine if we
717 need to rewrite them:-), so assume a write is needed if the props
718 aren't already cached. */
719 if (! all_wcprops
|| apr_hash_count(all_wcprops
) > 0)
721 svn_wc__adm_access_set_wcprops
722 (adm_access
, apr_hash_make(svn_wc_adm_access_pool(adm_access
)));
731 SVN_ERR(read_wcprops(adm_access
, pool
));
732 all_wcprops
= svn_wc__adm_access_wcprops(adm_access
);
735 wcprops
= apr_hash_get(all_wcprops
, name
, APR_HASH_KEY_STRING
);
738 if (wcprops
&& apr_hash_count(wcprops
) > 0)
740 apr_hash_set(all_wcprops
, name
, APR_HASH_KEY_STRING
, NULL
);
745 SVN_ERR(write_wcprops(adm_access
, pool
));
751 svn_wc__loggy_props_delete(svn_stringbuf_t
**log_accum
,
753 svn_wc__props_kind_t props_kind
,
754 svn_wc_adm_access_t
*adm_access
,
757 const char *props_file
;
759 if (props_kind
== svn_wc__props_wcprop
)
761 /* We use 1 file for all wcprops in a directory,
762 use a helper to remove them from that file */
764 apr_pool_t
*iterpool
= svn_pool_create(pool
);
765 apr_hash_index_t
*hi
;
767 SVN_ERR(svn_wc__wcprop_list(&props
, path
, adm_access
, pool
));
768 /* ### TODO: There's no log command to delete all wcprops
769 from a file at once. Removing all props should do it though. */
771 for (hi
= apr_hash_first(pool
, props
); hi
; hi
= apr_hash_next(hi
))
776 svn_pool_clear(iterpool
);
778 apr_hash_this(hi
, &key
, NULL
, NULL
);
781 SVN_ERR(svn_wc__loggy_modify_wcprop(log_accum
,
783 name
, NULL
, iterpool
));
786 svn_pool_destroy(iterpool
);
790 SVN_ERR(get_prop_path(&props_file
, path
, props_kind
, adm_access
, pool
));
791 SVN_ERR(svn_wc__loggy_remove(log_accum
, adm_access
, props_file
, pool
));
799 svn_wc__props_delete(const char *path
,
800 svn_wc__props_kind_t props_kind
,
801 svn_wc_adm_access_t
*adm_access
,
804 const char *props_file
;
806 if (props_kind
== svn_wc__props_wcprop
)
808 /* We use 1 file for all wcprops in a directory,
809 use a helper to remove them from that file */
811 svn_wc_adm_access_t
*path_access
;
813 SVN_ERR(svn_wc_adm_probe_retrieve(&path_access
, adm_access
,
815 SVN_ERR(remove_wcprops
817 svn_path_is_child(svn_wc_adm_access_path(path_access
),
822 SVN_ERR(get_prop_path(&props_file
, path
, props_kind
, adm_access
, pool
));
823 SVN_ERR(remove_file_if_present(props_file
, pool
));
830 svn_wc__loggy_revert_props_create(svn_stringbuf_t
**log_accum
,
832 svn_wc_adm_access_t
*adm_access
,
833 svn_boolean_t destroy_baseprops
,
836 const svn_wc_entry_t
*entry
;
837 const char *dst_rprop
;
838 const char *dst_bprop
;
839 const char *tmp_rprop
;
840 svn_node_kind_t kind
;
842 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
844 SVN_ERR(svn_wc__prop_path(&dst_rprop
, path
,
845 entry
->kind
, svn_wc__props_revert
, FALSE
, pool
));
846 SVN_ERR(svn_wc__prop_path(&tmp_rprop
, path
,
847 entry
->kind
, svn_wc__props_revert
, TRUE
, pool
));
848 SVN_ERR(svn_wc__prop_path(&dst_bprop
, path
,
849 entry
->kind
, svn_wc__props_base
, FALSE
, pool
));
851 /* If prop base exist, copy it to revert base. */
852 SVN_ERR(svn_io_check_path(dst_bprop
, &kind
, pool
));
853 if (kind
== svn_node_file
)
855 if (destroy_baseprops
)
856 SVN_ERR(svn_wc__loggy_move(log_accum
, NULL
,
857 adm_access
, dst_bprop
, dst_rprop
,
861 SVN_ERR(svn_io_copy_file(dst_bprop
, tmp_rprop
, TRUE
, pool
));
862 SVN_ERR(svn_wc__loggy_move(log_accum
, NULL
, adm_access
,
863 tmp_rprop
, dst_rprop
, FALSE
, pool
));
866 else if (kind
== svn_node_none
)
868 /* If there wasn't any prop base we still need an empty revert
869 propfile, otherwise a revert won't know that a change to the
870 props needs to be made (it'll just see no file, and do nothing).
871 So manufacture an empty propfile and force it to be written out. */
873 SVN_ERR(svn_wc__prop_path(&dst_bprop
, path
, entry
->kind
,
874 svn_wc__props_revert
, TRUE
, pool
));
876 SVN_ERR(save_prop_file(dst_bprop
, apr_hash_make(pool
), TRUE
, pool
));
878 SVN_ERR(svn_wc__loggy_move(log_accum
, NULL
,
879 adm_access
, dst_bprop
, dst_rprop
,
887 /*### Some day, when we get better log primitives,
888 we probably want to stat() less, which can be done coding
889 'calls' to functions like the one below into as a log command.*/
891 svn_wc__revert_props_create(const char *path
,
892 svn_wc_adm_access_t
*adm_access
,
893 svn_boolean_t destroy_baseprops
,
894 svn_boolean_t maybe_rerun
,
897 const svn_wc_entry_t
*entry
;
898 const char *revert_file
, *base_file
;
899 const char *tmp_revert_file
;
902 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
904 SVN_ERR(svn_wc__prop_path(&base_file
, path
, entry
->kind
, svn_wc__props_base
,
906 SVN_ERR(svn_wc__prop_path(&revert_file
, path
, entry
->kind
,
907 svn_wc__props_revert
, FALSE
, pool
));
908 SVN_ERR(svn_wc__prop_path(&tmp_revert_file
, path
, entry
->kind
,
909 svn_wc__props_revert
, TRUE
, pool
));
912 if (destroy_baseprops
)
913 err
= svn_io_file_rename(base_file
, revert_file
, pool
);
916 err
= svn_io_copy_file(base_file
, tmp_revert_file
, TRUE
, pool
);
918 SVN_ERR(svn_io_file_rename(tmp_revert_file
, revert_file
, pool
));
921 if (err
&& APR_STATUS_IS_ENOENT(err
->apr_err
))
922 /* If there's no file to move or copy, create one. */
924 svn_node_kind_t kind
= svn_node_none
;
926 svn_error_clear(err
);
929 SVN_ERR(svn_io_check_path(revert_file
, &kind
, pool
));
931 if (kind
== svn_node_none
)
933 SVN_ERR(save_prop_file(tmp_revert_file
,
934 apr_hash_make(pool
), TRUE
, pool
));
935 SVN_ERR(svn_io_file_rename(base_file
, revert_file
, pool
));
944 svn_wc__loggy_revert_props_restore(svn_stringbuf_t
**log_accum
,
946 svn_wc_adm_access_t
*adm_access
,
949 const svn_wc_entry_t
*entry
;
950 const char *revert_file
, *base_file
;
952 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
954 SVN_ERR(svn_wc__prop_path(&base_file
, path
, entry
->kind
, svn_wc__props_base
,
956 SVN_ERR(svn_wc__prop_path(&revert_file
, path
, entry
->kind
,
957 svn_wc__props_revert
, FALSE
, pool
));
959 SVN_ERR(svn_wc__loggy_move(log_accum
, NULL
, adm_access
,
960 revert_file
, base_file
, FALSE
, pool
));
966 /*### Some day, when we get better log primitives,
967 we probably want to stat() less, which can be done coding
968 'calls' to functions like the one below into as a log command.*/
970 svn_wc__revert_props_restore(const char *path
,
971 svn_wc_adm_access_t
*adm_access
,
974 const svn_wc_entry_t
*entry
;
975 const char *revert_file
, *base_file
;
977 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
979 SVN_ERR(svn_wc__prop_path(&base_file
, path
, entry
->kind
, svn_wc__props_base
,
981 SVN_ERR(svn_wc__prop_path(&revert_file
, path
, entry
->kind
,
982 svn_wc__props_revert
, FALSE
, pool
));
984 SVN_ERR(svn_io_file_rename(revert_file
, base_file
, pool
));
989 /*---------------------------------------------------------------------*/
991 /*** Merging propchanges into the working copy ***/
994 /* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
995 calculate the deltas between them. */
997 diff_mergeinfo_props(svn_mergeinfo_t
*deleted
, svn_mergeinfo_t
*added
,
998 const svn_string_t
*from_prop_val
,
999 const svn_string_t
*to_prop_val
, apr_pool_t
*pool
)
1001 if (svn_string_compare(from_prop_val
, to_prop_val
))
1003 /* Don't bothering parsing identical mergeinfo. */
1004 *deleted
= apr_hash_make(pool
);
1005 *added
= apr_hash_make(pool
);
1009 svn_mergeinfo_t from
, to
;
1010 SVN_ERR(svn_mergeinfo_parse(&from
, from_prop_val
->data
, pool
));
1011 SVN_ERR(svn_mergeinfo_parse(&to
, to_prop_val
->data
, pool
));
1012 SVN_ERR(svn_mergeinfo_diff(deleted
, added
, from
, to
,
1015 return SVN_NO_ERROR
;
1018 /* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
1019 reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
1020 been modified to combine it with incoming mergeinfo from the
1022 static svn_error_t
*
1023 combine_mergeinfo_props(const svn_string_t
**output
,
1024 const svn_string_t
*prop_val1
,
1025 const svn_string_t
*prop_val2
,
1028 svn_mergeinfo_t mergeinfo1
, mergeinfo2
;
1029 SVN_ERR(svn_mergeinfo_parse(&mergeinfo1
, prop_val1
->data
, pool
));
1030 SVN_ERR(svn_mergeinfo_parse(&mergeinfo2
, prop_val2
->data
, pool
));
1031 SVN_ERR(svn_mergeinfo_merge(mergeinfo1
, mergeinfo2
, pool
));
1032 SVN_ERR(svn_mergeinfo_to_string((svn_string_t
**)output
, mergeinfo1
, pool
));
1033 return SVN_NO_ERROR
;
1036 /* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
1037 the "base" property value, WORKING_PROP_VAL is the current value,
1038 and TO_PROP_VAL is the new value. */
1039 static svn_error_t
*
1040 combine_forked_mergeinfo_props(const svn_string_t
**output
,
1041 const svn_string_t
*from_prop_val
,
1042 const svn_string_t
*working_prop_val
,
1043 const svn_string_t
*to_prop_val
,
1046 svn_mergeinfo_t from_mergeinfo
, l_deleted
, l_added
, r_deleted
, r_added
;
1048 /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
1049 SVN_ERR(diff_mergeinfo_props(&l_deleted
, &l_added
, from_prop_val
,
1050 working_prop_val
, pool
));
1051 SVN_ERR(diff_mergeinfo_props(&r_deleted
, &r_added
, from_prop_val
,
1052 to_prop_val
, pool
));
1053 SVN_ERR(svn_mergeinfo_merge(l_deleted
, r_deleted
, pool
));
1054 SVN_ERR(svn_mergeinfo_merge(l_added
, r_added
, pool
));
1056 /* Apply the combined deltas to the base. */
1057 SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo
, from_prop_val
->data
, pool
));
1058 SVN_ERR(svn_mergeinfo_merge(from_mergeinfo
, l_added
, pool
));
1060 SVN_ERR(svn_mergeinfo_remove(&from_mergeinfo
, l_deleted
,
1061 from_mergeinfo
, pool
));
1063 return svn_mergeinfo_to_string((svn_string_t
**)output
, from_mergeinfo
, pool
);
1068 svn_wc_merge_props(svn_wc_notify_state_t
*state
,
1070 svn_wc_adm_access_t
*adm_access
,
1071 apr_hash_t
*baseprops
,
1072 const apr_array_header_t
*propchanges
,
1073 svn_boolean_t base_merge
,
1074 svn_boolean_t dry_run
,
1077 return svn_wc_merge_props2(state
, path
, adm_access
, baseprops
, propchanges
,
1078 base_merge
, dry_run
, NULL
, NULL
, pool
);
1083 svn_wc_merge_props2(svn_wc_notify_state_t
*state
,
1085 svn_wc_adm_access_t
*adm_access
,
1086 apr_hash_t
*baseprops
,
1087 const apr_array_header_t
*propchanges
,
1088 svn_boolean_t base_merge
,
1089 svn_boolean_t dry_run
,
1090 svn_wc_conflict_resolver_func_t conflict_func
,
1091 void *conflict_baton
,
1094 const svn_wc_entry_t
*entry
;
1095 svn_stringbuf_t
*log_accum
;
1097 /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
1100 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, FALSE
, pool
));
1102 /* Notice that we're not using svn_path_split_if_file(), because
1103 that looks at the actual working file. Its existence shouldn't
1104 matter, so we're looking at entry->kind instead. */
1105 switch (entry
->kind
)
1111 return SVN_NO_ERROR
; /* ### svn_node_none or svn_node_unknown */
1115 log_accum
= svn_stringbuf_create("", pool
);
1117 /* Note that while this routine does the "real" work, it's only
1118 prepping tempfiles and writing log commands. */
1119 SVN_ERR(svn_wc__merge_props(state
, adm_access
, path
, baseprops
, NULL
, NULL
,
1120 propchanges
, base_merge
, dry_run
,
1121 conflict_func
, conflict_baton
, pool
, &log_accum
));
1125 SVN_ERR(svn_wc__write_log(adm_access
, 0, log_accum
, pool
));
1126 SVN_ERR(svn_wc__run_log(adm_access
, NULL
, pool
));
1129 return SVN_NO_ERROR
;
1134 /* Set the value of *STATE to NEW_VALUE if STATE is not NULL
1135 * and NEW_VALUE is a higer order value than *STATE's current value
1136 * using this ordering (lower order first):
1138 * - unknown, unchanged, inapplicable
1147 set_prop_merge_state(svn_wc_notify_state_t
*state
,
1148 svn_wc_notify_state_t new_value
)
1150 static char ordering
[] =
1151 { svn_wc_notify_state_unknown
,
1152 svn_wc_notify_state_unchanged
,
1153 svn_wc_notify_state_inapplicable
,
1154 svn_wc_notify_state_changed
,
1155 svn_wc_notify_state_merged
,
1156 svn_wc_notify_state_obstructed
,
1157 svn_wc_notify_state_conflicted
};
1158 int state_pos
= 0, i
;
1163 /* Find *STATE in our ordering */
1164 for (i
= 0; i
< sizeof(ordering
); i
++)
1166 if (*state
== ordering
[i
])
1173 /* Find NEW_VALUE in our ordering
1174 * We don't need to look further than where we found *STATE though:
1175 * If we find our value, it's order is too low.
1176 * If we don't find it, we'll want to set it, no matter its order.
1179 for (i
= 0; i
<= state_pos
; i
++)
1181 if (new_value
== ordering
[i
])
1190 /* Helper function for the three apply_* functions below, used when
1191 * merging properties together.
1193 * Given property PROPNAME on PATH, and four possible property values,
1194 * generate four tmpfiles and pass them to CONFLICT_FUNC callback.
1195 * This gives the client an opportunity to interactively resolve the
1196 * property conflict. (ADM_ACCESS provides the ability to examine
1199 * BASE_VAL/WORKING_VAL represent the current state of the working
1200 * copy, and OLD_VAL/NEW_VAL represents the incoming propchange. Any
1201 * of these values might be NULL, indicating either non-existence or
1204 * If the callback isn't available, or if it responds with
1205 * 'choose_postpone', then set *CONFLICT_REMAINS to true and return.
1207 * If the callback responds with a choice of 'base', 'theirs', 'mine',
1208 * or 'merged', then install the proper value into WORKING_PROPS and
1209 * set *CONFLICT_REMAINS to false.
1212 static svn_error_t
*
1213 maybe_generate_propconflict(svn_boolean_t
*conflict_remains
,
1215 svn_wc_adm_access_t
*adm_access
,
1216 svn_boolean_t is_dir
,
1217 const char *propname
,
1218 apr_hash_t
*working_props
,
1219 const svn_string_t
*old_val
,
1220 const svn_string_t
*new_val
,
1221 const svn_string_t
*base_val
,
1222 const svn_string_t
*working_val
,
1223 svn_wc_conflict_resolver_func_t conflict_func
,
1224 void *conflict_baton
,
1227 svn_wc_conflict_result_t
*result
= NULL
;
1228 svn_string_t
*mime_propval
= NULL
;
1229 apr_file_t
*working_file
, *base_file
, *new_file
, *merged_file
;
1230 const char *working_path
, *base_path
, *new_path
, *merged_path
;
1231 apr_pool_t
*filepool
= svn_pool_create(pool
);
1232 svn_wc_conflict_description_t
*cdesc
;
1234 if (! conflict_func
)
1236 /* Just postpone the conflict. */
1237 *conflict_remains
= TRUE
;
1238 return SVN_NO_ERROR
;
1241 cdesc
= apr_pcalloc(pool
, sizeof(*cdesc
));
1243 /* Create a tmpfile for each of the string_t's we've got. */
1246 SVN_ERR(svn_io_open_unique_file2(&working_file
, &working_path
,
1248 svn_io_file_del_on_pool_cleanup
,
1250 SVN_ERR(svn_io_file_write_full(working_file
, working_val
->data
,
1251 working_val
->len
, NULL
, filepool
));
1252 SVN_ERR(svn_io_file_close(working_file
, filepool
));
1253 cdesc
->my_file
= working_path
;
1258 SVN_ERR(svn_io_open_unique_file2(&new_file
, &new_path
,
1260 svn_io_file_del_on_pool_cleanup
,
1262 SVN_ERR(svn_io_file_write_full(new_file
, new_val
->data
,
1263 new_val
->len
, NULL
, filepool
));
1264 SVN_ERR(svn_io_file_close(new_file
, filepool
));
1265 cdesc
->their_file
= new_path
;
1268 if (!base_val
&& !old_val
)
1270 /* If base and old are both NULL, then that's fine, we just let
1271 base_file stay NULL as-is. Both agents are attempting to add a
1275 else if ((base_val
&& !old_val
)
1276 || (!base_val
&& old_val
))
1278 /* If only one of base and old are defined, then we've got a
1279 situation where one agent is attempting to add the property
1280 for the first time, and the other agent is changing a
1281 property it thinks already exists. In this case, we return
1282 whichever older-value happens to be defined, so that the
1283 conflict-callback can still attempt a 3-way merge. */
1285 const svn_string_t
*the_val
= base_val
? base_val
: old_val
;
1286 SVN_ERR(svn_io_open_unique_file2(&base_file
, &base_path
,
1288 svn_io_file_del_on_pool_cleanup
,
1290 SVN_ERR(svn_io_file_write_full(base_file
,
1292 the_val
->len
, NULL
, filepool
));
1293 SVN_ERR(svn_io_file_close(base_file
, filepool
));
1294 cdesc
->base_file
= base_path
;
1297 else /* base and old are both non-NULL */
1299 const svn_string_t
*the_val
;
1301 if (! svn_string_compare(base_val
, old_val
))
1303 /* What happens if 'base' and 'old' don't match up? In an
1304 ideal situation, they would. But if they don't, this is
1305 a classic example of a patch 'hunk' failing to apply due
1306 to a lack of context. For example: imagine that the user
1307 is busy changing the property from a value of "cat" to
1308 "dog", but the incoming propchange wants to change the
1309 same property value from "red" to "green". Total context
1312 HOWEVER: we can still pass one of the two base values as
1313 'base_file' to the callback anyway. It's still useful to
1314 present the working and new values to the user to
1317 if (working_val
&& svn_string_compare(base_val
, working_val
))
1327 SVN_ERR(svn_io_open_unique_file2(&base_file
, &base_path
,
1329 svn_io_file_del_on_pool_cleanup
,
1331 SVN_ERR(svn_io_file_write_full(base_file
, the_val
->data
,
1332 the_val
->len
, NULL
, filepool
));
1333 SVN_ERR(svn_io_file_close(base_file
, filepool
));
1334 cdesc
->base_file
= base_path
;
1336 if (working_val
&& new_val
)
1338 svn_stream_t
*mergestream
;
1340 svn_diff_file_options_t
*options
=
1341 svn_diff_file_options_create(filepool
);
1343 SVN_ERR(svn_io_open_unique_file2(&merged_file
, &merged_path
,
1345 svn_io_file_del_on_pool_cleanup
,
1347 mergestream
= svn_stream_from_aprfile2(merged_file
, FALSE
,
1349 SVN_ERR(svn_diff_mem_string_diff3(&diff
, the_val
, working_val
,
1350 new_val
, options
, filepool
));
1351 SVN_ERR(svn_diff_mem_string_output_merge
1352 (mergestream
, diff
, the_val
, working_val
, new_val
,
1353 NULL
, NULL
, NULL
, NULL
, FALSE
, FALSE
, filepool
));
1354 svn_stream_close(mergestream
);
1356 cdesc
->merged_file
= merged_path
;
1360 /* Build the rest of the description object: */
1362 cdesc
->node_kind
= is_dir
? svn_node_dir
: svn_node_file
;
1363 cdesc
->kind
= svn_wc_conflict_kind_property
;
1364 cdesc
->property_name
= propname
;
1365 cdesc
->access
= adm_access
;
1367 if (!is_dir
&& working_props
)
1368 mime_propval
= apr_hash_get(working_props
, SVN_PROP_MIME_TYPE
,
1369 APR_HASH_KEY_STRING
);
1370 cdesc
->mime_type
= mime_propval
? mime_propval
->data
: NULL
;
1371 cdesc
->is_binary
= mime_propval
?
1372 svn_mime_type_is_binary(mime_propval
->data
) : FALSE
;
1374 if (!old_val
&& new_val
)
1375 cdesc
->action
= svn_wc_conflict_action_add
;
1376 else if (old_val
&& !new_val
)
1377 cdesc
->action
= svn_wc_conflict_action_delete
;
1379 cdesc
->action
= svn_wc_conflict_action_edit
;
1381 if (base_val
&& !working_val
)
1382 cdesc
->reason
= svn_wc_conflict_reason_deleted
;
1383 else if (!base_val
&& working_val
)
1384 cdesc
->reason
= svn_wc_conflict_reason_obstructed
;
1386 cdesc
->reason
= svn_wc_conflict_reason_edited
;
1388 /* Invoke the interactive conflict callback. */
1389 SVN_ERR(conflict_func(&result
, cdesc
, conflict_baton
, pool
));
1392 *conflict_remains
= TRUE
;
1393 return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE
,
1394 NULL
, _("Conflict callback violated API:"
1395 " returned no results."));
1398 switch (result
->choice
)
1401 case svn_wc_conflict_choose_postpone
:
1403 *conflict_remains
= TRUE
;
1406 case svn_wc_conflict_choose_mine_full
:
1408 /* No need to change working_props; it already contains working_val */
1409 *conflict_remains
= FALSE
;
1412 /* I think _mine_full and _theirs_full are appropriate for prop
1413 behavior as well as the text behavior. There should even be
1414 analogous behaviors for _mine and _theirs when those are
1415 ready, namely: fold in all non-conflicting prop changes, and
1416 then choose _mine side or _theirs side for conflicting ones. */
1417 case svn_wc_conflict_choose_theirs_full
:
1419 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, new_val
);
1420 *conflict_remains
= FALSE
;
1423 case svn_wc_conflict_choose_base
:
1425 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, base_val
);
1426 *conflict_remains
= FALSE
;
1429 case svn_wc_conflict_choose_merged
:
1431 if (!cdesc
->merged_file
&& !result
->merged_file
)
1432 return svn_error_create
1433 (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE
,
1434 NULL
, _("Conflict callback violated API:"
1435 " returned no merged file."));
1438 svn_stringbuf_t
*merged_stringbuf
;
1439 svn_string_t
*merged_string
;
1441 SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf
,
1442 result
->merged_file
?
1443 result
->merged_file
:
1446 merged_string
= svn_string_create_from_buf(merged_stringbuf
, pool
);
1447 apr_hash_set(working_props
, propname
,
1448 APR_HASH_KEY_STRING
, merged_string
);
1449 *conflict_remains
= FALSE
;
1455 /* Delete any tmpfiles we made. */
1456 svn_pool_destroy(filepool
);
1458 return SVN_NO_ERROR
;
1462 /* Add the property with name PROPNAME to the set of WORKING_PROPS on
1463 * PATH, setting *STATE or *CONFLICT according to merge outcomes.
1465 * *STATE is an input and output parameter, its value is to be
1466 * set using set_merge_prop_state().
1468 * BASE_VAL contains the working copy base property value
1470 * NEW_VAL contains the value to be set.
1472 * CONFLICT_FUNC/BATON is a callback to be called before declaring a
1473 * property conflict; it gives the client a chance to resolve the
1474 * conflict interactively. It uses ADM_ACCESS to possibly examine
1477 static svn_error_t
*
1478 apply_single_prop_add(svn_wc_notify_state_t
*state
,
1480 svn_boolean_t is_dir
,
1481 apr_hash_t
*working_props
,
1482 svn_string_t
**conflict
,
1483 const char *propname
,
1484 const svn_string_t
*base_val
,
1485 const svn_string_t
*new_val
,
1486 svn_wc_conflict_resolver_func_t conflict_func
,
1487 void *conflict_baton
,
1488 svn_wc_adm_access_t
*adm_access
,
1492 svn_boolean_t got_conflict
= FALSE
;
1493 svn_string_t
*working_val
1494 = apr_hash_get(working_props
, propname
, APR_HASH_KEY_STRING
);
1498 /* the property already exists in working_props... */
1500 if (svn_string_compare(working_val
, new_val
))
1501 /* The value we want is already there, so it's a merge. */
1502 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1506 /* The WC difference doesn't match the new value.
1507 We only merge mergeinfo; other props conflict */
1508 if (strcmp(propname
, SVN_PROP_MERGEINFO
) == 0)
1510 SVN_ERR(combine_mergeinfo_props(&new_val
, working_val
,
1512 apr_hash_set(working_props
, propname
,
1513 APR_HASH_KEY_STRING
, new_val
);
1514 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1518 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
,
1520 propname
, working_props
,
1522 base_val
, working_val
,
1523 conflict_func
, conflict_baton
,
1526 *conflict
= svn_string_createf
1528 _("Trying to add new property '%s' with value "
1529 "'%s',\nbut property already exists with value '%s'."),
1530 propname
, new_val
->data
, working_val
->data
);
1536 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
, adm_access
,
1538 working_props
, NULL
, new_val
,
1540 conflict_func
, conflict_baton
, pool
));
1542 *conflict
= svn_string_createf
1543 (pool
, _("Trying to create property '%s' with value '%s',\n"
1544 "but it has been locally deleted."),
1545 propname
, new_val
->data
);
1547 else /* property doesn't yet exist in working_props... */
1548 /* so just set it */
1549 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, new_val
);
1551 return SVN_NO_ERROR
;
1555 /* Delete the property with name PROPNAME from the set of
1556 * WORKING_PROPS on PATH, setting *STATE or *CONFLICT according to
1559 * *STATE is an input and output parameter, its value is to be
1560 * set using set_merge_prop_state().
1562 * BASE_VAL contains the working copy base property value
1564 * OLD_VAL contains the value the of the property the server
1565 * thinks it's deleting.
1567 * CONFLICT_FUNC/BATON is a callback to be called before declaring a
1568 * property conflict; it gives the client a chance to resolve the
1569 * conflict interactively. It uses ADM_ACCESS to possibly examine
1572 static svn_error_t
*
1573 apply_single_prop_delete(svn_wc_notify_state_t
*state
,
1575 svn_boolean_t is_dir
,
1576 apr_hash_t
*working_props
,
1577 svn_string_t
**conflict
,
1578 const char *propname
,
1579 const svn_string_t
*base_val
,
1580 const svn_string_t
*old_val
,
1581 svn_wc_conflict_resolver_func_t conflict_func
,
1582 void *conflict_baton
,
1583 svn_wc_adm_access_t
*adm_access
,
1586 svn_boolean_t got_conflict
= FALSE
;
1587 svn_string_t
*working_val
1588 = apr_hash_get(working_props
, propname
, APR_HASH_KEY_STRING
);
1592 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, NULL
);
1594 /* This is a merge, merging a delete into non-existent */
1595 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1598 else if (svn_string_compare(base_val
, old_val
))
1602 if (svn_string_compare(working_val
, old_val
))
1603 /* they have the same values, so it's an update */
1604 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, NULL
);
1607 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
,
1609 propname
, working_props
,
1611 base_val
, working_val
,
1612 conflict_func
, conflict_baton
,
1615 *conflict
= svn_string_createf
1617 _("Trying to delete property '%s' with value '%s'\n"
1618 "but it has been modified from '%s' to '%s'."),
1619 propname
, old_val
->data
,
1620 base_val
->data
, working_val
->data
);
1624 /* The property is locally deleted, so it's a merge */
1625 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1630 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
, adm_access
,
1632 working_props
, old_val
, NULL
,
1633 base_val
, working_val
,
1634 conflict_func
, conflict_baton
, pool
));
1636 *conflict
= svn_string_createf
1638 _("Trying to delete property '%s' with value '%s'\n"
1639 "but the local value is '%s'."),
1640 propname
, base_val
->data
, working_val
->data
);
1643 return SVN_NO_ERROR
;
1647 /* Change the property with name PROPNAME in the set of WORKING_PROPS
1648 * on PATH, setting *STATE or *CONFLICT according to merge outcomes.
1650 * *STATE is an input and output parameter, its value is to be
1651 * set using set_merge_prop_state().
1653 * BASE_VAL contains the working copy base property value
1655 * OLD_VAL contains the value the of the property the server
1656 * thinks it's overwriting
1658 * NEW_VAL contains the value to be set.
1660 * CONFLICT_FUNC/BATON is a callback to be called before declaring a
1661 * property conflict; it gives the client a chance to resolve the
1662 * conflict interactively. It uses ADM_ACCESS to possibly examine the
1665 static svn_error_t
*
1666 apply_single_prop_change(svn_wc_notify_state_t
*state
,
1668 svn_boolean_t is_dir
,
1669 apr_hash_t
*working_props
,
1670 svn_string_t
**conflict
,
1671 const char *propname
,
1672 const svn_string_t
*base_val
,
1673 const svn_string_t
*old_val
,
1674 const svn_string_t
*new_val
,
1675 svn_wc_conflict_resolver_func_t conflict_func
,
1676 void *conflict_baton
,
1677 svn_wc_adm_access_t
*adm_access
,
1680 svn_boolean_t got_conflict
= FALSE
;
1681 svn_string_t
*working_val
1682 = apr_hash_get(working_props
, propname
, APR_HASH_KEY_STRING
);
1684 if ((working_val
&& ! base_val
)
1685 || (! working_val
&& base_val
)
1686 || (working_val
&& base_val
1687 && !svn_string_compare(working_val
, base_val
)))
1689 /* Locally changed property */
1692 if (svn_string_compare(working_val
, new_val
))
1693 /* The new value equals the changed value: a merge */
1694 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1697 if (strcmp(propname
, SVN_PROP_MERGEINFO
) == 0)
1699 /* We have base, WC, and new values. Discover
1700 deltas between base <-> WC, and base <->
1701 incoming. Combine those deltas, and apply
1702 them to base to get the new value. */
1703 SVN_ERR(combine_forked_mergeinfo_props(&new_val
, old_val
,
1706 apr_hash_set(working_props
, propname
,
1707 APR_HASH_KEY_STRING
, new_val
);
1708 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1712 SVN_ERR(maybe_generate_propconflict(&got_conflict
,
1713 path
, adm_access
, is_dir
,
1714 propname
, working_props
,
1716 base_val
, working_val
,
1723 *conflict
= svn_string_createf
1725 _("Trying to change property '%s' from '%s' to '%s',\n"
1726 "but property has been locally changed "
1727 "from '%s' to '%s'."),
1728 propname
, old_val
->data
, new_val
->data
,
1729 base_val
->data
, working_val
->data
);
1731 *conflict
= svn_string_createf
1733 _("Trying to change property '%s' from '%s' to '%s',\n"
1734 "but property has been locally added with "
1736 propname
, old_val
->data
, new_val
->data
,
1745 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
, adm_access
,
1746 is_dir
, propname
, working_props
,
1748 base_val
, working_val
,
1749 conflict_func
, conflict_baton
,
1752 *conflict
= svn_string_createf
1754 _("Trying to change property '%s' from '%s' to '%s',\n"
1755 "but it has been locally deleted."),
1756 propname
, old_val
->data
, new_val
->data
);
1760 else if (! working_val
) /* means !working_val && !base_val due
1761 to conditions above: no prop at all */
1763 if (strcmp(propname
, SVN_PROP_MERGEINFO
) == 0)
1765 /* Discover any mergeinfo additions in the
1766 incoming value relative to the base, and
1767 "combine" those with the empty WC value. */
1768 svn_mergeinfo_t deleted_mergeinfo
, added_mergeinfo
;
1769 SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo
,
1771 old_val
, new_val
, pool
));
1772 SVN_ERR(svn_mergeinfo_to_string((svn_string_t
**)&new_val
,
1773 added_mergeinfo
, pool
));
1774 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, new_val
);
1778 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
, adm_access
,
1779 is_dir
, propname
, working_props
,
1781 base_val
, working_val
,
1782 conflict_func
, conflict_baton
,
1785 *conflict
= svn_string_createf
1787 _("Trying to change property '%s' from '%s' to '%s',\n"
1788 "but the property does not exist."),
1789 propname
, old_val
->data
, new_val
->data
);
1793 else /* means working && base && svn_string_compare(working, base) */
1795 if (svn_string_compare(old_val
, base_val
))
1796 apr_hash_set(working_props
, propname
, APR_HASH_KEY_STRING
, new_val
);
1800 if (strcmp(propname
, SVN_PROP_MERGEINFO
) == 0)
1802 /* We have base, WC, and new values. Discover
1803 deltas between base <-> WC, and base <->
1804 incoming. Combine those deltas, and apply
1805 them to base to get the new value. */
1806 SVN_ERR(combine_forked_mergeinfo_props(&new_val
, old_val
,
1809 apr_hash_set(working_props
, propname
,
1810 APR_HASH_KEY_STRING
, new_val
);
1811 set_prop_merge_state(state
, svn_wc_notify_state_merged
);
1815 SVN_ERR(maybe_generate_propconflict(&got_conflict
, path
,
1817 propname
, working_props
,
1819 base_val
, working_val
,
1820 conflict_func
, conflict_baton
,
1823 *conflict
= svn_string_createf
1825 _("Trying to change property '%s' from '%s' to '%s',\n"
1826 "but property already exists with value '%s'."),
1827 propname
, old_val
->data
, new_val
->data
,
1833 return SVN_NO_ERROR
;
1838 svn_wc__merge_props(svn_wc_notify_state_t
*state
,
1839 svn_wc_adm_access_t
*adm_access
,
1841 apr_hash_t
*server_baseprops
,
1842 apr_hash_t
*base_props
,
1843 apr_hash_t
*working_props
,
1844 const apr_array_header_t
*propchanges
,
1845 svn_boolean_t base_merge
,
1846 svn_boolean_t dry_run
,
1847 svn_wc_conflict_resolver_func_t conflict_func
,
1848 void *conflict_baton
,
1850 svn_stringbuf_t
**entry_accum
)
1853 svn_boolean_t is_dir
;
1855 const char *reject_path
= NULL
;
1856 apr_file_t
*reject_tmp_fp
= NULL
; /* the temporary conflicts file */
1857 const char *reject_tmp_path
= NULL
;
1859 if (! svn_path_is_child(svn_wc_adm_access_path(adm_access
), path
, NULL
))
1864 /* If not provided, load the base & working property files into hashes */
1865 if (! base_props
|| ! working_props
)
1866 SVN_ERR(svn_wc__load_props(base_props
? NULL
: &base_props
,
1867 working_props
? NULL
: &working_props
,
1868 NULL
, adm_access
, path
, pool
));
1869 if (!server_baseprops
)
1870 server_baseprops
= base_props
;
1874 /* Start out assuming no changes or conflicts. Don't bother to
1875 examine propchanges->nelts yet; even if we knew there were
1876 propchanges, we wouldn't yet know if they are "normal" props,
1877 as opposed wc or entry props. */
1878 *state
= svn_wc_notify_state_unchanged
;
1881 /* Looping over the array of incoming propchanges we want to apply: */
1882 for (i
= 0; i
< propchanges
->nelts
; i
++)
1884 const char *propname
;
1885 svn_string_t
*conflict
= NULL
;
1886 const svn_prop_t
*incoming_change
;
1887 const svn_string_t
*from_val
, *to_val
, *working_val
, *base_val
;
1888 svn_boolean_t is_normal
;
1890 /* For the incoming propchange, figure out the TO and FROM values. */
1891 incoming_change
= &APR_ARRAY_IDX(propchanges
, i
, svn_prop_t
);
1892 propname
= incoming_change
->name
;
1893 is_normal
= svn_wc_is_normal_prop(propname
);
1894 to_val
= incoming_change
->value
1895 ? svn_string_dup(incoming_change
->value
, pool
) : NULL
;
1896 from_val
= apr_hash_get(server_baseprops
, propname
, APR_HASH_KEY_STRING
);
1898 working_val
= apr_hash_get(working_props
, propname
, APR_HASH_KEY_STRING
);
1899 base_val
= apr_hash_get(base_props
, propname
, APR_HASH_KEY_STRING
);
1902 apr_hash_set(base_props
, propname
, APR_HASH_KEY_STRING
, to_val
);
1904 /* We already know that state is at least `changed', so mark
1905 that, but remember that we may later upgrade to `merged' or
1906 even `conflicted'. */
1908 set_prop_merge_state(state
, svn_wc_notify_state_changed
);
1910 if (! from_val
) /* adding a new property */
1911 SVN_ERR(apply_single_prop_add(is_normal
? state
: NULL
, path
, is_dir
,
1912 working_props
, &conflict
,
1913 propname
, base_val
, to_val
,
1914 conflict_func
, conflict_baton
,
1917 else if (! to_val
) /* delete an existing property */
1918 SVN_ERR(apply_single_prop_delete(is_normal
? state
: NULL
, path
, is_dir
,
1919 working_props
, &conflict
,
1920 propname
, base_val
, from_val
,
1921 conflict_func
, conflict_baton
,
1924 else /* changing an existing property */
1925 SVN_ERR(apply_single_prop_change(is_normal
? state
: NULL
, path
, is_dir
,
1926 working_props
, &conflict
,
1927 propname
, base_val
, from_val
, to_val
,
1928 conflict_func
, conflict_baton
,
1932 /* merging logic complete, now we need to possibly log conflict
1933 data to tmpfiles. */
1938 set_prop_merge_state(state
, svn_wc_notify_state_conflicted
);
1941 continue; /* skip to next incoming change */
1943 if (! reject_tmp_fp
)
1944 /* This is the very first prop conflict found on this item. */
1945 SVN_ERR(open_reject_tmp_file(&reject_tmp_fp
, &reject_tmp_path
,
1946 path
, adm_access
, is_dir
,
1949 /* Append the conflict to the open tmp/PROPS/---.prej file */
1950 SVN_ERR(append_prop_conflict(reject_tmp_fp
, conflict
, pool
));
1953 } /* foreach propchange ... */
1955 /* Finished applying all incoming propchanges to our hashes! */
1958 return SVN_NO_ERROR
;
1960 SVN_ERR(svn_wc__install_props(entry_accum
, adm_access
, path
,
1961 base_props
, working_props
, base_merge
,
1966 /* There's a .prej file sitting in .svn/tmp/ somewhere. Deal
1967 with the conflicts. */
1969 /* First, _close_ this temporary conflicts file. We've been
1970 appending to it all along. */
1971 SVN_ERR(svn_io_file_close(reject_tmp_fp
, pool
));
1973 /* Now try to get the name of a pre-existing .prej file from the
1975 SVN_ERR(get_existing_prop_reject_file(&reject_path
,
1976 adm_access
, path
, pool
));
1980 /* Reserve a new .prej file *above* the .svn/ directory by
1981 opening and closing it. */
1982 const char *full_reject_path
;
1984 full_reject_path
= (!is_dir
) ? path
:
1985 svn_path_join(path
, SVN_WC__THIS_DIR_PREJ
, pool
);
1987 SVN_ERR(svn_io_open_unique_file2(NULL
, &reject_path
,
1989 SVN_WC__PROP_REJ_EXT
,
1990 svn_io_file_del_none
, pool
));
1992 /* This file will be overwritten when the log is run; that's
1993 ok, because at least now we have a reservation on
1997 /* We've now guaranteed that some kind of .prej file exists
1998 above the .svn/ dir. We write log entries to append our
2000 SVN_ERR(svn_wc__loggy_append(entry_accum
, adm_access
,
2001 reject_tmp_path
, reject_path
, pool
));
2003 /* And of course, delete the temporary reject file. */
2004 SVN_ERR(svn_wc__loggy_remove(entry_accum
, adm_access
,
2005 reject_tmp_path
, pool
));
2007 /* Mark entry as "conflicted" with a particular .prej file. */
2009 svn_wc_entry_t entry
;
2011 entry
.prejfile
= svn_path_is_child(svn_wc_adm_access_path(adm_access
),
2013 SVN_ERR(svn_wc__loggy_entry_modify(entry_accum
,
2017 SVN_WC__ENTRY_MODIFY_PREJFILE
,
2021 } /* if (reject_tmp_fp) */
2023 return SVN_NO_ERROR
;
2027 /* This is DEPRECATED, use svn_wc_merge_props() instead. */
2029 svn_wc_merge_prop_diffs(svn_wc_notify_state_t
*state
,
2031 svn_wc_adm_access_t
*adm_access
,
2032 const apr_array_header_t
*propchanges
,
2033 svn_boolean_t base_merge
,
2034 svn_boolean_t dry_run
,
2037 /* NOTE: Here, we use implementation knowledge. The public
2038 svn_wc_merge_props doesn't allow NULL as baseprops argument, but we know
2040 return svn_wc_merge_props(state
, path
, adm_access
, NULL
, propchanges
,
2041 base_merge
, dry_run
, pool
);
2046 /*------------------------------------------------------------------*/
2048 /*** Private 'wc prop' functions ***/
2052 svn_wc__wcprop_list(apr_hash_t
**wcprops
,
2053 const char *entryname
,
2054 svn_wc_adm_access_t
*adm_access
,
2057 const char *prop_path
;
2058 const svn_wc_entry_t
*entry
;
2059 apr_hash_t
*all_wcprops
;
2060 apr_pool_t
*cache_pool
= svn_wc_adm_access_pool(adm_access
);
2061 const char *path
= svn_path_join(svn_wc_adm_access_path(adm_access
),
2064 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
2067 /* No entry exists, therefore no wcprop-file can exist */
2068 *wcprops
= apr_hash_make(pool
);
2069 return SVN_NO_ERROR
;
2072 /* Try the cache first. */
2073 all_wcprops
= svn_wc__adm_access_wcprops(adm_access
);
2076 SVN_ERR(read_wcprops(adm_access
, pool
));
2077 all_wcprops
= svn_wc__adm_access_wcprops(adm_access
);
2081 *wcprops
= apr_hash_get(all_wcprops
, entryname
, APR_HASH_KEY_STRING
);
2082 /* The cache contains no hash tables for empty proplist, so we just
2083 create one here if that's the case. */
2086 *wcprops
= apr_hash_make(cache_pool
);
2087 entryname
= apr_pstrdup(cache_pool
, entryname
);
2088 apr_hash_set(all_wcprops
, entryname
, APR_HASH_KEY_STRING
, *wcprops
);
2090 return SVN_NO_ERROR
;
2093 /* Fall back on individual files for backwards compatibility. */
2095 /* Construct a path to the relevant property file */
2096 SVN_ERR(svn_wc__prop_path(&prop_path
, path
, entry
->kind
,
2097 svn_wc__props_wcprop
, FALSE
, pool
));
2098 *wcprops
= apr_hash_make(pool
);
2099 SVN_ERR(load_prop_file(prop_path
, *wcprops
, pool
));
2101 return SVN_NO_ERROR
;
2105 /* Get a single 'wcprop' NAME for versioned object PATH, return in
2106 *VALUE. ADM_ACCESS is an access baton set that contains PATH. */
2107 static svn_error_t
*
2108 wcprop_get(const svn_string_t
**value
,
2111 svn_wc_adm_access_t
*adm_access
,
2114 apr_hash_t
*prophash
;
2115 const svn_wc_entry_t
*entry
;
2117 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
2121 return SVN_NO_ERROR
;
2123 if (entry
->kind
== svn_node_dir
)
2124 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
, path
, pool
));
2126 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
,
2127 svn_path_dirname(path
, pool
), pool
));
2129 SVN_ERR_W(svn_wc__wcprop_list(&prophash
, entry
->name
, adm_access
, pool
),
2130 _("Failed to load properties from disk"));
2132 *value
= apr_hash_get(prophash
, name
, APR_HASH_KEY_STRING
);
2133 return SVN_NO_ERROR
;
2138 svn_wc__wcprop_set(const char *name
,
2139 const svn_string_t
*value
,
2141 svn_wc_adm_access_t
*adm_access
,
2142 svn_boolean_t force_write
,
2145 apr_hash_t
*prophash
;
2146 apr_file_t
*fp
= NULL
;
2147 apr_pool_t
*cache_pool
= svn_wc_adm_access_pool(adm_access
);
2148 const svn_wc_entry_t
*entry
;
2150 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, FALSE
, pool
));
2152 if (entry
->kind
== svn_node_dir
)
2153 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
, path
, pool
));
2155 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
,
2156 svn_path_dirname(path
, pool
), pool
));
2157 SVN_ERR_W(svn_wc__wcprop_list(&prophash
, entry
->name
, adm_access
, pool
),
2158 _("Failed to load properties from disk"));
2160 /* Now we have all the properties in our hash. Simply merge the new
2161 property into it. */
2162 name
= apr_pstrdup(cache_pool
, name
);
2164 value
= svn_string_dup(value
, cache_pool
);
2165 apr_hash_set(prophash
, name
, APR_HASH_KEY_STRING
, value
);
2167 if (svn_wc__adm_wc_format(adm_access
) > SVN_WC__WCPROPS_MANY_FILES_VERSION
)
2170 SVN_ERR(write_wcprops(adm_access
, pool
));
2174 /* For backwards compatibility. We don't use the cache in this case,
2175 so write to disk regardless of force_write. */
2176 /* Open the propfile for writing. */
2177 SVN_ERR(svn_wc__open_props(&fp
,
2178 path
, /* open in PATH */ entry
->kind
,
2179 (APR_WRITE
| APR_CREATE
| APR_BUFFERED
),
2180 0, /* not base props */
2181 1, /* we DO want wcprops */
2184 SVN_ERR_W(svn_hash_write(prophash
, fp
, pool
),
2186 _("Cannot write property hash for '%s'"),
2187 svn_path_local_style(path
, pool
)));
2189 /* Close file, doing an atomic "move". */
2190 SVN_ERR(svn_wc__close_props(fp
, path
, entry
->kind
, 0, 1,
2195 return SVN_NO_ERROR
;
2198 /*------------------------------------------------------------------*/
2201 /*** Public Functions ***/
2205 svn_wc_prop_list(apr_hash_t
**props
,
2207 svn_wc_adm_access_t
*adm_access
,
2210 const svn_wc_entry_t
*entry
;
2212 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, TRUE
, pool
));
2214 /* if there is no entry, 'path' is not under version control and
2215 therefore has no props */
2218 *props
= apr_hash_make(pool
);
2219 return SVN_NO_ERROR
;
2222 if (entry
->kind
== svn_node_dir
)
2223 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
, path
, pool
));
2225 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
,
2226 svn_path_dirname(path
, pool
), pool
));
2228 return svn_wc__load_props(NULL
, props
, NULL
, adm_access
, path
, pool
);
2231 /* Determine if PROPNAME is contained in the list of space separated
2234 static svn_boolean_t
2235 string_contains_prop(const char *string
, const char *propname
)
2237 const char *place
= strstr(string
, propname
);
2238 int proplen
= strlen(propname
);
2245 if (place
[proplen
] == ' ' || place
[proplen
] == 0)
2247 place
= strstr(place
+ 1, propname
);
2253 svn_wc_prop_get(const svn_string_t
**value
,
2256 svn_wc_adm_access_t
*adm_access
,
2259 apr_hash_t
*prophash
;
2260 enum svn_prop_kind kind
= svn_property_kind(NULL
, name
);
2261 const svn_wc_entry_t
*entry
;
2263 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, TRUE
, pool
));
2268 return SVN_NO_ERROR
;
2271 if (entry
->cachable_props
2272 && string_contains_prop(entry
->cachable_props
, name
))
2274 /* We separate these two cases so that we can return the correct
2275 value for booleans if they exist in the string. */
2276 if (!entry
->present_props
2277 || !string_contains_prop(entry
->present_props
, name
))
2280 return SVN_NO_ERROR
;
2282 if (svn_prop_is_boolean(name
))
2284 *value
= svn_string_create(SVN_PROP_BOOLEAN_TRUE
, pool
);
2285 assert(*value
!= NULL
);
2286 return SVN_NO_ERROR
;
2290 if (kind
== svn_prop_wc_kind
)
2292 return wcprop_get(value
, name
, path
, adm_access
, pool
);
2294 if (kind
== svn_prop_entry_kind
)
2296 return svn_error_createf
/* we don't do entry properties here */
2297 (SVN_ERR_BAD_PROP_KIND
, NULL
,
2298 _("Property '%s' is an entry property"), name
);
2300 else /* regular prop */
2302 SVN_ERR_W(svn_wc_prop_list(&prophash
, path
, adm_access
, pool
),
2303 _("Failed to load properties from disk"));
2305 *value
= apr_hash_get(prophash
, name
, APR_HASH_KEY_STRING
);
2307 return SVN_NO_ERROR
;
2312 /* The special Subversion properties are not valid for all node kinds.
2313 Return an error if NAME is an invalid Subversion property for PATH which
2314 is of kind NODE_KIND. */
2315 static svn_error_t
*
2316 validate_prop_against_node_kind(const char *name
,
2318 svn_node_kind_t node_kind
,
2322 const char *file_prohibit
[] = { SVN_PROP_IGNORE
,
2325 const char *dir_prohibit
[] = { SVN_PROP_EXECUTABLE
,
2329 SVN_PROP_NEEDS_LOCK
,
2331 const char **node_kind_prohibit
;
2332 const char *path_display
2333 = svn_path_is_url(path
) ? path
: svn_path_local_style(path
, pool
);
2338 node_kind_prohibit
= dir_prohibit
;
2339 while (*node_kind_prohibit
)
2340 if (strcmp(name
, *node_kind_prohibit
++) == 0)
2341 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET
, NULL
,
2342 _("Cannot set '%s' on a directory ('%s')"),
2343 name
, path_display
);
2346 node_kind_prohibit
= file_prohibit
;
2347 while (*node_kind_prohibit
)
2348 if (strcmp(name
, *node_kind_prohibit
++) == 0)
2349 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET
, NULL
,
2350 _("Cannot set '%s' on a file ('%s')"),
2355 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND
, NULL
,
2356 _("'%s' is not a file or directory"),
2360 return SVN_NO_ERROR
;
2364 struct getter_baton
{
2366 svn_wc_adm_access_t
*adm_access
;
2370 static svn_error_t
*
2371 get_file_for_validation(const svn_string_t
**mime_type
,
2372 svn_stream_t
*stream
,
2376 struct getter_baton
*gb
= baton
;
2379 SVN_ERR(svn_wc_prop_get(mime_type
, SVN_PROP_MIME_TYPE
,
2380 gb
->path
, gb
->adm_access
, pool
));
2384 svn_stream_t
*read_stream
;
2387 SVN_ERR(svn_io_file_open(&fp
, gb
->path
,
2388 (APR_READ
| APR_BINARY
| APR_BUFFERED
),
2391 /* Get a READ_STREAM from the file we just opened. */
2392 read_stream
= svn_stream_from_aprfile(fp
, pool
);
2394 /* Copy from the file into the translating stream. */
2395 SVN_ERR(svn_stream_copy(read_stream
, stream
, pool
));
2397 SVN_ERR(svn_stream_close(read_stream
));
2398 SVN_ERR(svn_io_file_close(fp
, pool
));
2401 return SVN_NO_ERROR
;
2405 static svn_error_t
*
2406 validate_eol_prop_against_file(const char *path
,
2407 svn_wc_canonicalize_svn_prop_get_file_t getter
,
2411 svn_stream_t
*translating_stream
;
2413 const svn_string_t
*mime_type
;
2414 const char *path_display
2415 = svn_path_is_url(path
) ? path
: svn_path_local_style(path
, pool
);
2417 /* First just ask the "getter" for the MIME type. */
2418 SVN_ERR(getter(&mime_type
, NULL
, getter_baton
, pool
));
2420 /* See if this file has been determined to be binary. */
2421 if (mime_type
&& svn_mime_type_is_binary(mime_type
->data
))
2422 return svn_error_createf
2423 (SVN_ERR_ILLEGAL_TARGET
, NULL
,
2424 _("File '%s' has binary mime type property"),
2427 /* Now ask the getter for the contents of the file; this will do a
2428 newline translation. All we really care about here is whether or
2429 not the function fails on inconsistent line endings. The
2430 function is "translating" to an empty stream. This is
2431 sneeeeeeeeeeeaky. */
2432 translating_stream
= svn_subst_stream_translated(svn_stream_empty(pool
),
2433 "", FALSE
, NULL
, FALSE
,
2436 err
= getter(NULL
, translating_stream
, getter_baton
, pool
);
2439 err
= svn_stream_close(translating_stream
);
2441 if (err
&& err
->apr_err
== SVN_ERR_IO_INCONSISTENT_EOL
)
2442 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET
, err
,
2443 _("File '%s' has inconsistent newlines"),
2448 return SVN_NO_ERROR
;
2453 svn_wc_prop_set2(const char *name
,
2454 const svn_string_t
*value
,
2456 svn_wc_adm_access_t
*adm_access
,
2457 svn_boolean_t skip_checks
,
2460 apr_hash_t
*prophash
, *base_prophash
;
2461 enum svn_prop_kind prop_kind
= svn_property_kind(NULL
, name
);
2462 svn_stringbuf_t
*log_accum
= svn_stringbuf_create("", pool
);
2463 const svn_wc_entry_t
*entry
;
2465 if (prop_kind
== svn_prop_wc_kind
)
2466 return svn_wc__wcprop_set(name
, value
, path
, adm_access
, TRUE
, pool
);
2467 else if (prop_kind
== svn_prop_entry_kind
)
2468 return svn_error_createf
/* we don't do entry properties here */
2469 (SVN_ERR_BAD_PROP_KIND
, NULL
,
2470 _("Property '%s' is an entry property"), name
);
2472 /* Else, handle a regular property: */
2474 /* Get the entry and name for this path. */
2475 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, FALSE
, pool
));
2477 /* Get the access baton for the entry's directory. */
2478 if (entry
->kind
== svn_node_dir
)
2479 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
, path
, pool
));
2481 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
,
2482 svn_path_dirname(path
, pool
), pool
));
2484 /* Setting an inappropriate property is not allowed (unless
2485 overridden by 'skip_checks', in some circumstances). Deleting an
2486 inappropriate property is allowed, however, since older clients
2487 allowed (and other clients possibly still allow) setting it in
2489 if (value
&& svn_prop_is_svn_prop(name
))
2491 const svn_string_t
*new_value
;
2492 struct getter_baton
*gb
= apr_pcalloc(pool
, sizeof(*gb
));
2495 gb
->adm_access
= adm_access
;
2497 SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value
, name
, value
, path
,
2498 entry
->kind
, skip_checks
,
2499 get_file_for_validation
, gb
, pool
));
2503 if (entry
->kind
== svn_node_file
&& strcmp(name
, SVN_PROP_EXECUTABLE
) == 0)
2505 /* If the svn:executable property was set, then chmod +x.
2506 If the svn:executable property was deleted (NULL value passed
2507 in), then chmod -x. */
2509 SVN_ERR(svn_io_set_file_executable(path
, FALSE
, TRUE
, pool
));
2511 SVN_ERR(svn_io_set_file_executable(path
, TRUE
, TRUE
, pool
));
2514 if (entry
->kind
== svn_node_file
&& strcmp(name
, SVN_PROP_NEEDS_LOCK
) == 0)
2516 /* If the svn:needs-lock property was set to NULL, set the file
2519 SVN_ERR(svn_io_set_file_read_write(path
, FALSE
, pool
));
2521 /* If not, we'll set the file to read-only at commit time. */
2524 SVN_ERR_W(svn_wc__load_props(&base_prophash
, &prophash
, NULL
,
2525 adm_access
, path
, pool
),
2526 _("Failed to load properties from disk"));
2528 /* If we're changing this file's list of expanded keywords, then
2529 * we'll need to invalidate its text timestamp, since keyword
2530 * expansion affects the comparison of working file to text base.
2532 * Here we retrieve the old list of expanded keywords; after the
2533 * property is set, we'll grab the new list and see if it differs
2536 if (entry
->kind
== svn_node_file
&& strcmp(name
, SVN_PROP_KEYWORDS
) == 0)
2538 svn_string_t
*old_value
= apr_hash_get(prophash
, SVN_PROP_KEYWORDS
,
2539 APR_HASH_KEY_STRING
);
2540 apr_hash_t
*old_keywords
, *new_keywords
;
2542 SVN_ERR(svn_wc__get_keywords(&old_keywords
, path
, adm_access
,
2543 old_value
? old_value
->data
: "", pool
));
2544 SVN_ERR(svn_wc__get_keywords(&new_keywords
, path
, adm_access
,
2545 value
? value
->data
: "", pool
));
2547 if (svn_subst_keywords_differ2(old_keywords
, new_keywords
, FALSE
, pool
))
2549 svn_wc_entry_t tmp_entry
;
2551 /* If we changed the keywords or newlines, void the entry
2552 timestamp for this file, so svn_wc_text_modified_p() does
2553 a real (albeit slow) check later on. */
2554 tmp_entry
.kind
= svn_node_file
;
2555 tmp_entry
.text_time
= 0;
2556 SVN_ERR(svn_wc__loggy_entry_modify(&log_accum
, adm_access
,
2558 SVN_WC__ENTRY_MODIFY_TEXT_TIME
,
2563 /* Now we have all the properties in our hash. Simply merge the new
2564 property into it. */
2565 apr_hash_set(prophash
, name
, APR_HASH_KEY_STRING
, value
);
2567 SVN_ERR(svn_wc__install_props(&log_accum
, adm_access
, path
,
2568 base_prophash
, prophash
, FALSE
, pool
));
2569 SVN_ERR(svn_wc__write_log(adm_access
, 0, log_accum
, pool
));
2570 SVN_ERR(svn_wc__run_log(adm_access
, NULL
, pool
));
2572 return SVN_NO_ERROR
;
2577 svn_wc_prop_set(const char *name
,
2578 const svn_string_t
*value
,
2580 svn_wc_adm_access_t
*adm_access
,
2583 return svn_wc_prop_set2(name
, value
, path
, adm_access
, FALSE
, pool
);
2588 svn_wc_canonicalize_svn_prop(const svn_string_t
**propval_p
,
2589 const char *propname
,
2590 const svn_string_t
*propval
,
2592 svn_node_kind_t kind
,
2593 svn_boolean_t skip_some_checks
,
2594 svn_wc_canonicalize_svn_prop_get_file_t getter
,
2598 svn_stringbuf_t
*new_value
= NULL
;
2600 /* Keep this static, it may get stored (for read-only purposes) in a
2601 hash that outlives this function. */
2602 static const svn_string_t boolean_value
=
2604 SVN_PROP_BOOLEAN_TRUE
,
2605 sizeof(SVN_PROP_BOOLEAN_TRUE
) - 1
2608 SVN_ERR(validate_prop_against_node_kind(propname
, path
, kind
, pool
));
2610 if (!skip_some_checks
&& (strcmp(propname
, SVN_PROP_EOL_STYLE
) == 0))
2612 svn_subst_eol_style_t eol_style
;
2613 const char *ignored_eol
;
2614 new_value
= svn_stringbuf_create_from_string(propval
, pool
);
2615 svn_stringbuf_strip_whitespace(new_value
);
2616 svn_subst_eol_style_from_value(&eol_style
, &ignored_eol
, new_value
->data
);
2617 if (eol_style
== svn_subst_eol_style_unknown
)
2618 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL
, NULL
,
2619 _("Unrecognized line ending style for '%s'"),
2620 svn_path_local_style(path
, pool
));
2621 SVN_ERR(validate_eol_prop_against_file(path
, getter
, getter_baton
,
2624 else if (!skip_some_checks
&& (strcmp(propname
, SVN_PROP_MIME_TYPE
) == 0))
2626 new_value
= svn_stringbuf_create_from_string(propval
, pool
);
2627 svn_stringbuf_strip_whitespace(new_value
);
2628 SVN_ERR(svn_mime_type_validate(new_value
->data
, pool
));
2630 else if (strcmp(propname
, SVN_PROP_IGNORE
) == 0
2631 || strcmp(propname
, SVN_PROP_EXTERNALS
) == 0)
2633 /* Make sure that the last line ends in a newline */
2634 if (propval
->data
[propval
->len
- 1] != '\n')
2636 new_value
= svn_stringbuf_create_from_string(propval
, pool
);
2637 svn_stringbuf_appendbytes(new_value
, "\n", 1);
2640 /* Make sure this is a valid externals property. Do not
2641 allow 'skip_some_checks' to override, as there is no circumstance in
2642 which this is proper (because there is no circumstance in
2643 which Subversion can handle it). */
2644 if (strcmp(propname
, SVN_PROP_EXTERNALS
) == 0)
2646 /* We don't allow "." nor ".." as target directories in
2647 an svn:externals line. As it happens, our parse code
2648 checks for this, so all we have to is invoke it --
2649 we're not interested in the parsed result, only in
2650 whether or the parsing errored. */
2651 SVN_ERR(svn_wc_parse_externals_description3
2652 (NULL
, path
, propval
->data
, FALSE
, pool
));
2655 else if (strcmp(propname
, SVN_PROP_KEYWORDS
) == 0)
2657 new_value
= svn_stringbuf_create_from_string(propval
, pool
);
2658 svn_stringbuf_strip_whitespace(new_value
);
2660 else if (strcmp(propname
, SVN_PROP_EXECUTABLE
) == 0
2661 || strcmp(propname
, SVN_PROP_NEEDS_LOCK
) == 0)
2663 new_value
= svn_stringbuf_create_from_string(&boolean_value
, pool
);
2665 else if (strcmp(propname
, SVN_PROP_MERGEINFO
) == 0)
2667 apr_hash_t
*mergeinfo
;
2668 SVN_ERR(svn_mergeinfo_parse(&mergeinfo
, propval
->data
, pool
));
2672 *propval_p
= svn_string_create_from_buf(new_value
, pool
);
2674 *propval_p
= propval
;
2676 return SVN_NO_ERROR
;
2681 svn_wc_is_normal_prop(const char *name
)
2683 enum svn_prop_kind kind
= svn_property_kind(NULL
, name
);
2684 return (kind
== svn_prop_regular_kind
);
2689 svn_wc_is_wc_prop(const char *name
)
2691 enum svn_prop_kind kind
= svn_property_kind(NULL
, name
);
2692 return (kind
== svn_prop_wc_kind
);
2697 svn_wc_is_entry_prop(const char *name
)
2699 enum svn_prop_kind kind
= svn_property_kind(NULL
, name
);
2700 return (kind
== svn_prop_entry_kind
);
2704 /* Helper to optimize svn_wc_props_modified_p().
2706 If PATH_TO_PROP_FILE is nonexistent, is empty, or is of size 4 bytes
2707 ("END\n"), then set EMPTY_P to true. Otherwise set EMPTY_P to false,
2708 which means that the file must contain real properties. */
2709 static svn_error_t
*
2710 empty_props_p(svn_boolean_t
*empty_p
,
2711 const char *path_to_prop_file
,
2717 err
= svn_io_stat(&finfo
, path_to_prop_file
, APR_FINFO_MIN
| APR_FINFO_TYPE
,
2721 if (! APR_STATUS_IS_ENOENT(err
->apr_err
)
2722 && ! APR_STATUS_IS_ENOTDIR(err
->apr_err
))
2726 svn_error_clear(err
);
2733 /* If we remove props from a propfile, eventually the file will
2734 be empty, or, for working copies written by pre-1.3 libraries, will
2735 contain nothing but "END\n" */
2736 if (finfo
.filetype
== APR_REG
&& (finfo
.size
== 4 || finfo
.size
== 0))
2741 /* If the size is between 1 and 4, then something is corrupt.
2742 If the size is between 4 and 16, then something is corrupt,
2743 because 16 is the -smallest- the file can possibly be if it
2744 contained only one property. So long as we say it is "not
2745 empty", we will discover such corruption later when we try
2746 to read the properties from the file. */
2749 return SVN_NO_ERROR
;
2753 /* Simple wrapper around empty_props_p, and inversed. */
2755 svn_wc__has_props(svn_boolean_t
*has_props
,
2757 svn_wc_adm_access_t
*adm_access
,
2760 svn_boolean_t is_empty
;
2761 const char *prop_path
;
2762 const svn_wc_entry_t
*entry
;
2763 svn_boolean_t has_propcaching
=
2764 svn_wc__adm_wc_format(adm_access
) > SVN_WC__NO_PROPCACHING_VERSION
;
2766 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
2768 /*### Maybe assert (entry); calling svn_wc__has_props
2769 for an unversioned path is bogus */
2773 return SVN_NO_ERROR
;
2776 /* Use the flag in the entry if the WC is recent enough. */
2777 if (has_propcaching
)
2779 *has_props
= entry
->has_props
;
2780 return SVN_NO_ERROR
;
2783 /* The rest is for compatibility with WCs that don't have propcaching. */
2785 SVN_ERR(svn_wc__prop_path(&prop_path
, path
, entry
->kind
,
2786 svn_wc__props_working
, FALSE
, pool
));
2787 SVN_ERR(empty_props_p(&is_empty
, prop_path
, pool
));
2794 return SVN_NO_ERROR
;
2798 /* Common implementation for svn_wc_props_modified_p()
2799 and svn_wc__props_modified().
2801 Set *MODIFIED_P to true if PATH's properties are modified
2802 with regard to the base revision, else set MODIFIED_P to false.
2804 If WHICH_PROPS is non-null and there are prop mods then set
2805 *WHICH_PROPS to a (const char *propname) ->
2806 (const svn_string_t *propvalue) key:value mapping of only
2807 the modified properties. */
2808 static svn_error_t
*
2809 modified_props(svn_boolean_t
*modified_p
,
2811 apr_hash_t
**which_props
,
2812 svn_wc_adm_access_t
*adm_access
,
2815 const char *prop_path
;
2816 const char *prop_base_path
;
2817 const svn_wc_entry_t
*entry
;
2818 apr_pool_t
*subpool
= svn_pool_create(pool
);
2819 int wc_format
= svn_wc__adm_wc_format(adm_access
);
2820 svn_boolean_t want_props
= which_props
? TRUE
: FALSE
;
2823 *which_props
= apr_hash_make(pool
);
2825 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, TRUE
, subpool
));
2827 /* If we have no entry, we can't have any prop mods. */
2830 *modified_p
= FALSE
;
2834 /* For newer WCs, if there is an entry for the path, we have a fast
2835 * and nice way to retrieve the information from the entry. */
2836 if (wc_format
> SVN_WC__NO_PROPCACHING_VERSION
)
2838 /* Only continue if there are prop mods
2839 and we want to know the details. */
2840 *modified_p
= entry
->has_prop_mods
;
2841 if (!*modified_p
|| !want_props
)
2845 /* So, we have a WC in an older format or we have propcaching
2846 but need to find the specific prop changes. Either way we
2847 have some work to do... */
2849 /* First, get the paths of the working and 'base' prop files. */
2850 SVN_ERR(svn_wc__prop_path(&prop_path
, path
, entry
->kind
,
2851 svn_wc__props_working
, FALSE
, subpool
));
2852 SVN_ERR(svn_wc__prop_path(&prop_base_path
, path
, entry
->kind
,
2853 svn_wc__props_base
, FALSE
, subpool
));
2855 /* Check for numerous easy outs on older WC formats before we
2856 resort to svn_prop_diffs(). */
2857 if (wc_format
<= SVN_WC__NO_PROPCACHING_VERSION
)
2859 svn_boolean_t bempty
, wempty
;
2860 /* Decide if either path is "empty" of properties. */
2861 SVN_ERR(empty_props_p(&wempty
, prop_path
, subpool
));
2862 SVN_ERR(empty_props_p(&bempty
, prop_base_path
, subpool
));
2864 /* If something is scheduled for replacement, we do *not* want to
2865 pay attention to any base-props; they might be residual from the
2866 old deleted file. */
2867 if (entry
->schedule
== svn_wc_schedule_replace
)
2869 *modified_p
= wempty
? FALSE
: TRUE
;
2871 /* Only continue if there are prop mods
2872 and we want to know the details. */
2873 if (!*modified_p
|| !want_props
)
2877 /* Easy out: if the base file is empty, we know the answer
2883 /* base is empty, but working is not */
2886 /* Only continue if we want to know the details. */
2892 /* base and working are both empty */
2893 *modified_p
= FALSE
;
2897 /* OK, so the base file is non-empty. One more easy out: */
2900 /* base exists, working is empty */
2903 /* Only continue if we want to know the details. */
2909 svn_boolean_t different_filesizes
;
2911 /* At this point, we know both files exists. Therefore we have no
2912 choice but to start checking their contents. */
2914 /* There are at least three tests we can try in succession. */
2916 /* Easy-answer attempt #1: (### this stat's the files again) */
2918 /* Check if the local and prop-base file have *definitely*
2919 different filesizes. */
2920 SVN_ERR(svn_io_filesizes_different_p(&different_filesizes
,
2924 if (different_filesizes
)
2928 /* Only continue if we want to know the details. */
2934 svn_boolean_t equal_timestamps
;
2936 /* Easy-answer attempt #2: (### this stat's the files again) */
2938 /* See if the local file's prop timestamp is the same as the
2939 one recorded in the administrative directory. */
2940 SVN_ERR(svn_wc__timestamps_equal_p(&equal_timestamps
, path
,
2944 if (equal_timestamps
)
2946 *modified_p
= FALSE
;
2951 } /* wc_format <= SVN_WC__NO_PROPCACHING_VERSION */
2953 /* If we get here, then we either known we have prop changes and want
2954 the specific changed props or we have a pre-propcaching WC version
2955 and still haven't figured out if we even have changes. Regardless,
2956 our approach is the same in both cases.
2958 In the pre-propcaching case:
2960 We know that the filesizes are the same,
2961 but the timestamps are different. That's still not enough
2962 evidence to make a correct decision; we need to look at the
2963 files' contents directly.
2965 However, doing a byte-for-byte comparison won't work. The two
2966 properties files may have the *exact* same name/value pairs, but
2967 arranged in a different order. (Our hashdump format makes no
2968 guarantees about ordering.)
2970 Therefore, rather than use contents_identical_p(), we use
2971 svn_prop_diffs(). */
2973 apr_array_header_t
*local_propchanges
;
2974 apr_hash_t
*localprops
= apr_hash_make(subpool
);
2975 apr_hash_t
*baseprops
= apr_hash_make(subpool
);
2977 /* ### Amazingly, this stats the files again! */
2978 SVN_ERR(load_prop_file(prop_path
, localprops
, subpool
));
2979 SVN_ERR(load_prop_file(prop_base_path
, baseprops
, subpool
));
2981 /* Don't use the subpool is we are hanging on to the changed props. */
2982 SVN_ERR(svn_prop_diffs(&local_propchanges
, localprops
,
2984 want_props
? pool
: subpool
));
2986 if (local_propchanges
->nelts
== 0)
2988 *modified_p
= FALSE
;
2994 /* Record the changed props if that's what we want. */
2998 for (i
= 0; i
< local_propchanges
->nelts
; i
++)
3000 svn_prop_t
*propt
= &APR_ARRAY_IDX(local_propchanges
, i
,
3002 apr_hash_set(*which_props
, propt
->name
,
3003 APR_HASH_KEY_STRING
, propt
->value
);
3010 svn_pool_destroy(subpool
);
3012 return SVN_NO_ERROR
;
3017 svn_wc__props_modified(const char *path
,
3018 apr_hash_t
**which_props
,
3019 svn_wc_adm_access_t
*adm_access
,
3022 svn_boolean_t modified_p
;
3023 return modified_props(&modified_p
, path
, which_props
, adm_access
, pool
);
3028 svn_wc_props_modified_p(svn_boolean_t
*modified_p
,
3030 svn_wc_adm_access_t
*adm_access
,
3033 return modified_props(modified_p
, path
, NULL
, adm_access
, pool
);
3038 svn_wc__has_prop_mods(svn_boolean_t
*prop_mods
,
3040 svn_wc_adm_access_t
*adm_access
,
3044 /* For an enough recent WC, we can have a really easy out. */
3045 if (svn_wc__adm_wc_format(adm_access
) > SVN_WC__NO_PROPCACHING_VERSION
)
3047 const svn_wc_entry_t
*entry
;
3048 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, TRUE
, pool
));
3049 *prop_mods
= entry
->has_prop_mods
;
3053 apr_array_header_t
*propmods
;
3054 apr_hash_t
*localprops
= apr_hash_make(pool
);
3055 apr_hash_t
*baseprops
= apr_hash_make(pool
);
3057 /* Load all properties into hashes */
3058 SVN_ERR(svn_wc__load_props(&baseprops
, &localprops
, NULL
,
3059 adm_access
, path
, pool
));
3061 /* Get an array of local changes by comparing the hashes. */
3062 SVN_ERR(svn_prop_diffs(&propmods
, localprops
, baseprops
, pool
));
3064 *prop_mods
= propmods
->nelts
> 0;
3067 return SVN_NO_ERROR
;
3072 svn_wc_get_prop_diffs(apr_array_header_t
**propchanges
,
3073 apr_hash_t
**original_props
,
3075 svn_wc_adm_access_t
*adm_access
,
3078 const svn_wc_entry_t
*entry
;
3079 apr_hash_t
*baseprops
, *props
;
3080 const char *entryname
;
3082 /*### Maybe assert (entry); calling svn_wc_get_prop_diffs
3083 for an unversioned path is bogus */
3084 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
, FALSE
, pool
));
3089 *original_props
= apr_hash_make(pool
);
3092 *propchanges
= apr_array_make(pool
, 0, sizeof(svn_prop_t
));
3094 return SVN_NO_ERROR
;
3097 if (entry
->kind
== svn_node_dir
)
3099 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
, path
, pool
));
3100 entryname
= SVN_WC_ENTRY_THIS_DIR
;
3104 const char *dirname
;
3105 svn_path_split(path
, &dirname
, &entryname
, pool
);
3106 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, adm_access
, dirname
, pool
));
3109 SVN_ERR(svn_wc__load_props(&baseprops
, propchanges
? &props
: NULL
, NULL
,
3110 adm_access
, path
, pool
));
3112 if (original_props
!= NULL
)
3113 *original_props
= baseprops
;
3115 if (propchanges
!= NULL
)
3116 SVN_ERR(svn_prop_diffs(propchanges
, props
, baseprops
, pool
));
3118 return SVN_NO_ERROR
;
3131 * in the LINE_PARTS array and update the revision field in ITEM with
3132 * the revision if the revision is found. Set REV_IDX to the index in
3133 * LINE_PARTS where the revision specification starts. Remove from
3134 * LINE_PARTS the element(s) that specify the revision.
3135 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
3138 * If this function returns successfully, then LINE_PARTS will have
3139 * only two elements in it.
3141 static svn_error_t
*
3142 find_and_remove_externals_revision(int *rev_idx
,
3143 apr_array_header_t
*line_parts
,
3144 svn_wc_external_item2_t
*item
,
3145 const char *parent_directory_display
,
3150 for (i
= 0; i
< 2; ++i
)
3152 const char *token
= APR_ARRAY_IDX(line_parts
, i
, const char *);
3154 if (token
[0] == '-' && token
[1] == 'r')
3156 const char *digits_ptr
;
3157 const char *end_ptr
;
3163 if (token
[2] == '\0')
3165 /* There must be a total of four elements in the line if
3167 if (line_parts
->nelts
!= 4)
3171 digits_ptr
= APR_ARRAY_IDX(line_parts
, i
+1, const char *);
3175 /* There must be a total of three elements in the line
3177 if (line_parts
->nelts
!= 3)
3181 digits_ptr
= token
+2;
3184 item
->revision
.kind
= svn_opt_revision_number
;
3185 SVN_ERR(svn_revnum_parse(&item
->revision
.value
.number
,
3189 /* If there's trailing garbage after the digits, then treat
3190 the revision as invalid. */
3191 if (*end_ptr
!= '\0')
3194 /* Shift any line elements past the revision specification
3195 down over the revision specification. */
3196 for (j
= i
; j
< line_parts
->nelts
-shift_count
; ++j
)
3197 APR_ARRAY_IDX(line_parts
, j
, const char *) =
3198 APR_ARRAY_IDX(line_parts
, j
+shift_count
, const char *);
3199 for (j
= 0; j
< shift_count
; ++j
)
3200 apr_array_pop(line_parts
);
3202 /* Found the revision, so leave the function immediately, do
3203 * not continue looking for additional revisions. */
3204 return SVN_NO_ERROR
;
3208 /* No revision was found, so there must be exactly two items in the
3210 if (line_parts
->nelts
== 2)
3211 return SVN_NO_ERROR
;
3214 return svn_error_createf
3215 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION
, NULL
,
3216 _("Error parsing %s property on '%s': '%s'"),
3218 parent_directory_display
,
3223 svn_wc_parse_externals_description3(apr_array_header_t
**externals_p
,
3224 const char *parent_directory
,
3226 svn_boolean_t canonicalize_url
,
3229 apr_array_header_t
*lines
= svn_cstring_split(desc
, "\n\r", TRUE
, pool
);
3231 const char *parent_directory_display
= svn_path_is_url(parent_directory
) ?
3232 parent_directory
: svn_path_local_style(parent_directory
, pool
);
3235 *externals_p
= apr_array_make(pool
, 1, sizeof(svn_wc_external_item2_t
*));
3237 for (i
= 0; i
< lines
->nelts
; i
++)
3239 const char *line
= APR_ARRAY_IDX(lines
, i
, const char *);
3240 apr_array_header_t
*line_parts
;
3241 svn_wc_external_item2_t
*item
;
3244 svn_boolean_t token0_is_url
;
3245 svn_boolean_t token1_is_url
;
3247 /* Index into line_parts where the revision specification
3251 if ((! line
) || (line
[0] == '#'))
3256 line_parts
= svn_cstring_split(line
, " \t", TRUE
, pool
);
3258 SVN_ERR(svn_wc_external_item_create
3259 ((const svn_wc_external_item2_t
**) &item
, pool
));
3260 item
->revision
.kind
= svn_opt_revision_unspecified
;
3261 item
->peg_revision
.kind
= svn_opt_revision_unspecified
;
3264 * There are six different formats of externals:
3273 * The last three allow peg revisions in the URL.
3275 * With relative URLs and no '-rN' or '-r N', there is no way to
3276 * distinguish between 'DIR URL' and 'URL DIR' when URL is a
3277 * relative URL like /svn/repos/trunk, so this case is taken as
3280 if (line_parts
->nelts
< 2 || line_parts
->nelts
> 4)
3281 return svn_error_createf
3282 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION
, NULL
,
3283 _("Error parsing %s property on '%s': '%s'"),
3285 parent_directory_display
,
3288 /* To make it easy to check for the forms, find and remove -r N
3289 or -rN from the line item array. If it is found, rev_idx
3290 contains the index into line_parts where '-r' was found and
3291 set item->revision to the parsed revision. */
3292 SVN_ERR(find_and_remove_externals_revision(&rev_idx
, line_parts
, item
,
3293 parent_directory_display
,
3296 token0
= APR_ARRAY_IDX(line_parts
, 0, const char *);
3297 token1
= APR_ARRAY_IDX(line_parts
, 1, const char *);
3299 token0_is_url
= svn_path_is_url(token0
);
3300 token1_is_url
= svn_path_is_url(token1
);
3302 /* If -r is at the beginning of the line or the first token is
3303 an absolute URL or if the second token is not an absolute
3304 URL, then the URL supports peg revisions. */
3305 if (0 == rev_idx
|| token0_is_url
|| ! token1_is_url
)
3307 /* The URL is passed to svn_opt_parse_path in
3308 uncanonicalized form so that the scheme relative URL
3309 //hostname/foo is not collapsed to a server root relative
3310 URL /hostname/foo. */
3311 SVN_ERR(svn_opt_parse_path(&item
->peg_revision
, &item
->url
,
3313 item
->target_dir
= token1
;
3317 item
->target_dir
= token0
;
3319 item
->peg_revision
= item
->revision
;
3322 SVN_ERR(svn_opt_resolve_revisions(&item
->peg_revision
,
3323 &item
->revision
, TRUE
, FALSE
,
3326 item
->target_dir
= svn_path_canonicalize
3327 (svn_path_internal_style(item
->target_dir
, pool
), pool
);
3329 if (item
->target_dir
[0] == '\0' || item
->target_dir
[0] == '/'
3330 || svn_path_is_backpath_present(item
->target_dir
))
3331 return svn_error_createf
3332 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION
, NULL
,
3333 _("Invalid %s property on '%s': "
3334 "target '%s' is an absolute path or involves '..'"),
3336 parent_directory_display
,
3339 if (canonicalize_url
)
3340 item
->url
= svn_path_canonicalize(item
->url
, pool
);
3343 APR_ARRAY_PUSH(*externals_p
, svn_wc_external_item2_t
*) = item
;
3346 return SVN_NO_ERROR
;
3350 svn_wc_parse_externals_description2(apr_array_header_t
**externals_p
,
3351 const char *parent_directory
,
3355 apr_array_header_t
*list
;
3356 apr_pool_t
*subpool
= svn_pool_create(pool
);
3359 SVN_ERR(svn_wc_parse_externals_description3(externals_p
? &list
: NULL
,
3360 parent_directory
, desc
,
3365 *externals_p
= apr_array_make(pool
, list
->nelts
,
3366 sizeof(svn_wc_external_item_t
*));
3367 for (i
= 0; i
< list
->nelts
; i
++)
3369 svn_wc_external_item2_t
*item2
= APR_ARRAY_IDX(list
, i
,
3370 svn_wc_external_item2_t
*);
3371 svn_wc_external_item_t
*item
= apr_palloc(pool
, sizeof (*item
));
3373 if (item2
->target_dir
)
3374 item
->target_dir
= apr_pstrdup(pool
, item2
->target_dir
);
3376 item
->url
= apr_pstrdup(pool
, item2
->url
);
3377 item
->revision
= item2
->revision
;
3379 APR_ARRAY_PUSH(*externals_p
, svn_wc_external_item_t
*) = item
;
3383 svn_pool_destroy(subpool
);
3385 return SVN_NO_ERROR
;
3390 svn_wc_parse_externals_description(apr_hash_t
**externals_p
,
3391 const char *parent_directory
,
3395 apr_array_header_t
*list
;
3398 SVN_ERR(svn_wc_parse_externals_description2(externals_p
? &list
: NULL
,
3399 parent_directory
, desc
, pool
));
3401 /* Store all of the items into the hash if that was requested. */
3404 *externals_p
= apr_hash_make(pool
);
3405 for (i
= 0; i
< list
->nelts
; i
++)
3407 svn_wc_external_item_t
*item
;
3408 item
= APR_ARRAY_IDX(list
, i
, svn_wc_external_item_t
*);
3410 apr_hash_set(*externals_p
, item
->target_dir
,
3411 APR_HASH_KEY_STRING
, item
);
3414 return SVN_NO_ERROR
;
3418 svn_wc__has_special_property(apr_hash_t
*props
)
3420 return apr_hash_get(props
, SVN_PROP_SPECIAL
, APR_HASH_KEY_STRING
) != NULL
;
3424 svn_wc__has_magic_property(const apr_array_header_t
*properties
)
3428 for (i
= 0; i
< properties
->nelts
; i
++)
3430 const svn_prop_t
*property
= &APR_ARRAY_IDX(properties
, i
, svn_prop_t
);
3432 if (strcmp(property
->name
, SVN_PROP_EXECUTABLE
) == 0
3433 || strcmp(property
->name
, SVN_PROP_KEYWORDS
) == 0
3434 || strcmp(property
->name
, SVN_PROP_EOL_STYLE
) == 0
3435 || strcmp(property
->name
, SVN_PROP_SPECIAL
) == 0
3436 || strcmp(property
->name
, SVN_PROP_NEEDS_LOCK
) == 0)