blob: ed6ccc46c945a3e33bd843a223998a642f60dcdc [file] [log] [blame]
/*
* adm_crawler.c: report local WC mods to an Editor.
*
* ====================================================================
* Copyright (c) 2000-2007 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
/* ==================================================================== */
#include <string.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_hash.h>
#include <apr_md5.h>
#include <assert.h>
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_wc.h"
#include "svn_io.h"
#include "svn_md5.h"
#include "svn_base64.h"
#include "svn_delta.h"
#include "svn_path.h"
#include "private/svn_wc_private.h"
#include "wc.h"
#include "adm_files.h"
#include "props.h"
#include "translate.h"
#include "entries.h"
#include "lock.h"
#include "svn_private_config.h"
/* Helper for report_revisions_and_depths().
Perform an atomic restoration of the file FILE_PATH; that is, copy
the file's text-base to the administrative tmp area, and then move
that file to FILE_PATH with possible translations/expansions. If
USE_COMMIT_TIMES is set, then set working file's timestamp to
last-commit-time. Either way, set entry-timestamp to match that of
the working file when all is finished. */
static svn_error_t *
restore_file(const char *file_path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t use_commit_times,
apr_pool_t *pool)
{
const char *tmp_file, *text_base_path;
svn_wc_entry_t newentry;
const char *bname;
svn_boolean_t special;
text_base_path = svn_wc__text_base_path(file_path, FALSE, pool);
bname = svn_path_basename(file_path, pool);
/* Copy / translate into a temporary file, which afterwards can
be atomically moved over the original working copy file. */
SVN_ERR(svn_wc_translated_file2(&tmp_file,
text_base_path, file_path, adm_access,
SVN_WC_TRANSLATE_FROM_NF
| SVN_WC_TRANSLATE_FORCE_COPY, pool));
SVN_ERR(svn_io_file_rename(tmp_file, file_path, pool));
SVN_ERR(svn_wc__maybe_set_read_only(NULL, file_path, adm_access, pool));
/* If necessary, tweak the new working file's executable bit. */
SVN_ERR(svn_wc__maybe_set_executable(NULL, file_path, adm_access, pool));
/* Remove any text conflict */
SVN_ERR(svn_wc_resolved_conflict3(file_path, adm_access, TRUE, FALSE,
svn_depth_empty,
svn_wc_conflict_choose_merged,
NULL, NULL, NULL, NULL, pool));
if (use_commit_times)
{
SVN_ERR(svn_wc__get_special(&special, file_path, adm_access, pool));
}
/* Possibly set timestamp to last-commit-time. */
if (use_commit_times && (! special))
{
const svn_wc_entry_t *entry;
SVN_ERR(svn_wc_entry(&entry, file_path, adm_access, FALSE, pool));
assert(entry != NULL);
SVN_ERR(svn_io_set_file_affected_time(entry->cmt_date,
file_path, pool));
newentry.text_time = entry->cmt_date;
}
else
{
SVN_ERR(svn_io_file_affected_time(&newentry.text_time,
file_path, pool));
}
/* Modify our entry's text-timestamp to match the working file. */
SVN_ERR(svn_wc__entry_modify(adm_access, bname,
&newentry, SVN_WC__ENTRY_MODIFY_TEXT_TIME,
TRUE /* do_sync now */, pool));
return SVN_NO_ERROR;
}
/* The recursive crawler that describes a mixed-revision working
copy to an RA layer. Used to initiate updates.
This is a depth-first recursive walk of DIR_PATH under ADM_ACCESS.
Look at each entry and check if its revision is different than
DIR_REV. If so, report this fact to REPORTER. If an entry is
missing from disk, report its absence to REPORTER. If an entry has
a different URL than expected, report that to REPORTER. If an
entry has a different depth than its parent, report that to
REPORTER.
Alternatively, if REPORT_EVERYTHING is set, then report all
children unconditionally.
DEPTH is actually the *requested* depth for the update-like
operation for which we are reporting working copy state. However,
certain requested depths affect the depth of the report crawl. For
example, if the requested depth is svn_depth_empty, there's no
point descending into subdirs, no matter what their depths. So:
If DEPTH is svn_depth_empty, don't report any files and don't
descend into any subdirs. If svn_depth_files, report files but
still don't descend into subdirs. If svn_depth_immediates, report
files, and report subdirs themselves but not their entries. If
svn_depth_infinity or svn_depth_unknown, report everything all the
way down. (That last sentence might sound counterintuitive, but
since you can't go deeper than the local ambient depth anyway,
requesting svn_depth_infinity really means "as deep as the various
parts of this working copy go". Of course, the information that
comes back from the server will be different for svn_depth_unknown
than for svn_depth_infinity.)
DEPTH_COMPATIBILITY_TRICK means the same thing here as it does
in svn_wc_crawl_revisions3().
If TRAVERSAL_INFO is non-null, record this directory's
value of svn:externals in both TRAVERSAL_INFO->externals_old and
TRAVERSAL_INFO->externals_new, using wc_path + dir_path as the key,
and the raw (unparsed) value of the property as the value; store
this directory's depth in TRAVERSAL_INFO->depths, using the same
key and svn_depth_to_word(depth) as the value. (Note: We set the
property value in both places, because its absence in just one or
the other place signals that the property was added or deleted;
thus, storing it in both places signals that it is present and, by
default, unchanged.)
If RESTORE_FILES is set, then unexpectedly missing working files
will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON
will be called to report the restoration. USE_COMMIT_TIMES is
passed to restore_file() helper. */
static svn_error_t *
report_revisions_and_depths(svn_wc_adm_access_t *adm_access,
const char *dir_path,
svn_revnum_t dir_rev,
const svn_ra_reporter3_t *reporter,
void *report_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
svn_boolean_t restore_files,
svn_depth_t depth,
svn_boolean_t depth_compatibility_trick,
svn_boolean_t report_everything,
svn_boolean_t use_commit_times,
svn_wc_traversal_info_t *traversal_info,
apr_pool_t *pool)
{
apr_hash_t *entries, *dirents;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create(pool), *iterpool;
const svn_wc_entry_t *dot_entry;
const char *this_url, *this_path, *full_path, *this_full_path;
svn_wc_adm_access_t *dir_access;
svn_wc_notify_t *notify;
/* Get both the SVN Entries and the actual on-disk entries. Also
notice that we're picking up hidden entries too. */
full_path = svn_path_join(svn_wc_adm_access_path(adm_access),
dir_path, subpool);
SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, full_path, subpool));
SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, subpool));
SVN_ERR(svn_io_get_dir_filenames(&dirents, full_path, subpool));
/*** Do the real reporting and recursing. ***/
/* First, look at "this dir" to see what its URL is. */
dot_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
APR_HASH_KEY_STRING);
/* If "this dir" has "svn:externals" property set on it, store its name
and depth in traversal_info. */
if (traversal_info)
{
const svn_string_t *val;
SVN_ERR(svn_wc_prop_get(&val, SVN_PROP_EXTERNALS, full_path, adm_access,
subpool));
if (val)
{
apr_pool_t *dup_pool = traversal_info->pool;
const char *dup_path = apr_pstrdup(dup_pool, full_path);
const char *dup_val = apr_pstrmemdup(dup_pool, val->data, val->len);
apr_hash_set(traversal_info->externals_old,
dup_path, APR_HASH_KEY_STRING, dup_val);
apr_hash_set(traversal_info->externals_new,
dup_path, APR_HASH_KEY_STRING, dup_val);
apr_hash_set(traversal_info->depths,
dup_path, APR_HASH_KEY_STRING,
svn_depth_to_word(dot_entry->depth));
}
}
/* Looping over current directory's SVN entries: */
iterpool = svn_pool_create(subpool);
for (hi = apr_hash_first(subpool, entries); hi; hi = apr_hash_next(hi))
{
const void *key;
apr_ssize_t klen;
void *val;
const svn_wc_entry_t *current_entry;
svn_io_dirent_t *dirent;
svn_node_kind_t dirent_kind;
svn_boolean_t missing = FALSE;
/* Clear the iteration subpool here because the loop has a bunch
of 'continue' jump statements. */
svn_pool_clear(iterpool);
/* Get the next entry */
apr_hash_this(hi, &key, &klen, &val);
current_entry = val;
/* Compute the name of the entry. Skip THIS_DIR altogether. */
if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR))
continue;
/* Compute the paths and URLs we need. */
this_url = svn_path_url_add_component(dot_entry->url, key, iterpool);
this_path = svn_path_join(dir_path, key, iterpool);
this_full_path = svn_path_join(full_path, key, iterpool);
/*** The Big Tests: ***/
/* If the entry is 'deleted' or 'absent', make sure the server
knows it's gone... */
if (current_entry->deleted || current_entry->absent)
{
/* ...unless we're reporting everything, in which case we're
going to report it missing later anyway. */
if (! report_everything)
SVN_ERR(reporter->delete_path(report_baton, this_path, iterpool));
continue;
}
/* Is the entry on disk? Set a flag if not. */
dirent = apr_hash_get(dirents, key, klen);
if (! dirent)
{
/* It is possible on a case insensitive system that the
entry is not really missing, so we call our trusty but
expensive friend svn_io_check_path to be sure. */
SVN_ERR(svn_io_check_path(this_full_path, &dirent_kind,
iterpool));
if (dirent_kind == svn_node_none)
missing = TRUE;
}
/* From here on out, ignore any entry scheduled for addition */
if (current_entry->schedule == svn_wc_schedule_add)
continue;
/*** Files ***/
if (current_entry->kind == svn_node_file)
{
/* If the item is missing from disk, and we're supposed to
restore missing things, and it isn't missing as a result
of a scheduling operation, then ... */
if (missing
&& restore_files
&& (current_entry->schedule != svn_wc_schedule_delete)
&& (current_entry->schedule != svn_wc_schedule_replace))
{
/* ... recreate file from text-base, and ... */
SVN_ERR(restore_file(this_full_path, dir_access,
use_commit_times, iterpool));
/* ... report the restoration to the caller. */
if (notify_func != NULL)
{
notify = svn_wc_create_notify(this_full_path,
svn_wc_notify_restore,
iterpool);
notify->kind = svn_node_file;
(*notify_func)(notify_baton, notify, iterpool);
}
}
if (report_everything)
{
/* Report the file unconditionally, one way or another. */
if (strcmp(current_entry->url, this_url) != 0)
SVN_ERR(reporter->link_path(report_baton, this_path,
current_entry->url,
current_entry->revision,
current_entry->depth,
FALSE, current_entry->lock_token,
iterpool));
else
SVN_ERR(reporter->set_path(report_baton, this_path,
current_entry->revision,
current_entry->depth,
FALSE, current_entry->lock_token,
iterpool));
}
/* Possibly report a disjoint URL ... */
else if ((current_entry->schedule != svn_wc_schedule_add)
&& (current_entry->schedule != svn_wc_schedule_replace)
&& (strcmp(current_entry->url, this_url) != 0))
SVN_ERR(reporter->link_path(report_baton,
this_path,
current_entry->url,
current_entry->revision,
current_entry->depth,
FALSE,
current_entry->lock_token,
iterpool));
/* ... or perhaps just a differing revision or lock token,
or the mere presence of the file in a depth-empty dir. */
else if (current_entry->revision != dir_rev
|| current_entry->lock_token
|| dot_entry->depth == svn_depth_empty)
SVN_ERR(reporter->set_path(report_baton,
this_path,
current_entry->revision,
current_entry->depth,
FALSE,
current_entry->lock_token,
iterpool));
} /* end file case */
/*** Directories (in recursive mode) ***/
else if (current_entry->kind == svn_node_dir
&& (depth > svn_depth_files
|| depth == svn_depth_unknown))
{
svn_wc_adm_access_t *subdir_access;
const svn_wc_entry_t *subdir_entry;
svn_boolean_t start_empty;
/* If a directory is missing from disk, we have no way to
recreate it locally, so report as missing and move
along. Again, don't bother if we're reporting
everything, because the dir is already missing on the server. */
if (missing)
{
if (! report_everything)
SVN_ERR(reporter->delete_path(report_baton, this_path,
iterpool));
continue;
}
/* We need to read the full entry of the directory from its
own "this dir", if available. */
if (svn_wc__adm_missing(adm_access, this_full_path))
continue;
SVN_ERR(svn_wc_adm_retrieve(&subdir_access, adm_access,
this_full_path, iterpool));
SVN_ERR(svn_wc_entry(&subdir_entry, this_full_path, subdir_access,
TRUE, iterpool));
start_empty = subdir_entry->incomplete;
if (depth_compatibility_trick
&& subdir_entry->depth <= svn_depth_files
&& depth > subdir_entry->depth)
{
start_empty = TRUE;
}
if (report_everything)
{
/* Report the dir unconditionally, one way or another. */
if (strcmp(subdir_entry->url, this_url) != 0)
SVN_ERR(reporter->link_path(report_baton, this_path,
subdir_entry->url,
subdir_entry->revision,
subdir_entry->depth,
start_empty,
subdir_entry->lock_token,
iterpool));
else
SVN_ERR(reporter->set_path(report_baton, this_path,
subdir_entry->revision,
subdir_entry->depth,
start_empty,
subdir_entry->lock_token,
iterpool));
}
/* Possibly report a disjoint URL ... */
else if (strcmp(subdir_entry->url, this_url) != 0)
SVN_ERR(reporter->link_path(report_baton,
this_path,
subdir_entry->url,
subdir_entry->revision,
subdir_entry->depth,
start_empty,
subdir_entry->lock_token,
iterpool));
/* ... or perhaps just a differing revision, lock token, incomplete
subdir, the mere presence of the directory in a depth-empty or
depth-files dir, or if the parent dir is at depth-immediates but
the child is not at depth-empty. */
else if (subdir_entry->revision != dir_rev
|| subdir_entry->lock_token
|| subdir_entry->incomplete
|| dot_entry->depth == svn_depth_empty
|| dot_entry->depth == svn_depth_files
|| (dot_entry->depth == svn_depth_immediates
&& subdir_entry->depth != svn_depth_empty))
SVN_ERR(reporter->set_path(report_baton,
this_path,
subdir_entry->revision,
subdir_entry->depth,
start_empty,
subdir_entry->lock_token,
iterpool));
if (SVN_DEPTH_IS_RECURSIVE(depth))
SVN_ERR(report_revisions_and_depths(adm_access, this_path,
subdir_entry->revision,
reporter, report_baton,
notify_func, notify_baton,
restore_files, depth,
depth_compatibility_trick,
start_empty,
use_commit_times,
traversal_info,
iterpool));
} /* end directory case */
} /* end main entries loop */
/* We're done examining this dir's entries, so free everything. */
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
/*------------------------------------------------------------------*/
/*** Public Interfaces ***/
svn_error_t *
svn_wc_crawl_revisions3(const char *path,
svn_wc_adm_access_t *adm_access,
const svn_ra_reporter3_t *reporter,
void *report_baton,
svn_boolean_t restore_files,
svn_depth_t depth,
svn_boolean_t depth_compatibility_trick,
svn_boolean_t use_commit_times,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
svn_wc_traversal_info_t *traversal_info,
apr_pool_t *pool)
{
svn_error_t *fserr, *err = SVN_NO_ERROR;
const svn_wc_entry_t *entry;
svn_revnum_t base_rev = SVN_INVALID_REVNUM;
svn_boolean_t missing = FALSE;
const svn_wc_entry_t *parent_entry = NULL;
svn_wc_notify_t *notify;
svn_boolean_t start_empty;
/* The first thing we do is get the base_rev from the working copy's
ROOT_DIRECTORY. This is the first revnum that entries will be
compared to. */
SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
if ((! entry) || ((entry->schedule == svn_wc_schedule_add)
&& (entry->kind == svn_node_dir)))
{
/* There aren't any versioned paths to crawl which are known to
the repository. */
SVN_ERR(svn_wc__entry_versioned(&parent_entry,
svn_path_dirname(path, pool),
adm_access, FALSE, pool));
base_rev = parent_entry->revision;
/* If no versioned path exists, we use the requested depth, which
is the depth at which the new path should be brought in. Default
to infinity if no explicit depth was given. */
if (depth == svn_depth_unknown)
depth = svn_depth_infinity;
SVN_ERR(reporter->set_path(report_baton, "", base_rev, depth,
entry ? entry->incomplete : TRUE,
NULL, pool));
SVN_ERR(reporter->delete_path(report_baton, "", pool));
/* Finish the report, which causes the update editor to be
driven. */
SVN_ERR(reporter->finish_report(report_baton, pool));
return SVN_NO_ERROR;
}
base_rev = entry->revision;
start_empty = entry->incomplete;
if (depth_compatibility_trick
&& entry->depth <= svn_depth_immediates
&& depth > entry->depth)
{
start_empty = TRUE;
}
if (base_rev == SVN_INVALID_REVNUM)
{
const char *dirname = svn_path_dirname(path, pool);
SVN_ERR(svn_wc__entry_versioned(&parent_entry, dirname, adm_access,
FALSE, pool));
base_rev = parent_entry->revision;
}
/* The first call to the reporter merely informs it that the
top-level directory being updated is at BASE_REV. Its PATH
argument is ignored. */
SVN_ERR(reporter->set_path(report_baton, "", base_rev, entry->depth,
start_empty, NULL, pool));
if (entry->schedule != svn_wc_schedule_delete)
{
apr_finfo_t info;
err = svn_io_stat(&info, path, APR_FINFO_MIN, pool);
if (err)
{
if (APR_STATUS_IS_ENOENT(err->apr_err))
missing = TRUE;
svn_error_clear(err);
err = NULL;
}
}
if (entry->kind == svn_node_dir)
{
if (missing)
{
/* Always report directories as missing; we can't recreate
them locally. */
err = reporter->delete_path(report_baton, "", pool);
if (err)
goto abort_report;
}
else if (depth != svn_depth_empty)
{
/* Recursively crawl ROOT_DIRECTORY and report differing
revisions. */
err = report_revisions_and_depths(adm_access,
"",
base_rev,
reporter, report_baton,
notify_func, notify_baton,
restore_files, depth,
depth_compatibility_trick,
start_empty,
use_commit_times,
traversal_info,
pool);
if (err)
goto abort_report;
}
}
else if (entry->kind == svn_node_file)
{
const char *pdir, *bname;
if (missing && restore_files)
{
/* Recreate file from text-base. */
err = restore_file(path, adm_access, use_commit_times, pool);
if (err)
goto abort_report;
/* Report the restoration to the caller. */
if (notify_func != NULL)
{
notify = svn_wc_create_notify(path, svn_wc_notify_restore,
pool);
notify->kind = svn_node_file;
(*notify_func)(notify_baton, notify, pool);
}
}
/* Split PATH into parent PDIR and basename BNAME. */
svn_path_split(path, &pdir, &bname, pool);
if (! parent_entry)
{
err = svn_wc_entry(&parent_entry, pdir, adm_access, FALSE, pool);
if (err)
goto abort_report;
}
if (parent_entry
&& parent_entry->url
&& entry->url
&& strcmp(entry->url,
svn_path_url_add_component(parent_entry->url,
bname, pool)))
{
/* This file is disjoint with respect to its parent
directory. Since we are looking at the actual target of
the report (not some file in a subdirectory of a target
directory), and that target is a file, we need to pass an
empty string to link_path. */
err = reporter->link_path(report_baton,
"",
entry->url,
entry->revision,
entry->depth,
FALSE,
entry->lock_token,
pool);
if (err)
goto abort_report;
}
else if (entry->revision != base_rev || entry->lock_token)
{
/* If this entry is a file node, we just want to report that
node's revision. Since we are looking at the actual target
of the report (not some file in a subdirectory of a target
directory), and that target is a file, we need to pass an
empty string to set_path. */
err = reporter->set_path(report_baton, "", base_rev, entry->depth,
FALSE,
entry->lock_token, pool);
if (err)
goto abort_report;
}
}
/* Finish the report, which causes the update editor to be driven. */
return reporter->finish_report(report_baton, pool);
abort_report:
/* Clean up the fs transaction. */
if ((fserr = reporter->abort_report(report_baton, pool)))
{
fserr = svn_error_quick_wrap(fserr, _("Error aborting report"));
svn_error_compose(err, fserr);
}
return err;
}
/*** Compatibility wrapper: turns an svn_ra_reporter2_t into an
svn_ra_reporter3_t.
This code looks like it duplicates code in libsvn_ra/ra_loader.c,
but it does not. That code makes an new thing look like an old
thing; this code makes an old thing look like a new thing. ***/
struct wrap_3to2_report_baton {
const svn_ra_reporter2_t *reporter;
void *baton;
};
static svn_error_t *wrap_3to2_set_path(void *report_baton,
const char *path,
svn_revnum_t revision,
svn_depth_t depth,
svn_boolean_t start_empty,
const char *lock_token,
apr_pool_t *pool)
{
struct wrap_3to2_report_baton *wrb = report_baton;
return wrb->reporter->set_path(wrb->baton, path, revision, start_empty,
lock_token, pool);
}
static svn_error_t *wrap_3to2_delete_path(void *report_baton,
const char *path,
apr_pool_t *pool)
{
struct wrap_3to2_report_baton *wrb = report_baton;
return wrb->reporter->delete_path(wrb->baton, path, pool);
}
static svn_error_t *wrap_3to2_link_path(void *report_baton,
const char *path,
const char *url,
svn_revnum_t revision,
svn_depth_t depth,
svn_boolean_t start_empty,
const char *lock_token,
apr_pool_t *pool)
{
struct wrap_3to2_report_baton *wrb = report_baton;
return wrb->reporter->link_path(wrb->baton, path, url, revision,
start_empty, lock_token, pool);
}
static svn_error_t *wrap_3to2_finish_report(void *report_baton,
apr_pool_t *pool)
{
struct wrap_3to2_report_baton *wrb = report_baton;
return wrb->reporter->finish_report(wrb->baton, pool);
}
static svn_error_t *wrap_3to2_abort_report(void *report_baton,
apr_pool_t *pool)
{
struct wrap_3to2_report_baton *wrb = report_baton;
return wrb->reporter->abort_report(wrb->baton, pool);
}
static const svn_ra_reporter3_t wrap_3to2_reporter = {
wrap_3to2_set_path,
wrap_3to2_delete_path,
wrap_3to2_link_path,
wrap_3to2_finish_report,
wrap_3to2_abort_report
};
svn_error_t *
svn_wc_crawl_revisions2(const char *path,
svn_wc_adm_access_t *adm_access,
const svn_ra_reporter2_t *reporter,
void *report_baton,
svn_boolean_t restore_files,
svn_boolean_t recurse,
svn_boolean_t use_commit_times,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
svn_wc_traversal_info_t *traversal_info,
apr_pool_t *pool)
{
struct wrap_3to2_report_baton wrb;
wrb.reporter = reporter;
wrb.baton = report_baton;
return svn_wc_crawl_revisions3(path,
adm_access,
&wrap_3to2_reporter, &wrb,
restore_files,
SVN_DEPTH_INFINITY_OR_FILES(recurse),
FALSE,
use_commit_times,
notify_func,
notify_baton,
traversal_info,
pool);
}
/*** Compatibility wrapper: turns an svn_ra_reporter_t into an
svn_ra_reporter2_t.
This code looks like it duplicates code in libsvn_ra/ra_loader.c,
but it does not. That code makes an new thing look like an old
thing; this code makes an old thing look like a new thing. ***/
struct wrap_2to1_report_baton {
const svn_ra_reporter_t *reporter;
void *baton;
};
static svn_error_t *wrap_2to1_set_path(void *report_baton,
const char *path,
svn_revnum_t revision,
svn_boolean_t start_empty,
const char *lock_token,
apr_pool_t *pool)
{
struct wrap_2to1_report_baton *wrb = report_baton;
return wrb->reporter->set_path(wrb->baton, path, revision, start_empty,
pool);
}
static svn_error_t *wrap_2to1_delete_path(void *report_baton,
const char *path,
apr_pool_t *pool)
{
struct wrap_2to1_report_baton *wrb = report_baton;
return wrb->reporter->delete_path(wrb->baton, path, pool);
}
static svn_error_t *wrap_2to1_link_path(void *report_baton,
const char *path,
const char *url,
svn_revnum_t revision,
svn_boolean_t start_empty,
const char *lock_token,
apr_pool_t *pool)
{
struct wrap_2to1_report_baton *wrb = report_baton;
return wrb->reporter->link_path(wrb->baton, path, url, revision,
start_empty, pool);
}
static svn_error_t *wrap_2to1_finish_report(void *report_baton,
apr_pool_t *pool)
{
struct wrap_2to1_report_baton *wrb = report_baton;
return wrb->reporter->finish_report(wrb->baton, pool);
}
static svn_error_t *wrap_2to1_abort_report(void *report_baton,
apr_pool_t *pool)
{
struct wrap_2to1_report_baton *wrb = report_baton;
return wrb->reporter->abort_report(wrb->baton, pool);
}
static const svn_ra_reporter2_t wrap_2to1_reporter = {
wrap_2to1_set_path,
wrap_2to1_delete_path,
wrap_2to1_link_path,
wrap_2to1_finish_report,
wrap_2to1_abort_report
};
svn_error_t *
svn_wc_crawl_revisions(const char *path,
svn_wc_adm_access_t *adm_access,
const svn_ra_reporter_t *reporter,
void *report_baton,
svn_boolean_t restore_files,
svn_boolean_t recurse,
svn_boolean_t use_commit_times,
svn_wc_notify_func_t notify_func,
void *notify_baton,
svn_wc_traversal_info_t *traversal_info,
apr_pool_t *pool)
{
struct wrap_2to1_report_baton wrb;
svn_wc__compat_notify_baton_t nb;
wrb.reporter = reporter;
wrb.baton = report_baton;
nb.func = notify_func;
nb.baton = notify_baton;
return svn_wc_crawl_revisions2(path, adm_access, &wrap_2to1_reporter, &wrb,
restore_files, recurse, use_commit_times,
svn_wc__compat_call_notify_func, &nb,
traversal_info,
pool);
}
/*** Copying stream ***/
/* A copying stream is a bit like the unix tee utility:
*
* It reads the SOURCE when asked for data and while returning it,
* also writes the same data to TARGET.
*/
struct copying_stream_baton
{
/* Stream to read input from. */
svn_stream_t *source;
/* Stream to write all data read to. */
svn_stream_t *target;
};
static svn_error_t *
read_handler_copy(void *baton, char *buffer, apr_size_t *len)
{
struct copying_stream_baton *btn = baton;
SVN_ERR(svn_stream_read(btn->source, buffer, len));
return svn_stream_write(btn->target, buffer, len);
}
static svn_error_t *
close_handler_copy(void *baton)
{
struct copying_stream_baton *btn = baton;
SVN_ERR(svn_stream_close(btn->target));
return svn_stream_close(btn->source);
}
/* Return a stream - allocated in POOL - which reads its input
* from SOURCE and, while returning that to the caller, at the
* same time writes that to TARGET.
*/
static svn_stream_t *
copying_stream(svn_stream_t *source,
svn_stream_t *target,
apr_pool_t *pool)
{
struct copying_stream_baton *baton;
svn_stream_t *stream;
baton = apr_palloc(pool, sizeof (*baton));
baton->source = source;
baton->target = target;
stream = svn_stream_create(baton, pool);
svn_stream_set_read(stream, read_handler_copy);
svn_stream_set_close(stream, close_handler_copy);
return stream;
}
svn_error_t *
svn_wc_transmit_text_deltas2(const char **tempfile,
unsigned char digest[],
const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t fulltext,
const svn_delta_editor_t *editor,
void *file_baton,
apr_pool_t *pool)
{
const char *tmp_base;
svn_txdelta_window_handler_t handler;
void *wh_baton;
svn_txdelta_stream_t *txdelta_stream;
apr_file_t *basefile = NULL;
apr_file_t *tempbasefile;
const char *base_digest_hex = NULL;
const unsigned char *base_digest = NULL;
const unsigned char *local_digest = NULL;
svn_error_t *err;
const svn_wc_entry_t *ent;
svn_stream_t *base_stream;
svn_stream_t *local_stream;
apr_time_t wf_time;
SVN_ERR(svn_wc_entry(&ent, path, adm_access, FALSE, pool));
/* Get timestamp of working file, to check for modifications during
commit. */
SVN_ERR(svn_io_file_affected_time(&wf_time, path, pool));
/* Translated input */
SVN_ERR(svn_wc_translated_stream(&local_stream, path, path,
adm_access, SVN_WC_TRANSLATE_TO_NF, pool));
tmp_base = svn_wc__text_base_path(path, TRUE, pool);
/* Alert the caller that we have created a temporary file that might
need to be cleaned up, if he asked for one. */
if (tempfile)
{
*tempfile = tmp_base;
/* Make an untranslated copy of the working file in the
administrative tmp area because a) we need to detranslate eol
and keywords anyway, and b) after the commit, we're going to
copy the tmp file to become the new text base anyway. */
SVN_ERR(svn_io_file_open(&tempbasefile, tmp_base,
APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
/* Wrap the translated stream with a new stream that writes the
translated contents into the new text base file as we read from it.
Note that the new text base file will be closed when the new stream
is closed. */
local_stream
= copying_stream(local_stream,
svn_stream_from_aprfile2(tempbasefile, FALSE, pool),
pool);
}
if (! fulltext)
{
if (! ent->checksum)
{
/*### FIXME: The entries file should hold a checksum */
unsigned char tmp_digest[APR_MD5_DIGESTSIZE];
/* If there's no checksum in this entry, calculate one */
const char *tb = svn_wc__text_base_path (path, FALSE, pool);
SVN_ERR (svn_io_file_checksum (tmp_digest, tb, pool));
base_digest_hex = svn_md5_digest_to_cstring_display(tmp_digest, pool);
}
else
base_digest_hex = ent->checksum;
SVN_ERR(svn_wc__open_text_base(&basefile, path, APR_READ, pool));
}
/* Tell the editor that we're about to apply a textdelta to the
file baton; the editor returns to us a window consumer and baton. */
SVN_ERR(editor->apply_textdelta
(file_baton, base_digest_hex, pool, &handler, &wh_baton));
/* Create a text-delta stream object that pulls
data out of the two files. */
base_stream = svn_stream_from_aprfile2(basefile, TRUE, pool);
if (! fulltext)
base_stream
= svn_stream_checksummed(base_stream, &base_digest, NULL, TRUE, pool);
svn_txdelta(&txdelta_stream, base_stream, local_stream, pool);
/* Pull windows from the delta stream and feed to the consumer. */
err = svn_txdelta_send_txstream(txdelta_stream, handler, wh_baton, pool);
/* Close the two streams to force writing the digest,
if we already have an error, ignore this one. */
if (err)
{
svn_error_clear(svn_stream_close(base_stream));
svn_error_clear(svn_stream_close(local_stream));
}
else
{
SVN_ERR(svn_stream_close(base_stream));
SVN_ERR(svn_stream_close(local_stream));
}
/* If we have an error, it may be caused by a corrupt text base.
Check the checksum and discard `err' if they don't match. */
if (! fulltext && ent->checksum && base_digest)
{
/*### FIXME: The entries file should hold a checksum,
meaning the above condition should not include ent->checksum */
base_digest_hex = svn_md5_digest_to_cstring_display(base_digest, pool);
if (strcmp(base_digest_hex, ent->checksum) != 0)
{
/* The entry checksum does not match the actual text
base checksum. Extreme badness. Of course,
theoretically we could just switch to
fulltext transmission here, and everything would
work fine; after all, we're going to replace the
text base with a new one in a moment anyway, and
we'd fix the checksum then. But it's better to
error out. People should know that their text
bases are getting corrupted, so they can
investigate. Other commands could be affected,
too, such as `svn diff'. */
/* Deliberately ignore errors; the error about the
checksum mismatch is more important to return. */
svn_error_clear(err);
svn_error_clear(svn_io_remove_file(tmp_base, pool));
return svn_error_createf
(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
_("Checksum mismatch for '%s'; "
"expected: '%s', actual: '%s'"),
svn_path_local_style(svn_wc__text_base_path(path, FALSE, pool),
pool),
ent->checksum, base_digest_hex);
}
}
/* Now, handle that delta transmission error if any, so we can stop
thinking about it after this point. */
SVN_ERR_W(err, apr_psprintf(pool,
_("While preparing '%s' for commit"),
svn_path_local_style(path, pool)));
/* Close base file, if it was opened. */
if (basefile)
SVN_ERR(svn_wc__close_text_base(basefile, path, 0, pool));
local_digest = svn_txdelta_md5_digest(txdelta_stream);
if (digest)
memcpy(digest, local_digest, APR_MD5_DIGESTSIZE);
/* Close the file baton, and get outta here. */
return editor->close_file
(file_baton, svn_md5_digest_to_cstring(local_digest, pool), pool);
}
svn_error_t *
svn_wc_transmit_text_deltas(const char *path,
svn_wc_adm_access_t *adm_access,
svn_boolean_t fulltext,
const svn_delta_editor_t *editor,
void *file_baton,
const char **tempfile,
apr_pool_t *pool)
{
return svn_wc_transmit_text_deltas2(tempfile, NULL, path, adm_access,
fulltext, editor, file_baton, pool);
}
svn_error_t *
svn_wc_transmit_prop_deltas(const char *path,
svn_wc_adm_access_t *adm_access,
const svn_wc_entry_t *entry,
const svn_delta_editor_t *editor,
void *baton,
const char **tempfile,
apr_pool_t *pool)
{
int i;
apr_array_header_t *propmods;
if (tempfile)
*tempfile = NULL;
/* Get an array of local changes by comparing the hashes. */
SVN_ERR(svn_wc_get_prop_diffs(&propmods, NULL,
path, adm_access, pool));
/* Apply each local change to the baton */
for (i = 0; i < propmods->nelts; i++)
{
const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t);
if (entry->kind == svn_node_file)
SVN_ERR(editor->change_file_prop(baton, p->name, p->value, pool));
else
SVN_ERR(editor->change_dir_prop(baton, p->name, p->value, pool));
}
return SVN_NO_ERROR;
}