~svn/ubuntu/raring/subversion/ppa

« back to all changes in this revision

Viewing changes to subversion/libsvn_client/repos_diff.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
 * repos_diff.c -- The diff editor for comparing two repository versions
 
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
/* This code uses an editor driven by a tree delta between two
 
20
 * repository revisions (REV1 and REV2). For each file encountered in
 
21
 * the delta the editor constructs two temporary files, one for each
 
22
 * revision. This necessitates a separate request for the REV1 version
 
23
 * of the file when the delta shows the file being modified or
 
24
 * deleted. Files that are added by the delta do not require a
 
25
 * separate request, the REV1 version is empty and the delta is
 
26
 * sufficient to construct the REV2 version. When both versions of
 
27
 * each file have been created the diff callback is invoked to display
 
28
 * the difference between the two files.  */
 
29
 
 
30
#include "svn_wc.h"
 
31
#include "svn_pools.h"
 
32
#include "svn_path.h"
 
33
#include "svn_io.h"
 
34
#include "svn_props.h"
 
35
 
 
36
#include "client.h"
 
37
 
 
38
/* Overall crawler editor baton.  */
 
39
struct edit_baton {
 
40
  /* TARGET is a working-copy directory which corresponds to the base
 
41
     URL open in RA_SESSION below. */
 
42
  const char *target;
 
43
 
 
44
  /* ADM_ACCESS is an access baton that includes the TARGET directory */
 
45
  svn_wc_adm_access_t *adm_access;
 
46
 
 
47
  /* The callback and calback argument that implement the file comparison
 
48
     function */
 
49
  const svn_wc_diff_callbacks2_t *diff_callbacks;
 
50
  void *diff_cmd_baton;
 
51
 
 
52
  /* RECURSE is TRUE if this is a recursive diff or merge, false otherwise */
 
53
  svn_boolean_t recurse;
 
54
 
 
55
  /* DRY_RUN is TRUE if this is a dry-run diff, false otherwise. */
 
56
  svn_boolean_t dry_run;
 
57
 
 
58
  /* RA_LIB is the vtable for making requests to the RA layer, RA_SESSION
 
59
     is the open session for these requests */
 
60
  svn_ra_session_t *ra_session;
 
61
 
 
62
  /* The rev1 from the '-r Rev1:Rev2' command line option */
 
63
  svn_revnum_t revision;
 
64
 
 
65
  /* The rev2 from the '-r Rev1:Rev2' option, specifically set by
 
66
     set_target_revision(). */
 
67
  svn_revnum_t target_revision;
 
68
 
 
69
  /* A temporary empty file. Used for add/delete differences. This is
 
70
     cached here so that it can be reused, all empty files are the same. */
 
71
  const char *empty_file;
 
72
 
 
73
  /* Empty hash used for adds. */
 
74
  apr_hash_t *empty_hash;
 
75
 
 
76
  /* If the func is non-null, send notifications of actions. */
 
77
  svn_wc_notify_func2_t notify_func;
 
78
  void *notify_baton;
 
79
 
 
80
  apr_pool_t *pool;
 
81
};
 
82
 
 
83
/* Directory level baton.
 
84
 */
 
85
struct dir_baton {
 
86
  /* Gets set if the directory is added rather than replaced/unchanged. */
 
87
  svn_boolean_t added;
 
88
 
 
89
  /* The path of the directory within the repository */
 
90
  const char *path;
 
91
 
 
92
  /* The path of the directory in the wc, relative to cwd */
 
93
  const char *wcpath;
 
94
 
 
95
  /* The baton for the parent directory, or null if this is the root of the
 
96
     hierarchy to be compared. */
 
97
  struct dir_baton *dir_baton;
 
98
 
 
99
  /* The overall crawler editor baton. */
 
100
  struct edit_baton *edit_baton;
 
101
 
 
102
  /* A cache of any property changes (svn_prop_t) received for this dir. */
 
103
  apr_array_header_t *propchanges;
 
104
 
 
105
  /* The pristine-property list attached to this directory. */
 
106
  apr_hash_t *pristine_props;
 
107
 
 
108
  /* The pool passed in by add_dir, open_dir, or open_root.
 
109
     Also, the pool this dir baton is allocated in. */
 
110
  apr_pool_t *pool;
 
111
};
 
112
 
 
113
/* File level baton.
 
114
 */
 
115
struct file_baton {
 
116
  /* Gets set if the file is added rather than replaced. */
 
117
  svn_boolean_t added;
 
118
 
 
119
  /* The path of the file within the repository */
 
120
  const char *path;
 
121
 
 
122
  /* The path of the file in the wc, relative to cwd */
 
123
  const char *wcpath;
 
124
 
 
125
  /* The path and APR file handle to the temporary file that contains the
 
126
     first repository version.  Also, the pristine-property list of
 
127
     this file. */
 
128
  const char *path_start_revision;
 
129
  apr_file_t *file_start_revision;
 
130
  apr_hash_t *pristine_props;
 
131
 
 
132
  /* The path and APR file handle to the temporary file that contains the
 
133
     second repository version.  These fields are set when processing
 
134
     textdelta and file deletion, and will be NULL if there's no
 
135
     textual difference between the two revisions. */
 
136
  const char *path_end_revision;
 
137
  apr_file_t *file_end_revision;
 
138
 
 
139
  /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */
 
140
  svn_txdelta_window_handler_t apply_handler;
 
141
  void *apply_baton;
 
142
 
 
143
  /* The overall crawler editor baton. */
 
144
  struct edit_baton *edit_baton;
 
145
 
 
146
  /* A cache of any property changes (svn_prop_t) received for this file. */
 
147
  apr_array_header_t *propchanges;
 
148
 
 
149
  /* The pool passed in by add_file or open_file.
 
150
     Also, the pool this file_baton is allocated in. */
 
151
  apr_pool_t *pool;
 
152
};
 
153
 
 
154
/* Data used by the apr pool temp file cleanup handler */
 
155
struct temp_file_cleanup_s {
 
156
  /* The path to the file to be deleted.  NOTE: this path is
 
157
     APR-encoded, _not_ utf8-encoded! */
 
158
  const char *path;
 
159
  /* The pool to which the deletion of the file is linked. */
 
160
  apr_pool_t *pool;
 
161
};
 
162
 
 
163
/* Create a new directory baton for PATH in POOL.  ADDED is set if
 
164
 * this directory is being added rather than replaced. PARENT_BATON is
 
165
 * the baton of the parent directory (or NULL if this is the root of
 
166
 * the comparison hierarchy). The directory and its parent may or may
 
167
 * not exist in the working copy.  EDIT_BATON is the overall crawler
 
168
 * editor baton.
 
169
 */
 
170
static struct dir_baton *
 
171
make_dir_baton (const char *path,
 
172
                struct dir_baton *parent_baton,
 
173
                struct edit_baton *edit_baton,
 
174
                svn_boolean_t added,
 
175
                apr_pool_t *pool)
 
176
{
 
177
  struct dir_baton *dir_baton = apr_pcalloc (pool, sizeof (*dir_baton));
 
178
 
 
179
  dir_baton->dir_baton = parent_baton;
 
180
  dir_baton->edit_baton = edit_baton;
 
181
  dir_baton->added = added;
 
182
  dir_baton->pool = pool;
 
183
  dir_baton->path = apr_pstrdup (pool, path);
 
184
  dir_baton->wcpath = svn_path_join (edit_baton->target, path, pool);
 
185
  dir_baton->propchanges  = apr_array_make (pool, 1, sizeof (svn_prop_t));
 
186
 
 
187
  return dir_baton;
 
188
}
 
189
 
 
190
/* Create a new file baton for PATH in POOL, which is a child of
 
191
 * directory PARENT_PATH. ADDED is set if this file is being added
 
192
 * rather than replaced.  EDIT_BATON is a pointer to the global edit
 
193
 * baton.
 
194
 */
 
195
static struct file_baton *
 
196
make_file_baton (const char *path,
 
197
                 svn_boolean_t added,
 
198
                 void *edit_baton,
 
199
                 apr_pool_t *pool)
 
200
{
 
201
  struct file_baton *file_baton = apr_pcalloc (pool, sizeof (*file_baton));
 
202
  struct edit_baton *eb = edit_baton;
 
203
 
 
204
  file_baton->edit_baton = edit_baton;
 
205
  file_baton->added = added;
 
206
  file_baton->pool = pool;
 
207
  file_baton->path = apr_pstrdup (pool, path);
 
208
  file_baton->wcpath = svn_path_join (eb->target, path, pool);
 
209
  file_baton->propchanges  = apr_array_make (pool, 1, sizeof (svn_prop_t));
 
210
 
 
211
  return file_baton;
 
212
}
 
213
 
 
214
 
 
215
/* Helper function: return up to two svn:mime-type values buried
 
216
 * within a file baton.  Set *MIMETYPE1 to the value within the file's
 
217
 * pristine properties, or NULL if not available.  Set *MIMETYPE2 to
 
218
 * the value within the "new" file's propchanges, or NULL if not
 
219
 * available.
 
220
 */
 
221
static void
 
222
get_file_mime_types (const char **mimetype1,
 
223
                     const char **mimetype2,
 
224
                     struct file_baton *b)
 
225
{
 
226
  /* Defaults */
 
227
  *mimetype1 = NULL;
 
228
  *mimetype2 = NULL;
 
229
 
 
230
  if (b->pristine_props)
 
231
    {
 
232
      svn_string_t *pristine_val;
 
233
      pristine_val = apr_hash_get (b->pristine_props, SVN_PROP_MIME_TYPE,
 
234
                                   strlen(SVN_PROP_MIME_TYPE));
 
235
      if (pristine_val)
 
236
        *mimetype1 = pristine_val->data;
 
237
    }
 
238
 
 
239
  if (b->propchanges)
 
240
    {
 
241
      int i;
 
242
      svn_prop_t *propchange;
 
243
 
 
244
      for (i = 0; i < b->propchanges->nelts; i++)
 
245
        {
 
246
          propchange = &APR_ARRAY_IDX(b->propchanges, i, svn_prop_t);
 
247
          if (strcmp (propchange->name, SVN_PROP_MIME_TYPE) == 0)
 
248
            {
 
249
              if (propchange->value)
 
250
                *mimetype2 = propchange->value->data;
 
251
              break;
 
252
            }
 
253
        }
 
254
    }
 
255
}
 
256
 
 
257
 
 
258
/* An apr pool cleanup handler, this deletes one of the temporary files.
 
259
 */
 
260
static apr_status_t
 
261
temp_file_plain_cleanup_handler (void *arg)
 
262
{
 
263
  struct temp_file_cleanup_s *s = arg;
 
264
 
 
265
  /* Note to UTF-8 watchers: this is ok because the path is already in
 
266
     APR internal encoding. */ 
 
267
  return apr_file_remove (s->path, s->pool);
 
268
}
 
269
 
 
270
/* An apr pool cleanup handler, this removes a cleanup handler.
 
271
 */
 
272
static apr_status_t
 
273
temp_file_child_cleanup_handler (void *arg)
 
274
{
 
275
  struct temp_file_cleanup_s *s = arg;
 
276
 
 
277
  apr_pool_cleanup_kill (s->pool, s, temp_file_plain_cleanup_handler);
 
278
 
 
279
  return APR_SUCCESS;
 
280
}
 
281
 
 
282
/* Register a pool cleanup to delete PATH when POOL is destroyed.
 
283
 *
 
284
 * PATH is not copied; caller should probably ensure that it is
 
285
 * allocated in a pool at least as long-lived as POOL.
 
286
 *
 
287
 * The main "gotcha" is that if the process forks a child by calling
 
288
 * apr_proc_create, then the child's copy of the cleanup handler will run
 
289
 * and delete the file while the parent still expects it to be around. To
 
290
 * avoid this a child cleanup handler is also installed to kill the plain
 
291
 * cleanup handler in the child.
 
292
 *
 
293
 * ### TODO: This a candidate to be a general utility function.
 
294
 */
 
295
static svn_error_t *
 
296
temp_file_cleanup_register (const char *path,
 
297
                            apr_pool_t *pool)
 
298
{
 
299
  struct temp_file_cleanup_s *s = apr_palloc (pool, sizeof (*s));
 
300
  SVN_ERR (svn_path_cstring_from_utf8 (&(s->path), path, pool));
 
301
  s->pool = pool;
 
302
  apr_pool_cleanup_register (s->pool, s, temp_file_plain_cleanup_handler,
 
303
                             temp_file_child_cleanup_handler);
 
304
  return SVN_NO_ERROR;
 
305
}
 
306
 
 
307
 
 
308
/* Get the repository version of a file. This makes an RA request to
 
309
 * retrieve the file contents. A pool cleanup handler is installed to
 
310
 * delete this file.
 
311
 *
 
312
 * ### TODO: The editor calls this function to get REV1 of the file. Can we
 
313
 * get the file props as well?  Then get_wc_prop() could return them later
 
314
 * on enabling the REV1:REV2 request to send diffs.
 
315
 */
 
316
static svn_error_t *
 
317
get_file_from_ra (struct file_baton *b)
 
318
{
 
319
  apr_file_t *file;
 
320
  svn_stream_t *fstream;
 
321
  const char *temp_dir;
 
322
 
 
323
  SVN_ERR (svn_io_temp_dir (&temp_dir, b->pool));
 
324
  SVN_ERR (svn_io_open_unique_file (&file, &(b->path_start_revision),
 
325
                                    svn_path_join (temp_dir, "tmp", b->pool),
 
326
                                    "", FALSE, b->pool));
 
327
 
 
328
  /* Install a pool cleanup handler to delete the file */
 
329
  SVN_ERR (temp_file_cleanup_register (b->path_start_revision, b->pool));
 
330
 
 
331
  fstream = svn_stream_from_aprfile (file, b->pool);
 
332
  SVN_ERR (svn_ra_get_file (b->edit_baton->ra_session,
 
333
                            b->path,
 
334
                            b->edit_baton->revision,
 
335
                            fstream, NULL,
 
336
                            &(b->pristine_props),
 
337
                            b->pool));
 
338
  SVN_ERR (svn_io_file_close (file, b->pool));
 
339
 
 
340
  return SVN_NO_ERROR;
 
341
}
 
342
 
 
343
/* Get the props attached to a directory in the repository. */
 
344
static svn_error_t *
 
345
get_dirprops_from_ra (struct dir_baton *b)
 
346
{
 
347
  SVN_ERR (svn_ra_get_dir (b->edit_baton->ra_session,
 
348
                           b->path,
 
349
                           b->edit_baton->revision,
 
350
                           NULL, NULL,
 
351
                           &(b->pristine_props),
 
352
                           b->pool));
 
353
 
 
354
  return SVN_NO_ERROR;
 
355
}
 
356
 
 
357
 
 
358
/* Create an empty file, the path to the file is returned in EMPTY_FILE.
 
359
 * If HAVE_WRITE_LOCK is true, create the file in the working directory,
 
360
 * otherwise use a system temp dir.
 
361
 */
 
362
static svn_error_t *
 
363
create_empty_file (const char **empty_file,
 
364
                   svn_boolean_t have_write_lock,
 
365
                   apr_pool_t *pool)
 
366
{
 
367
  apr_file_t *file;
 
368
  const char *temp_path;
 
369
 
 
370
  if (have_write_lock)
 
371
    {
 
372
      temp_path = "tmp";
 
373
    }
 
374
  else 
 
375
    {
 
376
      const char *temp_dir;
 
377
      SVN_ERR (svn_io_temp_dir (&temp_dir, pool));
 
378
      temp_path = svn_path_join (temp_dir, "tmp", pool);
 
379
    }
 
380
 
 
381
  SVN_ERR (svn_io_open_unique_file (&file, empty_file, temp_path,
 
382
                                    "", FALSE, pool));
 
383
  SVN_ERR (svn_io_file_close (file, pool));
 
384
 
 
385
  return SVN_NO_ERROR;
 
386
}
 
387
 
 
388
/* Return in *PATH_ACCESS the access baton for the directory PATH by
 
389
   searching the access baton set of ADM_ACCESS.  If ADM_ACCESS is NULL
 
390
   then *PATH_ACCESS will be NULL.  If LENIENT is TRUE then failure to find
 
391
   an access baton will not return an error but will set *PATH_ACCESS to
 
392
   NULL instead. */
 
393
static svn_error_t *
 
394
get_path_access (svn_wc_adm_access_t **path_access,
 
395
                 svn_wc_adm_access_t *adm_access,
 
396
                 const char *path,
 
397
                 svn_boolean_t lenient,
 
398
                 apr_pool_t *pool)
 
399
{
 
400
  if (! adm_access)
 
401
    *path_access = NULL;
 
402
  else
 
403
    {
 
404
      svn_error_t *err = svn_wc_adm_retrieve (path_access, adm_access, path,
 
405
                                              pool);
 
406
      if (err)
 
407
        {
 
408
          if (! lenient)
 
409
            return err;
 
410
          svn_error_clear (err);
 
411
          *path_access = NULL;
 
412
        }
 
413
    }
 
414
 
 
415
  return SVN_NO_ERROR;
 
416
}
 
417
                  
 
418
/* Like get_path_access except the returned access baton, in
 
419
   *PARENT_ACCESS, is for the parent of PATH rather than for PATH
 
420
   itself. */
 
421
static svn_error_t *
 
422
get_parent_access (svn_wc_adm_access_t **parent_access,
 
423
                   svn_wc_adm_access_t *adm_access,
 
424
                   const char *path,
 
425
                   svn_boolean_t lenient,
 
426
                   apr_pool_t *pool)
 
427
{
 
428
  if (! adm_access)
 
429
    *parent_access = NULL;  /* Avoid messing around with paths */
 
430
  else
 
431
    {
 
432
      const char *parent_path = svn_path_dirname (path, pool);
 
433
      SVN_ERR (get_path_access (parent_access, adm_access, parent_path,
 
434
                                lenient, pool));
 
435
    }
 
436
  return SVN_NO_ERROR;
 
437
}
 
438
 
 
439
/* Get the empty file associated with the edit baton. This is cached so
 
440
 * that it can be reused, all empty files are the same.
 
441
 */
 
442
static svn_error_t *
 
443
get_empty_file (struct edit_baton *b,
 
444
                const char **empty_file)
 
445
{
 
446
  /* Create the file if it does not exist */
 
447
  if (!b->empty_file)
 
448
    {
 
449
      svn_boolean_t have_lock;
 
450
      have_lock = (b->adm_access && svn_wc_adm_locked (b->adm_access));
 
451
      SVN_ERR (create_empty_file (&(b->empty_file), have_lock, b->pool));
 
452
 
 
453
      /* Install a pool cleanup handler to delete the file */
 
454
      SVN_ERR (temp_file_cleanup_register (b->empty_file, b->pool));
 
455
    }
 
456
 
 
457
  *empty_file = b->empty_file;
 
458
 
 
459
  return SVN_NO_ERROR;
 
460
}
 
461
 
 
462
/* An editor function. The root of the comparison hierarchy */
 
463
static svn_error_t *
 
464
set_target_revision (void *edit_baton, 
 
465
                     svn_revnum_t target_revision,
 
466
                     apr_pool_t *pool)
 
467
{
 
468
  struct edit_baton *eb = edit_baton;
 
469
  
 
470
  eb->target_revision = target_revision;
 
471
  return SVN_NO_ERROR;
 
472
}
 
473
 
 
474
/* An editor function. The root of the comparison hierarchy */
 
475
static svn_error_t *
 
476
open_root (void *edit_baton,
 
477
           svn_revnum_t base_revision,
 
478
           apr_pool_t *pool,
 
479
           void **root_baton)
 
480
{
 
481
  struct edit_baton *eb = edit_baton;
 
482
  struct dir_baton *b = make_dir_baton ("", NULL, eb, FALSE, pool);
 
483
 
 
484
  /* Override the wcpath in our baton. */
 
485
  b->wcpath = eb->target ? apr_pstrdup (pool, eb->target) : "";
 
486
 
 
487
  SVN_ERR (get_dirprops_from_ra (b));
 
488
 
 
489
  *root_baton = b;
 
490
  return SVN_NO_ERROR;
 
491
}
 
492
 
 
493
/* An editor function.  */
 
494
static svn_error_t *
 
495
delete_entry (const char *path,
 
496
              svn_revnum_t base_revision,
 
497
              void *parent_baton,
 
498
              apr_pool_t *pool)
 
499
{
 
500
  struct dir_baton *pb = parent_baton;
 
501
  struct edit_baton *eb = pb->edit_baton;
 
502
  svn_node_kind_t kind;
 
503
  svn_wc_adm_access_t *adm_access;
 
504
  svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
 
505
  svn_wc_notify_action_t action = svn_wc_notify_skip;
 
506
 
 
507
  /* We need to know if this is a directory or a file */
 
508
  SVN_ERR (svn_ra_check_path (pb->edit_baton->ra_session,
 
509
                              path,
 
510
                              pb->edit_baton->revision,
 
511
                              &kind,
 
512
                              pool));
 
513
  SVN_ERR (get_path_access (&adm_access, eb->adm_access, pb->wcpath,
 
514
                            TRUE, pool));
 
515
  if ((! eb->adm_access) || adm_access)
 
516
    {
 
517
      switch (kind)
 
518
        {
 
519
        case svn_node_file:
 
520
          {
 
521
            const char *mimetype1, *mimetype2;
 
522
            struct file_baton *b;
 
523
            
 
524
            /* Compare a file being deleted against an empty file */
 
525
            b = make_file_baton (path, FALSE, pb->edit_baton, pool);
 
526
            SVN_ERR (get_file_from_ra (b));
 
527
            SVN_ERR (get_empty_file(b->edit_baton, &(b->path_end_revision)));
 
528
            
 
529
            get_file_mime_types (&mimetype1, &mimetype2, b);
 
530
            
 
531
            SVN_ERR (pb->edit_baton->diff_callbacks->file_deleted 
 
532
                     (adm_access, &state, b->wcpath,
 
533
                      b->path_start_revision,
 
534
                      b->path_end_revision,
 
535
                      mimetype1, mimetype2,
 
536
                      b->pristine_props,
 
537
                      b->edit_baton->diff_cmd_baton));
 
538
            
 
539
            break;
 
540
          }
 
541
        case svn_node_dir:
 
542
          {
 
543
            SVN_ERR (pb->edit_baton->diff_callbacks->dir_deleted 
 
544
                     (adm_access, &state, 
 
545
                      svn_path_join (eb->target, path, pool),
 
546
                      pb->edit_baton->diff_cmd_baton));
 
547
            break;
 
548
          }
 
549
        default:
 
550
          break;
 
551
        }
 
552
      
 
553
      if ((state != svn_wc_notify_state_missing)
 
554
          && (state != svn_wc_notify_state_obstructed))
 
555
        action = svn_wc_notify_update_delete;
 
556
    }
 
557
 
 
558
  if (pb->edit_baton->notify_func)
 
559
    {
 
560
      svn_wc_notify_t *notify
 
561
        = svn_wc_create_notify (svn_path_join (eb->target, path, pool),
 
562
                                action, pool);
 
563
      notify->kind = kind;
 
564
      notify->content_state = notify->prop_state = state;
 
565
      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
 
566
      (*pb->edit_baton->notify_func) (pb->edit_baton->notify_baton, notify,
 
567
                                      pool);
 
568
    }
 
569
  return SVN_NO_ERROR;
 
570
}
 
571
 
 
572
/* An editor function.  */
 
573
static svn_error_t *
 
574
add_directory (const char *path,
 
575
               void *parent_baton,
 
576
               const char *copyfrom_path,
 
577
               svn_revnum_t copyfrom_revision,
 
578
               apr_pool_t *pool,
 
579
               void **child_baton)
 
580
{
 
581
  struct dir_baton *pb = parent_baton;
 
582
  struct edit_baton *eb = pb->edit_baton;
 
583
  struct dir_baton *b;
 
584
  svn_wc_adm_access_t *adm_access;
 
585
  svn_wc_notify_state_t state;
 
586
  svn_wc_notify_action_t action;
 
587
 
 
588
  /* ### TODO: support copyfrom? */
 
589
 
 
590
  b = make_dir_baton (path, pb, pb->edit_baton, TRUE, pool);
 
591
  *child_baton = b;
 
592
 
 
593
  SVN_ERR (get_path_access (&adm_access,
 
594
                            pb->edit_baton->adm_access, pb->wcpath,
 
595
                            pb->edit_baton->dry_run, pool));
 
596
 
 
597
  SVN_ERR (pb->edit_baton->diff_callbacks->dir_added 
 
598
           (adm_access, &state, b->wcpath, eb->target_revision,
 
599
            pb->edit_baton->diff_cmd_baton));
 
600
 
 
601
  if ((state == svn_wc_notify_state_missing)
 
602
      || (state == svn_wc_notify_state_obstructed))
 
603
    action = svn_wc_notify_skip;
 
604
  else
 
605
    action = svn_wc_notify_update_add;
 
606
 
 
607
  if (pb->edit_baton->notify_func)
 
608
    {
 
609
      svn_wc_notify_t *notify = svn_wc_create_notify (b->wcpath, action, pool);
 
610
      notify->kind = svn_node_dir;
 
611
      (*pb->edit_baton->notify_func) (pb->edit_baton->notify_baton, notify,
 
612
                                      pool);
 
613
    }
 
614
 
 
615
  return SVN_NO_ERROR;
 
616
}
 
617
 
 
618
/* An editor function.  */
 
619
static svn_error_t *
 
620
open_directory (const char *path,
 
621
                void *parent_baton,
 
622
                svn_revnum_t base_revision,
 
623
                apr_pool_t *pool,
 
624
                void **child_baton)
 
625
{
 
626
  struct dir_baton *pb = parent_baton;
 
627
  struct dir_baton *b;
 
628
 
 
629
  b = make_dir_baton (path, pb, pb->edit_baton, FALSE, pool);
 
630
  *child_baton = b;
 
631
 
 
632
  SVN_ERR (get_dirprops_from_ra (b));
 
633
 
 
634
  return SVN_NO_ERROR;
 
635
}
 
636
 
 
637
 
 
638
/* An editor function.  */
 
639
static svn_error_t *
 
640
add_file (const char *path,
 
641
          void *parent_baton,
 
642
          const char *copyfrom_path,
 
643
          svn_revnum_t copyfrom_revision,
 
644
          apr_pool_t *pool,
 
645
          void **file_baton)
 
646
{
 
647
  struct dir_baton *pb = parent_baton;
 
648
  struct file_baton *b;
 
649
 
 
650
  /* ### TODO: support copyfrom? */
 
651
 
 
652
  b = make_file_baton (path, TRUE, pb->edit_baton, pool);
 
653
  *file_baton = b;
 
654
 
 
655
  SVN_ERR (get_empty_file (b->edit_baton, &(b->path_start_revision)));
 
656
  b->pristine_props = pb->edit_baton->empty_hash;
 
657
 
 
658
  return SVN_NO_ERROR;
 
659
}
 
660
 
 
661
/* An editor function.  */
 
662
static svn_error_t *
 
663
open_file (const char *path,
 
664
           void *parent_baton,
 
665
           svn_revnum_t base_revision,
 
666
           apr_pool_t *pool,
 
667
           void **file_baton)
 
668
{
 
669
  struct dir_baton *pb = parent_baton;
 
670
  struct file_baton *b;
 
671
 
 
672
  b = make_file_baton (path, FALSE, pb->edit_baton, pool);
 
673
  *file_baton = b;
 
674
 
 
675
  SVN_ERR (get_file_from_ra (b));
 
676
 
 
677
  return SVN_NO_ERROR;
 
678
}
 
679
 
 
680
/* An editor function.  Do the work of applying the text delta.  */
 
681
static svn_error_t *
 
682
window_handler (svn_txdelta_window_t *window,
 
683
                void *window_baton)
 
684
{
 
685
  struct file_baton *b = window_baton;
 
686
 
 
687
  SVN_ERR (b->apply_handler (window, b->apply_baton));
 
688
 
 
689
  if (!window)
 
690
    {
 
691
      SVN_ERR (svn_io_file_close (b->file_start_revision, b->pool));
 
692
      SVN_ERR (svn_io_file_close (b->file_end_revision, b->pool));
 
693
    }
 
694
 
 
695
  return SVN_NO_ERROR;
 
696
}
 
697
 
 
698
/* An editor function.  */
 
699
static svn_error_t *
 
700
apply_textdelta (void *file_baton,
 
701
                 const char *base_checksum,
 
702
                 apr_pool_t *pool,
 
703
                 svn_txdelta_window_handler_t *handler,
 
704
                 void **handler_baton)
 
705
{
 
706
  struct file_baton *b = file_baton;
 
707
  svn_boolean_t have_lock;
 
708
 
 
709
  /* Open the file to be used as the base for second revision */
 
710
  SVN_ERR (svn_io_file_open (&(b->file_start_revision),
 
711
                             b->path_start_revision,
 
712
                             APR_READ, APR_OS_DEFAULT, b->pool));
 
713
 
 
714
  /* Open the file that will become the second revision after applying the
 
715
     text delta, it starts empty */
 
716
  have_lock = (b->edit_baton->adm_access 
 
717
               && svn_wc_adm_locked (b->edit_baton->adm_access));
 
718
  SVN_ERR (create_empty_file (&(b->path_end_revision), have_lock, b->pool));
 
719
  SVN_ERR (temp_file_cleanup_register (b->path_end_revision, b->pool));
 
720
  SVN_ERR (svn_io_file_open (&(b->file_end_revision), b->path_end_revision,
 
721
                             APR_WRITE, APR_OS_DEFAULT, b->pool));
 
722
 
 
723
  svn_txdelta_apply (svn_stream_from_aprfile (b->file_start_revision, b->pool),
 
724
                     svn_stream_from_aprfile (b->file_end_revision, b->pool),
 
725
                     NULL,
 
726
                     b->path,
 
727
                     b->pool,
 
728
                     &(b->apply_handler), &(b->apply_baton));
 
729
 
 
730
  *handler = window_handler;
 
731
  *handler_baton = file_baton;
 
732
 
 
733
  return SVN_NO_ERROR;
 
734
}
 
735
 
 
736
/* An editor function.  When the file is closed we have a temporary
 
737
 * file containing a pristine version of the repository file. This can
 
738
 * be compared against the working copy.
 
739
 *
 
740
 * ### Ignore TEXT_CHECKSUM for now.  Someday we can use it to verify
 
741
 * ### the integrity of the file being diffed.  Done efficiently, this
 
742
 * ### would probably involve calculating the checksum as the data is
 
743
 * ### received, storing the final checksum in the file_baton, and
 
744
 * ### comparing against it here.
 
745
 */
 
746
static svn_error_t *
 
747
close_file (void *file_baton,
 
748
            const char *text_checksum,
 
749
            apr_pool_t *pool)
 
750
{
 
751
  struct file_baton *b = file_baton;
 
752
  struct edit_baton *eb = b->edit_baton;
 
753
  svn_wc_adm_access_t *adm_access;
 
754
  svn_error_t *err;
 
755
  svn_wc_notify_action_t action;
 
756
  svn_wc_notify_state_t
 
757
    content_state = svn_wc_notify_state_unknown,
 
758
    prop_state = svn_wc_notify_state_unknown;
 
759
 
 
760
  err = get_parent_access (&adm_access, eb->adm_access, 
 
761
                           b->wcpath, eb->dry_run, b->pool);
 
762
 
 
763
  if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
 
764
    {
 
765
      /* ### maybe try to stat the local b->wcpath? */      
 
766
      /* If the file path doesn't exist, then send a 'skipped' notification. */
 
767
      if (eb->notify_func)
 
768
        {
 
769
          svn_wc_notify_t *notify = svn_wc_create_notify (b->wcpath,
 
770
                                                          svn_wc_notify_skip,
 
771
                                                          pool);
 
772
          notify->kind = svn_node_file;
 
773
          notify->content_state = svn_wc_notify_state_missing;
 
774
          notify->prop_state = prop_state;
 
775
          (*eb->notify_func) (eb->notify_baton, notify, pool);
 
776
        }
 
777
      
 
778
      svn_error_clear (err);
 
779
      return SVN_NO_ERROR;
 
780
    }
 
781
  else if (err)
 
782
    return err;
 
783
 
 
784
  if (b->path_end_revision || b->propchanges->nelts > 0)
 
785
    {
 
786
      const char *mimetype1, *mimetype2;
 
787
      get_file_mime_types (&mimetype1, &mimetype2, b);
 
788
 
 
789
      if (b->added)
 
790
        SVN_ERR (eb->diff_callbacks->file_added
 
791
                 (adm_access, &content_state, &prop_state,
 
792
                  b->wcpath,
 
793
                  b->path_end_revision ? b->path_start_revision : NULL,
 
794
                  b->path_end_revision,
 
795
                  0,
 
796
                  b->edit_baton->target_revision,
 
797
                  mimetype1, mimetype2,
 
798
                  b->propchanges, b->pristine_props,
 
799
                  b->edit_baton->diff_cmd_baton));
 
800
      else
 
801
        SVN_ERR (eb->diff_callbacks->file_changed
 
802
                 (adm_access, &content_state, &prop_state,
 
803
                  b->wcpath,
 
804
                  b->path_end_revision ? b->path_start_revision : NULL,
 
805
                  b->path_end_revision,
 
806
                  b->edit_baton->revision,
 
807
                  b->edit_baton->target_revision,
 
808
                  mimetype1, mimetype2,
 
809
                  b->propchanges, b->pristine_props,
 
810
                  b->edit_baton->diff_cmd_baton));
 
811
    }
 
812
 
 
813
 
 
814
  /* ### Is b->path the repos path?  Probably.  This doesn't really
 
815
     matter while issue #748 (svn merge only happens in ".") is
 
816
     outstanding.  But when we take a wc_path as an argument to
 
817
     merge, then we'll need to pass around a wc path somehow. */
 
818
 
 
819
  if ((content_state == svn_wc_notify_state_missing)
 
820
      || (content_state == svn_wc_notify_state_obstructed))
 
821
    action = svn_wc_notify_skip;
 
822
  else if (b->added)
 
823
    action = svn_wc_notify_update_add;
 
824
  else
 
825
    action = svn_wc_notify_update_update;
 
826
 
 
827
  if (eb->notify_func)
 
828
    {
 
829
      svn_wc_notify_t *notify = svn_wc_create_notify (b->wcpath, action,
 
830
                                                      pool);
 
831
      notify->kind = svn_node_file;
 
832
      notify->content_state = content_state;
 
833
      notify->prop_state = prop_state;
 
834
      (*eb->notify_func) (eb->notify_baton, notify, pool);
 
835
    }
 
836
 
 
837
  return SVN_NO_ERROR;
 
838
}
 
839
 
 
840
/* An editor function.  */
 
841
static svn_error_t *
 
842
close_directory (void *dir_baton,
 
843
                 apr_pool_t *pool)
 
844
{
 
845
  struct dir_baton *b = dir_baton;
 
846
  struct edit_baton *eb = b->edit_baton;
 
847
  svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
 
848
  svn_error_t *err;
 
849
 
 
850
  if (b->propchanges->nelts > 0)
 
851
    {
 
852
      svn_wc_adm_access_t *adm_access;
 
853
      err = get_path_access (&adm_access, eb->adm_access, b->wcpath,
 
854
                             eb->dry_run, b->pool);
 
855
 
 
856
      if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
 
857
        {
 
858
          /* ### maybe try to stat the local b->wcpath? */          
 
859
          /* If the path doesn't exist, then send a 'skipped' notification. */
 
860
          if (eb->notify_func)
 
861
            {
 
862
              svn_wc_notify_t *notify
 
863
                = svn_wc_create_notify (b->wcpath, svn_wc_notify_skip, pool);
 
864
              notify->kind = svn_node_dir;
 
865
              notify->content_state = notify->prop_state
 
866
                = svn_wc_notify_state_missing;
 
867
              (*eb->notify_func) (eb->notify_baton, notify, pool);
 
868
            }
 
869
          svn_error_clear (err);      
 
870
          return SVN_NO_ERROR;
 
871
        }
 
872
      else if (err)
 
873
        return err;
 
874
 
 
875
      /* Don't do the props_changed stuff if this is a dry_run and we don't
 
876
         have an access baton, since in that case the directory will already
 
877
         have been recognised as added, in which case they cannot conflict. */
 
878
      if (! eb->dry_run || adm_access)
 
879
        SVN_ERR (eb->diff_callbacks->dir_props_changed
 
880
                 (adm_access, &prop_state,
 
881
                  b->wcpath,
 
882
                  b->propchanges, b->pristine_props,
 
883
                  b->edit_baton->diff_cmd_baton));
 
884
    }
 
885
 
 
886
  if (eb->notify_func)
 
887
    {
 
888
      svn_wc_notify_t *notify
 
889
        = svn_wc_create_notify (b->wcpath, svn_wc_notify_update_update, pool);
 
890
      notify->kind = svn_node_dir;
 
891
      notify->content_state = svn_wc_notify_state_inapplicable;
 
892
      notify->prop_state = prop_state;
 
893
      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
 
894
      (*eb->notify_func) (eb->notify_baton, notify, pool);
 
895
    }
 
896
 
 
897
  return SVN_NO_ERROR;
 
898
}
 
899
 
 
900
 
 
901
/* An editor function.  */
 
902
static svn_error_t *
 
903
change_file_prop (void *file_baton,
 
904
                  const char *name,
 
905
                  const svn_string_t *value,
 
906
                  apr_pool_t *pool)
 
907
{
 
908
  struct file_baton *b = file_baton;
 
909
  svn_prop_t *propchange;
 
910
 
 
911
  propchange = apr_array_push (b->propchanges);
 
912
  propchange->name = apr_pstrdup (b->pool, name);
 
913
  propchange->value = value ? svn_string_dup (value, b->pool) : NULL;
 
914
  
 
915
  return SVN_NO_ERROR;
 
916
}
 
917
 
 
918
/* An editor function.  */
 
919
static svn_error_t *
 
920
change_dir_prop (void *dir_baton,
 
921
                 const char *name,
 
922
                 const svn_string_t *value,
 
923
                 apr_pool_t *pool)
 
924
{
 
925
  struct dir_baton *db = dir_baton;
 
926
  svn_prop_t *propchange;
 
927
 
 
928
  propchange = apr_array_push (db->propchanges);
 
929
  propchange->name = apr_pstrdup (db->pool, name);
 
930
  propchange->value = value ? svn_string_dup (value, db->pool) : NULL;
 
931
 
 
932
  return SVN_NO_ERROR;
 
933
}
 
934
 
 
935
 
 
936
/* An editor function.  */
 
937
static svn_error_t *
 
938
close_edit (void *edit_baton,
 
939
            apr_pool_t *pool)
 
940
{
 
941
  struct edit_baton *eb = edit_baton;
 
942
 
 
943
  svn_pool_destroy (eb->pool);
 
944
 
 
945
  return SVN_NO_ERROR;
 
946
}
 
947
 
 
948
/* Create a repository diff editor and baton.  */
 
949
svn_error_t *
 
950
svn_client__get_diff_editor (const char *target,
 
951
                             svn_wc_adm_access_t *adm_access,
 
952
                             const svn_wc_diff_callbacks2_t *diff_callbacks,
 
953
                             void *diff_cmd_baton,
 
954
                             svn_boolean_t recurse,
 
955
                             svn_boolean_t dry_run,
 
956
                             svn_ra_session_t *ra_session,
 
957
                             svn_revnum_t revision,
 
958
                             svn_wc_notify_func2_t notify_func,
 
959
                             void *notify_baton,
 
960
                             svn_cancel_func_t cancel_func,
 
961
                             void *cancel_baton,
 
962
                             const svn_delta_editor_t **editor,
 
963
                             void **edit_baton,
 
964
                             apr_pool_t *pool)
 
965
{
 
966
  apr_pool_t *subpool = svn_pool_create (pool);
 
967
  svn_delta_editor_t *tree_editor = svn_delta_default_editor (subpool);
 
968
  struct edit_baton *eb = apr_palloc (subpool, sizeof (*eb));
 
969
 
 
970
  eb->target = target;
 
971
  eb->adm_access = adm_access;
 
972
  eb->diff_callbacks = diff_callbacks;
 
973
  eb->diff_cmd_baton = diff_cmd_baton;
 
974
  eb->recurse = recurse;
 
975
  eb->dry_run = dry_run;
 
976
  eb->ra_session = ra_session;
 
977
  eb->revision = revision;
 
978
  eb->empty_file = NULL;
 
979
  eb->empty_hash = apr_hash_make (subpool);
 
980
  eb->pool = subpool;
 
981
  eb->notify_func = notify_func;
 
982
  eb->notify_baton = notify_baton;
 
983
 
 
984
  tree_editor->set_target_revision = set_target_revision;
 
985
  tree_editor->open_root = open_root;
 
986
  tree_editor->delete_entry = delete_entry;
 
987
  tree_editor->add_directory = add_directory;
 
988
  tree_editor->open_directory = open_directory;
 
989
  tree_editor->add_file = add_file;
 
990
  tree_editor->open_file = open_file;
 
991
  tree_editor->apply_textdelta = apply_textdelta;
 
992
  tree_editor->close_file = close_file;
 
993
  tree_editor->close_directory = close_directory;
 
994
  tree_editor->change_file_prop = change_file_prop;
 
995
  tree_editor->change_dir_prop = change_dir_prop;
 
996
  tree_editor->close_edit = close_edit;
 
997
 
 
998
  SVN_ERR (svn_delta_get_cancellation_editor (cancel_func,
 
999
                                              cancel_baton,
 
1000
                                              tree_editor,
 
1001
                                              eb,
 
1002
                                              editor,
 
1003
                                              edit_baton,
 
1004
                                              pool));
 
1005
 
 
1006
  return SVN_NO_ERROR;
 
1007
}