[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] diff against the repository

From: Philip Martin <pmartin_at_uklinux.net>
Date: 2001-11-05 03:25:11 CET

Hello

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
Received on Sat Oct 21 14:36:47 2006

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.