summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/update.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cvs/src/update.c')
-rw-r--r--contrib/cvs/src/update.c3049
1 files changed, 0 insertions, 3049 deletions
diff --git a/contrib/cvs/src/update.c b/contrib/cvs/src/update.c
deleted file mode 100644
index f2c8087..0000000
--- a/contrib/cvs/src/update.c
+++ /dev/null
@@ -1,3049 +0,0 @@
-/*
- * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
- *
- * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
- * and others.
- *
- * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
- * Portions Copyright (C) 1989-1992, Brian Berliner
- *
- * You may distribute under the terms of the GNU General Public License as
- * specified in the README file that comes with the CVS source distribution.
- *
- * "update" updates the version in the present directory with respect to the RCS
- * repository. The present version must have been created by "checkout". The
- * user can keep up-to-date by calling "update" whenever he feels like it.
- *
- * The present version can be committed by "commit", but this keeps the version
- * in tact.
- *
- * Arguments following the options are taken to be file names to be updated,
- * rather than updating the entire directory.
- *
- * Modified or non-existent RCS files are checked out and reported as U
- * <user_file>
- *
- * Modified user files are reported as M <user_file>. If both the RCS file and
- * the user file have been modified, the user file is replaced by the result
- * of rcsmerge, and a backup file is written for the user in .#file.version.
- * If this throws up irreconcilable differences, the file is reported as C
- * <user_file>, and as M <user_file> otherwise.
- *
- * Files added but not yet committed are reported as A <user_file>. Files
- * removed but not yet committed are reported as R <user_file>.
- *
- * If the current directory contains subdirectories that hold concurrent
- * versions, these are updated too. If the -d option was specified, new
- * directories added to the repository are automatically created and updated
- * as well.
- *
- * $FreeBSD$
- */
-
-#include "cvs.h"
-#include <assert.h>
-#include "savecwd.h"
-#ifdef SERVER_SUPPORT
-# include "md5.h"
-#endif
-#include "watch.h"
-#include "fileattr.h"
-#include "edit.h"
-#include "getline.h"
-#include "buffer.h"
-#include "hardlink.h"
-
-static int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts,
- int adding, int merging, int update_server));
-#ifdef SERVER_SUPPORT
-static void checkout_to_buffer PROTO ((void *, const char *, size_t));
-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 /* SERVER_SUPPORT */
-static int merge_file PROTO ((struct file_info *finfo, Vers_TS *vers));
-static int scratch_file PROTO((struct file_info *finfo, Vers_TS *vers));
-static Dtype update_dirent_proc PROTO ((void *callerdat, const char *dir,
- const char *repository,
- const char *update_dir,
- List *entries));
-static int update_dirleave_proc PROTO ((void *callerdat, const char *dir,
- int err, const 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,
- const char *repository,
- const char *update_dir,
- List *entries));
-#ifdef PRESERVE_PERMISSIONS_SUPPORT
-static int get_linkinfo_proc PROTO ((void *callerdat, struct file_info *));
-#endif
-static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts));
-
-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;
-
-/* If we set the tag or date for a subdirectory, we use this to undo
- the setting. See update_dirent_proc. */
-static char *tag_update_dir;
-
-static char *join_rev1, *date_rev1;
-static char *join_rev2, *date_rev2;
-static int aflag = 0;
-static int toss_local_changes = 0;
-static int force_tag_match = 1;
-static int pull_template = 0;
-static int update_build_dirs = 0;
-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;
-static const char *const update_usage[] =
-{
- "Usage: %s %s [-APCdflRp] [-k kopt] [-r rev] [-D date] [-j rev]\n",
- " [-I ign] [-W spec] [files...]\n",
- "\t-A\tReset any sticky tags/date/kopts.\n",
- "\t-P\tPrune empty directories.\n",
- "\t-C\tOverwrite locally modified files with clean repository copies.\n",
- "\t-d\tBuild directories, like checkout does.\n",
- "\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 (avoids stickiness).\n",
- "\t-k kopt\tUse RCS kopt -k option on checkout. (is sticky)\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",
- "\t-T\tCreate CVS/Template.\n",
- "(Specify the --help global option for a list of other help options)\n",
- NULL
-};
-
-/*
- * update is the argv,argc based front end for arg parsing
- */
-int
-update (argc, argv)
- int argc;
- char **argv;
-{
- int c, err;
- int local = 0; /* recursive by default */
- int which; /* where to look for files and dirs */
- int xpull_template = 0;
-
- if (argc == -1)
- usage (update_usage);
-
- ign_setup ();
- wrap_setup ();
-
- /* parse the args */
- optind = 0;
- while ((c = getopt (argc, argv, "+ApCPflRQTqduk:r:D:j:I:W:")) != -1)
- {
- switch (c)
- {
- case 'A':
- aflag = 1;
- break;
- case 'C':
- toss_local_changes = 1;
- break;
- case 'I':
- ign_add (optarg, 0);
- break;
- case 'W':
- wrap_add (optarg, 0);
- break;
- case 'k':
- if (options)
- free (options);
- options = RCS_check_kflag (optarg);
- break;
- case 'l':
- local = 1;
- break;
- case 'R':
- local = 0;
- break;
- case 'Q':
- case 'q':
- /* The CVS 1.5 client sends these options (in addition to
- Global_option requests), so we must ignore them. */
- if (!server_active)
- error (1, 0,
- "-q or -Q must be specified before \"%s\"",
- cvs_cmd_name);
- break;
- case 'T':
- xpull_template = 1;
- break;
- case 'd':
- update_build_dirs = 1;
- break;
- case 'f':
- force_tag_match = 0;
- break;
- case 'r':
- tag = optarg;
- break;
- case 'D':
- if (date) free (date);
- date = Make_Date (optarg);
- break;
- case 'P':
- update_prune_dirs = 1;
- break;
- case 'p':
- pipeout = 1;
- noexec = 1; /* so no locks will be created */
- break;
- case 'j':
- if (join_rev2)
- error (1, 0, "only two -j options can be specified");
- if (join_rev1)
- join_rev2 = optarg;
- else
- join_rev1 = optarg;
- break;
- case 'u':
-#ifdef SERVER_SUPPORT
- if (server_active)
- {
- patches = 1;
- rcs_diff_patches = server_use_rcs_diff ();
- }
- else
-#endif
- usage (update_usage);
- break;
- case '?':
- default:
- usage (update_usage);
- break;
- }
- }
- argc -= optind;
- argv += optind;
-
-#ifdef CLIENT_SUPPORT
- if (current_parsed_root->isremote)
- {
- 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;
-
- start_server ();
-
- if (local)
- send_arg("-l");
- if (update_build_dirs)
- send_arg("-d");
- if (pipeout)
- send_arg("-p");
- if (!force_tag_match)
- send_arg("-f");
- if (aflag)
- send_arg("-A");
- if (toss_local_changes)
- send_arg("-C");
- if (update_prune_dirs)
- 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 (failed_patches_count == 0)
- {
- unsigned int flags = 0;
-
- /* 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.
-
- We don't send -u if failed_patches != NULL, so that the
- server doesn't try to send patches which will just fail
- again. At least currently, the client also clobbers the
- file and tells the server it is lost, which also will get
- a full file instead of a patch, but it seems clean to omit
- -u. */
- if (supported_request ("update-patches"))
- send_arg ("-u");
-
- send_arg ("--");
-
- if (update_build_dirs)
- flags |= SEND_BUILD_DIRS;
-
- if (toss_local_changes) {
- flags |= SEND_NO_CONTENTS;
- flags |= BACKUP_MODIFIED_FILES;
- }
-
- /* If noexec, probably could be setting SEND_NO_CONTENTS.
- Same caveats as for "cvs status" apply. */
-
- send_files (argc, argv, local, aflag, flags);
- send_file_names (argc, argv, SEND_EXPAND_WILD);
- }
- else
- {
- int i;
-
- (void) printf ("%s client: refetching unpatchable files\n",
- program_name);
-
- if (toplevel_wd != NULL
- && CVS_CHDIR (toplevel_wd) < 0)
- {
- error (1, errno, "could not chdir to %s", toplevel_wd);
- }
-
- send_arg ("--");
-
- for (i = 0; i < failed_patches_count; i++)
- if (unlink_file (failed_patches[i]) < 0
- && !existence_error (errno))
- error (0, errno, "cannot remove %s",
- failed_patches[i]);
- send_files (failed_patches_count, failed_patches, local,
- aflag, update_build_dirs ? SEND_BUILD_DIRS : 0);
- send_file_names (failed_patches_count, failed_patches, 0);
- free_names (&failed_patches_count, failed_patches);
- }
-
- send_to_server ("update\012", 0);
-
- status = get_responses_and_close ();
-
- /* 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_count == 0 || pass > 1))
- {
- if (failed_patches_count > 0)
- free_names (&failed_patches_count, failed_patches);
- return status;
- }
-
- ++pass;
- } while (failed_patches_count > 0);
-
- return 0;
- }
-#endif
-
- if (tag != NULL)
- tag_check_valid (tag, argc, argv, local, aflag, "");
- 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
- * as we go, we make sure there is no static entries file and write the
- * tag file as appropriate
- */
- if (argc <= 0 && !pipeout)
- {
- if (update_build_dirs)
- {
- if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
- error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
-#ifdef SERVER_SUPPORT
- if (server_active)
- {
- char *repos = Name_Repository (NULL, NULL);
- server_clear_entstat (".", repos);
- free (repos);
- }
-#endif
- }
-
- /* keep the CVS/Tag file current with the specified arguments */
- if (aflag || tag || date)
- {
- char *repos = Name_Repository (NULL, NULL);
- WriteTag ((char *) NULL, tag, date, 0, ".", repos);
- free (repos);
- rewrite_tag = 1;
- nonbranch = 0;
- }
- }
-
- /* look for files/dirs locally and in the repository */
- which = W_LOCAL | W_REPOS;
-
- /* look in the attic too if a tag or date is specified */
- if (tag != NULL || date != NULL || joining())
- which |= W_ATTIC;
-
- /* call the command line interface */
- err = do_update (argc, argv, options, tag, date, force_tag_match,
- local, update_build_dirs, aflag, update_prune_dirs,
- pipeout, which, join_rev1, join_rev2, (char *) NULL,
- xpull_template, (char *) NULL);
-
- /* free the space Make_Date allocated if necessary */
- if (date != NULL)
- free (date);
-
- return err;
-}
-
-
-
-/*
- * Command line interface to update (used by checkout)
- */
-int
-do_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag,
- xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir,
- xpull_template, repository)
- int argc;
- char **argv;
- char *xoptions;
- char *xtag;
- char *xdate;
- int xforce;
- int local;
- int xbuild;
- int xaflag;
- int xprune;
- int xpipeout;
- int which;
- char *xjoin_rev1;
- char *xjoin_rev2;
- char *preload_update_dir;
- int xpull_template;
- char *repository;
-{
- int err = 0;
- char *cp;
-
- /* fill in the statics */
- options = xoptions;
- tag = xtag;
- date = xdate;
- force_tag_match = xforce;
- update_build_dirs = xbuild;
- aflag = xaflag;
- update_prune_dirs = xprune;
- pipeout = xpipeout;
- pull_template = xpull_template;
-
- /* setup the join support */
- join_rev1 = xjoin_rev1;
- join_rev2 = xjoin_rev2;
- if (join_rev1 && (cp = strchr (join_rev1, ':')) != NULL)
- {
- *cp++ = '\0';
- date_rev1 = Make_Date (cp);
- }
- else
- date_rev1 = (char *) NULL;
- if (join_rev2 && (cp = strchr (join_rev2, ':')) != NULL)
- {
- *cp++ = '\0';
- date_rev2 = Make_Date (cp);
- }
- else
- date_rev2 = (char *) NULL;
-
-#ifdef PRESERVE_PERMISSIONS_SUPPORT
- if (preserve_perms)
- {
- /* We need to do an extra recursion, bleah. It's to make sure
- that we know as much as possible about file linkage. */
- hardlist = getlist();
- working_dir = xgetwd(); /* save top-level working dir */
-
- /* FIXME-twp: the arguments to start_recursion make me dizzy. This
- function call was copied from the update_fileproc call that
- follows it; someone should make sure that I did it right. */
- err = start_recursion (get_linkinfo_proc, (FILESDONEPROC) NULL,
- (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
- argc, argv, local, which, aflag, CVS_LOCK_READ,
- preload_update_dir, 1, (char *) NULL);
- if (err)
- return err;
-
- /* FIXME-twp: at this point we should walk the hardlist
- and update the `links' field of each hardlink_info struct
- to list the files that are linked on dist. That would make
- it easier & more efficient to compare the disk linkage with
- the repository linkage (a simple strcmp). */
- }
-#endif
-
- /* call the recursion processor */
- err = start_recursion (update_fileproc, update_filesdone_proc,
- update_dirent_proc, update_dirleave_proc, NULL,
- argc, argv, local, which, aflag, CVS_LOCK_READ,
- preload_update_dir, 1, repository);
-
- /* see if we need to sleep before returning to avoid time-stamp races */
- if (!server_active && last_register_time)
- {
- sleep_past (last_register_time);
- }
-
- return err;
-}
-
-#ifdef PRESERVE_PERMISSIONS_SUPPORT
-/*
- * The get_linkinfo_proc callback adds each file to the hardlist
- * (see hardlink.c).
- */
-
-static int
-get_linkinfo_proc (callerdat, finfo)
- void *callerdat;
- struct file_info *finfo;
-{
- char *fullpath;
- Node *linkp;
- struct hardlink_info *hlinfo;
-
- /* Get the full pathname of the current file. */
- fullpath = xmalloc (strlen(working_dir) +
- strlen(finfo->fullname) + 2);
- sprintf (fullpath, "%s/%s", working_dir, finfo->fullname);
-
- /* To permit recursing into subdirectories, files
- are keyed on the full pathname and not on the basename. */
- linkp = lookup_file_by_inode (fullpath);
- if (linkp == NULL)
- {
- /* The file isn't on disk; we are probably restoring
- a file that was removed. */
- return 0;
- }
-
- /* Create a new, empty hardlink_info node. */
- hlinfo = (struct hardlink_info *)
- xmalloc (sizeof (struct hardlink_info));
-
- hlinfo->status = (Ctype) 0; /* is this dumb? */
- hlinfo->checked_out = 0;
-
- linkp->data = hlinfo;
-
- return 0;
-}
-#endif
-
-
-
-/*
- * This is the callback proc for update. It is called for each file in each
- * directory by the recursion code. The current directory is the local
- * instantiation. file is the file name we are to operate on. update_dir is
- * set to the path relative to where we started (for pretty printing).
- * repository is the repository. entries and srcfiles are the pre-parsed
- * entries and source control files.
- *
- * This routine decides what needs to be done for each file and does the
- * appropriate magic for checkout
- */
-static int
-update_fileproc (callerdat, finfo)
- void *callerdat;
- struct file_info *finfo;
-{
- int retval;
- Ctype status;
- Vers_TS *vers;
-
- 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, date, 1, NULL);
- if (rev != NULL
- && !RCS_nodeisbranch (finfo->rcs, tag))
- nonbranch = 1;
- if (rev != NULL)
- free (rev);
- }
-
- if (pipeout)
- {
- /*
- * We just return success without doing anything if any of the really
- * funky cases occur
- *
- * If there is still a valid RCS file, do a regular checkout type
- * operation
- */
- switch (status)
- {
- case T_UNKNOWN: /* unknown file was explicitly asked
- * about */
- case T_REMOVE_ENTRY: /* needs to be un-registered */
- case T_ADDED: /* added but not committed */
- retval = 0;
- break;
- case T_CONFLICT: /* old punt-type errors */
- retval = 1;
- break;
- case T_UPTODATE: /* file was already up-to-date */
- case T_NEEDS_MERGE: /* needs merging */
- case T_MODIFIED: /* locally modified */
- case T_REMOVED: /* removed but not committed */
- case T_CHECKOUT: /* needs checkout */
- case T_PATCH: /* needs patch */
- retval = checkout_file (finfo, vers, 0, 0, 0);
- break;
-
- default: /* can't ever happen :-) */
- error (0, 0,
- "unknown file status %d for file %s", status, finfo->file);
- retval = 0;
- break;
- }
- }
- else
- {
- switch (status)
- {
- case T_UNKNOWN: /* unknown file was explicitly asked
- * about */
- case T_UPTODATE: /* file was already up-to-date */
- retval = 0;
- break;
- case T_CONFLICT: /* old punt-type errors */
- retval = 1;
- write_letter (finfo, 'C');
- break;
- case T_NEEDS_MERGE: /* needs merging */
- if (! toss_local_changes)
- {
- retval = merge_file (finfo, vers);
- break;
- }
- /* else FALL THROUGH */
- case T_MODIFIED: /* locally modified */
- retval = 0;
- if (toss_local_changes)
- {
- char *bakname;
- bakname = backup_file (finfo->file, vers->vn_user);
- /* This behavior is sufficiently unexpected to
- justify overinformativeness, I think. */
- if (!really_quiet && !server_active)
- (void) printf ("(Locally modified %s moved to %s)\n",
- finfo->file, bakname);
- free (bakname);
-
- /* The locally modified file is still present, but
- it will be overwritten by the repository copy
- after this. */
- status = T_CHECKOUT;
- retval = checkout_file (finfo, vers, 0, 0, 1);
- }
- else
- {
- if (vers->ts_conflict)
- {
- if (file_has_markers (finfo))
- {
- write_letter (finfo, 'C');
- retval = 1;
- }
- else
- {
- /* Reregister to clear conflict flag. */
- Register (finfo->entries, finfo->file,
- vers->vn_rcs, vers->ts_rcs,
- vers->options, vers->tag,
- vers->date, (char *)0);
- }
- }
- if (!retval)
- write_letter (finfo, 'M');
- }
- break;
- case T_PATCH: /* needs patch */
-#ifdef SERVER_SUPPORT
- if (patches)
- {
- int docheckout;
- struct stat file_info;
- unsigned char checksum[16];
-
- retval = patch_file (finfo,
- vers, &docheckout,
- &file_info, checksum);
- if (! docheckout)
- {
- if (server_active && retval == 0)
- server_updated (finfo, vers,
- (rcs_diff_patches
- ? SERVER_RCS_DIFF
- : SERVER_PATCHED),
- file_info.st_mode, checksum,
- (struct buffer *) NULL);
- break;
- }
- }
-#endif
- /* If we're not running as a server, just check the
- file out. It's simpler and faster than producing
- and applying patches. */
- /* Fall through. */
- case T_CHECKOUT: /* needs checkout */
- retval = checkout_file (finfo, vers, 0, 0, 1);
- break;
- case T_ADDED: /* added but not committed */
- write_letter (finfo, 'A');
- retval = 0;
- break;
- case T_REMOVED: /* removed but not committed */
- write_letter (finfo, 'R');
- retval = 0;
- break;
- case T_REMOVE_ENTRY: /* needs to be un-registered */
- retval = scratch_file (finfo, vers);
- break;
- default: /* can't ever happen :-) */
- error (0, 0,
- "unknown file status %d for file %s", status, finfo->file);
- retval = 0;
- break;
- }
- }
-
- /* only try to join if things have gone well thus far */
- if (retval == 0 && join_rev1)
- join_file (finfo, vers);
-
- /* if this directory has an ignore list, add this file to it */
- if (ignlist && (status != T_UNKNOWN || vers->ts_user == NULL))
- {
- Node *p;
-
- p = getnode ();
- p->type = FILES;
- p->key = xstrdup (finfo->file);
- if (addnode (ignlist, p) != 0)
- freenode (p);
- }
-
- freevers_ts (&vers);
- return retval;
-}
-
-
-
-static void update_ignproc PROTO ((const char *, const char *));
-
-static void
-update_ignproc (file, dir)
- const char *file;
- const char *dir;
-{
- struct file_info finfo;
- char *tmp;
-
- memset (&finfo, 0, sizeof (finfo));
- finfo.file = file;
- finfo.update_dir = dir;
- if (dir[0] == '\0')
- tmp = xstrdup (file);
- else
- {
- tmp = xmalloc (strlen (file) + strlen (dir) + 10);
- strcpy (tmp, dir);
- strcat (tmp, "/");
- strcat (tmp, file);
- }
-
- finfo.fullname = tmp;
- write_letter (&finfo, '?');
- free (tmp);
-}
-
-
-
-/* ARGSUSED */
-static int
-update_filesdone_proc (callerdat, err, repository, update_dir, entries)
- void *callerdat;
- int err;
- const char *repository;
- const 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, entries, update_dir, update_ignproc);
- dellist (&ignlist);
- }
-
- /* Clean up CVS admin dirs if we are export */
- if (strcmp (cvs_cmd_name, "export") == 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 (CVSADM) < 0 && !existence_error (errno))
- error (0, errno, "cannot remove %s directory", CVSADM);
- }
- else if (!server_active && !pipeout)
- {
- /* If there is no CVS/Root file, add one */
- if (!isfile (CVSADM_ROOT))
- Create_Root ((char *) NULL, current_parsed_root->original);
- }
-
- return err;
-}
-
-
-
-/*
- * update_dirent_proc () is called back by the recursion processor before a
- * sub-directory is processed for update. In this case, update_dirent proc
- * will probably create the directory unless -d isn't specified and this is a
- * new directory. A return code of 0 indicates the directory should be
- * processed by the recursion code. A return of non-zero indicates the
- * recursion code should skip this directory.
- */
-static Dtype
-update_dirent_proc (callerdat, dir, repository, update_dir, entries)
- void *callerdat;
- const char *dir;
- const char *repository;
- const 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))
- {
- /* if we aren't building dirs, blow it off */
- if (!update_build_dirs)
- return R_SKIP_ALL;
-
- /* Various CVS administrators are in the habit of removing
- the repository directory for things they don't want any
- more. I've even been known to do it myself (on rare
- occasions). Not the usual recommended practice, but we
- want to try to come up with some kind of
- reasonable/documented/sensible behavior. Generally
- the behavior is to just skip over that directory (see
- dirs test in sanity.sh; the case which reaches here
- is when update -d is specified, and the working directory
- is gone but the subdirectory is still mentioned in
- CVS/Entries). */
- /* In the remote case, the client should refrain from
- sending us the directory in the first place. So we
- want to continue to give an error, so clients make
- sure to do this. */
- if (!server_active && !isdir (repository))
- return R_SKIP_ALL;
-
- if (noexec)
- {
- /* ignore the missing dir if -n is specified */
- error (0, 0, "New directory `%s' -- ignored", update_dir);
- return R_SKIP_ALL;
- }
- else
- {
- /* otherwise, create the dir and appropriate adm files */
-
- /* If no tag or date were specified on the command line,
- and we're not using -A, we want the subdirectory to use
- the tag and date, if any, of the current directory.
- That way, update -d will work correctly when working on
- a branch.
-
- We use TAG_UPDATE_DIR to undo the tag setting in
- update_dirleave_proc. If we did not do this, we would
- not correctly handle a working directory with multiple
- tags (and maybe we should prohibit such working
- directories, but they work now and we shouldn't make
- them stop working without more thought). */
- if ((tag == NULL && date == NULL) && ! aflag)
- {
- ParseTag (&tag, &date, &nonbranch);
- if (tag != NULL || date != NULL)
- tag_update_dir = xstrdup (update_dir);
- }
-
- make_directory (dir);
- Create_Admin (dir, update_dir, repository, tag, date,
- /* This is a guess. We will rewrite it later
- via WriteTag. */
- 0,
- 0,
- pull_template);
- rewrite_tag = 1;
- nonbranch = 0;
- Subdir_Register (entries, (char *) NULL, dir);
- }
- }
- /* Do we need to check noexec here? */
- else if (!pipeout)
- {
- char *cvsadmdir;
-
- /* The directory exists. Check to see if it has a CVS
- subdirectory. */
-
- cvsadmdir = xmalloc (strlen (dir) + 80);
- strcpy (cvsadmdir, dir);
- strcat (cvsadmdir, "/");
- strcat (cvsadmdir, CVSADM);
-
- if (!isdir (cvsadmdir))
- {
- /* We cannot successfully recurse into a directory without a CVS
- subdirectory. Generally we will have already printed
- "? foo". */
- free (cvsadmdir);
- return R_SKIP_ALL;
- }
- free (cvsadmdir);
- }
-
- /*
- * If we are building dirs and not going to stdout, we make sure there is
- * no static entries file and write the tag file as appropriate
- */
- if (!pipeout)
- {
- if (update_build_dirs)
- {
- 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);
-#ifdef SERVER_SUPPORT
- 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, 0, update_dir, repository);
- rewrite_tag = 1;
- nonbranch = 0;
- }
-
- /* keep the CVS/Template file current */
- if (pull_template)
- {
- WriteTemplate (dir, update_dir);
- }
-
- /* initialize the ignore list for this directory */
- ignlist = getlist ();
- }
-
- /* print the warm fuzzy message */
- if (!quiet)
- error (0, 0, "Updating %s", update_dir);
-
- return R_PROCESS;
-}
-
-
-
-/*
- * update_dirleave_proc () is called back by the recursion code upon leaving
- * a directory. It will prune empty directories if needed and will execute
- * any appropriate update programs.
- */
-/* ARGSUSED */
-static int
-update_dirleave_proc (callerdat, dir, err, update_dir, entries)
- void *callerdat;
- const char *dir;
- int err;
- const char *update_dir;
- List *entries;
-{
- /* Delete the ignore list if it hasn't already been done. */
- if (ignlist)
- dellist (&ignlist);
-
- /* If we set the tag or date for a new subdirectory in
- update_dirent_proc, and we're now done with that subdirectory,
- undo the tag/date setting. Note that we know that the tag and
- date were both originally NULL in this case. */
- if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0)
- {
- if (tag != NULL)
- {
- free (tag);
- tag = NULL;
- }
- if (date != NULL)
- {
- free (date);
- date = NULL;
- }
- nonbranch = 0;
- free (tag_update_dir);
- tag_update_dir = NULL;
- }
-
- if (strchr (dir, '/') == NULL)
- {
- /* 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;
-}
-
-
-
-static int isremoved PROTO ((Node *, void *));
-
-/* Returns 1 if the file indicated by node has been removed. */
-static int
-isremoved (node, closure)
- Node *node;
- void *closure;
-{
- Entnode *entdata = 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)
- const char *dir;
- int might_not_exist;
-{
- DIR *dirp;
- struct dirent *dp;
-
- if ((dirp = CVS_OPENDIR (dir)) == NULL)
- {
- 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 = CVS_READDIR (dirp)) != NULL)
- {
- if (strcmp (dp->d_name, ".") != 0
- && strcmp (dp->d_name, "..") != 0)
- {
- if (strcmp (dp->d_name, CVSADM) != 0)
- {
- /* An entry other than the CVS directory. The directory
- is certainly not empty. */
- (void) CVS_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, NULL);
- 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) CVS_CLOSEDIR (dirp);
- return 0;
- }
- }
- }
- errno = 0;
- }
- if (errno != 0)
- {
- error (0, errno, "cannot read directory %s", dir);
- (void) CVS_CLOSEDIR (dirp);
- return 0;
- }
- (void) CVS_CLOSEDIR (dirp);
- return 1;
-}
-
-
-
-/*
- * scratch the Entries file entry associated with a file
- */
-static int
-scratch_file (finfo, vers)
- struct file_info *finfo;
- Vers_TS *vers;
-{
- history_write ('W', finfo->update_dir, "", finfo->file, finfo->repository);
- Scratch_Entry (finfo->entries, finfo->file);
-#ifdef SERVER_SUPPORT
- if (server_active)
- {
- if (vers->ts_user == NULL)
- server_scratch_entry_only ();
- server_updated (finfo, vers,
- SERVER_UPDATED, (mode_t) -1,
- (unsigned char *) NULL,
- (struct buffer *) NULL);
- }
-#endif
- if (unlink_file (finfo->file) < 0 && ! existence_error (errno))
- error (0, errno, "unable to remove %s", finfo->fullname);
- else if (!server_active)
- {
- /* skip this step when the server is running since
- * server_updated should have handled it */
- /* keep the vers structure up to date in case we do a join
- * - if there isn't a file, it can't very well have a version number, can it?
- */
- if (vers->vn_user != NULL)
- {
- free (vers->vn_user);
- vers->vn_user = NULL;
- }
- if (vers->ts_user != NULL)
- {
- free (vers->ts_user);
- vers->ts_user = NULL;
- }
- }
- return 0;
-}
-
-
-
-/*
- * Check out a file.
- */
-static int
-checkout_file (finfo, vers_ts, adding, merging, update_server)
- struct file_info *finfo;
- Vers_TS *vers_ts;
- int adding;
- int merging;
- int update_server;
-{
- char *backup;
- int set_time, retval = 0;
- int status;
- int file_is_dead;
- struct buffer *revbuf;
-
- backup = NULL;
- revbuf = NULL;
-
- /* Don't screw with backup files if we're going to stdout, or if
- we are the server. */
- if (!pipeout && !server_active)
- {
- 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
- {
- /* If -f/-t wrappers are being used to wrap up a directory,
- then backup might be a directory instead of just a file. */
- if (unlink_file_dir (backup) < 0)
- {
- /* Not sure if the existence_error check is needed here. */
- if (!existence_error (errno))
- /* FIXME: should include update_dir in message. */
- error (0, errno, "error removing %s", backup);
- }
- free (backup);
- backup = NULL;
- }
- }
-
- file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs);
-
- if (!file_is_dead)
- {
- /*
- * if we are checking out to stdout, print a nice message to
- * stderr, and add the -p flag to the command */
- if (pipeout)
- {
- if (!quiet)
- {
- 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);
- }
- }
-
-#ifdef SERVER_SUPPORT
- if (update_server
- && server_active
- && ! pipeout
- && ! file_gzip_level
- && ! joining ()
- && ! wrap_name_has (finfo->file, WRAP_FROMCVS))
- {
- revbuf = buf_nonio_initialize ((BUFMEMERRPROC) NULL);
- status = RCS_checkout (vers_ts->srcfile, (char *) NULL,
- vers_ts->vn_rcs, vers_ts->tag,
- vers_ts->options, RUN_TTY,
- checkout_to_buffer, revbuf);
- }
- else
-#endif
- status = RCS_checkout (vers_ts->srcfile,
- pipeout ? NULL : finfo->file,
- vers_ts->vn_rcs, vers_ts->tag,
- vers_ts->options, RUN_TTY,
- (RCSCHECKOUTPROC) NULL, (void *) NULL);
- }
- if (file_is_dead || status == 0)
- {
- mode_t mode;
-
- mode = (mode_t) -1;
-
- if (!pipeout)
- {
- Vers_TS *xvers_ts;
-
- if (revbuf != NULL && !noexec)
- {
- struct stat sb;
-
- /* FIXME: We should have RCS_checkout return the mode.
- That would also fix the kludge with noexec, above, which
- is here only because noexec doesn't write srcfile->path
- for us to stat. */
- if (stat (vers_ts->srcfile->path, &sb) < 0)
- {
-#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
- buf_free (revbuf);
-#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */
- error (1, errno, "cannot stat %s",
- vers_ts->srcfile->path);
- }
- mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH);
- }
-
- if (cvswrite
- && !file_is_dead
- && !fileattr_get (finfo->file, "_watched"))
- {
- if (revbuf == NULL)
- xchmod (finfo->file, 1);
- else
- {
- /* We know that we are the server here, so
- although xchmod checks umask, we don't bother. */
- mode |= (((mode & S_IRUSR) ? S_IWUSR : 0)
- | ((mode & S_IRGRP) ? S_IWGRP : 0)
- | ((mode & S_IROTH) ? S_IWOTH : 0));
- }
- }
-
- {
- /* A newly checked out file is never under the spell
- of "cvs edit". If we think we were editing it
- from a previous life, clean up. Would be better to
- check for same the working directory instead of
- same user, but that is hairy. */
-
- struct addremove_args args;
-
- editor_set (finfo->file, getcaller (), NULL);
-
- memset (&args, 0, sizeof args);
- args.remove_temp = 1;
- watch_modify_watchers (finfo->file, &args);
- }
-
- /* set the time from the RCS file iff it was unknown before */
- set_time =
- (!noexec
- && (vers_ts->vn_user == NULL ||
- strncmp (vers_ts->ts_rcs, "Initial", 7) == 0)
- && !file_is_dead);
-
- wrap_fromcvs_process_file (finfo->file);
-
- 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';
-
- if (revbuf != NULL)
- {
- /* If we stored the file data into a buffer, then we
- didn't create a file at all, so xvers_ts->ts_user
- is wrong. The correct value is to have it be the
- same as xvers_ts->ts_rcs, meaning that the working
- file is unchanged from the RCS file.
-
- FIXME: We should tell Version_TS not to waste time
- statting the nonexistent file.
-
- FIXME: Actually, I don't think the ts_user value
- matters at all here. The only use I know of is
- that it is printed in a trace message by
- Server_Register. */
-
- if (xvers_ts->ts_user != NULL)
- free (xvers_ts->ts_user);
- xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs);
- }
-
- (void) time (&last_register_time);
-
- if (file_is_dead)
- {
- if (xvers_ts->vn_user != NULL)
- {
- error (0, 0,
- "warning: %s is not (any longer) pertinent",
- finfo->fullname);
- }
- 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))
- {
- error (0, errno, "cannot remove %s", finfo->fullname);
- }
- }
- else
- 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)
- {
- /* FIXME: It seems like we should be preserving ts_user
- * & ts_rcs here, but setting them causes problems in
- * join_file().
- */
- if (vers_ts->vn_user != NULL)
- free (vers_ts->vn_user);
- if (vers_ts->vn_rcs != NULL)
- free (vers_ts->vn_rcs);
- vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs);
- vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs);
- }
-
- /* If this is really Update and not Checkout, recode history */
- if (strcmp (cvs_cmd_name, "update") == 0)
- 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 (finfo, 'U');
- }
- }
-
-#ifdef SERVER_SUPPORT
- if (update_server && server_active)
- server_updated (finfo, vers_ts,
- merging ? SERVER_MERGED : SERVER_UPDATED,
- mode, (unsigned char *) NULL, revbuf);
-#endif
- }
- else
- {
- if (backup != NULL)
- {
- rename_file (backup, finfo->file);
- free (backup);
- backup = NULL;
- }
-
- error (0, 0, "could not check out %s", finfo->fullname);
-
- retval = status;
- }
-
- if (backup != NULL)
- {
- /* If -f/-t wrappers are being used to wrap up a directory,
- then backup might be a directory instead of just a file. */
- if (unlink_file_dir (backup) < 0)
- {
- /* Not sure if the existence_error check is needed here. */
- if (!existence_error (errno))
- /* FIXME: should include update_dir in message. */
- error (0, errno, "error removing %s", backup);
- }
- free (backup);
- }
-
-#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
- if (revbuf != NULL)
- buf_free (revbuf);
-#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */
- return retval;
-}
-
-
-
-#ifdef SERVER_SUPPORT
-
-/* This function is used to write data from a file being checked out
- into a buffer. */
-
-static void
-checkout_to_buffer (callerdat, data, len)
- void *callerdat;
- const char *data;
- size_t len;
-{
- struct buffer *buf = (struct buffer *) callerdat;
-
- buf_output (buf, data, len);
-}
-
-#endif /* SERVER_SUPPORT */
-
-#ifdef SERVER_SUPPORT
-
-/* 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 cvs_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 (finfo, vers_ts, docheckout, file_info, checksum)
- struct file_info *finfo;
- Vers_TS *vers_ts;
- int *docheckout;
- struct stat *file_info;
- unsigned char *checksum;
-{
- char *backup;
- char *file1;
- char *file2;
- int retval = 0;
- int retcode = 0;
- int fail;
- FILE *e;
- struct patch_file_data data;
-
- *docheckout = 0;
-
- if (noexec || pipeout || joining ())
- {
- *docheckout = 1;
- return 0;
- }
-
- /* 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;
- }
-
- /* First check that the first revision exists. If it has been nuked
- by cvs admin -o, then just fall back to checking out entire
- revisions. In some sense maybe we don't have to do this; after
- all cvs.texinfo says "Make sure that no-one has checked out a
- copy of the revision you outdate" but then again, that advice
- doesn't really make complete sense, because "cvs admin" operates
- on a working directory and so _someone_ will almost always have
- _some_ revision checked out. */
- {
- char *rev;
-
- rev = RCS_gettag (finfo->rcs, vers_ts->vn_user, 1, NULL);
- if (rev == NULL)
- {
- *docheckout = 1;
- return 0;
- }
- else
- free (rev);
- }
-
- /* If the revision is dead, let checkout_file handle it rather
- than duplicating the processing here. */
- if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs))
- {
- *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
- {
- if (unlink_file (backup) < 0
- && !existence_error (errno))
- error (0, errno, "cannot remove %s", backup);
- }
-
- 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. */
-
- 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;
-
- /* Duplicating the client working file, so use the original sticky options.
- */
- retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
- vers_ts->vn_user, vers_ts->entdata->tag,
- vers_ts->entdata->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)
- {
- e = CVS_FOPEN (file2, "w");
- if (e == NULL)
- error (1, errno, "cannot open %s", file2);
-
- data.filename = file2;
- data.fp = e;
- data.final_nl = 0;
- data.compute_checksum = 1;
- cvs_MD5Init (&data.context);
-
- retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL,
- vers_ts->vn_rcs, vers_ts->tag,
- vers_ts->options, RUN_TTY,
- patch_file_write, (void *) &data);
-
- if (fclose (e) < 0)
- error (1, errno, "cannot close %s", file2);
-
- if (retcode != 0 || ! data.final_nl)
- fail = 1;
- else
- cvs_MD5Final (checksum, &data.context);
- }
-
- retcode = 0;
- if (! fail)
- {
- int dargc = 0;
- size_t darg_allocated = 0;
- char **dargv = NULL;
-
- /* 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 that is what CVS has
- traditionally used. Kind of a moot point, now that
- Rcs-diff is preferred, so there is no point in making
- the compatibility issues worse. */
- run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c");
- else
- /* Now that diff is librarified, we could be passing -a if
- we wanted to. However, it is unclear to me whether we
- would want to. Does diff -a, in any significant
- percentage of cases, produce patches which are smaller
- than the files it is patching? I guess maybe text
- files with character sets which diff regards as
- 'binary'. Conversely, do they tend to be much larger
- in the bad cases? This needs some more
- thought/investigation, I suspect. */
- run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n");
- retcode = diff_exec (file1, file2, NULL, NULL, dargc, dargv,
- finfo->file);
- run_arg_free_p (dargc, dargv);
- free (dargv);
-
- /* A retcode of 0 means no differences. 1 means some differences. */
- if (retcode != 0
- && retcode != 1)
- {
- fail = 1;
- }
- }
-
- if (! fail)
- {
- struct stat file2_info;
-
- /* Check to make sure the patch is really shorter */
- if (CVS_STAT (file2, &file2_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 (file2_info.st_size <= file_info->st_size)
- fail = 1;
- }
-
- if (! fail)
- {
-# define BINARY "Binary"
- char buf[sizeof BINARY];
- unsigned int c;
-
- /* Check the diff output to make sure patch will be handle it. */
- e = CVS_FOPEN (finfo->file, "r");
- if (e == NULL)
- 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)
- {
- /* These are binary files. We could use diff -a, but
- patch can't handle that. */
- fail = 1;
- }
- fclose (e);
- }
-
- if (! fail)
- {
- Vers_TS *xvers_ts;
-
- /* 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
- && !fileattr_get (finfo->file, "_watched"))
- xchmod (finfo->file, 1);
-
- /* This stuff is just copied blindly from checkout_file. I
- don't really know what it does. */
- 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 (finfo->entries, finfo->file, xvers_ts->vn_rcs,
- xvers_ts->ts_user, xvers_ts->options,
- xvers_ts->tag, xvers_ts->date, NULL);
-
- 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, record history. */
- if (strcmp (cvs_cmd_name, "update") == 0)
- history_write ('P', finfo->update_dir, xvers_ts->vn_rcs,
- finfo->file, finfo->repository);
-
- freevers_ts (&xvers_ts);
-
- if (!really_quiet)
- {
- write_letter (finfo, 'P');
- }
- }
- else
- {
- int old_errno = errno; /* save errno value over the rename */
-
- if (isfile (backup))
- rename_file (backup, finfo->file);
-
- if (retcode != 0 && retcode != 1)
- error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0,
- "could not diff %s", finfo->fullname);
-
- *docheckout = 1;
- retval = retcode;
- }
-
- if (unlink_file (backup) < 0
- && !existence_error (errno))
- error (0, errno, "cannot remove %s", backup);
- if (unlink_file (file1) < 0
- && !existence_error (errno))
- error (0, errno, "cannot remove %s", file1);
- if (unlink_file (file2) < 0
- && !existence_error (errno))
- error (0, errno, "cannot remove %s", file2);
-
- free (backup);
- free (file1);
- free (file2);
- return retval;
-}
-
-
-
-/* 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)
- cvs_MD5Update (&data->context, (unsigned char *) buffer, len);
-}
-
-#endif /* SERVER_SUPPORT */
-
-/*
- * Several of the types we process only print a bit of information consisting
- * of a single letter and the name.
- */
-void
-write_letter (finfo, letter)
- struct file_info *finfo;
- int letter;
-{
- if (!really_quiet)
- {
- char *tag = NULL;
- /* Big enough for "+updated" or any of its ilk. */
- char buf[80];
-
- switch (letter)
- {
- case 'U':
- tag = "updated";
- break;
- default:
- /* We don't yet support tagged output except for "U". */
- break;
- }
-
- if (tag != NULL)
- {
- sprintf (buf, "+%s", tag);
- cvs_output_tagged (buf, NULL);
- }
- buf[0] = letter;
- buf[1] = ' ';
- buf[2] = '\0';
- cvs_output_tagged ("text", buf);
- cvs_output_tagged ("fname", finfo->fullname);
- cvs_output_tagged ("newline", NULL);
- if (tag != NULL)
- {
- sprintf (buf, "-%s", tag);
- cvs_output_tagged (buf, NULL);
- }
- }
- return;
-}
-
-
-
-/* Reregister a file after a merge. */
-static void
-RegisterMerge PROTO((struct file_info *finfo, Vers_TS *vers,
- const char *backup, int has_conflicts));
-static void
-RegisterMerge (finfo, vers, backup, has_conflicts)
- struct file_info *finfo;
- Vers_TS *vers;
- const char *backup;
- int has_conflicts;
-{
- /* This file is the result of a merge, which means that it has
- been modified. We use a special timestamp string which will
- not compare equal to any actual timestamp. */
- char *cp = NULL;
-
- if (has_conflicts)
- {
- time (&last_register_time);
- cp = time_stamp (finfo->file);
- }
- Register (finfo->entries, finfo->file, vers->vn_rcs ? vers->vn_rcs : "0",
- "Result of merge", vers->options, vers->tag, vers->date, cp);
- if (cp)
- free (cp);
-
-#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, (mode_t) -1, NULL, NULL);
- }
-#endif
-}
-
-
-
-/*
- * Do all the magic associated with a file which needs to be merged
- */
-static int
-merge_file (finfo, vers)
- struct file_info *finfo;
- Vers_TS *vers;
-{
- char *backup;
- int status;
- int retcode = 0;
- int retval;
-
- assert (vers->vn_user);
-
- /*
- * The users currently modified file is moved to a backup file name
- * ".#filename.version", so that it will stay around for a few days
- * before being automatically removed by some cron daemon. The "version"
- * is the version of the file that the user was most up-to-date with
- * before the merge.
- */
- backup = xmalloc (strlen (finfo->file)
- + strlen (vers->vn_user)
- + sizeof (BAKPREFIX)
- + 10);
- (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
-
- if (unlink_file (backup) && !existence_error (errno))
- error (0, errno, "unable to remove %s", backup);
- copy_file (finfo->file, backup);
- xchmod (finfo->file, 1);
-
- if (strcmp (vers->options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file)
- || special_file_mismatch (finfo, NULL, vers->vn_rcs))
- {
- /* For binary files, a merge is always a conflict. Same for
- files whose permissions or linkage do not match. 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. */
-
- /* TODO: it may not always be necessary to regard a permission
- mismatch as a conflict. The working file and the RCS file
- have a common ancestor `A'; if the working file's permissions
- match A's, then it's probably safe to overwrite them with the
- RCS permissions. Only if the working file, the RCS file, and
- A all disagree should this be considered a conflict. But more
- thought needs to go into this, and in the meantime it is safe
- to treat any such mismatch as an automatic conflict. -twp */
-
- retcode = RCS_checkout (finfo->rcs, finfo->file,
- vers->vn_rcs, vers->tag,
- vers->options, NULL, NULL, NULL);
- if (retcode)
- {
- error (0, 0, "failed to check out `%s' file", finfo->fullname);
- error (0, 0, "restoring `%s' from backup file `%s'",
- finfo->fullname, backup);
- rename_file (backup, finfo->file);
- retval = 1;
- goto out;
- }
- xchmod (finfo->file, 1);
-
- RegisterMerge (finfo, vers, backup, 1);
-
- /* Is there a better term than "nonmergeable file"? What we
- really mean is, not something that CVS cannot or does not
- want to merge (there might be an external manual or
- automatic merge process). */
- error (0, 0, "nonmergeable 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, 'C');
-
- history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file,
- finfo->repository);
- retval = 0;
- goto out;
- }
-
- status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
- 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, finfo->fullname);
- error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
- finfo->fullname, backup);
- rename_file (backup, finfo->file);
- retval = 1;
- goto out;
- }
-
- if (strcmp (vers->options, "-V4") == 0)
- vers->options[0] = '\0';
-
- /* fix up the vers structure, in case it is used by join */
- if (join_rev1)
- {
- /* FIXME: Throwing away the original revision info is almost
- certainly wrong -- what if join_rev1 is "BASE"? */
- if (vers->vn_user != NULL)
- free (vers->vn_user);
- vers->vn_user = xstrdup (vers->vn_rcs);
- }
-
- RegisterMerge (finfo, vers, backup, status);
-
- /* FIXME: the noexec case is broken. RCS_merge could be doing the
- xcmp on the temporary files without much hassle, I think. */
- if (!noexec && !xcmp (backup, finfo->file))
- {
- cvs_output (finfo->fullname, 0);
- cvs_output (" already contains the differences between ", 0);
- cvs_output (vers->vn_user, 0);
- cvs_output (" and ", 0);
- cvs_output (vers->vn_rcs, 0);
- cvs_output ("\n", 1);
-
- history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
- finfo->repository);
- retval = 0;
- goto out;
- }
-
- if (status == 1)
- {
- error (0, 0, "conflicts found in %s", finfo->fullname);
-
- write_letter (finfo, 'C');
-
- 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",
- finfo->fullname);
- }
- else
- {
- write_letter (finfo, 'M');
- history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file,
- finfo->repository);
- }
- retval = 0;
- out:
- free (backup);
- return retval;
-}
-
-
-
-/*
- * Do all the magic associated with a file which needs to be joined
- * (reached via the -j option to checkout or update).
- *
- * INPUTS
- * finfo File information about the destination file.
- * vers The Vers_TS structure for finfo.
- *
- * GLOBALS
- * join_rev1 From the command line.
- * join_rev2 From the command line.
- * server_active Natch.
- *
- * ASSUMPTIONS
- * 1. Is not called in client mode.
- */
-static void
-join_file (finfo, vers)
- struct file_info *finfo;
- Vers_TS *vers;
-{
- char *backup;
- char *t_options;
- int status;
-
- char *rev1;
- char *rev2;
- char *jrev1;
- char *jrev2;
- char *jdate1;
- char *jdate2;
-
- if (trace)
- fprintf (stderr, "%s-> join_file(%s, %s%s%s%s, %s, %s)\n",
- CLIENT_SERVER_STR,
- finfo->file,
- vers->tag ? vers->tag : "",
- vers->tag ? " (" : "",
- vers->vn_rcs ? vers->vn_rcs : "",
- vers->tag ? ")" : "",
- join_rev1 ? join_rev1 : "",
- join_rev2 ? join_rev2 : "");
-
- jrev1 = join_rev1;
- jrev2 = join_rev2;
- jdate1 = date_rev1;
- jdate2 = date_rev2;
-
- /* Determine if we need to do anything at all. */
- if (vers->srcfile == NULL ||
- vers->srcfile->path == NULL)
- {
- return;
- }
-
- /* If only one join revision is specified, it becomes the second
- revision. */
- if (jrev2 == NULL)
- {
- jrev2 = jrev1;
- jrev1 = NULL;
- jdate2 = jdate1;
- jdate1 = NULL;
- }
-
- /* FIXME: Need to handle "BASE" for jrev1 and/or jrev2. Note caveat
- below about vn_user. */
-
- /* 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
- {
- /* 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
- rev1 = gca (vers->vn_rcs, rev2);
- }
-
- /* Handle a nonexistent or dead merge target. */
- if (rev2 == NULL || RCS_isdead (vers->srcfile, rev2))
- {
- char *mrev;
- short conflict = 0;
-
- if (rev2 != NULL)
- free (rev2);
-
- /* 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;
- }
-
- /* 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))
- {
- 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 (/* may have changed on destination branch */
- /* file added locally */
- !strcmp (vers->vn_user, "0")
- || /* destination branch modified in repository */
- strcmp (rev1, vers->vn_user)
- || /* locally modified */
- vers->ts_user && strcmp (vers->ts_user, vers->ts_rcs))
- {
- /* The removal should happen if either the file has never changed
- * on the destination or the file has changed to be identical to
- * the first join revision.
- *
- * ------R-----------D
- * |
- * \----J1---J2-----S
- *
- * So:
- *
- * J2 is dead.
- * D is destination.
- * R is source branch root/GCA.
- * if J1 == D removal should happen
- * if D == R removal should happen
- * otherwise, fail.
- *
- * (In the source, J2 = REV2, D = user file (potentially VN_USER),
- * R = GCA computed below)
- */
- char *gca_rev1 = gca (rev1, vers->vn_user);
-#ifdef SERVER_SUPPORT
- if (server_active && !isreadable (finfo->file))
- {
- int retcode;
- /* The file is up to date. Need to check out the current
- * contents.
- */
- /* FIXME - see the FIXME comment above the call to RCS_checkout
- * in the patch_file function.
- */
- retcode = RCS_checkout (vers->srcfile, finfo->file,
- vers->vn_user, vers->tag,
- NULL, RUN_TTY, NULL, NULL);
- if (retcode)
- error (1, 0,
- "failed to check out %s file", finfo->fullname);
- }
-#endif
- if (/* genuinely changed on destination branch */
- RCS_cmp_file (vers->srcfile, gca_rev1, NULL,
- NULL, vers->options, finfo->file)
- && /* genuinely different from REV1 */
- RCS_cmp_file (vers->srcfile, rev1, NULL,
- NULL, vers->options, finfo->file))
- conflict = 1;
- }
-
- free (rev1);
-
- if (conflict)
- {
- char *cp;
-
- if (jdate2)
- error (0, 0,
- "file %s has been removed in revision %s as of %s, but the destination is incompatibly modified",
- finfo->fullname, jrev2, jdate2);
- else
- error (0, 0,
- "file %s has been removed in revision %s, but the destination is incompatibly modified",
- finfo->fullname, jrev2);
-
- /* Register the conflict with the client. */
-
- /* FIXME: vers->ts_user should always be set here but sometimes
- * isn't, namely when checkout_file() has just created the file,
- * but simply setting it in checkout_file() appears to cause other
- * problems.
- */
- if (isfile (finfo->file))
- cp = time_stamp (finfo->file);
- else
- cp = xstrdup (vers->ts_user);
-
- Register (finfo->entries, finfo->file, vers->vn_user,
- "Result of merge", vers->options, vers->tag, vers->date,
- cp);
- write_letter (finfo, 'C');
- free (cp);
-
-#ifdef SERVER_SUPPORT
- /* Abuse server_checked_in() to send the updated entry without
- * needing to update the file.
- */
- if (server_active)
- server_checked_in (finfo->file, finfo->update_dir,
- finfo->repository);
-#endif
-
- return;
- }
-
- /* 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.
-
- The same goes for a removal then an add. e.g.
- cvs up -rbr -jbr2 could remove and readd the same file
- */
- /* save the rev since server_updated might invalidate it */
- mrev = xmalloc (strlen (vers->vn_user) + 2);
- sprintf (mrev, "-%s", vers->vn_user);
-#ifdef SERVER_SUPPORT
- if (server_active)
- {
- server_scratch (finfo->file);
- server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1,
- (unsigned char *) NULL, (struct buffer *) NULL);
- }
-#endif
- 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;
- }
-
- /* If the two merge revisions are the same, then there is nothing
- * to do. This needs to be checked before the rev2 == up-to-date base
- * revision check tha comes next. Otherwise, rev1 can == rev2 and get an
- * "already contains the changes between <rev1> and <rev1>" message.
- */
- if (rev1 && strcmp (rev1, rev2) == 0)
- {
- free (rev1);
- free (rev2);
- return;
- }
-
- /* If we know that the user file is up-to-date, then it becomes an
- * optimization to skip the merge when rev2 is the same as the base
- * revision. i.e. we know that diff3(file2,file1,file2) will produce
- * file2.
- */
- if (vers->vn_user != NULL && vers->ts_user != NULL
- && strcmp (vers->ts_user, vers->ts_rcs) == 0
- && strcmp (rev2, vers->vn_user) == 0)
- {
- if (!really_quiet)
- {
- cvs_output (finfo->fullname, 0);
- cvs_output (" already contains the differences between ", 0);
- cvs_output (rev1 ? rev1 : "creation", 0);
- cvs_output (" and ", 0);
- cvs_output (rev2, 0);
- cvs_output ("\n", 1);
- }
-
- if (rev1 != NULL)
- free (rev1);
- free (rev2);
-
- return;
- }
-
- /* 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))
- {
- if (rev1 != NULL)
- free (rev1);
- free (rev2);
-
- /* 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)
- {
- char *saved_options = options;
- Vers_TS *xvers;
-
- xvers = Version_TS (finfo, vers->options, jrev2, jdate2, 1, 0);
-
- /* Reset any keyword expansion option. Otherwise, when a
- command like `cvs update -kk -jT1 -jT2' creates a new file
- (because a file had the T2 tag, but not T1), the subsequent
- commit of that just-added file effectively would set the
- admin `-kk' option for that file in the repository. */
- options = NULL;
-
- /* FIXME: If checkout_file fails, we should arrange to
- return a non-zero exit status. */
- status = checkout_file (finfo, xvers, 1, 0, 1);
- options = saved_options;
-
- freevers_ts (&xvers);
-
- 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;
- }
-
- /* If there is no working file, then we can't do the merge. */
- if (vers->vn_user == NULL || vers->vn_user[0] == '-')
- {
- free (rev1);
- free (rev2);
-
- if (jdate2 != NULL)
- error (0, 0,
- "file %s does not exist, but is present in revision %s as of %s",
- finfo->fullname, jrev2, jdate2);
- else
- error (0, 0,
- "file %s does not exist, but is present in revision %s",
- finfo->fullname, jrev2);
-
- /* FIXME: Should we arrange to return a non-zero exit status? */
-
- return;
- }
-
-#ifdef SERVER_SUPPORT
- if (server_active && !isreadable (finfo->file))
- {
- int retcode;
- /* The file is up to date. Need to check out the current contents. */
- /* FIXME - see the FIXME comment above the call to RCS_checkout in the
- * patch_file function.
- */
- retcode = RCS_checkout (vers->srcfile, finfo->file,
- vers->vn_user, vers->tag,
- (char *) NULL, RUN_TTY,
- (RCSCHECKOUTPROC) NULL, (void *) NULL);
- if (retcode != 0)
- error (1, 0,
- "failed to check out %s file", finfo->fullname);
- }
-#endif
-
- /*
- * The users currently modified file is moved to a backup file name
- * ".#filename.version", so that it will stay around for a few days
- * before being automatically removed by some cron daemon. The "version"
- * is the version of the file that the user was most up-to-date with
- * before the merge.
- */
- backup = xmalloc (strlen (finfo->file)
- + strlen (vers->vn_user)
- + sizeof (BAKPREFIX)
- + 10);
- (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user);
-
- if (unlink_file (backup) < 0
- && !existence_error (errno))
- error (0, errno, "cannot remove %s", backup);
- copy_file (finfo->file, backup);
- xchmod (finfo->file, 1);
-
- t_options = vers->options;
-#if 0
- if (*t_options == '\0')
- t_options = "-kk"; /* to ignore keyword expansions */
-#endif
-
- /* If the source of the merge is the same as the working file
- revision, then we can just RCS_checkout the target (no merging
- as such). In the text file case, this is probably quite
- similar to the RCS_merge, but in the binary file case,
- RCS_merge gives all kinds of trouble. */
- if (vers->vn_user != NULL
- && strcmp (rev1, vers->vn_user) == 0
- /* See comments above about how No_Difference has already been
- called. */
- && vers->ts_user != NULL
- && strcmp (vers->ts_user, vers->ts_rcs) == 0
-
- /* Avoid this in the text file case. See below for why.
- */
- && (strcmp (t_options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file)))
- {
- /* FIXME: Verify my comment below:
- *
- * RCS_merge does nothing with keywords. It merges the changes between
- * two revisions without expanding the keywords (it might expand in
- * -kk mode before computing the diff between rev1 and rev2 - I'm not
- * sure). In other words, the keyword lines in the current work file
- * get left alone.
- *
- * Therfore, checking out the destination revision (rev2) is probably
- * incorrect in the text case since we should see the keywords that were
- * substituted into the original file at the time it was checked out
- * and not the keywords from rev2.
- *
- * Also, it is safe to pass in NULL for nametag since we know no
- * substitution is happening during the binary mode checkout.
- */
- if (RCS_checkout ( finfo->rcs, finfo->file, rev2, (char *)NULL, t_options,
- RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0 )
- status = 2;
- else
- status = 0;
-
- /* OK, this is really stupid. RCS_checkout carefully removes
- write permissions, and we carefully put them back. But
- until someone gets around to fixing it, that seems like the
- easiest way to get what would seem to be the right mode.
- I don't check CVSWRITE or _watched; I haven't thought about
- that in great detail, but it seems like a watched file should
- be checked out (writable) after a merge. */
- xchmod (finfo->file, 1);
-
- /* Traditionally, the text file case prints a whole bunch of
- scary looking and verbose output which fails to tell the user
- what is really going on (it gives them rev1 and rev2 but doesn't
- indicate in any way that rev1 == vn_user). I think just a
- simple "U foo" is good here; it seems analogous to the case in
- which the file was added on the branch in terms of what to
- print. */
- write_letter (finfo, 'U');
- }
- else if (strcmp (t_options, "-kb") == 0
- || wrap_merge_is_copy (finfo->file)
- || special_file_mismatch (finfo, rev1, rev2))
- {
- /* We are dealing with binary files, or files with a
- permission/linkage mismatch (this second case only occurs when
- PRESERVE_PERMISSIONS_SUPPORT is enabled), and real merging would
- need to take place. This is 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. */
- if (RCS_checkout ( finfo->rcs, finfo->file, rev2, (char *)NULL,
- t_options, RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0)
- status = 2;
- else
- status = 0;
-
- /* OK, this is really stupid. RCS_checkout carefully removes
- write permissions, and we carefully put them back. But
- until someone gets around to fixing it, that seems like the
- easiest way to get what would seem to be the right mode.
- I don't check CVSWRITE or _watched; I haven't thought about
- that in great detail, but it seems like a watched file should
- be checked out (writable) after a merge. */
- xchmod (finfo->file, 1);
-
- /* Hmm. We don't give them REV1 anywhere. I guess most people
- probably don't have a 3-way merge tool for the file type in
- question, and might just get confused if we tried to either
- provide them with a copy of the file from REV1, or even just
- told them what REV1 is so they can get it themself, but it
- might be worth thinking about. */
- /* See comment in merge_file about the "nonmergeable file"
- terminology. */
- error (0, 0, "nonmergeable file needs merge");
- error (0, 0, "revision %s from repository is now in %s",
- rev2, finfo->fullname);
- error (0, 0, "file from working directory is now in %s", backup);
- write_letter (finfo, 'C');
- }
- else
- status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file,
- t_options, rev1, rev2);
-
- if (status != 0)
- {
- if (status != 1)
- {
- error (0, status == -1 ? errno : 0,
- "could not merge revision %s of %s", rev2, finfo->fullname);
- error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s",
- finfo->fullname, backup);
- rename_file (backup, finfo->file);
- }
- }
- else /* status == 0 */
- {
- /* FIXME: the noexec case is broken. RCS_merge could be doing the
- xcmp on the temporary files without much hassle, I think. */
- if (!noexec && !xcmp (backup, finfo->file))
- {
- if (!really_quiet)
- {
- cvs_output (finfo->fullname, 0);
- cvs_output (" already contains the differences between ", 0);
- cvs_output (rev1, 0);
- cvs_output (" and ", 0);
- cvs_output (rev2, 0);
- cvs_output ("\n", 1);
- }
-
- /* and skip the registering and sending the new file since it
- * hasn't been updated.
- */
- goto out;
- }
- }
-
- /* The file has changed, but if we just checked it out it may
- still have the same timestamp it did when it was first
- registered above in checkout_file. We register it again with a
- dummy timestamp to make sure that later runs of CVS will
- recognize that it has changed.
-
- We don't actually need to register again if we called
- RCS_checkout above, and we aren't running as the server.
- However, that is not the normal case, and calling Register
- again won't cost much in that case. */
- RegisterMerge (finfo, vers, backup, status);
-
-out:
- free (rev1);
- free (rev2);
- free (backup);
-}
-
-
-
-/*
- * Report whether revisions REV1 and REV2 of FINFO agree on:
- * . file ownership
- * . permissions
- * . major and minor device numbers
- * . symbolic links
- * . hard links
- *
- * If either REV1 or REV2 is NULL, the working copy is used instead.
- *
- * Return 1 if the files differ on these data.
- */
-
-int
-special_file_mismatch (finfo, rev1, rev2)
- struct file_info *finfo;
- char *rev1;
- char *rev2;
-{
-#ifdef PRESERVE_PERMISSIONS_SUPPORT
- struct stat sb;
- RCSVers *vp;
- Node *n;
- uid_t rev1_uid, rev2_uid;
- gid_t rev1_gid, rev2_gid;
- mode_t rev1_mode, rev2_mode;
- unsigned long dev_long;
- dev_t rev1_dev, rev2_dev;
- char *rev1_symlink = NULL;
- char *rev2_symlink = NULL;
- List *rev1_hardlinks = NULL;
- List *rev2_hardlinks = NULL;
- int check_uids, check_gids, check_modes;
- int result;
-
- /* If we don't care about special file info, then
- don't report a mismatch in any case. */
- if (!preserve_perms)
- return 0;
-
- /* When special_file_mismatch is called from No_Difference, the
- RCS file has been only partially parsed. We must read the
- delta tree in order to compare special file info recorded in
- the delta nodes. (I think this is safe. -twp) */
- if (finfo->rcs->flags & PARTIAL)
- RCS_reparsercsfile (finfo->rcs, NULL, NULL);
-
- check_uids = check_gids = check_modes = 1;
-
- /* Obtain file information for REV1. If this is null, then stat
- finfo->file and use that info. */
- /* If a revision does not know anything about its status,
- then presumably it doesn't matter, and indicates no conflict. */
-
- if (rev1 == NULL)
- {
- if (islink (finfo->file))
- rev1_symlink = xreadlink (finfo->file);
- else
- {
-# ifdef HAVE_STRUCT_STAT_ST_RDEV
- if (CVS_LSTAT (finfo->file, &sb) < 0)
- error (1, errno, "could not get file information for %s",
- finfo->file);
- rev1_uid = sb.st_uid;
- rev1_gid = sb.st_gid;
- rev1_mode = sb.st_mode;
- if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode))
- rev1_dev = sb.st_rdev;
-# else
- error (1, 0, "cannot handle device files on this system (%s)",
- finfo->file);
-# endif
- }
- rev1_hardlinks = list_linked_files_on_disk (finfo->file);
- }
- else
- {
- n = findnode (finfo->rcs->versions, rev1);
- vp = n->data;
-
- n = findnode (vp->other_delta, "symlink");
- if (n != NULL)
- rev1_symlink = xstrdup (n->data);
- else
- {
- n = findnode (vp->other_delta, "owner");
- if (n == NULL)
- check_uids = 0; /* don't care */
- else
- rev1_uid = strtoul (n->data, NULL, 10);
-
- n = findnode (vp->other_delta, "group");
- if (n == NULL)
- check_gids = 0; /* don't care */
- else
- rev1_gid = strtoul (n->data, NULL, 10);
-
- n = findnode (vp->other_delta, "permissions");
- if (n == NULL)
- check_modes = 0; /* don't care */
- else
- rev1_mode = strtoul (n->data, NULL, 8);
-
- n = findnode (vp->other_delta, "special");
- if (n == NULL)
- rev1_mode |= S_IFREG;
- else
- {
- /* If the size of `ftype' changes, fix the sscanf call also */
- char ftype[16];
- if (sscanf (n->data, "%15s %lu", ftype,
- &dev_long) < 2)
- error (1, 0, "%s:%s has bad `special' newphrase %s",
- finfo->file, rev1, (char *)n->data);
- rev1_dev = dev_long;
- if (strcmp (ftype, "character") == 0)
- rev1_mode |= S_IFCHR;
- else if (strcmp (ftype, "block") == 0)
- rev1_mode |= S_IFBLK;
- else
- error (0, 0, "%s:%s unknown file type `%s'",
- finfo->file, rev1, ftype);
- }
-
- rev1_hardlinks = vp->hardlinks;
- if (rev1_hardlinks == NULL)
- rev1_hardlinks = getlist();
- }
- }
-
- /* Obtain file information for REV2. */
- if (rev2 == NULL)
- {
- if (islink (finfo->file))
- rev2_symlink = xreadlink (finfo->file);
- else
- {
-# ifdef HAVE_STRUCT_STAT_ST_RDEV
- if (CVS_LSTAT (finfo->file, &sb) < 0)
- error (1, errno, "could not get file information for %s",
- finfo->file);
- rev2_uid = sb.st_uid;
- rev2_gid = sb.st_gid;
- rev2_mode = sb.st_mode;
- if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode))
- rev2_dev = sb.st_rdev;
-# else
- error (1, 0, "cannot handle device files on this system (%s)",
- finfo->file);
-# endif
- }
- rev2_hardlinks = list_linked_files_on_disk (finfo->file);
- }
- else
- {
- n = findnode (finfo->rcs->versions, rev2);
- vp = n->data;
-
- n = findnode (vp->other_delta, "symlink");
- if (n != NULL)
- rev2_symlink = xstrdup (n->data);
- else
- {
- n = findnode (vp->other_delta, "owner");
- if (n == NULL)
- check_uids = 0; /* don't care */
- else
- rev2_uid = strtoul (n->data, NULL, 10);
-
- n = findnode (vp->other_delta, "group");
- if (n == NULL)
- check_gids = 0; /* don't care */
- else
- rev2_gid = strtoul (n->data, NULL, 10);
-
- n = findnode (vp->other_delta, "permissions");
- if (n == NULL)
- check_modes = 0; /* don't care */
- else
- rev2_mode = strtoul (n->data, NULL, 8);
-
- n = findnode (vp->other_delta, "special");
- if (n == NULL)
- rev2_mode |= S_IFREG;
- else
- {
- /* If the size of `ftype' changes, fix the sscanf call also */
- char ftype[16];
- if (sscanf (n->data, "%15s %lu", ftype,
- &dev_long) < 2)
- error (1, 0, "%s:%s has bad `special' newphrase %s",
- finfo->file, rev2, (char *)n->data);
- rev2_dev = dev_long;
- if (strcmp (ftype, "character") == 0)
- rev2_mode |= S_IFCHR;
- else if (strcmp (ftype, "block") == 0)
- rev2_mode |= S_IFBLK;
- else
- error (0, 0, "%s:%s unknown file type `%s'",
- finfo->file, rev2, ftype);
- }
-
- rev2_hardlinks = vp->hardlinks;
- if (rev2_hardlinks == NULL)
- rev2_hardlinks = getlist();
- }
- }
-
- /* Check the user/group ownerships and file permissions, printing
- an error for each mismatch found. Return 0 if all characteristics
- matched, and 1 otherwise. */
-
- result = 0;
-
- /* Compare symlinks first, since symlinks are simpler (don't have
- any other characteristics). */
- if (rev1_symlink != NULL && rev2_symlink == NULL)
- {
- error (0, 0, "%s is a symbolic link",
- (rev1 == NULL ? "working file" : rev1));
- result = 1;
- }
- else if (rev1_symlink == NULL && rev2_symlink != NULL)
- {
- error (0, 0, "%s is a symbolic link",
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
- else if (rev1_symlink != NULL)
- result = (strcmp (rev1_symlink, rev2_symlink) == 0);
- else
- {
- /* Compare user ownership. */
- if (check_uids && rev1_uid != rev2_uid)
- {
- error (0, 0, "%s: owner mismatch between %s and %s",
- finfo->file,
- (rev1 == NULL ? "working file" : rev1),
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
-
- /* Compare group ownership. */
- if (check_gids && rev1_gid != rev2_gid)
- {
- error (0, 0, "%s: group mismatch between %s and %s",
- finfo->file,
- (rev1 == NULL ? "working file" : rev1),
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
-
- /* Compare permissions. */
- if (check_modes &&
- (rev1_mode & 07777) != (rev2_mode & 07777))
- {
- error (0, 0, "%s: permission mismatch between %s and %s",
- finfo->file,
- (rev1 == NULL ? "working file" : rev1),
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
-
- /* Compare device file characteristics. */
- if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT))
- {
- error (0, 0, "%s: %s and %s are different file types",
- finfo->file,
- (rev1 == NULL ? "working file" : rev1),
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
- else if (S_ISBLK (rev1_mode))
- {
- if (rev1_dev != rev2_dev)
- {
- error (0, 0, "%s: device numbers of %s and %s do not match",
- finfo->file,
- (rev1 == NULL ? "working file" : rev1),
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
- }
-
- /* Compare hard links. */
- if (compare_linkage_lists (rev1_hardlinks, rev2_hardlinks) == 0)
- {
- error (0, 0, "%s: hard linkage of %s and %s do not match",
- finfo->file,
- (rev1 == NULL ? "working file" : rev1),
- (rev2 == NULL ? "working file" : rev2));
- result = 1;
- }
- }
-
- if (rev1_symlink != NULL)
- free (rev1_symlink);
- if (rev2_symlink != NULL)
- free (rev2_symlink);
- if (rev1_hardlinks != NULL)
- dellist (&rev1_hardlinks);
- if (rev2_hardlinks != NULL)
- dellist (&rev2_hardlinks);
-
- return result;
-#else
- return 0;
-#endif
-}
-
-
-
-int
-joining ()
-{
- return join_rev1 != NULL;
-}
OpenPOWER on IntegriCloud