vcs-svn / svndump.con commit vcs-svn: use higher mark numbers for blobs (d38f844)
   1/*
   2 * Parse and rearrange a svnadmin dump.
   3 * Create the dump with:
   4 * svnadmin dump --incremental -r<startrev>:<endrev> <repository> >outfile
   5 *
   6 * Licensed under a two-clause BSD-style license.
   7 * See LICENSE for details.
   8 */
   9
  10#include "cache.h"
  11#include "repo_tree.h"
  12#include "fast_export.h"
  13#include "line_buffer.h"
  14#include "obj_pool.h"
  15#include "string_pool.h"
  16
  17#define REPORT_FILENO 3
  18
  19#define NODEACT_REPLACE 4
  20#define NODEACT_DELETE 3
  21#define NODEACT_ADD 2
  22#define NODEACT_CHANGE 1
  23#define NODEACT_UNKNOWN 0
  24
  25#define DUMP_CTX 0
  26#define REV_CTX  1
  27#define NODE_CTX 2
  28
  29#define LENGTH_UNKNOWN (~0)
  30#define DATE_RFC2822_LEN 31
  31
  32/* Create memory pool for log messages */
  33obj_pool_gen(log, char, 4096)
  34
  35static struct line_buffer input = LINE_BUFFER_INIT;
  36
  37static char *log_copy(uint32_t length, const char *log)
  38{
  39        char *buffer;
  40        log_free(log_pool.size);
  41        buffer = log_pointer(log_alloc(length));
  42        strncpy(buffer, log, length);
  43        return buffer;
  44}
  45
  46static struct {
  47        uint32_t action, propLength, textLength, srcRev, type;
  48        uint32_t src[REPO_MAX_PATH_DEPTH], dst[REPO_MAX_PATH_DEPTH];
  49        uint32_t text_delta, prop_delta;
  50} node_ctx;
  51
  52static struct {
  53        uint32_t revision, author;
  54        unsigned long timestamp;
  55        char *log;
  56} rev_ctx;
  57
  58static struct {
  59        uint32_t version, uuid, url;
  60} dump_ctx;
  61
  62static struct {
  63        uint32_t svn_log, svn_author, svn_date, svn_executable, svn_special, uuid,
  64                revision_number, node_path, node_kind, node_action,
  65                node_copyfrom_path, node_copyfrom_rev, text_content_length,
  66                prop_content_length, content_length, svn_fs_dump_format_version,
  67                /* version 3 format */
  68                text_delta, prop_delta;
  69} keys;
  70
  71static void reset_node_ctx(char *fname)
  72{
  73        node_ctx.type = 0;
  74        node_ctx.action = NODEACT_UNKNOWN;
  75        node_ctx.propLength = LENGTH_UNKNOWN;
  76        node_ctx.textLength = LENGTH_UNKNOWN;
  77        node_ctx.src[0] = ~0;
  78        node_ctx.srcRev = 0;
  79        pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.dst, "/", fname);
  80        node_ctx.text_delta = 0;
  81        node_ctx.prop_delta = 0;
  82}
  83
  84static void reset_rev_ctx(uint32_t revision)
  85{
  86        rev_ctx.revision = revision;
  87        rev_ctx.timestamp = 0;
  88        rev_ctx.log = NULL;
  89        rev_ctx.author = ~0;
  90}
  91
  92static void reset_dump_ctx(uint32_t url)
  93{
  94        dump_ctx.url = url;
  95        dump_ctx.version = 1;
  96        dump_ctx.uuid = ~0;
  97}
  98
  99static void init_keys(void)
 100{
 101        keys.svn_log = pool_intern("svn:log");
 102        keys.svn_author = pool_intern("svn:author");
 103        keys.svn_date = pool_intern("svn:date");
 104        keys.svn_executable = pool_intern("svn:executable");
 105        keys.svn_special = pool_intern("svn:special");
 106        keys.uuid = pool_intern("UUID");
 107        keys.revision_number = pool_intern("Revision-number");
 108        keys.node_path = pool_intern("Node-path");
 109        keys.node_kind = pool_intern("Node-kind");
 110        keys.node_action = pool_intern("Node-action");
 111        keys.node_copyfrom_path = pool_intern("Node-copyfrom-path");
 112        keys.node_copyfrom_rev = pool_intern("Node-copyfrom-rev");
 113        keys.text_content_length = pool_intern("Text-content-length");
 114        keys.prop_content_length = pool_intern("Prop-content-length");
 115        keys.content_length = pool_intern("Content-length");
 116        keys.svn_fs_dump_format_version = pool_intern("SVN-fs-dump-format-version");
 117        /* version 3 format (Subversion 1.1.0) */
 118        keys.text_delta = pool_intern("Text-delta");
 119        keys.prop_delta = pool_intern("Prop-delta");
 120}
 121
 122static void handle_property(uint32_t key, const char *val, uint32_t len,
 123                                uint32_t *type_set)
 124{
 125        if (key == keys.svn_log) {
 126                if (!val)
 127                        die("invalid dump: unsets svn:log");
 128                /* Value length excludes terminating nul. */
 129                rev_ctx.log = log_copy(len + 1, val);
 130        } else if (key == keys.svn_author) {
 131                rev_ctx.author = pool_intern(val);
 132        } else if (key == keys.svn_date) {
 133                if (!val)
 134                        die("invalid dump: unsets svn:date");
 135                if (parse_date_basic(val, &rev_ctx.timestamp, NULL))
 136                        warning("invalid timestamp: %s", val);
 137        } else if (key == keys.svn_executable || key == keys.svn_special) {
 138                if (*type_set) {
 139                        if (!val)
 140                                return;
 141                        die("invalid dump: sets type twice");
 142                }
 143                if (!val) {
 144                        node_ctx.type = REPO_MODE_BLB;
 145                        return;
 146                }
 147                *type_set = 1;
 148                node_ctx.type = key == keys.svn_executable ?
 149                                REPO_MODE_EXE :
 150                                REPO_MODE_LNK;
 151        }
 152}
 153
 154static void read_props(void)
 155{
 156        uint32_t key = ~0;
 157        const char *t;
 158        /*
 159         * NEEDSWORK: to support simple mode changes like
 160         *      K 11
 161         *      svn:special
 162         *      V 1
 163         *      *
 164         *      D 14
 165         *      svn:executable
 166         * we keep track of whether a mode has been set and reset to
 167         * plain file only if not.  We should be keeping track of the
 168         * symlink and executable bits separately instead.
 169         */
 170        uint32_t type_set = 0;
 171        while ((t = buffer_read_line(&input)) && strcmp(t, "PROPS-END")) {
 172                uint32_t len;
 173                const char *val;
 174                const char type = t[0];
 175
 176                if (!type || t[1] != ' ')
 177                        die("invalid property line: %s\n", t);
 178                len = atoi(&t[2]);
 179                val = buffer_read_string(&input, len);
 180                buffer_skip_bytes(&input, 1);   /* Discard trailing newline. */
 181
 182                switch (type) {
 183                case 'K':
 184                        key = pool_intern(val);
 185                        continue;
 186                case 'D':
 187                        key = pool_intern(val);
 188                        val = NULL;
 189                        len = 0;
 190                        /* fall through */
 191                case 'V':
 192                        handle_property(key, val, len, &type_set);
 193                        key = ~0;
 194                        continue;
 195                default:
 196                        die("invalid property line: %s\n", t);
 197                }
 198        }
 199}
 200
 201static void handle_node(void)
 202{
 203        uint32_t mark = 0;
 204        const uint32_t type = node_ctx.type;
 205        const int have_props = node_ctx.propLength != LENGTH_UNKNOWN;
 206        const int have_text = node_ctx.textLength != LENGTH_UNKNOWN;
 207
 208        if (node_ctx.text_delta)
 209                die("text deltas not supported");
 210        if (have_text)
 211                mark = next_blob_mark();
 212        if (node_ctx.action == NODEACT_DELETE) {
 213                if (have_text || have_props || node_ctx.srcRev)
 214                        die("invalid dump: deletion node has "
 215                                "copyfrom info, text, or properties");
 216                return repo_delete(node_ctx.dst);
 217        }
 218        if (node_ctx.action == NODEACT_REPLACE) {
 219                repo_delete(node_ctx.dst);
 220                node_ctx.action = NODEACT_ADD;
 221        }
 222        if (node_ctx.srcRev) {
 223                repo_copy(node_ctx.srcRev, node_ctx.src, node_ctx.dst);
 224                if (node_ctx.action == NODEACT_ADD)
 225                        node_ctx.action = NODEACT_CHANGE;
 226        }
 227        if (have_text && type == REPO_MODE_DIR)
 228                die("invalid dump: directories cannot have text attached");
 229
 230        /*
 231         * Decide on the new content (mark) and mode (node_ctx.type).
 232         */
 233        if (node_ctx.action == NODEACT_CHANGE && !~*node_ctx.dst) {
 234                if (type != REPO_MODE_DIR)
 235                        die("invalid dump: root of tree is not a regular file");
 236        } else if (node_ctx.action == NODEACT_CHANGE) {
 237                uint32_t mode;
 238                if (!have_text)
 239                        mark = repo_read_path(node_ctx.dst);
 240                mode = repo_read_mode(node_ctx.dst);
 241                if (mode == REPO_MODE_DIR && type != REPO_MODE_DIR)
 242                        die("invalid dump: cannot modify a directory into a file");
 243                if (mode != REPO_MODE_DIR && type == REPO_MODE_DIR)
 244                        die("invalid dump: cannot modify a file into a directory");
 245                node_ctx.type = mode;
 246        } else if (node_ctx.action == NODEACT_ADD) {
 247                if (!have_text && type != REPO_MODE_DIR)
 248                        die("invalid dump: adds node without text");
 249        } else {
 250                die("invalid dump: Node-path block lacks Node-action");
 251        }
 252
 253        /*
 254         * Adjust mode to reflect properties.
 255         */
 256        if (have_props) {
 257                if (!node_ctx.prop_delta)
 258                        node_ctx.type = type;
 259                if (node_ctx.propLength)
 260                        read_props();
 261        }
 262
 263        /*
 264         * Save the result.
 265         */
 266        repo_add(node_ctx.dst, node_ctx.type, mark);
 267        if (have_text)
 268                fast_export_blob(node_ctx.type, mark,
 269                                 node_ctx.textLength, &input);
 270}
 271
 272static void handle_revision(void)
 273{
 274        if (rev_ctx.revision)
 275                repo_commit(rev_ctx.revision, rev_ctx.author, rev_ctx.log,
 276                        dump_ctx.uuid, dump_ctx.url, rev_ctx.timestamp);
 277}
 278
 279void svndump_read(const char *url)
 280{
 281        char *val;
 282        char *t;
 283        uint32_t active_ctx = DUMP_CTX;
 284        uint32_t len;
 285        uint32_t key;
 286
 287        reset_dump_ctx(pool_intern(url));
 288        while ((t = buffer_read_line(&input))) {
 289                val = strstr(t, ": ");
 290                if (!val)
 291                        continue;
 292                *val++ = '\0';
 293                *val++ = '\0';
 294                key = pool_intern(t);
 295
 296                if (key == keys.svn_fs_dump_format_version) {
 297                        dump_ctx.version = atoi(val);
 298                        if (dump_ctx.version > 3)
 299                                die("expected svn dump format version <= 3, found %"PRIu32,
 300                                    dump_ctx.version);
 301                } else if (key == keys.uuid) {
 302                        dump_ctx.uuid = pool_intern(val);
 303                } else if (key == keys.revision_number) {
 304                        if (active_ctx == NODE_CTX)
 305                                handle_node();
 306                        if (active_ctx != DUMP_CTX)
 307                                handle_revision();
 308                        active_ctx = REV_CTX;
 309                        reset_rev_ctx(atoi(val));
 310                } else if (key == keys.node_path) {
 311                        if (active_ctx == NODE_CTX)
 312                                handle_node();
 313                        active_ctx = NODE_CTX;
 314                        reset_node_ctx(val);
 315                } else if (key == keys.node_kind) {
 316                        if (!strcmp(val, "dir"))
 317                                node_ctx.type = REPO_MODE_DIR;
 318                        else if (!strcmp(val, "file"))
 319                                node_ctx.type = REPO_MODE_BLB;
 320                        else
 321                                fprintf(stderr, "Unknown node-kind: %s\n", val);
 322                } else if (key == keys.node_action) {
 323                        if (!strcmp(val, "delete")) {
 324                                node_ctx.action = NODEACT_DELETE;
 325                        } else if (!strcmp(val, "add")) {
 326                                node_ctx.action = NODEACT_ADD;
 327                        } else if (!strcmp(val, "change")) {
 328                                node_ctx.action = NODEACT_CHANGE;
 329                        } else if (!strcmp(val, "replace")) {
 330                                node_ctx.action = NODEACT_REPLACE;
 331                        } else {
 332                                fprintf(stderr, "Unknown node-action: %s\n", val);
 333                                node_ctx.action = NODEACT_UNKNOWN;
 334                        }
 335                } else if (key == keys.node_copyfrom_path) {
 336                        pool_tok_seq(REPO_MAX_PATH_DEPTH, node_ctx.src, "/", val);
 337                } else if (key == keys.node_copyfrom_rev) {
 338                        node_ctx.srcRev = atoi(val);
 339                } else if (key == keys.text_content_length) {
 340                        node_ctx.textLength = atoi(val);
 341                } else if (key == keys.prop_content_length) {
 342                        node_ctx.propLength = atoi(val);
 343                } else if (key == keys.text_delta) {
 344                        node_ctx.text_delta = !strcmp(val, "true");
 345                } else if (key == keys.prop_delta) {
 346                        node_ctx.prop_delta = !strcmp(val, "true");
 347                } else if (key == keys.content_length) {
 348                        len = atoi(val);
 349                        buffer_read_line(&input);
 350                        if (active_ctx == REV_CTX) {
 351                                read_props();
 352                        } else if (active_ctx == NODE_CTX) {
 353                                handle_node();
 354                                active_ctx = REV_CTX;
 355                        } else {
 356                                fprintf(stderr, "Unexpected content length header: %"PRIu32"\n", len);
 357                                buffer_skip_bytes(&input, len);
 358                        }
 359                }
 360        }
 361        if (active_ctx == NODE_CTX)
 362                handle_node();
 363        if (active_ctx != DUMP_CTX)
 364                handle_revision();
 365}
 366
 367int svndump_init(const char *filename)
 368{
 369        if (buffer_init(&input, filename))
 370                return error("cannot open %s: %s", filename, strerror(errno));
 371        repo_init();
 372        fast_export_init(REPORT_FILENO);
 373        reset_dump_ctx(~0);
 374        reset_rev_ctx(0);
 375        reset_node_ctx(NULL);
 376        init_keys();
 377        return 0;
 378}
 379
 380void svndump_deinit(void)
 381{
 382        log_reset();
 383        fast_export_deinit();
 384        repo_reset();
 385        reset_dump_ctx(~0);
 386        reset_rev_ctx(0);
 387        reset_node_ctx(NULL);
 388        if (buffer_deinit(&input))
 389                fprintf(stderr, "Input error\n");
 390        if (ferror(stdout))
 391                fprintf(stderr, "Output error\n");
 392}
 393
 394void svndump_reset(void)
 395{
 396        log_reset();
 397        fast_export_reset();
 398        buffer_reset(&input);
 399        repo_reset();
 400        reset_dump_ctx(~0);
 401        reset_rev_ctx(0);
 402        reset_node_ctx(NULL);
 403}