Below is the file 'automate.cc' from this revision. You can also download the file.

// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
// copyright (C) 2004 nathaniel smith <njs@pobox.com>
// all rights reserved.
// licensed to the public under the terms of the GNU GPL (>= 2)
// see the file COPYING for details

#include <string>
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <sstream>
#include <unistd.h>

#include <boost/bind.hpp>
#include <boost/function.hpp>

#include "app_state.hh"
#include "basic_io.hh"
#include "commands.hh"
#include "constants.hh"
#include "restrictions.hh"
#include "revision.hh"
#include "transforms.hh"
#include "vocab.hh"

static std::string const interface_version = "1.0";

// Name: interface_version
// Arguments: none
// Added in: 0.0
// Purpose: Prints version of automation interface.  Major number increments
//   whenever a backwards incompatible change is made; minor number increments
//   whenever any change is made (but is reset when major number increments).
// Output format: "<decimal number>.<decimal number>\n".  Always matches
//   "[0-9]+\.[0-9]+\n".
// Error conditions: None.
static void
automate_interface_version(std::vector<utf8> args,
                           std::string const & help_name,
                           app_state & app,
                           std::ostream & output)
{
  if (args.size() != 0)
    throw usage(help_name);

  output << interface_version << std::endl;
}

// Name: heads
// Arguments:
//   1: branch name (optional, default branch is used if non-existant)
// Added in: 0.0
// Purpose: Prints the heads of the given branch.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline. Revision ids are printed in alphabetically sorted order.
// Error conditions: If the branch does not exist, prints nothing.  (There are
//   no heads.)
static void
automate_heads(std::vector<utf8> args,
               std::string const & help_name,
               app_state & app,
               std::ostream & output)
{
  if (args.size() > 1)
    throw usage(help_name);

  if (args.size() ==1 ) {
    // branchname was explicitly given, use that
    app.set_branch(idx(args, 0));
  }
  std::set<revision_id> heads;
  get_branch_heads(app.branch_name(), app, heads);
  for (std::set<revision_id>::const_iterator i = heads.begin(); i != heads.end(); ++i)
    output << (*i).inner()() << std::endl;
}

// Name: ancestors
// Arguments:
//   1 or more: revision ids
// Added in: 0.2
// Purpose: Prints the ancestors (exclusive) of the given revisions
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline. Revision ids are printed in alphabetically sorted order.
// Error conditions: If any of the revisions do not exist, prints nothing to
//   stdout, prints an error message to stderr, and exits with status 1.
static void
automate_ancestors(std::vector<utf8> args,
                     std::string const & help_name,
                     app_state & app,
                     std::ostream & output)
{
  if (args.size() == 0)
    throw usage(help_name);

  std::set<revision_id> ancestors;
  std::vector<revision_id> frontier;
  for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
    {
      revision_id rid((*i)());
      N(app.db.revision_exists(rid), F("No such revision %s") % rid);
      frontier.push_back(rid);
    }
  while (!frontier.empty())
    {
      revision_id rid = frontier.back();
      frontier.pop_back();
      if(!null_id(rid)) {
        std::set<revision_id> parents;
        app.db.get_revision_parents(rid, parents);
        for (std::set<revision_id>::const_iterator i = parents.begin();
             i != parents.end(); ++i)
          {
            if (ancestors.find(*i) == ancestors.end())
              {
                frontier.push_back(*i);
                ancestors.insert(*i);
              }
          }
      }
    }
  for (std::set<revision_id>::const_iterator i = ancestors.begin();
       i != ancestors.end(); ++i)
    if (!null_id(*i))
      output << (*i).inner()() << std::endl;
}


// Name: descendents
// Arguments:
//   1 or more: revision ids
// Added in: 0.1
// Purpose: Prints the descendents (exclusive) of the given revisions
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline. Revision ids are printed in alphabetically sorted order.
// Error conditions: If any of the revisions do not exist, prints nothing to
//   stdout, prints an error message to stderr, and exits with status 1.
static void
automate_descendents(std::vector<utf8> args,
                     std::string const & help_name,
                     app_state & app,
                     std::ostream & output)
{
  if (args.size() == 0)
    throw usage(help_name);

  std::set<revision_id> descendents;
  std::vector<revision_id> frontier;
  for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
    {
      revision_id rid((*i)());
      N(app.db.revision_exists(rid), F("No such revision %s") % rid);
      frontier.push_back(rid);
    }
  while (!frontier.empty())
    {
      revision_id rid = frontier.back();
      frontier.pop_back();
      std::set<revision_id> children;
      app.db.get_revision_children(rid, children);
      for (std::set<revision_id>::const_iterator i = children.begin();
           i != children.end(); ++i)
        {
          if (descendents.find(*i) == descendents.end())
            {
              frontier.push_back(*i);
              descendents.insert(*i);
            }
        }
    }
  for (std::set<revision_id>::const_iterator i = descendents.begin();
       i != descendents.end(); ++i)
    output << (*i).inner()() << std::endl;
}


// Name: erase_ancestors
// Arguments:
//   0 or more: revision ids
// Added in: 0.1
// Purpose: Prints all arguments, except those that are an ancestor of some
//   other argument.  One way to think about this is that it prints the
//   minimal elements of the given set, under the ordering imposed by the
//   "child of" relation.  Another way to think of it is if the arguments were
//   a branch, then we print the heads of that branch.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline.  Revision ids are printed in alphabetically sorted order.
// Error conditions: If any of the revisions do not exist, prints nothing to
//   stdout, prints an error message to stderr, and exits with status 1.
static void
automate_erase_ancestors(std::vector<utf8> args,
                         std::string const & help_name,
                         app_state & app,
                         std::ostream & output)
{
  std::set<revision_id> revs;
  for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
    {
      revision_id rid((*i)());
      N(app.db.revision_exists(rid), F("No such revision %s") % rid);
      revs.insert(rid);
    }
  erase_ancestors(revs, app);
  for (std::set<revision_id>::const_iterator i = revs.begin(); i != revs.end(); ++i)
    output << (*i).inner()() << std::endl;
}

// Name: attributes
// Arguments:
//   1: file name (optional, if non-existant prints all files with attributes)
// Added in: 1.0
// Purpose: Prints all attributes for a file, or all  all files with attributes
//   if a file name provided.
// Output format: A list of file names in alphabetically sorted order,
//   or a list of attributes if a file name provided.
// Error conditions: If the file name has no attributes, prints nothing.
static void
automate_attributes(std::vector<utf8> args,
                    std::string const & help_name,
                    app_state & app,
                    std::ostream & output)
{
  if (args.size() > 1)
    throw usage(help_name);

  // is there an .mt-attrs?
  file_path attr_path;
  get_attr_path(attr_path);
  if (!file_exists(attr_path)) return;

  // read attribute map
  data attr_data;
  attr_map attrs;

  read_data(attr_path, attr_data);
  read_attr_map(attr_data, attrs);

  if (args.size() == 1) {
    // a filename was given, if it has attributes, print them
    file_path path = file_path_external(idx(args,0));
    attr_map::const_iterator i = attrs.find(path);
    if (i == attrs.end()) return;

    for (std::map<std::string, std::string>::const_iterator j = i->second.begin();
         j != i->second.end(); ++j)
      output << j->first << std::endl;
  }
  else {
    for (attr_map::const_iterator i = attrs.begin(); i != attrs.end(); ++i)
      {
        output << (*i).first << std::endl;
      }
  }
}

// Name: toposort
// Arguments:
//   0 or more: revision ids
// Added in: 0.1
// Purpose: Prints all arguments, topologically sorted.  I.e., if A is an
//   ancestor of B, then A will appear before B in the output list.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline.  Revisions are printed in topologically sorted order.
// Error conditions: If any of the revisions do not exist, prints nothing to
//   stdout, prints an error message to stderr, and exits with status 1.
static void
automate_toposort(std::vector<utf8> args,
                  std::string const & help_name,
                  app_state & app,
                  std::ostream & output)
{
  std::set<revision_id> revs;
  for (std::vector<utf8>::const_iterator i = args.begin(); i != args.end(); ++i)
    {
      revision_id rid((*i)());
      N(app.db.revision_exists(rid), F("No such revision %s") % rid);
      revs.insert(rid);
    }
  std::vector<revision_id> sorted;
  toposort(revs, sorted, app);
  for (std::vector<revision_id>::const_iterator i = sorted.begin();
       i != sorted.end(); ++i)
    output << (*i).inner()() << std::endl;
}

// Name: ancestry_difference
// Arguments:
//   1: a revision id
//   0 or more further arguments: also revision ids
// Added in: 0.1
// Purpose: Prints all ancestors of the first revision A, that are not also
//   ancestors of the other revision ids, the "Bs".  For purposes of this
//   command, "ancestor" is an inclusive term; that is, A is an ancestor of
//   one of the Bs, it will not be printed, but otherwise, it will be; and
//   none of the Bs will ever be printed.  If A is a new revision, and Bs are
//   revisions that you have processed before, then this command tells you
//   which revisions are new since then.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline.  Revisions are printed in topologically sorted order.
// Error conditions: If any of the revisions do not exist, prints nothing to
//   stdout, prints an error message to stderr, and exits with status 1.
static void
automate_ancestry_difference(std::vector<utf8> args,
                             std::string const & help_name,
                             app_state & app,
                             std::ostream & output)
{
  if (args.size() == 0)
    throw usage(help_name);

  revision_id a;
  std::set<revision_id> bs;
  std::vector<utf8>::const_iterator i = args.begin();
  a = revision_id((*i)());
  N(app.db.revision_exists(a), F("No such revision %s") % a);
  for (++i; i != args.end(); ++i)
    {
      revision_id b((*i)());
      N(app.db.revision_exists(b), F("No such revision %s") % b);
      bs.insert(b);
    }
  std::set<revision_id> ancestors;
  ancestry_difference(a, bs, ancestors, app);

  std::vector<revision_id> sorted;
  toposort(ancestors, sorted, app);
  for (std::vector<revision_id>::const_iterator i = sorted.begin();
       i != sorted.end(); ++i)
    output << (*i).inner()() << std::endl;
}

// Name: leaves
// Arguments:
//   None
// Added in: 0.1
// Purpose: Prints the leaves of the revision graph, i.e., all revisions that
//   have no children.  This is similar, but not identical to the
//   functionality of 'heads', which prints every revision in a branch, that
//   has no descendents in that branch.  If every revision in the database was
//   in the same branch, then they would be identical.  Generally, every leaf
//   is the head of some branch, but not every branch head is a leaf.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline.  Revision ids are printed in alphabetically sorted order.
// Error conditions: None.
static void
automate_leaves(std::vector<utf8> args,
               std::string const & help_name,
               app_state & app,
               std::ostream & output)
{
  if (args.size() != 0)
    throw usage(help_name);

  // this might be more efficient in SQL, but for now who cares.
  std::set<revision_id> leaves;
  app.db.get_revision_ids(leaves);
  std::multimap<revision_id, revision_id> graph;
  app.db.get_revision_ancestry(graph);
  for (std::multimap<revision_id, revision_id>::const_iterator i = graph.begin();
       i != graph.end(); ++i)
    leaves.erase(i->first);
  for (std::set<revision_id>::const_iterator i = leaves.begin(); i != leaves.end(); ++i)
    output << (*i).inner()() << std::endl;
}

// Name: parents
// Arguments:
//   1: a revision id
// Added in: 0.2
// Purpose: Prints the immediate ancestors of the given revision, i.e., the
//   parents.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline.  Revision ids are printed in alphabetically sorted order.
// Error conditions: If the revision does not exist, prints nothing to stdout,
//   prints an error message to stderr, and exits with status 1.
static void
automate_parents(std::vector<utf8> args,
                 std::string const & help_name,
                 app_state & app,
                 std::ostream & output)
{
  if (args.size() != 1)
    throw usage(help_name);
  revision_id rid(idx(args, 0)());
  N(app.db.revision_exists(rid), F("No such revision %s") % rid);
  std::set<revision_id> parents;
  app.db.get_revision_parents(rid, parents);
  for (std::set<revision_id>::const_iterator i = parents.begin();
       i != parents.end(); ++i)
      if (!null_id(*i))
          output << (*i).inner()() << std::endl;
}

// Name: children
// Arguments:
//   1: a revision id
// Added in: 0.2
// Purpose: Prints the immediate descendents of the given revision, i.e., the
//   children.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline.  Revision ids are printed in alphabetically sorted order.
// Error conditions: If the revision does not exist, prints nothing to stdout,
//   prints an error message to stderr, and exits with status 1.
static void
automate_children(std::vector<utf8> args,
                  std::string const & help_name,
                  app_state & app,
                  std::ostream & output)
{
  if (args.size() != 1)
    throw usage(help_name);
  revision_id rid(idx(args, 0)());
  N(app.db.revision_exists(rid), F("No such revision %s") % rid);
  std::set<revision_id> children;
  app.db.get_revision_children(rid, children);
  for (std::set<revision_id>::const_iterator i = children.begin();
       i != children.end(); ++i)
      if (!null_id(*i))
          output << (*i).inner()() << std::endl;
}

// Name: graph
// Arguments:
//   None
// Added in: 0.2
// Purpose: Prints out the complete ancestry graph of this database.
// Output format:
//   Each line begins with a revision id.  Following this are zero or more
//   space-prefixed revision ids.  Each revision id after the first is a
//   parent (in the sense of 'automate parents') of the first.  For instance,
//   the following are valid lines:
//     07804171823d963f78d6a0ff1763d694dd74ff40
//     07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01
//     07804171823d963f78d6a0ff1763d694dd74ff40 79d755c197e54dd3db65751d3803833d4cbf0d01 a02e7a1390e3e4745c31be922f03f56450c13dce
//   The first would indicate that 07804171823d963f78d6a0ff1763d694dd74ff40
//   was a root node; the second would indicate that it had one parent, and
//   the third would indicate that it had two parents, i.e., was a merge.
//
//   The output as a whole is alphabetically sorted; additionally, the parents
//   within each line are alphabetically sorted.
// Error conditions: None.
static void
automate_graph(std::vector<utf8> args,
               std::string const & help_name,
               app_state & app,
               std::ostream & output)
{
  if (args.size() != 0)
    throw usage(help_name);

  std::multimap<revision_id, revision_id> edges_mmap;
  std::map<revision_id, std::set<revision_id> > child_to_parents;

  app.db.get_revision_ancestry(edges_mmap);

  for (std::multimap<revision_id, revision_id>::const_iterator i = edges_mmap.begin();
       i != edges_mmap.end(); ++i)
    {
      if (child_to_parents.find(i->second) == child_to_parents.end())
        child_to_parents.insert(std::make_pair(i->second, std::set<revision_id>()));
      if (null_id(i->first))
        continue;
      std::map<revision_id, std::set<revision_id> >::iterator
        j = child_to_parents.find(i->second);
      I(j->first == i->second);
      j->second.insert(i->first);
    }

  for (std::map<revision_id, std::set<revision_id> >::const_iterator i = child_to_parents.begin();
       i != child_to_parents.end(); ++i)
    {
      output << (i->first).inner()();
      for (std::set<revision_id>::const_iterator j = i->second.begin();
           j != i->second.end(); ++j)
        output << " " << (*j).inner()();
      output << std::endl;
    }
}

// Name: select
// Arguments:
//   1: selector
// Added in: 0.2
// Purpose: Prints all the revisions that match the given selector.
// Output format: A list of revision ids, in hexadecimal, each followed by a
//   newline. Revision ids are printed in alphabetically sorted order.
// Error conditions: None.
static void
automate_select(std::vector<utf8> args,
               std::string const & help_name,
               app_state & app,
               std::ostream & output)
{
  if (args.size() != 1)
    throw usage(help_name);

  std::vector<std::pair<selectors::selector_type, std::string> >
    sels(selectors::parse_selector(args[0](), app));

  // we jam through an "empty" selection on sel_ident type
  std::set<std::string> completions;
  selectors::selector_type ty = selectors::sel_ident;
  selectors::complete_selector("", sels, ty, completions, app);

  for (std::set<std::string>::const_iterator i = completions.begin();
       i != completions.end(); ++i)
    output << *i << std::endl;
}

// consider a changeset with the following
//
// deletions
// renames from to
// additions
//
// pre-state  corresponds to deletions and the "from" side of renames
// post-state corresponds to the "to" side of renames and additions
// file-state corresponds to the state of the file with the given name
//
// pre and post state are related to the path rearrangement specified in MT/work
// file state is related to the details of the resulting file

struct inventory_item
{
  enum pstate
    { KNOWN_PATH, ADDED_PATH, DROPPED_PATH, RENAMED_PATH }
    pre_state, post_state;

  enum fstate
    { KNOWN_FILE, PATCHED_FILE, MISSING_FILE, UNKNOWN_FILE, IGNORED_FILE }
    file_state;

  enum ptype
    { FILE, DIRECTORY }
    path_type;

  size_t pre_id, post_id;

  inventory_item():
    pre_state(KNOWN_PATH), post_state(KNOWN_PATH),
    file_state(KNOWN_FILE),
    path_type(FILE),
    pre_id(0), post_id(0) {}
};

typedef std::map<file_path, inventory_item> inventory_map;

static void
inventory_pre_state(inventory_map & inventory,
                    path_set const & paths,
                    inventory_item::pstate pre_state,
                    size_t id = 0,
                    inventory_item::ptype path_type = inventory_item::FILE)
{
  for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
    {
      L(F("%d %d %s\n") % inventory[*i].pre_state % pre_state % *i);
      I(inventory[*i].pre_state == inventory_item::KNOWN_PATH);
      inventory[*i].pre_state = pre_state;
      inventory[*i].path_type = path_type;
      if (id != 0)
        {
          I(inventory[*i].pre_id == 0);
          inventory[*i].pre_id = id;
        }
    }
}

static void
inventory_post_state(inventory_map & inventory,
                     path_set const & paths,
                     inventory_item::pstate post_state,
                     size_t id = 0,
                     inventory_item::ptype path_type = inventory_item::FILE)
{
  for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
    {
      L(F("%d %d %s\n") % inventory[*i].post_state % post_state % *i);
      I(inventory[*i].post_state == inventory_item::KNOWN_PATH);
      inventory[*i].post_state = post_state;
      inventory[*i].path_type = path_type;
      if (id != 0)
        {
          I(inventory[*i].post_id == 0);
          inventory[*i].post_id = id;
        }
    }
}

static void
inventory_file_state(inventory_map & inventory,
                     path_set const & paths,
                     inventory_item::fstate file_state)
{
  for (path_set::const_iterator i = paths.begin(); i != paths.end(); i++)
    {
      L(F("%d %d %s\n") % inventory[*i].file_state % file_state % *i);
      I(inventory[*i].file_state == inventory_item::KNOWN_FILE);
      inventory[*i].file_state = file_state;
    }
}

static void
inventory_renames(inventory_map & inventory,
                  std::map<file_path,file_path> const & renames,
                  inventory_item::ptype path_type = inventory_item::FILE)
{
  path_set old_name;
  path_set new_name;

  static size_t id = 1;

  for (std::map<file_path,file_path>::const_iterator i = renames.begin();
       i != renames.end(); i++)
    {
      old_name.insert(i->first);
      new_name.insert(i->second);

      inventory_pre_state(inventory, old_name, inventory_item::RENAMED_PATH, id, path_type);
      inventory_post_state(inventory, new_name, inventory_item::RENAMED_PATH, id, path_type);

      id++;

      old_name.clear();
      new_name.clear();
    }
}

// Name: inventory
// Arguments: none
// Added in: 1.0
// Purpose: Prints a summary of every file found in the working copy or its
//   associated base manifest. Each unique path is listed on a line prefixed by
//   three status characters and two numeric values used for identifying
//   renames. The three status characters are as follows.
//
//   column 1 pre-state
//         ' ' the path was unchanged in the pre-state
//         'D' the path was deleted from the pre-state
//         'R' the path was renamed from the pre-state name
//   column 2 post-state
//         ' ' the path was unchanged in the post-state
//         'R' the path was renamed to the post-state name
//         'A' the path was added to the post-state
//   column 3 file-state
//         ' ' the file is known and unchanged from the current manifest version
//         'P' the file is patched to a new version
//         'U' the file is unknown and not included in the current manifest
//         'I' the file is ignored and not included in the current manifest
//         'M' the file is missing but is included in the current manifest
//
// Output format: Each path is printed on its own line, prefixed by three status
//   characters as described above. The status is followed by a single space and
//   two numbers, each separated by a single space, used for identifying renames.
//   The numbers are followed by a single space and then the pathname, which
//   includes the rest of the line. Directory paths are identified as ending with
//   the "/" character, file paths do not end in this character.
//
// Error conditions: If no working copy book keeping MT directory is found,
//   prints an error message to stderr, and exits with status 1.

static void
automate_inventory(std::vector<utf8> args,
                   std::string const & help_name,
                   app_state & app,
                   std::ostream & output)
{
  if (args.size() != 0)
    throw usage(help_name);

  manifest_id old_manifest_id;
  revision_id old_revision_id;
  manifest_map m_old, m_new;
  path_set old_paths, new_paths, empty;
  change_set::path_rearrangement included, excluded;
  change_set cs;
  path_set missing, changed, unchanged, unknown, ignored;
  inventory_map inventory;
  app.require_working_copy();

  calculate_restricted_rearrangement(app, args,
                                     old_manifest_id, old_revision_id,
                                     m_old, old_paths, new_paths,
                                     included, excluded);

  // this is a bit screwey. we need to rearrange the old manifest
  // according to the included rearrangement and for that we need
  // a complete changeset, which is normally obtained from both
  // the old and the new manifest. we can't do that because there
  // may be missing files, so instead we add our own set of deltas
  // below.

  // we have the rearrangement of the changeset from above
  // now we need to build up the deltas for the added files

  cs.rearrangement = included;

  hexenc<id> null_ident;

  for (path_set::const_iterator
         i = included.added_files.begin();
       i != included.added_files.end(); ++i)
    {
      if (path_exists(*i))
        {
          // add path from [] to [xxx]
          hexenc<id> ident;
          calculate_ident(*i, ident, app.lua);
          cs.deltas.insert(std::make_pair(*i,std::make_pair(null_ident, ident)));
        }
      else
        {
          // remove missing files from the added list since they have not deltas
          missing.insert(*i);
          cs.rearrangement.added_files.erase(*i);
        }
    }

  apply_change_set(m_old, cs, m_new);

  classify_manifest_paths(app, m_new, missing, changed, unchanged);

  // remove the remaining added files from the unchanged set since they have been
  // changed in the deltas construction above. also, only consider the file as
  // changed if its not missing

  for (path_set::const_iterator
         i = included.added_files.begin();
       i != included.added_files.end(); ++i)
    {
      unchanged.erase(*i);
      if (missing.find(*i) == missing.end())
        changed.insert(*i);
    }

  file_itemizer u(app, new_paths, unknown, ignored);
  walk_tree(file_path(), u);

  inventory_file_state(inventory, missing, inventory_item::MISSING_FILE);

  inventory_pre_state(inventory, included.deleted_files, inventory_item::DROPPED_PATH);
  inventory_pre_state(inventory, included.deleted_dirs,
                      inventory_item::DROPPED_PATH, inventory_item::DIRECTORY);

  inventory_renames(inventory, included.renamed_files);
  inventory_renames(inventory, included.renamed_dirs, inventory_item::DIRECTORY);

  inventory_post_state(inventory, included.added_files, inventory_item::ADDED_PATH);

  inventory_file_state(inventory, changed, inventory_item::PATCHED_FILE);
  inventory_file_state(inventory, unchanged, inventory_item::KNOWN_FILE);
  inventory_file_state(inventory, unknown, inventory_item::UNKNOWN_FILE);
  inventory_file_state(inventory, ignored, inventory_item::IGNORED_FILE);

  for (inventory_map::const_iterator i = inventory.begin(); i != inventory.end(); ++i)
    {
      switch (inventory[i->first].pre_state)
        {
        case inventory_item::KNOWN_PATH:   output << " "; break;
        case inventory_item::DROPPED_PATH: output << "D"; break;
        case inventory_item::RENAMED_PATH: output << "R"; break;
        default: I(false); // invalid pre_state
        }

      switch (inventory[i->first].post_state)
        {
        case inventory_item::KNOWN_PATH:   output << " "; break;
        case inventory_item::RENAMED_PATH: output << "R"; break;
        case inventory_item::ADDED_PATH:   output << "A"; break;
        default: I(false); // invalid post_state
        }

      switch (inventory[i->first].file_state)
        {
        case inventory_item::KNOWN_FILE:   output << " "; break;
        case inventory_item::PATCHED_FILE: output << "P"; break;
        case inventory_item::UNKNOWN_FILE: output << "U"; break;
        case inventory_item::IGNORED_FILE: output << "I"; break;
        case inventory_item::MISSING_FILE: output << "M"; break;
        }

      // need directory indicators

      output << " " << inventory[i->first].pre_id
             << " " << inventory[i->first].post_id
             << " " << i->first;

      if (inventory[i->first].path_type  == inventory_item::DIRECTORY)
        output << "/";

      output << std::endl;
    }

}

// Name: certs
// Arguments:
//   1: a revision id
// Added in: 1.0
// Purpose: Prints all certificates associated with the given revision ID.
//   Each certificate is contained in a basic IO stanza. For each certificate,
//   the following values are provided:
//
//   'key' : a string indicating the key used to sign this certificate.
//   'signature': a string indicating the status of the signature. Possible
//   values of this string are:
//     'ok'        : the signature is correct
//     'bad'       : the signature is invalid
//     'unknown'   : signature was made with an unknown key
//   'name' : the name of this certificate
//   'value' : the value of this certificate
//   'trust' : is this certificate trusted by the defined trust metric
//   Possible values of this string are:
//     'trusted'   : this certificate is trusted
//     'untrusted' : this certificate is not trusted
//
// Output format: All stanzas are formatted by basic_io. Stanzas are seperated
// by a blank line. Values will be escaped, '\' -> '\\' and '"' -> '\"'.
//
// Error conditions: If a certificate is signed with an unknown public key, a
// warning message is printed to stderr. If the revision specified is unknown
// or invalid prints an error message to stderr and exits with status 1.
static void
automate_certs(std::vector<utf8> args,
                 std::string const & help_name,
                 app_state & app,
                 std::ostream & output)
{
  if (args.size() != 1)
    throw usage(help_name);

  std::vector<cert> certs;

  transaction_guard guard(app.db);

  revision_id rid(idx(args, 0)());
  N(app.db.revision_exists(rid), F("No such revision %s") % rid);
  hexenc<id> ident(rid.inner());

  std::vector< revision<cert> > ts;
  app.db.get_revision_certs(rid, ts);
  for (size_t i = 0; i < ts.size(); ++i)
    certs.push_back(idx(ts, i).inner());

  {
    std::set<rsa_keypair_id> checked;
    for (size_t i = 0; i < certs.size(); ++i)
      {
        if (checked.find(idx(certs, i).key) == checked.end() &&
            !app.db.public_key_exists(idx(certs, i).key))
          P(F("warning: no public key '%s' found in database\n")
            % idx(certs, i).key);
        checked.insert(idx(certs, i).key);
      }
  }

  // Make the output deterministic; this is useful for the test suite, in
  // particular.
  std::sort(certs.begin(), certs.end());

  basic_io::printer pr(output);

  for (size_t i = 0; i < certs.size(); ++i)
    {
      basic_io::stanza st;
      cert_status status = check_cert(app, idx(certs, i));
      cert_value tv;
      cert_name name = idx(certs, i).name();
      std::set<rsa_keypair_id> signers;

      decode_base64(idx(certs, i).value, tv);

      rsa_keypair_id keyid = idx(certs, i).key();
      signers.insert(keyid);

      bool trusted = app.lua.hook_get_revision_cert_trust(signers, ident,
                                                          name, tv);

      st.push_str_pair("key", keyid());

      std::string stat;
      switch (status)
        {
        case cert_ok:
          stat = "ok";
          break;
        case cert_bad:
          stat = "bad";
          break;
        case cert_unknown:
          stat = "unknown";
          break;
        }
      st.push_str_pair("signature", stat);

      st.push_str_pair("name", name());
      st.push_str_pair("value", tv());
      st.push_str_pair("trust", (trusted ? "trusted" : "untrusted"));

      pr.print_stanza(st);
    }

  guard.commit();
}

// Name: get_revision
// Arguments:
//   1: a revision id (optional, determined from working directory if non-existant)
// Added in: 1.0
// Purpose: Prints changeset information for the specified revision id.
//
// There are several changes that are described; each of these is described by
// a different basic_io stanza. The first string pair of each stanza indicates the
// type of change represented.
//
// Possible values of this first value are along with an ordered list of
// basic_io formatted string pairs that will be provided are:
//
//  'old_revision' : represents a parent revision.
//                   format: ('old_revision', revision id)
//  'new_manifest' : represents the new manifest associated with the revision.
//                   format: ('new_manifest', manifest id)
//  'old_manifest' : represents a manifest associated with a parent revision.
//                   format: ('old_manifest', manifest id)
//  'patch' : represents a file that was modified.
//            format: ('patch', filename), ('from', file id), ('to', file id)
//  'add_file' : represents a file that was added.
//               format: ('add_file', filename)
//  'delete_file' : represents a file that was deleted.
//                  format: ('delete_file', filename)
//  'delete_dir' : represents a directory that was deleted.
//                 format: ('delete_dir', filename)
//  'rename_file' : represents a file that was renamed.
//                  format: ('rename_file', old filename), ('to', new filename)
//  'rename_dir' : represents a directory that was renamed.
//                 format: ('rename_dir', old filename), ('to', new filename)
//
// Output format: All stanzas are formatted by basic_io. Stanzas are seperated
// by a blank line. Values will be escaped, '\' -> '\\' and '"' -> '\"'.
//
// Error conditions: If the revision specified is unknown or invalid prints an
// error message to stderr and exits with status 1.
static void
automate_get_revision(std::vector<utf8> args,
                 std::string const & help_name,
                 app_state & app,
                 std::ostream & output)
{
  if (args.size() > 1)
    throw usage(help_name);

  revision_data dat;
  revision_id ident;

  if (args.size() == 0)
    {
      revision_set rev;
      manifest_map m_old, m_new;

      app.require_working_copy();
      calculate_unrestricted_revision(app, rev, m_old, m_new);
      calculate_ident(rev, ident);
      write_revision_set(rev, dat);
    }
  else
    {
      ident = revision_id(idx(args, 0)());
      N(app.db.revision_exists(ident),
        F("no revision %s found in database") % ident);
      app.db.get_revision(ident, dat);
    }

  L(F("dumping revision %s\n") % ident);
  output.write(dat.inner()().data(), dat.inner()().size());
}

// Name: get_manifest
// Arguments:
//   1: a manifest id (optional, determined from working directory if non-existant)
// Added in: 1.0
// Purpose: Prints the contents of the manifest associated with the given manifest ID.
//
// Output format: One line for each file in the manifest. Each line begins with a
// 40 character file ID, followed by two space characters (' ') and then the filename.
// eg:
// 22382ac1bdffec21170a88ff2580fe39b508243f  vocab.hh
//
// Error conditions:  If the manifest ID specified is unknown or invalid prints an
// error message to stderr and exits with status 1.
static void
automate_get_manifest(std::vector<utf8> args,
                 std::string const & help_name,
                 app_state & app,
                 std::ostream & output)
{
  if (args.size() > 1)
    throw usage(help_name);

  manifest_data dat;
  manifest_id ident;

  if (args.size() == 0)
    {
      revision_set rev;
      manifest_map m_old, m_new;

      app.require_working_copy();
      calculate_unrestricted_revision(app, rev, m_old, m_new);

      calculate_ident(m_new, ident);
      write_manifest_map(m_new, dat);
    }
  else
    {
      ident = manifest_id(idx(args, 0)());
      N(app.db.manifest_version_exists(ident),
        F("no manifest version %s found in database") % ident);
      app.db.get_manifest_version(ident, dat);
    }

  L(F("dumping manifest %s\n") % ident);
  output.write(dat.inner()().data(), dat.inner()().size());
}

// Name: get_file
// Arguments:
//   1: a file id
// Added in: 1.0
// Purpose: Prints the contents of the specified file.
//
// Output format: The file contents are output without modification.
//
// Error conditions: If the file id specified is unknown or invalid prints
// an error message to stderr and exits with status 1.
static void
automate_get_file(std::vector<utf8> args,
                 std::string const & help_name,
                 app_state & app,
                 std::ostream & output)
{
  if (args.size() != 1)
    throw usage(help_name);

  file_id ident(idx(args, 0)());
  N(app.db.file_version_exists(ident),
    F("no file version %s found in database") % ident);

  file_data dat;
  L(F("dumping file %s\n") % ident);
  app.db.get_file_version(ident, dat);
  output.write(dat.inner()().data(), dat.inner()().size());
}

void
automate_command(utf8 cmd, std::vector<utf8> args,
                 std::string const & root_cmd_name,
                 app_state & app,
                 std::ostream & output);

// Name: stdio
// Arguments: none
// Added in: 1.0
// Purpose: Allow multiple automate commands to be run from one instance
//   of monotone.
//
// Input format: The input is a series of lines of the form
//   'l'<size>':'<string>[<size>':'<string>...]'e', with characters
//   after the 'e' of one command, but before the 'l' of the next ignored.
//   This space is reserved, and should not contain characters other
//   than '\n'.
//   Example:
//     l6:leavese
//     l7:parents40:0e3171212f34839c2e3263e7282cdeea22fc5378e
//
// Output format: <command number>:<err code>:<last?>:<size>:<output>
//   <command number> is a decimal number specifying which command
//   this output is from. It is 0 for the first command, and increases
//   by one each time.
//   <err code> is 0 for success, 1 for a syntax error, and 2 for any
//   other error.
//   <last?> is 'l' if this is the last piece of output for this command,
//   and 'm' if there is more output to come.
//   <size> is the number of bytes in the output.
//   <output> is the output of the command.
//   Example:
//     0:0:l:205:0e3171212f34839c2e3263e7282cdeea22fc5378
//     1f4ef73c3e056883c6a5ff66728dd764557db5e6
//     2133c52680aa2492b18ed902bdef7e083464c0b8
//     23501f8afd1f9ee037019765309b0f8428567f8a
//     2c295fcf5fe20301557b9b3a5b4d437b5ab8ec8c
//     1:0:l:41:7706a422ccad41621c958affa999b1a1dd644e79
//
// Error conditions: Errors encountered by the commands run only set the error
//   code in the output for that command. Malformed input results in exit with
//   a non-zero return value and an error message.

//We use our own stringbuf class so we can put in a callback on write.
//This lets us dump output at a set length, rather than waiting until
//we have all of the output.
typedef std::basic_stringbuf<char,
                             std::char_traits<char>,
                             std::allocator<char> > char_stringbuf;
struct my_stringbuf : public char_stringbuf
{
private:
  std::streamsize written;
  boost::function1<void, int> on_write;
  std::streamsize last_call;
  std::streamsize call_every;
  bool clear;
public:
  my_stringbuf() : char_stringbuf(),
                   written(0),
                   last_call(0),
                   call_every(constants::automate_stdio_size)
  {}
  virtual std::streamsize
  xsputn(const char_stringbuf::char_type* __s, std::streamsize __n)
  {
    std::streamsize ret=char_stringbuf::xsputn(__s, __n);
    written+=__n;
    while(written>=last_call+call_every)
      {
        if(on_write)
          on_write(call_every);
        last_call+=call_every;
      }
    return ret;
  }
  virtual int sync()
  {
    int ret=char_stringbuf::sync();
    if(on_write)
      on_write(-1);
    last_call=written;
    return ret;
  }
  void set_on_write(boost::function1<void, int> x)
  {
    on_write = x;
  }
};

void print_some_output(int cmdnum,
                       int err,
                       bool last,
                       std::string const & text,
                       std::ostream & s,
                       int & pos,
                       int size)
{
  if(size==-1)
    {
      while(text.size()-pos > constants::automate_stdio_size)
        {
          s<<cmdnum<<':'<<err<<':'<<'m'<<':';
          s<<constants::automate_stdio_size<<':'
           <<text.substr(pos, constants::automate_stdio_size);
          pos+=constants::automate_stdio_size;
          s.flush();
        }
      s<<cmdnum<<':'<<err<<':'<<(last?'l':'m')<<':';
      s<<(text.size()-pos)<<':'<<text.substr(pos);
      pos=text.size();
    }
  else
    {
      I((unsigned int)(size) <= constants::automate_stdio_size);
      s<<cmdnum<<':'<<err<<':'<<(last?'l':'m')<<':';
      s<<size<<':'<<text.substr(pos, size);
      pos+=size;
    }
  s.flush();
}

static void
automate_stdio(std::vector<utf8> args,
                   std::string const & help_name,
                   app_state & app,
                   std::ostream & output)
{
  if (args.size() != 0)
    throw usage(help_name);
  int cmdnum = 0;
  char c;
  ssize_t n=1;
  while(n)//while(!EOF)
    {
      std::string x;
      utf8 cmd