summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/update.c
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>1997-05-15 22:46:24 +0000
committerpeter <peter@FreeBSD.org>1997-05-15 22:46:24 +0000
commit4f40fe8334ad5f056e1d9105f23fe7ac859c39ba (patch)
tree3b2f0092fa216d9f61059ba94b7f10b5bacf9496 /contrib/cvs/src/update.c
parent8982e501c77217c860f79bba431f46a62b607a21 (diff)
downloadFreeBSD-src-4f40fe8334ad5f056e1d9105f23fe7ac859c39ba.zip
FreeBSD-src-4f40fe8334ad5f056e1d9105f23fe7ac859c39ba.tar.gz
Import of cvs-1.9.9-970515 onto vendor branch.
Obtained from: cyclic.com
Diffstat (limited to 'contrib/cvs/src/update.c')
-rw-r--r--contrib/cvs/src/update.c1443
1 files changed, 853 insertions, 590 deletions
diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c
index 2478316..4b7022a 100644
--- a/contrib/cvs/src/update.c
+++ b/contrib/cvs/src/update.c
@@ -34,43 +34,55 @@
*/
#include "cvs.h"
+#include "savecwd.h"
#ifdef SERVER_SUPPORT
#include "md5.h"
#endif
#include "watch.h"
#include "fileattr.h"
#include "edit.h"
+#include "getline.h"
-static int checkout_file PROTO((char *file, char *repository, List *entries,
- RCSNode *rcsnode, Vers_TS *vers_ts, char *update_dir));
+static int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts,
+ int adding));
#ifdef SERVER_SUPPORT
-static int patch_file PROTO((char *file, char *repository, List *entries,
- RCSNode*rcsnode, Vers_TS *vers_ts, char *update_dir,
- int *docheckout, struct stat *file_info,
- unsigned char *checksum));
+static int patch_file PROTO ((struct file_info *finfo,
+ Vers_TS *vers_ts,
+ int *docheckout, struct stat *file_info,
+ unsigned char *checksum));
+static void patch_file_write PROTO ((void *, const char *, size_t));
#endif
-static int isemptydir PROTO((char *dir));
-static int merge_file PROTO((char *file, char *repository, List *entries,
- Vers_TS *vers, char *update_dir));
-static int scratch_file PROTO((char *file, char *repository, List * entries,
- char *update_dir));
-static Dtype update_dirent_proc PROTO((char *dir, char *repository, char *update_dir));
-static int update_dirleave_proc PROTO((char *dir, int err, char *update_dir));
-static int update_fileproc PROTO ((struct file_info *));
-static int update_filesdone_proc PROTO((int err, char *repository,
- char *update_dir));
+static int merge_file PROTO ((struct file_info *finfo, Vers_TS *vers));
+static int scratch_file PROTO((struct file_info *finfo));
+static Dtype update_dirent_proc PROTO ((void *callerdat, char *dir,
+ char *repository, char *update_dir,
+ List *entries));
+static int update_dirleave_proc PROTO ((void *callerdat, char *dir,
+ int err, char *update_dir,
+ List *entries));
+static int update_fileproc PROTO ((void *callerdat, struct file_info *));
+static int update_filesdone_proc PROTO ((void *callerdat, int err,
+ char *repository, char *update_dir,
+ List *entries));
static int write_letter PROTO((char *file, int letter, char *update_dir));
#ifdef SERVER_SUPPORT
-static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts,
- char *update_dir, List *entries, char *repository));
+static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
#else
-static void join_file PROTO((char *file, RCSNode *rcsnode, Vers_TS *vers_ts,
- char *update_dir, List *entries));
+static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
#endif
static char *options = NULL;
static char *tag = NULL;
static char *date = NULL;
+/* This is a bit of a kludge. We call WriteTag at the beginning
+ before we know whether nonbranch is set or not. And then at the
+ end, once we have the right value for nonbranch, we call WriteTag
+ again. I don't know whether the first call is necessary or not.
+ rewrite_tag is nonzero if we are going to have to make that second
+ call. */
+static int rewrite_tag;
+static int nonbranch;
+
static char *join_rev1, *date_rev1;
static char *join_rev2, *date_rev2;
static int aflag = 0;
@@ -80,6 +92,7 @@ static int update_prune_dirs = 0;
static int pipeout = 0;
#ifdef SERVER_SUPPORT
static int patches = 0;
+static int rcs_diff_patches = 0;
#endif
static List *ignlist = (List *) NULL;
static time_t last_register_time;
@@ -93,10 +106,10 @@ static const char *const update_usage[] =
"\t-f\tForce a head revision match if tag/date not found.\n",
"\t-l\tLocal directory only, no recursion.\n",
"\t-R\tProcess directories recursively.\n",
- "\t-p\tSend updates to standard output.\n",
+ "\t-p\tSend updates to standard output (avoids stickiness).\n",
"\t-k kopt\tUse RCS kopt -k option on checkout.\n",
- "\t-r rev\tUpdate using specified revision/tag.\n",
- "\t-D date\tSet date to update from.\n",
+ "\t-r rev\tUpdate using specified revision/tag (is sticky).\n",
+ "\t-D date\tSet date to update from (is sticky).\n",
"\t-j rev\tMerge in changes made between current revision and rev.\n",
"\t-I ign\tMore files to ignore (! to reset).\n",
"\t-W spec\tWrappers specification line.\n",
@@ -123,7 +136,7 @@ update (argc, argv)
/* parse the args */
optind = 1;
- while ((c = getopt (argc, argv, "ApPflRQqduk:r:D:j:I:W:")) != -1)
+ while ((c = getopt (argc, argv, "+ApPflRQqduk:r:D:j:I:W:")) != -1)
{
switch (c)
{
@@ -188,7 +201,10 @@ update (argc, argv)
case 'u':
#ifdef SERVER_SUPPORT
if (server_active)
+ {
patches = 1;
+ rcs_diff_patches = server_use_rcs_diff ();
+ }
else
#endif
usage (update_usage);
@@ -205,9 +221,12 @@ update (argc, argv)
#ifdef CLIENT_SUPPORT
if (client_active)
{
+ int pass;
+
/* The first pass does the regular update. If we receive at least
one patch which failed, we do a second pass and just fetch
those files whose patches failed. */
+ pass = 1;
do
{
int status;
@@ -228,37 +247,37 @@ update (argc, argv)
send_arg("-P");
client_prune_dirs = update_prune_dirs;
option_with_arg ("-r", tag);
+ if (options && options[0] != '\0')
+ send_arg (options);
if (date)
client_senddate (date);
if (join_rev1)
option_with_arg ("-j", join_rev1);
if (join_rev2)
option_with_arg ("-j", join_rev2);
+ wrap_send ();
/* If the server supports the command "update-patches", that means
that it knows how to handle the -u argument to update, which
means to send patches instead of complete files. */
if (failed_patches == NULL)
{
- struct request *rq;
-
- for (rq = requests; rq->name != NULL; rq++)
- {
- if (strcmp (rq->name, "update-patches") == 0)
- {
- if (rq->status == rq_supported)
- {
- send_arg("-u");
- }
- break;
- }
- }
+#ifndef DONT_USE_PATCH
+ /* Systems which don't have the patch program ported to them
+ will want to define DONT_USE_PATCH; then CVS won't try to
+ invoke patch. */
+ if (supported_request ("update-patches"))
+ send_arg ("-u");
+#endif
}
if (failed_patches == NULL)
{
send_file_names (argc, argv, SEND_EXPAND_WILD);
- send_files (argc, argv, local, aflag);
+ /* If noexec, probably could be setting SEND_NO_CONTENTS.
+ Same caveats as for "cvs status" apply. */
+ send_files (argc, argv, local, aflag,
+ update_build_dirs ? SEND_BUILD_DIRS : 0);
}
else
{
@@ -267,8 +286,8 @@ update (argc, argv)
(void) printf ("%s client: refetching unpatchable files\n",
program_name);
- if (toplevel_wd[0] != '\0'
- && chdir (toplevel_wd) < 0)
+ if (toplevel_wd != NULL
+ && CVS_CHDIR (toplevel_wd) < 0)
{
error (1, errno, "could not chdir to %s", toplevel_wd);
}
@@ -277,7 +296,7 @@ update (argc, argv)
(void) unlink_file (failed_patches[i]);
send_file_names (failed_patches_count, failed_patches, 0);
send_files (failed_patches_count, failed_patches, local,
- aflag);
+ aflag, update_build_dirs ? SEND_BUILD_DIRS : 0);
}
failed_patches = NULL;
@@ -286,9 +305,27 @@ update (argc, argv)
send_to_server ("update\012", 0);
status = get_responses_and_close ();
- if (status != 0)
+
+ /* If there are any conflicts, the server will return a
+ non-zero exit status. If any patches failed, we still
+ want to run the update again. We use a pass count to
+ avoid an endless loop. */
+
+ /* Notes: (1) assuming that status != 0 implies a
+ potential conflict is the best we can cleanly do given
+ the current protocol. I suppose that trying to
+ re-fetch in cases where there was a more serious error
+ is probably more or less harmless, but it isn't really
+ ideal. (2) it would be nice to have a testsuite case for the
+ conflict-and-patch-failed case. */
+
+ if (status != 0
+ && (failed_patches == NULL || pass > 1))
+ {
return status;
+ }
+ ++pass;
} while (failed_patches != NULL);
return 0;
@@ -297,8 +334,10 @@ update (argc, argv)
if (tag != NULL)
tag_check_valid (tag, argc, argv, local, aflag, "");
- /* FIXME: We don't call tag_check_valid on join_rev1 and join_rev2
- yet (make sure to handle ':' correctly if we do, though). */
+ if (join_rev1 != NULL)
+ tag_check_valid_join (join_rev1, argc, argv, local, aflag, "");
+ if (join_rev2 != NULL)
+ tag_check_valid_join (join_rev2, argc, argv, local, aflag, "");
/*
* If we are updating the entire directory (for real) and building dirs
@@ -320,11 +359,10 @@ update (argc, argv)
/* keep the CVS/Tag file current with the specified arguments */
if (aflag || tag || date)
{
- WriteTag ((char *) NULL, tag, date);
-#ifdef SERVER_SUPPORT
- if (server_active)
- server_set_sticky (".", Name_Repository (NULL, NULL), tag, date);
-#endif
+ WriteTag ((char *) NULL, tag, date, 0,
+ ".", Name_Repository (NULL, NULL));
+ rewrite_tag = 1;
+ nonbranch = 0;
}
}
@@ -402,9 +440,9 @@ do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
/* call the recursion processor */
err = start_recursion (update_fileproc, update_filesdone_proc,
- update_dirent_proc, update_dirleave_proc,
+ update_dirent_proc, update_dirleave_proc, NULL,
argc, argv, local, which, aflag, 1,
- preload_update_dir, 1, 0);
+ preload_update_dir, 1);
/* see if we need to sleep before returning */
if (last_register_time)
@@ -431,16 +469,36 @@ do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
* appropriate magic for checkout
*/
static int
-update_fileproc (finfo)
+update_fileproc (callerdat, finfo)
+ void *callerdat;
struct file_info *finfo;
{
int retval;
Ctype status;
Vers_TS *vers;
+ int resurrecting;
+
+ resurrecting = 0;
+
+ status = Classify_File (finfo, tag, date, options, force_tag_match,
+ aflag, &vers, pipeout);
+
+ /* Keep track of whether TAG is a branch tag.
+ Note that if it is a branch tag in some files and a nonbranch tag
+ in others, treat it as a nonbranch tag. It is possible that case
+ should elicit a warning or an error. */
+ if (rewrite_tag
+ && tag != NULL
+ && finfo->rcs != NULL)
+ {
+ char *rev = RCS_getversion (finfo->rcs, tag, NULL, 1, NULL);
+ if (rev != NULL
+ && !RCS_nodeisbranch (finfo->rcs, tag))
+ nonbranch = 1;
+ if (rev != NULL)
+ free (rev);
+ }
- status = Classify_File (finfo->file, tag, date, options, force_tag_match,
- aflag, finfo->repository, finfo->entries, finfo->rcs, &vers,
- finfo->update_dir, pipeout);
if (pipeout)
{
/*
@@ -469,8 +527,7 @@ update_fileproc (finfo)
#ifdef SERVER_SUPPORT
case T_PATCH: /* needs patch */
#endif
- retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
- vers, finfo->update_dir);
+ retval = checkout_file (finfo, vers, 0);
break;
default: /* can't ever happen :-) */
@@ -504,11 +561,10 @@ update_fileproc (finfo)
if (wrap_merge_is_copy (finfo->file))
/* Should we be warning the user that we are
* overwriting the user's copy of the file? */
- retval = checkout_file (finfo->file, finfo->repository, finfo->entries,
- finfo->rcs, vers, finfo->update_dir);
+ retval =
+ checkout_file (finfo, vers, 0);
else
- retval = merge_file (finfo->file, finfo->repository, finfo->entries,
- vers, finfo->update_dir);
+ retval = merge_file (finfo, vers);
}
break;
case T_MODIFIED: /* locally modified */
@@ -538,22 +594,11 @@ update_fileproc (finfo)
if (retcode)
{
- /*
- * If the timestamps differ, look for Conflict
- * indicators to see if 'C' anyway.
- */
- run_setup ("%s", GREP);
- run_arg (RCS_MERGE_PAT);
- run_arg (finfo->file);
- retcode = run_exec (RUN_TTY, DEVNULL,
- RUN_TTY,RUN_NORMAL);
- if (retcode == -1)
- {
- error (1, errno,
- "fork failed while examining conflict in `%s'",
- finfo->fullname);
- }
+ /* The timestamps differ. But if there are conflict
+ markers print 'C' anyway. */
+ retcode = !file_has_markers (finfo);
}
+
if (!retcode)
{
(void) write_letter (finfo->file, 'C', finfo->update_dir);
@@ -578,15 +623,17 @@ update_fileproc (finfo)
struct stat file_info;
unsigned char checksum[16];
- retval = patch_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
- vers, finfo->update_dir, &docheckout,
+ retval = patch_file (finfo,
+ vers, &docheckout,
&file_info, checksum);
if (! docheckout)
{
if (server_active && retval == 0)
- server_updated (finfo->file, finfo->update_dir, finfo->repository,
- SERVER_PATCHED, &file_info,
- checksum);
+ server_updated (finfo, vers,
+ (rcs_diff_patches
+ ? SERVER_RCS_DIFF
+ : SERVER_PATCHED),
+ &file_info, checksum);
break;
}
}
@@ -597,11 +644,10 @@ update_fileproc (finfo)
/* Fall through. */
#endif
case T_CHECKOUT: /* needs checkout */
- retval = checkout_file (finfo->file, finfo->repository, finfo->entries, finfo->rcs,
- vers, finfo->update_dir);
+ retval = checkout_file (finfo, vers, 0);
#ifdef SERVER_SUPPORT
if (server_active && retval == 0)
- server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ server_updated (finfo, vers,
SERVER_UPDATED, (struct stat *) NULL,
(unsigned char *) NULL);
#endif
@@ -613,12 +659,16 @@ update_fileproc (finfo)
retval = write_letter (finfo->file, 'R', finfo->update_dir);
break;
case T_REMOVE_ENTRY: /* needs to be un-registered */
- retval = scratch_file (finfo->file, finfo->repository, finfo->entries, finfo->update_dir);
+ retval = scratch_file (finfo);
#ifdef SERVER_SUPPORT
if (server_active && retval == 0)
- server_updated (finfo->file, finfo->update_dir, finfo->repository,
+ {
+ if (vers->ts_user == NULL)
+ server_scratch_entry_only ();
+ server_updated (finfo, vers,
SERVER_UPDATED, (struct stat *) NULL,
(unsigned char *) NULL);
+ }
#endif
break;
default: /* can't ever happen :-) */
@@ -631,11 +681,7 @@ update_fileproc (finfo)
/* only try to join if things have gone well thus far */
if (retval == 0 && join_rev1)
-#ifdef SERVER_SUPPORT
- join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries, finfo->repository);
-#else
- join_file (finfo->file, finfo->rcs, vers, finfo->update_dir, finfo->entries);
-#endif
+ join_file (finfo, vers);
/* if this directory has an ignore list, add this file to it */
if (ignlist)
@@ -665,15 +711,23 @@ update_ignproc (file, dir)
/* ARGSUSED */
static int
-update_filesdone_proc (err, repository, update_dir)
+update_filesdone_proc (callerdat, err, repository, update_dir, entries)
+ void *callerdat;
int err;
char *repository;
char *update_dir;
+ List *entries;
{
+ if (rewrite_tag)
+ {
+ WriteTag (NULL, tag, date, nonbranch, update_dir, repository);
+ rewrite_tag = 0;
+ }
+
/* if this directory has an ignore list, process it then free it */
if (ignlist)
{
- ignore_files (ignlist, update_dir, update_ignproc);
+ ignore_files (ignlist, entries, update_dir, update_ignproc);
dellist (&ignlist);
}
@@ -694,7 +748,7 @@ update_filesdone_proc (err, repository, update_dir)
{
/* If there is no CVS/Root file, add one */
if (!isfile (CVSADM_ROOT))
- Create_Root( (char *) NULL, CVSroot );
+ Create_Root ((char *) NULL, CVSroot_original);
}
return (err);
@@ -709,18 +763,20 @@ update_filesdone_proc (err, repository, update_dir)
* recursion code should skip this directory.
*/
static Dtype
-update_dirent_proc (dir, repository, update_dir)
+update_dirent_proc (callerdat, dir, repository, update_dir, entries)
+ void *callerdat;
char *dir;
char *repository;
char *update_dir;
+ List *entries;
{
if (ignore_directory (update_dir))
- {
+ {
/* print the warm fuzzy message */
if (!quiet)
error (0, 0, "Ignoring %s", update_dir);
return R_SKIP_ALL;
- }
+ }
if (!isdir (dir))
{
@@ -738,7 +794,13 @@ update_dirent_proc (dir, repository, update_dir)
{
/* otherwise, create the dir and appropriate adm files */
make_directory (dir);
- Create_Admin (dir, update_dir, repository, tag, date);
+ Create_Admin (dir, update_dir, repository, tag, date,
+ /* This is a guess. We will rewrite it later
+ via WriteTag. */
+ 0);
+ rewrite_tag = 1;
+ nonbranch = 0;
+ Subdir_Register (entries, (char *) NULL, dir);
}
}
/* Do we need to check noexec here? */
@@ -773,8 +835,9 @@ update_dirent_proc (dir, repository, update_dir)
{
if (update_build_dirs)
{
- char tmp[PATH_MAX];
+ char *tmp;
+ tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ENTSTAT) + 10);
(void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT);
if (unlink_file (tmp) < 0 && ! existence_error (errno))
error (1, errno, "cannot remove file %s", tmp);
@@ -782,16 +845,15 @@ update_dirent_proc (dir, repository, update_dir)
if (server_active)
server_clear_entstat (update_dir, repository);
#endif
+ free (tmp);
}
/* keep the CVS/Tag file current with the specified arguments */
if (aflag || tag || date)
{
- WriteTag (dir, tag, date);
-#ifdef SERVER_SUPPORT
- if (server_active)
- server_set_sticky (update_dir, repository, tag, date);
-#endif
+ WriteTag (dir, tag, date, 0, update_dir, repository);
+ rewrite_tag = 1;
+ nonbranch = 0;
}
/* initialize the ignore list for this directory */
@@ -812,75 +874,157 @@ update_dirent_proc (dir, repository, update_dir)
*/
/* ARGSUSED */
static int
-update_dirleave_proc (dir, err, update_dir)
+update_dirleave_proc (callerdat, dir, err, update_dir, entries)
+ void *callerdat;
char *dir;
int err;
char *update_dir;
+ List *entries;
{
FILE *fp;
/* run the update_prog if there is one */
+ /* FIXME: should be checking for errors from CVS_FOPEN and printing
+ them if not existence_error. */
if (err == 0 && !pipeout && !noexec &&
- (fp = fopen (CVSADM_UPROG, "r")) != NULL)
+ (fp = CVS_FOPEN (CVSADM_UPROG, "r")) != NULL)
{
char *cp;
char *repository;
- char line[MAXLINELEN];
+ char *line = NULL;
+ size_t line_allocated = 0;
repository = Name_Repository ((char *) NULL, update_dir);
- if (fgets (line, sizeof (line), fp) != NULL)
+ if (getline (&line, &line_allocated, fp) >= 0)
{
if ((cp = strrchr (line, '\n')) != NULL)
*cp = '\0';
run_setup ("%s %s", line, repository);
- (void) printf ("%s %s: Executing '", program_name, command_name);
+ cvs_output (program_name, 0);
+ cvs_output (" ", 1);
+ cvs_output (command_name, 0);
+ cvs_output (": Executing '", 0);
run_print (stdout);
- (void) printf ("'\n");
+ cvs_output ("'\n", 0);
(void) run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
}
- (void) fclose (fp);
+ else if (ferror (fp))
+ error (0, errno, "cannot read %s", CVSADM_UPROG);
+ else
+ error (0, 0, "unexpected end of file on %s", CVSADM_UPROG);
+
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", CVSADM_UPROG);
+ if (line != NULL)
+ free (line);
free (repository);
}
- /* FIXME: chdir ("..") loses with symlinks. */
- /* Prune empty dirs on the way out - if necessary */
- (void) chdir ("..");
- if (update_prune_dirs && isemptydir (dir))
+ if (strchr (dir, '/') == NULL)
{
- /* I'm not sure the existence_error is actually possible (except
- in cases where we really should print a message), but since
- this code used to ignore all errors, I'll play it safe. */
- if (unlink_file_dir (dir) < 0 && !existence_error (errno))
- error (0, errno, "cannot remove %s directory", dir);
+ /* FIXME: chdir ("..") loses with symlinks. */
+ /* Prune empty dirs on the way out - if necessary */
+ (void) CVS_CHDIR ("..");
+ if (update_prune_dirs && isemptydir (dir, 0))
+ {
+ /* I'm not sure the existence_error is actually possible (except
+ in cases where we really should print a message), but since
+ this code used to ignore all errors, I'll play it safe. */
+ if (unlink_file_dir (dir) < 0 && !existence_error (errno))
+ error (0, errno, "cannot remove %s directory", dir);
+ Subdir_Deregister (entries, (char *) NULL, dir);
+ }
}
return (err);
}
-/*
- * Returns 1 if the argument directory is completely empty, other than the
- * existence of the CVS directory entry. Zero otherwise.
- */
+static int isremoved PROTO ((Node *, void *));
+
+/* Returns 1 if the file indicated by node has been removed. */
static int
-isemptydir (dir)
+isremoved (node, closure)
+ Node *node;
+ void *closure;
+{
+ Entnode *entdata = (Entnode*) node->data;
+
+ /* If the first character of the version is a '-', the file has been
+ removed. */
+ return (entdata->version && entdata->version[0] == '-') ? 1 : 0;
+}
+
+/* Returns 1 if the argument directory is completely empty, other than the
+ existence of the CVS directory entry. Zero otherwise. If MIGHT_NOT_EXIST
+ and the directory doesn't exist, then just return 0. */
+int
+isemptydir (dir, might_not_exist)
char *dir;
+ int might_not_exist;
{
DIR *dirp;
struct dirent *dp;
- if ((dirp = opendir (dir)) == NULL)
+ if ((dirp = CVS_OPENDIR (dir)) == NULL)
{
- error (0, 0, "cannot open directory %s for empty check", dir);
+ if (might_not_exist && existence_error (errno))
+ return 0;
+ error (0, errno, "cannot open directory %s for empty check", dir);
return (0);
}
+ errno = 0;
while ((dp = readdir (dirp)) != NULL)
{
- if (strcmp (dp->d_name, ".") != 0 && strcmp (dp->d_name, "..") != 0 &&
- strcmp (dp->d_name, CVSADM) != 0)
+ if (strcmp (dp->d_name, ".") != 0
+ && strcmp (dp->d_name, "..") != 0)
{
- (void) closedir (dirp);
- return (0);
+ if (strcmp (dp->d_name, CVSADM) != 0)
+ {
+ /* An entry other than the CVS directory. The directory
+ is certainly not empty. */
+ (void) closedir (dirp);
+ return (0);
+ }
+ else
+ {
+ /* The CVS directory entry. We don't have to worry about
+ this unless the Entries file indicates that files have
+ been removed, but not committed, in this directory.
+ (Removing the directory would prevent people from
+ comitting the fact that they removed the files!) */
+ List *l;
+ int files_removed;
+ struct saved_cwd cwd;
+
+ if (save_cwd (&cwd))
+ error_exit ();
+
+ if (CVS_CHDIR (dir) < 0)
+ error (1, errno, "cannot change directory to %s", dir);
+ l = Entries_Open (0);
+ files_removed = walklist (l, isremoved, 0);
+ Entries_Close (l);
+
+ if (restore_cwd (&cwd, NULL))
+ error_exit ();
+ free_cwd (&cwd);
+
+ if (files_removed != 0)
+ {
+ /* There are files that have been removed, but not
+ committed! Do not consider the directory empty. */
+ (void) closedir (dirp);
+ return (0);
+ }
+ }
}
+ errno = 0;
+ }
+ if (errno != 0)
+ {
+ error (0, errno, "cannot read directory %s", dir);
+ (void) closedir (dirp);
+ return (0);
}
(void) closedir (dirp);
return (1);
@@ -890,31 +1034,26 @@ isemptydir (dir)
* scratch the Entries file entry associated with a file
*/
static int
-scratch_file (file, repository, entries, update_dir)
- char *file;
- char *repository;
- List *entries;
- char *update_dir;
+scratch_file (finfo)
+ struct file_info *finfo;
{
- history_write ('W', update_dir, "", file, repository);
- Scratch_Entry (entries, file);
- (void) unlink_file (file);
+ history_write ('W', finfo->update_dir, "", finfo->file, finfo->repository);
+ Scratch_Entry (finfo->entries, finfo->file);
+ if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+ error (0, errno, "unable to remove %s", finfo->fullname);
return (0);
}
/*
- * check out a file - essentially returns the result of the fork on "co".
+ * Check out a file.
*/
static int
-checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
- char *file;
- char *repository;
- List *entries;
- RCSNode *rcsnode;
+checkout_file (finfo, vers_ts, adding)
+ struct file_info *finfo;
Vers_TS *vers_ts;
- char *update_dir;
+ int adding;
{
- char backup[PATH_MAX];
+ char *backup;
int set_time, retval = 0;
int retcode = 0;
int status;
@@ -923,11 +1062,17 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
/* don't screw with backup files if we're going to stdout */
if (!pipeout)
{
- (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
- if (isfile (file))
- rename_file (file, backup);
+ backup = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
+ if (isfile (finfo->file))
+ rename_file (finfo->file, backup);
else
- (void) unlink_file (backup);
+ /* If -f/-t wrappers are being used to wrap up a directory,
+ then backup might be a directory instead of just a file. */
+ (void) unlink_file_dir (backup);
}
file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
@@ -941,75 +1086,36 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
{
if (!quiet)
{
- (void) fprintf (stderr, "\
-===================================================================\n");
- if (update_dir[0])
- (void) fprintf (stderr, "Checking out %s/%s\n",
- update_dir, file);
- else
- (void) fprintf (stderr, "Checking out %s\n", file);
- (void) fprintf (stderr, "RCS: %s\n", vers_ts->srcfile->path);
- (void) fprintf (stderr, "VERS: %s\n", vers_ts->vn_rcs);
- (void) fprintf (stderr, "***************\n");
+ cvs_outerr ("\
+===================================================================\n\
+Checking out ", 0);
+ cvs_outerr (finfo->fullname, 0);
+ cvs_outerr ("\n\
+RCS: ", 0);
+ cvs_outerr (vers_ts->srcfile->path, 0);
+ cvs_outerr ("\n\
+VERS: ", 0);
+ cvs_outerr (vers_ts->vn_rcs, 0);
+ cvs_outerr ("\n***************\n", 0);
}
}
- status = RCS_checkout (vers_ts->srcfile->path,
- pipeout ? NULL : file, vers_ts->vn_tag,
- vers_ts->options, RUN_TTY, 0, 0);
+ status = RCS_checkout (vers_ts->srcfile,
+ pipeout ? NULL : finfo->file,
+ vers_ts->vn_rcs, vers_ts->vn_tag,
+ vers_ts->options, RUN_TTY,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
}
if (file_is_dead || status == 0)
{
if (!pipeout)
{
Vers_TS *xvers_ts;
- int resurrecting;
-
- resurrecting = 0;
-
- if (file_is_dead && joining())
- {
- if (RCS_getversion (vers_ts->srcfile, join_rev1,
- date_rev1, 1, 0)
- || (join_rev2 != NULL &&
- RCS_getversion (vers_ts->srcfile, join_rev2,
- date_rev2, 1, 0)))
- {
- /* when joining, we need to get dead files checked
- out. Try harder. */
- /* I think that RCS_FLAGS_FORCE is here only because
- passing -f to co used to enable checking out
- a dead revision in the old version of death
- support which used a hacked RCS instead of using
- the RCS state. */
- retcode = RCS_checkout (vers_ts->srcfile->path, file,
- vers_ts->vn_rcs,
- vers_ts->options, RUN_TTY,
- RCS_FLAGS_FORCE, 0);
- if (retcode != 0)
- {
- error (retcode == -1 ? 1 : 0,
- retcode == -1 ? errno : 0,
- "could not check out %s", file);
- (void) unlink_file (backup);
- return (retcode);
- }
- file_is_dead = 0;
- resurrecting = 1;
- }
- else
- {
- /* If the file is dead and does not contain either of
- the join revisions, then we don't want to check it
- out. */
- return 0;
- }
- }
if (cvswrite == TRUE
&& !file_is_dead
- && !fileattr_get (file, "_watched"))
- xchmod (file, 1);
+ && !fileattr_get (finfo->file, "_watched"))
+ xchmod (finfo->file, 1);
{
/* A newly checked out file is never under the spell
@@ -1020,11 +1126,11 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
struct addremove_args args;
- editor_set (file, getcaller (), NULL);
+ editor_set (finfo->file, getcaller (), NULL);
memset (&args, 0, sizeof args);
args.remove_temp = 1;
- watch_modify_watchers (file, &args);
+ watch_modify_watchers (finfo->file, &args);
}
/* set the time from the RCS file iff it was unknown before */
@@ -1036,10 +1142,10 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
else
set_time = 0;
- wrap_fromcvs_process_file (file);
+ wrap_fromcvs_process_file (finfo->file);
- xvers_ts = Version_TS (repository, options, tag, date, file,
- force_tag_match, set_time, entries, rcsnode);
+ xvers_ts = Version_TS (finfo, options, tag, date,
+ force_tag_match, set_time);
if (strcmp (xvers_ts->options, "-V4") == 0)
xvers_ts->options[0] = '\0';
@@ -1049,31 +1155,31 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
{
if (xvers_ts->vn_user != NULL)
{
- if (update_dir[0] == '\0')
- error (0, 0,
- "warning: %s is not (any longer) pertinent",
- file);
- else
- error (0, 0,
- "warning: %s/%s is not (any longer) pertinent",
- update_dir, file);
+ error (0, 0,
+ "warning: %s is not (any longer) pertinent",
+ finfo->fullname);
}
- Scratch_Entry (entries, file);
- if (unlink_file (file) < 0 && ! existence_error (errno))
+ Scratch_Entry (finfo->entries, finfo->file);
+#ifdef SERVER_SUPPORT
+ if (server_active && xvers_ts->ts_user == NULL)
+ server_scratch_entry_only ();
+#endif
+ /* FIXME: Rather than always unlink'ing, and ignoring the
+ existence_error, we should do the unlink only if
+ vers_ts->ts_user is non-NULL. Then there would be no
+ need to ignore an existence_error (for example, if the
+ user removes the file while we are running). */
+ if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
{
- if (update_dir[0] == '\0')
- error (0, errno, "cannot remove %s", file);
- else
- error (0, errno, "cannot remove %s/%s", update_dir,
- file);
+ error (0, errno, "cannot remove %s", finfo->fullname);
}
}
else
- Register (entries, file,
- resurrecting ? "0" : xvers_ts->vn_rcs,
- xvers_ts->ts_user, xvers_ts->options,
- xvers_ts->tag, xvers_ts->date,
- (char *)0); /* Clear conflict flag on fresh checkout */
+ Register (finfo->entries, finfo->file,
+ adding ? "0" : xvers_ts->vn_rcs,
+ xvers_ts->ts_user, xvers_ts->options,
+ xvers_ts->tag, xvers_ts->date,
+ (char *)0); /* Clear conflict flag on fresh checkout */
/* fix up the vers structure, in case it is used by join */
if (join_rev1)
@@ -1088,14 +1194,14 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
/* If this is really Update and not Checkout, recode history */
if (strcmp (command_name, "update") == 0)
- history_write ('U', update_dir, xvers_ts->vn_rcs, file,
- repository);
+ history_write ('U', finfo->update_dir, xvers_ts->vn_rcs, finfo->file,
+ finfo->repository);
freevers_ts (&xvers_ts);
if (!really_quiet && !file_is_dead)
{
- write_letter (file, 'U', update_dir);
+ write_letter (finfo->file, 'U', finfo->update_dir);
}
}
}
@@ -1104,165 +1210,190 @@ checkout_file (file, repository, entries, rcsnode, vers_ts, update_dir)
int old_errno = errno; /* save errno value over the rename */
if (!pipeout && isfile (backup))
- rename_file (backup, file);
+ rename_file (backup, finfo->file);
error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
- "could not check out %s", file);
+ "could not check out %s", finfo->fullname);
retval = retcode;
}
if (!pipeout)
- (void) unlink_file (backup);
+ {
+ /* If -f/-t wrappers are being used to wrap up a directory,
+ then backup might be a directory instead of just a file. */
+ (void) unlink_file_dir (backup);
+ free (backup);
+ }
return (retval);
}
#ifdef SERVER_SUPPORT
-/* Patch a file. Runs rcsdiff. This is only done when running as the
+
+/* This structure is used to pass information between patch_file and
+ patch_file_write. */
+
+struct patch_file_data
+{
+ /* File name, for error messages. */
+ const char *filename;
+ /* File to which to write. */
+ FILE *fp;
+ /* Whether to compute the MD5 checksum. */
+ int compute_checksum;
+ /* Data structure for computing the MD5 checksum. */
+ struct MD5Context context;
+ /* Set if the file has a final newline. */
+ int final_nl;
+};
+
+/* Patch a file. Runs diff. This is only done when running as the
* server. The hope is that the diff will be smaller than the file
* itself.
*/
static int
-patch_file (file, repository, entries, rcsnode, vers_ts, update_dir,
- docheckout, file_info, checksum)
- char *file;
- char *repository;
- List *entries;
- RCSNode *rcsnode;
+patch_file (finfo, vers_ts, docheckout, file_info, checksum)
+ struct file_info *finfo;
Vers_TS *vers_ts;
- char *update_dir;
int *docheckout;
struct stat *file_info;
unsigned char *checksum;
{
- char backup[PATH_MAX];
- char file1[PATH_MAX];
- char file2[PATH_MAX];
+ char *backup;
+ char *file1;
+ char *file2;
int retval = 0;
int retcode = 0;
int fail;
FILE *e;
+ struct patch_file_data data;
*docheckout = 0;
- if (pipeout || joining ())
+ if (noexec || pipeout || joining ())
{
*docheckout = 1;
return 0;
}
- (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, file);
- if (isfile (file))
- rename_file (file, backup);
+ /* If this file has been marked as being binary, then never send a
+ patch. */
+ if (strcmp (vers_ts->options, "-kb") == 0)
+ {
+ *docheckout = 1;
+ return 0;
+ }
+
+ backup = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
+ if (isfile (finfo->file))
+ rename_file (finfo->file, backup);
else
(void) unlink_file (backup);
-
- (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, file);
- (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, file);
+
+ file1 = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, finfo->file);
+ file2 = xmalloc (strlen (finfo->file)
+ + sizeof (CVSADM)
+ + sizeof (CVSPREFIX)
+ + 10);
+ (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, finfo->file);
fail = 0;
/* We need to check out both revisions first, to see if either one
has a trailing newline. Because of this, we don't use rcsdiff,
but just use diff. */
- if (noexec)
- retcode = 0;
- else
- retcode = RCS_checkout (vers_ts->srcfile->path, NULL,
- vers_ts->vn_user,
- vers_ts->options, file1, 0, 0);
- if (retcode != 0)
- fail = 1;
- else
- {
- e = fopen (file1, "r");
- if (e == NULL)
- fail = 1;
- else
- {
- if (fseek (e, (long) -1, SEEK_END) == 0
- && getc (e) != '\n')
- {
- fail = 1;
- }
- fclose (e);
- }
- }
+
+ e = CVS_FOPEN (file1, "w");
+ if (e == NULL)
+ error (1, errno, "cannot open %s", file1);
+
+ data.filename = file1;
+ data.fp = e;
+ data.final_nl = 0;
+ data.compute_checksum = 0;
+
+ retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_user, (char *) NULL,
+ vers_ts->options, RUN_TTY,
+ patch_file_write, (void *) &data);
+
+ if (fclose (e) < 0)
+ error (1, errno, "cannot close %s", file1);
+
+ if (retcode != 0 || ! data.final_nl)
+ fail = 1;
if (! fail)
{
- /* Check it out into file, and then move to file2, so that we
- can get the right modes into *FILE_INFO. We can't check it
- out directly into file2 because co doesn't understand how
- to do that. */
- retcode = RCS_checkout (vers_ts->srcfile->path, file,
- vers_ts->vn_rcs,
- vers_ts->options, RUN_TTY, 0, 0);
- if (retcode != 0)
- fail = 1;
- else
- {
- if (!isreadable (file))
- {
- /* File is dead. */
- fail = 1;
- }
- else
- {
- rename_file (file, file2);
- if (cvswrite == TRUE
- && !fileattr_get (file, "_watched"))
- xchmod (file2, 1);
- e = fopen (file2, "r");
- if (e == NULL)
- fail = 1;
- else
- {
- struct MD5Context context;
- int nl;
- unsigned char buf[8192];
- unsigned len;
+ e = CVS_FOPEN (file2, "w");
+ if (e == NULL)
+ error (1, errno, "cannot open %s", file2);
- nl = 0;
+ data.filename = file2;
+ data.fp = e;
+ data.final_nl = 0;
+ data.compute_checksum = 1;
+ MD5Init (&data.context);
- /* Compute the MD5 checksum and make sure there is
- a trailing newline. */
- MD5Init (&context);
- while ((len = fread (buf, 1, sizeof buf, e)) != 0)
- {
- nl = buf[len - 1] == '\n';
- MD5Update (&context, buf, len);
- }
- MD5Final (checksum, &context);
+ retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
+ vers_ts->vn_rcs, (char *) NULL,
+ vers_ts->options, RUN_TTY,
+ patch_file_write, (void *) &data);
- if (ferror (e) || ! nl)
- {
- fail = 1;
- }
+ if (fclose (e) < 0)
+ error (1, errno, "cannot close %s", file2);
- fclose (e);
- }
- }
- }
+ if (retcode != 0 || ! data.final_nl)
+ fail = 1;
+ else
+ MD5Final (checksum, &data.context);
}
retcode = 0;
if (! fail)
{
- /* FIXME: This whole thing with diff/patch is rather more
- convoluted than necessary (lots of forks and execs, need to
- worry about versions of diff and patch, etc.). Also, we
- send context lines which aren't needed (in the rare case in
- which the diff doesn't apply, the checksum would catches it).
- Solution perhaps is to librarify the RCS routines which apply
- deltas or something equivalent. */
- /* This is -c, not -u, because we have no way of knowing which
- DIFF is in use. */
- run_setup ("%s -c %s %s", DIFF, file1, file2);
+ const char *diff_options;
+
+ /* FIXME: It might be better to come up with a diff library
+ which can be shared with the diffutils. */
+ /* If the client does not support the Rcs-diff command, we
+ send a context diff, and the client must invoke patch.
+ That approach was problematical for various reasons. The
+ new approach only requires running diff in the server; the
+ client can handle everything without invoking an external
+ program. */
+ if (! rcs_diff_patches)
+ {
+ /* We use -c, not -u, because we have no way of knowing
+ which DIFF is in use. */
+ diff_options = "-c";
+ }
+ else
+ {
+ /* FIXME: We should use -a if diff supports it. We should
+ probably just copy over most or all of the diff
+ handling in the RCS configure script. */
+ /* IMHO, we shouldn't copy over anything which even
+ vaguely resembles the RCS configure script. That kind of
+ thing tends to be ugly, slow, and fragile. It also is a
+ a support headache for CVS to behave differently in subtle
+ ways based on whether it was installed correctly. Instead we
+ should come up with a diff library. -kingdon, Apr 1997. */
+ diff_options = "-n";
+ }
+ run_setup ("%s %s %s %s", DIFF, diff_options, file1, file2);
/* A retcode of 0 means no differences. 1 means some differences. */
- if ((retcode = run_exec (RUN_TTY, file, RUN_TTY, RUN_NORMAL)) != 0
+ if ((retcode = run_exec (RUN_TTY, finfo->file, RUN_TTY, RUN_NORMAL)) != 0
&& retcode != 1)
{
fail = 1;
@@ -1273,10 +1404,24 @@ patch_file (file, repository, entries, rcsnode, vers_ts, update_dir,
char buf[sizeof BINARY];
unsigned int c;
+ /* Stat the original RCS file, and then adjust it the way
+ that RCS_checkout would. FIXME: This is an abstraction
+ violation. */
+ if (CVS_STAT (vers_ts->srcfile->path, file_info) < 0)
+ error (1, errno, "could not stat %s", vers_ts->srcfile->path);
+ if (chmod (finfo->file,
+ file_info->st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH))
+ < 0)
+ error (0, errno, "cannot change mode of file %s", finfo->file);
+ if (cvswrite == TRUE
+ && !fileattr_get (finfo->file, "_watched"))
+ xchmod (finfo->file, 1);
+
/* Check the diff output to make sure patch will be handle it. */
- e = fopen (file, "r");
+ e = CVS_FOPEN (finfo->file, "r");
if (e == NULL)
- error (1, errno, "could not open diff output file %s", file);
+ error (1, errno, "could not open diff output file %s",
+ finfo->fullname);
c = fread (buf, 1, sizeof BINARY - 1, e);
buf[c] = '\0';
if (strcmp (buf, BINARY) == 0)
@@ -1295,28 +1440,28 @@ patch_file (file, repository, entries, rcsnode, vers_ts, update_dir,
/* This stuff is just copied blindly from checkout_file. I
don't really know what it does. */
- xvers_ts = Version_TS (repository, options, tag, date, file,
- force_tag_match, 0, entries, rcsnode);
+ xvers_ts = Version_TS (finfo, options, tag, date,
+ force_tag_match, 0);
if (strcmp (xvers_ts->options, "-V4") == 0)
xvers_ts->options[0] = '\0';
- Register (entries, file, xvers_ts->vn_rcs,
+ Register (finfo->entries, finfo->file, xvers_ts->vn_rcs,
xvers_ts->ts_user, xvers_ts->options,
xvers_ts->tag, xvers_ts->date, NULL);
- if (stat (file2, file_info) < 0)
- error (1, errno, "could not stat %s", file2);
+ if (CVS_STAT (finfo->file, file_info) < 0)
+ error (1, errno, "could not stat %s", finfo->file);
/* If this is really Update and not Checkout, recode history */
if (strcmp (command_name, "update") == 0)
- history_write ('P', update_dir, xvers_ts->vn_rcs, file,
- repository);
+ history_write ('P', finfo->update_dir, xvers_ts->vn_rcs, finfo->file,
+ finfo->repository);
freevers_ts (&xvers_ts);
if (!really_quiet)
{
- write_letter (file, 'P', update_dir);
+ write_letter (finfo->file, 'P', finfo->update_dir);
}
}
else
@@ -1324,11 +1469,11 @@ patch_file (file, repository, entries, rcsnode, vers_ts, update_dir,
int old_errno = errno; /* save errno value over the rename */
if (isfile (backup))
- rename_file (backup, file);
+ rename_file (backup, finfo->file);
if (retcode != 0 && retcode != 1)
error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
- "could not diff %s", file);
+ "could not diff %s", finfo->fullname);
*docheckout = 1;
retval = retcode;
@@ -1338,9 +1483,34 @@ patch_file (file, repository, entries, rcsnode, vers_ts, update_dir,
(void) unlink_file (file1);
(void) unlink_file (file2);
+ free (backup);
+ free (file1);
+ free (file2);
return (retval);
}
-#endif
+
+/* Write data to a file. Record whether the last byte written was a
+ newline. Optionally compute a checksum. This is called by
+ patch_file via RCS_checkout. */
+
+static void
+patch_file_write (callerdat, buffer, len)
+ void *callerdat;
+ const char *buffer;
+ size_t len;
+{
+ struct patch_file_data *data = (struct patch_file_data *) callerdat;
+
+ if (fwrite (buffer, 1, len, data->fp) != len)
+ error (1, errno, "cannot write %s", data->filename);
+
+ data->final_nl = (buffer[len - 1] == '\n');
+
+ if (data->compute_checksum)
+ MD5Update (&data->context, buffer, len);
+}
+
+#endif /* SERVER_SUPPORT */
/*
* Several of the types we process only print a bit of information consisting
@@ -1373,17 +1543,14 @@ write_letter (file, letter, update_dir)
* Do all the magic associated with a file which needs to be merged
*/
static int
-merge_file (file, repository, entries, vers, update_dir)
- char *file;
- char *repository;
- List *entries;
+merge_file (finfo, vers)
+ struct file_info *finfo;
Vers_TS *vers;
- char *update_dir;
{
- char user[PATH_MAX];
- char backup[PATH_MAX];
+ char *backup;
int status;
int retcode = 0;
+ int retval;
/*
* The users currently modified file is moved to a backup file name
@@ -1392,26 +1559,58 @@ merge_file (file, repository, entries, vers, update_dir)
* is the version of the file that the user was most up-to-date with
* before the merge.
*/
- (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
- if (update_dir[0])
- (void) sprintf (user, "%s/%s", update_dir, file);
- else
- (void) strcpy (user, file);
+ backup = xmalloc (strlen (finfo->file)
+ + strlen (vers->vn_user)
+ + sizeof (BAKPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
(void) unlink_file (backup);
- copy_file (file, backup);
- xchmod (file, 1);
+ copy_file (finfo->file, backup);
+ xchmod (finfo->file, 1);
+
+ if (strcmp (vers->options, "-kb") == 0)
+ {
+ /* For binary files, a merge is always a conflict. We give the
+ user the two files, and let them resolve it. It is possible
+ that we should require a "touch foo" or similar step before
+ we allow a checkin. */
+ status = checkout_file (finfo, vers, 0);
+#ifdef SERVER_SUPPORT
+ /* Send the new contents of the file before the message. If we
+ wanted to be totally correct, we would have the client write
+ the message only after the file has safely been written. */
+ if (server_active)
+ {
+ server_copy_file (finfo->file, finfo->update_dir,
+ finfo->repository, backup);
+ server_updated (finfo, vers, SERVER_MERGED,
+ (struct stat *) NULL, (unsigned char *) NULL);
+ }
+#endif
+ error (0, 0, "binary file needs merge");
+ error (0, 0, "revision %s from repository is now in %s",
+ vers->vn_rcs, finfo->fullname);
+ error (0, 0, "file from working directory is now in %s", backup);
+ write_letter (finfo->file, 'C', finfo->update_dir);
+
+ history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
+ finfo->repository);
+ retval = 0;
+ goto out;
+ }
status = RCS_merge(vers->srcfile->path,
vers->options, vers->vn_user, vers->vn_rcs);
if (status != 0 && status != 1)
{
error (0, status == -1 ? errno : 0,
- "could not merge revision %s of %s", vers->vn_user, user);
+ "could not merge revision %s of %s", vers->vn_user, finfo->fullname);
error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
- user, backup);
- rename_file (backup, file);
- return (1);
+ finfo->fullname, backup);
+ rename_file (backup, finfo->file);
+ retval = 1;
+ goto out;
}
if (strcmp (vers->options, "-V4") == 0)
@@ -1421,8 +1620,8 @@ merge_file (file, repository, entries, vers, update_dir)
char *cp = 0;
if (status)
- cp = time_stamp (file);
- Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options,
+ cp = time_stamp (finfo->file);
+ Register (finfo->entries, finfo->file, vers->vn_rcs, vers->ts_rcs, vers->options,
vers->tag, vers->date, cp);
if (cp)
free (cp);
@@ -1442,40 +1641,48 @@ merge_file (file, repository, entries, vers, update_dir)
the message only after the file has safely been written. */
if (server_active)
{
- server_copy_file (file, update_dir, repository, backup);
- server_updated (file, update_dir, repository, SERVER_MERGED,
+ server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
+ backup);
+ server_updated (finfo, vers, SERVER_MERGED,
(struct stat *) NULL, (unsigned char *) NULL);
}
#endif
- if (!noexec && !xcmp (backup, file))
+ if (!noexec && !xcmp (backup, finfo->file))
{
printf ("%s already contains the differences between %s and %s\n",
- user, vers->vn_user, vers->vn_rcs);
- history_write ('G', update_dir, vers->vn_rcs, file, repository);
- return (0);
+ finfo->fullname, vers->vn_user, vers->vn_rcs);
+ history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
+ finfo->repository);
+ retval = 0;
+ goto out;
}
if (status == 1)
{
if (!noexec)
- error (0, 0, "conflicts found in %s", user);
+ error (0, 0, "conflicts found in %s", finfo->fullname);
- write_letter (file, 'C', update_dir);
+ write_letter (finfo->file, 'C', finfo->update_dir);
- history_write ('C', update_dir, vers->vn_rcs, file, repository);
+ history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file, finfo->repository);
}
else if (retcode == -1)
{
- error (1, errno, "fork failed while examining update of %s", user);
+ error (1, errno, "fork failed while examining update of %s",
+ finfo->fullname);
}
else
{
- write_letter (file, 'M', update_dir);
- history_write ('G', update_dir, vers->vn_rcs, file, repository);
+ write_letter (finfo->file, 'M', finfo->update_dir);
+ history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
+ finfo->repository);
}
- return (0);
+ retval = 0;
+ out:
+ free (backup);
+ return retval;
}
/*
@@ -1483,20 +1690,11 @@ merge_file (file, repository, entries, vers, update_dir)
* (-j option)
*/
static void
-#ifdef SERVER_SUPPORT
-join_file (file, rcsnode, vers, update_dir, entries, repository)
- char *repository;
-#else
-join_file (file, rcsnode, vers, update_dir, entries)
-#endif
- char *file;
- RCSNode *rcsnode;
+join_file (finfo, vers)
+ struct file_info *finfo;
Vers_TS *vers;
- char *update_dir;
- List *entries;
{
- char user[PATH_MAX];
- char backup[PATH_MAX];
+ char *backup;
char *options;
int status;
@@ -1512,24 +1710,23 @@ join_file (file, rcsnode, vers, update_dir, entries)
jdate1 = date_rev1;
jdate2 = date_rev2;
- if (wrap_merge_is_copy (file))
+ if (wrap_merge_is_copy (finfo->file))
{
- /* FIXME: Should be including update_dir in message. */
error (0, 0,
- "Cannot merge %s because it is a merge-by-copy file.", file);
+ "Cannot merge %s because it is a merge-by-copy file.",
+ finfo->fullname);
return;
}
- /* determine if we need to do anything at all */
+ /* Determine if we need to do anything at all. */
if (vers->srcfile == NULL ||
vers->srcfile->path == NULL)
{
return;
}
- /* in all cases, use two revs. */
-
- /* if only one rev is specified, it becomes the second rev */
+ /* If only one join revision is specified, it becomes the second
+ revision. */
if (jrev2 == NULL)
{
jrev2 = jrev1;
@@ -1538,219 +1735,282 @@ join_file (file, rcsnode, vers, update_dir, entries)
jdate1 = NULL;
}
- /* The file in the working directory doesn't exist in CVS/Entries.
- FIXME: Shouldn't this case result in additional processing (if
- the file was added going from rev1 to rev2, then do the equivalent
- of a "cvs add")? (yes; easier said than done.. :-) */
- if (vers->vn_user == NULL)
+ /* Convert the second revision, walking branches and dates. */
+ rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, (int *) NULL);
+
+ /* If this is a merge of two revisions, get the first revision.
+ If only one join tag was specified, then the first revision is
+ the greatest common ancestor of the second revision and the
+ working file. */
+ if (jrev1 != NULL)
+ rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, (int *) NULL);
+ else
{
- /* No merge possible YET. */
- if (jdate2 != NULL)
- error (0, 0,
- "file %s is present in revision %s as of %s",
- file, jrev2, jdate2);
+ /* Note that we use vn_rcs here, since vn_user may contain a
+ special string such as "-nn". */
+ if (vers->vn_rcs == NULL)
+ rev1 = NULL;
+ else if (rev2 == NULL)
+ {
+ /* This means that the file never existed on the branch.
+ It does not mean that the file was removed on the
+ branch: that case is represented by a dead rev2. If
+ the file never existed on the branch, then we have
+ nothing to merge, so we just return. */
+ return;
+ }
else
- error (0, 0,
- "file %s is present in revision %s",
- file, jrev2);
- return;
+ rev1 = gca (vers->vn_rcs, rev2);
}
- /* Fix for bug CVS/193:
- * Used to dump core if the file had been removed on the current branch.
- */
- if (strcmp(vers->vn_user, "0") == 0)
+ /* Handle a nonexistent or dead merge target. */
+ if (rev2 == NULL || RCS_isdead (vers->srcfile, rev2))
{
- error(0, 0,
- "file %s has been deleted",
- file);
- return;
- }
+ char *mrev;
+
+ if (rev2 != NULL)
+ free (rev2);
- /* convert the second rev spec, walking branches and dates. */
+ /* If the first revision doesn't exist either, then there is
+ no change between the two revisions, so we don't do
+ anything. */
+ if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
+ {
+ if (rev1 != NULL)
+ free (rev1);
+ return;
+ }
- rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, 0);
- if (rev2 == NULL)
- {
- if (!quiet)
+ /* If we are merging two revisions, then the file was removed
+ between the first revision and the second one. In this
+ case we want to mark the file for removal.
+
+ If we are merging one revision, then the file has been
+ removed between the greatest common ancestor and the merge
+ revision. From the perspective of the branch on to which
+ we ar emerging, which may be the trunk, either 1) the file
+ does not currently exist on the target, or 2) the file has
+ not been modified on the target branch since the greatest
+ common ancestor, or 3) the file has been modified on the
+ target branch since the greatest common ancestor. In case
+ 1 there is nothing to do. In case 2 we mark the file for
+ removal. In case 3 we have a conflict.
+
+ Note that the handling is slightly different depending upon
+ whether one or two join targets were specified. If two
+ join targets were specified, we don't check whether the
+ file was modified since a given point. My reasoning is
+ that if you ask for an explicit merge between two tags,
+ then you want to merge in whatever was changed between
+ those two tags. If a file was removed between the two
+ tags, then you want it to be removed. However, if you ask
+ for a merge of a branch, then you want to merge in all
+ changes which were made on the branch. If a file was
+ removed on the branch, that is a change to the file. If
+ the file was also changed on the main line, then that is
+ also a change. These two changes--the file removal and the
+ modification--must be merged. This is a conflict. */
+
+ /* If the user file is dead, or does not exist, or has been
+ marked for removal, then there is nothing to do. */
+ if (vers->vn_user == NULL
+ || vers->vn_user[0] == '-'
+ || RCS_isdead (vers->srcfile, vers->vn_user))
+ {
+ if (rev1 != NULL)
+ free (rev1);
+ return;
+ }
+
+ /* If the user file has been marked for addition, or has been
+ locally modified, then we have a conflict which we can not
+ resolve. No_Difference will already have been called in
+ this case, so comparing the timestamps is sufficient to
+ determine whether the file is locally modified. */
+ if (strcmp (vers->vn_user, "0") == 0
+ || (vers->ts_user != NULL
+ && strcmp (vers->ts_user, vers->ts_rcs) != 0))
{
if (jdate2 != NULL)
error (0, 0,
- "cannot find revision %s as of %s in file %s",
- jrev2, jdate2, file);
+ "file %s is locally modified, but has been removed in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
else
error (0, 0,
- "cannot find revision %s in file %s",
- jrev2, file);
+ "file %s is locally modified, but has been removed in revision %s",
+ finfo->fullname, jrev2);
+
+ /* FIXME: Should we arrange to return a non-zero exit
+ status? */
+
+ if (rev1 != NULL)
+ free (rev1);
+
+ return;
}
+
+ /* If only one join tag was specified, and the user file has
+ been changed since the greatest common ancestor (rev1),
+ then there is a conflict we can not resolve. See above for
+ the rationale. */
+ if (join_rev2 == NULL
+ && strcmp (rev1, vers->vn_user) != 0)
+ {
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s has been modified, but has been removed in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s has been modified, but has been removed in revision %s",
+ finfo->fullname, jrev2);
+
+ /* FIXME: Should we arrange to return a non-zero exit
+ status? */
+
+ if (rev1 != NULL)
+ free (rev1);
+
+ return;
+ }
+
+ if (rev1 != NULL)
+ free (rev1);
+
+ /* The user file exists and has not been modified. Mark it
+ for removal. FIXME: If we are doing a checkout, this has
+ the effect of first checking out the file, and then
+ removing it. It would be better to just register the
+ removal. */
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ server_scratch (finfo->file);
+ server_updated (finfo, vers, SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+ }
+#endif
+ mrev = xmalloc (strlen (vers->vn_user) + 2);
+ sprintf (mrev, "-%s", vers->vn_user);
+ Register (finfo->entries, finfo->file, mrev, vers->ts_rcs,
+ vers->options, vers->tag, vers->date, vers->ts_conflict);
+ free (mrev);
+ /* We need to check existence_error here because if we are
+ running as the server, and the file is up to date in the
+ working directory, the client will not have sent us a copy. */
+ if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
+ error (0, errno, "cannot remove file %s", finfo->fullname);
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ server_checked_in (finfo->file, finfo->update_dir,
+ finfo->repository);
+#endif
+ if (! really_quiet)
+ error (0, 0, "scheduling %s for removal", finfo->fullname);
+
return;
}
- /* skip joining identical revs */
- if (strcmp (rev2, vers->vn_user) == 0)
+ /* If the target of the merge is the same as the working file
+ revision, then there is nothing to do. */
+ if (vers->vn_user != NULL && strcmp (rev2, vers->vn_user) == 0)
{
- /* No merge necessary. */
+ if (rev1 != NULL)
+ free (rev1);
free (rev2);
return;
}
- if (jrev1 == NULL)
+ /* If rev1 is dead or does not exist, then the file was added
+ between rev1 and rev2. */
+ if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1))
{
- char *tst;
- /* if the first rev is missing, then it is implied to be the
- greatest common ancestor of both the join rev, and the
- checked out rev. */
-
- /* FIXME: What is this check for '!' about? If it is legal to
- have '!' in the first character of vn_user, it isn't
- documented at struct vers_ts in cvs.h. */
- tst = vers->vn_user;
- if (*tst == '!')
- {
- /* file was dead. merge anyway and pretend it's been
- added. */
- ++tst;
- Register (entries, file, "0", vers->ts_user, vers->options,
- vers->tag, (char *) 0, (char *) 0);
- }
- rev1 = gca (tst, rev2);
- if (rev1 == NULL)
- {
- /* this should not be possible */
- error (0, 0, "bad gca");
- abort();
- }
+ if (rev1 != NULL)
+ free (rev1);
+ free (rev2);
- tst = RCS_gettag (vers->srcfile, rev2, 1, 0);
- if (tst == NULL)
+ /* If the file does not exist in the working directory, then
+ we can just check out the new revision and mark it for
+ addition. */
+ if (vers->vn_user == NULL)
{
- /* this should not be possible. */
- error (0, 0, "cannot find gca");
- abort();
- }
+ Vers_TS *xvers;
- free (tst);
+ xvers = Version_TS (finfo, vers->options, jrev2, jdate2, 1, 0);
+
+ /* FIXME: If checkout_file fails, we should arrange to
+ return a non-zero exit status. */
+ status = checkout_file (finfo, xvers, 1);
+
+#ifdef SERVER_SUPPORT
+ if (server_active && status == 0)
+ server_updated (finfo, xvers,
+ SERVER_UPDATED, (struct stat *) NULL,
+ (unsigned char *) NULL);
+#endif
+
+ freevers_ts (&xvers);
- /* these two cases are noops */
- if (strcmp (rev1, rev2) == 0)
- {
- free (rev1);
- free (rev2);
return;
}
+
+ /* The file currently exists in the working directory, so we
+ have a conflict which we can not resolve. Note that this
+ is true even if the file is marked for addition or removal. */
+
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s exists, but has been added in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
+ else
+ error (0, 0,
+ "file %s exists, but has been added in revision %s",
+ finfo->fullname, jrev2);
+
+ return;
}
- else
- {
- /* otherwise, convert the first rev spec, walking branches and
- dates. */
- rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, 0);
- if (rev1 == NULL)
- {
- if (!quiet) {
- if (jdate1 != NULL)
- error (0, 0,
- "cannot find revision %s as of %s in file %s",
- jrev1, jdate1, file);
- else
- error (0, 0,
- "cannot find revision %s in file %s",
- jrev1, file);
- }
- return;
- }
+ /* If the two merge revisions are the same, then there is nothing
+ to do. */
+ if (strcmp (rev1, rev2) == 0)
+ {
+ free (rev1);
+ free (rev2);
+ return;
}
- /* do the join */
+ /* If there is no working file, then we can't do the merge. */
+ if (vers->vn_user == NULL)
+ {
+ free (rev1);
+ free (rev2);
-#if 0
- dome {
- /* special handling when two revisions are specified */
- if (join_rev1 && join_rev2)
- {
- rev = RCS_getversion (vers->srcfile, join_rev2, date_rev2, 1, 0);
- if (rev == NULL)
- {
- if (!quiet && date_rev2 == NULL)
- error (0, 0,
- "cannot find revision %s in file %s", join_rev2, file);
- return;
- }
-
- baserev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0);
- if (baserev == NULL)
- {
- if (!quiet && date_rev1 == NULL)
- error (0, 0,
- "cannot find revision %s in file %s", join_rev1, file);
- free (rev);
- return;
- }
-
- /*
- * nothing to do if:
- * second revision matches our BASE revision (vn_user) &&
- * both revisions are on the same branch
- */
- if (strcmp (vers->vn_user, rev) == 0 &&
- numdots (baserev) == numdots (rev))
- {
- /* might be the same branch. take a real look */
- char *dot = strrchr (baserev, '.');
- int len = (dot - baserev) + 1;
-
- if (strncmp (baserev, rev, len) == 0)
- return;
- }
- }
+ if (jdate2 != NULL)
+ error (0, 0,
+ "file %s is present in revision %s as of %s",
+ finfo->fullname, jrev2, jdate2);
else
- {
- rev = RCS_getversion (vers->srcfile, join_rev1, date_rev1, 1, 0);
- if (rev == NULL)
- return;
- if (strcmp (rev, vers->vn_user) == 0) /* no merge necessary */
- {
- free (rev);
- return;
- }
-
- baserev = RCS_whatbranch (file, join_rev1, rcsnode);
- if (baserev)
- {
- char *cp;
-
- /* we get a branch -- turn it into a revision, or NULL if trunk */
- if ((cp = strrchr (baserev, '.')) == NULL)
- {
- free (baserev);
- baserev = (char *) NULL;
- }
- else
- *cp = '\0';
- }
- }
- if (baserev && strcmp (baserev, rev) == 0)
- {
- /* they match -> nothing to do */
- free (rev);
- free (baserev);
- return;
- }
- }
-#endif
+ error (0, 0,
+ "file %s is present in revision %s",
+ finfo->fullname, jrev2);
- /* OK, so we have two revisions; continue on */
+ /* FIXME: Should we arrange to return a non-zero exit status? */
+
+ return;
+ }
#ifdef SERVER_SUPPORT
- if (server_active && !isreadable (file))
+ if (server_active && !isreadable (finfo->file))
{
int retcode;
/* The file is up to date. Need to check out the current contents. */
- retcode = RCS_checkout (vers->srcfile->path, "", vers->vn_user, NULL,
- RUN_TTY, 0, 0);
+ retcode = RCS_checkout (vers->srcfile, finfo->file,
+ vers->vn_user, (char *) NULL,
+ (char *) NULL, RUN_TTY,
+ (RCSCHECKOUTPROC) NULL, (void *) NULL);
if (retcode != 0)
error (1, retcode == -1 ? errno : 0,
- "failed to check out %s file", file);
+ "failed to check out %s file", finfo->fullname);
}
#endif
@@ -1761,15 +2021,15 @@ join_file (file, rcsnode, vers, update_dir, entries)
* is the version of the file that the user was most up-to-date with
* before the merge.
*/
- (void) sprintf (backup, "%s%s.%s", BAKPREFIX, file, vers->vn_user);
- if (update_dir[0])
- (void) sprintf (user, "%s/%s", update_dir, file);
- else
- (void) strcpy (user, file);
+ backup = xmalloc (strlen (finfo->file)
+ + strlen (vers->vn_user)
+ + sizeof (BAKPREFIX)
+ + 10);
+ (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
(void) unlink_file (backup);
- copy_file (file, backup);
- xchmod (file, 1);
+ copy_file (finfo->file, backup);
+ xchmod (finfo->file, 1);
options = vers->options;
#ifdef HAVE_RCS5
@@ -1783,10 +2043,10 @@ join_file (file, rcsnode, vers, update_dir, entries)
if (status != 0 && status != 1)
{
error (0, status == -1 ? errno : 0,
- "could not merge revision %s of %s", rev2, user);
+ "could not merge revision %s of %s", rev2, finfo->fullname);
error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
- user, backup);
- rename_file (backup, file);
+ finfo->fullname, backup);
+ rename_file (backup, finfo->file);
}
free (rev1);
free (rev2);
@@ -1806,8 +2066,9 @@ join_file (file, rcsnode, vers, update_dir, entries)
char *cp = 0;
if (status)
- cp = time_stamp (file);
- Register (entries, file, vers->vn_rcs, vers->ts_rcs, vers->options,
+ cp = time_stamp (finfo->file);
+ Register (finfo->entries, finfo->file,
+ vers->vn_rcs, vers->ts_rcs, vers->options,
vers->tag, vers->date, cp);
if (cp)
free(cp);
@@ -1816,11 +2077,13 @@ join_file (file, rcsnode, vers, update_dir, entries)
#ifdef SERVER_SUPPORT
if (server_active)
{
- server_copy_file (file, update_dir, repository, backup);
- server_updated (file, update_dir, repository, SERVER_MERGED,
+ server_copy_file (finfo->file, finfo->update_dir, finfo->repository,
+ backup);
+ server_updated (finfo, vers, SERVER_MERGED,
(struct stat *) NULL, (unsigned char *) NULL);
}
#endif
+ free (backup);
}
int
OpenPOWER on IntegriCloud