static svn_error_t * test_one_long_keyword(const char *keyword, const char *expected, apr_pool_t *pool) { svn_string_t *src_string; svn_stream_t *src_stream, *dst_stream; svn_stringbuf_t *dst_stringbuf, *src_stringbuf; apr_hash_t *keywords = apr_hash_make(pool); svn_string_t *expanded = svn_string_create("abcdefg", pool); svn_hash_sets(keywords, keyword, expanded); /* Expand */ src_string = svn_string_createf(pool, "$%s$", keyword); src_stream = svn_stream_from_string(src_string, pool); dst_stringbuf = svn_stringbuf_create_empty(pool); dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool); dst_stream = svn_subst_stream_translated(dst_stream, NULL, FALSE, keywords, TRUE, pool); SVN_ERR(svn_stream_copy3(src_stream, dst_stream, NULL, NULL, pool)); SVN_TEST_STRING_ASSERT(dst_stringbuf->data, expected); /* Unexpand */ src_stringbuf = dst_stringbuf; src_stream = svn_stream_from_stringbuf(src_stringbuf, pool); dst_stringbuf = svn_stringbuf_create_empty(pool); dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool); dst_stream = svn_subst_stream_translated(dst_stream, NULL, FALSE, keywords, FALSE, pool); SVN_ERR(svn_stream_copy3(src_stream, dst_stream, NULL, NULL, pool)); SVN_TEST_STRING_ASSERT(dst_stringbuf->data, src_string->data); return SVN_NO_ERROR; }
svn_error_t * svn_subst_stream_translated_to_normal_form(svn_stream_t **stream, svn_stream_t *source, svn_subst_eol_style_t eol_style, const char *eol_str, svn_boolean_t always_repair_eols, apr_hash_t *keywords, apr_pool_t *pool) { if (eol_style == svn_subst_eol_style_native) eol_str = SVN_SUBST_NATIVE_EOL_STR; else if (! (eol_style == svn_subst_eol_style_fixed || eol_style == svn_subst_eol_style_none)) return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); *stream = svn_subst_stream_translated(source, eol_str, eol_style == svn_subst_eol_style_fixed || always_repair_eols, keywords, FALSE, pool); return SVN_NO_ERROR; }
static svn_error_t * test_svn_subst_truncated_keywords(apr_pool_t *pool) { svn_string_t *src_string = svn_string_create("$Qq: " "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "012345678901234567890123456789012345678901234567" " $", pool); svn_stream_t *src_stream = svn_stream_from_string(src_string, pool); svn_stringbuf_t *dst_stringbuf = svn_stringbuf_create_empty(pool); svn_stream_t *dst_stream = svn_stream_from_stringbuf(dst_stringbuf, pool); apr_hash_t *keywords = apr_hash_make(pool); svn_string_t *expanded = svn_string_create("01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "01234567890123456789012345678901234567890123456789" "012345678901234567890123456789012345678901234567" "xxxxxxxxxx", pool); /* The source is already at the maximum length. */ SVN_TEST_ASSERT(src_string->len == SVN_KEYWORD_MAX_LEN); svn_hash_sets(keywords, "Qq", expanded); dst_stream = svn_subst_stream_translated(dst_stream, NULL, FALSE, keywords, TRUE, pool); SVN_ERR(svn_stream_copy3(src_stream, dst_stream, NULL, NULL, pool)); /* The expanded value would make the keyword longer than the maximum allowed so it must be truncated; the remaining part of the expanded value is the same as the source. */ SVN_TEST_STRING_ASSERT(dst_stringbuf->data, src_string->data); return SVN_NO_ERROR; }
svn_error_t * svn_subst_translate_stream3(svn_stream_t *src_stream, svn_stream_t *dst_stream, const char *eol_str, svn_boolean_t repair, apr_hash_t *keywords, svn_boolean_t expand, apr_pool_t *pool) { /* The docstring requires that *some* translation be requested. */ SVN_ERR_ASSERT(eol_str || keywords); /* We don't want the copy3 to close the provided streams. */ src_stream = svn_stream_disown(src_stream, pool); dst_stream = svn_stream_disown(dst_stream, pool); /* Wrap the destination stream with our translation stream. It is more efficient than wrapping the source stream. */ dst_stream = svn_subst_stream_translated(dst_stream, eol_str, repair, keywords, expand, pool); return svn_error_trace(svn_stream_copy3(src_stream, dst_stream, NULL, NULL, pool)); }
/* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it * to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * Expand the file's keywords according to the source file's 'svn:keywords' * property, if present. If copying a locally modified working version, * append 'M' to the revision number and use '(local)' for the author. * * Translate the file's line endings according to the source file's * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it * in place of the native EOL style. Throw an error if the source file has * inconsistent line endings and EOL translation is attempted. * * Set the destination file's modification time to the source file's * modification time if copying the working version and the working version * is locally modified; otherwise set it to the versioned file's last * changed time. * * Set the destination file's 'executable' flag according to the source * file's 'svn:executable' property. */ static svn_error_t * copy_one_versioned_file(const char *from_abspath, const char *to_abspath, svn_client_ctx_t *ctx, const svn_opt_revision_t *revision, const char *native_eol, svn_boolean_t ignore_keywords, apr_pool_t *scratch_pool) { apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *executable, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *source; svn_stream_t *dst_stream; const char *dst_tmp; svn_error_t *err; svn_boolean_t is_deleted; svn_wc_context_t *wc_ctx = ctx->wc_ctx; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, from_abspath, scratch_pool)); /* Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind == svn_opt_revision_working && is_deleted) return SVN_NO_ERROR; if (revision->kind != svn_opt_revision_working) { /* Only export 'added' files when the revision is WORKING. This is not WORKING, so skip the 'added' files, since they didn't exist in the BASE revision and don't have an associated text-base. 'replaced' files are technically the same as 'added' files. ### TODO: Handle replaced nodes properly. ### svn_opt_revision_base refers to the "new" ### base of the node. That means, if a node is locally ### replaced, export skips this node, as if it was locally ### added, because svn_opt_revision_base refers to the base ### of the added node, not to the node that was deleted. ### In contrast, when the node is copied-here or moved-here, ### the copy/move source's content will be exported. ### It is currently not possible to export the revert-base ### when a node is locally replaced. We need a new ### svn_opt_revision_ enum value for proper distinction ### between revert-base and commit-base. Copied-/moved-here nodes have a base, so export both added and replaced files when they involve a copy-/move-here. We get all this for free from evaluating SOURCE == NULL: */ SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (source == NULL) return SVN_NO_ERROR; SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } else { svn_wc_status3_t *status; /* ### hmm. this isn't always a specialfile. this will simply open ### the file readonly if it is a regular file. */ SVN_ERR(svn_subst_read_specialfile(&source, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_status3(&status, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } /* We can early-exit if we're creating a special file. */ special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (special != NULL) { /* Create the destination as a special file, and copy the source details into the destination stream. */ SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath, scratch_pool, scratch_pool)); return svn_error_trace( svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool)); } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); executable = apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING); if (eol_style) SVN_ERR(get_eol_style(&style, &eol, eol_style->data, native_eol)); if (local_mod) { /* Use the modified time from the working copy of the file */ SVN_ERR(svn_io_file_affected_time(&tm, from_abspath, scratch_pool)); } else { SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } if (keywords) { svn_revnum_t changed_rev; const char *suffix; const char *url; const char *author; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ suffix = "M"; author = _("(local)"); } else { suffix = ""; } SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, apr_psprintf(scratch_pool, "%ld%s", changed_rev, suffix), url, tm, author, scratch_pool)); } /* For atomicity, we translate to a tmp file and then rename the tmp file over the real destination. */ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, svn_dirent_dirname(to_abspath, scratch_pool), svn_io_file_del_none, scratch_pool, scratch_pool)); /* If some translation is needed, then wrap the output stream (this is more efficient than wrapping the input). */ if (eol || (kw && (apr_hash_count(kw) > 0))) dst_stream = svn_subst_stream_translated(dst_stream, eol, FALSE /* repair */, kw, ! ignore_keywords /* expand */, scratch_pool); /* ###: use cancel func/baton in place of NULL/NULL below. */ err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool); if (!err && executable) err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool); if (!err) err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool); if (err) return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE, scratch_pool)); /* Now that dst_tmp contains the translated data, do the atomic rename. */ SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath, svn_wc_notify_update_add, scratch_pool); notify->kind = svn_node_file; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } return SVN_NO_ERROR; }
static svn_error_t * test_stream_seek_translated(apr_pool_t *pool) { svn_stream_t *stream, *translated_stream; svn_stringbuf_t *stringbuf; char buf[44]; /* strlen("One$MyKeyword: my keyword was expanded $Two") + \0 */ apr_size_t len; svn_stream_mark_t *mark; apr_hash_t *keywords; svn_string_t *keyword_val; keywords = apr_hash_make(pool); keyword_val = svn_string_create("my keyword was expanded", pool); apr_hash_set(keywords, "MyKeyword", APR_HASH_KEY_STRING, keyword_val); stringbuf = svn_stringbuf_create("One$MyKeyword$Two", pool); stream = svn_stream_from_stringbuf(stringbuf, pool); translated_stream = svn_subst_stream_translated(stream, APR_EOL_STR, FALSE, keywords, TRUE, pool); /* Seek from outside of keyword to inside of keyword. */ len = 25; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 25); buf[25] = '\0'; SVN_TEST_STRING_ASSERT(buf, "One$MyKeyword: my keyword"); SVN_ERR(svn_stream_mark(translated_stream, &mark, pool)); SVN_ERR(svn_stream_reset(translated_stream)); SVN_ERR(svn_stream_seek(translated_stream, mark)); len = 4; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 4); buf[4] = '\0'; SVN_TEST_STRING_ASSERT(buf, " was"); SVN_ERR(svn_stream_seek(translated_stream, mark)); SVN_ERR(svn_stream_skip(translated_stream, 2)); len = 2; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 2); buf[len] = '\0'; SVN_TEST_STRING_ASSERT(buf, "as"); /* Seek from inside of keyword to inside of keyword. */ SVN_ERR(svn_stream_mark(translated_stream, &mark, pool)); len = 9; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 9); buf[9] = '\0'; SVN_TEST_STRING_ASSERT(buf, " expanded"); SVN_ERR(svn_stream_seek(translated_stream, mark)); len = 9; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 9); buf[9] = '\0'; SVN_TEST_STRING_ASSERT(buf, " expanded"); SVN_ERR(svn_stream_seek(translated_stream, mark)); SVN_ERR(svn_stream_skip(translated_stream, 6)); len = 3; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 3); buf[len] = '\0'; SVN_TEST_STRING_ASSERT(buf, "ded"); /* Seek from inside of keyword to outside of keyword. */ SVN_ERR(svn_stream_mark(translated_stream, &mark, pool)); len = 4; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 4); buf[4] = '\0'; SVN_TEST_STRING_ASSERT(buf, " $Tw"); SVN_ERR(svn_stream_seek(translated_stream, mark)); len = 4; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 4); buf[4] = '\0'; SVN_TEST_STRING_ASSERT(buf, " $Tw"); SVN_ERR(svn_stream_seek(translated_stream, mark)); SVN_ERR(svn_stream_skip(translated_stream, 2)); len = 2; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 2); buf[len] = '\0'; SVN_TEST_STRING_ASSERT(buf, "Tw"); /* Seek from outside of keyword to outside of keyword. */ SVN_ERR(svn_stream_mark(translated_stream, &mark, pool)); len = 1; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 1); buf[1] = '\0'; SVN_TEST_STRING_ASSERT(buf, "o"); SVN_ERR(svn_stream_seek(translated_stream, mark)); len = 1; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 1); buf[1] = '\0'; SVN_TEST_STRING_ASSERT(buf, "o"); SVN_ERR(svn_stream_seek(translated_stream, mark)); SVN_ERR(svn_stream_skip(translated_stream, 2)); len = 1; SVN_ERR(svn_stream_read_full(translated_stream, buf, &len)); SVN_TEST_ASSERT(len == 0); buf[len] = '\0'; SVN_TEST_STRING_ASSERT(buf, ""); SVN_ERR(svn_stream_close(stream)); return SVN_NO_ERROR; }
svn_error_t * svn_client_blame5(const char *target, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start, const svn_opt_revision_t *end, const svn_diff_file_options_t *diff_options, svn_boolean_t ignore_mime_type, svn_boolean_t include_merged_revisions, svn_client_blame_receiver3_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { struct file_rev_baton frb; svn_ra_session_t *ra_session; svn_revnum_t start_revnum, end_revnum; struct blame *walk, *walk_merged = NULL; apr_pool_t *iterpool; svn_stream_t *last_stream; svn_stream_t *stream; const char *target_abspath_or_url; if (start->kind == svn_opt_revision_unspecified || end->kind == svn_opt_revision_unspecified) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); if (svn_path_is_url(target)) target_abspath_or_url = target; else SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool)); /* Get an RA plugin for this filesystem object. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &end_revnum, NULL, target, NULL, peg_revision, end, ctx, pool)); SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, target_abspath_or_url, ra_session, start, pool)); if (end_revnum < start_revnum) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, _("Start revision must precede end revision")); frb.start_rev = start_revnum; frb.end_rev = end_revnum; frb.target = target; frb.ctx = ctx; frb.diff_options = diff_options; frb.ignore_mime_type = ignore_mime_type; frb.include_merged_revisions = include_merged_revisions; frb.last_filename = NULL; frb.last_original_filename = NULL; frb.chain = apr_palloc(pool, sizeof(*frb.chain)); frb.chain->blame = NULL; frb.chain->avail = NULL; frb.chain->pool = pool; if (include_merged_revisions) { frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain)); frb.merged_chain->blame = NULL; frb.merged_chain->avail = NULL; frb.merged_chain->pool = pool; } SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool)); frb.mainpool = pool; /* The callback will flip the following two pools, because it needs information from the previous call. Obviously, it can't rely on the lifetime of the pool provided by get_file_revs. */ frb.lastpool = svn_pool_create(pool); frb.currpool = svn_pool_create(pool); if (include_merged_revisions) { frb.filepool = svn_pool_create(pool); frb.prevfilepool = svn_pool_create(pool); } /* Collect all blame information. We need to ensure that we get one revision before the start_rev, if available so that we can know what was actually changed in the start revision. */ SVN_ERR(svn_ra_get_file_revs2(ra_session, "", start_revnum - (start_revnum > 0 ? 1 : 0), end_revnum, include_merged_revisions, file_rev_handler, &frb, pool)); if (end->kind == svn_opt_revision_working) { /* If the local file is modified we have to call the handler on the working copy file with keywords unexpanded */ svn_wc_status3_t *status; SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool, pool)); if (status->text_status != svn_wc_status_normal) { apr_hash_t *props; svn_stream_t *wcfile; svn_string_t *keywords; svn_stream_t *tempfile; const char *temppath; apr_hash_t *kw = NULL; SVN_ERR(svn_wc_prop_list2(&props, ctx->wc_ctx, target_abspath_or_url, pool, pool)); SVN_ERR(svn_stream_open_readonly(&wcfile, target, pool, pool)); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); if (keywords) SVN_ERR(svn_subst_build_keywords2(&kw, keywords->data, NULL, NULL, 0, NULL, pool)); wcfile = svn_subst_stream_translated(wcfile, "\n", TRUE, kw, FALSE, pool); SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL, svn_io_file_del_on_pool_cleanup, pool, pool)); SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func, ctx->cancel_baton, pool)); SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL, frb.diff_options, pool)); frb.last_filename = temppath; } } /* Report the blame to the caller. */ /* The callback has to have been called at least once. */ SVN_ERR_ASSERT(frb.last_filename != NULL); /* Create a pool for the iteration below. */ iterpool = svn_pool_create(pool); /* Open the last file and get a stream. */ SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename, pool, pool)); stream = svn_subst_stream_translated(last_stream, "\n", TRUE, NULL, FALSE, pool); /* Perform optional merged chain normalization. */ if (include_merged_revisions) { /* If we never created any blame for the original chain, create it now, with the most recent changed revision. This could occur if a file was created on a branch and them merged to another branch. This is semanticly a copy, and we want to use the revision on the branch as the most recently changed revision. ### Is this really what we want to do here? Do the sematics of copy change? */ if (!frb.chain->blame) frb.chain->blame = blame_create(frb.chain, frb.rev, 0); normalize_blames(frb.chain, frb.merged_chain, pool); walk_merged = frb.merged_chain->blame; } /* Process each blame item. */ for (walk = frb.chain->blame; walk; walk = walk->next) { apr_off_t line_no; svn_revnum_t merged_rev; const char *merged_path; apr_hash_t *merged_rev_props; if (walk_merged) { merged_rev = walk_merged->rev->revision; merged_rev_props = walk_merged->rev->rev_props; merged_path = walk_merged->rev->path; } else { merged_rev = SVN_INVALID_REVNUM; merged_rev_props = NULL; merged_path = NULL; } for (line_no = walk->start; !walk->next || line_no < walk->next->start; ++line_no) { svn_boolean_t eof; svn_stringbuf_t *sb; svn_pool_clear(iterpool); SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); if (!eof || sb->len) { if (walk->rev) SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, line_no, walk->rev->revision, walk->rev->rev_props, merged_rev, merged_rev_props, merged_path, sb->data, FALSE, iterpool)); else SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, line_no, SVN_INVALID_REVNUM, NULL, SVN_INVALID_REVNUM, NULL, NULL, sb->data, TRUE, iterpool)); } if (eof) break; } if (walk_merged) walk_merged = walk_merged->next; } SVN_ERR(svn_stream_close(stream)); svn_pool_destroy(frb.lastpool); svn_pool_destroy(frb.currpool); if (include_merged_revisions) { svn_pool_destroy(frb.filepool); svn_pool_destroy(frb.prevfilepool); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
svn_error_t * svn_client_blame4(const char *target, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start, const svn_opt_revision_t *end, const svn_diff_file_options_t *diff_options, svn_boolean_t ignore_mime_type, svn_boolean_t include_merged_revisions, svn_client_blame_receiver2_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { struct file_rev_baton frb; svn_ra_session_t *ra_session; const char *url; svn_revnum_t start_revnum, end_revnum; struct blame *walk, *walk_merged = NULL; apr_file_t *file; apr_pool_t *iterpool; svn_stream_t *stream; if (start->kind == svn_opt_revision_unspecified || end->kind == svn_opt_revision_unspecified) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); else if (start->kind == svn_opt_revision_working || end->kind == svn_opt_revision_working) return svn_error_create (SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("blame of the WORKING revision is not supported")); /* Get an RA plugin for this filesystem object. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &end_revnum, &url, target, NULL, peg_revision, end, ctx, pool)); SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ra_session, start, target, pool)); if (end_revnum < start_revnum) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, _("Start revision must precede end revision")); frb.start_rev = start_revnum; frb.end_rev = end_revnum; frb.target = target; frb.ctx = ctx; frb.diff_options = diff_options; frb.ignore_mime_type = ignore_mime_type; frb.include_merged_revisions = include_merged_revisions; frb.last_filename = NULL; frb.last_original_filename = NULL; frb.chain = apr_palloc(pool, sizeof(*frb.chain)); frb.chain->blame = NULL; frb.chain->avail = NULL; frb.chain->pool = pool; if (include_merged_revisions) { frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain)); frb.merged_chain->blame = NULL; frb.merged_chain->avail = NULL; frb.merged_chain->pool = pool; } SVN_ERR(svn_io_temp_dir(&frb.tmp_path, pool)); frb.tmp_path = svn_path_join(frb.tmp_path, "tmp", pool), frb.mainpool = pool; /* The callback will flip the following two pools, because it needs information from the previous call. Obviously, it can't rely on the lifetime of the pool provided by get_file_revs. */ frb.lastpool = svn_pool_create(pool); frb.currpool = svn_pool_create(pool); if (include_merged_revisions) { frb.filepool = svn_pool_create(pool); frb.prevfilepool = svn_pool_create(pool); } /* Collect all blame information. We need to ensure that we get one revision before the start_rev, if available so that we can know what was actually changed in the start revision. */ SVN_ERR(svn_ra_get_file_revs2(ra_session, "", start_revnum - (start_revnum > 0 ? 1 : 0), end_revnum, include_merged_revisions, file_rev_handler, &frb, pool)); /* Report the blame to the caller. */ /* The callback has to have been called at least once. */ assert(frb.last_filename != NULL); /* Create a pool for the iteration below. */ iterpool = svn_pool_create(pool); /* Open the last file and get a stream. */ SVN_ERR(svn_io_file_open(&file, frb.last_filename, APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); stream = svn_subst_stream_translated(svn_stream_from_aprfile(file, pool), "\n", TRUE, NULL, FALSE, pool); /* Perform optional merged chain normalization. */ if (include_merged_revisions) { /* If we never created any blame for the original chain, create it now, with the most recent changed revision. This could occur if a file was created on a branch and them merged to another branch. This is semanticly a copy, and we want to use the revision on the branch as the most recently changed revision. ### Is this really what we want to do here? Do the sematics of copy change? */ if (!frb.chain->blame) frb.chain->blame = blame_create(frb.chain, frb.rev, 0); normalize_blames(frb.chain, frb.merged_chain, pool); walk_merged = frb.merged_chain->blame; } /* Process each blame item. */ for (walk = frb.chain->blame; walk; walk = walk->next) { apr_off_t line_no; svn_revnum_t merged_rev; const char *merged_author, *merged_date, *merged_path; if (walk_merged) { merged_rev = walk_merged->rev->revision; merged_author = walk_merged->rev->author; merged_date = walk_merged->rev->date; merged_path = walk_merged->rev->path; } else { merged_rev = SVN_INVALID_REVNUM; merged_author = NULL; merged_date = NULL; merged_path = NULL; } for (line_no = walk->start; !walk->next || line_no < walk->next->start; ++line_no) { svn_boolean_t eof; svn_stringbuf_t *sb; svn_pool_clear(iterpool); SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); if (!eof || sb->len) SVN_ERR(receiver(receiver_baton, line_no, walk->rev->revision, walk->rev->author, walk->rev->date, merged_rev, merged_author, merged_date, merged_path, sb->data, iterpool)); if (eof) break; } if (walk_merged) walk_merged = walk_merged->next; } SVN_ERR(svn_stream_close(stream)); /* We don't need the temp file any more. */ SVN_ERR(svn_io_file_close(file, pool)); svn_pool_destroy(frb.lastpool); svn_pool_destroy(frb.currpool); if (include_merged_revisions) { svn_pool_destroy(frb.filepool); svn_pool_destroy(frb.prevfilepool); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
svn_error_t * svn_client__get_normalized_stream(svn_stream_t **normal_stream, svn_wc_context_t *wc_ctx, const char *local_abspath, const svn_opt_revision_t *revision, svn_boolean_t expand_keywords, svn_boolean_t normalize_eols, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *input; svn_node_kind_t kind; SVN_ERR_ASSERT(SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)); SVN_ERR(svn_wc_read_kind(&kind, wc_ctx, local_abspath, FALSE, scratch_pool)); if (kind == svn_node_unknown || kind == svn_node_none) return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, _("'%s' is not under version control"), svn_dirent_local_style(local_abspath, scratch_pool)); if (kind != svn_node_file) return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL, _("'%s' refers to a directory"), svn_dirent_local_style(local_abspath, scratch_pool)); if (revision->kind != svn_opt_revision_working) { SVN_ERR(svn_wc_get_pristine_contents2(&input, wc_ctx, local_abspath, result_pool, scratch_pool)); if (input == NULL) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' has no base revision until it is committed"), svn_dirent_local_style(local_abspath, scratch_pool)); SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath, scratch_pool, scratch_pool)); } else { svn_wc_status3_t *status; SVN_ERR(svn_stream_open_readonly(&input, local_abspath, scratch_pool, result_pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_status3(&status, wc_ctx, local_abspath, scratch_pool, scratch_pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (eol_style) svn_subst_eol_style_from_value(&style, &eol, eol_style->data); if (local_mod && (! special)) { /* Use the modified time from the working copy if the file */ SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool)); } else { SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx, local_abspath, scratch_pool, scratch_pool)); } if (keywords) { svn_revnum_t changed_rev; const char *rev_str; const char *author; const char *url; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx, local_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, local_abspath, scratch_pool, scratch_pool)); if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ rev_str = apr_psprintf(scratch_pool, "%ldM", changed_rev); author = _("(local)"); } else { rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); } SVN_ERR(svn_subst_build_keywords2(&kw, keywords->data, rev_str, url, tm, author, scratch_pool)); } /* Wrap the output stream if translation is needed. */ if (eol != NULL || kw != NULL) input = svn_subst_stream_translated( input, (eol_style && normalize_eols) ? SVN_SUBST_NATIVE_EOL_STR : eol, FALSE, kw, expand_keywords, result_pool); *normal_stream = input; return SVN_NO_ERROR; }
svn_error_t * svn_client_cat2(svn_stream_t *out, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session; svn_revnum_t rev; svn_string_t *eol_style; svn_string_t *keywords; apr_hash_t *props; const char *url; svn_stream_t *output = out; svn_error_t *err; /* ### Inconsistent default revision logic in this command. */ if (peg_revision->kind == svn_opt_revision_unspecified) { peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, path_or_url); revision = svn_cl__rev_default_to_head_or_base(revision, path_or_url); } else { peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, path_or_url); revision = svn_cl__rev_default_to_peg(revision, peg_revision); } if (! svn_path_is_url(path_or_url) && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind) && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) { const char *local_abspath; svn_stream_t *normal_stream; SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, pool)); SVN_ERR(svn_client__get_normalized_stream(&normal_stream, ctx->wc_ctx, local_abspath, revision, TRUE, FALSE, ctx->cancel_func, ctx->cancel_baton, pool, pool)); /* We don't promise to close output, so disown it to ensure we don't. */ output = svn_stream_disown(output, pool); return svn_error_trace(svn_stream_copy3(normal_stream, output, ctx->cancel_func, ctx->cancel_baton, pool)); } /* Get an RA plugin for this filesystem object. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev, &url, path_or_url, NULL, peg_revision, revision, ctx, pool)); /* Grab some properties we need to know in order to figure out if anything special needs to be done with this file. */ err = svn_ra_get_file(ra_session, "", rev, NULL, NULL, &props, pool); if (err) { if (err->apr_err == SVN_ERR_FS_NOT_FILE) { return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, err, _("URL '%s' refers to a directory"), url); } else { return svn_error_trace(err); } } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); if (eol_style || keywords) { /* It's a file with no special eol style or keywords. */ svn_subst_eol_style_t eol; const char *eol_str; apr_hash_t *kw; if (eol_style) svn_subst_eol_style_from_value(&eol, &eol_str, eol_style->data); else { eol = svn_subst_eol_style_none; eol_str = NULL; } if (keywords) { svn_string_t *cmt_rev, *cmt_date, *cmt_author; apr_time_t when = 0; cmt_rev = apr_hash_get(props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING); cmt_date = apr_hash_get(props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING); cmt_author = apr_hash_get(props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING); if (cmt_date) SVN_ERR(svn_time_from_cstring(&when, cmt_date->data, pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, cmt_rev->data, url, when, cmt_author ? cmt_author->data : NULL, pool)); } else kw = NULL; /* Interject a translating stream */ output = svn_subst_stream_translated(svn_stream_disown(out, pool), eol_str, FALSE, kw, TRUE, pool); } SVN_ERR(svn_ra_get_file(ra_session, "", rev, output, NULL, NULL, pool)); if (out != output) /* Close the interjected stream */ SVN_ERR(svn_stream_close(output)); return SVN_NO_ERROR; }
svn_error_t * svn_wc__internal_translated_stream(svn_stream_t **stream, svn_wc__db_t *db, const char *local_abspath, const char *versioned_abspath, apr_uint32_t flags, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_boolean_t special; svn_boolean_t to_nf = flags & SVN_WC_TRANSLATE_TO_NF; svn_subst_eol_style_t style; const char *eol; apr_hash_t *keywords; svn_boolean_t repair_forced = flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath)); SVN_ERR(svn_wc__get_translate_info(&style, &eol, &keywords, &special, db, versioned_abspath, NULL, FALSE, scratch_pool, scratch_pool)); if (special) { if (to_nf) return svn_subst_read_specialfile(stream, local_abspath, result_pool, scratch_pool); return svn_subst_create_specialfile(stream, local_abspath, result_pool, scratch_pool); } if (to_nf) SVN_ERR(svn_stream_open_readonly(stream, local_abspath, result_pool, scratch_pool)); else { apr_file_t *file; /* We don't want the "open-exclusively" feature of the normal svn_stream_open_writable interface. Do this manually. */ SVN_ERR(svn_io_file_open(&file, local_abspath, APR_CREATE | APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, result_pool)); *stream = svn_stream_from_aprfile2(file, FALSE, result_pool); } if (svn_subst_translation_required(style, eol, keywords, special, TRUE)) { if (to_nf) { if (style == svn_subst_eol_style_native) eol = SVN_SUBST_NATIVE_EOL_STR; else if (style == svn_subst_eol_style_fixed) repair_forced = TRUE; else if (style != svn_subst_eol_style_none) return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); /* Wrap the stream to translate to normal form */ *stream = svn_subst_stream_translated(*stream, eol, repair_forced, keywords, FALSE /* expand */, result_pool); /* Enforce our contract. TO_NF streams are readonly */ svn_stream_set_write(*stream, write_handler_unsupported); } else { *stream = svn_subst_stream_translated(*stream, eol, TRUE, keywords, TRUE, result_pool); /* Enforce our contract. FROM_NF streams are write-only */ svn_stream_set_read(*stream, read_handler_unsupported); } } return SVN_NO_ERROR; }
/* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS. * Use PATH as the name passed to diff callbacks. * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback * function to use to compare the files (added/deleted/changed). * * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties * instead of reading properties from LOCAL_ABSPATH1. This is required when * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that * file content must be diffed against, but properties to diff against come * from the replaced directory. */ static svn_error_t * do_arbitrary_files_diff(const char *local_abspath1, const char *local_abspath2, const char *path, svn_boolean_t file1_is_empty, svn_boolean_t file2_is_empty, apr_hash_t *original_props_override, const svn_wc_diff_callbacks4_t *callbacks, void *diff_baton, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { apr_hash_t *original_props; apr_hash_t *modified_props; apr_array_header_t *prop_changes; svn_string_t *original_mime_type = NULL; svn_string_t *modified_mime_type = NULL; if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); /* Try to get properties from either file. It's OK if the files do not * have properties, or if they are unversioned. */ if (original_props_override) original_props = original_props_override; else SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx, scratch_pool, scratch_pool)); SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx, scratch_pool, scratch_pool)); SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props, scratch_pool)); /* Try to determine the mime-type of each file. */ original_mime_type = svn_hash_gets(original_props, SVN_PROP_MIME_TYPE); if (!file1_is_empty && !original_mime_type) { const char *mime_type; SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1, ctx->mimetypes_map, scratch_pool)); if (mime_type) original_mime_type = svn_string_create(mime_type, scratch_pool); } modified_mime_type = svn_hash_gets(modified_props, SVN_PROP_MIME_TYPE); if (!file2_is_empty && !modified_mime_type) { const char *mime_type; SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1, ctx->mimetypes_map, scratch_pool)); if (mime_type) modified_mime_type = svn_string_create(mime_type, scratch_pool); } /* Produce the diff. */ if (file1_is_empty && !file2_is_empty) SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path, local_abspath1, local_abspath2, /* ### TODO get real revision info * for versioned files? */ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, original_mime_type ? original_mime_type->data : NULL, modified_mime_type ? modified_mime_type->data : NULL, /* ### TODO get copyfrom? */ NULL, SVN_INVALID_REVNUM, prop_changes, original_props, diff_baton, scratch_pool)); else if (!file1_is_empty && file2_is_empty) SVN_ERR(callbacks->file_deleted(NULL, NULL, path, local_abspath1, local_abspath2, original_mime_type ? original_mime_type->data : NULL, modified_mime_type ? modified_mime_type->data : NULL, original_props, diff_baton, scratch_pool)); else { svn_stream_t *file1; svn_stream_t *file2; svn_boolean_t same; svn_string_t *val; /* We have two files, which may or may not be the same. ### Our caller assumes that we should ignore symlinks here and handle them as normal paths. Perhaps that should change? */ SVN_ERR(svn_stream_open_readonly(&file1, local_abspath1, scratch_pool, scratch_pool)); SVN_ERR(svn_stream_open_readonly(&file2, local_abspath2, scratch_pool, scratch_pool)); /* Wrap with normalization, etc. if necessary */ if (original_props) { val = svn_hash_gets(original_props, SVN_PROP_EOL_STYLE); if (val) { svn_subst_eol_style_t style; const char *eol; svn_subst_eol_style_from_value(&style, &eol, val->data); /* ### Ignoring keywords */ if (eol) file1 = svn_subst_stream_translated(file1, eol, TRUE, NULL, FALSE, scratch_pool); } } if (modified_props) { val = svn_hash_gets(modified_props, SVN_PROP_EOL_STYLE); if (val) { svn_subst_eol_style_t style; const char *eol; svn_subst_eol_style_from_value(&style, &eol, val->data); /* ### Ignoring keywords */ if (eol) file2 = svn_subst_stream_translated(file2, eol, TRUE, NULL, FALSE, scratch_pool); } } SVN_ERR(svn_stream_contents_same2(&same, file1, file2, scratch_pool)); if (! same || prop_changes->nelts > 0) { /* ### We should probably pass the normalized data we created using the subst streams as that is what diff users expect */ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path, same ? NULL : local_abspath1, same ? NULL : local_abspath2, /* ### TODO get real revision info * for versioned files? */ SVN_INVALID_REVNUM /* rev1 */, SVN_INVALID_REVNUM /* rev2 */, original_mime_type ? original_mime_type->data : NULL, modified_mime_type ? modified_mime_type->data : NULL, prop_changes, original_props, diff_baton, scratch_pool)); } } return SVN_NO_ERROR; }