sync.c revision 299742
1246145Shselasky/*
2246145Shselasky * ====================================================================
3246145Shselasky *    Licensed to the Apache Software Foundation (ASF) under one
4246145Shselasky *    or more contributor license agreements.  See the NOTICE file
5246145Shselasky *    distributed with this work for additional information
6246145Shselasky *    regarding copyright ownership.  The ASF licenses this file
7246145Shselasky *    to you under the Apache License, Version 2.0 (the
8246145Shselasky *    "License"); you may not use this file except in compliance
9246145Shselasky *    with the License.  You may obtain a copy of the License at
10246145Shselasky *
11246145Shselasky *      http://www.apache.org/licenses/LICENSE-2.0
12246145Shselasky *
13246145Shselasky *    Unless required by applicable law or agreed to in writing,
14246145Shselasky *    software distributed under the License is distributed on an
15246145Shselasky *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16246145Shselasky *    KIND, either express or implied.  See the License for the
17246145Shselasky *    specific language governing permissions and limitations
18246145Shselasky *    under the License.
19246145Shselasky * ====================================================================
20246145Shselasky */
21246145Shselasky
22246145Shselasky#include "svn_hash.h"
23246145Shselasky#include "svn_cmdline.h"
24246145Shselasky#include "svn_config.h"
25246145Shselasky#include "svn_pools.h"
26246145Shselasky#include "svn_delta.h"
27246145Shselasky#include "svn_dirent_uri.h"
28246145Shselasky#include "svn_path.h"
29246363Shselasky#include "svn_props.h"
30246363Shselasky#include "svn_auth.h"
31246145Shselasky#include "svn_opt.h"
32246145Shselasky#include "svn_ra.h"
33246145Shselasky#include "svn_utf.h"
34246145Shselasky#include "svn_subst.h"
35246145Shselasky#include "svn_string.h"
36246145Shselasky
37246145Shselasky#include "private/svn_string_private.h"
38246145Shselasky
39246145Shselasky#include "sync.h"
40246145Shselasky
41246145Shselasky#include "svn_private_config.h"
42246145Shselasky
43246145Shselasky#include <apr_network_io.h>
44246145Shselasky#include <apr_signal.h>
45246145Shselasky#include <apr_uuid.h>
46246145Shselasky
47246145Shselasky
48246145Shselasky/* Normalize the encoding and line ending style of *STR, so that it contains
49246145Shselasky * only LF (\n) line endings and is encoded in UTF-8. After return, *STR may
50246145Shselasky * point at a new svn_string_t* allocated in RESULT_POOL.
51246145Shselasky *
52246145Shselasky * If SOURCE_PROP_ENCODING is NULL, then *STR is presumed to be encoded in
53246145Shselasky * UTF-8.
54246145Shselasky *
55246145Shselasky * *WAS_NORMALIZED is set to TRUE when *STR needed line ending normalization.
56246145Shselasky * Otherwise it is set to FALSE.
57246145Shselasky *
58246145Shselasky * SCRATCH_POOL is used for temporary allocations.
59246145Shselasky */
60246145Shselaskystatic svn_error_t *
61246145Shselaskynormalize_string(const svn_string_t **str,
62246145Shselasky                 svn_boolean_t *was_normalized,
63246145Shselasky                 const char *source_prop_encoding,
64246145Shselasky                 apr_pool_t *result_pool,
65246145Shselasky                 apr_pool_t *scratch_pool)
66246145Shselasky{
67246145Shselasky  svn_string_t *new_str;
68246145Shselasky
69246145Shselasky  *was_normalized = FALSE;
70246145Shselasky
71246145Shselasky  if (*str == NULL)
72246145Shselasky    return SVN_NO_ERROR;
73246145Shselasky
74246145Shselasky  SVN_ERR_ASSERT((*str)->data != NULL);
75246145Shselasky
76246145Shselasky  if (source_prop_encoding == NULL)
77246145Shselasky    source_prop_encoding = "UTF-8";
78246145Shselasky
79246145Shselasky  new_str = NULL;
80246145Shselasky  SVN_ERR(svn_subst_translate_string2(&new_str, NULL, was_normalized,
81246145Shselasky                                      *str, source_prop_encoding, TRUE,
82246145Shselasky                                      result_pool, scratch_pool));
83246145Shselasky  *str = new_str;
84246145Shselasky
85246145Shselasky  return SVN_NO_ERROR;
86246145Shselasky}
87246145Shselasky
88246145Shselasky/* Remove r0 references from the mergeinfo string *STR.
89246145Shselasky *
90246145Shselasky * r0 was never a valid mergeinfo reference and cannot be committed with
91246145Shselasky * recent servers, but can be committed through a server older than 1.6.18
92246145Shselasky * for HTTP or older than 1.6.17 for the other protocols. See issue #4476
93246145Shselasky * "Mergeinfo containing r0 makes svnsync and dump and load fail".
94246145Shselasky *
95246145Shselasky * Set *WAS_CHANGED to TRUE if *STR was changed, otherwise to FALSE.
96246145Shselasky */
97246145Shselaskystatic svn_error_t *
98246145Shselaskyremove_r0_mergeinfo(const svn_string_t **str,
99246145Shselasky                    svn_boolean_t *was_changed,
100246145Shselasky                    apr_pool_t *result_pool,
101246145Shselasky                    apr_pool_t *scratch_pool)
102246145Shselasky{
103246145Shselasky  svn_stringbuf_t *new_str = svn_stringbuf_create_empty(result_pool);
104246145Shselasky  apr_array_header_t *lines;
105246145Shselasky  int i;
106246145Shselasky
107246145Shselasky  SVN_ERR_ASSERT(*str && (*str)->data);
108246145Shselasky
109246145Shselasky  *was_changed = FALSE;
110246145Shselasky
111246145Shselasky  /* for each line */
112246145Shselasky  lines = svn_cstring_split((*str)->data, "\n", FALSE, scratch_pool);
113246145Shselasky
114246145Shselasky  for (i = 0; i < lines->nelts; i++)
115246145Shselasky    {
116246145Shselasky      char *line = APR_ARRAY_IDX(lines, i, char *);
117246145Shselasky      char *colon;
118246145Shselasky      char *rangelist;
119246145Shselasky
120246145Shselasky      /* split at the last colon */
121246145Shselasky      colon = strrchr(line, ':');
122246145Shselasky
123246145Shselasky      if (! colon)
124246145Shselasky        return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
125246145Shselasky                                 _("Missing colon in svn:mergeinfo "
126246145Shselasky                                   "property"));
127246145Shselasky
128246145Shselasky      rangelist = colon + 1;
129246145Shselasky
130246145Shselasky      /* remove r0 */
131246145Shselasky      if (colon[1] == '0')
132246145Shselasky        {
133246145Shselasky          if (strncmp(rangelist, "0*,", 3) == 0)
134246145Shselasky            {
135246145Shselasky              rangelist += 3;
136246145Shselasky            }
137246145Shselasky          else if (strcmp(rangelist, "0*") == 0
138246145Shselasky                   || strncmp(rangelist, "0,", 2) == 0
139246145Shselasky                   || strncmp(rangelist, "0-1*", 4) == 0
140246145Shselasky                   || strncmp(rangelist, "0-1,", 4) == 0
141246145Shselasky                   || strcmp(rangelist, "0-1") == 0)
142246145Shselasky            {
143246145Shselasky              rangelist += 2;
144246145Shselasky            }
145246145Shselasky          else if (strcmp(rangelist, "0") == 0)
146246145Shselasky            {
147246145Shselasky              rangelist += 1;
148246145Shselasky            }
149246145Shselasky          else if (strncmp(rangelist, "0-", 2) == 0)
150246145Shselasky            {
151246145Shselasky              rangelist[0] = '1';
152246145Shselasky            }
153246145Shselasky        }
154246145Shselasky
155246145Shselasky      /* reassemble */
156246145Shselasky      if (rangelist[0])
157246145Shselasky        {
158246145Shselasky          if (new_str->len)
159246145Shselasky            svn_stringbuf_appendbyte(new_str, '\n');
160246145Shselasky          svn_stringbuf_appendbytes(new_str, line, colon + 1 - line);
161246145Shselasky          svn_stringbuf_appendcstr(new_str, rangelist);
162246145Shselasky        }
163246145Shselasky    }
164246145Shselasky
165246145Shselasky  if (strcmp((*str)->data, new_str->data) != 0)
166246145Shselasky    {
167246145Shselasky      *was_changed = TRUE;
168246145Shselasky    }
169246145Shselasky
170246145Shselasky  *str = svn_stringbuf__morph_into_string(new_str);
171246145Shselasky  return SVN_NO_ERROR;
172246145Shselasky}
173246145Shselasky
174246145Shselasky
175246145Shselasky/* Normalize the encoding and line ending style of the values of properties
176246145Shselasky * in REV_PROPS that "need translation" (according to
177246145Shselasky * svn_prop_needs_translation(), which is currently all svn:* props) so that
178246145Shselasky * they are encoded in UTF-8 and contain only LF (\n) line endings.
179246145Shselasky *
180246145Shselasky * The number of properties that needed line ending normalization is returned in
181246145Shselasky * *NORMALIZED_COUNT.
182246145Shselasky *
183246145Shselasky * No re-encoding is performed if SOURCE_PROP_ENCODING is NULL.
184246145Shselasky */
185246145Shselaskysvn_error_t *
186246145Shselaskysvnsync_normalize_revprops(apr_hash_t *rev_props,
187246145Shselasky                           int *normalized_count,
188246145Shselasky                           const char *source_prop_encoding,
189246145Shselasky                           apr_pool_t *pool)
190246145Shselasky{
191246145Shselasky  apr_hash_index_t *hi;
192246145Shselasky  *normalized_count = 0;
193246145Shselasky
194246145Shselasky  for (hi = apr_hash_first(pool, rev_props);
195246145Shselasky       hi;
196246145Shselasky       hi = apr_hash_next(hi))
197246145Shselasky    {
198246145Shselasky      const char *propname = apr_hash_this_key(hi);
199246145Shselasky      const svn_string_t *propval = apr_hash_this_val(hi);
200246145Shselasky
201246145Shselasky      if (svn_prop_needs_translation(propname))
202246145Shselasky        {
203246145Shselasky          svn_boolean_t was_normalized;
204246145Shselasky          SVN_ERR(normalize_string(&propval, &was_normalized,
205246145Shselasky                  source_prop_encoding, pool, pool));
206246145Shselasky
207246145Shselasky          /* Replace the existing prop value. */
208246145Shselasky          svn_hash_sets(rev_props, propname, propval);
209246145Shselasky
210246145Shselasky          if (was_normalized)
211246145Shselasky            (*normalized_count)++; /* Count it. */
212246145Shselasky        }
213246145Shselasky    }
214246145Shselasky  return SVN_NO_ERROR;
215246145Shselasky}
216246145Shselasky
217246145Shselasky
218246145Shselasky/*** Synchronization Editor ***/
219246145Shselasky
220246145Shselasky/* This editor has a couple of jobs.
221246145Shselasky *
222246145Shselasky * First, it needs to filter out the propchanges that can't be passed over
223246145Shselasky * libsvn_ra.
224246145Shselasky *
225246145Shselasky * Second, it needs to adjust for the fact that we might not actually have
226246145Shselasky * permission to see all of the data from the remote repository, which means
227246145Shselasky * we could get revisions that are totally empty from our point of view.
228246145Shselasky *
229246145Shselasky * Third, it needs to adjust copyfrom paths, adding the root url for the
230246145Shselasky * destination repository to the beginning of them.
231246145Shselasky */
232246145Shselasky
233246145Shselasky
234246145Shselasky/* Edit baton */
235246145Shselaskytypedef struct edit_baton_t {
236246145Shselasky  const svn_delta_editor_t *wrapped_editor;
237246145Shselasky  void *wrapped_edit_baton;
238246145Shselasky  const char *to_url;  /* URL we're copying into, for correct copyfrom URLs */
239246145Shselasky  const char *source_prop_encoding;
240246145Shselasky  svn_boolean_t called_open_root;
241246145Shselasky  svn_boolean_t got_textdeltas;
242246145Shselasky  svn_revnum_t base_revision;
243246145Shselasky  svn_boolean_t quiet;
244246145Shselasky  svn_boolean_t mergeinfo_tweaked;  /* Did we tweak svn:mergeinfo? */
245246145Shselasky  svn_boolean_t strip_mergeinfo;    /* Are we stripping svn:mergeinfo? */
246246145Shselasky  svn_boolean_t migrate_svnmerge;   /* Are we converting svnmerge.py data? */
247246145Shselasky  svn_boolean_t mergeinfo_stripped; /* Did we strip svn:mergeinfo? */
248246145Shselasky  svn_boolean_t svnmerge_migrated;  /* Did we convert svnmerge.py data? */
249246145Shselasky  svn_boolean_t svnmerge_blocked;   /* Was there any blocked svnmerge data? */
250246145Shselasky  int *normalized_node_props_counter;  /* Where to count normalizations? */
251246145Shselasky} edit_baton_t;
252246145Shselasky
253246145Shselasky
254246145Shselasky/* A dual-purpose baton for files and directories. */
255246145Shselaskytypedef struct node_baton_t {
256246145Shselasky  void *edit_baton;
257246145Shselasky  void *wrapped_node_baton;
258246145Shselasky} node_baton_t;
259246145Shselasky
260246145Shselasky
261246145Shselasky/*** Editor vtable functions ***/
262246145Shselasky
263246145Shselaskystatic svn_error_t *
264246145Shselaskyset_target_revision(void *edit_baton,
265246145Shselasky                    svn_revnum_t target_revision,
266246145Shselasky                    apr_pool_t *pool)
267246145Shselasky{
268246145Shselasky  edit_baton_t *eb = edit_baton;
269246145Shselasky  return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
270246145Shselasky                                                 target_revision, pool);
271246145Shselasky}
272246145Shselasky
273246145Shselaskystatic svn_error_t *
274246145Shselaskyopen_root(void *edit_baton,
275246145Shselasky          svn_revnum_t base_revision,
276246145Shselasky          apr_pool_t *pool,
277246145Shselasky          void **root_baton)
278246145Shselasky{
279246145Shselasky  edit_baton_t *eb = edit_baton;
280246145Shselasky  node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
281246145Shselasky
282246145Shselasky  SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
283246145Shselasky                                        base_revision, pool,
284246145Shselasky                                        &dir_baton->wrapped_node_baton));
285246145Shselasky
286246145Shselasky  eb->called_open_root = TRUE;
287246145Shselasky  dir_baton->edit_baton = edit_baton;
288246145Shselasky  *root_baton = dir_baton;
289246145Shselasky
290246145Shselasky  return SVN_NO_ERROR;
291246145Shselasky}
292246145Shselasky
293246145Shselaskystatic svn_error_t *
294246145Shselaskydelete_entry(const char *path,
295246145Shselasky             svn_revnum_t base_revision,
296246145Shselasky             void *parent_baton,
297246145Shselasky             apr_pool_t *pool)
298246145Shselasky{
299246145Shselasky  node_baton_t *pb = parent_baton;
300246145Shselasky  edit_baton_t *eb = pb->edit_baton;
301246145Shselasky
302246145Shselasky  return eb->wrapped_editor->delete_entry(path, base_revision,
303246145Shselasky                                          pb->wrapped_node_baton, pool);
304246145Shselasky}
305246145Shselasky
306246145Shselaskystatic svn_error_t *
307246145Shselaskyadd_directory(const char *path,
308246145Shselasky              void *parent_baton,
309246145Shselasky              const char *copyfrom_path,
310246145Shselasky              svn_revnum_t copyfrom_rev,
311246145Shselasky              apr_pool_t *pool,
312246145Shselasky              void **child_baton)
313246145Shselasky{
314246145Shselasky  node_baton_t *pb = parent_baton;
315246145Shselasky  edit_baton_t *eb = pb->edit_baton;
316246145Shselasky  node_baton_t *b = apr_palloc(pool, sizeof(*b));
317246145Shselasky
318246145Shselasky  /* if copyfrom_path is an fspath create a proper uri */
319246145Shselasky  if (copyfrom_path && copyfrom_path[0] == '/')
320246145Shselasky    copyfrom_path = svn_path_url_add_component2(eb->to_url,
321246145Shselasky                                                copyfrom_path + 1, pool);
322246145Shselasky
323246145Shselasky  SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
324246145Shselasky                                            copyfrom_path,
325246145Shselasky                                            copyfrom_rev, pool,
326246145Shselasky                                            &b->wrapped_node_baton));
327246145Shselasky
328246145Shselasky  b->edit_baton = eb;
329246145Shselasky  *child_baton = b;
330246145Shselasky
331246145Shselasky  return SVN_NO_ERROR;
332246145Shselasky}
333246145Shselasky
334246145Shselaskystatic svn_error_t *
335246145Shselaskyopen_directory(const char *path,
336246145Shselasky               void *parent_baton,
337246145Shselasky               svn_revnum_t base_revision,
338246145Shselasky               apr_pool_t *pool,
339246145Shselasky               void **child_baton)
340246145Shselasky{
341246145Shselasky  node_baton_t *pb = parent_baton;
342246145Shselasky  edit_baton_t *eb = pb->edit_baton;
343246145Shselasky  node_baton_t *db = apr_palloc(pool, sizeof(*db));
344246145Shselasky
345246145Shselasky  SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
346246145Shselasky                                             base_revision, pool,
347246145Shselasky                                             &db->wrapped_node_baton));
348246145Shselasky
349246145Shselasky  db->edit_baton = eb;
350246145Shselasky  *child_baton = db;
351246145Shselasky
352246145Shselasky  return SVN_NO_ERROR;
353246145Shselasky}
354246145Shselasky
355246145Shselaskystatic svn_error_t *
356246145Shselaskyadd_file(const char *path,
357246145Shselasky         void *parent_baton,
358246145Shselasky         const char *copyfrom_path,
359246145Shselasky         svn_revnum_t copyfrom_rev,
360246145Shselasky         apr_pool_t *pool,
361246145Shselasky         void **file_baton)
362246145Shselasky{
363246145Shselasky  node_baton_t *pb = parent_baton;
364246145Shselasky  edit_baton_t *eb = pb->edit_baton;
365246145Shselasky  node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
366246145Shselasky
367246145Shselasky  /* if copyfrom_path is an fspath create a proper uri */
368246145Shselasky  if (copyfrom_path && copyfrom_path[0] == '/')
369246145Shselasky    copyfrom_path = svn_path_url_add_component2(eb->to_url,
370246145Shselasky                                                copyfrom_path + 1, pool);
371246145Shselasky
372246145Shselasky  SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
373246145Shselasky                                       copyfrom_path, copyfrom_rev,
374246145Shselasky                                       pool, &fb->wrapped_node_baton));
375246145Shselasky
376246145Shselasky  fb->edit_baton = eb;
377246145Shselasky  *file_baton = fb;
378246145Shselasky
379246145Shselasky  return SVN_NO_ERROR;
380246145Shselasky}
381246145Shselasky
382246145Shselaskystatic svn_error_t *
383269921Shselaskyopen_file(const char *path,
384269921Shselasky          void *parent_baton,
385246145Shselasky          svn_revnum_t base_revision,
386269921Shselasky          apr_pool_t *pool,
387246145Shselasky          void **file_baton)
388246145Shselasky{
389246145Shselasky  node_baton_t *pb = parent_baton;
390246145Shselasky  edit_baton_t *eb = pb->edit_baton;
391246145Shselasky  node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
392246145Shselasky
393246145Shselasky  SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
394246145Shselasky                                        base_revision, pool,
395246145Shselasky                                        &fb->wrapped_node_baton));
396246145Shselasky
397246145Shselasky  fb->edit_baton = eb;
398246145Shselasky  *file_baton = fb;
399246145Shselasky
400269921Shselasky  return SVN_NO_ERROR;
401269921Shselasky}
402269921Shselasky
403269921Shselaskystatic svn_error_t *
404269921Shselaskyapply_textdelta(void *file_baton,
405269921Shselasky                const char *base_checksum,
406269921Shselasky                apr_pool_t *pool,
407269921Shselasky                svn_txdelta_window_handler_t *handler,
408269921Shselasky                void **handler_baton)
409269921Shselasky{
410246145Shselasky  node_baton_t *fb = file_baton;
411246145Shselasky  edit_baton_t *eb = fb->edit_baton;
412246145Shselasky
413246145Shselasky  if (! eb->quiet)
414246145Shselasky    {
415246145Shselasky      if (! eb->got_textdeltas)
416246145Shselasky        SVN_ERR(svn_cmdline_printf(pool, _("Transmitting file data ")));
417246145Shselasky      SVN_ERR(svn_cmdline_printf(pool, "."));
418246145Shselasky      SVN_ERR(svn_cmdline_fflush(stdout));
419246145Shselasky    }
420246145Shselasky
421246145Shselasky  eb->got_textdeltas = TRUE;
422246145Shselasky  return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
423246145Shselasky                                             base_checksum, pool,
424246145Shselasky                                             handler, handler_baton);
425246145Shselasky}
426246145Shselasky
427246145Shselaskystatic svn_error_t *
428246145Shselaskyclose_file(void *file_baton,
429246145Shselasky           const char *text_checksum,
430246145Shselasky           apr_pool_t *pool)
431246145Shselasky{
432246145Shselasky  node_baton_t *fb = file_baton;
433246145Shselasky  edit_baton_t *eb = fb->edit_baton;
434246145Shselasky  return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
435246145Shselasky                                        text_checksum, pool);
436246145Shselasky}
437246145Shselasky
438246145Shselaskystatic svn_error_t *
439246145Shselaskyabsent_file(const char *path,
440246145Shselasky            void *file_baton,
441246145Shselasky            apr_pool_t *pool)
442246145Shselasky{
443246145Shselasky  node_baton_t *fb = file_baton;
444246145Shselasky  edit_baton_t *eb = fb->edit_baton;
445246145Shselasky  return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
446246145Shselasky}
447246145Shselasky
448246145Shselaskystatic svn_error_t *
449246145Shselaskyclose_directory(void *dir_baton,
450246145Shselasky                apr_pool_t *pool)
451246145Shselasky{
452246145Shselasky  node_baton_t *db = dir_baton;
453246145Shselasky  edit_baton_t *eb = db->edit_baton;
454246145Shselasky  return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
455246145Shselasky}
456246145Shselasky
457246145Shselaskystatic svn_error_t *
458246145Shselaskyabsent_directory(const char *path,
459246145Shselasky                 void *dir_baton,
460246145Shselasky                 apr_pool_t *pool)
461246145Shselasky{
462246145Shselasky  node_baton_t *db = dir_baton;
463246145Shselasky  edit_baton_t *eb = db->edit_baton;
464246145Shselasky  return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
465246145Shselasky                                              pool);
466246145Shselasky}
467246145Shselasky
468246145Shselaskystatic svn_error_t *
469246145Shselaskychange_file_prop(void *file_baton,
470246145Shselasky                 const char *name,
471246145Shselasky                 const svn_string_t *value,
472246145Shselasky                 apr_pool_t *pool)
473246145Shselasky{
474246145Shselasky  node_baton_t *fb = file_baton;
475246145Shselasky  edit_baton_t *eb = fb->edit_baton;
476246145Shselasky
477246145Shselasky  /* only regular properties can pass over libsvn_ra */
478246145Shselasky  if (svn_property_kind2(name) != svn_prop_regular_kind)
479246145Shselasky    return SVN_NO_ERROR;
480246145Shselasky
481246145Shselasky  /* Maybe drop svn:mergeinfo.  */
482246145Shselasky  if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0))
483246145Shselasky    {
484246145Shselasky      eb->mergeinfo_stripped = TRUE;
485246145Shselasky      return SVN_NO_ERROR;
486246145Shselasky    }
487246145Shselasky
488246145Shselasky  /* Maybe drop (errantly set, as this is a file) svnmerge.py properties. */
489246145Shselasky  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0))
490246145Shselasky    {
491246145Shselasky      eb->svnmerge_migrated = TRUE;
492246145Shselasky      return SVN_NO_ERROR;
493246145Shselasky    }
494246145Shselasky
495246145Shselasky  /* Remember if we see any svnmerge-blocked properties.  (They really
496246145Shselasky     shouldn't be here, as this is a file, but whatever...)  */
497246145Shselasky  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0))
498246145Shselasky    {
499246145Shselasky      eb->svnmerge_blocked = TRUE;
500246145Shselasky    }
501246145Shselasky
502246145Shselasky  /* Normalize svn:* properties as necessary. */
503246145Shselasky  if (svn_prop_needs_translation(name))
504246145Shselasky    {
505246145Shselasky      svn_boolean_t was_normalized;
506246145Shselasky      svn_boolean_t mergeinfo_tweaked = FALSE;
507246145Shselasky
508246145Shselasky      /* Normalize encoding to UTF-8, and EOL style to LF. */
509246145Shselasky      SVN_ERR(normalize_string(&value, &was_normalized,
510246145Shselasky                               eb->source_prop_encoding, pool, pool));
511246145Shselasky      /* Correct malformed mergeinfo. */
512246145Shselasky      if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
513246145Shselasky        {
514246145Shselasky          SVN_ERR(remove_r0_mergeinfo(&value, &mergeinfo_tweaked,
515246145Shselasky                                      pool, pool));
516246145Shselasky          if (mergeinfo_tweaked)
517246145Shselasky            eb->mergeinfo_tweaked = TRUE;
518246145Shselasky        }
519246145Shselasky      if (was_normalized)
520246145Shselasky        (*(eb->normalized_node_props_counter))++;
521246145Shselasky    }
522246145Shselasky
523246145Shselasky  return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
524246145Shselasky                                              name, value, pool);
525246145Shselasky}
526246145Shselasky
527246145Shselaskystatic svn_error_t *
528246145Shselaskychange_dir_prop(void *dir_baton,
529246145Shselasky                const char *name,
530246145Shselasky                const svn_string_t *value,
531246145Shselasky                apr_pool_t *pool)
532246145Shselasky{
533246145Shselasky  node_baton_t *db = dir_baton;
534246145Shselasky  edit_baton_t *eb = db->edit_baton;
535246145Shselasky
536246145Shselasky  /* Only regular properties can pass over libsvn_ra */
537246145Shselasky  if (svn_property_kind2(name) != svn_prop_regular_kind)
538246145Shselasky    return SVN_NO_ERROR;
539246145Shselasky
540246145Shselasky  /* Maybe drop svn:mergeinfo.  */
541246145Shselasky  if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0))
542246145Shselasky    {
543246145Shselasky      eb->mergeinfo_stripped = TRUE;
544246145Shselasky      return SVN_NO_ERROR;
545246145Shselasky    }
546246145Shselasky
547246145Shselasky  /* Maybe convert svnmerge-integrated data into svn:mergeinfo.  (We
548246145Shselasky     ignore svnmerge-blocked for now.) */
549246145Shselasky  /* ### FIXME: Consult the mirror repository's HEAD prop values and
550246145Shselasky     ### merge svn:mergeinfo, svnmerge-integrated, and svnmerge-blocked. */
551246145Shselasky  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0))
552246145Shselasky    {
553246145Shselasky      if (value)
554246145Shselasky        {
555246145Shselasky          /* svnmerge-integrated differs from svn:mergeinfo in a pair
556246145Shselasky             of ways.  First, it can use tabs, newlines, or spaces to
557246145Shselasky             delimit source information.  Secondly, the source paths
558246145Shselasky             are relative URLs, whereas svn:mergeinfo uses relative
559246145Shselasky             paths (not URI-encoded). */
560246145Shselasky          svn_error_t *err;
561246145Shselasky          svn_stringbuf_t *mergeinfo_buf = svn_stringbuf_create_empty(pool);
562246145Shselasky          svn_mergeinfo_t mergeinfo;
563246145Shselasky          int i;
564246145Shselasky          apr_array_header_t *sources =
565246145Shselasky            svn_cstring_split(value->data, " \t\n", TRUE, pool);
566246145Shselasky          svn_string_t *new_value;
567246145Shselasky
568246145Shselasky          for (i = 0; i < sources->nelts; i++)
569246145Shselasky            {
570246145Shselasky              const char *rel_path;
571246145Shselasky              apr_array_header_t *path_revs =
572246145Shselasky                svn_cstring_split(APR_ARRAY_IDX(sources, i, const char *),
573246145Shselasky                                  ":", TRUE, pool);
574246145Shselasky
575246145Shselasky              /* ### TODO: Warn? */
576246145Shselasky              if (path_revs->nelts != 2)
577246145Shselasky                continue;
578246145Shselasky
579246145Shselasky              /* Append this source's mergeinfo data. */
580246145Shselasky              rel_path = APR_ARRAY_IDX(path_revs, 0, const char *);
581246145Shselasky              rel_path = svn_path_uri_decode(rel_path, pool);
582246145Shselasky              svn_stringbuf_appendcstr(mergeinfo_buf, rel_path);
583246145Shselasky              svn_stringbuf_appendcstr(mergeinfo_buf, ":");
584246145Shselasky              svn_stringbuf_appendcstr(mergeinfo_buf,
585246145Shselasky                                       APR_ARRAY_IDX(path_revs, 1,
586246145Shselasky                                                     const char *));
587246145Shselasky              svn_stringbuf_appendcstr(mergeinfo_buf, "\n");
588246145Shselasky            }
589246145Shselasky
590246145Shselasky          /* Try to parse the mergeinfo string we've created, just to
591246145Shselasky             check for bogosity.  If all goes well, we'll unparse it
592246145Shselasky             again and use that as our property value.  */
593246145Shselasky          err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_buf->data, pool);
594246145Shselasky          if (err)
595246145Shselasky            {
596246145Shselasky              svn_error_clear(err);
597246145Shselasky              return SVN_NO_ERROR;
598246145Shselasky            }
599246145Shselasky          SVN_ERR(svn_mergeinfo_to_string(&new_value, mergeinfo, pool));
600246145Shselasky          value = new_value;
601246145Shselasky        }
602246145Shselasky      name = SVN_PROP_MERGEINFO;
603246145Shselasky      eb->svnmerge_migrated = TRUE;
604246145Shselasky    }
605246145Shselasky
606246145Shselasky  /* Remember if we see any svnmerge-blocked properties. */
607246145Shselasky  if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0))
608246145Shselasky    {
609246145Shselasky      eb->svnmerge_blocked = TRUE;
610246145Shselasky    }
611246145Shselasky
612246145Shselasky  /* Normalize svn:* properties as necessary. */
613246145Shselasky  if (svn_prop_needs_translation(name))
614246145Shselasky    {
615246145Shselasky      svn_boolean_t was_normalized;
616246145Shselasky      svn_boolean_t mergeinfo_tweaked = FALSE;
617246145Shselasky
618246145Shselasky      /* Normalize encoding to UTF-8, and EOL style to LF. */
619246145Shselasky      SVN_ERR(normalize_string(&value, &was_normalized, eb->source_prop_encoding,
620246145Shselasky                               pool, pool));
621246145Shselasky      /* Maybe adjust svn:mergeinfo. */
622246145Shselasky      if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
623246145Shselasky        {
624246145Shselasky          SVN_ERR(remove_r0_mergeinfo(&value, &mergeinfo_tweaked,
625246145Shselasky                                      pool, pool));
626246145Shselasky          if (mergeinfo_tweaked)
627246145Shselasky            eb->mergeinfo_tweaked = TRUE;
628246145Shselasky        }
629246145Shselasky      if (was_normalized)
630246145Shselasky        (*(eb->normalized_node_props_counter))++;
631246145Shselasky    }
632246145Shselasky
633246145Shselasky  return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
634246145Shselasky                                             name, value, pool);
635246145Shselasky}
636246145Shselasky
637246145Shselaskystatic svn_error_t *
638246145Shselaskyclose_edit(void *edit_baton,
639246145Shselasky           apr_pool_t *pool)
640246145Shselasky{
641246145Shselasky  edit_baton_t *eb = edit_baton;
642246145Shselasky
643246145Shselasky  /* If we haven't opened the root yet, that means we're transfering
644246145Shselasky     an empty revision, probably because we aren't allowed to see the
645246145Shselasky     contents for some reason.  In any event, we need to open the root
646246145Shselasky     and close it again, before we can close out the edit, or the
647246145Shselasky     commit will fail. */
648246145Shselasky
649246145Shselasky  if (! eb->called_open_root)
650246145Shselasky    {
651246145Shselasky      void *baton;
652246145Shselasky      SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
653246145Shselasky                                            eb->base_revision, pool,
654246145Shselasky                                            &baton));
655246145Shselasky      SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
656246145Shselasky    }
657246145Shselasky
658246145Shselasky  if (! eb->quiet)
659246145Shselasky    {
660246145Shselasky      if (eb->got_textdeltas)
661246145Shselasky        SVN_ERR(svn_cmdline_printf(pool, "\n"));
662246145Shselasky      if (eb->mergeinfo_tweaked)
663246145Shselasky        SVN_ERR(svn_cmdline_printf(pool,
664246145Shselasky                                   "NOTE: Adjusted Subversion mergeinfo in "
665246145Shselasky                                   "this revision.\n"));
666246145Shselasky      if (eb->mergeinfo_stripped)
667246145Shselasky        SVN_ERR(svn_cmdline_printf(pool,
668246145Shselasky                                   "NOTE: Dropped Subversion mergeinfo "
669246145Shselasky                                   "from this revision.\n"));
670246145Shselasky      if (eb->svnmerge_migrated)
671246145Shselasky        SVN_ERR(svn_cmdline_printf(pool,
672246145Shselasky                                   "NOTE: Migrated 'svnmerge-integrated' in "
673246145Shselasky                                   "this revision.\n"));
674246145Shselasky      if (eb->svnmerge_blocked)
675246145Shselasky        SVN_ERR(svn_cmdline_printf(pool,
676246145Shselasky                                   "NOTE: Saw 'svnmerge-blocked' in this "
677246145Shselasky                                   "revision (but didn't migrate it).\n"));
678246145Shselasky    }
679246145Shselasky
680246145Shselasky  return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
681246145Shselasky}
682246145Shselasky
683246145Shselaskystatic svn_error_t *
684246145Shselaskyabort_edit(void *edit_baton,
685246145Shselasky           apr_pool_t *pool)
686246145Shselasky{
687246145Shselasky  edit_baton_t *eb = edit_baton;
688246145Shselasky  return eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool);
689246145Shselasky}
690246145Shselasky
691246145Shselasky
692246145Shselasky/*** Editor factory function ***/
693246145Shselasky
694246145Shselaskysvn_error_t *
695246145Shselaskysvnsync_get_sync_editor(const svn_delta_editor_t *wrapped_editor,
696246145Shselasky                        void *wrapped_edit_baton,
697246145Shselasky                        svn_revnum_t base_revision,
698246145Shselasky                        const char *to_url,
699246145Shselasky                        const char *source_prop_encoding,
700246145Shselasky                        svn_boolean_t quiet,
701246145Shselasky                        const svn_delta_editor_t **editor,
702246145Shselasky                        void **edit_baton,
703246145Shselasky                        int *normalized_node_props_counter,
704246145Shselasky                        apr_pool_t *pool)
705246145Shselasky{
706246145Shselasky  svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
707246145Shselasky  edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
708246145Shselasky
709246145Shselasky  tree_editor->set_target_revision = set_target_revision;
710246145Shselasky  tree_editor->open_root = open_root;
711246145Shselasky  tree_editor->delete_entry = delete_entry;
712246145Shselasky  tree_editor->add_directory = add_directory;
713246145Shselasky  tree_editor->open_directory = open_directory;
714246145Shselasky  tree_editor->change_dir_prop = change_dir_prop;
715246145Shselasky  tree_editor->close_directory = close_directory;
716246145Shselasky  tree_editor->absent_directory = absent_directory;
717246145Shselasky  tree_editor->add_file = add_file;
718246145Shselasky  tree_editor->open_file = open_file;
719246145Shselasky  tree_editor->apply_textdelta = apply_textdelta;
720246145Shselasky  tree_editor->change_file_prop = change_file_prop;
721246145Shselasky  tree_editor->close_file = close_file;
722246145Shselasky  tree_editor->absent_file = absent_file;
723246145Shselasky  tree_editor->close_edit = close_edit;
724246145Shselasky  tree_editor->abort_edit = abort_edit;
725246145Shselasky
726246145Shselasky  eb->wrapped_editor = wrapped_editor;
727246145Shselasky  eb->wrapped_edit_baton = wrapped_edit_baton;
728246145Shselasky  eb->base_revision = base_revision;
729246145Shselasky  eb->to_url = to_url;
730246145Shselasky  eb->source_prop_encoding = source_prop_encoding;
731246145Shselasky  eb->quiet = quiet;
732246145Shselasky  eb->normalized_node_props_counter = normalized_node_props_counter;
733246145Shselasky
734246145Shselasky  if (getenv("SVNSYNC_UNSUPPORTED_STRIP_MERGEINFO"))
735246145Shselasky    {
736246145Shselasky      eb->strip_mergeinfo = TRUE;
737246145Shselasky    }
738246145Shselasky  if (getenv("SVNSYNC_UNSUPPORTED_MIGRATE_SVNMERGE"))
739246145Shselasky    {
740246145Shselasky      /* Current we can't merge property values.  That's only possible
741246145Shselasky         if all the properties to be merged were always modified in
742246145Shselasky         exactly the same revisions, or if we allow ourselves to
743246145Shselasky         lookup the current state of properties in the sync
744246145Shselasky         destination.  So for now, migrating svnmerge.py data implies
745246145Shselasky         stripping pre-existing svn:mergeinfo. */
746246145Shselasky      /* ### FIXME: Do a real migration by consulting the mirror
747246145Shselasky         ### repository's HEAD propvalues and merging svn:mergeinfo,
748246145Shselasky         ### svnmerge-integrated, and svnmerge-blocked together. */
749246145Shselasky      eb->migrate_svnmerge = TRUE;
750246145Shselasky      eb->strip_mergeinfo = TRUE;
751246145Shselasky    }
752246145Shselasky
753246145Shselasky  *editor = tree_editor;
754246145Shselasky  *edit_baton = eb;
755246145Shselasky
756246145Shselasky  return SVN_NO_ERROR;
757246145Shselasky}
758246145Shselasky
759246145Shselasky