~svn/ubuntu/raring/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/libsvn_repos/reporter.c

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-12-05 01:26:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20051205012614-qom4xfypgtsqc2xq
Tags: 1.2.3dfsg1-3ubuntu1
Merge with the final Debian release of 1.2.3dfsg1-3, bringing in
fixes to the clean target, better documentation of the libdb4.3
upgrade and build fixes to work with swig1.3_1.3.27.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * reporter.c : `reporter' vtable routines for updates.
 
3
 *
 
4
 * ====================================================================
 
5
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 
6
 *
 
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.
 
12
 *
 
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
 * ====================================================================
 
17
 */
 
18
 
 
19
#include "svn_path.h"
 
20
#include "svn_fs.h"
 
21
#include "svn_repos.h"
 
22
#include "svn_pools.h"
 
23
#include "svn_md5.h"
 
24
#include "svn_props.h"
 
25
#include "repos.h"
 
26
#include "svn_private_config.h"
 
27
 
 
28
#define NUM_CACHED_SOURCE_ROOTS 4
 
29
 
 
30
/* Theory of operation: we write report operations out to a temporary
 
31
   file as we receive them.  When the report is finished, we read the
 
32
   operations back out again, using them to guide the progression of
 
33
   the delta between the source and target revs.
 
34
 
 
35
   Temporary file format: we use a simple ad-hoc format to store the
 
36
   report operations.  Each report operation is the concatention of
 
37
   the following ("+/-" indicates the single character '+' or '-';
 
38
   <length> and <revnum> are written out as decimal strings):
 
39
 
 
40
     +/-                      '-' marks the end of the report
 
41
     If previous is +:
 
42
       <length>:<bytes>       Length-counted path string
 
43
       +/-                    '+' indicates the presence of link_path
 
44
       If previous is +:
 
45
         <length>:<bytes>     Length-counted link_path string
 
46
       +/-                    '+' indicates presence of revnum
 
47
       If previous is +:
 
48
         <revnum>:            Revnum of set_path or link_path
 
49
       +/-                    '+' indicates start_empty field set
 
50
       +/-                    '+' indicates presence of lock_token field.
 
51
       If previous is +:
 
52
         <length>:<bytes>     Length-counted lock_token string
 
53
 
 
54
   Terminology: for brevity, this file frequently uses the prefixes
 
55
   "s_" for source, "t_" for target, and "e_" for editor.  Also, to
 
56
   avoid overloading the word "target", we talk about the source
 
57
   "anchor and operand", rather than the usual "anchor and target". */
 
58
 
 
59
/* Describes the state of a working copy subtree, as given by a
 
60
   report.  Because we keep a lookahead pathinfo, we need to allocate
 
61
   each one of these things in a subpool of the report baton and free
 
62
   it when done. */
 
63
typedef struct path_info_t
 
64
{
 
65
  const char *path;            /* path, munged to be anchor-relative */
 
66
  const char *link_path;       /* NULL for set_path or delete_path */
 
67
  svn_revnum_t rev;            /* SVN_INVALID_REVNUM for delete_path */
 
68
  svn_boolean_t start_empty;   /* Meaningless for delete_path */
 
69
  const char *lock_token;      /* NULL if no token */
 
70
  apr_pool_t *pool;            /* Container pool */
 
71
} path_info_t;
 
72
 
 
73
/* A structure used by the routines within the `reporter' vtable,
 
74
   driven by the client as it describes its working copy revisions. */
 
75
typedef struct report_baton_t
 
76
{
 
77
  /* Parameters remembered from svn_repos_begin_report */
 
78
  svn_repos_t *repos;
 
79
  const char *fs_base;         /* FS path corresponding to wc anchor */
 
80
  const char *s_operand;       /* Anchor-relative wc target (may be empty) */
 
81
  svn_revnum_t t_rev;          /* Revnum which the edit will bring the wc to */
 
82
  const char *t_path;          /* FS path the edit will bring the wc to */
 
83
  svn_boolean_t text_deltas;   /* Whether to report text deltas */
 
84
  svn_boolean_t recurse;
 
85
  svn_boolean_t ignore_ancestry;
 
86
  svn_boolean_t is_switch;
 
87
  const svn_delta_editor_t *editor;
 
88
  void *edit_baton; 
 
89
  svn_repos_authz_func_t authz_read_func;
 
90
  void *authz_read_baton;
 
91
 
 
92
  /* The temporary file in which we are stashing the report. */
 
93
  apr_file_t *tempfile;
 
94
 
 
95
  /* For the actual editor drive, we'll need a lookahead path info
 
96
     entry, a cache of FS roots, and a pool to store them. */
 
97
  path_info_t *lookahead;
 
98
  svn_fs_root_t *t_root;
 
99
  svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS];
 
100
  apr_pool_t *pool;
 
101
} report_baton_t;
 
102
 
 
103
/* The type of a function that accepts changes to an object's property
 
104
   list.  OBJECT is the object whose properties are being changed.
 
105
   NAME is the name of the property to change.  VALUE is the new value
 
106
   for the property, or zero if the property should be deleted. */
 
107
typedef svn_error_t *proplist_change_fn_t (report_baton_t *b, void *object,
 
108
                                           const char *name,
 
109
                                           const svn_string_t *value,
 
110
                                           apr_pool_t *pool);
 
111
 
 
112
static svn_error_t *delta_dirs (report_baton_t *b, svn_revnum_t s_rev,
 
113
                                const char *s_path, const char *t_path,
 
114
                                void *dir_baton, const char *e_path,
 
115
                                svn_boolean_t start_empty, apr_pool_t *pool);
 
116
 
 
117
/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
 
118
 
 
119
static svn_error_t *
 
120
read_number (apr_uint64_t *num, apr_file_t *temp, apr_pool_t *pool)
 
121
{
 
122
  char c;
 
123
 
 
124
  *num = 0;
 
125
  while (1)
 
126
    {
 
127
      SVN_ERR (svn_io_file_getc (&c, temp, pool));
 
128
      if (c == ':')
 
129
        break;
 
130
      *num = *num * 10 + (c - '0');
 
131
    }
 
132
  return SVN_NO_ERROR;
 
133
}
 
134
 
 
135
static svn_error_t *
 
136
read_string (const char **str, apr_file_t *temp, apr_pool_t *pool)
 
137
{
 
138
  apr_uint64_t len;
 
139
  char *buf;
 
140
 
 
141
  SVN_ERR (read_number (&len, temp, pool));
 
142
  buf = apr_palloc (pool, len + 1);
 
143
  SVN_ERR (svn_io_file_read_full (temp, buf, len, NULL, pool));
 
144
  buf[len] = 0;
 
145
  *str = buf;
 
146
  return SVN_NO_ERROR;
 
147
}
 
148
 
 
149
static svn_error_t *
 
150
read_rev (svn_revnum_t *rev, apr_file_t *temp, apr_pool_t *pool)
 
151
{
 
152
  char c;
 
153
  apr_uint64_t num;
 
154
 
 
155
  SVN_ERR (svn_io_file_getc (&c, temp, pool));
 
156
  if (c == '+')
 
157
    {
 
158
      SVN_ERR (read_number (&num, temp, pool));
 
159
      *rev = num;
 
160
    }
 
161
  else
 
162
    *rev = SVN_INVALID_REVNUM;
 
163
  return SVN_NO_ERROR;
 
164
}
 
165
 
 
166
/* Read a report operation *PI out of TEMP.  Set *PI to NULL if we
 
167
   have reached the end of the report. */
 
168
static svn_error_t *
 
169
read_path_info (path_info_t **pi, apr_file_t *temp, apr_pool_t *pool)
 
170
{
 
171
  char c;
 
172
 
 
173
  SVN_ERR (svn_io_file_getc (&c, temp, pool));
 
174
  if (c == '-')
 
175
    {
 
176
      *pi = NULL;
 
177
      return SVN_NO_ERROR;
 
178
    }
 
179
 
 
180
  *pi = apr_palloc (pool, sizeof (**pi));
 
181
  SVN_ERR (read_string (&(*pi)->path, temp, pool));
 
182
  SVN_ERR (svn_io_file_getc (&c, temp, pool));
 
183
  if (c == '+')
 
184
    SVN_ERR (read_string (&(*pi)->link_path, temp, pool));
 
185
  else
 
186
    (*pi)->link_path = NULL;
 
187
  SVN_ERR (read_rev (&(*pi)->rev, temp, pool));
 
188
  SVN_ERR (svn_io_file_getc (&c, temp, pool));
 
189
  (*pi)->start_empty = (c == '+');
 
190
  SVN_ERR (svn_io_file_getc (&c, temp, pool));
 
191
  if (c == '+')
 
192
    SVN_ERR (read_string (&(*pi)->lock_token, temp, pool));
 
193
  else
 
194
    (*pi)->lock_token = NULL;
 
195
  (*pi)->pool = pool;
 
196
  return SVN_NO_ERROR;
 
197
}
 
198
 
 
199
/* Return true if PI's path is a child of PREFIX (which has length PLEN). */
 
200
static svn_boolean_t 
 
201
relevant (path_info_t *pi, const char *prefix, apr_size_t plen)
 
202
{
 
203
  return (pi && strncmp (pi->path, prefix, plen) == 0 &&
 
204
          (!*prefix || pi->path[plen] == '/'));
 
205
}
 
206
 
 
207
/* Fetch the next pathinfo from B->tempfile for a descendent of
 
208
   PREFIX.  If the next pathinfo is for an immediate child of PREFIX,
 
209
   set *ENTRY to the path component of the report information and
 
210
   *INFO to the path information for that entry.  If the next pathinfo
 
211
   is for a grandchild or other more remote descendent of PREFIX, set
 
212
   *ENTRY to the immediate child corresponding to that descendent and
 
213
   set *INFO to NULL.  If the next pathinfo is not for a descendent of
 
214
   PREFIX, or if we reach the end of the report, set both *ENTRY and
 
215
   *INFO to NULL.
 
216
 
 
217
   At all times, B->lookahead is presumed to be the next pathinfo not
 
218
   yet returned as an immediate child, or NULL if we have reached the
 
219
   end of the report.  Because we use a lookahead element, we can't
 
220
   rely on the usual nested pool lifetimes, so allocate each pathinfo
 
221
   in a subpool of the report baton's pool.  The caller should delete
 
222
   (*INFO)->pool when it is done with the information. */
 
223
static svn_error_t *
 
224
fetch_path_info (report_baton_t *b, const char **entry, path_info_t **info,
 
225
                 const char *prefix, apr_pool_t *pool)
 
226
{
 
227
  apr_size_t plen = strlen (prefix);
 
228
  const char *relpath, *sep;
 
229
  apr_pool_t *subpool;
 
230
 
 
231
  if (!relevant (b->lookahead, prefix, plen))
 
232
    {
 
233
      /* No more entries relevant to prefix. */
 
234
      *entry = NULL;
 
235
      *info = NULL;
 
236
    }
 
237
  else
 
238
    {
 
239
      /* Take a look at the prefix-relative part of the path. */
 
240
      relpath = b->lookahead->path + (*prefix ? plen + 1 : 0);
 
241
      sep = strchr (relpath, '/');
 
242
      if (sep)
 
243
        {
 
244
          /* Return the immediate child part; do not advance. */
 
245
          *entry = apr_pstrmemdup (pool, relpath, sep - relpath);
 
246
          *info = NULL;
 
247
        }
 
248
      else
 
249
        {
 
250
          /* This is an immediate child; return it and advance. */
 
251
          *entry = relpath;
 
252
          *info = b->lookahead;
 
253
          subpool = svn_pool_create (b->pool);
 
254
          SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
 
255
        }
 
256
    }
 
257
  return SVN_NO_ERROR;
 
258
}
 
259
 
 
260
/* Skip all path info entries relevant to *PREFIX.  Call this when the
 
261
   editor drive skips a directory. */
 
262
static svn_error_t *
 
263
skip_path_info (report_baton_t *b, const char *prefix)
 
264
{
 
265
  apr_size_t plen = strlen (prefix);
 
266
  apr_pool_t *subpool;
 
267
 
 
268
  while (relevant (b->lookahead, prefix, plen))
 
269
    {
 
270
      svn_pool_destroy (b->lookahead->pool);
 
271
      subpool = svn_pool_create (b->pool);
 
272
      SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
 
273
    }
 
274
  return SVN_NO_ERROR;
 
275
}
 
276
 
 
277
/* Return true if there is at least one path info entry relevant to *PREFIX. */
 
278
static svn_boolean_t
 
279
any_path_info (report_baton_t *b, const char *prefix)
 
280
{
 
281
  return relevant (b->lookahead, prefix, strlen(prefix));
 
282
}
 
283
 
 
284
/* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */
 
285
 
 
286
/* While driving the editor, the target root will remain constant, but
 
287
   we may have to jump around between source roots depending on the
 
288
   state of the working copy.  If we were to open a root each time we
 
289
   revisit a rev, we would get no benefit from node-id caching; on the
 
290
   other hand, if we hold open all the roots we ever visit, we'll use
 
291
   an unbounded amount of memory.  As a compromise, we maintain a
 
292
   fixed-size LRU cache of source roots.  get_source_root retrieves a
 
293
   root from the cache, using POOL to allocate the new root if
 
294
   necessary.  Be careful not to hold onto the root for too long,
 
295
   particularly after recursing, since another call to get_source_root
 
296
   can close it. */
 
297
static svn_error_t *
 
298
get_source_root (report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev)
 
299
{
 
300
  int i;
 
301
  svn_fs_root_t *root, *prev = NULL;
 
302
 
 
303
  /* Look for the desired root in the cache, sliding all the unmatched
 
304
     entries backwards a slot to make room for the right one. */
 
305
  for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
 
306
    {
 
307
      root = b->s_roots[i];
 
308
      b->s_roots[i] = prev;
 
309
      if (root && svn_fs_revision_root_revision (root) == rev)
 
310
        break;
 
311
      prev = root;
 
312
    }
 
313
 
 
314
  /* If we didn't find it, throw out the oldest root and open a new one. */
 
315
  if (i == NUM_CACHED_SOURCE_ROOTS)
 
316
    {
 
317
      if (prev)
 
318
        svn_fs_close_root (prev);
 
319
      SVN_ERR (svn_fs_revision_root (&root, b->repos->fs, rev, b->pool));
 
320
    }
 
321
 
 
322
  /* Assign the desired root to the first cache slot and hand it back. */
 
323
  b->s_roots[0] = root;
 
324
  *s_root = root;
 
325
  return SVN_NO_ERROR;
 
326
}
 
327
 
 
328
/* Call the directory property-setting function of B->editor to set
 
329
   the property NAME to VALUE on DIR_BATON. */
 
330
static svn_error_t *
 
331
change_dir_prop (report_baton_t *b, void *dir_baton, const char *name, 
 
332
                 const svn_string_t *value, apr_pool_t *pool)
 
333
{
 
334
  return b->editor->change_dir_prop (dir_baton, name, value, pool);
 
335
}
 
336
 
 
337
/* Call the file property-setting function of B->editor to set the
 
338
   property NAME to VALUE on FILE_BATON. */
 
339
static svn_error_t *
 
340
change_file_prop (report_baton_t *b, void *file_baton, const char *name, 
 
341
                  const svn_string_t *value, apr_pool_t *pool)
 
342
{
 
343
  return b->editor->change_file_prop (file_baton, name, value, pool);
 
344
}
 
345
 
 
346
/* Generate the appropriate property editing calls to turn the
 
347
   properties of S_REV/S_PATH into those of B->t_root/T_PATH.  If
 
348
   S_PATH is NULL, this is an add, so assume the target starts with no
 
349
   properties.  Pass OBJECT on to the editor function wrapper
 
350
   CHANGE_FN. */
 
351
static svn_error_t *
 
352
delta_proplists (report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
 
353
                 const char *t_path, const char *lock_token,
 
354
                 proplist_change_fn_t *change_fn,
 
355
                 void *object, apr_pool_t *pool)
 
356
{
 
357
  svn_fs_root_t *s_root;
 
358
  apr_hash_t *s_props, *t_props, *r_props;
 
359
  apr_array_header_t *prop_diffs;
 
360
  int i;
 
361
  svn_revnum_t crev;
 
362
  const char *uuid;
 
363
  svn_string_t *cr_str, *cdate, *last_author;
 
364
  svn_boolean_t changed;
 
365
  const svn_prop_t *pc;
 
366
  svn_lock_t *lock;
 
367
 
 
368
  /* Fetch the created-rev and send entry props. */
 
369
  SVN_ERR (svn_fs_node_created_rev (&crev, b->t_root, t_path, pool));
 
370
  if (SVN_IS_VALID_REVNUM (crev))
 
371
    {
 
372
      /* Transmit the committed-rev. */
 
373
      cr_str = svn_string_createf (pool, "%ld", crev);
 
374
      SVN_ERR (change_fn (b, object,
 
375
                          SVN_PROP_ENTRY_COMMITTED_REV, cr_str, pool));
 
376
 
 
377
      SVN_ERR (svn_fs_revision_proplist (&r_props, b->repos->fs, crev, pool));
 
378
 
 
379
      /* Transmit the committed-date. */
 
380
      cdate = apr_hash_get (r_props, SVN_PROP_REVISION_DATE,
 
381
                            APR_HASH_KEY_STRING);
 
382
      if (cdate || s_path)
 
383
        SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_COMMITTED_DATE, 
 
384
                            cdate, pool));
 
385
 
 
386
      /* Transmit the last-author. */
 
387
      last_author = apr_hash_get (r_props, SVN_PROP_REVISION_AUTHOR,
 
388
                                  APR_HASH_KEY_STRING);
 
389
      if (last_author || s_path)
 
390
        SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_LAST_AUTHOR,
 
391
                            last_author, pool));
 
392
 
 
393
      /* Transmit the UUID. */
 
394
      SVN_ERR (svn_fs_get_uuid (b->repos->fs, &uuid, pool));
 
395
      if (uuid || s_path)
 
396
        SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_UUID,
 
397
                            svn_string_create (uuid, pool), pool));
 
398
    }
 
399
 
 
400
  /* Update lock properties. */
 
401
  if (lock_token)
 
402
    {
 
403
      SVN_ERR (svn_fs_get_lock (&lock, b->repos->fs, t_path, pool));
 
404
 
 
405
      /* Delete a defunct lock. */
 
406
      if (! lock || strcmp (lock_token, lock->token) != 0)
 
407
        SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_LOCK_TOKEN,
 
408
                            NULL, pool));
 
409
    }
 
410
 
 
411
  if (s_path)
 
412
    {
 
413
      SVN_ERR (get_source_root (b, &s_root, s_rev));
 
414
 
 
415
      /* Is this deltification worth our time? */
 
416
      SVN_ERR (svn_fs_props_changed (&changed, b->t_root, t_path, s_root,
 
417
                                     s_path, pool));
 
418
      if (! changed)
 
419
        return SVN_NO_ERROR;
 
420
 
 
421
      /* If so, go ahead and get the source path's properties. */
 
422
      SVN_ERR (svn_fs_node_proplist (&s_props, s_root, s_path, pool));
 
423
    }
 
424
  else
 
425
    s_props = apr_hash_make (pool);
 
426
 
 
427
  /* Get the target path's properties */
 
428
  SVN_ERR (svn_fs_node_proplist (&t_props, b->t_root, t_path, pool));
 
429
 
 
430
  /* Now transmit the differences. */
 
431
  SVN_ERR (svn_prop_diffs (&prop_diffs, t_props, s_props, pool));
 
432
  for (i = 0; i < prop_diffs->nelts; i++)
 
433
    {
 
434
      pc = &APR_ARRAY_IDX (prop_diffs, i, svn_prop_t);
 
435
      SVN_ERR (change_fn (b, object, pc->name, pc->value, pool));
 
436
    }
 
437
 
 
438
  return SVN_NO_ERROR;
 
439
}
 
440
 
 
441
/* Set *CHANGED_P to TRUE if ROOT1/PATH1 and ROOT2/PATH2 have
 
442
   different contents, FALSE if they have the same contents. */
 
443
static svn_error_t *
 
444
compare_files (svn_boolean_t *changed_p, svn_fs_root_t *root1,
 
445
               const char *path1, svn_fs_root_t *root2, const char *path2,
 
446
               apr_pool_t *pool)
 
447
{
 
448
  svn_filesize_t size1, size2;
 
449
  unsigned char digest1[APR_MD5_DIGESTSIZE], digest2[APR_MD5_DIGESTSIZE];
 
450
  svn_stream_t *stream1, *stream2;
 
451
  char buf1[SVN_STREAM_CHUNK_SIZE], buf2[SVN_STREAM_CHUNK_SIZE];
 
452
  apr_size_t len1, len2;
 
453
 
 
454
  /* If the filesystem claims the things haven't changed, then they
 
455
     haven't changed. */
 
456
  SVN_ERR (svn_fs_contents_changed (changed_p, root1, path1,
 
457
                                    root2, path2, pool));
 
458
  if (!*changed_p)
 
459
    return SVN_NO_ERROR;
 
460
 
 
461
  /* From this point on, assume things haven't changed. */
 
462
  *changed_p = FALSE;
 
463
 
 
464
  /* So, things have changed.  But we need to know if the two sets of
 
465
     file contents are actually different.  If they have differing
 
466
     sizes, then we know they differ. */
 
467
  SVN_ERR (svn_fs_file_length (&size1, root1, path1, pool));
 
468
  SVN_ERR (svn_fs_file_length (&size2, root2, path2, pool));
 
469
  if (size1 != size2)
 
470
    {
 
471
      *changed_p = TRUE;
 
472
      return SVN_NO_ERROR;
 
473
    }
 
474
 
 
475
  /* Same sizes, huh?  Well, if their checksums differ, we know they
 
476
     differ. */
 
477
  SVN_ERR (svn_fs_file_md5_checksum (digest1, root1, path1, pool));
 
478
  SVN_ERR (svn_fs_file_md5_checksum (digest2, root2, path2, pool));
 
479
  if (!svn_md5_digests_match (digest1, digest2))
 
480
    {
 
481
      *changed_p = TRUE;
 
482
      return SVN_NO_ERROR;
 
483
    }
 
484
 
 
485
  /* Same sizes, same checksums.  Chances are reallllly good that they
 
486
     don't differ, but to be absolute sure, we need to compare bytes. */
 
487
  SVN_ERR (svn_fs_file_contents (&stream1, root1, path1, pool));
 
488
  SVN_ERR (svn_fs_file_contents (&stream2, root2, path2, pool));
 
489
 
 
490
  do
 
491
    {
 
492
      len1 = len2 = SVN_STREAM_CHUNK_SIZE;
 
493
      SVN_ERR (svn_stream_read (stream1, buf1, &len1));
 
494
      SVN_ERR (svn_stream_read (stream2, buf2, &len2));
 
495
      
 
496
      if (len1 != len2 || memcmp (buf1, buf2, len1))
 
497
        {
 
498
          *changed_p = TRUE;
 
499
          return SVN_NO_ERROR;
 
500
        }
 
501
    }
 
502
  while (len1 > 0);
 
503
 
 
504
  return SVN_NO_ERROR;
 
505
}
 
506
 
 
507
/* Make the appropriate edits on FILE_BATON to change its contents and
 
508
   properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
 
509
   possibly using LOCK_TOKEN to determine if the client's lock on the file
 
510
   is defunct. */
 
511
static svn_error_t *
 
512
delta_files (report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
 
513
             const char *s_path, const char *t_path, const char *lock_token,
 
514
             apr_pool_t *pool)
 
515
{
 
516
  svn_boolean_t changed;
 
517
  svn_fs_root_t *s_root = NULL;
 
518
  svn_txdelta_stream_t *dstream = NULL;
 
519
  unsigned char s_digest[APR_MD5_DIGESTSIZE];
 
520
  const char *s_hex_digest = NULL;
 
521
  svn_txdelta_window_handler_t dhandler;
 
522
  void *dbaton;
 
523
 
 
524
  /* Compare the files' property lists.  */
 
525
  SVN_ERR (delta_proplists (b, s_rev, s_path, t_path, lock_token,
 
526
                            change_file_prop, file_baton, pool));
 
527
 
 
528
  if (s_path)
 
529
    {
 
530
      SVN_ERR (get_source_root (b, &s_root, s_rev));
 
531
 
 
532
      /* Is this delta calculation worth our time?  If we are ignoring
 
533
         ancestry, then our editor implementor isn't concerned by the
 
534
         theoretical differences between "has contents which have not
 
535
         changed with respect to" and "has the same actual contents
 
536
         as".  We'll do everything we can to avoid transmitting even
 
537
         an empty text-delta in that case.  */
 
538
      if (b->ignore_ancestry)
 
539
        SVN_ERR (compare_files (&changed, b->t_root, t_path, s_root, s_path,
 
540
                                pool));
 
541
      else
 
542
        SVN_ERR (svn_fs_contents_changed (&changed, b->t_root, t_path, s_root,
 
543
                                          s_path, pool));
 
544
      if (!changed)
 
545
        return SVN_NO_ERROR;
 
546
 
 
547
      SVN_ERR (svn_fs_file_md5_checksum (s_digest, s_root, s_path, pool));
 
548
      s_hex_digest = svn_md5_digest_to_cstring (s_digest, pool);
 
549
    }
 
550
 
 
551
  /* Send the delta stream if desired, or just a NULL window if not. */
 
552
  SVN_ERR (b->editor->apply_textdelta (file_baton, s_hex_digest, pool,
 
553
                                       &dhandler, &dbaton));
 
554
  if (b->text_deltas)
 
555
    {
 
556
      SVN_ERR (svn_fs_get_file_delta_stream (&dstream, s_root, s_path,
 
557
                                             b->t_root, t_path, pool));
 
558
      return svn_txdelta_send_txstream (dstream, dhandler, dbaton, pool);
 
559
    }
 
560
  else
 
561
    return dhandler (NULL, dbaton);
 
562
}
 
563
 
 
564
/* Determine if the user is authorized to view B->t_root/PATH. */
 
565
static svn_error_t *
 
566
check_auth (report_baton_t *b, svn_boolean_t *allowed, const char *path,
 
567
            apr_pool_t *pool)
 
568
{
 
569
  if (b->authz_read_func)
 
570
    return b->authz_read_func (allowed, b->t_root, path,
 
571
                               b->authz_read_baton, pool);
 
572
  *allowed = TRUE;
 
573
  return SVN_NO_ERROR;
 
574
}
 
575
 
 
576
/* Create a dirent in *ENTRY for the given ROOT and PATH.  We use this to
 
577
   replace the source or target dirent when a report pathinfo tells us to
 
578
   change paths or revisions. */
 
579
static svn_error_t *
 
580
fake_dirent (const svn_fs_dirent_t **entry, svn_fs_root_t *root,
 
581
             const char *path, apr_pool_t *pool)
 
582
{
 
583
  svn_node_kind_t kind;
 
584
  svn_fs_dirent_t *ent;
 
585
 
 
586
  SVN_ERR (svn_fs_check_path (&kind, root, path, pool));
 
587
  if (kind == svn_node_none)
 
588
    *entry = NULL;
 
589
  else
 
590
    {
 
591
      ent = apr_palloc (pool, sizeof (**entry));
 
592
      ent->name = svn_path_basename (path, pool);
 
593
      SVN_ERR (svn_fs_node_id (&ent->id, root, path, pool));
 
594
      ent->kind = kind;
 
595
      *entry = ent;
 
596
    }
 
597
  return SVN_NO_ERROR;
 
598
}
 
599
 
 
600
 
 
601
/* Emit a series of editing operations to transform a source entry to
 
602
   a target entry.
 
603
 
 
604
   S_REV and S_PATH specify the source entry.  S_ENTRY contains the
 
605
   already-looked-up information about the node-revision existing at
 
606
   that location.
 
607
 
 
608
   B->t_root and T_PATH specify the target entry.  T_ENTRY contains
 
609
   the already-looked-up information about the node-revision existing
 
610
   at that location.
 
611
 
 
612
   DIR_BATON and E_PATH contain the parameters which should be passed
 
613
   to the editor calls--DIR_BATON for the parent directory baton and
 
614
   E_PATH for the pathname.  (E_PATH is the anchor-relative working
 
615
   copy pathname, which may differ from the source and target
 
616
   pathnames if the report contains a link_path.)
 
617
 
 
618
   INFO contains the report information for this working copy path.
 
619
   This function will internally modify the source and target entries
 
620
   as appropriate based on the report information.
 
621
 
 
622
   If RECURSE is not set, avoid operating on directories.  (Normally
 
623
   RECURSE is simply taken from B->recurse, but drive() needs to force
 
624
   us to recurse into the target even if that flag is not set.) */
 
625
static svn_error_t *
 
626
update_entry (report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
 
627
              const svn_fs_dirent_t *s_entry, const char *t_path,
 
628
              const svn_fs_dirent_t *t_entry, void *dir_baton,
 
629
              const char *e_path, path_info_t *info, svn_boolean_t recurse,
 
630
              apr_pool_t *pool)
 
631
{
 
632
  svn_fs_root_t *s_root;
 
633
  svn_boolean_t allowed, related;
 
634
  void *new_baton;
 
635
  unsigned char digest[APR_MD5_DIGESTSIZE];
 
636
  const char *hex_digest;
 
637
  int distance;
 
638
 
 
639
  /* For non-switch operations, follow link_path in the target. */
 
640
  if (info && info->link_path && !b->is_switch)
 
641
    {
 
642
      t_path = info->link_path;
 
643
      SVN_ERR (fake_dirent (&t_entry, b->t_root, t_path, pool));
 
644
    }
 
645
 
 
646
  if (info && !SVN_IS_VALID_REVNUM (info->rev))
 
647
    {
 
648
      /* Delete this entry in the source. */
 
649
      s_path = NULL;
 
650
      s_entry = NULL;
 
651
    }
 
652
  else if (info && s_path)
 
653
    {
 
654
      /* Follow the rev and possibly path in this entry. */
 
655
      s_path = (info->link_path) ? info->link_path : s_path;
 
656
      s_rev = info->rev;
 
657
      SVN_ERR (get_source_root (b, &s_root, s_rev));
 
658
      SVN_ERR (fake_dirent (&s_entry, s_root, s_path, pool));
 
659
    }
 
660
 
 
661
  /* Don't let the report carry us somewhere nonexistent. */
 
662
  if (s_path && !s_entry)
 
663
    return svn_error_createf (SVN_ERR_FS_NOT_FOUND, NULL,
 
664
                              _("Working copy path '%s' does not exist in "
 
665
                                "repository"), e_path);
 
666
 
 
667
  if (!recurse && ((s_entry && s_entry->kind == svn_node_dir)
 
668
                   || (t_entry && t_entry->kind == svn_node_dir)))
 
669
    return skip_path_info (b, e_path);
 
670
 
 
671
  /* If the source and target both exist and are of the same kind,
 
672
     then find out whether they're related.  If they're exactly the
 
673
     same, then we don't have to do anything (unless the report has
 
674
     changes to the source).  If we're ignoring ancestry, then any two
 
675
     nodes of the same type are related enough for us. */
 
676
  related = FALSE;
 
677
  if (s_entry && t_entry && s_entry->kind == t_entry->kind)
 
678
    {
 
679
      distance = svn_fs_compare_ids (s_entry->id, t_entry->id);
 
680
      if (distance == 0 && !any_path_info (b, e_path)
 
681
          && (!info || (!info->start_empty && !info->lock_token)))
 
682
        return SVN_NO_ERROR;
 
683
      else if (distance != -1 || b->ignore_ancestry)
 
684
        related = TRUE;
 
685
    }
 
686
 
 
687
  /* If there's a source and it's not related to the target, nuke it. */
 
688
  if (s_entry && !related)
 
689
    {
 
690
      SVN_ERR (b->editor->delete_entry (e_path, SVN_INVALID_REVNUM, dir_baton,
 
691
                                        pool));
 
692
      s_path = NULL;
 
693
    }
 
694
 
 
695
  /* If there's no target, we have nothing more to do. */
 
696
  if (!t_entry)
 
697
    return skip_path_info (b, e_path);
 
698
 
 
699
  /* Check if the user is authorized to find out about the target. */
 
700
  SVN_ERR (check_auth (b, &allowed, t_path, pool));
 
701
  if (!allowed)
 
702
    {
 
703
      if (t_entry->kind == svn_node_dir)
 
704
        SVN_ERR (b->editor->absent_directory (e_path, dir_baton, pool));
 
705
      else
 
706
        SVN_ERR (b->editor->absent_file (e_path, dir_baton, pool));
 
707
      return skip_path_info (b, e_path);
 
708
    }
 
709
 
 
710
  if (t_entry->kind == svn_node_dir)
 
711
    {
 
712
      if (related)
 
713
        SVN_ERR (b->editor->open_directory (e_path, dir_baton, s_rev, pool, 
 
714
                                            &new_baton));
 
715
      else
 
716
        SVN_ERR (b->editor->add_directory (e_path, dir_baton, NULL,
 
717
                                           SVN_INVALID_REVNUM, pool,
 
718
                                           &new_baton));
 
719
      SVN_ERR (delta_dirs (b, s_rev, s_path, t_path, new_baton, e_path,
 
720
                           info ? info->start_empty : FALSE, pool));
 
721
      return b->editor->close_directory (new_baton, pool);
 
722
    }
 
723
  else
 
724
    {
 
725
      if (related)
 
726
        SVN_ERR (b->editor->open_file (e_path, dir_baton, s_rev, pool,
 
727
                                       &new_baton));
 
728
      else
 
729
        SVN_ERR (b->editor->add_file (e_path, dir_baton, NULL,
 
730
                                      SVN_INVALID_REVNUM, pool, &new_baton));
 
731
      SVN_ERR (delta_files (b, new_baton, s_rev, s_path, t_path,
 
732
                            info ? info->lock_token : NULL, pool));
 
733
      SVN_ERR (svn_fs_file_md5_checksum (digest, b->t_root, t_path, pool));
 
734
      hex_digest = svn_md5_digest_to_cstring (digest, pool);
 
735
      return b->editor->close_file (new_baton, hex_digest, pool);
 
736
    }
 
737
}
 
738
 
 
739
 
 
740
/* Emit edits within directory DIR_BATON (with corresponding path
 
741
   E_PATH) with the changes from the directory S_REV/S_PATH to the
 
742
   directory B->t_rev/T_PATH.  S_PATH may be NULL if the entry does
 
743
   not exist in the source. */
 
744
static svn_error_t *
 
745
delta_dirs (report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
 
746
            const char *t_path, void *dir_baton, const char *e_path,
 
747
            svn_boolean_t start_empty, apr_pool_t *pool)
 
748
{
 
749
  svn_fs_root_t *s_root;
 
750
  apr_hash_t *s_entries = NULL, *t_entries;
 
751
  apr_hash_index_t *hi;
 
752
  apr_pool_t *subpool;
 
753
  const svn_fs_dirent_t *s_entry, *t_entry;
 
754
  void *val;
 
755
  const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
 
756
  path_info_t *info;
 
757
 
 
758
  /* Compare the property lists.  If we're starting empty, pass a NULL
 
759
     source path so that we add all the properties.
 
760
     
 
761
     When we support directory locks, we must pass the lock token here. */
 
762
  SVN_ERR (delta_proplists (b, s_rev, start_empty ? NULL : s_path, t_path,
 
763
                            NULL, change_dir_prop, dir_baton, pool));
 
764
 
 
765
  /* Get the list of entries in each of source and target. */
 
766
  if (s_path && !start_empty)
 
767
    {
 
768
      SVN_ERR (get_source_root (b, &s_root, s_rev));
 
769
      SVN_ERR (svn_fs_dir_entries (&s_entries, s_root, s_path, pool));
 
770
    }
 
771
  SVN_ERR (svn_fs_dir_entries (&t_entries, b->t_root, t_path, pool));
 
772
 
 
773
  /* Iterate over the report information for this directory. */
 
774
  subpool = svn_pool_create (pool);
 
775
 
 
776
  while (1)
 
777
    {
 
778
      svn_pool_clear (subpool);
 
779
      SVN_ERR (fetch_path_info (b, &name, &info, e_path, subpool));
 
780
      if (!name)
 
781
        break;
 
782
 
 
783
      if (info && !SVN_IS_VALID_REVNUM (info->rev))
 
784
        {
 
785
          /* We want to perform deletes before non-replacement adds,
 
786
             for graceful handling of case-only renames on
 
787
             case-insensitive client filesystems.  So, if the report
 
788
             item is a delete, remove the entry from the source hash,
 
789
             but don't update the entry yet. */
 
790
          if (s_entries)
 
791
            apr_hash_set (s_entries, name, APR_HASH_KEY_STRING, NULL);
 
792
          continue;
 
793
        }
 
794
 
 
795
      e_fullpath = svn_path_join (e_path, name, subpool);
 
796
      t_fullpath = svn_path_join (t_path, name, subpool);
 
797
      t_entry = apr_hash_get (t_entries, name, APR_HASH_KEY_STRING);
 
798
      s_fullpath = s_path ? svn_path_join (s_path, name, subpool) : NULL;
 
799
      s_entry = s_entries ?
 
800
        apr_hash_get (s_entries, name, APR_HASH_KEY_STRING) : NULL;
 
801
 
 
802
      SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, t_fullpath,
 
803
                             t_entry, dir_baton, e_fullpath, info,
 
804
                             b->recurse, subpool));
 
805
 
 
806
      /* Don't revisit this name in the target or source entries. */
 
807
      apr_hash_set (t_entries, name, APR_HASH_KEY_STRING, NULL);
 
808
      if (s_entries)
 
809
        apr_hash_set (s_entries, name, APR_HASH_KEY_STRING, NULL);
 
810
 
 
811
      /* pathinfo entries live in their own subpools due to lookahead,
 
812
         so we need to clear each one out as we finish with it. */
 
813
      if (info)
 
814
        svn_pool_destroy (info->pool);
 
815
    }
 
816
 
 
817
  /* Remove any deleted entries.  Do this before processing the
 
818
     target, for graceful handling of case-only renames. */
 
819
  if (s_entries)
 
820
    {
 
821
      for (hi = apr_hash_first (pool, s_entries); hi; hi = apr_hash_next (hi))
 
822
        {
 
823
          svn_pool_clear (subpool);
 
824
          apr_hash_this (hi, NULL, NULL, &val);
 
825
          s_entry = val;
 
826
 
 
827
          if (apr_hash_get (t_entries, s_entry->name,
 
828
                            APR_HASH_KEY_STRING) == NULL)
 
829
            {
 
830
              /* There is no corresponding target entry, so delete. */
 
831
              e_fullpath = svn_path_join (e_path, s_entry->name, subpool);
 
832
              if (b->recurse || s_entry->kind != svn_node_dir)
 
833
                SVN_ERR (b->editor->delete_entry (e_fullpath,
 
834
                                                  SVN_INVALID_REVNUM,
 
835
                                                  dir_baton, subpool));
 
836
            }
 
837
        }
 
838
    }
 
839
 
 
840
  /* Loop over the dirents in the target. */
 
841
  for (hi = apr_hash_first (pool, t_entries); hi; hi = apr_hash_next (hi))
 
842
    {
 
843
      svn_pool_clear (subpool);
 
844
      apr_hash_this (hi, NULL, NULL, &val);
 
845
      t_entry = val;
 
846
 
 
847
      /* Compose the report, editor, and target paths for this entry. */
 
848
      e_fullpath = svn_path_join (e_path, t_entry->name, subpool);
 
849
      t_fullpath = svn_path_join (t_path, t_entry->name, subpool);
 
850
 
 
851
      /* Look for an entry with the same name in the source dirents. */
 
852
      s_entry = s_entries ?
 
853
        apr_hash_get (s_entries, t_entry->name, APR_HASH_KEY_STRING) : NULL;
 
854
      s_fullpath = s_entry ? svn_path_join (s_path, t_entry->name, subpool)
 
855
        : NULL;
 
856
 
 
857
      SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, t_fullpath,
 
858
                             t_entry, dir_baton, e_fullpath, NULL,
 
859
                             b->recurse, subpool));
 
860
    }
 
861
 
 
862
  /* Destroy iteration subpool. */
 
863
  svn_pool_destroy (subpool);
 
864
 
 
865
  return SVN_NO_ERROR;
 
866
}
 
867
 
 
868
static svn_error_t *
 
869
drive (report_baton_t *b, svn_revnum_t s_rev, path_info_t *info,
 
870
       apr_pool_t *pool)
 
871
{
 
872
  const char *t_anchor, *s_fullpath;
 
873
  svn_boolean_t allowed, info_is_set_path;
 
874
  svn_fs_root_t *s_root;
 
875
  const svn_fs_dirent_t *s_entry, *t_entry;
 
876
  void *root_baton;
 
877
 
 
878
  /* Compute the target path corresponding to the working copy anchor,
 
879
     and check its authorization. */
 
880
  t_anchor = *b->s_operand ? svn_path_dirname (b->t_path, pool) : b->t_path;
 
881
  SVN_ERR (check_auth (b, &allowed, t_anchor, pool));
 
882
  if (!allowed)
 
883
    return svn_error_create
 
884
      (SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL,
 
885
       _("Not authorized to open root of edit operation"));
 
886
 
 
887
  SVN_ERR (b->editor->set_target_revision (b->edit_baton, b->t_rev, pool));
 
888
 
 
889
  /* Collect information about the source and target nodes. */
 
890
  s_fullpath = svn_path_join (b->fs_base, b->s_operand, pool);
 
891
  SVN_ERR (get_source_root (b, &s_root, s_rev));
 
892
  SVN_ERR (fake_dirent (&s_entry, s_root, s_fullpath, pool));
 
893
  SVN_ERR (fake_dirent (&t_entry, b->t_root, b->t_path, pool));
 
894
 
 
895
  /* If the operand is a locally added file or directory, it won't
 
896
     exist in the source, so accept that. */
 
897
  info_is_set_path = (SVN_IS_VALID_REVNUM (info->rev) && !info->link_path);
 
898
  if (info_is_set_path && !s_entry)
 
899
    s_fullpath = NULL;
 
900
 
 
901
  /* If the anchor is the operand, the source and target must be dirs.
 
902
     Check this before opening the root to avoid modifying the wc. */
 
903
  if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir
 
904
                         || !t_entry || t_entry->kind != svn_node_dir))
 
905
    return svn_error_create (SVN_ERR_FS_PATH_SYNTAX, NULL,
 
906
                             _("Cannot replace a directory from within"));
 
907
 
 
908
  SVN_ERR (b->editor->open_root (b->edit_baton, s_rev, pool, &root_baton));
 
909
 
 
910
  /* If the anchor is the operand, diff the two directories; otherwise
 
911
     update the operand within the anchor directory. */
 
912
  if (!*b->s_operand)
 
913
    SVN_ERR (delta_dirs (b, s_rev, s_fullpath, b->t_path, root_baton,
 
914
                         "", info->start_empty, pool));
 
915
  else
 
916
    SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, b->t_path,
 
917
                           t_entry, root_baton, b->s_operand, info,
 
918
                           TRUE, pool));
 
919
 
 
920
  SVN_ERR (b->editor->close_directory (root_baton, pool));
 
921
  SVN_ERR (b->editor->close_edit (b->edit_baton, pool));
 
922
  return SVN_NO_ERROR;
 
923
}
 
924
 
 
925
/* Initialize the baton fields for editor-driving, and drive the editor. */
 
926
static svn_error_t *
 
927
finish_report (report_baton_t *b, apr_pool_t *pool)
 
928
{
 
929
  apr_off_t offset;
 
930
  path_info_t *info;
 
931
  apr_pool_t *subpool;
 
932
  svn_revnum_t s_rev;
 
933
  int i;
 
934
 
 
935
  /* Save our pool to manage the lookahead and fs_root cache with. */
 
936
  b->pool = pool;
 
937
 
 
938
  /* Add an end marker and rewind the temporary file. */
 
939
  SVN_ERR (svn_io_file_write_full (b->tempfile, "-", 1, NULL, pool));
 
940
  offset = 0;
 
941
  SVN_ERR (svn_io_file_seek (b->tempfile, APR_SET, &offset, pool));
 
942
 
 
943
  /* Read the first pathinfo from the report and verify that it is a top-level
 
944
     set_path entry. */
 
945
  SVN_ERR (read_path_info (&info, b->tempfile, pool));
 
946
  if (!info || strcmp (info->path, b->s_operand) != 0
 
947
      || info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
 
948
    return svn_error_create (SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
 
949
                             _("Invalid report for top level of working copy"));
 
950
  s_rev = info->rev;
 
951
 
 
952
  /* Initialize the lookahead pathinfo. */
 
953
  subpool = svn_pool_create (pool);
 
954
  SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
 
955
 
 
956
  if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
 
957
    {
 
958
      /* If the operand of the wc operation is switched or deleted,
 
959
         then info above is just a place-holder, and the only thing we
 
960
         have to do is pass the revision it contains to open_root.
 
961
         The next pathinfo actually describes the target. */
 
962
      if (!*b->s_operand)
 
963
        return svn_error_create (SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
 
964
                                 _("Two top-level reports with no target"));
 
965
      info = b->lookahead;
 
966
      SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
 
967
    }
 
968
 
 
969
  /* Open the target root and initialize the source root cache. */
 
970
  SVN_ERR (svn_fs_revision_root (&b->t_root, b->repos->fs, b->t_rev, pool));
 
971
  for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
 
972
    b->s_roots[i] = NULL;
 
973
 
 
974
  return drive (b, s_rev, info, pool);
 
975
}
 
976
 
 
977
/* --- COLLECTING THE REPORT INFORMATION --- */
 
978
 
 
979
/* Record a report operation into the temporary file. */
 
980
static svn_error_t *
 
981
write_path_info (report_baton_t *b, const char *path, const char *lpath,
 
982
                 svn_revnum_t rev, svn_boolean_t start_empty,
 
983
                 const char *lock_token, apr_pool_t *pool)
 
984
{
 
985
  const char *lrep, *rrep, *ltrep, *rep;
 
986
 
 
987
  /* Munge the path to be anchor-relative, so that we can use edit paths
 
988
     as report paths. */
 
989
  path = svn_path_join (b->s_operand, path, pool);
 
990
 
 
991
  lrep = lpath ? apr_psprintf (pool, "+%" APR_SIZE_T_FMT ":%s",
 
992
                               strlen(lpath), lpath) : "-";
 
993
  rrep = (SVN_IS_VALID_REVNUM (rev)) ?
 
994
    apr_psprintf (pool, "+%ld:", rev) : "-";
 
995
  ltrep = lock_token ? apr_psprintf (pool, "+%" APR_SIZE_T_FMT ":%s",
 
996
                                     strlen(lock_token), lock_token) : "-";
 
997
  rep = apr_psprintf (pool, "+%" APR_SIZE_T_FMT ":%s%s%s%c%s",
 
998
                      strlen(path), path, lrep, rrep, start_empty ? '+' : '-',
 
999
                      ltrep);
 
1000
  return svn_io_file_write_full (b->tempfile, rep, strlen(rep), NULL, pool);
 
1001
}
 
1002
 
 
1003
svn_error_t *
 
1004
svn_repos_set_path2 (void *baton, const char *path, svn_revnum_t rev,
 
1005
                     svn_boolean_t start_empty, const char *lock_token,
 
1006
                     apr_pool_t *pool)
 
1007
{
 
1008
  return write_path_info (baton, path, NULL, rev, start_empty,
 
1009
                          lock_token, pool);
 
1010
}
 
1011
 
 
1012
svn_error_t *
 
1013
svn_repos_set_path (void *baton, const char *path, svn_revnum_t rev,
 
1014
                    svn_boolean_t start_empty, apr_pool_t *pool)
 
1015
{
 
1016
  return svn_repos_set_path2 (baton, path, rev, start_empty, NULL, pool);
 
1017
}
 
1018
 
 
1019
svn_error_t *
 
1020
svn_repos_link_path2 (void *baton, const char *path, const char *link_path,
 
1021
                      svn_revnum_t rev, svn_boolean_t start_empty,
 
1022
                      const char *lock_token, apr_pool_t *pool)
 
1023
{
 
1024
  return write_path_info (baton, path, link_path, rev, start_empty, lock_token,
 
1025
                          pool);
 
1026
}
 
1027
 
 
1028
svn_error_t *
 
1029
svn_repos_link_path (void *baton, const char *path, const char *link_path,
 
1030
                     svn_revnum_t rev, svn_boolean_t start_empty,
 
1031
                     apr_pool_t *pool)
 
1032
{
 
1033
  return svn_repos_link_path2 (baton, path, link_path, rev, start_empty,
 
1034
                               NULL, pool);
 
1035
}
 
1036
 
 
1037
svn_error_t *
 
1038
svn_repos_delete_path (void *baton, const char *path, apr_pool_t *pool)
 
1039
{
 
1040
  return write_path_info (baton, path, NULL, SVN_INVALID_REVNUM, FALSE, NULL,
 
1041
                          pool);
 
1042
}
 
1043
 
 
1044
svn_error_t *
 
1045
svn_repos_finish_report (void *baton, apr_pool_t *pool)
 
1046
{
 
1047
  report_baton_t *b = baton;
 
1048
  svn_error_t *finish_err, *close_err;
 
1049
 
 
1050
  finish_err = finish_report (b, pool);
 
1051
  close_err = svn_io_file_close (b->tempfile, pool);
 
1052
  if (finish_err)
 
1053
    svn_error_clear (close_err);
 
1054
  return finish_err ? finish_err : close_err;
 
1055
}
 
1056
 
 
1057
svn_error_t *
 
1058
svn_repos_abort_report (void *baton, apr_pool_t *pool)
 
1059
{
 
1060
  report_baton_t *b = baton;
 
1061
 
 
1062
  return svn_io_file_close (b->tempfile, pool);
 
1063
}
 
1064
 
 
1065
/* --- BEGINNING THE REPORT --- */
 
1066
 
 
1067
svn_error_t *
 
1068
svn_repos_begin_report (void **report_baton,
 
1069
                        svn_revnum_t revnum,
 
1070
                        const char *username,
 
1071
                        svn_repos_t *repos,
 
1072
                        const char *fs_base,
 
1073
                        const char *s_operand,
 
1074
                        const char *switch_path,
 
1075
                        svn_boolean_t text_deltas,
 
1076
                        svn_boolean_t recurse,
 
1077
                        svn_boolean_t ignore_ancestry,
 
1078
                        const svn_delta_editor_t *editor,
 
1079
                        void *edit_baton,
 
1080
                        svn_repos_authz_func_t authz_read_func,
 
1081
                        void *authz_read_baton,
 
1082
                        apr_pool_t *pool)
 
1083
{
 
1084
  report_baton_t *b;
 
1085
  const char *tempdir, *dummy;
 
1086
 
 
1087
  /* Build a reporter baton.  Copy strings in case the caller doesn't
 
1088
     keep track of them. */
 
1089
  b = apr_palloc (pool, sizeof (*b));
 
1090
  b->repos = repos;
 
1091
  b->fs_base = apr_pstrdup (pool, fs_base);
 
1092
  b->s_operand = apr_pstrdup (pool, s_operand);
 
1093
  b->t_rev = revnum;
 
1094
  b->t_path = switch_path ? switch_path
 
1095
    : svn_path_join (fs_base, s_operand, pool);
 
1096
  b->text_deltas = text_deltas;
 
1097
  b->recurse = recurse;
 
1098
  b->ignore_ancestry = ignore_ancestry;
 
1099
  b->is_switch = (switch_path != NULL);
 
1100
  b->editor = editor;
 
1101
  b->edit_baton = edit_baton;
 
1102
  b->authz_read_func = authz_read_func;
 
1103
  b->authz_read_baton = authz_read_baton;
 
1104
 
 
1105
  SVN_ERR (svn_io_temp_dir (&tempdir, pool));
 
1106
  SVN_ERR (svn_io_open_unique_file (&b->tempfile, &dummy,
 
1107
                                    apr_psprintf (pool, "%s/report", tempdir),
 
1108
                                    ".tmp", TRUE, pool));
 
1109
 
 
1110
  /* Hand reporter back to client. */
 
1111
  *report_baton = b;
 
1112
  return SVN_NO_ERROR;
 
1113
}