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