From 172be96ac471deaecb64f55c313652e3887196d8 Mon Sep 17 00:00:00 2001 From: tholo Date: Mon, 21 Apr 1997 04:40:59 +0000 Subject: [PATCH] Integrate local changes --- gnu/usr.bin/cvs/src/checkout.c | 282 +++++++++++++++++++-------------- gnu/usr.bin/cvs/src/commit.c | 48 ++++-- gnu/usr.bin/cvs/src/cvs.h | 19 ++- gnu/usr.bin/cvs/src/main.c | 18 +-- gnu/usr.bin/cvs/src/patch.c | 8 +- gnu/usr.bin/cvs/src/rcscmds.c | 32 +--- gnu/usr.bin/cvs/src/server.c | 75 ++++++++- gnu/usr.bin/cvs/src/update.c | 255 ++++++++++++++++++----------- 8 files changed, 457 insertions(+), 280 deletions(-) diff --git a/gnu/usr.bin/cvs/src/checkout.c b/gnu/usr.bin/cvs/src/checkout.c index c2b18c53df6..737cbdc8ea1 100644 --- a/gnu/usr.bin/cvs/src/checkout.c +++ b/gnu/usr.bin/cvs/src/checkout.c @@ -36,8 +36,6 @@ #include "cvs.h" static char *findslash PROTO((char *start, char *p)); -static int build_dirs_and_chdir PROTO((char *dir, char *prepath, char *realdir, - int sticky)); static int checkout_proc PROTO((int *pargc, char **argv, char *where, char *mwhere, char *mfile, int shorten, int local_specified, char *omodule, @@ -118,13 +116,13 @@ checkout (argc, argv) if (strcmp (command_name, "export") == 0) { m_type = EXPORT; - valid_options = "Nnk:d:flRQqr:D:"; + valid_options = "+Nnk:d:flRQqr:D:"; valid_usage = export_usage; } else { m_type = CHECKOUT; - valid_options = "ANnk:d:flRpQqcsr:D:j:P"; + valid_options = "+ANnk:d:flRpQqcsr:D:j:P"; valid_usage = checkout_usage; } @@ -297,6 +295,8 @@ checkout (argc, argv) } if (status) send_arg("-s"); + /* Why not send -k for export? This would appear to make + remote export differ from local export. FIXME. */ if (strcmp (command_name, "export") != 0 && options != NULL && options[0] != '\0') @@ -368,7 +368,7 @@ checkout (argc, argv) error (1, 0, "there is no repository %s", repository); Create_Admin (".", preload_update_dir, repository, - (char *) NULL, (char *) NULL); + (char *) NULL, (char *) NULL, 0); if (!noexec) { FILE *fp; @@ -479,6 +479,60 @@ safe_location () return retval; } +struct dir_to_build +{ + /* What to put in CVS/Repository. */ + char *repository; + /* The path to the directory. */ + char *dirpath; + + struct dir_to_build *next; +}; + +static int build_dirs_and_chdir PROTO ((struct dir_to_build *list, + int sticky)); + +static void build_one_dir PROTO ((char *, char *, int)); + +static void +build_one_dir (repository, dirpath, sticky) + char *repository; + char *dirpath; + int sticky; +{ + FILE *fp; + + if (!isfile (CVSADM) && strcmp (command_name, "export") != 0) + { + /* I suspect that this check could be omitted. */ + if (!isdir (repository)) + error (1, 0, "there is no repository %s", repository); + + Create_Admin (".", dirpath, repository, + sticky ? (char *) NULL : tag, + sticky ? (char *) NULL : date, + + /* FIXME? This is a guess. If it is important + for nonbranch to be set correctly here I + think we need to write it one way now and + then rewrite it later via WriteTag, once + we've had a chance to call RCS_nodeisbranch + on each file. */ + 0); + + if (!noexec) + { + fp = open_file (CVSADM_ENTSTAT, "w+"); + if (fclose (fp) == EOF) + error (1, errno, "cannot close %s", CVSADM_ENTSTAT); +#ifdef SERVER_SUPPORT + if (server_active) + server_set_entstat (dirpath, repository); +#endif + } + } +} + /* * process_module calls us back here so we do the actual checkout stuff */ @@ -504,7 +558,6 @@ checkout_proc (pargc, argv, where, mwhere, mfile, shorten, char *xwhere = NULL; char *oldupdate = NULL; char *prepath; - char *realdirs; /* * OK, so we're doing the checkout! Our args are as follows: @@ -678,24 +731,80 @@ checkout_proc (pargc, argv, where, mwhere, mfile, shorten, */ if (!pipeout) { - - /* - * We need to tell build_dirs not only the path we want it to build, - * but also the repositories we want it to populate the path with. To - * accomplish this, we pass build_dirs a ``real path'' with valid - * repositories and a string to pre-pend based on how many path - * elements exist in where. Big Black Magic - */ + size_t root_len; + struct dir_to_build *head; + + /* We need to tell build_dirs not only the path we want it to + build, but also the repositories we want it to populate the + path with. To accomplish this, we walk the path backwards, + one pathname component at a time, constucting a linked + list of struct dir_to_build. */ prepath = xstrdup (repository); + + /* We don't want to start stripping elements off prepath after we + get to CVSROOT. */ + root_len = strlen (CVSroot_directory); + if (strncmp (repository, CVSroot_directory, root_len) != 0) + error (1, 0, "\ +internal error: %s doesn't start with %s in checkout_proc", + repository, CVSroot_directory); + + /* We always create at least one directory, which corresponds to + the entire strings for WHERE and REPOSITORY. */ + head = (struct dir_to_build *) xmalloc (sizeof (struct dir_to_build)); + /* Special marker to indicate that we don't want build_dirs_and_chdir + to create the CVSADM directory for us. */ + head->repository = NULL; + head->dirpath = xstrdup (where); + head->next = NULL; + cp = strrchr (where, '/'); cp2 = strrchr (prepath, '/'); + while (cp != NULL) { + struct dir_to_build *new; + new = (struct dir_to_build *) + xmalloc (sizeof (struct dir_to_build)); + new->dirpath = xmalloc (strlen (where)); + strncpy (new->dirpath, where, cp - where); + new->dirpath[cp - where] = '\0'; + if (cp2 == NULL || cp2 < prepath + root_len) + { + /* Don't walk up past CVSROOT; instead put in CVSNULLREPOS. */ + new->repository = + xmalloc (strlen (CVSroot_directory) + 80); + (void) sprintf (new->repository, "%s/%s/%s", + CVSroot_directory, + CVSROOTADM, CVSNULLREPOS); + if (!isfile (new->repository)) + { + mode_t omask; + omask = umask (cvsumask); + if (CVS_MKDIR (new->repository, 0777) < 0) + error (0, errno, "cannot create %s", + new->repository); + (void) umask (omask); + } + } + else + { + new->repository = xmalloc (strlen (prepath)); + strncpy (new->repository, prepath, cp2 - prepath); + new->repository[cp2 - prepath] = '\0'; + } + new->next = head; + head = new; + cp = findslash (where, cp - 1); cp2 = findslash (prepath, cp2 - 1); } - *cp2 = '\0'; - realdirs = cp2 + 1; + + /* First build the top-level CVSADM directory. The value we + pass in here for repository is probably wrong; see modules3-7f + in the testsuite. */ + build_one_dir (head->repository != NULL ? head->repository : prepath, + ".", *pargc <= 1); /* * build dirs on the path if necessary and leave us in the bottom @@ -703,7 +812,7 @@ checkout_proc (pargc, argv, where, mwhere, mfile, shorten, * subdir yet, but all the others contain CVS and Entries.Static * files */ - if (build_dirs_and_chdir (where, prepath, realdirs, *pargc <= 1) != 0) + if (build_dirs_and_chdir (head, *pargc <= 1) != 0) { error (0, 0, "ignoring module %s", omodule); free (prepath); @@ -726,7 +835,7 @@ checkout_proc (pargc, argv, where, mwhere, mfile, shorten, error (1, 0, "there is no repository %s", repository); Create_Admin (".", preload_update_dir, repository, - (char *) NULL, (char *) NULL); + (char *) NULL, (char *) NULL, 0); fp = open_file (CVSADM_ENTSTAT, "w+"); if (fclose(fp) == EOF) error(1, errno, "cannot close %s", CVSADM_ENTSTAT); @@ -741,7 +850,15 @@ checkout_proc (pargc, argv, where, mwhere, mfile, shorten, if (!isdir (repository)) error (1, 0, "there is no repository %s", repository); - Create_Admin (".", preload_update_dir, repository, tag, date); + Create_Admin (".", preload_update_dir, repository, tag, date, + + /* FIXME? This is a guess. If it is important + for nonbranch to be set correctly here I + think we need to write it one way now and + then rewrite it later via WriteTag, once + we've had a chance to call RCS_nodeisbranch + on each file. */ + 0); } } else @@ -897,130 +1014,49 @@ findslash (start, p) { while (p >= start && *p != '/') p--; + /* FIXME: indexing off the start of the array like this is *NOT* + OK according to ANSI, and will break some of the time on certain + segmented architectures. */ if (p < start) return (NULL); else return (p); } -/* - * build all the dirs along the path to dir with CVS subdirs with appropriate - * repositories and Entries.Static files - */ +/* Build all the dirs along the path to DIRS with CVS subdirs with appropriate + repositories. If ->repository is NULL, do not create a CVSADM directory + for that subdirectory; just CVS_CHDIR into it. */ static int -build_dirs_and_chdir (dir, prepath, realdir, sticky) - char *dir; - char *prepath; - char *realdir; +build_dirs_and_chdir (dirs, sticky) + struct dir_to_build *dirs; int sticky; { - FILE *fp; - char *path; - char *path2; - char *slash; - char *slash2; - char *cp; - char *cp2; int retval = 0; + struct dir_to_build *nextdir; - path = xstrdup (dir); - path2 = xstrdup (realdir); - for (cp = path, cp2 = path2; - (slash = strchr (cp, '/')) != NULL && (slash2 = strchr (cp2, '/')) != NULL; - cp = slash + 1, cp2 = slash2 + 1) + while (dirs != NULL) { - *slash = '\0'; - *slash2 = '\0'; - if (!isfile (CVSADM) && strcmp (command_name, "export") != 0) - { - char *repository; + char *dir = last_component (dirs->dirpath); - repository = xmalloc (strlen (prepath) + strlen (path2) + 5); - (void) sprintf (repository, "%s/%s", prepath, path2); - /* I'm not sure whether this check is redundant. */ - if (!isdir (repository)) - error (1, 0, "there is no repository %s", repository); - Create_Admin (".", path, repository, sticky ? (char *) NULL : tag, - sticky ? (char *) NULL : date); - if (!noexec) - { - fp = open_file (CVSADM_ENTSTAT, "w+"); - if (fclose(fp) == EOF) - error(1, errno, "cannot close %s", CVSADM_ENTSTAT); -#ifdef SERVER_SUPPORT - if (server_active) - server_set_entstat (path, repository); -#endif - } - free (repository); - } - mkdir_if_needed (cp); - Subdir_Register ((List *) NULL, (char *) NULL, cp); - if ( CVS_CHDIR (cp) < 0) + mkdir_if_needed (dir); + Subdir_Register (NULL, NULL, dir); + if (CVS_CHDIR (dir) < 0) { - error (0, errno, "cannot chdir to %s", cp); + error (0, errno, "cannot chdir to %s", dir); retval = 1; goto out; } - if (!isfile (CVSADM) && strcmp (command_name, "export") != 0) + if (dirs->repository != NULL) { - char *repository; - - repository = xmalloc (strlen (prepath) + strlen (path2) + 5); - (void) sprintf (repository, "%s/%s", prepath, path2); - /* I'm not sure whether this check is redundant. */ - if (!isdir (repository)) - error (1, 0, "there is no repository %s", repository); - Create_Admin (".", path, repository, sticky ? (char *) NULL : tag, - sticky ? (char *) NULL : date); - if (!noexec) - { - fp = open_file (CVSADM_ENTSTAT, "w+"); - if (fclose(fp) == EOF) - error(1, errno, "cannot close %s", CVSADM_ENTSTAT); -#ifdef SERVER_SUPPORT - if (server_active) - server_set_entstat (path, repository); -#endif - } - free (repository); + build_one_dir (dirs->repository, dirs->dirpath, sticky); + free (dirs->repository); } - *slash = '/'; - *slash2 = '/'; + nextdir = dirs->next; + free (dirs->dirpath); + free (dirs); + dirs = nextdir; } - if (!isfile (CVSADM) && strcmp (command_name, "export") != 0) - { - char *repository; - repository = xmalloc (strlen (prepath) + strlen (path2) + 5); - (void) sprintf (repository, "%s/%s", prepath, path2); - /* I'm not sure whether this check is redundant. */ - if (!isdir (repository)) - error (1, 0, "there is no repository %s", repository); - Create_Admin (".", path, repository, sticky ? (char *) NULL : tag, - sticky ? (char *) NULL : date); - if (!noexec) - { - fp = open_file (CVSADM_ENTSTAT, "w+"); - if (fclose(fp) == EOF) - error(1, errno, "cannot close %s", CVSADM_ENTSTAT); -#ifdef SERVER_SUPPORT - if (server_active) - server_set_entstat (path, repository); -#endif - } - free (repository); - } - mkdir_if_needed (cp); - Subdir_Register ((List *) NULL, (char *) NULL, cp); - if ( CVS_CHDIR (cp) < 0) - { - error (0, errno, "cannot chdir to %s", cp); - retval = 1; - goto out; - } -out: - free (path); - free (path2); + out: return retval; } diff --git a/gnu/usr.bin/cvs/src/commit.c b/gnu/usr.bin/cvs/src/commit.c index ae2699ecade..9d13391950b 100644 --- a/gnu/usr.bin/cvs/src/commit.c +++ b/gnu/usr.bin/cvs/src/commit.c @@ -75,6 +75,7 @@ static int run_module_prog = 1; static int aflag; static char *tag; static char *write_dirtag; +static int write_dirnonbranch; static char *logfile; static List *mulist; static char *message; @@ -123,6 +124,10 @@ struct find_data { the repository (pointer into storage managed by the recursion processor. */ char *repository; + + /* Non-zero if we should force the commit. This is enabled by + either -f or -r options, unlike force_ci which is just -f. */ + int force; }; static Dtype find_dirent_proc PROTO ((void *callerdat, char *dir, @@ -254,7 +259,7 @@ find_fileproc (callerdat, finfo) status = T_ADDED; else if (vers->ts_user != NULL && vers->ts_rcs != NULL - && (force_ci || strcmp (vers->ts_user, vers->ts_rcs) != 0)) + && (args->force || strcmp (vers->ts_user, vers->ts_rcs) != 0)) /* If we are forcing commits, pretend that the file is modified. */ status = T_MODIFIED; @@ -328,7 +333,7 @@ commit (argc, argv) #endif /* CVS_BADROOT */ optind = 1; - while ((c = getopt (argc, argv, "nlRm:fF:r:")) != -1) + while ((c = getopt (argc, argv, "+nlRm:fF:r:")) != -1) { switch (c) { @@ -430,6 +435,12 @@ commit (argc, argv) find_args.ignlist = NULL; find_args.repository = NULL; + /* It is possible that only a numeric tag should set this. + I haven't really thought about it much. + Anyway, I suspect that setting it unnecessarily only causes + a little unneeded network traffic. */ + find_args.force = force_ci || tag != NULL; + err = start_recursion (find_fileproc, find_filesdoneproc, find_dirent_proc, (DIRLEAVEPROC) NULL, (void *)&find_args, @@ -535,7 +546,8 @@ commit (argc, argv) previous versions of client/server CVS, but it probably is a Good Thing, or at least Not Such A Bad Thing. */ send_file_names (find_args.argc, find_args.argv, 0); - send_files (find_args.argc, find_args.argv, local, 0, 0, force_ci); + send_files (find_args.argc, find_args.argv, local, 0, + find_args.force ? SEND_FORCE : 0); send_to_server ("ci\012", 0); return get_responses_and_close (); @@ -573,6 +585,7 @@ commit (argc, argv) /* * Run the recursion processor to commit the files */ + write_dirnonbranch = 0; if (noexec == 0) err = start_recursion (commit_fileproc, commit_filesdoneproc, commit_direntproc, commit_dirleaveproc, NULL, @@ -1048,6 +1061,21 @@ commit_fileproc (callerdat, finfo) List *ulist, *cilist; struct commit_info *ci; + /* Keep track of whether write_dirtag 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 (write_dirtag != NULL + && finfo->rcs != NULL) + { + char *rev = RCS_getversion (finfo->rcs, write_dirtag, NULL, 1, NULL); + if (rev != NULL + && !RCS_nodeisbranch (finfo->rcs, write_dirtag)) + write_dirnonbranch = 1; + if (rev != NULL) + free (rev); + } + if (finfo->update_dir[0] == '\0') p = findnode (mulist, "."); else @@ -1380,14 +1408,13 @@ commit_dirleaveproc (callerdat, dir, err, update_dir, entries) List *entries; { /* update the per-directory tag info */ + /* FIXME? Why? The "commit examples" node of cvs.texinfo briefly + mentions commit -r being sticky, but apparently in the context of + this being a confusing feature! */ if (err == 0 && write_dirtag != NULL) { - WriteTag ((char *) NULL, write_dirtag, (char *) NULL); -#ifdef SERVER_SUPPORT - if (server_active) - server_set_sticky (update_dir, Name_Repository (dir, update_dir), - write_dirtag, (char *) NULL); -#endif + WriteTag (NULL, write_dirtag, NULL, write_dirnonbranch, + update_dir, Name_Repository (dir, update_dir)); } return (err); @@ -1534,7 +1561,8 @@ remove_file (finfo, tag, message) /* check something out. Generally this is the head. If we have a particular rev, then name it. */ retcode = RCS_checkout (finfo->rcs, finfo->file, rev ? corev : NULL, - (char *) NULL, (char *) NULL, RUN_TTY); + (char *) NULL, (char *) NULL, RUN_TTY, + (RCSCHECKOUTPROC) NULL, (void *) NULL); if (retcode != 0) { if (!quiet) diff --git a/gnu/usr.bin/cvs/src/cvs.h b/gnu/usr.bin/cvs/src/cvs.h index a5f12b269f2..60a8e8aefa9 100644 --- a/gnu/usr.bin/cvs/src/cvs.h +++ b/gnu/usr.bin/cvs/src/cvs.h @@ -328,6 +328,8 @@ struct stickydirtag int aflag; char *tag; char *date; + int nonbranch; + /* This field is set by Entries_Open() if there was subdirectory information; Find_Directories() uses it to see whether it needs to scan the directory itself. */ @@ -397,8 +399,6 @@ int RCS_exec_setbranch PROTO((const char *, const char *)); int RCS_exec_lock PROTO((const char *, const char *, int)); int RCS_exec_unlock PROTO((const char *, const char *, int)); int RCS_merge PROTO((const char *, const char *, const char *, const char *)); -int RCS_exec_checkout PROTO ((char *rcsfile, char *workfile, char *tag, - char *options, char *sout)); /* Flags used by RCS_* functions. See the description of the individual functions for which flags mean what for each function. */ #define RCS_FLAGS_FORCE 1 @@ -464,7 +464,8 @@ int yesno PROTO((void)); void *valloc PROTO((size_t bytes)); time_t get_date PROTO((char *date, struct timeb *now)); void Create_Admin PROTO((char *dir, char *update_dir, - char *repository, char *tag, char *date)); + char *repository, char *tag, char *date, + int nonbranch)); /* Locking subsystem (implemented in lock.c). */ @@ -478,9 +479,10 @@ void lock_tree_for_write PROTO ((int argc, char **argv, int local, int aflag)); /* See lock.c for description. */ extern void lock_dir_for_write PROTO ((char *)); -void ParseTag PROTO((char **tagp, char **datep)); void Scratch_Entry PROTO((List * list, char *fname)); -void WriteTag PROTO((char *dir, char *tag, char *date)); +void ParseTag PROTO((char **tagp, char **datep, int *nonbranchp)); +void WriteTag PROTO ((char *dir, char *tag, char *date, int nonbranch, + char *update_dir, char *repository)); void cat_module PROTO((int status)); void check_entries PROTO((char *dir)); void close_module PROTO((DBM * db)); @@ -655,7 +657,9 @@ struct vers_ts char *vn_tag; /* This is the timestamp from stating the file in the working directory. - It is NULL if there is no file in the working directory. */ + It is NULL if there is no file in the working directory. It is + "Is-modified" if we know the file is modified but don't have its + contents. */ char *ts_user; /* Timestamp from CVS/Entries. For the server, ts_user and ts_rcs are computed in a slightly different way, but the fact remains that @@ -678,6 +682,9 @@ struct vers_ts /* Date specified on the command line, or if none, date stored in CVS/Entries. */ char *date; + /* If this is 1, then tag is not a branch tag. If this is 0, then + tag may or may not be a branch tag. */ + int nonbranch; /* Pointer to entries file node */ Entnode *entdata; diff --git a/gnu/usr.bin/cvs/src/main.c b/gnu/usr.bin/cvs/src/main.c index c9d6d34ee1e..fc97f231be7 100644 --- a/gnu/usr.bin/cvs/src/main.c +++ b/gnu/usr.bin/cvs/src/main.c @@ -413,11 +413,9 @@ main (argc, argv) CVSUMASK_ENV, cp); } - /* This has the effect of setting getopt's ordering to REQUIRE_ORDER, - which is what we need to distinguish between global options and - command options. FIXME: It would appear to be possible to do this - much less kludgily by passing "+" as the first character to the - option string we pass to getopt_long. */ + /* I'm not sure whether this needs to be 1 instead of 0 anymore. Using + 1 used to accomplish what passing "+" as the first character to + the option string does, but that reason doesn't exist anymore. */ optind = 1; @@ -428,13 +426,13 @@ main (argc, argv) opterr = 0; while ((c = getopt_long - (argc, argv, "f", NULL, NULL)) + (argc, argv, "+f", NULL, NULL)) != EOF) - { + { if (c == 'f') use_cvsrc = FALSE; - } - + } + /* * Scan cvsrc file for global options. */ @@ -445,7 +443,7 @@ main (argc, argv) opterr = 1; while ((c = getopt_long - (argc, argv, "Qqrwtnlvb:T:e:d:Hfz:s:x", long_options, &option_index)) + (argc, argv, "+Qqrwtnlvb:T:e:d:Hfz:s:x", long_options, &option_index)) != EOF) { switch (c) diff --git a/gnu/usr.bin/cvs/src/patch.c b/gnu/usr.bin/cvs/src/patch.c index 97386247a8b..ac82e4b65b9 100644 --- a/gnu/usr.bin/cvs/src/patch.c +++ b/gnu/usr.bin/cvs/src/patch.c @@ -70,7 +70,7 @@ patch (argc, argv) usage (patch_usage); optind = 1; - while ((c = getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1) + while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1) { switch (c) { @@ -500,7 +500,8 @@ patch_fileproc (callerdat, finfo) if (vers_tag != NULL) { retcode = RCS_checkout (rcsfile, (char *) NULL, vers_tag, - rev1, options, tmpfile1); + rev1, options, tmpfile1, + (RCSCHECKOUTPROC) NULL, (void *) NULL); if (retcode != 0) { if (!really_quiet) @@ -522,7 +523,8 @@ patch_fileproc (callerdat, finfo) if (vers_head != NULL) { retcode = RCS_checkout (rcsfile, (char *) NULL, vers_head, - rev2, options, tmpfile2); + rev2, options, tmpfile2, + (RCSCHECKOUTPROC) NULL, (void *) NULL); if (retcode != 0) { if (!really_quiet) diff --git a/gnu/usr.bin/cvs/src/rcscmds.c b/gnu/usr.bin/cvs/src/rcscmds.c index ebea517bd65..1f93fcfb94e 100644 --- a/gnu/usr.bin/cvs/src/rcscmds.c +++ b/gnu/usr.bin/cvs/src/rcscmds.c @@ -51,8 +51,8 @@ many tools (not just CVS and RCS) can at least import this format. RCS and CVS must preserve the current ability to import/export it (preferably improved--magic branches are currently a roadblock). - TODO: improve rcsfile.5 in the RCS distribution so that it more - completely documents this format. + See doc/RCSFILES in the CVS distribution for documentation of this + file format. On somewhat related notes: @@ -161,34 +161,6 @@ RCS_merge(path, options, rev1, rev2) return status; } -/* Check out a revision from RCSFILE into WORKFILE, or to standard output - if WORKFILE is NULL. TAG is the tag to check out, or NULL if one - should check out the head of the default branch. OPTIONS is a string - such as -kb or -kkv, for keyword expansion options, or NULL if there - are none. If WORKFILE is NULL, run regardless of noexec; if non-NULL, - noexec inhibits execution. SOUT is what to do with standard output - (typically RUN_TTY). */ -int -RCS_exec_checkout (rcsfile, workfile, tag, options, sout) - char *rcsfile; - char *workfile; - char *tag; - char *options; - char *sout; -{ - run_setup ("%s%s -x,v/ -q %s%s", Rcsbin, RCS_CO, - tag ? "-r" : "", tag ? tag : ""); - if (options != NULL && options[0] != '\0') - run_arg (options); - if (workfile == NULL) - run_arg ("-p"); - run_arg (rcsfile); - if (workfile != NULL) - run_arg (workfile); - return run_exec (RUN_TTY, sout, RUN_TTY, - workfile == NULL ? (RUN_NORMAL | RUN_REALLY) : RUN_NORMAL); -} - /* Check in to RCSFILE with revision REV (which must be greater than the largest revision) and message MESSAGE (which is checked for legality). If FLAGS & RCS_FLAGS_DEAD, check in a dead revision. If FLAGS & diff --git a/gnu/usr.bin/cvs/src/server.c b/gnu/usr.bin/cvs/src/server.c index be434016a59..34b6906440b 100644 --- a/gnu/usr.bin/cvs/src/server.c +++ b/gnu/usr.bin/cvs/src/server.c @@ -1099,6 +1099,8 @@ struct an_entry { static struct an_entry *entries; +static void serve_unchanged PROTO ((char *)); + static void serve_unchanged (arg) char *arg; @@ -1137,6 +1139,68 @@ serve_unchanged (arg) } } +static void serve_is_modified PROTO ((char *)); + +static void +serve_is_modified (arg) + char *arg; +{ + struct an_entry *p; + char *name; + char *cp; + char *timefield; + /* Have we found this file in "entries" yet. */ + int found; + + if (error_pending ()) + return; + + /* Rewrite entries file to have `M' in timestamp field. */ + found = 0; + for (p = entries; p != NULL; p = p->next) + { + name = p->entry + 1; + cp = strchr (name, '/'); + if (cp != NULL + && strlen (arg) == cp - name + && strncmp (arg, name, cp - name) == 0) + { + timefield = strchr (cp + 1, '/') + 1; + if (!(timefield[0] == 'M' && timefield[1] == '/')) + { + cp = timefield + strlen (timefield); + cp[1] = '\0'; + while (cp > timefield) + { + *cp = cp[-1]; + --cp; + } + *timefield = 'M'; + } + found = 1; + break; + } + } + if (!found) + { + /* We got Is-modified but no Entry. Add a dummy entry. + The "D" timestamp is what makes it a dummy. */ + struct an_entry *p; + p = (struct an_entry *) malloc (sizeof (struct an_entry)); + if (p == NULL) + { + pending_error = ENOMEM; + return; + } + p->entry = xmalloc (strlen (arg) + 80); + strcpy (p->entry, "/"); + strcat (p->entry, arg); + strcat (p->entry, "//D//"); + p->next = entries; + entries = p; + } +} + static void serve_entry (arg) char *arg; @@ -3243,11 +3307,12 @@ server_clear_entstat (update_dir, repository) } void -server_set_sticky (update_dir, repository, tag, date) +server_set_sticky (update_dir, repository, tag, date, nonbranch) char *update_dir; char *repository; char *tag; char *date; + int nonbranch; { static int set_sticky_supported = -1; @@ -3273,7 +3338,10 @@ server_set_sticky (update_dir, repository, tag, date) buf_output0 (protocol, "\n"); if (tag != NULL) { - buf_output0 (protocol, "T"); + if (nonbranch) + buf_output0 (protocol, "N"); + else + buf_output0 (protocol, "T"); buf_output0 (protocol, tag); } else @@ -3599,7 +3667,7 @@ struct request requests[] = REQ_LINE("Root", serve_root, rq_essential), REQ_LINE("Valid-responses", serve_valid_responses, rq_essential), REQ_LINE("valid-requests", serve_valid_requests, rq_essential), - REQ_LINE("Repository", serve_repository, rq_essential), + REQ_LINE("Repository", serve_repository, rq_optional), REQ_LINE("Directory", serve_directory, rq_essential), REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional), REQ_LINE("Static-directory", serve_static_directory, rq_optional), @@ -3608,6 +3676,7 @@ struct request requests[] = REQ_LINE("Update-prog", serve_update_prog, rq_optional), REQ_LINE("Entry", serve_entry, rq_essential), REQ_LINE("Modified", serve_modified, rq_essential), + REQ_LINE("Is-modified", serve_is_modified, rq_optional), /* The client must send this request to interoperate with CVS 1.5 through 1.9 servers. The server must support it (although it can diff --git a/gnu/usr.bin/cvs/src/update.c b/gnu/usr.bin/cvs/src/update.c index dcb793b21fa..2dd6e0ff22f 100644 --- a/gnu/usr.bin/cvs/src/update.c +++ b/gnu/usr.bin/cvs/src/update.c @@ -50,6 +50,7 @@ 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 merge_file PROTO ((struct file_info *finfo, Vers_TS *vers)); static int scratch_file PROTO((struct file_info *finfo)); @@ -73,6 +74,15 @@ static void join_file PROTO ((struct file_info *finfo, Vers_TS *vers_ts)); static char *options = NULL; static char *tag = NULL; static char *date = NULL; +/* This is a bit of a kludge. We call WriteTag at the beginning + before we know whether nonbranch is set or not. And then at the + end, once we have the right value for nonbranch, we call WriteTag + again. I don't know whether the first call is necessary or not. + rewrite_tag is nonzero if we are going to have to make that second + call. */ +static int rewrite_tag; +static int nonbranch; + static char *join_rev1, *date_rev1; static char *join_rev2, *date_rev2; static int aflag = 0; @@ -125,7 +135,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) { @@ -260,7 +270,10 @@ update (argc, argv) if (failed_patches == NULL) { send_file_names (argc, argv, SEND_EXPAND_WILD); - send_files (argc, argv, local, aflag, update_build_dirs, 0); + /* 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 { @@ -279,7 +292,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, update_build_dirs, 0); + aflag, update_build_dirs ? SEND_BUILD_DIRS : 0); } failed_patches = NULL; @@ -342,11 +355,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; } } @@ -466,6 +478,23 @@ update_fileproc (callerdat, finfo) 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); + } + if (pipeout) { /* @@ -683,6 +712,12 @@ update_filesdone_proc (callerdat, err, repository, update_dir, entries) 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) { @@ -753,7 +788,12 @@ update_dirent_proc (callerdat, dir, repository, update_dir, entries) { /* 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); } } @@ -805,11 +845,9 @@ update_dirent_proc (callerdat, dir, repository, update_dir, entries) /* 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 */ @@ -1059,7 +1097,8 @@ VERS: ", 0); status = RCS_checkout (vers_ts->srcfile, pipeout ? NULL : finfo->file, vers_ts->vn_rcs, vers_ts->vn_tag, - vers_ts->options, RUN_TTY); + vers_ts->options, RUN_TTY, + (RCSCHECKOUTPROC) NULL, (void *) NULL); } if (file_is_dead || status == 0) { @@ -1185,6 +1224,24 @@ VERS: ", 0); } #ifdef SERVER_SUPPORT + +/* This structure is used to pass information between patch_file and + patch_file_write. */ + +struct patch_file_data +{ + /* File name, for error messages. */ + const char *filename; + /* File to which to write. */ + FILE *fp; + /* Whether to compute the MD5 checksum. */ + int compute_checksum; + /* Data structure for computing the MD5 checksum. */ + struct MD5Context context; + /* Set if the file has a final newline. */ + int final_nl; +}; + /* Patch a file. Runs rcsdiff. This is only done when running as the * server. The hope is that the diff will be smaller than the file * itself. @@ -1205,10 +1262,19 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) int fail; long file_size; FILE *e; + struct patch_file_data data; *docheckout = 0; - if (pipeout || joining ()) + if (noexec || pipeout || joining ()) + { + *docheckout = 1; + return 0; + } + + /* If this file has been marked as being binary, then never send a + patch. */ + if (strcmp (vers_ts->options, "-kb") == 0) { *docheckout = 1; return 0; @@ -1240,88 +1306,51 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) /* 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, (char *) NULL, - vers_ts->vn_user, (char *) NULL, - vers_ts->options, file1); - if (retcode != 0) - fail = 1; - else - { - e = CVS_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 finfo->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, finfo->file, - vers_ts->vn_rcs, (char *) NULL, - vers_ts->options, RUN_TTY); - if (retcode != 0) - fail = 1; - else - { - if (!isreadable (finfo->file)) - { - /* File is dead. */ - fail = 1; - } - else - { - rename_file (finfo->file, file2); - if (cvswrite == TRUE - && !fileattr_get (finfo->file, "_watched")) - xchmod (file2, 1); - e = CVS_FOPEN (file2, "r"); - if (e == NULL) - fail = 1; - else - { - struct MD5Context context; - int nl; - unsigned char buf[8192]; - unsigned len; - - nl = 0; + e = CVS_FOPEN (file2, "w"); + if (e == NULL) + error (1, errno, "cannot open %s", file2); - /* 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); + data.filename = file2; + data.fp = e; + data.final_nl = 0; + data.compute_checksum = 1; + MD5Init (&data.context); - if (ferror (e) || ! nl) - { - fail = 1; - } + retcode = RCS_checkout (vers_ts->srcfile, (char *) NULL, + vers_ts->vn_rcs, (char *) NULL, + vers_ts->options, RUN_TTY, + patch_file_write, (void *) &data); - fseek(e, 0L, SEEK_END); - file_size = ftell(e); + 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; @@ -1350,6 +1379,19 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) 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 = CVS_FOPEN (finfo->file, "r"); if (e == NULL) @@ -1392,8 +1434,8 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) xvers_ts->ts_user, xvers_ts->options, xvers_ts->tag, xvers_ts->date, NULL); - if ( CVS_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) @@ -1431,7 +1473,29 @@ patch_file (finfo, vers_ts, docheckout, file_info, checksum) 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 @@ -1927,7 +1991,8 @@ join_file (finfo, vers) /* The file is up to date. Need to check out the current contents. */ retcode = RCS_checkout (vers->srcfile, finfo->file, vers->vn_user, (char *) NULL, - (char *) NULL, RUN_TTY); + (char *) NULL, RUN_TTY, + (RCSCHECKOUTPROC) NULL, (void *) NULL); if (retcode != 0) error (1, retcode == -1 ? errno : 0, "failed to check out %s file", finfo->fullname); -- 2.20.1