/* patch-logs.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/pika-escaping-utils.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/dir-listing.h"
#include "tla/libfsutils/string-files.h"
#include "tla/libfsutils/file-contents.h"
#include "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libarch/arch.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/merge-points.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/patch-logs.h"



t_uchar *
arch_make_log_file (const t_uchar * tree_root,
                    const t_uchar * archive,
                    const t_uchar * version)
{
  t_uchar * log_name = 0;
  t_uchar * log_path = 0;

  invariant (arch_valid_archive_name (archive));
  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  /* The VI-user flame generator:
   */
  log_name = str_alloc_cat_many (0, "++log.", version, "--", archive, str_end);
  log_path = file_name_in_vicinity (0, tree_root, log_name);

  lim_free (0, log_name);
  return log_path;
}


t_uchar *
arch_make_log (const t_uchar * tree_root,
               const t_uchar * archive,
               const t_uchar * version)
{
  int errn;
  t_uchar * log_path = 0;
  int out_fd;

  log_path = arch_make_log_file (tree_root, archive, version);

  out_fd = vu_open (&errn, log_path, O_WRONLY | O_CREAT | O_EXCL, 0666);

  if (out_fd >= 0)
    {
      safe_printfmt (out_fd, "Summary: \nKeywords: \n\n");
      safe_close (out_fd);
    }
  else if (errn != EEXIST)
    {
      safe_printfmt (2, "arch_make_log: I/O error (%s) creating %s\n", errno_to_string (errn), log_path);
      exit (2);
    }

  return log_path;
}


t_uchar *
arch_auto_log_message (const t_uchar * tree_root,
                       const t_uchar * archive,
                       const t_uchar * version,
                       const t_uchar * summary,
                       const t_uchar * body)
{
  t_uchar * merge_summary = 0;
  t_uchar * answer = 0;

  merge_summary = arch_log_for_merge (tree_root, archive, version, 0);

  answer = str_alloc_cat_many (0,
                               "Summary: ", summary, "\n",
                               "Keywords: \n",
                               "\n",
                               body, (body ? "\n" : 0),
                               (body ? "\n" : 0),
                               merge_summary,
                               str_end);

  lim_free (0, merge_summary);
  return answer;
}


t_uchar *
arch_log_for_merge (const t_uchar * tree_root,
                    const t_uchar * archive,
                    const t_uchar * version,
                    int reverse)
{
  rel_table new_logs = rel_table_nil;
  int string_fd;

  new_logs = arch_new_in_version (tree_root, archive, version);

  if (reverse)
    arch_sort_table_by_name_field (1, new_logs, 0);

  string_fd = make_output_to_string_fd ();

  if (rel_n_records (new_logs))
    {
      int x;

      safe_printfmt (string_fd, "Patches applied:\n\n");

      for (x = 0; x < rel_n_records (new_logs); ++x)
        {
          t_uchar * archive = 0;
          t_uchar * revision = 0;
          t_uchar * log_file = 0;
          t_uchar * log = 0;
          assoc_table headers = 0;
          const t_uchar * summary;

          archive = arch_parse_package_name (arch_ret_archive, 0, rel_peek_str (new_logs, x, 0));
          revision = arch_parse_package_name (arch_ret_non_archive, 0, rel_peek_str (new_logs, x, 0));

          invariant (!!archive && !!revision);

          log_file = arch_log_file (tree_root, archive, revision);
          log = file_contents (log_file);

          arch_parse_log (0, &headers, 0, log);
          summary = assoc_get_str_taking (headers, rel_make_field_str ("summary"));

          safe_printfmt (string_fd, " * %s\n", rel_peek_str (new_logs, x, 0));
          safe_printfmt (string_fd, "   %s\n\n", (summary ? summary : (t_uchar *)"<no summary provided>"));

          lim_free (0, archive);
          lim_free (0, revision);
          lim_free (0, log_file);
          lim_free (0, log);
          free_assoc_table (headers);
        }
    }

  rel_free_table (new_logs);
  return string_fd_close (string_fd);
}


t_uchar *
arch_log_dir (const t_uchar * tree_root,
              const t_uchar * archive,
              const t_uchar * version)
{
  t_uchar * category;
  t_uchar * package;
  t_uchar * package_version;

  t_uchar * t;
  t_uchar * log_dir;

  invariant (arch_valid_archive_name (archive));
  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  category = arch_parse_package_name (arch_ret_category, 0, version);
  package = arch_parse_package_name (arch_ret_package, 0, version);
  package_version = arch_parse_package_name (arch_ret_package_version, 0, version);

  log_dir = arch_tree_ctl_dir (tree_root);
  t = file_name_in_vicinity (0, log_dir, category);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, package);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, package_version);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, archive);
  lim_free (0, log_dir);
  log_dir = t;
  t = file_name_in_vicinity (0, log_dir, "patch-log");
  lim_free (0, log_dir);
  log_dir = t;

  lim_free (0, category);
  lim_free (0, package);
  lim_free (0, package_version);

  return log_dir;
}


t_uchar *
arch_log_file (const t_uchar * tree_root,
               const t_uchar * archive,
               const t_uchar * revision)
{
  t_uchar * version;
  t_uchar * dir;
  t_uchar * lvl;
  t_uchar * file;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);

  dir = arch_log_dir (tree_root, archive, version);
  lvl = arch_parse_package_name (arch_ret_patch_level, 0, revision);
  file = file_name_in_vicinity (0, dir, lvl);

  lim_free (0, version);
  lim_free (0, dir);
  lim_free (0, lvl);
  return file;
}


t_uchar *
arch_log_contents (const t_uchar * tree_root,
                   const t_uchar * archive,
                   const t_uchar * revision)
{
  t_uchar * file = 0;
  t_uchar * answer = 0;

  if (!arch_tree_has_log (tree_root, archive, revision))
    return 0;

  file = arch_log_file (tree_root, archive, revision);
  answer = file_contents (file);
  lim_free (0, file);

  return answer;
}


int
arch_tree_has_log (const t_uchar * tree_root,
                   const t_uchar * archive,
                   const t_uchar * revision)
{
  t_uchar * file = 0;
  int answer;

  file = arch_log_file (tree_root, archive, revision);

  answer = !safe_access (file, F_OK);

  lim_free (0, file);

  return answer;
}


void
arch_add_log_version (const t_uchar * tree_root,
                      const t_uchar * archive,
                      const t_uchar * version)
{
  t_uchar * log_dir;

  log_dir = arch_log_dir (tree_root, archive, version);
  ensure_directory_exists (log_dir);

  lim_free (0, log_dir);
}


void
arch_remove_log_version (const t_uchar * tree_root,
                         const t_uchar * archive,
                         const t_uchar * version)
{
  t_uchar * log_dir;

  log_dir = arch_log_dir (tree_root, archive, version);
  rmrf_file (log_dir);

  lim_free (0, log_dir);
}


int
arch_has_patch_log (const t_uchar * tree_root,
                    const t_uchar * archive,
                    const t_uchar * version)
{
  t_uchar * dir = 0;
  int answer = 0;

  dir = arch_log_dir (tree_root, archive, version);

  if (!safe_access (dir, F_OK))
    answer = 1;

  lim_free (0, dir);
  return answer;
}


rel_table
arch_log_versions (const t_uchar * tree_root,
                   const t_uchar * only_archive,
                   const t_uchar * only_category_param,
                   const t_uchar * only_branch_param,
                   const t_uchar * only_version_param)
{
  t_uchar * only_category = 0;
  t_uchar * only_branch = 0;
  t_uchar * only_version = 0;
  t_uchar * logs_root;
  int lim;
  int x;
  rel_table dir_listing;
  rel_table categories;
  int c_lim;
  int c;
  rel_table branches;           /* [0] == cat--branch,  [1] ==  path of branch's log dir */
  int b_lim;
  int b;
  rel_table versions;           /* [0] == cat--branch--vsn,  [1] ==  path of vsn's log dir */
  int v_lim;
  int v;
  rel_table log_versions;               /* [0] == fqversion, [1] == patch-log dir for fqversion  */

  logs_root = arch_tree_ctl_dir (tree_root);


  if (only_version_param)
    only_version = str_save (0, only_version_param);
  else
    only_version = 0;

  if (only_version && !only_branch_param)
    {
      only_branch = arch_parse_package_name (arch_ret_package, 0, only_version);
    }
  else if (only_branch_param)
    only_branch = str_save (0, only_branch_param);
  else
    only_branch = 0;

  if (only_branch && !only_category_param)
    {
      only_category = arch_parse_package_name (arch_ret_category, 0, only_branch);
    }
  else if (only_category_param)
    only_category = str_save (0, only_category_param);
  else
    only_category = 0;



  /****************************************************************
   * Get a list of all categories in the log
   */

  dir_listing = directory_files (logs_root);

  categories = rel_table_nil;
  lim = rel_n_records (dir_listing);
  for (x = 0; x < lim; ++x)
    {
      if (only_category && str_cmp (rel_peek_str (dir_listing, x, 0), only_category))
        continue;

      if (arch_valid_package_name (rel_peek_str (dir_listing, x, 0), arch_no_archive, arch_req_category, 0))
        rel_add_records (&categories, rel_copy_record (rel_peek_record (dir_listing, x)), rel_record_null);
    }
  rel_free_table (dir_listing);

  /****************************************************************
   * For each category, get a list of each branch for it in the log
   */

  branches = rel_table_nil;
  c_lim = rel_n_records (categories);
  for (c = 0; c < c_lim; ++c)
    {
      const t_uchar * category;
      t_uchar * cat_dir = 0;

      category = rel_peek_str (categories, c, 0);
      cat_dir = file_name_in_vicinity (0, logs_root, category);

      dir_listing = directory_files (cat_dir);
      lim = rel_n_records (dir_listing);
      for (x = 0; x < lim; ++x)
        {
          rel_field maybe_branch_field;
          const t_uchar * maybe_branch;
          t_uchar * branch_cat = 0;

          maybe_branch_field = rel_get_field (dir_listing, x, 0);
          maybe_branch = rel_field_str (maybe_branch_field);

          if (only_branch && str_cmp (only_branch, maybe_branch))
            continue;

          if (!arch_valid_package_name (maybe_branch, arch_no_archive, arch_req_package, 0))
            continue;

          branch_cat = arch_parse_package_name (arch_ret_category, 0, maybe_branch);
          if (!str_cmp (category, branch_cat))
            {
              t_uchar * branch_path;

              branch_path = file_name_in_vicinity (0, cat_dir, maybe_branch);
              rel_field_ref (maybe_branch_field);
              rel_add_records (&branches, rel_make_record_2_taking (maybe_branch_field, rel_make_field_str (branch_path)), rel_record_null);
              lim_free (0, branch_path);
            }

          rel_field_unref (maybe_branch_field);
          lim_free (0, branch_cat);
        }

      rel_free_table (dir_listing);
      lim_free (0, cat_dir);
    }

  rel_free_table (categories);

  /****************************************************************
   * For each branch, get a list of each version for it in the log
   */


  versions = rel_table_nil;
  b_lim = rel_n_records (branches);
  for (b = 0; b < b_lim; ++b)
    {
      const t_uchar * branch;
      const t_uchar * branch_dir;

      branch = rel_peek_str (branches, b, 0);
      branch_dir = rel_peek_str (branches, b, 1);

      dir_listing = directory_files (branch_dir);
      lim = rel_n_records (dir_listing);
      for (x = 0; x < lim; ++x)
        {
          rel_field maybe_version_field;
          const t_uchar * maybe_version;
          t_uchar * version_pkg = 0;

          maybe_version_field = rel_get_field (dir_listing, x, 0);
          maybe_version = rel_field_str (maybe_version_field);

          if (only_version && str_cmp (only_version, maybe_version))
            continue;

          if (!arch_valid_package_name (maybe_version, arch_no_archive, arch_req_version, 0))
            continue;

          version_pkg = arch_parse_package_name (arch_ret_package, 0, maybe_version);
          if (!str_cmp (branch, version_pkg))
            {
              t_uchar * version_path;

              version_path = file_name_in_vicinity (0, branch_dir, maybe_version);
              rel_field_ref (maybe_version_field);
              rel_add_records (&versions, rel_make_record_2_taking (maybe_version_field, rel_make_field_str (version_path)), rel_record_null);
              lim_free (0, version_path);
            }


          rel_field_unref (maybe_version_field);
          lim_free (0, version_pkg);
        }

      rel_free_table (dir_listing);
    }

  rel_free_table (branches);


  /****************************************************************
   * For each version, get a list of each archive for it in the log
   */

  log_versions = rel_table_nil;
  v_lim = rel_n_records (versions);
  for (v = 0; v < v_lim; ++v)
    {
      const t_uchar * version;
      const t_uchar * vsn_dir;

      version = rel_peek_str (versions, v, 0);
      vsn_dir = rel_peek_str (versions, v, 1);

      dir_listing = directory_files (vsn_dir);
      lim = rel_n_records (dir_listing);
      for (x = 0; x < lim; ++x)
        {
          const t_uchar * maybe_archive;
          t_uchar * archive_path = 0;
          t_uchar * patch_log_path = 0;

          maybe_archive = rel_peek_str (dir_listing, x, 0);

          if (only_archive && str_cmp (only_archive, maybe_archive))
            continue;

          if (!arch_valid_archive_name (maybe_archive))
            continue;

          archive_path = file_name_in_vicinity (0, vsn_dir, maybe_archive);
          patch_log_path = file_name_in_vicinity (0, archive_path, "patch-log");

          if (!safe_access (patch_log_path, F_OK))
            {
              t_uchar * fqversion;

              fqversion = arch_fully_qualify (maybe_archive, version);
              rel_add_records (&log_versions, rel_make_record_2_taking (rel_make_field_str (fqversion), rel_make_field_str (patch_log_path)), rel_record_null);
              lim_free (0, fqversion);
            }

          lim_free (0, archive_path);
          lim_free (0, patch_log_path);
        }

      rel_free_table (dir_listing);
    }

  rel_free_table (versions);

  if (only_category)
    lim_free (0, only_category);
  if (only_branch)
    lim_free (0, only_branch);
  if (only_version)
    lim_free (0, only_version);

  return log_versions;
}


t_uchar *
arch_highest_patch_level (const t_uchar * tree_root,
                          const t_uchar * archive,
                          const t_uchar * version)
{
  rel_table logs = rel_table_nil;
  t_uchar * answer = 0;

  logs = arch_logs (tree_root, archive, version, 0);

  if (rel_n_records (logs))
    answer = str_save (0, rel_peek_str (logs, rel_n_records(logs) - 1, 0));

  rel_free_table (logs);
  return answer;
}


t_uchar *
arch_latest_logged_version (const t_uchar * tree_root,
                            const t_uchar * archive,
                            const t_uchar * package)
{
  rel_table log_versions = rel_table_nil;
  t_uchar * answer = 0;

  log_versions = arch_log_versions (tree_root, archive, 0, package, 0);
  arch_sort_table_by_name_field (1, log_versions, 0);

  if (rel_n_records (log_versions))
    {
      answer = arch_parse_package_name (arch_ret_non_archive, 0, rel_peek_str (log_versions, 0, 0));
    }

  rel_free_table (log_versions);
  return answer;
}


rel_table
arch_logs (const t_uchar * tree_root,
           const t_uchar * archive,
           const t_uchar * version,
           int full)
{
  rel_table answer = rel_table_nil;             /* [0] == lvl, [2] == path */
  t_uchar * log_dir = 0;
  rel_table dir_listing = rel_table_nil;
  int lim;
  int x;

  answer = rel_table_nil;

  log_dir = arch_log_dir (tree_root, archive, version);

  if (safe_access (log_dir, F_OK))
    goto leave;

  dir_listing = directory_files (log_dir);

  lim = rel_n_records (dir_listing);
  for (x = 0; x < lim; ++x)
    {
      if (arch_valid_patch_level_name (rel_peek_str (dir_listing, x, 0)))
        {
          t_uchar * path = 0;

          path = file_name_in_vicinity (0, log_dir, rel_peek_str (dir_listing, x, 0));

          if (!full)
            {
              rel_add_records (&answer, rel_make_record_2_taking (rel_get_field (dir_listing, x, 0), rel_make_field_str (path)), rel_record_null);
            }
          else
            {
              t_uchar * full_name = 0;

              full_name = str_alloc_cat_many (0, archive, "/", version, "--", rel_peek_str (dir_listing, x, 0), str_end);
              rel_add_records (&answer, rel_make_record_2_taking (rel_make_field_str (full_name), rel_make_field_str (path)), rel_record_null);
              lim_free (0, full_name);
            }

          lim_free (0, path);
        }
    }

 leave:
  lim_free (0, log_dir);
  rel_free_table (dir_listing);

  if (full)
    arch_sort_table_by_name_field (0, answer, 0);
  else
    arch_sort_table_by_patch_level_field (0, answer, 0);
  return answer;
}


int
arch_valid_log_file (const t_uchar * log)
{
  size_t len;
  const t_uchar * pos;

  len = str_length (log);
  pos = log;

  /* Parse the headers.
   */

  while (1)
    {
      const t_uchar * eol;
      const t_uchar * colon;

      eol = (const t_uchar *)str_chr_index (pos, '\n');
      if (!eol)
        eol = pos + len;

      if (eol == pos)
        break;

      colon = (const t_uchar *)str_chr_index_n (pos, eol - pos, ':');
      if (!colon)
        return 0;

      len -= (1 + (colon - pos));
      pos = colon + 1;

      while (char_is_blank (*pos))
        {
          ++pos;
          --len;
        }

      while (*eol && char_is_blank (eol[1]))
        {
          eol = str_chr_index (eol + 1, '\n');
          if (!eol)
            eol = pos + len;
        }

      if (*eol)
        ++eol;

      len -= (eol - pos);
      pos = eol;
    }

  /* skip the blank line */

  if (!*pos)
    return 0;

  return 1;
}


void
arch_parse_log (rel_table * headers_list,
                assoc_table * headers,
                const t_uchar ** body,
                const t_uchar * log)
{
  size_t len;
  const t_uchar * pos;

  len = str_length (log);
  pos = log;

  /* Parse and assemble the headers.
   */

  while (1)
    {
      const t_uchar * eol;
      const t_uchar * colon;
      t_uchar * field_name = 0;
      t_uchar * field_value = 0;

      eol = (const t_uchar *)str_chr_index (pos, '\n');
      if (!eol)
        eol = pos + len;

      if (eol == pos)
        break;

      colon = (const t_uchar *)str_chr_index_n (pos, eol - pos, ':');
      if (!colon)
        panic ("bogusly formatted log message (missing field name in headers)");

      field_name = str_save_n (0, pos, colon - pos);
      {
        size_t x;
        size_t l;

        l = str_length (field_name);
        for (x = 0; x < l; ++x)
          field_name[x] = char_to_lower (field_name[x]);
      }


      len -= (1 + (colon - pos));
      pos = colon + 1;

      while (char_is_blank (*pos))
        {
          ++pos;
          --len;
        }

      while (*eol && char_is_blank (eol[1]))
        {
          eol = str_chr_index (eol + 1, '\n');
          if (!eol)
            eol = pos + len;
        }

      field_value = str_save_n (0, pos, eol - pos);

      if (headers)
        assoc_set_taking (headers, rel_make_field_str (field_name), rel_make_field_str (field_value));
      if (headers_list)
        rel_add_records (headers_list, rel_make_record_2_taking (rel_make_field_str (field_name), rel_make_field_str (field_value)), rel_record_null);

      lim_free (0, field_name);
      lim_free (0, field_value);

      if (*eol)
        ++eol;

      len -= (eol - pos);
      pos = eol;
    }

  /* skip the blank line */

  if (*pos)
    ++pos;

  /* save the body */
  if (body)
    *body = str_save (0, pos);
}


void
arch_print_headers_summary (int out_fd,
                            int indent_level,
                            assoc_table headers,
                            int summarized_headers)
{
  if (arch_include_date & summarized_headers)
    {
      const t_uchar * d;
      const t_uchar * separator;

      if (arch_include_creator & summarized_headers)
        {
          separator = "";
        }
      else
        {
          separator = "\n";
        }

      d = assoc_get_str_taking (headers, rel_make_field_str ("standard-date"));

      if (!d)
        safe_printfmt (out_fd, "%*s<<<no date: header>>>%s", indent_level, "", separator);
      else
        safe_printfmt (out_fd, "%*s%s%s", indent_level, "", d, separator);
    }

  if (arch_include_creator & summarized_headers)
    {
      const t_uchar * c;
      int creator_indent;

      if (arch_include_date & summarized_headers)
        {
          creator_indent = 6;
        }
      else
        {
          creator_indent = indent_level;
        }

      c = assoc_get_str_taking (headers, rel_make_field_str ("creator"));
      if (!c)
        safe_printfmt (out_fd, "%*s<<<no creator: header>>>\n", creator_indent, "");
      else
        safe_printfmt (out_fd, "%*s%s\n", creator_indent, "", c);
    }

  if (arch_include_summary & summarized_headers)
    {
      const t_uchar * s;

      s = assoc_get_str_taking (headers, rel_make_field_str ("summary"));
      if (!s)
        safe_printfmt (out_fd, "%*s<<<no summary: header>>>\n", indent_level, "");
      else
        {
          /* Iterate over the contents of the summary field.
           *
           * For each non-empty line, indent by `indent_level' spaces,
           * skip the leading whitespace of the summary line,
           * print all but the trailing whitespace on that line,
           * and print a newline.
           */

          while (*s)
            {
              const t_uchar * this_line_start;
              const t_uchar * this_line_scan;
              const t_uchar * this_line_end;
              int this_line_len;

              this_line_start = s;

              /* Skip leading whitespace.   Note that this will skip
               * over the newlines of entirely blank lines because
               * we are using `char_is_space'.  On the second and
               * subsequent pass through this loop, it will also skip
               * over the final newline of the previous line.
               */
              while (*this_line_start && char_is_space (*this_line_start))
                ++this_line_start;

              /* Find last non-whitespace character on this line.
               * Leave `this_line_scan' at either the final 0 of the 
               * string or at the newline that terminates this line.
               */
              this_line_scan = this_line_start;
              this_line_end = this_line_start;
              while (*this_line_scan && !('\n' == *this_line_scan))
                {
                  if (!char_is_blank (*this_line_scan))
                    this_line_end = this_line_scan + 1;
                  ++this_line_scan;
                }

              this_line_len = (int)(this_line_end - this_line_start);
              invariant (this_line_len >= 0);
              invariant ((size_t)this_line_len <= (this_line_end - this_line_start));

              safe_printfmt (out_fd, "%*s%.*s\n", indent_level, "", this_line_len, this_line_start);

              s = this_line_scan;
            }
        }
    }

  if (summarized_headers & (arch_include_foreign_merges | arch_include_local_merges))
    {
      t_uchar * this_archive = 0;
      t_uchar * this_revision = 0;
      t_uchar * this_fqrevision = 0;
      t_uchar * raw_merges = 0;
      rel_table new_patches = rel_table_nil;
      int any = 0;
      int x;

      this_archive = str_save_trimming (0, assoc_get_str_taking (headers, rel_make_field_str ("archive")));
      this_revision = str_save_trimming (0, assoc_get_str_taking (headers, rel_make_field_str ("revision")));
      this_fqrevision = arch_fully_qualify (this_archive, this_revision);

      raw_merges = str_save_trimming (0, assoc_get_str_taking (headers, rel_make_field_str ("new-patches")));

      new_patches = rel_ws_split (raw_merges);
      arch_sort_table_by_name_field (0, new_patches, 0);

      for (x = 0; x < rel_n_records (new_patches); ++x)
        {
          if (arch_valid_package_name (rel_peek_str (new_patches, x, 0), arch_req_archive, arch_req_patch_level, 0)
              && str_cmp (rel_peek_str (new_patches, x, 0), this_fqrevision))
            {
              t_uchar * patch_archive = 0;

              patch_archive = arch_parse_package_name (arch_ret_archive, 0, rel_peek_str (new_patches, x, 0));

              if (str_cmp (patch_archive, this_archive)
                  ? (summarized_headers & arch_include_foreign_merges)
                  : (summarized_headers & arch_include_local_merges))
                {
                  if (!any)
                    {
                      if (arch_include_summary & summarized_headers)
                        safe_printfmt (out_fd, "\n");

                      safe_printfmt (out_fd, "%*smerges in:\n", indent_level, "");
                      any = 1;
                    }
                  safe_printfmt (out_fd, "%*s%s\n", indent_level + 2, "", rel_peek_str (new_patches, x, 0));
                }

              lim_free (0, patch_archive);
            }
        }

      lim_free (0, this_archive);
      lim_free (0, this_revision);
      lim_free (0, this_fqrevision);
      lim_free (0, raw_merges);
      rel_free_table (new_patches);
    }
}


static const char *
no_dot (const char *name)
{
  if (name[0] == '.' && name[1] == '/')
    return name + 2;
  else
    return name;
}


void
arch_print_log_list_header (int out_fd,
                            const t_uchar * header,
                            rel_table list,
                            int field)
{
  int num_recs = rel_n_records (list);
  int escape_classes = arch_escape_classes;

  if (num_recs > 0)
    {
      int x;
      size_t col;
      int any_on_this_line;

      safe_printfmt (out_fd, "%s: ", header);
      col = str_length (header) + 2;
      any_on_this_line = 0;

      for (x = 0; x < num_recs; ++x)
        {
          t_uchar * escape_tmp = 0;
          size_t len_this;

          escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, no_dot (rel_peek_str (list, x, field)));
          len_this = str_length (escape_tmp);

          if (!any_on_this_line)
            {
              safe_printfmt (out_fd, "%s", escape_tmp);
              col += len_this;
              any_on_this_line = 1;
            }
          else
            {
              if ((col + 1 + len_this) >= 60)
                {
                  safe_printfmt (out_fd, "\n    %s", escape_tmp);
                  col = 4 + len_this;
                  any_on_this_line = 1;
                }
              else
                {
                  safe_printfmt (out_fd, " %s", escape_tmp);
                  col += len_this;
                  any_on_this_line = 1;
                }
            }

          lim_free (0, escape_tmp);
        }
      safe_printfmt (out_fd, "\n");
    }
}


void
arch_print_log_pairs_header (int out_fd,
                             const t_uchar * header,
                             rel_table list,
                             int field_a,
                             int field_b)
{
  int num_recs = rel_n_records (list);
  int escape_classes = arch_escape_classes;

  if (num_recs > 0)
    {
      int x;
      int once;

      safe_printfmt (out_fd, "%s: ", header);

      once = 0;

      for (x = 0; x < num_recs; ++x)
        {
          t_uchar * escape_tmp_a;
          t_uchar * escape_tmp_b;
          escape_tmp_a = pika_save_escape_iso8859_1 (0, 0, escape_classes, rel_peek_str (list, x, field_a));
          escape_tmp_b = pika_save_escape_iso8859_1 (0, 0, escape_classes, rel_peek_str (list, x, field_b));
          safe_printfmt (out_fd, "%s%s %s", (once ? "\n    " : ""),
                         no_dot (escape_tmp_a), no_dot (escape_tmp_b));
          once = 1;
          lim_free (0, escape_tmp_b);
          lim_free (0, escape_tmp_a);
        }
      safe_printfmt (out_fd, "\n");
    }
}


void
arch_copy_to_patch_log (const t_uchar * tree_root,
                        const t_uchar * archive,
                        const t_uchar * revision,
                        const t_uchar * log_file)
{
  int ign;
  t_uchar * version = 0;
  t_uchar * lvl = 0;
  t_uchar * log_dir = 0;
  t_uchar * dest_path = 0;
  t_uchar * dest_dir = 0;
  t_uchar * tmp_path = 0;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  lvl = arch_parse_package_name (arch_ret_patch_level, 0, revision);

  log_dir = arch_log_dir (tree_root, archive, version);
  dest_path = file_name_in_vicinity (0, log_dir, lvl);
  dest_dir = file_name_directory_file (0, dest_path);
  tmp_path = file_name_in_vicinity (0, dest_dir, ",,new-log");

  ensure_directory_exists (log_dir);

  vu_unlink (&ign, tmp_path);

  copy_file (log_file, tmp_path);

  safe_rename (tmp_path, dest_path);

  lim_free (0, version);
  lim_free (0, lvl);
  lim_free (0, log_dir);
  lim_free (0, dest_path);
  lim_free (0, dest_dir);
  lim_free (0, tmp_path);
}


void
arch_rename_to_patch_log (const t_uchar * tree_root,
                          const t_uchar * archive,
                          const t_uchar * revision,
                          const t_uchar * log_file)
{
  t_uchar * version = 0;
  t_uchar * lvl = 0;
  t_uchar * log_dir = 0;
  t_uchar * dest_path = 0;

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  lvl = arch_parse_package_name (arch_ret_patch_level, 0, revision);

  log_dir = arch_log_dir (tree_root, archive, version);
  dest_path = file_name_in_vicinity (0, log_dir, lvl);

  ensure_directory_exists (log_dir);
  safe_rename (log_file, dest_path);

  lim_free (0, version);
  lim_free (0, lvl);
  lim_free (0, log_dir);
  lim_free (0, dest_path);
}


rel_table
arch_all_logs (const t_uchar * tree_root)
{
  rel_table log_versions = rel_table_nil;
  rel_table answer = rel_table_nil;
  int x;

  log_versions = arch_log_versions (tree_root, 0, 0, 0, 0);

  for (x = 0; x < rel_n_records (log_versions); ++x)
    {
      t_uchar * archive = 0;
      t_uchar * version = 0;
      rel_table in_this_log = rel_table_nil;

      archive = arch_parse_package_name (arch_ret_archive, 0,
                                         rel_peek_str (log_versions, x, 0));
      version = arch_parse_package_name (arch_ret_non_archive, 0,
                                         rel_peek_str (log_versions, x, 0));

      in_this_log = arch_logs (tree_root, archive, version, 1);
      rel_append_x (&answer, in_this_log);

      lim_free (0, archive);
      lim_free (0, version);
      rel_free_table (in_this_log);
    }

  arch_sort_table_by_name_field (0, answer, 0);

  rel_free_table (log_versions);
  return answer;
}

extern t_uchar * 
arch_tree_latest_revision (t_uchar * base_tree_root)
{
  t_uchar * fq_version = arch_tree_version (base_tree_root);
  t_uchar * archive = arch_parse_package_name (arch_ret_archive, 0, fq_version);
  t_uchar * version = arch_parse_package_name (arch_ret_package_version, 0, fq_version);
  t_uchar * patch_level = arch_highest_patch_level (base_tree_root, archive, version);
  t_uchar * revision = str_alloc_cat_many (0, version, "--", patch_level, str_end);
  lim_free (0, patch_level);
  lim_free (0, version);
  lim_free (0, archive);
  lim_free (0, fq_version);
  return revision;
}




/* tag: Tom Lord Mon May 12 19:20:16 2003 (patch-logs.c)
 */
