Integrate local changes
authortholo <tholo@openbsd.org>
Mon, 21 Apr 1997 04:40:59 +0000 (04:40 +0000)
committertholo <tholo@openbsd.org>
Mon, 21 Apr 1997 04:40:59 +0000 (04:40 +0000)
gnu/usr.bin/cvs/src/checkout.c
gnu/usr.bin/cvs/src/commit.c
gnu/usr.bin/cvs/src/cvs.h
gnu/usr.bin/cvs/src/main.c
gnu/usr.bin/cvs/src/patch.c
gnu/usr.bin/cvs/src/rcscmds.c
gnu/usr.bin/cvs/src/server.c
gnu/usr.bin/cvs/src/update.c

index c2b18c5..737cbdc 100644 (file)
@@ -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;
 }
index ae2699e..9d13391 100644 (file)
@@ -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)
index a5f12b2..60a8e8a 100644 (file)
@@ -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));
 \f
 /* 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 *));
 \f
-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;
index c9d6d34..fc97f23 100644 (file)
@@ -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)
index 9738624..ac82e4b 100644 (file)
@@ -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)
index ebea517..1f93fcf 100644 (file)
@@ -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 &
index be43401..34b6906 100644 (file)
@@ -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)
 }
 \f
 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
index dcb793b..2dd6e0f 100644 (file)
@@ -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);