1251881Speter/*
2251881Speter *  dump_editor.c: The svn_delta_editor_t editor used by svnrdump to
3251881Speter *  dump revisions.
4251881Speter *
5251881Speter * ====================================================================
6251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
7251881Speter *    or more contributor license agreements.  See the NOTICE file
8251881Speter *    distributed with this work for additional information
9251881Speter *    regarding copyright ownership.  The ASF licenses this file
10251881Speter *    to you under the Apache License, Version 2.0 (the
11251881Speter *    "License"); you may not use this file except in compliance
12251881Speter *    with the License.  You may obtain a copy of the License at
13251881Speter *
14251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
15251881Speter *
16251881Speter *    Unless required by applicable law or agreed to in writing,
17251881Speter *    software distributed under the License is distributed on an
18251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19251881Speter *    KIND, either express or implied.  See the License for the
20251881Speter *    specific language governing permissions and limitations
21251881Speter *    under the License.
22251881Speter * ====================================================================
23251881Speter */
24251881Speter
25251881Speter#include "svn_hash.h"
26251881Speter#include "svn_pools.h"
27251881Speter#include "svn_repos.h"
28251881Speter#include "svn_path.h"
29251881Speter#include "svn_props.h"
30251881Speter#include "svn_subst.h"
31251881Speter#include "svn_dirent_uri.h"
32251881Speter
33251881Speter#include "private/svn_subr_private.h"
34251881Speter#include "private/svn_dep_compat.h"
35251881Speter#include "private/svn_editor.h"
36251881Speter
37251881Speter#include "svnrdump.h"
38251881Speter#include <assert.h>
39251881Speter
40251881Speter#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
41251881Speter
42251881Speter#if 0
43251881Speter#define LDR_DBG(x) SVN_DBG(x)
44251881Speter#else
45251881Speter#define LDR_DBG(x) while(0)
46251881Speter#endif
47251881Speter
48251881Speter/* A directory baton used by all directory-related callback functions
49251881Speter * in the dump editor.  */
50251881Speterstruct dir_baton
51251881Speter{
52251881Speter  struct dump_edit_baton *eb;
53251881Speter  struct dir_baton *parent_dir_baton;
54251881Speter
55251881Speter  /* Pool for per-directory allocations */
56251881Speter  apr_pool_t *pool;
57251881Speter
58251881Speter  /* is this directory a new addition to this revision? */
59251881Speter  svn_boolean_t added;
60251881Speter
61251881Speter  /* has this directory been written to the output stream? */
62251881Speter  svn_boolean_t written_out;
63251881Speter
64251881Speter  /* the path to this directory */
65251881Speter  const char *repos_relpath; /* a relpath */
66251881Speter
67251881Speter  /* Copyfrom info for the node, if any. */
68251881Speter  const char *copyfrom_path; /* a relpath */
69251881Speter  svn_revnum_t copyfrom_rev;
70251881Speter
71251881Speter  /* Properties which were modified during change_dir_prop. */
72251881Speter  apr_hash_t *props;
73251881Speter
74251881Speter  /* Properties which were deleted during change_dir_prop. */
75251881Speter  apr_hash_t *deleted_props;
76251881Speter
77251881Speter  /* Hash of paths that need to be deleted, though some -might- be
78251881Speter     replaced.  Maps const char * paths to this dir_baton. Note that
79251881Speter     they're full paths, because that's what the editor driver gives
80251881Speter     us, although they're all really within this directory. */
81251881Speter  apr_hash_t *deleted_entries;
82251881Speter
83251881Speter  /* Flags to trigger dumping props and record termination newlines. */
84251881Speter  svn_boolean_t dump_props;
85251881Speter  svn_boolean_t dump_newlines;
86251881Speter};
87251881Speter
88251881Speter/* A file baton used by all file-related callback functions in the dump
89251881Speter * editor */
90251881Speterstruct file_baton
91251881Speter{
92251881Speter  struct dump_edit_baton *eb;
93251881Speter  struct dir_baton *parent_dir_baton;
94251881Speter
95251881Speter  /* Pool for per-file allocations */
96251881Speter  apr_pool_t *pool;
97251881Speter
98251881Speter  /* the path to this file */
99251881Speter  const char *repos_relpath; /* a relpath */
100251881Speter
101251881Speter  /* Properties which were modified during change_file_prop. */
102251881Speter  apr_hash_t *props;
103251881Speter
104251881Speter  /* Properties which were deleted during change_file_prop. */
105251881Speter  apr_hash_t *deleted_props;
106251881Speter
107251881Speter  /* The checksum of the file the delta is being applied to */
108251881Speter  const char *base_checksum;
109251881Speter
110251881Speter  /* Copy state and source information (if any). */
111251881Speter  svn_boolean_t is_copy;
112251881Speter  const char *copyfrom_path;
113251881Speter  svn_revnum_t copyfrom_rev;
114251881Speter
115251881Speter  /* The action associate with this node. */
116251881Speter  enum svn_node_action action;
117251881Speter
118251881Speter  /* Flags to trigger dumping props and text. */
119251881Speter  svn_boolean_t dump_text;
120251881Speter  svn_boolean_t dump_props;
121251881Speter};
122251881Speter
123251881Speter/* A handler baton to be used in window_handler().  */
124251881Speterstruct handler_baton
125251881Speter{
126251881Speter  svn_txdelta_window_handler_t apply_handler;
127251881Speter  void *apply_baton;
128251881Speter};
129251881Speter
130251881Speter/* The baton used by the dump editor. */
131251881Speterstruct dump_edit_baton {
132251881Speter  /* The output stream we write the dumpfile to */
133251881Speter  svn_stream_t *stream;
134251881Speter
135251881Speter  /* A backdoor ra session to fetch additional information during the edit. */
136251881Speter  svn_ra_session_t *ra_session;
137251881Speter
138251881Speter  /* The repository relpath of the anchor of the editor when driven
139251881Speter     via the RA update mechanism; NULL otherwise. (When the editor is
140251881Speter     driven via the RA "replay" mechanism instead, the editor is
141251881Speter     always anchored at the repository, we don't need to prepend an
142251881Speter     anchor path to the dumped node paths, and open_root() doesn't
143251881Speter     need to manufacture directory additions.)  */
144251881Speter  const char *update_anchor_relpath;
145251881Speter
146251881Speter  /* Pool for per-revision allocations */
147251881Speter  apr_pool_t *pool;
148251881Speter
149251881Speter  /* Temporary file used for textdelta application along with its
150251881Speter     absolute path; these two variables should be allocated in the
151251881Speter     per-edit-session pool */
152251881Speter  const char *delta_abspath;
153251881Speter  apr_file_t *delta_file;
154251881Speter
155251881Speter  /* The revision we're currently dumping. */
156251881Speter  svn_revnum_t current_revision;
157251881Speter
158251881Speter  /* The kind (file or directory) and baton of the item whose block of
159251881Speter     dump stream data has not been fully completed; NULL if there's no
160251881Speter     such item. */
161251881Speter  svn_node_kind_t pending_kind;
162251881Speter  void *pending_baton;
163251881Speter};
164251881Speter
165251881Speter/* Make a directory baton to represent the directory at PATH (relative
166251881Speter * to the EDIT_BATON).
167251881Speter *
168251881Speter * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
169251881Speter * directory should be compared for changes. If the copyfrom
170251881Speter * information is valid, the directory will be compared against its
171251881Speter * copy source.
172251881Speter *
173251881Speter * PB is the directory baton of this directory's parent, or NULL if
174251881Speter * this is the top-level directory of the edit.  ADDED indicates if
175251881Speter * this directory is newly added in this revision.  Perform all
176251881Speter * allocations in POOL.  */
177251881Speterstatic struct dir_baton *
178251881Spetermake_dir_baton(const char *path,
179251881Speter               const char *copyfrom_path,
180251881Speter               svn_revnum_t copyfrom_rev,
181251881Speter               void *edit_baton,
182251881Speter               struct dir_baton *pb,
183251881Speter               svn_boolean_t added,
184251881Speter               apr_pool_t *pool)
185251881Speter{
186251881Speter  struct dump_edit_baton *eb = edit_baton;
187251881Speter  struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
188251881Speter  const char *repos_relpath;
189251881Speter
190251881Speter  /* Construct the full path of this node. */
191251881Speter  if (pb)
192251881Speter    repos_relpath = svn_relpath_canonicalize(path, pool);
193251881Speter  else
194251881Speter    repos_relpath = "";
195251881Speter
196251881Speter  /* Strip leading slash from copyfrom_path so that the path is
197251881Speter     canonical and svn_relpath_join can be used */
198251881Speter  if (copyfrom_path)
199251881Speter    copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
200251881Speter
201251881Speter  new_db->eb = eb;
202251881Speter  new_db->parent_dir_baton = pb;
203251881Speter  new_db->pool = pool;
204251881Speter  new_db->repos_relpath = repos_relpath;
205251881Speter  new_db->copyfrom_path = copyfrom_path
206251881Speter                            ? svn_relpath_canonicalize(copyfrom_path, pool)
207251881Speter                            : NULL;
208251881Speter  new_db->copyfrom_rev = copyfrom_rev;
209251881Speter  new_db->added = added;
210251881Speter  new_db->written_out = FALSE;
211251881Speter  new_db->props = apr_hash_make(pool);
212251881Speter  new_db->deleted_props = apr_hash_make(pool);
213251881Speter  new_db->deleted_entries = apr_hash_make(pool);
214251881Speter
215251881Speter  return new_db;
216251881Speter}
217251881Speter
218251881Speter/* Make a file baton to represent the directory at PATH (relative to
219251881Speter * PB->eb).  PB is the directory baton of this directory's parent, or
220251881Speter * NULL if this is the top-level directory of the edit.  Perform all
221251881Speter * allocations in POOL.  */
222251881Speterstatic struct file_baton *
223251881Spetermake_file_baton(const char *path,
224251881Speter                struct dir_baton *pb,
225251881Speter                apr_pool_t *pool)
226251881Speter{
227251881Speter  struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
228251881Speter
229251881Speter  new_fb->eb = pb->eb;
230251881Speter  new_fb->parent_dir_baton = pb;
231251881Speter  new_fb->pool = pool;
232251881Speter  new_fb->repos_relpath = svn_relpath_canonicalize(path, pool);
233251881Speter  new_fb->props = apr_hash_make(pool);
234251881Speter  new_fb->deleted_props = apr_hash_make(pool);
235251881Speter  new_fb->is_copy = FALSE;
236251881Speter  new_fb->copyfrom_path = NULL;
237251881Speter  new_fb->copyfrom_rev = SVN_INVALID_REVNUM;
238251881Speter  new_fb->action = svn_node_action_change;
239251881Speter
240251881Speter  return new_fb;
241251881Speter}
242251881Speter
243251881Speter/* Return in *HEADER and *CONTENT the headers and content for PROPS. */
244251881Speterstatic svn_error_t *
245251881Speterget_props_content(svn_stringbuf_t **header,
246251881Speter                  svn_stringbuf_t **content,
247251881Speter                  apr_hash_t *props,
248251881Speter                  apr_hash_t *deleted_props,
249251881Speter                  apr_pool_t *result_pool,
250251881Speter                  apr_pool_t *scratch_pool)
251251881Speter{
252251881Speter  svn_stream_t *content_stream;
253251881Speter  apr_hash_t *normal_props;
254251881Speter  const char *buf;
255251881Speter
256251881Speter  *content = svn_stringbuf_create_empty(result_pool);
257251881Speter  *header = svn_stringbuf_create_empty(result_pool);
258251881Speter
259251881Speter  content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
260251881Speter
261251881Speter  SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool));
262251881Speter  SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props,
263251881Speter                                     content_stream, "PROPS-END",
264251881Speter                                     scratch_pool));
265251881Speter  SVN_ERR(svn_stream_close(content_stream));
266251881Speter
267251881Speter  /* Prop-delta: true */
268251881Speter  *header = svn_stringbuf_createf(result_pool, SVN_REPOS_DUMPFILE_PROP_DELTA
269251881Speter                                  ": true\n");
270251881Speter
271251881Speter  /* Prop-content-length: 193 */
272251881Speter  buf = apr_psprintf(scratch_pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
273251881Speter                     ": %" APR_SIZE_T_FMT "\n", (*content)->len);
274251881Speter  svn_stringbuf_appendcstr(*header, buf);
275251881Speter
276251881Speter  return SVN_NO_ERROR;
277251881Speter}
278251881Speter
279251881Speter/* Extract and dump properties stored in PROPS and property deletions
280251881Speter * stored in DELETED_PROPS. If TRIGGER_VAR is not NULL, it is set to
281251881Speter * FALSE.
282251881Speter *
283251881Speter * If PROPSTRING is non-NULL, set *PROPSTRING to a string containing
284251881Speter * the content block of the property changes; otherwise, dump that to
285251881Speter * the stream, too.
286251881Speter */
287251881Speterstatic svn_error_t *
288251881Speterdo_dump_props(svn_stringbuf_t **propstring,
289251881Speter              svn_stream_t *stream,
290251881Speter              apr_hash_t *props,
291251881Speter              apr_hash_t *deleted_props,
292251881Speter              svn_boolean_t *trigger_var,
293251881Speter              apr_pool_t *result_pool,
294251881Speter              apr_pool_t *scratch_pool)
295251881Speter{
296251881Speter  svn_stringbuf_t *header;
297251881Speter  svn_stringbuf_t *content;
298251881Speter  apr_size_t len;
299251881Speter
300251881Speter  if (trigger_var && !*trigger_var)
301251881Speter    return SVN_NO_ERROR;
302251881Speter
303251881Speter  SVN_ERR(get_props_content(&header, &content, props, deleted_props,
304251881Speter                            result_pool, scratch_pool));
305251881Speter  len = header->len;
306251881Speter  SVN_ERR(svn_stream_write(stream, header->data, &len));
307251881Speter
308251881Speter  if (propstring)
309251881Speter    {
310251881Speter      *propstring = content;
311251881Speter    }
312251881Speter  else
313251881Speter    {
314251881Speter      /* Content-length: 14 */
315251881Speter      SVN_ERR(svn_stream_printf(stream, scratch_pool,
316251881Speter                                SVN_REPOS_DUMPFILE_CONTENT_LENGTH
317251881Speter                                ": %" APR_SIZE_T_FMT "\n\n",
318251881Speter                                content->len));
319251881Speter
320251881Speter      len = content->len;
321251881Speter      SVN_ERR(svn_stream_write(stream, content->data, &len));
322251881Speter
323251881Speter      /* No text is going to be dumped. Write a couple of newlines and
324251881Speter         wait for the next node/ revision. */
325251881Speter      SVN_ERR(svn_stream_puts(stream, "\n\n"));
326251881Speter
327251881Speter      /* Cleanup so that data is never dumped twice. */
328251881Speter      apr_hash_clear(props);
329251881Speter      apr_hash_clear(deleted_props);
330251881Speter      if (trigger_var)
331251881Speter        *trigger_var = FALSE;
332251881Speter    }
333251881Speter
334251881Speter  return SVN_NO_ERROR;
335251881Speter}
336251881Speter
337251881Speterstatic svn_error_t *
338251881Speterdo_dump_newlines(struct dump_edit_baton *eb,
339251881Speter                 svn_boolean_t *trigger_var,
340251881Speter                 apr_pool_t *pool)
341251881Speter{
342251881Speter  if (trigger_var && *trigger_var)
343251881Speter    {
344251881Speter      SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
345251881Speter      *trigger_var = FALSE;
346251881Speter    }
347251881Speter  return SVN_NO_ERROR;
348251881Speter}
349251881Speter
350251881Speter/*
351251881Speter * Write out a node record for PATH of type KIND under EB->FS_ROOT.
352251881Speter * ACTION describes what is happening to the node (see enum
353251881Speter * svn_node_action). Write record to writable EB->STREAM, using
354251881Speter * EB->BUFFER to write in chunks.
355251881Speter *
356251881Speter * If the node was itself copied, IS_COPY is TRUE and the
357251881Speter * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
358251881Speter * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
359251881Speter * node is part of a copied subtree.
360251881Speter */
361251881Speterstatic svn_error_t *
362251881Speterdump_node(struct dump_edit_baton *eb,
363251881Speter          const char *repos_relpath,
364251881Speter          struct dir_baton *db,
365251881Speter          struct file_baton *fb,
366251881Speter          enum svn_node_action action,
367251881Speter          svn_boolean_t is_copy,
368251881Speter          const char *copyfrom_path,
369251881Speter          svn_revnum_t copyfrom_rev,
370251881Speter          apr_pool_t *pool)
371251881Speter{
372251881Speter  const char *node_relpath = repos_relpath;
373251881Speter
374251881Speter  assert(svn_relpath_is_canonical(repos_relpath));
375251881Speter  assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
376251881Speter  assert(! (db && fb));
377251881Speter
378251881Speter  /* Add the edit root relpath prefix if necessary. */
379251881Speter  if (eb->update_anchor_relpath)
380251881Speter    node_relpath = svn_relpath_join(eb->update_anchor_relpath,
381251881Speter                                    node_relpath, pool);
382251881Speter
383251881Speter  /* Node-path: ... */
384251881Speter  SVN_ERR(svn_stream_printf(eb->stream, pool,
385251881Speter                            SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
386251881Speter                            node_relpath));
387251881Speter
388251881Speter  /* Node-kind: "file" | "dir" */
389251881Speter  if (fb)
390251881Speter    SVN_ERR(svn_stream_printf(eb->stream, pool,
391251881Speter                              SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
392251881Speter  else if (db)
393251881Speter    SVN_ERR(svn_stream_printf(eb->stream, pool,
394251881Speter                              SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
395251881Speter
396251881Speter
397251881Speter  /* Write the appropriate Node-action header */
398251881Speter  switch (action)
399251881Speter    {
400251881Speter    case svn_node_action_change:
401251881Speter      /* We are here after a change_file_prop or change_dir_prop. They
402251881Speter         set up whatever dump_props they needed to- nothing to
403251881Speter         do here but print node action information.
404251881Speter
405251881Speter         Node-action: change.  */
406251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
407251881Speter                              SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
408251881Speter      break;
409251881Speter
410251881Speter    case svn_node_action_replace:
411251881Speter      if (is_copy)
412251881Speter        {
413251881Speter          /* Delete the original, and then re-add the replacement as a
414251881Speter             copy using recursive calls into this function. */
415251881Speter          SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_delete,
416251881Speter                            FALSE, NULL, SVN_INVALID_REVNUM, pool));
417251881Speter          SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_add,
418251881Speter                            is_copy, copyfrom_path, copyfrom_rev, pool));
419251881Speter        }
420251881Speter      else
421251881Speter        {
422251881Speter          /* Node-action: replace */
423251881Speter          SVN_ERR(svn_stream_puts(eb->stream,
424251881Speter                                  SVN_REPOS_DUMPFILE_NODE_ACTION
425251881Speter                                  ": replace\n"));
426251881Speter
427251881Speter          /* Wait for a change_*_prop to be called before dumping
428251881Speter             anything */
429251881Speter          if (fb)
430251881Speter            fb->dump_props = TRUE;
431251881Speter          else if (db)
432251881Speter            db->dump_props = TRUE;
433251881Speter        }
434251881Speter      break;
435251881Speter
436251881Speter    case svn_node_action_delete:
437251881Speter      /* Node-action: delete */
438251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
439251881Speter                              SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
440251881Speter
441251881Speter      /* We can leave this routine quietly now. Nothing more to do-
442251881Speter         print a couple of newlines because we're not dumping props or
443251881Speter         text. */
444251881Speter      SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
445251881Speter
446251881Speter      break;
447251881Speter
448251881Speter    case svn_node_action_add:
449251881Speter      /* Node-action: add */
450251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
451251881Speter                              SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
452251881Speter
453251881Speter      if (is_copy)
454251881Speter        {
455251881Speter          /* Node-copyfrom-rev / Node-copyfrom-path */
456251881Speter          SVN_ERR(svn_stream_printf(eb->stream, pool,
457251881Speter                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
458251881Speter                                    ": %ld\n"
459251881Speter                                    SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
460251881Speter                                    ": %s\n",
461251881Speter                                    copyfrom_rev, copyfrom_path));
462251881Speter
463251881Speter          /* Ugly hack: If a directory was copied from a previous
464251881Speter             revision, nothing like close_file() will be called to write two
465251881Speter             blank lines. If change_dir_prop() is called, props are dumped
466251881Speter             (along with the necessary PROPS-END\n\n and we're good. So
467251881Speter             set DUMP_NEWLINES here to print the newlines unless
468251881Speter             change_dir_prop() is called next otherwise the `svnadmin load`
469251881Speter             parser will fail.  */
470251881Speter          if (db)
471251881Speter            db->dump_newlines = TRUE;
472251881Speter        }
473251881Speter      else
474251881Speter        {
475251881Speter          /* fb->dump_props (for files) is handled in close_file()
476251881Speter             which is called immediately.
477251881Speter
478251881Speter             However, directories are not closed until all the work
479251881Speter             inside them has been done; db->dump_props (for directories)
480251881Speter             is handled (via dump_pending()) in all the functions that
481251881Speter             can possibly be called after add_directory():
482251881Speter
483251881Speter               - add_directory()
484251881Speter               - open_directory()
485251881Speter               - delete_entry()
486251881Speter               - close_directory()
487251881Speter               - add_file()
488251881Speter               - open_file()
489251881Speter
490251881Speter             change_dir_prop() is a special case. */
491251881Speter          if (fb)
492251881Speter            fb->dump_props = TRUE;
493251881Speter          else if (db)
494251881Speter            db->dump_props = TRUE;
495251881Speter        }
496251881Speter
497251881Speter      break;
498251881Speter    }
499251881Speter  return SVN_NO_ERROR;
500251881Speter}
501251881Speter
502251881Speterstatic svn_error_t *
503251881Speterdump_mkdir(struct dump_edit_baton *eb,
504251881Speter           const char *repos_relpath,
505251881Speter           apr_pool_t *pool)
506251881Speter{
507251881Speter  svn_stringbuf_t *prop_header, *prop_content;
508251881Speter  apr_size_t len;
509251881Speter  const char *buf;
510251881Speter
511251881Speter  /* Node-path: ... */
512251881Speter  SVN_ERR(svn_stream_printf(eb->stream, pool,
513251881Speter                            SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
514251881Speter                            repos_relpath));
515251881Speter
516251881Speter  /* Node-kind: dir */
517251881Speter  SVN_ERR(svn_stream_printf(eb->stream, pool,
518251881Speter                            SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
519251881Speter
520251881Speter  /* Node-action: add */
521251881Speter  SVN_ERR(svn_stream_puts(eb->stream,
522251881Speter                          SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
523251881Speter
524251881Speter  /* Dump the (empty) property block. */
525251881Speter  SVN_ERR(get_props_content(&prop_header, &prop_content,
526251881Speter                            apr_hash_make(pool), apr_hash_make(pool),
527251881Speter                            pool, pool));
528251881Speter  len = prop_header->len;
529251881Speter  SVN_ERR(svn_stream_write(eb->stream, prop_header->data, &len));
530251881Speter  len = prop_content->len;
531251881Speter  buf = apr_psprintf(pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH
532251881Speter                     ": %" APR_SIZE_T_FMT "\n", len);
533251881Speter  SVN_ERR(svn_stream_puts(eb->stream, buf));
534251881Speter  SVN_ERR(svn_stream_puts(eb->stream, "\n"));
535251881Speter  SVN_ERR(svn_stream_write(eb->stream, prop_content->data, &len));
536251881Speter
537251881Speter  /* Newlines to tie it all off. */
538251881Speter  SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
539251881Speter
540251881Speter  return SVN_NO_ERROR;
541251881Speter}
542251881Speter
543251881Speter/* Dump pending items from the specified node, to allow starting the dump
544251881Speter   of a child node */
545251881Speterstatic svn_error_t *
546251881Speterdump_pending(struct dump_edit_baton *eb,
547251881Speter             apr_pool_t *scratch_pool)
548251881Speter{
549251881Speter  if (! eb->pending_baton)
550251881Speter    return SVN_NO_ERROR;
551251881Speter
552251881Speter  if (eb->pending_kind == svn_node_dir)
553251881Speter    {
554251881Speter      struct dir_baton *db = eb->pending_baton;
555251881Speter
556251881Speter      /* Some pending properties to dump? */
557251881Speter      SVN_ERR(do_dump_props(NULL, eb->stream, db->props, db->deleted_props,
558251881Speter                            &(db->dump_props), db->pool, scratch_pool));
559251881Speter
560251881Speter      /* Some pending newlines to dump? */
561251881Speter      SVN_ERR(do_dump_newlines(eb, &(db->dump_newlines), scratch_pool));
562251881Speter    }
563251881Speter  else if (eb->pending_kind == svn_node_file)
564251881Speter    {
565251881Speter      struct file_baton *fb = eb->pending_baton;
566251881Speter
567251881Speter      /* Some pending properties to dump? */
568251881Speter      SVN_ERR(do_dump_props(NULL, eb->stream, fb->props, fb->deleted_props,
569251881Speter                            &(fb->dump_props), fb->pool, scratch_pool));
570251881Speter    }
571251881Speter  else
572251881Speter    abort();
573251881Speter
574251881Speter  /* Anything that was pending is pending no longer. */
575251881Speter  eb->pending_baton = NULL;
576251881Speter  eb->pending_kind = svn_node_none;
577251881Speter
578251881Speter  return SVN_NO_ERROR;
579251881Speter}
580251881Speter
581251881Speter
582251881Speter
583251881Speter/*** Editor Function Implementations ***/
584251881Speter
585251881Speterstatic svn_error_t *
586251881Speteropen_root(void *edit_baton,
587251881Speter          svn_revnum_t base_revision,
588251881Speter          apr_pool_t *pool,
589251881Speter          void **root_baton)
590251881Speter{
591251881Speter  struct dump_edit_baton *eb = edit_baton;
592251881Speter  struct dir_baton *new_db = NULL;
593251881Speter
594251881Speter  /* Clear the per-revision pool after each revision */
595251881Speter  svn_pool_clear(eb->pool);
596251881Speter
597251881Speter  LDR_DBG(("open_root %p\n", *root_baton));
598251881Speter
599251881Speter  if (eb->update_anchor_relpath)
600251881Speter    {
601251881Speter      int i;
602251881Speter      const char *parent_path = eb->update_anchor_relpath;
603251881Speter      apr_array_header_t *dirs_to_add =
604251881Speter        apr_array_make(pool, 4, sizeof(const char *));
605251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
606251881Speter
607251881Speter      while (! svn_path_is_empty(parent_path))
608251881Speter        {
609251881Speter          APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
610251881Speter          parent_path = svn_relpath_dirname(parent_path, pool);
611251881Speter        }
612251881Speter
613251881Speter      for (i = dirs_to_add->nelts; i; --i)
614251881Speter        {
615251881Speter          const char *dir_to_add =
616251881Speter            APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
617251881Speter
618251881Speter          svn_pool_clear(iterpool);
619251881Speter
620251881Speter          /* For parents of the source directory, we just manufacture
621251881Speter             the adds ourselves. */
622251881Speter          if (i > 1)
623251881Speter            {
624251881Speter              SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
625251881Speter            }
626251881Speter          else
627251881Speter            {
628251881Speter              /* ... but for the source directory itself, we'll defer
629251881Speter                 to letting the typical plumbing handle this task. */
630251881Speter              new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
631251881Speter                                      edit_baton, NULL, TRUE, pool);
632251881Speter              SVN_ERR(dump_node(eb, new_db->repos_relpath, new_db,
633251881Speter                                NULL, svn_node_action_add, FALSE,
634251881Speter                                NULL, SVN_INVALID_REVNUM, pool));
635251881Speter
636251881Speter              /* Remember that we've started but not yet finished
637251881Speter                 handling this directory. */
638251881Speter              new_db->written_out = TRUE;
639251881Speter              eb->pending_baton = new_db;
640251881Speter              eb->pending_kind = svn_node_dir;
641251881Speter            }
642251881Speter        }
643251881Speter      svn_pool_destroy(iterpool);
644251881Speter    }
645251881Speter
646251881Speter  if (! new_db)
647251881Speter    {
648251881Speter      new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
649251881Speter                              edit_baton, NULL, FALSE, pool);
650251881Speter    }
651251881Speter
652251881Speter  *root_baton = new_db;
653251881Speter  return SVN_NO_ERROR;
654251881Speter}
655251881Speter
656251881Speterstatic svn_error_t *
657251881Speterdelete_entry(const char *path,
658251881Speter             svn_revnum_t revision,
659251881Speter             void *parent_baton,
660251881Speter             apr_pool_t *pool)
661251881Speter{
662251881Speter  struct dir_baton *pb = parent_baton;
663251881Speter
664251881Speter  LDR_DBG(("delete_entry %s\n", path));
665251881Speter
666251881Speter  SVN_ERR(dump_pending(pb->eb, pool));
667251881Speter
668251881Speter  /* We don't dump this deletion immediate.  Rather, we add this path
669251881Speter     to the deleted_entries of the parent directory baton.  That way,
670251881Speter     we can tell (later) an addition from a replacement.  All the real
671251881Speter     deletions get handled in close_directory().  */
672251881Speter  svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), pb);
673251881Speter
674251881Speter  return SVN_NO_ERROR;
675251881Speter}
676251881Speter
677251881Speterstatic svn_error_t *
678251881Speteradd_directory(const char *path,
679251881Speter              void *parent_baton,
680251881Speter              const char *copyfrom_path,
681251881Speter              svn_revnum_t copyfrom_rev,
682251881Speter              apr_pool_t *pool,
683251881Speter              void **child_baton)
684251881Speter{
685251881Speter  struct dir_baton *pb = parent_baton;
686251881Speter  void *val;
687251881Speter  struct dir_baton *new_db;
688251881Speter  svn_boolean_t is_copy;
689251881Speter
690251881Speter  LDR_DBG(("add_directory %s\n", path));
691251881Speter
692251881Speter  SVN_ERR(dump_pending(pb->eb, pool));
693251881Speter
694251881Speter  new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb,
695251881Speter                          pb, TRUE, pb->eb->pool);
696251881Speter
697251881Speter  /* This might be a replacement -- is the path already deleted? */
698251881Speter  val = svn_hash_gets(pb->deleted_entries, path);
699251881Speter
700251881Speter  /* Detect an add-with-history */
701251881Speter  is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
702251881Speter
703251881Speter  /* Dump the node */
704251881Speter  SVN_ERR(dump_node(pb->eb, new_db->repos_relpath, new_db, NULL,
705251881Speter                    val ? svn_node_action_replace : svn_node_action_add,
706251881Speter                    is_copy,
707251881Speter                    is_copy ? new_db->copyfrom_path : NULL,
708251881Speter                    is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
709251881Speter                    pool));
710251881Speter
711251881Speter  if (val)
712251881Speter    /* Delete the path, it's now been dumped */
713251881Speter    svn_hash_sets(pb->deleted_entries, path, NULL);
714251881Speter
715251881Speter  /* Remember that we've started, but not yet finished handling this
716251881Speter     directory. */
717251881Speter  new_db->written_out = TRUE;
718251881Speter  pb->eb->pending_baton = new_db;
719251881Speter  pb->eb->pending_kind = svn_node_dir;
720251881Speter
721251881Speter  *child_baton = new_db;
722251881Speter  return SVN_NO_ERROR;
723251881Speter}
724251881Speter
725251881Speterstatic svn_error_t *
726251881Speteropen_directory(const char *path,
727251881Speter               void *parent_baton,
728251881Speter               svn_revnum_t base_revision,
729251881Speter               apr_pool_t *pool,
730251881Speter               void **child_baton)
731251881Speter{
732251881Speter  struct dir_baton *pb = parent_baton;
733251881Speter  struct dir_baton *new_db;
734251881Speter  const char *copyfrom_path = NULL;
735251881Speter  svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
736251881Speter
737251881Speter  LDR_DBG(("open_directory %s\n", path));
738251881Speter
739251881Speter  SVN_ERR(dump_pending(pb->eb, pool));
740251881Speter
741251881Speter  /* If the parent directory has explicit comparison path and rev,
742251881Speter     record the same for this one. */
743251881Speter  if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
744251881Speter    {
745251881Speter      copyfrom_path = svn_relpath_join(pb->copyfrom_path,
746251881Speter                                       svn_relpath_basename(path, NULL),
747251881Speter                                       pb->eb->pool);
748251881Speter      copyfrom_rev = pb->copyfrom_rev;
749251881Speter    }
750251881Speter
751251881Speter  new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
752251881Speter                          FALSE, pb->eb->pool);
753251881Speter
754251881Speter  *child_baton = new_db;
755251881Speter  return SVN_NO_ERROR;
756251881Speter}
757251881Speter
758251881Speterstatic svn_error_t *
759251881Speterclose_directory(void *dir_baton,
760251881Speter                apr_pool_t *pool)
761251881Speter{
762251881Speter  struct dir_baton *db = dir_baton;
763251881Speter  apr_hash_index_t *hi;
764251881Speter  svn_boolean_t this_pending;
765251881Speter
766251881Speter  LDR_DBG(("close_directory %p\n", dir_baton));
767251881Speter
768251881Speter  /* Remember if this directory is the one currently pending. */
769251881Speter  this_pending = (db->eb->pending_baton == db);
770251881Speter
771251881Speter  SVN_ERR(dump_pending(db->eb, pool));
772251881Speter
773251881Speter  /* If this directory was pending, then dump_pending() should have
774251881Speter     taken care of all the props and such.  Of course, the only way
775251881Speter     that would be the case is if this directory was added/replaced.
776251881Speter
777251881Speter     Otherwise, if stuff for this directory has already been written
778251881Speter     out (at some point in the past, prior to our handling other
779251881Speter     nodes), we might need to generate a second "change" record just
780251881Speter     to carry the information we've since learned about the
781251881Speter     directory. */
782251881Speter  if ((! this_pending) && (db->dump_props))
783251881Speter    {
784251881Speter      SVN_ERR(dump_node(db->eb, db->repos_relpath, db, NULL,
785251881Speter                        svn_node_action_change, FALSE,
786251881Speter                        NULL, SVN_INVALID_REVNUM, pool));
787251881Speter      db->eb->pending_baton = db;
788251881Speter      db->eb->pending_kind = svn_node_dir;
789251881Speter      SVN_ERR(dump_pending(db->eb, pool));
790251881Speter    }
791251881Speter
792251881Speter  /* Dump the deleted directory entries */
793251881Speter  for (hi = apr_hash_first(pool, db->deleted_entries); hi;
794251881Speter       hi = apr_hash_next(hi))
795251881Speter    {
796251881Speter      const char *path = svn__apr_hash_index_key(hi);
797251881Speter
798251881Speter      SVN_ERR(dump_node(db->eb, path, NULL, NULL, svn_node_action_delete,
799251881Speter                        FALSE, NULL, SVN_INVALID_REVNUM, pool));
800251881Speter    }
801251881Speter
802251881Speter  /* ### should be unnecessary */
803251881Speter  apr_hash_clear(db->deleted_entries);
804251881Speter
805251881Speter  return SVN_NO_ERROR;
806251881Speter}
807251881Speter
808251881Speterstatic svn_error_t *
809251881Speteradd_file(const char *path,
810251881Speter         void *parent_baton,
811251881Speter         const char *copyfrom_path,
812251881Speter         svn_revnum_t copyfrom_rev,
813251881Speter         apr_pool_t *pool,
814251881Speter         void **file_baton)
815251881Speter{
816251881Speter  struct dir_baton *pb = parent_baton;
817251881Speter  struct file_baton *fb;
818251881Speter  void *val;
819251881Speter
820251881Speter  LDR_DBG(("add_file %s\n", path));
821251881Speter
822251881Speter  SVN_ERR(dump_pending(pb->eb, pool));
823251881Speter
824251881Speter  /* Make the file baton. */
825251881Speter  fb = make_file_baton(path, pb, pool);
826251881Speter
827251881Speter  /* This might be a replacement -- is the path already deleted? */
828251881Speter  val = svn_hash_gets(pb->deleted_entries, path);
829251881Speter
830251881Speter  /* Detect add-with-history. */
831251881Speter  if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
832251881Speter    {
833251881Speter      fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
834251881Speter      fb->copyfrom_rev = copyfrom_rev;
835251881Speter      fb->is_copy = TRUE;
836251881Speter    }
837251881Speter  fb->action = val ? svn_node_action_replace : svn_node_action_add;
838251881Speter
839251881Speter  /* Delete the path, it's now been dumped. */
840251881Speter  if (val)
841251881Speter    svn_hash_sets(pb->deleted_entries, path, NULL);
842251881Speter
843251881Speter  *file_baton = fb;
844251881Speter  return SVN_NO_ERROR;
845251881Speter}
846251881Speter
847251881Speterstatic svn_error_t *
848251881Speteropen_file(const char *path,
849251881Speter          void *parent_baton,
850251881Speter          svn_revnum_t ancestor_revision,
851251881Speter          apr_pool_t *pool,
852251881Speter          void **file_baton)
853251881Speter{
854251881Speter  struct dir_baton *pb = parent_baton;
855251881Speter  struct file_baton *fb;
856251881Speter
857251881Speter  LDR_DBG(("open_file %s\n", path));
858251881Speter
859251881Speter  SVN_ERR(dump_pending(pb->eb, pool));
860251881Speter
861251881Speter  /* Make the file baton. */
862251881Speter  fb = make_file_baton(path, pb, pool);
863251881Speter
864251881Speter  /* If the parent directory has explicit copyfrom path and rev,
865251881Speter     record the same for this one. */
866251881Speter  if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
867251881Speter    {
868251881Speter      fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
869251881Speter                                           svn_relpath_basename(path, NULL),
870251881Speter                                           pb->eb->pool);
871251881Speter      fb->copyfrom_rev = pb->copyfrom_rev;
872251881Speter    }
873251881Speter
874251881Speter  *file_baton = fb;
875251881Speter  return SVN_NO_ERROR;
876251881Speter}
877251881Speter
878251881Speterstatic svn_error_t *
879251881Speterchange_dir_prop(void *parent_baton,
880251881Speter                const char *name,
881251881Speter                const svn_string_t *value,
882251881Speter                apr_pool_t *pool)
883251881Speter{
884251881Speter  struct dir_baton *db = parent_baton;
885251881Speter  svn_boolean_t this_pending;
886251881Speter
887251881Speter  LDR_DBG(("change_dir_prop %p\n", parent_baton));
888251881Speter
889251881Speter  /* This directory is not pending, but something else is, so handle
890251881Speter     the "something else".  */
891251881Speter  this_pending = (db->eb->pending_baton == db);
892251881Speter  if (! this_pending)
893251881Speter    SVN_ERR(dump_pending(db->eb, pool));
894251881Speter
895251881Speter  if (svn_property_kind2(name) != svn_prop_regular_kind)
896251881Speter    return SVN_NO_ERROR;
897251881Speter
898251881Speter  if (value)
899251881Speter    svn_hash_sets(db->props,
900251881Speter                  apr_pstrdup(db->pool, name),
901251881Speter                  svn_string_dup(value, db->pool));
902251881Speter  else
903251881Speter    svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
904251881Speter
905251881Speter  /* Make sure we eventually output the props, and disable printing
906251881Speter     a couple of extra newlines */
907251881Speter  db->dump_newlines = FALSE;
908251881Speter  db->dump_props = TRUE;
909251881Speter
910251881Speter  return SVN_NO_ERROR;
911251881Speter}
912251881Speter
913251881Speterstatic svn_error_t *
914251881Speterchange_file_prop(void *file_baton,
915251881Speter                 const char *name,
916251881Speter                 const svn_string_t *value,
917251881Speter                 apr_pool_t *pool)
918251881Speter{
919251881Speter  struct file_baton *fb = file_baton;
920251881Speter
921251881Speter  LDR_DBG(("change_file_prop %p\n", file_baton));
922251881Speter
923251881Speter  if (svn_property_kind2(name) != svn_prop_regular_kind)
924251881Speter    return SVN_NO_ERROR;
925251881Speter
926251881Speter  if (value)
927251881Speter    svn_hash_sets(fb->props,
928251881Speter                  apr_pstrdup(fb->pool, name),
929251881Speter                  svn_string_dup(value, fb->pool));
930251881Speter  else
931251881Speter    svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
932251881Speter
933251881Speter  /* Dump the property headers and wait; close_file might need
934251881Speter     to write text headers too depending on whether
935251881Speter     apply_textdelta is called */
936251881Speter  fb->dump_props = TRUE;
937251881Speter
938251881Speter  return SVN_NO_ERROR;
939251881Speter}
940251881Speter
941251881Speterstatic svn_error_t *
942251881Speterwindow_handler(svn_txdelta_window_t *window, void *baton)
943251881Speter{
944251881Speter  struct handler_baton *hb = baton;
945251881Speter  static svn_error_t *err;
946251881Speter
947251881Speter  err = hb->apply_handler(window, hb->apply_baton);
948251881Speter  if (window != NULL && !err)
949251881Speter    return SVN_NO_ERROR;
950251881Speter
951251881Speter  if (err)
952251881Speter    SVN_ERR(err);
953251881Speter
954251881Speter  return SVN_NO_ERROR;
955251881Speter}
956251881Speter
957251881Speterstatic svn_error_t *
958251881Speterapply_textdelta(void *file_baton, const char *base_checksum,
959251881Speter                apr_pool_t *pool,
960251881Speter                svn_txdelta_window_handler_t *handler,
961251881Speter                void **handler_baton)
962251881Speter{
963251881Speter  struct file_baton *fb = file_baton;
964251881Speter  struct dump_edit_baton *eb = fb->eb;
965251881Speter  struct handler_baton *hb;
966251881Speter  svn_stream_t *delta_filestream;
967251881Speter
968251881Speter  LDR_DBG(("apply_textdelta %p\n", file_baton));
969251881Speter
970251881Speter  /* This is custom handler_baton, allocated from a separate pool.  */
971251881Speter  hb = apr_pcalloc(eb->pool, sizeof(*hb));
972251881Speter
973251881Speter  /* Use a temporary file to measure the Text-content-length */
974251881Speter  delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
975251881Speter
976251881Speter  /* Prepare to write the delta to the delta_filestream */
977251881Speter  svn_txdelta_to_svndiff3(&(hb->apply_handler), &(hb->apply_baton),
978251881Speter                          delta_filestream, 0,
979251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
980251881Speter
981251881Speter  /* Record that there's text to be dumped, and its base checksum. */
982251881Speter  fb->dump_text = TRUE;
983251881Speter  fb->base_checksum = apr_pstrdup(eb->pool, base_checksum);
984251881Speter
985251881Speter  /* The actual writing takes place when this function has
986251881Speter     finished. Set handler and handler_baton now so for
987251881Speter     window_handler() */
988251881Speter  *handler = window_handler;
989251881Speter  *handler_baton = hb;
990251881Speter
991251881Speter  return SVN_NO_ERROR;
992251881Speter}
993251881Speter
994251881Speterstatic svn_error_t *
995251881Speterclose_file(void *file_baton,
996251881Speter           const char *text_checksum,
997251881Speter           apr_pool_t *pool)
998251881Speter{
999251881Speter  struct file_baton *fb = file_baton;
1000251881Speter  struct dump_edit_baton *eb = fb->eb;
1001251881Speter  apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t));
1002251881Speter  svn_stringbuf_t *propstring;
1003251881Speter
1004251881Speter  LDR_DBG(("close_file %p\n", file_baton));
1005251881Speter
1006251881Speter  SVN_ERR(dump_pending(eb, pool));
1007251881Speter
1008251881Speter  /* Dump the node. */
1009251881Speter  SVN_ERR(dump_node(eb, fb->repos_relpath, NULL, fb,
1010251881Speter                    fb->action, fb->is_copy, fb->copyfrom_path,
1011251881Speter                    fb->copyfrom_rev, pool));
1012251881Speter
1013251881Speter  /* Some pending properties to dump?  We'll dump just the headers for
1014251881Speter     now, then dump the actual propchange content only after dumping
1015251881Speter     the text headers too (if present). */
1016251881Speter  SVN_ERR(do_dump_props(&propstring, eb->stream, fb->props, fb->deleted_props,
1017251881Speter                        &(fb->dump_props), pool, pool));
1018251881Speter
1019251881Speter  /* Dump the text headers */
1020251881Speter  if (fb->dump_text)
1021251881Speter    {
1022251881Speter      apr_status_t err;
1023251881Speter
1024251881Speter      /* Text-delta: true */
1025251881Speter      SVN_ERR(svn_stream_puts(eb->stream,
1026251881Speter                              SVN_REPOS_DUMPFILE_TEXT_DELTA
1027251881Speter                              ": true\n"));
1028251881Speter
1029251881Speter      err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file);
1030251881Speter      if (err)
1031251881Speter        SVN_ERR(svn_error_wrap_apr(err, NULL));
1032251881Speter
1033251881Speter      if (fb->base_checksum)
1034251881Speter        /* Text-delta-base-md5: */
1035251881Speter        SVN_ERR(svn_stream_printf(eb->stream, pool,
1036251881Speter                                  SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
1037251881Speter                                  ": %s\n",
1038251881Speter                                  fb->base_checksum));
1039251881Speter
1040251881Speter      /* Text-content-length: 39 */
1041251881Speter      SVN_ERR(svn_stream_printf(eb->stream, pool,
1042251881Speter                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
1043251881Speter                                ": %lu\n",
1044251881Speter                                (unsigned long)info->size));
1045251881Speter
1046251881Speter      /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
1047251881Speter      SVN_ERR(svn_stream_printf(eb->stream, pool,
1048251881Speter                                SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
1049251881Speter                                ": %s\n",
1050251881Speter                                text_checksum));
1051251881Speter    }
1052251881Speter
1053251881Speter  /* Content-length: 1549 */
1054251881Speter  /* If both text and props are absent, skip this header */
1055251881Speter  if (fb->dump_props)
1056251881Speter    SVN_ERR(svn_stream_printf(eb->stream, pool,
1057251881Speter                              SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1058251881Speter                              ": %ld\n\n",
1059251881Speter                              (unsigned long)info->size + propstring->len));
1060251881Speter  else if (fb->dump_text)
1061251881Speter    SVN_ERR(svn_stream_printf(eb->stream, pool,
1062251881Speter                              SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1063251881Speter                              ": %ld\n\n",
1064251881Speter                              (unsigned long)info->size));
1065251881Speter
1066251881Speter  /* Dump the props now */
1067251881Speter  if (fb->dump_props)
1068251881Speter    {
1069251881Speter      SVN_ERR(svn_stream_write(eb->stream, propstring->data,
1070251881Speter                               &(propstring->len)));
1071251881Speter
1072251881Speter      /* Cleanup */
1073251881Speter      fb->dump_props = FALSE;
1074251881Speter      apr_hash_clear(fb->props);
1075251881Speter      apr_hash_clear(fb->deleted_props);
1076251881Speter    }
1077251881Speter
1078251881Speter  /* Dump the text */
1079251881Speter  if (fb->dump_text)
1080251881Speter    {
1081251881Speter      /* Seek to the beginning of the delta file, map it to a stream,
1082251881Speter         and copy the stream to eb->stream. Then close the stream and
1083251881Speter         truncate the file so we can reuse it for the next textdelta
1084251881Speter         application. Note that the file isn't created, opened or
1085251881Speter         closed here */
1086251881Speter      svn_stream_t *delta_filestream;
1087251881Speter      apr_off_t offset = 0;
1088251881Speter
1089251881Speter      SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
1090251881Speter      delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
1091251881Speter      SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool));
1092251881Speter
1093251881Speter      /* Cleanup */
1094251881Speter      SVN_ERR(svn_stream_close(delta_filestream));
1095251881Speter      SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
1096251881Speter    }
1097251881Speter
1098251881Speter  /* Write a couple of blank lines for matching output with `svnadmin
1099251881Speter     dump` */
1100251881Speter  SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
1101251881Speter
1102251881Speter  return SVN_NO_ERROR;
1103251881Speter}
1104251881Speter
1105251881Speterstatic svn_error_t *
1106251881Speterclose_edit(void *edit_baton, apr_pool_t *pool)
1107251881Speter{
1108251881Speter  return SVN_NO_ERROR;
1109251881Speter}
1110251881Speter
1111251881Speterstatic svn_error_t *
1112251881Speterfetch_base_func(const char **filename,
1113251881Speter                void *baton,
1114251881Speter                const char *path,
1115251881Speter                svn_revnum_t base_revision,
1116251881Speter                apr_pool_t *result_pool,
1117251881Speter                apr_pool_t *scratch_pool)
1118251881Speter{
1119251881Speter  struct dump_edit_baton *eb = baton;
1120251881Speter  svn_stream_t *fstream;
1121251881Speter  svn_error_t *err;
1122251881Speter
1123251881Speter  if (path[0] == '/')
1124251881Speter    path += 1;
1125251881Speter
1126251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
1127251881Speter    base_revision = eb->current_revision - 1;
1128251881Speter
1129251881Speter  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1130251881Speter                                 svn_io_file_del_on_pool_cleanup,
1131251881Speter                                 result_pool, scratch_pool));
1132251881Speter
1133251881Speter  err = svn_ra_get_file(eb->ra_session, path, base_revision,
1134251881Speter                        fstream, NULL, NULL, scratch_pool);
1135251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1136251881Speter    {
1137251881Speter      svn_error_clear(err);
1138251881Speter      SVN_ERR(svn_stream_close(fstream));
1139251881Speter
1140251881Speter      *filename = NULL;
1141251881Speter      return SVN_NO_ERROR;
1142251881Speter    }
1143251881Speter  else if (err)
1144251881Speter    return svn_error_trace(err);
1145251881Speter
1146251881Speter  SVN_ERR(svn_stream_close(fstream));
1147251881Speter
1148251881Speter  return SVN_NO_ERROR;
1149251881Speter}
1150251881Speter
1151251881Speterstatic svn_error_t *
1152251881Speterfetch_props_func(apr_hash_t **props,
1153251881Speter                 void *baton,
1154251881Speter                 const char *path,
1155251881Speter                 svn_revnum_t base_revision,
1156251881Speter                 apr_pool_t *result_pool,
1157251881Speter                 apr_pool_t *scratch_pool)
1158251881Speter{
1159251881Speter  struct dump_edit_baton *eb = baton;
1160251881Speter  svn_node_kind_t node_kind;
1161251881Speter
1162251881Speter  if (path[0] == '/')
1163251881Speter    path += 1;
1164251881Speter
1165251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
1166251881Speter    base_revision = eb->current_revision - 1;
1167251881Speter
1168251881Speter  SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1169251881Speter                            scratch_pool));
1170251881Speter
1171251881Speter  if (node_kind == svn_node_file)
1172251881Speter    {
1173251881Speter      SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1174251881Speter                              NULL, NULL, props, result_pool));
1175251881Speter    }
1176251881Speter  else if (node_kind == svn_node_dir)
1177251881Speter    {
1178251881Speter      apr_array_header_t *tmp_props;
1179251881Speter
1180251881Speter      SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1181251881Speter                              base_revision, 0 /* Dirent fields */,
1182251881Speter                              result_pool));
1183251881Speter      tmp_props = svn_prop_hash_to_array(*props, result_pool);
1184251881Speter      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1185251881Speter                                   result_pool));
1186251881Speter      *props = svn_prop_array_to_hash(tmp_props, result_pool);
1187251881Speter    }
1188251881Speter  else
1189251881Speter    {
1190251881Speter      *props = apr_hash_make(result_pool);
1191251881Speter    }
1192251881Speter
1193251881Speter  return SVN_NO_ERROR;
1194251881Speter}
1195251881Speter
1196251881Speterstatic svn_error_t *
1197251881Speterfetch_kind_func(svn_node_kind_t *kind,
1198251881Speter                void *baton,
1199251881Speter                const char *path,
1200251881Speter                svn_revnum_t base_revision,
1201251881Speter                apr_pool_t *scratch_pool)
1202251881Speter{
1203251881Speter  struct dump_edit_baton *eb = baton;
1204251881Speter
1205251881Speter  if (path[0] == '/')
1206251881Speter    path += 1;
1207251881Speter
1208251881Speter  if (! SVN_IS_VALID_REVNUM(base_revision))
1209251881Speter    base_revision = eb->current_revision - 1;
1210251881Speter
1211251881Speter  SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1212251881Speter                            scratch_pool));
1213251881Speter
1214251881Speter  return SVN_NO_ERROR;
1215251881Speter}
1216251881Speter
1217251881Spetersvn_error_t *
1218251881Spetersvn_rdump__get_dump_editor(const svn_delta_editor_t **editor,
1219251881Speter                           void **edit_baton,
1220251881Speter                           svn_revnum_t revision,
1221251881Speter                           svn_stream_t *stream,
1222251881Speter                           svn_ra_session_t *ra_session,
1223251881Speter                           const char *update_anchor_relpath,
1224251881Speter                           svn_cancel_func_t cancel_func,
1225251881Speter                           void *cancel_baton,
1226251881Speter                           apr_pool_t *pool)
1227251881Speter{
1228251881Speter  struct dump_edit_baton *eb;
1229251881Speter  svn_delta_editor_t *de;
1230251881Speter  svn_delta_shim_callbacks_t *shim_callbacks =
1231251881Speter                                        svn_delta_shim_callbacks_default(pool);
1232251881Speter
1233251881Speter  eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
1234251881Speter  eb->stream = stream;
1235251881Speter  eb->ra_session = ra_session;
1236251881Speter  eb->update_anchor_relpath = update_anchor_relpath;
1237251881Speter  eb->current_revision = revision;
1238251881Speter  eb->pending_kind = svn_node_none;
1239251881Speter
1240251881Speter  /* Create a special per-revision pool */
1241251881Speter  eb->pool = svn_pool_create(pool);
1242251881Speter
1243251881Speter  /* Open a unique temporary file for all textdelta applications in
1244251881Speter     this edit session. The file is automatically closed and cleaned
1245251881Speter     up when the edit session is done. */
1246251881Speter  SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
1247251881Speter                                   NULL, svn_io_file_del_on_close, pool, pool));
1248251881Speter
1249251881Speter  de = svn_delta_default_editor(pool);
1250251881Speter  de->open_root = open_root;
1251251881Speter  de->delete_entry = delete_entry;
1252251881Speter  de->add_directory = add_directory;
1253251881Speter  de->open_directory = open_directory;
1254251881Speter  de->close_directory = close_directory;
1255251881Speter  de->change_dir_prop = change_dir_prop;
1256251881Speter  de->change_file_prop = change_file_prop;
1257251881Speter  de->apply_textdelta = apply_textdelta;
1258251881Speter  de->add_file = add_file;
1259251881Speter  de->open_file = open_file;
1260251881Speter  de->close_file = close_file;
1261251881Speter  de->close_edit = close_edit;
1262251881Speter
1263251881Speter  /* Set the edit_baton and editor. */
1264251881Speter  *edit_baton = eb;
1265251881Speter  *editor = de;
1266251881Speter
1267251881Speter  /* Wrap this editor in a cancellation editor. */
1268251881Speter  SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1269251881Speter                                            de, eb, editor, edit_baton, pool));
1270251881Speter
1271251881Speter  shim_callbacks->fetch_base_func = fetch_base_func;
1272251881Speter  shim_callbacks->fetch_props_func = fetch_props_func;
1273251881Speter  shim_callbacks->fetch_kind_func = fetch_kind_func;
1274251881Speter  shim_callbacks->fetch_baton = eb;
1275251881Speter
1276251881Speter  SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1277251881Speter                                   NULL, NULL, shim_callbacks, pool, pool));
1278251881Speter
1279251881Speter  return SVN_NO_ERROR;
1280251881Speter}
1281