blob: 89f21947edb27561a78a918ca42edbb0c08bfe3d [file] [log] [blame]
/*
* questions.c: routines for asking questions about working copies
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <string.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_time.h>
#include <apr_strings.h>
#include "svn_pools.h"
#include "svn_types.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_hash.h"
#include "svn_path.h"
#include "svn_time.h"
#include "svn_wc.h"
#include "wc.h"
#include "adm_files.h"
#include "questions.h"
/* kff todo: make this compare repository too? Or do so in parallel code. */
svn_error_t *
svn_wc_check_wc (const svn_stringbuf_t *path,
svn_boolean_t *is_wc,
apr_pool_t *pool)
{
/* Nothing fancy, just check for an administrative subdir and a
`README' file. */
apr_file_t *f = NULL;
svn_error_t *err = NULL;
enum svn_node_kind kind;
err = svn_io_check_path (path->data, &kind, pool);
if (err)
return err;
if (kind == svn_node_none)
{
return svn_error_createf
(APR_ENOENT, 0, NULL, pool,
"svn_wc_check_wc: %s does not exist", path->data);
}
else if (kind != svn_node_dir)
*is_wc = FALSE;
else
{
err = svn_wc__open_adm_file (&f, path, SVN_WC__ADM_README,
APR_READ, pool);
/* It really doesn't matter what kind of error it is; if there
was an error at all, then for our purposes this is not a
working copy. */
if (err)
{
svn_error_clear_all (err);
*is_wc = FALSE;
}
else
{
*is_wc = TRUE;
err = svn_wc__close_adm_file (f, path, SVN_WC__ADM_README, 0, pool);
if (err)
return err;
}
}
return SVN_NO_ERROR;
}
/*** svn_wc_text_modified_p ***/
/* svn_wc_text_modified_p answers the question:
"Are the contents of F different than the contents of
.svn/text-base/F.svn-base?"
or
"Are the contents of .svn/props/xxx different than
.svn/prop-base/xxx.svn-base?"
In other words, we're looking to see if a user has made local
modifications to a file since the last update or commit.
Note: Assuming that F lives in a directory D at revision V, please
notice that we are *NOT* answering the question, "are the contents
of F different than revision V of F?" While F may be at a different
revision number than its parent directory, but we're only looking
for local edits on F, not for consistent directory revisions.
TODO: the logic of the routines on this page might change in the
future, as they bear some relation to the user interface. For
example, if a file is removed -- without telling subversion about
it -- how should subversion react? Should it copy the file back
out of text-base? Should it ask whether one meant to officially
mark it for removal?
*/
/* Is PATH's timestamp the same as the one recorded in our
`entries' file? Return the answer in EQUAL_P. TIMESTAMP_KIND
should be one of the enumerated type above. */
svn_error_t *
svn_wc__timestamps_equal_p (svn_boolean_t *equal_p,
svn_stringbuf_t *path,
const enum svn_wc__timestamp_kind timestamp_kind,
apr_pool_t *pool)
{
apr_time_t wfile_time, entrytime = 0;
svn_stringbuf_t *dirpath, *entryname;
apr_hash_t *entries = NULL;
struct svn_wc_entry_t *entry;
enum svn_node_kind kind;
SVN_ERR (svn_io_check_path (path->data, &kind, pool));
if (kind == svn_node_dir)
{
dirpath = path;
entryname = svn_stringbuf_create (SVN_WC_ENTRY_THIS_DIR, pool);
}
else
svn_path_split (path, &dirpath, &entryname, pool);
/* Get the timestamp from the entries file */
SVN_ERR (svn_wc_entries_read (&entries, dirpath, pool));
entry = apr_hash_get (entries, entryname->data, entryname->len);
/* Can't compare timestamps for an unversioned file. */
if (entry == NULL)
return svn_error_createf
(SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool,
"timestamps_equal_p: `%s' not under revision control", entryname->data);
/* Get the timestamp from the working file and the entry */
if (timestamp_kind == svn_wc__text_time)
{
SVN_ERR (svn_io_file_affected_time (&wfile_time, path, pool));
entrytime = entry->text_time;
}
else if (timestamp_kind == svn_wc__prop_time)
{
svn_stringbuf_t *prop_path;
SVN_ERR (svn_wc__prop_path (&prop_path, path, 0, pool));
SVN_ERR (svn_io_file_affected_time (&wfile_time, prop_path, pool));
entrytime = entry->prop_time;
}
if (! entrytime)
{
/* TODO: If either timestamp is inaccessible, the test cannot
return an answer. Assume that the timestamps are
different. */
*equal_p = FALSE;
return SVN_NO_ERROR;
}
{
/* Put the disk timestamp through a string conversion, so it's
at the same resolution as entry timestamps. */
const char *tstr = svn_time_to_nts (wfile_time, pool);
wfile_time = svn_time_from_nts (tstr);
}
if (wfile_time == entrytime)
*equal_p = TRUE;
else
*equal_p = FALSE;
return SVN_NO_ERROR;
}
/* Do a byte-for-byte comparison of FILE1 and FILE2. */
static svn_error_t *
contents_identical_p (svn_boolean_t *identical_p,
const char *file1,
const char *file2,
apr_pool_t *pool)
{
apr_status_t status;
apr_size_t bytes_read1, bytes_read2;
char buf1[BUFSIZ], buf2[BUFSIZ];
apr_file_t *file1_h = NULL;
apr_file_t *file2_h = NULL;
status = apr_file_open (&file1_h, file1,
APR_READ, APR_OS_DEFAULT, pool);
if (status)
return svn_error_createf
(status, 0, NULL, pool,
"contents_identical_p: apr_file_open failed on `%s'", file1);
status = apr_file_open (&file2_h, file2, APR_READ,
APR_OS_DEFAULT, pool);
if (status)
return svn_error_createf
(status, 0, NULL, pool,
"contents_identical_p: apr_file_open failed on `%s'", file2);
*identical_p = TRUE; /* assume TRUE, until disproved below */
while (!APR_STATUS_IS_EOF(status))
{
status = apr_file_read_full (file1_h, buf1, sizeof(buf1), &bytes_read1);
if (status && !APR_STATUS_IS_EOF(status))
return svn_error_createf
(status, 0, NULL, pool,
"contents_identical_p: apr_file_read_full() failed on %s.",
file1);
status = apr_file_read_full (file2_h, buf2, sizeof(buf2), &bytes_read2);
if (status && !APR_STATUS_IS_EOF(status))
return svn_error_createf
(status, 0, NULL, pool,
"contents_identical_p: apr_file_read_full() failed on %s.",
file2);
if ((bytes_read1 != bytes_read2)
|| (memcmp (buf1, buf2, bytes_read1)))
{
*identical_p = FALSE;
break;
}
}
status = apr_file_close (file1_h);
if (status)
return svn_error_createf
(status, 0, NULL, pool,
"contents_identical_p: apr_file_close failed on %s.", file1);
status = apr_file_close (file2_h);
if (status)
return svn_error_createf
(status, 0, NULL, pool,
"contents_identical_p: apr_file_close failed on %s.", file2);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__files_contents_same_p (svn_boolean_t *same,
svn_stringbuf_t *file1,
svn_stringbuf_t *file2,
apr_pool_t *pool)
{
svn_error_t *err;
svn_boolean_t q;
err = svn_io_filesizes_different_p (&q, file1->data, file2->data, pool);
if (err)
return err;
if (q)
{
*same = 0;
return SVN_NO_ERROR;
}
err = contents_identical_p (&q, file1->data, file2->data, pool);
if (err)
return err;
if (q)
*same = 1;
else
*same = 0;
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__versioned_file_modcheck (svn_boolean_t *modified_p,
svn_stringbuf_t *versioned_file,
svn_stringbuf_t *base_file,
apr_pool_t *pool)
{
svn_boolean_t same;
svn_stringbuf_t *tmp_vfile;
svn_error_t *err = SVN_NO_ERROR;
SVN_ERR (svn_wc_translated_file (&tmp_vfile, versioned_file, pool));
err = svn_wc__files_contents_same_p (&same, tmp_vfile, base_file, pool);
*modified_p = (! same);
if (tmp_vfile != versioned_file)
SVN_ERR (svn_io_remove_file (tmp_vfile->data, pool));
return err;
}
svn_error_t *
svn_wc_text_modified_p (svn_boolean_t *modified_p,
svn_stringbuf_t *filename,
apr_pool_t *pool)
{
svn_stringbuf_t *textbase_filename;
svn_boolean_t equal_timestamps;
apr_pool_t *subpool = svn_pool_create (pool);
enum svn_node_kind kind;
/* Sanity check: if the path doesn't exist, return FALSE. */
SVN_ERR (svn_io_check_path (filename->data, &kind, subpool));
if (kind != svn_node_file)
{
*modified_p = FALSE;
goto cleanup;
}
/* See if the local file's timestamp is the same as the one recorded
in the administrative directory. This could, theoretically, be
wrong in certain rare cases, but with the addition of a forced
delay after commits (see revision 419 and issue #542) it's highly
unlikely to be a problem. */
SVN_ERR (svn_wc__timestamps_equal_p (&equal_timestamps, filename,
svn_wc__text_time, subpool));
if (equal_timestamps)
{
*modified_p = FALSE;
goto cleanup;
}
/* If there's no text-base file, we have to assume the working file
is modified. For example, a file scheduled for addition but not
yet committed. */
textbase_filename = svn_wc__text_base_path (filename, 0, subpool);
SVN_ERR (svn_io_check_path (textbase_filename->data, &kind, subpool));
if (kind != svn_node_file)
{
*modified_p = TRUE;
goto cleanup;
}
/* Otherwise, fall back on the standard mod detector. */
SVN_ERR (svn_wc__versioned_file_modcheck (modified_p,
filename,
textbase_filename,
subpool));
cleanup:
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_conflicted_p (svn_boolean_t *text_conflicted_p,
svn_boolean_t *prop_conflicted_p,
svn_stringbuf_t *dir_path,
svn_wc_entry_t *entry,
apr_pool_t *pool)
{
svn_stringbuf_t *path;
enum svn_node_kind kind;
apr_pool_t *subpool = svn_pool_create (pool);
*text_conflicted_p = FALSE;
*prop_conflicted_p = FALSE;
/* Look for any text conflict, exercising only as much effort as
necessary to obtain a definitive answer. This only applies to
files, but we don't have to explicitly check that entry is a
file, since these attributes would never be set on a directory
anyway. A conflict file entry notation only counts if the
conflict file still exists on disk. */
if (entry->conflict_old)
{
path = svn_stringbuf_dup (dir_path, subpool);
svn_path_add_component (path, entry->conflict_old);
SVN_ERR (svn_io_check_path (path->data, &kind, subpool));
if (kind == svn_node_file)
*text_conflicted_p = TRUE;
}
if ((! *text_conflicted_p) && (entry->conflict_new))
{
path = svn_stringbuf_dup (dir_path, subpool);
svn_path_add_component (path, entry->conflict_new);
SVN_ERR (svn_io_check_path (path->data, &kind, subpool));
if (kind == svn_node_file)
*text_conflicted_p = TRUE;
}
if ((! *text_conflicted_p) && (entry->conflict_wrk))
{
path = svn_stringbuf_dup (dir_path, subpool);
svn_path_add_component (path, entry->conflict_wrk);
SVN_ERR (svn_io_check_path (path->data, &kind, subpool));
if (kind == svn_node_file)
*text_conflicted_p = TRUE;
}
/* What about prop conflicts? */
if (entry->prejfile)
{
path = svn_stringbuf_dup (dir_path, subpool);
svn_path_add_component (path, entry->prejfile);
SVN_ERR (svn_io_check_path (path->data, &kind, subpool));
if (kind == svn_node_file)
*prop_conflicted_p = TRUE;
}
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_has_binary_prop (svn_boolean_t *has_binary_prop,
const svn_stringbuf_t *path,
apr_pool_t *pool)
{
const svn_string_t *value;
apr_pool_t *subpool = svn_pool_create (pool);
/* The heuristic here is simple; a file is of type `binary' iff it
has the `svn:mime-type' property and its value does *not* start
with `text/'. */
SVN_ERR (svn_wc_prop_get (&value, SVN_PROP_MIME_TYPE, path->data, subpool));
if (value
&& (value->len > 5)
&& (strncmp (value->data, "text/", 5)))
*has_binary_prop = TRUE;
else
*has_binary_prop = FALSE;
svn_pool_destroy (subpool);
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */