📄 diff.c
字号:
/*
* diff.c -- The diff editor for comparing the working copy against the
* repository.
*
* ====================================================================
* Copyright (c) 2000-2004 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_editor_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: 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 <assert.h>
#include <apr_hash.h>
#include "svn_pools.h"
#include "svn_path.h"
#include "wc.h"
#include "props.h"
#include "adm_files.h"
/*-------------------------------------------------------------------------*/
/* A little helper function.
You see, when we ask the server to update us to a certain revision,
we construct the new fulltext, and then run
'diff <repos_fulltext> <working_fulltext>'
which is, of course, actually backwards from the repository's point
of view. It thinks we want to move from working->repos.
So when the server sends property changes, they're effectively
backwards from what we want. We don't want working->repos, but
repos->working. So this little helper "reverses" the value in
BASEPROPS and PROPCHANGES before we pass them off to the
prop_changed() diff-callback. */
static void
reverse_propchanges (apr_hash_t *baseprops,
apr_array_header_t *propchanges,
apr_pool_t *pool)
{
int i;
/* ### todo: research lifetimes for property values below */
for (i = 0; i < propchanges->nelts; i++)
{
svn_prop_t *propchange
= &APR_ARRAY_IDX (propchanges, i, svn_prop_t);
const svn_string_t *original_value =
apr_hash_get (baseprops, propchange->name, APR_HASH_KEY_STRING);
if ((original_value == NULL) && (propchange->value != NULL))
{
/* found an addition. make it look like a deletion. */
apr_hash_set (baseprops, propchange->name, APR_HASH_KEY_STRING,
svn_string_dup (propchange->value, pool));
propchange->value = NULL;
}
else if ((original_value != NULL) && (propchange->value == NULL))
{
/* found a deletion. make it look like an addition. */
propchange->value = svn_string_dup (original_value, pool);
apr_hash_set (baseprops, propchange->name, APR_HASH_KEY_STRING,
NULL);
}
else if ((original_value != NULL) && (propchange->value != NULL))
{
/* found a change. just swap the values. */
const svn_string_t *str = svn_string_dup (propchange->value, pool);
propchange->value = svn_string_dup (original_value, pool);
apr_hash_set (baseprops, propchange->name, APR_HASH_KEY_STRING, str);
}
}
}
/*-------------------------------------------------------------------------*/
/* Overall crawler editor baton.
*/
struct edit_baton {
/* ANCHOR/TARGET represent the base of the hierarchy to be compared. */
svn_wc_adm_access_t *anchor;
const char *anchor_path;
const char *target;
/* Target revision */
svn_revnum_t revnum;
/* Was the root opened? */
svn_boolean_t root_opened;
/* The callbacks and callback argument that implement the file comparison
functions */
const svn_wc_diff_callbacks_t *callbacks;
void *callback_baton;
/* Flags whether to diff recursively or not. If set the diff is
recursive. */
svn_boolean_t recurse;
/* Should this diff ignore node ancestry. */
svn_boolean_t ignore_ancestry;
/* Possibly diff repos against text-bases instead of working files. */
svn_boolean_t use_text_base;
/* Possibly show the diffs backwards. */
svn_boolean_t reverse_order;
apr_pool_t *pool;
};
/* Directory level baton.
*/
struct dir_baton {
/* Gets set if the directory is added rather than replaced/unchanged. */
svn_boolean_t added;
/* The "correct" path of the directory, but it may not exist in the
working copy. */
const char *path;
/* Identifies those directory elements that get compared while running the
crawler. These elements should not be compared again when recursively
looking for local only diffs. */
apr_hash_t *compared;
/* The baton for the parent directory, or null if this is the root of the
hierarchy to be compared. */
struct dir_baton *dir_baton;
/* The original property hash, and the list of incoming propchanges. */
apr_hash_t *baseprops;
apr_array_header_t *propchanges;
svn_boolean_t fetched_baseprops; /* did we get the working props yet? */
/* The overall crawler editor baton. */
struct edit_baton *edit_baton;
apr_pool_t *pool;
};
/* File level baton.
*/
struct file_baton {
/* Gets set if the file is added rather than replaced. */
svn_boolean_t added;
/* PATH is the "correct" path of the file, but it may not exist in the
working copy. WC_PATH is a path we can use to make temporary files
or open empty files; it doesn't necessarily exist either, but the
directory part of it does. */
const char *path;
const char *wc_path;
/* When constructing the requested repository version of the file,
ORIGINAL_FILE is version of the file in the working copy. TEMP_FILE is
the pristine repository file obtained by applying the repository diffs
to ORIGINAL_FILE. */
apr_file_t *original_file;
apr_file_t *temp_file;
/* The original property hash, and the list of incoming propchanges. */
apr_hash_t *baseprops;
apr_array_header_t *propchanges;
svn_boolean_t fetched_baseprops; /* did we get the working props yet? */
/* APPLY_HANDLER/APPLY_BATON represent the delta applcation baton. */
svn_txdelta_window_handler_t apply_handler;
void *apply_baton;
/* Is this file scheduled to be deleted? */
svn_boolean_t schedule_delete;
/* The overall crawler editor baton. */
struct edit_baton *edit_baton;
apr_pool_t *pool;
};
/* Create a new edit baton. TARGET/ANCHOR are working copy paths that
* describe the root of the comparison. CALLBACKS/CALLBACK_BATON
* define the callbacks to compare files. RECURSE defines whether to
* descend into subdirectories. IGNORE_ANCESTRY defines whether to
* utilize node ancestry when calculating diffs. USE_TEXT_BASE
* defines whether to compare against working files or text-bases.
* REVERSE_ORDER defines which direction to perform the diff.
*/
static struct edit_baton *
make_editor_baton (svn_wc_adm_access_t *anchor,
const char *target,
const svn_wc_diff_callbacks_t *callbacks,
void *callback_baton,
svn_boolean_t recurse,
svn_boolean_t ignore_ancestry,
svn_boolean_t use_text_base,
svn_boolean_t reverse_order,
apr_pool_t *pool)
{
struct edit_baton *eb = apr_pcalloc (pool, sizeof (*eb));
eb->anchor = anchor;
eb->anchor_path = svn_wc_adm_access_path (anchor);
eb->target = apr_pstrdup (pool, target);
eb->callbacks = callbacks;
eb->callback_baton = callback_baton;
eb->recurse = recurse;
eb->ignore_ancestry = ignore_ancestry;
eb->use_text_base = use_text_base;
eb->reverse_order = reverse_order;
eb->pool = pool;
return eb;
}
/* Create a new directory baton. PATH is the directory path,
* including anchor_path. ADDED is set if this directory is being
* added rather than replaced. 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. EDIT_BATON is the overall crawler
* editor baton.
*/
static struct dir_baton *
make_dir_baton (const char *path,
struct dir_baton *parent_baton,
struct edit_baton *edit_baton,
svn_boolean_t added,
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->added = added;
dir_baton->pool = pool;
dir_baton->baseprops = apr_hash_make (dir_baton->pool);
dir_baton->propchanges = apr_array_make (pool, 1, sizeof (svn_prop_t));
dir_baton->compared = apr_hash_make (dir_baton->pool);
dir_baton->path = path;
return dir_baton;
}
/* Create a new file baton. PATH is the file path, including
* anchor_path. ADDED is set if this file is being added rather than
* replaced. PARENT_BATON is the baton of the parent directory. The
* directory and its parent may or may not exist in the working copy.
*/
static struct file_baton *
make_file_baton (const char *path,
svn_boolean_t added,
struct dir_baton *parent_baton,
apr_pool_t *pool)
{
struct file_baton *file_baton = apr_pcalloc (pool, sizeof (*file_baton));
struct edit_baton *edit_baton = parent_baton->edit_baton;
file_baton->edit_baton = edit_baton;
file_baton->added = added;
file_baton->pool = pool;
file_baton->baseprops = apr_hash_make (file_baton->pool);
file_baton->propchanges = apr_array_make (pool, 1, sizeof (svn_prop_t));
file_baton->path = path;
file_baton->schedule_delete = FALSE;
/* If the parent directory is added rather than replaced it does not
exist in the working copy. Determine a working copy path whose
directory part does exist; we can use that to create temporary
files. It doesn't matter whether the file part exists in the
directory. */
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_path_join (wc_dir_baton->path, "unimportant",
file_baton->pool);
}
else
{
file_baton->wc_path = file_baton->path;
}
return file_baton;
}
/* Helper function: load a file_baton's base_props. */
static void
load_base_props (struct file_baton *b)
{
/* the 'base' props to compare against, in this case, are
actually the working props. that's what we do with texts,
anyway, in the 'svn diff -rN foo' case. */
/* also notice we're ignoring error here; there's a chance that
this path might not exist in the working copy, in which case
the baseprops remains an empty hash. */
svn_error_t *err = svn_wc_prop_list (&(b->baseprops), b->path,
b->edit_baton->anchor, b->pool);
if (err)
svn_error_clear (err);
b->fetched_baseprops = TRUE;
}
/* Helper function for retrieving svn:mime-type properties, if
present, on file PATH. File baton B is optional: if present,
assume it refers to PATH, and use its caching abilities.
If PRISTINE_MIMETYPE is non-NULL, then set *PRISTINE_MIMETYPE to
the value of svn:mime-type if available. Else set to NULL. Search
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -