Logo Search packages:      
Sourcecode: fhist version File versions

main.c

/*
 *    fhist - file history and comparison tools
 *    Copyright (C) 1991-1994, 1998-2002 Peter Miller;
 *    All rights reserved.
 *
 *    Derived from a work
 *    Copyright (C) 1990 David I. Bell.
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: operating system entry point, and parse arguments
 *
 * Program to perform source control operations for modules.
 */

#include <ac/ctype.h>
#include <sys/types.h>
#include <ac/stdio.h>
#include <ac/errno.h>
#include <setjmp.h>
#include <ac/dirent.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/unistd.h>
#include <sys/types.h>
#include <sys/stat.h> /* for mkdir */

#include <arglex.h>
#include <breaks.h>
#include <cmalloc.h>
#include <compare.h>
#include <diff.h>
#include <error_intl.h>
#include <extract.h>
#include <fhist.h>
#include <help.h>
#include <isdir.h>
#include <list.h>
#include <modlin.h>
#include <name.h>
#include <progname.h>
#include <prune.h>
#include <quit.h>
#include <str.h>
#include <subroutine.h>
#include <trace.h>
#include <update.h>
#include <version.h>


/*
 * Actions to be performed for modules
 */
#define A_CREATE  0x0001 /* want to create */
#define A_UPDATE  0x0002 /* want to update */
#define A_EXTRACT 0x0004 /* want to extract */
#define A_LIST          0x0008 /* want to list changes */
#define A_DIFF          0x0010 /* want to list differences */
#define A_TYPE          0x0020 /* want to type output */
#define A_NAME          0x0080 /* want to set edit name */
#define A_CLEAN         0x0100 /* clean away up to date copy of module */
#define A_CONDUPDATE    0x0200 /* do update if there are differences */
#define A_CHECK         0x0400 /* check to see if a file is up to date */
#define A_PRUNE         0x0800 /* prune old edits from edithistory */


/*
 * Definitions of which actions accept various extra arguments
 */
#define REMARK_ACTIONS  (A_CREATE | A_UPDATE | A_CONDUPDATE)
#define NAME_ACTIONS    (REMARK_ACTIONS | A_NAME)
#define INPUT_ACTIONS   (REMARK_ACTIONS | A_DIFF | A_CHECK | A_CLEAN)
#define OUTPUT_ACTIONS  (A_EXTRACT | A_LIST | A_DIFF)


SC_DATA           sc;         /* storage for sc program */


enum
{
    arglex_token_all,
    arglex_token_binary,
    arglex_token_check,
    arglex_token_clean,
    arglex_token_conditional_update,
    arglex_token_create,
    arglex_token_debug,
    arglex_token_difference,
    arglex_token_difference_update,
    arglex_token_extract,
    arglex_token_force_writing,
    arglex_token_forced_update,
    arglex_token_input,
    arglex_token_list,
    arglex_token_make_path,
    arglex_token_modified,
    arglex_token_nname,
    arglex_token_no_write,
    arglex_token_output,
    arglex_token_path,
    arglex_token_prune,
    arglex_token_quick,
    arglex_token_remark,
    arglex_token_terminal,
    arglex_token_trace,
    arglex_token_update,
    arglex_token_verbose,
    arglex_token_what,
    arglex_token_keywords_not,
};

static arglex_table_ty argtab[] =
{
    {"-All", arglex_token_all, },
    {"-BINary", arglex_token_binary, },
    {"-CLean", arglex_token_clean, },
    {"-CReate", arglex_token_create, },
    {"-Check", arglex_token_check, },
    {"-Conditional_Update", arglex_token_conditional_update, },
    {"-DEbug", arglex_token_debug, },
    {"-Difference", arglex_token_difference, },
    {"-Difference_Update", arglex_token_difference_update, },
    {"-Extract", arglex_token_extract, },
    {"-Force_Writing", arglex_token_force_writing, },
    {"-Forced_Update", arglex_token_forced_update, },
    {"-Input", arglex_token_input, },
    {"-List", arglex_token_list, },
    {"-MaKe_Path", arglex_token_make_path, },
    {"-Modify", arglex_token_modified, },
    {"-Name", arglex_token_nname, },
    {"-No_Keywords", arglex_token_keywords_not, },
    {"-No_Write", arglex_token_no_write, },
    {"-Output", arglex_token_output, },
    {"-PRune", arglex_token_prune, },
    {"-Path", arglex_token_path, },
    {"-Quick", arglex_token_quick, },
    {"-Remark", arglex_token_remark, },
    {"-Terminal", arglex_token_terminal, },
    {"-TRace", arglex_token_trace, },
    {"-Update", arglex_token_update, },
    {"-Verbose", arglex_token_verbose, },
    {"-What", arglex_token_what, },
    {0, 0, }, /* end marker */
};


static void
usage(void)
{
    char        *progname;

    progname = progname_get();
    fprintf(stderr, "usage: %s <filename> <option>...\n", progname);
    fprintf(stderr, "       %s -Help\n", progname);
    fprintf(stderr, "       %s -VERSion\n", progname);
    quit(1);
}


static void
main_help(void)
{
    help(NULL, usage);
}


/*
 * Routine called after errors to restart the program for the next file.
 * This reenables breaks, closes all files, frees all allocated memory,
 * resets static variables, and then possibly longjmps back to the top
 * level code.
 */

static void
restart(int jumpflag)
{
    int               i;

    breakson();
    cm_reset();
    fcompreset();
    editreset();
    sc.modifybuffer = NULL;
    sc.firstedit = 0;
    sc.lastedit = 0;
    sc.tablepos = 0;
    for (i = 3; i < 10; i++)
      close(i);
}


/*
 * Perform the specified action for a particular module.
 * If the wildcard flag is 1, then any input or output name is a
 * directory which needs to have the module name appended to it.
 */

static void
domodule(int action, char *modulename, char *inputname, char *outputname,
    char *editname, char *editstring, char *editstring2, int wildcard)
{
    int               module_max;
    char        fullinputname[1024]; /* full input name if a dir */
    char        fulloutputname[1024]; /* full output name if a dir */

    trace(("domodule(name = \"%s\")\n{\n", modulename));
    restart(0);
    if (strchr(modulename, PATH_STR[0]))
    {
      sub_context_ty    *scp;

      scp = sub_context_new();
      sub_var_set_charstar(scp, "Module", modulename);
      fatal_intl
      (
          scp,
          i18n("module name \"$module\" cannot contain path separators")
      );
      /* NOTREACHED */
      sub_context_delete(scp);
    }
    module_max = pathconf_name_max(sc.dirname) - 2;
    if (strlen(modulename) > module_max)
    {
      sub_context_ty    *scp;

      scp = sub_context_new();
      sub_var_set_charstar(scp, "Module", modulename);
      fatal_intl(scp, i18n("module name \"$module\" is too long"));
      /* NOTREACHED */
      sub_context_delete(scp);
    }
    if (inputname && wildcard)
    {
      strcpy(fullinputname, inputname);
      strcat(fullinputname, PATH_STR);
      strcat(fullinputname, modulename);
      inputname = fullinputname;
    }
    if (outputname && wildcard)
    {
      strcpy(fulloutputname, outputname);
      strcat(fulloutputname, PATH_STR);
      strcat(fulloutputname, modulename);
      outputname = fulloutputname;
    }
    sc.modulename = modulename;
    strcpy(sc.basename, sc.dirname);
    strcat(sc.basename, PATH_STR);
    strcat(sc.basename, modulename);
    trace_string(sc.basename);
    strcpy(sc.sourcename, sc.basename);
    strcat(sc.sourcename, EXT_SOURCE);
    trace_string(sc.sourcename);
    strcpy(sc.historyname, sc.basename);
    strcat(sc.historyname, EXT_HISTORY);
    trace_string(sc.historyname);

    switch (action)
    {
    case A_EXTRACT:
      trace(("extract\n"));
      if (outputname == NULL)
          outputname = modulename;
      extracthistory(editstring, outputname, (fc.verbosity != 0));
      break;

    case A_CREATE:
      trace(("create\n"));
      if (inputname == NULL)
          inputname = modulename;
      createhistory(inputname, editname);
      break;

    case A_DIFF:
      if (inputname && editstring2)
      {
          fatal_intl(0, i18n("conflicting options for differences"));
      }
      if (inputname == NULL)
          inputname = modulename;
      diffhistory(inputname, outputname, editstring, editstring2, 1);
      break;

    case A_DIFF | A_UPDATE:
      if (inputname == NULL)
          inputname = modulename;
      diffhistory(inputname, outputname, editstring, editstring2, 0);
      if ((fc.inserts == 0) && (fc.deletes == 0))
      {
          if (fc.verbosity)
          {
            sub_context_ty    *scp;

            scp = sub_context_new();
            sub_var_set_charstar(scp, "Module", modulename);
            error_intl
            (
                scp,
                i18n("module \"$module\" does not need updating")
            );
            sub_context_delete(scp);
          }
          break;
      }
      printf("\n");
      updatehistory(inputname, editname);
      break;

    case A_CREATE | A_CONDUPDATE:
      trace(("creaye | condupdate\n"));
      if (inputname == NULL)
          inputname = modulename;
      if (!history_file_exists())
      {
          createhistory(inputname, editname);
          break;
      }
      /* fall through... */

    case A_CONDUPDATE:
      trace(("condupdate\n"));
      if (inputname == NULL)
          inputname = modulename;
      comphistory(inputname);
      if ((fc.inserts == 0) && (fc.deletes == 0))
      {
          if (fc.verbosity)
          {
            sub_context_ty    *scp;

            scp = sub_context_new();
            sub_var_set_charstar(scp, "Module", modulename);
            error_intl
            (
                scp,
                i18n("module \"$module\" does not need updating")
            );
            sub_context_delete(scp);
          }
          break;
      }
      updatehistory(inputname, editname);
      break;

    case A_CREATE | A_UPDATE:
      trace(("create | update\n"));
      if (inputname == NULL)
          inputname = modulename;
      if (!history_file_exists())
      {
          createhistory(inputname, editname);
          break;
      }
      /* fall through... */

    case A_UPDATE:
      trace(("update\n"));
      if (inputname == NULL)
          inputname = modulename;
      updatehistory(inputname, editname);
      break;

    case A_LIST:
      trace(("list\n"));
      listhistory(editstring, editstring2, outputname);
      break;

    case A_NAME:
      trace(("name\n"));
      namehistory(editname);
      break;

    case A_TYPE:
      trace(("type\n"));
      extracthistory(editstring, outputname, VERBOSE_NONE);
      break;

    case A_CLEAN:
      trace(("clean\n"));
      if (inputname == NULL)
          inputname = modulename;
      cleanhistory(inputname);
      break;

    case A_CHECK:
      trace(("check\n"));
      if (inputname == NULL)
          inputname = modulename;
      checkhistory(inputname);
      break;

    case A_PRUNE:
      trace(("prune\n"));
      prunehistory(editstring);
      break;
    }
    trace(("}\n"));
}


/*
 * Perform the specified action for all modules in the edit history directory.
 */

static void
doallmodules(int action, char *inputname, char *outputname, char *editstring,
    char *editstring2, char *editname)
{
    char        *modulename;  /* current file name */
    DIR               *dirp;        /* directory handle */
    struct dirent   *dp;            /* current directory entry */
    int               len;          /* length of filename */
    char        *buffer;            /* buffer for name storage */
    unsigned long   bufsize;        /* current size of buffer */
    long        bufindex;           /* index into buffer */

    bufindex = 0;
    bufsize = 1000;
    buffer = r_alloc_and_check(bufsize);
    dirp = opendir(sc.dirname);
    if (dirp == NULL)
    {
      sub_context_ty    *scp;

      scp = sub_context_new();
      sub_errno_set(scp);
      sub_var_set_charstar(scp, "File_Name", sc.dirname);
      fatal_intl(scp, i18n("opendir $filename: $errno"));
      /* NOTREACHED */
      sub_context_delete(scp);
    }
    while ((dp = readdir(dirp)) != 0)
    {
      modulename = dp->d_name;
      len = strlen(modulename) - 2;
      if (len <= 0)
          continue;
      if ((modulename[len] != '.') || (modulename[len + 1] != 'e'))
          continue;
      modulename[len] = '\0';
      if ((bufindex + len + 2) > bufsize)
      {
          bufsize += 1000;
          buffer = r_realloc_and_check(buffer, bufsize);
      }
      strcpy(&buffer[bufindex], modulename);
      bufindex += (len + 1);
    }
    closedir(dirp);
    buffer[bufindex] = '\0';

    modulename = buffer;
    while (*modulename)
    {
      domodule
      (
          action,
          modulename,
          inputname,
          outputname,
          editname,
          editstring,
          editstring2,
          1
      );
      modulename += (strlen(modulename) + 1);
    }
}


/*
 * Routine to read an optional edit number string from the command line.
 * Returns the string converted to lower case, or NULL if one is not there.
 */

static char *
geteditstring(void)
{
    char        *str;
    char        *cp;

    switch (arglex_token)
    {
    default:
      str = 0;
      break;

    case arglex_token_number:
      str = arglex_value.alv_string;
      arglex();
      break;

    case arglex_token_string:
      str = arglex_value.alv_string;
      for (cp = str; *cp; ++cp)
          if (isupper((unsigned char)*cp))
            *cp = tolower((unsigned char)*cp);
          arglex();
          break;
      }
    return str;
}


/*
 * Routine to read an optional non-negative number from the command line.
 * Returns the specified default value if no number is present.
 */

static long
getvalue(long value, arglex_token_ty name)
{
    long        result;

    if (arglex_token != arglex_token_number)
      result = value;
    else
    {
      result = arglex_value.alv_number;
      arglex();
      if (result < 0)
      {
          sub_context_ty  *scp;

          scp = sub_context_new();
          sub_var_set_charstar(scp, "Name", arglex_token_name(name));
          sub_var_set_long(scp, "Number", result);
          sub_var_optional(scp, "Number");
          fatal_intl
          (
            scp,
            i18n("the $name option needs a non-negative argument")
          );
          /* NOTREACHED */
          sub_context_delete(scp);
      }
    }
    return result;
}


/*
 * Expand a global variable name.
 * Returns original argument if string is not a global variable,
 * or returns copied value of global variable.
 */

static char *
expand(char *cp)
{
    char        *var;   /* variable value */

    if ((cp == NULL) || (*cp != '.'))
      return cp;
    var = getenv(cp);
    if ((var == NULL) || (*var == '\0'))
      return cp;
    cp = r_alloc_and_check(strlen(var) + 1);
    strcpy(cp, var);
    return cp;
}


static void
mkdir_minus_p(char *path)
{
    char        *slash;

    slash = strrchr(path, '/');
    if (slash && slash > path)
    {
      string_ty   *tmp;

      tmp = str_n_from_c(path, slash - path);
      mkdir_minus_p(tmp->str_text);
      str_free(tmp);
    }

    /*
     * Ignore any error codes coming back.  (The commonest will be
     * EEXISTS, which we won't be complaining about anyway.)  When we
     * try to access the directory contents, some other fatal error
     * will occur, so the user foinds out anyway.
     */
    mkdir(path, 0777);
}


int
main(int argc, char **argv)
{
    char        *outputname;  /* output name for extraction */
    char        *inputname;         /* input name for update */
    char        *editname;          /* name for this edit */
    char        *editstring;  /* edit number string */
    char        *editstring2; /* second edit number string */
    int               modulecount;  /* number of modules */
    int               curmod;       /* current module number */
    int               action;       /* action to be performed */
    int               allflag;            /* 1 if want to do all files */
    char        *modules[MAXMODULES]; /* list of modules */
    int               make_path;

    arglex_init(argc, argv, argtab);
    switch (arglex())
    {
    case arglex_token_help:
      main_help();
      quit(0);

    case arglex_token_version:
      version();
      quit(0);

    default:
      break;
    }
    modifyline_register(modifyline);
    fc.debugflag = 0;
    fc.quickflag = 0;
    fc.verbosity = VERBOSE_DEFAULT;
    fc.binary = 0;
    sc.nowriteflag = 0;
    sc.forcewriteflag = 0;
    sc.forceupdateflag = 0;
    sc.modifylines = MODIFY_LINES;
    sc.firstedit = 0;
    sc.lastedit = 0;
    sc.tablepos = 0;
    sc.dirname = DEFAULTPATH;
    sc.modulename = NULL;
    make_path = 0;
    allflag = 0;
    modulecount = 0;
    action = 0;
    outputname = NULL;
    inputname = NULL;
    editname = NULL;
    editstring = NULL;
    editstring2 = NULL;
    sc.remarkname = NULL;

    while (arglex_token != arglex_token_eoln)
    {
      switch (arglex_token)
      {
      default:
          bad_argument(usage);
          /* NOTREACHED */

      case arglex_token_string:
          if (modulecount >= MAXMODULES)
          {
            fatal_intl(0, i18n("too many modules specified"));
            /* NOTREACHED */
          }
          modules[modulecount++] = arglex_value.alv_string;
          break;

      case arglex_token_difference:
          /* generate differences */
          action |= A_DIFF;
          arglex();
          editstring = geteditstring();
          editstring2 = geteditstring();
          continue;

      case arglex_token_extract:
          /* extract edit */
          action |= A_EXTRACT;
          arglex();
          editstring = geteditstring();
          continue;

      case arglex_token_input:
          /* input file */
          if (arglex() != arglex_token_string)
          {
            error_intl(0, i18n("no input file supplied"));
            usage();
          }
          inputname = expand(arglex_value.alv_string);
          break;

      case arglex_token_list:
          /* list edit */
          action |= A_LIST;
          arglex();
          editstring = geteditstring();
          editstring2 = geteditstring();
          continue;

      case arglex_token_modified:
          /* set number of modified lines */
          arglex();
          sc.modifylines =
            getvalue((long)MODIFY_LINES, arglex_token_modified);
          continue;

      case arglex_token_output:
          /* output file */
          if (arglex() != arglex_token_string)
          {
            error_intl(0, i18n("no output file supplied"));
            usage();
          }
          outputname = expand(arglex_value.alv_string);
          break;

      case arglex_token_make_path:
          make_path = 1;
          break;

      case arglex_token_path:
          /* directory path for edit histories */
          if (arglex() != arglex_token_string)
          {
            error_intl(0, i18n("no directory path supplied"));
            usage();
          }
          sc.dirname = expand(arglex_value.alv_string);
          break;

      case arglex_token_quick:
          /* be quick on comparison or list output */
          fc.quickflag = 1;
          break;

      case arglex_token_remark:
          /* remark file */
          if (arglex() != arglex_token_string)
          {
            sc.remarkname = "";
            continue;
          }
          sc.remarkname = expand(arglex_value.alv_string);
          break;

      case arglex_token_terminal:
          /* type edit to terminal */
          action |= A_TYPE;
          arglex();
          editstring = geteditstring();
          continue;

      case arglex_token_update:
          /* update history file */
          action |= A_UPDATE;
          break;

      case arglex_token_verbose:
          /* be verbose */
          arglex();
          fc.verbosity = getvalue((long)VERBOSE_FULL, arglex_token_verbose);
          continue;

      case arglex_token_what:
          /* descripe what changes are */
          fc.whatflag = 1;
          break;

      case arglex_token_debug:
          /* debugging enabled */
          fc.debugflag = 1;
          break;

      case arglex_token_create:
          /* create new module */
          action |= A_CREATE;
          break;

      case arglex_token_difference_update:
          /* do difference and update */
          action |= A_DIFF | A_UPDATE;
          break;

      case arglex_token_conditional_update:
          /* update if differences */
          action |= A_CONDUPDATE;
          break;

      case arglex_token_clean:
          /* clean up to date file */
          action |= A_CLEAN;
          break;

      case arglex_token_check:
          /* see if file is up to date */
          action |= A_CHECK;
          break;

      case arglex_token_forced_update:
          /* force update */
          sc.forceupdateflag = 1;
          break;

      case arglex_token_force_writing:
          /* force writing */
          sc.forcewriteflag = 1;
          break;

      case arglex_token_all:
          /* do all files */
          allflag = 1;
          break;

      case arglex_token_nname:
          /* set edit name */
          if (arglex() != arglex_token_string)
          {
            error_intl(0, i18n("no edit name supplied"));
            usage();
          }
          editname = arglex_value.alv_string;
          checkeditname(editname);
          break;

      case arglex_token_no_write:
          /* don't overwrite */
          sc.nowriteflag = 1;
          break;

      case arglex_token_prune:
          /* prune old edits */
          action |= A_PRUNE;
          arglex();
          editstring = geteditstring();
          continue;

      case arglex_token_binary:
          fc.binary = 1;
          break;

      case arglex_token_keywords_not:
          sc.modifylines = 0;
          break;

      case arglex_token_trace:
          for (;;)
          {
#ifdef DEBUG
            trace_enable(arglex_value.alv_string);
#endif
            if (arglex() != arglex_token_string)
                break;
          }
#ifndef DEBUG
          error_intl(0, i18n("-TRace needs DEBUG"));
#endif
          continue;
      }
      arglex();
    }
    if (action == 0)
      action = (editname ? A_NAME : A_EXTRACT);
    if
    (
      action != (A_CREATE | A_CONDUPDATE)
    &&
      action != (A_CREATE | A_UPDATE)
    &&
      action != (A_DIFF | A_UPDATE)
    &&
      action != (action & -action)
    )
      fatal_intl(0, i18n("multiple actions specified"));
    if (sc.remarkname && ((action & REMARK_ACTIONS) == 0))
      fatal_intl(0, i18n("cannot use -Remark with specified action"));
    if (inputname && ((action & INPUT_ACTIONS) == 0))
      fatal_intl(0, i18n("cannot use -Input with specified action"));
    if (outputname && ((action & OUTPUT_ACTIONS) == 0))
      fatal_intl(0, i18n("cannot use -Output with specified action"));
    if (editname && ((action & NAME_ACTIONS) == 0))
      fatal_intl(0, i18n("cannot use -Name with specified action"));
    if (sc.forcewriteflag && sc.nowriteflag)
      fatal_intl(0, i18n("cannot specify both -Force_Write and -No_Write"));
    if (fc.quickflag && fc.whatflag)
      fatal_intl(0, i18n("cannot specify both -What and -Quick"));
    if ((modulecount <= 0) && (action == A_CHECK))
      allflag = 1;
    if ((modulecount <= 0) && !allflag)
      fatal_intl(0, i18n("no module name specified"));
    if (allflag)
    {
      if (modulecount)
          fatal_intl(0, i18n("cannot specify module names with -ALL"));
      if (inputname && !isdir(inputname))
          fatal_intl(0, i18n("input name must be a directory for -ALL"));
      if (outputname && !isdir(outputname))
          fatal_intl(0, i18n("output name must be a directory for -ALL"));
    }
    if (modulecount > 1)
    {
      if (inputname && !isdir(inputname))
      {
          fatal_intl
          (
            0,
            i18n("input name must be a directory for multiple modules")
          );
      }
      if (outputname && !isdir(outputname))
      {
          fatal_intl
          (
            0,
            i18n("output name must be a directory for multiple modules")
          );
      }
    }

    if (make_path)
      mkdir_minus_p(sc.dirname);
    if (access(sc.dirname, R_OK) < 0)
    {
      sub_context_ty    *scp;
      int         what_happened;

      what_happened = errno;
      scp = sub_context_new();
      sub_var_set_charstar(scp, "File_Name", sc.dirname);
      if (what_happened == ENOENT)
      {
          fatal_intl
          (
            scp,
            i18n("directory \"$filename\" needs to be created")
          );
      }
      else
      {
          fatal_intl(scp, i18n("opendir $filename: $errno"));
      }
      /* NOTREACHED */
      sub_context_delete(scp);
    }

    if (allflag)
    {
      doallmodules
      (
          action,
          inputname,
          outputname,
          editstring,
          editstring2,
          editname
      );
      quit(0);
    }
    for (curmod = 0; curmod < modulecount; curmod++)
    {
      domodule
      (
          action,
          modules[curmod],
          inputname,
          outputname,
          editname,
          editstring,
          editstring2,
          (modulecount > 1)
      );
    }
    quit(0);
    return 0;
}

Generated by  Doxygen 1.6.0   Back to index