#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,
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;
}
}
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')
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;
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
*/
char *xwhere = NULL;
char *oldupdate = NULL;
char *prepath;
- char *realdirs;
/*
* OK, so we're doing the checkout! Our args are as follows:
*/
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
* 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);
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);
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
{
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;
}
static int aflag;
static char *tag;
static char *write_dirtag;
+static int write_dirnonbranch;
static char *logfile;
static List *mulist;
static char *message;
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,
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;
#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)
{
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,
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 ();
/*
* 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,
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
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);
/* 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)
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. */
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
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). */
/* 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));
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
/* 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;
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;
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.
*/
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)
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)
{
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)
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)
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:
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 &
static struct an_entry *entries;
+static void serve_unchanged PROTO ((char *));
+
static void
serve_unchanged (arg)
char *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;
}
\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;
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
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),
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
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));
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;
/* 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)
{
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
{
(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;
/* 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;
}
}
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)
{
/*
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)
{
{
/* 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);
}
}
/* 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 */
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)
{
}
#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.
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;
/* 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;
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)
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)
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
/* 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);