blob: df550a8210faa3ad3f4553eea7f9780e5a54741d [file] [log] [blame]
/*
* adm_crawler.c: report local WC mods to an Editor.
*
* ====================================================================
* Copyright (c) 2000-2002 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 <assert.h>
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_wc.h"
#include "svn_io.h"
#include "svn_sorts.h"
#include "svn_delta.h"
#include "wc.h"
#include "adm_files.h"
#include "props.h"
#include "translate.h"
/* Helper for report_revisions().
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. */
static svn_error_t *
restore_file (svn_stringbuf_t *file_path,
apr_pool_t *pool)
{
svn_stringbuf_t *text_base_path, *tmp_text_base_path;
svn_wc_keywords_t *keywords;
enum svn_wc__eol_style eol_style;
const char *eol;
text_base_path = svn_wc__text_base_path (file_path, FALSE, pool);
tmp_text_base_path = svn_wc__text_base_path (file_path, TRUE, pool);
SVN_ERR (svn_io_copy_file (text_base_path->data, tmp_text_base_path->data,
FALSE, pool));
SVN_ERR (svn_wc__get_eol_style (&eol_style, &eol,
file_path->data, pool));
SVN_ERR (svn_wc__get_keywords (&keywords,
file_path->data, NULL, pool));
/* When copying the tmp-text-base out to the working copy, make
sure to do any eol translations or keyword substitutions,
as dictated by the property values. If these properties
are turned off, then this is just a normal copy. */
SVN_ERR (svn_wc_copy_and_translate (tmp_text_base_path->data,
file_path->data,
eol, FALSE, /* don't repair */
keywords,
TRUE, /* expand keywords */
pool));
SVN_ERR (svn_io_remove_file (tmp_text_base_path->data, 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 WC_PATH.
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 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. */
static svn_error_t *
report_revisions (svn_stringbuf_t *wc_path,
svn_stringbuf_t *dir_path,
svn_revnum_t dir_rev,
const svn_ra_reporter_t *reporter,
void *report_baton,
svn_wc_notify_func_t notify_func,
void *notify_baton,
svn_boolean_t restore_files,
svn_boolean_t recurse,
apr_pool_t *pool)
{
apr_hash_t *entries, *dirents;
apr_hash_index_t *hi;
apr_pool_t *subpool = svn_pool_create (pool);
/* Construct the actual 'fullpath' = wc_path + dir_path */
svn_stringbuf_t *full_path = svn_stringbuf_dup (wc_path, subpool);
svn_path_add_component (full_path, dir_path);
/* Get both the SVN Entries and the actual on-disk entries. */
SVN_ERR (svn_wc_entries_read (&entries, full_path, subpool));
SVN_ERR (svn_io_get_dirents (&dirents, full_path, subpool));
/* Do the real reporting and recursing. */
/* Looping over current directory's SVN entries: */
for (hi = apr_hash_first (subpool, entries); hi; hi = apr_hash_next (hi))
{
const void *key;
const char *keystring;
apr_ssize_t klen;
void *val;
svn_stringbuf_t *current_entry_name;
svn_wc_entry_t *current_entry;
svn_stringbuf_t *full_entry_path;
enum svn_node_kind *dirent_kind;
svn_boolean_t missing = FALSE;
/* Get the next entry */
apr_hash_this (hi, &key, &klen, &val);
keystring = (const char *) key;
current_entry = (svn_wc_entry_t *) val;
/* Compute the name of the entry. Skip THIS_DIR altogether. */
if (! strcmp (keystring, SVN_WC_ENTRY_THIS_DIR))
continue;
else
current_entry_name = svn_stringbuf_create (keystring, subpool);
/* Compute the complete path of the entry, relative to dir_path. */
full_entry_path = svn_stringbuf_dup (dir_path, subpool);
if (current_entry_name)
svn_path_add_component (full_entry_path, current_entry_name);
/* The Big Tests: */
/* Is the entry on disk? Set a flag if not. */
dirent_kind = (enum svn_node_kind *) apr_hash_get (dirents, key, klen);
if (! dirent_kind)
missing = TRUE;
/* From here on out, ignore any entry scheduled for addition
or deletion */
if (current_entry->schedule == svn_wc_schedule_normal)
/* The entry exists on disk, and isn't `deleted'. */
{
if (current_entry->kind == svn_node_file)
{
if (dirent_kind && (*dirent_kind != svn_node_file))
{
/* If the dirent changed kind, report it as missing.
Later on, the update editor will return an
'obstructed update' error. :) */
SVN_ERR (reporter->delete_path (report_baton,
full_entry_path->data));
continue; /* move to next entry */
}
if (missing && restore_files)
{
svn_stringbuf_t *long_file_path
= svn_stringbuf_dup (full_path, pool);
svn_path_add_component (long_file_path, current_entry_name);
/* Recreate file from text-base. */
SVN_ERR (restore_file (long_file_path, pool));
/* Report the restoration to the caller. */
if (notify_func != NULL)
(*notify_func) (notify_baton,
svn_wc_notify_restore,
long_file_path->data);
}
/* Possibly report a differing revision. */
if (current_entry->revision != dir_rev)
SVN_ERR (reporter->set_path (report_baton,
full_entry_path->data,
current_entry->revision));
}
else if (current_entry->kind == svn_node_dir && recurse)
{
if (missing)
{
/* We can't recreate dirs locally, so report as missing. */
SVN_ERR (reporter->delete_path (report_baton,
full_entry_path->data));
continue; /* move on to next entry */
}
if (dirent_kind && (*dirent_kind != svn_node_dir))
/* No excuses here. If the user changed a
revision-controlled directory into something else,
the working copy is FUBAR. It can't receive
updates within this dir anymore. Throw a real
error. */
return svn_error_createf
(SVN_ERR_WC_OBSTRUCTED_UPDATE, 0, NULL, subpool,
"The entry '%s' is no longer a directory,\n"
"which prevents proper updates.\n"
"Please remove this entry and try updating again.",
full_entry_path->data);
/* Otherwise, possibly report a differing revision, and
recurse. */
{
svn_wc_entry_t *subdir_entry;
svn_stringbuf_t *megalong_path =
svn_stringbuf_dup (wc_path, subpool);
svn_path_add_component (megalong_path, full_entry_path);
SVN_ERR (svn_wc_entry (&subdir_entry, megalong_path, subpool));
if (subdir_entry->revision != dir_rev)
SVN_ERR (reporter->set_path (report_baton,
full_entry_path->data,
subdir_entry->revision));
/* Recurse. */
SVN_ERR (report_revisions (wc_path,
full_entry_path,
subdir_entry->revision,
reporter, report_baton,
notify_func, notify_baton,
restore_files, recurse,
subpool));
}
} /* end directory case */
} /* end 'entry exists on disk' */
} /* 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 ***/
/* This is the main driver of the working copy state "reporter", used
for updates. */
svn_error_t *
svn_wc_crawl_revisions (svn_stringbuf_t *path,
const svn_ra_reporter_t *reporter,
void *report_baton,
svn_boolean_t restore_files,
svn_boolean_t recurse,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
svn_error_t *err;
svn_wc_entry_t *entry;
svn_revnum_t base_rev = SVN_INVALID_REVNUM;
svn_boolean_t missing = FALSE;
/* 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, pool));
base_rev = entry->revision;
if (base_rev == SVN_INVALID_REVNUM)
{
svn_stringbuf_t *parent_name = svn_stringbuf_dup (path, pool);
svn_wc_entry_t *parent_entry;
svn_path_remove_component (parent_name);
SVN_ERR (svn_wc_entry (&parent_entry, parent_name, 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));
if (entry->schedule != svn_wc_schedule_delete)
{
apr_finfo_t info;
apr_status_t apr_err;
apr_err = apr_stat (&info, path->data, APR_FINFO_MIN, pool);
if (APR_STATUS_IS_ENOENT(apr_err))
missing = TRUE;
}
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, "");
if (err)
{
/* Clean up the fs transaction. */
svn_error_t *fserr;
fserr = reporter->abort_report (report_baton);
if (fserr)
return svn_error_quick_wrap (fserr, "Error aborting report.");
else
return err;
}
}
else
{
/* Recursively crawl ROOT_DIRECTORY and report differing
revisions. */
err = report_revisions (path,
svn_stringbuf_create ("", pool),
base_rev,
reporter, report_baton,
notify_func, notify_baton,
restore_files, recurse, pool);
if (err)
{
/* Clean up the fs transaction. */
svn_error_t *fserr;
fserr = reporter->abort_report (report_baton);
if (fserr)
return svn_error_quick_wrap (fserr, "Error aborting report.");
else
return err;
}
}
}
else if (entry->kind == svn_node_file)
{
if (missing && restore_files)
{
/* Recreate file from text-base. */
SVN_ERR (restore_file (path, pool));
/* Report the restoration to the caller. */
if (notify_func != NULL)
(*notify_func) (notify_baton, svn_wc_notify_restore, path->data);
}
if (entry->revision != base_rev)
{
/* 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);
if (err)
{
/* Clean up the fs transaction. */
svn_error_t *fserr;
fserr = reporter->abort_report (report_baton);
if (fserr)
return svn_error_quick_wrap (fserr, "Error aborting report.");
else
return err;
}
}
}
/* Finish the report, which causes the update editor to be driven. */
err = reporter->finish_report (report_baton);
if (err)
{
/* Clean up the fs transaction. */
svn_error_t *fserr;
fserr = reporter->abort_report (report_baton);
if (fserr)
return svn_error_quick_wrap (fserr, "Error aborting report.");
else
return err;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_transmit_text_deltas (svn_stringbuf_t *path,
svn_boolean_t fulltext,
const svn_delta_editor_t *editor,
void *file_baton,
svn_stringbuf_t **tempfile,
apr_pool_t *pool)
{
svn_stringbuf_t *tmpf, *tmp_base;
apr_status_t status;
svn_txdelta_window_handler_t handler;
void *wh_baton;
svn_txdelta_stream_t *txdelta_stream;
apr_file_t *localfile = NULL;
apr_file_t *basefile = NULL;
/* Tell the editor that we're about to apply a textdelta to the
file baton; the editor returns to us a window consumer routine
and baton. If there is no handler provided, just close the file
and get outta here. */
SVN_ERR (editor->apply_textdelta (file_baton, &handler, &wh_baton));
if (! handler)
return editor->close_file (file_baton);
/* Make an untranslated copy of the working file in the
adminstrative tmp area because a) we want this to work even if
someone changes the working file while we're generating the
txdelta, b) we need to detranslate eol and keywords anyway, and
c) after the commit, we're going to copy the tmp file to become
the new text base anyway.
Note that since the translation routine doesn't let you choose
the filename, we have to do one extra copy. But what the heck,
we're about to generate an svndiff anyway. */
SVN_ERR (svn_wc_translated_file (&tmpf, path, pool));
tmp_base = svn_wc__text_base_path (path, TRUE, pool);
SVN_ERR (svn_io_copy_file (tmpf->data, tmp_base->data, FALSE, pool));
/* Alert the caller that we have created a temporary file that might
need to be cleaned up. */
if (tempfile)
*tempfile = tmp_base;
/* If the translation step above actually created a new file, delete
the old one. */
if (tmpf != path)
SVN_ERR (svn_io_remove_file (tmpf->data, pool));
/* Open a filehandle for tmp text-base. */
if ((status = apr_file_open (&localfile, tmp_base->data,
APR_READ, APR_OS_DEFAULT, pool)))
return svn_error_createf (status, 0, NULL, pool,
"do_apply_textdelta: error opening '%s'",
tmp_base->data);
/* If we're not sending fulltext, we'll be sending diffs against the
text-base. */
if (! fulltext)
SVN_ERR (svn_wc__open_text_base (&basefile, path, APR_READ, pool));
/* Create a text-delta stream object that pulls data out of the two
files. */
svn_txdelta (&txdelta_stream,
svn_stream_from_aprfile (basefile, pool),
svn_stream_from_aprfile (localfile, pool),
pool);
/* Pull windows from the delta stream and feed to the consumer. */
SVN_ERR (svn_txdelta_send_txstream (txdelta_stream, handler,
wh_baton, pool));
/* Close the two files */
if ((status = apr_file_close (localfile)))
return svn_error_create (status, 0, NULL, pool,
"error closing local file");
if (basefile)
SVN_ERR (svn_wc__close_text_base (basefile, path, 0, pool));
/* Close the file baton, and get outta here. */
return editor->close_file (file_baton);
}
svn_error_t *
svn_wc_transmit_prop_deltas (svn_stringbuf_t *path,
svn_node_kind_t kind,
const svn_delta_editor_t *editor,
void *baton,
svn_stringbuf_t **tempfile,
apr_pool_t *pool)
{
int i;
svn_stringbuf_t *props, *props_base, *props_tmp;
apr_array_header_t *propmods;
apr_hash_t *localprops = apr_hash_make (pool);
apr_hash_t *baseprops = apr_hash_make (pool);
/* First, get the prop_path from the original path */
SVN_ERR (svn_wc__prop_path (&props, path, 0, pool));
/* Get the full path of the prop-base `pristine' file */
SVN_ERR (svn_wc__prop_base_path (&props_base, path, 0, pool));
/* Copy the local prop file to the administrative temp area */
SVN_ERR (svn_wc__prop_path (&props_tmp, path, 1, pool));
SVN_ERR (svn_io_copy_file (props->data, props_tmp->data, FALSE, pool));
/* Alert the caller that we have created a temporary file that might
need to be cleaned up. */
if (tempfile)
*tempfile = props_tmp;
/* Load all properties into hashes */
SVN_ERR (svn_wc__load_prop_file (props_tmp->data, localprops, pool));
SVN_ERR (svn_wc__load_prop_file (props_base->data, baseprops, pool));
/* Get an array of local changes by comparing the hashes. */
SVN_ERR (svn_wc_get_local_propchanges (&propmods, localprops,
baseprops, 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 (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;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */