Logo Search packages:      
Sourcecode: fhist version File versions

extract.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: functions to extract versions of a file
 */

#include <ac/libintl.h>
#include <ac/stdio.h>
#include <ac/string.h>
#include <ac/unistd.h>

#include <cmalloc.h>
#include <compare.h>
#include <error_intl.h>
#include <extract.h>
#include <fcheck.h>
#include <fhist.h>
#include <fileio.h>
#include <modlin.h>
#include <output/file.h>
#include <output/stdout.h>
#include <output/quotprindeco.h>
#include <subroutine.h>


/*
 * States for examining history file.
 */
#define S_OUTSIDE 0           /* outside of an edit */
#define S_INSIDE 1            /* just inside of an edit */
#define S_CHANGE 2            /* saw changes in an edit */
#define S_FILE 3        /* saw file reference in an edit */


/*
 * In-memory edit history structure.
 */
typedef struct edit EDIT;
struct edit
{
      EDIT  *e_next;    /* next edit history structure in list */
      EDIT  *e_prev;    /* previous edit history structure in list */
      long  e_count;    /* line count */
      char  *e_text;    /* text line if any */
      long  e_textlen;  /* length of text line */
};


/*
 * Local storage.
 */
static EDIT *editfreelist;    /* free list for edit structures */
static EDIT *editnewbegin;    /* pointer to chunk of new edit structures */
static EDIT *editnewend;      /* pointer to end of new edit structures */
static EDIT edithead;         /* head of edit list */
static EDIT edittail;         /* tail of edit list */
static EDIT newedithead;      /* header of new edit list */
static EDIT newedittail;      /* tail of new edit list */
static long curline;          /* current line number */
static char fromsource;       /* dummy addr to indicate source file lines */


/*
 * Initialize an edit list.
 */

static void
initlist(EDIT *hp, EDIT *tp)
{
      hp->e_count = 0;
      hp->e_textlen = 0;
      hp->e_text = NULL;
      hp->e_prev = NULL;
      hp->e_next = tp;
      tp->e_count = 0x40000000;     /* large count of lines */
      tp->e_text = &fromsource;     /* all from the source file */
      tp->e_textlen = 0;
      tp->e_next = NULL;
      tp->e_prev = hp;
      curline = 1;
}


/*
 * Allocate a new edit structure and insert it in front of the
 * specified edit structure in a list.
 */

static EDIT *
allocedit(EDIT *ep)
{
      EDIT  *np;        /* new edit structure */

      np = editfreelist;
      if (np)
            editfreelist = np->e_next;
      else
      {
            if (editnewbegin == editnewend)
            {
                  np = (EDIT *)cm_alloc_and_check(sizeof(EDIT) * EDITALLOCSIZE);
                  editnewbegin = np;
                  editnewend = np + EDITALLOCSIZE;
            }
            np = editnewbegin++;
      }
      np->e_text = NULL;
      np->e_textlen = 0;
      np->e_count = 0;
      np->e_next = ep;
      np->e_prev = ep->e_prev;
      np->e_prev->e_next = np;
      ep->e_prev = np;
      return np;
}


/*
 * Append a change to the new edit list.
 */

static void
addtolist(FILE *fp, long line, long insertcount, long deletecount)
{
      EDIT  *ep;        /* pointer to current element structure */

      if (curline < line)
      {
            /* edit for reading data from source */
            ep = allocedit(&newedittail);
            ep->e_text = &fromsource;
            ep->e_count = line - curline;
            curline = line;
      }
      if (deletecount > 0)
      {
            /* edit for deleting from source */
            ep = allocedit(&newedittail);
            ep->e_count = deletecount;
            curline += deletecount;
      }
      while (--insertcount >= 0)
      {
            /* edits for inserting new text */
            ep = allocedit(&newedittail);
            ep->e_text = get_a_line(fp, NOSEEK, T_TEXT, sc.historyname);
            ep->e_textlen = sc.linelen;
            ep->e_count = 1;
      }
}


static void
printone(const char *str, EDIT *ep)
{
      char  *blab;

      blab = "insert";
      if (ep->e_text == NULL)
            blab = "delete";
      if (ep->e_text == &fromsource)
            blab = "source";
      fprintf
      (
            stderr,
            "  %s at %08lX: %s %ld\n",
            str,
            (unsigned long)ep,
            blab,
            ep->e_count
      );
}


/*
 * Print out an edit list
 */

static void
printlist(const  char *msg, EDIT *ep)
{
      long  line;
      char  *blab;

      fprintf(stderr, "%s\n", msg);
      line = 1;
      while (ep)
      {
            blab = "insert";
            if (ep->e_text == NULL)
                  blab = "delete";
            if (ep->e_text == &fromsource)
                  blab = "source";
            fprintf
            (
                  stderr,
                  "edit at %08lX: line %ld: %s %ld\n",
                  (unsigned long)ep,
                  line,
                  blab,
                  ep->e_count
            );
            if ((ep->e_text == NULL) || (ep->e_text == &fromsource))
                  line += ep->e_count;
            ep = ep->e_next;
      }
}


/*
 * Remove an edit from an edit list and put it onto the free list.
 * Returns the next edit structure on the list.
 */

static EDIT *
remedit(EDIT *ep)
{     
      EDIT  *np;        /* next edit structure in list */

      np = ep->e_prev;
      np->e_next = ep->e_next;
      np = ep->e_next;
      np->e_prev = ep->e_prev;
      ep->e_next = editfreelist;
      editfreelist = ep;
      return np;
}


/*
 * Routine to merge the current and new edit lists together to form
 * the new current edit list containing both changes.  This works by
 * conceptually walking through each 'line' and determining the result
 * of interactions of deleting, inserting, and copying from the source.
 */

static void
mergelists(void)
{
      EDIT  *op;        /* pointer to current old edit structure */
      EDIT  *np;        /* pointer to current new edit structure */
      long  count;            /* number of lines in common */
      int   selectvalue;      /* select value */

      op = edithead.e_next;
      np = newedithead.e_next;
      if (fc.debugflag)
      {
            printlist("old list", op);
            printlist("new list", np);
            fprintf(stderr, "MERGES:\n");
      }

      while (np != &newedittail)
      {
            count = np->e_count;
            if (op->e_count < count) count = op->e_count;
            selectvalue = 0;
            if (np->e_text == NULL) selectvalue = 1;
            if (np->e_text == &fromsource) selectvalue = 2;
            if (op->e_text == NULL) selectvalue += 3;
            if (op->e_text == &fromsource) selectvalue += 6;
            if (fc.debugflag)
            {
                  fprintf(stderr, "case %d:\n", selectvalue);
                  printone("op:", op);
                  printone("np:", np);
            }

            switch (selectvalue)
            {
            case 0:                 /* old inserts and new inserts */
            case 2:                 /* old inserts and new from source */
                  if (fc.debugflag)
                        printone("out:", op);
                  op = op->e_next;
                  break;

            case 1:                 /* old inserts and new deletes */
            case 4:                 /* old and new both delete */
            case 7:                 /* old from source and new deletes */
                  op = allocedit(op);
                  op->e_count = np->e_count;
                  if (fc.debugflag)
                        printone("out:", op);
                  op = op->e_next;
                  np = np->e_next;
                  break;

            case 3:                 /* old deletes and new inserts */
                  np->e_count -= count;
                  if (np->e_count <= 0)
                  {
                        cm_free(np->e_text - 2);      /* HACK */
                        np = np->e_next;
                  }
                  op->e_count--;
                  if (op->e_count <= 0)
                        op = remedit(op);
                  break;

            case 5:                 /* old deletes and new from source */
                  np->e_count -= count;
                  if (np->e_count <= 0)
                        np = np->e_next;
                  if (op->e_count == count)
                  {
                        if (fc.debugflag)
                        printone("out:", op);
                        op = op->e_next;
                        break;
                  }
                  op = allocedit(op);
                  op->e_count = count;
                  if (fc.debugflag)
                        printone("out:", op);
                  op = op->e_next;
                  op->e_count -= count;
                  break;

            case 6:                 /* old from source and new inserts */
                  op = allocedit(op);
                  op->e_text = np->e_text;
                  op->e_textlen = np->e_textlen;
                  op->e_count = 1;
                  if (fc.debugflag)
                        printone("out:", op);
                  op = op->e_next;
                  np = np->e_next;
                  if (op == &edittail)
                        break;
                  op->e_count--;
                  if (op->e_count <= 0)
                        op = remedit(op);
                  break;

            case 8:                 /* old and new both from source */
                  np->e_count -= count;
                  if (np->e_count <= 0)
                        np = np->e_next;
                  if (op->e_count <= count)
                  {
                        if (op != &edittail)
                        {
                              if (fc.debugflag)
                                    printone("out:", op);
                              op = op->e_next;
                        }
                        break;
                  }
                  op = allocedit(op);
                  op->e_text = &fromsource;
                  op->e_count = count;
                  if (fc.debugflag)
                        printone("out:", op);
                  op = op->e_next;
                  if (op != &edittail)
                        op->e_count -= count;
                  break;
            }
            if (fc.debugflag)
                  fprintf(stderr, "\n");
      }
      if (fc.debugflag)
      {
            printlist("Updated old list", edithead.e_next);
            fprintf(stderr, "\n");
      }
}


/*
 * Routine to collect edit scripts from the log file for a given edit.
 * This builds an in-memory edit list which can then be used to generate
 * the specified version of the source file.  The history file has already
 * been opened, the edit number checked, and the first line of the desired
 * edit sequence read and checked.
 */

static void
readedits(FILE *fp, long editnumber)
{
      char  *cp;        /* line of data */
      long  filenumber; /* file number containing whole edit */
      long  linenumber;
      long  insertcount;
      long  deletecount;
      long  inscc;
      long  delcc;
      long  linecc;
      long  temp;
      short more;
      short state;            /* state of current edit */
      int   bin;        /* IGNORED */

      insertcount = 0;
      deletecount = 0;
      state = S_INSIDE;
      more = 1;
      linenumber = 1;
      initlist(&edithead, &edittail);
      initlist(&newedithead, &newedittail);

      /*
       * Here for each line of the edit history file.
       * Switch on the type of each line, verifying that the
       * sequence of types is legitimate.  Convert each change
       * line into one or more edit structures.  After each
       * set of changes for an edit has been read, then merge
       * together the old and new edit lists to form the final
       * edit list.
       */
      while (more)
      {
            cp = readlinef(fp, (long *) NULL, 0, sc.historyname, &bin);
            if (cp == NULL)
                  fatal_intl(0, i18n("error reading edit"));
            switch (*cp++)
            {

            case T_BEGINEDIT: /* begin another edit */
                  cp = getnumber(cp, &temp);
                  if (cp == NULL)
                        fatal_intl(0, i18n("no number in begin edit"));
                  if (state != S_OUTSIDE)
                  {
                        sub_context_ty    *scp;

                        scp = sub_context_new();
                        sub_var_set_long(scp, "Number", temp);
                        fatal_intl
                        (
                              scp,
                  i18n("beginning edit $number without ending previous one")
                        );
                  }
                  state = S_INSIDE;
                  if (temp != editnumber + 1)
                        fatal_intl(0, i18n("non-consecutive begin edit"));
                  editnumber = temp;
                  insertcount = 0;
                  deletecount = 0;
                  linenumber = 1;
                  initlist(&newedithead, &newedittail);
                  break;

            case T_CHANGE:          /* change some lines */
                  if (state == S_OUTSIDE)
                        fatal_intl(0, i18n("change line outside of edit"));
                  if ((state != S_INSIDE) && (state != S_CHANGE))
                        fatal_intl(0, i18n("change line not expected in edit"));
                  state = S_CHANGE;
                  cp = getnumber(cp, &linecc);
                  if (cp == NULL)
                        fatal_intl(0, i18n("missing line number in change line"));
                  if (linecc < linenumber)
                        fatal_intl(0, i18n("decreasing line number in change line"));
                  cp = getnumber(cp, &inscc);
                  cp = getnumber(cp, &delcc);
                  if (cp == NULL)
                        fatal_intl(0, i18n("bad line counts in change line"));
                  if ((inscc == 0) && (delcc == 0))
                        fatal_intl(0, i18n("change line does nothing"));
                  addtolist(fp, linecc, inscc, delcc);
                  insertcount += inscc;
                  deletecount += delcc;
                  if (delcc == 0) delcc = 1;
                  linenumber = linecc + delcc;
                  break;

            case T_TEXT:            /* text line */
                  if (state != S_OUTSIDE)
                        fatal_intl(0, i18n("text line not within edit"));
                  fatal_intl(0, i18n("unexpected text line"));
                  break;

            case T_FILE:            /* get results from explicit file */
                  if (state == S_OUTSIDE)
                        fatal_intl(0, i18n("file line not inside edit"));
                  if (state != S_INSIDE)
                        fatal_intl(0, i18n("file line not expected in edit"));
                  state = S_FILE;
                  cp = getnumber(cp, &filenumber);
                  if (cp == NULL)
                        fatal_intl(0, i18n("missing file number in file line"));
                  /* NEEDS MORE WORK */
                  break;

            case T_ENDEDIT:         /* end of current edit */
                  if (state == S_OUTSIDE)
                        fatal_intl(0, i18n("ending edit not yet started"));
                  state = S_OUTSIDE;
                  cp = getnumber(cp, &temp);
                  if (cp == NULL)
                        fatal_intl(0, i18n("no edit number at end of edit"));
                  if (temp != editnumber)
                  {
                        sub_context_ty    *scp;

                        scp = sub_context_new();
                        sub_var_set_long(scp, "Number", editnumber);
                        fatal_intl
                        (
                              scp,
                          i18n("end of edit $number not correct number")
                        );
                  }
                  cp = getnumber(cp, &inscc);
                  cp = getnumber(cp, &delcc);
                  if (cp == NULL)
                        fatal_intl(0, i18n("bad total counts in end of edit line"));
                  if (inscc != insertcount)
                        fatal_intl(0, i18n("wrong insert count at end of edit"));
                  if (delcc != deletecount)
                        fatal_intl(0, i18n("wrong delete count at end of edit"));
                  more = (editnumber != sc.lastedit);
                  mergelists();
                  break;

            case T_REMARK:          /* remark line */
                  if (state == S_OUTSIDE)
                        fatal_intl(0, i18n("remark line outside of edit"));
                  break;

            case T_EOF:
                  fatal_intl(0, i18n("unexpected end of file line"));

            default:
                  fatal_intl(0, i18n("unexpected line in edit"));
            }
      }
      fclose_and_check(fp, sc.historyname);
}


/*
 * Write out the extracted file using the information in the edit list
 * and the current source file.
 *
 * The history (.e) file is binary, because we need to seek in it.
 * The source (.s) file is text, because we don't need to seek in it.
 * The input files are text, by definition.
 * The output files are text, by definition.
 */

static void
writefile(const char *outputname, INFO *info)
{
      FILE  *sf;        /* source file */
      output_ty *of;          /* output file */
      EDIT  *ep;        /* current edit */
      long  line;       /* current line number in source file */
      long  count;
      char  *cp;
      int   bin;

      line = 0;
      sf = opensourcefile();
      if (fc.binary)
      {
            of = output_file_binary_open(outputname);
            of = output_quoted_printable_decode(of, 1);
      }
      else
            of = output_file_text_open(outputname);

      /*
       * Loop over each of the edit structures applying them to the
       * source file to produce the output file.
       */
      for (ep = edithead.e_next; ep; ep = ep->e_next)
      {
            count = ep->e_count;

            /*
             * If we are deleting lines from the source file,
             * THEN just skip the required number of lines.
             */
            if (ep->e_text == NULL)
            {
                  skipf(sf, count, sc.sourcename);
                  continue;
            }

            /*
             * If we are inserting lines from the history file,
             * then insert them from the edit structure.
             */
            if (ep->e_text != &fromsource)
            {
                  if (count != 1)
                        fatal_intl(0, i18n("edit command for text has bad count value"));
                  cp = ep->e_text;
                  sc.linelen = ep->e_textlen;
                  if (++line <= sc.modifylines)
                        cp = modifyline(cp, &sc.linelen, info);
                  output_write(of, cp, sc.linelen);
                  continue;
            }

            /*
             * The final choice is preserving lines from the source file,
             * so copy the lines from the source file.
             */
            while (count-- > 0)
            {
                  bin = 0;
                  cp = readlinef(sf, &sc.linelen, 0, sc.sourcename, &bin);
                  if (cp == NULL)
                  {
                        if (ep != &edittail)
                              fatal_intl(0, i18n("premature end of file"));
                        output_delete(of);
                        return;
                  }
                  if (bin)
                  {
                        /*
                         * Because all should be encoded using
                         * quoted printable.
                         */
                        binary_fatal(sc.sourcename);
                  }
                  if (++line <= sc.modifylines)
                        cp = modifyline(cp, &sc.linelen, info);
                  output_write(of, cp, sc.linelen);
            }
      }
      fatal_intl(0, i18n("unexpected end of edit list"));
}


/*
 * Copy the data from one file to another.
 * This is used when extracting the latest version of a module.
 *
 * The history (.e) file is binary, because we need to seek in it.
 * The source (.s) file is text, because we don't need to seek in it.
 * The input files are text, by definition.
 * The output files are text, by definition.
 */

static void
copyfile(const char *outputname, INFO *info)
{
      FILE  *sf;        /* source file for copying */
      output_ty *of;          /* output file for copying */
      long  linelen;    /* length of line */
      long  line;       /* current line number */
      char  *cp;        /* current line of file */
      int   bin;        /* IGNORED */

      sf = opensourcefile();
      if (fc.binary)
      {
            of = output_file_binary_open(outputname);
            of = output_quoted_printable_decode(of, 1);
      }
      else
            of = output_file_text_open(outputname);
      for (line = 0; ; ++line)
      {
            cp = readlinef(sf, &linelen, 0, sc.sourcename, &bin);
            if (cp == NULL)
                  break;
            if (line < sc.modifylines)
                cp = modifyline(cp, &linelen, info);
            output_write(of, cp, linelen);
      }
      output_delete(of);
      fclose_and_check(sf, sc.sourcename);
}


/*
 * Extract a revision of a module using the edit history.
 */

void
extracthistory(char *editname, char *outputname, int blabflag)
{
      char  *str;       /* string being manipulated */
      long  editnumber; /* edit number being extracted */
      long  i;          /* temporary value */
      INFO  info;       /* information about edit */
      char  infoline[MAX_INFO]; /* first remark line containing info */
      char  editstr[16];      /* edit number string */
      FILE  *fp;

      fp = openhistoryfile(OHF_READ);
      editnumber = findeditnumber(fp, editname);

      /*
       * If there is an output file, then check to see if we are overwriting
       * an old file, and let the user know what is being extracted.
       */
      if (outputname)
      {
            if (sc.nowriteflag && (access(outputname, 0) == 0))
            {
                  if (fc.verbosity)
                  {
                        sub_context_ty    *scp;

                        scp = sub_context_new();
                        sub_var_set_charstar(scp, "File_Name", outputname);
                        sub_var_set_charstar(scp, "Module", sc.modulename);
                        error_intl
                        (
                              scp,
       i18n("existing file \"$filename\" not overwritten by module \"$module\"")
                        );
                        sub_context_delete(scp);
                  }
                  fclose_and_check(fp, sc.historyname);
                  return;
            }
            if (fc.verbosity || blabflag)
            {
                  sub_context_ty    *scp;

                  scp = sub_context_new();
                  sub_var_set_charstar(scp, "Module", sc.modulename);
                  sub_var_set_long(scp, "Number", editnumber);
                  sub_var_set_charstar(scp, "File_Name", outputname);
                  if (strcmp(outputname, sc.modulename))
                  {
                        if (editnumber == sc.firstedit)
                        {
                              error_intl
                              (
                                    scp,
i18n("extracting initial edit $number of module \"$module\" to file \
\"$filename\"")
                              );
                        }
                        else if (editnumber == sc.lastedit)
                        {
                              error_intl
                              (
                                    scp,
i18n("extracting latest edit $number of module \"$module\" to file \"$filename\"")
                              );
                        }
                        else
                        {
                              error_intl
                              (
                                    scp,
     i18n("extracting edit $number of module \"$module\" to file \"$filename\"")
                              );
                        }
                  }
                  else
                  {
                        sub_var_optional(scp, "File_Name");
                        if (editnumber == sc.firstedit)
                        {
                              error_intl
                              (
                                    scp,
               i18n("extracting initial edit $number of module \"$module\"")
                              );
                        }
                        else if (editnumber == sc.lastedit)
                        {
                              error_intl
                              (
                                    scp,
                i18n("extracting latest edit $number of module \"$module\"")
                              );
                        }
                        else
                        {
                              error_intl
                              (
                                    scp,
                     i18n("extracting edit $number of module \"$module\"")
                              );
                        }
                  }
                  sub_context_delete(scp);
            }
      }
      if (checknewfile(outputname))
      {
            fclose_and_check(fp, sc.historyname);
            return;
      }
      startedit(fp, editnumber, infoline);

      /*
       * Fill in the information structure for line modifying to use.
       */
      str = infoline;
      while (*str == ' ')
            str++;
      info.i_user = str;
      while (*str && (*str != ' '))
            str++;
      *str++ = '\0';
      while (*str == ' ')
            str++;

      /*
       * Parse and modify the date:
       * Incoming:  Sun Sep 16 01:03:52 1985\n
       * Outgoing:  16-Sep-85
       */
      str[1] = str[8];
      str[2] = str[9];
      str[3] = '-';
      str[7] = '-';
      str[8] = str[22];
      str[9] = str[23];
      str[10] = '\0';
      if (*++str == ' ')
            str++;
      info.i_date = str;
      str = &editstr[15];
      *str = '\0';
      i = editnumber;
      do
      {
            *(--str) = (i % 10) + '0';
            i /= 10;
      }
            while (i);
      info.i_edit = str;

      /*
       * If we are extracting the latest edit, then just copy it from
       * the source file.  Otherwise, do the work of applying edits
       * from the history file.
       */
      if (editnumber == sc.lastedit)
      {
            fclose_and_check(fp, sc.historyname);
            copyfile(outputname, &info);
            return;
      }
      readedits(fp, editnumber);
      writefile(outputname, &info);
}


/*
 * Reset editing data for new file.
 */

void
editreset(void)
{
      editfreelist = NULL;
      editnewbegin = NULL;
      editnewend = NULL;
}

Generated by  Doxygen 1.6.0   Back to index