summaryrefslogtreecommitdiff
path: root/src/classify.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/classify.c')
-rw-r--r--src/classify.c437
1 files changed, 437 insertions, 0 deletions
diff --git a/src/classify.c b/src/classify.c
new file mode 100644
index 0000000..320cf4f
--- /dev/null
+++ b/src/classify.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
+ *
+ * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
+ * and others.
+ *
+ * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
+ * Portions Copyright (C) 1989-1992, Brian Berliner
+ *
+ * You may distribute under the terms of the GNU General Public License as
+ * specified in the README file that comes with the CVS source distribution.
+ *
+ */
+
+#include "cvs.h"
+
+static void sticky_ck (struct file_info *finfo, int aflag,
+ Vers_TS * vers);
+
+/*
+ * Classify the state of a file.
+ *
+ * INPUTS
+ * finfo Information about the file to be classified.
+ * tag
+ * date
+ * options Keyword expansion options. Can be either NULL or "" to
+ * indicate none are specified here.
+ * force_tag_match
+ * aflag
+ * versp
+ * pipeout Did the user pass the "pipeout" flag to request that
+ * all output go to STDOUT rather than to a file or files?
+ *
+ * RETURNS
+ * A Ctype (defined as an enum) describing the state of the file relative to
+ * the repository. See the definition of Ctype for more.
+ */
+Ctype
+Classify_File (struct file_info *finfo, char *tag, char *date, char *options,
+ int force_tag_match, int aflag, Vers_TS **versp, int pipeout)
+{
+ Vers_TS *vers;
+ Ctype ret;
+
+ /* get all kinds of good data about the file */
+ vers = Version_TS (finfo, options, tag, date,
+ force_tag_match, 0);
+
+ if (vers->vn_user == NULL)
+ {
+ /* No entry available, ts_rcs is invalid */
+ if (vers->vn_rcs == NULL)
+ {
+ /* there is no RCS file either */
+ if (vers->ts_user == NULL)
+ {
+ /* there is no user file */
+ /* FIXME: Why do we skip this message if vers->tag or
+ vers->date is set? It causes "cvs update -r tag98 foo"
+ to silently do nothing, which is seriously confusing
+ behavior. "cvs update foo" gives this message, which
+ is what I would expect. */
+ if (!force_tag_match || !(vers->tag || vers->date))
+ if (!really_quiet)
+ error (0, 0, "nothing known about `%s'",
+ finfo->fullname);
+ ret = T_UNKNOWN;
+ }
+ else
+ {
+ /* there is a user file */
+ /* FIXME: Why do we skip this message if vers->tag or
+ vers->date is set? It causes "cvs update -r tag98 foo"
+ to silently do nothing, which is seriously confusing
+ behavior. "cvs update foo" gives this message, which
+ is what I would expect. */
+ if (!force_tag_match || !(vers->tag || vers->date))
+ if (!really_quiet)
+ error (0, 0, "use `%s add' to create an entry for `%s'",
+ program_name, finfo->fullname);
+ ret = T_UNKNOWN;
+ }
+ }
+ else if (RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+ /* there is an RCS file, but it's dead */
+ if (vers->ts_user == NULL)
+ ret = T_UPTODATE;
+ else
+ {
+ error (0, 0, "use `%s add' to create an entry for `%s'",
+ program_name, finfo->fullname);
+ ret = T_UNKNOWN;
+ }
+ }
+ else if (!pipeout && vers->ts_user && No_Difference (finfo, vers))
+ {
+ /* the files were different so it is a conflict */
+ if (!really_quiet)
+ error (0, 0, "move away `%s'; it is in the way",
+ finfo->fullname);
+ ret = T_CONFLICT;
+ }
+ else
+ /* no user file or no difference, just checkout */
+ ret = T_CHECKOUT;
+ }
+ else if (strcmp (vers->vn_user, "0") == 0)
+ {
+ /* An entry for a new-born file; ts_rcs is dummy */
+
+ if (vers->ts_user == NULL)
+ {
+ if (pipeout)
+ {
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+ /*
+ * There is no user file, but there should be one; remove the
+ * entry
+ */
+ if (!really_quiet)
+ error (0, 0, "warning: new-born `%s' has disappeared",
+ finfo->fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ }
+ else if (vers->vn_rcs == NULL ||
+ RCS_isdead (vers->srcfile, vers->vn_rcs))
+ /* No RCS file or RCS file revision is dead */
+ ret = T_ADDED;
+ else
+ {
+ if (pipeout)
+ {
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+ if (vers->srcfile->flags & INATTIC
+ && vers->srcfile->flags & VALID)
+ {
+ /* This file has been added on some branch other than
+ the one we are looking at. In the branch we are
+ looking at, the file was already valid. */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: `%s' has been added, but already exists",
+ finfo->fullname);
+ }
+ else
+ {
+ /*
+ * There is an RCS file, so someone else must have checked
+ * one in behind our back; conflict
+ */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: `%s' created independently by"
+ " second party",
+ finfo->fullname);
+ }
+ ret = T_CONFLICT;
+ }
+ }
+ }
+ else if (vers->vn_user[0] == '-')
+ {
+ /* An entry for a removed file, ts_rcs is invalid */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file (as it should be) */
+
+ if (vers->vn_rcs == NULL
+ || RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+
+ /*
+ * There is no RCS file; this is all-right, but it has been
+ * removed independently by a second party; remove the entry
+ */
+ ret = T_REMOVE_ENTRY;
+ }
+ else if (strcmp (vers->vn_rcs, vers->vn_user + 1) == 0)
+ /*
+ * The RCS file is the same version as the user file was, and
+ * that's OK; remove it
+ */
+ ret = T_REMOVED;
+ else if (pipeout)
+ /*
+ * The RCS file doesn't match the user's file, but it doesn't
+ * matter in this case
+ */
+ ret = T_NEEDS_MERGE;
+ else
+ {
+
+ /*
+ * The RCS file is a newer version than the removed user file
+ * and this is definitely not OK; make it a conflict.
+ */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: removed `%s' was modified by"
+ " second party",
+ finfo->fullname);
+ ret = T_CONFLICT;
+ }
+ }
+ else
+ {
+ /* The user file shouldn't be there */
+ if (!really_quiet)
+ error (0, 0, "`%s' should be removed and is still there",
+ finfo->fullname);
+ ret = T_REMOVED;
+ }
+ }
+ else
+ {
+ /* A normal entry, TS_Rcs is valid */
+ if (vers->vn_rcs == NULL || RCS_isdead (vers->srcfile, vers->vn_rcs))
+ {
+ /* There is no RCS file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file, so just remove the entry */
+ if (!really_quiet)
+ error (0, 0, "warning: `%s' is not (any longer) pertinent",
+ finfo->fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs)
+ && No_Difference (finfo, vers))
+ {
+ /* they are different -> conflict */
+ if (!really_quiet)
+ error (0, 0,
+ "conflict: `%s' is modified but no longer in the"
+ " repository",
+ finfo->fullname);
+ ret = T_CONFLICT;
+ }
+ else
+ {
+
+ /*
+ * The user file is still unmodified, so just remove it from
+ * the entry list
+ */
+ if (!really_quiet)
+ error (0, 0, "`%s' is no longer in the repository",
+ finfo->fullname);
+ ret = T_REMOVE_ENTRY;
+ }
+ }
+ else if (strcmp (vers->vn_rcs, vers->vn_user) == 0)
+ {
+ /* The RCS file is the same version as the user file */
+
+ if (vers->ts_user == NULL)
+ {
+
+ /*
+ * There is no user file, so note that it was lost and
+ * extract a new version
+ */
+ /* Comparing the cvs_cmd_name against "update", in
+ addition to being an ugly way to operate, means
+ that this message does not get printed by the
+ server. That might be considered just a straight
+ bug, although there is one subtlety: that case also
+ gets hit when a patch fails and the client fetches
+ a file. I'm not sure there is currently any way
+ for the server to distinguish those two cases. */
+ if (strcmp (cvs_cmd_name, "update") == 0)
+ if (!really_quiet)
+ error (0, 0, "warning: `%s' was lost", finfo->fullname);
+ ret = T_CHECKOUT;
+ }
+ else if (!strcmp (vers->ts_user,
+ vers->ts_conflict
+ ? vers->ts_conflict : vers->ts_rcs))
+ {
+
+ /*
+ * The user file is still unmodified, so nothing special at
+ * all to do -- no lists updated, unless the sticky -k option
+ * has changed. If the sticky tag has changed, we just need
+ * to re-register the entry
+ */
+ /* TODO: decide whether we need to check file permissions
+ for a mismatch, and return T_CONFLICT if so. */
+ if (vers->entdata->options &&
+ strcmp (vers->entdata->options, vers->options) != 0)
+ ret = T_CHECKOUT;
+ else if (vers->ts_conflict)
+ ret = T_CONFLICT;
+ else
+ {
+ sticky_ck (finfo, aflag, vers);
+ ret = T_UPTODATE;
+ }
+ }
+ else if (No_Difference (finfo, vers))
+ {
+
+ /*
+ * they really are different; modified if we aren't
+ * changing any sticky -k options, else needs merge
+ */
+#ifdef XXX_FIXME_WHEN_RCSMERGE_IS_FIXED
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) == 0)
+ ret = T_MODIFIED;
+ else
+ ret = T_NEEDS_MERGE;
+#else
+ /* Files with conflict markers and new timestamps fall through
+ * here, but they need to. T_CONFLICT is an error in
+ * commit_fileproc, whereas T_MODIFIED with conflict markers
+ * is caught but only warned about. Similarly, update_fileproc
+ * currently reregisters a file that was conflicted but lost
+ * its markers.
+ */
+ ret = T_MODIFIED;
+ sticky_ck (finfo, aflag, vers);
+#endif
+ }
+ else if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) != 0)
+ {
+ /* file has not changed; check out if -k changed */
+ ret = T_CHECKOUT;
+ }
+ else
+ {
+
+ /*
+ * else -> note that No_Difference will Register the
+ * file already for us, using the new tag/date. This
+ * is the desired behaviour
+ */
+ ret = T_UPTODATE;
+ }
+ }
+ else
+ {
+ /* The RCS file is a newer version than the user file */
+
+ if (vers->ts_user == NULL)
+ {
+ /* There is no user file, so just get it */
+
+ /* See comment at other "update" compare, for more
+ thoughts on this comparison. */
+ if (strcmp (cvs_cmd_name, "update") == 0)
+ if (!really_quiet)
+ error (0, 0, "warning: `%s' was lost", finfo->fullname);
+ ret = T_CHECKOUT;
+ }
+ else if (strcmp (vers->ts_user, vers->ts_rcs) == 0)
+ {
+
+ /*
+ * The user file is still unmodified, so just get it as well
+ */
+ if (strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options) != 0
+ || (vers->srcfile != NULL
+ && (vers->srcfile->flags & INATTIC) != 0))
+ ret = T_CHECKOUT;
+ else
+ ret = T_PATCH;
+ }
+ else if (No_Difference (finfo, vers))
+ /* really modified, needs to merge */
+ ret = T_NEEDS_MERGE;
+ else if ((strcmp (vers->entdata->options ?
+ vers->entdata->options : "", vers->options)
+ != 0)
+ || (vers->srcfile != NULL
+ && (vers->srcfile->flags & INATTIC) != 0))
+ /* not really modified, check it out */
+ ret = T_CHECKOUT;
+ else
+ ret = T_PATCH;
+ }
+ }
+
+ /* free up the vers struct, or just return it */
+ if (versp != NULL)
+ *versp = vers;
+ else
+ freevers_ts (&vers);
+
+ /* return the status of the file */
+ return (ret);
+}
+
+static void
+sticky_ck (struct file_info *finfo, int aflag, Vers_TS *vers)
+{
+ if (aflag || vers->tag || vers->date)
+ {
+ char *enttag = vers->entdata->tag;
+ char *entdate = vers->entdata->date;
+
+ if ((enttag && vers->tag && strcmp (enttag, vers->tag)) ||
+ ((enttag && !vers->tag) || (!enttag && vers->tag)) ||
+ (entdate && vers->date && strcmp (entdate, vers->date)) ||
+ ((entdate && !vers->date) || (!entdate && vers->date)))
+ {
+ Register (finfo->entries, finfo->file, vers->vn_user, vers->ts_rcs,
+ vers->options, vers->tag, vers->date, vers->ts_conflict);
+
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ {
+ /* We need to update the entries line on the client side.
+ It is possible we will later update it again via
+ server_updated or some such, but that is OK. */
+ server_update_entries
+ (finfo->file, finfo->update_dir, finfo->repository,
+ strcmp (vers->ts_rcs, vers->ts_user) == 0 ?
+ SERVER_UPDATED : SERVER_MERGED);
+ }
+#endif
+ }
+ }
+}