diff options
Diffstat (limited to 'contrib/cvs/src/update.c')
-rw-r--r-- | contrib/cvs/src/update.c | 3049 |
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; -} |