Great, thanks Philip!
Apologies that this can't get reviewed & applied immediately (need to
finish M6 stuff first), but it is near the top of the list and will
get reviewed very soon. Watch issue #422.
-K
Philip Martin <pmartin@uklinux.net> writes:
> My editor/crawler patch to provide 'svn diff -r' is now ready. I have
> removed my earlier rdiff command and integrated the behaviour into the
> existing diff command. Plain 'svn diff' remains a local only diff, I
> haven't changed that code or behavior. Only when the -r option is
> specified is the new repository diff code used.
>
> There are a few outstanding issues, marked TODO in the code, however I
> think the code is in a state to be merged now. I've written some
> tests, however I have never even looked at python before, so that code
> may be a bit clunky :-)
>
> This patch, against revision 394, was produced using the new code.
>
> Philip
>
>
>
> * subversion/include/svn_wc.h: Add svn_wc_diff_cmd_t the new diff
> callback, and svn_wc_get_diff_editor
>
> * subversion/libsvn_wc/diff.c: Create. Provides
> svn_wc_get_diff_editor.
>
> This code uses an svn_delta_edit_fns_t editor driven by
> svn_wc_crawl_revisions (like the update command) to retrieve the
> differences between the working copy and the requested repository
> version. Rather than updating the working copy, this new editor creates
> temporary files that contain the pristine repository versions. When the
> crawler closes the files the editor calls back to a client layer
> function to compare the working copy and the temporary file. There is
> only ever one temporary file in existence at any time.
>
> When the crawler closes a directory, the editor then calls back to the
> client layer to compare any remaining files that may have been modified
> locally. Added directories do not have corresponding temporary
> directories created, as they are not needed.
>
> * subversion/libsvn_wc/wc.h: Add svn_wc__empty_file_path,
> svn_wc__open_empty_file and svn_wc__close_empty_file.
>
> * subversion/libsvn_wc/adm_files.c: Add svn_wc__empty_file_path,
> svn_wc__open_empty_file, and svn_wc__close_empty_file to support a
> permanent empty file in the admin area. init_adm: create the empty
> file.
>
> * subversion/include/svn_client.h: Add svn_client_diff
>
> * subversion/libsvn_client/diff.c: Added svn_client_diff, the client
> interface to the new diff code. Added svn_client__diff_cmd, the
> client layer callback to compare two files.
>
> * subversion/clients/cmdline/diff-cmd.c: Added svn_cl__wc_repository_diff
> a substitute 'svn diff' command using the new diff code. svn_cl__diff
> calls svn_cl__wc_repository_diff when the -r option is given.
>
> * subversion/clients/cmdline/main.c: modify the usage string to
> reflect the new diff functionality
>
> * subversion/tests/clients/cmdline/diff_tests.py: Create. Provides
> tests of the new diff code.
>
>
>
> Index: ./subversion/include/svn_wc.h
> ===================================================================
> --- ./subversion/include/svn_wc.h
> +++ ./subversion/include/svn_wc.h Mon Nov 5 01:41:13 2001
> @@ -691,6 +691,41 @@
>
> /*** Diffs ***/
>
> +
> +/* The function type called by the diff editor when it has determined the
> + * two files that are to be diffed.
> + *
> + * PATH1 and PATH2 are the two files to be compared, these files
> + * exist. Since the PATH1 file may be temporary, it is possible that it is
> + * not in the "correct" location in the working copy, if this is the case
> + * then LABEL will be non-null and will contain the "correct" location.
> + *
> + * BATON is passed through by the diff editor.
> + */
> +typedef svn_error_t *(*svn_wc_diff_cmd_t)(svn_stringbuf_t *path1,
> + svn_stringbuf_t *path2,
> + svn_stringbuf_t *label,
> + void *baton);
> +
> +
> +/* Return an EDITOR/EDIT_BATON for diffing a working copy against the
> + * repository.
> + *
> + * ANCHOR and TARGET have the same meaning as svn_wc_get_update_editor.
> + *
> + * DIFF_CMD/DIFF_CMD_BATON are the function/baton to be called when two
> + * files are to be compared.
> + */
> +svn_error_t *svn_wc_get_diff_editor (svn_stringbuf_t *anchor,
> + svn_stringbuf_t *target,
> + svn_wc_diff_cmd_t diff_cmd,
> + void *diff_cmd_baton,
> + svn_boolean_t recurse,
> + const svn_delta_edit_fns_t **editor,
> + void **edit_baton,
> + apr_pool_t *pool);
> +
> +
> /* Given a PATH to a wc file, return a PRISTINE_PATH which points to a
> pristine version of the file. This is needed so clients can do
> diffs. If the WC has no text-base, return a NULL instead of a
> Index: ./subversion/include/svn_client.h
> ===================================================================
> --- ./subversion/include/svn_client.h
> +++ ./subversion/include/svn_client.h Sat Nov 3 17:29:27 2001
> @@ -397,6 +397,25 @@
> apr_pool_t *pool);
>
>
> +/* Given a PATH in the working copy, compare the working copy against a
> + * revision in the repository. The repository revision can be specified as
> + * REVISION, or as a time TM.
> + *
> + * If PATH is a directory and RECURSE is true, this will be a recursive
> + * operation.
> + *
> + * DIFF_OPTIONS is used to pass additional command line options to the diff
> + * processes invoked to compare files.
> + */
> +svn_error_t *svn_client_diff (svn_stringbuf_t *path,
> + apr_array_header_t *diff_options,
> + svn_client_auth_baton_t *auth_baton,
> + svn_revnum_t revision,
> + apr_time_t tm,
> + svn_boolean_t recurse,
> + apr_pool_t *pool);
> +
> +
> /* Recursively cleanup a working copy directory DIR, finishing any
> incomplete operations, removing lockfiles, etc. */
> svn_error_t *
> Index: ./subversion/libsvn_wc/diff.c
> ===================================================================
> --- ./subversion/libsvn_wc/diff.c
> +++ ./subversion/libsvn_wc/diff.c Mon Nov 5 01:39:24 2001
> @@ -0,0 +1,747 @@
> +/*
> + * diff.c -- The diff editor for comparing the working copy against the
> + * repository.
> + *
> + * ====================================================================
> + * Copyright (c) 2001 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/.
> + * ====================================================================
> + */
> +
> +/*
> + * This code uses an svn_delta_edit_fns_t editor driven by
> + * svn_wc_crawl_revisions (like the update command) to retrieve the
> + * differences between the working copy and the requested repository
> + * version. Rather than updating the working copy, this new editor creates
> + * temporary files that contain the pristine repository versions. When the
> + * crawler closes the files the editor calls back to a client layer
> + * function to compare the working copy and the temporary file. There is
> + * only ever one temporary file in existence at any time.
> + *
> + * When the crawler closes a directory, the editor then calls back to the
> + * client layer to compare any remaining files that may have been modified
> + * locally. Added directories do not have corresponding temporary
> + * directories created, as they are not needed.
> + *
> + * ### TODO: It might be better if the temporary files were not created in
> + * the admin's temp area, but in a more general area (/tmp, $TMPDIR) as
> + * then diff could be run on a read-only working copy.
> + *
> + * ### TODO: Provide an interface to directory_elements_diff that can
> + * be called by the client, to do local only diff without contacting the
> + * repository.
> + *
> + * ### TODO: Replacements where the node kind changes needs support. It
> + * mostly works when the change is in the repository, but not when it is
> + * in the working copy.
> + *
> + * ### TODO: Do we need to support copyfrom?
> + *
> + */
> +
> +#include <apr_hash.h>
> +#include "wc.h"
> +#include "svn_pools.h"
> +#include <assert.h>
> +
> +/* Overall diff editor baton.
> + *
> + * ANCHOR/TARGET represent the base of the hierarchy to be compared.
> + * DIFF_CMD/DIFF_CMD_BATON provide the callback that implements the file
> + * comparison function.
> + *
> + * RECURSE flags whether to diff recursively or not.
> + */
> +struct edit_baton {
> + svn_stringbuf_t *anchor;
> + svn_stringbuf_t *target;
> + svn_wc_diff_cmd_t diff_cmd;
> + void *diff_cmd_baton;
> + svn_boolean_t recurse;
> + apr_pool_t *pool;
> +};
> +
> +/* Directory level baton.
> + *
> + * ADDED gets set if the directory is added rather than replaced/unchanged.
> + *
> + * PATH is the "correct" path of the directory, but it may not exist in the
> + * working copy.
> + *
> + * ENTRIES is a list of directory elements that have already been compared
> + * and thus can be ignored when recursively comparing the directory.
> + *
> + * DIR_BATON is the baton for the parent directory, it will be null if this
> + * is the root of the hierarchy to be compared. EDIT_BATON is the overall
> + * editor baton.
> + */
> +struct dir_baton {
> + svn_boolean_t added;
> + svn_stringbuf_t *path;
> + apr_hash_t *compared;
> + struct dir_baton *dir_baton;
> + struct edit_baton *edit_baton;
> + apr_pool_t *pool;
> +};
> +
> +/* File level baton.
> + *
> + * ADDED gets set if the directory is added rather than replaced.
> + *
> + * PATH is the "correct" path of the file, but it may not exist in the
> + * working copy. WC_PATH is the ancestor path in the working copy that does
> + * exist.
> + *
> + * When generating the repository version of the file, ORIGINAL_FILE is
> + * base version of the file. TEMP_FILE is the pristine repository file
> + * obtained by applying the repository diffs to ORIGINAL_FILE.
> + *
> + * APPLY_HANDLER/APPLY_BATON represent the delta applcation baton.
> + *
> + * DIR_BATON is the baton for the parent directory, it will be null if this
> + * is the root of the hierarchy to be compared. EDIT_BATON is the overall
> + * editor baton.
> + */
> +struct file_baton {
> + svn_boolean_t added;
> + svn_stringbuf_t *path;
> + svn_stringbuf_t *wc_path;
> + apr_file_t *original_file;
> + apr_file_t *temp_file;
> + svn_txdelta_window_handler_t apply_handler;
> + void *apply_baton;
> + struct dir_baton *dir_baton;
> + struct edit_baton *edit_baton;
> + apr_pool_t *pool;
> +};
> +
> +/* Create a new directory baton. PARENT_BATON is the baton of the parent
> + * directory, it will be null if this is the root of the comparison
> + * hierarchy. The directory and its parent may or may not exist in the
> + * working copy.
> + */
> +static struct dir_baton *
> +make_dir_baton (svn_stringbuf_t *name,
> + struct dir_baton *parent_baton,
> + struct edit_baton *edit_baton,
> + apr_pool_t *pool)
> +{
> + struct dir_baton *dir_baton = apr_pcalloc (pool, sizeof (*dir_baton));
> +
> + dir_baton->dir_baton = parent_baton;
> + dir_baton->edit_baton = edit_baton;
> + dir_baton->pool = svn_pool_create (pool);
> +
> + dir_baton->compared = apr_hash_make (dir_baton->pool);
> +
> + if (parent_baton)
> + {
> + /* The path is allocated in the parent's pool since it will be put
> + into the parent's list of already diff'd entries. */
> + dir_baton->path = svn_stringbuf_dup (parent_baton->path,
> + parent_baton->pool);
> + }
> + else
> + {
> + dir_baton->path = svn_stringbuf_dup (edit_baton->anchor, dir_baton->pool);
> + }
> +
> + if (name)
> + svn_path_add_component (dir_baton->path, name, svn_path_local_style);
> +
> + return dir_baton;
> +}
> +
> +/* Create a new file baton. PARENT_BATON is the baton of the parent
> + * directory. The file and directory may or may not exist in the working
> + * copy.
> + */
> +static struct file_baton *
> +make_file_baton (svn_stringbuf_t *name,
> + struct dir_baton *parent_baton)
> +{
> + struct file_baton *file_baton = apr_pcalloc (parent_baton->pool,
> + sizeof (*file_baton));
> +
> + file_baton->dir_baton = parent_baton;
> + file_baton->edit_baton = parent_baton->edit_baton;
> + file_baton->pool = svn_pool_create (parent_baton->pool);
> +
> + /* The path is allocated in the directory's pool since it will be put
> + into the directory's list of already diff'd entries. */
> + file_baton->path = svn_stringbuf_dup (parent_baton->path, parent_baton->pool);
> + svn_path_add_component (file_baton->path, name, svn_path_local_style);
> +
> + /* If the parent directory is added rather than replaced it does not
> + exist in the working copy. Determine a working copy path that does
> + exist. */
> + if (parent_baton->added)
> + {
> + struct dir_baton *wc_dir_baton = parent_baton;
> +
> + /* Ascend until a directory is not being added, this will be a
> + directory that does exist. This must terminate since the root of
> + the comparison cannot be added. */
> + while (wc_dir_baton->added)
> + wc_dir_baton = wc_dir_baton->dir_baton;
> +
> + file_baton->wc_path = svn_stringbuf_dup (wc_dir_baton->path,
> + file_baton->pool);
> + svn_path_add_component (file_baton->wc_path, name, svn_path_local_style);
> + }
> + else
> + {
> + file_baton->wc_path = file_baton->path;
> + }
> +
> + return file_baton;
> +}
> +
> +
> +/* Called by directory_elements_diff when a file is to be compared. At this
> + * stage we are dealing with a file that does exist in the working copy.
> + *
> + * ### TODO: Need to work on replace if the new filename used to be a
> + * directory.
> + */
> +static svn_error_t *
> +file_diff (struct dir_baton *dir_baton,
> + svn_stringbuf_t *path,
> + svn_wc_entry_t *entry,
> + svn_boolean_t deleted)
> +{
> + svn_wc_diff_cmd_t diff_cmd = dir_baton->edit_baton->diff_cmd;
> + svn_stringbuf_t *pristine_copy, *empty_file;
> + svn_boolean_t modified;
> + enum svn_wc_schedule_t schedule = entry->schedule;
> +
> + /* If the directory is being deleted, then this file will need to be
> + deleted. */
> + if (deleted)
> + schedule = svn_wc_schedule_delete;
> +
> + switch (schedule)
> + {
> + /* Replace is treated like a delete plus an add: two
> + comparisons are generated, first one for the delete and
> + then one for the add. */
> + case svn_wc_schedule_replace:
> + case svn_wc_schedule_delete:
> + pristine_copy = svn_wc__text_base_path (path, FALSE, dir_baton->pool);
> + empty_file = svn_wc__empty_file_path (path, dir_baton->pool);
> +
> + SVN_ERR (diff_cmd (pristine_copy, empty_file, path,
> + dir_baton->edit_baton->diff_cmd_baton));
> +
> + /* Replace will fallthrough! */
> + if (schedule == svn_wc_schedule_delete)
> + break;
> +
> + case svn_wc_schedule_add:
> + empty_file = svn_wc__empty_file_path (path, dir_baton->pool);
> +
> + SVN_ERR (diff_cmd (empty_file, path, path,
> + dir_baton->edit_baton->diff_cmd_baton));
> + break;
> +
> + default:
> + SVN_ERR (svn_wc_text_modified_p (&modified, path, dir_baton->pool));
> + if (modified)
> + {
> + pristine_copy = svn_wc__text_base_path (path, FALSE, dir_baton->pool);
> + SVN_ERR (diff_cmd (pristine_copy, path, path,
> + dir_baton->edit_baton->diff_cmd_baton));
> + }
> + break;
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Called when the directory is closed to compare any elements that have
> + * not yet been compared. This identifies local, working copy only
> + * changes. At this stage we are dealing with files/directories that do
> + * exist in the working copy.
> + */
> +static svn_error_t *
> +directory_elements_diff (struct dir_baton *dir_baton,
> + svn_boolean_t deleted)
> +{
> + apr_hash_t *entries;
> + apr_hash_index_t *hi;
> + svn_boolean_t in_anchor_not_target;
> +
> + /* This directory should have been been unchanged or replaced, not
> + added. */
> + assert (!dir_baton->added);
> +
> + /* Determine if this is the anchor directory if the anchor is different
> + to the target */
> + if (dir_baton->edit_baton->target
> + && !svn_path_compare_paths (dir_baton->path,
> + dir_baton->edit_baton->anchor,
> + svn_path_local_style))
> + in_anchor_not_target = TRUE;
> + else
> + in_anchor_not_target = FALSE;
> +
> + SVN_ERR (svn_wc_entries_read (&entries, dir_baton->path, dir_baton->pool));
> +
> + for (hi = apr_hash_first (dir_baton->pool, entries); hi;
> + hi = apr_hash_next (hi))
> + {
> + svn_wc_entry_t *entry;
> + const char *key;
> + svn_stringbuf_t *path, *name;
> + struct dir_baton *subdir_baton;
> +
> + apr_hash_this(hi, (const void **)&key, NULL, (void **)&entry);
> +
> + /* Skip entry for the directory itself. */
> + if (strcmp (key, SVN_WC_ENTRY_THIS_DIR) == 0)
> + continue;
> +
> + /* Skip this entry if we are in the anchor and neither the anchor nor
> + the entry is the target */
> + name = svn_stringbuf_create (key, dir_baton->pool);
> + if (in_anchor_not_target
> + && !svn_stringbuf_compare (dir_baton->edit_baton->target, name))
> + continue;
> +
> + path = svn_stringbuf_dup (dir_baton->path, dir_baton->pool);
> + svn_path_add_component (path, name, svn_path_local_style);
> +
> + /* Skip entry if it is in the list of entries already diff'd. */
> + if (apr_hash_get (dir_baton->compared, path->data, path->len))
> + continue;
> +
> + switch (entry->kind)
> + {
> + case svn_node_file:
> + SVN_ERR (file_diff (dir_baton, path, entry, deleted));
> + break;
> +
> + case svn_node_dir:
> + if (entry->schedule == svn_wc_schedule_replace)
> + {
> + /* ### TODO: Don't know how to do this bit. How do I get
> + information about what is being replaced? If it was a
> + directory then the directory elements are also going to be
> + deleted. We need to show deletion diffs for these
> + files. If it was a file we need to show a deletion diff
> + for that file. */
> + }
> +
> + /* Check the subdir if in the anchor (the subdir is the target), or
> + if recursive */
> + if (in_anchor_not_target || dir_baton->edit_baton->recurse)
> + {
> + subdir_baton = make_dir_baton (name, dir_baton,
> + dir_baton->edit_baton,
> + dir_baton->pool);
> + subdir_baton->added = FALSE;
> +
> + SVN_ERR (directory_elements_diff (subdir_baton, deleted));
> + svn_pool_destroy (subdir_baton->pool);
> + }
> + break;
> +
> + default:
> + break;
> + }
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function. The root of the comparison
> + * hierarchy
> + */
> +static svn_error_t *
> +set_target_revision (void *edit_baton, svn_revnum_t target_revision)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function. The root of the comparison
> + * hierarchy
> + */
> +static svn_error_t *
> +replace_root (void *edit_baton,
> + svn_revnum_t base_revision,
> + void **root_baton)
> +{
> + struct edit_baton *eb = edit_baton;
> + struct dir_baton *b;
> +
> + b = make_dir_baton (NULL, NULL, eb, eb->pool);
> + *root_baton = b;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +delete_entry (svn_stringbuf_t *name,
> + void *parent_baton)
> +{
> + struct dir_baton *pb = parent_baton;
> + apr_pool_t *pool = svn_pool_create (pb->pool);
> + svn_stringbuf_t *path = svn_stringbuf_dup (pb->path, pool);
> + svn_wc_entry_t *entry;
> + struct dir_baton *b;
> +
> + svn_path_add_component (path, name, svn_path_local_style);
> + SVN_ERR (svn_wc_entry (&entry, path, pool));
> + switch (entry->kind)
> + {
> + case svn_node_file:
> + /* For a file that is being deleted show a diff with an empty
> + file. */
> + SVN_ERR (pb->edit_baton->diff_cmd (path,
> + svn_wc__empty_file_path (path, pool),
> + NULL,
> + pb->edit_baton->diff_cmd_baton));
> + break;
> +
> + case svn_node_dir:
> + b = make_dir_baton (name, pb, pb->edit_baton, pool);
> + b->added = FALSE;
> + SVN_ERR (directory_elements_diff (b, TRUE));
> + break;
> +
> + default:
> + break;
> + }
> +
> + svn_pool_destroy (pool);
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +add_directory (svn_stringbuf_t *name,
> + void *parent_baton,
> + svn_stringbuf_t *copyfrom_path,
> + svn_revnum_t copyfrom_revision,
> + void **child_baton)
> +{
> + struct dir_baton *pb = parent_baton;
> + struct dir_baton *b;
> +
> + /* ### TODO: support copyfrom? */
> +
> + b = make_dir_baton (name, pb, pb->edit_baton, pb->pool);
> + *child_baton = b;
> + b->added = TRUE;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +replace_directory (svn_stringbuf_t *name,
> + void *parent_baton,
> + svn_revnum_t base_revision,
> + void **child_baton)
> +{
> + struct dir_baton *pb = parent_baton;
> + struct dir_baton *b;
> +
> + b = make_dir_baton (name, pb, pb->edit_baton, pb->pool);
> + *child_baton = b;
> + b->added = FALSE;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function. When a directory is closed,
> + * all the directory elements that have been added or replaced will already
> + * have been diff'd. However there may be other elements in the working
> + * copy that have not yet been considered.
> + */
> +static svn_error_t *
> +close_directory (void *dir_baton)
> +{
> + struct dir_baton *b = dir_baton;
> +
> + /* Skip added directories, they can only contain added elements all of
> + which have already been diff'd. */
> + if (!b->added)
> + SVN_ERR (directory_elements_diff (dir_baton, FALSE));
> +
> + /* Mark this directory as compared in the parent directory's baton. */
> + if (b->dir_baton)
> + {
> + apr_hash_set (b->dir_baton->compared, b->path->data, b->path->len,
> + (void*)TRUE);
> + }
> +
> + svn_pool_destroy (b->pool);
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +add_file (svn_stringbuf_t *name,
> + void *parent_baton,
> + svn_stringbuf_t *copyfrom_path,
> + svn_revnum_t copyfrom_revision,
> + void **file_baton)
> +{
> + struct dir_baton *pb = parent_baton;
> + struct file_baton *b;
> +
> + /* ### TODO: support copyfrom? */
> +
> + b = make_file_baton (name, pb);
> + *file_baton = b;
> +
> + b->added = TRUE;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +replace_file (svn_stringbuf_t *name,
> + void *parent_baton,
> + svn_revnum_t base_revision,
> + void **file_baton)
> +{
> + struct dir_baton *pb = parent_baton;
> + struct file_baton *b;
> +
> + b = make_file_baton (name, pb);
> + *file_baton = b;
> +
> + b->added = FALSE;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function. Do the work of applying the
> + * text delta.
> + *
> + * NOTE: make sure the temporary file gets deleted if an error occurs
> + */
> +static svn_error_t *
> +window_handler (svn_txdelta_window_t *window,
> + void *window_baton)
> +{
> + struct file_baton *b = window_baton;
> + svn_error_t *err1, *err2 = SVN_NO_ERROR, *err3 = SVN_NO_ERROR;
> +
> + err1 = b->apply_handler (window, b->apply_baton);
> +
> + if (!window)
> + {
> + err2 = svn_wc__close_text_base (b->temp_file, b->wc_path, 0, b->pool);
> +
> + if (b->added)
> + {
> + err3 = svn_wc__close_empty_file (b->original_file, b->wc_path,
> + b->pool);
> + }
> + else
> + {
> + err3 = svn_wc__close_text_base (b->original_file, b->wc_path, 0,
> + b->pool);
> + }
> + }
> +
> + /* Remove the temporary file if an error occured. */
> + if (err1 != SVN_NO_ERROR || err2 != SVN_NO_ERROR || err3 != SVN_NO_ERROR)
> + {
> + svn_stringbuf_t *temp_file_path
> + = svn_wc__text_base_path (b->wc_path, TRUE, b->pool);
> +
> + apr_file_remove (temp_file_path->data, b->pool);
> +
> + /* Return the first error encountered. */
> + if (err1)
> + return err1;
> + if (err2)
> + return err2;
> + return err3;
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +apply_textdelta (void *file_baton,
> + svn_txdelta_window_handler_t *handler,
> + void **handler_baton)
> +{
> + struct file_baton *b = file_baton;
> +
> + if (b->added)
> + {
> + /* An empty file is the starting point if the file is being added */
> + SVN_ERR (svn_wc__open_empty_file (&b->original_file, b->wc_path,
> + b->pool));
> + }
> + else
> + {
> + /* The current text-base is the starting point if replacing */
> + SVN_ERR (svn_wc__open_text_base (&b->original_file, b->wc_path,
> + APR_READ, b->pool));
> + }
> +
> + /* This will be the new version. It is created in the temporary part of
> + * the admin area. This file exists until the diff callback is run.
> + *
> + * NOTE: need to be very careful with the error handling from this point
> + * on, until the temporary file is deleted, to ensure that it is deleted
> + * if an error occurs.
> + */
> + SVN_ERR (svn_wc__open_text_base (&b->temp_file, b->wc_path,
> + (APR_WRITE | APR_TRUNCATE | APR_CREATE),
> + b->pool));
> +
> + svn_txdelta_apply (svn_stream_from_aprfile (b->original_file, b->pool),
> + svn_stream_from_aprfile (b->temp_file, b->pool),
> + b->pool,
> + &b->apply_handler, &b->apply_baton);
> +
> + *handler = window_handler;
> + *handler_baton = file_baton;
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function. When the file is closed we
> + * have a temporary file containing a pristine version of the repository
> + * file. This can be compared against the working copy.
> + *
> + * NOTE: make sure the temporary file gets deleted if an error occurs
> + */
> +static svn_error_t *
> +close_file (void *file_baton)
> +{
> + struct file_baton *b = file_baton;
> + svn_wc_diff_cmd_t diff_cmd = b->edit_baton->diff_cmd;
> + svn_error_t *err;
> +
> + /* The path to the temporary copy of the pristine repository version. */
> + svn_stringbuf_t *temp_file_path
> + = svn_wc__text_base_path (b->wc_path, TRUE, b->pool);
> +
> + if (b->added)
> + {
> + /* If the file is being added, it doesn't exist in the working
> + copy. Compare against an empty file. */
> + err = diff_cmd (svn_wc__empty_file_path (b->wc_path, b->pool),
> + temp_file_path, b->path,
> + b->edit_baton->diff_cmd_baton);
> + }
> + else
> + {
> + /* Compare with the working copy. */
> + err = diff_cmd (b->path, temp_file_path, NULL,
> + b->edit_baton->diff_cmd_baton);
> + }
> +
> + /* Remove the temporary file. */
> + apr_file_remove (temp_file_path->data, b->pool);
> +
> + /* Add this file to the parent directory's list of elements that have
> + been compared. */
> + apr_hash_set (b->dir_baton->compared, b->path->data, b->path->len,
> + (void*)TRUE);
> +
> + svn_pool_destroy (b->pool);
> +
> + return err;
> +}
> +
> +/* An svn_delta_edit_fns_t editor function.
> + */
> +static svn_error_t *
> +close_edit (void *edit_baton)
> +{
> + /* ### TODO: If the root has not been replaced, then we need to do this
> + to pick up local changes. Can this happen? Well, at the meoment, it
> + does if I replace a file with a directory, but I don't know if that is
> + supposed to be supported yet. I would do something like this...
> + */
> +# if 0
> + struct edit_baton *eb = edit_baton;
> +
> + if (!eb->root_replaced)
> + {
> + struct dir_baton *b;
> +
> + b = make_dir_baton (NULL, NULL, eb, eb->pool);
> + SVN_ERR (directory_elements_diff (b, FALSE));
> + svn_pool_destroy (b->pool);
> + }
> +#endif
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Create a diff editor and baton.
> + */
> +svn_error_t *
> +svn_wc_get_diff_editor (svn_stringbuf_t *anchor,
> + svn_stringbuf_t *target,
> + svn_wc_diff_cmd_t diff_cmd,
> + void *diff_cmd_baton,
> + svn_boolean_t recurse,
> + const svn_delta_edit_fns_t **editor,
> + void **edit_baton,
> + apr_pool_t *pool)
> +{
> + svn_delta_edit_fns_t *tree_editor = svn_delta_default_editor (pool);
> + struct edit_baton *eb = apr_palloc (pool, sizeof (*eb));
> +
> + eb->anchor = anchor;
> + eb->target = target;
> + eb->diff_cmd = diff_cmd;
> + eb->diff_cmd_baton = diff_cmd_baton;
> + eb->recurse = recurse;
> + eb->pool = pool;
> +
> + tree_editor->set_target_revision = set_target_revision;
> + tree_editor->replace_root = replace_root;
> + tree_editor->delete_entry = delete_entry;
> + tree_editor->add_directory = add_directory;
> + tree_editor->replace_directory = replace_directory;
> + tree_editor->close_directory = close_directory;
> + tree_editor->add_file = add_file;
> + tree_editor->replace_file = replace_file;
> + tree_editor->apply_textdelta = apply_textdelta;
> + tree_editor->close_file = close_file;
> + tree_editor->close_edit = close_edit;
> +
> + *edit_baton = eb;
> + *editor = tree_editor;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +
> +/* ----------------------------------------------------------------
> + * local variables:
> + * eval: (load-file "../svn-dev.el")
> + * end:
> + */
> Index: ./subversion/libsvn_wc/wc.h
> ===================================================================
> --- ./subversion/libsvn_wc/wc.h
> +++ ./subversion/libsvn_wc/wc.h Sat Nov 3 13:25:55 2001
> @@ -100,6 +100,7 @@
> #define SVN_WC__ADM_LOG "log"
> #define SVN_WC__ADM_KILLME "KILLME"
> #define SVN_WC__ADM_AUTH_DIR "auth"
> +#define SVN_WC__ADM_EMPTY_FILE "empty-file"
>
>
> /* The basename of the ".prej" file, if a directory ever has property
> @@ -111,6 +112,11 @@
> svn_stringbuf_t *svn_wc__adm_subdir (apr_pool_t *pool);
>
>
> +/* Return the path to the empty file in the adm area of PATH */
> +svn_stringbuf_t *svn_wc__empty_file_path (const svn_stringbuf_t *path,
> + apr_pool_t *pool);
> +
> +
> /* Return a path to something in PATH's administrative area.
> * Return path to the thing in the tmp area if TMP is non-zero.
> * Varargs are (const char *)'s, the final one must be NULL.
> @@ -182,6 +188,19 @@
> svn_error_t *svn_wc__remove_adm_file (svn_stringbuf_t *path,
> apr_pool_t *pool,
> ...);
> +
> +/* Open *readonly* the empty file in the in adm area of PATH */
> +svn_error_t *svn_wc__open_empty_file (apr_file_t **handle,
> + svn_stringbuf_t *path,
> + apr_pool_t *pool);
> +
> +/* Close the empty file in the adm area of PATH. FP was obtain from
> + * svn_wc__open_empty_file().
> + */
> +svn_error_t *svn_wc__close_empty_file (apr_file_t *fp,
> + svn_stringbuf_t *path,
> + apr_pool_t *pool);
> +
>
> /* Open the text-base for FILE.
> * FILE can be any kind of path ending with a filename.
> Index: ./subversion/libsvn_wc/adm_files.c
> ===================================================================
> --- ./subversion/libsvn_wc/adm_files.c
> +++ ./subversion/libsvn_wc/adm_files.c Sat Nov 3 13:25:36 2001
> @@ -376,6 +376,16 @@
> return thing_path (path, SVN_WC__ADM_TEXT_BASE, tmp, pool);
> }
>
> +svn_stringbuf_t *
> +svn_wc__empty_file_path (const svn_stringbuf_t *path,
> + apr_pool_t *pool)
> +{
> + svn_stringbuf_t *empty_file_path = svn_stringbuf_dup (path, pool);
> + svn_path_remove_component (empty_file_path, svn_path_local_style);
> + extend_with_adm_name (empty_file_path, 0, pool, SVN_WC__ADM_EMPTY_FILE, NULL);
> + return empty_file_path;
> +}
> +
>
> static svn_error_t *
> prop_path_internal (svn_stringbuf_t **prop_path,
> @@ -707,6 +717,30 @@
>
>
> svn_error_t *
> +svn_wc__open_empty_file (apr_file_t **handle,
> + svn_stringbuf_t *path,
> + apr_pool_t *pool)
> +{
> + svn_stringbuf_t *newpath;
> + svn_path_split (path, &newpath, NULL, svn_path_local_style, pool);
> + return open_adm_file (handle, newpath, APR_READ, pool,
> + SVN_WC__ADM_EMPTY_FILE, NULL);
> +}
> +
> +
> +svn_error_t *
> +svn_wc__close_empty_file (apr_file_t *fp,
> + svn_stringbuf_t *path,
> + apr_pool_t *pool)
> +{
> + svn_stringbuf_t *newpath, *basename;
> + svn_path_split (path, &newpath, &basename, svn_path_local_style, pool);
> + return close_adm_file (fp, newpath, 0, pool,
> + SVN_WC__ADM_EMPTY_FILE, NULL);
> +}
> +
> +
> +svn_error_t *
> svn_wc__open_text_base (apr_file_t **handle,
> svn_stringbuf_t *path,
> apr_int32_t flags,
> @@ -1180,6 +1214,14 @@
>
> /* SVN_WC__ADM_ENTRIES */
> SVN_ERR (svn_wc__entries_init (path, ancestor_path, pool));
> +
> + /* SVN_WC__ADM_EMPTY_FILE exists because sometimes an readable, empty
> + file is required (in the repository diff for example). Creating such a
> + file temporarily, only to delete it again, would appear to be less
> + efficient than just having one around. It doesn't take up much space
> + after all. */
> + SVN_ERR (svn_wc__make_adm_thing (path, SVN_WC__ADM_EMPTY_FILE, svn_node_file,
> + APR_UREAD, 0, pool));
>
> /* THIS FILE MUST BE CREATED LAST:
> After this exists, the dir is considered complete. */
> Index: ./subversion/libsvn_client/diff.c
> ===================================================================
> --- ./subversion/libsvn_client/diff.c
> +++ ./subversion/libsvn_client/diff.c Mon Nov 5 01:51:43 2001
> @@ -1,5 +1,5 @@
> /*
> - * diff.c: return two temporary file paths that can be diffed
> + * diff.c: Compare working copy with text-base or repository.
> *
> * ====================================================================
> * Copyright (c) 2000-2001 CollabNet. All rights reserved.
> @@ -33,7 +33,97 @@
> #include "svn_path.h"
> #include "svn_test.h"
> #include "svn_io.h"
> +#include "svn_pools.h"
> +#include "client.h"
> +#include "svn_private_config.h" /* for SVN_CLIENT_DIFF */
> +#include <assert.h>
> +
> +struct diff_cmd_baton {
> + apr_array_header_t *options;
> + apr_pool_t *pool;
> +};
> +
> +/* This is an svn_wc_diff_cmd_t callback */
> +static svn_error_t *
> +svn_client__diff_cmd (svn_stringbuf_t *path1,
> + svn_stringbuf_t *path2,
> + svn_stringbuf_t *label,
> + void *baton)
> +{
> + struct diff_cmd_baton *diff_cmd_baton = baton;
> + apr_status_t status;
> + const char **args;
> + int i = 0;
> + int nargs = 4; /* Eek! A magic number! It's checked by a later assert */
> + apr_file_t *outhandle = NULL;
> +
> + /* Use a subpool here because the pool comes right through from the top
> + level.*/
> + apr_pool_t *subpool = svn_pool_create (diff_cmd_baton->pool);
> +
> + /* Get an apr_file_t representing stdout, which is where we'll have
> + the diff program print to. */
> + status = apr_file_open_stdout (&outhandle, subpool);
> + if (status)
> + return svn_error_create (status, 0, NULL, subpool,
> + "error: can't open handle to stdout");
> +
> + /* Execute local diff command on these two paths, print to stdout. */
> +
> + if (label)
> + nargs += 2; /* Another magic number checked by a later assert */
> + if (diff_cmd_baton->options->nelts)
> + nargs += diff_cmd_baton->options->nelts;
> + else
> + ++nargs;
> + args = apr_palloc(subpool, nargs*sizeof(char*));
> +
> + args[i++] = SVN_CLIENT_DIFF; /* the autoconfiscated system diff program */
> + if (diff_cmd_baton->options->nelts)
> + {
> + /* Add the user specified diff options to the diff command line. */
> + int j;
> + for (j = 0; j < diff_cmd_baton->options->nelts; ++j)
> + args[i++]
> + = ((svn_stringbuf_t **)(diff_cmd_baton->options->elts))[j]->data;
> + }
> + else
> + {
> + /* The user didn't specify any options, default to unified diff. */
> + args[i++] = "-u";
> + }
> + if (label)
> + {
> + args[i++] = "-L";
> + args[i++] = label->data;
> + }
> + args[i++] = path1->data;
> + args[i++] = path2->data;
> + args[i++] = NULL;
> + assert (i==nargs);
> +
> + /* ### TODO: This printf is NOT "my final answer" -- placeholder for
> + real work to be done. */
> + if (label)
> + apr_file_printf (outhandle, "Index: %s\n", label->data);
> + else
> + apr_file_printf (outhandle, "Index: %s\n", path1->data);
> + apr_file_printf (outhandle, "===================================================================\n");
> +
> + SVN_ERR(svn_io_run_cmd (".", SVN_CLIENT_DIFF, args, NULL, NULL,
> + NULL, outhandle, NULL, subpool));
> +
> + /* TODO: Handle exit code == 2 (i.e. errors with diff) here */
> +
> + /* TODO: someday we'll need to worry about whether we're going to need to
> + write a diff plug-in mechanism that makes use of the two paths,
> + instead of just blindly running SVN_CLIENT_DIFF.
> + */
> +
> + svn_pool_destroy (subpool);
>
> + return SVN_NO_ERROR;
> +}
>
>
>
> @@ -59,10 +149,73 @@
> return SVN_NO_ERROR;
> }
>
> +/* Compare working copy against the repository */
> +svn_error_t *
> +svn_client_diff (svn_stringbuf_t *path,
> + apr_array_header_t *diff_options,
> + svn_client_auth_baton_t *auth_baton,
> + svn_revnum_t revision,
> + apr_time_t tm,
> + svn_boolean_t recurse,
> + apr_pool_t *pool)
> +{
> + svn_stringbuf_t *anchor, *target;
> + svn_wc_entry_t *entry;
> + svn_stringbuf_t *URL;
> + void *ra_baton, *session, *cb_baton;
> + svn_ra_plugin_t *ra_lib;
> + svn_ra_callbacks_t *ra_callbacks;
> + const svn_ra_reporter_t *reporter;
> + void *report_baton;
> + const svn_delta_edit_fns_t *diff_editor;
> + void *diff_edit_baton;
> + struct diff_cmd_baton diff_cmd_baton = { diff_options, pool };
> +
> + /* If both REVISION and TM are specified, this is an error.
> + They mostly likely contradict one another. */
> + if ((revision != SVN_INVALID_REVNUM) && tm)
> + return
> + svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, 0, NULL, pool,
> + "Cannot specify _both_ revision and time.");
> +
> + SVN_ERR (svn_wc_get_actual_target (path, &anchor, &target, pool));
> + SVN_ERR (svn_wc_entry (&entry, anchor, pool));
> + URL = svn_stringbuf_create (entry->ancestor->data, pool);
> +
> + SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
> + SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL->data, pool));
> +
> + SVN_ERR (svn_client__get_ra_callbacks (&ra_callbacks, &cb_baton,
> + auth_baton,
> + anchor,
> + TRUE,
> + TRUE,
> + pool));
> + SVN_ERR (ra_lib->open (&session, URL, ra_callbacks, cb_baton, pool));
> +
> + if (tm)
> + SVN_ERR (ra_lib->get_dated_revision (session, &revision, tm));
> +
> + SVN_ERR (svn_wc_get_diff_editor (anchor, target,
> + svn_client__diff_cmd, &diff_cmd_baton,
> + recurse,
> + &diff_editor, &diff_edit_baton,
> + pool));
> +
> + SVN_ERR (ra_lib->do_update (session,
> + &reporter, &report_baton,
> + revision,
> + target,
> + recurse,
> + diff_editor, diff_edit_baton));
>
> + SVN_ERR (svn_wc_crawl_revisions (path, reporter, report_baton,
> + FALSE, FALSE, recurse, pool));
>
> + SVN_ERR (ra_lib->close (session));
>
> -
> + return SVN_NO_ERROR;
> +}
>
>
>
> Index: ./subversion/clients/cmdline/diff-cmd.c
> ===================================================================
> --- ./subversion/clients/cmdline/diff-cmd.c
> +++ ./subversion/clients/cmdline/diff-cmd.c Sat Nov 3 21:40:30 2001
> @@ -34,6 +34,56 @@
>
> /*** Code. ***/
>
> +/* Compare working copy against a given repository version. */
> +static svn_error_t *
> +svn_cl__wc_repository_diff (apr_getopt_t *os,
> + svn_cl__opt_state_t *opt_state,
> + apr_pool_t *pool)
> +{
> + apr_array_header_t *options;
> + apr_array_header_t *targets;
> + apr_array_header_t *condensed_targets;
> + svn_client_auth_baton_t *auth_baton;
> + int i;
> +
> + if (opt_state->start_revision != opt_state->end_revision)
> + {
> + return svn_error_createf (SVN_ERR_UNSUPPORTED_FEATURE, 0, NULL, pool,
> + "Only a single revision can be specified");
> + }
> +
> + options = svn_cl__stringlist_to_array(opt_state->extensions, pool);
> +
> + targets = svn_cl__args_to_target_array (os, pool);
> + svn_cl__push_implicit_dot_target (targets, pool);
> + SVN_ERR (svn_path_remove_redundancies (&condensed_targets,
> + targets,
> + svn_path_local_style,
> + pool));
> +
> + auth_baton = svn_cl__make_auth_baton (opt_state, pool);
> +
> + for (i = 0; i < condensed_targets->nelts; ++i)
> + {
> + svn_stringbuf_t *target
> + = ((svn_stringbuf_t **) (condensed_targets->elts))[i];
> + svn_stringbuf_t *parent_dir, *entry;
> +
> + SVN_ERR (svn_wc_get_actual_target (target, &parent_dir, &entry, pool));
> +
> + SVN_ERR (svn_client_diff (target,
> + options,
> + auth_baton,
> + opt_state->start_revision,
> + opt_state->start_date,
> + opt_state->nonrecursive ? FALSE : TRUE,
> + pool));
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An svn_cl__cmd_proc_t to handle the 'diff' command. */
> svn_error_t *
> svn_cl__diff (apr_getopt_t *os,
> svn_cl__opt_state_t *opt_state,
> @@ -45,6 +95,15 @@
> svn_boolean_t recurse = TRUE;
> int i;
>
> + /* If a revision has been specified then compare against the repository.
> + This won't catch 'svn diff -rHEAD:1' since that doesn't change the
> + revisons from their default value. */
> + if (opt_state->start_revision != SVN_INVALID_REVNUM
> + || opt_state->end_revision != 1)
> + {
> + return svn_cl__wc_repository_diff (os, opt_state, pool);
> + }
> +
> options = svn_cl__stringlist_to_array(opt_state->extensions, pool);
> targets = svn_cl__args_to_target_array(os, pool);
>
> @@ -88,6 +147,7 @@
>
> return SVN_NO_ERROR;
> }
> +
>
>
> /*
> Index: ./subversion/clients/cmdline/main.c
> ===================================================================
> --- ./subversion/clients/cmdline/main.c
> +++ ./subversion/clients/cmdline/main.c Sat Nov 3 17:29:33 2001
> @@ -141,7 +141,8 @@
> { "st", TRUE, NULL, NULL },
>
> { "diff", FALSE, svn_cl__diff,
> - "Display local file changes as contextual diffs.\n"
> + "Display local changes in the working copy, or changes between the\n"
> + "working copy and the repository if a revision is given.\n"
> "usage: diff [TARGETS]\n" },
> { "di", TRUE, NULL, NULL },
>
> Index: ./subversion/tests/clients/cmdline/diff_tests.py
> ===================================================================
> --- ./subversion/tests/clients/cmdline/diff_tests.py
> +++ ./subversion/tests/clients/cmdline/diff_tests.py Mon Nov 5 01:47:31 2001
> @@ -0,0 +1,498 @@
> +#!/usr/bin/env python
> +#
> +# diff_tests.py: some basic diff tests
> +#
> +# Subversion is a tool for revision control.
> +# See http://subversion.tigris.org for more information.
> +#
> +# ====================================================================
> +# Copyright (c) 2001 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.
> +#
> +######################################################################
> +
> +# General modules
> +import shutil, string, sys, re, os.path, traceback
> +
> +try:
> + import svntest
> +except SyntaxError:
> + sys.stderr.write('[SKIPPED] ')
> + print "<<< Please make sure you have Python 2 or better! >>>"
> + traceback.print_exc(None,sys.stdout)
> + raise SystemExit
> +
> +# Quick macro for auto-generating sandbox names
> +def sandbox(x):
> + return "diff_tests-" + `test_list.index(x)`
> +
> +
> +######################################################################
> +# Diff output checker
> +#
> +# Looks for the correct filenames and a suitable number of +/- lines
> +# depending on whether this is an addition, modification or deletion.
> +
> +def check_diff_output(diff_output, name, diff_type):
> + "check diff output"
> +
> + i_re = re.compile('^Index:')
> + d_re = re.compile('^Index: \\./' + name)
> + p_re = re.compile('^--- \\./' + name)
> + add_re = re.compile('^\\+')
> + sub_re = re.compile('^-')
> +
> + i = 0
> + while i < len(diff_output) - 4:
> +
> + # identify a possible diff
> + if (d_re.match(diff_output[i])
> + and p_re.match(diff_output[i+2])):
> +
> + # count lines added and deleted
> + i += 4
> + add_lines = 0
> + sub_lines = 0
> + while i < len(diff_output) and not i_re.match(diff_output[i]):
> + if add_re.match(diff_output[i][0]):
> + add_lines += 1
> + if sub_re.match(diff_output[i][0]):
> + sub_lines += 1
> + i += 1
> +
> + # check if this looks like the right sort of diff
> + if add_lines > 0 and sub_lines == 0 and diff_type == 'A':
> + return 0
> + if sub_lines > 0 and add_lines == 0 and diff_type == 'D':
> + return 0
> + if add_lines > 0 and sub_lines > 0 and diff_type == 'M':
> + return 0
> +
> + else:
> + i += 1
> +
> + # no suitable diff found
> + return 1
> +
> +def count_diff_output(diff_output):
> + "count the number of file diffs in the output"
> +
> + i_re = re.compile('Index:')
> + diff_count = 0
> + i = 0
> + while i < len(diff_output) - 4:
> + if i_re.match(diff_output[i]):
> + i += 4
> + diff_count += 1
> + else:
> + i += 1
> +
> + return diff_count
> +
> +######################################################################
> +# Changes makers and change checkers
> +
> +def update_a_file():
> + "update a file"
> + svntest.main.file_append(os.path.join('A', 'B', 'E', 'alpha'), "new atext")
> + return 0
> +
> +def check_update_a_file(diff_output):
> + "check diff for update a file"
> + return check_diff_output(diff_output,
> + os.path.join('A', 'B', 'E', 'alpha'),
> + 'M')
> +
> +#----------------------------------------------------------------------
> +
> +def add_a_file():
> + "add a file"
> + svntest.main.file_append(os.path.join('A', 'B', 'E', 'theta'), "theta")
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'E', 'theta'))
> + return 0
> +
> +def check_add_a_file(diff_output):
> + "check diff for add a file"
> + return check_diff_output(diff_output,
> + os.path.join('A', 'B', 'E', 'theta'),
> + 'A')
> +
> +def check_add_a_file_reverse(diff_output):
> + "check diff for add a file"
> + return check_diff_output(diff_output,
> + os.path.join('A', 'B', 'E', 'theta'),
> + 'D')
> +
> +#----------------------------------------------------------------------
> +
> +def add_a_file_in_a_subdir():
> + "add a file in a subdir"
> + os.mkdir(os.path.join('A', 'B', 'T'))
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'T'))
> + svntest.main.file_append(os.path.join('A', 'B', 'T', 'phi'), "phi")
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'T', 'phi'))
> + return 0
> +
> +def check_add_a_file_in_a_subdir(diff_output):
> + "check diff for add a file in a subdir"
> + return check_diff_output(diff_output,
> + os.path.join('A', 'B', 'T', 'phi'),
> + 'A')
> +
> +def check_add_a_file_in_a_subdir_reverse(diff_output):
> + "check diff for add a file in a subdir"
> + return check_diff_output(diff_output,
> + os.path.join('A', 'B', 'T', 'phi'),
> + 'D')
> +
> +#----------------------------------------------------------------------
> +
> +def replace_a_file_with_a_dir():
> + "replace a file with a dir"
> + svntest.main.run_svn(None, 'rm', os.path.join('A', 'B', 'lambda'))
> + os.remove(os.path.join('A', 'B', 'lambda'))
> + os.mkdir(os.path.join('A', 'B', 'lambda'))
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'lambda'))
> + return 0
> +
> +def check_replace_a_file_with_a_dir(diff_output):
> + "check diff for replace a file with a dir"
> + return check_diff_output(diff_output,
> + os.path.join('A', 'B', 'lambda'),
> + 'D')
> +
> +#----------------------------------------------------------------------
> +
> +def replace_a_file():
> + "replace a file"
> + svntest.main.run_svn(None, 'rm', os.path.join('A', 'D', 'G', 'rho'))
> + svntest.main.file_append(os.path.join('A', 'D', 'G', 'rho'), "new rho")
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'D', 'G', 'rho'))
> + return 0
> +
> +def check_replace_a_file(diff_output):
> + "check diff for replace a file"
> + if check_diff_output(diff_output,
> + os.path.join('A', 'D', 'G', 'rho'),
> + 'D'):
> + return 1
> + if check_diff_output(diff_output,
> + os.path.join('A', 'D', 'G', 'rho'),
> + 'A'):
> + return 1
> + return 0
> +
> +#----------------------------------------------------------------------
> +
> +def replace_a_dir_and_file():
> + "replace a dir and file"
> + svntest.main.run_svn(None, 'rm', os.path.join('A', 'B', 'E'))
> + shutil.rmtree(os.path.join('A', 'B', 'E'))
> + os.mkdir(os.path.join('A', 'B', 'E'))
> + svntest.main.file_append(os.path.join('A', 'B', 'E', 'alpha'), "new alpha")
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'E'))
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'E', 'alpha'))
> + return 0
> +
> +def check_replace_a_dir_and_file(diff_output):
> + "check diff for replace a dir and file"
> + if check_diff_output(diff_output,
> + os.path.join('A', 'B', 'E', 'alpha'),
> + 'D'):
> + return 1
> + if check_diff_output(diff_output,
> + os.path.join('A', 'B', 'E', 'alpha'),
> + 'A'):
> + return 1
> + return 0
> +
> +#----------------------------------------------------------------------
> +
> +def update_three_files():
> + "update three files"
> + svntest.main.file_append(os.path.join('A', 'D', 'gamma'), "new gamma")
> + svntest.main.file_append(os.path.join('A', 'D', 'G', 'tau'), "new tau")
> + svntest.main.file_append(os.path.join('A', 'D', 'H', 'psi'), "new psi")
> + return 0
> +
> +def check_update_three_files(diff_output):
> + "check update three files"
> + if check_diff_output(diff_output,
> + os.path.join('A', 'D', 'gamma'),
> + 'M'):
> + return 1
> + if check_diff_output(diff_output,
> + os.path.join('A', 'D', 'G', 'tau'),
> + 'M'):
> + return 1
> + if check_diff_output(diff_output,
> + os.path.join('A', 'D', 'H', 'psi'),
> + 'M'):
> + return 1
> + return 0
> +
> +
> +#----------------------------------------------------------------------
> +
> +### TODO: subversion doesn't support this yet
> +def replace_a_dir_with_a_file():
> + "replace a dir with a file"
> + svntest.main.run_svn(None, 'rm', os.path.join('A', 'B', 'E'))
> + shutil.rmtree(os.path.join('A', 'B', 'E'))
> + svntest.main.file_append(os.path.join('A', 'B', 'E'), "new E")
> + svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'E'))
> + return 0
> +
> +######################################################################
> +# make a change, check the diff, commit the change, check the diff
> +
> +def change_diff_commit_diff(wc_dir, revision, change_fn, check_fn):
> + "make a change, diff, commit, update and diff again"
> +
> + was_cwd = os.getcwd()
> + os.chdir(wc_dir)
> +
> + svntest.main.run_svn(None, 'up', '-rHEAD')
> +
> + change_fn()
> +
> + diff_output, err_output = svntest.main.run_svn(None, 'diff', '-rHEAD')
> + if check_fn(diff_output):
> + os.chdir(was_cwd)
> + return 1
> +
> + svntest.main.run_svn(None, 'ci')
> + svntest.main.run_svn(None, 'up', '-r', revision)
> + diff_output, err_output = svntest.main.run_svn(None, 'diff', '-rHEAD')
> + if check_fn(diff_output):
> + os.chdir(was_cwd)
> + return 1
> +
> + os.chdir(was_cwd)
> + return 0
> +
> +######################################################################
> +# update, check the diff
> +
> +def just_diff(wc_dir, rev_up, rev_check, check_fn):
> + "check that the given diff is seen"
> +
> + was_cwd = os.getcwd()
> + os.chdir(wc_dir)
> +
> + svntest.main.run_svn(None, 'up', '-r', rev_up)
> + diff_output, err_output = svntest.main.run_svn(None, 'diff', '-r', rev_check)
> + if check_fn(diff_output): return 1
> +
> + os.chdir(was_cwd)
> + return 0
> +
> +
> +######################################################################
> +# Tests
> +#
> +
> +# test 1
> +def diff_update_a_file():
> + "update a file"
> +
> + sbox = sandbox(diff_update_a_file)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + return change_diff_commit_diff(wc_dir, 1,
> + update_a_file,
> + check_update_a_file)
> +
> +# test 2
> +def diff_add_a_file():
> + "add a file"
> +
> + sbox = sandbox(diff_add_a_file)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + return change_diff_commit_diff(wc_dir, 1,
> + add_a_file,
> + check_add_a_file)
> +
> +#test 3
> +def diff_add_a_file_in_a_subdir():
> + "add a file in an added directory"
> +
> + sbox = sandbox(diff_add_a_file_in_a_subdir)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + return change_diff_commit_diff(wc_dir, 1,
> + add_a_file_in_a_subdir,
> + check_add_a_file_in_a_subdir)
> +
> +# test 4
> +def diff_replace_a_file_with_a_dir():
> + "replace a file with a directory"
> +
> + sbox = sandbox(diff_replace_a_file_with_a_dir)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + ### TODO: this bit will fail, 'svn diff' doesn't support this in the wc
> + change_diff_commit_diff(wc_dir, 1,
> + replace_a_file_with_a_dir,
> + check_replace_a_file_with_a_dir)
> +
> + # However it does support it in the repository, so force the commit
> + # and then diff the repository
> + svntest.main.run_svn(None, 'ci', wc_dir)
> +
> + if just_diff(wc_dir, 1, 2, check_replace_a_file_with_a_dir): return 1
> + return 0
> +
> +
> +# test 5
> +def diff_replace_a_file():
> + "replace a file with a file"
> +
> + sbox = sandbox(diff_replace_a_file)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + return change_diff_commit_diff(wc_dir, 1,
> + replace_a_file,
> + check_replace_a_file)
> +
> +# test 6
> +def diff_two_add_reverse():
> + "two commits diff'd forwards and backwards"
> +
> + sbox = sandbox(diff_two_add_reverse)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + # rev 2
> + if change_diff_commit_diff(wc_dir, 1,
> + add_a_file,
> + check_add_a_file):
> + return 1
> +
> + #rev 3
> + if change_diff_commit_diff(wc_dir, 2,
> + add_a_file_in_a_subdir,
> + check_add_a_file_in_a_subdir):
> + return 1
> +
> + # check diffs both ways
> + if just_diff(wc_dir, 1, 3, check_add_a_file_in_a_subdir):
> + return 1
> + if just_diff(wc_dir, 1, 3, check_add_a_file):
> + return 1
> + if just_diff(wc_dir, 3, 1, check_add_a_file_in_a_subdir_reverse):
> + return 1
> + if just_diff(wc_dir, 3, 1, check_add_a_file_reverse):
> + return 1
> +
> + return 0
> +
> +# test 7
> +def diff_replace_a_dir_and_file():
> + "replace a file/directory with a fil/directory"
> +
> + sbox = sandbox(diff_replace_a_dir_and_file)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + ### TODO: this bit will fail, 'svn diff' doesn't support this in the wc
> + change_diff_commit_diff(wc_dir, 1,
> + replace_a_dir_and_file,
> + check_replace_a_dir_and_file)
> +
> + # However it does support it in the repository, so force the commit
> + # and then diff the repository
> + svntest.main.run_svn(None, 'ci', wc_dir)
> +
> + if just_diff(wc_dir, 1, 2, check_replace_a_dir_and_file): return 1
> + return 0
> +
> +# test 8
> +def diff_non_recursive():
> + "check non-recursive behaviour"
> +
> + sbox = sandbox(diff_non_recursive)
> + wc_dir = os.path.join(svntest.main.general_wc_dir, sbox)
> + if svntest.actions.make_repo_and_wc(sbox): return 1
> +
> + if change_diff_commit_diff(wc_dir, 1,
> + update_three_files,
> + check_update_three_files):
> + return 1
> +
> + # The changes are in: ./A/D/gamma
> + # ./A/D/G/tau
> + # ./A/D/H/psi
> +
> + # When checking D recursively there are three changes. When checking
> + # D non-recursively there is only one change. When checking G
> + # recursively, there is only one change even though D is the anchor
> +
> + # full diff has three changes
> + diff_output, err_output = svntest.main.run_svn(None, 'diff', '-rHEAD',
> + os.path.join(wc_dir, 'A', 'D'))
> + if count_diff_output(diff_output) != 3:
> + return 1
> +
> + # non-recursive has one change
> + diff_output, err_output = svntest.main.run_svn(None, 'diff', '-rHEAD', '-n',
> + os.path.join(wc_dir, 'A', 'D'))
> + if count_diff_output(diff_output) != 1:
> + return 1
> +
> + # diffing a directory doesn't pick up other diffs in the anchor
> + diff_output, err_output = svntest.main.run_svn(None, 'diff', '-rHEAD',
> + os.path.join(wc_dir,
> + 'A', 'D', 'G'))
> + if count_diff_output(diff_output) != 1:
> + return 1
> +
> + return 0
> +
> +########################################################################
> +# Run the tests
> +
> +
> +# list all tests here, starting with None:
> +test_list = [ None,
> + diff_update_a_file,
> + diff_add_a_file,
> + diff_add_a_file_in_a_subdir,
> + diff_replace_a_file_with_a_dir,
> + diff_replace_a_file,
> + diff_two_add_reverse,
> + diff_replace_a_dir_and_file,
> + diff_non_recursive
> + ]
> +
> +if __name__ == '__main__':
> +
> + ## run the main test routine on them:
> + err = svntest.main.run_tests(test_list)
> +
> + ## remove all scratchwork: the 'pristine' repository, greek tree, etc.
> + ## This ensures that an 'import' will happen the next time we run.
> + if os.path.exists(svntest.main.temp_dir):
> + shutil.rmtree(svntest.main.temp_dir)
> +
> + ## return whatever main() returned to the OS.
> + sys.exit(err)
> +
> +
> +### End of file.
> +# local variables:
> +# eval: (load-file "../../../svn-dev.el")
> +# end:
> +
> +
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
> For additional commands, e-mail: dev-help@subversion.tigris.org
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Oct 21 14:36:48 2006