117721Speter/* 2175280Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 354431Speter * 4175280Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5175280Sobrien * and others. 6175280Sobrien * 7175280Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8175280Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner 9175280Sobrien * 1017721Speter * You may distribute under the terms of the GNU General Public License as 1132788Speter * specified in the README file that comes with the CVS source distribution. 1254431Speter * 1317721Speter * "update" updates the version in the present directory with respect to the RCS 1417721Speter * repository. The present version must have been created by "checkout". The 1517721Speter * user can keep up-to-date by calling "update" whenever he feels like it. 1654431Speter * 1717721Speter * The present version can be committed by "commit", but this keeps the version 1817721Speter * in tact. 1954431Speter * 2017721Speter * Arguments following the options are taken to be file names to be updated, 2117721Speter * rather than updating the entire directory. 2254431Speter * 2317721Speter * Modified or non-existent RCS files are checked out and reported as U 2417721Speter * <user_file> 2554431Speter * 2617721Speter * Modified user files are reported as M <user_file>. If both the RCS file and 2717721Speter * the user file have been modified, the user file is replaced by the result 2817721Speter * of rcsmerge, and a backup file is written for the user in .#file.version. 2917721Speter * If this throws up irreconcilable differences, the file is reported as C 3017721Speter * <user_file>, and as M <user_file> otherwise. 3154431Speter * 3217721Speter * Files added but not yet committed are reported as A <user_file>. Files 3317721Speter * removed but not yet committed are reported as R <user_file>. 3454431Speter * 3517721Speter * If the current directory contains subdirectories that hold concurrent 3617721Speter * versions, these are updated too. If the -d option was specified, new 3717721Speter * directories added to the repository are automatically created and updated 3817721Speter * as well. 3954431Speter * 4054431Speter * $FreeBSD$ 4117721Speter */ 4217721Speter 4317721Speter#include "cvs.h" 44175280Sobrien#include <assert.h> 4525839Speter#include "savecwd.h" 4617721Speter#ifdef SERVER_SUPPORT 47102843Speter# include "md5.h" 4817721Speter#endif 4917721Speter#include "watch.h" 5017721Speter#include "fileattr.h" 5117721Speter#include "edit.h" 5225839Speter#include "getline.h" 5334467Speter#include "buffer.h" 5434467Speter#include "hardlink.h" 5517721Speter 5625839Speterstatic int checkout_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts, 5734467Speter int adding, int merging, int update_server)); 5817721Speter#ifdef SERVER_SUPPORT 5934467Speterstatic void checkout_to_buffer PROTO ((void *, const char *, size_t)); 6025839Speterstatic int patch_file PROTO ((struct file_info *finfo, 6125839Speter Vers_TS *vers_ts, 6225839Speter int *docheckout, struct stat *file_info, 6325839Speter unsigned char *checksum)); 6425839Speterstatic void patch_file_write PROTO ((void *, const char *, size_t)); 65128269Speter#endif /* SERVER_SUPPORT */ 6625839Speterstatic int merge_file PROTO ((struct file_info *finfo, Vers_TS *vers)); 6781407Speterstatic int scratch_file PROTO((struct file_info *finfo, Vers_TS *vers)); 68128269Speterstatic Dtype update_dirent_proc PROTO ((void *callerdat, const char *dir, 69128269Speter const char *repository, 70128269Speter const char *update_dir, 71128269Speter List *entries)); 72128269Speterstatic int update_dirleave_proc PROTO ((void *callerdat, const char *dir, 73128269Speter int err, const char *update_dir, 7425839Speter List *entries)); 7525839Speterstatic int update_fileproc PROTO ((void *callerdat, struct file_info *)); 7625839Speterstatic int update_filesdone_proc PROTO ((void *callerdat, int err, 77128269Speter const char *repository, 78128269Speter const char *update_dir, 79128269Speter List *entries)); 8034467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT 8134467Speterstatic int get_linkinfo_proc PROTO ((void *callerdat, struct file_info *)); 8234467Speter#endif 8325839Speterstatic void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts)); 8417721Speter 8517721Speterstatic char *options = NULL; 8617721Speterstatic char *tag = NULL; 8717721Speterstatic char *date = NULL; 8825839Speter/* This is a bit of a kludge. We call WriteTag at the beginning 8925839Speter before we know whether nonbranch is set or not. And then at the 9025839Speter end, once we have the right value for nonbranch, we call WriteTag 9125839Speter again. I don't know whether the first call is necessary or not. 9225839Speter rewrite_tag is nonzero if we are going to have to make that second 9325839Speter call. */ 9425839Speterstatic int rewrite_tag; 9525839Speterstatic int nonbranch; 9625839Speter 9734467Speter/* If we set the tag or date for a subdirectory, we use this to undo 9834467Speter the setting. See update_dirent_proc. */ 9934467Speterstatic char *tag_update_dir; 10034467Speter 10117721Speterstatic char *join_rev1, *date_rev1; 10217721Speterstatic char *join_rev2, *date_rev2; 10317721Speterstatic int aflag = 0; 10466528Speterstatic int toss_local_changes = 0; 10517721Speterstatic int force_tag_match = 1; 10683496Sdillonstatic int pull_template = 0; 10717721Speterstatic int update_build_dirs = 0; 10817721Speterstatic int update_prune_dirs = 0; 10917721Speterstatic int pipeout = 0; 11017721Speter#ifdef SERVER_SUPPORT 11117721Speterstatic int patches = 0; 11225839Speterstatic int rcs_diff_patches = 0; 11317721Speter#endif 11417721Speterstatic List *ignlist = (List *) NULL; 11517721Speterstatic time_t last_register_time; 11617721Speterstatic const char *const update_usage[] = 11717721Speter{ 11881407Speter "Usage: %s %s [-APCdflRp] [-k kopt] [-r rev] [-D date] [-j rev]\n", 11917721Speter " [-I ign] [-W spec] [files...]\n", 12017721Speter "\t-A\tReset any sticky tags/date/kopts.\n", 12117721Speter "\t-P\tPrune empty directories.\n", 12266528Speter "\t-C\tOverwrite locally modified files with clean repository copies.\n", 12317721Speter "\t-d\tBuild directories, like checkout does.\n", 12417721Speter "\t-f\tForce a head revision match if tag/date not found.\n", 12517721Speter "\t-l\tLocal directory only, no recursion.\n", 12617721Speter "\t-R\tProcess directories recursively.\n", 12725839Speter "\t-p\tSend updates to standard output (avoids stickiness).\n", 12881407Speter "\t-k kopt\tUse RCS kopt -k option on checkout. (is sticky)\n", 12925839Speter "\t-r rev\tUpdate using specified revision/tag (is sticky).\n", 13025839Speter "\t-D date\tSet date to update from (is sticky).\n", 13117721Speter "\t-j rev\tMerge in changes made between current revision and rev.\n", 13217721Speter "\t-I ign\tMore files to ignore (! to reset).\n", 13317721Speter "\t-W spec\tWrappers specification line.\n", 13483496Sdillon "\t-T\tCreate CVS/Template.\n", 13532788Speter "(Specify the --help global option for a list of other help options)\n", 13617721Speter NULL 13717721Speter}; 13817721Speter 13917721Speter/* 14017721Speter * update is the argv,argc based front end for arg parsing 14117721Speter */ 14217721Speterint 14317721Speterupdate (argc, argv) 14417721Speter int argc; 14517721Speter char **argv; 14617721Speter{ 14717721Speter int c, err; 14817721Speter int local = 0; /* recursive by default */ 14917721Speter int which; /* where to look for files and dirs */ 15083496Sdillon int xpull_template = 0; 15117721Speter 15217721Speter if (argc == -1) 15317721Speter usage (update_usage); 15417721Speter 15517721Speter ign_setup (); 15617721Speter wrap_setup (); 15717721Speter 15817721Speter /* parse the args */ 15926065Speter optind = 0; 16083496Sdillon while ((c = getopt (argc, argv, "+ApCPflRQTqduk:r:D:j:I:W:")) != -1) 16117721Speter { 16217721Speter switch (c) 16317721Speter { 16417721Speter case 'A': 16517721Speter aflag = 1; 16617721Speter break; 16766528Speter case 'C': 16866528Speter toss_local_changes = 1; 16966528Speter break; 17017721Speter case 'I': 17117721Speter ign_add (optarg, 0); 17217721Speter break; 17317721Speter case 'W': 17417721Speter wrap_add (optarg, 0); 17517721Speter break; 17617721Speter case 'k': 17717721Speter if (options) 17817721Speter free (options); 17917721Speter options = RCS_check_kflag (optarg); 18017721Speter break; 18117721Speter case 'l': 18217721Speter local = 1; 18317721Speter break; 18417721Speter case 'R': 18517721Speter local = 0; 18617721Speter break; 18717721Speter case 'Q': 18817721Speter case 'q': 18917721Speter /* The CVS 1.5 client sends these options (in addition to 19017721Speter Global_option requests), so we must ignore them. */ 19117721Speter if (!server_active) 19217721Speter error (1, 0, 19317721Speter "-q or -Q must be specified before \"%s\"", 194128269Speter cvs_cmd_name); 19517721Speter break; 19683496Sdillon case 'T': 19783496Sdillon xpull_template = 1; 19883496Sdillon break; 19917721Speter case 'd': 20017721Speter update_build_dirs = 1; 20117721Speter break; 20217721Speter case 'f': 20317721Speter force_tag_match = 0; 20417721Speter break; 20517721Speter case 'r': 20617721Speter tag = optarg; 20717721Speter break; 20817721Speter case 'D': 209175280Sobrien if (date) free (date); 21017721Speter date = Make_Date (optarg); 21117721Speter break; 21217721Speter case 'P': 21317721Speter update_prune_dirs = 1; 21417721Speter break; 21517721Speter case 'p': 21617721Speter pipeout = 1; 21717721Speter noexec = 1; /* so no locks will be created */ 21817721Speter break; 21917721Speter case 'j': 22017721Speter if (join_rev2) 22117721Speter error (1, 0, "only two -j options can be specified"); 22217721Speter if (join_rev1) 22317721Speter join_rev2 = optarg; 22417721Speter else 22517721Speter join_rev1 = optarg; 22617721Speter break; 22717721Speter case 'u': 22817721Speter#ifdef SERVER_SUPPORT 22917721Speter if (server_active) 23025839Speter { 23117721Speter patches = 1; 23225839Speter rcs_diff_patches = server_use_rcs_diff (); 23325839Speter } 23417721Speter else 23517721Speter#endif 23617721Speter usage (update_usage); 23717721Speter break; 23817721Speter case '?': 23917721Speter default: 24017721Speter usage (update_usage); 24117721Speter break; 24217721Speter } 24317721Speter } 24417721Speter argc -= optind; 24517721Speter argv += optind; 24617721Speter 24717721Speter#ifdef CLIENT_SUPPORT 24881407Speter if (current_parsed_root->isremote) 24917721Speter { 25025839Speter int pass; 25125839Speter 25217721Speter /* The first pass does the regular update. If we receive at least 25317721Speter one patch which failed, we do a second pass and just fetch 25417721Speter those files whose patches failed. */ 25525839Speter pass = 1; 25617721Speter do 25717721Speter { 25817721Speter int status; 25917721Speter 26017721Speter start_server (); 26117721Speter 26217721Speter if (local) 26317721Speter send_arg("-l"); 26417721Speter if (update_build_dirs) 26517721Speter send_arg("-d"); 26617721Speter if (pipeout) 26717721Speter send_arg("-p"); 26817721Speter if (!force_tag_match) 26917721Speter send_arg("-f"); 27017721Speter if (aflag) 27117721Speter send_arg("-A"); 27266528Speter if (toss_local_changes) 27366528Speter send_arg("-C"); 27417721Speter if (update_prune_dirs) 27517721Speter send_arg("-P"); 27617721Speter client_prune_dirs = update_prune_dirs; 27717721Speter option_with_arg ("-r", tag); 27825839Speter if (options && options[0] != '\0') 27925839Speter send_arg (options); 28017721Speter if (date) 28117721Speter client_senddate (date); 28217721Speter if (join_rev1) 28317721Speter option_with_arg ("-j", join_rev1); 28417721Speter if (join_rev2) 28517721Speter option_with_arg ("-j", join_rev2); 28625839Speter wrap_send (); 28717721Speter 28866528Speter if (failed_patches_count == 0) 28966528Speter { 29066528Speter unsigned int flags = 0; 29132788Speter 29266528Speter /* If the server supports the command "update-patches", that 29366528Speter means that it knows how to handle the -u argument to update, 29466528Speter which means to send patches instead of complete files. 29566528Speter 29666528Speter We don't send -u if failed_patches != NULL, so that the 29766528Speter server doesn't try to send patches which will just fail 29866528Speter again. At least currently, the client also clobbers the 29966528Speter file and tells the server it is lost, which also will get 30066528Speter a full file instead of a patch, but it seems clean to omit 30166528Speter -u. */ 30225839Speter if (supported_request ("update-patches")) 30325839Speter send_arg ("-u"); 30417721Speter 305107487Speter send_arg ("--"); 306107487Speter 30766528Speter if (update_build_dirs) 30866528Speter flags |= SEND_BUILD_DIRS; 30966528Speter 31066528Speter if (toss_local_changes) { 31166528Speter flags |= SEND_NO_CONTENTS; 31266528Speter flags |= BACKUP_MODIFIED_FILES; 31366528Speter } 31466528Speter 31525839Speter /* If noexec, probably could be setting SEND_NO_CONTENTS. 31625839Speter Same caveats as for "cvs status" apply. */ 31766528Speter 31866528Speter send_files (argc, argv, local, aflag, flags); 31954431Speter send_file_names (argc, argv, SEND_EXPAND_WILD); 32017721Speter } 32117721Speter else 32217721Speter { 32317721Speter int i; 32417721Speter 32517721Speter (void) printf ("%s client: refetching unpatchable files\n", 32617721Speter program_name); 32717721Speter 32825839Speter if (toplevel_wd != NULL 32925839Speter && CVS_CHDIR (toplevel_wd) < 0) 33017721Speter { 33117721Speter error (1, errno, "could not chdir to %s", toplevel_wd); 33217721Speter } 33317721Speter 334107487Speter send_arg ("--"); 335107487Speter 33617721Speter for (i = 0; i < failed_patches_count; i++) 33754431Speter if (unlink_file (failed_patches[i]) < 0 33854431Speter && !existence_error (errno)) 33954431Speter error (0, errno, "cannot remove %s", 34054431Speter failed_patches[i]); 34117721Speter send_files (failed_patches_count, failed_patches, local, 34225839Speter aflag, update_build_dirs ? SEND_BUILD_DIRS : 0); 34354431Speter send_file_names (failed_patches_count, failed_patches, 0); 34466528Speter free_names (&failed_patches_count, failed_patches); 34517721Speter } 34617721Speter 34717721Speter send_to_server ("update\012", 0); 34817721Speter 34917721Speter status = get_responses_and_close (); 35025839Speter 35125839Speter /* If there are any conflicts, the server will return a 35225839Speter non-zero exit status. If any patches failed, we still 35325839Speter want to run the update again. We use a pass count to 35425839Speter avoid an endless loop. */ 35525839Speter 35625839Speter /* Notes: (1) assuming that status != 0 implies a 35725839Speter potential conflict is the best we can cleanly do given 35825839Speter the current protocol. I suppose that trying to 35925839Speter re-fetch in cases where there was a more serious error 36025839Speter is probably more or less harmless, but it isn't really 36125839Speter ideal. (2) it would be nice to have a testsuite case for the 36225839Speter conflict-and-patch-failed case. */ 36325839Speter 36425839Speter if (status != 0 36566528Speter && (failed_patches_count == 0 || pass > 1)) 36625839Speter { 36766528Speter if (failed_patches_count > 0) 36866528Speter free_names (&failed_patches_count, failed_patches); 36917721Speter return status; 37025839Speter } 37117721Speter 37225839Speter ++pass; 37366528Speter } while (failed_patches_count > 0); 37417721Speter 37517721Speter return 0; 37617721Speter } 37717721Speter#endif 37817721Speter 37917721Speter if (tag != NULL) 38017721Speter tag_check_valid (tag, argc, argv, local, aflag, ""); 38125839Speter if (join_rev1 != NULL) 38225839Speter tag_check_valid_join (join_rev1, argc, argv, local, aflag, ""); 38325839Speter if (join_rev2 != NULL) 38425839Speter tag_check_valid_join (join_rev2, argc, argv, local, aflag, ""); 38517721Speter 38617721Speter /* 38717721Speter * If we are updating the entire directory (for real) and building dirs 38817721Speter * as we go, we make sure there is no static entries file and write the 38917721Speter * tag file as appropriate 39017721Speter */ 39117721Speter if (argc <= 0 && !pipeout) 39217721Speter { 39317721Speter if (update_build_dirs) 39417721Speter { 39517721Speter if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno)) 39617721Speter error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT); 39717721Speter#ifdef SERVER_SUPPORT 39817721Speter if (server_active) 39966528Speter { 40066528Speter char *repos = Name_Repository (NULL, NULL); 40166528Speter server_clear_entstat (".", repos); 40266528Speter free (repos); 40366528Speter } 40417721Speter#endif 40517721Speter } 40617721Speter 40717721Speter /* keep the CVS/Tag file current with the specified arguments */ 40817721Speter if (aflag || tag || date) 40917721Speter { 41066528Speter char *repos = Name_Repository (NULL, NULL); 41166528Speter WriteTag ((char *) NULL, tag, date, 0, ".", repos); 41266528Speter free (repos); 41325839Speter rewrite_tag = 1; 41425839Speter nonbranch = 0; 41517721Speter } 41617721Speter } 41717721Speter 41817721Speter /* look for files/dirs locally and in the repository */ 41917721Speter which = W_LOCAL | W_REPOS; 42017721Speter 42117721Speter /* look in the attic too if a tag or date is specified */ 42217721Speter if (tag != NULL || date != NULL || joining()) 42317721Speter which |= W_ATTIC; 42417721Speter 42517721Speter /* call the command line interface */ 42617721Speter err = do_update (argc, argv, options, tag, date, force_tag_match, 42717721Speter local, update_build_dirs, aflag, update_prune_dirs, 42883496Sdillon pipeout, which, join_rev1, join_rev2, (char *) NULL, 429128269Speter xpull_template, (char *) NULL); 43017721Speter 43117721Speter /* free the space Make_Date allocated if necessary */ 43217721Speter if (date != NULL) 43317721Speter free (date); 43417721Speter 435130307Speter return err; 43617721Speter} 43717721Speter 438130307Speter 439130307Speter 44017721Speter/* 44117721Speter * Command line interface to update (used by checkout) 44217721Speter */ 44317721Speterint 44417721Speterdo_update (argc, argv, xoptions, xtag, xdate, xforce, local, xbuild, xaflag, 44581407Speter xprune, xpipeout, which, xjoin_rev1, xjoin_rev2, preload_update_dir, 446128269Speter xpull_template, repository) 44717721Speter int argc; 44817721Speter char **argv; 44917721Speter char *xoptions; 45017721Speter char *xtag; 45117721Speter char *xdate; 45217721Speter int xforce; 45317721Speter int local; 45417721Speter int xbuild; 45517721Speter int xaflag; 45617721Speter int xprune; 45717721Speter int xpipeout; 45817721Speter int which; 45917721Speter char *xjoin_rev1; 46017721Speter char *xjoin_rev2; 46117721Speter char *preload_update_dir; 46283496Sdillon int xpull_template; 463128269Speter char *repository; 46417721Speter{ 46517721Speter int err = 0; 46617721Speter char *cp; 46717721Speter 46817721Speter /* fill in the statics */ 46917721Speter options = xoptions; 47017721Speter tag = xtag; 47117721Speter date = xdate; 47217721Speter force_tag_match = xforce; 47317721Speter update_build_dirs = xbuild; 47417721Speter aflag = xaflag; 47517721Speter update_prune_dirs = xprune; 47617721Speter pipeout = xpipeout; 47783496Sdillon pull_template = xpull_template; 47817721Speter 47917721Speter /* setup the join support */ 48017721Speter join_rev1 = xjoin_rev1; 48117721Speter join_rev2 = xjoin_rev2; 48217721Speter if (join_rev1 && (cp = strchr (join_rev1, ':')) != NULL) 48317721Speter { 48417721Speter *cp++ = '\0'; 48517721Speter date_rev1 = Make_Date (cp); 48617721Speter } 48717721Speter else 48817721Speter date_rev1 = (char *) NULL; 48917721Speter if (join_rev2 && (cp = strchr (join_rev2, ':')) != NULL) 49017721Speter { 49117721Speter *cp++ = '\0'; 49217721Speter date_rev2 = Make_Date (cp); 49317721Speter } 49417721Speter else 49517721Speter date_rev2 = (char *) NULL; 49617721Speter 49734467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT 49834467Speter if (preserve_perms) 49934467Speter { 50034467Speter /* We need to do an extra recursion, bleah. It's to make sure 50134467Speter that we know as much as possible about file linkage. */ 50234467Speter hardlist = getlist(); 50334467Speter working_dir = xgetwd(); /* save top-level working dir */ 50434467Speter 50534467Speter /* FIXME-twp: the arguments to start_recursion make me dizzy. This 50634467Speter function call was copied from the update_fileproc call that 50734467Speter follows it; someone should make sure that I did it right. */ 50834467Speter err = start_recursion (get_linkinfo_proc, (FILESDONEPROC) NULL, 50934467Speter (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 510109660Speter argc, argv, local, which, aflag, CVS_LOCK_READ, 511128269Speter preload_update_dir, 1, (char *) NULL); 51234467Speter if (err) 513130307Speter return err; 51434467Speter 51534467Speter /* FIXME-twp: at this point we should walk the hardlist 51634467Speter and update the `links' field of each hardlink_info struct 51734467Speter to list the files that are linked on dist. That would make 51834467Speter it easier & more efficient to compare the disk linkage with 51934467Speter the repository linkage (a simple strcmp). */ 52034467Speter } 52134467Speter#endif 52234467Speter 52317721Speter /* call the recursion processor */ 52417721Speter err = start_recursion (update_fileproc, update_filesdone_proc, 52525839Speter update_dirent_proc, update_dirleave_proc, NULL, 526109660Speter argc, argv, local, which, aflag, CVS_LOCK_READ, 527128269Speter preload_update_dir, 1, repository); 52817721Speter 52966528Speter /* see if we need to sleep before returning to avoid time-stamp races */ 530175280Sobrien if (!server_active && last_register_time) 53117721Speter { 53281407Speter sleep_past (last_register_time); 53317721Speter } 53417721Speter 535130307Speter return err; 53617721Speter} 53717721Speter 53834467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT 53917721Speter/* 54034467Speter * The get_linkinfo_proc callback adds each file to the hardlist 54134467Speter * (see hardlink.c). 54234467Speter */ 54334467Speter 54434467Speterstatic int 54534467Speterget_linkinfo_proc (callerdat, finfo) 54634467Speter void *callerdat; 54734467Speter struct file_info *finfo; 54834467Speter{ 54934467Speter char *fullpath; 55034467Speter Node *linkp; 55134467Speter struct hardlink_info *hlinfo; 55234467Speter 55334467Speter /* Get the full pathname of the current file. */ 55434467Speter fullpath = xmalloc (strlen(working_dir) + 55534467Speter strlen(finfo->fullname) + 2); 55634467Speter sprintf (fullpath, "%s/%s", working_dir, finfo->fullname); 55734467Speter 55834467Speter /* To permit recursing into subdirectories, files 55934467Speter are keyed on the full pathname and not on the basename. */ 56034467Speter linkp = lookup_file_by_inode (fullpath); 56134467Speter if (linkp == NULL) 56234467Speter { 56334467Speter /* The file isn't on disk; we are probably restoring 56434467Speter a file that was removed. */ 56534467Speter return 0; 56634467Speter } 56734467Speter 56834467Speter /* Create a new, empty hardlink_info node. */ 56934467Speter hlinfo = (struct hardlink_info *) 57034467Speter xmalloc (sizeof (struct hardlink_info)); 57134467Speter 57234467Speter hlinfo->status = (Ctype) 0; /* is this dumb? */ 57334467Speter hlinfo->checked_out = 0; 57434467Speter 575128269Speter linkp->data = hlinfo; 57634467Speter 57734467Speter return 0; 57834467Speter} 57934467Speter#endif 58034467Speter 581128269Speter 582128269Speter 58334467Speter/* 58417721Speter * This is the callback proc for update. It is called for each file in each 58517721Speter * directory by the recursion code. The current directory is the local 58617721Speter * instantiation. file is the file name we are to operate on. update_dir is 58717721Speter * set to the path relative to where we started (for pretty printing). 58817721Speter * repository is the repository. entries and srcfiles are the pre-parsed 58917721Speter * entries and source control files. 59017721Speter * 59117721Speter * This routine decides what needs to be done for each file and does the 59217721Speter * appropriate magic for checkout 59317721Speter */ 59417721Speterstatic int 59525839Speterupdate_fileproc (callerdat, finfo) 59625839Speter void *callerdat; 59717721Speter struct file_info *finfo; 59817721Speter{ 59917721Speter int retval; 60017721Speter Ctype status; 60117721Speter Vers_TS *vers; 60217721Speter 60325839Speter status = Classify_File (finfo, tag, date, options, force_tag_match, 60425839Speter aflag, &vers, pipeout); 60525839Speter 60625839Speter /* Keep track of whether TAG is a branch tag. 60725839Speter Note that if it is a branch tag in some files and a nonbranch tag 60825839Speter in others, treat it as a nonbranch tag. It is possible that case 60925839Speter should elicit a warning or an error. */ 61025839Speter if (rewrite_tag 61125839Speter && tag != NULL 61225839Speter && finfo->rcs != NULL) 61325839Speter { 61432467Sjulian char *rev = RCS_getversion (finfo->rcs, tag, date, 1, NULL); 61525839Speter if (rev != NULL 61625839Speter && !RCS_nodeisbranch (finfo->rcs, tag)) 61725839Speter nonbranch = 1; 61825839Speter if (rev != NULL) 61925839Speter free (rev); 62025839Speter } 62125839Speter 62217721Speter if (pipeout) 62317721Speter { 62417721Speter /* 62517721Speter * We just return success without doing anything if any of the really 62617721Speter * funky cases occur 62717721Speter * 62817721Speter * If there is still a valid RCS file, do a regular checkout type 62917721Speter * operation 63017721Speter */ 63117721Speter switch (status) 63217721Speter { 63317721Speter case T_UNKNOWN: /* unknown file was explicitly asked 63417721Speter * about */ 63517721Speter case T_REMOVE_ENTRY: /* needs to be un-registered */ 63617721Speter case T_ADDED: /* added but not committed */ 63717721Speter retval = 0; 63817721Speter break; 63917721Speter case T_CONFLICT: /* old punt-type errors */ 64017721Speter retval = 1; 64117721Speter break; 64217721Speter case T_UPTODATE: /* file was already up-to-date */ 64317721Speter case T_NEEDS_MERGE: /* needs merging */ 64417721Speter case T_MODIFIED: /* locally modified */ 64517721Speter case T_REMOVED: /* removed but not committed */ 64617721Speter case T_CHECKOUT: /* needs checkout */ 64717721Speter case T_PATCH: /* needs patch */ 64834467Speter retval = checkout_file (finfo, vers, 0, 0, 0); 64917721Speter break; 65017721Speter 65117721Speter default: /* can't ever happen :-) */ 65217721Speter error (0, 0, 65317721Speter "unknown file status %d for file %s", status, finfo->file); 65417721Speter retval = 0; 65517721Speter break; 65617721Speter } 65717721Speter } 65817721Speter else 65917721Speter { 66017721Speter switch (status) 66117721Speter { 66217721Speter case T_UNKNOWN: /* unknown file was explicitly asked 66317721Speter * about */ 66417721Speter case T_UPTODATE: /* file was already up-to-date */ 66517721Speter retval = 0; 66617721Speter break; 66717721Speter case T_CONFLICT: /* old punt-type errors */ 66817721Speter retval = 1; 66932788Speter write_letter (finfo, 'C'); 67017721Speter break; 67117721Speter case T_NEEDS_MERGE: /* needs merging */ 67281407Speter if (! toss_local_changes) 67381407Speter { 67481407Speter retval = merge_file (finfo, vers); 67581407Speter break; 67681407Speter } 67781407Speter /* else FALL THROUGH */ 67817721Speter case T_MODIFIED: /* locally modified */ 67917721Speter retval = 0; 68066528Speter if (toss_local_changes) 68166528Speter { 68266528Speter char *bakname; 68366528Speter bakname = backup_file (finfo->file, vers->vn_user); 68466528Speter /* This behavior is sufficiently unexpected to 68566528Speter justify overinformativeness, I think. */ 686175280Sobrien if (!really_quiet && !server_active) 68766528Speter (void) printf ("(Locally modified %s moved to %s)\n", 68866528Speter finfo->file, bakname); 68966528Speter free (bakname); 69017721Speter 69166528Speter /* The locally modified file is still present, but 69266528Speter it will be overwritten by the repository copy 69366528Speter after this. */ 69466528Speter status = T_CHECKOUT; 69566528Speter retval = checkout_file (finfo, vers, 0, 0, 1); 69666528Speter } 69766528Speter else 69866528Speter { 69966528Speter if (vers->ts_conflict) 70066528Speter { 701175280Sobrien if (file_has_markers (finfo)) 70266528Speter { 70366528Speter write_letter (finfo, 'C'); 70466528Speter retval = 1; 70566528Speter } 70666528Speter else 70766528Speter { 70866528Speter /* Reregister to clear conflict flag. */ 70966528Speter Register (finfo->entries, finfo->file, 71066528Speter vers->vn_rcs, vers->ts_rcs, 71166528Speter vers->options, vers->tag, 71266528Speter vers->date, (char *)0); 71366528Speter } 71466528Speter } 71566528Speter if (!retval) 71666528Speter write_letter (finfo, 'M'); 71766528Speter } 71817721Speter break; 71981407Speter case T_PATCH: /* needs patch */ 72017721Speter#ifdef SERVER_SUPPORT 72117721Speter if (patches) 72217721Speter { 72317721Speter int docheckout; 72417721Speter struct stat file_info; 72517721Speter unsigned char checksum[16]; 72617721Speter 72725839Speter retval = patch_file (finfo, 72825839Speter vers, &docheckout, 72917721Speter &file_info, checksum); 73017721Speter if (! docheckout) 73117721Speter { 73217721Speter if (server_active && retval == 0) 73325839Speter server_updated (finfo, vers, 73425839Speter (rcs_diff_patches 73525839Speter ? SERVER_RCS_DIFF 73625839Speter : SERVER_PATCHED), 73734467Speter file_info.st_mode, checksum, 73834467Speter (struct buffer *) NULL); 73917721Speter break; 74017721Speter } 74117721Speter } 74281407Speter#endif 74317721Speter /* If we're not running as a server, just check the 74432788Speter file out. It's simpler and faster than producing 74532788Speter and applying patches. */ 74617721Speter /* Fall through. */ 74717721Speter case T_CHECKOUT: /* needs checkout */ 74834467Speter retval = checkout_file (finfo, vers, 0, 0, 1); 74917721Speter break; 75017721Speter case T_ADDED: /* added but not committed */ 75132788Speter write_letter (finfo, 'A'); 75232788Speter retval = 0; 75317721Speter break; 75417721Speter case T_REMOVED: /* removed but not committed */ 75532788Speter write_letter (finfo, 'R'); 75632788Speter retval = 0; 75717721Speter break; 75817721Speter case T_REMOVE_ENTRY: /* needs to be un-registered */ 75981407Speter retval = scratch_file (finfo, vers); 76017721Speter break; 76117721Speter default: /* can't ever happen :-) */ 76217721Speter error (0, 0, 76317721Speter "unknown file status %d for file %s", status, finfo->file); 76417721Speter retval = 0; 76517721Speter break; 76617721Speter } 76717721Speter } 76817721Speter 76917721Speter /* only try to join if things have gone well thus far */ 77017721Speter if (retval == 0 && join_rev1) 77125839Speter join_file (finfo, vers); 77217721Speter 77317721Speter /* if this directory has an ignore list, add this file to it */ 77481407Speter if (ignlist && (status != T_UNKNOWN || vers->ts_user == NULL)) 77517721Speter { 77617721Speter Node *p; 77717721Speter 77817721Speter p = getnode (); 77917721Speter p->type = FILES; 78017721Speter p->key = xstrdup (finfo->file); 78117721Speter if (addnode (ignlist, p) != 0) 78217721Speter freenode (p); 78317721Speter } 78417721Speter 78517721Speter freevers_ts (&vers); 786128269Speter return retval; 78717721Speter} 78817721Speter 78917721Speter 790128269Speter 791128269Speterstatic void update_ignproc PROTO ((const char *, const char *)); 792128269Speter 79317721Speterstatic void 79417721Speterupdate_ignproc (file, dir) 795128269Speter const char *file; 796128269Speter const char *dir; 79717721Speter{ 79832788Speter struct file_info finfo; 799128269Speter char *tmp; 80032788Speter 80132788Speter memset (&finfo, 0, sizeof (finfo)); 80232788Speter finfo.file = file; 80332788Speter finfo.update_dir = dir; 80432788Speter if (dir[0] == '\0') 805128269Speter tmp = xstrdup (file); 80632788Speter else 80732788Speter { 808128269Speter tmp = xmalloc (strlen (file) + strlen (dir) + 10); 809128269Speter strcpy (tmp, dir); 810128269Speter strcat (tmp, "/"); 811128269Speter strcat (tmp, file); 81232788Speter } 81332788Speter 814128269Speter finfo.fullname = tmp; 81532788Speter write_letter (&finfo, '?'); 816128269Speter free (tmp); 81717721Speter} 81817721Speter 819128269Speter 820128269Speter 82117721Speter/* ARGSUSED */ 82217721Speterstatic int 82325839Speterupdate_filesdone_proc (callerdat, err, repository, update_dir, entries) 82425839Speter void *callerdat; 82517721Speter int err; 826128269Speter const char *repository; 827128269Speter const char *update_dir; 82825839Speter List *entries; 82917721Speter{ 83025839Speter if (rewrite_tag) 83125839Speter { 83225839Speter WriteTag (NULL, tag, date, nonbranch, update_dir, repository); 83325839Speter rewrite_tag = 0; 83425839Speter } 83525839Speter 83617721Speter /* if this directory has an ignore list, process it then free it */ 83717721Speter if (ignlist) 83817721Speter { 83925839Speter ignore_files (ignlist, entries, update_dir, update_ignproc); 84017721Speter dellist (&ignlist); 84117721Speter } 84217721Speter 84317721Speter /* Clean up CVS admin dirs if we are export */ 844128269Speter if (strcmp (cvs_cmd_name, "export") == 0) 84517721Speter { 84617721Speter /* I'm not sure the existence_error is actually possible (except 84717721Speter in cases where we really should print a message), but since 84817721Speter this code used to ignore all errors, I'll play it safe. */ 84917721Speter if (unlink_file_dir (CVSADM) < 0 && !existence_error (errno)) 85017721Speter error (0, errno, "cannot remove %s directory", CVSADM); 85117721Speter } 85217721Speter else if (!server_active && !pipeout) 85317721Speter { 85417721Speter /* If there is no CVS/Root file, add one */ 85517721Speter if (!isfile (CVSADM_ROOT)) 85681407Speter Create_Root ((char *) NULL, current_parsed_root->original); 85717721Speter } 85817721Speter 859130307Speter return err; 86017721Speter} 86117721Speter 862128269Speter 863128269Speter 86417721Speter/* 86517721Speter * update_dirent_proc () is called back by the recursion processor before a 86617721Speter * sub-directory is processed for update. In this case, update_dirent proc 86717721Speter * will probably create the directory unless -d isn't specified and this is a 86817721Speter * new directory. A return code of 0 indicates the directory should be 86917721Speter * processed by the recursion code. A return of non-zero indicates the 87017721Speter * recursion code should skip this directory. 87117721Speter */ 87217721Speterstatic Dtype 87325839Speterupdate_dirent_proc (callerdat, dir, repository, update_dir, entries) 87425839Speter void *callerdat; 875128269Speter const char *dir; 876128269Speter const char *repository; 877128269Speter const char *update_dir; 87825839Speter List *entries; 87917721Speter{ 88017721Speter if (ignore_directory (update_dir)) 88125839Speter { 88217721Speter /* print the warm fuzzy message */ 88317721Speter if (!quiet) 88417721Speter error (0, 0, "Ignoring %s", update_dir); 88517721Speter return R_SKIP_ALL; 88625839Speter } 88717721Speter 88817721Speter if (!isdir (dir)) 88917721Speter { 89017721Speter /* if we aren't building dirs, blow it off */ 89117721Speter if (!update_build_dirs) 892130307Speter return R_SKIP_ALL; 89317721Speter 89454431Speter /* Various CVS administrators are in the habit of removing 89554431Speter the repository directory for things they don't want any 89654431Speter more. I've even been known to do it myself (on rare 89754431Speter occasions). Not the usual recommended practice, but we 89854431Speter want to try to come up with some kind of 89954431Speter reasonable/documented/sensible behavior. Generally 90054431Speter the behavior is to just skip over that directory (see 90154431Speter dirs test in sanity.sh; the case which reaches here 90254431Speter is when update -d is specified, and the working directory 90354431Speter is gone but the subdirectory is still mentioned in 90454431Speter CVS/Entries). */ 905175280Sobrien /* In the remote case, the client should refrain from 906175280Sobrien sending us the directory in the first place. So we 907175280Sobrien want to continue to give an error, so clients make 908175280Sobrien sure to do this. */ 909175280Sobrien if (!server_active && !isdir (repository)) 91054431Speter return R_SKIP_ALL; 91154431Speter 91217721Speter if (noexec) 91317721Speter { 91417721Speter /* ignore the missing dir if -n is specified */ 91532788Speter error (0, 0, "New directory `%s' -- ignored", update_dir); 916130307Speter return R_SKIP_ALL; 91717721Speter } 91817721Speter else 91917721Speter { 92017721Speter /* otherwise, create the dir and appropriate adm files */ 92134467Speter 92234467Speter /* If no tag or date were specified on the command line, 92334467Speter and we're not using -A, we want the subdirectory to use 92434467Speter the tag and date, if any, of the current directory. 92534467Speter That way, update -d will work correctly when working on 92634467Speter a branch. 92734467Speter 92834467Speter We use TAG_UPDATE_DIR to undo the tag setting in 92934467Speter update_dirleave_proc. If we did not do this, we would 93034467Speter not correctly handle a working directory with multiple 93134467Speter tags (and maybe we should prohibit such working 93234467Speter directories, but they work now and we shouldn't make 93334467Speter them stop working without more thought). */ 93434467Speter if ((tag == NULL && date == NULL) && ! aflag) 93534467Speter { 93634467Speter ParseTag (&tag, &date, &nonbranch); 93734467Speter if (tag != NULL || date != NULL) 93834467Speter tag_update_dir = xstrdup (update_dir); 93934467Speter } 94034467Speter 94117721Speter make_directory (dir); 94225839Speter Create_Admin (dir, update_dir, repository, tag, date, 94325839Speter /* This is a guess. We will rewrite it later 94425839Speter via WriteTag. */ 94532788Speter 0, 94666528Speter 0, 94783496Sdillon pull_template); 94825839Speter rewrite_tag = 1; 94925839Speter nonbranch = 0; 95025839Speter Subdir_Register (entries, (char *) NULL, dir); 95117721Speter } 95217721Speter } 95317721Speter /* Do we need to check noexec here? */ 95417721Speter else if (!pipeout) 95517721Speter { 95617721Speter char *cvsadmdir; 95717721Speter 95817721Speter /* The directory exists. Check to see if it has a CVS 95917721Speter subdirectory. */ 96017721Speter 96117721Speter cvsadmdir = xmalloc (strlen (dir) + 80); 96217721Speter strcpy (cvsadmdir, dir); 96317721Speter strcat (cvsadmdir, "/"); 96417721Speter strcat (cvsadmdir, CVSADM); 96517721Speter 96617721Speter if (!isdir (cvsadmdir)) 96717721Speter { 96817721Speter /* We cannot successfully recurse into a directory without a CVS 96917721Speter subdirectory. Generally we will have already printed 97017721Speter "? foo". */ 97117721Speter free (cvsadmdir); 97217721Speter return R_SKIP_ALL; 97317721Speter } 97417721Speter free (cvsadmdir); 97517721Speter } 97617721Speter 97717721Speter /* 97817721Speter * If we are building dirs and not going to stdout, we make sure there is 97917721Speter * no static entries file and write the tag file as appropriate 98017721Speter */ 98117721Speter if (!pipeout) 98217721Speter { 98317721Speter if (update_build_dirs) 98417721Speter { 98525839Speter char *tmp; 98617721Speter 98725839Speter tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ENTSTAT) + 10); 98817721Speter (void) sprintf (tmp, "%s/%s", dir, CVSADM_ENTSTAT); 98917721Speter if (unlink_file (tmp) < 0 && ! existence_error (errno)) 99017721Speter error (1, errno, "cannot remove file %s", tmp); 99117721Speter#ifdef SERVER_SUPPORT 99217721Speter if (server_active) 99317721Speter server_clear_entstat (update_dir, repository); 99417721Speter#endif 99525839Speter free (tmp); 99617721Speter } 99717721Speter 99817721Speter /* keep the CVS/Tag file current with the specified arguments */ 99917721Speter if (aflag || tag || date) 100017721Speter { 100125839Speter WriteTag (dir, tag, date, 0, update_dir, repository); 100225839Speter rewrite_tag = 1; 100325839Speter nonbranch = 0; 100417721Speter } 100517721Speter 100683496Sdillon /* keep the CVS/Template file current */ 100783496Sdillon if (pull_template) 100883496Sdillon { 100983496Sdillon WriteTemplate (dir, update_dir); 101083496Sdillon } 101183496Sdillon 101217721Speter /* initialize the ignore list for this directory */ 101317721Speter ignlist = getlist (); 101417721Speter } 101517721Speter 101617721Speter /* print the warm fuzzy message */ 101717721Speter if (!quiet) 101817721Speter error (0, 0, "Updating %s", update_dir); 101917721Speter 1020130307Speter return R_PROCESS; 102117721Speter} 102217721Speter 1023128269Speter 1024128269Speter 102517721Speter/* 102617721Speter * update_dirleave_proc () is called back by the recursion code upon leaving 102717721Speter * a directory. It will prune empty directories if needed and will execute 102817721Speter * any appropriate update programs. 102917721Speter */ 103017721Speter/* ARGSUSED */ 103117721Speterstatic int 103225839Speterupdate_dirleave_proc (callerdat, dir, err, update_dir, entries) 103325839Speter void *callerdat; 1034128269Speter const char *dir; 103517721Speter int err; 1036128269Speter const char *update_dir; 103725839Speter List *entries; 103817721Speter{ 103966528Speter /* Delete the ignore list if it hasn't already been done. */ 104066528Speter if (ignlist) 104166528Speter dellist (&ignlist); 104266528Speter 104334467Speter /* If we set the tag or date for a new subdirectory in 104434467Speter update_dirent_proc, and we're now done with that subdirectory, 104534467Speter undo the tag/date setting. Note that we know that the tag and 104634467Speter date were both originally NULL in this case. */ 104734467Speter if (tag_update_dir != NULL && strcmp (update_dir, tag_update_dir) == 0) 104834467Speter { 104934467Speter if (tag != NULL) 105034467Speter { 105134467Speter free (tag); 105234467Speter tag = NULL; 105334467Speter } 105434467Speter if (date != NULL) 105534467Speter { 105634467Speter free (date); 105734467Speter date = NULL; 105834467Speter } 105934467Speter nonbranch = 0; 106034467Speter free (tag_update_dir); 106134467Speter tag_update_dir = NULL; 106234467Speter } 106334467Speter 106425839Speter if (strchr (dir, '/') == NULL) 106517721Speter { 106625839Speter /* FIXME: chdir ("..") loses with symlinks. */ 106725839Speter /* Prune empty dirs on the way out - if necessary */ 106825839Speter (void) CVS_CHDIR (".."); 106925839Speter if (update_prune_dirs && isemptydir (dir, 0)) 107025839Speter { 107125839Speter /* I'm not sure the existence_error is actually possible (except 107225839Speter in cases where we really should print a message), but since 107325839Speter this code used to ignore all errors, I'll play it safe. */ 107425839Speter if (unlink_file_dir (dir) < 0 && !existence_error (errno)) 107525839Speter error (0, errno, "cannot remove %s directory", dir); 107625839Speter Subdir_Deregister (entries, (char *) NULL, dir); 107725839Speter } 107817721Speter } 107917721Speter 1080130307Speter return err; 108117721Speter} 108217721Speter 1083130307Speter 1084130307Speter 108525839Speterstatic int isremoved PROTO ((Node *, void *)); 108625839Speter 108725839Speter/* Returns 1 if the file indicated by node has been removed. */ 108817721Speterstatic int 108925839Speterisremoved (node, closure) 109025839Speter Node *node; 109125839Speter void *closure; 109225839Speter{ 1093128269Speter Entnode *entdata = node->data; 109425839Speter 109525839Speter /* If the first character of the version is a '-', the file has been 109625839Speter removed. */ 109725839Speter return (entdata->version && entdata->version[0] == '-') ? 1 : 0; 109825839Speter} 109925839Speter 1100130307Speter 1101130307Speter 110225839Speter/* Returns 1 if the argument directory is completely empty, other than the 110325839Speter existence of the CVS directory entry. Zero otherwise. If MIGHT_NOT_EXIST 110425839Speter and the directory doesn't exist, then just return 0. */ 110525839Speterint 110625839Speterisemptydir (dir, might_not_exist) 1107128269Speter const char *dir; 110825839Speter int might_not_exist; 110917721Speter{ 111017721Speter DIR *dirp; 111117721Speter struct dirent *dp; 111217721Speter 111325839Speter if ((dirp = CVS_OPENDIR (dir)) == NULL) 111417721Speter { 111525839Speter if (might_not_exist && existence_error (errno)) 111625839Speter return 0; 111725839Speter error (0, errno, "cannot open directory %s for empty check", dir); 1118130307Speter return 0; 111917721Speter } 112025839Speter errno = 0; 112181407Speter while ((dp = CVS_READDIR (dirp)) != NULL) 112217721Speter { 112325839Speter if (strcmp (dp->d_name, ".") != 0 112425839Speter && strcmp (dp->d_name, "..") != 0) 112517721Speter { 112625839Speter if (strcmp (dp->d_name, CVSADM) != 0) 112725839Speter { 112825839Speter /* An entry other than the CVS directory. The directory 112925839Speter is certainly not empty. */ 113081407Speter (void) CVS_CLOSEDIR (dirp); 1131130307Speter return 0; 113225839Speter } 113325839Speter else 113425839Speter { 113525839Speter /* The CVS directory entry. We don't have to worry about 113625839Speter this unless the Entries file indicates that files have 113725839Speter been removed, but not committed, in this directory. 113825839Speter (Removing the directory would prevent people from 113925839Speter comitting the fact that they removed the files!) */ 114025839Speter List *l; 114125839Speter int files_removed; 114225839Speter struct saved_cwd cwd; 114325839Speter 114425839Speter if (save_cwd (&cwd)) 114525839Speter error_exit (); 114625839Speter 114725839Speter if (CVS_CHDIR (dir) < 0) 114825839Speter error (1, errno, "cannot change directory to %s", dir); 114934467Speter l = Entries_Open (0, NULL); 115025839Speter files_removed = walklist (l, isremoved, 0); 115125839Speter Entries_Close (l); 115225839Speter 115325839Speter if (restore_cwd (&cwd, NULL)) 115425839Speter error_exit (); 115525839Speter free_cwd (&cwd); 115625839Speter 115725839Speter if (files_removed != 0) 115825839Speter { 115925839Speter /* There are files that have been removed, but not 116025839Speter committed! Do not consider the directory empty. */ 116181407Speter (void) CVS_CLOSEDIR (dirp); 1162130307Speter return 0; 116325839Speter } 116425839Speter } 116517721Speter } 116625839Speter errno = 0; 116717721Speter } 116825839Speter if (errno != 0) 116925839Speter { 117025839Speter error (0, errno, "cannot read directory %s", dir); 117181407Speter (void) CVS_CLOSEDIR (dirp); 1172130307Speter return 0; 117325839Speter } 117481407Speter (void) CVS_CLOSEDIR (dirp); 1175130307Speter return 1; 117617721Speter} 117717721Speter 1178130307Speter 1179130307Speter 118017721Speter/* 118117721Speter * scratch the Entries file entry associated with a file 118217721Speter */ 118317721Speterstatic int 118481407Speterscratch_file (finfo, vers) 118525839Speter struct file_info *finfo; 118681407Speter Vers_TS *vers; 118717721Speter{ 118825839Speter history_write ('W', finfo->update_dir, "", finfo->file, finfo->repository); 118925839Speter Scratch_Entry (finfo->entries, finfo->file); 119081407Speter#ifdef SERVER_SUPPORT 119181407Speter if (server_active) 119281407Speter { 119381407Speter if (vers->ts_user == NULL) 119481407Speter server_scratch_entry_only (); 119581407Speter server_updated (finfo, vers, 119681407Speter SERVER_UPDATED, (mode_t) -1, 119781407Speter (unsigned char *) NULL, 119881407Speter (struct buffer *) NULL); 119981407Speter } 120081407Speter#endif 120125839Speter if (unlink_file (finfo->file) < 0 && ! existence_error (errno)) 120225839Speter error (0, errno, "unable to remove %s", finfo->fullname); 1203175280Sobrien else if (!server_active) 1204175280Sobrien { 120581407Speter /* skip this step when the server is running since 120681407Speter * server_updated should have handled it */ 120781407Speter /* keep the vers structure up to date in case we do a join 120881407Speter * - if there isn't a file, it can't very well have a version number, can it? 120981407Speter */ 121081407Speter if (vers->vn_user != NULL) 121181407Speter { 121281407Speter free (vers->vn_user); 121381407Speter vers->vn_user = NULL; 121481407Speter } 121581407Speter if (vers->ts_user != NULL) 121681407Speter { 121781407Speter free (vers->ts_user); 121881407Speter vers->ts_user = NULL; 121981407Speter } 122081407Speter } 1221130307Speter return 0; 122217721Speter} 122317721Speter 1224130307Speter 1225130307Speter 122617721Speter/* 122725839Speter * Check out a file. 122817721Speter */ 122917721Speterstatic int 123034467Spetercheckout_file (finfo, vers_ts, adding, merging, update_server) 123125839Speter struct file_info *finfo; 123217721Speter Vers_TS *vers_ts; 123325839Speter int adding; 123434467Speter int merging; 123534467Speter int update_server; 123617721Speter{ 123725839Speter char *backup; 123817721Speter int set_time, retval = 0; 123917721Speter int status; 124017721Speter int file_is_dead; 124134467Speter struct buffer *revbuf; 124217721Speter 124332788Speter backup = NULL; 124434467Speter revbuf = NULL; 124532788Speter 124634467Speter /* Don't screw with backup files if we're going to stdout, or if 124734467Speter we are the server. */ 1248175280Sobrien if (!pipeout && !server_active) 124917721Speter { 125025839Speter backup = xmalloc (strlen (finfo->file) 125125839Speter + sizeof (CVSADM) 125225839Speter + sizeof (CVSPREFIX) 125325839Speter + 10); 125425839Speter (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file); 125525839Speter if (isfile (finfo->file)) 125625839Speter rename_file (finfo->file, backup); 125717721Speter else 125834467Speter { 125925839Speter /* If -f/-t wrappers are being used to wrap up a directory, 126025839Speter then backup might be a directory instead of just a file. */ 126132788Speter if (unlink_file_dir (backup) < 0) 126232788Speter { 126332788Speter /* Not sure if the existence_error check is needed here. */ 126432788Speter if (!existence_error (errno)) 126532788Speter /* FIXME: should include update_dir in message. */ 126632788Speter error (0, errno, "error removing %s", backup); 126732788Speter } 126834467Speter free (backup); 126934467Speter backup = NULL; 127034467Speter } 127117721Speter } 127217721Speter 127317721Speter file_is_dead = RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs); 127417721Speter 127517721Speter if (!file_is_dead) 127617721Speter { 127717721Speter /* 127817721Speter * if we are checking out to stdout, print a nice message to 127917721Speter * stderr, and add the -p flag to the command */ 128017721Speter if (pipeout) 128117721Speter { 128217721Speter if (!quiet) 128317721Speter { 128425839Speter cvs_outerr ("\ 128525839Speter===================================================================\n\ 128625839SpeterChecking out ", 0); 128725839Speter cvs_outerr (finfo->fullname, 0); 128825839Speter cvs_outerr ("\n\ 128925839SpeterRCS: ", 0); 129025839Speter cvs_outerr (vers_ts->srcfile->path, 0); 129125839Speter cvs_outerr ("\n\ 129225839SpeterVERS: ", 0); 129325839Speter cvs_outerr (vers_ts->vn_rcs, 0); 129425839Speter cvs_outerr ("\n***************\n", 0); 129517721Speter } 129617721Speter } 129717721Speter 129834467Speter#ifdef SERVER_SUPPORT 129934467Speter if (update_server 130034467Speter && server_active 130134467Speter && ! pipeout 130234467Speter && ! file_gzip_level 130334467Speter && ! joining () 130434467Speter && ! wrap_name_has (finfo->file, WRAP_FROMCVS)) 130534467Speter { 130634467Speter revbuf = buf_nonio_initialize ((BUFMEMERRPROC) NULL); 130734467Speter status = RCS_checkout (vers_ts->srcfile, (char *) NULL, 1308128269Speter vers_ts->vn_rcs, vers_ts->tag, 130934467Speter vers_ts->options, RUN_TTY, 131034467Speter checkout_to_buffer, revbuf); 131134467Speter } 131234467Speter else 131334467Speter#endif 131434467Speter status = RCS_checkout (vers_ts->srcfile, 131534467Speter pipeout ? NULL : finfo->file, 1316128269Speter vers_ts->vn_rcs, vers_ts->tag, 131734467Speter vers_ts->options, RUN_TTY, 131834467Speter (RCSCHECKOUTPROC) NULL, (void *) NULL); 131917721Speter } 132017721Speter if (file_is_dead || status == 0) 132117721Speter { 132234467Speter mode_t mode; 132334467Speter 132434467Speter mode = (mode_t) -1; 132534467Speter 132617721Speter if (!pipeout) 132717721Speter { 132817721Speter Vers_TS *xvers_ts; 132917721Speter 133054431Speter if (revbuf != NULL && !noexec) 133134467Speter { 133234467Speter struct stat sb; 133334467Speter 133454431Speter /* FIXME: We should have RCS_checkout return the mode. 133554431Speter That would also fix the kludge with noexec, above, which 133654431Speter is here only because noexec doesn't write srcfile->path 133754431Speter for us to stat. */ 133834467Speter if (stat (vers_ts->srcfile->path, &sb) < 0) 1339128269Speter { 1340175280Sobrien#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) 1341128269Speter buf_free (revbuf); 1342175280Sobrien#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */ 134334467Speter error (1, errno, "cannot stat %s", 134434467Speter vers_ts->srcfile->path); 1345128269Speter } 134634467Speter mode = sb.st_mode &~ (S_IWRITE | S_IWGRP | S_IWOTH); 134734467Speter } 134834467Speter 134932788Speter if (cvswrite 135017721Speter && !file_is_dead 135125839Speter && !fileattr_get (finfo->file, "_watched")) 135234467Speter { 135334467Speter if (revbuf == NULL) 135434467Speter xchmod (finfo->file, 1); 135534467Speter else 135634467Speter { 135734467Speter /* We know that we are the server here, so 135834467Speter although xchmod checks umask, we don't bother. */ 135934467Speter mode |= (((mode & S_IRUSR) ? S_IWUSR : 0) 136034467Speter | ((mode & S_IRGRP) ? S_IWGRP : 0) 136134467Speter | ((mode & S_IROTH) ? S_IWOTH : 0)); 136234467Speter } 136334467Speter } 136417721Speter 136517721Speter { 136617721Speter /* A newly checked out file is never under the spell 136717721Speter of "cvs edit". If we think we were editing it 136817721Speter from a previous life, clean up. Would be better to 136917721Speter check for same the working directory instead of 137017721Speter same user, but that is hairy. */ 137117721Speter 137217721Speter struct addremove_args args; 137317721Speter 137425839Speter editor_set (finfo->file, getcaller (), NULL); 137517721Speter 137617721Speter memset (&args, 0, sizeof args); 137717721Speter args.remove_temp = 1; 137825839Speter watch_modify_watchers (finfo->file, &args); 137917721Speter } 138017721Speter 138117721Speter /* set the time from the RCS file iff it was unknown before */ 138232788Speter set_time = 138332788Speter (!noexec 138432788Speter && (vers_ts->vn_user == NULL || 138532788Speter strncmp (vers_ts->ts_rcs, "Initial", 7) == 0) 138632788Speter && !file_is_dead); 138717721Speter 138825839Speter wrap_fromcvs_process_file (finfo->file); 138917721Speter 139025839Speter xvers_ts = Version_TS (finfo, options, tag, date, 139125839Speter force_tag_match, set_time); 139217721Speter if (strcmp (xvers_ts->options, "-V4") == 0) 139317721Speter xvers_ts->options[0] = '\0'; 139417721Speter 139534467Speter if (revbuf != NULL) 139634467Speter { 139734467Speter /* If we stored the file data into a buffer, then we 139834467Speter didn't create a file at all, so xvers_ts->ts_user 139934467Speter is wrong. The correct value is to have it be the 140034467Speter same as xvers_ts->ts_rcs, meaning that the working 140134467Speter file is unchanged from the RCS file. 140234467Speter 140334467Speter FIXME: We should tell Version_TS not to waste time 140434467Speter statting the nonexistent file. 140534467Speter 140634467Speter FIXME: Actually, I don't think the ts_user value 140734467Speter matters at all here. The only use I know of is 140834467Speter that it is printed in a trace message by 140934467Speter Server_Register. */ 141034467Speter 141134467Speter if (xvers_ts->ts_user != NULL) 141234467Speter free (xvers_ts->ts_user); 141334467Speter xvers_ts->ts_user = xstrdup (xvers_ts->ts_rcs); 141434467Speter } 141534467Speter 141617721Speter (void) time (&last_register_time); 141717721Speter 141817721Speter if (file_is_dead) 141917721Speter { 142017721Speter if (xvers_ts->vn_user != NULL) 142117721Speter { 142225839Speter error (0, 0, 142325839Speter "warning: %s is not (any longer) pertinent", 142434467Speter finfo->fullname); 142517721Speter } 142625839Speter Scratch_Entry (finfo->entries, finfo->file); 142725839Speter#ifdef SERVER_SUPPORT 142825839Speter if (server_active && xvers_ts->ts_user == NULL) 142925839Speter server_scratch_entry_only (); 143025839Speter#endif 143125839Speter /* FIXME: Rather than always unlink'ing, and ignoring the 143225839Speter existence_error, we should do the unlink only if 143325839Speter vers_ts->ts_user is non-NULL. Then there would be no 143425839Speter need to ignore an existence_error (for example, if the 143525839Speter user removes the file while we are running). */ 143625839Speter if (unlink_file (finfo->file) < 0 && ! existence_error (errno)) 143717721Speter { 143825839Speter error (0, errno, "cannot remove %s", finfo->fullname); 143917721Speter } 144017721Speter } 144117721Speter else 144225839Speter Register (finfo->entries, finfo->file, 144325839Speter adding ? "0" : xvers_ts->vn_rcs, 144425839Speter xvers_ts->ts_user, xvers_ts->options, 144525839Speter xvers_ts->tag, xvers_ts->date, 144625839Speter (char *)0); /* Clear conflict flag on fresh checkout */ 144717721Speter 144817721Speter /* fix up the vers structure, in case it is used by join */ 144917721Speter if (join_rev1) 145017721Speter { 1451177401Sobrien /* FIXME: It seems like we should be preserving ts_user 1452177401Sobrien * & ts_rcs here, but setting them causes problems in 1453177401Sobrien * join_file(). 1454177401Sobrien */ 145517721Speter if (vers_ts->vn_user != NULL) 145617721Speter free (vers_ts->vn_user); 145717721Speter if (vers_ts->vn_rcs != NULL) 145817721Speter free (vers_ts->vn_rcs); 145917721Speter vers_ts->vn_user = xstrdup (xvers_ts->vn_rcs); 146017721Speter vers_ts->vn_rcs = xstrdup (xvers_ts->vn_rcs); 146117721Speter } 146217721Speter 146317721Speter /* If this is really Update and not Checkout, recode history */ 1464128269Speter if (strcmp (cvs_cmd_name, "update") == 0) 146525839Speter history_write ('U', finfo->update_dir, xvers_ts->vn_rcs, finfo->file, 146625839Speter finfo->repository); 146717721Speter 146817721Speter freevers_ts (&xvers_ts); 146917721Speter 147017721Speter if (!really_quiet && !file_is_dead) 147117721Speter { 147232788Speter write_letter (finfo, 'U'); 147317721Speter } 147417721Speter } 147534467Speter 147634467Speter#ifdef SERVER_SUPPORT 147734467Speter if (update_server && server_active) 147834467Speter server_updated (finfo, vers_ts, 147934467Speter merging ? SERVER_MERGED : SERVER_UPDATED, 148034467Speter mode, (unsigned char *) NULL, revbuf); 148134467Speter#endif 148217721Speter } 148317721Speter else 148417721Speter { 148534467Speter if (backup != NULL) 148634467Speter { 148725839Speter rename_file (backup, finfo->file); 148834467Speter free (backup); 148934467Speter backup = NULL; 149034467Speter } 149117721Speter 149234467Speter error (0, 0, "could not check out %s", finfo->fullname); 149317721Speter 149434467Speter retval = status; 149517721Speter } 149617721Speter 149734467Speter if (backup != NULL) 149825839Speter { 149925839Speter /* If -f/-t wrappers are being used to wrap up a directory, 150025839Speter then backup might be a directory instead of just a file. */ 150132788Speter if (unlink_file_dir (backup) < 0) 150232788Speter { 150332788Speter /* Not sure if the existence_error check is needed here. */ 150432788Speter if (!existence_error (errno)) 150532788Speter /* FIXME: should include update_dir in message. */ 150632788Speter error (0, errno, "error removing %s", backup); 150732788Speter } 150825839Speter free (backup); 150925839Speter } 151017721Speter 1511175280Sobrien#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) 1512128269Speter if (revbuf != NULL) 1513128269Speter buf_free (revbuf); 1514175280Sobrien#endif /* defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT) */ 1515130307Speter return retval; 151617721Speter} 151717721Speter 1518130307Speter 1519130307Speter 152017721Speter#ifdef SERVER_SUPPORT 152125839Speter 152234467Speter/* This function is used to write data from a file being checked out 152334467Speter into a buffer. */ 152434467Speter 152534467Speterstatic void 152634467Spetercheckout_to_buffer (callerdat, data, len) 152734467Speter void *callerdat; 152834467Speter const char *data; 152934467Speter size_t len; 153034467Speter{ 153134467Speter struct buffer *buf = (struct buffer *) callerdat; 153234467Speter 153334467Speter buf_output (buf, data, len); 153434467Speter} 153534467Speter 153634467Speter#endif /* SERVER_SUPPORT */ 153734467Speter 153834467Speter#ifdef SERVER_SUPPORT 153934467Speter 154025839Speter/* This structure is used to pass information between patch_file and 154125839Speter patch_file_write. */ 154225839Speter 154325839Speterstruct patch_file_data 154425839Speter{ 154525839Speter /* File name, for error messages. */ 154625839Speter const char *filename; 154725839Speter /* File to which to write. */ 154825839Speter FILE *fp; 154925839Speter /* Whether to compute the MD5 checksum. */ 155025839Speter int compute_checksum; 155125839Speter /* Data structure for computing the MD5 checksum. */ 155254431Speter struct cvs_MD5Context context; 155325839Speter /* Set if the file has a final newline. */ 155425839Speter int final_nl; 155525839Speter}; 155625839Speter 155725839Speter/* Patch a file. Runs diff. This is only done when running as the 155817721Speter * server. The hope is that the diff will be smaller than the file 155917721Speter * itself. 156017721Speter */ 156117721Speterstatic int 156225839Speterpatch_file (finfo, vers_ts, docheckout, file_info, checksum) 156325839Speter struct file_info *finfo; 156417721Speter Vers_TS *vers_ts; 156517721Speter int *docheckout; 156617721Speter struct stat *file_info; 156717721Speter unsigned char *checksum; 156817721Speter{ 156925839Speter char *backup; 157025839Speter char *file1; 157125839Speter char *file2; 157217721Speter int retval = 0; 157317721Speter int retcode = 0; 157417721Speter int fail; 157517721Speter FILE *e; 157625839Speter struct patch_file_data data; 157717721Speter 157817721Speter *docheckout = 0; 157917721Speter 158025839Speter if (noexec || pipeout || joining ()) 158117721Speter { 158217721Speter *docheckout = 1; 158317721Speter return 0; 158417721Speter } 158517721Speter 158625839Speter /* If this file has been marked as being binary, then never send a 158725839Speter patch. */ 158825839Speter if (strcmp (vers_ts->options, "-kb") == 0) 158925839Speter { 159025839Speter *docheckout = 1; 159125839Speter return 0; 159225839Speter } 159325839Speter 159432788Speter /* First check that the first revision exists. If it has been nuked 159532788Speter by cvs admin -o, then just fall back to checking out entire 159632788Speter revisions. In some sense maybe we don't have to do this; after 159732788Speter all cvs.texinfo says "Make sure that no-one has checked out a 159832788Speter copy of the revision you outdate" but then again, that advice 159932788Speter doesn't really make complete sense, because "cvs admin" operates 160032788Speter on a working directory and so _someone_ will almost always have 160132788Speter _some_ revision checked out. */ 160232788Speter { 160332788Speter char *rev; 160432788Speter 160532788Speter rev = RCS_gettag (finfo->rcs, vers_ts->vn_user, 1, NULL); 160632788Speter if (rev == NULL) 160732788Speter { 160832788Speter *docheckout = 1; 160932788Speter return 0; 161032788Speter } 161132788Speter else 161232788Speter free (rev); 161332788Speter } 161432788Speter 161534467Speter /* If the revision is dead, let checkout_file handle it rather 161634467Speter than duplicating the processing here. */ 161734467Speter if (RCS_isdead (vers_ts->srcfile, vers_ts->vn_rcs)) 161834467Speter { 161934467Speter *docheckout = 1; 162034467Speter return 0; 162134467Speter } 162234467Speter 162325839Speter backup = xmalloc (strlen (finfo->file) 162425839Speter + sizeof (CVSADM) 162525839Speter + sizeof (CVSPREFIX) 162625839Speter + 10); 162725839Speter (void) sprintf (backup, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file); 162825839Speter if (isfile (finfo->file)) 162925839Speter rename_file (finfo->file, backup); 163017721Speter else 163154431Speter { 163254431Speter if (unlink_file (backup) < 0 163354431Speter && !existence_error (errno)) 163454431Speter error (0, errno, "cannot remove %s", backup); 163554431Speter } 163617721Speter 163725839Speter file1 = xmalloc (strlen (finfo->file) 163825839Speter + sizeof (CVSADM) 163925839Speter + sizeof (CVSPREFIX) 164025839Speter + 10); 164125839Speter (void) sprintf (file1, "%s/%s%s-1", CVSADM, CVSPREFIX, finfo->file); 164225839Speter file2 = xmalloc (strlen (finfo->file) 164325839Speter + sizeof (CVSADM) 164425839Speter + sizeof (CVSPREFIX) 164525839Speter + 10); 164625839Speter (void) sprintf (file2, "%s/%s%s-2", CVSADM, CVSPREFIX, finfo->file); 164725839Speter 164817721Speter fail = 0; 164917721Speter 165017721Speter /* We need to check out both revisions first, to see if either one 165117721Speter has a trailing newline. Because of this, we don't use rcsdiff, 165217721Speter but just use diff. */ 165317721Speter 165425839Speter e = CVS_FOPEN (file1, "w"); 165525839Speter if (e == NULL) 165625839Speter error (1, errno, "cannot open %s", file1); 165725839Speter 165825839Speter data.filename = file1; 165925839Speter data.fp = e; 166025839Speter data.final_nl = 0; 166125839Speter data.compute_checksum = 0; 166225839Speter 1663177401Sobrien /* Duplicating the client working file, so use the original sticky options. 1664128269Speter */ 166525839Speter retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL, 1666177401Sobrien vers_ts->vn_user, vers_ts->entdata->tag, 1667177401Sobrien vers_ts->entdata->options, RUN_TTY, 166825839Speter patch_file_write, (void *) &data); 166925839Speter 167025839Speter if (fclose (e) < 0) 167125839Speter error (1, errno, "cannot close %s", file1); 167225839Speter 167325839Speter if (retcode != 0 || ! data.final_nl) 167425839Speter fail = 1; 167525839Speter 167617721Speter if (! fail) 167717721Speter { 167825839Speter e = CVS_FOPEN (file2, "w"); 167925839Speter if (e == NULL) 168025839Speter error (1, errno, "cannot open %s", file2); 168117721Speter 168225839Speter data.filename = file2; 168325839Speter data.fp = e; 168425839Speter data.final_nl = 0; 168525839Speter data.compute_checksum = 1; 168654431Speter cvs_MD5Init (&data.context); 168717721Speter 168825839Speter retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL, 1689128269Speter vers_ts->vn_rcs, vers_ts->tag, 169025839Speter vers_ts->options, RUN_TTY, 169125839Speter patch_file_write, (void *) &data); 169217721Speter 169325839Speter if (fclose (e) < 0) 169425839Speter error (1, errno, "cannot close %s", file2); 169517721Speter 169625839Speter if (retcode != 0 || ! data.final_nl) 169725839Speter fail = 1; 169825839Speter else 169954431Speter cvs_MD5Final (checksum, &data.context); 170017721Speter } 170117721Speter 170217721Speter retcode = 0; 170317721Speter if (! fail) 170417721Speter { 1705175280Sobrien int dargc = 0; 1706175280Sobrien size_t darg_allocated = 0; 1707175280Sobrien char **dargv = NULL; 170817721Speter 170925839Speter /* If the client does not support the Rcs-diff command, we 171025839Speter send a context diff, and the client must invoke patch. 171125839Speter That approach was problematical for various reasons. The 171225839Speter new approach only requires running diff in the server; the 171325839Speter client can handle everything without invoking an external 171425839Speter program. */ 1715175280Sobrien if (!rcs_diff_patches) 171644856Speter /* We use -c, not -u, because that is what CVS has 171744856Speter traditionally used. Kind of a moot point, now that 171844856Speter Rcs-diff is preferred, so there is no point in making 171944856Speter the compatibility issues worse. */ 1720175280Sobrien run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c"); 172125839Speter else 172232788Speter /* Now that diff is librarified, we could be passing -a if 172332788Speter we wanted to. However, it is unclear to me whether we 172432788Speter would want to. Does diff -a, in any significant 172532788Speter percentage of cases, produce patches which are smaller 172632788Speter than the files it is patching? I guess maybe text 172732788Speter files with character sets which diff regards as 172832788Speter 'binary'. Conversely, do they tend to be much larger 172932788Speter in the bad cases? This needs some more 173032788Speter thought/investigation, I suspect. */ 1731175280Sobrien run_add_arg_p (&dargc, &darg_allocated, &dargv, "-n"); 1732175280Sobrien retcode = diff_exec (file1, file2, NULL, NULL, dargc, dargv, 1733175280Sobrien finfo->file); 1734175280Sobrien run_arg_free_p (dargc, dargv); 1735175280Sobrien free (dargv); 173632788Speter 173717721Speter /* A retcode of 0 means no differences. 1 means some differences. */ 173832788Speter if (retcode != 0 173917721Speter && retcode != 1) 174017721Speter { 174117721Speter fail = 1; 174217721Speter } 1743107487Speter } 1744107487Speter 1745107487Speter if (! fail) 1746107487Speter { 1747107487Speter struct stat file2_info; 1748107487Speter 1749107487Speter /* Check to make sure the patch is really shorter */ 1750107487Speter if (CVS_STAT (file2, &file2_info) < 0) 1751107487Speter error (1, errno, "could not stat %s", file2); 1752107487Speter if (CVS_STAT (finfo->file, file_info) < 0) 1753107487Speter error (1, errno, "could not stat %s", finfo->file); 1754107487Speter if (file2_info.st_size <= file_info->st_size) 1755107487Speter fail = 1; 1756107487Speter } 1757107487Speter 1758107487Speter if (! fail) 1759107487Speter { 1760102843Speter# define BINARY "Binary" 1761107487Speter char buf[sizeof BINARY]; 1762107487Speter unsigned int c; 176317721Speter 1764107487Speter /* Check the diff output to make sure patch will be handle it. */ 1765107487Speter e = CVS_FOPEN (finfo->file, "r"); 1766107487Speter if (e == NULL) 1767107487Speter error (1, errno, "could not open diff output file %s", 1768107487Speter finfo->fullname); 1769107487Speter c = fread (buf, 1, sizeof BINARY - 1, e); 1770107487Speter buf[c] = '\0'; 1771107487Speter if (strcmp (buf, BINARY) == 0) 1772107487Speter { 1773107487Speter /* These are binary files. We could use diff -a, but 1774107487Speter patch can't handle that. */ 1775107487Speter fail = 1; 177617721Speter } 1777107487Speter fclose (e); 177817721Speter } 177917721Speter 178017721Speter if (! fail) 178117721Speter { 178217721Speter Vers_TS *xvers_ts; 178317721Speter 1784107487Speter /* Stat the original RCS file, and then adjust it the way 1785107487Speter that RCS_checkout would. FIXME: This is an abstraction 1786107487Speter violation. */ 1787107487Speter if (CVS_STAT (vers_ts->srcfile->path, file_info) < 0) 1788107487Speter error (1, errno, "could not stat %s", vers_ts->srcfile->path); 1789107487Speter if (chmod (finfo->file, 1790107487Speter file_info->st_mode & ~(S_IWRITE | S_IWGRP | S_IWOTH)) 1791107487Speter < 0) 1792107487Speter error (0, errno, "cannot change mode of file %s", finfo->file); 1793107487Speter if (cvswrite 1794107487Speter && !fileattr_get (finfo->file, "_watched")) 1795107487Speter xchmod (finfo->file, 1); 1796107487Speter 179717721Speter /* This stuff is just copied blindly from checkout_file. I 179817721Speter don't really know what it does. */ 179925839Speter xvers_ts = Version_TS (finfo, options, tag, date, 180025839Speter force_tag_match, 0); 180117721Speter if (strcmp (xvers_ts->options, "-V4") == 0) 180217721Speter xvers_ts->options[0] = '\0'; 180317721Speter 180425839Speter Register (finfo->entries, finfo->file, xvers_ts->vn_rcs, 180517721Speter xvers_ts->ts_user, xvers_ts->options, 180617721Speter xvers_ts->tag, xvers_ts->date, NULL); 180717721Speter 180825839Speter if (CVS_STAT (finfo->file, file_info) < 0) 180925839Speter error (1, errno, "could not stat %s", finfo->file); 181017721Speter 1811128269Speter /* If this is really Update and not Checkout, record history. */ 1812128269Speter if (strcmp (cvs_cmd_name, "update") == 0) 1813128269Speter history_write ('P', finfo->update_dir, xvers_ts->vn_rcs, 1814128269Speter finfo->file, finfo->repository); 181517721Speter 181617721Speter freevers_ts (&xvers_ts); 181717721Speter 181817721Speter if (!really_quiet) 181917721Speter { 182032788Speter write_letter (finfo, 'P'); 182117721Speter } 182217721Speter } 182317721Speter else 182417721Speter { 182517721Speter int old_errno = errno; /* save errno value over the rename */ 182617721Speter 182717721Speter if (isfile (backup)) 182825839Speter rename_file (backup, finfo->file); 182917721Speter 183017721Speter if (retcode != 0 && retcode != 1) 183117721Speter error (retcode == -1 ? 1 : 0, retcode == -1 ? old_errno : 0, 183225839Speter "could not diff %s", finfo->fullname); 183317721Speter 183417721Speter *docheckout = 1; 183517721Speter retval = retcode; 183617721Speter } 183717721Speter 183854431Speter if (unlink_file (backup) < 0 183954431Speter && !existence_error (errno)) 184054431Speter error (0, errno, "cannot remove %s", backup); 184154431Speter if (unlink_file (file1) < 0 184254431Speter && !existence_error (errno)) 184354431Speter error (0, errno, "cannot remove %s", file1); 184454431Speter if (unlink_file (file2) < 0 184554431Speter && !existence_error (errno)) 184654431Speter error (0, errno, "cannot remove %s", file2); 184717721Speter 184825839Speter free (backup); 184925839Speter free (file1); 185025839Speter free (file2); 1851130307Speter return retval; 185217721Speter} 185317721Speter 1854130307Speter 1855130307Speter 185625839Speter/* Write data to a file. Record whether the last byte written was a 185725839Speter newline. Optionally compute a checksum. This is called by 185825839Speter patch_file via RCS_checkout. */ 185925839Speter 186025839Speterstatic void 186125839Speterpatch_file_write (callerdat, buffer, len) 186225839Speter void *callerdat; 186325839Speter const char *buffer; 186425839Speter size_t len; 186525839Speter{ 186625839Speter struct patch_file_data *data = (struct patch_file_data *) callerdat; 186725839Speter 186825839Speter if (fwrite (buffer, 1, len, data->fp) != len) 186925839Speter error (1, errno, "cannot write %s", data->filename); 187025839Speter 187125839Speter data->final_nl = (buffer[len - 1] == '\n'); 187225839Speter 187325839Speter if (data->compute_checksum) 187454431Speter cvs_MD5Update (&data->context, (unsigned char *) buffer, len); 187525839Speter} 187625839Speter 187725839Speter#endif /* SERVER_SUPPORT */ 187825839Speter 187917721Speter/* 188017721Speter * Several of the types we process only print a bit of information consisting 188117721Speter * of a single letter and the name. 188217721Speter */ 1883128269Spetervoid 188432788Speterwrite_letter (finfo, letter) 188532788Speter struct file_info *finfo; 188617721Speter int letter; 188717721Speter{ 188817721Speter if (!really_quiet) 188917721Speter { 189032788Speter char *tag = NULL; 189132788Speter /* Big enough for "+updated" or any of its ilk. */ 189232788Speter char buf[80]; 189332788Speter 189432788Speter switch (letter) 189532788Speter { 189632788Speter case 'U': 189732788Speter tag = "updated"; 189832788Speter break; 189932788Speter default: 190032788Speter /* We don't yet support tagged output except for "U". */ 190132788Speter break; 190232788Speter } 190332788Speter 190432788Speter if (tag != NULL) 190532788Speter { 190632788Speter sprintf (buf, "+%s", tag); 190732788Speter cvs_output_tagged (buf, NULL); 190832788Speter } 190917721Speter buf[0] = letter; 191017721Speter buf[1] = ' '; 191132788Speter buf[2] = '\0'; 191232788Speter cvs_output_tagged ("text", buf); 191332788Speter cvs_output_tagged ("fname", finfo->fullname); 191432788Speter cvs_output_tagged ("newline", NULL); 191532788Speter if (tag != NULL) 191617721Speter { 191732788Speter sprintf (buf, "-%s", tag); 191832788Speter cvs_output_tagged (buf, NULL); 191917721Speter } 192017721Speter } 192132788Speter return; 192217721Speter} 192317721Speter 1924128269Speter 1925128269Speter 1926175280Sobrien/* Reregister a file after a merge. */ 1927175280Sobrienstatic void 1928175280SobrienRegisterMerge PROTO((struct file_info *finfo, Vers_TS *vers, 1929175280Sobrien const char *backup, int has_conflicts)); 1930175280Sobrienstatic void 1931175280SobrienRegisterMerge (finfo, vers, backup, has_conflicts) 1932175280Sobrien struct file_info *finfo; 1933175280Sobrien Vers_TS *vers; 1934175280Sobrien const char *backup; 1935175280Sobrien int has_conflicts; 1936175280Sobrien{ 1937175280Sobrien /* This file is the result of a merge, which means that it has 1938175280Sobrien been modified. We use a special timestamp string which will 1939175280Sobrien not compare equal to any actual timestamp. */ 1940175280Sobrien char *cp = NULL; 1941175280Sobrien 1942175280Sobrien if (has_conflicts) 1943175280Sobrien { 1944175280Sobrien time (&last_register_time); 1945175280Sobrien cp = time_stamp (finfo->file); 1946175280Sobrien } 1947175280Sobrien Register (finfo->entries, finfo->file, vers->vn_rcs ? vers->vn_rcs : "0", 1948175280Sobrien "Result of merge", vers->options, vers->tag, vers->date, cp); 1949175280Sobrien if (cp) 1950175280Sobrien free (cp); 1951175280Sobrien 1952175280Sobrien#ifdef SERVER_SUPPORT 1953175280Sobrien /* Send the new contents of the file before the message. If we 1954175280Sobrien wanted to be totally correct, we would have the client write 1955175280Sobrien the message only after the file has safely been written. */ 1956175280Sobrien if (server_active) 1957175280Sobrien { 1958175280Sobrien server_copy_file (finfo->file, finfo->update_dir, finfo->repository, 1959175280Sobrien backup); 1960175280Sobrien server_updated (finfo, vers, SERVER_MERGED, (mode_t) -1, NULL, NULL); 1961175280Sobrien } 1962175280Sobrien#endif 1963175280Sobrien} 1964175280Sobrien 1965175280Sobrien 1966175280Sobrien 196717721Speter/* 196817721Speter * Do all the magic associated with a file which needs to be merged 196917721Speter */ 197017721Speterstatic int 197125839Spetermerge_file (finfo, vers) 197225839Speter struct file_info *finfo; 197317721Speter Vers_TS *vers; 197417721Speter{ 197525839Speter char *backup; 197617721Speter int status; 197717721Speter int retcode = 0; 197825839Speter int retval; 197917721Speter 1980175280Sobrien assert (vers->vn_user); 1981175280Sobrien 198217721Speter /* 198317721Speter * The users currently modified file is moved to a backup file name 198417721Speter * ".#filename.version", so that it will stay around for a few days 198517721Speter * before being automatically removed by some cron daemon. The "version" 198617721Speter * is the version of the file that the user was most up-to-date with 198717721Speter * before the merge. 198817721Speter */ 198925839Speter backup = xmalloc (strlen (finfo->file) 199025839Speter + strlen (vers->vn_user) 199125839Speter + sizeof (BAKPREFIX) 199225839Speter + 10); 199325839Speter (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user); 199417721Speter 199554431Speter if (unlink_file (backup) && !existence_error (errno)) 199654431Speter error (0, errno, "unable to remove %s", backup); 199725839Speter copy_file (finfo->file, backup); 199825839Speter xchmod (finfo->file, 1); 199917721Speter 200032788Speter if (strcmp (vers->options, "-kb") == 0 200134467Speter || wrap_merge_is_copy (finfo->file) 200234467Speter || special_file_mismatch (finfo, NULL, vers->vn_rcs)) 200325839Speter { 200434467Speter /* For binary files, a merge is always a conflict. Same for 200534467Speter files whose permissions or linkage do not match. We give the 200625839Speter user the two files, and let them resolve it. It is possible 200725839Speter that we should require a "touch foo" or similar step before 200825839Speter we allow a checkin. */ 200934467Speter 201034467Speter /* TODO: it may not always be necessary to regard a permission 201134467Speter mismatch as a conflict. The working file and the RCS file 201234467Speter have a common ancestor `A'; if the working file's permissions 201334467Speter match A's, then it's probably safe to overwrite them with the 201434467Speter RCS permissions. Only if the working file, the RCS file, and 201534467Speter A all disagree should this be considered a conflict. But more 201634467Speter thought needs to go into this, and in the meantime it is safe 201734467Speter to treat any such mismatch as an automatic conflict. -twp */ 201834467Speter 2019175280Sobrien retcode = RCS_checkout (finfo->rcs, finfo->file, 2020175280Sobrien vers->vn_rcs, vers->tag, 2021175280Sobrien vers->options, NULL, NULL, NULL); 2022175280Sobrien if (retcode) 2023175280Sobrien { 2024175280Sobrien error (0, 0, "failed to check out `%s' file", finfo->fullname); 2025175280Sobrien error (0, 0, "restoring `%s' from backup file `%s'", 2026175280Sobrien finfo->fullname, backup); 2027175280Sobrien rename_file (backup, finfo->file); 2028175280Sobrien retval = 1; 2029175280Sobrien goto out; 2030175280Sobrien } 2031175280Sobrien xchmod (finfo->file, 1); 203234467Speter 2033175280Sobrien RegisterMerge (finfo, vers, backup, 1); 203434467Speter 203532788Speter /* Is there a better term than "nonmergeable file"? What we 203632788Speter really mean is, not something that CVS cannot or does not 203732788Speter want to merge (there might be an external manual or 203832788Speter automatic merge process). */ 203932788Speter error (0, 0, "nonmergeable file needs merge"); 204025839Speter error (0, 0, "revision %s from repository is now in %s", 204125839Speter vers->vn_rcs, finfo->fullname); 204225839Speter error (0, 0, "file from working directory is now in %s", backup); 204332788Speter write_letter (finfo, 'C'); 204425839Speter 204525839Speter history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file, 204625839Speter finfo->repository); 204725839Speter retval = 0; 204825839Speter goto out; 204925839Speter } 205025839Speter 2051128269Speter status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file, 2052128269Speter vers->options, vers->vn_user, vers->vn_rcs); 205317721Speter if (status != 0 && status != 1) 205417721Speter { 205517721Speter error (0, status == -1 ? errno : 0, 205625839Speter "could not merge revision %s of %s", vers->vn_user, finfo->fullname); 205717721Speter error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", 205825839Speter finfo->fullname, backup); 205925839Speter rename_file (backup, finfo->file); 206025839Speter retval = 1; 206125839Speter goto out; 206217721Speter } 206317721Speter 206417721Speter if (strcmp (vers->options, "-V4") == 0) 206517721Speter vers->options[0] = '\0'; 206644856Speter 206717721Speter /* fix up the vers structure, in case it is used by join */ 206817721Speter if (join_rev1) 206917721Speter { 2070128269Speter /* FIXME: Throwing away the original revision info is almost 2071128269Speter certainly wrong -- what if join_rev1 is "BASE"? */ 207217721Speter if (vers->vn_user != NULL) 207317721Speter free (vers->vn_user); 207417721Speter vers->vn_user = xstrdup (vers->vn_rcs); 207517721Speter } 207617721Speter 2077175280Sobrien RegisterMerge (finfo, vers, backup, status); 207817721Speter 207954431Speter /* FIXME: the noexec case is broken. RCS_merge could be doing the 208054431Speter xcmp on the temporary files without much hassle, I think. */ 208125839Speter if (!noexec && !xcmp (backup, finfo->file)) 208217721Speter { 208354431Speter cvs_output (finfo->fullname, 0); 208454431Speter cvs_output (" already contains the differences between ", 0); 208554431Speter cvs_output (vers->vn_user, 0); 208654431Speter cvs_output (" and ", 0); 208754431Speter cvs_output (vers->vn_rcs, 0); 208854431Speter cvs_output ("\n", 1); 208954431Speter 209025839Speter history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file, 209125839Speter finfo->repository); 209225839Speter retval = 0; 209325839Speter goto out; 209417721Speter } 209517721Speter 209617721Speter if (status == 1) 209717721Speter { 209854431Speter error (0, 0, "conflicts found in %s", finfo->fullname); 209917721Speter 210032788Speter write_letter (finfo, 'C'); 210117721Speter 2102128269Speter history_write ('C', finfo->update_dir, vers->vn_rcs, finfo->file, 2103128269Speter finfo->repository); 210417721Speter 210517721Speter } 210617721Speter else if (retcode == -1) 210717721Speter { 210825839Speter error (1, errno, "fork failed while examining update of %s", 210925839Speter finfo->fullname); 211017721Speter } 211117721Speter else 211217721Speter { 211332788Speter write_letter (finfo, 'M'); 211425839Speter history_write ('G', finfo->update_dir, vers->vn_rcs, finfo->file, 211525839Speter finfo->repository); 211617721Speter } 211725839Speter retval = 0; 211825839Speter out: 211925839Speter free (backup); 212025839Speter return retval; 212117721Speter} 212217721Speter 2123128269Speter 2124128269Speter 212517721Speter/* 212617721Speter * Do all the magic associated with a file which needs to be joined 2127128269Speter * (reached via the -j option to checkout or update). 2128128269Speter * 2129128269Speter * INPUTS 2130128269Speter * finfo File information about the destination file. 2131128269Speter * vers The Vers_TS structure for finfo. 2132128269Speter * 2133128269Speter * GLOBALS 2134128269Speter * join_rev1 From the command line. 2135128269Speter * join_rev2 From the command line. 2136128269Speter * server_active Natch. 2137128269Speter * 2138128269Speter * ASSUMPTIONS 2139128269Speter * 1. Is not called in client mode. 214017721Speter */ 214117721Speterstatic void 214225839Speterjoin_file (finfo, vers) 214325839Speter struct file_info *finfo; 214417721Speter Vers_TS *vers; 214517721Speter{ 214625839Speter char *backup; 214766528Speter char *t_options; 214817721Speter int status; 214917721Speter 215017721Speter char *rev1; 215117721Speter char *rev2; 215217721Speter char *jrev1; 215317721Speter char *jrev2; 215417721Speter char *jdate1; 215517721Speter char *jdate2; 215617721Speter 215781407Speter if (trace) 215881407Speter fprintf (stderr, "%s-> join_file(%s, %s%s%s%s, %s, %s)\n", 215981407Speter CLIENT_SERVER_STR, 216081407Speter finfo->file, 216181407Speter vers->tag ? vers->tag : "", 216281407Speter vers->tag ? " (" : "", 216381407Speter vers->vn_rcs ? vers->vn_rcs : "", 216481407Speter vers->tag ? ")" : "", 216581407Speter join_rev1 ? join_rev1 : "", 216681407Speter join_rev2 ? join_rev2 : ""); 216781407Speter 216817721Speter jrev1 = join_rev1; 216917721Speter jrev2 = join_rev2; 217017721Speter jdate1 = date_rev1; 217117721Speter jdate2 = date_rev2; 217217721Speter 217325839Speter /* Determine if we need to do anything at all. */ 217417721Speter if (vers->srcfile == NULL || 217517721Speter vers->srcfile->path == NULL) 217617721Speter { 217717721Speter return; 217817721Speter } 217917721Speter 218025839Speter /* If only one join revision is specified, it becomes the second 218125839Speter revision. */ 218217721Speter if (jrev2 == NULL) 218317721Speter { 218417721Speter jrev2 = jrev1; 218517721Speter jrev1 = NULL; 218617721Speter jdate2 = jdate1; 218717721Speter jdate1 = NULL; 218817721Speter } 218917721Speter 2190128269Speter /* FIXME: Need to handle "BASE" for jrev1 and/or jrev2. Note caveat 2191128269Speter below about vn_user. */ 2192128269Speter 219325839Speter /* Convert the second revision, walking branches and dates. */ 219425839Speter rev2 = RCS_getversion (vers->srcfile, jrev2, jdate2, 1, (int *) NULL); 219525839Speter 219625839Speter /* If this is a merge of two revisions, get the first revision. 219725839Speter If only one join tag was specified, then the first revision is 219825839Speter the greatest common ancestor of the second revision and the 219925839Speter working file. */ 220025839Speter if (jrev1 != NULL) 220125839Speter rev1 = RCS_getversion (vers->srcfile, jrev1, jdate1, 1, (int *) NULL); 220225839Speter else 220317721Speter { 220425839Speter /* Note that we use vn_rcs here, since vn_user may contain a 220525839Speter special string such as "-nn". */ 220625839Speter if (vers->vn_rcs == NULL) 220725839Speter rev1 = NULL; 220825839Speter else if (rev2 == NULL) 220925839Speter { 221025839Speter /* This means that the file never existed on the branch. 221125839Speter It does not mean that the file was removed on the 221225839Speter branch: that case is represented by a dead rev2. If 221325839Speter the file never existed on the branch, then we have 221425839Speter nothing to merge, so we just return. */ 221525839Speter return; 221625839Speter } 221717721Speter else 221825839Speter rev1 = gca (vers->vn_rcs, rev2); 221917721Speter } 222017721Speter 222125839Speter /* Handle a nonexistent or dead merge target. */ 222225839Speter if (rev2 == NULL || RCS_isdead (vers->srcfile, rev2)) 222317721Speter { 222425839Speter char *mrev; 2225177401Sobrien short conflict = 0; 222617721Speter 222725839Speter if (rev2 != NULL) 222825839Speter free (rev2); 222917721Speter 223025839Speter /* If the first revision doesn't exist either, then there is 223125839Speter no change between the two revisions, so we don't do 223225839Speter anything. */ 223325839Speter if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1)) 223417721Speter { 223525839Speter if (rev1 != NULL) 223625839Speter free (rev1); 223725839Speter return; 223825839Speter } 223925839Speter 224025839Speter /* If we are merging two revisions, then the file was removed 224125839Speter between the first revision and the second one. In this 224225839Speter case we want to mark the file for removal. 224325839Speter 224425839Speter If we are merging one revision, then the file has been 224525839Speter removed between the greatest common ancestor and the merge 224625839Speter revision. From the perspective of the branch on to which 224725839Speter we ar emerging, which may be the trunk, either 1) the file 224825839Speter does not currently exist on the target, or 2) the file has 224925839Speter not been modified on the target branch since the greatest 225025839Speter common ancestor, or 3) the file has been modified on the 225125839Speter target branch since the greatest common ancestor. In case 225225839Speter 1 there is nothing to do. In case 2 we mark the file for 225325839Speter removal. In case 3 we have a conflict. 225425839Speter 225525839Speter Note that the handling is slightly different depending upon 225625839Speter whether one or two join targets were specified. If two 225725839Speter join targets were specified, we don't check whether the 225825839Speter file was modified since a given point. My reasoning is 225925839Speter that if you ask for an explicit merge between two tags, 226025839Speter then you want to merge in whatever was changed between 226125839Speter those two tags. If a file was removed between the two 226225839Speter tags, then you want it to be removed. However, if you ask 226325839Speter for a merge of a branch, then you want to merge in all 226425839Speter changes which were made on the branch. If a file was 226525839Speter removed on the branch, that is a change to the file. If 226625839Speter the file was also changed on the main line, then that is 226725839Speter also a change. These two changes--the file removal and the 226825839Speter modification--must be merged. This is a conflict. */ 226925839Speter 227025839Speter /* If the user file is dead, or does not exist, or has been 227125839Speter marked for removal, then there is nothing to do. */ 227225839Speter if (vers->vn_user == NULL 227325839Speter || vers->vn_user[0] == '-' 227425839Speter || RCS_isdead (vers->srcfile, vers->vn_user)) 227525839Speter { 2276177401Sobrien free (rev1); 227725839Speter return; 227825839Speter } 227925839Speter 228025839Speter /* If the user file has been marked for addition, or has been 228125839Speter locally modified, then we have a conflict which we can not 228225839Speter resolve. No_Difference will already have been called in 228325839Speter this case, so comparing the timestamps is sufficient to 228425839Speter determine whether the file is locally modified. */ 2285177401Sobrien if (/* may have changed on destination branch */ 2286177401Sobrien /* file added locally */ 2287177401Sobrien !strcmp (vers->vn_user, "0") 2288177401Sobrien || /* destination branch modified in repository */ 2289177401Sobrien strcmp (rev1, vers->vn_user) 2290177401Sobrien || /* locally modified */ 2291177401Sobrien vers->ts_user && strcmp (vers->ts_user, vers->ts_rcs)) 229225839Speter { 2293177401Sobrien /* The removal should happen if either the file has never changed 2294177401Sobrien * on the destination or the file has changed to be identical to 2295177401Sobrien * the first join revision. 2296177401Sobrien * 2297177401Sobrien * ------R-----------D 2298177401Sobrien * | 2299177401Sobrien * \----J1---J2-----S 2300177401Sobrien * 2301177401Sobrien * So: 2302177401Sobrien * 2303177401Sobrien * J2 is dead. 2304177401Sobrien * D is destination. 2305177401Sobrien * R is source branch root/GCA. 2306177401Sobrien * if J1 == D removal should happen 2307177401Sobrien * if D == R removal should happen 2308177401Sobrien * otherwise, fail. 2309177401Sobrien * 2310177401Sobrien * (In the source, J2 = REV2, D = user file (potentially VN_USER), 2311177401Sobrien * R = GCA computed below) 2312177401Sobrien */ 2313177401Sobrien char *gca_rev1 = gca (rev1, vers->vn_user); 2314177401Sobrien#ifdef SERVER_SUPPORT 2315177401Sobrien if (server_active && !isreadable (finfo->file)) 2316177401Sobrien { 2317177401Sobrien int retcode; 2318177401Sobrien /* The file is up to date. Need to check out the current 2319177401Sobrien * contents. 2320177401Sobrien */ 2321177401Sobrien /* FIXME - see the FIXME comment above the call to RCS_checkout 2322177401Sobrien * in the patch_file function. 2323177401Sobrien */ 2324177401Sobrien retcode = RCS_checkout (vers->srcfile, finfo->file, 2325177401Sobrien vers->vn_user, vers->tag, 2326177401Sobrien NULL, RUN_TTY, NULL, NULL); 2327177401Sobrien if (retcode) 2328177401Sobrien error (1, 0, 2329177401Sobrien "failed to check out %s file", finfo->fullname); 2330177401Sobrien } 2331177401Sobrien#endif 2332177401Sobrien if (/* genuinely changed on destination branch */ 2333177401Sobrien RCS_cmp_file (vers->srcfile, gca_rev1, NULL, 2334177401Sobrien NULL, vers->options, finfo->file) 2335177401Sobrien && /* genuinely different from REV1 */ 2336177401Sobrien RCS_cmp_file (vers->srcfile, rev1, NULL, 2337177401Sobrien NULL, vers->options, finfo->file)) 2338177401Sobrien conflict = 1; 2339177401Sobrien } 234025839Speter 2341177401Sobrien free (rev1); 234225839Speter 2343177401Sobrien if (conflict) 2344177401Sobrien { 2345177401Sobrien char *cp; 234625839Speter 2347177401Sobrien if (jdate2) 234825839Speter error (0, 0, 2349177401Sobrien "file %s has been removed in revision %s as of %s, but the destination is incompatibly modified", 235025839Speter finfo->fullname, jrev2, jdate2); 235125839Speter else 235225839Speter error (0, 0, 2353177401Sobrien "file %s has been removed in revision %s, but the destination is incompatibly modified", 235425839Speter finfo->fullname, jrev2); 235525839Speter 2356177401Sobrien /* Register the conflict with the client. */ 235725839Speter 2358177401Sobrien /* FIXME: vers->ts_user should always be set here but sometimes 2359177401Sobrien * isn't, namely when checkout_file() has just created the file, 2360177401Sobrien * but simply setting it in checkout_file() appears to cause other 2361177401Sobrien * problems. 2362177401Sobrien */ 2363177401Sobrien if (isfile (finfo->file)) 2364177401Sobrien cp = time_stamp (finfo->file); 2365177401Sobrien else 2366177401Sobrien cp = xstrdup (vers->ts_user); 236725839Speter 2368177401Sobrien Register (finfo->entries, finfo->file, vers->vn_user, 2369177401Sobrien "Result of merge", vers->options, vers->tag, vers->date, 2370177401Sobrien cp); 2371177401Sobrien write_letter (finfo, 'C'); 2372177401Sobrien free (cp); 2373177401Sobrien 2374177401Sobrien#ifdef SERVER_SUPPORT 2375177401Sobrien /* Abuse server_checked_in() to send the updated entry without 2376177401Sobrien * needing to update the file. 2377177401Sobrien */ 2378177401Sobrien if (server_active) 2379177401Sobrien server_checked_in (finfo->file, finfo->update_dir, 2380177401Sobrien finfo->repository); 2381177401Sobrien#endif 2382177401Sobrien 238325839Speter return; 238425839Speter } 238525839Speter 238625839Speter /* The user file exists and has not been modified. Mark it 238725839Speter for removal. FIXME: If we are doing a checkout, this has 238825839Speter the effect of first checking out the file, and then 238925839Speter removing it. It would be better to just register the 239081407Speter removal. 239181407Speter 239281407Speter The same goes for a removal then an add. e.g. 239381407Speter cvs up -rbr -jbr2 could remove and readd the same file 239481407Speter */ 239581407Speter /* save the rev since server_updated might invalidate it */ 239681407Speter mrev = xmalloc (strlen (vers->vn_user) + 2); 239781407Speter sprintf (mrev, "-%s", vers->vn_user); 239825839Speter#ifdef SERVER_SUPPORT 239925839Speter if (server_active) 240025839Speter { 240125839Speter server_scratch (finfo->file); 240234467Speter server_updated (finfo, vers, SERVER_UPDATED, (mode_t) -1, 240334467Speter (unsigned char *) NULL, (struct buffer *) NULL); 240425839Speter } 240525839Speter#endif 240625839Speter Register (finfo->entries, finfo->file, mrev, vers->ts_rcs, 240725839Speter vers->options, vers->tag, vers->date, vers->ts_conflict); 240825839Speter free (mrev); 240925839Speter /* We need to check existence_error here because if we are 241025839Speter running as the server, and the file is up to date in the 241125839Speter working directory, the client will not have sent us a copy. */ 241225839Speter if (unlink_file (finfo->file) < 0 && ! existence_error (errno)) 241325839Speter error (0, errno, "cannot remove file %s", finfo->fullname); 241425839Speter#ifdef SERVER_SUPPORT 241525839Speter if (server_active) 241625839Speter server_checked_in (finfo->file, finfo->update_dir, 241725839Speter finfo->repository); 241825839Speter#endif 241925839Speter if (! really_quiet) 242025839Speter error (0, 0, "scheduling %s for removal", finfo->fullname); 242125839Speter 242217721Speter return; 242317721Speter } 242417721Speter 2425128269Speter /* If the two merge revisions are the same, then there is nothing 2426128269Speter * to do. This needs to be checked before the rev2 == up-to-date base 2427128269Speter * revision check tha comes next. Otherwise, rev1 can == rev2 and get an 2428128269Speter * "already contains the changes between <rev1> and <rev1>" message. 2429128269Speter */ 2430128269Speter if (rev1 && strcmp (rev1, rev2) == 0) 243117721Speter { 2432128269Speter free (rev1); 2433128269Speter free (rev2); 2434128269Speter return; 2435128269Speter } 2436128269Speter 2437128269Speter /* If we know that the user file is up-to-date, then it becomes an 2438128269Speter * optimization to skip the merge when rev2 is the same as the base 2439128269Speter * revision. i.e. we know that diff3(file2,file1,file2) will produce 2440128269Speter * file2. 2441128269Speter */ 2442128269Speter if (vers->vn_user != NULL && vers->ts_user != NULL 2443128269Speter && strcmp (vers->ts_user, vers->ts_rcs) == 0 2444128269Speter && strcmp (rev2, vers->vn_user) == 0) 2445128269Speter { 2446128269Speter if (!really_quiet) 2447128269Speter { 2448128269Speter cvs_output (finfo->fullname, 0); 2449128269Speter cvs_output (" already contains the differences between ", 0); 2450128269Speter cvs_output (rev1 ? rev1 : "creation", 0); 2451128269Speter cvs_output (" and ", 0); 2452128269Speter cvs_output (rev2, 0); 2453128269Speter cvs_output ("\n", 1); 2454128269Speter } 2455128269Speter 245625839Speter if (rev1 != NULL) 245725839Speter free (rev1); 245817721Speter free (rev2); 2459128269Speter 246017721Speter return; 246117721Speter } 246217721Speter 246325839Speter /* If rev1 is dead or does not exist, then the file was added 246425839Speter between rev1 and rev2. */ 246525839Speter if (rev1 == NULL || RCS_isdead (vers->srcfile, rev1)) 246617721Speter { 246725839Speter if (rev1 != NULL) 246825839Speter free (rev1); 246925839Speter free (rev2); 247017721Speter 247125839Speter /* If the file does not exist in the working directory, then 247225839Speter we can just check out the new revision and mark it for 247325839Speter addition. */ 247425839Speter if (vers->vn_user == NULL) 247517721Speter { 247681407Speter char *saved_options = options; 247725839Speter Vers_TS *xvers; 247817721Speter 247925839Speter xvers = Version_TS (finfo, vers->options, jrev2, jdate2, 1, 0); 248017721Speter 248166528Speter /* Reset any keyword expansion option. Otherwise, when a 248266528Speter command like `cvs update -kk -jT1 -jT2' creates a new file 248366528Speter (because a file had the T2 tag, but not T1), the subsequent 248466528Speter commit of that just-added file effectively would set the 248566528Speter admin `-kk' option for that file in the repository. */ 248666528Speter options = NULL; 248766528Speter 248825839Speter /* FIXME: If checkout_file fails, we should arrange to 248925839Speter return a non-zero exit status. */ 249034467Speter status = checkout_file (finfo, xvers, 1, 0, 1); 249181407Speter options = saved_options; 249225839Speter 249325839Speter freevers_ts (&xvers); 249425839Speter 249517721Speter return; 249617721Speter } 249725839Speter 249825839Speter /* The file currently exists in the working directory, so we 249925839Speter have a conflict which we can not resolve. Note that this 250025839Speter is true even if the file is marked for addition or removal. */ 250125839Speter 250225839Speter if (jdate2 != NULL) 250325839Speter error (0, 0, 250425839Speter "file %s exists, but has been added in revision %s as of %s", 250525839Speter finfo->fullname, jrev2, jdate2); 250625839Speter else 250725839Speter error (0, 0, 250825839Speter "file %s exists, but has been added in revision %s", 250925839Speter finfo->fullname, jrev2); 251025839Speter 251125839Speter return; 251217721Speter } 251325839Speter 251425839Speter /* If there is no working file, then we can't do the merge. */ 2515128269Speter if (vers->vn_user == NULL || vers->vn_user[0] == '-') 251625839Speter { 251725839Speter free (rev1); 251825839Speter free (rev2); 251917721Speter 252025839Speter if (jdate2 != NULL) 252125839Speter error (0, 0, 252266528Speter "file %s does not exist, but is present in revision %s as of %s", 252325839Speter finfo->fullname, jrev2, jdate2); 252417721Speter else 252525839Speter error (0, 0, 252666528Speter "file %s does not exist, but is present in revision %s", 252725839Speter finfo->fullname, jrev2); 252825839Speter 252925839Speter /* FIXME: Should we arrange to return a non-zero exit status? */ 253025839Speter 253125839Speter return; 253217721Speter } 253317721Speter 253417721Speter#ifdef SERVER_SUPPORT 253525839Speter if (server_active && !isreadable (finfo->file)) 253617721Speter { 253717721Speter int retcode; 253817721Speter /* The file is up to date. Need to check out the current contents. */ 2539128269Speter /* FIXME - see the FIXME comment above the call to RCS_checkout in the 2540128269Speter * patch_file function. 2541128269Speter */ 254225839Speter retcode = RCS_checkout (vers->srcfile, finfo->file, 2543128269Speter vers->vn_user, vers->tag, 254425839Speter (char *) NULL, RUN_TTY, 254525839Speter (RCSCHECKOUTPROC) NULL, (void *) NULL); 254617721Speter if (retcode != 0) 254734467Speter error (1, 0, 254825839Speter "failed to check out %s file", finfo->fullname); 254917721Speter } 255017721Speter#endif 255132788Speter 255217721Speter /* 255317721Speter * The users currently modified file is moved to a backup file name 255417721Speter * ".#filename.version", so that it will stay around for a few days 255517721Speter * before being automatically removed by some cron daemon. The "version" 255617721Speter * is the version of the file that the user was most up-to-date with 255717721Speter * before the merge. 255817721Speter */ 255925839Speter backup = xmalloc (strlen (finfo->file) 256025839Speter + strlen (vers->vn_user) 256125839Speter + sizeof (BAKPREFIX) 256225839Speter + 10); 256325839Speter (void) sprintf (backup, "%s%s.%s", BAKPREFIX, finfo->file, vers->vn_user); 256417721Speter 256554431Speter if (unlink_file (backup) < 0 256654431Speter && !existence_error (errno)) 256754431Speter error (0, errno, "cannot remove %s", backup); 256825839Speter copy_file (finfo->file, backup); 256925839Speter xchmod (finfo->file, 1); 257017721Speter 257166528Speter t_options = vers->options; 257217721Speter#if 0 257366528Speter if (*t_options == '\0') 257466528Speter t_options = "-kk"; /* to ignore keyword expansions */ 257517721Speter#endif 257617721Speter 257732788Speter /* If the source of the merge is the same as the working file 257832788Speter revision, then we can just RCS_checkout the target (no merging 257932788Speter as such). In the text file case, this is probably quite 258032788Speter similar to the RCS_merge, but in the binary file case, 258132788Speter RCS_merge gives all kinds of trouble. */ 258232788Speter if (vers->vn_user != NULL 258332788Speter && strcmp (rev1, vers->vn_user) == 0 258432788Speter /* See comments above about how No_Difference has already been 258532788Speter called. */ 258632788Speter && vers->ts_user != NULL 258732788Speter && strcmp (vers->ts_user, vers->ts_rcs) == 0 258832788Speter 2589128269Speter /* Avoid this in the text file case. See below for why. 2590128269Speter */ 259166528Speter && (strcmp (t_options, "-kb") == 0 259232788Speter || wrap_merge_is_copy (finfo->file))) 259332788Speter { 2594128269Speter /* FIXME: Verify my comment below: 2595128269Speter * 2596128269Speter * RCS_merge does nothing with keywords. It merges the changes between 2597128269Speter * two revisions without expanding the keywords (it might expand in 2598128269Speter * -kk mode before computing the diff between rev1 and rev2 - I'm not 2599128269Speter * sure). In other words, the keyword lines in the current work file 2600128269Speter * get left alone. 2601128269Speter * 2602128269Speter * Therfore, checking out the destination revision (rev2) is probably 2603128269Speter * incorrect in the text case since we should see the keywords that were 2604128269Speter * substituted into the original file at the time it was checked out 2605128269Speter * and not the keywords from rev2. 2606128269Speter * 2607128269Speter * Also, it is safe to pass in NULL for nametag since we know no 2608128269Speter * substitution is happening during the binary mode checkout. 2609128269Speter */ 2610128269Speter if (RCS_checkout ( finfo->rcs, finfo->file, rev2, (char *)NULL, t_options, 2611128269Speter RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0 ) 261232788Speter status = 2; 261332788Speter else 261432788Speter status = 0; 261532788Speter 261632788Speter /* OK, this is really stupid. RCS_checkout carefully removes 261732788Speter write permissions, and we carefully put them back. But 261832788Speter until someone gets around to fixing it, that seems like the 261932788Speter easiest way to get what would seem to be the right mode. 262032788Speter I don't check CVSWRITE or _watched; I haven't thought about 262132788Speter that in great detail, but it seems like a watched file should 262232788Speter be checked out (writable) after a merge. */ 262332788Speter xchmod (finfo->file, 1); 262432788Speter 262532788Speter /* Traditionally, the text file case prints a whole bunch of 262632788Speter scary looking and verbose output which fails to tell the user 262732788Speter what is really going on (it gives them rev1 and rev2 but doesn't 262832788Speter indicate in any way that rev1 == vn_user). I think just a 262932788Speter simple "U foo" is good here; it seems analogous to the case in 263032788Speter which the file was added on the branch in terms of what to 263132788Speter print. */ 263232788Speter write_letter (finfo, 'U'); 263332788Speter } 263466528Speter else if (strcmp (t_options, "-kb") == 0 263534467Speter || wrap_merge_is_copy (finfo->file) 263634467Speter || special_file_mismatch (finfo, rev1, rev2)) 263732788Speter { 263834467Speter /* We are dealing with binary files, or files with a 2639128269Speter permission/linkage mismatch (this second case only occurs when 2640128269Speter PRESERVE_PERMISSIONS_SUPPORT is enabled), and real merging would 264132788Speter need to take place. This is a conflict. We give the user 264232788Speter the two files, and let them resolve it. It is possible 264332788Speter that we should require a "touch foo" or similar step before 264432788Speter we allow a checkin. */ 2645128269Speter if (RCS_checkout ( finfo->rcs, finfo->file, rev2, (char *)NULL, 2646128269Speter t_options, RUN_TTY, (RCSCHECKOUTPROC)0, NULL) != 0) 264732788Speter status = 2; 264832788Speter else 264932788Speter status = 0; 265032788Speter 265132788Speter /* OK, this is really stupid. RCS_checkout carefully removes 265232788Speter write permissions, and we carefully put them back. But 265332788Speter until someone gets around to fixing it, that seems like the 265432788Speter easiest way to get what would seem to be the right mode. 265532788Speter I don't check CVSWRITE or _watched; I haven't thought about 265632788Speter that in great detail, but it seems like a watched file should 265732788Speter be checked out (writable) after a merge. */ 265832788Speter xchmod (finfo->file, 1); 265932788Speter 266032788Speter /* Hmm. We don't give them REV1 anywhere. I guess most people 266132788Speter probably don't have a 3-way merge tool for the file type in 266232788Speter question, and might just get confused if we tried to either 266332788Speter provide them with a copy of the file from REV1, or even just 266432788Speter told them what REV1 is so they can get it themself, but it 266532788Speter might be worth thinking about. */ 266632788Speter /* See comment in merge_file about the "nonmergeable file" 266732788Speter terminology. */ 266832788Speter error (0, 0, "nonmergeable file needs merge"); 266932788Speter error (0, 0, "revision %s from repository is now in %s", 267032788Speter rev2, finfo->fullname); 267132788Speter error (0, 0, "file from working directory is now in %s", backup); 267232788Speter write_letter (finfo, 'C'); 267332788Speter } 267432788Speter else 267532788Speter status = RCS_merge (finfo->rcs, vers->srcfile->path, finfo->file, 267666528Speter t_options, rev1, rev2); 267732788Speter 2678128269Speter if (status != 0) 267917721Speter { 2680128269Speter if (status != 1) 2681128269Speter { 2682128269Speter error (0, status == -1 ? errno : 0, 2683128269Speter "could not merge revision %s of %s", rev2, finfo->fullname); 2684128269Speter error (status == -1 ? 1 : 0, 0, "restoring %s from backup file %s", 2685128269Speter finfo->fullname, backup); 2686128269Speter rename_file (backup, finfo->file); 2687128269Speter } 268817721Speter } 2689128269Speter else /* status == 0 */ 2690128269Speter { 2691128269Speter /* FIXME: the noexec case is broken. RCS_merge could be doing the 2692128269Speter xcmp on the temporary files without much hassle, I think. */ 2693128269Speter if (!noexec && !xcmp (backup, finfo->file)) 2694128269Speter { 2695128269Speter if (!really_quiet) 2696128269Speter { 2697128269Speter cvs_output (finfo->fullname, 0); 2698128269Speter cvs_output (" already contains the differences between ", 0); 2699128269Speter cvs_output (rev1, 0); 2700128269Speter cvs_output (" and ", 0); 2701128269Speter cvs_output (rev2, 0); 2702128269Speter cvs_output ("\n", 1); 2703128269Speter } 270417721Speter 2705128269Speter /* and skip the registering and sending the new file since it 2706128269Speter * hasn't been updated. 2707128269Speter */ 2708128269Speter goto out; 2709128269Speter } 2710128269Speter } 2711128269Speter 271244856Speter /* The file has changed, but if we just checked it out it may 271344856Speter still have the same timestamp it did when it was first 271444856Speter registered above in checkout_file. We register it again with a 271544856Speter dummy timestamp to make sure that later runs of CVS will 271644856Speter recognize that it has changed. 271744856Speter 271844856Speter We don't actually need to register again if we called 271944856Speter RCS_checkout above, and we aren't running as the server. 272044856Speter However, that is not the normal case, and calling Register 272144856Speter again won't cost much in that case. */ 2722175280Sobrien RegisterMerge (finfo, vers, backup, status); 272317721Speter 2724128269Speterout: 2725128269Speter free (rev1); 2726128269Speter free (rev2); 272725839Speter free (backup); 272817721Speter} 272917721Speter 2730128269Speter 2731128269Speter 273234467Speter/* 273334467Speter * Report whether revisions REV1 and REV2 of FINFO agree on: 273434467Speter * . file ownership 273534467Speter * . permissions 273634467Speter * . major and minor device numbers 273734467Speter * . symbolic links 273834467Speter * . hard links 273934467Speter * 274034467Speter * If either REV1 or REV2 is NULL, the working copy is used instead. 274134467Speter * 274234467Speter * Return 1 if the files differ on these data. 274334467Speter */ 274434467Speter 274517721Speterint 274634467Speterspecial_file_mismatch (finfo, rev1, rev2) 274734467Speter struct file_info *finfo; 274834467Speter char *rev1; 274934467Speter char *rev2; 275034467Speter{ 275134467Speter#ifdef PRESERVE_PERMISSIONS_SUPPORT 275234467Speter struct stat sb; 275334467Speter RCSVers *vp; 275434467Speter Node *n; 275534467Speter uid_t rev1_uid, rev2_uid; 275634467Speter gid_t rev1_gid, rev2_gid; 275734467Speter mode_t rev1_mode, rev2_mode; 275834467Speter unsigned long dev_long; 275934467Speter dev_t rev1_dev, rev2_dev; 276034467Speter char *rev1_symlink = NULL; 276134467Speter char *rev2_symlink = NULL; 276266528Speter List *rev1_hardlinks = NULL; 276366528Speter List *rev2_hardlinks = NULL; 276434467Speter int check_uids, check_gids, check_modes; 276534467Speter int result; 276634467Speter 276734467Speter /* If we don't care about special file info, then 276834467Speter don't report a mismatch in any case. */ 276934467Speter if (!preserve_perms) 277034467Speter return 0; 277134467Speter 277234467Speter /* When special_file_mismatch is called from No_Difference, the 277334467Speter RCS file has been only partially parsed. We must read the 277434467Speter delta tree in order to compare special file info recorded in 277534467Speter the delta nodes. (I think this is safe. -twp) */ 277634467Speter if (finfo->rcs->flags & PARTIAL) 277734467Speter RCS_reparsercsfile (finfo->rcs, NULL, NULL); 277834467Speter 277934467Speter check_uids = check_gids = check_modes = 1; 278034467Speter 278134467Speter /* Obtain file information for REV1. If this is null, then stat 278234467Speter finfo->file and use that info. */ 278334467Speter /* If a revision does not know anything about its status, 278434467Speter then presumably it doesn't matter, and indicates no conflict. */ 278534467Speter 278634467Speter if (rev1 == NULL) 278734467Speter { 278834467Speter if (islink (finfo->file)) 278934467Speter rev1_symlink = xreadlink (finfo->file); 279034467Speter else 279134467Speter { 2792107487Speter# ifdef HAVE_STRUCT_STAT_ST_RDEV 279334467Speter if (CVS_LSTAT (finfo->file, &sb) < 0) 279434467Speter error (1, errno, "could not get file information for %s", 279534467Speter finfo->file); 279634467Speter rev1_uid = sb.st_uid; 279734467Speter rev1_gid = sb.st_gid; 279834467Speter rev1_mode = sb.st_mode; 279934467Speter if (S_ISBLK (rev1_mode) || S_ISCHR (rev1_mode)) 280034467Speter rev1_dev = sb.st_rdev; 2801102843Speter# else 280266528Speter error (1, 0, "cannot handle device files on this system (%s)", 280366528Speter finfo->file); 2804102843Speter# endif 280534467Speter } 280644856Speter rev1_hardlinks = list_linked_files_on_disk (finfo->file); 280734467Speter } 280834467Speter else 280934467Speter { 281034467Speter n = findnode (finfo->rcs->versions, rev1); 2811128269Speter vp = n->data; 281234467Speter 281334467Speter n = findnode (vp->other_delta, "symlink"); 281434467Speter if (n != NULL) 281534467Speter rev1_symlink = xstrdup (n->data); 281634467Speter else 281734467Speter { 281834467Speter n = findnode (vp->other_delta, "owner"); 281934467Speter if (n == NULL) 282034467Speter check_uids = 0; /* don't care */ 282134467Speter else 282234467Speter rev1_uid = strtoul (n->data, NULL, 10); 282334467Speter 282434467Speter n = findnode (vp->other_delta, "group"); 282534467Speter if (n == NULL) 282634467Speter check_gids = 0; /* don't care */ 282734467Speter else 282834467Speter rev1_gid = strtoul (n->data, NULL, 10); 282934467Speter 283034467Speter n = findnode (vp->other_delta, "permissions"); 283134467Speter if (n == NULL) 283234467Speter check_modes = 0; /* don't care */ 283334467Speter else 283434467Speter rev1_mode = strtoul (n->data, NULL, 8); 283534467Speter 283634467Speter n = findnode (vp->other_delta, "special"); 283734467Speter if (n == NULL) 283834467Speter rev1_mode |= S_IFREG; 283934467Speter else 284034467Speter { 284134467Speter /* If the size of `ftype' changes, fix the sscanf call also */ 284234467Speter char ftype[16]; 2843102843Speter if (sscanf (n->data, "%15s %lu", ftype, 284434467Speter &dev_long) < 2) 284534467Speter error (1, 0, "%s:%s has bad `special' newphrase %s", 2846128269Speter finfo->file, rev1, (char *)n->data); 284734467Speter rev1_dev = dev_long; 284834467Speter if (strcmp (ftype, "character") == 0) 284934467Speter rev1_mode |= S_IFCHR; 285034467Speter else if (strcmp (ftype, "block") == 0) 285134467Speter rev1_mode |= S_IFBLK; 285234467Speter else 285334467Speter error (0, 0, "%s:%s unknown file type `%s'", 285434467Speter finfo->file, rev1, ftype); 285534467Speter } 285634467Speter 285744856Speter rev1_hardlinks = vp->hardlinks; 285844856Speter if (rev1_hardlinks == NULL) 285944856Speter rev1_hardlinks = getlist(); 286034467Speter } 286134467Speter } 286234467Speter 286334467Speter /* Obtain file information for REV2. */ 286434467Speter if (rev2 == NULL) 286534467Speter { 286634467Speter if (islink (finfo->file)) 286734467Speter rev2_symlink = xreadlink (finfo->file); 286834467Speter else 286934467Speter { 2870107487Speter# ifdef HAVE_STRUCT_STAT_ST_RDEV 287134467Speter if (CVS_LSTAT (finfo->file, &sb) < 0) 287234467Speter error (1, errno, "could not get file information for %s", 287334467Speter finfo->file); 287434467Speter rev2_uid = sb.st_uid; 287534467Speter rev2_gid = sb.st_gid; 287634467Speter rev2_mode = sb.st_mode; 287734467Speter if (S_ISBLK (rev2_mode) || S_ISCHR (rev2_mode)) 287834467Speter rev2_dev = sb.st_rdev; 2879102843Speter# else 288066528Speter error (1, 0, "cannot handle device files on this system (%s)", 288166528Speter finfo->file); 2882102843Speter# endif 288334467Speter } 288444856Speter rev2_hardlinks = list_linked_files_on_disk (finfo->file); 288534467Speter } 288634467Speter else 288734467Speter { 288834467Speter n = findnode (finfo->rcs->versions, rev2); 2889128269Speter vp = n->data; 289034467Speter 289134467Speter n = findnode (vp->other_delta, "symlink"); 289234467Speter if (n != NULL) 289334467Speter rev2_symlink = xstrdup (n->data); 289434467Speter else 289534467Speter { 289634467Speter n = findnode (vp->other_delta, "owner"); 289734467Speter if (n == NULL) 289834467Speter check_uids = 0; /* don't care */ 289934467Speter else 290034467Speter rev2_uid = strtoul (n->data, NULL, 10); 290134467Speter 290234467Speter n = findnode (vp->other_delta, "group"); 290334467Speter if (n == NULL) 290434467Speter check_gids = 0; /* don't care */ 290534467Speter else 290634467Speter rev2_gid = strtoul (n->data, NULL, 10); 290734467Speter 290834467Speter n = findnode (vp->other_delta, "permissions"); 290934467Speter if (n == NULL) 291034467Speter check_modes = 0; /* don't care */ 291134467Speter else 291234467Speter rev2_mode = strtoul (n->data, NULL, 8); 291334467Speter 291434467Speter n = findnode (vp->other_delta, "special"); 291534467Speter if (n == NULL) 291634467Speter rev2_mode |= S_IFREG; 291734467Speter else 291834467Speter { 291934467Speter /* If the size of `ftype' changes, fix the sscanf call also */ 292034467Speter char ftype[16]; 2921102843Speter if (sscanf (n->data, "%15s %lu", ftype, 292234467Speter &dev_long) < 2) 292334467Speter error (1, 0, "%s:%s has bad `special' newphrase %s", 2924128269Speter finfo->file, rev2, (char *)n->data); 292534467Speter rev2_dev = dev_long; 292634467Speter if (strcmp (ftype, "character") == 0) 292734467Speter rev2_mode |= S_IFCHR; 292834467Speter else if (strcmp (ftype, "block") == 0) 292934467Speter rev2_mode |= S_IFBLK; 293034467Speter else 293134467Speter error (0, 0, "%s:%s unknown file type `%s'", 293234467Speter finfo->file, rev2, ftype); 293334467Speter } 293434467Speter 293544856Speter rev2_hardlinks = vp->hardlinks; 293644856Speter if (rev2_hardlinks == NULL) 293744856Speter rev2_hardlinks = getlist(); 293834467Speter } 293934467Speter } 294034467Speter 294134467Speter /* Check the user/group ownerships and file permissions, printing 294234467Speter an error for each mismatch found. Return 0 if all characteristics 294334467Speter matched, and 1 otherwise. */ 294434467Speter 294534467Speter result = 0; 294634467Speter 294734467Speter /* Compare symlinks first, since symlinks are simpler (don't have 294834467Speter any other characteristics). */ 294934467Speter if (rev1_symlink != NULL && rev2_symlink == NULL) 295034467Speter { 295134467Speter error (0, 0, "%s is a symbolic link", 295234467Speter (rev1 == NULL ? "working file" : rev1)); 295334467Speter result = 1; 295434467Speter } 295534467Speter else if (rev1_symlink == NULL && rev2_symlink != NULL) 295634467Speter { 295734467Speter error (0, 0, "%s is a symbolic link", 295834467Speter (rev2 == NULL ? "working file" : rev2)); 295934467Speter result = 1; 296034467Speter } 296134467Speter else if (rev1_symlink != NULL) 296234467Speter result = (strcmp (rev1_symlink, rev2_symlink) == 0); 296334467Speter else 296434467Speter { 296534467Speter /* Compare user ownership. */ 296634467Speter if (check_uids && rev1_uid != rev2_uid) 296734467Speter { 296834467Speter error (0, 0, "%s: owner mismatch between %s and %s", 296934467Speter finfo->file, 297034467Speter (rev1 == NULL ? "working file" : rev1), 297134467Speter (rev2 == NULL ? "working file" : rev2)); 297234467Speter result = 1; 297334467Speter } 297434467Speter 297534467Speter /* Compare group ownership. */ 297634467Speter if (check_gids && rev1_gid != rev2_gid) 297734467Speter { 297834467Speter error (0, 0, "%s: group mismatch between %s and %s", 297934467Speter finfo->file, 298034467Speter (rev1 == NULL ? "working file" : rev1), 298134467Speter (rev2 == NULL ? "working file" : rev2)); 298234467Speter result = 1; 298334467Speter } 298434467Speter 298534467Speter /* Compare permissions. */ 298634467Speter if (check_modes && 298734467Speter (rev1_mode & 07777) != (rev2_mode & 07777)) 298834467Speter { 298934467Speter error (0, 0, "%s: permission mismatch between %s and %s", 299034467Speter finfo->file, 299134467Speter (rev1 == NULL ? "working file" : rev1), 299234467Speter (rev2 == NULL ? "working file" : rev2)); 299334467Speter result = 1; 299434467Speter } 299534467Speter 299634467Speter /* Compare device file characteristics. */ 299734467Speter if ((rev1_mode & S_IFMT) != (rev2_mode & S_IFMT)) 299834467Speter { 299934467Speter error (0, 0, "%s: %s and %s are different file types", 300034467Speter finfo->file, 300134467Speter (rev1 == NULL ? "working file" : rev1), 300234467Speter (rev2 == NULL ? "working file" : rev2)); 300334467Speter result = 1; 300434467Speter } 300534467Speter else if (S_ISBLK (rev1_mode)) 300634467Speter { 300734467Speter if (rev1_dev != rev2_dev) 300834467Speter { 300934467Speter error (0, 0, "%s: device numbers of %s and %s do not match", 301034467Speter finfo->file, 301134467Speter (rev1 == NULL ? "working file" : rev1), 301234467Speter (rev2 == NULL ? "working file" : rev2)); 301334467Speter result = 1; 301434467Speter } 301534467Speter } 301634467Speter 301734467Speter /* Compare hard links. */ 301844856Speter if (compare_linkage_lists (rev1_hardlinks, rev2_hardlinks) == 0) 301934467Speter { 302034467Speter error (0, 0, "%s: hard linkage of %s and %s do not match", 302134467Speter finfo->file, 302234467Speter (rev1 == NULL ? "working file" : rev1), 302334467Speter (rev2 == NULL ? "working file" : rev2)); 302434467Speter result = 1; 302534467Speter } 302634467Speter } 302734467Speter 302834467Speter if (rev1_symlink != NULL) 302934467Speter free (rev1_symlink); 303034467Speter if (rev2_symlink != NULL) 303134467Speter free (rev2_symlink); 303234467Speter if (rev1_hardlinks != NULL) 303344856Speter dellist (&rev1_hardlinks); 303434467Speter if (rev2_hardlinks != NULL) 303544856Speter dellist (&rev2_hardlinks); 303634467Speter 303734467Speter return result; 303834467Speter#else 303934467Speter return 0; 304034467Speter#endif 304134467Speter} 304234467Speter 3043130307Speter 3044130307Speter 304534467Speterint 304617721Speterjoining () 304717721Speter{ 3048130307Speter return join_rev1 != NULL; 304917721Speter} 3050